/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: 2018-03-24 17:48:04 UTC
  • mfrom: (6921 work)
  • mto: This revision was merged to the branch mainline in revision 6923.
  • Revision ID: jelmer@jelmer.uk-20180324174804-xf22o05byoj12x1q
Merge trunk.

Show diffs side-by-side

added added

removed removed

Lines of Context:
61
61
        self.path = path
62
62
 
63
63
 
 
64
class TreeEntry(object):
 
65
    """An entry that implements the minimum interface used by commands.
 
66
    """
 
67
 
 
68
    def __eq__(self, other):
 
69
        # yes, this is ugly, TODO: best practice __eq__ style.
 
70
        return (isinstance(other, TreeEntry)
 
71
                and other.__class__ == self.__class__)
 
72
 
 
73
    def kind_character(self):
 
74
        return "???"
 
75
 
 
76
 
 
77
class TreeDirectory(TreeEntry):
 
78
    """See TreeEntry. This is a directory in a working tree."""
 
79
 
 
80
    def kind_character(self):
 
81
        return "/"
 
82
 
 
83
 
 
84
class TreeFile(TreeEntry):
 
85
    """See TreeEntry. This is a regular file in a working tree."""
 
86
 
 
87
    def kind_character(self):
 
88
        return ''
 
89
 
 
90
 
 
91
class TreeLink(TreeEntry):
 
92
    """See TreeEntry. This is a symlink in a working tree."""
 
93
 
 
94
    def kind_character(self):
 
95
        return ''
 
96
 
 
97
 
64
98
class Tree(object):
65
99
    """Abstract file tree.
66
100
 
74
108
    trees or versioned trees.
75
109
    """
76
110
 
 
111
    def supports_rename_tracking(self):
 
112
        """Whether this tree supports rename tracking.
 
113
 
 
114
        This defaults to True, but some implementations may want to override
 
115
        it.
 
116
        """
 
117
        return True
 
118
 
77
119
    def has_versioned_directories(self):
78
120
        """Whether this tree can contain explicitly versioned directories.
79
121
 
171
213
        """
172
214
        raise NotImplementedError(self.id2path)
173
215
 
174
 
    def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
 
216
    def iter_entries_by_dir(self, specific_files=None, yield_parents=False):
175
217
        """Walk the tree in 'by_dir' order.
176
218
 
177
219
        This will yield each entry in the tree as a (path, entry) tuple.
197
239
          a, f, a/b, a/d, a/b/c, a/d/e, f/g
198
240
 
199
241
        :param yield_parents: If True, yield the parents from the root leading
200
 
            down to specific_file_ids that have been requested. This has no
201
 
            impact if specific_file_ids is None.
 
242
            down to specific_files that have been requested. This has no
 
243
            impact if specific_files is None.
202
244
        """
203
245
        raise NotImplementedError(self.iter_entries_by_dir)
204
246
 
275
317
        """
276
318
        raise NotImplementedError(self._comparison_data)
277
319
 
278
 
    def _file_size(self, entry, stat_value):
279
 
        raise NotImplementedError(self._file_size)
280
 
 
281
320
    def get_file(self, path, file_id=None):
282
321
        """Return a file object for the file file_id in the tree.
283
322
 
399
438
        this implementation, it is a tuple containing a single bytestring with
400
439
        the complete text of the file.
401
440
 
402
 
        :param desired_files: a list of (file_id, identifier) pairs
 
441
        :param desired_files: a list of (path, identifier) pairs
403
442
        """
404
 
        for file_id, identifier in desired_files:
 
443
        for path, identifier in desired_files:
405
444
            # We wrap the string in a tuple so that we can return an iterable
406
445
            # of bytestrings.  (Technically, a bytestring is also an iterable
407
446
            # of bytestrings, but iterating through each character is not
408
447
            # performant.)
409
 
            # TODO(jelmer): Pass paths into iter_files_bytes
410
 
            path = self.id2path(file_id)
411
 
            cur_file = (self.get_file_text(path, file_id),)
 
448
            cur_file = (self.get_file_text(path),)
412
449
            yield identifier, cur_file
413
450
 
414
451
    def get_symlink_target(self, path, file_id=None):
442
479
        """
