32
33
revision as _mod_revision,
35
37
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
36
ReusingTransform, NotVersionedError, CantMoveRoot,
38
ReusingTransform, CantMoveRoot,
37
39
ExistingLimbo, ImmortalLimbo, NoFinalPath,
38
40
UnableCreateSymlink)
39
41
from bzrlib.filters import filtered_output_bytes, ContentFilterContext
78
80
class TreeTransformBase(object):
79
81
"""The base class for TreeTransform and its kin."""
81
def __init__(self, tree, pb=DummyProgress(),
83
def __init__(self, tree, pb=None,
82
84
case_sensitive=True):
85
87
:param tree: The tree that will be transformed, but not necessarily
87
:param pb: A ProgressBar indicating how much progress is being made
88
90
:param case_sensitive: If True, the target of the transform is
89
91
case sensitive, not just case preserving.
162
164
def adjust_path(self, name, parent, trans_id):
163
165
"""Change the path that is assigned to a transaction id."""
167
raise ValueError("Parent trans-id may not be None")
164
168
if trans_id == self._new_root:
165
169
raise CantMoveRoot
166
170
self._new_name[trans_id] = name
167
171
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
173
def adjust_root_path(self, name, parent):
174
174
"""Emulate moving the root by moving all children, instead.
202
202
self.version_file(old_root_file_id, old_root)
203
203
self.unversion_file(self._new_root)
205
def fixup_new_roots(self):
206
"""Reinterpret requests to change the root directory
208
Instead of creating a root directory, or moving an existing directory,
209
all the attributes and children of the new root are applied to the
210
existing root directory.
212
This means that the old root trans-id becomes obsolete, so it is
213
recommended only to invoke this after the root trans-id has become
216
new_roots = [k for k, v in self._new_parent.iteritems() if v is
218
if len(new_roots) < 1:
220
if len(new_roots) != 1:
221
raise ValueError('A tree cannot have two roots!')
222
if self._new_root is None:
223
self._new_root = new_roots[0]
225
old_new_root = new_roots[0]
226
# TODO: What to do if a old_new_root is present, but self._new_root is
227
# not listed as being removed? This code explicitly unversions
228
# the old root and versions it with the new file_id. Though that
229
# seems like an incomplete delta
231
# unversion the new root's directory.
232
file_id = self.final_file_id(old_new_root)
233
if old_new_root in self._new_id:
234
self.cancel_versioning(old_new_root)
236
self.unversion_file(old_new_root)
237
# if, at this stage, root still has an old file_id, zap it so we can
238
# stick a new one in.
239
if (self.tree_file_id(self._new_root) is not None and
240
self._new_root not in self._removed_id):
241
self.unversion_file(self._new_root)
242
self.version_file(file_id, self._new_root)
244
# Now move children of new root into old root directory.
245
# Ensure all children are registered with the transaction, but don't
246
# use directly-- some tree children have new parents
247
list(self.iter_tree_children(old_new_root))
248
# Move all children of new root into old root directory.
249
for child in self.by_parent().get(old_new_root, []):
250
self.adjust_path(self.final_name(child), self._new_root, child)
252
# Ensure old_new_root has no directory.
253
if old_new_root in self._new_contents:
254
self.cancel_creation(old_new_root)
256
self.delete_contents(old_new_root)
258
# prevent deletion of root directory.
259
if self._new_root in self._removed_contents:
260
self.cancel_deletion(self._new_root)
262
# destroy path info for old_new_root.
263
del self._new_parent[old_new_root]
264
del self._new_name[old_new_root]
205
266
def trans_id_tree_file_id(self, inventory_id):
206
267
"""Determine the transaction id of a working tree file.
854
922
def get_preview_tree(self):
855
923
"""Return a tree representing the result of the transform.
857
This tree only supports the subset of Tree functionality required
858
by show_diff_trees. It must only be compared to tt._tree.
925
The tree is a snapshot, and altering the TreeTransform will invalidate
860
928
return _PreviewTree(self)
930
def commit(self, branch, message, merge_parents=None, strict=False):
931
"""Commit the result of this TreeTransform to a branch.
933
:param branch: The branch to commit to.
934
:param message: The message to attach to the commit.
935
:param merge_parents: Additional parents specified by pending merges.
936
:return: The revision_id of the revision committed.
938
self._check_malformed()
940
unversioned = set(self._new_contents).difference(set(self._new_id))
941
for trans_id in unversioned:
942
if self.final_file_id(trans_id) is None:
943
raise errors.StrictCommitFailed()
945
revno, last_rev_id = branch.last_revision_info()
946
if last_rev_id == _mod_revision.NULL_REVISION:
947
if merge_parents is not None:
948
raise ValueError('Cannot supply merge parents for first'
952
parent_ids = [last_rev_id]
953
if merge_parents is not None:
954
parent_ids.extend(merge_parents)
955
if self._tree.get_revision_id() != last_rev_id:
956
raise ValueError('TreeTransform not based on branch basis: %s' %
957
self._tree.get_revision_id())
958
builder = branch.get_commit_builder(parent_ids)
959
preview = self.get_preview_tree()
960
list(builder.record_iter_changes(preview, last_rev_id,
961
self.iter_changes()))
962
builder.finish_inventory()
963
revision_id = builder.commit(message)
964
branch.set_last_revision_info(revno + 1, revision_id)
862
967
def _text_parent(self, trans_id):
863
968
file_id = self.tree_file_id(trans_id)
958
1063
class DiskTreeTransform(TreeTransformBase):
959
1064
"""Tree transform storing its contents on disk."""
961
def __init__(self, tree, limbodir, pb=DummyProgress(),
1066
def __init__(self, tree, limbodir, pb=None,
962
1067
case_sensitive=True):
964
1069
:param tree: The tree that will be transformed, but not necessarily
965
1070
the output tree.
966
1071
:param limbodir: A directory where new files can be stored until
967
1072
they are installed in their proper places
968
:param pb: A ProgressBar indicating how much progress is being made
969
1074
:param case_sensitive: If True, the target of the transform is
970
1075
case sensitive, not just case preserving.
995
1101
self._new_contents.iteritems()]
996
1102
entries.sort(reverse=True)
997
1103
for path, trans_id, kind in entries:
998
if kind == "directory":
1003
os.rmdir(self._limbodir)
1106
delete_any(self._limbodir)
1004
1107
except OSError:
1005
1108
# We don't especially care *why* the dir is immortal.
1006
1109
raise ImmortalLimbo(self._limbodir)
1008
1111
if self._deletiondir is not None:
1009
os.rmdir(self._deletiondir)
1112
delete_any(self._deletiondir)
1010
1113
except OSError:
1011
1114
raise errors.ImmortalPendingDeletion(self._deletiondir)
1015
1118
def _limbo_name(self, trans_id):
1016
1119
"""Generate the limbo name of a file"""
1017
1120
limbo_name = self._limbo_files.get(trans_id)
1018
if limbo_name is not None:
1020
parent = self._new_parent.get(trans_id)
1021
# if the parent directory is already in limbo (e.g. when building a
1022
# tree), choose a limbo name inside the parent, to reduce further
1024
use_direct_path = False
1025
if self._new_contents.get(parent) == 'directory':
1026
filename = self._new_name.get(trans_id)
1027
if filename is not None:
1028
if parent not in self._limbo_children:
1029
self._limbo_children[parent] = set()
1030
self._limbo_children_names[parent] = {}
1031
use_direct_path = True
1032
# the direct path can only be used if no other file has
1033
# already taken this pathname, i.e. if the name is unused, or
1034
# if it is already associated with this trans_id.
1035
elif self._case_sensitive_target:
1036
if (self._limbo_children_names[parent].get(filename)
1037
in (trans_id, None)):
1038
use_direct_path = True
1040
for l_filename, l_trans_id in\
1041
self._limbo_children_names[parent].iteritems():
1042
if l_trans_id == trans_id:
1044
if l_filename.lower() == filename.lower():
1047
use_direct_path = True
1050
limbo_name = pathjoin(self._limbo_files[parent], filename)
1051
self._limbo_children[parent].add(trans_id)
1052
self._limbo_children_names[parent][filename] = trans_id
1054
limbo_name = pathjoin(self._limbodir, trans_id)
1055
self._needs_rename.add(trans_id)
1056
self._limbo_files[trans_id] = limbo_name
1121
if limbo_name is None:
1122
limbo_name = self._generate_limbo_path(trans_id)
1123
self._limbo_files[trans_id] = limbo_name
1057
1124
return limbo_name
1126
def _generate_limbo_path(self, trans_id):
1127
"""Generate a limbo path using the trans_id as the relative path.
1129
This is suitable as a fallback, and when the transform should not be
1130
sensitive to the path encoding of the limbo directory.
1132
self._needs_rename.add(trans_id)
1133
return pathjoin(self._limbodir, trans_id)
1059
1135
def adjust_path(self, name, parent, trans_id):
1060
1136
previous_parent = self._new_parent.get(trans_id)
1061
1137
previous_name = self._new_name.get(trans_id)
1063
1139
if (trans_id in self._limbo_files and
1064
1140
trans_id not in self._needs_rename):
1065
1141
self._rename_in_limbo([trans_id])
1066
self._limbo_children[previous_parent].remove(trans_id)
1067
del self._limbo_children_names[previous_parent][previous_name]
1142
if previous_parent != parent:
1143
self._limbo_children[previous_parent].remove(trans_id)
1144
if previous_parent != parent or previous_name != name:
1145
del self._limbo_children_names[previous_parent][previous_name]
1069
1147
def _rename_in_limbo(self, trans_ids):
1070
1148
"""Fix limbo names so that the right final path is produced.
1082
1160
if trans_id not in self._new_contents:
1084
1162
new_path = self._limbo_name(trans_id)
1085
os.rename(old_path, new_path)
1163
osutils.rename(old_path, new_path)
1164
for descendant in self._limbo_descendants(trans_id):
1165
desc_path = self._limbo_files[descendant]
1166
desc_path = new_path + desc_path[len(old_path):]
1167
self._limbo_files[descendant] = desc_path
1169
def _limbo_descendants(self, trans_id):
1170
"""Return the set of trans_ids whose limbo paths descend from this."""
1171
descendants = set(self._limbo_children.get(trans_id, []))
1172
for descendant in list(descendants):
1173
descendants.update(self._limbo_descendants(descendant))
1087
1176
def create_file(self, contents, trans_id, mode_id=None):
1088
1177
"""Schedule creation of a new file.
1122
1212
def _read_symlink_target(self, trans_id):
1123
1213
return os.readlink(self._limbo_name(trans_id))
1215
def _set_mtime(self, path):
1216
"""All files that are created get the same mtime.
1218
This time is set by the first object to be created.
1220
if self._creation_mtime is None:
1221
self._creation_mtime = time.time()
1222
os.utime(path, (self._creation_mtime, self._creation_mtime))
1125
1224
def create_hardlink(self, path, trans_id):
1126
1225
"""Schedule creation of a hard link"""
1127
1226
name = self._limbo_name(trans_id)
1358
1457
yield self.trans_id_tree_path(childpath)
1459
def _generate_limbo_path(self, trans_id):
1460
"""Generate a limbo path using the final path if possible.
1462
This optimizes the performance of applying the tree transform by
1463
avoiding renames. These renames can be avoided only when the parent
1464
directory is already scheduled for creation.
1466
If the final path cannot be used, falls back to using the trans_id as
1469
parent = self._new_parent.get(trans_id)
1470
# if the parent directory is already in limbo (e.g. when building a
1471
# tree), choose a limbo name inside the parent, to reduce further
1473
use_direct_path = False
1474
if self._new_contents.get(parent) == 'directory':
1475
filename = self._new_name.get(trans_id)
1476
if filename is not None:
1477
if parent not in self._limbo_children:
1478
self._limbo_children[parent] = set()
1479
self._limbo_children_names[parent] = {}
1480
use_direct_path = True
1481
# the direct path can only be used if no other file has
1482
# already taken this pathname, i.e. if the name is unused, or
1483
# if it is already associated with this trans_id.
1484
elif self._case_sensitive_target:
1485
if (self._limbo_children_names[parent].get(filename)
1486
in (trans_id, None)):
1487
use_direct_path = True
1489
for l_filename, l_trans_id in\
1490
self._limbo_children_names[parent].iteritems():
1491
if l_trans_id == trans_id:
1493
if l_filename.lower() == filename.lower():
1496
use_direct_path = True
1498
if not use_direct_path:
1499
return DiskTreeTransform._generate_limbo_path(self, trans_id)
1501
limbo_name = pathjoin(self._limbo_files[parent], filename)
1502
self._limbo_children[parent].add(trans_id)
1503
self._limbo_children_names[parent][filename] = trans_id
1361
1507
def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1362
1508
"""Apply all changes to the inventory and filesystem.
1485
1629
child_pb.update('removing file', num, len(tree_paths))
1486
1630
full_path = self._tree.abspath(path)
1487
1631
if trans_id in self._removed_contents:
1488
mover.pre_delete(full_path, os.path.join(self._deletiondir,
1490
elif trans_id in self._new_name or trans_id in \
1632
delete_path = os.path.join(self._deletiondir, trans_id)
1633
mover.pre_delete(full_path, delete_path)
1634
elif (trans_id in self._new_name
1635
or trans_id in self._new_parent):
1493
1637
mover.rename(full_path, self._limbo_name(trans_id))
1494
1638
except OSError, e:
1548
1692
unversioned files in the input tree.
1551
def __init__(self, tree, pb=DummyProgress(), case_sensitive=True):
1695
def __init__(self, tree, pb=None, case_sensitive=True):
1552
1696
tree.lock_read()
1553
1697
limbodir = osutils.mkdtemp(prefix='bzr-limbo-')
1554
1698
DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive)
1599
1743
self._all_children_cache = {}
1600
1744
self._path2trans_id_cache = {}
1601
1745
self._final_name_cache = {}
1603
def _changes(self, file_id):
1604
for changes in self._transform.iter_changes():
1605
if changes[0] == file_id:
1746
self._iter_changes_cache = dict((c[0], c) for c in
1747
self._transform.iter_changes())
1608
1749
def _content_change(self, file_id):
1609
1750
"""Return True if the content of this file changed"""
1610
changes = self._changes(file_id)
1751
changes = self._iter_changes_cache.get(file_id)
1611
1752
# changes[2] is true if the file content changed. See
1612
1753
# InterTree.iter_changes.
1613
1754
return (changes is not None and changes[2])
1682
1823
def __iter__(self):
1683
1824
return iter(self.all_file_ids())
1685
def has_id(self, file_id):
1826
def _has_id(self, file_id, fallback_check):
1686
1827
if file_id in self._transform._r_new_id:
1688
1829
elif file_id in set([self._transform.tree_file_id(trans_id) for
1689
1830
trans_id in self._transform._removed_id]):
1692
return self._transform._tree.has_id(file_id)
1833
return fallback_check(file_id)
1835
def has_id(self, file_id):
1836
return self._has_id(file_id, self._transform._tree.has_id)
1838
def has_or_had_id(self, file_id):
1839
return self._has_id(file_id, self._transform._tree.has_or_had_id)
1694
1841
def _path2trans_id(self, path):
1695
1842
# We must not use None here, because that is a valid value to store.
1748
1895
if self._transform.final_file_id(trans_id) is None:
1749
1896
yield self._final_paths._determine_path(trans_id)
1751
def _make_inv_entries(self, ordered_entries, specific_file_ids=None):
1898
def _make_inv_entries(self, ordered_entries, specific_file_ids=None,
1899
yield_parents=False):
1752
1900
for trans_id, parent_file_id in ordered_entries:
1753
1901
file_id = self._transform.final_file_id(trans_id)
1754
1902
if file_id is None:
1780
1928
ordered_ids.append((trans_id, parent_file_id))
1781
1929
return ordered_ids
1783
def iter_entries_by_dir(self, specific_file_ids=None):
1931
def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
1784
1932
# This may not be a maximally efficient implementation, but it is
1785
1933
# reasonably straightforward. An implementation that grafts the
1786
1934
# TreeTransform changes onto the tree's iter_entries_by_dir results
1841
1989
def get_file_mtime(self, file_id, path=None):
1842
1990
"""See Tree.get_file_mtime"""
1843
1991
if not self._content_change(file_id):
1844
return self._transform._tree.get_file_mtime(file_id, path)
1992
return self._transform._tree.get_file_mtime(file_id)
1845
1993
return self._stat_limbo_file(file_id).st_mtime
1847
1995
def _file_size(self, entry, stat_value):
1909
2057
executable = None
1910
2058
if kind == 'symlink':
1911
2059
link_or_sha1 = os.readlink(limbo_name).decode(osutils._fs_enc)
1912
if supports_executable():
1913
executable = tt._new_executability.get(trans_id, executable)
2060
executable = tt._new_executability.get(trans_id, executable)
1914
2061
return kind, size, executable, link_or_sha1
1916
2063
def iter_changes(self, from_tree, include_unchanged=False,
1965
2112
return old_annotation
1966
2113
if not changed_content:
1967
2114
return old_annotation
2115
# TODO: This is doing something similar to what WT.annotate_iter is
2116
# doing, however it fails slightly because it doesn't know what
2117
# the *other* revision_id is, so it doesn't know how to give the
2118
# other as the origin for some lines, they all get
2119
# 'default_revision'
2120
# It would be nice to be able to use the new Annotator based
2121
# approach, as well.
1968
2122
return annotate.reannotate([old_annotation],
1969
2123
self.get_file(file_id).readlines(),
1970
2124
default_revision)
2222
2376
new_desired_files = desired_files
2224
2378
iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
2225
unchanged = dict((f, p[1]) for (f, p, c, v, d, n, k, e)
2226
in iter if not (c or e[0] != e[1]))
2379
unchanged = [(f, p[1]) for (f, p, c, v, d, n, k, e)
2380
in iter if not (c or e[0] != e[1])]
2381
if accelerator_tree.supports_content_filtering():
2382
unchanged = [(f, p) for (f, p) in unchanged
2383
if not accelerator_tree.iter_search_rules([p]).next()]
2384
unchanged = dict(unchanged)
2227
2385
new_desired_files = []
2229
2387
for file_id, (trans_id, tree_path) in desired_files:
2352
2510
tt.create_directory(trans_id)
2355
def create_from_tree(tt, trans_id, tree, file_id, bytes=None):
2356
"""Create new file contents according to tree contents."""
2513
def create_from_tree(tt, trans_id, tree, file_id, bytes=None,
2514
filter_tree_path=None):
2515
"""Create new file contents according to tree contents.
2517
:param filter_tree_path: the tree path to use to lookup
2518
content filters to apply to the bytes output in the working tree.
2519
This only applies if the working tree supports content filtering.
2357
2521
kind = tree.kind(file_id)
2358
2522
if kind == 'directory':
2359
2523
tt.create_directory(trans_id)
2364
2528
bytes = tree_file.readlines()
2366
2530
tree_file.close()
2532
if wt.supports_content_filtering() and filter_tree_path is not None:
2533
filters = wt._content_filter_stack(filter_tree_path)
2534
bytes = filtered_output_bytes(bytes, filters,
2535
ContentFilterContext(filter_tree_path, tree))
2367
2536
tt.create_file(bytes, trans_id)
2368
2537
elif kind == "symlink":
2369
2538
tt.create_symlink(tree.get_symlink_target(file_id), trans_id)
2423
2592
def revert(working_tree, target_tree, filenames, backups=False,
2424
pb=DummyProgress(), change_reporter=None):
2593
pb=None, change_reporter=None):
2425
2594
"""Revert a working tree's contents to those of a target tree."""
2426
2595
target_tree.lock_read()
2596
pb = ui.ui_factory.nested_progress_bar()
2427
2597
tt = TreeTransform(working_tree, pb)
2429
2599
pp = ProgressPhase("Revert phase", 3, pb)
2557
2725
parent_trans = ROOT_PARENT
2559
2727
parent_trans = tt.trans_id_file_id(parent[1])
2560
tt.adjust_path(name[1], parent_trans, trans_id)
2728
if parent[0] is None and versioned[0]:
2729
tt.adjust_root_path(name[1], parent_trans)
2731
tt.adjust_path(name[1], parent_trans, trans_id)
2561
2732
if executable[0] != executable[1] and kind[1] == "file":
2562
2733
tt.set_executability(executable[1], trans_id)
2563
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2565
tt.create_file(bytes, trans_id, mode_id)
2734
if working_tree.supports_content_filtering():
2735
for index, ((trans_id, mode_id), bytes) in enumerate(
2736
target_tree.iter_files_bytes(deferred_files)):
2737
file_id = deferred_files[index][0]
2738
# We're reverting a tree to the target tree so using the
2739
# target tree to find the file path seems the best choice
2740
# here IMO - Ian C 27/Oct/2009
2741
filter_tree_path = target_tree.id2path(file_id)
2742
filters = working_tree._content_filter_stack(filter_tree_path)
2743
bytes = filtered_output_bytes(bytes, filters,
2744
ContentFilterContext(filter_tree_path, working_tree))
2745
tt.create_file(bytes, trans_id, mode_id)
2747
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2749
tt.create_file(bytes, trans_id, mode_id)
2750
tt.fixup_new_roots()
2567
2752
if basis_tree is not None:
2568
2753
basis_tree.unlock()
2569
2754
return merge_modified
2572
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
2757
def resolve_conflicts(tt, pb=None, pass_func=None):
2573
2758
"""Make many conflict-resolution attempts, but die if they fail"""
2574
2759
if pass_func is None:
2575
2760
pass_func = conflict_pass
2576
2761
new_conflicts = set()
2762
pb = ui.ui_factory.nested_progress_bar()
2578
2764
for n in range(10):
2579
2765
pb.update('Resolution pass', n+1, 10)
2712
2898
self.pending_deletions = []
2714
2900
def rename(self, from_, to):
2715
"""Rename a file from one path to another. Functions like os.rename"""
2901
"""Rename a file from one path to another."""
2717
os.rename(from_, to)
2903
osutils.rename(from_, to)
2718
2904
except OSError, e:
2719
2905
if e.errno in (errno.EEXIST, errno.ENOTEMPTY):
2720
2906
raise errors.FileExists(to, str(e))