/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-25 00:39:16 UTC
  • mfrom: (6926 work)
  • mto: This revision was merged to the branch mainline in revision 6928.
  • Revision ID: jelmer@jelmer.uk-20180325003916-mqa5uj4uq55hrjq9
merge trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
108
108
    trees or versioned trees.
109
109
    """
110
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
 
111
119
    def has_versioned_directories(self):
112
120
        """Whether this tree can contain explicitly versioned directories.
113
121
 
205
213
        """
206
214
        raise NotImplementedError(self.id2path)
207
215
 
208
 
    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):
209
217
        """Walk the tree in 'by_dir' order.
210
218
 
211
219
        This will yield each entry in the tree as a (path, entry) tuple.
231
239
          a, f, a/b, a/d, a/b/c, a/d/e, f/g
232
240
 
233
241
        :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.
 
242
            down to specific_files that have been requested. This has no
 
243
            impact if specific_files is None.
236
244
        """
237
245
        raise NotImplementedError(self.iter_entries_by_dir)
238
246
 
309
317
        """
310
318
        raise NotImplementedError(self._comparison_data)
311
319
 
312
 
    def _file_size(self, entry, stat_value):
313
 
        raise NotImplementedError(self._file_size)
314
 
 
315
320
    def get_file(self, path, file_id=None):
316
321
        """Return a file object for the file file_id in the tree.
317
322
 
433
438
        this implementation, it is a tuple containing a single bytestring with
434
439
        the complete text of the file.
435
440
 
436
 
        :param desired_files: a list of (file_id, identifier) pairs
 
441
        :param desired_files: a list of (path, identifier) pairs
437
442
        """
438
 
        for file_id, identifier in desired_files:
 
443
        for path, identifier in desired_files:
439
444
            # We wrap the string in a tuple so that we can return an iterable
440
445
            # of bytestrings.  (Technically, a bytestring is also an iterable
441
446
            # of bytestrings, but iterating through each character is not
442
447
            # 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),)
 
448
            cur_file = (self.get_file_text(path),)
446
449
            yield identifier, cur_file
447
450
 
448
451
    def get_symlink_target(self, path, file_id=None):
476
479
        """
477
480
        raise NotImplementedError(self.annotate_iter)
478
481
 
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
482
    def _iter_parent_trees(self):
520
483
        """Iterate through parent trees, defaulting to Tree.revision_tree."""
521
484
        for revision_id in self.get_parent_ids():
524
487
            except errors.NoSuchRevisionInTree:
525
488
                yield self.repository.revision_tree(revision_id)
526
489
 
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
490
    def path2id(self, path):
567
491
        """Return the id for path in this tree."""
568
492
        raise NotImplementedError(self.path2id)
575
499
        """
576
500
        return self.path2id(path) is not None
577
501
 
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)
 
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)
604
519
 
605
520
    def lock_read(self):
606
521
        """Lock this tree for multiple read only operations.
677
592
    def supports_content_filtering(self):
678
593
        return False
679
594
 
680
 
    def _content_filter_stack(self, path=None, file_id=None):
 
595
    def _content_filter_stack(self, path=None):
681
596
        """The stack of content filters for a path if filtering is supported.
682
597
 
683
598
        Readers will be applied in first-to-last order.
686
601
 
687
602
        :param path: path relative to the root of the tree
688
603
            or None if unknown
689
 
        :param file_id: file_id or None if unknown
690
604
        :return: the list of filters - [] if there are none
691
605
        """
692
606
        filter_pref_names = filters._get_registered_names()
693
607
        if len(filter_pref_names) == 0:
694
608
            return []
695
 
        if path is None:
696
 
            path = self.id2path(file_id)
697
609
        prefs = next(self.iter_search_rules([path], filter_pref_names))
698
610
        stk = filters._get_filter_stack_for(prefs)
699
611
        if 'filters' in debug.debug_flags:
710
622
        """
711
623
        if self.supports_content_filtering():
712
624
            return lambda path, file_id: \
713
 
                    self._content_filter_stack(path, file_id)
 
625
                    self._content_filter_stack(path)
714
626
        else:
715
627
            return None
716
628
 
742
654
        return searcher
743
655
 
