34
32
revision as _mod_revision,
38
35
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
39
ReusingTransform, CantMoveRoot,
36
ReusingTransform, NotVersionedError, CantMoveRoot,
40
37
ExistingLimbo, ImmortalLimbo, NoFinalPath,
41
38
UnableCreateSymlink)
42
39
from bzrlib.filters import filtered_output_bytes, ContentFilterContext
81
78
class TreeTransformBase(object):
82
79
"""The base class for TreeTransform and its kin."""
84
def __init__(self, tree, pb=None,
81
def __init__(self, tree, pb=DummyProgress(),
85
82
case_sensitive=True):
88
85
:param tree: The tree that will be transformed, but not necessarily
87
:param pb: A ProgressBar indicating how much progress is being made
91
88
:param case_sensitive: If True, the target of the transform is
92
89
case sensitive, not just case preserving.
165
162
def adjust_path(self, name, parent, trans_id):
166
163
"""Change the path that is assigned to a transaction id."""
168
raise ValueError("Parent trans-id may not be None")
169
164
if trans_id == self._new_root:
170
165
raise CantMoveRoot
171
166
self._new_name[trans_id] = name
172
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
174
173
def adjust_root_path(self, name, parent):
175
174
"""Emulate moving the root by moving all children, instead.
203
202
self.version_file(old_root_file_id, old_root)
204
203
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]
267
205
def trans_id_tree_file_id(self, inventory_id):
268
206
"""Determine the transaction id of a working tree file.
923
854
def get_preview_tree(self):
924
855
"""Return a tree representing the result of the transform.
926
The tree is a snapshot, and altering the TreeTransform will invalidate
857
This tree only supports the subset of Tree functionality required
858
by show_diff_trees. It must only be compared to tt._tree.
929
860
return _PreviewTree(self)
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):
934
"""Commit the result of this TreeTransform to a branch.
936
:param branch: The branch to commit to.
937
:param message: The message to attach to the commit.
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.)
952
:return: The revision_id of the revision committed.
954
self._check_malformed()
956
unversioned = set(self._new_contents).difference(set(self._new_id))
957
for trans_id in unversioned:
958
if self.final_file_id(trans_id) is None:
959
raise errors.StrictCommitFailed()
961
revno, last_rev_id = branch.last_revision_info()
962
if last_rev_id == _mod_revision.NULL_REVISION:
963
if merge_parents is not None:
964
raise ValueError('Cannot supply merge parents for first'
968
parent_ids = [last_rev_id]
969
if merge_parents is not None:
970
parent_ids.extend(merge_parents)
971
if self._tree.get_revision_id() != last_rev_id:
972
raise ValueError('TreeTransform not based on branch basis: %s' %
973
self._tree.get_revision_id())
974
revprops = commit.Commit.update_revprops(revprops, branch, authors)
975
builder = branch.get_commit_builder(parent_ids,
980
revision_id=revision_id)
981
preview = self.get_preview_tree()
982
list(builder.record_iter_changes(preview, last_rev_id,
983
self.iter_changes()))
984
builder.finish_inventory()
985
revision_id = builder.commit(message)
986
branch.set_last_revision_info(revno + 1, revision_id)
989
862
def _text_parent(self, trans_id):
990
863
file_id = self.tree_file_id(trans_id)
1085
958
class DiskTreeTransform(TreeTransformBase):
1086
959
"""Tree transform storing its contents on disk."""
1088
def __init__(self, tree, limbodir, pb=None,
961
def __init__(self, tree, limbodir, pb=DummyProgress(),
1089
962
case_sensitive=True):
1091
964
:param tree: The tree that will be transformed, but not necessarily
1092
965
the output tree.
1093
966
:param limbodir: A directory where new files can be stored until
1094
967
they are installed in their proper places
968
:param pb: A ProgressBar indicating how much progress is being made
1096
969
:param case_sensitive: If True, the target of the transform is
1097
970
case sensitive, not just case preserving.
1140
1012
def _limbo_name(self, trans_id):
1141
1013
"""Generate the limbo name of a file"""
1142
1014
limbo_name = self._limbo_files.get(trans_id)
1143
if limbo_name is None:
1144
limbo_name = self._generate_limbo_path(trans_id)
1145
self._limbo_files[trans_id] = limbo_name
1015
if limbo_name is not None:
1017
parent = self._new_parent.get(trans_id)
1018
# if the parent directory is already in limbo (e.g. when building a
1019
# tree), choose a limbo name inside the parent, to reduce further
1021
use_direct_path = False
1022
if self._new_contents.get(parent) == 'directory':
1023
filename = self._new_name.get(trans_id)
1024
if filename is not None:
1025
if parent not in self._limbo_children:
1026
self._limbo_children[parent] = set()
1027
self._limbo_children_names[parent] = {}
1028
use_direct_path = True
1029
# the direct path can only be used if no other file has
1030
# already taken this pathname, i.e. if the name is unused, or
1031
# if it is already associated with this trans_id.
1032
elif self._case_sensitive_target:
1033
if (self._limbo_children_names[parent].get(filename)
1034
in (trans_id, None)):
1035
use_direct_path = True
1037
for l_filename, l_trans_id in\
1038
self._limbo_children_names[parent].iteritems():
1039
if l_trans_id == trans_id:
1041
if l_filename.lower() == filename.lower():
1044
use_direct_path = True
1047
limbo_name = pathjoin(self._limbo_files[parent], filename)
1048
self._limbo_children[parent].add(trans_id)
1049
self._limbo_children_names[parent][filename] = trans_id
1051
limbo_name = pathjoin(self._limbodir, trans_id)
1052
self._needs_rename.add(trans_id)
1053
self._limbo_files[trans_id] = limbo_name
1146
1054
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)
1157
1056
def adjust_path(self, name, parent, trans_id):
1158
1057
previous_parent = self._new_parent.get(trans_id)
1159
1058
previous_name = self._new_name.get(trans_id)
1161
1060
if (trans_id in self._limbo_files and
1162
1061
trans_id not in self._needs_rename):
1163
1062
self._rename_in_limbo([trans_id])
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]
1063
self._limbo_children[previous_parent].remove(trans_id)
1064
del self._limbo_children_names[previous_parent][previous_name]
1169
1066
def _rename_in_limbo(self, trans_ids):
1170
1067
"""Fix limbo names so that the right final path is produced.
1182
1079
if trans_id not in self._new_contents:
1184
1081
new_path = self._limbo_name(trans_id)
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))
1082
os.rename(old_path, new_path)
1198
1084
def create_file(self, contents, trans_id, mode_id=None):
1199
1085
"""Schedule creation of a new file.
1234
1119
def _read_symlink_target(self, trans_id):
1235
1120
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))
1246
1122
def create_hardlink(self, path, trans_id):
1247
1123
"""Schedule creation of a hard link"""
1248
1124
name = self._limbo_name(trans_id)
1479
1355
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
1529
1358
def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1530
1359
"""Apply all changes to the inventory and filesystem.
1651
1482
child_pb.update('removing file', num, len(tree_paths))
1652
1483
full_path = self._tree.abspath(path)
1653
1484
if trans_id in self._removed_contents:
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):
1485
mover.pre_delete(full_path, os.path.join(self._deletiondir,
1487
elif trans_id in self._new_name or trans_id in \
1659
1490
mover.rename(full_path, self._limbo_name(trans_id))
1660
except errors.TransformRenameFailed, e:
1661
1492
if e.errno != errno.ENOENT:
1714
1545
unversioned files in the input tree.
1717
def __init__(self, tree, pb=None, case_sensitive=True):
1548
def __init__(self, tree, pb=DummyProgress(), case_sensitive=True):
1718
1549
tree.lock_read()
1719
1550
limbodir = osutils.mkdtemp(prefix='bzr-limbo-')
1720
1551
DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive)
1765
1596
self._all_children_cache = {}
1766
1597
self._path2trans_id_cache = {}
1767
1598
self._final_name_cache = {}
1768
self._iter_changes_cache = dict((c[0], c) for c in
1769
self._transform.iter_changes())
1600
def _changes(self, file_id):
1601
for changes in self._transform.iter_changes():
1602
if changes[0] == file_id:
1771
1605
def _content_change(self, file_id):
1772
1606
"""Return True if the content of this file changed"""
1773
changes = self._iter_changes_cache.get(file_id)
1607
changes = self._changes(file_id)
1774
1608
# changes[2] is true if the file content changed. See
1775
1609
# InterTree.iter_changes.
1776
1610
return (changes is not None and changes[2])
1792
1626
parent_keys = [(file_id, self._file_revision(t, file_id)) for t in
1793
1627
self._iter_parent_trees()]
1794
1628
vf.add_lines((file_id, tree_revision), parent_keys,
1795
self.get_file_lines(file_id))
1629
self.get_file(file_id).readlines())
1796
1630
repo = self._get_repository()
1797
1631
base_vf = repo.texts
1798
1632
if base_vf not in vf.fallback_versionedfiles:
1848
1679
def __iter__(self):
1849
1680
return iter(self.all_file_ids())
1851
def _has_id(self, file_id, fallback_check):
1682
def has_id(self, file_id):
1852
1683
if file_id in self._transform._r_new_id:
1854
1685
elif file_id in set([self._transform.tree_file_id(trans_id) for
1855
1686
trans_id in self._transform._removed_id]):
1858
return fallback_check(file_id)
1860
def has_id(self, file_id):
1861
return self._has_id(file_id, self._transform._tree.has_id)
1863
def has_or_had_id(self, file_id):
1864
return self._has_id(file_id, self._transform._tree.has_or_had_id)
1689
return self._transform._tree.has_id(file_id)
1866
1691
def _path2trans_id(self, path):
1867
1692
# We must not use None here, because that is a valid value to store.
1920
1745
if self._transform.final_file_id(trans_id) is None:
1921
1746
yield self._final_paths._determine_path(trans_id)
1923
def _make_inv_entries(self, ordered_entries, specific_file_ids=None,
1924
yield_parents=False):
1748
def _make_inv_entries(self, ordered_entries, specific_file_ids=None):
1925
1749
for trans_id, parent_file_id in ordered_entries:
1926
1750
file_id = self._transform.final_file_id(trans_id)
1927
1751
if file_id is None:
1953
1777
ordered_ids.append((trans_id, parent_file_id))
1954
1778
return ordered_ids
1956
def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
1780
def iter_entries_by_dir(self, specific_file_ids=None):
1957
1781
# This may not be a maximally efficient implementation, but it is
1958
1782
# reasonably straightforward. An implementation that grafts the
1959
1783
# TreeTransform changes onto the tree's iter_entries_by_dir results
2014
1838
def get_file_mtime(self, file_id, path=None):
2015
1839
"""See Tree.get_file_mtime"""
2016
1840
if not self._content_change(file_id):
2017
return self._transform._tree.get_file_mtime(file_id)
1841
return self._transform._tree.get_file_mtime(file_id, path)
2018
1842
return self._stat_limbo_file(file_id).st_mtime
2020
1844
def _file_size(self, entry, stat_value):
2082
1906
executable = None
2083
1907
if kind == 'symlink':
2084
1908
link_or_sha1 = os.readlink(limbo_name).decode(osutils._fs_enc)
2085
executable = tt._new_executability.get(trans_id, executable)
1909
if supports_executable():
1910
executable = tt._new_executability.get(trans_id, executable)
2086
1911
return kind, size, executable, link_or_sha1
2088
1913
def iter_changes(self, from_tree, include_unchanged=False,
2401
2226
new_desired_files = desired_files
2403
2228
iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
2404
unchanged = [(f, p[1]) for (f, p, c, v, d, n, k, e)
2405
in iter if not (c or e[0] != e[1])]
2406
if accelerator_tree.supports_content_filtering():
2407
unchanged = [(f, p) for (f, p) in unchanged
2408
if not accelerator_tree.iter_search_rules([p]).next()]
2409
unchanged = dict(unchanged)
2229
unchanged = dict((f, p[1]) for (f, p, c, v, d, n, k, e)
2230
in iter if not (c or e[0] != e[1]))
2410
2231
new_desired_files = []
2412
2233
for file_id, (trans_id, tree_path) in desired_files:
2539
2356
tt.create_directory(trans_id)
2542
def create_from_tree(tt, trans_id, tree, file_id, bytes=None,
2543
filter_tree_path=None):
2544
"""Create new file contents according to tree contents.
2546
:param filter_tree_path: the tree path to use to lookup
2547
content filters to apply to the bytes output in the working tree.
2548
This only applies if the working tree supports content filtering.
2359
def create_from_tree(tt, trans_id, tree, file_id, bytes=None):
2360
"""Create new file contents according to tree contents."""
2550
2361
kind = tree.kind(file_id)
2551
2362
if kind == 'directory':
2552
2363
tt.create_directory(trans_id)
2557
2368
bytes = tree_file.readlines()
2559
2370
tree_file.close()
2561
if wt.supports_content_filtering() and filter_tree_path is not None:
2562
filters = wt._content_filter_stack(filter_tree_path)
2563
bytes = filtered_output_bytes(bytes, filters,
2564
ContentFilterContext(filter_tree_path, tree))
2565
2371
tt.create_file(bytes, trans_id)
2566
2372
elif kind == "symlink":
2567
2373
tt.create_symlink(tree.get_symlink_target(file_id), trans_id)
2621
2427
def revert(working_tree, target_tree, filenames, backups=False,
2622
pb=None, change_reporter=None):
2428
pb=DummyProgress(), change_reporter=None):
2623
2429
"""Revert a working tree's contents to those of a target tree."""
2624
2430
target_tree.lock_read()
2625
pb = ui.ui_factory.nested_progress_bar()
2626
2431
tt = TreeTransform(working_tree, pb)
2628
2433
pp = ProgressPhase("Revert phase", 3, pb)
2754
2561
parent_trans = ROOT_PARENT
2756
2563
parent_trans = tt.trans_id_file_id(parent[1])
2757
if parent[0] is None and versioned[0]:
2758
tt.adjust_root_path(name[1], parent_trans)
2760
tt.adjust_path(name[1], parent_trans, trans_id)
2564
tt.adjust_path(name[1], parent_trans, trans_id)
2761
2565
if executable[0] != executable[1] and kind[1] == "file":
2762
2566
tt.set_executability(executable[1], trans_id)
2763
if working_tree.supports_content_filtering():
2764
for index, ((trans_id, mode_id), bytes) in enumerate(
2765
target_tree.iter_files_bytes(deferred_files)):
2766
file_id = deferred_files[index][0]
2767
# We're reverting a tree to the target tree so using the
2768
# target tree to find the file path seems the best choice
2769
# here IMO - Ian C 27/Oct/2009
2770
filter_tree_path = target_tree.id2path(file_id)
2771
filters = working_tree._content_filter_stack(filter_tree_path)
2772
bytes = filtered_output_bytes(bytes, filters,
2773
ContentFilterContext(filter_tree_path, working_tree))
2774
tt.create_file(bytes, trans_id, mode_id)
2776
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2778
tt.create_file(bytes, trans_id, mode_id)
2779
tt.fixup_new_roots()
2567
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2569
tt.create_file(bytes, trans_id, mode_id)
2781
2571
if basis_tree is not None:
2782
2572
basis_tree.unlock()
2783
2573
return merge_modified
2786
def resolve_conflicts(tt, pb=None, pass_func=None):
2576
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
2787
2577
"""Make many conflict-resolution attempts, but die if they fail"""
2788
2578
if pass_func is None:
2789
2579
pass_func = conflict_pass
2790
2580
new_conflicts = set()
2791
pb = ui.ui_factory.nested_progress_bar()
2793
2582
for n in range(10):
2794
2583
pb.update('Resolution pass', n+1, 10)
2927
2716
self.pending_deletions = []
2929
2718
def rename(self, from_, to):
2930
"""Rename a file from one path to another."""
2719
"""Rename a file from one path to another. Functions like os.rename"""
2932
osutils.rename(from_, to)
2933
except (IOError, OSError), e:
2721
os.rename(from_, to)
2934
2723
if e.errno in (errno.EEXIST, errno.ENOTEMPTY):
2935
2724
raise errors.FileExists(to, str(e))
2936
# normal OSError doesn't include filenames so it's hard to see where
2937
# the problem is, see https://bugs.launchpad.net/bzr/+bug/491763
2938
raise errors.TransformRenameFailed(from_, to, str(e), e.errno)
2939
2726
self.past_renames.append((from_, to))
2941
2728
def pre_delete(self, from_, to):
2951
2738
def rollback(self):
2952
2739
"""Reverse all renames that have been performed"""
2953
2740
for from_, to in reversed(self.past_renames):
2955
osutils.rename(to, from_)
2956
except (OSError, IOError), e:
2957
raise errors.TransformRenameFailed(to, from_, str(e), e.errno)
2741
os.rename(to, from_)
2958
2742
# after rollback, don't reuse _FileMover
2959
2743
past_renames = None
2960
2744
pending_deletions = None