78
80
class TreeTransformBase(object):
79
81
"""The base class for TreeTransform and its kin."""
81
def __init__(self, tree, pb=DummyProgress(),
83
def __init__(self, tree, pb=None,
82
84
case_sensitive=True):
85
87
:param tree: The tree that will be transformed, but not necessarily
87
:param pb: A ProgressTask indicating how much progress is being made
88
90
:param case_sensitive: If True, the target of the transform is
89
91
case sensitive, not just case preserving.
162
164
def adjust_path(self, name, parent, trans_id):
163
165
"""Change the path that is assigned to a transaction id."""
167
raise ValueError("Parent trans-id may not be None")
164
168
if trans_id == self._new_root:
165
169
raise CantMoveRoot
166
170
self._new_name[trans_id] = name
167
171
self._new_parent[trans_id] = parent
168
if parent == ROOT_PARENT:
169
if self._new_root is not None:
170
raise ValueError("Cannot have multiple roots.")
171
self._new_root = trans_id
173
173
def adjust_root_path(self, name, parent):
174
174
"""Emulate moving the root by moving all children, instead.
202
202
self.version_file(old_root_file_id, old_root)
203
203
self.unversion_file(self._new_root)
205
def fixup_new_roots(self):
206
"""Reinterpret requests to change the root directory
208
Instead of creating a root directory, or moving an existing directory,
209
all the attributes and children of the new root are applied to the
210
existing root directory.
212
This means that the old root trans-id becomes obsolete, so it is
213
recommended only to invoke this after the root trans-id has become
216
new_roots = [k for k, v in self._new_parent.iteritems() if v is
218
if len(new_roots) < 1:
220
if len(new_roots) != 1:
221
raise ValueError('A tree cannot have two roots!')
222
if self._new_root is None:
223
self._new_root = new_roots[0]
225
old_new_root = new_roots[0]
226
# TODO: What to do if a old_new_root is present, but self._new_root is
227
# not listed as being removed? This code explicitly unversions
228
# the old root and versions it with the new file_id. Though that
229
# seems like an incomplete delta
231
# unversion the new root's directory.
232
file_id = self.final_file_id(old_new_root)
233
if old_new_root in self._new_id:
234
self.cancel_versioning(old_new_root)
236
self.unversion_file(old_new_root)
237
# if, at this stage, root still has an old file_id, zap it so we can
238
# stick a new one in.
239
if (self.tree_file_id(self._new_root) is not None and
240
self._new_root not in self._removed_id):
241
self.unversion_file(self._new_root)
242
self.version_file(file_id, self._new_root)
244
# Now move children of new root into old root directory.
245
# Ensure all children are registered with the transaction, but don't
246
# use directly-- some tree children have new parents
247
list(self.iter_tree_children(old_new_root))
248
# Move all children of new root into old root directory.
249
for child in self.by_parent().get(old_new_root, []):
250
self.adjust_path(self.final_name(child), self._new_root, child)
252
# Ensure old_new_root has no directory.
253
if old_new_root in self._new_contents:
254
self.cancel_creation(old_new_root)
256
self.delete_contents(old_new_root)
258
# prevent deletion of root directory.
259
if self._new_root in self._removed_contents:
260
self.cancel_deletion(self._new_root)
262
# destroy path info for old_new_root.
263
del self._new_parent[old_new_root]
264
del self._new_name[old_new_root]
205
266
def trans_id_tree_file_id(self, inventory_id):
206
267
"""Determine the transaction id of a working tree file.
1000
1063
class DiskTreeTransform(TreeTransformBase):
1001
1064
"""Tree transform storing its contents on disk."""
1003
def __init__(self, tree, limbodir, pb=DummyProgress(),
1066
def __init__(self, tree, limbodir, pb=None,
1004
1067
case_sensitive=True):
1005
1068
"""Constructor.
1006
1069
:param tree: The tree that will be transformed, but not necessarily
1007
1070
the output tree.
1008
1071
:param limbodir: A directory where new files can be stored until
1009
1072
they are installed in their proper places
1010
:param pb: A ProgressBar indicating how much progress is being made
1011
1074
:param case_sensitive: If True, the target of the transform is
1012
1075
case sensitive, not just case preserving.
1075
1139
if (trans_id in self._limbo_files and
1076
1140
trans_id not in self._needs_rename):
1077
1141
self._rename_in_limbo([trans_id])
1078
self._limbo_children[previous_parent].remove(trans_id)
1079
del self._limbo_children_names[previous_parent][previous_name]
1142
if previous_parent != parent:
1143
self._limbo_children[previous_parent].remove(trans_id)
1144
if previous_parent != parent or previous_name != name:
1145
del self._limbo_children_names[previous_parent][previous_name]
1081
1147
def _rename_in_limbo(self, trans_ids):
1082
1148
"""Fix limbo names so that the right final path is produced.
1145
1212
def _read_symlink_target(self, trans_id):
1146
1213
return os.readlink(self._limbo_name(trans_id))
1215
def _set_mtime(self, path):
1216
"""All files that are created get the same mtime.
1218
This time is set by the first object to be created.
1220
if self._creation_mtime is None:
1221
self._creation_mtime = time.time()
1222
os.utime(path, (self._creation_mtime, self._creation_mtime))
1148
1224
def create_hardlink(self, path, trans_id):
1149
1225
"""Schedule creation of a hard link"""
1150
1226
name = self._limbo_name(trans_id)
1553
1629
child_pb.update('removing file', num, len(tree_paths))
1554
1630
full_path = self._tree.abspath(path)
1555
1631
if trans_id in self._removed_contents:
1556
mover.pre_delete(full_path, os.path.join(self._deletiondir,
1558
elif trans_id in self._new_name or trans_id in \
1632
delete_path = os.path.join(self._deletiondir, trans_id)
1633
mover.pre_delete(full_path, delete_path)
1634
elif (trans_id in self._new_name
1635
or trans_id in self._new_parent):
1561
1637
mover.rename(full_path, self._limbo_name(trans_id))
1562
1638
except OSError, e:
1616
1692
unversioned files in the input tree.
1619
def __init__(self, tree, pb=DummyProgress(), case_sensitive=True):
1695
def __init__(self, tree, pb=None, case_sensitive=True):
1620
1696
tree.lock_read()
1621
1697
limbodir = osutils.mkdtemp(prefix='bzr-limbo-')
1622
1698
DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive)
1981
2057
executable = None
1982
2058
if kind == 'symlink':
1983
2059
link_or_sha1 = os.readlink(limbo_name).decode(osutils._fs_enc)
1984
if supports_executable():
1985
executable = tt._new_executability.get(trans_id, executable)
2060
executable = tt._new_executability.get(trans_id, executable)
1986
2061
return kind, size, executable, link_or_sha1
1988
2063
def iter_changes(self, from_tree, include_unchanged=False,
2301
2376
new_desired_files = desired_files
2303
2378
iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
2304
unchanged = dict((f, p[1]) for (f, p, c, v, d, n, k, e)
2305
in iter if not (c or e[0] != e[1]))
2379
unchanged = [(f, p[1]) for (f, p, c, v, d, n, k, e)
2380
in iter if not (c or e[0] != e[1])]
2381
if accelerator_tree.supports_content_filtering():
2382
unchanged = [(f, p) for (f, p) in unchanged
2383
if not accelerator_tree.iter_search_rules([p]).next()]
2384
unchanged = dict(unchanged)
2306
2385
new_desired_files = []
2308
2387
for file_id, (trans_id, tree_path) in desired_files:
2431
2510
tt.create_directory(trans_id)
2434
def create_from_tree(tt, trans_id, tree, file_id, bytes=None):
2435
"""Create new file contents according to tree contents."""
2513
def create_from_tree(tt, trans_id, tree, file_id, bytes=None,
2514
filter_tree_path=None):
2515
"""Create new file contents according to tree contents.
2517
:param filter_tree_path: the tree path to use to lookup
2518
content filters to apply to the bytes output in the working tree.
2519
This only applies if the working tree supports content filtering.
2436
2521
kind = tree.kind(file_id)
2437
2522
if kind == 'directory':
2438
2523
tt.create_directory(trans_id)
2443
2528
bytes = tree_file.readlines()
2445
2530
tree_file.close()
2532
if wt.supports_content_filtering() and filter_tree_path is not None:
2533
filters = wt._content_filter_stack(filter_tree_path)
2534
bytes = filtered_output_bytes(bytes, filters,
2535
ContentFilterContext(filter_tree_path, tree))
2446
2536
tt.create_file(bytes, trans_id)
2447
2537
elif kind == "symlink":
2448
2538
tt.create_symlink(tree.get_symlink_target(file_id), trans_id)
2502
2592
def revert(working_tree, target_tree, filenames, backups=False,
2503
pb=DummyProgress(), change_reporter=None):
2593
pb=None, change_reporter=None):
2504
2594
"""Revert a working tree's contents to those of a target tree."""
2505
2595
target_tree.lock_read()
2596
pb = ui.ui_factory.nested_progress_bar()
2506
2597
tt = TreeTransform(working_tree, pb)
2508
2599
pp = ProgressPhase("Revert phase", 3, pb)
2636
2725
parent_trans = ROOT_PARENT
2638
2727
parent_trans = tt.trans_id_file_id(parent[1])
2639
tt.adjust_path(name[1], parent_trans, trans_id)
2728
if parent[0] is None and versioned[0]:
2729
tt.adjust_root_path(name[1], parent_trans)
2731
tt.adjust_path(name[1], parent_trans, trans_id)
2640
2732
if executable[0] != executable[1] and kind[1] == "file":
2641
2733
tt.set_executability(executable[1], trans_id)
2642
2734
if working_tree.supports_content_filtering():
2655
2747
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2656
2748
deferred_files):
2657
2749
tt.create_file(bytes, trans_id, mode_id)
2750
tt.fixup_new_roots()
2659
2752
if basis_tree is not None:
2660
2753
basis_tree.unlock()
2661
2754
return merge_modified
2664
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
2757
def resolve_conflicts(tt, pb=None, pass_func=None):
2665
2758
"""Make many conflict-resolution attempts, but die if they fail"""
2666
2759
if pass_func is None:
2667
2760
pass_func = conflict_pass
2668
2761
new_conflicts = set()
2762
pb = ui.ui_factory.nested_progress_bar()
2670
2764
for n in range(10):
2671
2765
pb.update('Resolution pass', n+1, 10)