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.
1140
1054
def _limbo_name(self, trans_id):
1141
1055
"""Generate the limbo name of a file"""
1142
1056
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
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
1146
1096
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
1098
def adjust_path(self, name, parent, trans_id):
1158
1099
previous_parent = self._new_parent.get(trans_id)
1159
1100
previous_name = self._new_name.get(trans_id)
1161
1102
if (trans_id in self._limbo_files and
1162
1103
trans_id not in self._needs_rename):
1163
1104
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]
1105
self._limbo_children[previous_parent].remove(trans_id)
1106
del self._limbo_children_names[previous_parent][previous_name]
1169
1108
def _rename_in_limbo(self, trans_ids):
1170
1109
"""Fix limbo names so that the right final path is produced.
1182
1121
if trans_id not in self._new_contents:
1184
1123
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))
1124
os.rename(old_path, new_path)
1198
1126
def create_file(self, contents, trans_id, mode_id=None):
1199
1127
"""Schedule creation of a new file.
1234
1161
def _read_symlink_target(self, trans_id):
1235
1162
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
1164
def create_hardlink(self, path, trans_id):
1247
1165
"""Schedule creation of a hard link"""
1248
1166
name = self._limbo_name(trans_id)
1479
1397
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
1399
def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1530
1400
"""Apply all changes to the inventory and filesystem.
1651
1521
child_pb.update('removing file', num, len(tree_paths))
1652
1522
full_path = self._tree.abspath(path)
1653
1523
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):
1524
mover.pre_delete(full_path, os.path.join(self._deletiondir,
1526
elif trans_id in self._new_name or trans_id in \
1659
1529
mover.rename(full_path, self._limbo_name(trans_id))
1660
1530
except OSError, e:
1714
1584
unversioned files in the input tree.
1717
def __init__(self, tree, pb=None, case_sensitive=True):
1587
def __init__(self, tree, pb=DummyProgress(), case_sensitive=True):
1718
1588
tree.lock_read()
1719
1589
limbodir = osutils.mkdtemp(prefix='bzr-limbo-')
1720
1590
DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive)
2082
1949
executable = None
2083
1950
if kind == 'symlink':
2084
1951
link_or_sha1 = os.readlink(limbo_name).decode(osutils._fs_enc)
2085
executable = tt._new_executability.get(trans_id, executable)
1952
if supports_executable():
1953
executable = tt._new_executability.get(trans_id, executable)
2086
1954
return kind, size, executable, link_or_sha1
2088
1956
def iter_changes(self, from_tree, include_unchanged=False,
2401
2269
new_desired_files = desired_files
2403
2271
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)
2272
unchanged = dict((f, p[1]) for (f, p, c, v, d, n, k, e)
2273
in iter if not (c or e[0] != e[1]))
2410
2274
new_desired_files = []
2412
2276
for file_id, (trans_id, tree_path) in desired_files:
2535
2399
tt.create_directory(trans_id)
2538
def create_from_tree(tt, trans_id, tree, file_id, bytes=None,
2539
filter_tree_path=None):
2540
"""Create new file contents according to tree contents.
2542
:param filter_tree_path: the tree path to use to lookup
2543
content filters to apply to the bytes output in the working tree.
2544
This only applies if the working tree supports content filtering.
2402
def create_from_tree(tt, trans_id, tree, file_id, bytes=None):
2403
"""Create new file contents according to tree contents."""
2546
2404
kind = tree.kind(file_id)
2547
2405
if kind == 'directory':
2548
2406
tt.create_directory(trans_id)
2553
2411
bytes = tree_file.readlines()
2555
2413
tree_file.close()
2557
if wt.supports_content_filtering() and filter_tree_path is not None:
2558
filters = wt._content_filter_stack(filter_tree_path)
2559
bytes = filtered_output_bytes(bytes, filters,
2560
ContentFilterContext(filter_tree_path, tree))
2561
2414
tt.create_file(bytes, trans_id)
2562
2415
elif kind == "symlink":
2563
2416
tt.create_symlink(tree.get_symlink_target(file_id), trans_id)
2617
2470
def revert(working_tree, target_tree, filenames, backups=False,
2618
pb=None, change_reporter=None):
2471
pb=DummyProgress(), change_reporter=None):
2619
2472
"""Revert a working tree's contents to those of a target tree."""
2620
2473
target_tree.lock_read()
2621
pb = ui.ui_factory.nested_progress_bar()
2622
2474
tt = TreeTransform(working_tree, pb)
2624
2476
pp = ProgressPhase("Revert phase", 3, pb)
2750
2604
parent_trans = ROOT_PARENT
2752
2606
parent_trans = tt.trans_id_file_id(parent[1])
2753
if parent[0] is None and versioned[0]:
2754
tt.adjust_root_path(name[1], parent_trans)
2756
tt.adjust_path(name[1], parent_trans, trans_id)
2607
tt.adjust_path(name[1], parent_trans, trans_id)
2757
2608
if executable[0] != executable[1] and kind[1] == "file":
2758
2609
tt.set_executability(executable[1], trans_id)
2759
if working_tree.supports_content_filtering():
2760
for index, ((trans_id, mode_id), bytes) in enumerate(
2761
target_tree.iter_files_bytes(deferred_files)):
2762
file_id = deferred_files[index][0]
2763
# We're reverting a tree to the target tree so using the
2764
# target tree to find the file path seems the best choice
2765
# here IMO - Ian C 27/Oct/2009
2766
filter_tree_path = target_tree.id2path(file_id)
2767
filters = working_tree._content_filter_stack(filter_tree_path)
2768
bytes = filtered_output_bytes(bytes, filters,
2769
ContentFilterContext(filter_tree_path, working_tree))
2770
tt.create_file(bytes, trans_id, mode_id)
2772
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2774
tt.create_file(bytes, trans_id, mode_id)
2775
tt.fixup_new_roots()
2610
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2612
tt.create_file(bytes, trans_id, mode_id)
2777
2614
if basis_tree is not None:
2778
2615
basis_tree.unlock()
2779
2616
return merge_modified
2782
def resolve_conflicts(tt, pb=None, pass_func=None):
2619
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
2783
2620
"""Make many conflict-resolution attempts, but die if they fail"""
2784
2621
if pass_func is None:
2785
2622
pass_func = conflict_pass
2786
2623
new_conflicts = set()
2787
pb = ui.ui_factory.nested_progress_bar()
2789
2625
for n in range(10):
2790
2626
pb.update('Resolution pass', n+1, 10)
2923
2759
self.pending_deletions = []
2925
2761
def rename(self, from_, to):
2926
"""Rename a file from one path to another."""
2762
"""Rename a file from one path to another. Functions like os.rename"""
2928
osutils.rename(from_, to)
2764
os.rename(from_, to)
2929
2765
except OSError, e:
2930
2766
if e.errno in (errno.EEXIST, errno.ENOTEMPTY):
2931
2767
raise errors.FileExists(to, str(e))