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 (
73
72
from .tree import (
79
77
ROOT_PARENT = "root-parent"
82
79
def unique_add(map, key, value):
84
81
raise DuplicateKey(key=key)
88
86
class _TransformResults(object):
89
87
def __init__(self, modified_paths, rename_count):
90
88
object.__init__(self)
997
990
if from_kind != to_kind:
999
992
elif to_kind in ('file', 'symlink') and (
1000
to_trans_id != from_trans_id
1001
or to_trans_id in self._new_contents):
993
to_trans_id != from_trans_id or
994
to_trans_id in self._new_contents):
1003
if (not modified and from_versioned == to_versioned
1004
and from_parent == to_parent and from_name == to_name
1005
and from_executable == to_executable):
996
if (not modified and from_versioned == to_versioned and
997
from_parent==to_parent and from_name == to_name and
998
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)))
1017
return (c.path[0] or '', c.path[1] or '')
1018
return iter(sorted(results, key=path_key))
1000
results.append((file_id, (from_path, to_path), modified,
1001
(from_versioned, to_versioned),
1002
(from_parent, to_parent),
1003
(from_name, to_name),
1004
(from_kind, to_kind),
1005
(from_executable, to_executable)))
1006
return iter(sorted(results, key=lambda x:x[1]))
1020
1008
def get_preview_tree(self):
1021
1009
"""Return a tree representing the result of the transform.
1112
1100
:param serializer: A Serialiser like pack.ContainerSerializer.
1114
new_name = {k.encode('utf-8'): v.encode('utf-8')
1115
for k, v in viewitems(self._new_name)}
1116
new_parent = {k.encode('utf-8'): v.encode('utf-8')
1117
for k, v in viewitems(self._new_parent)}
1118
new_id = {k.encode('utf-8'): v
1119
for k, v in viewitems(self._new_id)}
1120
new_executability = {k.encode('utf-8'): int(v)
1121
for k, v in viewitems(self._new_executability)}
1122
tree_path_ids = {k.encode('utf-8'): v.encode('utf-8')
1123
for k, v in viewitems(self._tree_path_ids)}
1124
non_present_ids = {k: v.encode('utf-8')
1125
for k, v in viewitems(self._non_present_ids)}
1126
removed_contents = [trans_id.encode('utf-8')
1127
for trans_id in self._removed_contents]
1128
removed_id = [trans_id.encode('utf-8')
1129
for trans_id in self._removed_id]
1102
new_name = dict((k, v.encode('utf-8')) for k, v in
1103
viewitems(self._new_name))
1104
new_executability = dict((k, int(v)) for k, v in
1105
viewitems(self._new_executability))
1106
tree_path_ids = dict((k.encode('utf-8'), v)
1107
for k, v in viewitems(self._tree_path_ids))
1131
b'_id_number': self._id_number,
1132
b'_new_name': new_name,
1133
b'_new_parent': new_parent,
1134
b'_new_executability': new_executability,
1136
b'_tree_path_ids': tree_path_ids,
1137
b'_removed_id': removed_id,
1138
b'_removed_contents': removed_contents,
1139
b'_non_present_ids': non_present_ids,
1109
'_id_number': self._id_number,
1110
'_new_name': new_name,
1111
'_new_parent': self._new_parent,
1112
'_new_executability': new_executability,
1113
'_new_id': self._new_id,
1114
'_tree_path_ids': tree_path_ids,
1115
'_removed_id': list(self._removed_id),
1116
'_removed_contents': list(self._removed_contents),
1117
'_non_present_ids': self._non_present_ids,
1141
1119
yield serializer.bytes_record(bencode.bencode(attribs),
1143
for trans_id, kind in sorted(viewitems(self._new_contents)):
1121
for trans_id, kind in viewitems(self._new_contents):
1144
1122
if kind == 'file':
1145
with open(self._limbo_name(trans_id), 'rb') as cur_file:
1146
lines = cur_file.readlines()
1123
lines = osutils.chunks_to_lines(
1124
self._read_file_chunks(trans_id))
1147
1125
parents = self._get_parents_lines(trans_id)
1148
1126
mpdiff = multiparent.MultiParent.from_lines(lines, parents)
1149
content = b''.join(mpdiff.to_patch())
1127
content = ''.join(mpdiff.to_patch())
1150
1128
if kind == 'directory':
1152
1130
if kind == 'symlink':
1153
1131
content = self._read_symlink_target(trans_id)
1154
if not isinstance(content, bytes):
1155
content = content.encode('utf-8')
1156
yield serializer.bytes_record(
1157
content, ((trans_id.encode('utf-8'), kind.encode('ascii')),))
1132
yield serializer.bytes_record(content, ((trans_id, kind),))
1159
1134
def deserialize(self, records):
1160
1135
"""Deserialize a stored TreeTransform.
1165
1140
names, content = next(records)
1166
1141
attribs = bencode.bdecode(content)
1167
self._id_number = attribs[b'_id_number']
1168
self._new_name = {k.decode('utf-8'): v.decode('utf-8')
1169
for k, v in viewitems(attribs[b'_new_name'])}
1170
self._new_parent = {k.decode('utf-8'): v.decode('utf-8')
1171
for k, v in viewitems(attribs[b'_new_parent'])}
1172
self._new_executability = {
1173
k.decode('utf-8'): bool(v)
1174
for k, v in viewitems(attribs[b'_new_executability'])}
1175
self._new_id = {k.decode('utf-8'): v
1176
for k, v in viewitems(attribs[b'_new_id'])}
1177
self._r_new_id = {v: k for k, v in viewitems(self._new_id)}
1142
self._id_number = attribs['_id_number']
1143
self._new_name = dict((k, v.decode('utf-8'))
1144
for k, v in viewitems(attribs['_new_name']))
1145
self._new_parent = attribs['_new_parent']
1146
self._new_executability = dict((k, bool(v))
1147
for k, v in viewitems(attribs['_new_executability']))
1148
self._new_id = attribs['_new_id']
1149
self._r_new_id = dict((v, k) for k, v in viewitems(self._new_id))
1178
1150
self._tree_path_ids = {}
1179
1151
self._tree_id_paths = {}
1180
for bytepath, trans_id in viewitems(attribs[b'_tree_path_ids']):
1152
for bytepath, trans_id in viewitems(attribs['_tree_path_ids']):
1181
1153
path = bytepath.decode('utf-8')
1182
trans_id = trans_id.decode('utf-8')
1183
1154
self._tree_path_ids[path] = trans_id
1184
1155
self._tree_id_paths[trans_id] = path
1185
self._removed_id = {trans_id.decode('utf-8')
1186
for trans_id in attribs[b'_removed_id']}
1187
self._removed_contents = set(
1188
trans_id.decode('utf-8')
1189
for trans_id in attribs[b'_removed_contents'])
1190
self._non_present_ids = {
1191
k: v.decode('utf-8')
1192
for k, v in viewitems(attribs[b'_non_present_ids'])}
1156
self._removed_id = set(attribs['_removed_id'])
1157
self._removed_contents = set(attribs['_removed_contents'])
1158
self._non_present_ids = attribs['_non_present_ids']
1193
1159
for ((trans_id, kind),), content in records:
1194
trans_id = trans_id.decode('utf-8')
1195
kind = kind.decode('ascii')
1196
1160
if kind == 'file':
1197
1161
mpdiff = multiparent.MultiParent.from_patch(content)
1198
1162
lines = mpdiff.to_lines(self._get_parents_texts(trans_id))
1202
1166
if kind == 'symlink':
1203
1167
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
1170
class DiskTreeTransform(TreeTransformBase):
1246
1171
"""Tree transform storing its contents on disk."""
1248
def __init__(self, tree, limbodir, pb=None, case_sensitive=True):
1173
def __init__(self, tree, limbodir, pb=None,
1174
case_sensitive=True):
1249
1175
"""Constructor.
1250
1176
:param tree: The tree that will be transformed, but not necessarily
1251
1177
the output tree.
1330
1256
previous_parent = self._new_parent.get(trans_id)
1331
1257
previous_name = self._new_name.get(trans_id)
1332
1258
TreeTransformBase.adjust_path(self, name, parent, trans_id)
1333
if (trans_id in self._limbo_files
1334
and trans_id not in self._needs_rename):
1259
if (trans_id in self._limbo_files and
1260
trans_id not in self._needs_rename):
1335
1261
self._rename_in_limbo([trans_id])
1336
1262
if previous_parent != parent:
1337
1263
self._limbo_children[previous_parent].remove(trans_id)
1439
1375
target is a bytestring.
1440
1376
See also new_symlink.
1442
if self._create_symlinks:
1443
1379
os.symlink(target, self._limbo_name(trans_id))
1380
unique_add(self._new_contents, trans_id, 'symlink')
1446
1383
path = FinalPaths(self).get_path(trans_id)
1447
1384
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')
1386
raise UnableCreateSymlink(path=path)
1456
1388
def cancel_creation(self, trans_id):
1457
1389
"""Cancel the creation of new file contents."""
1531
1463
orphaning_registry = registry.Registry()
1532
1464
orphaning_registry.register(
1533
u'conflict', refuse_orphan,
1465
'conflict', refuse_orphan,
1534
1466
'Leave orphans in place and create a conflict on the directory.')
1535
1467
orphaning_registry.register(
1536
u'move', move_orphan,
1468
'move', move_orphan,
1537
1469
'Move orphans into the brz-orphans directory.')
1538
orphaning_registry._set_default_key(u'conflict')
1470
orphaning_registry._set_default_key('conflict')
1541
1473
opt_transform_orphan = _mod_config.RegistryOption(
1821
1753
with ui.ui_factory.nested_progress_bar() as child_pb:
1822
1754
for num, trans_id in enumerate(self._removed_id):
1823
1755
if (num % 10) == 0:
1824
child_pb.update(gettext('removing file'),
1756
child_pb.update(gettext('removing file'), num, total_entries)
1826
1757
if trans_id == self._new_root:
1827
file_id = self._tree.path2id('')
1758
file_id = self._tree.get_root_id()
1829
1760
file_id = self.tree_file_id(trans_id)
1830
1761
# File-id isn't really being deleted, just moved
1912
1846
new_paths = self.new_paths(filesystem_only=True)
1913
1847
modified_paths = []
1848
new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1914
1850
with ui.ui_factory.nested_progress_bar() as child_pb:
1915
1851
for num, (path, trans_id) in enumerate(new_paths):
1916
1852
if (num % 10) == 0:
1917
child_pb.update(gettext('adding file'),
1918
num, len(new_paths))
1853
child_pb.update(gettext('adding file'), num, len(new_paths))
1919
1854
full_path = self._tree.abspath(path)
1920
1855
if trans_id in self._needs_rename:
2033
1968
self._all_children_cache = {}
2034
1969
self._path2trans_id_cache = {}
2035
1970
self._final_name_cache = {}
2036
self._iter_changes_cache = dict((c.file_id, c) for c in
1971
self._iter_changes_cache = dict((c[0], c) for c in
2037
1972
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
1974
def _content_change(self, file_id):
2045
1975
"""Return True if the content of this file changed"""
2046
1976
changes = self._iter_changes_cache.get(file_id)
2047
return (changes is not None and changes.changed_content)
1977
# changes[2] is true if the file content changed. See
1978
# InterTree.iter_changes.
1979
return (changes is not None and changes[2])
2049
1981
def _get_repository(self):
2050
1982
repo = getattr(self._transform._tree, '_repository', None)
2062
1994
def _get_file_revision(self, path, file_id, vf, tree_revision):
2063
1995
parent_keys = [
2064
(file_id, t.get_file_revision(t.id2path(file_id)))
2065
for t in self._iter_parent_trees()]
1996
(file_id, t.get_file_revision(t.id2path(file_id), file_id))
1997
for t in self._iter_parent_trees()]
2066
1998
vf.add_lines((file_id, tree_revision), parent_keys,
2067
self.get_file_lines(path))
1999
self.get_file_lines(path, file_id))
2068
2000
repo = self._get_repository()
2069
2001
base_vf = repo.texts
2070
2002
if base_vf not in vf.fallback_versionedfiles:
2114
2049
return tree_ids
2116
2051
def all_versioned_paths(self):
2117
tree_paths = set(self._transform._tree.all_versioned_paths())
2119
tree_paths.difference_update(
2120
self._transform.trans_id_tree_path(t)
2121
for t in self._transform._removed_id)
2124
self._final_paths._determine_path(t)
2125
for t in self._transform._new_id)
2052
return {self.id2path(fid) for fid in self.all_file_ids()}
2054
def _has_id(self, file_id, fallback_check):
2055
if file_id in self._transform._r_new_id:
2057
elif file_id in {self._transform.tree_file_id(trans_id) for
2058
trans_id in self._transform._removed_id}:
2061
return fallback_check(file_id)
2063
def has_id(self, file_id):
2064
return self._has_id(file_id, self._transform._tree.has_id)
2066
def has_or_had_id(self, file_id):
2067
return self._has_id(file_id, self._transform._tree.has_or_had_id)
2129
2069
def _path2trans_id(self, path):
2130
2070
# We must not use None here, because that is a valid value to store.
2187
2132
file_id = self._transform.final_file_id(trans_id)
2188
2133
if file_id is None:
2190
if (specific_files is not None
2191
and self._final_paths.get_path(trans_id) not in specific_files):
2135
if (specific_files is not None and
2136
unicode(self._final_paths.get_path(trans_id)) not in specific_files):
2193
2138
kind = self._transform.final_kind(trans_id)
2194
2139
if kind is None:
2195
2140
kind = self._transform._tree.stored_kind(
2196
self._transform._tree.id2path(file_id))
2141
self._transform._tree.id2path(file_id),
2197
2143
new_entry = inventory.make_entry(
2199
2145
self._transform.final_name(trans_id),
2245
2187
dir_id = self._transform.final_file_id(dir_trans_id)
2246
2188
for child_trans_id in self._all_children(dir_trans_id):
2247
2189
ordered_ids.append((child_trans_id, dir_id))
2249
2190
for entry, trans_id in self._make_inv_entries(ordered_ids):
2250
path_entries.append((self._final_paths.get_path(trans_id), entry))
2191
yield unicode(self._final_paths.get_path(trans_id)), entry
2254
def list_files(self, include_root=False, from_dir=None, recursive=True,
2255
recurse_nested=False):
2193
def list_files(self, include_root=False, from_dir=None, recursive=True):
2256
2194
"""See WorkingTree.list_files."""
2258
raise NotImplementedError(
2259
'follow tree references not yet supported')
2261
2195
# XXX This should behave like WorkingTree.list_files, but is really
2262
2196
# more like RevisionTree.list_files.
2274
2206
if not path.startswith(prefix):
2276
2208
path = path[len(prefix):]
2277
yield path, 'V', entry.kind, entry
2209
yield path, 'V', entry.kind, entry.file_id, entry
2279
2211
if from_dir is None and include_root is True:
2280
root_entry = inventory.make_entry(
2281
'directory', '', ROOT_PARENT, self.path2id(''))
2282
yield '', 'V', 'directory', root_entry
2212
root_entry = inventory.make_entry('directory', '',
2213
ROOT_PARENT, self.get_root_id())
2214
yield '', 'V', 'directory', root_entry.file_id, root_entry
2283
2215
entries = self._iter_entries_for_dir(from_dir or '')
2284
2216
for path, entry in entries:
2285
yield path, 'V', entry.kind, entry
2217
yield path, 'V', entry.kind, entry.file_id, entry
2287
def kind(self, path):
2219
def kind(self, path, file_id=None):
2288
2220
trans_id = self._path2trans_id(path)
2289
2221
if trans_id is None:
2290
2222
raise errors.NoSuchFile(path)
2291
2223
return self._transform.final_kind(trans_id)
2293
def stored_kind(self, path):
2225
def stored_kind(self, path, file_id=None):
2294
2226
trans_id = self._path2trans_id(path)
2295
2227
if trans_id is None:
2296
2228
raise errors.NoSuchFile(path)
2298
2230
return self._transform._new_contents[trans_id]
2299
2231
except KeyError:
2300
return self._transform._tree.stored_kind(path)
2232
return self._transform._tree.stored_kind(path, file_id)
2302
def get_file_mtime(self, path):
2234
def get_file_mtime(self, path, file_id=None):
2303
2235
"""See Tree.get_file_mtime"""
2304
file_id = self.path2id(path)
2237
file_id = self.path2id(path)
2305
2238
if file_id is None:
2306
2239
raise errors.NoSuchFile(path)
2307
2240
if not self._content_change(file_id):
2308
2241
return self._transform._tree.get_file_mtime(
2309
self._transform._tree.id2path(file_id))
2242
self._transform._tree.id2path(file_id), file_id)
2310
2243
trans_id = self._path2trans_id(path)
2311
2244
return self._stat_limbo_file(trans_id).st_mtime
2313
def get_file_size(self, path):
2246
def get_file_size(self, path, file_id=None):
2314
2247
"""See Tree.get_file_size"""
2315
2248
trans_id = self._path2trans_id(path)
2316
2249
if trans_id is None:
2321
2254
if trans_id in self._transform._new_contents:
2322
2255
return self._stat_limbo_file(trans_id).st_size
2323
if self.kind(path) == 'file':
2324
return self._transform._tree.get_file_size(path)
2256
if self.kind(path, file_id) == 'file':
2257
return self._transform._tree.get_file_size(path, file_id)
2328
def get_file_verifier(self, path, stat_value=None):
2261
def get_file_verifier(self, path, file_id=None, stat_value=None):
2329
2262
trans_id = self._path2trans_id(path)
2330
2263
if trans_id is None:
2331
2264
raise errors.NoSuchFile(path)
2332
2265
kind = self._transform._new_contents.get(trans_id)
2333
2266
if kind is None:
2334
return self._transform._tree.get_file_verifier(path)
2267
return self._transform._tree.get_file_verifier(path, file_id)
2335
2268
if kind == 'file':
2336
with self.get_file(path) as fileobj:
2269
fileobj = self.get_file(path, file_id)
2337
2271
return ("SHA1", sha_file(fileobj))
2339
def get_file_sha1(self, path, stat_value=None):
2275
def get_file_sha1(self, path, file_id=None, stat_value=None):
2340
2276
trans_id = self._path2trans_id(path)
2341
2277
if trans_id is None:
2342
2278
raise errors.NoSuchFile(path)
2343
2279
kind = self._transform._new_contents.get(trans_id)
2344
2280
if kind is None:
2345
return self._transform._tree.get_file_sha1(path)
2281
return self._transform._tree.get_file_sha1(path, file_id)
2346
2282
if kind == 'file':
2347
with self.get_file(path) as fileobj:
2283
fileobj = self.get_file(path, file_id)
2348
2285
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
def is_executable(self, path):
2289
def is_executable(self, path, file_id=None):
2360
2290
trans_id = self._path2trans_id(path)
2361
2291
if trans_id is None:
2408
2338
executable = None
2409
2339
if kind == 'symlink':
2410
link_or_sha1 = os.readlink(limbo_name)
2411
if not isinstance(link_or_sha1, text_type):
2412
link_or_sha1 = link_or_sha1.decode(osutils._fs_enc)
2340
link_or_sha1 = os.readlink(limbo_name).decode(osutils._fs_enc)
2413
2341
executable = tt._new_executability.get(trans_id, executable)
2414
2342
return kind, size, executable, link_or_sha1
2416
2344
def iter_changes(self, from_tree, include_unchanged=False,
2417
specific_files=None, pb=None, extra_trees=None,
2418
require_versioned=True, want_unversioned=False):
2345
specific_files=None, pb=None, extra_trees=None,
2346
require_versioned=True, want_unversioned=False):
2419
2347
"""See InterTree.iter_changes.
2421
2349
This has a fast path that is only used when the from_tree matches
2422
2350
the transform tree, and no fancy options are supplied.
2424
if (from_tree is not self._transform._tree or include_unchanged
2425
or specific_files or want_unversioned):
2352
if (from_tree is not self._transform._tree or include_unchanged or
2353
specific_files or want_unversioned):
2426
2354
return tree.InterTree(from_tree, self).iter_changes(
2427
2355
include_unchanged=include_unchanged,
2428
2356
specific_files=specific_files,
2434
2362
raise ValueError('want_unversioned is not supported')
2435
2363
return self._transform.iter_changes()
2437
def get_file(self, path):
2365
def get_file(self, path, file_id=None):
2438
2366
"""See Tree.get_file"""
2439
file_id = self.path2id(path)
2368
file_id = self.path2id(path)
2440
2369
if not self._content_change(file_id):
2441
return self._transform._tree.get_file(path)
2370
return self._transform._tree.get_file(path, file_id)
2442
2371
trans_id = self._path2trans_id(path)
2443
2372
name = self._transform._limbo_name(trans_id)
2444
2373
return open(name, 'rb')
2446
def get_file_with_stat(self, path):
2447
return self.get_file(path), None
2375
def get_file_with_stat(self, path, file_id=None):
2376
return self.get_file(path, file_id), None
2449
def annotate_iter(self, path,
2378
def annotate_iter(self, path, file_id=None,
2450
2379
default_revision=_mod_revision.CURRENT_REVISION):
2451
file_id = self.path2id(path)
2381
file_id = self.path2id(path)
2452
2382
changes = self._iter_changes_cache.get(file_id)
2453
2383
if changes is None:
2456
changed_content, versioned, kind = (
2457
changes.changed_content, changes.versioned, changes.kind)
2386
changed_content, versioned, kind = (changes[2], changes[3],
2458
2388
if kind[1] is None:
2460
2390
get_old = (kind[0] == 'file' and versioned[0])
2462
2392
old_annotation = self._transform._tree.annotate_iter(
2463
path, default_revision=default_revision)
2393
path, file_id=file_id, default_revision=default_revision)
2465
2395
old_annotation = []
2466
2396
if changes is None:
2475
2405
# It would be nice to be able to use the new Annotator based
2476
2406
# approach, as well.
2477
2407
return annotate.reannotate([old_annotation],
2478
self.get_file(path).readlines(),
2408
self.get_file(path, file_id).readlines(),
2479
2409
default_revision)
2481
def get_symlink_target(self, path):
2411
def get_symlink_target(self, path, file_id=None):
2482
2412
"""See Tree.get_symlink_target"""
2483
file_id = self.path2id(path)
2414
file_id = self.path2id(path)
2484
2415
if not self._content_change(file_id):
2485
2416
return self._transform._tree.get_symlink_target(path)
2486
2417
trans_id = self._path2trans_id(path)
2500
2431
path_from_root = self._final_paths.get_path(child_id)
2501
2432
basename = self._transform.final_name(child_id)
2502
2433
file_id = self._transform.final_file_id(child_id)
2503
kind = self._transform.final_kind(child_id)
2434
kind = self._transform.final_kind(child_id)
2504
2435
if kind is not None:
2505
2436
versioned_kind = kind
2507
2438
kind = 'unknown'
2508
2439
versioned_kind = self._transform._tree.stored_kind(
2509
self._transform._tree.id2path(file_id))
2440
self._transform._tree.id2path(file_id),
2510
2442
if versioned_kind == 'directory':
2511
2443
subdirs.append(child_id)
2512
2444
children.append((path_from_root, basename, kind, None,
2593
2532
:param delta_from_tree: If true, build_tree may use the input Tree to
2594
2533
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())
2535
with wt.lock_tree_write(), tree.lock_read():
2599
2536
if accelerator_tree is not None:
2600
exit_stack.enter_context(accelerator_tree.lock_read())
2601
return _build_tree(tree, wt, accelerator_tree, hardlink,
2537
accelerator_tree.lock_read()
2539
return _build_tree(tree, wt, accelerator_tree, hardlink,
2542
if accelerator_tree is not None:
2543
accelerator_tree.unlock()
2605
2546
def _build_tree(tree, wt, accelerator_tree, hardlink, delta_from_tree):
2644
2585
for dir, files in wt.walkdirs():
2645
2586
existing_files.update(f[0] for f in files)
2646
2587
for num, (tree_path, entry) in \
2647
enumerate(tree.iter_entries_by_dir()):
2648
pb.update(gettext("Building tree"), num
2649
- len(deferred_contents), total)
2588
enumerate(tree.iter_entries_by_dir()):
2589
pb.update(gettext("Building tree"), num - len(deferred_contents), total)
2650
2590
if entry.parent_id is None:
2652
2592
reparent = False
2676
2616
trans_id = tt.create_path(entry.name, parent_id)
2677
2617
file_trans_id[file_id] = trans_id
2678
2618
tt.version_file(file_id, trans_id)
2679
executable = tree.is_executable(tree_path)
2619
executable = tree.is_executable(tree_path, file_id)
2681
2621
tt.set_executability(executable, trans_id)
2682
trans_data = (trans_id, file_id,
2683
tree_path, entry.text_sha1)
2622
trans_data = (trans_id, file_id, tree_path, entry.text_sha1)
2684
2623
deferred_contents.append((tree_path, trans_data))
2686
2625
file_trans_id[file_id] = new_by_entry(
2687
tree_path, tt, entry, parent_id, tree)
2626
tree_path, tt, entry, parent_id, tree)
2689
2628
new_trans_id = file_trans_id[file_id]
2690
2629
old_parent = tt.trans_id_tree_path(tree_path)
2694
2633
accelerator_tree, hardlink)
2695
2634
pp.next_phase()
2696
2635
divert_trans = set(file_trans_id[f] for f in divert)
2699
return resolve_checkout(t, c, divert_trans)
2636
resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
2700
2637
raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
2701
2638
if len(raw_conflicts) > 0:
2702
2639
precomputed_delta = None
2703
2640
conflicts = cook_conflicts(raw_conflicts, tt)
2704
2641
for conflict in conflicts:
2705
trace.warning(text_type(conflict))
2642
trace.warning(unicode(conflict))
2707
2644
wt.add_conflicts(conflicts)
2708
2645
except errors.UnsupportedOperation:
2723
2660
new_desired_files = desired_files
2725
2662
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])]
2663
unchanged = [(p[0], p[1]) for (f, p, c, v, d, n, k, e)
2664
in iter if not (c or e[0] != e[1])]
2729
2665
if accelerator_tree.supports_content_filtering():
2730
2666
unchanged = [(tp, ap) for (tp, ap) in unchanged
2731
2667
if not next(accelerator_tree.iter_search_rules([ap]))]
2736
2672
accelerator_path = unchanged.get(tree_path)
2737
2673
if accelerator_path is None:
2738
2674
new_desired_files.append((tree_path,
2739
(trans_id, file_id, tree_path, text_sha1)))
2675
(trans_id, file_id, tree_path, text_sha1)))
2741
2677
pb.update(gettext('Adding file contents'), count + offset, total)
2743
2679
tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
2746
with accelerator_tree.get_file(accelerator_path) as f:
2747
chunks = osutils.file_iterator(f)
2748
if wt.supports_content_filtering():
2749
filters = wt._content_filter_stack(tree_path)
2750
chunks = filtered_output_bytes(chunks, filters,
2751
ContentFilterContext(tree_path, tree))
2752
tt.create_file(chunks, trans_id, sha1=text_sha1)
2682
contents = accelerator_tree.get_file(accelerator_path, file_id)
2683
if wt.supports_content_filtering():
2684
filters = wt._content_filter_stack(tree_path)
2685
contents = filtered_output_bytes(contents, filters,
2686
ContentFilterContext(tree_path, tree))
2688
tt.create_file(contents, trans_id, sha1=text_sha1)
2692
except AttributeError:
2693
# after filtering, contents may no longer be file-like
2754
2696
offset += count
2755
2697
for count, ((trans_id, file_id, tree_path, text_sha1), contents) in enumerate(
2774
2716
return by_parent[old_parent]
2777
def _content_match(tree, entry, tree_path, kind, target_path):
2719
def _content_match(tree, entry, tree_path, file_id, kind, target_path):
2778
2720
if entry.kind != kind:
2780
2722
if entry.kind == "directory":
2782
2724
if entry.kind == "file":
2783
with open(target_path, 'rb') as f1, \
2784
tree.get_file(tree_path) as f2:
2785
if osutils.compare_files(f1, f2):
2725
f = file(target_path, 'rb')
2727
if tree.get_file_text(tree_path, file_id) == f.read():
2787
2731
elif entry.kind == "symlink":
2788
if tree.get_symlink_target(tree_path) == os.readlink(target_path):
2732
if tree.get_symlink_target(tree_path, file_id) == os.readlink(target_path):
2809
2753
final_parent = tt.final_parent(old_file)
2810
2754
if new_file in divert:
2811
new_name = tt.final_name(old_file) + '.diverted'
2755
new_name = tt.final_name(old_file)+'.diverted'
2812
2756
tt.adjust_path(new_name, final_parent, new_file)
2813
2757
new_conflicts.add((c_type, 'Diverted to',
2814
2758
new_file, old_file))
2816
new_name = tt.final_name(old_file) + '.moved'
2760
new_name = tt.final_name(old_file)+'.moved'
2817
2761
tt.adjust_path(new_name, final_parent, old_file)
2818
2762
new_conflicts.add((c_type, 'Moved existing file to',
2819
2763
old_file, new_file))
2825
2769
name = entry.name
2826
2770
kind = entry.kind
2827
2771
if kind == 'file':
2828
with tree.get_file(path) as f:
2829
executable = tree.is_executable(path)
2831
name, parent_id, osutils.file_iterator(f), entry.file_id,
2772
contents = tree.get_file(path, entry.file_id).readlines()
2773
executable = tree.is_executable(path, entry.file_id)
2774
return tt.new_file(name, parent_id, contents, entry.file_id,
2833
2776
elif kind in ('directory', 'tree-reference'):
2834
2777
trans_id = tt.new_directory(name, parent_id, entry.file_id)
2835
2778
if kind == 'tree-reference':
2836
2779
tt.set_tree_reference(entry.reference_revision, trans_id)
2837
2780
return trans_id
2838
2781
elif kind == 'symlink':
2839
target = tree.get_symlink_target(path)
2782
target = tree.get_symlink_target(path, entry.file_id)
2840
2783
return tt.new_symlink(name, parent_id, target, entry.file_id)
2842
2785
raise errors.BadFileKindError(name, kind)
2845
def create_from_tree(tt, trans_id, tree, path, chunks=None,
2846
filter_tree_path=None):
2788
def create_from_tree(tt, trans_id, tree, path, file_id=None, bytes=None,
2789
filter_tree_path=None):
2847
2790
"""Create new file contents according to tree contents.
2849
2792
:param filter_tree_path: the tree path to use to lookup
2850
2793
content filters to apply to the bytes output in the working tree.
2851
2794
This only applies if the working tree supports content filtering.
2853
kind = tree.kind(path)
2796
kind = tree.kind(path, file_id)
2854
2797
if kind == 'directory':
2855
2798
tt.create_directory(trans_id)
2856
2799
elif kind == "file":
2858
f = tree.get_file(path)
2859
chunks = osutils.file_iterator(f)
2864
if wt.supports_content_filtering() and filter_tree_path is not None:
2865
filters = wt._content_filter_stack(filter_tree_path)
2866
chunks = filtered_output_bytes(
2868
ContentFilterContext(filter_tree_path, tree))
2869
tt.create_file(chunks, trans_id)
2801
tree_file = tree.get_file(path, file_id)
2803
bytes = tree_file.readlines()
2807
if wt.supports_content_filtering() and filter_tree_path is not None:
2808
filters = wt._content_filter_stack(filter_tree_path)
2809
bytes = filtered_output_bytes(bytes, filters,
2810
ContentFilterContext(filter_tree_path, tree))
2811
tt.create_file(bytes, trans_id)
2873
2812
elif kind == "symlink":
2874
tt.create_symlink(tree.get_symlink_target(path), trans_id)
2813
tt.create_symlink(tree.get_symlink_target(path, file_id), trans_id)
2876
2815
raise AssertionError('Unknown kind %r' % kind)
2885
2824
def revert(working_tree, target_tree, filenames, backups=False,
2886
2825
pb=None, change_reporter=None):
2887
2826
"""Revert a working tree's contents to those of a target tree."""
2827
target_tree.lock_read()
2888
2828
pb = ui.ui_factory.nested_progress_bar()
2829
tt = TreeTransform(working_tree, pb)
2890
with target_tree.lock_read(), working_tree.get_transform(pb) as tt:
2891
pp = ProgressPhase("Revert phase", 3, pb)
2892
conflicts, merge_modified = _prepare_revert_transform(
2893
working_tree, target_tree, tt, filenames, backups, pp)
2895
change_reporter = delta._ChangeReporter(
2896
unversioned_filter=working_tree.is_ignored)
2897
delta.report_changes(tt.iter_changes(), change_reporter)
2898
for conflict in conflicts:
2899
trace.warning(text_type(conflict))
2902
if working_tree.supports_merge_modified():
2903
working_tree.set_merge_modified(merge_modified)
2831
pp = ProgressPhase("Revert phase", 3, pb)
2832
conflicts, merge_modified = _prepare_revert_transform(
2833
working_tree, target_tree, tt, filenames, backups, pp)
2835
change_reporter = delta._ChangeReporter(
2836
unversioned_filter=working_tree.is_ignored)
2837
delta.report_changes(tt.iter_changes(), change_reporter)
2838
for conflict in conflicts:
2839
trace.warning(unicode(conflict))
2842
if working_tree.supports_merge_modified():
2843
working_tree.set_merge_modified(merge_modified)
2845
target_tree.unlock()
2906
2848
return conflicts
2916
2858
child_pb, filenames, backups,
2917
2859
merge_modified, basis_tree)
2918
2860
with ui.ui_factory.nested_progress_bar() as child_pb:
2919
raw_conflicts = resolve_conflicts(
2920
tt, child_pb, lambda t, c: conflict_pass(t, c, target_tree))
2861
raw_conflicts = resolve_conflicts(tt, child_pb,
2862
lambda t, c: conflict_pass(t, c, target_tree))
2921
2863
conflicts = cook_conflicts(raw_conflicts, tt)
2922
2864
return conflicts, merge_modified
2930
2872
# than the target changes relative to the working tree. Because WT4 has an
2931
2873
# optimizer to compare itself to a target, but no optimizer for the
2933
change_list = working_tree.iter_changes(
2934
target_tree, specific_files=specific_files, pb=pb)
2935
if not target_tree.is_versioned(u''):
2875
change_list = working_tree.iter_changes(target_tree,
2876
specific_files=specific_files, pb=pb)
2877
if target_tree.get_root_id() is None:
2936
2878
skip_root = True
2938
2880
skip_root = False
2940
2882
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
2883
for id_num, (file_id, path, changed_content, versioned, parent, name,
2884
kind, executable) in enumerate(change_list):
2885
target_path, wt_path = path
2886
target_versioned, wt_versioned = versioned
2887
target_parent, wt_parent = parent
2888
target_name, wt_name = name
2889
target_kind, wt_kind = kind
2890
target_executable, wt_executable = executable
2949
2891
if skip_root and wt_parent is None:
2951
2893
trans_id = tt.trans_id_file_id(file_id)
2953
if change.changed_content:
2954
2896
keep_content = False
2955
2897
if wt_kind == 'file' and (backups or target_kind is None):
2956
wt_sha1 = working_tree.get_file_sha1(wt_path)
2957
if merge_modified.get(wt_path) != wt_sha1:
2898
wt_sha1 = working_tree.get_file_sha1(wt_path, file_id)
2899
if merge_modified.get(file_id) != wt_sha1:
2958
2900
# acquire the basis tree lazily to prevent the
2959
2901
# expense of accessing it when it's not needed ?
2960
2902
# (Guessing, RBC, 200702)
2961
2903
if basis_tree is None:
2962
2904
basis_tree = working_tree.basis_tree()
2963
2905
basis_tree.lock_read()
2964
basis_inter = InterTree.get(basis_tree, working_tree)
2965
basis_path = basis_inter.find_source_path(wt_path)
2906
basis_path = find_previous_path(working_tree, basis_tree, wt_path)
2966
2907
if basis_path is None:
2967
2908
if target_kind is None and not target_versioned:
2968
2909
keep_content = True
2970
if wt_sha1 != basis_tree.get_file_sha1(basis_path):
2911
if wt_sha1 != basis_tree.get_file_sha1(basis_path, file_id):
2971
2912
keep_content = True
2972
2913
if wt_kind is not None:
2973
2914
if not keep_content:
2989
2930
tt.create_directory(trans_id)
2990
2931
if target_kind == 'tree-reference':
2991
2932
revision = target_tree.get_reference_revision(
2933
target_path, file_id)
2993
2934
tt.set_tree_reference(revision, trans_id)
2994
2935
elif target_kind == 'symlink':
2995
2936
tt.create_symlink(target_tree.get_symlink_target(
2996
target_path), trans_id)
2937
target_path, file_id), trans_id)
2997
2938
elif target_kind == 'file':
2998
deferred_files.append(
2999
(target_path, (trans_id, mode_id, file_id)))
2939
deferred_files.append((target_path, (trans_id, mode_id, file_id)))
3000
2940
if basis_tree is None:
3001
2941
basis_tree = working_tree.basis_tree()
3002
2942
basis_tree.lock_read()
3003
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)
2943
new_sha1 = target_tree.get_file_sha1(target_path, file_id)
2944
basis_path = find_previous_path(target_tree, basis_tree, target_path)
3006
2945
if (basis_path is not None and
3007
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]
2946
new_sha1 == basis_tree.get_file_sha1(basis_path, file_id)):
2947
if file_id in merge_modified:
2948
del merge_modified[file_id]
3013
merge_modified[target_path] = new_sha1
2950
merge_modified[file_id] = new_sha1
3015
2952
# preserve the execute bit when backing up
3016
2953
if keep_content and wt_executable == target_executable:
3035
2972
tt.set_executability(target_executable, trans_id)
3036
2973
if working_tree.supports_content_filtering():
3037
2974
for (trans_id, mode_id, file_id), bytes in (
3038
target_tree.iter_files_bytes(deferred_files)):
2975
target_tree.iter_files_bytes(deferred_files)):
3039
2976
# We're reverting a tree to the target tree so using the
3040
2977
# target tree to find the file path seems the best choice
3041
2978
# here IMO - Ian C 27/Oct/2009
3042
2979
filter_tree_path = target_tree.id2path(file_id)
3043
2980
filters = working_tree._content_filter_stack(filter_tree_path)
3044
bytes = filtered_output_bytes(
2981
bytes = filtered_output_bytes(bytes, filters,
3046
2982
ContentFilterContext(filter_tree_path, working_tree))
3047
2983
tt.create_file(bytes, trans_id, mode_id)
3049
2985
for (trans_id, mode_id, file_id), bytes in target_tree.iter_files_bytes(
3051
2987
tt.create_file(bytes, trans_id, mode_id)
3052
2988
tt.fixup_new_roots()
3245
3181
except OSError as e:
3246
3182
raise errors.TransformRenameFailed(to, from_, str(e), e.errno)
3247
3183
# after rollback, don't reuse _FileMover
3248
self.past_renames = None
3249
self.pending_deletions = None
3185
pending_deletions = None
3251
3187
def apply_deletions(self):
3252
3188
"""Apply all marked deletions"""
3253
3189
for path in self.pending_deletions:
3254
3190
delete_any(path)
3255
3191
# after apply_deletions, don't reuse _FileMover
3256
self.past_renames = None
3257
self.pending_deletions = None
3193
pending_deletions = None
3260
3196
def link_tree(target_tree, source_tree):
3263
3199
:param target_tree: Tree to change
3264
3200
: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])
3202
tt = TreeTransform(target_tree)
3204
for (file_id, paths, changed_content, versioned, parent, name, kind,
3205
executable) in target_tree.iter_changes(source_tree,
3206
include_unchanged=True):
3209
if kind != ('file', 'file'):
3211
if executable[0] != executable[1]:
3213
trans_id = tt.trans_id_tree_path(paths[1])
3275
3214
tt.delete_contents(trans_id)
3276
tt.create_hardlink(source_tree.abspath(change.path[0]), trans_id)
3215
tt.create_hardlink(source_tree.abspath(paths[0]), trans_id)