/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/tree.py

  • Committer: Jelmer Vernooij
  • Date: 2020-03-22 01:35:14 UTC
  • mfrom: (7490.7.6 work)
  • mto: This revision was merged to the branch mainline in revision 7499.
  • Revision ID: jelmer@jelmer.uk-20200322013514-7vw1ntwho04rcuj3
merge lp:brz/3.1.

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
"""Tree classes, representing directory at point in time.
18
18
"""
19
19
 
20
 
from __future__ import absolute_import
21
 
 
22
 
import os
 
20
try:
 
21
    from collections.abc import deque
 
22
except ImportError:  # python < 3.7
 
23
    from collections import deque
23
24
 
24
25
from .lazy_import import lazy_import
25
26
lazy_import(globals(), """
26
 
import collections
27
27
 
28
28
from breezy import (
29
29
    conflicts as _mod_conflicts,
34
34
    rules,
35
35
    trace,
36
36
    )
37
 
from breezy.bzr import (
38
 
    inventory,
39
 
    )
40
37
from breezy.i18n import gettext
41
38
""")
42
39
 
46
43
    osutils,
47
44
    )
48
45
from .inter import InterObject
49
 
from .sixish import (
50
 
    viewvalues,
51
 
    )
52
46
 
53
47
 
54
48
class FileTimestampUnavailable(errors.BzrError):
61
55
        self.path = path
62
56
 
63
57
 
 
58
class MissingNestedTree(errors.BzrError):
 
59
 
 
60
    _fmt = "The nested tree for %(path)s can not be resolved."""
 
61
 
 
62
    def __init__(self, path):
 
63
        self.path = path
 
64
 
 
65
 
64
66
class TreeEntry(object):
65
67
    """An entry that implements the minimum interface used by commands.
66
68
    """
67
69
 
 
70
    __slots__ = []
 
71
 
68
72
    def __eq__(self, other):
69
73
        # yes, this is ugly, TODO: best practice __eq__ style.
70
74
        return (isinstance(other, TreeEntry)
71
75
                and other.__class__ == self.__class__)
72
76
 
 
77
    kind = None
 
78
 
73
79
    def kind_character(self):
74
80
        return "???"
75
81
 
 
82
    def is_unmodified(self, other):
 
83
        """Does this entry reference the same entry?
 
84
 
 
85
        This is mostly the same as __eq__, but returns False
 
86
        for entries without enough information (i.e. revision is None)
 
87
        """
 
88
        return False
 
89
 
76
90
 
77
91
class TreeDirectory(TreeEntry):
78
92
    """See TreeEntry. This is a directory in a working tree."""
79
93
 
 
94
    __slots__ = []
 
95
 
 
96
    kind = 'directory'
 
97
 
80
98
    def kind_character(self):
81
99
        return "/"
82
100
 
84
102
class TreeFile(TreeEntry):
85
103
    """See TreeEntry. This is a regular file in a working tree."""
86
104
 
 
105
    __slots__ = []
 
106
 
 
107
    kind = 'file'
 
108
 
87
109
    def kind_character(self):
88
110
        return ''
89
111
 
91
113
class TreeLink(TreeEntry):
92
114
    """See TreeEntry. This is a symlink in a working tree."""
93
115
 
 
116
    __slots__ = []
 
117
 
 
118
    kind = 'symlink'
 
119
 
94
120
    def kind_character(self):
95
121
        return ''
96
122
 
97
123
 
 
124
class TreeReference(TreeEntry):
 
125
    """See TreeEntry. This is a reference to a nested tree in a working tree."""
 
126
 
 
127
    __slots__ = []
 
128
 
 
129
    kind = 'tree-reference'
 
130
 
 
131
    def kind_character(self):
 
132
        return '+'
 
133
 
 
134
 
 
135
class TreeChange(object):
 
136
    """Describes the changes between the same item in two different trees."""
 
137
 
 
138
    __slots__ = ['file_id', 'path', 'changed_content', 'versioned', 'parent_id',
 
139
                 'name', 'kind', 'executable', 'copied']
 
140
 
 
141
    def __init__(self, file_id, path, changed_content, versioned, parent_id,
 
142
                 name, kind, executable, copied=False):
 
143
        self.file_id = file_id
 
144
        self.path = path
 
145
        self.changed_content = changed_content
 
146
        self.versioned = versioned
 
147
        self.parent_id = parent_id
 
148
        self.name = name
 
149
        self.kind = kind
 
150
        self.executable = executable
 
151
        self.copied = copied
 
152
 
 
153
    def __repr__(self):
 
154
        return "%s%r" % (self.__class__.__name__, self._as_tuple())
 
155
 
 
156
    def __len__(self):
 
157
        return len(self.__slots__)
 
158
 
 
159
    def _as_tuple(self):
 
160
        return (self.file_id, self.path, self.changed_content, self.versioned,
 
161
                self.parent_id, self.name, self.kind, self.executable, self.copied)
 
162
 
 
163
    def __eq__(self, other):
 
164
        if isinstance(other, TreeChange):
 
165
            return self._as_tuple() == other._as_tuple()
 
166
        if isinstance(other, tuple):
 
167
            return self._as_tuple() == other
 
168
        return False
 
169
 
 
170
    def __lt__(self, other):
 
171
        return self._as_tuple() < other._as_tuple()
 
172
 
 
173
    def meta_modified(self):
 
174
        if self.versioned == (True, True):
 
175
            return (self.executable[0] != self.executable[1])
 
176
        return False
 
177
 
 
178
    def is_reparented(self):
 
179
        return self.parent_id[0] != self.parent_id[1]
 
180
 
 
181
    def discard_new(self):
 
182
        return self.__class__(
 
183
            self.file_id, (self.path[0], None), self.changed_content,
 
184
            (self.versioned[0], None), (self.parent_id[0], None),
 
185
            (self.name[0], None), (self.kind[0], None),
 
186
            (self.executable[0], None),
 
187
            copied=False)
 
188
 
 
189
 
98
190
class Tree(object):
99
191
    """Abstract file tree.
100
192
 
108
200
    trees or versioned trees.
109
201
    """
110
202
 
 
203
    def supports_rename_tracking(self):
 
204
        """Whether this tree supports rename tracking.
 
205
 
 
206
        This defaults to True, but some implementations may want to override
 
207
        it.
 
208
        """
 
209
        return True
 
210
 
111
211
    def has_versioned_directories(self):
112
212
        """Whether this tree can contain explicitly versioned directories.
113
213
 
116
216
        """
117
217
        return True
118
218
 
 
219
    def supports_symlinks(self):
 
220
        """Does this tree support symbolic links?
 
221
        """
 
222
        return osutils.has_symlinks()
 
223
 
119
224
    def changes_from(self, other, want_unchanged=False, specific_files=None,
120
 
        extra_trees=None, require_versioned=False, include_root=False,
121
 
        want_unversioned=False):
 
225
                     extra_trees=None, require_versioned=False, include_root=False,
 
226
                     want_unversioned=False):
122
227
        """Return a TreeDelta of the changes from other to this tree.
123
228
 
124
229
        :param other: A tree to compare with.
129
234
        :param want_unchanged: An optional boolean requesting the inclusion of
130
235
            unchanged entries in the result.
131
236
        :param extra_trees: An optional list of additional trees to use when
132
 
            mapping the contents of specific_files (paths) to file_ids.
 
237
            mapping the contents of specific_files (paths) to their identities.
133
238
        :param require_versioned: An optional boolean (defaults to False). When
134
239
            supplied and True all the 'specific_files' must be versioned, or
135
240
            a PathsNotVersionedError will be thrown.
156
261
        """See InterTree.iter_changes"""
157
262
        intertree = InterTree.get(from_tree, self)
158
263
        return intertree.iter_changes(include_unchanged, specific_files, pb,
159
 
            extra_trees, require_versioned, want_unversioned=want_unversioned)
 
264
                                      extra_trees, require_versioned,
 
265
                                      want_unversioned=want_unversioned)
160
266
 
161
267
    def conflicts(self):
162
268
        """Get a list of the conflicts in the tree.
198
304
        """Iterate through all paths, including paths for missing files."""
199
305
        raise NotImplementedError(self.all_versioned_paths)
200
306
 
201
 
    def id2path(self, file_id):
 
307
    def id2path(self, file_id, recurse='down'):
202
308
        """Return the path for a file id.
203
309
 
204
310
        :raises NoSuchId:
205
311
        """
206
312
        raise NotImplementedError(self.id2path)
207
313
 
208
 
    def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
 
314
    def iter_entries_by_dir(self, specific_files=None, recurse_nested=False):
