/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 bzrlib/tree.py

  • Committer: Robert Collins
  • Date: 2010-05-06 11:08:10 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506110810-h3j07fh5gmw54s25
Cleaner matcher matching revised unlocking protocol.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
 
1
# Copyright (C) 2005-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
17
17
"""Tree classes, representing directory at point in time.
18
18
"""
19
19
 
20
 
from __future__ import absolute_import
21
 
 
22
20
import os
23
 
 
24
 
from .lazy_import import lazy_import
25
 
lazy_import(globals(), """
26
 
import collections
27
 
 
28
 
from breezy import (
 
21
from collections import deque
 
22
 
 
23
import bzrlib
 
24
from bzrlib import (
29
25
    conflicts as _mod_conflicts,
30
26
    debug,
31
27
    delta,
32
 
    errors,
33
28
    filters,
34
29
    osutils,
35
30
    revision as _mod_revision,
36
31
    rules,
37
 
    trace,
38
 
    )
39
 
from breezy.bzr import (
40
 
    inventory,
41
 
    )
42
 
from breezy.i18n import gettext
43
 
""")
44
 
 
45
 
from .decorators import needs_read_lock
46
 
from .inter import InterObject
47
 
from .sixish import (
48
 
    viewvalues,
49
 
    )
 
32
    )
 
33
from bzrlib.decorators import needs_read_lock
 
34
from bzrlib.errors import BzrError, NoSuchId
 
35
from bzrlib import errors
 
36
from bzrlib.inventory import InventoryFile
 
37
from bzrlib.inter import InterObject
 
38
from bzrlib.osutils import fingerprint_file
 
39
from bzrlib.symbol_versioning import deprecated_function, deprecated_in
 
40
from bzrlib.trace import note
50
41
 
51
42
 
52
43
class Tree(object):
58
49
 
59
50
    * `RevisionTree` is a tree as recorded at some point in the past.
60
51
 
 
52
    Trees contain an `Inventory` object, and also know how to retrieve
 
53
    file texts mentioned in the inventory, either from a working
 
54
    directory or from a store.
 
55
 
 
56
    It is possible for trees to contain files that are not described
 
57
    in their inventory or vice versa; for this use `filenames()`.
 
58
 
61
59
    Trees can be compared, etc, regardless of whether they are working
62
60
    trees or versioned trees.
63
61
    """
64
62
 
65
 
    def has_versioned_directories(self):
66
 
        """Whether this tree can contain explicitly versioned directories.
67
 
 
68
 
        This defaults to True, but some implementations may want to override
69
 
        it.
70
 
        """
71
 
        return True
72
 
 
73
63
    def changes_from(self, other, want_unchanged=False, specific_files=None,
74
64
        extra_trees=None, require_versioned=False, include_root=False,
75
65
        want_unversioned=False):
115
105
    def conflicts(self):
116
106
        """Get a list of the conflicts in the tree.
117
107
 
118
 
        Each conflict is an instance of breezy.conflicts.Conflict.
 
108
        Each conflict is an instance of bzrlib.conflicts.Conflict.
119
109
        """
120
110
        return _mod_conflicts.ConflictList()
121
111
 
137
127
        raise NotImplementedError(self.has_filename)
138
128
 
139
129
    def has_id(self, file_id):
140
 
        raise NotImplementedError(self.has_id)
 
130
        return self.inventory.has_id(file_id)
 
131
 
 
132
    def __contains__(self, file_id):
 
133
        return self.has_id(file_id)
141
134
 
142
135
    def has_or_had_id(self, file_id):
143
 
        raise NotImplementedError(self.has_or_had_id)
 
136
        return self.inventory.has_id(file_id)
144
137
 
145
138
    def is_ignored(self, filename):
146
139
        """Check whether the filename is ignored by this tree.
150
143
        """
151
144
        return False
152
145
 
 
146
    def __iter__(self):
 
147
        return iter(self.inventory)
 
148
 
153
149
    def all_file_ids(self):
154
150
        """Iterate through all file ids, including ids for missing files."""
155
 
        raise NotImplementedError(self.all_file_ids)
 
151
        return set(self.inventory)
156
152
 
157
153
    def id2path(self, file_id):
158
154
        """Return the path for a file id.
159
155
 
160
156
        :raises NoSuchId:
161
157
        """
162
 
        raise NotImplementedError(self.id2path)
163
 
 
 
158
        return self.inventory.id2path(file_id)
 
159
 
 
160
    def is_control_filename(self, filename):
 
161
        """True if filename is the name of a control file in this tree.
 
162
 
 
163
        :param filename: A filename within the tree. This is a relative path
 
164
        from the root of this tree.
 
165
 
 
166
        This is true IF and ONLY IF the filename is part of the meta data
 
167
        that bzr controls in this tree. I.E. a random .bzr directory placed
 
168
        on disk will not be a control file for this tree.
 
169
        """
 
170
        return self.bzrdir.is_control_filename(filename)
 
171
 
 
172
    @needs_read_lock
164
173
    def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
165
174
        """Walk the tree in 'by_dir' order.
166
175
 
