33
32
revision as _mod_revision,
37
35
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
38
ReusingTransform, CantMoveRoot,
36
ReusingTransform, NotVersionedError, CantMoveRoot,
39
37
ExistingLimbo, ImmortalLimbo, NoFinalPath,
40
38
UnableCreateSymlink)
41
39
from bzrlib.filters import filtered_output_bytes, ContentFilterContext
80
78
class TreeTransformBase(object):
81
79
"""The base class for TreeTransform and its kin."""
83
def __init__(self, tree, pb=None,
81
def __init__(self, tree, pb=DummyProgress(),
84
82
case_sensitive=True):
87
85
:param tree: The tree that will be transformed, but not necessarily
87
:param pb: A ProgressTask indicating how much progress is being made
90
88
:param case_sensitive: If True, the target of the transform is
91
89
case sensitive, not just case preserving.
164
162
def adjust_path(self, name, parent, trans_id):
165
163
"""Change the path that is assigned to a transaction id."""
167
raise ValueError("Parent trans-id may not be None")
168
164
if trans_id == self._new_root:
169
165
raise CantMoveRoot
170
166
self._new_name[trans_id] = name
171
167
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]
266
205
def trans_id_tree_file_id(self, inventory_id):
267
206
"""Determine the transaction id of a working tree file.
1063
1000
class DiskTreeTransform(TreeTransformBase):
1064
1001
"""Tree transform storing its contents on disk."""
1066
def __init__(self, tree, limbodir, pb=None,
1003
def __init__(self, tree, limbodir, pb=DummyProgress(),
1067
1004
case_sensitive=True):
1068
1005
"""Constructor.
1069
1006
:param tree: The tree that will be transformed, but not necessarily
1070
1007
the output tree.
1071
1008
:param limbodir: A directory where new files can be stored until
1072
1009
they are installed in their proper places
1010
:param pb: A ProgressBar indicating how much progress is being made
1074
1011
:param case_sensitive: If True, the target of the transform is
1075
1012
case sensitive, not just case preserving.
1139
1075
if (trans_id in self._limbo_files and
1140
1076
trans_id not in self._needs_rename):
1141
1077
self._rename_in_limbo([trans_id])
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]
1078
self._limbo_children[previous_parent].remove(trans_id)
1079
del self._limbo_children_names[previous_parent][previous_name]
1147
1081
def _rename_in_limbo(self, trans_ids):
1148
1082
"""Fix limbo names so that the right final path is produced.
1160
1094
if trans_id not in self._new_contents:
1162
1096
new_path = self._limbo_name(trans_id)
1163
osutils.rename(old_path, new_path)
1097
os.rename(old_path, new_path)
1164
1098
for descendant in self._limbo_descendants(trans_id):
1165
1099
desc_path = self._limbo_files[descendant]
1166
1100
desc_path = new_path + desc_path[len(old_path):]
1212
1145
def _read_symlink_target(self, trans_id):
1213
1146
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))
1224
1148
def create_hardlink(self, path, trans_id):
1225
1149
"""Schedule creation of a hard link"""
1226
1150
name = self._limbo_name(trans_id)
1629
1553
child_pb.update('removing file', num, len(tree_paths))
1630
1554
full_path = self._tree.abspath(path)
1631
1555
if trans_id in self._removed_contents:
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):
1556
mover.pre_delete(full_path, os.path.join(self._deletiondir,
1558
elif trans_id in self._new_name or trans_id in \
1637
1561
mover.rename(full_path, self._limbo_name(trans_id))
1638
1562
except OSError, e:
1692
1616
unversioned files in the input tree.
1695
def __init__(self, tree, pb=None, case_sensitive=True):
1619
def __init__(self, tree, pb=DummyProgress(), case_sensitive=True):
1696
1620
tree.lock_read()
1697
1621
limbodir = osutils.mkdtemp(prefix='bzr-limbo-')
1698
1622
DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive)
2592
2516
def revert(working_tree, target_tree, filenames, backups=False,
2593
pb=None, change_reporter=None):
2517
pb=DummyProgress(), change_reporter=None):
2594
2518
"""Revert a working tree's contents to those of a target tree."""
2595
2519
target_tree.lock_read()
2596
pb = ui.ui_factory.nested_progress_bar()
2597
2520
tt = TreeTransform(working_tree, pb)
2599
2522
pp = ProgressPhase("Revert phase", 3, pb)
2725
2650
parent_trans = ROOT_PARENT
2727
2652
parent_trans = tt.trans_id_file_id(parent[1])
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)
2653
tt.adjust_path(name[1], parent_trans, trans_id)
2732
2654
if executable[0] != executable[1] and kind[1] == "file":
2733
2655
tt.set_executability(executable[1], trans_id)
2734
2656
if working_tree.supports_content_filtering():
2747
2669
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2748
2670
deferred_files):
2749
2671
tt.create_file(bytes, trans_id, mode_id)
2750
tt.fixup_new_roots()
2752
2673
if basis_tree is not None:
2753
2674
basis_tree.unlock()
2754
2675
return merge_modified
2757
def resolve_conflicts(tt, pb=None, pass_func=None):
2678
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
2758
2679
"""Make many conflict-resolution attempts, but die if they fail"""
2759
2680
if pass_func is None:
2760
2681
pass_func = conflict_pass
2761
2682
new_conflicts = set()
2762
pb = ui.ui_factory.nested_progress_bar()
2764
2684
for n in range(10):
2765
2685
pb.update('Resolution pass', n+1, 10)
2898
2818
self.pending_deletions = []
2900
2820
def rename(self, from_, to):
2901
"""Rename a file from one path to another."""
2821
"""Rename a file from one path to another. Functions like os.rename"""
2903
osutils.rename(from_, to)
2823
os.rename(from_, to)
2904
2824
except OSError, e:
2905
2825
if e.errno in (errno.EEXIST, errno.ENOTEMPTY):
2906
2826
raise errors.FileExists(to, str(e))