209
315
        """Walk the tree in 'by_dir' order.
210
316
 
211
317
        This will yield each entry in the tree as a (path, entry) tuple.
230
336
 
231
337
          a, f, a/b, a/d, a/b/c, a/d/e, f/g
232
338
 
233
 
        :param yield_parents: If True, yield the parents from the root leading
234
 
            down to specific_file_ids that have been requested. This has no
235
 
            impact if specific_file_ids is None.
 
339
        If recurse_nested is enabled then nested trees are included as if
 
340
        they were a part of the tree. If is disabled then TreeReference
 
341
        objects (without any children) are yielded.
236
342
        """
237
343
        raise NotImplementedError(self.iter_entries_by_dir)
238
344
 
239
 
    def iter_child_entries(self, path, file_id=None):
 
345
    def iter_child_entries(self, path):
240
346
        """Iterate over the children of a directory or tree reference.
241
347
 
242
348
        :param path: Path of the directory
243
 
        :param file_id: Optional file id of the directory/tree-reference
244
 
        :raise NoSuchId: When the file_id does not exist
 
349
        :raise NoSuchFile: When the path does not exist
245
350
        :return: Iterator over entries in the directory
246
351
        """
247
352
        raise NotImplementedError(self.iter_child_entries)
248
353
 
249
 
    def list_files(self, include_root=False, from_dir=None, recursive=True):
 
354
    def list_files(self, include_root=False, from_dir=None, recursive=True,
 
355
                   recurse_nested=False):
250
356
        """List all files in this tree.
251
357
 
252
358
        :param include_root: Whether to include the entry for the tree root
253
359
        :param from_dir: Directory under which to list files
254
360
        :param recursive: Whether to list files recursively
255
 
        :return: iterator over tuples of (path, versioned, kind, file_id,
256
 
            inventory entry)
 
361
        :param recurse_nested: enter nested trees
 
362
        :return: iterator over tuples of
 
363
            (path, versioned, kind, inventory entry)
257
364
        """
258
365
        raise NotImplementedError(self.list_files)
259
366
 
261
368
        if self.supports_tree_reference():
262
369
            for path, entry in self.iter_entries_by_dir():
263
370
                if entry.kind == 'tree-reference':
264
 
                    yield path, entry.file_id
265
 
 
266
 
    def kind(self, path, file_id=None):
 
371
                    yield path
 
372
 
 
373
    def get_containing_nested_tree(self, path):
 
374
        """Find the nested tree that contains a path.
 
375
 
 
376
        :return: tuple with (nested tree and path inside the nested tree)
 
377
        """
 
378
        for nested_path in self.iter_references():
 
379
            nested_path += '/'
 
380
            if path.startswith(nested_path):
 
381
                nested_tree = self.get_nested_tree(nested_path)
 
382
                return nested_tree, path[len(nested_path):]
 
383
        else:
 
384
            return None, None
 
385
 
 
386
    def get_nested_tree(self, path):
 
387
        """Open the nested tree at the specified path.
 
388
 
 
389
        :param path: Path from which to resolve tree reference.
 
390
        :return: A Tree object for the nested tree
 
391
        :raise MissingNestedTree: If the nested tree can not be resolved
 
392
        """
 
393
        raise NotImplementedError(self.get_nested_tree)
 
394
 
 
395
    def kind(self, path):
267
396
        raise NotImplementedError("Tree subclass %s must implement kind"
268
 
            % self.__class__.__name__)
 
397
                                  % self.__class__.__name__)
269
398
 
270
 
    def stored_kind(self, path, file_id=None):
271
 
        """File kind stored for this file_id.
 
399
    def stored_kind(self, path):
 
400
        """File kind stored for this path.
272
401
 
273
402
        May not match kind on disk for working trees.  Always available
274
403
        for versioned files, even when the file itself is missing.
275
404
        """
276
 
        return self.kind(path, file_id)
 
405
        return self.kind(path)
277
406
 
278
407
    def path_content_summary(self, path):
279
408
        """Get a summary of the information about path.
284
413
        :param path: A relative path within the tree.
285
414
        :return: A tuple containing kind, size, exec, sha1-or-link.
286
415
            Kind is always present (see tree.kind()).
287
 
            size is present if kind is file and the size of the 
 
416
            size is present if kind is file and the size of the
288
417
                canonical form can be cheaply determined, None otherwise.
289
418
            exec is None unless kind is file and the platform supports the 'x'
290
419
                bit.
293
422
        """
294
423
        raise NotImplementedError(self.path_content_summary)
295
424
 
296
 
    def get_reference_revision(self, path, file_id=None):
 
425
    def get_reference_revision(self, path, branch=None):
297
426
        raise NotImplementedError("Tree subclass %s must implement "
298
427
                                  "get_reference_revision"
299
 
            % self.__class__.__name__)
 
428
                                  % self.__class__.__name__)
300
429
 
301
430
    def _comparison_data(self, entry, path):
302
431
        """Return a tuple of kind, executable, stat_value for a file.
309
438
        """
310
439
        raise NotImplementedError(self._comparison_data)
311
440
 
312
 
    def _file_size(self, entry, stat_value):
313
 
        raise NotImplementedError(self._file_size)
314
 
 
315
 
    def get_file(self, path, file_id=None):
316
 
        """Return a file object for the file file_id in the tree.
317
 
 
318
 
        If both file_id and path are defined, it is implementation defined as
319
 
        to which one is used.
 
441
    def get_file(self, path):
 
442
        """Return a file object for the file path in the tree.
320
443
        """
321
444
        raise NotImplementedError(self.get_file)
322
445
 
323
 
    def get_file_with_stat(self, path, file_id=None):
324
 
        """Get a file handle and stat object for file_id.
 
446
    def get_file_with_stat(self, path):
 
447
        """Get a file handle and stat object for path.
325
448
 
326
449
        The default implementation returns (self.get_file, None) for backwards
327
450
        compatibility.
328
451
 
329
452
        :param path: The path of the file.
330
 
        :param file_id: The file id to read, if it is known.
331
453
        :return: A tuple (file_handle, stat_value_or_None). If the tree has
332
454
            no stat facility, or need for a stat cache feedback during commit,
333
455
            it may return None for the second element of the tuple.
334
456
        """
335
 
        return (self.get_file(path, file_id), None)
 
457
        return (self.get_file(path), None)
336
458
 
337
 
    def get_file_text(self, path, file_id=None):
 
459
    def get_file_text(self, path):
338
460
        """Return the byte content of a file.
339
461
 
340
462
        :param path: The path of the file.
341
 
        :param file_id: The file_id of the file.
342
 
 
343
 
        If both file_id and path are supplied, an implementation may use
344
 
        either one.
345
463
 
346
464
        :returns: A single byte string for the whole file.
347
465
        """
348
 
        my_file = self.get_file(path, file_id)
349
 
        try:
 
466
        with self.get_file(path) as my_file:
350
467
            return my_file.read()
351
 
        finally:
352
 
            my_file.close()
353
468
 
354
 
    def get_file_lines(self, path, file_id=None):
 
469
    def get_file_lines(self, path):
355
470
        """Return the content of a file, as lines.
356
471
 
357
472
        :param path: The path of the file.
358
 
        :param file_id: The file_id of the file.
359
 
 
360
 
        If both file_id and path are supplied, an implementation may use
361
 
        either one.
362
473
        """
363
 
        return osutils.split_lines(self.get_file_text(path, file_id))
 
474
        return osutils.split_lines(self.get_file_text(path))
364
475
 
365
 
    def get_file_verifier(self, path, file_id=None, stat_value=None):
 
476
    def get_file_verifier(self, path, stat_value=None):
366
477
        """Return a verifier for a file.
367
478
 
368
479
        The default implementation returns a sha1.
369
480
 
370
 
        :param file_id: The handle for this file.
371
481
        :param path: The path that this file can be found at.
372
482
            These must point to the same object.
373
483
        :param stat_value: Optional stat value for the object
374
484
        :return: Tuple with verifier name and verifier data
375
485
        """
376
 
        return ("SHA1", self.get_file_sha1(path, file_id,
377
 
            stat_value=stat_value))
 
486
        return ("SHA1", self.get_file_sha1(path, stat_value=stat_value))
378
487
 
379
 
    def get_file_sha1(self, path, file_id=None, stat_value=None):
 
488
    def get_file_sha1(self, path, stat_value=None):
380
489
        """Return the SHA1 file for a file.
381
490
 
382
491
        :note: callers should use get_file_verifier instead
384
493
            have quicker access to a non-sha1 verifier.
385
494
 
386
495
        :param path: The path that this file can be found at.
387
 
        :param file_id: The handle for this file.
388
 
            These must point to the same object.
389
496
        :param stat_value: Optional stat value for the object
390
497
        """
391
498
        raise NotImplementedError(self.get_file_sha1)
392
499
 
393
 
    def get_file_mtime(self, path, file_id=None):
 
500
    def get_file_mtime(self, path):
394
501
        """Return the modification time for a file.
395
502
 
396
503
        :param path: The path that this file can be found at.
397
 
        :param file_id: The handle for this file.
398
 
            These must point to the same object.
