32
34
revision as _mod_revision,
35
38
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
36
ReusingTransform, NotVersionedError, CantMoveRoot,
39
ReusingTransform, CantMoveRoot,
37
40
ExistingLimbo, ImmortalLimbo, NoFinalPath,
38
41
UnableCreateSymlink)
39
42
from bzrlib.filters import filtered_output_bytes, ContentFilterContext
78
81
class TreeTransformBase(object):
79
82
"""The base class for TreeTransform and its kin."""
81
def __init__(self, tree, pb=DummyProgress(),
84
def __init__(self, tree, pb=None,
82
85
case_sensitive=True):
85
88
:param tree: The tree that will be transformed, but not necessarily
87
:param pb: A ProgressBar indicating how much progress is being made
88
91
:param case_sensitive: If True, the target of the transform is
89
92
case sensitive, not just case preserving.
162
165
def adjust_path(self, name, parent, trans_id):
163
166
"""Change the path that is assigned to a transaction id."""
168
raise ValueError("Parent trans-id may not be None")
164
169
if trans_id == self._new_root:
165
170
raise CantMoveRoot
166
171
self._new_name[trans_id] = name
167
172
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
174
def adjust_root_path(self, name, parent):
174
175
"""Emulate moving the root by moving all children, instead.
202
203
self.version_file(old_root_file_id, old_root)
203
204
self.unversion_file(self._new_root)
206
def fixup_new_roots(self):
207
"""Reinterpret requests to change the root directory
209
Instead of creating a root directory, or moving an existing directory,
210
all the attributes and children of the new root are applied to the
211
existing root directory.
213
This means that the old root trans-id becomes obsolete, so it is
214
recommended only to invoke this after the root trans-id has become
217
new_roots = [k for k, v in self._new_parent.iteritems() if v is
219
if len(new_roots) < 1:
221
if len(new_roots) != 1:
222
raise ValueError('A tree cannot have two roots!')
223
if self._new_root is None:
224
self._new_root = new_roots[0]
226
old_new_root = new_roots[0]
227
# TODO: What to do if a old_new_root is present, but self._new_root is
228
# not listed as being removed? This code explicitly unversions
229
# the old root and versions it with the new file_id. Though that
230
# seems like an incomplete delta
232
# unversion the new root's directory.
233
file_id = self.final_file_id(old_new_root)
234
if old_new_root in self._new_id:
235
self.cancel_versioning(old_new_root)
237
self.unversion_file(old_new_root)
238
# if, at this stage, root still has an old file_id, zap it so we can
239
# stick a new one in.
240
if (self.tree_file_id(self._new_root) is not None and
241
self._new_root not in self._removed_id):
242
self.unversion_file(self._new_root)
243
self.version_file(file_id, self._new_root)
245
# Now move children of new root into old root directory.
246
# Ensure all children are registered with the transaction, but don't
247
# use directly-- some tree children have new parents
248
list(self.iter_tree_children(old_new_root))
249
# Move all children of new root into old root directory.
250
for child in self.by_parent().get(old_new_root, []):
251
self.adjust_path(self.final_name(child), self._new_root, child)
253
# Ensure old_new_root has no directory.
254
if old_new_root in self._new_contents:
255
self.cancel_creation(old_new_root)
257
self.delete_contents(old_new_root)
259
# prevent deletion of root directory.
260
if self._new_root in self._removed_contents:
261
self.cancel_deletion(self._new_root)
263
# destroy path info for old_new_root.
264
del self._new_parent[old_new_root]
265
del self._new_name[old_new_root]
205
267
def trans_id_tree_file_id(self, inventory_id):
206
268
"""Determine the transaction id of a working tree file.
859
923
def get_preview_tree(self):
860
924
"""Return a tree representing the result of the transform.
862
This tree only supports the subset of Tree functionality required
863
by show_diff_trees. It must only be compared to tt._tree.
926
The tree is a snapshot, and altering the TreeTransform will invalidate
865
929
return _PreviewTree(self)
867
def commit(self, branch, message, merge_parents=None, strict=False):
931
def commit(self, branch, message, merge_parents=None, strict=False,
932
timestamp=None, timezone=None, committer=None, authors=None,
933
revprops=None, revision_id=None):
868
934
"""Commit the result of this TreeTransform to a branch.
870
936
:param branch: The branch to commit to.
871
937
:param message: The message to attach to the commit.
872
:param merge_parents: Additional parents specified by pending merges.
938
:param merge_parents: Additional parent revision-ids specified by
940
:param strict: If True, abort the commit if there are unversioned
942
:param timestamp: if not None, seconds-since-epoch for the time and
943
date. (May be a float.)
944
:param timezone: Optional timezone for timestamp, as an offset in
946
:param committer: Optional committer in email-id format.
947
(e.g. "J Random Hacker <jrandom@example.com>")
948
:param authors: Optional list of authors in email-id format.
949
:param revprops: Optional dictionary of revision properties.
950
:param revision_id: Optional revision id. (Specifying a revision-id
951
may reduce performance for some non-native formats.)
873
952
:return: The revision_id of the revision committed.
875
954
self._check_malformed()
892
971
if self._tree.get_revision_id() != last_rev_id:
893
972
raise ValueError('TreeTransform not based on branch basis: %s' %
894
973
self._tree.get_revision_id())
895
builder = branch.get_commit_builder(parent_ids)
974
revprops = commit.Commit.update_revprops(revprops, branch, authors)
975
builder = branch.get_commit_builder(parent_ids,
980
revision_id=revision_id)
896
981
preview = self.get_preview_tree()
897
982
list(builder.record_iter_changes(preview, last_rev_id,
898
983
self.iter_changes()))
1000
1085
class DiskTreeTransform(TreeTransformBase):
1001
1086
"""Tree transform storing its contents on disk."""
1003
def __init__(self, tree, limbodir, pb=DummyProgress(),
1088
def __init__(self, tree, limbodir, pb=None,
1004
1089
case_sensitive=True):
1005
1090
"""Constructor.
1006
1091
:param tree: The tree that will be transformed, but not necessarily
1007
1092
the output tree.
1008
1093
:param limbodir: A directory where new files can be stored until
1009
1094
they are installed in their proper places
1010
:param pb: A ProgressBar indicating how much progress is being made
1011
1096
:param case_sensitive: If True, the target of the transform is
1012
1097
case sensitive, not just case preserving.
1054
1140
def _limbo_name(self, trans_id):
1055
1141
"""Generate the limbo name of a file"""
1056
1142
limbo_name = self._limbo_files.get(trans_id)
1057
if limbo_name is not None:
1059
parent = self._new_parent.get(trans_id)
1060
# if the parent directory is already in limbo (e.g. when building a
1061
# tree), choose a limbo name inside the parent, to reduce further
1063
use_direct_path = False
1064
if self._new_contents.get(parent) == 'directory':
1065
filename = self._new_name.get(trans_id)
1066
if filename is not None:
1067
if parent not in self._limbo_children:
1068
self._limbo_children[parent] = set()
1069
self._limbo_children_names[parent] = {}
1070
use_direct_path = True
1071
# the direct path can only be used if no other file has
1072
# already taken this pathname, i.e. if the name is unused, or
1073
# if it is already associated with this trans_id.
1074
elif self._case_sensitive_target:
1075
if (self._limbo_children_names[parent].get(filename)
1076
in (trans_id, None)):
1077
use_direct_path = True
1079
for l_filename, l_trans_id in\
1080
self._limbo_children_names[parent].iteritems():
1081
if l_trans_id == trans_id:
1083
if l_filename.lower() == filename.lower():
1086
use_direct_path = True
1089
limbo_name = pathjoin(self._limbo_files[parent], filename)
1090
self._limbo_children[parent].add(trans_id)
1091
self._limbo_children_names[parent][filename] = trans_id
1093
limbo_name = pathjoin(self._limbodir, trans_id)
1094
self._needs_rename.add(trans_id)
1095
self._limbo_files[trans_id] = limbo_name
1143
if limbo_name is None:
1144
limbo_name = self._generate_limbo_path(trans_id)
1145
self._limbo_files[trans_id] = limbo_name
1096
1146
return limbo_name
1148
def _generate_limbo_path(self, trans_id):
1149
"""Generate a limbo path using the trans_id as the relative path.
1151
This is suitable as a fallback, and when the transform should not be
1152
sensitive to the path encoding of the limbo directory.
1154
self._needs_rename.add(trans_id)
1155
return pathjoin(self._limbodir, trans_id)
1098
1157
def adjust_path(self, name, parent, trans_id):
1099
1158
previous_parent = self._new_parent.get(trans_id)
1100
1159
previous_name = self._new_name.get(trans_id)
1102
1161
if (trans_id in self._limbo_files and
1103
1162
trans_id not in self._needs_rename):
1104
1163
self._rename_in_limbo([trans_id])
1105
self._limbo_children[previous_parent].remove(trans_id)
1106
del self._limbo_children_names[previous_parent][previous_name]
1164
if previous_parent != parent:
1165
self._limbo_children[previous_parent].remove(trans_id)
1166
if previous_parent != parent or previous_name != name:
1167
del self._limbo_children_names[previous_parent][previous_name]
1108
1169
def _rename_in_limbo(self, trans_ids):
1109
1170
"""Fix limbo names so that the right final path is produced.
1121
1182
if trans_id not in self._new_contents:
1123
1184
new_path = self._limbo_name(trans_id)
1124
os.rename(old_path, new_path)
1185
osutils.rename(old_path, new_path)
1186
for descendant in self._limbo_descendants(trans_id):
1187
desc_path = self._limbo_files[descendant]
1188
desc_path = new_path + desc_path[len(old_path):]
1189
self._limbo_files[descendant] = desc_path
1191
def _limbo_descendants(self, trans_id):
1192
"""Return the set of trans_ids whose limbo paths descend from this."""
1193
descendants = set(self._limbo_children.get(trans_id, []))
1194
for descendant in list(descendants):
1195
descendants.update(self._limbo_descendants(descendant))
1126
1198
def create_file(self, contents, trans_id, mode_id=None):
1127
1199
"""Schedule creation of a new file.
1161
1234
def _read_symlink_target(self, trans_id):
1162
1235
return os.readlink(self._limbo_name(trans_id))
1237
def _set_mtime(self, path):
1238
"""All files that are created get the same mtime.
1240
This time is set by the first object to be created.
1242
if self._creation_mtime is None:
1243
self._creation_mtime = time.time()
1244
os.utime(path, (self._creation_mtime, self._creation_mtime))
1164
1246
def create_hardlink(self, path, trans_id):
1165
1247
"""Schedule creation of a hard link"""
1166
1248
name = self._limbo_name(trans_id)
1397
1479
yield self.trans_id_tree_path(childpath)
1481
def _generate_limbo_path(self, trans_id):
1482
"""Generate a limbo path using the final path if possible.
1484
This optimizes the performance of applying the tree transform by
1485
avoiding renames. These renames can be avoided only when the parent
1486
directory is already scheduled for creation.
1488
If the final path cannot be used, falls back to using the trans_id as
1491
parent = self._new_parent.get(trans_id)
1492
# if the parent directory is already in limbo (e.g. when building a
1493
# tree), choose a limbo name inside the parent, to reduce further
1495
use_direct_path = False
1496
if self._new_contents.get(parent) == 'directory':
1497
filename = self._new_name.get(trans_id)
1498
if filename is not None:
1499
if parent not in self._limbo_children:
1500
self._limbo_children[parent] = set()
1501
self._limbo_children_names[parent] = {}
1502
use_direct_path = True
1503
# the direct path can only be used if no other file has
1504
# already taken this pathname, i.e. if the name is unused, or
1505
# if it is already associated with this trans_id.
1506
elif self._case_sensitive_target:
1507
if (self._limbo_children_names[parent].get(filename)
1508
in (trans_id, None)):
1509
use_direct_path = True
1511
for l_filename, l_trans_id in\
1512
self._limbo_children_names[parent].iteritems():
1513
if l_trans_id == trans_id:
1515
if l_filename.lower() == filename.lower():
1518
use_direct_path = True
1520
if not use_direct_path:
1521
return DiskTreeTransform._generate_limbo_path(self, trans_id)
1523
limbo_name = pathjoin(self._limbo_files[parent], filename)
1524
self._limbo_children[parent].add(trans_id)
1525
self._limbo_children_names[parent][filename] = trans_id
1399
1529
def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1400
1530
"""Apply all changes to the inventory and filesystem.
1521
1651
child_pb.update('removing file', num, len(tree_paths))
1522
1652
full_path = self._tree.abspath(path)
1523
1653
if trans_id in self._removed_contents:
1524
mover.pre_delete(full_path, os.path.join(self._deletiondir,
1526
elif trans_id in self._new_name or trans_id in \
1654
delete_path = os.path.join(self._deletiondir, trans_id)
1655
mover.pre_delete(full_path, delete_path)
1656
elif (trans_id in self._new_name
1657
or trans_id in self._new_parent):
1529
1659
mover.rename(full_path, self._limbo_name(trans_id))
1660
except errors.TransformRenameFailed, e:
1531
1661
if e.errno != errno.ENOENT:
1584
1714
unversioned files in the input tree.
1587
def __init__(self, tree, pb=DummyProgress(), case_sensitive=True):
1717
def __init__(self, tree, pb=None, case_sensitive=True):
1588
1718
tree.lock_read()
1589
1719
limbodir = osutils.mkdtemp(prefix='bzr-limbo-')
1590
1720
DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive)
1635
1765
self._all_children_cache = {}
1636
1766
self._path2trans_id_cache = {}
1637
1767
self._final_name_cache = {}
1639
def _changes(self, file_id):
1640
for changes in self._transform.iter_changes():
1641
if changes[0] == file_id:
1768
self._iter_changes_cache = dict((c[0], c) for c in
1769
self._transform.iter_changes())
1644
1771
def _content_change(self, file_id):
1645
1772
"""Return True if the content of this file changed"""
1646
changes = self._changes(file_id)
1773
changes = self._iter_changes_cache.get(file_id)
1647
1774
# changes[2] is true if the file content changed. See
1648
1775
# InterTree.iter_changes.
1649
1776
return (changes is not None and changes[2])
1665
1792
parent_keys = [(file_id, self._file_revision(t, file_id)) for t in
1666
1793
self._iter_parent_trees()]
1667
1794
vf.add_lines((file_id, tree_revision), parent_keys,
1668
self.get_file(file_id).readlines())
1795
self.get_file_lines(file_id))
1669
1796
repo = self._get_repository()
1670
1797
base_vf = repo.texts
1671
1798
if base_vf not in vf.fallback_versionedfiles:
1790
1920
if self._transform.final_file_id(trans_id) is None:
1791
1921
yield self._final_paths._determine_path(trans_id)
1793
def _make_inv_entries(self, ordered_entries, specific_file_ids=None):
1923
def _make_inv_entries(self, ordered_entries, specific_file_ids=None,
1924
yield_parents=False):
1794
1925
for trans_id, parent_file_id in ordered_entries:
1795
1926
file_id = self._transform.final_file_id(trans_id)
1796
1927
if file_id is None:
1822
1953
ordered_ids.append((trans_id, parent_file_id))
1823
1954
return ordered_ids
1825
def iter_entries_by_dir(self, specific_file_ids=None):
1956
def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
1826
1957
# This may not be a maximally efficient implementation, but it is
1827
1958
# reasonably straightforward. An implementation that grafts the
1828
1959
# TreeTransform changes onto the tree's iter_entries_by_dir results
1883
2014
def get_file_mtime(self, file_id, path=None):
1884
2015
"""See Tree.get_file_mtime"""
1885
2016
if not self._content_change(file_id):
1886
return self._transform._tree.get_file_mtime(file_id, path)
2017
return self._transform._tree.get_file_mtime(file_id)
1887
2018
return self._stat_limbo_file(file_id).st_mtime
1889
2020
def _file_size(self, entry, stat_value):
1951
2082
executable = None
1952
2083
if kind == 'symlink':
1953
2084
link_or_sha1 = os.readlink(limbo_name).decode(osutils._fs_enc)
1954
if supports_executable():
1955
executable = tt._new_executability.get(trans_id, executable)
2085
executable = tt._new_executability.get(trans_id, executable)
1956
2086
return kind, size, executable, link_or_sha1
1958
2088
def iter_changes(self, from_tree, include_unchanged=False,
2161
2291
for num, _unused in enumerate(wt.all_file_ids()):
2162
2292
if num > 0: # more than just a root
2163
2293
raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
2164
existing_files = set()
2165
for dir, files in wt.walkdirs():
2166
existing_files.update(f[0] for f in files)
2167
2294
file_trans_id = {}
2168
2295
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2169
2296
pp = ProgressPhase("Build phase", 2, top_pb)
2193
2320
precomputed_delta = []
2195
2322
precomputed_delta = None
2323
# Check if tree inventory has content. If so, we populate
2324
# existing_files with the directory content. If there are no
2325
# entries we skip populating existing_files as its not used.
2326
# This improves performance and unncessary work on large
2327
# directory trees. (#501307)
2329
existing_files = set()
2330
for dir, files in wt.walkdirs():
2331
existing_files.update(f[0] for f in files)
2196
2332
for num, (tree_path, entry) in \
2197
2333
enumerate(tree.inventory.iter_entries_by_dir()):
2198
2334
pb.update("Building tree", num - len(deferred_contents), total)
2271
2407
new_desired_files = desired_files
2273
2409
iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
2274
unchanged = dict((f, p[1]) for (f, p, c, v, d, n, k, e)
2275
in iter if not (c or e[0] != e[1]))
2410
unchanged = [(f, p[1]) for (f, p, c, v, d, n, k, e)
2411
in iter if not (c or e[0] != e[1])]
2412
if accelerator_tree.supports_content_filtering():
2413
unchanged = [(f, p) for (f, p) in unchanged
2414
if not accelerator_tree.iter_search_rules([p]).next()]
2415
unchanged = dict(unchanged)
2276
2416
new_desired_files = []
2278
2418
for file_id, (trans_id, tree_path) in desired_files:
2401
2545
tt.create_directory(trans_id)
2404
def create_from_tree(tt, trans_id, tree, file_id, bytes=None):
2405
"""Create new file contents according to tree contents."""
2548
def create_from_tree(tt, trans_id, tree, file_id, bytes=None,
2549
filter_tree_path=None):
2550
"""Create new file contents according to tree contents.
2552
:param filter_tree_path: the tree path to use to lookup
2553
content filters to apply to the bytes output in the working tree.
2554
This only applies if the working tree supports content filtering.
2406
2556
kind = tree.kind(file_id)
2407
2557
if kind == 'directory':
2408
2558
tt.create_directory(trans_id)
2413
2563
bytes = tree_file.readlines()
2415
2565
tree_file.close()
2567
if wt.supports_content_filtering() and filter_tree_path is not None:
2568
filters = wt._content_filter_stack(filter_tree_path)
2569
bytes = filtered_output_bytes(bytes, filters,
2570
ContentFilterContext(filter_tree_path, tree))
2416
2571
tt.create_file(bytes, trans_id)
2417
2572
elif kind == "symlink":
2418
2573
tt.create_symlink(tree.get_symlink_target(file_id), trans_id)
2472
2627
def revert(working_tree, target_tree, filenames, backups=False,
2473
pb=DummyProgress(), change_reporter=None):
2628
pb=None, change_reporter=None):
2474
2629
"""Revert a working tree's contents to those of a target tree."""
2475
2630
target_tree.lock_read()
2631
pb = ui.ui_factory.nested_progress_bar()
2476
2632
tt = TreeTransform(working_tree, pb)
2478
2634
pp = ProgressPhase("Revert phase", 3, pb)
2606
2760
parent_trans = ROOT_PARENT
2608
2762
parent_trans = tt.trans_id_file_id(parent[1])
2609
tt.adjust_path(name[1], parent_trans, trans_id)
2763
if parent[0] is None and versioned[0]:
2764
tt.adjust_root_path(name[1], parent_trans)
2766
tt.adjust_path(name[1], parent_trans, trans_id)
2610
2767
if executable[0] != executable[1] and kind[1] == "file":
2611
2768
tt.set_executability(executable[1], trans_id)
2612
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2614
tt.create_file(bytes, trans_id, mode_id)
2769
if working_tree.supports_content_filtering():
2770
for index, ((trans_id, mode_id), bytes) in enumerate(
2771
target_tree.iter_files_bytes(deferred_files)):
2772
file_id = deferred_files[index][0]
2773
# We're reverting a tree to the target tree so using the
2774
# target tree to find the file path seems the best choice
2775
# here IMO - Ian C 27/Oct/2009
2776
filter_tree_path = target_tree.id2path(file_id)
2777
filters = working_tree._content_filter_stack(filter_tree_path)
2778
bytes = filtered_output_bytes(bytes, filters,
2779
ContentFilterContext(filter_tree_path, working_tree))
2780
tt.create_file(bytes, trans_id, mode_id)
2782
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2784
tt.create_file(bytes, trans_id, mode_id)
2785
tt.fixup_new_roots()
2616
2787
if basis_tree is not None:
2617
2788
basis_tree.unlock()
2618
2789
return merge_modified
2621
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
2792
def resolve_conflicts(tt, pb=None, pass_func=None):
2622
2793
"""Make many conflict-resolution attempts, but die if they fail"""
2623
2794
if pass_func is None:
2624
2795
pass_func = conflict_pass
2625
2796
new_conflicts = set()
2797
pb = ui.ui_factory.nested_progress_bar()
2627
2799
for n in range(10):
2628
2800
pb.update('Resolution pass', n+1, 10)
2761
2933
self.pending_deletions = []
2763
2935
def rename(self, from_, to):
2764
"""Rename a file from one path to another. Functions like os.rename"""
2936
"""Rename a file from one path to another."""
2766
os.rename(from_, to)
2938
osutils.rename(from_, to)
2939
except (IOError, OSError), e:
2768
2940
if e.errno in (errno.EEXIST, errno.ENOTEMPTY):
2769
2941
raise errors.FileExists(to, str(e))
2942
# normal OSError doesn't include filenames so it's hard to see where
2943
# the problem is, see https://bugs.launchpad.net/bzr/+bug/491763
2944
raise errors.TransformRenameFailed(from_, to, str(e), e.errno)
2771
2945
self.past_renames.append((from_, to))
2773
2947
def pre_delete(self, from_, to):
2783
2957
def rollback(self):
2784
2958
"""Reverse all renames that have been performed"""
2785
2959
for from_, to in reversed(self.past_renames):
2786
os.rename(to, from_)
2961
osutils.rename(to, from_)
2962
except (OSError, IOError), e:
2963
raise errors.TransformRenameFailed(to, from_, str(e), e.errno)
2787
2964
# after rollback, don't reuse _FileMover
2788
2965
past_renames = None
2789
2966
pending_deletions = None