744
656
 
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
816
 
 
817
 
 
818
657
class InterTree(InterObject):
819
658
    """This class represents operations taking place between two Trees.
820
659
 
840
679
        # it works for all trees.
841
680
        return True
842
681
 
843
 
    def _changes_from_entries(self, source_entry, target_entry,
844
 
        source_path=None, target_path=None):
 
682
    def _changes_from_entries(self, source_entry, target_entry, source_path,
 
683
                              target_path):
845
684
        """Generate a iter_changes tuple between source_entry and target_entry.
846
685
 
847
686
        :param source_entry: An inventory entry from self.source, or None.
848
687
        :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.
 
688
        :param source_path: The path of source_entry.
 
689
        :param target_path: The path of target_entry.
853
690
        :return: A tuple, item 0 of which is an iter_changes result tuple, and
854
691
            item 1 is True if there are any changes in the result tuple.
855
692
        """
863
700
            source_versioned = True
864
701
            source_name = source_entry.name
865
702
            source_parent = source_entry.parent_id
866
 
            if source_path is None:
867
 
                source_path = self.source.id2path(file_id)
868
703
            source_kind, source_executable, source_stat = \
869
704
                self.source._comparison_data(source_entry, source_path)
870
705
        else:
877
712
            target_versioned = True
878
713
            target_name = target_entry.name
879
714
            target_parent = target_entry.parent_id
880
 
            if target_path is None:
881
 
                target_path = self.target.id2path(file_id)
882
715
            target_kind, target_executable, target_stat = \
883
716
                self.target._comparison_data(target_entry, target_path)
884
717
        else:
893
726
        if source_kind != target_kind:
894
727
            changed_content = True
895
728
        elif source_kind == 'file':
896
 
            if not self.file_content_matches(file_id, file_id, source_path,
897
 
                    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):
898
732
                changed_content = True
899
733
        elif source_kind == 'symlink':
900
734
            if (self.source.get_symlink_target(source_path, file_id) !=
939
773
        if extra_trees is not None:
940
774
            trees = trees + tuple(extra_trees)
941
775
        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
776
            return delta._compare_trees(self.source, self.target, want_unchanged,
955
777
                specific_files, include_root, extra_trees=extra_trees,
956
778
                require_versioned=require_versioned,
992
814
            output. An unversioned file is defined as one with (False, False)
993
815
            for the versioned pair.
994
816
        """
995
 
        lookup_trees = [self.source]
996
 
        if extra_trees:
997
 
             lookup_trees.extend(extra_trees)
 
817
        if not extra_trees:
 
818
             extra_trees = []
 
819
        else:
 
820
             extra_trees = list(extra_trees)
998
821
        # The ids of items we need to examine to insure delta consistency.
999
822
        precise_file_ids = set()
1000
823
        changed_file_ids = []
1001
824
        if specific_files == []:
1002
 
            specific_file_ids = []
 
825
            target_specific_files = []
 
826
            source_specific_files = []
1003
827
        else:
1004
 
            specific_file_ids = self.target.paths2ids(specific_files,
1005
 
                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)
1006
834
        if specific_files is not None:
1007
835
            # reparented or added entries must have their parents included
1008
836
            # so that valid deltas can be created. The seen_parents set
1022
850
            all_unversioned = collections.deque()
1023
851
        to_paths = {}
1024
852
        from_entries_by_dir = list(self.source.iter_entries_by_dir(
1025
 
            specific_file_ids=specific_file_ids))
 
853
            specific_files=source_specific_files))
1026
854
        from_data = dict((e.file_id, (p, e)) for p, e in from_entries_by_dir)
1027
855
        to_entries_by_dir = list(self.target.iter_entries_by_dir(
1028
 
            specific_file_ids=specific_file_ids))
 
856
            specific_files=target_specific_files))
1029
857
        num_entries = len(from_entries_by_dir) + len(to_entries_by_dir)
1030
858
        entry_count = 0
1031
859
        # the unversioned path lookup only occurs on real trees - where there
1054
882
            if pb is not None:
1055
883
                pb.update('comparing files', entry_count, num_entries)
1056
884
            if changes or include_unchanged:
1057
 
                if specific_file_ids is not None:
 