399
504
        """
400
505
        raise NotImplementedError(self.get_file_mtime)
401
506
 
402
 
    def get_file_size(self, path, file_id=None):
 
507
    def get_file_size(self, path):
403
508
        """Return the size of a file in bytes.
404
509
 
405
510
        This applies only to regular files.  If invoked on directories or
406
511
        symlinks, it will return None.
407
 
        :param file_id: The file-id of the file
408
512
        """
409
513
        raise NotImplementedError(self.get_file_size)
410
514
 
411
 
    def is_executable(self, path, file_id=None):
 
515
    def is_executable(self, path):
412
516
        """Check if a file is executable.
413
517
 
414
518
        :param path: The path that this file can be found at.
415
 
        :param file_id: The handle for this file.
416
 
            These must point to the same object.
417
519
        """
418
520
        raise NotImplementedError(self.is_executable)
419
521
 
433
535
        this implementation, it is a tuple containing a single bytestring with
434
536
        the complete text of the file.
435
537
 
436
 
        :param desired_files: a list of (file_id, identifier) pairs
 
538
        :param desired_files: a list of (path, identifier) pairs
437
539
        """
438
 
        for file_id, identifier in desired_files:
 
540
        for path, identifier in desired_files:
439
541
            # We wrap the string in a tuple so that we can return an iterable
440
542
            # of bytestrings.  (Technically, a bytestring is also an iterable
441
543
            # of bytestrings, but iterating through each character is not
442
544
            # performant.)
443
 
            # TODO(jelmer): Pass paths into iter_files_bytes
444
 
            path = self.id2path(file_id)
445
 
            cur_file = (self.get_file_text(path, file_id),)
 
545
            cur_file = (self.get_file_text(path),)
446
546
            yield identifier, cur_file
447
547
 
448
 
    def get_symlink_target(self, path, file_id=None):
449
 
        """Get the target for a given file_id.
 
548
    def get_symlink_target(self, path):
 
549
        """Get the target for a given path.
450
550
 
451
 
        It is assumed that the caller already knows that file_id is referencing
 
551
        It is assumed that the caller already knows that path is referencing
452
552
        a symlink.
453
 
        :param file_id: Handle for the symlink entry.
454
553
        :param path: The path of the file.
455
 
        If both file_id and path are supplied, an implementation may use
456
 
        either one.
457
554
        :return: The path the symlink points to.
458
555
        """
459
556
        raise NotImplementedError(self.get_symlink_target)
460
557
 
461
 
    def get_root_id(self):
462
 
        """Return the file_id for the root of this tree."""
463
 
        raise NotImplementedError(self.get_root_id)
464
 
 
465
 
    def annotate_iter(self, path, file_id=None,
 
558
    def annotate_iter(self, path,
466
559
                      default_revision=_mod_revision.CURRENT_REVISION):
467
560
        """Return an iterator of revision_id, line tuples.
468
561
 
469
562
        For working trees (and mutable trees in general), the special
470
563
        revision_id 'current:' will be used for lines that are new in this
471
564
        tree, e.g. uncommitted changes.
472
 
        :param file_id: The file to produce an annotated version from
 
565
        :param path: The file to produce an annotated version from
473
566
        :param default_revision: For lines that don't match a basis, mark them
474
567
            with this revision id. Not all implementations will make use of
475
568
            this value.
476
569
        """
477
570
        raise NotImplementedError(self.annotate_iter)
478
571
 
479
 
    def _get_plan_merge_data(self, file_id, other, base):
480
 
        from .bzr import versionedfile
481
 
        vf = versionedfile._PlanMergeVersionedFile(file_id)
482
 
        last_revision_a = self._get_file_revision(
483
 
                self.id2path(file_id), file_id, vf, 'this:')
484
 
        last_revision_b = other._get_file_revision(
485
 
                other.id2path(file_id), file_id, vf, 'other:')
486
 
        if base is None:
487
 
            last_revision_base = None
488
 
        else:
489
 
            last_revision_base = base._get_file_revision(
490
 
                    base.id2path(file_id), file_id, vf, 'base:')
491
 
        return vf, last_revision_a, last_revision_b, last_revision_base
492
 
 
493
 
    def plan_file_merge(self, file_id, other, base=None):
494
 
        """Generate a merge plan based on annotations.
495
 
 
496
 
        If the file contains uncommitted changes in this tree, they will be
497
 
        attributed to the 'current:' pseudo-revision.  If the file contains
498
 
        uncommitted changes in the other tree, they will be assigned to the
499
 
        'other:' pseudo-revision.
500
 
        """
501
 
        data = self._get_plan_merge_data(file_id, other, base)
502
 
        vf, last_revision_a, last_revision_b, last_revision_base = data
503
 
        return vf.plan_merge(last_revision_a, last_revision_b,
504
 
                             last_revision_base)
505
 
 
506
 
    def plan_file_lca_merge(self, file_id, other, base=None):
507
 
        """Generate a merge plan based lca-newness.
508
 
 
509
 
        If the file contains uncommitted changes in this tree, they will be
510
 
        attributed to the 'current:' pseudo-revision.  If the file contains
511
 
        uncommitted changes in the other tree, they will be assigned to the
512
 
        'other:' pseudo-revision.
513
 
        """
514
 
        data = self._get_plan_merge_data(file_id, other, base)
515
 
        vf, last_revision_a, last_revision_b, last_revision_base = data
516
 
        return vf.plan_lca_merge(last_revision_a, last_revision_b,
517
 
                                 last_revision_base)
518
 
 
519
 
    def _iter_parent_trees(self):
520
 
        """Iterate through parent trees, defaulting to Tree.revision_tree."""
521
 
        for revision_id in self.get_parent_ids():
522
 
            try:
523
 
                yield self.revision_tree(revision_id)
524
 
            except errors.NoSuchRevisionInTree:
525
 
                yield self.repository.revision_tree(revision_id)
526
 
 
527
 
    def _get_file_revision(self, path, file_id, vf, tree_revision):
528
 
        """Ensure that file_id, tree_revision is in vf to plan the merge."""
529
 
        if getattr(self, '_repository', None) is None:
530
 
            last_revision = tree_revision
531
 
            parent_keys = [(file_id, t.get_file_revision(path, file_id)) for t in
532
 
                self._iter_parent_trees()]
533
 
            vf.add_lines((file_id, last_revision), parent_keys,
534
 
                         self.get_file_lines(path, file_id))
535
 
            repo = self.branch.repository
536
 
            base_vf = repo.texts
537
 
        else:
538
 
            last_revision = self.get_file_revision(path, file_id)
539
 
            base_vf = self._repository.texts
540
 
        if base_vf not in vf.fallback_versionedfiles:
541
 
            vf.fallback_versionedfiles.append(base_vf)
542
 
        return last_revision
543
 
 
544
 
    def _check_retrieved(self, ie, f):
545
 
        if not __debug__:
546
 
            return
547
 
        fp = osutils.fingerprint_file(f)
548
 
        f.seek(0)
549
 
 
550
 
        if ie.text_size is not None:
551
 
            if ie.text_size != fp['size']:
552
 
                raise errors.BzrError(
553
 
                        "mismatched size for file %r in %r" %
554
 
                        (ie.file_id, self._store),
555
 
                        ["inventory expects %d bytes" % ie.text_size,
556
 
                         "file is actually %d bytes" % fp['size'],
557
 
                         "store is probably damaged/corrupt"])
558
 
 
559
 
        if ie.text_sha1 != fp['sha1']:
560
 
            raise errors.BzrError("wrong SHA-1 for file %r in %r" %
561
 
                    (ie.file_id, self._store),
562
 
                    ["inventory expects %s" % ie.text_sha1,
563
 
                     "file is actually %s" % fp['sha1'],
564
 
                     "store is probably damaged/corrupt"])
565
 
 
566
572
    def path2id(self, path):
567
573
        """Return the id for path in this tree."""
568
574
        raise NotImplementedError(self.path2id)
575
581
        """
576
582
        return self.path2id(path) is not None
577
583
 
578
 
    def paths2ids(self, paths, trees=[], require_versioned=True):
579
 
        """Return all the ids that can be reached by walking from paths.
580
 
 
581
 
        Each path is looked up in this tree and any extras provided in
582
 
        trees, and this is repeated recursively: the children in an extra tree
583
 
        of a directory that has been renamed under a provided path in this tree
584
 
        are all returned, even if none exist under a provided path in this
585
 
        tree, and vice versa.
586
 
 
587
 
        :param paths: An iterable of paths to start converting to ids from.
588
 
            Alternatively, if paths is None, no ids should be calculated and None
589
 
            will be returned. This is offered to make calling the api unconditional
590
 
            for code that *might* take a list of files.
591
 
        :param trees: Additional trees to consider.
592
 
        :param require_versioned: If False, do not raise NotVersionedError if
593
 
            an element of paths is not versioned in this tree and all of trees.
594
 
        """
595
 
        return find_ids_across_trees(paths, [self] + list(trees), require_versioned)