443
480
        raise NotImplementedError(self.annotate_iter)
444
481
 
445
 
    def _get_plan_merge_data(self, file_id, other, base):
446
 
        from .bzr import versionedfile
447
 
        vf = versionedfile._PlanMergeVersionedFile(file_id)
448
 
        last_revision_a = self._get_file_revision(
449
 
                self.id2path(file_id), file_id, vf, 'this:')
450
 
        last_revision_b = other._get_file_revision(
451
 
                other.id2path(file_id), file_id, vf, 'other:')
452
 
        if base is None:
453
 
            last_revision_base = None
454
 
        else:
455
 
            last_revision_base = base._get_file_revision(
456
 
                    base.id2path(file_id), file_id, vf, 'base:')
457
 
        return vf, last_revision_a, last_revision_b, last_revision_base
458
 
 
459
 
    def plan_file_merge(self, file_id, other, base=None):
460
 
        """Generate a merge plan based on annotations.
461
 
 
462
 
        If the file contains uncommitted changes in this tree, they will be
463
 
        attributed to the 'current:' pseudo-revision.  If the file contains
464
 
        uncommitted changes in the other tree, they will be assigned to the
465
 
        'other:' pseudo-revision.
466
 
        """
467
 
        data = self._get_plan_merge_data(file_id, other, base)
468
 
        vf, last_revision_a, last_revision_b, last_revision_base = data
469
 
        return vf.plan_merge(last_revision_a, last_revision_b,
470
 
                             last_revision_base)
471
 
 
472
 
    def plan_file_lca_merge(self, file_id, other, base=None):
473
 
        """Generate a merge plan based lca-newness.
474
 
 
475
 
        If the file contains uncommitted changes in this tree, they will be
476
 
        attributed to the 'current:' pseudo-revision.  If the file contains
477
 
        uncommitted changes in the other tree, they will be assigned to the
478
 
        'other:' pseudo-revision.
479
 
        """
480
 
        data = self._get_plan_merge_data(file_id, other, base)
481
 
        vf, last_revision_a, last_revision_b, last_revision_base = data
482
 
        return vf.plan_lca_merge(last_revision_a, last_revision_b,
483
 
                                 last_revision_base)
484
 
 
485
482
    def _iter_parent_trees(self):
486
483
        """Iterate through parent trees, defaulting to Tree.revision_tree."""
487
484
        for revision_id in self.get_parent_ids():
490
487
            except errors.NoSuchRevisionInTree:
491
488
                yield self.repository.revision_tree(revision_id)
492
489
 
493
 
    def _get_file_revision(self, path, file_id, vf, tree_revision):
494
 
        """Ensure that file_id, tree_revision is in vf to plan the merge."""
495
 
        if getattr(self, '_repository', None) is None:
496
 
            last_revision = tree_revision
497
 
            parent_keys = [(file_id, t.get_file_revision(path, file_id)) for t in
498
 
                self._iter_parent_trees()]
499
 
            vf.add_lines((file_id, last_revision), parent_keys,
500
 
                         self.get_file_lines(path, file_id))
501
 
            repo = self.branch.repository
502
 
            base_vf = repo.texts
503
 
        else:
504
 
            last_revision = self.get_file_revision(path, file_id)
505
 
            base_vf = self._repository.texts
506
 
        if base_vf not in vf.fallback_versionedfiles:
507
 
            vf.fallback_versionedfiles.append(base_vf)
508
 
        return last_revision
509
 
 
510
 
    def _check_retrieved(self, ie, f):
511
 
        if not __debug__:
512
 
            return
513
 
        fp = osutils.fingerprint_file(f)
514
 
        f.seek(0)
515
 
 
516
 
        if ie.text_size is not None:
517
 
            if ie.text_size != fp['size']:
518
 
                raise errors.BzrError(
519
 
                        "mismatched size for file %r in %r" %
520
 
                        (ie.file_id, self._store),
521
 
                        ["inventory expects %d bytes" % ie.text_size,
522
 
                         "file is actually %d bytes" % fp['size'],
523
 
                         "store is probably damaged/corrupt"])