885
                if specific_files is not None:
1058
886
                    new_parent_id = result[4][1]
1059
887
                    precise_file_ids.add(new_parent_id)
1060
888
                    changed_file_ids.append(result[0])
1085
913
            if file_id in to_paths:
1086
914
                # already returned
1087
915
                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)
 
916
            to_path = find_previous_path(self.source, self.target, path)
1094
917
            entry_count += 1
1095
918
            if pb is not None:
1096
919
                pb.update('comparing files', entry_count, num_entries)
1107
930
            yield(file_id, (path, to_path), changed_content, versioned, parent,
1108
931
                  name, kind, executable)
1109
932
        changed_file_ids = set(changed_file_ids)
1110
 
        if specific_file_ids is not None:
 
933
        if specific_files is not None:
1111
934
            for result in self._handle_precise_ids(precise_file_ids,
1112
935
                changed_file_ids):
1113
936
                yield result
1114
937
 
1115
 
    def _get_entry(self, tree, file_id):
 
938
    def _get_entry(self, tree, path):
1116
939
        """Get an inventory entry from a tree, with missing entries as None.
1117
940
 
1118
941
        If the tree raises NotImplementedError on accessing .inventory, then
1122
945
        :param tree: The tree to lookup the entry in.
1123
946
        :param file_id: The file_id to lookup.
1124
947
        """
 
948
        # No inventory available.
1125
949
        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
 
950
            iterator = tree.iter_entries_by_dir(specific_files=[path])
 
951
            return iterator.next()[1]
 
952
        except StopIteration:
 
953
            return None
1139
954
 
1140
955
    def _handle_precise_ids(self, precise_file_ids, changed_file_ids,
1141
956
        discarded_changes=None):
1183
998
                # Examine file_id
1184
999
                if discarded_changes:
1185
1000
                    result = discarded_changes.get(file_id)
1186
 
                    old_entry = None
 
1001
                    source_entry = None
1187
1002
                else:
1188
1003
                    result = None
1189
1004
                if result is None:
1190
 
                    old_entry = self._get_entry(self.source, file_id)
1191
 
                    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)
1192
1019
                    result, changes = self._changes_from_entries(
1193
 
                        old_entry, new_entry)
 
1020
                        source_entry, target_entry, source_path, target_path)
1194
1021
                else:
1195
1022
                    changes = True
1196
1023
                # Get this parents parent to examine.
1201
1028
                            result[6][1] != 'directory'):
1202
1029
                        # This stopped being a directory, the old children have
1203
1030
                        # to be included.
1204
 
                        if old_entry is None:
 
1031
                        if source_entry is None:
1205
1032
                            # Reusing a discarded change.
1206
 
                            old_entry = self._get_entry(self.source, file_id)
 
1033
                            source_entry = self._get_entry(self.source, result[1][0])
1207
1034
                        precise_file_ids.update(
1208
 
                                self.source.iter_children(file_id))
 
1035
                                child.file_id
 
1036
                                for child in self.source.iter_child_entries(result[1][0]))
1209
1037
                    changed_file_ids.add(result[0])
1210
1038
                    yield result
1211
1039
 
1212
1040
    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):
 
1041
            self, source_path, target_path,
 
1042
            source_file_id=None, target_file_id=None,
 
1043
            source_stat=None, target_stat=None):
1215
1044
        """Check if two files are the same in the source and target trees.
1216
1045
 
1217
1046
        This only checks that the contents of the files are the same,
1218
1047
        it does not touch anything else.
1219
1048
 
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
1049
        :param source_path: Path of the file in the source tree
1223
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
1224
1053
        :param source_stat: Optional stat value of the file in the source tree
1225
1054
        :param target_stat: Optional stat value of the file in the target tree
1226
1055
        :return: Boolean indicating whether the files have the same contents
1227
1056
        """
1228
1057
        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
1058
            source_verifier_kind, source_verifier_data = (
1234
1059
                    self.source.get_file_verifier(
1235
1060
                        source_path, source_file_id, source_stat))
1485
1310
                    other_values.append(self._lookup_by_file_id(
1486
1311
                                            alt_extra, alt_tree, file_id))
1487
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