596
 
 
597
 
    def iter_children(self, file_id):
598
 
        """Iterate over the file ids of the children of an entry.
599
 
 
600
 
        :param file_id: File id of the entry
601
 
        :return: Iterator over child file ids.
602
 
        """
603
 
        raise NotImplementedError(self.iter_children)
 
584
    def find_related_paths_across_trees(self, paths, trees=[],
 
585
                                        require_versioned=True):
 
586
        """Find related paths in tree corresponding to specified filenames in any
 
587
        of `lookup_trees`.
 
588
 
 
589
        All matches in all trees will be used, and all children of matched
 
590
        directories will be used.
 
591
 
 
592
        :param paths: The filenames to find related paths for (if None, returns
 
593
            None)
 
594
        :param trees: The trees to find file_ids within
 
595
        :param require_versioned: if true, all specified filenames must occur in
 
596
            at least one tree.
 
597
        :return: a set of paths for the specified filenames and their children
 
598
            in `tree`
 
599
        """
 
600
        raise NotImplementedError(self.find_related_paths_across_trees)
604
601
 
605
602
    def lock_read(self):
606
603
        """Lock this tree for multiple read only operations.
639
636
 
640
637
        :return: set of paths.
641
638
        """
642
 
        raise NotImplementedError(self.filter_unversioned_files)
 
639
        # NB: we specifically *don't* call self.has_filename, because for
 
640
        # WorkingTrees that can indicate files that exist on disk but that
 
641
        # are not versioned.
 
642
        return set(p for p in paths if not self.is_versioned(p))
643
643
 
644
644
    def walkdirs(self, prefix=""):
645
645
        """Walk the contents of this tree from path down.
677
677
    def supports_content_filtering(self):
678
678
        return False
679
679
 
680
 
    def _content_filter_stack(self, path=None, file_id=None):
 
680
    def _content_filter_stack(self, path=None):
681
681
        """The stack of content filters for a path if filtering is supported.
682
682
 
683
683
        Readers will be applied in first-to-last order.
686
686
 
687
687
        :param path: path relative to the root of the tree
688
688
            or None if unknown
689
 
        :param file_id: file_id or None if unknown
690
689
        :return: the list of filters - [] if there are none
691
690
        """
692
691
        filter_pref_names = filters._get_registered_names()
693
692
        if len(filter_pref_names) == 0:
694
693
            return []
695
 
        if path is None:
696
 
            path = self.id2path(file_id)
697
694
        prefs = next(self.iter_search_rules([path], filter_pref_names))
698
695
        stk = filters._get_filter_stack_for(prefs)
699
696
        if 'filters' in debug.debug_flags:
700
 
            trace.note(gettext("*** {0} content-filter: {1} => {2!r}").format(path, prefs, stk))
 
697
            trace.note(
 
698
                gettext("*** {0} content-filter: {1} => {2!r}").format(path, prefs, stk))
701
699
        return stk
702
700
 
703
701
    def _content_filter_stack_provider(self):
709
707
        :return: None if content filtering is not supported by this tree.
710
708
        """
711
709
        if self.supports_content_filtering():
712
 
            return lambda path, file_id: \
713
 
                    self._content_filter_stack(path, file_id)
 
710
            return self._content_filter_stack
714
711
        else:
715
712
            return None
716
713
 
717
714
    def iter_search_rules(self, path_names, pref_names=None,
718
 
        _default_searcher=None):
 
715
                          _default_searcher=None):
719
716
        """Find the preferences for filenames in a tree.
720
717
 
721
718
        :param path_names: an iterable of paths to find attributes for.
741
738
        searcher = default_searcher
742
739
        return searcher
743
740
 
744
 
 
745
 
def find_ids_across_trees(filenames, trees, require_versioned=True):
746
 
    """Find the ids corresponding to specified filenames.
747
 
 
748
 
    All matches in all trees will be used, and all children of matched
749
 
    directories will be used.
750
 
 
751
 
    :param filenames: The filenames to find file_ids for (if None, returns
752
 
        None)
753
 
    :param trees: The trees to find file_ids within
754
 
    :param require_versioned: if true, all specified filenames must occur in
755
 
        at least one tree.
756
 
    :return: a set of file ids for the specified filenames and their children.
757
 
    """
758
 
    if not filenames:
759
 
        return None
760
 
    specified_path_ids = _find_ids_across_trees(filenames, trees,
761
 
        require_versioned)
762
 
    return _find_children_across_trees(specified_path_ids, trees)
763
 
 
764
 
 
765
 
def _find_ids_across_trees(filenames, trees, require_versioned):
766
 
    """Find the ids corresponding to specified filenames.
767
 
 
768
 
    All matches in all trees will be used, but subdirectories are not scanned.
769
 
 
770
 
    :param filenames: The filenames to find file_ids for
771
 
    :param trees: The trees to find file_ids within
772
 
    :param require_versioned: if true, all specified filenames must occur in
773
 
        at least one tree.
774
 
    :return: a set of file ids for the specified filenames
775
 
    """
776
 
    not_versioned = []
777
 
    interesting_ids = set()
778
 
    for tree_path in filenames:
779
 
        not_found = True
780
 
        for tree in trees:
781
 
            file_id = tree.path2id(tree_path)
782
 
            if file_id is not None:
783
 
                interesting_ids.add(file_id)
784
 
                not_found = False
785
 
        if not_found:
786
 
            not_versioned.append(tree_path)
787
 
    if len(not_versioned) > 0 and require_versioned:
788
 
        raise errors.PathsNotVersionedError(not_versioned)
789
 
    return interesting_ids
790
 
 
791
 
 
792
 
def _find_children_across_trees(specified_ids, trees):
793
 
    """Return a set including specified ids and their children.
794
 
 
795
 
    All matches in all trees will be used.
796
 
 
797
 
    :param trees: The trees to find file_ids within
798
 
    :return: a set containing all specified ids and their children
799
 
    """
800
 
    interesting_ids = set(specified_ids)
801
 
    pending = interesting_ids
802
 
    # now handle children of interesting ids
803
 
    # we loop so that we handle all children of each id in both trees
804
 
    while len(pending) > 0:
805
 
        new_pending = set()
806
 
        for file_id in pending:
807
 
            for tree in trees:
808
 
                if not tree.has_or_had_id(file_id):
809
 
                    continue
810
 
                for child_id in tree.iter_children(file_id):
811
 
                    if child_id not in interesting_ids:
812
 
                        new_pending.add(child_id)
813
 
        interesting_ids.update(new_pending)
814
 
        pending = new_pending
815
 
    return interesting_ids
 
741
    def archive(self, format, name, root='', subdir=None,
 
742
                force_mtime=None):
 
743
        """Create an archive of this tree.
 
744
 
 
745
        :param format: Format name (e.g. 'tar')
 
746
        :param name: target file name
 
747
        :param root: Root directory name (or None)
 
748
        :param subdir: Subdirectory to export (or None)
 
749
        :return: Iterator over archive chunks
 
750
        """
 
751
        from .archive import create_archive
 
752
        with self.lock_read():
 
753
            return create_archive(format, self, name, root,
 
754
                                  subdir, force_mtime=force_mtime)
 
755
 
 
756
    @classmethod
 
757
    def versionable_kind(cls, kind):
 
758
        """Check if this tree support versioning a specific file kind."""
 
759
        return (kind in ('file', 'directory', 'symlink', 'tree-reference'))
816
760
 
817
761
 
818
762
class InterTree(InterObject):
840
784
        # it works for all trees.
841
785
        return True
842
786
 
843
 
    def _changes_from_entries(self, source_entry, target_entry,
844
 
        source_path=None, target_path=None):
 
787
    def _changes_from_entries(self, source_entry, target_entry, source_path,
 
788
                              target_path):
845
789
        """Generate a iter_changes tuple between source_entry and target_entry.
846
790
 
847
791
        :param source_entry: An inventory entry from self.source, or None.
848
792
        :param target_entry: An inventory entry from self.target, or None.
849
 
        :param source_path: The path of source_entry, if known. If not known
850
 
            it will be looked up.
851
 
        :param target_path: The path of target_entry, if known. If not known
852
 
            it will be looked up.
 
793
        :param source_path: The path of source_entry.
 
794
        :param target_path: The path of target_entry.
853
795
        :return: A tuple, item 0 of which is an iter_changes result tuple, and
854
796
            item 1 is True if there are any changes in the result tuple.
