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 ProgressTask 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.
929
865
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):
867
def commit(self, branch, message, merge_parents=None, strict=False):
934
868
"""Commit the result of this TreeTransform to a branch.
936
870
:param branch: The branch to commit to.
937
871
: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.)
872
:param merge_parents: Additional parents specified by pending merges.
952
873
:return: The revision_id of the revision committed.
954
875
self._check_malformed()
971
892
if self._tree.get_revision_id() != last_rev_id:
972
893
raise ValueError('TreeTransform not based on branch basis: %s' %
973
894
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)
895
builder = branch.get_commit_builder(parent_ids)
981
896
preview = self.get_preview_tree()
982
897
list(builder.record_iter_changes(preview, last_rev_id,
983
898
self.iter_changes()))
1085
1000
class DiskTreeTransform(TreeTransformBase):
1086
1001
"""Tree transform storing its contents on disk."""
1088
def __init__(self, tree, limbodir, pb=None,
1003
def __init__(self, tree, limbodir, pb=DummyProgress(),
1089
1004
case_sensitive=True):
1090
1005
"""Constructor.
1091
1006
:param tree: The tree that will be transformed, but not necessarily
1092
1007
the output tree.
1093
1008
:param limbodir: A directory where new files can be stored until
1094
1009
they are installed in their proper places
1010
:param pb: A ProgressBar indicating how much progress is being made
1096
1011
:param case_sensitive: If True, the target of the transform is
1097
1012
case sensitive, not just case preserving.
1161
1075
if (trans_id in self._limbo_files and
1162
1076
trans_id not in self._needs_rename):
1163
1077
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]
1078
self._limbo_children[previous_parent].remove(trans_id)
1079
del self._limbo_children_names[previous_parent][previous_name]
1169
1081
def _rename_in_limbo(self, trans_ids):
1170
1082
"""Fix limbo names so that the right final path is produced.
1234
1145
def _read_symlink_target(self, trans_id):
1235
1146
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
1148
def create_hardlink(self, path, trans_id):
1247
1149
"""Schedule creation of a hard link"""
1248
1150
name = self._limbo_name(trans_id)
1651
1553
child_pb.update('removing file', num, len(tree_paths))
1652
1554
full_path = self._tree.abspath(path)
1653
1555
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):
1556
mover.pre_delete(full_path, os.path.join(self._deletiondir,
1558
elif trans_id in self._new_name or trans_id in \
1659
1561
mover.rename(full_path, self._limbo_name(trans_id))
1660
except errors.TransformRenameFailed, e:
1661
1563
if e.errno != errno.ENOENT:
1714
1616
unversioned files in the input tree.
1717
def __init__(self, tree, pb=None, case_sensitive=True):
1619
def __init__(self, tree, pb=DummyProgress(), case_sensitive=True):
1718
1620
tree.lock_read()
1719
1621
limbodir = osutils.mkdtemp(prefix='bzr-limbo-')
1720
1622
DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive)
1792
1694
parent_keys = [(file_id, self._file_revision(t, file_id)) for t in
1793
1695
self._iter_parent_trees()]
1794
1696
vf.add_lines((file_id, tree_revision), parent_keys,
1795
self.get_file_lines(file_id))
1697
self.get_file(file_id).readlines())
1796
1698
repo = self._get_repository()
1797
1699
base_vf = repo.texts
1798
1700
if base_vf not in vf.fallback_versionedfiles:
2291
2190
for num, _unused in enumerate(wt.all_file_ids()):
2292
2191
if num > 0: # more than just a root
2293
2192
raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
2193
existing_files = set()
2194
for dir, files in wt.walkdirs():
2195
existing_files.update(f[0] for f in files)
2294
2196
file_trans_id = {}
2295
2197
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2296
2198
pp = ProgressPhase("Build phase", 2, top_pb)
2320
2222
precomputed_delta = []
2322
2224
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)
2332
2225
for num, (tree_path, entry) in \
2333
2226
enumerate(tree.inventory.iter_entries_by_dir()):
2334
2227
pb.update("Building tree", num - len(deferred_contents), total)
2627
2516
def revert(working_tree, target_tree, filenames, backups=False,
2628
pb=None, change_reporter=None):
2517
pb=DummyProgress(), change_reporter=None):
2629
2518
"""Revert a working tree's contents to those of a target tree."""
2630
2519
target_tree.lock_read()
2631
pb = ui.ui_factory.nested_progress_bar()
2632
2520
tt = TreeTransform(working_tree, pb)
2634
2522
pp = ProgressPhase("Revert phase", 3, pb)
2760
2650
parent_trans = ROOT_PARENT
2762
2652
parent_trans = tt.trans_id_file_id(parent[1])
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)
2653
tt.adjust_path(name[1], parent_trans, trans_id)
2767
2654
if executable[0] != executable[1] and kind[1] == "file":
2768
2655
tt.set_executability(executable[1], trans_id)
2769
2656
if working_tree.supports_content_filtering():
2782
2669
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2783
2670
deferred_files):
2784
2671
tt.create_file(bytes, trans_id, mode_id)
2785
tt.fixup_new_roots()
2787
2673
if basis_tree is not None:
2788
2674
basis_tree.unlock()
2789
2675
return merge_modified
2792
def resolve_conflicts(tt, pb=None, pass_func=None):
2678
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
2793
2679
"""Make many conflict-resolution attempts, but die if they fail"""
2794
2680
if pass_func is None:
2795
2681
pass_func = conflict_pass
2796
2682
new_conflicts = set()
2797
pb = ui.ui_factory.nested_progress_bar()
2799
2684
for n in range(10):
2800
2685
pb.update('Resolution pass', n+1, 10)
2933
2818
self.pending_deletions = []
2935
2820
def rename(self, from_, to):
2936
"""Rename a file from one path to another."""
2821
"""Rename a file from one path to another. Functions like os.rename"""
2938
2823
os.rename(from_, to)
2939
2824
except OSError, e:
2940
2825
if e.errno in (errno.EEXIST, errno.ENOTEMPTY):
2941
2826
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)
2945
2828
self.past_renames.append((from_, to))
2947
2830
def pre_delete(self, from_, to):