30
30
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
31
31
ReusingTransform, NotVersionedError, CantMoveRoot,
32
ExistingLimbo, ImmortalLimbo, NoFinalPath)
32
ExistingLimbo, ImmortalLimbo, NoFinalPath,
33
34
from bzrlib.inventory import InventoryEntry
34
35
from bzrlib.osutils import (file_kind, supports_executable, pathjoin, lexists,
36
delete_any, has_symlinks)
36
37
from bzrlib.progress import DummyProgress, ProgressPhase
37
from bzrlib.symbol_versioning import deprecated_function, zero_fifteen
38
from bzrlib.symbol_versioning import (
38
43
from bzrlib.trace import mutter, warning
39
44
from bzrlib import tree
85
90
* create_file or create_directory or create_symlink
87
92
* 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
89
130
def __init__(self, tree, pb=DummyProgress()):
90
131
"""Note: a tree_write lock is taken on the tree.
104
145
except OSError, e:
105
146
if e.errno == errno.EEXIST:
106
147
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)
108
157
self._tree.unlock()
160
# counter used to generate trans-ids (which are locally unique)
111
161
self._id_number = 0
162
# mapping of trans_id -> new basename
112
163
self._new_name = {}
164
# mapping of trans_id -> new parent trans_id
113
165
self._new_parent = {}
166
# mapping of trans_id with new contents -> new file_kind
114
167
self._new_contents = {}
115
168
# A mapping of transform ids to their limbo filename
116
169
self._limbo_files = {}
121
174
self._limbo_children_names = {}
122
175
# List of transform ids that need to be renamed from limbo into place
123
176
self._needs_rename = set()
177
# Set of trans_ids whose contents will be removed
124
178
self._removed_contents = set()
179
# Mapping of trans_id -> new execute-bit value
125
180
self._new_executability = {}
181
# Mapping of trans_id -> new tree-reference value
126
182
self._new_reference_revision = {}
183
# Mapping of trans_id -> new file_id
127
184
self._new_id = {}
185
# Mapping of old file-id -> trans_id
128
186
self._non_present_ids = {}
187
# Mapping of new file_id -> trans_id
129
188
self._r_new_id = {}
189
# Set of file_ids that will be removed
130
190
self._removed_id = set()
191
# Mapping of path in old tree -> trans_id
131
192
self._tree_path_ids = {}
193
# Mapping trans_id -> path in old tree
132
194
self._tree_id_paths = {}
133
195
# Cache of realpath results, to speed up canonical_path
134
196
self._realpaths = {}
135
197
# Cache of relpath results, to speed up canonical_path
136
198
self._relpaths = {}
199
# The trans_id that will be used as the tree root
137
200
self._new_root = self.trans_id_tree_file_id(tree.get_root_id())
201
# Indictor of whether the transform has been applied
138
202
self.__done = False
205
# A counter of how many files have been renamed
140
206
self.rename_count = 0
142
208
def __get_root(self):
371
441
target is a bytestring.
372
442
See also new_symlink.
374
os.symlink(target, self._limbo_name(trans_id))
375
unique_add(self._new_contents, trans_id, '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)
377
454
def cancel_creation(self, trans_id):
378
455
"""Cancel the creation of new file contents."""
480
557
# the file is old; the old id is still valid
481
558
if self._new_root == trans_id:
482
return self._tree.inventory.root.file_id
559
return self._tree.get_root_id()
483
560
return self._tree.inventory.path2id(path)
485
562
def final_file_id(self, trans_id):
581
658
self.tree_kind(t) == 'directory'])
582
659
for trans_id in self._removed_id:
583
660
file_id = self.tree_file_id(trans_id)
584
if self._tree.inventory[file_id].kind == 'directory':
661
if file_id is not None:
662
if self._tree.inventory[file_id].kind == 'directory':
663
parents.append(trans_id)
664
elif self.tree_kind(trans_id) == 'directory':
585
665
parents.append(trans_id)
587
667
for parent_id in parents:
717
797
def _duplicate_entries(self, by_parent):
718
798
"""No directory may have two entries with the same name."""
800
if (self._new_name, self._new_parent) == ({}, {}):
720
802
for children in by_parent.itervalues():
721
803
name_ids = [(self.final_name(t), t) for t in children]
804
if not self._tree.case_sensitive:
805
name_ids = [(n.lower(), t) for n, t in name_ids]
724
808
last_trans_id = None
870
def apply(self, no_conflicts=False, _mover=None):
787
871
"""Apply all changes to the inventory and filesystem.
789
873
If filesystem or inventory conflicts are present, MalformedTransform
792
876
If apply succeeds, finalize is not necessary.
878
:param no_conflicts: if True, the caller guarantees there are no
879
conflicts, so no check is made.
880
:param _mover: Supply an alternate FileMover, for testing
794
conflicts = self.find_conflicts()
795
if len(conflicts) != 0:
796
raise MalformedTransform(conflicts=conflicts)
883
conflicts = self.find_conflicts()
884
if len(conflicts) != 0:
885
raise MalformedTransform(conflicts=conflicts)
797
886
inv = self._tree.inventory
798
887
inventory_delta = []
799
888
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
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)
895
child_pb.update('Apply phase', 0, 2)
896
self._apply_removals(inv, inventory_delta, mover)
897
child_pb.update('Apply phase', 1, 2)
898
modified_paths = self._apply_insertions(inv, inventory_delta,
904
mover.apply_deletions()
806
906
child_pb.finished()
807
907
self._tree.apply_inventory_delta(inventory_delta)
829
929
# the direct path can only be used if no other file has
830
930
# already taken this pathname, i.e. if the name is unused, or
831
931
# if it is already associated with this trans_id.
832
elif (self._limbo_children_names[parent].get(filename)
833
in (trans_id, None)):
834
use_direct_path = True
932
elif self._tree.case_sensitive:
933
if (self._limbo_children_names[parent].get(filename)
934
in (trans_id, None)):
935
use_direct_path = True
937
for l_filename, l_trans_id in\
938
self._limbo_children_names[parent].iteritems():
939
if l_trans_id == trans_id:
941
if l_filename.lower() == filename.lower():
944
use_direct_path = True
835
946
if use_direct_path:
836
947
limbo_name = pathjoin(self._limbo_files[parent], filename)
837
948
self._limbo_children[parent].add(trans_id)
842
953
self._limbo_files[trans_id] = limbo_name
843
954
return limbo_name
845
def _apply_removals(self, inv, inventory_delta):
956
def _apply_removals(self, inv, inventory_delta, mover):
846
957
"""Perform tree operations that remove directory/inventory names.
848
959
That is, delete files that are to be deleted, and put any files that
858
969
child_pb.update('removing file', num, len(tree_paths))
859
970
full_path = self._tree.abspath(path)
860
971
if trans_id in self._removed_contents:
861
delete_any(full_path)
972
mover.pre_delete(full_path, os.path.join(self._deletiondir,
862
974
elif trans_id in self._new_name or trans_id in \
863
975
self._new_parent:
865
os.rename(full_path, self._limbo_name(trans_id))
977
mover.rename(full_path, self._limbo_name(trans_id))
866
978
except OSError, e:
867
979
if e.errno != errno.ENOENT:
870
982
self.rename_count += 1
871
983
if trans_id in self._removed_id:
872
984
if trans_id == self._new_root:
873
file_id = self._tree.inventory.root.file_id
985
file_id = self._tree.get_root_id()
875
987
file_id = self.tree_file_id(trans_id)
876
assert file_id is not None
877
inventory_delta.append((path, None, file_id, None))
988
if file_id is not None:
989
inventory_delta.append((path, None, file_id, None))
879
991
child_pb.finished()
881
def _apply_insertions(self, inv, inventory_delta):
993
def _apply_insertions(self, inv, inventory_delta, mover):
882
994
"""Perform tree operations that insert directory/inventory names.
884
996
That is, create any files that need to be created, and restore from
901
1014
full_path = self._tree.abspath(path)
902
1015
if trans_id in self._needs_rename:
904
os.rename(self._limbo_name(trans_id), full_path)
1017
mover.rename(self._limbo_name(trans_id), full_path)
905
1018
except OSError, e:
906
1019
# We may be renaming a dangling inventory id
907
1020
if e.errno != errno.ENOENT:
910
1023
self.rename_count += 1
911
1024
if trans_id in self._new_contents:
912
1025
modified_paths.append(full_path)
913
del self._new_contents[trans_id]
1026
completed_new.append(trans_id)
915
1028
if trans_id in self._new_id:
916
1029
if kind is None:
957
1070
child_pb.finished()
1071
for trans_id in completed_new:
1072
del self._new_contents[trans_id]
958
1073
return modified_paths
960
1075
def _set_executability(self, path, entry, trans_id):
1209
1325
return file_ids
1212
def build_tree(tree, wt):
1328
def build_tree(tree, wt, accelerator_tree=None):
1213
1329
"""Create working tree for a branch, using a TreeTransform.
1215
1331
This function should be used on empty trees, having a tree root at most.
1222
1338
- Otherwise, if the content on disk matches the content we are building,
1223
1339
it is silently replaced.
1224
1340
- Otherwise, conflict resolution will move the old file to 'oldname.moved'.
1342
:param tree: The tree to convert wt into a copy of
1343
:param wt: The working tree that files will be placed into
1344
:param accelerator_tree: A tree which can be used for retrieving file
1345
contents more quickly than tree itself, i.e. a workingtree. tree
1346
will be used for cases where accelerator_tree's content is different.
1226
1348
wt.lock_tree_write()
1228
1350
tree.lock_read()
1230
return _build_tree(tree, wt)
1352
if accelerator_tree is not None:
1353
accelerator_tree.lock_read()
1355
return _build_tree(tree, wt, accelerator_tree)
1357
if accelerator_tree is not None:
1358
accelerator_tree.unlock()
1236
def _build_tree(tree, wt):
1365
def _build_tree(tree, wt, accelerator_tree):
1237
1366
"""See build_tree."""
1238
1367
if len(wt.inventory) > 1: # more than just a root
1239
1368
raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
1248
1377
# is set within the tree, nor setting the root and thus
1249
1378
# marking the tree as dirty, because we use two different
1250
1379
# idioms here: tree interfaces and inventory interfaces.
1251
if wt.path2id('') != tree.inventory.root.file_id:
1252
wt.set_root_id(tree.inventory.root.file_id)
1380
if wt.get_root_id() != tree.get_root_id():
1381
wt.set_root_id(tree.get_root_id())
1254
1383
tt = TreeTransform(wt)
1259
1388
tt.trans_id_tree_file_id(wt.get_root_id())
1260
1389
pb = bzrlib.ui.ui_factory.nested_progress_bar()
1391
deferred_contents = []
1262
1392
for num, (tree_path, entry) in \
1263
1393
enumerate(tree.inventory.iter_entries_by_dir()):
1264
pb.update("Building tree", num, len(tree.inventory))
1394
pb.update("Building tree", num - len(deferred_contents),
1395
len(tree.inventory))
1265
1396
if entry.parent_id is None:
1267
1398
reparent = False
1290
1421
'entry %s parent id %r is not in file_trans_id %r'
1291
1422
% (entry, entry.parent_id, file_trans_id))
1292
1423
parent_id = file_trans_id[entry.parent_id]
1293
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1424
if entry.kind == 'file':
1425
# We *almost* replicate new_by_entry, so that we can defer
1426
# getting the file text, and get them all at once.
1427
trans_id = tt.create_path(entry.name, parent_id)
1428
file_trans_id[file_id] = trans_id
1429
tt.version_file(entry.file_id, trans_id)
1430
executable = tree.is_executable(entry.file_id, tree_path)
1431
if executable is not None:
1432
tt.set_executability(executable, trans_id)
1433
deferred_contents.append((entry.file_id, trans_id))
1435
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1296
1438
new_trans_id = file_trans_id[file_id]
1297
1439
old_parent = tt.trans_id_tree_path(tree_path)
1298
1440
_reparent_children(tt, old_parent, new_trans_id)
1441
for num, (trans_id, bytes) in enumerate(
1442
_iter_files_bytes_accelerated(tree, accelerator_tree,
1443
deferred_contents)):
1444
tt.create_file(bytes, trans_id)
1445
pb.update('Adding file contents',
1446
(num + len(tree.inventory) - len(deferred_contents)),
1447
len(tree.inventory))
1301
1450
pp.next_phase()
1309
1458
wt.add_conflicts(conflicts)
1310
1459
except errors.UnsupportedOperation:
1461
result = tt.apply(no_conflicts=True)
1315
1464
top_pb.finished()
1468
def _iter_files_bytes_accelerated(tree, accelerator_tree, desired_files):
1469
if accelerator_tree is None:
1470
new_desired_files = desired_files
1472
iter = accelerator_tree._iter_changes(tree, include_unchanged=True)
1473
unchanged = dict((f, p[1]) for (f, p, c, v, d, n, k, e)
1475
new_desired_files = []
1476
for file_id, identifier in desired_files:
1477
accelerator_path = unchanged.get(file_id)
1478
if accelerator_path is None:
1479
new_desired_files.append((file_id, identifier))
1481
contents = accelerator_tree.get_file(file_id, accelerator_path)
1484
contents_bytes = (contents.read(),)
1487
yield identifier, contents_bytes
1488
for result in tree.iter_files_bytes(new_desired_files):
1319
1492
def _reparent_children(tt, old_parent, new_parent):
1320
1493
for child in tt.iter_tree_children(old_parent):
1321
1494
tt.adjust_path(tt.final_name(child), new_parent, child)
1496
def _reparent_transform_children(tt, old_parent, new_parent):
1497
by_parent = tt.by_parent()
1498
for child in by_parent[old_parent]:
1499
tt.adjust_path(tt.final_name(child), new_parent, child)
1324
1501
def _content_match(tree, entry, file_id, kind, target_path):
1325
1502
if entry.kind != kind:
1385
1562
raise errors.BadFileKindError(name, kind)
1387
1565
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
1388
1566
"""Create new file contents according to an inventory entry."""
1389
1567
if entry.kind == "file":
1395
1573
elif entry.kind == "directory":
1396
1574
tt.create_directory(trans_id)
1398
1577
def create_entry_executability(tt, entry, trans_id):
1399
1578
"""Set the executability of a trans_id according to an inventory entry"""
1400
1579
if entry.kind == "file":
1418
1597
working_tree.unlock()
1600
@deprecated_function(zero_ninety)
1421
1601
def change_entry(tt, file_id, working_tree, target_tree,
1422
1602
trans_id_file_id, backups, trans_id, by_parent):
1423
1603
"""Replace a file_id's contents with those from a target tree."""
1604
if file_id is None and target_tree is None:
1605
# skip the logic altogether in the deprecation test
1424
1607
e_trans_id = trans_id_file_id(file_id)
1425
1608
entry = target_tree.inventory[file_id]
1426
1609
has_contents, contents_mod, meta_mod, = _entry_changes(file_id, entry,
1518
1701
pp.next_phase()
1519
1702
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1521
raw_conflicts = resolve_conflicts(tt, child_pb)
1704
raw_conflicts = resolve_conflicts(tt, child_pb,
1705
lambda t, c: conflict_pass(t, c, target_tree))
1523
1707
child_pb.finished()
1524
1708
conflicts = cook_conflicts(raw_conflicts, tt)
1549
1733
skip_root = False
1550
1734
basis_tree = None
1552
1737
for id_num, (file_id, path, changed_content, versioned, parent, name,
1553
1738
kind, executable) in enumerate(change_list):
1554
1739
if skip_root and file_id[0] is not None and parent[0] is None:
1594
1779
tt.create_symlink(target_tree.get_symlink_target(file_id),
1596
1781
elif kind[1] == 'file':
1597
tt.create_file(target_tree.get_file_lines(file_id),
1782
deferred_files.append((file_id, (trans_id, mode_id)))
1599
1783
if basis_tree is None:
1600
1784
basis_tree = working_tree.basis_tree()
1601
1785
basis_tree.lock_read()
1622
1806
name[1], tt.trans_id_file_id(parent[1]), trans_id)
1623
1807
if executable[0] != executable[1] and kind[1] == "file":
1624
1808
tt.set_executability(executable[1], trans_id)
1809
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
1811
tt.create_file(bytes, trans_id, mode_id)
1626
1813
if basis_tree is not None:
1627
1814
basis_tree.unlock()
1648
def conflict_pass(tt, conflicts):
1649
"""Resolve some classes of conflicts."""
1835
def conflict_pass(tt, conflicts, path_tree=None):
1836
"""Resolve some classes of conflicts.
1838
:param tt: The transform to resolve conflicts in
1839
:param conflicts: The conflicts to resolve
1840
:param path_tree: A Tree to get supplemental paths from
1650
1842
new_conflicts = set()
1651
1843
for c_type, conflict in ((c[0], c) for c in conflicts):
1652
1844
if c_type == 'duplicate id':
1655
1847
conflict[1], conflict[2], ))
1656
1848
elif c_type == 'duplicate':
1657
1849
# files that were renamed take precedence
1658
new_name = tt.final_name(conflict[1])+'.moved'
1659
1850
final_parent = tt.final_parent(conflict[1])
1660
1851
if tt.path_changed(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]))
1852
existing_file, new_file = conflict[2], conflict[1]
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]))
1854
existing_file, new_file = conflict[1], conflict[2]
1855
new_name = tt.final_name(existing_file)+'.moved'
1856
tt.adjust_path(new_name, final_parent, existing_file)
1857
new_conflicts.add((c_type, 'Moved existing file to',
1858
existing_file, new_file))
1668
1859
elif c_type == 'parent loop':
1669
1860
# break the loop by undoing one of the ops that caused the loop
1670
1861
cur = conflict[1]
1683
1874
except KeyError:
1684
1875
tt.create_directory(trans_id)
1685
1876
new_conflicts.add((c_type, 'Created directory', trans_id))
1878
tt.final_name(trans_id)
1880
if path_tree is not None:
1881
file_id = tt.final_file_id(trans_id)
1882
entry = path_tree.inventory[file_id]
1883
parent_trans_id = tt.trans_id_file_id(entry.parent_id)
1884
tt.adjust_path(entry.name, parent_trans_id, trans_id)
1686
1885
elif c_type == 'unversioned parent':
1687
1886
tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
1688
1887
new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
1888
elif c_type == 'non-directory parent':
1889
parent_id = conflict[1]
1890
parent_parent = tt.final_parent(parent_id)
1891
parent_name = tt.final_name(parent_id)
1892
parent_file_id = tt.final_file_id(parent_id)
1893
new_parent_id = tt.new_directory(parent_name + '.new',
1894
parent_parent, parent_file_id)
1895
_reparent_transform_children(tt, parent_id, new_parent_id)
1896
tt.unversion_file(parent_id)
1897
new_conflicts.add((c_type, 'Created directory', new_parent_id))
1689
1898
return new_conflicts
1715
1924
file_id=modified_id,
1716
1925
conflict_path=conflicting_path,
1717
1926
conflict_file_id=conflicting_id)
1929
class _FileMover(object):
1930
"""Moves and deletes files for TreeTransform, tracking operations"""
1933
self.past_renames = []
1934
self.pending_deletions = []
1936
def rename(self, from_, to):
1937
"""Rename a file from one path to another. Functions like os.rename"""
1939
os.rename(from_, to)
1941
if e.errno in (errno.EEXIST, errno.ENOTEMPTY):
1942
raise errors.FileExists(to, str(e))
1944
self.past_renames.append((from_, to))
1946
def pre_delete(self, from_, to):
1947
"""Rename a file out of the way and mark it for deletion.
1949
Unlike os.unlink, this works equally well for files and directories.
1950
:param from_: The current file path
1951
:param to: A temporary path for the file
1953
self.rename(from_, to)
1954
self.pending_deletions.append(to)
1957
"""Reverse all renames that have been performed"""
1958
for from_, to in reversed(self.past_renames):
1959
os.rename(to, from_)
1960
# after rollback, don't reuse _FileMover
1962
pending_deletions = None
1964
def apply_deletions(self):
1965
"""Apply all marked deletions"""
1966
for path in self.pending_deletions:
1968
# after apply_deletions, don't reuse _FileMover
1970
pending_deletions = None