733
"""Apply all changes to the inventory and filesystem.
735
If filesystem or inventory conflicts are present, MalformedTransform
738
conflicts = self.find_conflicts()
739
if len(conflicts) != 0:
740
raise MalformedTransform(conflicts=conflicts)
741
inv = self._tree.inventory
743
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
745
child_pb.update('Apply phase', 0, 2)
746
self._apply_removals(inv, inventory_delta)
747
child_pb.update('Apply phase', 1, 2)
748
modified_paths = self._apply_insertions(inv, inventory_delta)
751
self._tree.apply_inventory_delta(inventory_delta)
754
return _TransformResults(modified_paths)
870
756
def _limbo_name(self, trans_id):
871
757
"""Generate the limbo name of a file"""
872
limbo_name = self._limbo_files.get(trans_id)
873
if limbo_name is not None:
875
parent = self._new_parent.get(trans_id)
876
# if the parent directory is already in limbo (e.g. when building a
877
# tree), choose a limbo name inside the parent, to reduce further
879
use_direct_path = False
880
if self._new_contents.get(parent) == 'directory':
881
filename = self._new_name.get(trans_id)
882
if filename is not None:
883
if parent not in self._limbo_children:
884
self._limbo_children[parent] = set()
885
self._limbo_children_names[parent] = {}
886
use_direct_path = True
887
# the direct path can only be used if no other file has
888
# already taken this pathname, i.e. if the name is unused, or
889
# if it is already associated with this trans_id.
890
elif self._case_sensitive_target:
891
if (self._limbo_children_names[parent].get(filename)
892
in (trans_id, None)):
893
use_direct_path = True
758
return pathjoin(self._limbodir, trans_id)
760
def _apply_removals(self, inv, inventory_delta):
761
"""Perform tree operations that remove directory/inventory names.
763
That is, delete files that are to be deleted, and put any files that
764
need renaming into limbo. This must be done in strict child-to-parent
767
tree_paths = list(self._tree_path_ids.iteritems())
768
tree_paths.sort(reverse=True)
769
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
771
for num, data in enumerate(tree_paths):
772
path, trans_id = data
773
child_pb.update('removing file', num, len(tree_paths))
774
full_path = self._tree.abspath(path)
775
if trans_id in self._removed_contents:
776
delete_any(full_path)
777
elif trans_id in self._new_name or trans_id in \
780
os.rename(full_path, self._limbo_name(trans_id))
782
if e.errno != errno.ENOENT:
784
if trans_id in self._removed_id:
785
if trans_id == self._new_root:
786
file_id = self._tree.inventory.root.file_id
788
file_id = self.tree_file_id(trans_id)
789
assert file_id is not None
790
inventory_delta.append((path, None, file_id, None))
794
def _apply_insertions(self, inv, inventory_delta):
795
"""Perform tree operations that insert directory/inventory names.
797
That is, create any files that need to be created, and restore from
798
limbo any files that needed renaming. This must be done in strict
799
parent-to-child order.
801
new_paths = self.new_paths()
803
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
805
for num, (path, trans_id) in enumerate(new_paths):
807
child_pb.update('adding file', num, len(new_paths))
809
kind = self._new_contents[trans_id]
811
kind = contents = None
812
if trans_id in self._new_contents or \
813
self.path_changed(trans_id):
814
full_path = self._tree.abspath(path)
816
os.rename(self._limbo_name(trans_id), full_path)
818
# We may be renaming a dangling inventory id
819
if e.errno != errno.ENOENT:
821
if trans_id in self._new_contents:
822
modified_paths.append(full_path)
823
del self._new_contents[trans_id]
825
if trans_id in self._new_id:
827
kind = file_kind(self._tree.abspath(path))
828
if trans_id in self._new_reference_revision:
829
new_entry = inventory.TreeReference(
830
self._new_id[trans_id],
831
self._new_name[trans_id],
832
self.final_file_id(self._new_parent[trans_id]),
833
None, self._new_reference_revision[trans_id])
835
new_entry = inventory.make_entry(kind,
836
self.final_name(trans_id),
837
self.final_file_id(self.final_parent(trans_id)),
838
self._new_id[trans_id])
895
for l_filename, l_trans_id in\
896
self._limbo_children_names[parent].iteritems():
897
if l_trans_id == trans_id:
899
if l_filename.lower() == filename.lower():
840
if trans_id in self._new_name or trans_id in\
842
trans_id in self._new_executability:
843
file_id = self.final_file_id(trans_id)
844
if file_id is not None:
846
new_entry = entry.copy()
848
if trans_id in self._new_name or trans_id in\
850
if new_entry is not None:
851
new_entry.name = self.final_name(trans_id)
852
parent = self.final_parent(trans_id)
853
parent_id = self.final_file_id(parent)
854
new_entry.parent_id = parent_id
856
if trans_id in self._new_executability:
857
self._set_executability(path, new_entry, trans_id)
858
if new_entry is not None:
859
if new_entry.file_id in inv:
860
old_path = inv.id2path(new_entry.file_id)
902
use_direct_path = True
905
limbo_name = pathjoin(self._limbo_files[parent], filename)
906
self._limbo_children[parent].add(trans_id)
907
self._limbo_children_names[parent][filename] = trans_id
909
limbo_name = pathjoin(self._limbodir, trans_id)
910
self._needs_rename.add(trans_id)
911
self._limbo_files[trans_id] = limbo_name
914
def _set_executability(self, path, trans_id):
863
inventory_delta.append((old_path, path,
868
return modified_paths
870
def _set_executability(self, path, entry, trans_id):
915
871
"""Set the executability of versioned files """
872
new_executability = self._new_executability[trans_id]
873
entry.executable = new_executability
916
874
if supports_executable():
917
new_executability = self._new_executability[trans_id]
918
875
abspath = self._tree.abspath(path)
919
876
current_mode = os.stat(abspath).st_mode
920
877
if new_executability:
1119
1076
(from_executable, to_executable)))
1120
1077
return iter(sorted(results, key=lambda x:x[1]))
1122
def get_preview_tree(self):
1123
"""Return a tree representing the result of the transform.
1125
This tree only supports the subset of Tree functionality required
1126
by show_diff_trees. It must only be compared to tt._tree.
1128
return _PreviewTree(self)
1130
def _text_parent(self, trans_id):
1131
file_id = self.tree_file_id(trans_id)
1133
if file_id is None or self._tree.kind(file_id) != 'file':
1135
except errors.NoSuchFile:
1139
def _get_parents_texts(self, trans_id):
1140
"""Get texts for compression parents of this file."""
1141
file_id = self._text_parent(trans_id)
1144
return (self._tree.get_file_text(file_id),)
1146
def _get_parents_lines(self, trans_id):
1147
"""Get lines for compression parents of this file."""
1148
file_id = self._text_parent(trans_id)
1151
return (self._tree.get_file_lines(file_id),)
1153
def serialize(self, serializer):
1154
"""Serialize this TreeTransform.
1156
:param serializer: A Serialiser like pack.ContainerSerializer.
1158
new_name = dict((k, v.encode('utf-8')) for k, v in
1159
self._new_name.items())
1160
new_executability = dict((k, int(v)) for k, v in
1161
self._new_executability.items())
1162
tree_path_ids = dict((k.encode('utf-8'), v)
1163
for k, v in self._tree_path_ids.items())
1165
'_id_number': self._id_number,
1166
'_new_name': new_name,
1167
'_new_parent': self._new_parent,
1168
'_new_executability': new_executability,
1169
'_new_id': self._new_id,
1170
'_tree_path_ids': tree_path_ids,
1171
'_removed_id': list(self._removed_id),
1172
'_removed_contents': list(self._removed_contents),
1173
'_non_present_ids': self._non_present_ids,
1175
yield serializer.bytes_record(bencode.bencode(attribs),
1177
for trans_id, kind in self._new_contents.items():
1179
cur_file = open(self._limbo_name(trans_id), 'rb')
1181
lines = osutils.chunks_to_lines(cur_file.readlines())
1184
parents = self._get_parents_lines(trans_id)
1185
mpdiff = multiparent.MultiParent.from_lines(lines, parents)
1186
content = ''.join(mpdiff.to_patch())
1187
if kind == 'directory':
1189
if kind == 'symlink':
1190
content = os.readlink(self._limbo_name(trans_id))
1191
yield serializer.bytes_record(content, ((trans_id, kind),))
1194
def deserialize(self, records):
1195
"""Deserialize a stored TreeTransform.
1197
:param records: An iterable of (names, content) tuples, as per
1198
pack.ContainerPushParser.
1200
names, content = records.next()
1201
attribs = bencode.bdecode(content)
1202
self._id_number = attribs['_id_number']
1203
self._new_name = dict((k, v.decode('utf-8'))
1204
for k, v in attribs['_new_name'].items())
1205
self._new_parent = attribs['_new_parent']
1206
self._new_executability = dict((k, bool(v)) for k, v in
1207
attribs['_new_executability'].items())
1208
self._new_id = attribs['_new_id']
1209
self._r_new_id = dict((v, k) for k, v in self._new_id.items())
1210
self._tree_path_ids = {}
1211
self._tree_id_paths = {}
1212
for bytepath, trans_id in attribs['_tree_path_ids'].items():
1213
path = bytepath.decode('utf-8')
1214
self._tree_path_ids[path] = trans_id
1215
self._tree_id_paths[trans_id] = path
1216
self._removed_id = set(attribs['_removed_id'])
1217
self._removed_contents = set(attribs['_removed_contents'])
1218
self._non_present_ids = attribs['_non_present_ids']
1219
for ((trans_id, kind),), content in records:
1221
mpdiff = multiparent.MultiParent.from_patch(content)
1222
lines = mpdiff.to_lines(self._get_parents_texts(trans_id))
1223
self.create_file(lines, trans_id)
1224
if kind == 'directory':
1225
self.create_directory(trans_id)
1226
if kind == 'symlink':
1227
self.create_symlink(content.decode('utf-8'), trans_id)
1230
class TreeTransform(TreeTransformBase):
1231
"""Represent a tree transformation.
1233
This object is designed to support incremental generation of the transform,
1236
However, it gives optimum performance when parent directories are created
1237
before their contents. The transform is then able to put child files
1238
directly in their parent directory, avoiding later renames.
1240
It is easy to produce malformed transforms, but they are generally
1241
harmless. Attempting to apply a malformed transform will cause an
1242
exception to be raised before any modifications are made to the tree.
1244
Many kinds of malformed transforms can be corrected with the
1245
resolve_conflicts function. The remaining ones indicate programming error,
1246
such as trying to create a file with no path.
1248
Two sets of file creation methods are supplied. Convenience methods are:
1253
These are composed of the low-level methods:
1255
* create_file or create_directory or create_symlink
1259
Transform/Transaction ids
1260
-------------------------
1261
trans_ids are temporary ids assigned to all files involved in a transform.
1262
It's possible, even common, that not all files in the Tree have trans_ids.
1264
trans_ids are used because filenames and file_ids are not good enough
1265
identifiers; filenames change, and not all files have file_ids. File-ids
1266
are also associated with trans-ids, so that moving a file moves its
1269
trans_ids are only valid for the TreeTransform that generated them.
1273
Limbo is a temporary directory use to hold new versions of files.
1274
Files are added to limbo by create_file, create_directory, create_symlink,
1275
and their convenience variants (new_*). Files may be removed from limbo
1276
using cancel_creation. Files are renamed from limbo into their final
1277
location as part of TreeTransform.apply
1279
Limbo must be cleaned up, by either calling TreeTransform.apply or
1280
calling TreeTransform.finalize.
1282
Files are placed into limbo inside their parent directories, where
1283
possible. This reduces subsequent renames, and makes operations involving
1284
lots of files faster. This optimization is only possible if the parent
1285
directory is created *before* creating any of its children, so avoid
1286
creating children before parents, where possible.
1290
This temporary directory is used by _FileMover for storing files that are
1291
about to be deleted. In case of rollback, the files will be restored.
1292
FileMover does not delete files until it is sure that a rollback will not
1295
def __init__(self, tree, pb=DummyProgress()):
1296
"""Note: a tree_write lock is taken on the tree.
1298
Use TreeTransform.finalize() to release the lock (can be omitted if
1299
TreeTransform.apply() called).
1301
tree.lock_tree_write()
1304
limbodir = urlutils.local_path_from_url(
1305
tree._transport.abspath('limbo'))
1309
if e.errno == errno.EEXIST:
1310
raise ExistingLimbo(limbodir)
1311
deletiondir = urlutils.local_path_from_url(
1312
tree._transport.abspath('pending-deletion'))
1314
os.mkdir(deletiondir)
1316
if e.errno == errno.EEXIST:
1317
raise errors.ExistingPendingDeletion(deletiondir)
1322
TreeTransformBase.__init__(self, tree, limbodir, pb,
1323
tree.case_sensitive)
1324
self._deletiondir = deletiondir
1326
def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1327
"""Apply all changes to the inventory and filesystem.
1329
If filesystem or inventory conflicts are present, MalformedTransform
1332
If apply succeeds, finalize is not necessary.
1334
:param no_conflicts: if True, the caller guarantees there are no
1335
conflicts, so no check is made.
1336
:param precomputed_delta: An inventory delta to use instead of
1338
:param _mover: Supply an alternate FileMover, for testing
1340
if not no_conflicts:
1341
conflicts = self.find_conflicts()
1342
if len(conflicts) != 0:
1343
raise MalformedTransform(conflicts=conflicts)
1344
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1346
if precomputed_delta is None:
1347
child_pb.update('Apply phase', 0, 2)
1348
inventory_delta = self._generate_inventory_delta()
1351
inventory_delta = precomputed_delta
1354
mover = _FileMover()
1358
child_pb.update('Apply phase', 0 + offset, 2 + offset)
1359
self._apply_removals(mover)
1360
child_pb.update('Apply phase', 1 + offset, 2 + offset)
1361
modified_paths = self._apply_insertions(mover)
1366
mover.apply_deletions()
1369
self._tree.apply_inventory_delta(inventory_delta)
1372
return _TransformResults(modified_paths, self.rename_count)
1374
def _generate_inventory_delta(self):
1375
"""Generate an inventory delta for the current transform."""
1376
inventory_delta = []
1377
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1378
new_paths = self._inventory_altered()
1379
total_entries = len(new_paths) + len(self._removed_id)
1381
for num, trans_id in enumerate(self._removed_id):
1383
child_pb.update('removing file', num, total_entries)
1384
if trans_id == self._new_root:
1385
file_id = self._tree.get_root_id()
1387
file_id = self.tree_file_id(trans_id)
1388
# File-id isn't really being deleted, just moved
1389
if file_id in self._r_new_id:
1391
path = self._tree_id_paths[trans_id]
1392
inventory_delta.append((path, None, file_id, None))
1393
new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1395
entries = self._tree.iter_entries_by_dir(
1396
new_path_file_ids.values())
1397
old_paths = dict((e.file_id, p) for p, e in entries)
1399
for num, (path, trans_id) in enumerate(new_paths):
1401
child_pb.update('adding file',
1402
num + len(self._removed_id), total_entries)
1403
file_id = new_path_file_ids[trans_id]
1408
kind = self.final_kind(trans_id)
1410
kind = self._tree.stored_kind(file_id)
1411
parent_trans_id = self.final_parent(trans_id)
1412
parent_file_id = new_path_file_ids.get(parent_trans_id)
1413
if parent_file_id is None:
1414
parent_file_id = self.final_file_id(parent_trans_id)
1415
if trans_id in self._new_reference_revision:
1416
new_entry = inventory.TreeReference(
1418
self._new_name[trans_id],
1419
self.final_file_id(self._new_parent[trans_id]),
1420
None, self._new_reference_revision[trans_id])
1422
new_entry = inventory.make_entry(kind,
1423
self.final_name(trans_id),
1424
parent_file_id, file_id)
1425
old_path = old_paths.get(new_entry.file_id)
1426
new_executability = self._new_executability.get(trans_id)
1427
if new_executability is not None:
1428
new_entry.executable = new_executability
1429
inventory_delta.append(
1430
(old_path, path, new_entry.file_id, new_entry))
1433
return inventory_delta
1435
def _apply_removals(self, mover):
1436
"""Perform tree operations that remove directory/inventory names.
1438
That is, delete files that are to be deleted, and put any files that
1439
need renaming into limbo. This must be done in strict child-to-parent
1442
If inventory_delta is None, no inventory delta generation is performed.
1444
tree_paths = list(self._tree_path_ids.iteritems())
1445
tree_paths.sort(reverse=True)
1446
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1448
for num, data in enumerate(tree_paths):
1449
path, trans_id = data
1450
child_pb.update('removing file', num, len(tree_paths))
1451
full_path = self._tree.abspath(path)
1452
if trans_id in self._removed_contents:
1453
mover.pre_delete(full_path, os.path.join(self._deletiondir,
1455
elif trans_id in self._new_name or trans_id in \
1458
mover.rename(full_path, self._limbo_name(trans_id))
1460
if e.errno != errno.ENOENT:
1463
self.rename_count += 1
1467
def _apply_insertions(self, mover):
1468
"""Perform tree operations that insert directory/inventory names.
1470
That is, create any files that need to be created, and restore from
1471
limbo any files that needed renaming. This must be done in strict
1472
parent-to-child order.
1474
If inventory_delta is None, no inventory delta is calculated, and
1475
no list of modified paths is returned.
1477
new_paths = self.new_paths(filesystem_only=True)
1479
new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1481
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1483
for num, (path, trans_id) in enumerate(new_paths):
1485
child_pb.update('adding file', num, len(new_paths))
1486
full_path = self._tree.abspath(path)
1487
if trans_id in self._needs_rename:
1489
mover.rename(self._limbo_name(trans_id), full_path)
1491
# We may be renaming a dangling inventory id
1492
if e.errno != errno.ENOENT:
1495
self.rename_count += 1
1496
if (trans_id in self._new_contents or
1497
self.path_changed(trans_id)):
1498
if trans_id in self._new_contents:
1499
modified_paths.append(full_path)
1500
if trans_id in self._new_executability:
1501
self._set_executability(path, trans_id)
1504
self._new_contents.clear()
1505
return modified_paths
1508
class TransformPreview(TreeTransformBase):
1509
"""A TreeTransform for generating preview trees.
1511
Unlike TreeTransform, this version works when the input tree is a
1512
RevisionTree, rather than a WorkingTree. As a result, it tends to ignore
1513
unversioned files in the input tree.
1516
def __init__(self, tree, pb=DummyProgress(), case_sensitive=True):
1518
limbodir = osutils.mkdtemp(prefix='bzr-limbo-')
1519
TreeTransformBase.__init__(self, tree, limbodir, pb, case_sensitive)
1521
def canonical_path(self, path):
1524
def tree_kind(self, trans_id):
1525
path = self._tree_id_paths.get(trans_id)
1527
raise NoSuchFile(None)
1528
file_id = self._tree.path2id(path)
1529
return self._tree.kind(file_id)
1531
def _set_mode(self, trans_id, mode_id, typefunc):
1532
"""Set the mode of new file contents.
1533
The mode_id is the existing file to get the mode from (often the same
1534
as trans_id). The operation is only performed if there's a mode match
1535
according to typefunc.
1537
# is it ok to ignore this? probably
1540
def iter_tree_children(self, parent_id):
1541
"""Iterate through the entry's tree children, if any"""
1543
path = self._tree_id_paths[parent_id]
1546
file_id = self.tree_file_id(parent_id)
1549
entry = self._tree.iter_entries_by_dir([file_id]).next()[1]
1550
children = getattr(entry, 'children', {})
1551
for child in children:
1552
childpath = joinpath(path, child)
1553
yield self.trans_id_tree_path(childpath)
1556
class _PreviewTree(tree.Tree):
1557
"""Partial implementation of Tree to support show_diff_trees"""
1559
def __init__(self, transform):
1560
self._transform = transform
1561
self._final_paths = FinalPaths(transform)
1562
self.__by_parent = None
1563
self._parent_ids = []
1564
self._all_children_cache = {}
1565
self._path2trans_id_cache = {}
1566
self._final_name_cache = {}
1568
def _changes(self, file_id):
1569
for changes in self._transform.iter_changes():
1570
if changes[0] == file_id:
1573
def _content_change(self, file_id):
1574
"""Return True if the content of this file changed"""
1575
changes = self._changes(file_id)
1576
# changes[2] is true if the file content changed. See
1577
# InterTree.iter_changes.
1578
return (changes is not None and changes[2])
1580
def _get_repository(self):
1581
repo = getattr(self._transform._tree, '_repository', None)
1583
repo = self._transform._tree.branch.repository
1586
def _iter_parent_trees(self):
1587
for revision_id in self.get_parent_ids():
1589
yield self.revision_tree(revision_id)
1590
except errors.NoSuchRevisionInTree:
1591
yield self._get_repository().revision_tree(revision_id)
1593
def _get_file_revision(self, file_id, vf, tree_revision):
1594
parent_keys = [(file_id, self._file_revision(t, file_id)) for t in
1595
self._iter_parent_trees()]
1596
vf.add_lines((file_id, tree_revision), parent_keys,
1597
self.get_file(file_id).readlines())
1598
repo = self._get_repository()
1599
base_vf = repo.texts
1600
if base_vf not in vf.fallback_versionedfiles:
1601
vf.fallback_versionedfiles.append(base_vf)
1602
return tree_revision
1604
def _stat_limbo_file(self, file_id):
1605
trans_id = self._transform.trans_id_file_id(file_id)
1606
name = self._transform._limbo_name(trans_id)
1607
return os.lstat(name)
1610
def _by_parent(self):
1611
if self.__by_parent is None:
1612
self.__by_parent = self._transform.by_parent()
1613
return self.__by_parent
1615
def _comparison_data(self, entry, path):
1616
kind, size, executable, link_or_sha1 = self.path_content_summary(path)
1617
if kind == 'missing':
1621
file_id = self._transform.final_file_id(self._path2trans_id(path))
1622
executable = self.is_executable(file_id, path)
1623
return kind, executable, None
1625
def lock_read(self):
1626
# Perhaps in theory, this should lock the TreeTransform?
1633
def inventory(self):
1634
"""This Tree does not use inventory as its backing data."""
1635
raise NotImplementedError(_PreviewTree.inventory)
1637
def get_root_id(self):
1638
return self._transform.final_file_id(self._transform.root)
1640
def all_file_ids(self):
1641
tree_ids = set(self._transform._tree.all_file_ids())
1642
tree_ids.difference_update(self._transform.tree_file_id(t)
1643
for t in self._transform._removed_id)
1644
tree_ids.update(self._transform._new_id.values())
1648
return iter(self.all_file_ids())
1650
def has_id(self, file_id):
1651
if file_id in self._transform._r_new_id:
1653
elif file_id in set([self._transform.tree_file_id(trans_id) for
1654
trans_id in self._transform._removed_id]):
1657
return self._transform._tree.has_id(file_id)
1659
def _path2trans_id(self, path):
1660
# We must not use None here, because that is a valid value to store.
1661
trans_id = self._path2trans_id_cache.get(path, object)
1662
if trans_id is not object:
1664
segments = splitpath(path)
1665
cur_parent = self._transform.root
1666
for cur_segment in segments:
1667
for child in self._all_children(cur_parent):
1668
final_name = self._final_name_cache.get(child)
1669
if final_name is None:
1670
final_name = self._transform.final_name(child)
1671
self._final_name_cache[child] = final_name
1672
if final_name == cur_segment:
1676
self._path2trans_id_cache[path] = None
1678
self._path2trans_id_cache[path] = cur_parent
1681
def path2id(self, path):
1682
return self._transform.final_file_id(self._path2trans_id(path))
1684
def id2path(self, file_id):
1685
trans_id = self._transform.trans_id_file_id(file_id)
1687
return self._final_paths._determine_path(trans_id)
1689
raise errors.NoSuchId(self, file_id)
1691
def _all_children(self, trans_id):
1692
children = self._all_children_cache.get(trans_id)
1693
if children is not None:
1695
children = set(self._transform.iter_tree_children(trans_id))
1696
# children in the _new_parent set are provided by _by_parent.
1697
children.difference_update(self._transform._new_parent.keys())
1698
children.update(self._by_parent.get(trans_id, []))
1699
self._all_children_cache[trans_id] = children
1702
def iter_children(self, file_id):
1703
trans_id = self._transform.trans_id_file_id(file_id)
1704
for child_trans_id in self._all_children(trans_id):
1705
yield self._transform.final_file_id(child_trans_id)
1708
possible_extras = set(self._transform.trans_id_tree_path(p) for p
1709
in self._transform._tree.extras())
1710
possible_extras.update(self._transform._new_contents)
1711
possible_extras.update(self._transform._removed_id)
1712
for trans_id in possible_extras:
1713
if self._transform.final_file_id(trans_id) is None:
1714
yield self._final_paths._determine_path(trans_id)
1716
def _make_inv_entries(self, ordered_entries, specific_file_ids):
1717
for trans_id, parent_file_id in ordered_entries:
1718
file_id = self._transform.final_file_id(trans_id)
1721
if (specific_file_ids is not None
1722
and file_id not in specific_file_ids):
1725
kind = self._transform.final_kind(trans_id)
1727
kind = self._transform._tree.stored_kind(file_id)
1728
new_entry = inventory.make_entry(
1730
self._transform.final_name(trans_id),
1731
parent_file_id, file_id)
1732
yield new_entry, trans_id
1734
def _list_files_by_dir(self):
1735
todo = [ROOT_PARENT]
1737
while len(todo) > 0:
1739
parent_file_id = self._transform.final_file_id(parent)
1740
children = list(self._all_children(parent))
1741
paths = dict(zip(children, self._final_paths.get_paths(children)))
1742
children.sort(key=paths.get)
1743
todo.extend(reversed(children))
1744
for trans_id in children:
1745
ordered_ids.append((trans_id, parent_file_id))
1748
def iter_entries_by_dir(self, specific_file_ids=None):
1749
# This may not be a maximally efficient implementation, but it is
1750
# reasonably straightforward. An implementation that grafts the
1751
# TreeTransform changes onto the tree's iter_entries_by_dir results
1752
# might be more efficient, but requires tricky inferences about stack
1754
ordered_ids = self._list_files_by_dir()
1755
for entry, trans_id in self._make_inv_entries(ordered_ids,
1757
yield unicode(self._final_paths.get_path(trans_id)), entry
1759
def list_files(self, include_root=False):
1760
"""See Tree.list_files."""
1761
# XXX This should behave like WorkingTree.list_files, but is really
1762
# more like RevisionTree.list_files.
1763
for path, entry in self.iter_entries_by_dir():
1764
if entry.name == '' and not include_root:
1766
yield path, 'V', entry.kind, entry.file_id, entry
1768
def kind(self, file_id):
1769
trans_id = self._transform.trans_id_file_id(file_id)
1770
return self._transform.final_kind(trans_id)
1772
def stored_kind(self, file_id):
1773
trans_id = self._transform.trans_id_file_id(file_id)
1775
return self._transform._new_contents[trans_id]
1777
return self._transform._tree.stored_kind(file_id)
1779
def get_file_mtime(self, file_id, path=None):
1780
"""See Tree.get_file_mtime"""
1781
if not self._content_change(file_id):
1782
return self._transform._tree.get_file_mtime(file_id, path)
1783
return self._stat_limbo_file(file_id).st_mtime
1785
def _file_size(self, entry, stat_value):
1786
return self.get_file_size(entry.file_id)
1788
def get_file_size(self, file_id):
1789
"""See Tree.get_file_size"""
1790
if self.kind(file_id) == 'file':
1791
return self._transform._tree.get_file_size(file_id)
1795
def get_file_sha1(self, file_id, path=None, stat_value=None):
1796
trans_id = self._transform.trans_id_file_id(file_id)
1797
kind = self._transform._new_contents.get(trans_id)
1799
return self._transform._tree.get_file_sha1(file_id)
1801
fileobj = self.get_file(file_id)
1803
return sha_file(fileobj)
1807
def is_executable(self, file_id, path=None):
1810
trans_id = self._transform.trans_id_file_id(file_id)
1812
return self._transform._new_executability[trans_id]
1815
return self._transform._tree.is_executable(file_id, path)
1817
if e.errno == errno.ENOENT:
1820
except errors.NoSuchId:
1823
def path_content_summary(self, path):
1824
trans_id = self._path2trans_id(path)
1825
tt = self._transform
1826
tree_path = tt._tree_id_paths.get(trans_id)
1827
kind = tt._new_contents.get(trans_id)
1829
if tree_path is None or trans_id in tt._removed_contents:
1830
return 'missing', None, None, None
1831
summary = tt._tree.path_content_summary(tree_path)
1832
kind, size, executable, link_or_sha1 = summary
1835
limbo_name = tt._limbo_name(trans_id)
1836
if trans_id in tt._new_reference_revision:
1837
kind = 'tree-reference'
1839
statval = os.lstat(limbo_name)
1840
size = statval.st_size
1841
if not supports_executable():
1844
executable = statval.st_mode & S_IEXEC
1848
if kind == 'symlink':
1849
link_or_sha1 = os.readlink(limbo_name).decode(osutils._fs_enc)
1850
if supports_executable():
1851
executable = tt._new_executability.get(trans_id, executable)
1852
return kind, size, executable, link_or_sha1
1854
def iter_changes(self, from_tree, include_unchanged=False,
1855
specific_files=None, pb=None, extra_trees=None,
1856
require_versioned=True, want_unversioned=False):
1857
"""See InterTree.iter_changes.
1859
This has a fast path that is only used when the from_tree matches
1860
the transform tree, and no fancy options are supplied.
1862
if (from_tree is not self._transform._tree or include_unchanged or
1863
specific_files or want_unversioned):
1864
return tree.InterTree(from_tree, self).iter_changes(
1865
include_unchanged=include_unchanged,
1866
specific_files=specific_files,
1868
extra_trees=extra_trees,
1869
require_versioned=require_versioned,
1870
want_unversioned=want_unversioned)
1871
if want_unversioned:
1872
raise ValueError('want_unversioned is not supported')
1873
return self._transform.iter_changes()
1875
def get_file(self, file_id, path=None):
1876
"""See Tree.get_file"""
1877
if not self._content_change(file_id):
1878
return self._transform._tree.get_file(file_id, path)
1879
trans_id = self._transform.trans_id_file_id(file_id)
1880
name = self._transform._limbo_name(trans_id)
1881
return open(name, 'rb')
1883
def annotate_iter(self, file_id,
1884
default_revision=_mod_revision.CURRENT_REVISION):
1885
changes = self._changes(file_id)
1889
changed_content, versioned, kind = (changes[2], changes[3],
1893
get_old = (kind[0] == 'file' and versioned[0])
1895
old_annotation = self._transform._tree.annotate_iter(file_id,
1896
default_revision=default_revision)
1900
return old_annotation
1901
if not changed_content:
1902
return old_annotation
1903
return annotate.reannotate([old_annotation],
1904
self.get_file(file_id).readlines(),
1907
def get_symlink_target(self, file_id):
1908
"""See Tree.get_symlink_target"""
1909
if not self._content_change(file_id):
1910
return self._transform._tree.get_symlink_target(file_id)
1911
trans_id = self._transform.trans_id_file_id(file_id)
1912
name = self._transform._limbo_name(trans_id)
1913
return osutils.readlink(name)
1915
def walkdirs(self, prefix=''):
1916
pending = [self._transform.root]
1917
while len(pending) > 0:
1918
parent_id = pending.pop()
1921
prefix = prefix.rstrip('/')
1922
parent_path = self._final_paths.get_path(parent_id)
1923
parent_file_id = self._transform.final_file_id(parent_id)
1924
for child_id in self._all_children(parent_id):
1925
path_from_root = self._final_paths.get_path(child_id)
1926
basename = self._transform.final_name(child_id)
1927
file_id = self._transform.final_file_id(child_id)
1929
kind = self._transform.final_kind(child_id)
1930
versioned_kind = kind
1933
versioned_kind = self._transform._tree.stored_kind(file_id)
1934
if versioned_kind == 'directory':
1935
subdirs.append(child_id)
1936
children.append((path_from_root, basename, kind, None,
1937
file_id, versioned_kind))
1939
if parent_path.startswith(prefix):
1940
yield (parent_path, parent_file_id), children
1941
pending.extend(sorted(subdirs, key=self._final_paths.get_path,
1944
def get_parent_ids(self):
1945
return self._parent_ids
1947
def set_parent_ids(self, parent_ids):
1948
self._parent_ids = parent_ids
1950
def get_revision_tree(self, revision_id):
1951
return self._transform._tree.get_revision_tree(revision_id)
1954
1080
def joinpath(parent, child):
1955
1081
"""Join tree-relative paths, handling the tree root specially"""