53
53
class _TransformResults(object):
54
def __init__(self, modified_paths, rename_count):
54
def __init__(self, modified_paths):
55
55
object.__init__(self)
56
56
self.modified_paths = modified_paths
57
self.rename_count = rename_count
60
59
class TreeTransform(object):
61
60
"""Represent a tree transformation.
63
62
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.
70
65
It is easy to produce malformed transforms, but they are generally
71
66
harmless. Attempting to apply a malformed transform will cause an
89
84
def __init__(self, tree, pb=DummyProgress()):
90
85
"""Note: a tree_write lock is taken on the tree.
92
Use TreeTransform.finalize() to release the lock (can be omitted if
93
TreeTransform.apply() called).
87
Use TreeTransform.finalize() to release the lock
95
89
object.__init__(self)
112
106
self._new_name = {}
113
107
self._new_parent = {}
114
108
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()
124
109
self._removed_contents = set()
125
110
self._new_executability = {}
126
111
self._new_reference_revision = {}
145
129
root = property(__get_root)
147
131
def finalize(self):
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
132
"""Release the working tree lock, if held, clean up limbo dir."""
153
133
if self._tree is None:
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:
136
for trans_id, kind in self._new_contents.iteritems():
137
path = self._limbo_name(trans_id)
160
138
if kind == "directory":
187
165
"""Change the path that is assigned to a transaction id."""
188
166
if trans_id == self._new_root:
189
167
raise CantMoveRoot
190
previous_parent = self._new_parent.get(trans_id)
191
previous_name = self._new_name.get(trans_id)
192
168
self._new_name[trans_id] = name
193
169
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)
218
171
def adjust_root_path(self, name, parent):
219
172
"""Emulate moving the root by moving all children, instead.
377
330
def cancel_creation(self, trans_id):
378
331
"""Cancel the creation of new file contents."""
379
332
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]
387
333
delete_any(self._limbo_name(trans_id))
389
335
def delete_contents(self, trans_id):
717
663
def _duplicate_entries(self, by_parent):
718
664
"""No directory may have two entries with the same name."""
720
if (self._new_name, self._new_parent) == ({}, {}):
722
666
for children in by_parent.itervalues():
723
667
name_ids = [(self.final_name(t), t) for t in children]
788
def apply(self, no_conflicts=False):
789
733
"""Apply all changes to the inventory and filesystem.
791
735
If filesystem or inventory conflicts are present, MalformedTransform
794
If apply succeeds, finalize is not necessary.
796
:param no_conflicts: if True, the caller guarantees there are no
797
conflicts, so no check is made.
800
conflicts = self.find_conflicts()
801
if len(conflicts) != 0:
802
raise MalformedTransform(conflicts=conflicts)
738
conflicts = self.find_conflicts()
739
if len(conflicts) != 0:
740
raise MalformedTransform(conflicts=conflicts)
803
741
inv = self._tree.inventory
804
742
inventory_delta = []
805
743
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
813
751
self._tree.apply_inventory_delta(inventory_delta)
814
752
self.__done = True
816
return _TransformResults(modified_paths, self.rename_count)
754
return _TransformResults(modified_paths)
818
756
def _limbo_name(self, trans_id):
819
757
"""Generate the limbo name of a file"""
820
limbo_name = self._limbo_files.get(trans_id)
821
if limbo_name is not None:
823
parent = self._new_parent.get(trans_id)
824
# if the parent directory is already in limbo (e.g. when building a
825
# tree), choose a limbo name inside the parent, to reduce further
827
use_direct_path = False
828
if self._new_contents.get(parent) == 'directory':
829
filename = self._new_name.get(trans_id)
830
if filename is not None:
831
if parent not in self._limbo_children:
832
self._limbo_children[parent] = set()
833
self._limbo_children_names[parent] = {}
834
use_direct_path = True
835
# the direct path can only be used if no other file has
836
# already taken this pathname, i.e. if the name is unused, or
837
# if it is already associated with this trans_id.
838
elif (self._limbo_children_names[parent].get(filename)
839
in (trans_id, None)):
840
use_direct_path = True
842
limbo_name = pathjoin(self._limbo_files[parent], filename)
843
self._limbo_children[parent].add(trans_id)
844
self._limbo_children_names[parent][filename] = trans_id
846
limbo_name = pathjoin(self._limbodir, trans_id)
847
self._needs_rename.add(trans_id)
848
self._limbo_files[trans_id] = limbo_name
758
return pathjoin(self._limbodir, trans_id)
851
760
def _apply_removals(self, inv, inventory_delta):
852
761
"""Perform tree operations that remove directory/inventory names.
905
812
if trans_id in self._new_contents or \
906
813
self.path_changed(trans_id):
907
814
full_path = self._tree.abspath(path)
908
if trans_id in self._needs_rename:
910
os.rename(self._limbo_name(trans_id), full_path)
912
# We may be renaming a dangling inventory id
913
if e.errno != errno.ENOENT:
916
self.rename_count += 1
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:
917
821
if trans_id in self._new_contents:
918
822
modified_paths.append(full_path)
919
823
del self._new_contents[trans_id]
1247
1151
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1248
1152
pp = ProgressPhase("Build phase", 2, top_pb)
1249
1153
if tree.inventory.root is not None:
1250
# This is kind of a hack: we should be altering the root
1251
# as part of the regular tree shape diff logic.
1252
# The conditional test here is to avoid doing an
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
1253
1157
# expensive operation (flush) every time the root id
1254
1158
# is set within the tree, nor setting the root and thus
1255
1159
# marking the tree as dirty, because we use two different
1517
1420
pp.next_phase()
1518
1421
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1520
merge_modified = _alter_files(working_tree, target_tree, tt,
1521
child_pb, filenames, backups)
1423
_alter_files(working_tree, target_tree, tt, child_pb,
1523
1426
child_pb.finished()
1524
1427
pp.next_phase()
1566
1469
if kind[0] == 'file' and (backups or kind[1] is None):
1567
1470
wt_sha1 = working_tree.get_file_sha1(file_id)
1568
1471
if merge_modified.get(file_id) != wt_sha1:
1569
# acquire the basis tree lazily to prevent the
1570
# expense of accessing it when it's not needed ?
1571
# (Guessing, RBC, 200702)
1472
# acquire the basis tree lazyily to prevent the expense
1473
# of accessing it when its not needed ? (Guessing, RBC,
1572
1475
if basis_tree is None:
1573
1476
basis_tree = working_tree.basis_tree()
1574
1477
basis_tree.lock_read()
1602
1505
elif kind[1] == 'file':
1603
1506
tt.create_file(target_tree.get_file_lines(file_id),
1604
1507
trans_id, mode_id)
1605
if basis_tree is None:
1606
basis_tree = working_tree.basis_tree()
1607
basis_tree.lock_read()
1608
new_sha1 = target_tree.get_file_sha1(file_id)
1609
if (file_id in basis_tree and new_sha1 ==
1610
basis_tree.get_file_sha1(file_id)):
1611
if file_id in merge_modified:
1612
del merge_modified[file_id]
1614
merge_modified[file_id] = new_sha1
1616
1508
# preserve the execute bit when backing up
1617
1509
if keep_content and executable[0] == executable[1]:
1618
1510
tt.set_executability(executable[1], trans_id)
1654
def conflict_pass(tt, conflicts, path_tree=None):
1655
"""Resolve some classes of conflicts.
1657
:param tt: The transform to resolve conflicts in
1658
:param conflicts: The conflicts to resolve
1659
:param path_tree: A Tree to get supplemental paths from
1545
def conflict_pass(tt, conflicts):
1546
"""Resolve some classes of conflicts."""
1661
1547
new_conflicts = set()
1662
1548
for c_type, conflict in ((c[0], c) for c in conflicts):
1663
1549
if c_type == 'duplicate id':
1694
1580
except KeyError:
1695
1581
tt.create_directory(trans_id)
1696
1582
new_conflicts.add((c_type, 'Created directory', trans_id))
1698
tt.final_name(trans_id)
1700
file_id = tt.final_file_id(trans_id)
1701
entry = path_tree.inventory[file_id]
1702
parent_trans_id = tt.trans_id_file_id(entry.parent_id)
1703
tt.adjust_path(entry.name, parent_trans_id, trans_id)
1704
1583
elif c_type == 'unversioned parent':
1705
1584
tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
1706
1585
new_conflicts.add((c_type, 'Versioned directory', conflict[1]))