90
93
class TreeTransformBase(object):
91
94
"""The base class for TreeTransform and its kin."""
93
def __init__(self, tree, pb=None,
96
def __init__(self, tree, pb=None, case_sensitive=True):
97
99
:param tree: The tree that will be transformed, but not necessarily
132
134
# The trans_id that will be used as the tree root
133
135
root_id = tree.get_root_id()
134
136
if root_id is not None:
135
self._new_root = self.trans_id_tree_file_id(root_id)
137
self._new_root = self.trans_id_tree_path('')
137
139
self._new_root = None
138
140
# Indicator of whether the transform has been applied
216
218
# the physical root needs a new transaction id
217
219
self._tree_path_ids.pop("")
218
220
self._tree_id_paths.pop(old_root)
219
self._new_root = self.trans_id_tree_file_id(self._tree.get_root_id())
221
self._new_root = self.trans_id_tree_path('')
220
222
if parent == old_root:
221
223
parent = self._new_root
222
224
self.adjust_path(name, parent, old_root)
285
287
del self._new_parent[old_new_root]
286
288
del self._new_name[old_new_root]
288
def trans_id_tree_file_id(self, inventory_id):
289
"""Determine the transaction id of a working tree file.
291
This reflects only files that already exist, not ones that will be
292
added by transactions.
294
if inventory_id is None:
295
raise ValueError('None is not a valid file id')
296
path = self._tree.id2path(inventory_id)
297
return self.trans_id_tree_path(path)
299
290
def trans_id_file_id(self, file_id):
300
291
"""Determine or set the transaction id associated with a file ID.
301
292
A new id is only created for file_ids that were never present. If
308
299
return self._r_new_id[file_id]
311
next(self._tree.iter_entries_by_dir([file_id]))
312
except StopIteration:
302
path = self._tree.id2path(file_id)
303
except errors.NoSuchId:
313
304
if file_id in self._non_present_ids:
314
305
return self._non_present_ids[file_id]
317
308
self._non_present_ids[file_id] = trans_id
320
return self.trans_id_tree_file_id(file_id)
311
return self.trans_id_tree_path(path)
322
313
def trans_id_tree_path(self, path):
323
314
"""Determine (and maybe set) the transaction ID for a tree path."""
455
446
return self.tree_kind(trans_id)
448
def tree_path(self, trans_id):
449
"""Determine the tree path associated with the trans_id."""
450
return self._tree_id_paths.get(trans_id)
457
452
def tree_file_id(self, trans_id):
458
453
"""Determine the file id associated with the trans_id in the tree"""
460
path = self._tree_id_paths[trans_id]
462
# the file is a new, unversioned file, or invalid trans_id
454
path = self.tree_path(trans_id)
464
457
# the file is old; the old id is still valid
465
458
if self._new_root == trans_id:
567
560
parents.extend([t for t in self._removed_contents if
568
561
self.tree_kind(t) == 'directory'])
569
562
for trans_id in self._removed_id:
570
file_id = self.tree_file_id(trans_id)
571
path = self._tree.id2path(file_id)
572
if file_id is not None:
573
if self._tree.stored_kind(path, file_id) == 'directory':
563
path = self.tree_path(trans_id)
565
if self._tree.stored_kind(path) == 'directory':
574
566
parents.append(trans_id)
575
567
elif self.tree_kind(trans_id) == 'directory':
576
568
parents.append(trans_id)
735
727
active_tree_ids = all_ids.difference(removed_tree_ids)
736
728
for trans_id, file_id in viewitems(self._new_id):
737
729
if file_id in active_tree_ids:
738
old_trans_id = self.trans_id_tree_file_id(file_id)
730
path = self._tree.id2path(file_id)
731
old_trans_id = self.trans_id_tree_path(path)
739
732
conflicts.append(('duplicate id', old_trans_id, trans_id))
901
894
to_trans_ids[to_file_id] = trans_id
902
895
return from_trans_ids, to_trans_ids
904
def _from_file_data(self, from_trans_id, from_versioned, file_id):
897
def _from_file_data(self, from_trans_id, from_versioned, from_path):
905
898
"""Get data about a file in the from (tree) state
907
900
Return a (name, parent, kind, executable) tuple
909
902
from_path = self._tree_id_paths.get(from_trans_id)
910
903
if from_versioned:
911
904
# get data from working tree if versioned
912
from_entry = self._tree.iter_entries_by_dir([file_id]).next()[1]
905
from_entry = self._tree.iter_entries_by_dir(
906
specific_files=[from_path]).next()[1]
913
907
from_name = from_entry.name
914
908
from_parent = from_entry.parent_id
979
973
to_versioned = True
981
from_name, from_parent, from_kind, from_executable = \
982
self._from_file_data(from_trans_id, from_versioned, file_id)
984
to_name, to_parent, to_kind, to_executable = \
985
self._to_file_data(to_trans_id, from_trans_id, from_executable)
987
975
if not from_versioned:
994
982
to_path = final_paths.get_path(to_trans_id)
984
from_name, from_parent, from_kind, from_executable = \
985
self._from_file_data(from_trans_id, from_versioned, from_path)
987
to_name, to_parent, to_kind, to_executable = \
988
self._to_file_data(to_trans_id, from_trans_id, from_executable)
995
990
if from_kind != to_kind:
997
992
elif to_kind in ('file', 'symlink') and (
1077
1072
return revision_id
1079
1074
def _text_parent(self, trans_id):
1080
file_id = self.tree_file_id(trans_id)
1075
path = self.tree_path(trans_id)
1082
if (file_id is None or
1083
self._tree.kind(self._tree.id2path(file_id), file_id) != 'file'):
1077
if path is None or self._tree.kind(path) != 'file':
1085
1079
except errors.NoSuchFile:
1089
1083
def _get_parents_texts(self, trans_id):
1090
1084
"""Get texts for compression parents of this file."""
1091
file_id = self._text_parent(trans_id)
1085
path = self._text_parent(trans_id)
1094
return (self._tree.get_file_text(self._tree.id2path(file_id), file_id),)
1088
return (self._tree.get_file_text(path),)
1096
1090
def _get_parents_lines(self, trans_id):
1097
1091
"""Get lines for compression parents of this file."""
1098
file_id = self._text_parent(trans_id)
1092
path = self._text_parent(trans_id)
1101
return (self._tree.get_file_lines(self._tree.id2path(file_id), file_id),)
1095
return (self._tree.get_file_lines(path),)
1103
1097
def serialize(self, serializer):
1104
1098
"""Serialize this TreeTransform.
1408
1402
def new_orphan(self, trans_id, parent_id):
1409
1403
conf = self._tree.get_config_stack()
1410
handle_orphan = conf.get('bzr.transform.orphan_policy')
1404
handle_orphan = conf.get('transform.orphan_policy')
1411
1405
handle_orphan(self, trans_id, parent_id)
1479
1473
opt_transform_orphan = _mod_config.RegistryOption(
1480
'bzr.transform.orphan_policy', orphaning_registry,
1474
'transform.orphan_policy', orphaning_registry,
1481
1475
help='Policy for orphaned files during transform operations.',
1482
1476
invalid='warning')
1771
1765
inventory_delta.append((path, None, file_id, None))
1772
1766
new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1774
entries = self._tree.iter_entries_by_dir(
1775
viewvalues(new_path_file_ids))
1776
old_paths = dict((e.file_id, p) for p, e in entries)
1777
1768
final_kinds = {}
1778
1769
for num, (path, trans_id) in enumerate(new_paths):
1779
1770
if (num % 10) == 0:
1801
1792
new_entry = inventory.make_entry(kind,
1802
1793
self.final_name(trans_id),
1803
1794
parent_file_id, file_id)
1804
old_path = old_paths.get(new_entry.file_id)
1796
old_path = self._tree.id2path(new_entry.file_id)
1797
except errors.NoSuchId:
1805
1799
new_executability = self._new_executability.get(trans_id)
1806
1800
if new_executability is not None:
1807
1801
new_entry.executable = new_executability
1952
1946
path = self._tree_id_paths[parent_id]
1953
1947
except KeyError:
1955
file_id = self.tree_file_id(parent_id)
1958
entry = self._tree.iter_entries_by_dir([file_id]).next()[1]
1949
entry = self._tree.iter_entries_by_dir(
1950
specific_files=[path]).next()[1]
1959
1951
children = getattr(entry, 'children', {})
1960
1952
for child in children:
1961
1953
childpath = joinpath(path, child)
2011
2003
vf.fallback_versionedfiles.append(base_vf)
2012
2004
return tree_revision
2014
def _stat_limbo_file(self, file_id=None, trans_id=None):
2015
if trans_id is None:
2016
trans_id = self._transform.trans_id_file_id(file_id)
2006
def _stat_limbo_file(self, trans_id):
2017
2007
name = self._transform._limbo_name(trans_id)
2018
2008
return os.lstat(name)
2123
2113
self._all_children_cache[trans_id] = children
2124
2114
return children
2126
def iter_children(self, file_id):
2116
def _iter_children(self, file_id):
2127
2117
trans_id = self._transform.trans_id_file_id(file_id)
2128
2118
for child_trans_id in self._all_children(trans_id):
2129
2119
yield self._transform.final_file_id(child_trans_id)
2137
2127
if self._transform.final_file_id(trans_id) is None:
2138
2128
yield self._final_paths._determine_path(trans_id)
2140
def _make_inv_entries(self, ordered_entries, specific_file_ids=None,
2130
def _make_inv_entries(self, ordered_entries, specific_files=None,
2141
2131
yield_parents=False):
2142
2132
for trans_id, parent_file_id in ordered_entries:
2143
2133
file_id = self._transform.final_file_id(trans_id)
2144
2134
if file_id is None:
2146
if (specific_file_ids is not None
2147
and file_id not in specific_file_ids):
2136
if (specific_files is not None and
2137
unicode(self._final_paths.get_path(trans_id)) not in specific_files):
2149
2139
kind = self._transform.final_kind(trans_id)
2150
2140
if kind is None:
2172
2162
return ordered_ids
2174
2164
def iter_child_entries(self, path, file_id=None):
2176
file_id = self.path2id(path)
2165
trans_id = self._path2trans_id(path)
2166
if trans_id is None:
2178
2167
raise errors.NoSuchFile(path)
2179
trans_id = self._transform.trans_id_file_id(file_id)
2180
2168
todo = [(child_trans_id, trans_id) for child_trans_id in
2181
2169
self._all_children(trans_id)]
2182
2170
for entry, trans_id in self._make_inv_entries(todo):
2185
def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
2173
def iter_entries_by_dir(self, specific_files=None, yield_parents=False):
2186
2174
# This may not be a maximally efficient implementation, but it is
2187
2175
# reasonably straightforward. An implementation that grafts the
2188
2176
# TreeTransform changes onto the tree's iter_entries_by_dir results
2191
2179
ordered_ids = self._list_files_by_dir()
2192
2180
for entry, trans_id in self._make_inv_entries(ordered_ids,
2193
specific_file_ids, yield_parents=yield_parents):
2181
specific_files, yield_parents=yield_parents):
2194
2182
yield unicode(self._final_paths.get_path(trans_id)), entry
2196
2184
def _iter_entries_for_dir(self, dir_path):
2197
2185
"""Return path, entry for items in a directory without recursing down."""
2198
dir_file_id = self.path2id(dir_path)
2199
2186
ordered_ids = []
2200
for file_id in self.iter_children(dir_file_id):
2201
trans_id = self._transform.trans_id_file_id(file_id)
2202
ordered_ids.append((trans_id, file_id))
2187
dir_trans_id = self._path2trans_id(dir_path)
2188
dir_id = self._transform.final_file_id(dir_trans_id)
2189
for child_trans_id in self._all_children(dir_trans_id):
2190
ordered_ids.append((child_trans_id, dir_id))
2203
2191
for entry, trans_id in self._make_inv_entries(ordered_ids):
2204
2192
yield unicode(self._final_paths.get_path(trans_id)), entry
2230
2218
yield path, 'V', entry.kind, entry.file_id, entry
2232
2220
def kind(self, path, file_id=None):
2234
file_id = self.path2id(path)
2235
trans_id = self._transform.trans_id_file_id(file_id)
2221
trans_id = self._path2trans_id(path)
2222
if trans_id is None:
2223
raise errors.NoSuchFile(path)
2236
2224
return self._transform.final_kind(trans_id)
2238
2226
def stored_kind(self, path, file_id=None):
2240
file_id = self.path2id(path)
2241
trans_id = self._transform.trans_id_file_id(file_id)
2227
trans_id = self._path2trans_id(path)
2228
if trans_id is None:
2229
raise errors.NoSuchFile(path)
2243
2231
return self._transform._new_contents[trans_id]
2244
2232
except KeyError:
2253
2241
if not self._content_change(file_id):
2254
2242
return self._transform._tree.get_file_mtime(
2255
2243
self._transform._tree.id2path(file_id), file_id)
2256
return self._stat_limbo_file(file_id).st_mtime
2258
def _file_size(self, entry, stat_value):
2259
path = self.id2path(entry.file_id)
2260
return self.get_file_size(path, entry.file_id)
2244
trans_id = self._path2trans_id(path)
2245
return self._stat_limbo_file(trans_id).st_mtime
2262
2247
def get_file_size(self, path, file_id=None):
2263
2248
"""See Tree.get_file_size"""
2265
file_id = self.path2id(path)
2266
trans_id = self._transform.trans_id_file_id(file_id)
2249
trans_id = self._path2trans_id(path)
2250
if trans_id is None:
2251
raise errors.NoSuchFile(path)
2267
2252
kind = self._transform.final_kind(trans_id)
2268
2253
if kind != 'file':
2270
2255
if trans_id in self._transform._new_contents:
2271
return self._stat_limbo_file(trans_id=trans_id).st_size
2256
return self._stat_limbo_file(trans_id).st_size
2272
2257
if self.kind(path, file_id) == 'file':
2273
2258
return self._transform._tree.get_file_size(path, file_id)
2277
2262
def get_file_verifier(self, path, file_id=None, stat_value=None):
2279
file_id = self.path2id(path)
2280
trans_id = self._transform.trans_id_file_id(file_id)
2263
trans_id = self._path2trans_id(path)
2264
if trans_id is None:
2265
raise errors.NoSuchFile(path)
2281
2266
kind = self._transform._new_contents.get(trans_id)
2282
2267
if kind is None:
2283
2268
return self._transform._tree.get_file_verifier(path, file_id)
2289
2274
fileobj.close()
2291
2276
def get_file_sha1(self, path, file_id=None, stat_value=None):
2293
file_id = self.path2id(path)
2294
trans_id = self._transform.trans_id_file_id(file_id)
2277
trans_id = self._path2trans_id(path)
2278
if trans_id is None:
2279
raise errors.NoSuchFile(path)
2295
2280
kind = self._transform._new_contents.get(trans_id)
2296
2281
if kind is None:
2297
2282
return self._transform._tree.get_file_sha1(path, file_id)
2303
2288
fileobj.close()
2305
2290
def is_executable(self, path, file_id=None):
2307
file_id = self.path2id(path)
2291
trans_id = self._path2trans_id(path)
2292
if trans_id is None:
2310
trans_id = self._transform.trans_id_file_id(file_id)
2312
2295
return self._transform._new_executability[trans_id]
2313
2296
except KeyError:
2386
2369
file_id = self.path2id(path)
2387
2370
if not self._content_change(file_id):
2388
2371
return self._transform._tree.get_file(path, file_id)
2389
trans_id = self._transform.trans_id_file_id(file_id)
2372
trans_id = self._path2trans_id(path)
2390
2373
name = self._transform._limbo_name(trans_id)
2391
2374
return open(name, 'rb')
2432
2415
file_id = self.path2id(path)
2433
2416
if not self._content_change(file_id):
2434
2417
return self._transform._tree.get_symlink_target(path)
2435
trans_id = self._transform.trans_id_file_id(file_id)
2418
trans_id = self._path2trans_id(path)
2436
2419
name = self._transform._limbo_name(trans_id)
2437
2420
return osutils.readlink(name)
2624
2606
divert.add(file_id)
2625
2607
if (file_id not in divert and
2626
_content_match(tree, entry, file_id, kind,
2608
_content_match(tree, entry, tree_path, file_id, kind,
2628
2610
tt.delete_contents(tt.trans_id_tree_path(tree_path))
2629
2611
if kind == 'directory':
2735
2717
return by_parent[old_parent]
2738
def _content_match(tree, entry, file_id, kind, target_path):
2720
def _content_match(tree, entry, tree_path, file_id, kind, target_path):
2739
2721
if entry.kind != kind:
2741
2723
if entry.kind == "directory":
2743
path = tree.id2path(file_id)
2744
2725
if entry.kind == "file":
2745
2726
f = file(target_path, 'rb')
2747
if tree.get_file_text(path, file_id) == f.read():
2728
if tree.get_file_text(tree_path, file_id) == f.read():
2751
2732
elif entry.kind == "symlink":
2752
if tree.get_symlink_target(path, file_id) == os.readlink(target_path):
2733
if tree.get_symlink_target(tree_path, file_id) == os.readlink(target_path):
2925
2904
if basis_tree is None:
2926
2905
basis_tree = working_tree.basis_tree()
2927
2906
basis_tree.lock_read()
2929
basis_path = basis_tree.id2path(file_id)
2930
except errors.NoSuchId:
2907
basis_path = find_previous_path(working_tree, basis_tree, wt_path)
2908
if basis_path is None:
2931
2909
if target_kind is None and not target_versioned:
2932
2910
keep_content = True
2964
2942
basis_tree = working_tree.basis_tree()
2965
2943
basis_tree.lock_read()
2966
2944
new_sha1 = target_tree.get_file_sha1(target_path, file_id)
2968
basis_path = basis_tree.id2path(file_id)
2969
except errors.NoSuchId:
2945
basis_path = find_previous_path(target_tree, basis_tree, target_path)
2971
2946
if (basis_path is not None and
2972
2947
new_sha1 == basis_tree.get_file_sha1(basis_path, file_id)):
2973
2948
if file_id in merge_modified:
3101
3076
if file_id is None:
3102
3077
file_id = tt.inactive_file_id(trans_id)
3103
3078
_, entry = next(path_tree.iter_entries_by_dir(
3079
specific_files=[path_tree.id2path(file_id)]))
3105
3080
# special-case the other tree root (move its
3106
3081
# children to current root)
3107
3082
if entry.parent_id is None:
3122
3097
elif c_type == 'unversioned parent':
3123
3098
file_id = tt.inactive_file_id(conflict[1])
3124
3099
# special-case the other tree root (move its children instead)
3125
if path_tree and path_tree.has_id(file_id):
3126
if path_tree.path2id('') == file_id:
3100
if path_tree and path_tree.path2id('') == file_id:
3127
3101
# This is the root entry, skip it
3129
3103
tt.version_file(file_id, conflict[1])
3238
3212
if executable[0] != executable[1]:
3240
trans_id = tt.trans_id_tree_file_id(file_id)
3214
trans_id = tt.trans_id_tree_path(paths[1])
3241
3215
tt.delete_contents(trans_id)
3242
tt.create_hardlink(source_tree.id2abspath(file_id), trans_id)
3216
tt.create_hardlink(source_tree.abspath(paths[0]), trans_id)