/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: 2019-06-03 23:48:08 UTC
  • mfrom: (7316 work)
  • mto: This revision was merged to the branch mainline in revision 7328.
  • Revision ID: jelmer@jelmer.uk-20190603234808-15yk5c7054tj8e2b
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 MissingNestedTree(errors.BzrError):
65
 
 
66
 
    _fmt = "The nested tree for %(path)s can not be resolved."""
67
 
 
68
 
    def __init__(self, path):
69
 
        self.path = path
70
 
 
71
 
 
72
64
class TreeEntry(object):
73
65
    """An entry that implements the minimum interface used by commands.
74
66
    """
75
67
 
76
 
    __slots__ = []
77
 
 
78
68
    def __eq__(self, other):
79
69
        # yes, this is ugly, TODO: best practice __eq__ style.
80
70
        return (isinstance(other, TreeEntry)
85
75
    def kind_character(self):
86
76
        return "???"
87
77
 
88
 
    def is_unmodified(self, other):
89
 
        """Does this entry reference the same entry?
90
 
 
91
 
        This is mostly the same as __eq__, but returns False
92
 
        for entries without enough information (i.e. revision is None)
93
 
        """
94
 
        return False
95
 
 
96
78
 
97
79
class TreeDirectory(TreeEntry):
98
80
    """See TreeEntry. This is a directory in a working tree."""
99
81
 
100
 
    __slots__ = []
101
 
 
102
82
    kind = 'directory'
103
83
 
104
84
    def kind_character(self):
108
88
class TreeFile(TreeEntry):
109
89
    """See TreeEntry. This is a regular file in a working tree."""
110
90
 
111
 
    __slots__ = []
112
 
 
113
91
    kind = 'file'
114
92
 
115
93
    def kind_character(self):
119
97
class TreeLink(TreeEntry):
120
98
    """See TreeEntry. This is a symlink in a working tree."""
121
99
 
122
 
    __slots__ = []
123
 
 
124
100
    kind = 'symlink'
125
101
 
126
102
    def kind_character(self):
130
106
class TreeReference(TreeEntry):
131
107
    """See TreeEntry. This is a reference to a nested tree in a working tree."""
132
108
 
133
 
    __slots__ = []
134
 
 
135
109
    kind = 'tree-reference'
136
110
 
137
111
    def kind_character(self):
138
112
        return '+'
139
113
 
140
114
 
141
 
class TreeChange(object):
142
 
    """Describes the changes between the same item in two different trees."""
143
 
 
144
 
    __slots__ = ['file_id', 'path', 'changed_content', 'versioned', 'parent_id',
145
 
                 'name', 'kind', 'executable', 'copied']
146
 
 
147
 
    def __init__(self, file_id, path, changed_content, versioned, parent_id,
148
 
                 name, kind, executable, copied=False):
149
 
        self.file_id = file_id
150
 
        self.path = path
151
 
        self.changed_content = changed_content
152
 
        self.versioned = versioned
153
 
        self.parent_id = parent_id
154
 
        self.name = name
155
 
        self.kind = kind
156
 
        self.executable = executable
157
 
        self.copied = copied
158
 
 
159
 
    def __repr__(self):
160
 
        return "%s%r" % (self.__class__.__name__, self._as_tuple())
161
 
 
162
 
    def __len__(self):
163
 
        return len(self.__slots__)
164
 
 
165
 
    def _as_tuple(self):
166
 
        return (self.file_id, self.path, self.changed_content, self.versioned,
167
 
                self.parent_id, self.name, self.kind, self.executable, self.copied)
168
 
 
169
 
    def __eq__(self, other):
170
 
        if isinstance(other, TreeChange):
171
 
            return self._as_tuple() == other._as_tuple()
172
 
        if isinstance(other, tuple):
173
 
            return self._as_tuple() == other
174
 
        return False
175
 
 
176
 
    def __lt__(self, other):
177
 
        return self._as_tuple() < other._as_tuple()
178
 
 
179
 
    def meta_modified(self):
180
 
        if self.versioned == (True, True):
181
 
            return (self.executable[0] != self.executable[1])
182
 
        return False
183
 
 
184
 
    def is_reparented(self):
185
 
        return self.parent_id[0] != self.parent_id[1]
186
 
 
187
 
    def discard_new(self):
188
 
        return self.__class__(
189
 
            self.file_id, (self.path[0], None), self.changed_content,
190
 
            (self.versioned[0], None), (self.parent_id[0], None),
191
 
            (self.name[0], None), (self.kind[0], None),
192
 
            (self.executable[0], None),
193
 
            copied=False)
194
 
 
195
 
 
196
115
class Tree(object):
197
116
    """Abstract file tree.
198
117
 
222
141
        """
223
142
        return True
224
143
 
225
 
    def supports_symlinks(self):
226
 
        """Does this tree support symbolic links?
227
 
        """
228
 
        return osutils.has_symlinks()
229
 
 
230
144
    def changes_from(self, other, want_unchanged=False, specific_files=None,
231
145
                     extra_trees=None, require_versioned=False, include_root=False,
232
146
                     want_unversioned=False):
267
181
        """See InterTree.iter_changes"""
268
182
        intertree = InterTree.get(from_tree, self)
269
183
        return intertree.iter_changes(include_unchanged, specific_files, pb,
270
 
                                      extra_trees, require_versioned,
271
 
                                      want_unversioned=want_unversioned)
 
184
                                      extra_trees, require_versioned, want_unversioned=want_unversioned)
