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 ProgressTask 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.
254
316
def delete_contents(self, trans_id):
255
317
"""Schedule the contents of a path entry for deletion"""
256
self.tree_kind(trans_id)
257
self._removed_contents.add(trans_id)
318
kind = self.tree_kind(trans_id)
320
self._removed_contents.add(trans_id)
259
322
def cancel_deletion(self, trans_id):
260
323
"""Cancel a scheduled deletion"""
325
388
changed_kind = set(self._removed_contents)
326
389
changed_kind.intersection_update(self._new_contents)
327
390
changed_kind.difference_update(new_ids)
328
changed_kind = (t for t in changed_kind if self.tree_kind(t) !=
391
changed_kind = (t for t in changed_kind
392
if self.tree_kind(t) != self.final_kind(t))
330
393
new_ids.update(changed_kind)
331
394
return sorted(FinalPaths(self).get_paths(new_ids))
333
396
def final_kind(self, trans_id):
334
397
"""Determine the final file kind, after any changes applied.
336
Raises NoSuchFile if the file does not exist/has no contents.
337
(It is conceivable that a path would be created without the
338
corresponding contents insertion command)
399
:return: None if the file does not exist/has no contents. (It is
400
conceivable that a path would be created without the corresponding
401
contents insertion command)
340
403
if trans_id in self._new_contents:
341
404
return self._new_contents[trans_id]
342
405
elif trans_id in self._removed_contents:
343
raise NoSuchFile(None)
345
408
return self.tree_kind(trans_id)
641
686
def _any_contents(self, trans_ids):
642
687
"""Return true if any of the trans_ids, will have contents."""
643
688
for trans_id in trans_ids:
645
kind = self.final_kind(trans_id)
689
if self.final_kind(trans_id) is not None:
651
693
def _set_executability(self, path, trans_id):
781
823
Return a (name, parent, kind, executable) tuple
783
825
to_name = self.final_name(to_trans_id)
785
to_kind = self.final_kind(to_trans_id)
826
to_kind = self.final_kind(to_trans_id)
788
827
to_parent = self.final_file_id(self.final_parent(to_trans_id))
789
828
if to_trans_id in self._new_executability:
790
829
to_executable = self._new_executability[to_trans_id]
865
904
return _PreviewTree(self)
867
def commit(self, branch, message, merge_parents=None, strict=False):
906
def commit(self, branch, message, merge_parents=None, strict=False,
907
timestamp=None, timezone=None, committer=None, authors=None,
908
revprops=None, revision_id=None):
868
909
"""Commit the result of this TreeTransform to a branch.
870
911
:param branch: The branch to commit to.
871
912
:param message: The message to attach to the commit.
872
:param merge_parents: Additional parents specified by pending merges.
913
:param merge_parents: Additional parent revision-ids specified by
915
:param strict: If True, abort the commit if there are unversioned
917
:param timestamp: if not None, seconds-since-epoch for the time and
918
date. (May be a float.)
919
:param timezone: Optional timezone for timestamp, as an offset in
921
:param committer: Optional committer in email-id format.
922
(e.g. "J Random Hacker <jrandom@example.com>")
923
:param authors: Optional list of authors in email-id format.
924
:param revprops: Optional dictionary of revision properties.
925
:param revision_id: Optional revision id. (Specifying a revision-id
926
may reduce performance for some non-native formats.)
873
927
:return: The revision_id of the revision committed.
875
929
self._check_malformed()
892
946
if self._tree.get_revision_id() != last_rev_id:
893
947
raise ValueError('TreeTransform not based on branch basis: %s' %
894
948
self._tree.get_revision_id())
895
builder = branch.get_commit_builder(parent_ids)
949
revprops = commit.Commit.update_revprops(revprops, branch, authors)
950
builder = branch.get_commit_builder(parent_ids,
955
revision_id=revision_id)
896
956
preview = self.get_preview_tree()
897
957
list(builder.record_iter_changes(preview, last_rev_id,
898
958
self.iter_changes()))
1000
1060
class DiskTreeTransform(TreeTransformBase):
1001
1061
"""Tree transform storing its contents on disk."""
1003
def __init__(self, tree, limbodir, pb=DummyProgress(),
1063
def __init__(self, tree, limbodir, pb=None,
1004
1064
case_sensitive=True):
1005
1065
"""Constructor.
1006
1066
:param tree: The tree that will be transformed, but not necessarily
1007
1067
the output tree.
1008
1068
:param limbodir: A directory where new files can be stored until
1009
1069
they are installed in their proper places
1010
:param pb: A ProgressBar indicating how much progress is being made
1011
1071
:param case_sensitive: If True, the target of the transform is
1012
1072
case sensitive, not just case preserving.
1075
1136
if (trans_id in self._limbo_files and
1076
1137
trans_id not in self._needs_rename):
1077
1138
self._rename_in_limbo([trans_id])
1078
self._limbo_children[previous_parent].remove(trans_id)
1079
del self._limbo_children_names[previous_parent][previous_name]
1139
if previous_parent != parent:
1140
self._limbo_children[previous_parent].remove(trans_id)
1141
if previous_parent != parent or previous_name != name:
1142
del self._limbo_children_names[previous_parent][previous_name]
1081
1144
def _rename_in_limbo(self, trans_ids):
1082
1145
"""Fix limbo names so that the right final path is produced.
1145
1209
def _read_symlink_target(self, trans_id):
1146
1210
return os.readlink(self._limbo_name(trans_id))
1212
def _set_mtime(self, path):
1213
"""All files that are created get the same mtime.
1215
This time is set by the first object to be created.
1217
if self._creation_mtime is None:
1218
self._creation_mtime = time.time()
1219
os.utime(path, (self._creation_mtime, self._creation_mtime))
1148
1221
def create_hardlink(self, path, trans_id):
1149
1222
"""Schedule creation of a hard link"""
1150
1223
name = self._limbo_name(trans_id)
1321
1394
def tree_kind(self, trans_id):
1322
1395
"""Determine the file kind in the working tree.
1324
Raises NoSuchFile if the file does not exist
1397
:returns: The file kind or None if the file does not exist
1326
1399
path = self._tree_id_paths.get(trans_id)
1327
1400
if path is None:
1328
raise NoSuchFile(None)
1330
1403
return file_kind(self._tree.abspath(path))
1332
if e.errno != errno.ENOENT:
1335
raise NoSuchFile(path)
1404
except errors.NoSuchFile:
1337
1407
def _set_mode(self, trans_id, mode_id, typefunc):
1338
1408
"""Set the mode of new file contents.
1553
1622
child_pb.update('removing file', num, len(tree_paths))
1554
1623
full_path = self._tree.abspath(path)
1555
1624
if trans_id in self._removed_contents:
1556
mover.pre_delete(full_path, os.path.join(self._deletiondir,
1558
elif trans_id in self._new_name or trans_id in \
1625
delete_path = os.path.join(self._deletiondir, trans_id)
1626
mover.pre_delete(full_path, delete_path)
1627
elif (trans_id in self._new_name
1628
or trans_id in self._new_parent):
1561
1630
mover.rename(full_path, self._limbo_name(trans_id))
1631
except errors.TransformRenameFailed, e:
1563
1632
if e.errno != errno.ENOENT:
1616
1685
unversioned files in the input tree.
1619
def __init__(self, tree, pb=DummyProgress(), case_sensitive=True):
1688
def __init__(self, tree, pb=None, case_sensitive=True):
1620
1689
tree.lock_read()
1621
1690
limbodir = osutils.mkdtemp(prefix='bzr-limbo-')
1622
1691
DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive)
1627
1696
def tree_kind(self, trans_id):
1628
1697
path = self._tree_id_paths.get(trans_id)
1629
1698
if path is None:
1630
raise NoSuchFile(None)
1631
1700
file_id = self._tree.path2id(path)
1632
return self._tree.kind(file_id)
1702
return self._tree.kind(file_id)
1703
except errors.NoSuchFile:
1634
1706
def _set_mode(self, trans_id, mode_id, typefunc):
1635
1707
"""Set the mode of new file contents.
1694
1766
parent_keys = [(file_id, self._file_revision(t, file_id)) for t in
1695
1767
self._iter_parent_trees()]
1696
1768
vf.add_lines((file_id, tree_revision), parent_keys,
1697
self.get_file(file_id).readlines())
1769
self.get_file_lines(file_id))
1698
1770
repo = self._get_repository()
1699
1771
base_vf = repo.texts
1700
1772
if base_vf not in vf.fallback_versionedfiles:
2190
2264
for num, _unused in enumerate(wt.all_file_ids()):
2191
2265
if num > 0: # more than just a root
2192
2266
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)
2196
2267
file_trans_id = {}
2197
2268
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2198
2269
pp = ProgressPhase("Build phase", 2, top_pb)
2222
2293
precomputed_delta = []
2224
2295
precomputed_delta = None
2296
# Check if tree inventory has content. If so, we populate
2297
# existing_files with the directory content. If there are no
2298
# entries we skip populating existing_files as its not used.
2299
# This improves performance and unncessary work on large
2300
# directory trees. (#501307)
2302
existing_files = set()
2303
for dir, files in wt.walkdirs():
2304
existing_files.update(f[0] for f in files)
2225
2305
for num, (tree_path, entry) in \
2226
2306
enumerate(tree.inventory.iter_entries_by_dir()):
2227
2307
pb.update("Building tree", num - len(deferred_contents), total)
2418
2502
raise errors.BadFileKindError(name, kind)
2421
@deprecated_function(deprecated_in((1, 9, 0)))
2422
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
2423
"""Create new file contents according to an inventory entry.
2425
DEPRECATED. Use create_from_tree instead.
2427
if entry.kind == "file":
2429
lines = tree.get_file(entry.file_id).readlines()
2430
tt.create_file(lines, trans_id, mode_id=mode_id)
2431
elif entry.kind == "symlink":
2432
tt.create_symlink(tree.get_symlink_target(entry.file_id), trans_id)
2433
elif entry.kind == "directory":
2434
tt.create_directory(trans_id)
2437
2505
def create_from_tree(tt, trans_id, tree, file_id, bytes=None,
2438
2506
filter_tree_path=None):
2439
2507
"""Create new file contents according to tree contents.
2516
2584
def revert(working_tree, target_tree, filenames, backups=False,
2517
pb=DummyProgress(), change_reporter=None):
2585
pb=None, change_reporter=None):
2518
2586
"""Revert a working tree's contents to those of a target tree."""
2519
2587
target_tree.lock_read()
2588
pb = ui.ui_factory.nested_progress_bar()
2520
2589
tt = TreeTransform(working_tree, pb)
2522
2591
pp = ProgressPhase("Revert phase", 3, pb)
2650
2717
parent_trans = ROOT_PARENT
2652
2719
parent_trans = tt.trans_id_file_id(parent[1])
2653
tt.adjust_path(name[1], parent_trans, trans_id)
2720
if parent[0] is None and versioned[0]:
2721
tt.adjust_root_path(name[1], parent_trans)
2723
tt.adjust_path(name[1], parent_trans, trans_id)
2654
2724
if executable[0] != executable[1] and kind[1] == "file":
2655
2725
tt.set_executability(executable[1], trans_id)
2656
2726
if working_tree.supports_content_filtering():
2669
2739
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2670
2740
deferred_files):
2671
2741
tt.create_file(bytes, trans_id, mode_id)
2742
tt.fixup_new_roots()
2673
2744
if basis_tree is not None:
2674
2745
basis_tree.unlock()
2675
2746
return merge_modified
2678
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
2749
def resolve_conflicts(tt, pb=None, pass_func=None):
2679
2750
"""Make many conflict-resolution attempts, but die if they fail"""
2680
2751
if pass_func is None:
2681
2752
pass_func = conflict_pass
2682
2753
new_conflicts = set()
2754
pb = ui.ui_factory.nested_progress_bar()
2684
2756
for n in range(10):
2685
2757
pb.update('Resolution pass', n+1, 10)
2818
2890
self.pending_deletions = []
2820
2892
def rename(self, from_, to):
2821
"""Rename a file from one path to another. Functions like os.rename"""
2893
"""Rename a file from one path to another."""
2823
2895
os.rename(from_, to)
2824
2896
except OSError, e:
2825
2897
if e.errno in (errno.EEXIST, errno.ENOTEMPTY):
2826
2898
raise errors.FileExists(to, str(e))
2899
# normal OSError doesn't include filenames so it's hard to see where
2900
# the problem is, see https://bugs.launchpad.net/bzr/+bug/491763
2901
raise errors.TransformRenameFailed(from_, to, str(e), e.errno)
2828
2902
self.past_renames.append((from_, to))
2830
2904
def pre_delete(self, from_, to):