183
192
             g
184
193
 
185
194
        The yield order (ignoring root) would be::
186
 
 
187
195
          a, f, a/b, a/d, a/b/c, a/d/e, f/g
188
196
 
189
197
        :param yield_parents: If True, yield the parents from the root leading
190
198
            down to specific_file_ids that have been requested. This has no
191
199
            impact if specific_file_ids is None.
192
200
        """
193
 
        raise NotImplementedError(self.iter_entries_by_dir)
194
 
 
195
 
    def iter_child_entries(self, file_id, path=None):
196
 
        """Iterate over the children of a directory or tree reference.
197
 
 
198
 
        :param file_id: File id of the directory/tree-reference
199
 
        :param path: Optional path of the directory
200
 
        :raise NoSuchId: When the file_id does not exist
201
 
        :return: Iterator over entries in the directory
202
 
        """
203
 
        raise NotImplementedError(self.iter_child_entries)
204
 
 
205
 
    def list_files(self, include_root=False, from_dir=None, recursive=True):
206
 
        """List all files in this tree.
207
 
 
208
 
        :param include_root: Whether to include the entry for the tree root
209
 
        :param from_dir: Directory under which to list files
210
 
        :param recursive: Whether to list files recursively
211
 
        :return: iterator over tuples of (path, versioned, kind, file_id,
212
 
            inventory entry)
213
 
        """
214
 
        raise NotImplementedError(self.list_files)
 
201
        return self.inventory.iter_entries_by_dir(
 
202
            specific_file_ids=specific_file_ids, yield_parents=yield_parents)
215
203
 
216
204
    def iter_references(self):
217
205
        if self.supports_tree_reference():
268
256
    def _file_size(self, entry, stat_value):
269
257
        raise NotImplementedError(self._file_size)
270
258
 
 
259
    def _get_inventory(self):
 
260
        return self._inventory
 
261
 
271
262
    def get_file(self, file_id, path=None):
272
263
        """Return a file object for the file file_id in the tree.
273
264
 
295
286
 
296
287
        :param file_id: The file_id of the file.
297
288
        :param path: The path of the file.
298
 
 
299
289
        If both file_id and path are supplied, an implementation may use
300
290
        either one.
301
 
 
302
 
        :returns: A single byte string for the whole file.
303
291
        """
304
292
        my_file = self.get_file(file_id, path)
305
293
        try:
312
300
 
313
301
        :param file_id: The file_id of the file.
314
302
        :param path: The path of the file.
315
 
 
316
303
        If both file_id and path are supplied, an implementation may use
317
304
        either one.
318
305
        """
319
306
        return osutils.split_lines(self.get_file_text(file_id, path))
320
307
 
321
 
    def get_file_verifier(self, file_id, path=None, stat_value=None):
322
 
        """Return a verifier for a file.
323
 
 
324
 
        The default implementation returns a sha1.
325
 
 
326
 
        :param file_id: The handle for this file.
327
 
        :param path: The path that this file can be found at.
328
 
            These must point to the same object.
329
 
        :param stat_value: Optional stat value for the object
330
 
        :return: Tuple with verifier name and verifier data
331
 
        """
332
 
        return ("SHA1", self.get_file_sha1(file_id, path=path,
333
 
            stat_value=stat_value))
334
 
 
335
 
    def get_file_sha1(self, file_id, path=None, stat_value=None):
336
 
        """Return the SHA1 file for a file.
337
 
 
338
 
        :note: callers should use get_file_verifier instead
339
 
            where possible, as the underlying repository implementation may
340
 
            have quicker access to a non-sha1 verifier.
341
 
 
342
 
        :param file_id: The handle for this file.
343
 
        :param path: The path that this file can be found at.
344
 
            These must point to the same object.
345
 
        :param stat_value: Optional stat value for the object
346
 
        """
347
 
        raise NotImplementedError(self.get_file_sha1)
348
 
 
349
308
    def get_file_mtime(self, file_id, path=None):
350
309
        """Return the modification time for a file.
351
310
 
364
323
        """
365
324
        raise NotImplementedError(self.get_file_size)
366
325
 
367
 
    def is_executable(self, file_id, path=None):
368
 
        """Check if a file is executable.
369
 
 
370
 
        :param file_id: The handle for this file.
371
 
        :param path: The path that this file can be found at.
372
 
            These must point to the same object.
373
 
        """
374
 
        raise NotImplementedError(self.is_executable)
 
326
    def get_file_by_path(self, path):
 
327
        return self.get_file(self._inventory.path2id(path), path)
375
328
 
376
329
    def iter_files_bytes(self, desired_files):
377
330
        """Iterate through file contents.
399
352
            cur_file = (self.get_file_text(file_id),)
400
353
            yield identifier, cur_file
401
354
 
402
 
    def get_symlink_target(self, file_id, path=None):
 
355
    def get_symlink_target(self, file_id):
403
356
        """Get the target for a given file_id.
404
357
 
405
358
        It is assumed that the caller already knows that file_id is referencing
406
359
        a symlink.
407
360
        :param file_id: Handle for the symlink entry.
