729
717
def _duplicate_ids(self):
730
718
"""Each inventory id may only be used once"""
721
all_ids = self._tree.all_file_ids()
722
except errors.UnsupportedOperation:
723
# it's okay for non-file-id trees to raise UnsupportedOperation.
732
725
removed_tree_ids = set((self.tree_file_id(trans_id) for trans_id in
733
726
self._removed_id))
734
all_ids = self._tree.all_file_ids()
735
727
active_tree_ids = all_ids.difference(removed_tree_ids)
736
for trans_id, file_id in viewitems(self._new_id):
728
for trans_id, file_id in self._new_id.items():
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))
742
735
def _parent_type_conflicts(self, by_parent):
743
736
"""Children must have a directory parent"""
745
for parent_id, children in viewitems(by_parent):
738
for parent_id, children in by_parent.items():
746
739
if parent_id == ROOT_PARENT:
748
741
no_children = True
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 (
998
to_trans_id != from_trans_id or
999
to_trans_id in self._new_contents):
993
to_trans_id != from_trans_id
994
or to_trans_id in self._new_contents):
1001
if (not modified and from_versioned == to_versioned and
1002
from_parent==to_parent and from_name == to_name and
1003
from_executable == to_executable):
996
if (not modified and from_versioned == to_versioned
997
and from_parent == to_parent and from_name == to_name
998
and from_executable == to_executable):
1005
results.append((file_id, (from_path, to_path), modified,
1006
(from_versioned, to_versioned),
1007
(from_parent, to_parent),
1008
(from_name, to_name),
1009
(from_kind, to_kind),
1010
(from_executable, to_executable)))
1011
return iter(sorted(results, key=lambda x:x[1]))
1002
file_id, (from_path, to_path), modified,
1003
(from_versioned, to_versioned),
1004
(from_parent, to_parent),
1005
(from_name, to_name),
1006
(from_kind, to_kind),
1007
(from_executable, to_executable)))
1010
return (c.path[0] or '', c.path[1] or '')
1011
return iter(sorted(results, key=path_key))
1013
1013
def get_preview_tree(self):
1014
1014
"""Return a tree representing the result of the transform.
1077
1077
return revision_id
1079
1079
def _text_parent(self, trans_id):
1080
file_id = self.tree_file_id(trans_id)
1080
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'):
1082
if path is None or self._tree.kind(path) != 'file':
1085
1084
except errors.NoSuchFile:
1089
1088
def _get_parents_texts(self, trans_id):
1090
1089
"""Get texts for compression parents of this file."""
1091
file_id = self._text_parent(trans_id)
1090
path = self._text_parent(trans_id)
1094
return (self._tree.get_file_text(self._tree.id2path(file_id), file_id),)
1093
return (self._tree.get_file_text(path),)
1096
1095
def _get_parents_lines(self, trans_id):
1097
1096
"""Get lines for compression parents of this file."""
1098
file_id = self._text_parent(trans_id)
1097
path = self._text_parent(trans_id)
1101
return (self._tree.get_file_lines(self._tree.id2path(file_id), file_id),)
1100
return (self._tree.get_file_lines(path),)
1103
1102
def serialize(self, serializer):
1104
1103
"""Serialize this TreeTransform.
1106
1105
:param serializer: A Serialiser like pack.ContainerSerializer.
1108
new_name = dict((k, v.encode('utf-8')) for k, v in
1109
viewitems(self._new_name))
1110
new_executability = dict((k, int(v)) for k, v in
1111
viewitems(self._new_executability))
1112
tree_path_ids = dict((k.encode('utf-8'), v)
1113
for k, v in viewitems(self._tree_path_ids))
1107
new_name = {k.encode('utf-8'): v.encode('utf-8')
1108
for k, v in self._new_name.items()}
1109
new_parent = {k.encode('utf-8'): v.encode('utf-8')
1110
for k, v in self._new_parent.items()}
1111
new_id = {k.encode('utf-8'): v
1112
for k, v in self._new_id.items()}
1113
new_executability = {k.encode('utf-8'): int(v)
1114
for k, v in self._new_executability.items()}
1115
tree_path_ids = {k.encode('utf-8'): v.encode('utf-8')
1116
for k, v in self._tree_path_ids.items()}
1117
non_present_ids = {k: v.encode('utf-8')
1118
for k, v in self._non_present_ids.items()}
1119
removed_contents = [trans_id.encode('utf-8')
1120
for trans_id in self._removed_contents]
1121
removed_id = [trans_id.encode('utf-8')
1122
for trans_id in self._removed_id]
1115
'_id_number': self._id_number,
1116
'_new_name': new_name,
1117
'_new_parent': self._new_parent,
1118
'_new_executability': new_executability,
1119
'_new_id': self._new_id,
1120
'_tree_path_ids': tree_path_ids,
1121
'_removed_id': list(self._removed_id),
1122
'_removed_contents': list(self._removed_contents),
1123
'_non_present_ids': self._non_present_ids,
1124
b'_id_number': self._id_number,
1125
b'_new_name': new_name,
1126
b'_new_parent': new_parent,
1127
b'_new_executability': new_executability,
1129
b'_tree_path_ids': tree_path_ids,
1130
b'_removed_id': removed_id,
1131
b'_removed_contents': removed_contents,
1132
b'_non_present_ids': non_present_ids,
1125
1134
yield serializer.bytes_record(bencode.bencode(attribs),
1127
for trans_id, kind in viewitems(self._new_contents):
1136
for trans_id, kind in sorted(self._new_contents.items()):
1128
1137
if kind == 'file':
1129
lines = osutils.chunks_to_lines(
1130
self._read_file_chunks(trans_id))
1138
with open(self._limbo_name(trans_id), 'rb') as cur_file:
1139
lines = cur_file.readlines()
1131
1140
parents = self._get_parents_lines(trans_id)
1132
1141
mpdiff = multiparent.MultiParent.from_lines(lines, parents)
1133
content = ''.join(mpdiff.to_patch())
1142
content = b''.join(mpdiff.to_patch())
1134
1143
if kind == 'directory':
1136
1145
if kind == 'symlink':
1137
1146
content = self._read_symlink_target(trans_id)
1138
yield serializer.bytes_record(content, ((trans_id, kind),))
1147
if not isinstance(content, bytes):
1148
content = content.encode('utf-8')
1149
yield serializer.bytes_record(
1150
content, ((trans_id.encode('utf-8'), kind.encode('ascii')),))
1140
1152
def deserialize(self, records):
1141
1153
"""Deserialize a stored TreeTransform.
1146
1158
names, content = next(records)
1147
1159
attribs = bencode.bdecode(content)
1148
self._id_number = attribs['_id_number']
1149
self._new_name = dict((k, v.decode('utf-8'))
1150
for k, v in viewitems(attribs['_new_name']))
1151
self._new_parent = attribs['_new_parent']
1152
self._new_executability = dict((k, bool(v))
1153
for k, v in viewitems(attribs['_new_executability']))
1154
self._new_id = attribs['_new_id']
1155
self._r_new_id = dict((v, k) for k, v in viewitems(self._new_id))
1160
self._id_number = attribs[b'_id_number']
1161
self._new_name = {k.decode('utf-8'): v.decode('utf-8')
1162
for k, v in attribs[b'_new_name'].items()}
1163
self._new_parent = {k.decode('utf-8'): v.decode('utf-8')
1164
for k, v in attribs[b'_new_parent'].items()}
1165
self._new_executability = {
1166
k.decode('utf-8'): bool(v)
1167
for k, v in attribs[b'_new_executability'].items()}
1168
self._new_id = {k.decode('utf-8'): v
1169
for k, v in attribs[b'_new_id'].items()}
1170
self._r_new_id = {v: k for k, v in self._new_id.items()}
1156
1171
self._tree_path_ids = {}
1157
1172
self._tree_id_paths = {}
1158
for bytepath, trans_id in viewitems(attribs['_tree_path_ids']):
1173
for bytepath, trans_id in attribs[b'_tree_path_ids'].items():
1159
1174
path = bytepath.decode('utf-8')
1175
trans_id = trans_id.decode('utf-8')
1160
1176
self._tree_path_ids[path] = trans_id
1161
1177
self._tree_id_paths[trans_id] = path
1162
self._removed_id = set(attribs['_removed_id'])
1163
self._removed_contents = set(attribs['_removed_contents'])
1164
self._non_present_ids = attribs['_non_present_ids']
1178
self._removed_id = {trans_id.decode('utf-8')
1179
for trans_id in attribs[b'_removed_id']}
1180
self._removed_contents = set(
1181
trans_id.decode('utf-8')
1182
for trans_id in attribs[b'_removed_contents'])
1183
self._non_present_ids = {
1184
k: v.decode('utf-8')
1185
for k, v in attribs[b'_non_present_ids'].items()}
1165
1186
for ((trans_id, kind),), content in records:
1187
trans_id = trans_id.decode('utf-8')
1188
kind = kind.decode('ascii')
1166
1189
if kind == 'file':
1167
1190
mpdiff = multiparent.MultiParent.from_patch(content)
1168
1191
lines = mpdiff.to_lines(self._get_parents_texts(trans_id))
1172
1195
if kind == 'symlink':
1173
1196
self.create_symlink(content.decode('utf-8'), trans_id)
1198
def create_file(self, contents, trans_id, mode_id=None, sha1=None):
1199
"""Schedule creation of a new file.
1203
:param contents: an iterator of strings, all of which will be written
1204
to the target destination.
1205
:param trans_id: TreeTransform handle
1206
:param mode_id: If not None, force the mode of the target file to match
1207
the mode of the object referenced by mode_id.
1208
Otherwise, we will try to preserve mode bits of an existing file.
1209
:param sha1: If the sha1 of this content is already known, pass it in.
1210
We can use it to prevent future sha1 computations.
1212
raise NotImplementedError(self.create_file)
1214
def create_directory(self, trans_id):
1215
"""Schedule creation of a new directory.
1217
See also new_directory.
1219
raise NotImplementedError(self.create_directory)
1221
def create_symlink(self, target, trans_id):
1222
"""Schedule creation of a new symbolic link.
1224
target is a bytestring.
1225
See also new_symlink.
1227
raise NotImplementedError(self.create_symlink)
1229
def create_hardlink(self, path, trans_id):
1230
"""Schedule creation of a hard link"""
1231
raise NotImplementedError(self.create_hardlink)
1233
def cancel_creation(self, trans_id):
1234
"""Cancel the creation of new file contents."""
1235
raise NotImplementedError(self.cancel_creation)
1176
1238
class DiskTreeTransform(TreeTransformBase):
1177
1239
"""Tree transform storing its contents on disk."""
1179
def __init__(self, tree, limbodir, pb=None,
1180
case_sensitive=True):
1241
def __init__(self, tree, limbodir, pb=None, case_sensitive=True):
1181
1242
"""Constructor.
1182
1243
:param tree: The tree that will be transformed, but not necessarily
1183
1244
the output tree.
2060
2099
"""This Tree does not use inventory as its backing data."""
2061
2100
raise NotImplementedError(_PreviewTree.root_inventory)
2063
def get_root_id(self):
2064
return self._transform.final_file_id(self._transform.root)
2066
2102
def all_file_ids(self):
2067
2103
tree_ids = set(self._transform._tree.all_file_ids())
2068
2104
tree_ids.difference_update(self._transform.tree_file_id(t)
2069
2105
for t in self._transform._removed_id)
2070
tree_ids.update(viewvalues(self._transform._new_id))
2106
tree_ids.update(self._transform._new_id.values())
2071
2107
return tree_ids
2073
2109
def all_versioned_paths(self):
2074
return {self.id2path(fid) for fid in self.all_file_ids()}
2076
def _has_id(self, file_id, fallback_check):
2077
if file_id in self._transform._r_new_id:
2079
elif file_id in {self._transform.tree_file_id(trans_id) for
2080
trans_id in self._transform._removed_id}:
2083
return fallback_check(file_id)
2085
def has_id(self, file_id):
2086
return self._has_id(file_id, self._transform._tree.has_id)
2088
def has_or_had_id(self, file_id):
2089
return self._has_id(file_id, self._transform._tree.has_or_had_id)
2110
tree_paths = set(self._transform._tree.all_versioned_paths())
2112
tree_paths.difference_update(
2113
self._transform.trans_id_tree_path(t)
2114
for t in self._transform._removed_id)
2117
self._final_paths._determine_path(t)
2118
for t in self._transform._new_id)
2091
2122
def _path2trans_id(self, path):
2092
2123
# We must not use None here, because that is a valid value to store.
2203
2229
ordered_ids = self._list_files_by_dir()
2204
2230
for entry, trans_id in self._make_inv_entries(ordered_ids,
2205
specific_file_ids, yield_parents=yield_parents):
2206
yield unicode(self._final_paths.get_path(trans_id)), entry
2232
yield self._final_paths.get_path(trans_id), entry
2208
2234
def _iter_entries_for_dir(self, dir_path):
2209
2235
"""Return path, entry for items in a directory without recursing down."""
2210
dir_file_id = self.path2id(dir_path)
2211
2236
ordered_ids = []
2212
for file_id in self.iter_children(dir_file_id):
2213
trans_id = self._transform.trans_id_file_id(file_id)
2214
ordered_ids.append((trans_id, file_id))
2237
dir_trans_id = self._path2trans_id(dir_path)
2238
dir_id = self._transform.final_file_id(dir_trans_id)
2239
for child_trans_id in self._all_children(dir_trans_id):
2240
ordered_ids.append((child_trans_id, dir_id))
2215
2242
for entry, trans_id in self._make_inv_entries(ordered_ids):
2216
yield unicode(self._final_paths.get_path(trans_id)), entry
2243
path_entries.append((self._final_paths.get_path(trans_id), entry))
2218
def list_files(self, include_root=False, from_dir=None, recursive=True):
2247
def list_files(self, include_root=False, from_dir=None, recursive=True,
2248
recurse_nested=False):
2219
2249
"""See WorkingTree.list_files."""
2251
raise NotImplementedError(
2252
'follow tree references not yet supported')
2220
2254
# XXX This should behave like WorkingTree.list_files, but is really
2221
2255
# more like RevisionTree.list_files.
2231
2267
if not path.startswith(prefix):
2233
2269
path = path[len(prefix):]
2234
yield path, 'V', entry.kind, entry.file_id, entry
2270
yield path, 'V', entry.kind, entry
2236
2272
if from_dir is None and include_root is True:
2237
root_entry = inventory.make_entry('directory', '',
2238
ROOT_PARENT, self.get_root_id())
2239
yield '', 'V', 'directory', root_entry.file_id, root_entry
2273
root_entry = inventory.make_entry(
2274
'directory', '', ROOT_PARENT, self.path2id(''))
2275
yield '', 'V', 'directory', root_entry
2240
2276
entries = self._iter_entries_for_dir(from_dir or '')
2241
2277
for path, entry in entries:
2242
yield path, 'V', entry.kind, entry.file_id, entry
2278
yield path, 'V', entry.kind, entry
2244
def kind(self, path, file_id=None):
2246
file_id = self.path2id(path)
2247
trans_id = self._transform.trans_id_file_id(file_id)
2280
def kind(self, path):
2281
trans_id = self._path2trans_id(path)
2282
if trans_id is None:
2283
raise errors.NoSuchFile(path)
2248
2284
return self._transform.final_kind(trans_id)
2250
def stored_kind(self, path, file_id=None):
2252
file_id = self.path2id(path)
2253
trans_id = self._transform.trans_id_file_id(file_id)
2286
def stored_kind(self, path):
2287
trans_id = self._path2trans_id(path)
2288
if trans_id is None:
2289
raise errors.NoSuchFile(path)
2255
2291
return self._transform._new_contents[trans_id]
2256
2292
except KeyError:
2257
return self._transform._tree.stored_kind(path, file_id)
2293
return self._transform._tree.stored_kind(path)
2259
def get_file_mtime(self, path, file_id=None):
2295
def get_file_mtime(self, path):
2260
2296
"""See Tree.get_file_mtime"""
2262
file_id = self.path2id(path)
2297
file_id = self.path2id(path)
2263
2298
if file_id is None:
2264
2299
raise errors.NoSuchFile(path)
2265
2300
if not self._content_change(file_id):
2266
2301
return self._transform._tree.get_file_mtime(
2267
self._transform._tree.id2path(file_id), file_id)
2268
return self._stat_limbo_file(file_id).st_mtime
2270
def _file_size(self, entry, stat_value):
2271
path = self.id2path(entry.file_id)
2272
return self.get_file_size(path, entry.file_id)
2274
def get_file_size(self, path, file_id=None):
2302
self._transform._tree.id2path(file_id))
2303
trans_id = self._path2trans_id(path)
2304
return self._stat_limbo_file(trans_id).st_mtime
2306
def get_file_size(self, path):
2275
2307
"""See Tree.get_file_size"""
2277
file_id = self.path2id(path)
2278
trans_id = self._transform.trans_id_file_id(file_id)
2308
trans_id = self._path2trans_id(path)
2309
if trans_id is None:
2310
raise errors.NoSuchFile(path)
2279
2311
kind = self._transform.final_kind(trans_id)
2280
2312
if kind != 'file':
2282
2314
if trans_id in self._transform._new_contents:
2283
return self._stat_limbo_file(trans_id=trans_id).st_size
2284
if self.kind(path, file_id) == 'file':
2285
return self._transform._tree.get_file_size(path, file_id)
2315
return self._stat_limbo_file(trans_id).st_size
2316
if self.kind(path) == 'file':
2317
return self._transform._tree.get_file_size(path)
2289
def get_file_verifier(self, path, file_id=None, stat_value=None):
2291
file_id = self.path2id(path)
2292
trans_id = self._transform.trans_id_file_id(file_id)
2321
def get_file_verifier(self, path, stat_value=None):
2322
trans_id = self._path2trans_id(path)
2323
if trans_id is None:
2324
raise errors.NoSuchFile(path)
2293
2325
kind = self._transform._new_contents.get(trans_id)
2294
2326
if kind is None:
2295
return self._transform._tree.get_file_verifier(path, file_id)
2327
return self._transform._tree.get_file_verifier(path)
2296
2328
if kind == 'file':
2297
fileobj = self.get_file(path, file_id)
2329
with self.get_file(path) as fileobj:
2299
2330
return ("SHA1", sha_file(fileobj))
2303
def get_file_sha1(self, path, file_id=None, stat_value=None):
2305
file_id = self.path2id(path)
2306
trans_id = self._transform.trans_id_file_id(file_id)
2332
def get_file_sha1(self, path, stat_value=None):
2333
trans_id = self._path2trans_id(path)
2334
if trans_id is None:
2335
raise errors.NoSuchFile(path)
2307
2336
kind = self._transform._new_contents.get(trans_id)
2308
2337
if kind is None:
2309
return self._transform._tree.get_file_sha1(path, file_id)
2338
return self._transform._tree.get_file_sha1(path)
2310
2339
if kind == 'file':
2311
fileobj = self.get_file(path, file_id)
2340
with self.get_file(path) as fileobj:
2313
2341
return sha_file(fileobj)
2317
def is_executable(self, path, file_id=None):
2319
file_id = self.path2id(path)
2343
def get_reference_revision(self, path):
2344
trans_id = self._path2trans_id(path)
2345
if trans_id is None:
2346
raise errors.NoSuchFile(path)
2347
reference_revision = self._transform._new_reference_revision.get(trans_id)
2348
if reference_revision is None:
2349
return self._transform._tree.get_reference_revision(path)
2350
return reference_revision
2352
def is_executable(self, path):
2353
trans_id = self._path2trans_id(path)
2354
if trans_id is None:
2322
trans_id = self._transform.trans_id_file_id(file_id)
2324
2357
return self._transform._new_executability[trans_id]
2325
2358
except KeyError:
2327
return self._transform._tree.is_executable(path, file_id)
2360
return self._transform._tree.is_executable(path)
2328
2361
except OSError as e:
2329
2362
if e.errno == errno.ENOENT:
2332
except errors.NoSuchId:
2365
except errors.NoSuchFile:
2335
2368
def has_filename(self, path):
2368
2401
executable = None
2369
2402
if kind == 'symlink':
2370
link_or_sha1 = os.readlink(limbo_name).decode(osutils._fs_enc)
2403
link_or_sha1 = os.readlink(limbo_name)
2404
if not isinstance(link_or_sha1, str):
2405
link_or_sha1 = link_or_sha1.decode(osutils._fs_enc)
2371
2406
executable = tt._new_executability.get(trans_id, executable)
2372
2407
return kind, size, executable, link_or_sha1
2374
2409
def iter_changes(self, from_tree, include_unchanged=False,
2375
specific_files=None, pb=None, extra_trees=None,
2376
require_versioned=True, want_unversioned=False):
2410
specific_files=None, pb=None, extra_trees=None,
2411
require_versioned=True, want_unversioned=False):
2377
2412
"""See InterTree.iter_changes.
2379
2414
This has a fast path that is only used when the from_tree matches
2380
2415
the transform tree, and no fancy options are supplied.
2382
if (from_tree is not self._transform._tree or include_unchanged or
2383
specific_files or want_unversioned):
2417
if (from_tree is not self._transform._tree or include_unchanged
2418
or specific_files or want_unversioned):
2384
2419
return tree.InterTree(from_tree, self).iter_changes(
2385
2420
include_unchanged=include_unchanged,
2386
2421
specific_files=specific_files,
2392
2427
raise ValueError('want_unversioned is not supported')
2393
2428
return self._transform.iter_changes()
2395
def get_file(self, path, file_id=None):
2430
def get_file(self, path):
2396
2431
"""See Tree.get_file"""
2398
file_id = self.path2id(path)
2432
file_id = self.path2id(path)
2399
2433
if not self._content_change(file_id):
2400
return self._transform._tree.get_file(path, file_id)
2401
trans_id = self._transform.trans_id_file_id(file_id)
2434
return self._transform._tree.get_file(path)
2435
trans_id = self._path2trans_id(path)
2402
2436
name = self._transform._limbo_name(trans_id)
2403
2437
return open(name, 'rb')
2405
def get_file_with_stat(self, path, file_id=None):
2406
return self.get_file(path, file_id), None
2439
def get_file_with_stat(self, path):
2440
return self.get_file(path), None
2408
def annotate_iter(self, path, file_id=None,
2442
def annotate_iter(self, path,
2409
2443
default_revision=_mod_revision.CURRENT_REVISION):
2411
file_id = self.path2id(path)
2444
file_id = self.path2id(path)
2412
2445
changes = self._iter_changes_cache.get(file_id)
2413
2446
if changes is None:
2416
changed_content, versioned, kind = (changes[2], changes[3],
2449
changed_content, versioned, kind = (
2450
changes.changed_content, changes.versioned, changes.kind)
2418
2451
if kind[1] is None:
2420
2453
get_old = (kind[0] == 'file' and versioned[0])
2422
2455
old_annotation = self._transform._tree.annotate_iter(
2423
path, file_id=file_id, default_revision=default_revision)
2456
path, default_revision=default_revision)
2425
2458
old_annotation = []
2426
2459
if changes is None:
2648
2669
trans_id = tt.create_path(entry.name, parent_id)
2649
2670
file_trans_id[file_id] = trans_id
2650
2671
tt.version_file(file_id, trans_id)
2651
executable = tree.is_executable(tree_path, file_id)
2672
executable = tree.is_executable(tree_path)
2653
2674
tt.set_executability(executable, trans_id)
2654
trans_data = (trans_id, tree_path, entry.text_sha1)
2655
deferred_contents.append((file_id, trans_data))
2675
trans_data = (trans_id, file_id,
2676
tree_path, entry.text_sha1)
2677
deferred_contents.append((tree_path, trans_data))
2657
2679
file_trans_id[file_id] = new_by_entry(
2658
tree_path, tt, entry, parent_id, tree)
2680
tree_path, tt, entry, parent_id, tree)
2660
2682
new_trans_id = file_trans_id[file_id]
2661
2683
old_parent = tt.trans_id_tree_path(tree_path)
2694
2716
new_desired_files = desired_files
2696
2718
iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
2697
unchanged = [(f, p[1]) for (f, p, c, v, d, n, k, e)
2698
in iter if not (c or e[0] != e[1])]
2720
change.path for change in iter
2721
if not (change.changed_content or change.executable[0] != change.executable[1])]
2699
2722
if accelerator_tree.supports_content_filtering():
2700
unchanged = [(f, p) for (f, p) in unchanged
2701
if not next(accelerator_tree.iter_search_rules([p]))]
2723
unchanged = [(tp, ap) for (tp, ap) in unchanged
2724
if not next(accelerator_tree.iter_search_rules([ap]))]
2702
2725
unchanged = dict(unchanged)
2703
2726
new_desired_files = []
2705
for file_id, (trans_id, tree_path, text_sha1) in desired_files:
2706
accelerator_path = unchanged.get(file_id)
2728
for unused_tree_path, (trans_id, file_id, tree_path, text_sha1) in desired_files:
2729
accelerator_path = unchanged.get(tree_path)
2707
2730
if accelerator_path is None:
2708
new_desired_files.append((file_id,
2709
(trans_id, tree_path, text_sha1)))
2731
new_desired_files.append((tree_path,
2732
(trans_id, file_id, tree_path, text_sha1)))
2711
2734
pb.update(gettext('Adding file contents'), count + offset, total)
2713
2736
tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
2716
contents = accelerator_tree.get_file(accelerator_path, file_id)
2717
if wt.supports_content_filtering():
2718
filters = wt._content_filter_stack(tree_path)
2719
contents = filtered_output_bytes(contents, filters,
2720
ContentFilterContext(tree_path, tree))
2722
tt.create_file(contents, trans_id, sha1=text_sha1)
2726
except AttributeError:
2727
# after filtering, contents may no longer be file-like
2739
with accelerator_tree.get_file(accelerator_path) as f:
2740
chunks = osutils.file_iterator(f)
2741
if wt.supports_content_filtering():
2742
filters = wt._content_filter_stack(tree_path)
2743
chunks = filtered_output_bytes(chunks, filters,
2744
ContentFilterContext(tree_path, tree))
2745
tt.create_file(chunks, trans_id, sha1=text_sha1)
2730
2747
offset += count
2731
for count, ((trans_id, tree_path, text_sha1), contents) in enumerate(
2748
for count, ((trans_id, file_id, tree_path, text_sha1), contents) in enumerate(
2732
2749
tree.iter_files_bytes(new_desired_files)):
2733
2750
if wt.supports_content_filtering():
2734
2751
filters = wt._content_filter_stack(tree_path)
2735
2752
contents = filtered_output_bytes(contents, filters,
2736
ContentFilterContext(tree_path, tree))
2753
ContentFilterContext(tree_path, tree))
2737
2754
tt.create_file(contents, trans_id, sha1=text_sha1)
2738
2755
pb.update(gettext('Adding file contents'), count + offset, total)
2804
2818
name = entry.name
2805
2819
kind = entry.kind
2806
2820
if kind == 'file':
2807
contents = tree.get_file(path, entry.file_id).readlines()
2808
executable = tree.is_executable(path, entry.file_id)
2809
return tt.new_file(name, parent_id, contents, entry.file_id,
2821
with tree.get_file(path) as f:
2822
executable = tree.is_executable(path)
2824
name, parent_id, osutils.file_iterator(f), entry.file_id,
2811
2826
elif kind in ('directory', 'tree-reference'):
2812
2827
trans_id = tt.new_directory(name, parent_id, entry.file_id)
2813
2828
if kind == 'tree-reference':
2814
2829
tt.set_tree_reference(entry.reference_revision, trans_id)
2815
2830
return trans_id
2816
2831
elif kind == 'symlink':
2817
target = tree.get_symlink_target(path, entry.file_id)
2832
target = tree.get_symlink_target(path)
2818
2833
return tt.new_symlink(name, parent_id, target, entry.file_id)
2820
2835
raise errors.BadFileKindError(name, kind)
2823
def create_from_tree(tt, trans_id, tree, path, file_id=None, bytes=None,
2824
filter_tree_path=None):
2838
def create_from_tree(tt, trans_id, tree, path, chunks=None,
2839
filter_tree_path=None):
2825
2840
"""Create new file contents according to tree contents.
2827
2842
:param filter_tree_path: the tree path to use to lookup
2828
2843
content filters to apply to the bytes output in the working tree.
2829
2844
This only applies if the working tree supports content filtering.
2831
kind = tree.kind(path, file_id)
2846
kind = tree.kind(path)
2832
2847
if kind == 'directory':
2833
2848
tt.create_directory(trans_id)
2834
2849
elif kind == "file":
2836
tree_file = tree.get_file(path, file_id)
2838
bytes = tree_file.readlines()
2842
if wt.supports_content_filtering() and filter_tree_path is not None:
2843
filters = wt._content_filter_stack(filter_tree_path)
2844
bytes = filtered_output_bytes(bytes, filters,
2845
ContentFilterContext(filter_tree_path, tree))
2846
tt.create_file(bytes, trans_id)
2851
f = tree.get_file(path)
2852
chunks = osutils.file_iterator(f)
2857
if wt.supports_content_filtering() and filter_tree_path is not None:
2858
filters = wt._content_filter_stack(filter_tree_path)
2859
chunks = filtered_output_bytes(
2861
ContentFilterContext(filter_tree_path, tree))
2862
tt.create_file(chunks, trans_id)
2847
2866
elif kind == "symlink":
2848
tt.create_symlink(tree.get_symlink_target(path, file_id), trans_id)
2867
tt.create_symlink(tree.get_symlink_target(path), trans_id)
2850
2869
raise AssertionError('Unknown kind %r' % kind)
2859
2878
def revert(working_tree, target_tree, filenames, backups=False,
2860
2879
pb=None, change_reporter=None):
2861
2880
"""Revert a working tree's contents to those of a target tree."""
2862
target_tree.lock_read()
2863
2881
pb = ui.ui_factory.nested_progress_bar()
2864
tt = TreeTransform(working_tree, pb)
2866
pp = ProgressPhase("Revert phase", 3, pb)
2867
conflicts, merge_modified = _prepare_revert_transform(
2868
working_tree, target_tree, tt, filenames, backups, pp)
2870
change_reporter = delta._ChangeReporter(
2871
unversioned_filter=working_tree.is_ignored)
2872
delta.report_changes(tt.iter_changes(), change_reporter)
2873
for conflict in conflicts:
2874
trace.warning(unicode(conflict))
2877
working_tree.set_merge_modified(merge_modified)
2883
with target_tree.lock_read(), working_tree.get_transform(pb) as tt:
2884
pp = ProgressPhase("Revert phase", 3, pb)
2885
conflicts, merge_modified = _prepare_revert_transform(
2886
working_tree, target_tree, tt, filenames, backups, pp)
2888
change_reporter = delta._ChangeReporter(
2889
unversioned_filter=working_tree.is_ignored)
2890
delta.report_changes(tt.iter_changes(), change_reporter)
2891
for conflict in conflicts:
2892
trace.warning(str(conflict))
2895
if working_tree.supports_merge_modified():
2896
working_tree.set_merge_modified(merge_modified)
2879
target_tree.unlock()
2882
2899
return conflicts
2885
2902
def _prepare_revert_transform(working_tree, target_tree, tt, filenames,
2886
2903
backups, pp, basis_tree=None,
2887
2904
merge_modified=None):
2888
child_pb = ui.ui_factory.nested_progress_bar()
2905
with ui.ui_factory.nested_progress_bar() as child_pb:
2890
2906
if merge_modified is None:
2891
2907
merge_modified = working_tree.merge_modified()
2892
2908
merge_modified = _alter_files(working_tree, target_tree, tt,
2893
2909
child_pb, filenames, backups,
2894
2910
merge_modified, basis_tree)
2897
child_pb = ui.ui_factory.nested_progress_bar()
2899
raw_conflicts = resolve_conflicts(tt, child_pb,
2900
lambda t, c: conflict_pass(t, c, target_tree))
2911
with ui.ui_factory.nested_progress_bar() as child_pb:
2912
raw_conflicts = resolve_conflicts(
2913
tt, child_pb, lambda t, c: conflict_pass(t, c, target_tree))
2903
2914
conflicts = cook_conflicts(raw_conflicts, tt)
2904
2915
return conflicts, merge_modified
2912
2923
# than the target changes relative to the working tree. Because WT4 has an
2913
2924
# optimizer to compare itself to a target, but no optimizer for the
2915
change_list = working_tree.iter_changes(target_tree,
2916
specific_files=specific_files, pb=pb)
2917
if target_tree.get_root_id() is None:
2926
change_list = working_tree.iter_changes(
2927
target_tree, specific_files=specific_files, pb=pb)
2928
if not target_tree.is_versioned(u''):
2918
2929
skip_root = True
2920
2931
skip_root = False
2922
2933
deferred_files = []
2923
for id_num, (file_id, path, changed_content, versioned, parent, name,
2924
kind, executable) in enumerate(change_list):
2925
target_path, wt_path = path
2926
target_versioned, wt_versioned = versioned
2927
target_parent, wt_parent = parent
2928
target_name, wt_name = name
2929
target_kind, wt_kind = kind
2930
target_executable, wt_executable = executable
2934
for id_num, change in enumerate(change_list):
2935
file_id = change.file_id
2936
target_path, wt_path = change.path
2937
target_versioned, wt_versioned = change.versioned
2938
target_parent, wt_parent = change.parent_id
2939
target_name, wt_name = change.name
2940
target_kind, wt_kind = change.kind
2941
target_executable, wt_executable = change.executable
2931
2942
if skip_root and wt_parent is None:
2933
2944
trans_id = tt.trans_id_file_id(file_id)
2946
if change.changed_content:
2936
2947
keep_content = False
2937
2948
if wt_kind == 'file' and (backups or target_kind is None):
2938
wt_sha1 = working_tree.get_file_sha1(wt_path, file_id)
2939
if merge_modified.get(file_id) != wt_sha1:
2949
wt_sha1 = working_tree.get_file_sha1(wt_path)
2950
if merge_modified.get(wt_path) != wt_sha1:
2940
2951
# acquire the basis tree lazily to prevent the
2941
2952
# expense of accessing it when it's not needed ?
2942
2953
# (Guessing, RBC, 200702)
2943
2954
if basis_tree is None:
2944
2955
basis_tree = working_tree.basis_tree()
2945
2956
basis_tree.lock_read()
2947
basis_path = basis_tree.id2path(file_id)
2948
except errors.NoSuchId:
2957
basis_inter = InterTree.get(basis_tree, working_tree)
2958
basis_path = basis_inter.find_source_path(wt_path)
2959
if basis_path is None:
2949
2960
if target_kind is None and not target_versioned:
2950
2961
keep_content = True
2952
if wt_sha1 != basis_tree.get_file_sha1(basis_path, file_id):
2963
if wt_sha1 != basis_tree.get_file_sha1(basis_path):
2953
2964
keep_content = True
2954
2965
if wt_kind is not None:
2955
2966
if not keep_content:
2971
2982
tt.create_directory(trans_id)
2972
2983
if target_kind == 'tree-reference':
2973
2984
revision = target_tree.get_reference_revision(
2974
target_path, file_id)
2975
2986
tt.set_tree_reference(revision, trans_id)
2976
2987
elif target_kind == 'symlink':
2977
2988
tt.create_symlink(target_tree.get_symlink_target(
2978
target_path, file_id), trans_id)
2989
target_path), trans_id)
2979
2990
elif target_kind == 'file':
2980
deferred_files.append((file_id, (trans_id, mode_id)))
2991
deferred_files.append(
2992
(target_path, (trans_id, mode_id, file_id)))
2981
2993
if basis_tree is None:
2982
2994
basis_tree = working_tree.basis_tree()
2983
2995
basis_tree.lock_read()
2984
new_sha1 = target_tree.get_file_sha1(target_path, file_id)
2986
basis_path = basis_tree.id2path(file_id)
2987
except errors.NoSuchId:
2996
new_sha1 = target_tree.get_file_sha1(target_path)
2997
basis_inter = InterTree.get(basis_tree, target_tree)
2998
basis_path = basis_inter.find_source_path(target_path)
2989
2999
if (basis_path is not None and
2990
new_sha1 == basis_tree.get_file_sha1(basis_path, file_id)):
2991
if file_id in merge_modified:
2992
del merge_modified[file_id]
3000
new_sha1 == basis_tree.get_file_sha1(basis_path)):
3001
# If the new contents of the file match what is in basis,
3002
# then there is no need to store in merge_modified.
3003
if basis_path in merge_modified:
3004
del merge_modified[basis_path]
2994
merge_modified[file_id] = new_sha1
3006
merge_modified[target_path] = new_sha1
2996
3008
# preserve the execute bit when backing up
2997
3009
if keep_content and wt_executable == target_executable:
3015
3027
if wt_executable != target_executable and target_kind == "file":
3016
3028
tt.set_executability(target_executable, trans_id)
3017
3029
if working_tree.supports_content_filtering():
3018
for index, ((trans_id, mode_id), bytes) in enumerate(
3019
target_tree.iter_files_bytes(deferred_files)):
3020
file_id = deferred_files[index][0]
3030
for (trans_id, mode_id, file_id), bytes in (
3031
target_tree.iter_files_bytes(deferred_files)):
3021
3032
# We're reverting a tree to the target tree so using the
3022
3033
# target tree to find the file path seems the best choice
3023
3034
# here IMO - Ian C 27/Oct/2009
3024
3035
filter_tree_path = target_tree.id2path(file_id)
3025
3036
filters = working_tree._content_filter_stack(filter_tree_path)
3026
bytes = filtered_output_bytes(bytes, filters,
3037
bytes = filtered_output_bytes(
3027
3039
ContentFilterContext(filter_tree_path, working_tree))
3028
3040
tt.create_file(bytes, trans_id, mode_id)
3030
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
3042
for (trans_id, mode_id, file_id), bytes in target_tree.iter_files_bytes(
3032
3044
tt.create_file(bytes, trans_id, mode_id)
3033
3045
tt.fixup_new_roots()
3248
3256
:param target_tree: Tree to change
3249
3257
:param source_tree: Tree to hard-link from
3251
tt = TreeTransform(target_tree)
3253
for (file_id, paths, changed_content, versioned, parent, name, kind,
3254
executable) in target_tree.iter_changes(source_tree,
3255
include_unchanged=True):
3258
if kind != ('file', 'file'):
3260
if executable[0] != executable[1]:
3262
trans_id = tt.trans_id_tree_file_id(file_id)
3259
with target_tree.get_transform() as tt:
3260
for change in target_tree.iter_changes(source_tree, include_unchanged=True):
3261
if change.changed_content:
3263
if change.kind != ('file', 'file'):
3265
if change.executable[0] != change.executable[1]:
3267
trans_id = tt.trans_id_tree_path(change.path[1])
3263
3268
tt.delete_contents(trans_id)
3264
tt.create_hardlink(source_tree.id2abspath(file_id), trans_id)
3269
tt.create_hardlink(source_tree.abspath(change.path[0]), trans_id)