30
30
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
31
31
ReusingTransform, NotVersionedError, CantMoveRoot,
32
ExistingLimbo, ImmortalLimbo, NoFinalPath,
32
ExistingLimbo, ImmortalLimbo, NoFinalPath)
34
33
from bzrlib.inventory import InventoryEntry
35
34
from bzrlib.osutils import (file_kind, supports_executable, pathjoin, lexists,
36
delete_any, has_symlinks)
37
36
from bzrlib.progress import DummyProgress, ProgressPhase
38
from bzrlib.symbol_versioning import (
37
from bzrlib.symbol_versioning import deprecated_function, zero_fifteen
43
38
from bzrlib.trace import mutter, warning
44
39
from bzrlib import tree
90
85
* create_file or create_directory or create_symlink
92
87
* set_executability
94
Transform/Transaction ids
95
-------------------------
96
trans_ids are temporary ids assigned to all files involved in a transform.
97
It's possible, even common, that not all files in the Tree have trans_ids.
99
trans_ids are used because filenames and file_ids are not good enough
100
identifiers; filenames change, and not all files have file_ids. File-ids
101
are also associated with trans-ids, so that moving a file moves its
104
trans_ids are only valid for the TreeTransform that generated them.
108
Limbo is a temporary directory use to hold new versions of files.
109
Files are added to limbo by create_file, create_directory, create_symlink,
110
and their convenience variants (new_*). Files may be removed from limbo
111
using cancel_creation. Files are renamed from limbo into their final
112
location as part of TreeTransform.apply
114
Limbo must be cleaned up, by either calling TreeTransform.apply or
115
calling TreeTransform.finalize.
117
Files are placed into limbo inside their parent directories, where
118
possible. This reduces subsequent renames, and makes operations involving
119
lots of files faster. This optimization is only possible if the parent
120
directory is created *before* creating any of its children, so avoid
121
creating children before parents, where possible.
125
This temporary directory is used by _FileMover for storing files that are
126
about to be deleted. In case of rollback, the files will be restored.
127
FileMover does not delete files until it is sure that a rollback will not
130
89
def __init__(self, tree, pb=DummyProgress()):
131
90
"""Note: a tree_write lock is taken on the tree.
145
104
except OSError, e:
146
105
if e.errno == errno.EEXIST:
147
106
raise ExistingLimbo(self._limbodir)
148
self._deletiondir = urlutils.local_path_from_url(
149
control_files.controlfilename('pending-deletion'))
151
os.mkdir(self._deletiondir)
153
if e.errno == errno.EEXIST:
154
raise errors.ExistingPendingDeletion(self._deletiondir)
157
108
self._tree.unlock()
160
# counter used to generate trans-ids (which are locally unique)
161
111
self._id_number = 0
162
# mapping of trans_id -> new basename
163
112
self._new_name = {}
164
# mapping of trans_id -> new parent trans_id
165
113
self._new_parent = {}
166
# mapping of trans_id with new contents -> new file_kind
167
114
self._new_contents = {}
168
115
# A mapping of transform ids to their limbo filename
169
116
self._limbo_files = {}
174
121
self._limbo_children_names = {}
175
122
# List of transform ids that need to be renamed from limbo into place
176
123
self._needs_rename = set()
177
# Set of trans_ids whose contents will be removed
178
124
self._removed_contents = set()
179
# Mapping of trans_id -> new execute-bit value
180
125
self._new_executability = {}
181
# Mapping of trans_id -> new tree-reference value
182
126
self._new_reference_revision = {}
183
# Mapping of trans_id -> new file_id
184
127
self._new_id = {}
185
# Mapping of old file-id -> trans_id
186
128
self._non_present_ids = {}
187
# Mapping of new file_id -> trans_id
188
129
self._r_new_id = {}
189
# Set of file_ids that will be removed
190
130
self._removed_id = set()
191
# Mapping of path in old tree -> trans_id
192
131
self._tree_path_ids = {}
193
# Mapping trans_id -> path in old tree
194
132
self._tree_id_paths = {}
195
133
# Cache of realpath results, to speed up canonical_path
196
134
self._realpaths = {}
197
135
# Cache of relpath results, to speed up canonical_path
198
136
self._relpaths = {}
199
# The trans_id that will be used as the tree root
200
137
self._new_root = self.trans_id_tree_file_id(tree.get_root_id())
201
# Indictor of whether the transform has been applied
202
138
self.__done = False
205
# A counter of how many files have been renamed
206
140
self.rename_count = 0
208
142
def __get_root(self):
441
371
target is a bytestring.
442
372
See also new_symlink.
445
os.symlink(target, self._limbo_name(trans_id))
446
unique_add(self._new_contents, trans_id, 'symlink')
449
path = FinalPaths(self).get_path(trans_id)
452
raise UnableCreateSymlink(path=path)
374
os.symlink(target, self._limbo_name(trans_id))
375
unique_add(self._new_contents, trans_id, 'symlink')
454
377
def cancel_creation(self, trans_id):
455
378
"""Cancel the creation of new file contents."""
557
480
# the file is old; the old id is still valid
558
481
if self._new_root == trans_id:
559
return self._tree.get_root_id()
482
return self._tree.inventory.root.file_id
560
483
return self._tree.inventory.path2id(path)
562
485
def final_file_id(self, trans_id):
794
717
def _duplicate_entries(self, by_parent):
795
718
"""No directory may have two entries with the same name."""
797
if (self._new_name, self._new_parent) == ({}, {}):
799
720
for children in by_parent.itervalues():
800
721
name_ids = [(self.final_name(t), t) for t in children]
801
if not self._tree.case_sensitive:
802
name_ids = [(n.lower(), t) for n, t in name_ids]
805
724
last_trans_id = None
867
def apply(self, no_conflicts=False, _mover=None):
868
787
"""Apply all changes to the inventory and filesystem.
870
789
If filesystem or inventory conflicts are present, MalformedTransform
873
792
If apply succeeds, finalize is not necessary.
875
:param no_conflicts: if True, the caller guarantees there are no
876
conflicts, so no check is made.
877
:param _mover: Supply an alternate FileMover, for testing
880
conflicts = self.find_conflicts()
881
if len(conflicts) != 0:
882
raise MalformedTransform(conflicts=conflicts)
794
conflicts = self.find_conflicts()
795
if len(conflicts) != 0:
796
raise MalformedTransform(conflicts=conflicts)
883
797
inv = self._tree.inventory
884
798
inventory_delta = []
885
799
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
892
child_pb.update('Apply phase', 0, 2)
893
self._apply_removals(inv, inventory_delta, mover)
894
child_pb.update('Apply phase', 1, 2)
895
modified_paths = self._apply_insertions(inv, inventory_delta,
901
mover.apply_deletions()
801
child_pb.update('Apply phase', 0, 2)
802
self._apply_removals(inv, inventory_delta)
803
child_pb.update('Apply phase', 1, 2)
804
modified_paths = self._apply_insertions(inv, inventory_delta)
903
806
child_pb.finished()
904
807
self._tree.apply_inventory_delta(inventory_delta)
926
829
# the direct path can only be used if no other file has
927
830
# already taken this pathname, i.e. if the name is unused, or
928
831
# if it is already associated with this trans_id.
929
elif self._tree.case_sensitive:
930
if (self._limbo_children_names[parent].get(filename)
931
in (trans_id, None)):
932
use_direct_path = True
934
for l_filename, l_trans_id in\
935
self._limbo_children_names[parent].iteritems():
936
if l_trans_id == trans_id:
938
if l_filename.lower() == filename.lower():
941
use_direct_path = True
832
elif (self._limbo_children_names[parent].get(filename)
833
in (trans_id, None)):
834
use_direct_path = True
943
835
if use_direct_path:
944
836
limbo_name = pathjoin(self._limbo_files[parent], filename)
945
837
self._limbo_children[parent].add(trans_id)
950
842
self._limbo_files[trans_id] = limbo_name
951
843
return limbo_name
953
def _apply_removals(self, inv, inventory_delta, mover):
845
def _apply_removals(self, inv, inventory_delta):
954
846
"""Perform tree operations that remove directory/inventory names.
956
848
That is, delete files that are to be deleted, and put any files that
966
858
child_pb.update('removing file', num, len(tree_paths))
967
859
full_path = self._tree.abspath(path)
968
860
if trans_id in self._removed_contents:
969
mover.pre_delete(full_path, os.path.join(self._deletiondir,
861
delete_any(full_path)
971
862
elif trans_id in self._new_name or trans_id in \
972
863
self._new_parent:
974
mover.rename(full_path, self._limbo_name(trans_id))
865
os.rename(full_path, self._limbo_name(trans_id))
975
866
except OSError, e:
976
867
if e.errno != errno.ENOENT:
979
870
self.rename_count += 1
980
871
if trans_id in self._removed_id:
981
872
if trans_id == self._new_root:
982
file_id = self._tree.get_root_id()
873
file_id = self._tree.inventory.root.file_id
984
875
file_id = self.tree_file_id(trans_id)
985
876
assert file_id is not None
988
879
child_pb.finished()
990
def _apply_insertions(self, inv, inventory_delta, mover):
881
def _apply_insertions(self, inv, inventory_delta):
991
882
"""Perform tree operations that insert directory/inventory names.
993
884
That is, create any files that need to be created, and restore from
1011
901
full_path = self._tree.abspath(path)
1012
902
if trans_id in self._needs_rename:
1014
mover.rename(self._limbo_name(trans_id), full_path)
904
os.rename(self._limbo_name(trans_id), full_path)
1015
905
except OSError, e:
1016
906
# We may be renaming a dangling inventory id
1017
907
if e.errno != errno.ENOENT:
1020
910
self.rename_count += 1
1021
911
if trans_id in self._new_contents:
1022
912
modified_paths.append(full_path)
1023
completed_new.append(trans_id)
913
del self._new_contents[trans_id]
1025
915
if trans_id in self._new_id:
1026
916
if kind is None:
1067
957
child_pb.finished()
1068
for trans_id in completed_new:
1069
del self._new_contents[trans_id]
1070
958
return modified_paths
1072
960
def _set_executability(self, path, entry, trans_id):
1322
1209
return file_ids
1325
def build_tree(tree, wt, accelerator_tree=None):
1212
def build_tree(tree, wt):
1326
1213
"""Create working tree for a branch, using a TreeTransform.
1328
1215
This function should be used on empty trees, having a tree root at most.
1335
1222
- Otherwise, if the content on disk matches the content we are building,
1336
1223
it is silently replaced.
1337
1224
- Otherwise, conflict resolution will move the old file to 'oldname.moved'.
1339
:param tree: The tree to convert wt into a copy of
1340
:param wt: The working tree that files will be placed into
1341
:param accelerator_tree: A tree which can be used for retrieving file
1342
contents more quickly than tree itself, i.e. a workingtree. tree
1343
will be used for cases where accelerator_tree's content is different.
1345
1226
wt.lock_tree_write()
1347
1228
tree.lock_read()
1349
if accelerator_tree is not None:
1350
accelerator_tree.lock_read()
1352
return _build_tree(tree, wt, accelerator_tree)
1354
if accelerator_tree is not None:
1355
accelerator_tree.unlock()
1230
return _build_tree(tree, wt)
1362
def _build_tree(tree, wt, accelerator_tree):
1236
def _build_tree(tree, wt):
1363
1237
"""See build_tree."""
1364
1238
if len(wt.inventory) > 1: # more than just a root
1365
1239
raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
1374
1248
# is set within the tree, nor setting the root and thus
1375
1249
# marking the tree as dirty, because we use two different
1376
1250
# idioms here: tree interfaces and inventory interfaces.
1377
if wt.get_root_id() != tree.get_root_id():
1378
wt.set_root_id(tree.get_root_id())
1251
if wt.path2id('') != tree.inventory.root.file_id:
1252
wt.set_root_id(tree.inventory.root.file_id)
1380
1254
tt = TreeTransform(wt)
1385
1259
tt.trans_id_tree_file_id(wt.get_root_id())
1386
1260
pb = bzrlib.ui.ui_factory.nested_progress_bar()
1388
deferred_contents = []
1389
1262
for num, (tree_path, entry) in \
1390
1263
enumerate(tree.inventory.iter_entries_by_dir()):
1391
pb.update("Building tree", num - len(deferred_contents),
1392
len(tree.inventory))
1264
pb.update("Building tree", num, len(tree.inventory))
1393
1265
if entry.parent_id is None:
1395
1267
reparent = False
1418
1290
'entry %s parent id %r is not in file_trans_id %r'
1419
1291
% (entry, entry.parent_id, file_trans_id))
1420
1292
parent_id = file_trans_id[entry.parent_id]
1421
if entry.kind == 'file':
1422
# We *almost* replicate new_by_entry, so that we can defer
1423
# getting the file text, and get them all at once.
1424
trans_id = tt.create_path(entry.name, parent_id)
1425
file_trans_id[file_id] = trans_id
1426
tt.version_file(entry.file_id, trans_id)
1427
executable = tree.is_executable(entry.file_id, tree_path)
1428
if executable is not None:
1429
tt.set_executability(executable, trans_id)
1430
deferred_contents.append((entry.file_id, trans_id))
1432
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1293
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1435
1296
new_trans_id = file_trans_id[file_id]
1436
1297
old_parent = tt.trans_id_tree_path(tree_path)
1437
1298
_reparent_children(tt, old_parent, new_trans_id)
1438
for num, (trans_id, bytes) in enumerate(
1439
_iter_files_bytes_accelerated(tree, accelerator_tree,
1440
deferred_contents)):
1441
tt.create_file(bytes, trans_id)
1442
pb.update('Adding file contents',
1443
(num + len(tree.inventory) - len(deferred_contents)),
1444
len(tree.inventory))
1447
1301
pp.next_phase()
1455
1309
wt.add_conflicts(conflicts)
1456
1310
except errors.UnsupportedOperation:
1458
result = tt.apply(no_conflicts=True)
1461
1315
top_pb.finished()
1465
def _iter_files_bytes_accelerated(tree, accelerator_tree, desired_files):
1466
if accelerator_tree is None:
1467
new_desired_files = desired_files
1469
iter = accelerator_tree._iter_changes(tree, include_unchanged=True)
1470
unchanged = dict((f, p[1]) for (f, p, c, v, d, n, k, e)
1472
new_desired_files = []
1473
for file_id, identifier in desired_files:
1474
accelerator_path = unchanged.get(file_id)
1475
if accelerator_path is None:
1476
new_desired_files.append((file_id, identifier))
1478
contents = accelerator_tree.get_file(file_id, accelerator_path)
1481
contents_bytes = (contents.read(),)
1484
yield identifier, contents_bytes
1485
for result in tree.iter_files_bytes(new_desired_files):
1489
1319
def _reparent_children(tt, old_parent, new_parent):
1490
1320
for child in tt.iter_tree_children(old_parent):
1491
1321
tt.adjust_path(tt.final_name(child), new_parent, child)
1555
1385
raise errors.BadFileKindError(name, kind)
1558
1387
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
1559
1388
"""Create new file contents according to an inventory entry."""
1560
1389
if entry.kind == "file":
1566
1395
elif entry.kind == "directory":
1567
1396
tt.create_directory(trans_id)
1570
1398
def create_entry_executability(tt, entry, trans_id):
1571
1399
"""Set the executability of a trans_id according to an inventory entry"""
1572
1400
if entry.kind == "file":
1590
1418
working_tree.unlock()
1593
@deprecated_function(zero_ninety)
1594
1421
def change_entry(tt, file_id, working_tree, target_tree,
1595
1422
trans_id_file_id, backups, trans_id, by_parent):
1596
1423
"""Replace a file_id's contents with those from a target tree."""
1597
if file_id is None and target_tree is None:
1598
# skip the logic altogether in the deprecation test
1600
1424
e_trans_id = trans_id_file_id(file_id)
1601
1425
entry = target_tree.inventory[file_id]
1602
1426
has_contents, contents_mod, meta_mod, = _entry_changes(file_id, entry,
1694
1518
pp.next_phase()
1695
1519
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1697
raw_conflicts = resolve_conflicts(tt, child_pb,
1698
lambda t, c: conflict_pass(t, c, target_tree))
1521
raw_conflicts = resolve_conflicts(tt, child_pb)
1700
1523
child_pb.finished()
1701
1524
conflicts = cook_conflicts(raw_conflicts, tt)
1726
1549
skip_root = False
1727
1550
basis_tree = None
1730
1552
for id_num, (file_id, path, changed_content, versioned, parent, name,
1731
1553
kind, executable) in enumerate(change_list):
1732
1554
if skip_root and file_id[0] is not None and parent[0] is None:
1772
1594
tt.create_symlink(target_tree.get_symlink_target(file_id),
1774
1596
elif kind[1] == 'file':
1775
deferred_files.append((file_id, (trans_id, mode_id)))
1597
tt.create_file(target_tree.get_file_lines(file_id),
1776
1599
if basis_tree is None:
1777
1600
basis_tree = working_tree.basis_tree()
1778
1601
basis_tree.lock_read()
1799
1622
name[1], tt.trans_id_file_id(parent[1]), trans_id)
1800
1623
if executable[0] != executable[1] and kind[1] == "file":
1801
1624
tt.set_executability(executable[1], trans_id)
1802
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
1804
tt.create_file(bytes, trans_id, mode_id)
1806
1626
if basis_tree is not None:
1807
1627
basis_tree.unlock()
1828
def conflict_pass(tt, conflicts, path_tree=None):
1829
"""Resolve some classes of conflicts.
1831
:param tt: The transform to resolve conflicts in
1832
:param conflicts: The conflicts to resolve
1833
:param path_tree: A Tree to get supplemental paths from
1648
def conflict_pass(tt, conflicts):
1649
"""Resolve some classes of conflicts."""
1835
1650
new_conflicts = set()
1836
1651
for c_type, conflict in ((c[0], c) for c in conflicts):
1837
1652
if c_type == 'duplicate id':
1840
1655
conflict[1], conflict[2], ))
1841
1656
elif c_type == 'duplicate':
1842
1657
# files that were renamed take precedence
1658
new_name = tt.final_name(conflict[1])+'.moved'
1843
1659
final_parent = tt.final_parent(conflict[1])
1844
1660
if tt.path_changed(conflict[1]):
1845
existing_file, new_file = conflict[2], conflict[1]
1661
tt.adjust_path(new_name, final_parent, conflict[2])
1662
new_conflicts.add((c_type, 'Moved existing file to',
1663
conflict[2], conflict[1]))
1847
existing_file, new_file = conflict[1], conflict[2]
1848
new_name = tt.final_name(existing_file)+'.moved'
1849
tt.adjust_path(new_name, final_parent, existing_file)
1850
new_conflicts.add((c_type, 'Moved existing file to',
1851
existing_file, new_file))
1665
tt.adjust_path(new_name, final_parent, conflict[1])
1666
new_conflicts.add((c_type, 'Moved existing file to',
1667
conflict[1], conflict[2]))
1852
1668
elif c_type == 'parent loop':
1853
1669
# break the loop by undoing one of the ops that caused the loop
1854
1670
cur = conflict[1]
1867
1683
except KeyError:
1868
1684
tt.create_directory(trans_id)
1869
1685
new_conflicts.add((c_type, 'Created directory', trans_id))
1871
tt.final_name(trans_id)
1873
if path_tree is not None:
1874
file_id = tt.final_file_id(trans_id)
1875
entry = path_tree.inventory[file_id]
1876
parent_trans_id = tt.trans_id_file_id(entry.parent_id)
1877
tt.adjust_path(entry.name, parent_trans_id, trans_id)
1878
1686
elif c_type == 'unversioned parent':
1879
1687
tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
1880
1688
new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
1907
1715
file_id=modified_id,
1908
1716
conflict_path=conflicting_path,
1909
1717
conflict_file_id=conflicting_id)
1912
class _FileMover(object):
1913
"""Moves and deletes files for TreeTransform, tracking operations"""
1916
self.past_renames = []
1917
self.pending_deletions = []
1919
def rename(self, from_, to):
1920
"""Rename a file from one path to another. Functions like os.rename"""
1922
os.rename(from_, to)
1924
if e.errno in (errno.EEXIST, errno.ENOTEMPTY):
1925
raise errors.FileExists(to, str(e))
1927
self.past_renames.append((from_, to))
1929
def pre_delete(self, from_, to):
1930
"""Rename a file out of the way and mark it for deletion.
1932
Unlike os.unlink, this works equally well for files and directories.
1933
:param from_: The current file path
1934
:param to: A temporary path for the file
1936
self.rename(from_, to)
1937
self.pending_deletions.append(to)
1940
"""Reverse all renames that have been performed"""
1941
for from_, to in reversed(self.past_renames):
1942
os.rename(to, from_)
1943
# after rollback, don't reuse _FileMover
1945
pending_deletions = None
1947
def apply_deletions(self):
1948
"""Apply all marked deletions"""
1949
for path in self.pending_deletions:
1951
# after apply_deletions, don't reuse _FileMover
1953
pending_deletions = None