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:
1763
1788
file_id=modified_id,
1764
1789
conflict_path=conflicting_path,
1765
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