272
185
 
273
186
    def conflicts(self):
274
187
        """Get a list of the conflicts in the tree.
310
223
        """Iterate through all paths, including paths for missing files."""
311
224
        raise NotImplementedError(self.all_versioned_paths)
312
225
 
313
 
    def id2path(self, file_id, recurse='down'):
 
226
    def id2path(self, file_id):
314
227
        """Return the path for a file id.
315
228
 
316
229
        :raises NoSuchId:
317
230
        """
318
231
        raise NotImplementedError(self.id2path)
319
232
 
320
 
    def iter_entries_by_dir(self, specific_files=None, recurse_nested=False):
 
233
    def iter_entries_by_dir(self, specific_files=None):
321
234
        """Walk the tree in 'by_dir' order.
322
235
 
323
236
        This will yield each entry in the tree as a (path, entry) tuple.
341
254
        The yield order (ignoring root) would be::
342
255
 
343
256
          a, f, a/b, a/d, a/b/c, a/d/e, f/g
344
 
 
345
 
        If recurse_nested is enabled then nested trees are included as if
346
 
        they were a part of the tree. If is disabled then TreeReference
347
 
        objects (without any children) are yielded.
348
257
        """
349
258
        raise NotImplementedError(self.iter_entries_by_dir)
350
259
 
357
266
        """
358
267
        raise NotImplementedError(self.iter_child_entries)
359
268
 
360
 
    def list_files(self, include_root=False, from_dir=None, recursive=True,
361
 
                   recurse_nested=False):
 
269
    def list_files(self, include_root=False, from_dir=None, recursive=True):
362
270
        """List all files in this tree.
363
271
 
364
272
        :param include_root: Whether to include the entry for the tree root
365
273
        :param from_dir: Directory under which to list files
366
274
        :param recursive: Whether to list files recursively
367
 
        :param recurse_nested: enter nested trees
368
275
        :return: iterator over tuples of
369
276
            (path, versioned, kind, inventory entry)
370
277
        """
374
281
        if self.supports_tree_reference():
375
282
            for path, entry in self.iter_entries_by_dir():
376
283
                if entry.kind == 'tree-reference':
377
 
                    yield path
378
 
 
379
 
    def get_containing_nested_tree(self, path):
380
 
        """Find the nested tree that contains a path.
381
 
 
382
 
        :return: tuple with (nested tree and path inside the nested tree)
383
 
        """