408
 
        :param path: The path of the file.
409
 
        If both file_id and path are supplied, an implementation may use
410
 
        either one.
411
361
        :return: The path the symlink points to.
412
362
        """
413
363
        raise NotImplementedError(self.get_symlink_target)
414
364
 
 
365
    def get_canonical_inventory_paths(self, paths):
 
366
        """Like get_canonical_inventory_path() but works on multiple items.
 
367
 
 
368
        :param paths: A sequence of paths relative to the root of the tree.
 
369
        :return: A list of paths, with each item the corresponding input path
 
370
        adjusted to account for existing elements that match case
 
371
        insensitively.
 
372
        """
 
373
        return list(self._yield_canonical_inventory_paths(paths))
 
374
 
 
375
    def get_canonical_inventory_path(self, path):
 
376
        """Returns the first inventory item that case-insensitively matches path.
 
377
 
 
378
        If a path matches exactly, it is returned. If no path matches exactly
 
379
        but more than one path matches case-insensitively, it is implementation
 
380
        defined which is returned.
 
381
 
 
382
        If no path matches case-insensitively, the input path is returned, but
 
383
        with as many path entries that do exist changed to their canonical
 
384
        form.
 
385
 
 
386
        If you need to resolve many names from the same tree, you should
 
387
        use get_canonical_inventory_paths() to avoid O(N) behaviour.
 
388
 
 
389
        :param path: A paths relative to the root of the tree.
 
390
        :return: The input path adjusted to account for existing elements
 
391
        that match case insensitively.
 
392
        """
 
393
        return self._yield_canonical_inventory_paths([path]).next()
 
394
 
 
395
    def _yield_canonical_inventory_paths(self, paths):
 
396
        for path in paths:
 
397
            # First, if the path as specified exists exactly, just use it.
 
398
            if self.path2id(path) is not None:
 
399
                yield path
 
400
                continue
 
401
            # go walkin...
 
402
            cur_id = self.get_root_id()
 
403
            cur_path = ''
 
404
            bit_iter = iter(path.split("/"))
 
405
            for elt in bit_iter:
 
406
                lelt = elt.lower()
 
407
                new_path = None
 
408
                for child in self.iter_children(cur_id):
 
409
                    try:
 
410
                        # XXX: it seem like if the child is known to be in the
 
411
                        # tree, we shouldn't need to go from its id back to
 
412
                        # its path -- mbp 2010-02-11
 
413
                        #
 
414
                        # XXX: it seems like we could be more efficient
 
415
                        # by just directly looking up the original name and
 
416
                        # only then searching all children; also by not
 
417
                        # chopping paths so much. -- mbp 2010-02-11
 
418
                        child_base = os.path.basename(self.id2path(child))
 
419
                        if (child_base == elt):
 
420
                            # if we found an exact match, we can stop now; if
 
421
                            # we found an approximate match we need to keep
 
422
                            # searching because there might be an exact match
 
423
                            # later.  
 
424
                            cur_id = child
 
425
                            new_path = osutils.pathjoin(cur_path, child_base)
 
426
                            break
 
427
                        elif child_base.lower() == lelt:
 
428
                            cur_id = child
 
429
                            new_path = osutils.pathjoin(cur_path, child_base)
 
430
                    except NoSuchId:
 
431
                        # before a change is committed we can see this error...
 
432
                        continue
 
433
                if new_path:
 
434
                    cur_path = new_path
 
435
                else:
 
436
                    # got to the end of this directory and no entries matched.
 
437
                    # Return what matched so far, plus the rest as specified.
 
438
                    cur_path = osutils.pathjoin(cur_path, elt, *list(bit_iter))
 
439
                    break
 
440
            yield cur_path
 
441
        # all done.
 
442
 
415
443
    def get_root_id(self):
416
444
        """Return the file_id for the root of this tree."""
417
445
        raise NotImplementedError(self.get_root_id)
431
459
        raise NotImplementedError(self.annotate_iter)
432
460
 
433
461
    def _get_plan_merge_data(self, file_id, other, base):
434
 
        from .bzr import versionedfile
 
462
        from bzrlib import versionedfile
435
463
        vf = versionedfile._PlanMergeVersionedFile(file_id)
436
464
        last_revision_a = self._get_file_revision(file_id, vf, 'this:')
437
465
        last_revision_b = other._get_file_revision(file_id, vf, 'other:')
475
503
            except errors.NoSuchRevisionInTree:
476
504
                yield self.repository.revision_tree(revision_id)
477
505
 
 
506
    @staticmethod
 
507
    def _file_revision(revision_tree, file_id):
 
508
        """Determine the revision associated with a file in a given tree."""
 
509
        revision_tree.lock_read()
 
510
        try:
 
511
            return revision_tree.inventory[file_id].revision
 
512
        finally:
 
513
            revision_tree.unlock()
 
514
 
478
515
    def _get_file_revision(self, file_id, vf, tree_revision):
479
516
        """Ensure that file_id, tree_revision is in vf to plan the merge."""
480
517
 
481
518
        if getattr(self, '_repository', None) is None:
482
519
            last_revision = tree_revision
483
 
            parent_keys = [(file_id, t.get_file_revision(file_id)) for t in
 
520
            parent_keys = [(file_id, self._file_revision(t, file_id)) for t in
484
521
                self._iter_parent_trees()]
485
522
            vf.add_lines((file_id, last_revision), parent_keys,
486
 
                         self.get_file_lines(file_id))
 
523
                         self.get_file(file_id).readlines())
487
524
            repo = self.branch.repository
488
525
            base_vf = repo.texts
489
526
        else:
490
 
            last_revision = self.get_file_revision(file_id)
 
527
            last_revision = self._file_revision(self, file_id)
491
528
            base_vf = self._repository.texts
492
529
        if base_vf not in vf.fallback_versionedfiles:
493
530
            vf.fallback_versionedfiles.append(base_vf)
494
531
        return last_revision
495
532
 
 
533
    inventory = property(_get_inventory,
 
534
                         doc="Inventory of this Tree")
 
535
 
496
536
    def _check_retrieved(self, ie, f):
497
537
        if not __debug__:
498
538
            return
499
 
        fp = osutils.fingerprint_file(f)
 
539
        fp = fingerprint_file(f)
500
540
        f.seek(0)
501
541
 
502
542
        if ie.text_size is not None:
503
543
            if ie.text_size != fp['size']:
504
 
                raise errors.BzrError(
505
 
                        "mismatched size for file %r in %r" %
506
 
                        (ie.file_id, self._store),
 
544
                raise BzrError("mismatched size for file %r in %r" % (ie.file_id, self._store),
507
545
                        ["inventory expects %d bytes" % ie.text_size,
508
546
                         "file is actually %d bytes" % fp['size'],
509
547
                         "store is probably damaged/corrupt"])
510
548
 
511
549
        if ie.text_sha1 != fp['sha1']:
512
 
            raise errors.BzrError("wrong SHA-1 for file %r in %r" %
513
 
                    (ie.file_id, self._store),
 
550
            raise BzrError("wrong SHA-1 for file %r in %r" % (ie.file_id, self._store),
514
551
                    ["inventory expects %s" % ie.text_sha1,
515
552
                     "file is actually %s" % fp['sha1'],
516
553
                     "store is probably damaged/corrupt"])
517
554
 
 
555
    @needs_read_lock
518
556
    def path2id(self, path):
519
557
        """Return the id for path in this tree."""
520
 
        raise NotImplementedError(self.path2id)
 
558
        return self._inventory.path2id(path)
521
559
 
522
560
    def paths2ids(self, paths, trees=[], require_versioned=True):
523
561
        """Return all the ids that can be reached by walking from paths.
