33
32
revision as _mod_revision,
37
35
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
38
ReusingTransform, CantMoveRoot,
36
ReusingTransform, NotVersionedError, CantMoveRoot,
39
37
ExistingLimbo, ImmortalLimbo, NoFinalPath,
40
38
UnableCreateSymlink)
41
39
from bzrlib.filters import filtered_output_bytes, ContentFilterContext
80
78
class TreeTransformBase(object):
81
79
"""The base class for TreeTransform and its kin."""
83
def __init__(self, tree, pb=None,
81
def __init__(self, tree, pb=DummyProgress(),
84
82
case_sensitive=True):
87
85
:param tree: The tree that will be transformed, but not necessarily
87
:param pb: A ProgressBar indicating how much progress is being made
90
88
:param case_sensitive: If True, the target of the transform is
91
89
case sensitive, not just case preserving.
164
162
def adjust_path(self, name, parent, trans_id):
165
163
"""Change the path that is assigned to a transaction id."""
167
raise ValueError("Parent trans-id may not be None")
168
164
if trans_id == self._new_root:
169
165
raise CantMoveRoot
170
166
self._new_name[trans_id] = name
171
167
self._new_parent[trans_id] = parent
168
if parent == ROOT_PARENT:
169
if self._new_root is not None:
170
raise ValueError("Cannot have multiple roots.")
171
self._new_root = trans_id
173
173
def adjust_root_path(self, name, parent):
174
174
"""Emulate moving the root by moving all children, instead.
202
202
self.version_file(old_root_file_id, old_root)
203
203
self.unversion_file(self._new_root)
205
def fixup_new_roots(self):
206
"""Reinterpret requests to change the root directory
208
Instead of creating a root directory, or moving an existing directory,
209
all the attributes and children of the new root are applied to the
210
existing root directory.
212
This means that the old root trans-id becomes obsolete, so it is
213
recommended only to invoke this after the root trans-id has become
216
new_roots = [k for k, v in self._new_parent.iteritems() if v is
218
if len(new_roots) < 1:
220
if len(new_roots) != 1:
221
raise ValueError('A tree cannot have two roots!')
222
if self._new_root is None:
223
self._new_root = new_roots[0]
225
old_new_root = new_roots[0]
226
# TODO: What to do if a old_new_root is present, but self._new_root is
227
# not listed as being removed? This code explicitly unversions
228
# the old root and versions it with the new file_id. Though that
229
# seems like an incomplete delta
231
# unversion the new root's directory.
232
file_id = self.final_file_id(old_new_root)
233
if old_new_root in self._new_id:
234
self.cancel_versioning(old_new_root)
236
self.unversion_file(old_new_root)
237
# if, at this stage, root still has an old file_id, zap it so we can
238
# stick a new one in.
239
if (self.tree_file_id(self._new_root) is not None and
240
self._new_root not in self._removed_id):
241
self.unversion_file(self._new_root)
242
self.version_file(file_id, self._new_root)
244
# Now move children of new root into old root directory.
245
# Ensure all children are registered with the transaction, but don't
246
# use directly-- some tree children have new parents
247
list(self.iter_tree_children(old_new_root))
248
# Move all children of new root into old root directory.
249
for child in self.by_parent().get(old_new_root, []):
250
self.adjust_path(self.final_name(child), self._new_root, child)
252
# Ensure old_new_root has no directory.
253
if old_new_root in self._new_contents:
254
self.cancel_creation(old_new_root)
256
self.delete_contents(old_new_root)
258
# prevent deletion of root directory.
259
if self._new_root in self._removed_contents:
260
self.cancel_deletion(self._new_root)
262
# destroy path info for old_new_root.
263
del self._new_parent[old_new_root]
264
del self._new_name[old_new_root]
266
205
def trans_id_tree_file_id(self, inventory_id):
267
206
"""Determine the transaction id of a working tree file.
1063
994
class DiskTreeTransform(TreeTransformBase):
1064
995
"""Tree transform storing its contents on disk."""
1066
def __init__(self, tree, limbodir, pb=None,
997
def __init__(self, tree, limbodir, pb=DummyProgress(),
1067
998
case_sensitive=True):
1069
1000
:param tree: The tree that will be transformed, but not necessarily
1070
1001
the output tree.
1071
1002
:param limbodir: A directory where new files can be stored until
1072
1003
they are installed in their proper places
1004
:param pb: A ProgressBar indicating how much progress is being made
1074
1005
:param case_sensitive: If True, the target of the transform is
1075
1006
case sensitive, not just case preserving.
1118
1048
def _limbo_name(self, trans_id):
1119
1049
"""Generate the limbo name of a file"""
1120
1050
limbo_name = self._limbo_files.get(trans_id)
1121
if limbo_name is None:
1122
limbo_name = self._generate_limbo_path(trans_id)
1123
self._limbo_files[trans_id] = limbo_name
1051
if limbo_name is not None:
1053
parent = self._new_parent.get(trans_id)
1054
# if the parent directory is already in limbo (e.g. when building a
1055
# tree), choose a limbo name inside the parent, to reduce further
1057
use_direct_path = False
1058
if self._new_contents.get(parent) == 'directory':
1059
filename = self._new_name.get(trans_id)
1060
if filename is not None:
1061
if parent not in self._limbo_children:
1062
self._limbo_children[parent] = set()
1063
self._limbo_children_names[parent] = {}
1064
use_direct_path = True
1065
# the direct path can only be used if no other file has
1066
# already taken this pathname, i.e. if the name is unused, or
1067
# if it is already associated with this trans_id.
1068
elif self._case_sensitive_target:
1069
if (self._limbo_children_names[parent].get(filename)
1070
in (trans_id, None)):
1071
use_direct_path = True
1073
for l_filename, l_trans_id in\
1074
self._limbo_children_names[parent].iteritems():
1075
if l_trans_id == trans_id:
1077
if l_filename.lower() == filename.lower():
1080
use_direct_path = True
1083
limbo_name = pathjoin(self._limbo_files[parent], filename)
1084
self._limbo_children[parent].add(trans_id)
1085
self._limbo_children_names[parent][filename] = trans_id
1087
limbo_name = pathjoin(self._limbodir, trans_id)
1088
self._needs_rename.add(trans_id)
1089
self._limbo_files[trans_id] = limbo_name
1124
1090
return limbo_name
1126
def _generate_limbo_path(self, trans_id):
1127
"""Generate a limbo path using the trans_id as the relative path.
1129
This is suitable as a fallback, and when the transform should not be
1130
sensitive to the path encoding of the limbo directory.
1132
self._needs_rename.add(trans_id)
1133
return pathjoin(self._limbodir, trans_id)
1135
1092
def adjust_path(self, name, parent, trans_id):
1136
1093
previous_parent = self._new_parent.get(trans_id)
1137
1094
previous_name = self._new_name.get(trans_id)
1139
1096
if (trans_id in self._limbo_files and
1140
1097
trans_id not in self._needs_rename):
1141
1098
self._rename_in_limbo([trans_id])
1142
if previous_parent != parent:
1143
self._limbo_children[previous_parent].remove(trans_id)
1144
if previous_parent != parent or previous_name != name:
1145
del self._limbo_children_names[previous_parent][previous_name]
1099
self._limbo_children[previous_parent].remove(trans_id)
1100
del self._limbo_children_names[previous_parent][previous_name]
1147
1102
def _rename_in_limbo(self, trans_ids):
1148
1103
"""Fix limbo names so that the right final path is produced.
1160
1115
if trans_id not in self._new_contents:
1162
1117
new_path = self._limbo_name(trans_id)
1163
osutils.rename(old_path, new_path)
1164
for descendant in self._limbo_descendants(trans_id):
1165
desc_path = self._limbo_files[descendant]
1166
desc_path = new_path + desc_path[len(old_path):]
1167
self._limbo_files[descendant] = desc_path
1169
def _limbo_descendants(self, trans_id):
1170
"""Return the set of trans_ids whose limbo paths descend from this."""
1171
descendants = set(self._limbo_children.get(trans_id, []))
1172
for descendant in list(descendants):
1173
descendants.update(self._limbo_descendants(descendant))
1118
os.rename(old_path, new_path)
1176
1120
def create_file(self, contents, trans_id, mode_id=None):
1177
1121
"""Schedule creation of a new file.
1212
1155
def _read_symlink_target(self, trans_id):
1213
1156
return os.readlink(self._limbo_name(trans_id))
1215
def _set_mtime(self, path):
1216
"""All files that are created get the same mtime.
1218
This time is set by the first object to be created.
1220
if self._creation_mtime is None:
1221
self._creation_mtime = time.time()
1222
os.utime(path, (self._creation_mtime, self._creation_mtime))
1224
1158
def create_hardlink(self, path, trans_id):
1225
1159
"""Schedule creation of a hard link"""
1226
1160
name = self._limbo_name(trans_id)
1457
1391
yield self.trans_id_tree_path(childpath)
1459
def _generate_limbo_path(self, trans_id):
1460
"""Generate a limbo path using the final path if possible.
1462
This optimizes the performance of applying the tree transform by
1463
avoiding renames. These renames can be avoided only when the parent
1464
directory is already scheduled for creation.
1466
If the final path cannot be used, falls back to using the trans_id as
1469
parent = self._new_parent.get(trans_id)
1470
# if the parent directory is already in limbo (e.g. when building a
1471
# tree), choose a limbo name inside the parent, to reduce further
1473
use_direct_path = False
1474
if self._new_contents.get(parent) == 'directory':
1475
filename = self._new_name.get(trans_id)
1476
if filename is not None:
1477
if parent not in self._limbo_children:
1478
self._limbo_children[parent] = set()
1479
self._limbo_children_names[parent] = {}
1480
use_direct_path = True
1481
# the direct path can only be used if no other file has
1482
# already taken this pathname, i.e. if the name is unused, or
1483
# if it is already associated with this trans_id.
1484
elif self._case_sensitive_target:
1485
if (self._limbo_children_names[parent].get(filename)
1486
in (trans_id, None)):
1487
use_direct_path = True
1489
for l_filename, l_trans_id in\
1490
self._limbo_children_names[parent].iteritems():
1491
if l_trans_id == trans_id:
1493
if l_filename.lower() == filename.lower():
1496
use_direct_path = True
1498
if not use_direct_path:
1499
return DiskTreeTransform._generate_limbo_path(self, trans_id)
1501
limbo_name = pathjoin(self._limbo_files[parent], filename)
1502
self._limbo_children[parent].add(trans_id)
1503
self._limbo_children_names[parent][filename] = trans_id
1507
1394
def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1508
1395
"""Apply all changes to the inventory and filesystem.
1629
1518
child_pb.update('removing file', num, len(tree_paths))
1630
1519
full_path = self._tree.abspath(path)
1631
1520
if trans_id in self._removed_contents:
1632
delete_path = os.path.join(self._deletiondir, trans_id)
1633
mover.pre_delete(full_path, delete_path)
1634
elif (trans_id in self._new_name
1635
or trans_id in self._new_parent):
1521
mover.pre_delete(full_path, os.path.join(self._deletiondir,
1523
elif trans_id in self._new_name or trans_id in \
1637
1526
mover.rename(full_path, self._limbo_name(trans_id))
1638
1527
except OSError, e:
1692
1581
unversioned files in the input tree.
1695
def __init__(self, tree, pb=None, case_sensitive=True):
1584
def __init__(self, tree, pb=DummyProgress(), case_sensitive=True):
1696
1585
tree.lock_read()
1697
1586
limbodir = osutils.mkdtemp(prefix='bzr-limbo-')
1698
1587
DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive)
1743
1632
self._all_children_cache = {}
1744
1633
self._path2trans_id_cache = {}
1745
1634
self._final_name_cache = {}
1746
self._iter_changes_cache = dict((c[0], c) for c in
1747
self._transform.iter_changes())
1636
def _changes(self, file_id):
1637
for changes in self._transform.iter_changes():
1638
if changes[0] == file_id:
1749
1641
def _content_change(self, file_id):
1750
1642
"""Return True if the content of this file changed"""
1751
changes = self._iter_changes_cache.get(file_id)
1643
changes = self._changes(file_id)
1752
1644
# changes[2] is true if the file content changed. See
1753
1645
# InterTree.iter_changes.
1754
1646
return (changes is not None and changes[2])
1823
1715
def __iter__(self):
1824
1716
return iter(self.all_file_ids())
1826
def _has_id(self, file_id, fallback_check):
1718
def has_id(self, file_id):
1827
1719
if file_id in self._transform._r_new_id:
1829
1721
elif file_id in set([self._transform.tree_file_id(trans_id) for
1830
1722
trans_id in self._transform._removed_id]):
1833
return fallback_check(file_id)
1835
def has_id(self, file_id):
1836
return self._has_id(file_id, self._transform._tree.has_id)
1838
def has_or_had_id(self, file_id):
1839
return self._has_id(file_id, self._transform._tree.has_or_had_id)
1725
return self._transform._tree.has_id(file_id)
1841
1727
def _path2trans_id(self, path):
1842
1728
# We must not use None here, because that is a valid value to store.
1895
1781
if self._transform.final_file_id(trans_id) is None:
1896
1782
yield self._final_paths._determine_path(trans_id)
1898
def _make_inv_entries(self, ordered_entries, specific_file_ids=None,
1899
yield_parents=False):
1784
def _make_inv_entries(self, ordered_entries, specific_file_ids=None):
1900
1785
for trans_id, parent_file_id in ordered_entries:
1901
1786
file_id = self._transform.final_file_id(trans_id)
1902
1787
if file_id is None:
1928
1813
ordered_ids.append((trans_id, parent_file_id))
1929
1814
return ordered_ids
1931
def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
1816
def iter_entries_by_dir(self, specific_file_ids=None):
1932
1817
# This may not be a maximally efficient implementation, but it is
1933
1818
# reasonably straightforward. An implementation that grafts the
1934
1819
# TreeTransform changes onto the tree's iter_entries_by_dir results
1989
1874
def get_file_mtime(self, file_id, path=None):
1990
1875
"""See Tree.get_file_mtime"""
1991
1876
if not self._content_change(file_id):
1992
return self._transform._tree.get_file_mtime(file_id)
1877
return self._transform._tree.get_file_mtime(file_id, path)
1993
1878
return self._stat_limbo_file(file_id).st_mtime
1995
1880
def _file_size(self, entry, stat_value):
2057
1942
executable = None
2058
1943
if kind == 'symlink':
2059
1944
link_or_sha1 = os.readlink(limbo_name).decode(osutils._fs_enc)
2060
executable = tt._new_executability.get(trans_id, executable)
1945
if supports_executable():
1946
executable = tt._new_executability.get(trans_id, executable)
2061
1947
return kind, size, executable, link_or_sha1
2063
1949
def iter_changes(self, from_tree, include_unchanged=False,
2376
2262
new_desired_files = desired_files
2378
2264
iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
2379
unchanged = [(f, p[1]) for (f, p, c, v, d, n, k, e)
2380
in iter if not (c or e[0] != e[1])]
2381
if accelerator_tree.supports_content_filtering():
2382
unchanged = [(f, p) for (f, p) in unchanged
2383
if not accelerator_tree.iter_search_rules([p]).next()]
2384
unchanged = dict(unchanged)
2265
unchanged = dict((f, p[1]) for (f, p, c, v, d, n, k, e)
2266
in iter if not (c or e[0] != e[1]))
2385
2267
new_desired_files = []
2387
2269
for file_id, (trans_id, tree_path) in desired_files:
2510
2392
tt.create_directory(trans_id)
2513
def create_from_tree(tt, trans_id, tree, file_id, bytes=None,
2514
filter_tree_path=None):
2515
"""Create new file contents according to tree contents.
2517
:param filter_tree_path: the tree path to use to lookup
2518
content filters to apply to the bytes output in the working tree.
2519
This only applies if the working tree supports content filtering.
2395
def create_from_tree(tt, trans_id, tree, file_id, bytes=None):
2396
"""Create new file contents according to tree contents."""
2521
2397
kind = tree.kind(file_id)
2522
2398
if kind == 'directory':
2523
2399
tt.create_directory(trans_id)
2528
2404
bytes = tree_file.readlines()
2530
2406
tree_file.close()
2532
if wt.supports_content_filtering() and filter_tree_path is not None:
2533
filters = wt._content_filter_stack(filter_tree_path)
2534
bytes = filtered_output_bytes(bytes, filters,
2535
ContentFilterContext(filter_tree_path, tree))
2536
2407
tt.create_file(bytes, trans_id)
2537
2408
elif kind == "symlink":
2538
2409
tt.create_symlink(tree.get_symlink_target(file_id), trans_id)
2592
2463
def revert(working_tree, target_tree, filenames, backups=False,
2593
pb=None, change_reporter=None):
2464
pb=DummyProgress(), change_reporter=None):
2594
2465
"""Revert a working tree's contents to those of a target tree."""
2595
2466
target_tree.lock_read()
2596
pb = ui.ui_factory.nested_progress_bar()
2597
2467
tt = TreeTransform(working_tree, pb)
2599
2469
pp = ProgressPhase("Revert phase", 3, pb)
2725
2597
parent_trans = ROOT_PARENT
2727
2599
parent_trans = tt.trans_id_file_id(parent[1])
2728
if parent[0] is None and versioned[0]:
2729
tt.adjust_root_path(name[1], parent_trans)
2731
tt.adjust_path(name[1], parent_trans, trans_id)
2600
tt.adjust_path(name[1], parent_trans, trans_id)
2732
2601
if executable[0] != executable[1] and kind[1] == "file":
2733
2602
tt.set_executability(executable[1], trans_id)
2734
if working_tree.supports_content_filtering():
2735
for index, ((trans_id, mode_id), bytes) in enumerate(
2736
target_tree.iter_files_bytes(deferred_files)):
2737
file_id = deferred_files[index][0]
2738
# We're reverting a tree to the target tree so using the
2739
# target tree to find the file path seems the best choice
2740
# here IMO - Ian C 27/Oct/2009
2741
filter_tree_path = target_tree.id2path(file_id)
2742
filters = working_tree._content_filter_stack(filter_tree_path)
2743
bytes = filtered_output_bytes(bytes, filters,
2744
ContentFilterContext(filter_tree_path, working_tree))
2745
tt.create_file(bytes, trans_id, mode_id)
2747
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2749
tt.create_file(bytes, trans_id, mode_id)
2750
tt.fixup_new_roots()
2603
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2605
tt.create_file(bytes, trans_id, mode_id)
2752
2607
if basis_tree is not None:
2753
2608
basis_tree.unlock()
2754
2609
return merge_modified
2757
def resolve_conflicts(tt, pb=None, pass_func=None):
2612
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
2758
2613
"""Make many conflict-resolution attempts, but die if they fail"""
2759
2614
if pass_func is None:
2760
2615
pass_func = conflict_pass
2761
2616
new_conflicts = set()
2762
pb = ui.ui_factory.nested_progress_bar()
2764
2618
for n in range(10):
2765
2619
pb.update('Resolution pass', n+1, 10)
2898
2752
self.pending_deletions = []
2900
2754
def rename(self, from_, to):
2901
"""Rename a file from one path to another."""
2755
"""Rename a file from one path to another. Functions like os.rename"""
2903
osutils.rename(from_, to)
2757
os.rename(from_, to)
2904
2758
except OSError, e:
2905
2759
if e.errno in (errno.EEXIST, errno.ENOTEMPTY):
2906
2760
raise errors.FileExists(to, str(e))