855
797
        """
863
805
            source_versioned = True
864
806
            source_name = source_entry.name
865
807
            source_parent = source_entry.parent_id
866
 
            if source_path is None:
867
 
                source_path = self.source.id2path(file_id)
868
808
            source_kind, source_executable, source_stat = \
869
809
                self.source._comparison_data(source_entry, source_path)
870
810
        else:
877
817
            target_versioned = True
878
818
            target_name = target_entry.name
879
819
            target_parent = target_entry.parent_id
880
 
            if target_path is None:
881
 
                target_path = self.target.id2path(file_id)
882
820
            target_kind, target_executable, target_stat = \
883
821
                self.target._comparison_data(target_entry, target_path)
884
822
        else:
893
831
        if source_kind != target_kind:
894
832
            changed_content = True
895
833
        elif source_kind == 'file':
896
 
            if not self.file_content_matches(file_id, file_id, source_path,
897
 
                    target_path, source_stat, target_stat):
 
834
            if not self.file_content_matches(
 
835
                    source_path, target_path,
 
836
                    source_stat, target_stat):
898
837
                changed_content = True
899
838
        elif source_kind == 'symlink':
900
 
            if (self.source.get_symlink_target(source_path, file_id) !=
901
 
                self.target.get_symlink_target(target_path, file_id)):
 
839
            if (self.source.get_symlink_target(source_path) !=
 
840
                    self.target.get_symlink_target(target_path)):
902
841
                changed_content = True
903
842
        elif source_kind == 'tree-reference':
904
 
            if (self.source.get_reference_revision(source_path, file_id)
905
 
                != self.target.get_reference_revision(target_path, file_id)):
906
 
                    changed_content = True
 
843
            if (self.source.get_reference_revision(source_path)
 
844
                    != self.target.get_reference_revision(target_path)):
 
845
                changed_content = True
907
846
        parent = (source_parent, target_parent)
908
847
        name = (source_name, target_name)
909
848
        executable = (source_executable, target_executable)
910
 
        if (changed_content is not False or versioned[0] != versioned[1]
911
 
            or parent[0] != parent[1] or name[0] != name[1] or
912
 
            executable[0] != executable[1]):
 
849
        if (changed_content is not False or versioned[0] != versioned[1] or
 
850
            parent[0] != parent[1] or name[0] != name[1] or
 
851
                executable[0] != executable[1]):
913
852
            changes = True
914
853
        else:
915
854
            changes = False
916
 
        return (file_id, (source_path, target_path), changed_content,
917
 
                versioned, parent, name, kind, executable), changes
 
855
        return TreeChange(
 
856
            file_id, (source_path, target_path), changed_content,
 
857
            versioned, parent, name, kind, executable), changes
918
858
 
919
859
    def compare(self, want_unchanged=False, specific_files=None,
920
 
        extra_trees=None, require_versioned=False, include_root=False,
921
 
        want_unversioned=False):
 
860
                extra_trees=None, require_versioned=False, include_root=False,
 
861
                want_unversioned=False):
922
862
        """Return the changes from source to target.
923
863
 
924
864
        :return: A TreeDelta.
939
879
        if extra_trees is not None:
940
880
            trees = trees + tuple(extra_trees)
941
881
        with self.lock_read():
942
 
            # target is usually the newer tree:
943
 
            specific_file_ids = self.target.paths2ids(specific_files, trees,
944
 
                require_versioned=require_versioned)
945
 
            if specific_files and not specific_file_ids:
946
 
                # All files are unversioned, so just return an empty delta
947
 
                # _compare_trees would think we want a complete delta
948
 
                result = delta.TreeDelta()
949
 
                fake_entry = inventory.InventoryFile('unused', 'unused', 'unused')
950
 
                result.unversioned = [(path, None,
951
 
                    self.target._comparison_data(fake_entry, path)[0]) for path in
952
 
                    specific_files]
953
 
                return result
954
882
            return delta._compare_trees(self.source, self.target, want_unchanged,
955
 
                specific_files, include_root, extra_trees=extra_trees,
956
 
                require_versioned=require_versioned,
957
 
                want_unversioned=want_unversioned)
 
883
                                        specific_files, include_root, extra_trees=extra_trees,
 
884
                                        require_versioned=require_versioned,
 
885
                                        want_unversioned=want_unversioned)
958
886
 
959
887
    def iter_changes(self, include_unchanged=False,
960
 
                      specific_files=None, pb=None, extra_trees=[],
961
 
                      require_versioned=True, want_unversioned=False):
 
888
                     specific_files=None, pb=None, extra_trees=[],
 
889
                     require_versioned=True, want_unversioned=False):
962
890
        """Generate an iterator of changes between trees.
963
891
 
964
892
        A tuple is returned:
992
920
            output. An unversioned file is defined as one with (False, False)
993
921
            for the versioned pair.
994
922
        """
995
 
        lookup_trees = [self.source]
996
 
        if extra_trees:
997
 
             lookup_trees.extend(extra_trees)
 
923
        if not extra_trees:
 
924
            extra_trees = []
 
925
        else:
 
926
            extra_trees = list(extra_trees)
998
927
        # The ids of items we need to examine to insure delta consistency.
999
928
        precise_file_ids = set()
1000
929
        changed_file_ids = []
1001
930
        if specific_files == []:
1002
 
            specific_file_ids = []
 
931
            target_specific_files = []
 
932
            source_specific_files = []
1003
933
        else:
1004
 
            specific_file_ids = self.target.paths2ids(specific_files,
1005
 
                lookup_trees, require_versioned=require_versioned)
 
934
            target_specific_files = self.target.find_related_paths_across_trees(
 
935
                specific_files, [self.source] + extra_trees,
 
936
                require_versioned=require_versioned)
 
937
            source_specific_files = self.source.find_related_paths_across_trees(
 
938
                specific_files, [self.target] + extra_trees,
 
939
                require_versioned=require_versioned)
1006
940
        if specific_files is not None:
1007
941
            # reparented or added entries must have their parents included
1008
942
            # so that valid deltas can be created. The seen_parents set
1014
948
            seen_dirs = set()
1015
949
        if want_unversioned:
1016
950
            all_unversioned = sorted([(p.split('/'), p) for p in
1017
 
                                     self.target.extras()
1018
 
                if specific_files is None or
1019
 
                    osutils.is_inside_any(specific_files, p)])
1020
 
            all_unversioned = collections.deque(all_unversioned)
 
951
                                      self.target.extras()
 
952
                                      if specific_files is None or
 
953
                                      osutils.is_inside_any(specific_files, p)])
 
954
            all_unversioned = deque(all_unversioned)
1021
955
        else:
1022
 
            all_unversioned = collections.deque()
 
956
            all_unversioned = deque()
1023
957
        to_paths = {}
1024
958
        from_entries_by_dir = list(self.source.iter_entries_by_dir(
1025
 
            specific_file_ids=specific_file_ids))
1026
 
        from_data = dict((e.file_id, (p, e)) for p, e in from_entries_by_dir)
 
959
            specific_files=source_specific_files))
 
960
        from_data = dict(from_entries_by_dir)
1027
961
        to_entries_by_dir = list(self.target.iter_entries_by_dir(
1028
 
            specific_file_ids=specific_file_ids))
 
962
            specific_files=target_specific_files))
 
963
        path_equivs = self.find_source_paths([p for p, e in to_entries_by_dir])
1029
964
        num_entries = len(from_entries_by_dir) + len(to_entries_by_dir)
1030
965
        entry_count = 0
1031
966
        # the unversioned path lookup only occurs on real trees - where there
1032
967
        # can be extras. So the fake_entry is solely used to look up
1033
968
        # executable it values when execute is not supported.
1034
 
        fake_entry = inventory.InventoryFile('unused', 'unused', 'unused')
 
969
        fake_entry = TreeFile()
1035
970
        for target_path, target_entry in to_entries_by_dir:
1036
971
            while (all_unversioned and
1037
 
                all_unversioned[0][0] < target_path.split('/')):
 
972
                   all_unversioned[0][0] < target_path.split('/')):
1038
973
                unversioned_path = all_unversioned.popleft()
1039
974
                target_kind, target_executable, target_stat = \
1040
 
                    self.target._comparison_data(fake_entry, unversioned_path[1])
