108
108
except OSError, e:
109
109
if e.errno == errno.EEXIST:
110
110
raise ExistingLimbo(self._limbodir)
111
self._deletiondir = urlutils.local_path_from_url(
112
control_files.controlfilename('pending-deletion'))
114
os.mkdir(self._deletiondir)
116
if e.errno == errno.EEXIST:
117
raise errors.ExistingPendingDeletion(self._deletiondir)
112
120
self._tree.unlock()
792
def apply(self, no_conflicts=False):
804
def apply(self, no_conflicts=False, _mover=None):
793
805
"""Apply all changes to the inventory and filesystem.
795
807
If filesystem or inventory conflicts are present, MalformedTransform
800
812
:param no_conflicts: if True, the caller guarantees there are no
801
813
conflicts, so no check is made.
814
:param _mover: Supply an alternate FileMover, for testing
803
816
if not no_conflicts:
804
817
conflicts = self.find_conflicts()
808
821
inventory_delta = []
809
822
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
811
child_pb.update('Apply phase', 0, 2)
812
self._apply_removals(inv, inventory_delta)
813
child_pb.update('Apply phase', 1, 2)
814
modified_paths = self._apply_insertions(inv, inventory_delta)
829
child_pb.update('Apply phase', 0, 2)
830
self._apply_removals(inv, inventory_delta, mover)
831
child_pb.update('Apply phase', 1, 2)
832
modified_paths = self._apply_insertions(inv, inventory_delta,
838
mover.apply_deletions()
816
840
child_pb.finished()
817
841
self._tree.apply_inventory_delta(inventory_delta)
852
876
self._limbo_files[trans_id] = limbo_name
853
877
return limbo_name
855
def _apply_removals(self, inv, inventory_delta):
879
def _apply_removals(self, inv, inventory_delta, mover):
856
880
"""Perform tree operations that remove directory/inventory names.
858
882
That is, delete files that are to be deleted, and put any files that
868
892
child_pb.update('removing file', num, len(tree_paths))
869
893
full_path = self._tree.abspath(path)
870
894
if trans_id in self._removed_contents:
871
delete_any(full_path)
895
mover.pre_delete(full_path, os.path.join(self._deletiondir,
872
897
elif trans_id in self._new_name or trans_id in \
873
898
self._new_parent:
875
os.rename(full_path, self._limbo_name(trans_id))
900
mover.rename(full_path, self._limbo_name(trans_id))
876
901
except OSError, e:
877
902
if e.errno != errno.ENOENT:
889
914
child_pb.finished()
891
def _apply_insertions(self, inv, inventory_delta):
916
def _apply_insertions(self, inv, inventory_delta, mover):
892
917
"""Perform tree operations that insert directory/inventory names.
894
919
That is, create any files that need to be created, and restore from
911
936
full_path = self._tree.abspath(path)
912
937
if trans_id in self._needs_rename:
914
os.rename(self._limbo_name(trans_id), full_path)
939
mover.rename(self._limbo_name(trans_id), full_path)
915
940
except OSError, e:
916
941
# We may be renaming a dangling inventory id
917
942
if e.errno != errno.ENOENT:
1269
1294
tt.trans_id_tree_file_id(wt.get_root_id())
1270
1295
pb = bzrlib.ui.ui_factory.nested_progress_bar()
1297
deferred_contents = []
1272
1298
for num, (tree_path, entry) in \
1273
1299
enumerate(tree.inventory.iter_entries_by_dir()):
1274
pb.update("Building tree", num, len(tree.inventory))
1300
pb.update("Building tree", num - len(deferred_contents),
1301
len(tree.inventory))
1275
1302
if entry.parent_id is None:
1277
1304
reparent = False
1300
1327
'entry %s parent id %r is not in file_trans_id %r'
1301
1328
% (entry, entry.parent_id, file_trans_id))
1302
1329
parent_id = file_trans_id[entry.parent_id]
1303
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1330
if entry.kind == 'file':
1331
# We *almost* replicate new_by_entry, so that we can defer
1332
# getting the file text, and get them all at once.
1333
trans_id = tt.create_path(entry.name, parent_id)
1334
file_trans_id[file_id] = trans_id
1335
tt.version_file(entry.file_id, trans_id)
1336
executable = tree.is_executable(entry.file_id, tree_path)
1337
if executable is not None:
1338
tt.set_executability(executable, trans_id)
1339
deferred_contents.append((entry.file_id, trans_id))
1341
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1306
1344
new_trans_id = file_trans_id[file_id]
1307
1345
old_parent = tt.trans_id_tree_path(tree_path)
1308
1346
_reparent_children(tt, old_parent, new_trans_id)
1347
for num, (trans_id, bytes) in enumerate(
1348
tree.iter_files_bytes(deferred_contents)):
1349
tt.create_file(bytes, trans_id)
1350
pb.update('Adding file contents',
1351
(num + len(tree.inventory) - len(deferred_contents)),
1352
len(tree.inventory))
1311
1355
pp.next_phase()
1563
1607
skip_root = False
1564
1608
basis_tree = None
1566
1611
for id_num, (file_id, path, changed_content, versioned, parent, name,
1567
1612
kind, executable) in enumerate(change_list):
1568
1613
if skip_root and file_id[0] is not None and parent[0] is None:
1608
1653
tt.create_symlink(target_tree.get_symlink_target(file_id),
1610
1655
elif kind[1] == 'file':
1611
tt.create_file(target_tree.get_file_lines(file_id),
1656
deferred_files.append((file_id, (trans_id, mode_id)))
1613
1657
if basis_tree is None:
1614
1658
basis_tree = working_tree.basis_tree()
1615
1659
basis_tree.lock_read()
1636
1680
name[1], tt.trans_id_file_id(parent[1]), trans_id)
1637
1681
if executable[0] != executable[1] and kind[1] == "file":
1638
1682
tt.set_executability(executable[1], trans_id)
1683
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
1685
tt.create_file(bytes, trans_id, mode_id)
1640
1687
if basis_tree is not None:
1641
1688
basis_tree.unlock()
1741
1788
file_id=modified_id,
1742
1789
conflict_path=conflicting_path,
1743
1790
conflict_file_id=conflicting_id)
1793
class _FileMover(object):
1794
"""Moves and deletes files for TreeTransform, tracking operations"""
1797
self.past_renames = []
1798
self.pending_deletions = []
1800
def rename(self, from_, to):
1801
"""Rename a file from one path to another. Functions like os.rename"""
1802
os.rename(from_, to)
1803
self.past_renames.append((from_, to))
1805
def pre_delete(self, from_, to):
1806
"""Rename a file out of the way and mark it for deletion.
1808
Unlike os.unlink, this works equally well for files and directories.
1809
:param from_: The current file path
1810
:param to: A temporary path for the file
1812
self.rename(from_, to)
1813
self.pending_deletions.append(to)
1816
"""Reverse all renames that have been performed"""
1817
for from_, to in reversed(self.past_renames):
1818
os.rename(to, from_)
1819
# after rollback, don't reuse _FileMover
1821
pending_deletions = None
1823
def apply_deletions(self):
1824
"""Apply all marked deletions"""
1825
for path in self.pending_deletions:
1827
# after apply_deletions, don't reuse _FileMover
1829
pending_deletions = None