698
"""Apply all changes to the inventory and filesystem.
700
If filesystem or inventory conflicts are present, MalformedTransform
703
conflicts = self.find_conflicts()
704
if len(conflicts) != 0:
705
raise MalformedTransform(conflicts=conflicts)
707
inv = self._tree.inventory
708
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
710
child_pb.update('Apply phase', 0, 2)
711
self._apply_removals(inv, limbo_inv)
712
child_pb.update('Apply phase', 1, 2)
713
modified_paths = self._apply_insertions(inv, limbo_inv)
716
self._tree._write_inventory(inv)
719
return _TransformResults(modified_paths)
721
850
def _limbo_name(self, trans_id):
722
851
"""Generate the limbo name of a file"""
723
return pathjoin(self._limbodir, trans_id)
725
def _apply_removals(self, inv, limbo_inv):
726
"""Perform tree operations that remove directory/inventory names.
728
That is, delete files that are to be deleted, and put any files that
729
need renaming into limbo. This must be done in strict child-to-parent
732
tree_paths = list(self._tree_path_ids.iteritems())
733
tree_paths.sort(reverse=True)
734
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
736
for num, data in enumerate(tree_paths):
737
path, trans_id = data
738
child_pb.update('removing file', num, len(tree_paths))
739
full_path = self._tree.abspath(path)
740
if trans_id in self._removed_contents:
741
delete_any(full_path)
742
elif trans_id in self._new_name or trans_id in \
745
os.rename(full_path, self._limbo_name(trans_id))
747
if e.errno != errno.ENOENT:
749
if trans_id in self._removed_id:
750
if trans_id == self._new_root:
751
file_id = self._tree.inventory.root.file_id
852
limbo_name = self._limbo_files.get(trans_id)
853
if limbo_name is not None:
855
parent = self._new_parent.get(trans_id)
856
# if the parent directory is already in limbo (e.g. when building a
857
# tree), choose a limbo name inside the parent, to reduce further
859
use_direct_path = False
860
if self._new_contents.get(parent) == 'directory':
861
filename = self._new_name.get(trans_id)
862
if filename is not None:
863
if parent not in self._limbo_children:
864
self._limbo_children[parent] = set()
865
self._limbo_children_names[parent] = {}
866
use_direct_path = True
867
# the direct path can only be used if no other file has
868
# already taken this pathname, i.e. if the name is unused, or
869
# if it is already associated with this trans_id.
870
elif self._case_sensitive_target:
871
if (self._limbo_children_names[parent].get(filename)
872
in (trans_id, None)):
873
use_direct_path = True
875
for l_filename, l_trans_id in\
876
self._limbo_children_names[parent].iteritems():
877
if l_trans_id == trans_id:
879
if l_filename.lower() == filename.lower():
753
file_id = self.tree_file_id(trans_id)
755
elif trans_id in self._new_name or trans_id in self._new_parent:
756
file_id = self.tree_file_id(trans_id)
757
if file_id is not None:
758
limbo_inv[trans_id] = inv[file_id]
763
def _apply_insertions(self, inv, limbo_inv):
764
"""Perform tree operations that insert directory/inventory names.
766
That is, create any files that need to be created, and restore from
767
limbo any files that needed renaming. This must be done in strict
768
parent-to-child order.
770
new_paths = self.new_paths()
772
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
774
for num, (path, trans_id) in enumerate(new_paths):
775
child_pb.update('adding file', num, len(new_paths))
777
kind = self._new_contents[trans_id]
779
kind = contents = None
780
if trans_id in self._new_contents or \
781
self.path_changed(trans_id):
782
full_path = self._tree.abspath(path)
784
os.rename(self._limbo_name(trans_id), full_path)
786
# We may be renaming a dangling inventory id
787
if e.errno != errno.ENOENT:
789
if trans_id in self._new_contents:
790
modified_paths.append(full_path)
791
del self._new_contents[trans_id]
793
if trans_id in self._new_id:
795
kind = file_kind(self._tree.abspath(path))
796
inv.add_path(path, kind, self._new_id[trans_id])
797
elif trans_id in self._new_name or trans_id in\
799
entry = limbo_inv.get(trans_id)
800
if entry is not None:
801
entry.name = self.final_name(trans_id)
802
parent_path = os.path.dirname(path)
804
self._tree.inventory.path2id(parent_path)
807
# requires files and inventory entries to be in place
808
if trans_id in self._new_executability:
809
self._set_executability(path, inv, trans_id)
812
return modified_paths
814
def _set_executability(self, path, inv, trans_id):
882
use_direct_path = True
885
limbo_name = pathjoin(self._limbo_files[parent], filename)
886
self._limbo_children[parent].add(trans_id)
887
self._limbo_children_names[parent][filename] = trans_id
889
limbo_name = pathjoin(self._limbodir, trans_id)
890
self._needs_rename.add(trans_id)
891
self._limbo_files[trans_id] = limbo_name
894
def _set_executability(self, path, trans_id):
815
895
"""Set the executability of versioned files """
816
file_id = inv.path2id(path)
817
new_executability = self._new_executability[trans_id]
818
inv[file_id].executable = new_executability
819
896
if supports_executable():
897
new_executability = self._new_executability[trans_id]
820
898
abspath = self._tree.abspath(path)
821
899
current_mode = os.stat(abspath).st_mode
822
900
if new_executability:
882
960
self.create_symlink(target, trans_id)
963
def _affected_ids(self):
964
"""Return the set of transform ids affected by the transform"""
965
trans_ids = set(self._removed_id)
966
trans_ids.update(self._new_id.keys())
967
trans_ids.update(self._removed_contents)
968
trans_ids.update(self._new_contents.keys())
969
trans_ids.update(self._new_executability.keys())
970
trans_ids.update(self._new_name.keys())
971
trans_ids.update(self._new_parent.keys())
974
def _get_file_id_maps(self):
975
"""Return mapping of file_ids to trans_ids in the to and from states"""
976
trans_ids = self._affected_ids()
979
# Build up two dicts: trans_ids associated with file ids in the
980
# FROM state, vs the TO state.
981
for trans_id in trans_ids:
982
from_file_id = self.tree_file_id(trans_id)
983
if from_file_id is not None:
984
from_trans_ids[from_file_id] = trans_id
985
to_file_id = self.final_file_id(trans_id)
986
if to_file_id is not None:
987
to_trans_ids[to_file_id] = trans_id
988
return from_trans_ids, to_trans_ids
990
def _from_file_data(self, from_trans_id, from_versioned, file_id):
991
"""Get data about a file in the from (tree) state
993
Return a (name, parent, kind, executable) tuple
995
from_path = self._tree_id_paths.get(from_trans_id)
997
# get data from working tree if versioned
998
from_entry = self._tree.inventory[file_id]
999
from_name = from_entry.name
1000
from_parent = from_entry.parent_id
1003
if from_path is None:
1004
# File does not exist in FROM state
1008
# File exists, but is not versioned. Have to use path-
1010
from_name = os.path.basename(from_path)
1011
tree_parent = self.get_tree_parent(from_trans_id)
1012
from_parent = self.tree_file_id(tree_parent)
1013
if from_path is not None:
1014
from_kind, from_executable, from_stats = \
1015
self._tree._comparison_data(from_entry, from_path)
1018
from_executable = False
1019
return from_name, from_parent, from_kind, from_executable
1021
def _to_file_data(self, to_trans_id, from_trans_id, from_executable):
1022
"""Get data about a file in the to (target) state
1024
Return a (name, parent, kind, executable) tuple
1026
to_name = self.final_name(to_trans_id)
1028
to_kind = self.final_kind(to_trans_id)
1031
to_parent = self.final_file_id(self.final_parent(to_trans_id))
1032
if to_trans_id in self._new_executability:
1033
to_executable = self._new_executability[to_trans_id]
1034
elif to_trans_id == from_trans_id:
1035
to_executable = from_executable
1037
to_executable = False
1038
return to_name, to_parent, to_kind, to_executable
1040
def iter_changes(self):
1041
"""Produce output in the same format as Tree.iter_changes.
1043
Will produce nonsensical results if invoked while inventory/filesystem
1044
conflicts (as reported by TreeTransform.find_conflicts()) are present.
1046
This reads the Transform, but only reproduces changes involving a
1047
file_id. Files that are not versioned in either of the FROM or TO
1048
states are not reflected.
1050
final_paths = FinalPaths(self)
1051
from_trans_ids, to_trans_ids = self._get_file_id_maps()
1053
# Now iterate through all active file_ids
1054
for file_id in set(from_trans_ids.keys() + to_trans_ids.keys()):
1056
from_trans_id = from_trans_ids.get(file_id)
1057
# find file ids, and determine versioning state
1058
if from_trans_id is None:
1059
from_versioned = False
1060
from_trans_id = to_trans_ids[file_id]
1062
from_versioned = True
1063
to_trans_id = to_trans_ids.get(file_id)
1064
if to_trans_id is None:
1065
to_versioned = False
1066
to_trans_id = from_trans_id
1070
from_name, from_parent, from_kind, from_executable = \
1071
self._from_file_data(from_trans_id, from_versioned, file_id)
1073
to_name, to_parent, to_kind, to_executable = \
1074
self._to_file_data(to_trans_id, from_trans_id, from_executable)
1076
if not from_versioned:
1079
from_path = self._tree_id_paths.get(from_trans_id)
1080
if not to_versioned:
1083
to_path = final_paths.get_path(to_trans_id)
1084
if from_kind != to_kind:
1086
elif to_kind in ('file', 'symlink') and (
1087
to_trans_id != from_trans_id or
1088
to_trans_id in self._new_contents):
1090
if (not modified and from_versioned == to_versioned and
1091
from_parent==to_parent and from_name == to_name and
1092
from_executable == to_executable):
1094
results.append((file_id, (from_path, to_path), modified,
1095
(from_versioned, to_versioned),
1096
(from_parent, to_parent),
1097
(from_name, to_name),
1098
(from_kind, to_kind),
1099
(from_executable, to_executable)))
1100
return iter(sorted(results, key=lambda x:x[1]))
1102
def get_preview_tree(self):
1103
"""Return a tree representing the result of the transform.
1105
This tree only supports the subset of Tree functionality required
1106
by show_diff_trees. It must only be compared to tt._tree.
1108
return _PreviewTree(self)
1111
class TreeTransform(TreeTransformBase):
1112
"""Represent a tree transformation.
1114
This object is designed to support incremental generation of the transform,
1117
However, it gives optimum performance when parent directories are created
1118
before their contents. The transform is then able to put child files
1119
directly in their parent directory, avoiding later renames.
1121
It is easy to produce malformed transforms, but they are generally
1122
harmless. Attempting to apply a malformed transform will cause an
1123
exception to be raised before any modifications are made to the tree.
1125
Many kinds of malformed transforms can be corrected with the
1126
resolve_conflicts function. The remaining ones indicate programming error,
1127
such as trying to create a file with no path.
1129
Two sets of file creation methods are supplied. Convenience methods are:
1134
These are composed of the low-level methods:
1136
* create_file or create_directory or create_symlink
1140
Transform/Transaction ids
1141
-------------------------
1142
trans_ids are temporary ids assigned to all files involved in a transform.
1143
It's possible, even common, that not all files in the Tree have trans_ids.
1145
trans_ids are used because filenames and file_ids are not good enough
1146
identifiers; filenames change, and not all files have file_ids. File-ids
1147
are also associated with trans-ids, so that moving a file moves its
1150
trans_ids are only valid for the TreeTransform that generated them.
1154
Limbo is a temporary directory use to hold new versions of files.
1155
Files are added to limbo by create_file, create_directory, create_symlink,
1156
and their convenience variants (new_*). Files may be removed from limbo
1157
using cancel_creation. Files are renamed from limbo into their final
1158
location as part of TreeTransform.apply
1160
Limbo must be cleaned up, by either calling TreeTransform.apply or
1161
calling TreeTransform.finalize.
1163
Files are placed into limbo inside their parent directories, where
1164
possible. This reduces subsequent renames, and makes operations involving
1165
lots of files faster. This optimization is only possible if the parent
1166
directory is created *before* creating any of its children, so avoid
1167
creating children before parents, where possible.
1171
This temporary directory is used by _FileMover for storing files that are
1172
about to be deleted. In case of rollback, the files will be restored.
1173
FileMover does not delete files until it is sure that a rollback will not
1176
def __init__(self, tree, pb=DummyProgress()):
1177
"""Note: a tree_write lock is taken on the tree.
1179
Use TreeTransform.finalize() to release the lock (can be omitted if
1180
TreeTransform.apply() called).
1182
tree.lock_tree_write()
1185
limbodir = urlutils.local_path_from_url(
1186
tree._transport.abspath('limbo'))
1190
if e.errno == errno.EEXIST:
1191
raise ExistingLimbo(limbodir)
1192
deletiondir = urlutils.local_path_from_url(
1193
tree._transport.abspath('pending-deletion'))
1195
os.mkdir(deletiondir)
1197
if e.errno == errno.EEXIST:
1198
raise errors.ExistingPendingDeletion(deletiondir)
1203
TreeTransformBase.__init__(self, tree, limbodir, pb,
1204
tree.case_sensitive)
1205
self._deletiondir = deletiondir
1207
def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1208
"""Apply all changes to the inventory and filesystem.
1210
If filesystem or inventory conflicts are present, MalformedTransform
1213
If apply succeeds, finalize is not necessary.
1215
:param no_conflicts: if True, the caller guarantees there are no
1216
conflicts, so no check is made.
1217
:param precomputed_delta: An inventory delta to use instead of
1219
:param _mover: Supply an alternate FileMover, for testing
1221
if not no_conflicts:
1222
conflicts = self.find_conflicts()
1223
if len(conflicts) != 0:
1224
raise MalformedTransform(conflicts=conflicts)
1225
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1227
if precomputed_delta is None:
1228
child_pb.update('Apply phase', 0, 2)
1229
inventory_delta = self._generate_inventory_delta()
1232
inventory_delta = precomputed_delta
1235
mover = _FileMover()
1239
child_pb.update('Apply phase', 0 + offset, 2 + offset)
1240
self._apply_removals(mover)
1241
child_pb.update('Apply phase', 1 + offset, 2 + offset)
1242
modified_paths = self._apply_insertions(mover)
1247
mover.apply_deletions()
1250
self._tree.apply_inventory_delta(inventory_delta)
1253
return _TransformResults(modified_paths, self.rename_count)
1255
def _generate_inventory_delta(self):
1256
"""Generate an inventory delta for the current transform."""
1257
inventory_delta = []
1258
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1259
new_paths = self._inventory_altered()
1260
total_entries = len(new_paths) + len(self._removed_id)
1262
for num, trans_id in enumerate(self._removed_id):
1264
child_pb.update('removing file', num, total_entries)
1265
if trans_id == self._new_root:
1266
file_id = self._tree.get_root_id()
1268
file_id = self.tree_file_id(trans_id)
1269
# File-id isn't really being deleted, just moved
1270
if file_id in self._r_new_id:
1272
path = self._tree_id_paths[trans_id]
1273
inventory_delta.append((path, None, file_id, None))
1274
new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1276
entries = self._tree.iter_entries_by_dir(
1277
new_path_file_ids.values())
1278
old_paths = dict((e.file_id, p) for p, e in entries)
1280
for num, (path, trans_id) in enumerate(new_paths):
1282
child_pb.update('adding file',
1283
num + len(self._removed_id), total_entries)
1284
file_id = new_path_file_ids[trans_id]
1289
kind = self.final_kind(trans_id)
1291
kind = self._tree.stored_kind(file_id)
1292
parent_trans_id = self.final_parent(trans_id)
1293
parent_file_id = new_path_file_ids.get(parent_trans_id)
1294
if parent_file_id is None:
1295
parent_file_id = self.final_file_id(parent_trans_id)
1296
if trans_id in self._new_reference_revision:
1297
new_entry = inventory.TreeReference(
1299
self._new_name[trans_id],
1300
self.final_file_id(self._new_parent[trans_id]),
1301
None, self._new_reference_revision[trans_id])
1303
new_entry = inventory.make_entry(kind,
1304
self.final_name(trans_id),
1305
parent_file_id, file_id)
1306
old_path = old_paths.get(new_entry.file_id)
1307
new_executability = self._new_executability.get(trans_id)
1308
if new_executability is not None:
1309
new_entry.executable = new_executability
1310
inventory_delta.append(
1311
(old_path, path, new_entry.file_id, new_entry))
1314
return inventory_delta
1316
def _apply_removals(self, mover):
1317
"""Perform tree operations that remove directory/inventory names.
1319
That is, delete files that are to be deleted, and put any files that
1320
need renaming into limbo. This must be done in strict child-to-parent
1323
If inventory_delta is None, no inventory delta generation is performed.
1325
tree_paths = list(self._tree_path_ids.iteritems())
1326
tree_paths.sort(reverse=True)
1327
kind_changes = set()
1328
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1330
for num, data in enumerate(tree_paths):
1331
path, trans_id = data
1332
child_pb.update('removing file', num, len(tree_paths))
1333
full_path = self._tree.abspath(path)
1334
if trans_id in self._removed_contents:
1335
mover.pre_delete(full_path, os.path.join(self._deletiondir,
1337
elif trans_id in self._new_name or trans_id in \
1340
mover.rename(full_path, self._limbo_name(trans_id))
1342
if e.errno != errno.ENOENT:
1345
self.rename_count += 1
1350
def _apply_insertions(self, mover):
1351
"""Perform tree operations that insert directory/inventory names.
1353
That is, create any files that need to be created, and restore from
1354
limbo any files that needed renaming. This must be done in strict
1355
parent-to-child order.
1357
If inventory_delta is None, no inventory delta is calculated, and
1358
no list of modified paths is returned.
1360
kind_changes is a set of trans ids where the entry has changed
1361
kind, and so an inventory delta entry should be created for them.
1363
new_paths = self.new_paths(filesystem_only=True)
1365
new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1367
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1369
for num, (path, trans_id) in enumerate(new_paths):
1371
child_pb.update('adding file', num, len(new_paths))
1372
full_path = self._tree.abspath(path)
1373
if trans_id in self._needs_rename:
1375
mover.rename(self._limbo_name(trans_id), full_path)
1377
# We may be renaming a dangling inventory id
1378
if e.errno != errno.ENOENT:
1381
self.rename_count += 1
1382
if (trans_id in self._new_contents or
1383
self.path_changed(trans_id)):
1384
if trans_id in self._new_contents:
1385
modified_paths.append(full_path)
1386
if trans_id in self._new_executability:
1387
self._set_executability(path, trans_id)
1390
self._new_contents.clear()
1391
return modified_paths
1394
class TransformPreview(TreeTransformBase):
1395
"""A TreeTransform for generating preview trees.
1397
Unlike TreeTransform, this version works when the input tree is a
1398
RevisionTree, rather than a WorkingTree. As a result, it tends to ignore
1399
unversioned files in the input tree.
1402
def __init__(self, tree, pb=DummyProgress(), case_sensitive=True):
1404
limbodir = tempfile.mkdtemp(prefix='bzr-limbo-')
1405
TreeTransformBase.__init__(self, tree, limbodir, pb, case_sensitive)
1407
def canonical_path(self, path):
1410
def tree_kind(self, trans_id):
1411
path = self._tree_id_paths.get(trans_id)
1413
raise NoSuchFile(None)
1414
file_id = self._tree.path2id(path)
1415
return self._tree.kind(file_id)
1417
def _set_mode(self, trans_id, mode_id, typefunc):
1418
"""Set the mode of new file contents.
1419
The mode_id is the existing file to get the mode from (often the same
1420
as trans_id). The operation is only performed if there's a mode match
1421
according to typefunc.
1423
# is it ok to ignore this? probably
1426
def iter_tree_children(self, parent_id):
1427
"""Iterate through the entry's tree children, if any"""
1429
path = self._tree_id_paths[parent_id]
1432
file_id = self.tree_file_id(parent_id)
1435
children = getattr(self._tree.inventory[file_id], 'children', {})
1436
for child in children:
1437
childpath = joinpath(path, child)
1438
yield self.trans_id_tree_path(childpath)
1441
class _PreviewTree(tree.Tree):
1442
"""Partial implementation of Tree to support show_diff_trees"""
1444
def __init__(self, transform):
1445
self._transform = transform
1446
self._final_paths = FinalPaths(transform)
1447
self.__by_parent = None
1448
self._parent_ids = []
1450
def _changes(self, file_id):
1451
for changes in self._transform.iter_changes():
1452
if changes[0] == file_id:
1455
def _content_change(self, file_id):
1456
"""Return True if the content of this file changed"""
1457
changes = self._changes(file_id)
1458
# changes[2] is true if the file content changed. See
1459
# InterTree.iter_changes.
1460
return (changes is not None and changes[2])
1462
def _get_repository(self):
1463
repo = getattr(self._transform._tree, '_repository', None)
1465
repo = self._transform._tree.branch.repository
1468
def _iter_parent_trees(self):
1469
for revision_id in self.get_parent_ids():
1471
yield self.revision_tree(revision_id)
1472
except errors.NoSuchRevisionInTree:
1473
yield self._get_repository().revision_tree(revision_id)
1475
def _get_file_revision(self, file_id, vf, tree_revision):
1476
parent_keys = [(file_id, self._file_revision(t, file_id)) for t in
1477
self._iter_parent_trees()]
1478
vf.add_lines((file_id, tree_revision), parent_keys,
1479
self.get_file(file_id).readlines())
1480
repo = self._get_repository()
1481
base_vf = repo.texts
1482
if base_vf not in vf.fallback_versionedfiles:
1483
vf.fallback_versionedfiles.append(base_vf)
1484
return tree_revision
1486
def _stat_limbo_file(self, file_id):
1487
trans_id = self._transform.trans_id_file_id(file_id)
1488
name = self._transform._limbo_name(trans_id)
1489
return os.lstat(name)
1492
def _by_parent(self):
1493
if self.__by_parent is None:
1494
self.__by_parent = self._transform.by_parent()
1495
return self.__by_parent
1497
def lock_read(self):
1498
# Perhaps in theory, this should lock the TreeTransform?
1505
def inventory(self):
1506
"""This Tree does not use inventory as its backing data."""
1507
raise NotImplementedError(_PreviewTree.inventory)
1509
def get_root_id(self):
1510
return self._transform.final_file_id(self._transform.root)
1512
def all_file_ids(self):
1513
tree_ids = set(self._transform._tree.all_file_ids())
1514
tree_ids.difference_update(self._transform.tree_file_id(t)
1515
for t in self._transform._removed_id)
1516
tree_ids.update(self._transform._new_id.values())
1520
return iter(self.all_file_ids())
1522
def paths2ids(self, specific_files, trees=None, require_versioned=False):
1523
"""See Tree.paths2ids"""
1524
to_find = set(specific_files)
1526
for (file_id, paths, changed, versioned, parent, name, kind,
1527
executable) in self._transform.iter_changes():
1528
if paths[1] in to_find:
1529
result.append(file_id)
1530
to_find.remove(paths[1])
1531
result.update(self._transform._tree.paths2ids(to_find,
1532
trees=[], require_versioned=require_versioned))
1535
def _path2trans_id(self, path):
1536
segments = splitpath(path)
1537
cur_parent = self._transform.root
1538
for cur_segment in segments:
1539
for child in self._all_children(cur_parent):
1540
if self._transform.final_name(child) == cur_segment:
1547
def path2id(self, path):
1548
return self._transform.final_file_id(self._path2trans_id(path))
1550
def id2path(self, file_id):
1551
trans_id = self._transform.trans_id_file_id(file_id)
1553
return self._final_paths._determine_path(trans_id)
1555
raise errors.NoSuchId(self, file_id)
1557
def _all_children(self, trans_id):
1558
children = set(self._transform.iter_tree_children(trans_id))
1559
# children in the _new_parent set are provided by _by_parent.
1560
children.difference_update(self._transform._new_parent.keys())
1561
children.update(self._by_parent.get(trans_id, []))
1564
def _make_inv_entries(self, ordered_entries, specific_file_ids):
1565
for trans_id, parent_file_id in ordered_entries:
1566
file_id = self._transform.final_file_id(trans_id)
1569
if (specific_file_ids is not None
1570
and file_id not in specific_file_ids):
1573
kind = self._transform.final_kind(trans_id)
1575
kind = self._transform._tree.stored_kind(file_id)
1576
new_entry = inventory.make_entry(
1578
self._transform.final_name(trans_id),
1579
parent_file_id, file_id)
1580
yield new_entry, trans_id
1582
def iter_entries_by_dir(self, specific_file_ids=None):
1583
# This may not be a maximally efficient implementation, but it is
1584
# reasonably straightforward. An implementation that grafts the
1585
# TreeTransform changes onto the tree's iter_entries_by_dir results
1586
# might be more efficient, but requires tricky inferences about stack
1588
todo = [ROOT_PARENT]
1590
while len(todo) > 0:
1592
parent_file_id = self._transform.final_file_id(parent)
1593
children = list(self._all_children(parent))
1594
paths = dict(zip(children, self._final_paths.get_paths(children)))
1595
children.sort(key=paths.get)
1596
todo.extend(reversed(children))
1597
for trans_id in children:
1598
ordered_ids.append((trans_id, parent_file_id))
1599
for entry, trans_id in self._make_inv_entries(ordered_ids,
1601
yield unicode(self._final_paths.get_path(trans_id)), entry
1603
def kind(self, file_id):
1604
trans_id = self._transform.trans_id_file_id(file_id)
1605
return self._transform.final_kind(trans_id)
1607
def stored_kind(self, file_id):
1608
trans_id = self._transform.trans_id_file_id(file_id)
1610
return self._transform._new_contents[trans_id]
1612
return self._transform._tree.stored_kind(file_id)
1614
def get_file_mtime(self, file_id, path=None):
1615
"""See Tree.get_file_mtime"""
1616
if not self._content_change(file_id):
1617
return self._transform._tree.get_file_mtime(file_id, path)
1618
return self._stat_limbo_file(file_id).st_mtime
1620
def get_file_size(self, file_id):
1621
"""See Tree.get_file_size"""
1622
if self.kind(file_id) == 'file':
1623
return self._transform._tree.get_file_size(file_id)
1627
def get_file_sha1(self, file_id, path=None, stat_value=None):
1628
return self._transform._tree.get_file_sha1(file_id)
1630
def is_executable(self, file_id, path=None):
1631
trans_id = self._transform.trans_id_file_id(file_id)
1633
return self._transform._new_executability[trans_id]
1635
return self._transform._tree.is_executable(file_id, path)
1637
def path_content_summary(self, path):
1638
trans_id = self._path2trans_id(path)
1639
tt = self._transform
1640
tree_path = tt._tree_id_paths.get(trans_id)
1641
kind = tt._new_contents.get(trans_id)
1643
if tree_path is None or trans_id in tt._removed_contents:
1644
return 'missing', None, None, None
1645
summary = tt._tree.path_content_summary(tree_path)
1646
kind, size, executable, link_or_sha1 = summary
1649
limbo_name = tt._limbo_name(trans_id)
1650
if trans_id in tt._new_reference_revision:
1651
kind = 'tree-reference'
1653
statval = os.lstat(limbo_name)
1654
size = statval.st_size
1655
if not supports_executable():
1658
executable = statval.st_mode & S_IEXEC
1662
if kind == 'symlink':
1663
link_or_sha1 = os.readlink(limbo_name)
1664
if supports_executable():
1665
executable = tt._new_executability.get(trans_id, executable)
1666
return kind, size, executable, link_or_sha1
1668
def iter_changes(self, from_tree, include_unchanged=False,
1669
specific_files=None, pb=None, extra_trees=None,
1670
require_versioned=True, want_unversioned=False):
1671
"""See InterTree.iter_changes.
1673
This implementation does not support include_unchanged, specific_files,
1674
or want_unversioned. extra_trees, require_versioned, and pb are
1677
if from_tree is not self._transform._tree:
1678
raise ValueError('from_tree must be transform source tree.')
1679
if include_unchanged:
1680
raise ValueError('include_unchanged is not supported')
1681
if specific_files is not None:
1682
raise ValueError('specific_files is not supported')
1683
if want_unversioned:
1684
raise ValueError('want_unversioned is not supported')
1685
return self._transform.iter_changes()
1687
def get_file(self, file_id, path=None):
1688
"""See Tree.get_file"""
1689
if not self._content_change(file_id):
1690
return self._transform._tree.get_file(file_id, path)
1691
trans_id = self._transform.trans_id_file_id(file_id)
1692
name = self._transform._limbo_name(trans_id)
1693
return open(name, 'rb')
1695
def get_file_text(self, file_id):
1696
text_file = self.get_file(file_id)
1698
return text_file.read()
1702
def annotate_iter(self, file_id,
1703
default_revision=_mod_revision.CURRENT_REVISION):
1704
changes = self._changes(file_id)
1708
changed_content, versioned, kind = (changes[2], changes[3],
1712
get_old = (kind[0] == 'file' and versioned[0])
1714
old_annotation = self._transform._tree.annotate_iter(file_id,
1715
default_revision=default_revision)
1719
return old_annotation
1720
if not changed_content:
1721
return old_annotation
1722
return annotate.reannotate([old_annotation],
1723
self.get_file(file_id).readlines(),
1726
def get_symlink_target(self, file_id):
1727
"""See Tree.get_symlink_target"""
1728
if not self._content_change(file_id):
1729
return self._transform._tree.get_symlink_target(file_id)
1730
trans_id = self._transform.trans_id_file_id(file_id)
1731
name = self._transform._limbo_name(trans_id)
1732
return os.readlink(name)
1734
def list_files(self, include_root=False):
1735
return self._transform._tree.list_files(include_root)
1737
def walkdirs(self, prefix=""):
1738
return self._transform._tree.walkdirs(prefix)
1740
def get_parent_ids(self):
1741
return self._parent_ids
1743
def set_parent_ids(self, parent_ids):
1744
self._parent_ids = parent_ids
1746
def get_revision_tree(self, revision_id):
1747
return self._transform._tree.get_revision_tree(revision_id)
885
1750
def joinpath(parent, child):
886
1751
"""Join tree-relative paths, handling the tree root specially"""
887
1752
if parent is None or parent == "":
917
1782
self._known_paths[trans_id] = self._determine_path(trans_id)
918
1783
return self._known_paths[trans_id]
1785
def get_paths(self, trans_ids):
1786
return [(self.get_path(t), t) for t in trans_ids]
920
1790
def topology_sorted_ids(tree):
921
1791
"""Determine the topological order of the ids in a tree"""
922
1792
file_ids = list(tree)
923
1793
file_ids.sort(key=tree.id2path)
926
def build_tree(tree, wt):
927
"""Create working tree for a branch, using a Transaction."""
1797
def build_tree(tree, wt, accelerator_tree=None, hardlink=False,
1798
delta_from_tree=False):
1799
"""Create working tree for a branch, using a TreeTransform.
1801
This function should be used on empty trees, having a tree root at most.
1802
(see merge and revert functionality for working with existing trees)
1804
Existing files are handled like so:
1806
- Existing bzrdirs take precedence over creating new items. They are
1807
created as '%s.diverted' % name.
1808
- Otherwise, if the content on disk matches the content we are building,
1809
it is silently replaced.
1810
- Otherwise, conflict resolution will move the old file to 'oldname.moved'.
1812
:param tree: The tree to convert wt into a copy of
1813
:param wt: The working tree that files will be placed into
1814
:param accelerator_tree: A tree which can be used for retrieving file
1815
contents more quickly than tree itself, i.e. a workingtree. tree
1816
will be used for cases where accelerator_tree's content is different.
1817
:param hardlink: If true, hard-link files to accelerator_tree, where
1818
possible. accelerator_tree must implement abspath, i.e. be a
1820
:param delta_from_tree: If true, build_tree may use the input Tree to
1821
generate the inventory delta.
1823
wt.lock_tree_write()
1827
if accelerator_tree is not None:
1828
accelerator_tree.lock_read()
1830
return _build_tree(tree, wt, accelerator_tree, hardlink,
1833
if accelerator_tree is not None:
1834
accelerator_tree.unlock()
1841
def _build_tree(tree, wt, accelerator_tree, hardlink, delta_from_tree):
1842
"""See build_tree."""
1843
for num, _unused in enumerate(wt.all_file_ids()):
1844
if num > 0: # more than just a root
1845
raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
1846
existing_files = set()
1847
for dir, files in wt.walkdirs():
1848
existing_files.update(f[0] for f in files)
928
1849
file_trans_id = {}
929
1850
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
930
1851
pp = ProgressPhase("Build phase", 2, top_pb)
1852
if tree.inventory.root is not None:
1853
# This is kind of a hack: we should be altering the root
1854
# as part of the regular tree shape diff logic.
1855
# The conditional test here is to avoid doing an
1856
# expensive operation (flush) every time the root id
1857
# is set within the tree, nor setting the root and thus
1858
# marking the tree as dirty, because we use two different
1859
# idioms here: tree interfaces and inventory interfaces.
1860
if wt.get_root_id() != tree.get_root_id():
1861
wt.set_root_id(tree.get_root_id())
931
1863
tt = TreeTransform(wt)
934
file_trans_id[wt.get_root_id()] = tt.trans_id_tree_file_id(wt.get_root_id())
935
file_ids = topology_sorted_ids(tree)
1867
file_trans_id[wt.get_root_id()] = \
1868
tt.trans_id_tree_file_id(wt.get_root_id())
936
1869
pb = bzrlib.ui.ui_factory.nested_progress_bar()
938
for num, file_id in enumerate(file_ids):
939
pb.update("Building tree", num, len(file_ids))
940
entry = tree.inventory[file_id]
1871
deferred_contents = []
1873
total = len(tree.inventory)
1875
precomputed_delta = []
1877
precomputed_delta = None
1878
for num, (tree_path, entry) in \
1879
enumerate(tree.inventory.iter_entries_by_dir()):
1880
pb.update("Building tree", num - len(deferred_contents), total)
941
1881
if entry.parent_id is None:
943
if entry.parent_id not in file_trans_id:
944
raise repr(entry.parent_id)
1884
file_id = entry.file_id
1886
precomputed_delta.append((None, tree_path, file_id, entry))
1887
if tree_path in existing_files:
1888
target_path = wt.abspath(tree_path)
1889
kind = file_kind(target_path)
1890
if kind == "directory":
1892
bzrdir.BzrDir.open(target_path)
1893
except errors.NotBranchError:
1897
if (file_id not in divert and
1898
_content_match(tree, entry, file_id, kind,
1900
tt.delete_contents(tt.trans_id_tree_path(tree_path))
1901
if kind == 'directory':
945
1903
parent_id = file_trans_id[entry.parent_id]
946
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1904
if entry.kind == 'file':
1905
# We *almost* replicate new_by_entry, so that we can defer
1906
# getting the file text, and get them all at once.
1907
trans_id = tt.create_path(entry.name, parent_id)
1908
file_trans_id[file_id] = trans_id
1909
tt.version_file(file_id, trans_id)
1910
executable = tree.is_executable(file_id, tree_path)
1912
tt.set_executability(executable, trans_id)
1913
deferred_contents.append((file_id, trans_id))
1915
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1918
new_trans_id = file_trans_id[file_id]
1919
old_parent = tt.trans_id_tree_path(tree_path)
1920
_reparent_children(tt, old_parent, new_trans_id)
1921
offset = num + 1 - len(deferred_contents)
1922
_create_files(tt, tree, deferred_contents, pb, offset,
1923
accelerator_tree, hardlink)
1927
divert_trans = set(file_trans_id[f] for f in divert)
1928
resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
1929
raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
1930
if len(raw_conflicts) > 0:
1931
precomputed_delta = None
1932
conflicts = cook_conflicts(raw_conflicts, tt)
1933
for conflict in conflicts:
1936
wt.add_conflicts(conflicts)
1937
except errors.UnsupportedOperation:
1939
result = tt.apply(no_conflicts=True,
1940
precomputed_delta=precomputed_delta)
954
1943
top_pb.finished()
1947
def _create_files(tt, tree, desired_files, pb, offset, accelerator_tree,
1949
total = len(desired_files) + offset
1950
if accelerator_tree is None:
1951
new_desired_files = desired_files
1953
iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
1954
unchanged = dict((f, p[1]) for (f, p, c, v, d, n, k, e)
1955
in iter if not (c or e[0] != e[1]))
1956
new_desired_files = []
1958
for file_id, trans_id in desired_files:
1959
accelerator_path = unchanged.get(file_id)
1960
if accelerator_path is None:
1961
new_desired_files.append((file_id, trans_id))
1963
pb.update('Adding file contents', count + offset, total)
1965
tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
1968
contents = accelerator_tree.get_file(file_id, accelerator_path)
1970
tt.create_file(contents, trans_id)
1975
for count, (trans_id, contents) in enumerate(tree.iter_files_bytes(
1976
new_desired_files)):
1977
tt.create_file(contents, trans_id)
1978
pb.update('Adding file contents', count + offset, total)
1981
def _reparent_children(tt, old_parent, new_parent):
1982
for child in tt.iter_tree_children(old_parent):
1983
tt.adjust_path(tt.final_name(child), new_parent, child)
1985
def _reparent_transform_children(tt, old_parent, new_parent):
1986
by_parent = tt.by_parent()
1987
for child in by_parent[old_parent]:
1988
tt.adjust_path(tt.final_name(child), new_parent, child)
1989
return by_parent[old_parent]
1991
def _content_match(tree, entry, file_id, kind, target_path):
1992
if entry.kind != kind:
1994
if entry.kind == "directory":
1996
if entry.kind == "file":
1997
if tree.get_file(file_id).read() == file(target_path, 'rb').read():
1999
elif entry.kind == "symlink":
2000
if tree.get_symlink_target(file_id) == os.readlink(target_path):
2005
def resolve_checkout(tt, conflicts, divert):
2006
new_conflicts = set()
2007
for c_type, conflict in ((c[0], c) for c in conflicts):
2008
# Anything but a 'duplicate' would indicate programmer error
2009
if c_type != 'duplicate':
2010
raise AssertionError(c_type)
2011
# Now figure out which is new and which is old
2012
if tt.new_contents(conflict[1]):
2013
new_file = conflict[1]
2014
old_file = conflict[2]
2016
new_file = conflict[2]
2017
old_file = conflict[1]
2019
# We should only get here if the conflict wasn't completely
2021
final_parent = tt.final_parent(old_file)
2022
if new_file in divert:
2023
new_name = tt.final_name(old_file)+'.diverted'
2024
tt.adjust_path(new_name, final_parent, new_file)
2025
new_conflicts.add((c_type, 'Diverted to',
2026
new_file, old_file))
2028
new_name = tt.final_name(old_file)+'.moved'
2029
tt.adjust_path(new_name, final_parent, old_file)
2030
new_conflicts.add((c_type, 'Moved existing file to',
2031
old_file, new_file))
2032
return new_conflicts
956
2035
def new_by_entry(tt, entry, parent_id, tree):
957
2036
"""Create a new file according to its inventory entry"""
1086
2114
return has_contents, contents_mod, meta_mod
1089
def revert(working_tree, target_tree, filenames, backups=False,
1090
pb=DummyProgress()):
2117
def revert(working_tree, target_tree, filenames, backups=False,
2118
pb=DummyProgress(), change_reporter=None):
1091
2119
"""Revert a working tree's contents to those of a target tree."""
1092
interesting_ids = find_interesting(working_tree, target_tree, filenames)
1093
def interesting(file_id):
1094
return interesting_ids is None or file_id in interesting_ids
2120
target_tree.lock_read()
1096
2121
tt = TreeTransform(working_tree, pb)
1098
merge_modified = working_tree.merge_modified()
1100
def trans_id_file_id(file_id):
1102
return trans_id[file_id]
1104
return tt.trans_id_tree_file_id(file_id)
1106
pp = ProgressPhase("Revert phase", 4, pb)
1108
sorted_interesting = [i for i in topology_sorted_ids(target_tree) if
1110
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1112
by_parent = tt.by_parent()
1113
for id_num, file_id in enumerate(sorted_interesting):
1114
child_pb.update("Reverting file", id_num+1,
1115
len(sorted_interesting))
1116
if file_id not in working_tree.inventory:
1117
entry = target_tree.inventory[file_id]
1118
parent_id = trans_id_file_id(entry.parent_id)
1119
e_trans_id = new_by_entry(tt, entry, parent_id, target_tree)
1120
trans_id[file_id] = e_trans_id
1122
backup_this = backups
1123
if file_id in merge_modified:
1125
del merge_modified[file_id]
1126
change_entry(tt, file_id, working_tree, target_tree,
1127
trans_id_file_id, backup_this, trans_id,
1132
wt_interesting = [i for i in working_tree.inventory if interesting(i)]
1133
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1135
for id_num, file_id in enumerate(wt_interesting):
1136
child_pb.update("New file check", id_num+1,
1137
len(sorted_interesting))
1138
if file_id not in target_tree:
1139
trans_id = tt.trans_id_tree_file_id(file_id)
1140
tt.unversion_file(trans_id)
1141
if file_id in merge_modified:
2123
pp = ProgressPhase("Revert phase", 3, pb)
2125
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2127
merge_modified = _alter_files(working_tree, target_tree, tt,
2128
child_pb, filenames, backups)
2132
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2134
raw_conflicts = resolve_conflicts(tt, child_pb,
2135
lambda t, c: conflict_pass(t, c, target_tree))
2138
conflicts = cook_conflicts(raw_conflicts, tt)
2140
change_reporter = delta._ChangeReporter(
2141
unversioned_filter=working_tree.is_ignored)
2142
delta.report_changes(tt.iter_changes(), change_reporter)
2143
for conflict in conflicts:
2147
working_tree.set_merge_modified(merge_modified)
2149
target_tree.unlock()
2155
def _alter_files(working_tree, target_tree, tt, pb, specific_files,
2157
merge_modified = working_tree.merge_modified()
2158
change_list = target_tree.iter_changes(working_tree,
2159
specific_files=specific_files, pb=pb)
2160
if target_tree.inventory.root is None:
2167
for id_num, (file_id, path, changed_content, versioned, parent, name,
2168
kind, executable) in enumerate(change_list):
2169
if skip_root and file_id[0] is not None and parent[0] is None:
2171
trans_id = tt.trans_id_file_id(file_id)
2174
keep_content = False
2175
if kind[0] == 'file' and (backups or kind[1] is None):
2176
wt_sha1 = working_tree.get_file_sha1(file_id)
2177
if merge_modified.get(file_id) != wt_sha1:
2178
# acquire the basis tree lazily to prevent the
2179
# expense of accessing it when it's not needed ?
2180
# (Guessing, RBC, 200702)
2181
if basis_tree is None:
2182
basis_tree = working_tree.basis_tree()
2183
basis_tree.lock_read()
2184
if file_id in basis_tree:
2185
if wt_sha1 != basis_tree.get_file_sha1(file_id):
2187
elif kind[1] is None and not versioned[1]:
2189
if kind[0] is not None:
2190
if not keep_content:
1142
2191
tt.delete_contents(trans_id)
1143
del merge_modified[file_id]
1147
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1149
raw_conflicts = resolve_conflicts(tt, child_pb)
1152
conflicts = cook_conflicts(raw_conflicts, tt)
1153
for conflict in conflicts:
1157
working_tree.set_merge_modified({})
2192
elif kind[1] is not None:
2193
parent_trans_id = tt.trans_id_file_id(parent[0])
2194
by_parent = tt.by_parent()
2195
backup_name = _get_backup_name(name[0], by_parent,
2196
parent_trans_id, tt)
2197
tt.adjust_path(backup_name, parent_trans_id, trans_id)
2198
new_trans_id = tt.create_path(name[0], parent_trans_id)
2199
if versioned == (True, True):
2200
tt.unversion_file(trans_id)
2201
tt.version_file(file_id, new_trans_id)
2202
# New contents should have the same unix perms as old
2205
trans_id = new_trans_id
2206
if kind[1] == 'directory':
2207
tt.create_directory(trans_id)
2208
elif kind[1] == 'symlink':
2209
tt.create_symlink(target_tree.get_symlink_target(file_id),
2211
elif kind[1] == 'file':
2212
deferred_files.append((file_id, (trans_id, mode_id)))
2213
if basis_tree is None:
2214
basis_tree = working_tree.basis_tree()
2215
basis_tree.lock_read()
2216
new_sha1 = target_tree.get_file_sha1(file_id)
2217
if (file_id in basis_tree and new_sha1 ==
2218
basis_tree.get_file_sha1(file_id)):
2219
if file_id in merge_modified:
2220
del merge_modified[file_id]
2222
merge_modified[file_id] = new_sha1
2224
# preserve the execute bit when backing up
2225
if keep_content and executable[0] == executable[1]:
2226
tt.set_executability(executable[1], trans_id)
2227
elif kind[1] is not None:
2228
raise AssertionError(kind[1])
2229
if versioned == (False, True):
2230
tt.version_file(file_id, trans_id)
2231
if versioned == (True, False):
2232
tt.unversion_file(trans_id)
2233
if (name[1] is not None and
2234
(name[0] != name[1] or parent[0] != parent[1])):
2236
name[1], tt.trans_id_file_id(parent[1]), trans_id)
2237
if executable[0] != executable[1] and kind[1] == "file":
2238
tt.set_executability(executable[1], trans_id)
2239
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2241
tt.create_file(bytes, trans_id, mode_id)
1164
def resolve_conflicts(tt, pb=DummyProgress()):
2243
if basis_tree is not None:
2245
return merge_modified
2248
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
1165
2249
"""Make many conflict-resolution attempts, but die if they fail"""
2250
if pass_func is None:
2251
pass_func = conflict_pass
1166
2252
new_conflicts = set()
1168
2254
for n in range(10):