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
84
self._deletiondir = None
161
85
self._id_number = 0
162
86
# mapping of trans_id -> new basename
163
87
self._new_name = {}
962
850
self._limbo_files[trans_id] = limbo_name
963
851
return limbo_name
965
def _apply_removals(self, inv, inventory_delta, mover):
966
"""Perform tree operations that remove directory/inventory names.
968
That is, delete files that are to be deleted, and put any files that
969
need renaming into limbo. This must be done in strict child-to-parent
972
tree_paths = list(self._tree_path_ids.iteritems())
973
tree_paths.sort(reverse=True)
974
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
976
for num, data in enumerate(tree_paths):
977
path, trans_id = data
978
child_pb.update('removing file', num, len(tree_paths))
979
full_path = self._tree.abspath(path)
980
if trans_id in self._removed_contents:
981
mover.pre_delete(full_path, os.path.join(self._deletiondir,
983
elif trans_id in self._new_name or trans_id in \
986
mover.rename(full_path, self._limbo_name(trans_id))
988
if e.errno != errno.ENOENT:
991
self.rename_count += 1
992
if trans_id in self._removed_id:
993
if trans_id == self._new_root:
994
file_id = self._tree.get_root_id()
996
file_id = self.tree_file_id(trans_id)
997
assert file_id is not None
998
inventory_delta.append((path, None, file_id, None))
1002
def _apply_insertions(self, inv, inventory_delta, mover):
1003
"""Perform tree operations that insert directory/inventory names.
1005
That is, create any files that need to be created, and restore from
1006
limbo any files that needed renaming. This must be done in strict
1007
parent-to-child order.
1009
new_paths = self.new_paths()
1011
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1014
for num, (path, trans_id) in enumerate(new_paths):
1016
child_pb.update('adding file', num, len(new_paths))
1018
kind = self._new_contents[trans_id]
1020
kind = contents = None
1021
if trans_id in self._new_contents or \
1022
self.path_changed(trans_id):
1023
full_path = self._tree.abspath(path)
1024
if trans_id in self._needs_rename:
1026
mover.rename(self._limbo_name(trans_id), full_path)
1028
# We may be renaming a dangling inventory id
1029
if e.errno != errno.ENOENT:
1032
self.rename_count += 1
1033
if trans_id in self._new_contents:
1034
modified_paths.append(full_path)
1035
completed_new.append(trans_id)
1037
if trans_id in self._new_id:
1039
kind = file_kind(self._tree.abspath(path))
1040
if trans_id in self._new_reference_revision:
1041
new_entry = inventory.TreeReference(
1042
self._new_id[trans_id],
1043
self._new_name[trans_id],
1044
self.final_file_id(self._new_parent[trans_id]),
1045
None, self._new_reference_revision[trans_id])
1047
new_entry = inventory.make_entry(kind,
1048
self.final_name(trans_id),
1049
self.final_file_id(self.final_parent(trans_id)),
1050
self._new_id[trans_id])
1052
if trans_id in self._new_name or trans_id in\
1053
self._new_parent or\
1054
trans_id in self._new_executability:
1055
file_id = self.final_file_id(trans_id)
1056
if file_id is not None:
1057
entry = inv[file_id]
1058
new_entry = entry.copy()
1060
if trans_id in self._new_name or trans_id in\
1062
if new_entry is not None:
1063
new_entry.name = self.final_name(trans_id)
1064
parent = self.final_parent(trans_id)
1065
parent_id = self.final_file_id(parent)
1066
new_entry.parent_id = parent_id
1068
if trans_id in self._new_executability:
1069
self._set_executability(path, new_entry, trans_id)
1070
if new_entry is not None:
1071
if new_entry.file_id in inv:
1072
old_path = inv.id2path(new_entry.file_id)
1075
inventory_delta.append((old_path, path,
1080
for trans_id in completed_new:
1081
del self._new_contents[trans_id]
1082
return modified_paths
1084
853
def _set_executability(self, path, entry, trans_id):
1085
854
"""Set the executability of versioned files """
1086
855
new_executability = self._new_executability[trans_id]
1290
1059
(from_executable, to_executable)))
1291
1060
return iter(sorted(results, key=lambda x:x[1]))
1062
def get_preview_tree(self):
1063
"""Return a tree representing the result of the transform.
1065
This tree only supports the subset of Tree functionality required
1066
by show_diff_trees. It must only be compared to tt._tree.
1068
return _PreviewTree(self)
1071
class TreeTransform(TreeTransformBase):
1072
"""Represent a tree transformation.
1074
This object is designed to support incremental generation of the transform,
1077
However, it gives optimum performance when parent directories are created
1078
before their contents. The transform is then able to put child files
1079
directly in their parent directory, avoiding later renames.
1081
It is easy to produce malformed transforms, but they are generally
1082
harmless. Attempting to apply a malformed transform will cause an
1083
exception to be raised before any modifications are made to the tree.
1085
Many kinds of malformed transforms can be corrected with the
1086
resolve_conflicts function. The remaining ones indicate programming error,
1087
such as trying to create a file with no path.
1089
Two sets of file creation methods are supplied. Convenience methods are:
1094
These are composed of the low-level methods:
1096
* create_file or create_directory or create_symlink
1100
Transform/Transaction ids
1101
-------------------------
1102
trans_ids are temporary ids assigned to all files involved in a transform.
1103
It's possible, even common, that not all files in the Tree have trans_ids.
1105
trans_ids are used because filenames and file_ids are not good enough
1106
identifiers; filenames change, and not all files have file_ids. File-ids
1107
are also associated with trans-ids, so that moving a file moves its
1110
trans_ids are only valid for the TreeTransform that generated them.
1114
Limbo is a temporary directory use to hold new versions of files.
1115
Files are added to limbo by create_file, create_directory, create_symlink,
1116
and their convenience variants (new_*). Files may be removed from limbo
1117
using cancel_creation. Files are renamed from limbo into their final
1118
location as part of TreeTransform.apply
1120
Limbo must be cleaned up, by either calling TreeTransform.apply or
1121
calling TreeTransform.finalize.
1123
Files are placed into limbo inside their parent directories, where
1124
possible. This reduces subsequent renames, and makes operations involving
1125
lots of files faster. This optimization is only possible if the parent
1126
directory is created *before* creating any of its children, so avoid
1127
creating children before parents, where possible.
1131
This temporary directory is used by _FileMover for storing files that are
1132
about to be deleted. In case of rollback, the files will be restored.
1133
FileMover does not delete files until it is sure that a rollback will not
1136
def __init__(self, tree, pb=DummyProgress()):
1137
"""Note: a tree_write lock is taken on the tree.
1139
Use TreeTransform.finalize() to release the lock (can be omitted if
1140
TreeTransform.apply() called).
1142
tree.lock_tree_write()
1145
control_files = tree._control_files
1146
limbodir = urlutils.local_path_from_url(
1147
control_files.controlfilename('limbo'))
1151
if e.errno == errno.EEXIST:
1152
raise ExistingLimbo(limbodir)
1153
deletiondir = urlutils.local_path_from_url(
1154
control_files.controlfilename('pending-deletion'))
1156
os.mkdir(deletiondir)
1158
if e.errno == errno.EEXIST:
1159
raise errors.ExistingPendingDeletion(deletiondir)
1164
TreeTransformBase.__init__(self, tree, limbodir, pb,
1165
tree.case_sensitive)
1166
self._deletiondir = deletiondir
1168
def apply(self, no_conflicts=False, _mover=None):
1169
"""Apply all changes to the inventory and filesystem.
1171
If filesystem or inventory conflicts are present, MalformedTransform
1174
If apply succeeds, finalize is not necessary.
1176
:param no_conflicts: if True, the caller guarantees there are no
1177
conflicts, so no check is made.
1178
:param _mover: Supply an alternate FileMover, for testing
1180
if not no_conflicts:
1181
conflicts = self.find_conflicts()
1182
if len(conflicts) != 0:
1183
raise MalformedTransform(conflicts=conflicts)
1184
inv = self._tree.inventory
1185
inventory_delta = []
1186
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1189
mover = _FileMover()
1193
child_pb.update('Apply phase', 0, 2)
1194
self._apply_removals(inv, inventory_delta, mover)
1195
child_pb.update('Apply phase', 1, 2)
1196
modified_paths = self._apply_insertions(inv, inventory_delta,
1202
mover.apply_deletions()
1205
self._tree.apply_inventory_delta(inventory_delta)
1208
return _TransformResults(modified_paths, self.rename_count)
1210
def _apply_removals(self, inv, inventory_delta, mover):
1211
"""Perform tree operations that remove directory/inventory names.
1213
That is, delete files that are to be deleted, and put any files that
1214
need renaming into limbo. This must be done in strict child-to-parent
1217
tree_paths = list(self._tree_path_ids.iteritems())
1218
tree_paths.sort(reverse=True)
1219
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1221
for num, data in enumerate(tree_paths):
1222
path, trans_id = data
1223
child_pb.update('removing file', num, len(tree_paths))
1224
full_path = self._tree.abspath(path)
1225
if trans_id in self._removed_contents:
1226
mover.pre_delete(full_path, os.path.join(self._deletiondir,
1228
elif trans_id in self._new_name or trans_id in \
1231
mover.rename(full_path, self._limbo_name(trans_id))
1233
if e.errno != errno.ENOENT:
1236
self.rename_count += 1
1237
if trans_id in self._removed_id:
1238
if trans_id == self._new_root:
1239
file_id = self._tree.get_root_id()
1241
file_id = self.tree_file_id(trans_id)
1242
if file_id is not None:
1243
inventory_delta.append((path, None, file_id, None))
1247
def _apply_insertions(self, inv, inventory_delta, mover):
1248
"""Perform tree operations that insert directory/inventory names.
1250
That is, create any files that need to be created, and restore from
1251
limbo any files that needed renaming. This must be done in strict
1252
parent-to-child order.
1254
new_paths = self.new_paths()
1256
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1259
for num, (path, trans_id) in enumerate(new_paths):
1261
child_pb.update('adding file', num, len(new_paths))
1263
kind = self._new_contents[trans_id]
1265
kind = contents = None
1266
if trans_id in self._new_contents or \
1267
self.path_changed(trans_id):
1268
full_path = self._tree.abspath(path)
1269
if trans_id in self._needs_rename:
1271
mover.rename(self._limbo_name(trans_id), full_path)
1273
# We may be renaming a dangling inventory id
1274
if e.errno != errno.ENOENT:
1277
self.rename_count += 1
1278
if trans_id in self._new_contents:
1279
modified_paths.append(full_path)
1280
completed_new.append(trans_id)
1282
if trans_id in self._new_id:
1284
kind = file_kind(self._tree.abspath(path))
1285
if trans_id in self._new_reference_revision:
1286
new_entry = inventory.TreeReference(
1287
self._new_id[trans_id],
1288
self._new_name[trans_id],
1289
self.final_file_id(self._new_parent[trans_id]),
1290
None, self._new_reference_revision[trans_id])
1292
new_entry = inventory.make_entry(kind,
1293
self.final_name(trans_id),
1294
self.final_file_id(self.final_parent(trans_id)),
1295
self._new_id[trans_id])
1297
if trans_id in self._new_name or trans_id in\
1298
self._new_parent or\
1299
trans_id in self._new_executability:
1300
file_id = self.final_file_id(trans_id)
1301
if file_id is not None:
1302
entry = inv[file_id]
1303
new_entry = entry.copy()
1305
if trans_id in self._new_name or trans_id in\
1307
if new_entry is not None:
1308
new_entry.name = self.final_name(trans_id)
1309
parent = self.final_parent(trans_id)
1310
parent_id = self.final_file_id(parent)
1311
new_entry.parent_id = parent_id
1313
if trans_id in self._new_executability:
1314
self._set_executability(path, new_entry, trans_id)
1315
if new_entry is not None:
1316
if new_entry.file_id in inv:
1317
old_path = inv.id2path(new_entry.file_id)
1320
inventory_delta.append((old_path, path,
1325
for trans_id in completed_new:
1326
del self._new_contents[trans_id]
1327
return modified_paths
1330
class TransformPreview(TreeTransformBase):
1331
"""A TreeTransform for generating preview trees.
1333
Unlike TreeTransform, this version works when the input tree is a
1334
RevisionTree, rather than a WorkingTree. As a result, it tends to ignore
1335
unversioned files in the input tree.
1338
def __init__(self, tree, pb=DummyProgress(), case_sensitive=True):
1340
limbodir = tempfile.mkdtemp(prefix='bzr-limbo-')
1341
TreeTransformBase.__init__(self, tree, limbodir, pb, case_sensitive)
1343
def canonical_path(self, path):
1346
def tree_kind(self, trans_id):
1347
path = self._tree_id_paths.get(trans_id)
1349
raise NoSuchFile(None)
1350
file_id = self._tree.path2id(path)
1351
return self._tree.kind(file_id)
1353
def _set_mode(self, trans_id, mode_id, typefunc):
1354
"""Set the mode of new file contents.
1355
The mode_id is the existing file to get the mode from (often the same
1356
as trans_id). The operation is only performed if there's a mode match
1357
according to typefunc.
1359
# is it ok to ignore this? probably
1362
def iter_tree_children(self, parent_id):
1363
"""Iterate through the entry's tree children, if any"""
1365
path = self._tree_id_paths[parent_id]
1368
file_id = self.tree_file_id(parent_id)
1369
for child in self._tree.inventory[file_id].children.iterkeys():
1370
childpath = joinpath(path, child)
1371
yield self.trans_id_tree_path(childpath)
1374
class _PreviewTree(object):
1375
"""Partial implementation of Tree to support show_diff_trees"""
1377
def __init__(self, transform):
1378
self._transform = transform
1380
def lock_read(self):
1381
# Perhaps in theory, this should lock the TreeTransform?
1387
def _iter_changes(self, from_tree, include_unchanged=False,
1388
specific_files=None, pb=None, extra_trees=None,
1389
require_versioned=True, want_unversioned=False):
1390
"""See InterTree._iter_changes.
1392
This implementation does not support include_unchanged, specific_files,
1393
or want_unversioned. extra_trees, require_versioned, and pb are
1396
if from_tree is not self._transform._tree:
1397
raise ValueError('from_tree must be transform source tree.')
1398
if include_unchanged:
1399
raise ValueError('include_unchanged is not supported')
1400
if specific_files is not None:
1401
raise ValueError('specific_files is not supported')
1402
if want_unversioned:
1403
raise ValueError('want_unversioned is not supported')
1404
return self._transform._iter_changes()
1406
def kind(self, file_id):
1407
trans_id = self._transform.trans_id_file_id(file_id)
1408
return self._transform.final_kind(trans_id)
1410
def get_file_mtime(self, file_id, path=None):
1411
"""See Tree.get_file_mtime"""
1412
trans_id = self._transform.trans_id_file_id(file_id)
1413
name = self._transform._limbo_name(trans_id)
1414
return os.stat(name).st_mtime
1416
def get_file(self, file_id):
1417
"""See Tree.get_file"""
1418
trans_id = self._transform.trans_id_file_id(file_id)
1419
name = self._transform._limbo_name(trans_id)
1420
return open(name, 'rb')
1422
def get_symlink_target(self, file_id):
1423
"""See Tree.get_symlink_target"""
1424
trans_id = self._transform.trans_id_file_id(file_id)
1425
name = self._transform._limbo_name(trans_id)
1426
return os.readlink(name)
1428
def paths2ids(self, specific_files, trees=None, require_versioned=False):
1429
"""See Tree.paths2ids"""
1294
1433
def joinpath(parent, child):
1295
1434
"""Join tree-relative paths, handling the tree root specially"""