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 = {}
953
838
self._limbo_files[trans_id] = limbo_name
954
839
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
841
def _set_executability(self, path, entry, trans_id):
1076
842
"""Set the executability of versioned files """
1077
843
new_executability = self._new_executability[trans_id]
1281
1047
(from_executable, to_executable)))
1282
1048
return iter(sorted(results, key=lambda x:x[1]))
1050
def get_preview_tree(self):
1051
"""Return a tree representing the result of the transform.
1053
This tree only supports the subset of Tree functionality required
1054
by show_diff_trees. It must only be compared to tt._tree.
1056
return _PreviewTree(self)
1059
class TreeTransform(TreeTransformBase):
1060
"""Represent a tree transformation.
1062
This object is designed to support incremental generation of the transform,
1065
However, it gives optimum performance when parent directories are created
1066
before their contents. The transform is then able to put child files
1067
directly in their parent directory, avoiding later renames.
1069
It is easy to produce malformed transforms, but they are generally
1070
harmless. Attempting to apply a malformed transform will cause an
1071
exception to be raised before any modifications are made to the tree.
1073
Many kinds of malformed transforms can be corrected with the
1074
resolve_conflicts function. The remaining ones indicate programming error,
1075
such as trying to create a file with no path.
1077
Two sets of file creation methods are supplied. Convenience methods are:
1082
These are composed of the low-level methods:
1084
* create_file or create_directory or create_symlink
1088
Transform/Transaction ids
1089
-------------------------
1090
trans_ids are temporary ids assigned to all files involved in a transform.
1091
It's possible, even common, that not all files in the Tree have trans_ids.
1093
trans_ids are used because filenames and file_ids are not good enough
1094
identifiers; filenames change, and not all files have file_ids. File-ids
1095
are also associated with trans-ids, so that moving a file moves its
1098
trans_ids are only valid for the TreeTransform that generated them.
1102
Limbo is a temporary directory use to hold new versions of files.
1103
Files are added to limbo by create_file, create_directory, create_symlink,
1104
and their convenience variants (new_*). Files may be removed from limbo
1105
using cancel_creation. Files are renamed from limbo into their final
1106
location as part of TreeTransform.apply
1108
Limbo must be cleaned up, by either calling TreeTransform.apply or
1109
calling TreeTransform.finalize.
1111
Files are placed into limbo inside their parent directories, where
1112
possible. This reduces subsequent renames, and makes operations involving
1113
lots of files faster. This optimization is only possible if the parent
1114
directory is created *before* creating any of its children, so avoid
1115
creating children before parents, where possible.
1119
This temporary directory is used by _FileMover for storing files that are
1120
about to be deleted. In case of rollback, the files will be restored.
1121
FileMover does not delete files until it is sure that a rollback will not
1124
def __init__(self, tree, pb=DummyProgress()):
1125
"""Note: a tree_write lock is taken on the tree.
1127
Use TreeTransform.finalize() to release the lock (can be omitted if
1128
TreeTransform.apply() called).
1130
tree.lock_tree_write()
1133
control_files = tree._control_files
1134
limbodir = urlutils.local_path_from_url(
1135
control_files.controlfilename('limbo'))
1139
if e.errno == errno.EEXIST:
1140
raise ExistingLimbo(limbodir)
1141
deletiondir = urlutils.local_path_from_url(
1142
control_files.controlfilename('pending-deletion'))
1144
os.mkdir(deletiondir)
1146
if e.errno == errno.EEXIST:
1147
raise errors.ExistingPendingDeletion(deletiondir)
1152
TreeTransformBase.__init__(self, tree, limbodir, pb,
1153
tree.case_sensitive)
1154
self._deletiondir = deletiondir
1156
def apply(self, no_conflicts=False, _mover=None):
1157
"""Apply all changes to the inventory and filesystem.
1159
If filesystem or inventory conflicts are present, MalformedTransform
1162
If apply succeeds, finalize is not necessary.
1164
:param no_conflicts: if True, the caller guarantees there are no
1165
conflicts, so no check is made.
1166
:param _mover: Supply an alternate FileMover, for testing
1168
if not no_conflicts:
1169
conflicts = self.find_conflicts()
1170
if len(conflicts) != 0:
1171
raise MalformedTransform(conflicts=conflicts)
1172
inv = self._tree.inventory
1173
inventory_delta = []
1174
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1177
mover = _FileMover()
1181
child_pb.update('Apply phase', 0, 2)
1182
self._apply_removals(inv, inventory_delta, mover)
1183
child_pb.update('Apply phase', 1, 2)
1184
modified_paths = self._apply_insertions(inv, inventory_delta,
1190
mover.apply_deletions()
1193
self._tree.apply_inventory_delta(inventory_delta)
1196
return _TransformResults(modified_paths, self.rename_count)
1198
def _apply_removals(self, inv, inventory_delta, mover):
1199
"""Perform tree operations that remove directory/inventory names.
1201
That is, delete files that are to be deleted, and put any files that
1202
need renaming into limbo. This must be done in strict child-to-parent
1205
tree_paths = list(self._tree_path_ids.iteritems())
1206
tree_paths.sort(reverse=True)
1207
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1209
for num, data in enumerate(tree_paths):
1210
path, trans_id = data
1211
child_pb.update('removing file', num, len(tree_paths))
1212
full_path = self._tree.abspath(path)
1213
if trans_id in self._removed_contents:
1214
mover.pre_delete(full_path, os.path.join(self._deletiondir,
1216
elif trans_id in self._new_name or trans_id in \
1219
mover.rename(full_path, self._limbo_name(trans_id))
1221
if e.errno != errno.ENOENT:
1224
self.rename_count += 1
1225
if trans_id in self._removed_id:
1226
if trans_id == self._new_root:
1227
file_id = self._tree.get_root_id()
1229
file_id = self.tree_file_id(trans_id)
1230
if file_id is not None:
1231
inventory_delta.append((path, None, file_id, None))
1235
def _apply_insertions(self, inv, inventory_delta, mover):
1236
"""Perform tree operations that insert directory/inventory names.
1238
That is, create any files that need to be created, and restore from
1239
limbo any files that needed renaming. This must be done in strict
1240
parent-to-child order.
1242
new_paths = self.new_paths()
1244
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1247
for num, (path, trans_id) in enumerate(new_paths):
1249
child_pb.update('adding file', num, len(new_paths))
1251
kind = self._new_contents[trans_id]
1253
kind = contents = None
1254
if trans_id in self._new_contents or \
1255
self.path_changed(trans_id):
1256
full_path = self._tree.abspath(path)
1257
if trans_id in self._needs_rename:
1259
mover.rename(self._limbo_name(trans_id), full_path)
1261
# We may be renaming a dangling inventory id
1262
if e.errno != errno.ENOENT:
1265
self.rename_count += 1
1266
if trans_id in self._new_contents:
1267
modified_paths.append(full_path)
1268
completed_new.append(trans_id)
1270
if trans_id in self._new_id:
1272
kind = file_kind(self._tree.abspath(path))
1273
if trans_id in self._new_reference_revision:
1274
new_entry = inventory.TreeReference(
1275
self._new_id[trans_id],
1276
self._new_name[trans_id],
1277
self.final_file_id(self._new_parent[trans_id]),
1278
None, self._new_reference_revision[trans_id])
1280
new_entry = inventory.make_entry(kind,
1281
self.final_name(trans_id),
1282
self.final_file_id(self.final_parent(trans_id)),
1283
self._new_id[trans_id])
1285
if trans_id in self._new_name or trans_id in\
1286
self._new_parent or\
1287
trans_id in self._new_executability:
1288
file_id = self.final_file_id(trans_id)
1289
if file_id is not None:
1290
entry = inv[file_id]
1291
new_entry = entry.copy()
1293
if trans_id in self._new_name or trans_id in\
1295
if new_entry is not None:
1296
new_entry.name = self.final_name(trans_id)
1297
parent = self.final_parent(trans_id)
1298
parent_id = self.final_file_id(parent)
1299
new_entry.parent_id = parent_id
1301
if trans_id in self._new_executability:
1302
self._set_executability(path, new_entry, trans_id)
1303
if new_entry is not None:
1304
if new_entry.file_id in inv:
1305
old_path = inv.id2path(new_entry.file_id)
1308
inventory_delta.append((old_path, path,
1313
for trans_id in completed_new:
1314
del self._new_contents[trans_id]
1315
return modified_paths
1318
class TransformPreview(TreeTransformBase):
1319
"""A TreeTransform for generating preview trees.
1321
Unlike TreeTransform, this version works when the input tree is a
1322
RevisionTree, rather than a WorkingTree. As a result, it tends to ignore
1323
unversioned files in the input tree.
1326
def __init__(self, tree, pb=DummyProgress(), case_sensitive=True):
1328
limbodir = tempfile.mkdtemp(prefix='bzr-limbo-')
1329
TreeTransformBase.__init__(self, tree, limbodir, pb, case_sensitive)
1331
def canonical_path(self, path):
1334
def tree_kind(self, trans_id):
1335
path = self._tree_id_paths.get(trans_id)
1337
raise NoSuchFile(None)
1338
file_id = self._tree.path2id(path)
1339
return self._tree.kind(file_id)
1341
def _set_mode(self, trans_id, mode_id, typefunc):
1342
"""Set the mode of new file contents.
1343
The mode_id is the existing file to get the mode from (often the same
1344
as trans_id). The operation is only performed if there's a mode match
1345
according to typefunc.
1347
# is it ok to ignore this? probably
1350
def iter_tree_children(self, parent_id):
1351
"""Iterate through the entry's tree children, if any"""
1353
path = self._tree_id_paths[parent_id]
1356
file_id = self.tree_file_id(parent_id)
1357
for child in self._tree.inventory[file_id].children.iterkeys():
1358
childpath = joinpath(path, child)
1359
yield self.trans_id_tree_path(childpath)
1362
class _PreviewTree(object):
1363
"""Partial implementation of Tree to support show_diff_trees"""
1365
def __init__(self, transform):
1366
self._transform = transform
1368
def lock_read(self):
1369
# Perhaps in theory, this should lock the TreeTransform?
1375
def _iter_changes(self, from_tree, include_unchanged=False,
1376
specific_files=None, pb=None, extra_trees=None,
1377
require_versioned=True, want_unversioned=False):
1378
"""See InterTree._iter_changes.
1380
This implementation does not support include_unchanged, specific_files,
1381
or want_unversioned. extra_trees, require_versioned, and pb are
1384
if from_tree is not self._transform._tree:
1385
raise ValueError('from_tree must be transform source tree.')
1386
if include_unchanged:
1387
raise ValueError('include_unchanged is not supported')
1388
if specific_files is not None:
1389
raise ValueError('specific_files is not supported')
1390
if want_unversioned:
1391
raise ValueError('want_unversioned is not supported')
1392
return self._transform._iter_changes()
1394
def kind(self, file_id):
1395
trans_id = self._transform.trans_id_file_id(file_id)
1396
return self._transform.final_kind(trans_id)
1398
def get_file_mtime(self, file_id, path=None):
1399
"""See Tree.get_file_mtime"""
1400
trans_id = self._transform.trans_id_file_id(file_id)
1401
name = self._transform._limbo_name(trans_id)
1402
return os.stat(name).st_mtime
1404
def get_file(self, file_id):
1405
"""See Tree.get_file"""
1406
trans_id = self._transform.trans_id_file_id(file_id)
1407
name = self._transform._limbo_name(trans_id)
1408
return open(name, 'rb')
1410
def paths2ids(self, specific_files, trees=None, require_versioned=False):
1411
"""See Tree.paths2ids"""
1285
1415
def joinpath(parent, child):
1286
1416
"""Join tree-relative paths, handling the tree root specially"""