524
 
 
525
 
        if ie.text_sha1 != fp['sha1']:
526
 
            raise errors.BzrError("wrong SHA-1 for file %r in %r" %
527
 
                    (ie.file_id, self._store),
528
 
                    ["inventory expects %s" % ie.text_sha1,
529
 
                     "file is actually %s" % fp['sha1'],
530
 
                     "store is probably damaged/corrupt"])
531
 
 
532
490
    def path2id(self, path):
533
491
        """Return the id for path in this tree."""
534
492
        raise NotImplementedError(self.path2id)
535
493
 
536
 
    def paths2ids(self, paths, trees=[], require_versioned=True):
537
 
        """Return all the ids that can be reached by walking from paths.
538
 
 
539
 
        Each path is looked up in this tree and any extras provided in
540
 
        trees, and this is repeated recursively: the children in an extra tree
541
 
        of a directory that has been renamed under a provided path in this tree
542
 
        are all returned, even if none exist under a provided path in this
543
 
        tree, and vice versa.
544
 
 
545
 
        :param paths: An iterable of paths to start converting to ids from.
546
 
            Alternatively, if paths is None, no ids should be calculated and None
547
 
            will be returned. This is offered to make calling the api unconditional
548
 
            for code that *might* take a list of files.
549
 
        :param trees: Additional trees to consider.
550
 
        :param require_versioned: If False, do not raise NotVersionedError if
551
 
            an element of paths is not versioned in this tree and all of trees.
552
 
        """
553
 
        return find_ids_across_trees(paths, [self] + list(trees), require_versioned)
554
 
 
555
 
    def iter_children(self, file_id):
556
 
        """Iterate over the file ids of the children of an entry.
557
 
 
558
 
        :param file_id: File id of the entry
559
 
        :return: Iterator over child file ids.
560
 
        """
561
 
        raise NotImplementedError(self.iter_children)
 
494
    def is_versioned(self, path):
 
495
        """Check whether path is versioned.
 
496
 
 
497
        :param path: Path to check
 
498
        :return: boolean
 
499
        """
 
500
        return self.path2id(path) is not None
 
501
 
 
502
    def find_related_paths_across_trees(self, paths, trees=[],
 
503
            require_versioned=True):
 
504
        """Find related paths in tree corresponding to specified filenames in any
 
505
        of `lookup_trees`.
 
506
 
 
507
        All matches in all trees will be used, and all children of matched
 
508
        directories will be used.
 
509
 
 
510
        :param paths: The filenames to find related paths for (if None, returns
 
511
            None)
 
512
        :param trees: The trees to find file_ids within
 
513
        :param require_versioned: if true, all specified filenames must occur in
 
514
            at least one tree.
 
515
        :return: a set of paths for the specified filenames and their children
 
516
            in `tree`
 
517
        """
 
518
        raise NotImplementedError(self.find_related_paths_across_trees)
562
519
 
563
520
    def lock_read(self):
564
521
        """Lock this tree for multiple read only operations.
635
592
    def supports_content_filtering(self):
636
593
        return False
637
594
 
638
 
    def _content_filter_stack(self, path=None, file_id=None):
 
595
    def _content_filter_stack(self, path=None):
639
596
        """The stack of content filters for a path if filtering is supported.
640
597
 
641
598
        Readers will be applied in first-to-last order.
644
601
 
645
602
        :param path: path relative to the root of the tree
646
603
            or None if unknown
647
 
        :param file_id: file_id or None if unknown
648
604
        :return: the list of filters - [] if there are none
649
605
        """
650
606
        filter_pref_names = filters._get_registered_names()
651
607
        if len(filter_pref_names) == 0:
652
608
            return []
653
 
        if path is None:
654
 
            path = self.id2path(file_id)
655
609
        prefs = next(self.iter_search_rules([path], filter_pref_names))
656
610
        stk = filters._get_filter_stack_for(prefs)
657
611
        if 'filters' in debug.debug_flags:
668
622
        """
669
623
        if self.supports_content_filtering():
670
624
            return lambda path, file_id: \