384
 
        for nested_path in self.iter_references():
385
 
            nested_path += '/'
386
 
            if path.startswith(nested_path):
387
 
                nested_tree = self.get_nested_tree(nested_path)
388
 
                return nested_tree, path[len(nested_path):]
389
 
        else:
390
 
            return None, None
391
 
 
392
 
    def get_nested_tree(self, path):
393
 
        """Open the nested tree at the specified path.
394
 
 
395
 
        :param path: Path from which to resolve tree reference.
396
 
        :return: A Tree object for the nested tree
397
 
        :raise MissingNestedTree: If the nested tree can not be resolved
398
 
        """
399
 
        raise NotImplementedError(self.get_nested_tree)
 
284
                    yield path, entry.file_id
400
285
 
401
286
    def kind(self, path):
402
287
        raise NotImplementedError("Tree subclass %s must implement kind"
428
313
        """
429
314
        raise NotImplementedError(self.path_content_summary)
430
315
 
431
 
    def get_reference_revision(self, path, branch=None):
 
316
    def get_reference_revision(self, path):
432
317
        raise NotImplementedError("Tree subclass %s must implement "
433
318
                                  "get_reference_revision"
434
319
                                  % self.__class__.__name__)
561
446
        """
562
447
        raise NotImplementedError(self.get_symlink_target)
563
448
 
 
449
    def get_root_id(self):
 
450
        """Return the file_id for the root of this tree."""
 
451
        raise NotImplementedError(self.get_root_id)
 
452
 
564
453
    def annotate_iter(self, path,
565
454
                      default_revision=_mod_revision.CURRENT_REVISION):
566
455
        """Return an iterator of revision_id, line tuples.
575
464
        """
576
465
        raise NotImplementedError(self.annotate_iter)
577
466
 
 
467
    def _iter_parent_trees(self):
 
468
        """Iterate through parent trees, defaulting to Tree.revision_tree."""
 
469
        for revision_id in self.get_parent_ids():
 
470
            try:
 
471
                yield self.revision_tree(revision_id)
 
472
            except errors.NoSuchRevisionInTree:
 
473
                yield self.repository.revision_tree(revision_id)
 
474
 
578
475
    def path2id(self, path):
579
476
        """Return the id for path in this tree."""
580
477
        raise NotImplementedError(self.path2id)
713
610
        :return: None if content filtering is not supported by this tree.
714
611
        """
715
612
        if self.supports_content_filtering():
716
 
            return self._content_filter_stack
 
613
            return lambda path, file_id: \
 
614
                self._content_filter_stack(path)
717
615
        else:
718
616
            return None
719
617
 
858
756
            changes = True
859
757
        else:
860
758
            changes = False
861
 
        return TreeChange(
862
 
            file_id, (source_path, target_path), changed_content,
863
 
            versioned, parent, name, kind, executable), changes
 
759
        return (file_id, (source_path, target_path), changed_content,
 
760
                versioned, parent, name, kind, executable), changes
864
761
 
865
762
    def compare(self, want_unchanged=False, specific_files=None,
866
763
                extra_trees=None, require_versioned=False, include_root=False,
963
860
        to_paths = {}
964
861
        from_entries_by_dir = list(self.source.iter_entries_by_dir(
965
862
            specific_files=source_specific_files))
966
 
        from_data = dict(from_entries_by_dir)
 
863
        from_data = dict((e.file_id, (p, e)) for p, e in from_entries_by_dir)
967
864
        to_entries_by_dir = list(self.target.iter_entries_by_dir(
968
865
            specific_files=target_specific_files))
969
 
        path_equivs = self.find_source_paths([p for p, e in to_entries_by_dir])
970
866
        num_entries = len(from_entries_by_dir) + len(to_entries_by_dir)
971
867
        entry_count = 0
972
868
        # the unversioned path lookup only occurs on real trees - where there
980
876
                target_kind, target_executable, target_stat = \
981
877
                    self.target._comparison_data(
982
878
                        fake_entry, unversioned_path[1])
983
 
                yield TreeChange(
984
 
                    None, (None, unversioned_path[1]), True, (False, False),
985
 
                    (None, None),
986
 
                    (None, unversioned_path[0][-1]),
987
 
                    (None, target_kind),
988
 
                    (None, target_executable))
989
 
            source_path = path_equivs[target_path]
990
 
            if source_path is not None:
991
 
                source_entry = from_data.get(source_path)
992
 
            else:
993
 
                source_entry = None
994
 
            result, changes = self._changes_from_entries(
995
 
                source_entry, target_entry, source_path=source_path, target_path=target_path)
996
 
            to_paths[result.file_id] = result.path[1]
 
879
                yield (None, (None, unversioned_path[1]), True, (False, False),
 
880
                       (None, None),
 
881
                       (None, unversioned_path[0][-1]),
 
882
                       (None, target_kind),
 
883
                       (None, target_executable))
 
884
            source_path, source_entry = from_data.get(target_entry.file_id,
 
885
                                                      (None, None))
 
886
            result, changes = self._changes_from_entries(source_entry,
 
887
                                                         target_entry, source_path=source_path, target_path=target_path)
 
888
            to_paths[result[0]] = result[1][1]
997
889
            entry_count += 1
998
 
            if result.versioned[0]:
 
890
            if result[3][0]:
999
891
                entry_count += 1
1000
892
            if pb is not None:
1001
893
                pb.update('comparing files', entry_count, num_entries)
1002
894
            if changes or include_unchanged:
1003
895
                if specific_files is not None:
1004
 
                    precise_file_ids.add(result.parent_id[1])
1005
 
                    changed_file_ids.append(result.file_id)
 
896
                    new_parent_id = result[4][1]
 
897
                    precise_file_ids.add(new_parent_id)
 
898
                    changed_file_ids.append(result[0])
1006
899
                yield result
1007
900
            # Ensure correct behaviour for reparented/added specific files.
1008
901
            if specific_files is not None:
1009
902
                # Record output dirs
1010
 
                if result.kind[1] == 'directory':
1011
 
                    seen_dirs.add(result.file_id)
 
903
                if result[6][1] == 'directory':
 
904
                    seen_dirs.add(result[0])
1012
905
                # Record parents of reparented/added entries.
1013
 
                if not result.versioned[0] or result.is_reparented():
1014
 
                    seen_parents.add(result.parent_id[1])
 
906
                versioned = result[3]
 
907
                parents = result[4]
 
908
                if not versioned[0] or parents[0] != parents[1]:
 
909
                    seen_parents.add(parents[1])
1015
910
        while all_unversioned:
1016
911
            # yield any trailing unversioned paths
1017
912
            unversioned_path = all_unversioned.popleft()
1018
913
            to_kind, to_executable, to_stat = \
1019
914
                self.target._comparison_data(fake_entry, unversioned_path[1])
1020
 
            yield TreeChange(
1021
 
                None, (None, unversioned_path[1]), True, (False, False),
1022
 
                (None, None),
1023
 
                (None, unversioned_path[0][-1]),
1024
 
                (None, to_kind),
1025
 
                (None, to_executable))
 
915
            yield (None, (None, unversioned_path[1]), True, (False, False),
 
916
                   (None, None),
 
917
                   (None, unversioned_path[0][-1]),
 
918
                   (None, to_kind),
 
919
                   (None, to_executable))
1026
920
        # Yield all remaining source paths
1027
921
        for path, from_entry in from_entries_by_dir:
1028
922
            file_id = from_entry.file_id
1029
923
            if file_id in to_paths:
1030
924
                # already returned
1031
925
                continue
1032
 
            to_path = self.find_target_path(path)
 
926
            to_path = find_previous_path(self.source, self.target, path)
1033
927
            entry_count += 1
1034
928
            if pb is not None:
1035
929
                pb.update('comparing files', entry_count, num_entries)
1043
937
            changed_content = from_kind is not None
1044
938
            # the parent's path is necessarily known at this point.
1045
939
            changed_file_ids.append(file_id)
1046
 
            yield TreeChange(
1047
 
                file_id, (path, to_path), changed_content, versioned, parent,
1048
 
                name, kind, executable)
 
940
            yield(file_id, (path, to_path), changed_content, versioned, parent,
 
941
                  name, kind, executable)
1049
942
        changed_file_ids = set(changed_file_ids)
1050
943
        if specific_files is not None:
1051
944
            for result in self._handle_precise_ids(precise_file_ids,
1141
1034
                else:
1142
1035
                    changes = True
1143
1036
                # Get this parents parent to examine.
1144
 
                new_parent_id = result.parent_id[1]
 
1037
                new_parent_id = result[4][1]
1145
1038
                precise_file_ids.add(new_parent_id)
1146
1039
                if changes:
1147
 
                    if (result.kind[0] == 'directory' and
1148
 
                            result.kind[1] != 'directory'):
 
1040
                    if (result[6][0] == 'directory' and
 
1041
                            result[6][1] != 'directory'):
1149
1042
                        # This stopped being a directory, the old children have
1150
1043
                        # to be included.
1151
1044
                        if source_entry is None:
1152
1045
                            # Reusing a discarded change.
1153
1046
                            source_entry = self._get_entry(
1154
 
                                self.source, result.path[0])
 
1047
                                self.source, result[1][0])
1155
1048
                        precise_file_ids.update(
1156
1049
                            child.file_id
1157
 
                            for child in self.source.iter_child_entries(result.path[0]))
1158
 
                    changed_file_ids.add(result.file_id)
 
1050
                            for child in self.source.iter_child_entries(result[1][0]))
 
1051
                    changed_file_ids.add(result[0])
1159
1052
                    yield result
1160
1053
 
1161
1054
    def file_content_matches(
1168
1061
 
1169
1062
        :param source_path: Path of the file in the source tree
1170
1063
        :param target_path: Path of the file in the target tree
 
1064
        :param source_file_id: Optional file id of the file in the source tree
 
1065
        :param target_file_id: Optional file id of the file in the target tree
1171
1066
        :param source_stat: Optional stat value of the file in the source tree
1172
1067
        :param target_stat: Optional stat value of the file in the target tree
1173
1068
        :return: Boolean indicating whether the files have the same contents
1183
1078
            # Fall back to SHA1 for now
1184
1079
            if source_verifier_kind != "SHA1":
1185
1080
                source_sha1 = self.source.get_file_sha1(
1186
 
                    source_path, source_stat)
 
1081
                    source_path, source_file_id, source_stat)
1187
1082
            else:
1188
1083
                source_sha1 = source_verifier_data
1189
1084
            if target_verifier_kind != "SHA1":
1190
1085
                target_sha1 = self.target.get_file_sha1(
1191
 
                    target_path, target_stat)
 
1086
                    target_path, target_file_id, target_stat)
1192
1087
            else:
1193
1088
                target_sha1 = target_verifier_data
1194
1089
            return (source_sha1 == target_sha1)
1195
1090
 
1196
 
    def find_target_path(self, path, recurse='none'):
1197
 
        """Find target tree path.
1198
 
 
1199
 
        :param path: Path to search for (exists in source)
1200
 
        :return: path in target, or None if there is no equivalent path.
1201
 
        :raise NoSuchFile: If the path doesn't exist in source
1202
 
        """
1203
 
        file_id = self.source.path2id(path)
1204
 
        if file_id is None:
1205
 
            raise errors.NoSuchFile(path)
1206
 
        try:
1207
 
            return self.target.id2path(file_id, recurse=recurse)
1208
 
        except errors.NoSuchId:
1209
 
            return None
1210
 
 
1211
 
    def find_source_path(self, path, recurse='none'):
1212
 
        """Find the source tree path.
1213
 
 
1214
 
        :param path: Path to search for (exists in target)
1215
 
        :return: path in source, or None if there is no equivalent path.
1216
 
        :raise NoSuchFile: if the path doesn't exist in target
1217
 
        """
1218
 
        file_id = self.target.path2id(path)
1219
 
        if file_id is None:
1220
 
            raise errors.NoSuchFile(path)
1221
 
        try:
1222
 
            return self.source.id2path(file_id, recurse=recurse)
1223
 
        except errors.NoSuchId:
1224
 
            return None
1225
 
 
1226
 
    def find_target_paths(self, paths, recurse='none'):
1227
 
        """Find target tree paths.
1228
 
 
1229
 
        :param paths: Iterable over paths in target to search for
1230
 
        :return: Dictionary mapping from source paths to paths in target , or
1231
 
            None if there is no equivalent path.
1232
 
        """
1233
 
        ret = {}
1234
 
        for path in paths:
1235
 
            ret[path] = self.find_target_path(path, recurse=recurse)
1236
 
        return ret
1237
 
 
1238
 
    def find_source_paths(self, paths, recurse='none'):
1239
 
        """Find source tree paths.
1240
 
 
1241
 
        :param paths: Iterable over paths in target to search for
1242
 
        :return: Dictionary mapping from target paths to paths in source, or
1243
 
            None if there is no equivalent path.
1244
 
        """
1245
 
        ret = {}
1246
 
        for path in paths:
1247
 
            ret[path] = self.find_source_path(path, recurse=recurse)
1248
 
        return ret
1249
 
 
1250
1091
 
1251
1092
InterTree.register_optimiser(InterTree)
1252
1093
 
1253
1094
 
1254
 
def find_previous_paths(from_tree, to_tree, paths, recurse='none'):
 
1095
def find_previous_paths(from_tree, to_tree, paths):
1255
1096
    """Find previous tree paths.
1256
1097
 
1257
1098
    :param from_tree: From tree
1258
1099
    :param to_tree: To tree
1259
 
    :param paths: Iterable over paths in from_tree to search for
 
1100
    :param paths: Iterable over paths to search for
1260
1101
    :return: Dictionary mapping from from_tree paths to paths in to_tree, or
1261
1102
        None if there is no equivalent path.
1262
1103
    """
1263
 
    return InterTree.get(to_tree, from_tree).find_source_paths(paths, recurse=recurse)
1264
 
 
1265
 
 
1266
 
def find_previous_path(from_tree, to_tree, path, recurse='none'):
 
1104
    ret = {}
 
1105
    for path in paths:
 
1106
        ret[path] = find_previous_path(from_tree, to_tree, path)
 
1107
    return ret
 
1108
 
 
1109
 
 
1110
def find_previous_path(from_tree, to_tree, path, file_id=None):
1267
1111
    """Find previous tree path.
1268
1112
 
1269
1113
    :param from_tree: From tree
1270
1114
    :param to_tree: To tree
1271
 
    :param path: Path to search for (exists in from_tree)
 
1115
    :param path: Path to search for
1272
1116
    :return: path in to_tree, or None if there is no equivalent path.
1273
 
    :raise NoSuchFile: If the path doesn't exist in from_tree
1274
1117
    """
1275
 
    return InterTree.get(to_tree, from_tree).find_source_path(
1276
 
        path, recurse=recurse)
 
1118
    if file_id is None:
 
1119
        file_id = from_tree.path2id(path)
 
1120
    if file_id is None:
 
1121
        raise errors.NoSuchFile(path)
 
1122
    try:
 
1123
        return to_tree.id2path(file_id)
 
1124
    except errors.NoSuchId:
 
1125
        return None
1277
1126
 
1278
1127
 
1279
1128
def get_canonical_path(tree, path, normalize):