539
577
        return find_ids_across_trees(paths, [self] + list(trees), require_versioned)
540
578
 
541
579
    def iter_children(self, file_id):
542
 
        """Iterate over the file ids of the children of an entry.
543
 
 
544
 
        :param file_id: File id of the entry
545
 
        :return: Iterator over child file ids.
546
 
        """
547
 
        raise NotImplementedError(self.iter_children)
 
580
        entry = self.iter_entries_by_dir([file_id]).next()[1]
 
581
        for child in getattr(entry, 'children', {}).itervalues():
 
582
            yield child.file_id
548
583
 
549
584
    def lock_read(self):
550
 
        """Lock this tree for multiple read only operations.
551
 
 
552
 
        :return: A breezy.lock.LogicalLockResult.
553
 
        """
554
585
        pass
555
586
 
556
587
    def revision_tree(self, revision_id):
583
614
 
584
615
        :return: set of paths.
585
616
        """
586
 
        raise NotImplementedError(self.filter_unversioned_files)
 
617
        # NB: we specifically *don't* call self.has_filename, because for
 
618
        # WorkingTrees that can indicate files that exist on disk but that
 
619
        # are not versioned.
 
620
        pred = self.inventory.has_filename
 
621
        return set((p for p in paths if not pred(p)))
587
622
 
588
623
    def walkdirs(self, prefix=""):
589
624
        """Walk the contents of this tree from path down.
638
673
            return []
639
674
        if path is None:
640
675
            path = self.id2path(file_id)
641
 
        prefs = next(self.iter_search_rules([path], filter_pref_names))
 
676
        prefs = self.iter_search_rules([path], filter_pref_names).next()
642
677
        stk = filters._get_filter_stack_for(prefs)
643
678
        if 'filters' in debug.debug_flags:
644
 
            trace.note(gettext("*** {0} content-filter: {1} => {2!r}").format(path,prefs,stk))
 
679
            note("*** %s content-filter: %s => %r" % (path,prefs,stk))
645
680
        return stk
646
681
 
647
682
    def _content_filter_stack_provider(self):
686
721
        return searcher
687
722
 
688
723
 
689
 