671
 
                    self._content_filter_stack(path, file_id)
 
625
                    self._content_filter_stack(path)
672
626
        else:
673
627
            return None
674
628
 
700
654
        return searcher
701
655
 
702
656
 
703
 
def find_ids_across_trees(filenames, trees, require_versioned=True):
704
 
    """Find the ids corresponding to specified filenames.
705
 
 
706
 
    All matches in all trees will be used, and all children of matched
707
 
    directories will be used.
708
 
 
709
 
    :param filenames: The filenames to find file_ids for (if None, returns
710
 
        None)
711
 
    :param trees: The trees to find file_ids within
712
 
    :param require_versioned: if true, all specified filenames must occur in
713
 
        at least one tree.
714
 
    :return: a set of file ids for the specified filenames and their children.
715
 
    """
716
 
    if not filenames:
717
 
        return None
718
 
    specified_path_ids = _find_ids_across_trees(filenames, trees,
719
 
        require_versioned)
720
 
    return _find_children_across_trees(specified_path_ids, trees)
721
 
 
722
 
 
723
 
def _find_ids_across_trees(filenames, trees, require_versioned):
724
 
    """Find the ids corresponding to specified filenames.
725
 
 
726
 
    All matches in all trees will be used, but subdirectories are not scanned.
727
 
 
728
 
    :param filenames: The filenames to find file_ids for
729
 
    :param trees: The trees to find file_ids within
730
 
    :param require_versioned: if true, all specified filenames must occur in
731
 
        at least one tree.
732
 
    :return: a set of file ids for the specified filenames
733
 
    """
734
 
    not_versioned = []
735
 
    interesting_ids = set()
736
 
    for tree_path in filenames:
737
 
        not_found = True
738
 
        for tree in trees:
739
 
            file_id = tree.path2id(tree_path)
740
 
            if file_id is not None:
741
 
                interesting_ids.add(file_id)
742
 
                not_found = False
743
 
        if not_found:
744
 
            not_versioned.append(tree_path)
745
 
    if len(not_versioned) > 0 and require_versioned:
746
 
        raise errors.PathsNotVersionedError(not_versioned)
747
 
    return interesting_ids
748
 
 
749
 
 
750
 
def _find_children_across_trees(specified_ids, trees):
751
 
    """Return a set including specified ids and their children.
752
 
 
753
 
    All matches in all trees will be used.
754
 
 
755
 
    :param trees: The trees to find file_ids within
756
 
    :return: a set containing all specified ids and their children
757
 
    """
758
 
    interesting_ids = set(specified_ids)
759
 
    pending = interesting_ids
760
 
    # now handle children of interesting ids
761
 
    # we loop so that we handle all children of each id in both trees
762
 
    while len(pending) > 0:
763
 
        new_pending = set()
764
 
        for file_id in pending:
765
 
            for tree in trees:
766
 
                if not tree.has_or_had_id(file_id):
767
 
                    continue
768
 
                for child_id in tree.iter_children(file_id):
769
 
                    if child_id not in interesting_ids:
770
 
                        new_pending.add(child_id)
771
 
        interesting_ids.update(new_pending)
772
 
        pending = new_pending
773
 
    return interesting_ids
774
 
 
775
 
 
776
657
class InterTree(InterObject):
777
658
    """This class represents operations taking place between two Trees.
778
659
 
798
679
        # it works for all trees.
799
680
        return True
800
681
 
801
 
    def _changes_from_entries(self, source_entry, target_entry,
802
 
        source_path=None, target_path=None):
 
682
    def _changes_from_entries(self, source_entry, target_entry, source_path,
 
683
                              target_path):
803
684
        """Generate a iter_changes tuple between source_entry and target_entry.
804
685
 
805
686
        :param source_entry: An inventory entry from self.source, or None.
806
687
        :param target_entry: An inventory entry from self.target, or None.
807
 
        :param source_path: The path of source_entry, if known. If not known
808
 
            it will be looked up.
809
 
        :param target_path: The path of target_entry, if known. If not known
810
 
            it will be looked up.
 
688
        :param source_path: The path of source_entry.
 
