54
53
from .errors import (DuplicateKey, MalformedTransform,
55
54
ReusingTransform, CantMoveRoot,
56
ImmortalLimbo, NoFinalPath)
55
ImmortalLimbo, NoFinalPath,
57
57
from .filters import filtered_output_bytes, ContentFilterContext
58
58
from .mutabletree import MutableTree
59
59
from .osutils import (
67
67
from .progress import ProgressPhase
68
68
from .sixish import (
457
456
# the file is old; the old id is still valid
458
457
if self._new_root == trans_id:
459
return self._tree.path2id('')
458
return self._tree.get_root_id()
460
459
return self._tree.path2id(path)
462
461
def final_file_id(self, trans_id):
655
654
for trans_id in self._new_id:
656
655
kind = self.final_kind(trans_id)
657
if kind == 'symlink' and not self._tree.supports_symlinks():
658
# Ignore symlinks as they are not supported on this platform
661
657
conflicts.append(('versioning no contents', trans_id))
1004
1000
and from_parent == to_parent and from_name == to_name
1005
1001
and from_executable == to_executable):
1009
file_id, (from_path, to_path), modified,
1010
(from_versioned, to_versioned),
1011
(from_parent, to_parent),
1012
(from_name, to_name),
1013
(from_kind, to_kind),
1014
(from_executable, to_executable)))
1003
results.append((file_id, (from_path, to_path), modified,
1004
(from_versioned, to_versioned),
1005
(from_parent, to_parent),
1006
(from_name, to_name),
1007
(from_kind, to_kind),
1008
(from_executable, to_executable)))
1017
return (c.path[0] or '', c.path[1] or '')
1012
return (paths[0] or '', paths[1] or '')
1018
1013
return iter(sorted(results, key=path_key))
1020
1015
def get_preview_tree(self):
1202
1197
if kind == 'symlink':
1203
1198
self.create_symlink(content.decode('utf-8'), trans_id)
1205
def create_file(self, contents, trans_id, mode_id=None, sha1=None):
1206
"""Schedule creation of a new file.
1210
:param contents: an iterator of strings, all of which will be written
1211
to the target destination.
1212
:param trans_id: TreeTransform handle
1213
:param mode_id: If not None, force the mode of the target file to match
1214
the mode of the object referenced by mode_id.
1215
Otherwise, we will try to preserve mode bits of an existing file.
1216
:param sha1: If the sha1 of this content is already known, pass it in.
1217
We can use it to prevent future sha1 computations.
1219
raise NotImplementedError(self.create_file)
1221
def create_directory(self, trans_id):
1222
"""Schedule creation of a new directory.
1224
See also new_directory.
1226
raise NotImplementedError(self.create_directory)
1228
def create_symlink(self, target, trans_id):
1229
"""Schedule creation of a new symbolic link.
1231
target is a bytestring.
1232
See also new_symlink.
1234
raise NotImplementedError(self.create_symlink)
1236
def create_hardlink(self, path, trans_id):
1237
"""Schedule creation of a hard link"""
1238
raise NotImplementedError(self.create_hardlink)
1240
def cancel_creation(self, trans_id):
1241
"""Cancel the creation of new file contents."""
1242
raise NotImplementedError(self.cancel_creation)
1245
1201
class DiskTreeTransform(TreeTransformBase):
1246
1202
"""Tree transform storing its contents on disk."""
1248
def __init__(self, tree, limbodir, pb=None, case_sensitive=True):
1204
def __init__(self, tree, limbodir, pb=None,
1205
case_sensitive=True):
1249
1206
"""Constructor.
1250
1207
:param tree: The tree that will be transformed, but not necessarily
1251
1208
the output tree.
1269
1226
# List of transform ids that need to be renamed from limbo into place
1270
1227
self._needs_rename = set()
1271
1228
self._creation_mtime = None
1272
self._create_symlinks = osutils.supports_symlinks(self._limbodir)
1274
1230
def finalize(self):
1275
1231
"""Release the working tree lock, if held, clean up limbo dir.
1308
1264
def _limbo_supports_executable(self):
1309
1265
"""Check if the limbo path supports the executable bit."""
1310
return osutils.supports_executable(self._limbodir)
1266
# FIXME: Check actual file system capabilities of limbodir
1267
return osutils.supports_executable()
1312
1269
def _limbo_name(self, trans_id):
1313
1270
"""Generate the limbo name of a file"""
1439
1396
target is a bytestring.
1440
1397
See also new_symlink.
1442
if self._create_symlinks:
1443
1400
os.symlink(target, self._limbo_name(trans_id))
1401
unique_add(self._new_contents, trans_id, 'symlink')
1446
1404
path = FinalPaths(self).get_path(trans_id)
1447
1405
except KeyError:
1450
'Unable to create symlink "%s" on this filesystem.' % (path,))
1451
# We add symlink to _new_contents even if they are unsupported
1452
# and not created. These entries are subsequently used to avoid
1453
# conflicts on platforms that don't support symlink
1454
unique_add(self._new_contents, trans_id, 'symlink')
1407
raise UnableCreateSymlink(path=path)
1456
1409
def cancel_creation(self, trans_id):
1457
1410
"""Cancel the creation of new file contents."""
1824
1777
child_pb.update(gettext('removing file'),
1825
1778
num, total_entries)
1826
1779
if trans_id == self._new_root:
1827
file_id = self._tree.path2id('')
1780
file_id = self._tree.get_root_id()
1829
1782
file_id = self.tree_file_id(trans_id)
1830
1783
# File-id isn't really being deleted, just moved
2033
1986
self._all_children_cache = {}
2034
1987
self._path2trans_id_cache = {}
2035
1988
self._final_name_cache = {}
2036
self._iter_changes_cache = dict((c.file_id, c) for c in
1989
self._iter_changes_cache = dict((c[0], c) for c in
2037
1990
self._transform.iter_changes())
2039
def supports_tree_reference(self):
2040
# TODO(jelmer): Support tree references in _PreviewTree.
2041
# return self._transform._tree.supports_tree_reference()
2044
1992
def _content_change(self, file_id):
2045
1993
"""Return True if the content of this file changed"""
2046
1994
changes = self._iter_changes_cache.get(file_id)
2047
return (changes is not None and changes.changed_content)
1995
# changes[2] is true if the file content changed. See
1996
# InterTree.iter_changes.
1997
return (changes is not None and changes[2])
2049
1999
def _get_repository(self):
2050
2000
repo = getattr(self._transform._tree, '_repository', None)
2106
2056
"""This Tree does not use inventory as its backing data."""
2107
2057
raise NotImplementedError(_PreviewTree.root_inventory)
2059
def get_root_id(self):
2060
return self._transform.final_file_id(self._transform.root)
2109
2062
def all_file_ids(self):
2110
2063
tree_ids = set(self._transform._tree.all_file_ids())
2111
2064
tree_ids.difference_update(self._transform.tree_file_id(t)
2127
2080
return tree_paths
2082
def _has_id(self, file_id, fallback_check):
2083
if file_id in self._transform._r_new_id:
2085
elif file_id in {self._transform.tree_file_id(trans_id) for
2086
trans_id in self._transform._removed_id}:
2089
return fallback_check(file_id)
2091
def has_id(self, file_id):
2092
return self._has_id(file_id, self._transform._tree.has_id)
2094
def has_or_had_id(self, file_id):
2095
return self._has_id(file_id, self._transform._tree.has_or_had_id)
2129
2097
def _path2trans_id(self, path):
2130
2098
# We must not use None here, because that is a valid value to store.
2131
2099
trans_id = self._path2trans_id_cache.get(path, object)
2155
2123
path = osutils.pathjoin(*path)
2156
2124
return self._transform.final_file_id(self._path2trans_id(path))
2158
def id2path(self, file_id, recurse='down'):
2126
def id2path(self, file_id):
2159
2127
trans_id = self._transform.trans_id_file_id(file_id)
2161
2129
return self._final_paths._determine_path(trans_id)
2173
2141
self._all_children_cache[trans_id] = children
2174
2142
return children
2144
def _iter_children(self, file_id):
2145
trans_id = self._transform.trans_id_file_id(file_id)
2146
for child_trans_id in self._all_children(trans_id):
2147
yield self._transform.final_file_id(child_trans_id)
2176
2149
def extras(self):
2177
2150
possible_extras = set(self._transform.trans_id_tree_path(p) for p
2178
2151
in self._transform._tree.extras())
2223
2196
for entry, trans_id in self._make_inv_entries(todo):
2226
def iter_entries_by_dir(self, specific_files=None, recurse_nested=False):
2228
raise NotImplementedError(
2229
'follow tree references not yet supported')
2199
def iter_entries_by_dir(self, specific_files=None):
2231
2200
# This may not be a maximally efficient implementation, but it is
2232
2201
# reasonably straightforward. An implementation that grafts the
2233
2202
# TreeTransform changes onto the tree's iter_entries_by_dir results
2251
2220
path_entries.sort()
2252
2221
return path_entries
2254
def list_files(self, include_root=False, from_dir=None, recursive=True,
2255
recurse_nested=False):
2223
def list_files(self, include_root=False, from_dir=None, recursive=True):
2256
2224
"""See WorkingTree.list_files."""
2258
raise NotImplementedError(
2259
'follow tree references not yet supported')
2261
2225
# XXX This should behave like WorkingTree.list_files, but is really
2262
2226
# more like RevisionTree.list_files.
2263
2227
if from_dir == '.':
2279
2243
if from_dir is None and include_root is True:
2280
2244
root_entry = inventory.make_entry(
2281
'directory', '', ROOT_PARENT, self.path2id(''))
2245
'directory', '', ROOT_PARENT, self.get_root_id())
2282
2246
yield '', 'V', 'directory', root_entry
2283
2247
entries = self._iter_entries_for_dir(from_dir or '')
2284
2248
for path, entry in entries:
2347
2311
with self.get_file(path) as fileobj:
2348
2312
return sha_file(fileobj)
2350
def get_reference_revision(self, path):
2351
trans_id = self._path2trans_id(path)
2352
if trans_id is None:
2353
raise errors.NoSuchFile(path)
2354
reference_revision = self._transform._new_reference_revision.get(trans_id)
2355
if reference_revision is None:
2356
return self._transform._tree.get_reference_revision(path)
2357
return reference_revision
2359
2314
def is_executable(self, path):
2360
2315
trans_id = self._path2trans_id(path)
2361
2316
if trans_id is None:
2453
2408
if changes is None:
2456
changed_content, versioned, kind = (
2457
changes.changed_content, changes.versioned, changes.kind)
2411
changed_content, versioned, kind = (changes[2], changes[3],
2458
2413
if kind[1] is None:
2460
2415
get_old = (kind[0] == 'file' and versioned[0])
2567
2522
return [(self.get_path(t), t) for t in trans_ids]
2525
def topology_sorted_ids(tree):
2526
"""Determine the topological order of the ids in a tree"""
2527
file_ids = list(tree)
2528
file_ids.sort(key=tree.id2path)
2570
2532
def build_tree(tree, wt, accelerator_tree=None, hardlink=False,
2571
2533
delta_from_tree=False):
2572
2534
"""Create working tree for a branch, using a TreeTransform.
2593
2555
:param delta_from_tree: If true, build_tree may use the input Tree to
2594
2556
generate the inventory delta.
2596
with cleanup.ExitStack() as exit_stack:
2597
exit_stack.enter_context(wt.lock_tree_write())
2598
exit_stack.enter_context(tree.lock_read())
2558
with wt.lock_tree_write(), tree.lock_read():
2599
2559
if accelerator_tree is not None:
2600
exit_stack.enter_context(accelerator_tree.lock_read())
2601
return _build_tree(tree, wt, accelerator_tree, hardlink,
2560
accelerator_tree.lock_read()
2562
return _build_tree(tree, wt, accelerator_tree, hardlink,
2565
if accelerator_tree is not None:
2566
accelerator_tree.unlock()
2605
2569
def _build_tree(tree, wt, accelerator_tree, hardlink, delta_from_tree):
2610
2574
file_trans_id = {}
2611
2575
top_pb = ui.ui_factory.nested_progress_bar()
2612
2576
pp = ProgressPhase("Build phase", 2, top_pb)
2613
if tree.path2id('') is not None:
2577
if tree.get_root_id() is not None:
2614
2578
# This is kind of a hack: we should be altering the root
2615
2579
# as part of the regular tree shape diff logic.
2616
2580
# The conditional test here is to avoid doing an
2618
2582
# is set within the tree, nor setting the root and thus
2619
2583
# marking the tree as dirty, because we use two different
2620
2584
# idioms here: tree interfaces and inventory interfaces.
2621
if wt.path2id('') != tree.path2id(''):
2622
wt.set_root_id(tree.path2id(''))
2585
if wt.get_root_id() != tree.get_root_id():
2586
wt.set_root_id(tree.get_root_id())
2624
tt = wt.get_transform()
2588
tt = TreeTransform(wt)
2627
2591
pp.next_phase()
2628
file_trans_id[wt.path2id('')] = tt.trans_id_tree_path('')
2592
file_trans_id[wt.get_root_id()] = tt.trans_id_tree_path('')
2629
2593
with ui.ui_factory.nested_progress_bar() as pb:
2630
2594
deferred_contents = []
2665
2629
divert.add(file_id)
2666
2630
if (file_id not in divert
2667
2631
and _content_match(
2668
tree, entry, tree_path, kind, target_path)):
2632
tree, entry, tree_path, file_id, kind,
2669
2634
tt.delete_contents(tt.trans_id_tree_path(tree_path))
2670
2635
if kind == 'directory':
2671
2636
reparent = True
2723
2688
new_desired_files = desired_files
2725
2690
iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
2727
change.path for change in iter
2728
if not (change.changed_content or change.executable[0] != change.executable[1])]
2691
unchanged = [(p[0], p[1]) for (f, p, c, v, d, n, k, e)
2692
in iter if not (c or e[0] != e[1])]
2729
2693
if accelerator_tree.supports_content_filtering():
2730
2694
unchanged = [(tp, ap) for (tp, ap) in unchanged
2731
2695
if not next(accelerator_tree.iter_search_rules([ap]))]
2774
2738
return by_parent[old_parent]
2777
def _content_match(tree, entry, tree_path, kind, target_path):
2741
def _content_match(tree, entry, tree_path, file_id, kind, target_path):
2778
2742
if entry.kind != kind:
2780
2744
if entry.kind == "directory":
2842
2806
raise errors.BadFileKindError(name, kind)
2845
def create_from_tree(tt, trans_id, tree, path, chunks=None,
2809
def create_from_tree(tt, trans_id, tree, path, file_id=None, chunks=None,
2846
2810
filter_tree_path=None):
2847
2811
"""Create new file contents according to tree contents.
2887
2851
"""Revert a working tree's contents to those of a target tree."""
2888
2852
pb = ui.ui_factory.nested_progress_bar()
2890
with target_tree.lock_read(), working_tree.get_transform(pb) as tt:
2854
with target_tree.lock_read(), TreeTransform(working_tree, pb) as tt:
2891
2855
pp = ProgressPhase("Revert phase", 3, pb)
2892
2856
conflicts, merge_modified = _prepare_revert_transform(
2893
2857
working_tree, target_tree, tt, filenames, backups, pp)
2938
2902
skip_root = False
2940
2904
deferred_files = []
2941
for id_num, change in enumerate(change_list):
2942
file_id = change.file_id
2943
target_path, wt_path = change.path
2944
target_versioned, wt_versioned = change.versioned
2945
target_parent, wt_parent = change.parent_id
2946
target_name, wt_name = change.name
2947
target_kind, wt_kind = change.kind
2948
target_executable, wt_executable = change.executable
2905
for id_num, (file_id, path, changed_content, versioned, parent, name,
2906
kind, executable) in enumerate(change_list):
2907
target_path, wt_path = path
2908
target_versioned, wt_versioned = versioned
2909
target_parent, wt_parent = parent
2910
target_name, wt_name = name
2911
target_kind, wt_kind = kind
2912
target_executable, wt_executable = executable
2949
2913
if skip_root and wt_parent is None:
2951
2915
trans_id = tt.trans_id_file_id(file_id)
2953
if change.changed_content:
2954
2918
keep_content = False
2955
2919
if wt_kind == 'file' and (backups or target_kind is None):
2956
2920
wt_sha1 = working_tree.get_file_sha1(wt_path)
2957
if merge_modified.get(wt_path) != wt_sha1:
2921
if merge_modified.get(file_id) != wt_sha1:
2958
2922
# acquire the basis tree lazily to prevent the
2959
2923
# expense of accessing it when it's not needed ?
2960
2924
# (Guessing, RBC, 200702)
2961
2925
if basis_tree is None:
2962
2926
basis_tree = working_tree.basis_tree()
2963
2927
basis_tree.lock_read()
2964
basis_inter = InterTree.get(basis_tree, working_tree)
2965
basis_path = basis_inter.find_source_path(wt_path)
2928
basis_path = find_previous_path(
2929
working_tree, basis_tree, wt_path)
2966
2930
if basis_path is None:
2967
2931
if target_kind is None and not target_versioned:
2968
2932
keep_content = True
3001
2965
basis_tree = working_tree.basis_tree()
3002
2966
basis_tree.lock_read()
3003
2967
new_sha1 = target_tree.get_file_sha1(target_path)
3004
basis_inter = InterTree.get(basis_tree, target_tree)
3005
basis_path = basis_inter.find_source_path(target_path)
2968
basis_path = find_previous_path(target_tree, basis_tree, target_path)
3006
2969
if (basis_path is not None and
3007
2970
new_sha1 == basis_tree.get_file_sha1(basis_path)):
3008
# If the new contents of the file match what is in basis,
3009
# then there is no need to store in merge_modified.
3010
if basis_path in merge_modified:
3011
del merge_modified[basis_path]
2971
if file_id in merge_modified:
2972
del merge_modified[file_id]
3013
merge_modified[target_path] = new_sha1
2974
merge_modified[file_id] = new_sha1
3015
2976
# preserve the execute bit when backing up
3016
2977
if keep_content and wt_executable == target_executable:
3263
3224
:param target_tree: Tree to change
3264
3225
:param source_tree: Tree to hard-link from
3266
with target_tree.get_transform() as tt:
3267
for change in target_tree.iter_changes(source_tree, include_unchanged=True):
3268
if change.changed_content:
3270
if change.kind != ('file', 'file'):
3272
if change.executable[0] != change.executable[1]:
3274
trans_id = tt.trans_id_tree_path(change.path[1])
3227
tt = TreeTransform(target_tree)
3229
for (file_id, paths, changed_content, versioned, parent, name, kind,
3230
executable) in target_tree.iter_changes(source_tree,
3231
include_unchanged=True):
3234
if kind != ('file', 'file'):
3236
if executable[0] != executable[1]:
3238
trans_id = tt.trans_id_tree_path(paths[1])
3275
3239
tt.delete_contents(trans_id)
3276
tt.create_hardlink(source_tree.abspath(change.path[0]), trans_id)
3240
tt.create_hardlink(source_tree.abspath(paths[0]), trans_id)