1041
 
                yield (None, (None, unversioned_path[1]), True, (False, False),
 
975
                    self.target._comparison_data(
 
976
                        fake_entry, unversioned_path[1])
 
977
                yield TreeChange(
 
978
                    None, (None, unversioned_path[1]), True, (False, False),
1042
979
                    (None, None),
1043
980
                    (None, unversioned_path[0][-1]),
1044
981
                    (None, target_kind),
1045
982
                    (None, target_executable))
1046
 
            source_path, source_entry = from_data.get(target_entry.file_id,
1047
 
                (None, None))
1048
 
            result, changes = self._changes_from_entries(source_entry,
1049
 
                target_entry, source_path=source_path, target_path=target_path)
1050
 
            to_paths[result[0]] = result[1][1]
 
983
            source_path = path_equivs[target_path]
 
984
            if source_path is not None:
 
985
                source_entry = from_data.get(source_path)
 
986
            else:
 
987
                source_entry = None
 
988
            result, changes = self._changes_from_entries(
 
989
                source_entry, target_entry, source_path=source_path, target_path=target_path)
 
990
            to_paths[result.file_id] = result.path[1]
1051
991
            entry_count += 1
1052
 
            if result[3][0]:
 
992
            if result.versioned[0]:
1053
993
                entry_count += 1
1054
994
            if pb is not None:
1055
995
                pb.update('comparing files', entry_count, num_entries)
1056
996
            if changes or include_unchanged:
1057
 
                if specific_file_ids is not None:
1058
 
                    new_parent_id = result[4][1]
1059
 
                    precise_file_ids.add(new_parent_id)
1060
 
                    changed_file_ids.append(result[0])
 
997
                if specific_files is not None:
 
998
                    precise_file_ids.add(result.parent_id[1])
 
999
                    changed_file_ids.append(result.file_id)
1061
1000
                yield result
1062
1001
            # Ensure correct behaviour for reparented/added specific files.
1063
1002
            if specific_files is not None:
1064
1003
                # Record output dirs
1065
 
                if result[6][1] == 'directory':
1066
 
                    seen_dirs.add(result[0])
 
1004
                if result.kind[1] == 'directory':
 
1005
                    seen_dirs.add(result.file_id)
1067
1006
                # Record parents of reparented/added entries.
1068
 
                versioned = result[3]
1069
 
                parents = result[4]
1070
 
                if not versioned[0] or parents[0] != parents[1]:
1071
 
                    seen_parents.add(parents[1])
 
1007
                if not result.versioned[0] or result.is_reparented():
 
1008
                    seen_parents.add(result.parent_id[1])
1072
1009
        while all_unversioned:
1073
1010
            # yield any trailing unversioned paths
1074
1011
            unversioned_path = all_unversioned.popleft()
1075
1012
            to_kind, to_executable, to_stat = \
1076
1013
                self.target._comparison_data(fake_entry, unversioned_path[1])
1077
 
            yield (None, (None, unversioned_path[1]), True, (False, False),
 
1014
            yield TreeChange(
 
1015
                None, (None, unversioned_path[1]), True, (False, False),
1078
1016
                (None, None),
1079
1017
                (None, unversioned_path[0][-1]),
1080
1018
                (None, to_kind),
1085
1023
            if file_id in to_paths:
1086
1024
                # already returned
1087
1025
                continue
1088
 
            if not self.target.has_id(file_id):
1089
 
                # common case - paths we have not emitted are not present in
1090
 
                # target.
1091
 
                to_path = None
1092
 
            else:
1093
 
                to_path = self.target.id2path(file_id)
 
1026
            to_path = self.find_target_path(path)
1094
1027
            entry_count += 1
1095
1028
            if pb is not None:
1096
1029
                pb.update('comparing files', entry_count, num_entries)
1104
1037
            changed_content = from_kind is not None
1105
1038
            # the parent's path is necessarily known at this point.
1106
1039
            changed_file_ids.append(file_id)
1107
 
            yield(file_id, (path, to_path), changed_content, versioned, parent,
1108
 
                  name, kind, executable)
 
1040
            yield TreeChange(
 
1041
                file_id, (path, to_path), changed_content, versioned, parent,
 
1042
                name, kind, executable)
1109
1043
        changed_file_ids = set(changed_file_ids)
1110
 
        if specific_file_ids is not None:
 
1044
        if specific_files is not None:
1111
1045
            for result in self._handle_precise_ids(precise_file_ids,
1112
 
                changed_file_ids):
 
1046
                                                   changed_file_ids):
1113
1047
                yield result
1114
1048
 
1115
 
    def _get_entry(self, tree, file_id):
 
1049
    @staticmethod
 
1050
    def _get_entry(tree, path):
1116
1051
        """Get an inventory entry from a tree, with missing entries as None.
1117
1052
 
1118
1053
        If the tree raises NotImplementedError on accessing .inventory, then
1120
1055
        desired.
1121
1056
 
1122
1057
        :param tree: The tree to lookup the entry in.
1123
 
        :param file_id: The file_id to lookup.
 
1058
        :param path: The path to look up
1124
1059
        """
 
1060
        # No inventory available.
1125
1061
        try:
1126
 
            inventory = tree.root_inventory
1127
 
        except (AttributeError, NotImplementedError):
1128
 
            # No inventory available.
1129
 
            try:
1130
 
                iterator = tree.iter_entries_by_dir(specific_file_ids=[file_id])
1131
 
                return iterator.next()[1]
1132
 
            except StopIteration:
1133
 
                return None
1134
 
        else:
1135
 
            try:
1136
 
                return inventory[file_id]
1137
 
            except errors.NoSuchId:
1138
 
                return None
 
1062
            iterator = tree.iter_entries_by_dir(specific_files=[path])
 
1063
            return next(iterator)[1]
 
1064
        except StopIteration:
 
1065
            return None
1139
1066
 
1140
1067
    def _handle_precise_ids(self, precise_file_ids, changed_file_ids,
1141
 
        discarded_changes=None):
 
1068
                            discarded_changes=None):
1142
1069
        """Fill out a partial iter_changes to be consistent.
1143
1070
 
1144
1071
        :param precise_file_ids: The file ids of parents that were seen during
1183
1110
                # Examine file_id
1184
1111
                if discarded_changes:
1185
1112
                    result = discarded_changes.get(file_id)
1186
 
                    old_entry = None
 
1113
                    source_entry = None
1187
1114
                else:
1188
1115
                    result = None
1189
1116
                if result is None:
1190
 
                    old_entry = self._get_entry(self.source, file_id)
1191
 
                    new_entry = self._get_entry(self.target, file_id)
 
1117
                    try:
 
1118
                        source_path = self.source.id2path(file_id)
 
1119
                    except errors.NoSuchId:
 
1120
                        source_path = None
 
1121
                        source_entry = None
 
1122
                    else:
 
1123
                        source_entry = self._get_entry(
 
1124
                            self.source, source_path)
 
1125
                    try:
 
1126
                        target_path = self.target.id2path(file_id)
 
1127
                    except errors.NoSuchId:
 
1128
                        target_path = None
 
1129
                        target_entry = None
 
1130
                    else:
 
1131
                        target_entry = self._get_entry(
 
1132
                            self.target, target_path)
1192
1133
                    result, changes = self._changes_from_entries(
1193
 
                        old_entry, new_entry)
 
1134
                        source_entry, target_entry, source_path, target_path)
1194
1135
                else:
1195
1136
                    changes = True
1196
1137
                # Get this parents parent to examine.
1197
 
                new_parent_id = result[4][1]
 
1138
                new_parent_id = result.parent_id[1]
1198
1139
                precise_file_ids.add(new_parent_id)
1199
1140
                if changes:
1200
 
                    if (result[6][0] == 'directory' and
1201
 
                            result[6][1] != 'directory'):
 
1141
                    if (result.kind[0] == 'directory' and
 
1142
                            result.kind[1] != 'directory'):
1202
1143
                        # This stopped being a directory, the old children have
1203
1144
                        # to be included.
1204
 
                        if old_entry is None:
 
1145
                        if source_entry is None:
1205
1146
                            # Reusing a discarded change.
1206
 
                            old_entry = self._get_entry(self.source, file_id)
 
1147
                            source_entry = self._get_entry(
 
1148
                                self.source, result.path[0])
1207
1149
                        precise_file_ids.update(
1208
 
                                self.source.iter_children(file_id))
1209
 
                    changed_file_ids.add(result[0])
 
1150
                            child.file_id
 
1151
                            for child in self.source.iter_child_entries(result.path[0]))
 
1152
                    changed_file_ids.add(result.file_id)
1210
1153
                    yield result
1211
1154
 
1212
1155
    def file_content_matches(
1213
 
            self, source_file_id, target_file_id, source_path=None,
1214
 
            target_path=None, source_stat=None, target_stat=None):
 
1156
            self, source_path, target_path,
 
1157
            source_stat=None, target_stat=None):
1215
1158
        """Check if two files are the same in the source and target trees.
1216
1159
 
1217
1160
        This only checks that the contents of the files are the same,
1218
1161
        it does not touch anything else.
1219
1162
 
1220
 
        :param source_file_id: File id of the file in the source tree
1221
 
        :param target_file_id: File id of the file in the target tree
1222
1163
        :param source_path: Path of the file in the source tree
1223
1164
        :param target_path: Path of the file in the target tree
1224
1165
        :param source_stat: Optional stat value of the file in the source tree
1226
1167
        :return: Boolean indicating whether the files have the same contents
1227
1168
        """
1228
1169
        with self.lock_read():
1229
 
            if source_path is None:
1230
 
                source_path = self.source.id2path(source_file_id)
1231
 
            if target_path is None:
1232
 
                target_path = self.target.id2path(target_file_id)
1233
1170
            source_verifier_kind, source_verifier_data = (
1234
 
                    self.source.get_file_verifier(
1235
 
                        source_path, source_file_id, source_stat))
 
1171
                self.source.get_file_verifier(source_path, source_stat))
1236
1172
            target_verifier_kind, target_verifier_data = (
1237
1173
                self.target.get_file_verifier(
1238
 
                    target_path, target_file_id, target_stat))
 
1174
                    target_path, target_stat))
1239
1175
            if source_verifier_kind == target_verifier_kind:
1240
1176
                return (source_verifier_data == target_verifier_data)
1241
1177
            # Fall back to SHA1 for now
1242
1178
            if source_verifier_kind != "SHA1":
1243
1179
                source_sha1 = self.source.get_file_sha1(
1244
 
                        source_path, source_file_id, source_stat)
 
1180
                    source_path, source_stat)
1245
1181
            else:
1246
1182
                source_sha1 = source_verifier_data
1247
1183
            if target_verifier_kind != "SHA1":
1248
1184
                target_sha1 = self.target.get_file_sha1(
1249
 
                        target_path, target_file_id, target_stat)
 
1185
                    target_path, target_stat)
1250
1186
            else:
1251
1187
                target_sha1 = target_verifier_data
1252
1188
            return (source_sha1 == target_sha1)
1253
1189
 
 
1190
    def find_target_path(self, path, recurse='none'):
 
1191
        """Find target tree path.
 
1192
 
 
1193
        :param path: Path to search for (exists in source)
 
1194
        :return: path in target, or None if there is no equivalent path.
 
1195
        :raise NoSuchFile: If the path doesn't exist in source
 
1196
        """
 
1197
        file_id = self.source.path2id(path)
 
1198
        if file_id is None:
 
1199
            raise errors.NoSuchFile(path)
 
1200
        try:
 
1201
            return self.target.id2path(file_id, recurse=recurse)
 
1202
        except errors.NoSuchId:
 
1203
            return None
 
1204
 
 
1205
    def find_source_path(self, path, recurse='none'):
 
1206
        """Find the source tree path.
 
1207
 
 
1208
        :param path: Path to search for (exists in target)
 
1209
        :return: path in source, or None if there is no equivalent path.
 
1210
        :raise NoSuchFile: if the path doesn't exist in target
 
1211
        """
 
1212
        file_id = self.target.path2id(path)
 
1213
        if file_id is None:
 
1214
            raise errors.NoSuchFile(path)
 
1215
        try:
 
1216
            return self.source.id2path(file_id, recurse=recurse)
 
1217
        except errors.NoSuchId:
 
1218
            return None
 
1219
 
 
1220
    def find_target_paths(self, paths, recurse='none'):
 
1221
        """Find target tree paths.
 
1222
 
 
1223
        :param paths: Iterable over paths in target to search for
 
1224
        :return: Dictionary mapping from source paths to paths in target , or
 
1225
            None if there is no equivalent path.
 
1226
        """
 
1227
        ret = {}
 
1228
        for path in paths:
 
1229
            ret[path] = self.find_target_path(path, recurse=recurse)
 
1230
        return ret
 
1231
 
 
1232
    def find_source_paths(self, paths, recurse='none'):
 
1233
        """Find source tree paths.
 
1234
 
 
1235
        :param paths: Iterable over paths in target to search for
 
1236
        :return: Dictionary mapping from target paths to paths in source, or
 
1237
            None if there is no equivalent path.
 
1238
        """
 
1239
        ret = {}
 
1240
        for path in paths:
 
1241
            ret[path] = self.find_source_path(path, recurse=recurse)
 
1242
        return ret
 
1243
 
 
1244
 
1254
1245
InterTree.register_optimiser(InterTree)
1255
1246
 
1256
1247
 
1257
 
class MultiWalker(object):
1258
 
    """Walk multiple trees simultaneously, getting combined results."""
1259
 
 
1260
 
    # Note: This could be written to not assume you can do out-of-order
1261
 
    #       lookups. Instead any nodes that don't match in all trees could be
1262
 
    #       marked as 'deferred', and then returned in the final cleanup loop.
1263
 
    #       For now, I think it is "nicer" to return things as close to the
1264
 
    #       "master_tree" order as we can.
1265
 
 
1266
 
    def __init__(self, master_tree, other_trees):
1267
 
        """Create a new MultiWalker.
1268
 
 
1269
 
        All trees being walked must implement "iter_entries_by_dir()", such
1270
 
        that they yield (path, object) tuples, where that object will have a
1271
 
        '.file_id' member, that can be used to check equality.
1272
 
 
1273
 
        :param master_tree: All trees will be 'slaved' to the master_tree such
1274
 
            that nodes in master_tree will be used as 'first-pass' sync points.
1275
 
            Any nodes that aren't in master_tree will be merged in a second
1276
 
            pass.
1277
 
        :param other_trees: A list of other trees to walk simultaneously.
1278
 
        """
1279
 
        self._master_tree = master_tree
1280
 
        self._other_trees = other_trees
1281
 
 
1282
 
        # Keep track of any nodes that were properly processed just out of
1283
 
        # order, that way we don't return them at the end, we don't have to
1284
 
        # track *all* processed file_ids, just the out-of-order ones
1285
 
        self._out_of_order_processed = set()
1286
 
 
1287
 
    @staticmethod
1288
 
    def _step_one(iterator):
1289
 
        """Step an iter_entries_by_dir iterator.
1290
 
 
1291
 
        :return: (has_more, path, ie)
1292
 
            If has_more is False, path and ie will be None.
1293
 
        """
1294
 
        try:
1295
 
            path, ie = next(iterator)
1296
 
        except StopIteration:
1297
 
            return False, None, None
1298
 
        else:
1299
 
            return True, path, ie
1300
 
 
1301
 
    @staticmethod
1302
 
    def _cmp_path_by_dirblock(path1, path2):
1303
 
        """Compare two paths based on what directory they are in.
1304
 
 
1305
 
        This generates a sort order, such that all children of a directory are
1306
 
        sorted together, and grandchildren are in the same order as the
1307
 
        children appear. But all grandchildren come after all children.
1308
 
 
1309
 
        :param path1: first path
1310
 
        :param path2: the second path
1311
 
        :return: negative number if ``path1`` comes first,
1312
 
            0 if paths are equal
1313
 
            and a positive number if ``path2`` sorts first
1314
 
        """
1315
 
        # Shortcut this special case
1316
 
        if path1 == path2:
1317
 
            return 0
1318
 
        # This is stolen from _dirstate_helpers_py.py, only switching it to
1319
 
        # Unicode objects. Consider using encode_utf8() and then using the
1320
 
        # optimized versions, or maybe writing optimized unicode versions.
1321
 
        if not isinstance(path1, unicode):
1322
 
            raise TypeError("'path1' must be a unicode string, not %s: %r"
1323
 
                            % (type(path1), path1))
1324
 
        if not isinstance(path2, unicode):
1325
 
            raise TypeError("'path2' must be a unicode string, not %s: %r"
1326
 
                            % (type(path2), path2))
1327
 
        return cmp(MultiWalker._path_to_key(path1),
1328
 
                   MultiWalker._path_to_key(path2))
1329
 
 
1330
 
    @staticmethod
1331
 
    def _path_to_key(path):
1332
 
        dirname, basename = osutils.split(path)
1333
 
        return (dirname.split(u'/'), basename)
1334
 
 
1335
 
    def _lookup_by_file_id(self, extra_entries, other_tree, file_id):
1336
 
        """Lookup an inventory entry by file_id.
1337
 
 
1338
 
        This is called when an entry is missing in the normal order.
1339
 
        Generally this is because a file was either renamed, or it was
1340
 
        deleted/added. If the entry was found in the inventory and not in
1341
 
        extra_entries, it will be added to self._out_of_order_processed
1342
 
 
1343
 
        :param extra_entries: A dictionary of {file_id: (path, ie)}.  This
1344
 
            should be filled with entries that were found before they were
1345
 
            used. If file_id is present, it will be removed from the
1346
 
            dictionary.
1347
 
        :param other_tree: The Tree to search, in case we didn't find the entry
1348
 
            yet.
1349
 
        :param file_id: The file_id to look for
1350
 
        :return: (path, ie) if found or (None, None) if not present.
1351
 
        """
1352
 
        if file_id in extra_entries:
1353
 
            return extra_entries.pop(file_id)
1354
 
        # TODO: Is id2path better as the first call, or is
1355
 
        #       inventory[file_id] better as a first check?
1356
 
        try:
1357
 
            cur_path = other_tree.id2path(file_id)
1358
 
        except errors.NoSuchId:
1359
 
            cur_path = None
1360
 
        if cur_path is None:
1361
 
            return (None, None)
1362
 
        else:
1363
 
            self._out_of_order_processed.add(file_id)
1364
 
            cur_ie = other_tree.root_inventory[file_id]
1365
 
            return (cur_path, cur_ie)
1366
 
 
1367
 
    def iter_all(self):
1368
 
        """Match up the values in the different trees."""
1369
 
        for result in self._walk_master_tree():
1370
 
            yield result
1371
 
        self._finish_others()
1372
 
        for result in self._walk_others():
1373
 
            yield result
1374
 
 
1375
 
    def _walk_master_tree(self):
1376
 
        """First pass, walk all trees in lock-step.
1377
 
 
1378
 
        When we are done, all nodes in the master_tree will have been
1379
 
        processed. _other_walkers, _other_entries, and _others_extra will be
1380
 
        set on 'self' for future processing.
1381
 
        """
1382
 
        # This iterator has the most "inlining" done, because it tends to touch
1383
 
        # every file in the tree, while the others only hit nodes that don't
1384
 
        # match.
1385
 
        master_iterator = self._master_tree.iter_entries_by_dir()
1386
 
 
1387
 
        other_walkers = [other.iter_entries_by_dir()
1388
 
                         for other in self._other_trees]
1389
 
        other_entries = [self._step_one(walker) for walker in other_walkers]
1390
 
        # Track extra nodes in the other trees
1391
 
        others_extra = [{} for _ in range(len(self._other_trees))]
1392
 
 
1393
 
        master_has_more = True
1394
 
        step_one = self._step_one
1395
 
        lookup_by_file_id = self._lookup_by_file_id
1396
 
        out_of_order_processed = self._out_of_order_processed
1397
 
 
1398
 
        while master_has_more:
1399
 
            (master_has_more, path, master_ie) = step_one(master_iterator)
1400
 
            if not master_has_more:
1401
 
                break
1402
 
 
1403
 
            file_id = master_ie.file_id
1404
 
            other_values = []
1405
 
            other_values_append = other_values.append
1406
 
            next_other_entries = []
1407
 
            next_other_entries_append = next_other_entries.append
1408
 
            for idx, (other_has_more, other_path, other_ie) in enumerate(other_entries):
1409
 
                if not other_has_more:
1410
 
                    other_values_append(lookup_by_file_id(
1411
 
                        others_extra[idx], self._other_trees[idx], file_id))
1412
 
                    next_other_entries_append((False, None, None))
1413
 
                elif file_id == other_ie.file_id:
1414
 
                    # This is the critical code path, as most of the entries
1415
 
                    # should match between most trees.
1416
 
                    other_values_append((other_path, other_ie))
1417
 
                    next_other_entries_append(step_one(other_walkers[idx]))
1418
 
                else:
1419
 
                    # This walker did not match, step it until it either
1420
 
                    # matches, or we know we are past the current walker.
1421
 
                    other_walker = other_walkers[idx]
1422
 
                    other_extra = others_extra[idx]
1423
 
                    while (other_has_more and
1424
 
                           self._cmp_path_by_dirblock(other_path, path) < 0):
1425
 
                        other_file_id = other_ie.file_id
1426
 
                        if other_file_id not in out_of_order_processed:
1427
 
                            other_extra[other_file_id] = (other_path, other_ie)
1428
 
                        other_has_more, other_path, other_ie = \
1429
 
                            step_one(other_walker)
1430
 
                    if other_has_more and other_ie.file_id == file_id:
1431
 
                        # We ended up walking to this point, match and step
1432
 
                        # again
1433
 
                        other_values_append((other_path, other_ie))
1434
 
                        other_has_more, other_path, other_ie = \
1435
 
                            step_one(other_walker)
1436
 
                    else:
1437
 
                        # This record isn't in the normal order, see if it
1438
 
                        # exists at all.
1439
 
                        other_values_append(lookup_by_file_id(
1440
 
                            other_extra, self._other_trees[idx], file_id))
1441
 
                    next_other_entries_append((other_has_more, other_path,
1442
 
                                               other_ie))
1443
 
            other_entries = next_other_entries
1444
 
 
1445
 
            # We've matched all the walkers, yield this datapoint
1446
 
            yield path, file_id, master_ie, other_values
1447
 
        self._other_walkers = other_walkers
1448
 
        self._other_entries = other_entries
1449
 
        self._others_extra = others_extra
1450
 
 
1451
 
    def _finish_others(self):
1452
 
        """Finish walking the other iterators, so we get all entries."""
1453
 
        for idx, info in enumerate(self._other_entries):
1454
 
            other_extra = self._others_extra[idx]
1455
 
            (other_has_more, other_path, other_ie) = info
1456
 
            while other_has_more:
1457
 
                other_file_id = other_ie.file_id
1458
 
                if other_file_id not in self._out_of_order_processed:
1459
 
                    other_extra[other_file_id] = (other_path, other_ie)
1460
 
                other_has_more, other_path, other_ie = \
1461
 
                    self._step_one(self._other_walkers[idx])
1462
 
        del self._other_entries
1463
 
 
1464
 
    def _walk_others(self):
1465
 
        """Finish up by walking all the 'deferred' nodes."""
1466
 
        # TODO: One alternative would be to grab all possible unprocessed
1467
 
        #       file_ids, and then sort by path, and then yield them. That
1468
 
        #       might ensure better ordering, in case a caller strictly
1469
 
        #       requires parents before children.
1470
 
        for idx, other_extra in enumerate(self._others_extra):
1471
 
            others = sorted(viewvalues(other_extra),
1472
 
                            key=lambda x: self._path_to_key(x[0]))
1473
 
            for other_path, other_ie in others:
1474
 
                file_id = other_ie.file_id
1475
 
                # We don't need to check out_of_order_processed here, because
1476
 
                # the lookup_by_file_id will be removing anything processed
1477
 
                # from the extras cache
1478
 
                other_extra.pop(file_id)
1479
 
                other_values = [(None, None)] * idx
1480
 
                other_values.append((other_path, other_ie))
1481
 
                for alt_idx, alt_extra in enumerate(self._others_extra[idx+1:]):
1482
 
                    alt_idx = alt_idx + idx + 1
1483
 
                    alt_extra = self._others_extra[alt_idx]
1484
 
                    alt_tree = self._other_trees[alt_idx]
1485
 
                    other_values.append(self._lookup_by_file_id(
1486
 
                                            alt_extra, alt_tree, file_id))
1487
 
                yield other_path, file_id, None, other_values
 
1248
def find_previous_paths(from_tree, to_tree, paths, recurse='none'):
 
1249
    """Find previous tree paths.
 
1250
 
 
1251
    :param from_tree: From tree
 
1252
    :param to_tree: To tree
 
1253
    :param paths: Iterable over paths in from_tree to search for
 
1254
    :return: Dictionary mapping from from_tree paths to paths in to_tree, or
 
1255
        None if there is no equivalent path.
 
1256
    """
 
1257
    return InterTree.get(to_tree, from_tree).find_source_paths(paths, recurse=recurse)
 
1258
 
 
1259
 
 
1260
def find_previous_path(from_tree, to_tree, path, recurse='none'):
 
1261
    """Find previous tree path.
 
1262
 
 
1263
    :param from_tree: From tree
 
1264
    :param to_tree: To tree
 
1265
    :param path: Path to search for (exists in from_tree)
 
1266
    :return: path in to_tree, or None if there is no equivalent path.
 
1267
    :raise NoSuchFile: If the path doesn't exist in from_tree
 
1268
    """
 
1269
    return InterTree.get(to_tree, from_tree).find_source_path(
 
1270
        path, recurse=recurse)
 
1271
 
 
1272
 
 
1273
def get_canonical_path(tree, path, normalize):
 
1274
    """Find the canonical path of an item, ignoring case.
 
1275
 
 
1276
    :param tree: Tree to traverse
 
1277
    :param path: Case-insensitive path to look up
 
1278
    :param normalize: Function to normalize a filename for comparison
 
1279
    :return: The canonical path
 
1280
    """
 
1281
    # go walkin...
 
1282
    cur_path = ''
 
1283
    bit_iter = iter(path.split("/"))
 
1284
    for elt in bit_iter:
 
1285
        lelt = normalize(elt)
 
1286
        new_path = None
 
1287
        try:
 
1288
            for child in tree.iter_child_entries(cur_path):
 
1289
                try:
 
1290
                    if child.name == elt:
 
1291
                        # if we found an exact match, we can stop now; if
 
1292
                        # we found an approximate match we need to keep
 
1293
                        # searching because there might be an exact match
 
1294
                        # later.
 
1295
                        new_path = osutils.pathjoin(cur_path, child.name)
 
1296
                        break
 
1297
                    elif normalize(child.name) == lelt:
 
1298
                        new_path = osutils.pathjoin(cur_path, child.name)
 
1299
                except errors.NoSuchId:
 
1300
                    # before a change is committed we can see this error...
 
1301
                    continue
 
1302
        except errors.NotADirectory:
 
1303
            pass
 
1304
        if new_path:
 
1305
            cur_path = new_path
 
1306
        else:
 
1307
            # got to the end of this directory and no entries matched.
 
1308
            # Return what matched so far, plus the rest as specified.
 
1309
            cur_path = osutils.pathjoin(cur_path, elt, *list(bit_iter))
 
1310
            break
 
1311
    return cur_path