689
        :param target_path: The path of target_entry.
811
690
        :return: A tuple, item 0 of which is an iter_changes result tuple, and
812
691
            item 1 is True if there are any changes in the result tuple.
813
692
        """
821
700
            source_versioned = True
822
701
            source_name = source_entry.name
823
702
            source_parent = source_entry.parent_id
824
 
            if source_path is None:
825
 
                source_path = self.source.id2path(file_id)
826
703
            source_kind, source_executable, source_stat = \
827
704
                self.source._comparison_data(source_entry, source_path)
828
705
        else:
835
712
            target_versioned = True
836
713
            target_name = target_entry.name
837
714
            target_parent = target_entry.parent_id
838
 
            if target_path is None:
839
 
                target_path = self.target.id2path(file_id)
840
715
            target_kind, target_executable, target_stat = \
841
716
                self.target._comparison_data(target_entry, target_path)
842
717
        else:
851
726
        if source_kind != target_kind:
852
727
            changed_content = True
853
728
        elif source_kind == 'file':
854
 
            if not self.file_content_matches(file_id, file_id, source_path,
855
 
                    target_path, source_stat, target_stat):
 
729
            if not self.file_content_matches(
 
730
                    source_path, target_path,
 
731
                    file_id, file_id, source_stat, target_stat):
856
732
                changed_content = True
857
733
        elif source_kind == 'symlink':
858
734
            if (self.source.get_symlink_target(source_path, file_id) !=
897
773
        if extra_trees is not None:
898
774
            trees = trees + tuple(extra_trees)
899
775
        with self.lock_read():
900
 
            # target is usually the newer tree:
901
 
            specific_file_ids = self.target.paths2ids(specific_files, trees,
902
 
                require_versioned=require_versioned)
903
 
            if specific_files and not specific_file_ids:
904
 
                # All files are unversioned, so just return an empty delta
905
 
                # _compare_trees would think we want a complete delta
906
 
                result = delta.TreeDelta()
907
 
                fake_entry = inventory.InventoryFile('unused', 'unused', 'unused')
908
 
                result.unversioned = [(path, None,
909
 
                    self.target._comparison_data(fake_entry, path)[0]) for path in
910
 
                    specific_files]
911
 
                return result
912
776
            return delta._compare_trees(self.source, self.target, want_unchanged,
913
777
                specific_files, include_root, extra_trees=extra_trees,
914
778
                require_versioned=require_versioned,
950
814
            output. An unversioned file is defined as one with (False, False)
951
815
            for the versioned pair.
952
816
        """
953
 
        lookup_trees = [self.source]
954
 
        if extra_trees:
955
 
             lookup_trees.extend(extra_trees)
 
817
        if not extra_trees:
 
818
             extra_trees = []
 
819
        else:
 
820
             extra_trees = list(extra_trees)
956
821
        # The ids of items we need to examine to insure delta consistency.
957
822
        precise_file_ids = set()
958
823
        changed_file_ids = []
959
824
        if specific_files == []:
960
 
            specific_file_ids = []
 
825
            target_specific_files = []
 
826
            source_specific_files = []
961
827
        else:
962
 
            specific_file_ids = self.target.paths2ids(specific_files,
963
 
                lookup_trees, require_versioned=require_versioned)
 
828
            target_specific_files = self.target.find_related_paths_across_trees(
 
829
                    specific_files, [self.source] + extra_trees,
 
830
                    require_versioned=require_versioned)
 
831
            source_specific_files = self.source.find_related_paths_across_trees(
 
832
                    specific_files, [self.target] + extra_trees,
 
833
                    require_versioned=require_versioned)
964
834
        if specific_files is not None:
965
835
            # reparented or added entries must have their parents included
966
836
            # so that valid deltas can be created. The seen_parents set
980
850
            all_unversioned = collections.deque()
981
851
        to_paths = {}
982
852
        from_entries_by_dir = list(self.source.iter_entries_by_dir(
983
 
            specific_file_ids=specific_file_ids))
 
853
            specific_files=source_specific_files))
984
854
        from_data = dict((e.file_id, (p, e)) for p, e in from_entries_by_dir)
