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
38
from bzrlib.symbol_versioning import (
38
39
deprecated_function,
89
90
* create_file or create_directory or create_symlink
91
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
93
130
def __init__(self, tree, pb=DummyProgress()):
94
131
"""Note: a tree_write lock is taken on the tree.
120
157
self._tree.unlock()
160
# counter used to generate trans-ids (which are locally unique)
123
161
self._id_number = 0
162
# mapping of trans_id -> new basename
124
163
self._new_name = {}
164
# mapping of trans_id -> new parent trans_id
125
165
self._new_parent = {}
166
# mapping of trans_id with new contents -> new file_kind
126
167
self._new_contents = {}
127
168
# A mapping of transform ids to their limbo filename
128
169
self._limbo_files = {}
133
174
self._limbo_children_names = {}
134
175
# List of transform ids that need to be renamed from limbo into place
135
176
self._needs_rename = set()
177
# Set of trans_ids whose contents will be removed
136
178
self._removed_contents = set()
179
# Mapping of trans_id -> new execute-bit value
137
180
self._new_executability = {}
181
# Mapping of trans_id -> new tree-reference value
138
182
self._new_reference_revision = {}
183
# Mapping of trans_id -> new file_id
139
184
self._new_id = {}
185
# Mapping of old file-id -> trans_id
140
186
self._non_present_ids = {}
187
# Mapping of new file_id -> trans_id
141
188
self._r_new_id = {}
189
# Set of file_ids that will be removed
142
190
self._removed_id = set()
191
# Mapping of path in old tree -> trans_id
143
192
self._tree_path_ids = {}
193
# Mapping trans_id -> path in old tree
144
194
self._tree_id_paths = {}
145
195
# Cache of realpath results, to speed up canonical_path
146
196
self._realpaths = {}
147
197
# Cache of relpath results, to speed up canonical_path
148
198
self._relpaths = {}
199
# The trans_id that will be used as the tree root
149
200
self._new_root = self.trans_id_tree_file_id(tree.get_root_id())
201
# Indictor of whether the transform has been applied
150
202
self.__done = False
205
# A counter of how many files have been renamed
152
206
self.rename_count = 0
154
208
def __get_root(self):
387
441
target is a bytestring.
388
442
See also new_symlink.
390
os.symlink(target, self._limbo_name(trans_id))
391
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)
393
454
def cancel_creation(self, trans_id):
394
455
"""Cancel the creation of new file contents."""
496
557
# the file is old; the old id is still valid
497
558
if self._new_root == trans_id:
498
return self._tree.inventory.root.file_id
559
return self._tree.get_root_id()
499
560
return self._tree.inventory.path2id(path)
501
562
def final_file_id(self, trans_id):
738
799
for children in by_parent.itervalues():
739
800
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]
742
805
last_trans_id = None
863
926
# the direct path can only be used if no other file has
864
927
# already taken this pathname, i.e. if the name is unused, or
865
928
# if it is already associated with this trans_id.
866
elif (self._limbo_children_names[parent].get(filename)
867
in (trans_id, None)):
868
use_direct_path = True
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
869
943
if use_direct_path:
870
944
limbo_name = pathjoin(self._limbo_files[parent], filename)
871
945
self._limbo_children[parent].add(trans_id)
905
979
self.rename_count += 1
906
980
if trans_id in self._removed_id:
907
981
if trans_id == self._new_root:
908
file_id = self._tree.inventory.root.file_id
982
file_id = self._tree.get_root_id()
910
984
file_id = self.tree_file_id(trans_id)
911
985
assert file_id is not None
945
1020
self.rename_count += 1
946
1021
if trans_id in self._new_contents:
947
1022
modified_paths.append(full_path)
948
del self._new_contents[trans_id]
1023
completed_new.append(trans_id)
950
1025
if trans_id in self._new_id:
951
1026
if kind is None:
992
1067
child_pb.finished()
1068
for trans_id in completed_new:
1069
del self._new_contents[trans_id]
993
1070
return modified_paths
995
1072
def _set_executability(self, path, entry, trans_id):
1283
1362
# is set within the tree, nor setting the root and thus
1284
1363
# marking the tree as dirty, because we use two different
1285
1364
# idioms here: tree interfaces and inventory interfaces.
1286
if wt.path2id('') != tree.inventory.root.file_id:
1287
wt.set_root_id(tree.inventory.root.file_id)
1365
if wt.get_root_id() != tree.get_root_id():
1366
wt.set_root_id(tree.get_root_id())
1289
1368
tt = TreeTransform(wt)
1439
1518
raise errors.BadFileKindError(name, kind)
1441
1521
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
1442
1522
"""Create new file contents according to an inventory entry."""
1443
1523
if entry.kind == "file":
1449
1529
elif entry.kind == "directory":
1450
1530
tt.create_directory(trans_id)
1452
1533
def create_entry_executability(tt, entry, trans_id):
1453
1534
"""Set the executability of a trans_id according to an inventory entry"""
1454
1535
if entry.kind == "file":
1576
1657
pp.next_phase()
1577
1658
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1579
raw_conflicts = resolve_conflicts(tt, child_pb)
1660
raw_conflicts = resolve_conflicts(tt, child_pb,
1661
lambda t, c: conflict_pass(t, c, target_tree))
1581
1663
child_pb.finished()
1582
1664
conflicts = cook_conflicts(raw_conflicts, tt)
1721
1803
conflict[1], conflict[2], ))
1722
1804
elif c_type == 'duplicate':
1723
1805
# files that were renamed take precedence
1724
new_name = tt.final_name(conflict[1])+'.moved'
1725
1806
final_parent = tt.final_parent(conflict[1])
1726
1807
if tt.path_changed(conflict[1]):
1727
tt.adjust_path(new_name, final_parent, conflict[2])
1728
new_conflicts.add((c_type, 'Moved existing file to',
1729
conflict[2], conflict[1]))
1808
existing_file, new_file = conflict[2], conflict[1]
1731
tt.adjust_path(new_name, final_parent, conflict[1])
1732
new_conflicts.add((c_type, 'Moved existing file to',
1733
conflict[1], conflict[2]))
1810
existing_file, new_file = conflict[1], conflict[2]
1811
new_name = tt.final_name(existing_file)+'.moved'
1812
tt.adjust_path(new_name, final_parent, existing_file)
1813
new_conflicts.add((c_type, 'Moved existing file to',
1814
existing_file, new_file))
1734
1815
elif c_type == 'parent loop':
1735
1816
# break the loop by undoing one of the ops that caused the loop
1736
1817
cur = conflict[1]
1753
1834
tt.final_name(trans_id)
1754
1835
except NoFinalPath:
1755
file_id = tt.final_file_id(trans_id)
1756
entry = path_tree.inventory[file_id]
1757
parent_trans_id = tt.trans_id_file_id(entry.parent_id)
1758
tt.adjust_path(entry.name, parent_trans_id, trans_id)
1836
if path_tree is not None:
1837
file_id = tt.final_file_id(trans_id)
1838
entry = path_tree.inventory[file_id]
1839
parent_trans_id = tt.trans_id_file_id(entry.parent_id)
1840
tt.adjust_path(entry.name, parent_trans_id, trans_id)
1759
1841
elif c_type == 'unversioned parent':
1760
1842
tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
1761
1843
new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
1800
1882
def rename(self, from_, to):
1801
1883
"""Rename a file from one path to another. Functions like os.rename"""
1802
os.rename(from_, to)
1885
os.rename(from_, to)
1887
if e.errno in (errno.EEXIST, errno.ENOTEMPTY):
1888
raise errors.FileExists(to, str(e))
1803
1890
self.past_renames.append((from_, to))
1805
1892
def pre_delete(self, from_, to):