1
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
1
# Copyright (C) 2006, 2007, 2008, 2009 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
85
85
:param tree: The tree that will be transformed, but not necessarily
87
:param pb: A ProgressBar indicating how much progress is being made
87
:param pb: A ProgressTask indicating how much progress is being made
88
88
:param case_sensitive: If True, the target of the transform is
89
89
case sensitive, not just case preserving.
442
442
conflicts.extend(self._overwrite_conflicts())
445
def _check_malformed(self):
446
conflicts = self.find_conflicts()
447
if len(conflicts) != 0:
448
raise MalformedTransform(conflicts=conflicts)
445
450
def _add_tree_children(self):
446
451
"""Add all the children of all active parents to the known paths.
854
859
def get_preview_tree(self):
855
860
"""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.
862
The tree is a snapshot, and altering the TreeTransform will invalidate
860
865
return _PreviewTree(self)
867
def commit(self, branch, message, merge_parents=None, strict=False):
868
"""Commit the result of this TreeTransform to a branch.
870
:param branch: The branch to commit to.
871
:param message: The message to attach to the commit.
872
:param merge_parents: Additional parents specified by pending merges.
873
:return: The revision_id of the revision committed.
875
self._check_malformed()
877
unversioned = set(self._new_contents).difference(set(self._new_id))
878
for trans_id in unversioned:
879
if self.final_file_id(trans_id) is None:
880
raise errors.StrictCommitFailed()
882
revno, last_rev_id = branch.last_revision_info()
883
if last_rev_id == _mod_revision.NULL_REVISION:
884
if merge_parents is not None:
885
raise ValueError('Cannot supply merge parents for first'
889
parent_ids = [last_rev_id]
890
if merge_parents is not None:
891
parent_ids.extend(merge_parents)
892
if self._tree.get_revision_id() != last_rev_id:
893
raise ValueError('TreeTransform not based on branch basis: %s' %
894
self._tree.get_revision_id())
895
builder = branch.get_commit_builder(parent_ids)
896
preview = self.get_preview_tree()
897
list(builder.record_iter_changes(preview, last_rev_id,
898
self.iter_changes()))
899
builder.finish_inventory()
900
revision_id = builder.commit(message)
901
branch.set_last_revision_info(revno + 1, revision_id)
862
904
def _text_parent(self, trans_id):
863
905
file_id = self.tree_file_id(trans_id)
1012
1054
def _limbo_name(self, trans_id):
1013
1055
"""Generate the limbo name of a file"""
1014
1056
limbo_name = self._limbo_files.get(trans_id)
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
1057
if limbo_name is None:
1058
limbo_name = self._generate_limbo_path(trans_id)
1059
self._limbo_files[trans_id] = limbo_name
1054
1060
return limbo_name
1062
def _generate_limbo_path(self, trans_id):
1063
"""Generate a limbo path using the trans_id as the relative path.
1065
This is suitable as a fallback, and when the transform should not be
1066
sensitive to the path encoding of the limbo directory.
1068
self._needs_rename.add(trans_id)
1069
return pathjoin(self._limbodir, trans_id)
1056
1071
def adjust_path(self, name, parent, trans_id):
1057
1072
previous_parent = self._new_parent.get(trans_id)
1058
1073
previous_name = self._new_name.get(trans_id)
1081
1096
new_path = self._limbo_name(trans_id)
1082
1097
os.rename(old_path, new_path)
1098
for descendant in self._limbo_descendants(trans_id):
1099
desc_path = self._limbo_files[descendant]
1100
desc_path = new_path + desc_path[len(old_path):]
1101
self._limbo_files[descendant] = desc_path
1103
def _limbo_descendants(self, trans_id):
1104
"""Return the set of trans_ids whose limbo paths descend from this."""
1105
descendants = set(self._limbo_children.get(trans_id, []))
1106
for descendant in list(descendants):
1107
descendants.update(self._limbo_descendants(descendant))
1084
1110
def create_file(self, contents, trans_id, mode_id=None):
1085
1111
"""Schedule creation of a new file.
1355
1381
yield self.trans_id_tree_path(childpath)
1383
def _generate_limbo_path(self, trans_id):
1384
"""Generate a limbo path using the final path if possible.
1386
This optimizes the performance of applying the tree transform by
1387
avoiding renames. These renames can be avoided only when the parent
1388
directory is already scheduled for creation.
1390
If the final path cannot be used, falls back to using the trans_id as
1393
parent = self._new_parent.get(trans_id)
1394
# if the parent directory is already in limbo (e.g. when building a
1395
# tree), choose a limbo name inside the parent, to reduce further
1397
use_direct_path = False
1398
if self._new_contents.get(parent) == 'directory':
1399
filename = self._new_name.get(trans_id)
1400
if filename is not None:
1401
if parent not in self._limbo_children:
1402
self._limbo_children[parent] = set()
1403
self._limbo_children_names[parent] = {}
1404
use_direct_path = True
1405
# the direct path can only be used if no other file has
1406
# already taken this pathname, i.e. if the name is unused, or
1407
# if it is already associated with this trans_id.
1408
elif self._case_sensitive_target:
1409
if (self._limbo_children_names[parent].get(filename)
1410
in (trans_id, None)):
1411
use_direct_path = True
1413
for l_filename, l_trans_id in\
1414
self._limbo_children_names[parent].iteritems():
1415
if l_trans_id == trans_id:
1417
if l_filename.lower() == filename.lower():
1420
use_direct_path = True
1422
if not use_direct_path:
1423
return DiskTreeTransform._generate_limbo_path(self, trans_id)
1425
limbo_name = pathjoin(self._limbo_files[parent], filename)
1426
self._limbo_children[parent].add(trans_id)
1427
self._limbo_children_names[parent][filename] = trans_id
1358
1431
def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1359
1432
"""Apply all changes to the inventory and filesystem.
1596
1667
self._all_children_cache = {}
1597
1668
self._path2trans_id_cache = {}
1598
1669
self._final_name_cache = {}
1600
def _changes(self, file_id):
1601
for changes in self._transform.iter_changes():
1602
if changes[0] == file_id:
1670
self._iter_changes_cache = dict((c[0], c) for c in
1671
self._transform.iter_changes())
1605
1673
def _content_change(self, file_id):
1606
1674
"""Return True if the content of this file changed"""
1607
changes = self._changes(file_id)
1675
changes = self._iter_changes_cache.get(file_id)
1608
1676
# changes[2] is true if the file content changed. See
1609
1677
# InterTree.iter_changes.
1610
1678
return (changes is not None and changes[2])
1679
1747
def __iter__(self):
1680
1748
return iter(self.all_file_ids())
1682
def has_id(self, file_id):
1750
def _has_id(self, file_id, fallback_check):
1683
1751
if file_id in self._transform._r_new_id:
1685
1753
elif file_id in set([self._transform.tree_file_id(trans_id) for
1686
1754
trans_id in self._transform._removed_id]):
1689
return self._transform._tree.has_id(file_id)
1757
return fallback_check(file_id)
1759
def has_id(self, file_id):
1760
return self._has_id(file_id, self._transform._tree.has_id)
1762
def has_or_had_id(self, file_id):
1763
return self._has_id(file_id, self._transform._tree.has_or_had_id)
1691
1765
def _path2trans_id(self, path):
1692
1766
# We must not use None here, because that is a valid value to store.
1745
1819
if self._transform.final_file_id(trans_id) is None:
1746
1820
yield self._final_paths._determine_path(trans_id)
1748
def _make_inv_entries(self, ordered_entries, specific_file_ids=None):
1822
def _make_inv_entries(self, ordered_entries, specific_file_ids=None,
1823
yield_parents=False):
1749
1824
for trans_id, parent_file_id in ordered_entries:
1750
1825
file_id = self._transform.final_file_id(trans_id)
1751
1826
if file_id is None:
1777
1852
ordered_ids.append((trans_id, parent_file_id))
1778
1853
return ordered_ids
1780
def iter_entries_by_dir(self, specific_file_ids=None):
1855
def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
1781
1856
# This may not be a maximally efficient implementation, but it is
1782
1857
# reasonably straightforward. An implementation that grafts the
1783
1858
# TreeTransform changes onto the tree's iter_entries_by_dir results
1786
1861
ordered_ids = self._list_files_by_dir()
1787
1862
for entry, trans_id in self._make_inv_entries(ordered_ids,
1863
specific_file_ids, yield_parents=yield_parents):
1789
1864
yield unicode(self._final_paths.get_path(trans_id)), entry
1791
1866
def _iter_entries_for_dir(self, dir_path):
1838
1913
def get_file_mtime(self, file_id, path=None):
1839
1914
"""See Tree.get_file_mtime"""
1840
1915
if not self._content_change(file_id):
1841
return self._transform._tree.get_file_mtime(file_id, path)
1916
return self._transform._tree.get_file_mtime(file_id)
1842
1917
return self._stat_limbo_file(file_id).st_mtime
1844
1919
def _file_size(self, entry, stat_value):
1906
1981
executable = None
1907
1982
if kind == 'symlink':
1908
1983
link_or_sha1 = os.readlink(limbo_name).decode(osutils._fs_enc)
1909
if supports_executable():
1910
executable = tt._new_executability.get(trans_id, executable)
1984
executable = tt._new_executability.get(trans_id, executable)
1911
1985
return kind, size, executable, link_or_sha1
1913
1987
def iter_changes(self, from_tree, include_unchanged=False,
1962
2036
return old_annotation
1963
2037
if not changed_content:
1964
2038
return old_annotation
2039
# TODO: This is doing something similar to what WT.annotate_iter is
2040
# doing, however it fails slightly because it doesn't know what
2041
# the *other* revision_id is, so it doesn't know how to give the
2042
# other as the origin for some lines, they all get
2043
# 'default_revision'
2044
# It would be nice to be able to use the new Annotator based
2045
# approach, as well.
1965
2046
return annotate.reannotate([old_annotation],
1966
2047
self.get_file(file_id).readlines(),
1967
2048
default_revision)
2033
2114
self.transform = transform
2035
2116
def _determine_path(self, trans_id):
2036
if trans_id == self.transform.root:
2117
if (trans_id == self.transform.root or trans_id == ROOT_PARENT):
2038
2119
name = self.transform.final_name(trans_id)
2039
2120
parent_id = self.transform.final_parent(trans_id)
2219
2300
new_desired_files = desired_files
2221
2302
iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
2222
unchanged = dict((f, p[1]) for (f, p, c, v, d, n, k, e)
2223
in iter if not (c or e[0] != e[1]))
2303
unchanged = [(f, p[1]) for (f, p, c, v, d, n, k, e)
2304
in iter if not (c or e[0] != e[1])]
2305
if accelerator_tree.supports_content_filtering():
2306
unchanged = [(f, p) for (f, p) in unchanged
2307
if not accelerator_tree.iter_search_rules([p]).next()]
2308
unchanged = dict(unchanged)
2224
2309
new_desired_files = []
2226
2311
for file_id, (trans_id, tree_path) in desired_files:
2349
2434
tt.create_directory(trans_id)
2352
def create_from_tree(tt, trans_id, tree, file_id, bytes=None):
2353
"""Create new file contents according to tree contents."""
2437
def create_from_tree(tt, trans_id, tree, file_id, bytes=None,
2438
filter_tree_path=None):
2439
"""Create new file contents according to tree contents.
2441
:param filter_tree_path: the tree path to use to lookup
2442
content filters to apply to the bytes output in the working tree.
2443
This only applies if the working tree supports content filtering.
2354
2445
kind = tree.kind(file_id)
2355
2446
if kind == 'directory':
2356
2447
tt.create_directory(trans_id)
2361
2452
bytes = tree_file.readlines()
2363
2454
tree_file.close()
2456
if wt.supports_content_filtering() and filter_tree_path is not None:
2457
filters = wt._content_filter_stack(filter_tree_path)
2458
bytes = filtered_output_bytes(bytes, filters,
2459
ContentFilterContext(filter_tree_path, tree))
2364
2460
tt.create_file(bytes, trans_id)
2365
2461
elif kind == "symlink":
2366
2462
tt.create_symlink(tree.get_symlink_target(file_id), trans_id)
2557
2653
tt.adjust_path(name[1], parent_trans, trans_id)
2558
2654
if executable[0] != executable[1] and kind[1] == "file":
2559
2655
tt.set_executability(executable[1], trans_id)
2560
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2562
tt.create_file(bytes, trans_id, mode_id)
2656
if working_tree.supports_content_filtering():
2657
for index, ((trans_id, mode_id), bytes) in enumerate(
2658
target_tree.iter_files_bytes(deferred_files)):
2659
file_id = deferred_files[index][0]
2660
# We're reverting a tree to the target tree so using the
2661
# target tree to find the file path seems the best choice
2662
# here IMO - Ian C 27/Oct/2009
2663
filter_tree_path = target_tree.id2path(file_id)
2664
filters = working_tree._content_filter_stack(filter_tree_path)
2665
bytes = filtered_output_bytes(bytes, filters,
2666
ContentFilterContext(filter_tree_path, working_tree))
2667
tt.create_file(bytes, trans_id, mode_id)
2669
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2671
tt.create_file(bytes, trans_id, mode_id)
2564
2673
if basis_tree is not None:
2565
2674
basis_tree.unlock()