985
855
        to_entries_by_dir = list(self.target.iter_entries_by_dir(
986
 
            specific_file_ids=specific_file_ids))
 
856
            specific_files=target_specific_files))
987
857
        num_entries = len(from_entries_by_dir) + len(to_entries_by_dir)
988
858
        entry_count = 0
989
859
        # the unversioned path lookup only occurs on real trees - where there
1012
882
            if pb is not None:
1013
883
                pb.update('comparing files', entry_count, num_entries)
1014
884
            if changes or include_unchanged:
1015
 
                if specific_file_ids is not None:
 
885
                if specific_files is not None:
1016
886
                    new_parent_id = result[4][1]
1017
887
                    precise_file_ids.add(new_parent_id)
1018
888
                    changed_file_ids.append(result[0])
1043
913
            if file_id in to_paths:
1044
914
                # already returned
1045
915
                continue
1046
 
            if not self.target.has_id(file_id):
1047
 
                # common case - paths we have not emitted are not present in
1048
 
                # target.
1049
 
                to_path = None
1050
 
            else:
1051
 
                to_path = self.target.id2path(file_id)
 
916
            to_path = find_previous_path(self.source, self.target, path)
1052
917
            entry_count += 1
1053
918
            if pb is not None:
1054
919
                pb.update('comparing files', entry_count, num_entries)
1065
930
            yield(file_id, (path, to_path), changed_content, versioned, parent,
1066
931
                  name, kind, executable)
1067
932
        changed_file_ids = set(changed_file_ids)
1068
 
        if specific_file_ids is not None:
 
933
        if specific_files is not None:
1069
934
            for result in self._handle_precise_ids(precise_file_ids,
1070
935
                changed_file_ids):
1071
936
                yield result
1072
937
 
1073
 
    def _get_entry(self, tree, file_id):
 
938
    def _get_entry(self, tree, path):
1074
939
        """Get an inventory entry from a tree, with missing entries as None.
1075
940
 
1076
941
        If the tree raises NotImplementedError on accessing .inventory, then
1080
945
        :param tree: The tree to lookup the entry in.
1081
946
        :param file_id: The file_id to lookup.
1082
947
        """
 
948
        # No inventory available.
1083
949
        try:
1084
 
            inventory = tree.root_inventory
1085
 
        except (AttributeError, NotImplementedError):
1086
 
            # No inventory available.
1087
 
            try:
1088
 
                iterator = tree.iter_entries_by_dir(specific_file_ids=[file_id])
1089
 
                return iterator.next()[1]
1090
 
            except StopIteration:
1091
 
                return None
1092
 
        else:
1093
 
            try:
1094
 
                return inventory[file_id]
1095
 
            except errors.NoSuchId:
1096
 
                return None
 
950
            iterator = tree.iter_entries_by_dir(specific_files=[path])
 
951
            return iterator.next()[1]
 
952
        except StopIteration:
 
953
            return None
1097
954
 
1098
955
    def _handle_precise_ids(self, precise_file_ids, changed_file_ids,
1099
956
        discarded_changes=None):
1141
998
                # Examine file_id
1142
999
                if discarded_changes:
1143
1000
                    result = discarded_changes.get(file_id)
1144
 
                    old_entry = None
 
1001
                    source_entry = None
1145
1002
                else:
1146
1003
                    result = None
1147
1004
                if result is None:
1148
 
                    old_entry = self._get_entry(self.source, file_id)
1149
 
                    new_entry = self._get_entry(self.target, file_id)
 
1005
                    try:
 
1006
                        source_path = self.source.id2path(file_id)
 
1007
                    except errors.NoSuchId:
 
1008
                        source_path = None
 
1009
                        source_entry = None
 
1010
                    else:
 
1011
                        source_entry = self._get_entry(self.source, source_path)
 
1012
                    try:
 
1013
                        target_path = self.target.id2path(file_id)
 
1014
                    except errors.NoSuchId:
 
1015
                        target_path = None
 
1016
                        target_entry = None
 
1017
                    else:
 
1018
                        target_entry = self._get_entry(self.target, target_path)
