62
63
self.rename_count = rename_count
65
class TreeTransform(object):
66
"""Represent a tree transformation.
68
This object is designed to support incremental generation of the transform,
71
However, it gives optimum performance when parent directories are created
72
before their contents. The transform is then able to put child files
73
directly in their parent directory, avoiding later renames.
75
It is easy to produce malformed transforms, but they are generally
76
harmless. Attempting to apply a malformed transform will cause an
77
exception to be raised before any modifications are made to the tree.
79
Many kinds of malformed transforms can be corrected with the
80
resolve_conflicts function. The remaining ones indicate programming error,
81
such as trying to create a file with no path.
83
Two sets of file creation methods are supplied. Convenience methods are:
88
These are composed of the low-level methods:
90
* create_file or create_directory or create_symlink
94
Transform/Transaction ids
95
-------------------------
96
trans_ids are temporary ids assigned to all files involved in a transform.
97
It's possible, even common, that not all files in the Tree have trans_ids.
99
trans_ids are used because filenames and file_ids are not good enough
100
identifiers; filenames change, and not all files have file_ids. File-ids
101
are also associated with trans-ids, so that moving a file moves its
104
trans_ids are only valid for the TreeTransform that generated them.
108
Limbo is a temporary directory use to hold new versions of files.
109
Files are added to limbo by create_file, create_directory, create_symlink,
110
and their convenience variants (new_*). Files may be removed from limbo
111
using cancel_creation. Files are renamed from limbo into their final
112
location as part of TreeTransform.apply
114
Limbo must be cleaned up, by either calling TreeTransform.apply or
115
calling TreeTransform.finalize.
117
Files are placed into limbo inside their parent directories, where
118
possible. This reduces subsequent renames, and makes operations involving
119
lots of files faster. This optimization is only possible if the parent
120
directory is created *before* creating any of its children, so avoid
121
creating children before parents, where possible.
125
This temporary directory is used by _FileMover for storing files that are
126
about to be deleted. In case of rollback, the files will be restored.
127
FileMover does not delete files until it is sure that a rollback will not
130
def __init__(self, tree, pb=DummyProgress()):
131
"""Note: a tree_write lock is taken on the tree.
133
Use TreeTransform.finalize() to release the lock (can be omitted if
134
TreeTransform.apply() called).
66
class TreeTransformBase(object):
67
"""The base class for TreeTransform and TreeTransformBase"""
69
def __init__(self, tree, limbodir, pb=DummyProgress(),
73
:param tree: The tree that will be transformed, but not necessarily
75
:param limbodir: A directory where new files can be stored until
76
they are installed in their proper places
77
:param pb: A ProgressBar indicating how much progress is being made
78
:param case_sensitive: If True, the target of the transform is
79
case sensitive, not just case preserving.
136
81
object.__init__(self)
138
self._tree.lock_tree_write()
140
control_files = self._tree._control_files
141
self._limbodir = urlutils.local_path_from_url(
142
control_files.controlfilename('limbo'))
144
os.mkdir(self._limbodir)
146
if e.errno == errno.EEXIST:
147
raise ExistingLimbo(self._limbodir)
148
self._deletiondir = urlutils.local_path_from_url(
149
control_files.controlfilename('pending-deletion'))
151
os.mkdir(self._deletiondir)
153
if e.errno == errno.EEXIST:
154
raise errors.ExistingPendingDeletion(self._deletiondir)
160
# counter used to generate trans-ids (which are locally unique)
83
self._limbodir = limbodir
161
84
self._id_number = 0
162
85
# mapping of trans_id -> new basename
163
86
self._new_name = {}
953
836
self._limbo_files[trans_id] = limbo_name
954
837
return limbo_name
956
def _apply_removals(self, inv, inventory_delta, mover):
957
"""Perform tree operations that remove directory/inventory names.
959
That is, delete files that are to be deleted, and put any files that
960
need renaming into limbo. This must be done in strict child-to-parent
963
tree_paths = list(self._tree_path_ids.iteritems())
964
tree_paths.sort(reverse=True)
965
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
967
for num, data in enumerate(tree_paths):
968
path, trans_id = data
969
child_pb.update('removing file', num, len(tree_paths))
970
full_path = self._tree.abspath(path)
971
if trans_id in self._removed_contents:
972
mover.pre_delete(full_path, os.path.join(self._deletiondir,
974
elif trans_id in self._new_name or trans_id in \
977
mover.rename(full_path, self._limbo_name(trans_id))
979
if e.errno != errno.ENOENT:
982
self.rename_count += 1
983
if trans_id in self._removed_id:
984
if trans_id == self._new_root:
985
file_id = self._tree.get_root_id()
987
file_id = self.tree_file_id(trans_id)
988
if file_id is not None:
989
inventory_delta.append((path, None, file_id, None))
993
def _apply_insertions(self, inv, inventory_delta, mover):
994
"""Perform tree operations that insert directory/inventory names.
996
That is, create any files that need to be created, and restore from
997
limbo any files that needed renaming. This must be done in strict
998
parent-to-child order.
1000
new_paths = self.new_paths()
1002
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1005
for num, (path, trans_id) in enumerate(new_paths):
1007
child_pb.update('adding file', num, len(new_paths))
1009
kind = self._new_contents[trans_id]
1011
kind = contents = None
1012
if trans_id in self._new_contents or \
1013
self.path_changed(trans_id):
1014
full_path = self._tree.abspath(path)
1015
if trans_id in self._needs_rename:
1017
mover.rename(self._limbo_name(trans_id), full_path)
1019
# We may be renaming a dangling inventory id
1020
if e.errno != errno.ENOENT:
1023
self.rename_count += 1
1024
if trans_id in self._new_contents:
1025
modified_paths.append(full_path)
1026
completed_new.append(trans_id)
1028
if trans_id in self._new_id:
1030
kind = file_kind(self._tree.abspath(path))
1031
if trans_id in self._new_reference_revision:
1032
new_entry = inventory.TreeReference(
1033
self._new_id[trans_id],
1034
self._new_name[trans_id],
1035
self.final_file_id(self._new_parent[trans_id]),
1036
None, self._new_reference_revision[trans_id])
1038
new_entry = inventory.make_entry(kind,
1039
self.final_name(trans_id),
1040
self.final_file_id(self.final_parent(trans_id)),
1041
self._new_id[trans_id])
1043
if trans_id in self._new_name or trans_id in\
1044
self._new_parent or\
1045
trans_id in self._new_executability:
1046
file_id = self.final_file_id(trans_id)
1047
if file_id is not None:
1048
entry = inv[file_id]
1049
new_entry = entry.copy()
1051
if trans_id in self._new_name or trans_id in\
1053
if new_entry is not None:
1054
new_entry.name = self.final_name(trans_id)
1055
parent = self.final_parent(trans_id)
1056
parent_id = self.final_file_id(parent)
1057
new_entry.parent_id = parent_id
1059
if trans_id in self._new_executability:
1060
self._set_executability(path, new_entry, trans_id)
1061
if new_entry is not None:
1062
if new_entry.file_id in inv:
1063
old_path = inv.id2path(new_entry.file_id)
1066
inventory_delta.append((old_path, path,
1071
for trans_id in completed_new:
1072
del self._new_contents[trans_id]
1073
return modified_paths
1075
839
def _set_executability(self, path, entry, trans_id):
1076
840
"""Set the executability of versioned files """
1077
841
new_executability = self._new_executability[trans_id]
1281
1045
(from_executable, to_executable)))
1282
1046
return iter(sorted(results, key=lambda x:x[1]))
1048
def get_preview_tree(self):
1049
"""Return a tree representing the result of the transform.
1051
This tree only supports the subset of Tree functionality required
1052
by show_diff_trees. It must only be compared to tt._tree.
1054
return _PreviewTree(self)
1057
class TreeTransform(TreeTransformBase):
1058
"""Represent a tree transformation.
1060
This object is designed to support incremental generation of the transform,
1063
However, it gives optimum performance when parent directories are created
1064
before their contents. The transform is then able to put child files
1065
directly in their parent directory, avoiding later renames.
1067
It is easy to produce malformed transforms, but they are generally
1068
harmless. Attempting to apply a malformed transform will cause an
1069
exception to be raised before any modifications are made to the tree.
1071
Many kinds of malformed transforms can be corrected with the
1072
resolve_conflicts function. The remaining ones indicate programming error,
1073
such as trying to create a file with no path.
1075
Two sets of file creation methods are supplied. Convenience methods are:
1080
These are composed of the low-level methods:
1082
* create_file or create_directory or create_symlink
1086
Transform/Transaction ids
1087
-------------------------
1088
trans_ids are temporary ids assigned to all files involved in a transform.
1089
It's possible, even common, that not all files in the Tree have trans_ids.
1091
trans_ids are used because filenames and file_ids are not good enough
1092
identifiers; filenames change, and not all files have file_ids. File-ids
1093
are also associated with trans-ids, so that moving a file moves its
1096
trans_ids are only valid for the TreeTransform that generated them.
1100
Limbo is a temporary directory use to hold new versions of files.
1101
Files are added to limbo by create_file, create_directory, create_symlink,
1102
and their convenience variants (new_*). Files may be removed from limbo
1103
using cancel_creation. Files are renamed from limbo into their final
1104
location as part of TreeTransform.apply
1106
Limbo must be cleaned up, by either calling TreeTransform.apply or
1107
calling TreeTransform.finalize.
1109
Files are placed into limbo inside their parent directories, where
1110
possible. This reduces subsequent renames, and makes operations involving
1111
lots of files faster. This optimization is only possible if the parent
1112
directory is created *before* creating any of its children, so avoid
1113
creating children before parents, where possible.
1117
This temporary directory is used by _FileMover for storing files that are
1118
about to be deleted. In case of rollback, the files will be restored.
1119
FileMover does not delete files until it is sure that a rollback will not
1122
def __init__(self, tree, pb=DummyProgress()):
1123
"""Note: a tree_write lock is taken on the tree.
1125
Use TreeTransform.finalize() to release the lock (can be omitted if
1126
TreeTransform.apply() called).
1128
tree.lock_tree_write()
1131
control_files = tree._control_files
1132
limbodir = urlutils.local_path_from_url(
1133
control_files.controlfilename('limbo'))
1137
if e.errno == errno.EEXIST:
1138
raise ExistingLimbo(limbodir)
1139
self._deletiondir = urlutils.local_path_from_url(
1140
control_files.controlfilename('pending-deletion'))
1142
os.mkdir(self._deletiondir)
1144
if e.errno == errno.EEXIST:
1145
raise errors.ExistingPendingDeletion(self._deletiondir)
1150
TreeTransformBase.__init__(self, tree, limbodir, pb,
1151
tree.case_sensitive)
1153
def apply(self, no_conflicts=False, _mover=None):
1154
"""Apply all changes to the inventory and filesystem.
1156
If filesystem or inventory conflicts are present, MalformedTransform
1159
If apply succeeds, finalize is not necessary.
1161
:param no_conflicts: if True, the caller guarantees there are no
1162
conflicts, so no check is made.
1163
:param _mover: Supply an alternate FileMover, for testing
1165
if not no_conflicts:
1166
conflicts = self.find_conflicts()
1167
if len(conflicts) != 0:
1168
raise MalformedTransform(conflicts=conflicts)
1169
inv = self._tree.inventory
1170
inventory_delta = []
1171
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1174
mover = _FileMover()
1178
child_pb.update('Apply phase', 0, 2)
1179
self._apply_removals(inv, inventory_delta, mover)
1180
child_pb.update('Apply phase', 1, 2)
1181
modified_paths = self._apply_insertions(inv, inventory_delta,
1187
mover.apply_deletions()
1190
self._tree.apply_inventory_delta(inventory_delta)
1193
return _TransformResults(modified_paths, self.rename_count)
1195
def _apply_removals(self, inv, inventory_delta, mover):
1196
"""Perform tree operations that remove directory/inventory names.
1198
That is, delete files that are to be deleted, and put any files that
1199
need renaming into limbo. This must be done in strict child-to-parent
1202
tree_paths = list(self._tree_path_ids.iteritems())
1203
tree_paths.sort(reverse=True)
1204
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1206
for num, data in enumerate(tree_paths):
1207
path, trans_id = data
1208
child_pb.update('removing file', num, len(tree_paths))
1209
full_path = self._tree.abspath(path)
1210
if trans_id in self._removed_contents:
1211
mover.pre_delete(full_path, os.path.join(self._deletiondir,
1213
elif trans_id in self._new_name or trans_id in \
1216
mover.rename(full_path, self._limbo_name(trans_id))
1218
if e.errno != errno.ENOENT:
1221
self.rename_count += 1
1222
if trans_id in self._removed_id:
1223
if trans_id == self._new_root:
1224
file_id = self._tree.get_root_id()
1226
file_id = self.tree_file_id(trans_id)
1227
if file_id is not None:
1228
inventory_delta.append((path, None, file_id, None))
1232
def _apply_insertions(self, inv, inventory_delta, mover):
1233
"""Perform tree operations that insert directory/inventory names.
1235
That is, create any files that need to be created, and restore from
1236
limbo any files that needed renaming. This must be done in strict
1237
parent-to-child order.
1239
new_paths = self.new_paths()
1241
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1244
for num, (path, trans_id) in enumerate(new_paths):
1246
child_pb.update('adding file', num, len(new_paths))
1248
kind = self._new_contents[trans_id]
1250
kind = contents = None
1251
if trans_id in self._new_contents or \
1252
self.path_changed(trans_id):
1253
full_path = self._tree.abspath(path)
1254
if trans_id in self._needs_rename:
1256
mover.rename(self._limbo_name(trans_id), full_path)
1258
# We may be renaming a dangling inventory id
1259
if e.errno != errno.ENOENT:
1262
self.rename_count += 1
1263
if trans_id in self._new_contents:
1264
modified_paths.append(full_path)
1265
completed_new.append(trans_id)
1267
if trans_id in self._new_id:
1269
kind = file_kind(self._tree.abspath(path))
1270
if trans_id in self._new_reference_revision:
1271
new_entry = inventory.TreeReference(
1272
self._new_id[trans_id],
1273
self._new_name[trans_id],
1274
self.final_file_id(self._new_parent[trans_id]),
1275
None, self._new_reference_revision[trans_id])
1277
new_entry = inventory.make_entry(kind,
1278
self.final_name(trans_id),
1279
self.final_file_id(self.final_parent(trans_id)),
1280
self._new_id[trans_id])
1282
if trans_id in self._new_name or trans_id in\
1283
self._new_parent or\
1284
trans_id in self._new_executability:
1285
file_id = self.final_file_id(trans_id)
1286
if file_id is not None:
1287
entry = inv[file_id]
1288
new_entry = entry.copy()
1290
if trans_id in self._new_name or trans_id in\
1292
if new_entry is not None:
1293
new_entry.name = self.final_name(trans_id)
1294
parent = self.final_parent(trans_id)
1295
parent_id = self.final_file_id(parent)
1296
new_entry.parent_id = parent_id
1298
if trans_id in self._new_executability:
1299
self._set_executability(path, new_entry, trans_id)
1300
if new_entry is not None:
1301
if new_entry.file_id in inv:
1302
old_path = inv.id2path(new_entry.file_id)
1305
inventory_delta.append((old_path, path,
1310
for trans_id in completed_new:
1311
del self._new_contents[trans_id]
1312
return modified_paths
1315
class TransformPreview(TreeTransformBase):
1316
"""A TreeTransform for generating preview trees.
1318
Unlike TreeTransform, this version works when the input tree is a
1319
RevisionTree, rather than a WorkingTree. As a result, it tends to ignore
1320
unversioned files in the input tree.
1323
def __init__(self, tree, pb=DummyProgress(), case_sensitive=True):
1324
limbodir = tempfile.mkdtemp()
1325
TreeTransformBase.__init__(self, tree, limbodir, pb, case_sensitive)
1327
def canonical_path(self, path):
1330
def tree_kind(self, trans_id):
1331
path = self._tree_id_paths.get(trans_id)
1333
raise NoSuchFile(None)
1334
file_id = self._tree.path2id(path)
1335
return self._tree.kind(file_id)
1337
def _set_mode(self, trans_id, mode_id, typefunc):
1338
"""Set the mode of new file contents.
1339
The mode_id is the existing file to get the mode from (often the same
1340
as trans_id). The operation is only performed if there's a mode match
1341
according to typefunc.
1343
# is it ok to ignore this? probably
1346
def iter_tree_children(self, parent_id):
1347
"""Iterate through the entry's tree children, if any"""
1349
path = self._tree_id_paths[parent_id]
1352
file_id = self.tree_file_id(parent_id)
1353
for child in self._tree.inventory[file_id].children.iterkeys():
1354
childpath = joinpath(path, child)
1355
yield self.trans_id_tree_path(childpath)
1358
class _PreviewTree(object):
1359
"""Partial implementation of Tree to support show_diff_trees"""
1361
def __init__(self, transform):
1362
self._transform = transform
1364
def lock_read(self):
1365
# Perhaps in theory, this should lock the TreeTransform?
1371
def _iter_changes(self, from_tree, include_unchanged=False,
1372
specific_files=None, pb=None, extra_trees=None,
1373
require_versioned=True, want_unversioned=False):
1374
"""See InterTree._iter_changes.
1376
This implementation does not support include_unchanged, specific_files,
1377
or want_unversioned. extra_trees, require_versioned, and pb are
1380
if from_tree is not self._transform._tree:
1381
raise ValueError('from_tree must be transform source tree.')
1382
if include_unchanged:
1383
raise ValueError('include_unchanged is not supported')
1384
if specific_files is not None:
1385
raise ValueError('specific_files is not supported')
1386
if want_unversioned:
1387
raise ValueError('want_unversioned is not supported')
1388
return self._transform._iter_changes()
1390
def kind(self, file_id):
1391
trans_id = self._transform.trans_id_file_id(file_id)
1392
return self._transform.final_kind(trans_id)
1394
def get_file_mtime(self, file_id, path=None):
1395
"""See Tree.get_file_mtime"""
1396
trans_id = self._transform.trans_id_file_id(file_id)
1397
name = self._transform._limbo_name(trans_id)
1398
return os.stat(name).st_mtime
1400
def get_file(self, file_id):
1401
"""See Tree.get_file"""
1402
trans_id = self._transform.trans_id_file_id(file_id)
1403
name = self._transform._limbo_name(trans_id)
1404
return open(name, 'rb')
1406
def paths2ids(self, specific_files, trees=None, require_versioned=False):
1407
"""See Tree.paths2ids"""
1285
1411
def joinpath(parent, child):
1286
1412
"""Join tree-relative paths, handling the tree root specially"""