class InventoryTree(Tree):
690
 
    """A tree that relies on an inventory for its metadata.
691
 
 
692
 
    Trees contain an `Inventory` object, and also know how to retrieve
693
 
    file texts mentioned in the inventory, either from a working
694
 
    directory or from a store.
695
 
 
696
 
    It is possible for trees to contain files that are not described
697
 
    in their inventory or vice versa; for this use `filenames()`.
698
 
 
699
 
    Subclasses should set the _inventory attribute, which is considered
700
 
    private to external API users.
 
724
######################################################################
 
725
# diff
 
726
 
 
727
# TODO: Merge these two functions into a single one that can operate
 
728
# on either a whole tree or a set of files.
 
729
 
 
730
# TODO: Return the diff in order by filename, not by category or in
 
731
# random order.  Can probably be done by lock-stepping through the
 
732
# filenames from both trees.
 
733
 
 
734
 
 
735
def file_status(filename, old_tree, new_tree):
 
736
    """Return single-letter status, old and new names for a file.
 
737
 
 
738
    The complexity here is in deciding how to represent renames;
 
739
    many complex cases are possible.
701
740
    """
702
 
 
703
 
    def get_canonical_inventory_paths(self, paths):
704
 
        """Like get_canonical_inventory_path() but works on multiple items.
705
 
 
706
 
        :param paths: A sequence of paths relative to the root of the tree.
707
 
        :return: A list of paths, with each item the corresponding input path
708
 
        adjusted to account for existing elements that match case
709
 
        insensitively.
710
 
        """
711
 
        return list(self._yield_canonical_inventory_paths(paths))
712
 
 
713
 
    def get_canonical_inventory_path(self, path):
714
 
        """Returns the first inventory item that case-insensitively matches path.
715
 
 
716
 
        If a path matches exactly, it is returned. If no path matches exactly
717
 
        but more than one path matches case-insensitively, it is implementation
718
 
        defined which is returned.
719
 
 
720
 
        If no path matches case-insensitively, the input path is returned, but
721
 
        with as many path entries that do exist changed to their canonical
722
 
        form.
723
 
 
724
 
        If you need to resolve many names from the same tree, you should
725
 
        use get_canonical_inventory_paths() to avoid O(N) behaviour.
726
 
 
727
 
        :param path: A paths relative to the root of the tree.
728
 
        :return: The input path adjusted to account for existing elements
729
 
        that match case insensitively.
730
 
        """
731
 
        return next(self._yield_canonical_inventory_paths([path]))
732
 
 
733
 
    def _yield_canonical_inventory_paths(self, paths):
734
 
        for path in paths:
735
 
            # First, if the path as specified exists exactly, just use it.
736
 
            if self.path2id(path) is not None:
737
 
                yield path
738
 
                continue
739
 
            # go walkin...
740
 
            cur_id = self.get_root_id()
741
 
            cur_path = ''
742
 
            bit_iter = iter(path.split("/"))
743
 
            for elt in bit_iter:
744
 
                lelt = elt.lower()
745
 
                new_path = None
746
 
                for child in self.iter_children(cur_id):
747
 
                    try:
748
 
                        # XXX: it seem like if the child is known to be in the
749
 
                        # tree, we shouldn't need to go from its id back to
750
 
                        # its path -- mbp 2010-02-11
751
 
                        #
752
 
                        # XXX: it seems like we could be more efficient
753
 
                        # by just directly looking up the original name and
754
 
                        # only then searching all children; also by not
755
 
                        # chopping paths so much. -- mbp 2010-02-11
756
 
                        child_base = os.path.basename(self.id2path(child))
757
 
                        if (child_base == elt):
758
 
                            # if we found an exact match, we can stop now; if
759
 
                            # we found an approximate match we need to keep
760
 
                            # searching because there might be an exact match
761
 
                            # later.  
762
 
                            cur_id = child
763
 
                            new_path = osutils.pathjoin(cur_path, child_base)
764
 
                            break
765
 
                        elif child_base.lower() == lelt:
766
 
                            cur_id = child
767
 
                            new_path = osutils.pathjoin(cur_path, child_base)
768
 
                    except errors.NoSuchId:
769
 
                        # before a change is committed we can see this error...
770
 
                        continue
771
 
                if new_path:
772
 
                    cur_path = new_path
773
 
                else:
774
 
                    # got to the end of this directory and no entries matched.
775
 
                    # Return what matched so far, plus the rest as specified.
776
 
                    cur_path = osutils.pathjoin(cur_path, elt, *list(bit_iter))
777
 
                    break
778
 
            yield cur_path
779
 
        # all done.
780
 
 
781
 
    def _get_root_inventory(self):
782
 
        return self._inventory
783
 
 
784
 
    root_inventory = property(_get_root_inventory,
785
 
        doc="Root inventory of this tree")
786
 
 
787
 
    def _unpack_file_id(self, file_id):
788
 
        """Find the inventory and inventory file id for a tree file id.
789
 
 
790
 
        :param file_id: The tree file id, as bytestring or tuple
791
 
        :return: Inventory and inventory file id