1150
1019
                    result, changes = self._changes_from_entries(
1151
 
                        old_entry, new_entry)
 
1020
                        source_entry, target_entry, source_path, target_path)
1152
1021
                else:
1153
1022
                    changes = True
1154
1023
                # Get this parents parent to examine.
1159
1028
                            result[6][1] != 'directory'):
1160
1029
                        # This stopped being a directory, the old children have
1161
1030
                        # to be included.
1162
 
                        if old_entry is None:
 
1031
                        if source_entry is None:
1163
1032
                            # Reusing a discarded change.
1164
 
                            old_entry = self._get_entry(self.source, file_id)
 
1033
                            source_entry = self._get_entry(self.source, result[1][0])
1165
1034
                        precise_file_ids.update(
1166
 
                                self.source.iter_children(file_id))
 
1035
                                child.file_id
 
1036
                                for child in self.source.iter_child_entries(result[1][0]))
1167
1037
                    changed_file_ids.add(result[0])
1168
1038
                    yield result
1169
1039
 
1170
1040
    def file_content_matches(
1171
 
            self, source_file_id, target_file_id, source_path=None,
1172
 
            target_path=None, source_stat=None, target_stat=None):
 
1041
            self, source_path, target_path,
 
1042
            source_file_id=None, target_file_id=None,
 
1043
            source_stat=None, target_stat=None):
1173
1044
        """Check if two files are the same in the source and target trees.
1174
1045
 
1175
1046
        This only checks that the contents of the files are the same,
1176
1047
        it does not touch anything else.
1177
1048
 
1178
 
        :param source_file_id: File id of the file in the source tree
1179
 
        :param target_file_id: File id of the file in the target tree
1180
1049
        :param source_path: Path of the file in the source tree
1181
1050
        :param target_path: Path of the file in the target tree
 
1051
        :param source_file_id: Optional file id of the file in the source tree
 
1052
        :param target_file_id: Optional file id of the file in the target tree
1182
1053
        :param source_stat: Optional stat value of the file in the source tree
1183
1054
        :param target_stat: Optional stat value of the file in the target tree
1184
1055
        :return: Boolean indicating whether the files have the same contents
1185
1056
        """
1186
1057
        with self.lock_read():
1187
 
            if source_path is None:
1188
 
                source_path = self.source.id2path(source_file_id)
1189
 
            if target_path is None:
1190
 
                target_path = self.target.id2path(target_file_id)
1191
1058
            source_verifier_kind, source_verifier_data = (
1192
1059
                    self.source.get_file_verifier(
1193
1060
                        source_path, source_file_id, source_stat))
1443
1310
                    other_values.append(self._lookup_by_file_id(
1444
1311
                                            alt_extra, alt_tree, file_id))
1445
1312
                yield other_path, file_id, None, other_values
 
1313
 
 
1314
 
 
1315
def find_previous_paths(from_tree, to_tree, paths):
 
1316
    """Find previous tree paths.
 
1317
 
 
1318
    :param from_tree: From tree
 
1319
    :param to_tree: To tree
 
1320
    :param paths: Iterable over paths to search for
 
1321
    :return: Dictionary mapping from from_tree paths to paths in to_tree, or
 
1322
        None if there is no equivalent path.
 
1323
    """
 
1324
    ret = {}
 
1325
    for path in paths:
 
1326
        ret[path] = find_previous_path(from_tree, to_tree, path)
 
1327
    return ret
 
1328
 
 
1329
 
 
1330
def find_previous_path(from_tree, to_tree, path, file_id=None):
 
1331
    """Find previous tree path.
 
1332
 
 
1333
    :param from_tree: From tree
 
1334
    :param to_tree: To tree
 
1335
    :param path: Path to search for
 
1336
    :return: path in to_tree, or None if there is no equivalent path.
 
1337
    """
 
1338
    if file_id is None:
 
1339
        file_id = from_tree.path2id(path)
 
1340
    if file_id is None:
 
1341
        raise errors.NoSuchFile(path)
 
1342
    try:
 
1343
        return to_tree.id2path(file_id)
 
1344
    except errors.NoSuchId:
 
1345
        return None