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)
1164
for descendant in self._limbo_descendants(trans_id):
1165
desc_path = self._limbo_files[descendant]
1166
desc_path = new_path + desc_path[len(old_path):]
1167
self._limbo_files[descendant] = desc_path
1169
def _limbo_descendants(self, trans_id):
1170
"""Return the set of trans_ids whose limbo paths descend from this."""
1171
descendants = set(self._limbo_children.get(trans_id, []))
1172
for descendant in list(descendants):
1173
descendants.update(self._limbo_descendants(descendant))
1097
os.rename(old_path, new_path)
1176
1099
def create_file(self, contents, trans_id, mode_id=None):
1177
1100
"""Schedule creation of a new file.
1212
1134
def _read_symlink_target(self, trans_id):
1213
1135
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
1137
def create_hardlink(self, path, trans_id):
1225
1138
"""Schedule creation of a hard link"""
1226
1139
name = self._limbo_name(trans_id)
1629
1542
child_pb.update('removing file', num, len(tree_paths))
1630
1543
full_path = self._tree.abspath(path)
1631
1544
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):
1545
mover.pre_delete(full_path, os.path.join(self._deletiondir,
1547
elif trans_id in self._new_name or trans_id in \
1637
1550
mover.rename(full_path, self._limbo_name(trans_id))
1638
1551
except OSError, e:
1692
1605
unversioned files in the input tree.
1695
def __init__(self, tree, pb=None, case_sensitive=True):
1608
def __init__(self, tree, pb=DummyProgress(), case_sensitive=True):
1696
1609
tree.lock_read()
1697
1610
limbodir = osutils.mkdtemp(prefix='bzr-limbo-')
1698
1611
DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive)
1989
1902
def get_file_mtime(self, file_id, path=None):
1990
1903
"""See Tree.get_file_mtime"""
1991
1904
if not self._content_change(file_id):
1992
return self._transform._tree.get_file_mtime(file_id)
1905
return self._transform._tree.get_file_mtime(file_id, path)
1993
1906
return self._stat_limbo_file(file_id).st_mtime
1995
1908
def _file_size(self, entry, stat_value):
2057
1970
executable = None
2058
1971
if kind == 'symlink':
2059
1972
link_or_sha1 = os.readlink(limbo_name).decode(osutils._fs_enc)
2060
executable = tt._new_executability.get(trans_id, executable)
1973
if supports_executable():
1974
executable = tt._new_executability.get(trans_id, executable)
2061
1975
return kind, size, executable, link_or_sha1
2063
1977
def iter_changes(self, from_tree, include_unchanged=False,
2376
2290
new_desired_files = desired_files
2378
2292
iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
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)
2293
unchanged = dict((f, p[1]) for (f, p, c, v, d, n, k, e)
2294
in iter if not (c or e[0] != e[1]))
2385
2295
new_desired_files = []
2387
2297
for file_id, (trans_id, tree_path) in desired_files:
2592
2502
def revert(working_tree, target_tree, filenames, backups=False,
2593
pb=None, change_reporter=None):
2503
pb=DummyProgress(), change_reporter=None):
2594
2504
"""Revert a working tree's contents to those of a target tree."""
2595
2505
target_tree.lock_read()
2596
pb = ui.ui_factory.nested_progress_bar()
2597
2506
tt = TreeTransform(working_tree, pb)
2599
2508
pp = ProgressPhase("Revert phase", 3, pb)
2725
2636
parent_trans = ROOT_PARENT
2727
2638
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)
2639
tt.adjust_path(name[1], parent_trans, trans_id)
2732
2640
if executable[0] != executable[1] and kind[1] == "file":
2733
2641
tt.set_executability(executable[1], trans_id)
2734
2642
if working_tree.supports_content_filtering():
2747
2655
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2748
2656
deferred_files):
2749
2657
tt.create_file(bytes, trans_id, mode_id)
2750
tt.fixup_new_roots()
2752
2659
if basis_tree is not None:
2753
2660
basis_tree.unlock()
2754
2661
return merge_modified
2757
def resolve_conflicts(tt, pb=None, pass_func=None):
2664
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
2758
2665
"""Make many conflict-resolution attempts, but die if they fail"""
2759
2666
if pass_func is None:
2760
2667
pass_func = conflict_pass
2761
2668
new_conflicts = set()
2762
pb = ui.ui_factory.nested_progress_bar()
2764
2670
for n in range(10):
2765
2671
pb.update('Resolution pass', n+1, 10)
2898
2804
self.pending_deletions = []
2900
2806
def rename(self, from_, to):
2901
"""Rename a file from one path to another."""
2807
"""Rename a file from one path to another. Functions like os.rename"""
2903
osutils.rename(from_, to)
2809
os.rename(from_, to)
2904
2810
except OSError, e:
2905
2811
if e.errno in (errno.EEXIST, errno.ENOTEMPTY):
2906
2812
raise errors.FileExists(to, str(e))