34
34
from bzrlib.osutils import (file_kind, supports_executable, pathjoin, lexists,
36
36
from bzrlib.progress import DummyProgress, ProgressPhase
37
from bzrlib.symbol_versioning import deprecated_function, zero_fifteen
37
from bzrlib.symbol_versioning import (
38
42
from bzrlib.trace import mutter, warning
39
43
from bzrlib import tree
104
108
except OSError, e:
105
109
if e.errno == errno.EEXIST:
106
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)
108
120
self._tree.unlock()
788
def apply(self, no_conflicts=False):
804
def apply(self, no_conflicts=False, _mover=None):
789
805
"""Apply all changes to the inventory and filesystem.
791
807
If filesystem or inventory conflicts are present, MalformedTransform
796
812
:param no_conflicts: if True, the caller guarantees there are no
797
813
conflicts, so no check is made.
814
:param _mover: Supply an alternate FileMover, for testing
799
816
if not no_conflicts:
800
817
conflicts = self.find_conflicts()
804
821
inventory_delta = []
805
822
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
807
child_pb.update('Apply phase', 0, 2)
808
self._apply_removals(inv, inventory_delta)
809
child_pb.update('Apply phase', 1, 2)
810
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()
812
840
child_pb.finished()
813
841
self._tree.apply_inventory_delta(inventory_delta)
848
876
self._limbo_files[trans_id] = limbo_name
849
877
return limbo_name
851
def _apply_removals(self, inv, inventory_delta):
879
def _apply_removals(self, inv, inventory_delta, mover):
852
880
"""Perform tree operations that remove directory/inventory names.
854
882
That is, delete files that are to be deleted, and put any files that
864
892
child_pb.update('removing file', num, len(tree_paths))
865
893
full_path = self._tree.abspath(path)
866
894
if trans_id in self._removed_contents:
867
delete_any(full_path)
895
mover.pre_delete(full_path, os.path.join(self._deletiondir,
868
897
elif trans_id in self._new_name or trans_id in \
869
898
self._new_parent:
871
os.rename(full_path, self._limbo_name(trans_id))
900
mover.rename(full_path, self._limbo_name(trans_id))
872
901
except OSError, e:
873
902
if e.errno != errno.ENOENT:
885
914
child_pb.finished()
887
def _apply_insertions(self, inv, inventory_delta):
916
def _apply_insertions(self, inv, inventory_delta, mover):
888
917
"""Perform tree operations that insert directory/inventory names.
890
919
That is, create any files that need to be created, and restore from
907
936
full_path = self._tree.abspath(path)
908
937
if trans_id in self._needs_rename:
910
os.rename(self._limbo_name(trans_id), full_path)
939
mover.rename(self._limbo_name(trans_id), full_path)
911
940
except OSError, e:
912
941
# We may be renaming a dangling inventory id
913
942
if e.errno != errno.ENOENT:
1265
1294
tt.trans_id_tree_file_id(wt.get_root_id())
1266
1295
pb = bzrlib.ui.ui_factory.nested_progress_bar()
1297
deferred_contents = []
1268
1298
for num, (tree_path, entry) in \
1269
1299
enumerate(tree.inventory.iter_entries_by_dir()):
1270
pb.update("Building tree", num, len(tree.inventory))
1300
pb.update("Building tree", num - len(deferred_contents),
1301
len(tree.inventory))
1271
1302
if entry.parent_id is None:
1273
1304
reparent = False
1296
1327
'entry %s parent id %r is not in file_trans_id %r'
1297
1328
% (entry, entry.parent_id, file_trans_id))
1298
1329
parent_id = file_trans_id[entry.parent_id]
1299
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,
1302
1344
new_trans_id = file_trans_id[file_id]
1303
1345
old_parent = tt.trans_id_tree_path(tree_path)
1304
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))
1307
1355
pp.next_phase()
1424
1472
working_tree.unlock()
1475
@deprecated_function(zero_ninety)
1427
1476
def change_entry(tt, file_id, working_tree, target_tree,
1428
1477
trans_id_file_id, backups, trans_id, by_parent):
1429
1478
"""Replace a file_id's contents with those from a target tree."""
1479
if file_id is None and target_tree is None:
1480
# skip the logic altogether in the deprecation test
1430
1482
e_trans_id = trans_id_file_id(file_id)
1431
1483
entry = target_tree.inventory[file_id]
1432
1484
has_contents, contents_mod, meta_mod, = _entry_changes(file_id, entry,
1555
1607
skip_root = False
1556
1608
basis_tree = None
1558
1611
for id_num, (file_id, path, changed_content, versioned, parent, name,
1559
1612
kind, executable) in enumerate(change_list):
1560
1613
if skip_root and file_id[0] is not None and parent[0] is None:
1600
1653
tt.create_symlink(target_tree.get_symlink_target(file_id),
1602
1655
elif kind[1] == 'file':
1603
tt.create_file(target_tree.get_file_lines(file_id),
1656
deferred_files.append((file_id, (trans_id, mode_id)))
1605
1657
if basis_tree is None:
1606
1658
basis_tree = working_tree.basis_tree()
1607
1659
basis_tree.lock_read()
1628
1680
name[1], tt.trans_id_file_id(parent[1]), trans_id)
1629
1681
if executable[0] != executable[1] and kind[1] == "file":
1630
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)
1632
1687
if basis_tree is not None:
1633
1688
basis_tree.unlock()
1733
1788
file_id=modified_id,
1734
1789
conflict_path=conflicting_path,
1735
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