53
53
class _TransformResults(object):
54
def __init__(self, modified_paths):
54
def __init__(self, modified_paths, rename_count):
55
55
object.__init__(self)
56
56
self.modified_paths = modified_paths
57
self.rename_count = rename_count
59
60
class TreeTransform(object):
60
61
"""Represent a tree transformation.
62
63
This object is designed to support incremental generation of the transform,
66
However, it gives optimum performance when parent directories are created
67
before their contents. The transform is then able to put child files
68
directly in their parent directory, avoiding later renames.
65
70
It is easy to produce malformed transforms, but they are generally
66
71
harmless. Attempting to apply a malformed transform will cause an
84
89
def __init__(self, tree, pb=DummyProgress()):
85
90
"""Note: a tree_write lock is taken on the tree.
87
Use TreeTransform.finalize() to release the lock
92
Use TreeTransform.finalize() to release the lock (can be omitted if
93
TreeTransform.apply() called).
89
95
object.__init__(self)
106
112
self._new_name = {}
107
113
self._new_parent = {}
108
114
self._new_contents = {}
115
# A mapping of transform ids to their limbo filename
116
self._limbo_files = {}
117
# A mapping of transform ids to a set of the transform ids of children
118
# that their limbo directory has
119
self._limbo_children = {}
120
# Map transform ids to maps of child filename to child transform id
121
self._limbo_children_names = {}
122
# List of transform ids that need to be renamed from limbo into place
123
self._needs_rename = set()
109
124
self._removed_contents = set()
110
125
self._new_executability = {}
111
126
self._new_reference_revision = {}
115
130
self._removed_id = set()
116
131
self._tree_path_ids = {}
117
132
self._tree_id_paths = {}
133
# Cache of realpath results, to speed up canonical_path
118
134
self._realpaths = {}
119
# Cache of realpath results, to speed up canonical_path
135
# Cache of relpath results, to speed up canonical_path
120
136
self._relpaths = {}
121
# Cache of relpath results, to speed up canonical_path
122
137
self._new_root = self.trans_id_tree_file_id(tree.get_root_id())
123
138
self.__done = False
140
self.rename_count = 0
126
142
def __get_root(self):
127
143
return self._new_root
129
145
root = property(__get_root)
131
147
def finalize(self):
132
"""Release the working tree lock, if held, clean up limbo dir."""
148
"""Release the working tree lock, if held, clean up limbo dir.
150
This is required if apply has not been invoked, but can be invoked
133
153
if self._tree is None:
136
for trans_id, kind in self._new_contents.iteritems():
137
path = self._limbo_name(trans_id)
156
entries = [(self._limbo_name(t), t, k) for t, k in
157
self._new_contents.iteritems()]
158
entries.sort(reverse=True)
159
for path, trans_id, kind in entries:
138
160
if kind == "directory":
165
187
"""Change the path that is assigned to a transaction id."""
166
188
if trans_id == self._new_root:
167
189
raise CantMoveRoot
190
previous_parent = self._new_parent.get(trans_id)
191
previous_name = self._new_name.get(trans_id)
168
192
self._new_name[trans_id] = name
169
193
self._new_parent[trans_id] = parent
194
if (trans_id in self._limbo_files and
195
trans_id not in self._needs_rename):
196
self._rename_in_limbo([trans_id])
197
self._limbo_children[previous_parent].remove(trans_id)
198
del self._limbo_children_names[previous_parent][previous_name]
200
def _rename_in_limbo(self, trans_ids):
201
"""Fix limbo names so that the right final path is produced.
203
This means we outsmarted ourselves-- we tried to avoid renaming
204
these files later by creating them with their final names in their
205
final parents. But now the previous name or parent is no longer
206
suitable, so we have to rename them.
208
Even for trans_ids that have no new contents, we must remove their
209
entries from _limbo_files, because they are now stale.
211
for trans_id in trans_ids:
212
old_path = self._limbo_files.pop(trans_id)
213
if trans_id not in self._new_contents:
215
new_path = self._limbo_name(trans_id)
216
os.rename(old_path, new_path)
171
218
def adjust_root_path(self, name, parent):
172
219
"""Emulate moving the root by moving all children, instead.
330
377
def cancel_creation(self, trans_id):
331
378
"""Cancel the creation of new file contents."""
332
379
del self._new_contents[trans_id]
380
children = self._limbo_children.get(trans_id)
381
# if this is a limbo directory with children, move them before removing
383
if children is not None:
384
self._rename_in_limbo(children)
385
del self._limbo_children[trans_id]
386
del self._limbo_children_names[trans_id]
333
387
delete_any(self._limbo_name(trans_id))
335
389
def delete_contents(self, trans_id):
751
807
self._tree.apply_inventory_delta(inventory_delta)
752
808
self.__done = True
754
return _TransformResults(modified_paths)
810
return _TransformResults(modified_paths, self.rename_count)
756
812
def _limbo_name(self, trans_id):
757
813
"""Generate the limbo name of a file"""
758
return pathjoin(self._limbodir, trans_id)
814
limbo_name = self._limbo_files.get(trans_id)
815
if limbo_name is not None:
817
parent = self._new_parent.get(trans_id)
818
# if the parent directory is already in limbo (e.g. when building a
819
# tree), choose a limbo name inside the parent, to reduce further
821
use_direct_path = False
822
if self._new_contents.get(parent) == 'directory':
823
filename = self._new_name.get(trans_id)
824
if filename is not None:
825
if parent not in self._limbo_children:
826
self._limbo_children[parent] = set()
827
self._limbo_children_names[parent] = {}
828
use_direct_path = True
829
# the direct path can only be used if no other file has
830
# already taken this pathname, i.e. if the name is unused, or
831
# if it is already associated with this trans_id.
832
elif (self._limbo_children_names[parent].get(filename)
833
in (trans_id, None)):
834
use_direct_path = True
836
limbo_name = pathjoin(self._limbo_files[parent], filename)
837
self._limbo_children[parent].add(trans_id)
838
self._limbo_children_names[parent][filename] = trans_id
840
limbo_name = pathjoin(self._limbodir, trans_id)
841
self._needs_rename.add(trans_id)
842
self._limbo_files[trans_id] = limbo_name
760
845
def _apply_removals(self, inv, inventory_delta):
761
846
"""Perform tree operations that remove directory/inventory names.
812
899
if trans_id in self._new_contents or \
813
900
self.path_changed(trans_id):
814
901
full_path = self._tree.abspath(path)
816
os.rename(self._limbo_name(trans_id), full_path)
818
# We may be renaming a dangling inventory id
819
if e.errno != errno.ENOENT:
902
if trans_id in self._needs_rename:
904
os.rename(self._limbo_name(trans_id), full_path)
906
# We may be renaming a dangling inventory id
907
if e.errno != errno.ENOENT:
910
self.rename_count += 1
821
911
if trans_id in self._new_contents:
822
912
modified_paths.append(full_path)
823
913
del self._new_contents[trans_id]
1151
1241
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1152
1242
pp = ProgressPhase("Build phase", 2, top_pb)
1153
1243
if tree.inventory.root is not None:
1154
# this is kindof a hack: we should be altering the root
1155
# as partof the regular tree shape diff logic.
1156
# the conditional test hereis to avoid doing an
1244
# This is kind of a hack: we should be altering the root
1245
# as part of the regular tree shape diff logic.
1246
# The conditional test here is to avoid doing an
1157
1247
# expensive operation (flush) every time the root id
1158
1248
# is set within the tree, nor setting the root and thus
1159
1249
# marking the tree as dirty, because we use two different
1469
1560
if kind[0] == 'file' and (backups or kind[1] is None):
1470
1561
wt_sha1 = working_tree.get_file_sha1(file_id)
1471
1562
if merge_modified.get(file_id) != wt_sha1:
1472
# acquire the basis tree lazyily to prevent the expense
1473
# of accessing it when its not needed ? (Guessing, RBC,
1563
# acquire the basis tree lazily to prevent the
1564
# expense of accessing it when it's not needed ?
1565
# (Guessing, RBC, 200702)
1475
1566
if basis_tree is None:
1476
1567
basis_tree = working_tree.basis_tree()
1477
1568
basis_tree.lock_read()