792
 
        """
793
 
        if isinstance(file_id, tuple):
794
 
            if len(file_id) != 1:
795
 
                raise ValueError("nested trees not yet supported: %r" % file_id)
796
 
            file_id = file_id[0]
797
 
        return self.root_inventory, file_id
798
 
 
799
 
    @needs_read_lock
800
 
    def path2id(self, path):
801
 
        """Return the id for path in this tree."""
802
 
        return self._path2inv_file_id(path)[1]
803
 
 
804
 
    def _path2inv_file_id(self, path):
805
 
        """Lookup a inventory and inventory file id by path.
806
 
 
807
 
        :param path: Path to look up
808
 
        :return: tuple with inventory and inventory file id
809
 
        """
810
 
        # FIXME: Support nested trees
811
 
        return self.root_inventory, self.root_inventory.path2id(path)
812
 
 
813
 
    def id2path(self, file_id):
814
 
        """Return the path for a file id.
815
 
 
816
 
        :raises NoSuchId:
817
 
        """
818
 
        inventory, file_id = self._unpack_file_id(file_id)
819
 
        return inventory.id2path(file_id)
820
 
 
821
 
    def has_id(self, file_id):
822
 
        inventory, file_id = self._unpack_file_id(file_id)
823
 
        return inventory.has_id(file_id)
824
 
 
825
 
    def has_or_had_id(self, file_id):
826
 
        inventory, file_id = self._unpack_file_id(file_id)
827
 
        return inventory.has_id(file_id)
828
 
 
829
 
    def all_file_ids(self):
830
 
        return {entry.file_id for path, entry in self.iter_entries_by_dir()}
831
 
 
832
 
    def filter_unversioned_files(self, paths):
833
 
        """Filter out paths that are versioned.
834
 
 
835
 
        :return: set of paths.
836
 
        """
837
 
        # NB: we specifically *don't* call self.has_filename, because for
838
 
        # WorkingTrees that can indicate files that exist on disk but that
839
 
        # are not versioned.
840
 
        return set((p for p in paths if self.path2id(p) is None))
841
 
 
842
 
    @needs_read_lock
843
 
    def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
844
 
        """Walk the tree in 'by_dir' order.
845
 
 
846
 
        This will yield each entry in the tree as a (path, entry) tuple.
847
 
        The order that they are yielded is:
848
 
 
849
 
        See Tree.iter_entries_by_dir for details.
850
 
 
851
 
        :param yield_parents: If True, yield the parents from the root leading
852
 
            down to specific_file_ids that have been requested. This has no
853
 
            impact if specific_file_ids is None.
854
 
        """
855
 
        if specific_file_ids is None:
856
 
            inventory_file_ids = None
857
 
        else:
858
 
            inventory_file_ids = []
859
 
            for tree_file_id in specific_file_ids:
860
 
                inventory, inv_file_id = self._unpack_file_id(tree_file_id)
861
 
                if not inventory is self.root_inventory: # for now
862
 
                    raise AssertionError("%r != %r" % (
863
 
                        inventory, self.root_inventory))
864
 
                inventory_file_ids.append(inv_file_id)
865
 
        # FIXME: Handle nested trees
866
 
        return self.root_inventory.iter_entries_by_dir(
867
 
            specific_file_ids=inventory_file_ids, yield_parents=yield_parents)
868
 
 
869
 
    @needs_read_lock
870
 
    def iter_child_entries(self, file_id, path=None):
871
 
        inv, inv_file_id = self._unpack_file_id(file_id)
872
 
        return iter(viewvalues(inv[inv_file_id].children))
873
 
 
874
 
    def iter_children(self, file_id, path=None):
875
 
        """See Tree.iter_children."""
876
 
        entry = self.iter_entries_by_dir([file_id]).next()[1]
877
 
        for child in viewvalues(getattr(entry, 'children', {})):
878
 
            yield child.file_id
 
741
    old_inv = old_tree.inventory
 
742
    new_inv = new_tree.inventory
 
743
    new_id = new_inv.path2id(filename)
 
744
    old_id = old_inv.path2id(filename)
 
745
 
 
746
    if not new_id and not old_id:
 
747
        # easy: doesn't exist in either; not versioned at all
 
748
        if new_tree.is_ignored(filename):
 
749
            return 'I', None, None
 
750
        else:
 
751
            return '?', None, None
 
752
    elif new_id:
 
753
        # There is now a file of this name, great.
 
754
        pass
 
755
    else:
 
756
        # There is no longer a file of this name, but we can describe
 
757
        # what happened to the file that used to have
 
758
        # this name.  There are two possibilities: either it was
 
759
        # deleted entirely, or renamed.
 
760
        if new_inv.has_id(old_id):
 
761
            return 'X', old_inv.id2path(old_id), new_inv.id2path(old_id)
 
762
        else:
 
763
            return 'D', old_inv.id2path(old_id), None
 
764
 
 
765
    # if the file_id is new in this revision, it is added
 
766
    if new_id and not old_inv.has_id(new_id):
 
767
        return 'A'
 
768
 
 
769
    # if there used to be a file of this name, but that ID has now
 
770
    # disappeared, it is deleted
 
771
    if old_id and not new_inv.has_id(old_id):
 
772
        return 'D'
 
773
 
 
774
    return 'wtf?'
 
775
 
 
776
 
 
777
@deprecated_function(deprecated_in((1, 9, 0)))
 
778
def find_renames(old_inv, new_inv):
 
779
    for file_id in old_inv:
 
780
        if file_id not in new_inv:
 
781
            continue
 
782
        old_name = old_inv.id2path(file_id)
 
783
        new_name = new_inv.id2path(file_id)
 
784
        if old_name != new_name:
 
785
            yield (old_name, new_name)
879
786
 
880
787
 
881
788
def find_ids_across_trees(filenames, trees, require_versioned=True):
888
795
        None)
889
796
    :param trees: The trees to find file_ids within
890
797
    :param require_versioned: if true, all specified filenames must occur in
891
 
        at least one tree.
 
798
    at least one tree.
892
799
    :return: a set of file ids for the specified filenames and their children.
893
800
    """
