310
439
raise NotImplementedError(self._comparison_data)
312
def _file_size(self, entry, stat_value):
313
raise NotImplementedError(self._file_size)
315
def get_file(self, path, file_id=None):
316
"""Return a file object for the file file_id in the tree.
318
If both file_id and path are defined, it is implementation defined as
319
to which one is used.
441
def get_file(self, path):
442
"""Return a file object for the file path in the tree.
321
444
raise NotImplementedError(self.get_file)
323
def get_file_with_stat(self, path, file_id=None):
324
"""Get a file handle and stat object for file_id.
446
def get_file_with_stat(self, path):
447
"""Get a file handle and stat object for path.
326
449
The default implementation returns (self.get_file, None) for backwards
329
452
:param path: The path of the file.
330
:param file_id: The file id to read, if it is known.
331
453
:return: A tuple (file_handle, stat_value_or_None). If the tree has
332
454
no stat facility, or need for a stat cache feedback during commit,
333
455
it may return None for the second element of the tuple.
335
return (self.get_file(path, file_id), None)
457
return (self.get_file(path), None)
337
def get_file_text(self, path, file_id=None):
459
def get_file_text(self, path):
338
460
"""Return the byte content of a file.
340
462
:param path: The path of the file.
341
:param file_id: The file_id of the file.
343
If both file_id and path are supplied, an implementation may use
346
464
:returns: A single byte string for the whole file.
348
my_file = self.get_file(path, file_id)
466
with self.get_file(path) as my_file:
350
467
return my_file.read()
354
def get_file_lines(self, path, file_id=None):
469
def get_file_lines(self, path):
355
470
"""Return the content of a file, as lines.
357
472
:param path: The path of the file.
358
:param file_id: The file_id of the file.
360
If both file_id and path are supplied, an implementation may use
363
return osutils.split_lines(self.get_file_text(path, file_id))
474
return osutils.split_lines(self.get_file_text(path))
365
def get_file_verifier(self, path, file_id=None, stat_value=None):
476
def get_file_verifier(self, path, stat_value=None):
366
477
"""Return a verifier for a file.
368
479
The default implementation returns a sha1.
370
:param file_id: The handle for this file.
371
481
:param path: The path that this file can be found at.
372
482
These must point to the same object.
373
483
:param stat_value: Optional stat value for the object
374
484
:return: Tuple with verifier name and verifier data
376
return ("SHA1", self.get_file_sha1(path, file_id,
377
stat_value=stat_value))
486
return ("SHA1", self.get_file_sha1(path, stat_value=stat_value))
379
def get_file_sha1(self, path, file_id=None, stat_value=None):
488
def get_file_sha1(self, path, stat_value=None):
380
489
"""Return the SHA1 file for a file.
382
491
:note: callers should use get_file_verifier instead
433
535
this implementation, it is a tuple containing a single bytestring with
434
536
the complete text of the file.
436
:param desired_files: a list of (file_id, identifier) pairs
538
:param desired_files: a list of (path, identifier) pairs
438
for file_id, identifier in desired_files:
540
for path, identifier in desired_files:
439
541
# We wrap the string in a tuple so that we can return an iterable
440
542
# of bytestrings. (Technically, a bytestring is also an iterable
441
543
# of bytestrings, but iterating through each character is not
443
# TODO(jelmer): Pass paths into iter_files_bytes
444
path = self.id2path(file_id)
445
cur_file = (self.get_file_text(path, file_id),)
545
cur_file = (self.get_file_text(path),)
446
546
yield identifier, cur_file
448
def get_symlink_target(self, path, file_id=None):
449
"""Get the target for a given file_id.
548
def get_symlink_target(self, path):
549
"""Get the target for a given path.
451
It is assumed that the caller already knows that file_id is referencing
551
It is assumed that the caller already knows that path is referencing
453
:param file_id: Handle for the symlink entry.
454
553
:param path: The path of the file.
455
If both file_id and path are supplied, an implementation may use
457
554
:return: The path the symlink points to.
459
556
raise NotImplementedError(self.get_symlink_target)
461
def get_root_id(self):
462
"""Return the file_id for the root of this tree."""
463
raise NotImplementedError(self.get_root_id)
465
def annotate_iter(self, path, file_id=None,
558
def annotate_iter(self, path,
466
559
default_revision=_mod_revision.CURRENT_REVISION):
467
560
"""Return an iterator of revision_id, line tuples.
469
562
For working trees (and mutable trees in general), the special
470
563
revision_id 'current:' will be used for lines that are new in this
471
564
tree, e.g. uncommitted changes.
472
:param file_id: The file to produce an annotated version from
565
:param path: The file to produce an annotated version from
473
566
:param default_revision: For lines that don't match a basis, mark them
474
567
with this revision id. Not all implementations will make use of
477
570
raise NotImplementedError(self.annotate_iter)
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:')
487
last_revision_base = None
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
493
def plan_file_merge(self, file_id, other, base=None):
494
"""Generate a merge plan based on annotations.
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.
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,
506
def plan_file_lca_merge(self, file_id, other, base=None):
507
"""Generate a merge plan based lca-newness.
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.
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,
519
def _iter_parent_trees(self):
520
"""Iterate through parent trees, defaulting to Tree.revision_tree."""
521
for revision_id in self.get_parent_ids():
523
yield self.revision_tree(revision_id)
524
except errors.NoSuchRevisionInTree:
525
yield self.repository.revision_tree(revision_id)
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
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)
544
def _check_retrieved(self, ie, f):
547
fp = osutils.fingerprint_file(f)
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"])
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"])
566
572
def path2id(self, path):
567
573
"""Return the id for path in this tree."""
568
574
raise NotImplementedError(self.path2id)
741
738
searcher = default_searcher
745
def find_ids_across_trees(filenames, trees, require_versioned=True):
746
"""Find the ids corresponding to specified filenames.
748
All matches in all trees will be used, and all children of matched
749
directories will be used.
751
:param filenames: The filenames to find file_ids for (if None, returns
753
:param trees: The trees to find file_ids within
754
:param require_versioned: if true, all specified filenames must occur in
756
:return: a set of file ids for the specified filenames and their children.
760
specified_path_ids = _find_ids_across_trees(filenames, trees,
762
return _find_children_across_trees(specified_path_ids, trees)
765
def _find_ids_across_trees(filenames, trees, require_versioned):
766
"""Find the ids corresponding to specified filenames.
768
All matches in all trees will be used, but subdirectories are not scanned.
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
774
:return: a set of file ids for the specified filenames
777
interesting_ids = set()
778
for tree_path in filenames:
781
file_id = tree.path2id(tree_path)
782
if file_id is not None:
783
interesting_ids.add(file_id)
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
792
def _find_children_across_trees(specified_ids, trees):
793
"""Return a set including specified ids and their children.
795
All matches in all trees will be used.
797
:param trees: The trees to find file_ids within
798
:return: a set containing all specified ids and their children
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:
806
for file_id in pending:
808
if not tree.has_or_had_id(file_id):
810
for child_id in tree.iter_children(file_id):
811
if child_id not in interesting_ids:
812
new_pending.add(child_id)
813
interesting_ids.update(new_pending)
814
pending = new_pending
815
return interesting_ids
741
def archive(self, format, name, root='', subdir=None,
743
"""Create an archive of this tree.
745
:param format: Format name (e.g. 'tar')
746
:param name: target file name
747
:param root: Root directory name (or None)
748
:param subdir: Subdirectory to export (or None)
749
:return: Iterator over archive chunks
751
from .archive import create_archive
752
with self.lock_read():
753
return create_archive(format, self, name, root,
754
subdir, force_mtime=force_mtime)
757
def versionable_kind(cls, kind):
758
"""Check if this tree support versioning a specific file kind."""
759
return (kind in ('file', 'directory', 'symlink', 'tree-reference'))
818
762
class InterTree(InterObject):
1014
948
seen_dirs = set()
1015
949
if want_unversioned:
1016
950
all_unversioned = sorted([(p.split('/'), p) for p in
1017
self.target.extras()
1018
if specific_files is None or
1019
osutils.is_inside_any(specific_files, p)])
1020
all_unversioned = collections.deque(all_unversioned)
952
if specific_files is None or
953
osutils.is_inside_any(specific_files, p)])
954
all_unversioned = deque(all_unversioned)
1022
all_unversioned = collections.deque()
956
all_unversioned = deque()
1024
958
from_entries_by_dir = list(self.source.iter_entries_by_dir(
1025
specific_file_ids=specific_file_ids))
1026
from_data = dict((e.file_id, (p, e)) for p, e in from_entries_by_dir)
959
specific_files=source_specific_files))
960
from_data = dict(from_entries_by_dir)
1027
961
to_entries_by_dir = list(self.target.iter_entries_by_dir(
1028
specific_file_ids=specific_file_ids))
962
specific_files=target_specific_files))
963
path_equivs = self.find_source_paths([p for p, e in to_entries_by_dir])
1029
964
num_entries = len(from_entries_by_dir) + len(to_entries_by_dir)
1031
966
# the unversioned path lookup only occurs on real trees - where there
1032
967
# can be extras. So the fake_entry is solely used to look up
1033
968
# executable it values when execute is not supported.
1034
fake_entry = inventory.InventoryFile('unused', 'unused', 'unused')
969
fake_entry = TreeFile()
1035
970
for target_path, target_entry in to_entries_by_dir:
1036
971
while (all_unversioned and
1037
all_unversioned[0][0] < target_path.split('/')):
972
all_unversioned[0][0] < target_path.split('/')):
1038
973
unversioned_path = all_unversioned.popleft()
1039
974
target_kind, target_executable, target_stat = \
1040
self.target._comparison_data(fake_entry, unversioned_path[1])
1041
yield (None, (None, unversioned_path[1]), True, (False, False),
975
self.target._comparison_data(
976
fake_entry, unversioned_path[1])
978
None, (None, unversioned_path[1]), True, (False, False),
1043
980
(None, unversioned_path[0][-1]),
1044
981
(None, target_kind),
1045
982
(None, target_executable))
1046
source_path, source_entry = from_data.get(target_entry.file_id,
1048
result, changes = self._changes_from_entries(source_entry,
1049
target_entry, source_path=source_path, target_path=target_path)
1050
to_paths[result[0]] = result[1][1]
983
source_path = path_equivs[target_path]
984
if source_path is not None:
985
source_entry = from_data.get(source_path)
988
result, changes = self._changes_from_entries(
989
source_entry, target_entry, source_path=source_path, target_path=target_path)
990
to_paths[result.file_id] = result.path[1]
1051
991
entry_count += 1
992
if result.versioned[0]:
1053
993
entry_count += 1
1054
994
if pb is not None:
1055
995
pb.update('comparing files', entry_count, num_entries)
1056
996
if changes or include_unchanged:
1057
if specific_file_ids is not None:
1058
new_parent_id = result[4][1]
1059
precise_file_ids.add(new_parent_id)
1060
changed_file_ids.append(result[0])
997
if specific_files is not None:
998
precise_file_ids.add(result.parent_id[1])
999
changed_file_ids.append(result.file_id)
1062
1001
# Ensure correct behaviour for reparented/added specific files.
1063
1002
if specific_files is not None:
1064
1003
# Record output dirs
1065
if result[6][1] == 'directory':
1066
seen_dirs.add(result[0])
1004
if result.kind[1] == 'directory':
1005
seen_dirs.add(result.file_id)
1067
1006
# Record parents of reparented/added entries.
1068
versioned = result[3]
1070
if not versioned[0] or parents[0] != parents[1]:
1071
seen_parents.add(parents[1])
1007
if not result.versioned[0] or result.is_reparented():
1008
seen_parents.add(result.parent_id[1])
1072
1009
while all_unversioned:
1073
1010
# yield any trailing unversioned paths
1074
1011
unversioned_path = all_unversioned.popleft()
1075
1012
to_kind, to_executable, to_stat = \
1076
1013
self.target._comparison_data(fake_entry, unversioned_path[1])
1077
yield (None, (None, unversioned_path[1]), True, (False, False),
1015
None, (None, unversioned_path[1]), True, (False, False),
1079
1017
(None, unversioned_path[0][-1]),
1080
1018
(None, to_kind),
1226
1167
:return: Boolean indicating whether the files have the same contents
1228
1169
with self.lock_read():
1229
if source_path is None:
1230
source_path = self.source.id2path(source_file_id)
1231
if target_path is None:
1232
target_path = self.target.id2path(target_file_id)
1233
1170
source_verifier_kind, source_verifier_data = (
1234
self.source.get_file_verifier(
1235
source_path, source_file_id, source_stat))
1171
self.source.get_file_verifier(source_path, source_stat))
1236
1172
target_verifier_kind, target_verifier_data = (
1237
1173
self.target.get_file_verifier(
1238
target_path, target_file_id, target_stat))
1174
target_path, target_stat))
1239
1175
if source_verifier_kind == target_verifier_kind:
1240
1176
return (source_verifier_data == target_verifier_data)
1241
1177
# Fall back to SHA1 for now
1242
1178
if source_verifier_kind != "SHA1":
1243
1179
source_sha1 = self.source.get_file_sha1(
1244
source_path, source_file_id, source_stat)
1180
source_path, source_stat)
1246
1182
source_sha1 = source_verifier_data
1247
1183
if target_verifier_kind != "SHA1":
1248
1184
target_sha1 = self.target.get_file_sha1(
1249
target_path, target_file_id, target_stat)
1185
target_path, target_stat)
1251
1187
target_sha1 = target_verifier_data
1252
1188
return (source_sha1 == target_sha1)
1190
def find_target_path(self, path, recurse='none'):
1191
"""Find target tree path.
1193
:param path: Path to search for (exists in source)
1194
:return: path in target, or None if there is no equivalent path.
1195
:raise NoSuchFile: If the path doesn't exist in source
1197
file_id = self.source.path2id(path)
1199
raise errors.NoSuchFile(path)
1201
return self.target.id2path(file_id, recurse=recurse)
1202
except errors.NoSuchId:
1205
def find_source_path(self, path, recurse='none'):
1206
"""Find the source tree path.
1208
:param path: Path to search for (exists in target)
1209
:return: path in source, or None if there is no equivalent path.
1210
:raise NoSuchFile: if the path doesn't exist in target
1212
file_id = self.target.path2id(path)
1214
raise errors.NoSuchFile(path)
1216
return self.source.id2path(file_id, recurse=recurse)
1217
except errors.NoSuchId:
1220
def find_target_paths(self, paths, recurse='none'):
1221
"""Find target tree paths.
1223
:param paths: Iterable over paths in target to search for
1224
:return: Dictionary mapping from source paths to paths in target , or
1225
None if there is no equivalent path.
1229
ret[path] = self.find_target_path(path, recurse=recurse)
1232
def find_source_paths(self, paths, recurse='none'):
1233
"""Find source tree paths.
1235
:param paths: Iterable over paths in target to search for
1236
:return: Dictionary mapping from target paths to paths in source, or
1237
None if there is no equivalent path.
1241
ret[path] = self.find_source_path(path, recurse=recurse)
1254
1245
InterTree.register_optimiser(InterTree)
1257
class MultiWalker(object):
1258
"""Walk multiple trees simultaneously, getting combined results."""
1260
# Note: This could be written to not assume you can do out-of-order
1261
# lookups. Instead any nodes that don't match in all trees could be
1262
# marked as 'deferred', and then returned in the final cleanup loop.
1263
# For now, I think it is "nicer" to return things as close to the
1264
# "master_tree" order as we can.
1266
def __init__(self, master_tree, other_trees):
1267
"""Create a new MultiWalker.
1269
All trees being walked must implement "iter_entries_by_dir()", such
1270
that they yield (path, object) tuples, where that object will have a
1271
'.file_id' member, that can be used to check equality.
1273
:param master_tree: All trees will be 'slaved' to the master_tree such
1274
that nodes in master_tree will be used as 'first-pass' sync points.
1275
Any nodes that aren't in master_tree will be merged in a second
1277
:param other_trees: A list of other trees to walk simultaneously.
1279
self._master_tree = master_tree
1280
self._other_trees = other_trees
1282
# Keep track of any nodes that were properly processed just out of
1283
# order, that way we don't return them at the end, we don't have to
1284
# track *all* processed file_ids, just the out-of-order ones
1285
self._out_of_order_processed = set()
1288
def _step_one(iterator):
1289
"""Step an iter_entries_by_dir iterator.
1291
:return: (has_more, path, ie)
1292
If has_more is False, path and ie will be None.
1295
path, ie = next(iterator)
1296
except StopIteration:
1297
return False, None, None
1299
return True, path, ie
1302
def _cmp_path_by_dirblock(path1, path2):
1303
"""Compare two paths based on what directory they are in.
1305
This generates a sort order, such that all children of a directory are
1306
sorted together, and grandchildren are in the same order as the
1307
children appear. But all grandchildren come after all children.
1309
:param path1: first path
1310
:param path2: the second path
1311
:return: negative number if ``path1`` comes first,
1312
0 if paths are equal
1313
and a positive number if ``path2`` sorts first
1315
# Shortcut this special case
1318
# This is stolen from _dirstate_helpers_py.py, only switching it to
1319
# Unicode objects. Consider using encode_utf8() and then using the
1320
# optimized versions, or maybe writing optimized unicode versions.
1321
if not isinstance(path1, unicode):
1322
raise TypeError("'path1' must be a unicode string, not %s: %r"
1323
% (type(path1), path1))
1324
if not isinstance(path2, unicode):
1325
raise TypeError("'path2' must be a unicode string, not %s: %r"
1326
% (type(path2), path2))
1327
return cmp(MultiWalker._path_to_key(path1),
1328
MultiWalker._path_to_key(path2))
1331
def _path_to_key(path):
1332
dirname, basename = osutils.split(path)
1333
return (dirname.split(u'/'), basename)
1335
def _lookup_by_file_id(self, extra_entries, other_tree, file_id):
1336
"""Lookup an inventory entry by file_id.
1338
This is called when an entry is missing in the normal order.
1339
Generally this is because a file was either renamed, or it was
1340
deleted/added. If the entry was found in the inventory and not in
1341
extra_entries, it will be added to self._out_of_order_processed
1343
:param extra_entries: A dictionary of {file_id: (path, ie)}. This
1344
should be filled with entries that were found before they were
1345
used. If file_id is present, it will be removed from the
1347
:param other_tree: The Tree to search, in case we didn't find the entry
1349
:param file_id: The file_id to look for
1350
:return: (path, ie) if found or (None, None) if not present.
1352
if file_id in extra_entries:
1353
return extra_entries.pop(file_id)
1354
# TODO: Is id2path better as the first call, or is
1355
# inventory[file_id] better as a first check?
1357
cur_path = other_tree.id2path(file_id)
1358
except errors.NoSuchId:
1360
if cur_path is None:
1363
self._out_of_order_processed.add(file_id)
1364
cur_ie = other_tree.root_inventory[file_id]
1365
return (cur_path, cur_ie)
1368
"""Match up the values in the different trees."""
1369
for result in self._walk_master_tree():
1371
self._finish_others()
1372
for result in self._walk_others():
1375
def _walk_master_tree(self):
1376
"""First pass, walk all trees in lock-step.
1378
When we are done, all nodes in the master_tree will have been
1379
processed. _other_walkers, _other_entries, and _others_extra will be
1380
set on 'self' for future processing.
1382
# This iterator has the most "inlining" done, because it tends to touch
1383
# every file in the tree, while the others only hit nodes that don't
1385
master_iterator = self._master_tree.iter_entries_by_dir()
1387
other_walkers = [other.iter_entries_by_dir()
1388
for other in self._other_trees]
1389
other_entries = [self._step_one(walker) for walker in other_walkers]
1390
# Track extra nodes in the other trees
1391
others_extra = [{} for _ in range(len(self._other_trees))]
1393
master_has_more = True
1394
step_one = self._step_one
1395
lookup_by_file_id = self._lookup_by_file_id
1396
out_of_order_processed = self._out_of_order_processed
1398
while master_has_more:
1399
(master_has_more, path, master_ie) = step_one(master_iterator)
1400
if not master_has_more:
1403
file_id = master_ie.file_id
1405
other_values_append = other_values.append
1406
next_other_entries = []
1407
next_other_entries_append = next_other_entries.append
1408
for idx, (other_has_more, other_path, other_ie) in enumerate(other_entries):
1409
if not other_has_more:
1410
other_values_append(lookup_by_file_id(
1411
others_extra[idx], self._other_trees[idx], file_id))
1412
next_other_entries_append((False, None, None))
1413
elif file_id == other_ie.file_id:
1414
# This is the critical code path, as most of the entries
1415
# should match between most trees.
1416
other_values_append((other_path, other_ie))
1417
next_other_entries_append(step_one(other_walkers[idx]))
1419
# This walker did not match, step it until it either
1420
# matches, or we know we are past the current walker.
1421
other_walker = other_walkers[idx]
1422
other_extra = others_extra[idx]
1423
while (other_has_more and
1424
self._cmp_path_by_dirblock(other_path, path) < 0):
1425
other_file_id = other_ie.file_id
1426
if other_file_id not in out_of_order_processed:
1427
other_extra[other_file_id] = (other_path, other_ie)
1428
other_has_more, other_path, other_ie = \
1429
step_one(other_walker)
1430
if other_has_more and other_ie.file_id == file_id:
1431
# We ended up walking to this point, match and step
1433
other_values_append((other_path, other_ie))
1434
other_has_more, other_path, other_ie = \
1435
step_one(other_walker)
1437
# This record isn't in the normal order, see if it
1439
other_values_append(lookup_by_file_id(
1440
other_extra, self._other_trees[idx], file_id))
1441
next_other_entries_append((other_has_more, other_path,
1443
other_entries = next_other_entries
1445
# We've matched all the walkers, yield this datapoint
1446
yield path, file_id, master_ie, other_values
1447
self._other_walkers = other_walkers
1448
self._other_entries = other_entries
1449
self._others_extra = others_extra
1451
def _finish_others(self):
1452
"""Finish walking the other iterators, so we get all entries."""
1453
for idx, info in enumerate(self._other_entries):
1454
other_extra = self._others_extra[idx]
1455
(other_has_more, other_path, other_ie) = info
1456
while other_has_more:
1457
other_file_id = other_ie.file_id
1458
if other_file_id not in self._out_of_order_processed:
1459
other_extra[other_file_id] = (other_path, other_ie)
1460
other_has_more, other_path, other_ie = \
1461
self._step_one(self._other_walkers[idx])
1462
del self._other_entries
1464
def _walk_others(self):
1465
"""Finish up by walking all the 'deferred' nodes."""
1466
# TODO: One alternative would be to grab all possible unprocessed
1467
# file_ids, and then sort by path, and then yield them. That
1468
# might ensure better ordering, in case a caller strictly
1469
# requires parents before children.
1470
for idx, other_extra in enumerate(self._others_extra):
1471
others = sorted(viewvalues(other_extra),
1472
key=lambda x: self._path_to_key(x[0]))
1473
for other_path, other_ie in others:
1474
file_id = other_ie.file_id
1475
# We don't need to check out_of_order_processed here, because
1476
# the lookup_by_file_id will be removing anything processed
1477
# from the extras cache
1478
other_extra.pop(file_id)
1479
other_values = [(None, None)] * idx
1480
other_values.append((other_path, other_ie))
1481
for alt_idx, alt_extra in enumerate(self._others_extra[idx+1:]):
1482
alt_idx = alt_idx + idx + 1
1483
alt_extra = self._others_extra[alt_idx]
1484
alt_tree = self._other_trees[alt_idx]
1485
other_values.append(self._lookup_by_file_id(
1486
alt_extra, alt_tree, file_id))
1487
yield other_path, file_id, None, other_values
1248
def find_previous_paths(from_tree, to_tree, paths, recurse='none'):
1249
"""Find previous tree paths.
1251
:param from_tree: From tree
1252
:param to_tree: To tree
1253
:param paths: Iterable over paths in from_tree to search for
1254
:return: Dictionary mapping from from_tree paths to paths in to_tree, or
1255
None if there is no equivalent path.
1257
return InterTree.get(to_tree, from_tree).find_source_paths(paths, recurse=recurse)
1260
def find_previous_path(from_tree, to_tree, path, recurse='none'):
1261
"""Find previous tree path.
1263
:param from_tree: From tree
1264
:param to_tree: To tree
1265
:param path: Path to search for (exists in from_tree)
1266
:return: path in to_tree, or None if there is no equivalent path.
1267
:raise NoSuchFile: If the path doesn't exist in from_tree
1269
return InterTree.get(to_tree, from_tree).find_source_path(
1270
path, recurse=recurse)
1273
def get_canonical_path(tree, path, normalize):
1274
"""Find the canonical path of an item, ignoring case.
1276
:param tree: Tree to traverse
1277
:param path: Case-insensitive path to look up
1278
:param normalize: Function to normalize a filename for comparison
1279
:return: The canonical path
1283
bit_iter = iter(path.split("/"))
1284
for elt in bit_iter:
1285
lelt = normalize(elt)
1288
for child in tree.iter_child_entries(cur_path):
1290
if child.name == elt:
1291
# if we found an exact match, we can stop now; if
1292
# we found an approximate match we need to keep
1293
# searching because there might be an exact match
1295
new_path = osutils.pathjoin(cur_path, child.name)
1297
elif normalize(child.name) == lelt:
1298
new_path = osutils.pathjoin(cur_path, child.name)
1299
except errors.NoSuchId:
1300
# before a change is committed we can see this error...
1302
except errors.NotADirectory:
1307
# got to the end of this directory and no entries matched.
1308
# Return what matched so far, plus the rest as specified.
1309
cur_path = osutils.pathjoin(cur_path, elt, *list(bit_iter))