894
801
    if not filenames:
957
864
    Its instances have methods like 'compare' and contain references to the
958
865
    source and target trees these operations are to be carried out on.
959
866
 
960
 
    Clients of breezy should not need to use InterTree directly, rather they
 
867
    Clients of bzrlib should not need to use InterTree directly, rather they
961
868
    should use the convenience methods on Tree such as 'Tree.compare()' which
962
869
    will pass through to InterTree as appropriate.
963
870
    """
970
877
 
971
878
    _optimisers = []
972
879
 
973
 
    @classmethod
974
 
    def is_compatible(kls, source, target):
975
 
        # The default implementation is naive and uses the public API, so
976
 
        # it works for all trees.
977
 
        return True
978
 
 
979
880
    def _changes_from_entries(self, source_entry, target_entry,
980
881
        source_path=None, target_path=None):
981
882
        """Generate a iter_changes tuple between source_entry and target_entry.
1029
930
        if source_kind != target_kind:
1030
931
            changed_content = True
1031
932
        elif source_kind == 'file':
1032
 
            if not self.file_content_matches(file_id, file_id, source_path,
1033
 
                    target_path, source_stat, target_stat):
 
933
            if (self.source.get_file_sha1(file_id, source_path, source_stat) !=
 
934
                self.target.get_file_sha1(file_id, target_path, target_stat)):
1034
935
                changed_content = True
1035
936
        elif source_kind == 'symlink':
1036
937
            if (self.source.get_symlink_target(file_id) !=
1037
938
                self.target.get_symlink_target(file_id)):
1038
939
                changed_content = True
1039
 
        elif source_kind == 'tree-reference':
1040
 
            if (self.source.get_reference_revision(file_id, source_path)
1041
 
                != self.target.get_reference_revision(file_id, target_path)):
 
940
            # XXX: Yes, the indentation below is wrong. But fixing it broke
 
941
            # test_merge.TestMergerEntriesLCAOnDisk.
 
942
            # test_nested_tree_subtree_renamed_and_modified. We'll wait for
 
943
            # the fix from bzr.dev -- vila 2009026
 
944
            elif source_kind == 'tree-reference':
 
945
                if (self.source.get_reference_revision(file_id, source_path)
 
946
                    != self.target.get_reference_revision(file_id, target_path)):
1042
947
                    changed_content = True
1043
948
        parent = (source_parent, target_parent)
1044
949
        name = (source_name, target_name)
1082
987
            # All files are unversioned, so just return an empty delta
1083
988
            # _compare_trees would think we want a complete delta
1084
989
            result = delta.TreeDelta()
1085
 
            fake_entry = inventory.InventoryFile('unused', 'unused', 'unused')
 
990
            fake_entry = InventoryFile('unused', 'unused', 'unused')
1086
991
            result.unversioned = [(path, None,
1087
992
                self.target._comparison_data(fake_entry, path)[0]) for path in
1088
993
                specific_files]
1153
1058
                                     self.target.extras()
1154
1059
                if specific_files is None or
1155
1060
                    osutils.is_inside_any(specific_files, p)])
1156
 
            all_unversioned = collections.deque(all_unversioned)
 
1061
            all_unversioned = deque(all_unversioned)
1157
1062
        else:
1158
 
            all_unversioned = collections.deque()
 
1063
            all_unversioned = deque()
1159
1064
        to_paths = {}
1160
1065
        from_entries_by_dir = list(self.source.iter_entries_by_dir(
1161
1066
            specific_file_ids=specific_file_ids))
1167
1072
        # the unversioned path lookup only occurs on real trees - where there
1168
1073
        # can be extras. So the fake_entry is solely used to look up
1169
1074
        # executable it values when execute is not supported.
1170
 
        fake_entry = inventory.InventoryFile('unused', 'unused', 'unused')
 
1075
        fake_entry = InventoryFile('unused', 'unused', 'unused')
1171
1076
        for target_path, target_entry in to_entries_by_dir:
1172
1077
            while (all_unversioned and
1173
1078
                all_unversioned[0][0] < target_path.split('/')):
1221
1126
            if file_id in to_paths:
1222
1127
                # already returned
1223
1128
                continue
1224
 
            if not self.target.has_id(file_id):
 
1129
            if file_id not in self.target.all_file_ids():
1225
1130
                # common case - paths we have not emitted are not present in
1226
1131
                # target.
1227
1132
                to_path = None
1259
1164
        :param file_id: The file_id to lookup.
1260
1165
        """
1261
1166
        try:
1262
 
            inventory = tree.root_inventory
 
1167
            inventory = tree.inventory
1263
1168
        except NotImplementedError:
1264
1169
            # No inventory available.
1265
1170
            try:
1340
1245
                        if old_entry is None:
1341
1246
                            # Reusing a discarded change.
1342
1247
                            old_entry = self._get_entry(self.source, file_id)
1343
 
                        precise_file_ids.update(
1344
 
                                self.source.iter_children(file_id))
 
1248
                        for child in old_entry.children.values():
 
1249
                            precise_file_ids.add(child.file_id)
1345
1250
                    changed_file_ids.add(result[0])
1346
1251
                    yield result
1347
1252
 
1348
 
    @needs_read_lock
1349
 
    def file_content_matches(self, source_file_id, target_file_id,
1350
 
            source_path=None, target_path=None, source_stat=None, target_stat=None):
1351
 
        """Check if two files are the same in the source and target trees.
1352
 
 
1353
 
        This only checks that the contents of the files are the same,
1354
 
        it does not touch anything else.
1355
 
 
1356
 
        :param source_file_id: File id of the file in the source tree
1357
 
        :param target_file_id: File id of the file in the target tree
1358
 
        :param source_path: Path of the file in the source tree
1359
 
        :param target_path: Path of the file in the target tree
1360
 
        :param source_stat: Optional stat value of the file in the source tree
1361
 
        :param target_stat: Optional stat value of the file in the target tree
1362
 
        :return: Boolean indicating whether the files have the same contents
1363
 
        """
1364
 
        source_verifier_kind, source_verifier_data = self.source.get_file_verifier(
1365
 
            source_file_id, source_path, source_stat)
1366
 
        target_verifier_kind, target_verifier_data = self.target.get_file_verifier(
1367
 
            target_file_id, target_path, target_stat)
1368
 
        if source_verifier_kind == target_verifier_kind:
1369
 
            return (source_verifier_data == target_verifier_data)
1370
 
        # Fall back to SHA1 for now
1371
 
        if source_verifier_kind != "SHA1":
1372
 
            source_sha1 = self.source.get_file_sha1(source_file_id,
1373
 
                    source_path, source_stat)
1374
 
        else:
1375
 
            source_sha1 = source_verifier_data
1376
 
        if target_verifier_kind != "SHA1":
1377
 
            target_sha1 = self.target.get_file_sha1(target_file_id,
1378
 
                    target_path, target_stat)
1379
 
        else:
1380
 
            target_sha1 = target_verifier_data
1381
 
        return (source_sha1 == target_sha1)
1382
 
 
1383
 
InterTree.register_optimiser(InterTree)
1384
 
 
1385
1253
 
1386
1254
class MultiWalker(object):
1387
1255
    """Walk multiple trees simultaneously, getting combined results."""
1421
1289
            If has_more is False, path and ie will be None.
1422
1290
        """
1423
1291
        try:
1424
 
            path, ie = next(iterator)
 
1292
            path, ie = iterator.next()
1425
1293
        except StopIteration:
1426
1294
            return False, None, None
1427
1295
        else:
1490
1358
            return (None, None)
1491
1359
        else:
1492
1360
            self._out_of_order_processed.add(file_id)
1493
 
            cur_ie = other_tree.root_inventory[file_id]
 
1361
            cur_ie = other_tree.inventory[file_id]
1494
1362
            return (cur_path, cur_ie)
1495
1363
 
1496
1364
    def iter_all(self):
1517
1385
                         for other in self._other_trees]
1518
1386
        other_entries = [self._step_one(walker) for walker in other_walkers]
1519
1387
        # Track extra nodes in the other trees
1520
 
        others_extra = [{} for _ in range(len(self._other_trees))]
 
1388
        others_extra = [{} for i in xrange(len(self._other_trees))]
1521
1389
 
1522
1390
        master_has_more = True
1523
1391
        step_one = self._step_one
1597
1465
        #       might ensure better ordering, in case a caller strictly
1598
1466
        #       requires parents before children.
1599
1467
        for idx, other_extra in enumerate(self._others_extra):
1600
 
            others = sorted(viewvalues(other_extra),
 
1468
            others = sorted(other_extra.itervalues(),
1601
1469
                            key=lambda x: self._path_to_key(x[0]))
1602
1470
            for other_path, other_ie in others:
1603
1471
                file_id = other_ie.file_id
1605
1473
                # the lookup_by_file_id will be removing anything processed
1606
1474
                # from the extras cache
1607
1475
                other_extra.pop(file_id)
1608
 
                other_values = [(None, None)] * idx
 
1476
                other_values = [(None, None) for i in xrange(idx)]
1609
1477
                other_values.append((other_path, other_ie))
1610
1478
                for alt_idx, alt_extra in enumerate(self._others_extra[idx+1:]):
1611
1479
                    alt_idx = alt_idx + idx + 1