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 (
260
259
self.unversion_file(old_new_root)
261
260
# if, at this stage, root still has an old file_id, zap it so we can
262
261
# stick a new one in.
263
if (self.tree_file_id(self._new_root) is not None
264
and self._new_root not in self._removed_id):
262
if (self.tree_file_id(self._new_root) is not None and
263
self._new_root not in self._removed_id):
265
264
self.unversion_file(self._new_root)
266
265
if file_id is not None:
267
266
self.version_file(file_id, self._new_root)
724
721
def _duplicate_ids(self):
725
722
"""Each inventory id may only be used once"""
728
all_ids = self._tree.all_file_ids()
729
except errors.UnsupportedOperation:
730
# it's okay for non-file-id trees to raise UnsupportedOperation.
732
724
removed_tree_ids = set((self.tree_file_id(trans_id) for trans_id in
733
725
self._removed_id))
726
all_ids = self._tree.all_file_ids()
734
727
active_tree_ids = all_ids.difference(removed_tree_ids)
735
728
for trans_id, file_id in viewitems(self._new_id):
736
729
if file_id in active_tree_ids:
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.
1067
1055
parent_ids.extend(merge_parents)
1068
1056
if self._tree.get_revision_id() != last_rev_id:
1069
1057
raise ValueError('TreeTransform not based on branch basis: %s' %
1070
self._tree.get_revision_id().decode('utf-8'))
1058
self._tree.get_revision_id())
1071
1059
revprops = commit.Commit.update_revprops(revprops, branch, authors)
1072
1060
builder = branch.get_commit_builder(parent_ids,
1073
1061
timestamp=timestamp,
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
1109
b'_id_number': self._id_number,
1132
1110
b'_new_name': new_name,
1133
b'_new_parent': new_parent,
1111
b'_new_parent': self._new_parent,
1134
1112
b'_new_executability': new_executability,
1113
b'_new_id': self._new_id,
1136
1114
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,
1115
b'_removed_id': list(self._removed_id),
1116
b'_removed_contents': list(self._removed_contents),
1117
b'_non_present_ids': self._non_present_ids,
1141
1119
yield serializer.bytes_record(bencode.bencode(attribs),
1142
1120
((b'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
1123
with open(self._limbo_name(trans_id), 'rb') as cur_file:
1146
1124
lines = cur_file.readlines()
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
1142
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)}
1143
self._new_name = dict((k, v.decode('utf-8'))
1144
for k, v in viewitems(attribs[b'_new_name']))
1145
self._new_parent = attribs[b'_new_parent']
1146
self._new_executability = dict((k, bool(v))
1147
for k, v in viewitems(attribs[b'_new_executability']))
1148
self._new_id = attribs[b'_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
1152
for bytepath, trans_id in viewitems(attribs[b'_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[b'_removed_id'])
1157
self._removed_contents = set(attribs[b'_removed_contents'])
1158
self._non_present_ids = attribs[b'_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
1365
target is a bytestring.
1440
1366
See also new_symlink.
1442
if self._create_symlinks:
1443
1369
os.symlink(target, self._limbo_name(trans_id))
1370
unique_add(self._new_contents, trans_id, 'symlink')
1446
1373
path = FinalPaths(self).get_path(trans_id)
1447
1374
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')
1376
raise UnableCreateSymlink(path=path)
1456
1378
def cancel_creation(self, trans_id):
1457
1379
"""Cancel the creation of new file contents."""
1821
1743
with ui.ui_factory.nested_progress_bar() as child_pb:
1822
1744
for num, trans_id in enumerate(self._removed_id):
1823
1745
if (num % 10) == 0:
1824
child_pb.update(gettext('removing file'),
1746
child_pb.update(gettext('removing file'), num, total_entries)
1826
1747
if trans_id == self._new_root:
1827
file_id = self._tree.path2id('')
1748
file_id = self._tree.get_root_id()
1829
1750
file_id = self.tree_file_id(trans_id)
1830
1751
# File-id isn't really being deleted, just moved
1889
1813
if trans_id in self._removed_contents:
1890
1814
delete_path = os.path.join(self._deletiondir, trans_id)
1891
1815
mover.pre_delete(full_path, delete_path)
1892
elif (trans_id in self._new_name or
1893
trans_id in self._new_parent):
1816
elif (trans_id in self._new_name
1817
or trans_id in self._new_parent):
1895
1819
mover.rename(full_path, self._limbo_name(trans_id))
1896
1820
except errors.TransformRenameFailed as e:
1912
1836
new_paths = self.new_paths(filesystem_only=True)
1913
1837
modified_paths = []
1838
new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1914
1840
with ui.ui_factory.nested_progress_bar() as child_pb:
1915
1841
for num, (path, trans_id) in enumerate(new_paths):
1916
1842
if (num % 10) == 0:
1917
child_pb.update(gettext('adding file'),
1918
num, len(new_paths))
1843
child_pb.update(gettext('adding file'), num, len(new_paths))
1919
1844
full_path = self._tree.abspath(path)
1920
1845
if trans_id in self._needs_rename:
1929
1854
# TODO: if trans_id in self._observed_sha1s, we should
1930
1855
# re-stat the final target, since ctime will be
1931
1856
# updated by the change.
1932
if (trans_id in self._new_contents
1933
or self.path_changed(trans_id)):
1857
if (trans_id in self._new_contents or
1858
self.path_changed(trans_id)):
1934
1859
if trans_id in self._new_contents:
1935
1860
modified_paths.append(full_path)
1936
1861
if trans_id in self._new_executability:
1965
1890
paths = FinalPaths(self)
1966
1891
for trans_id, observed in viewitems(self._observed_sha1s):
1967
1892
path = paths.get_path(trans_id)
1968
self._tree._observed_sha1(path, observed)
1893
# We could get the file_id, but dirstate prefers to use the path
1894
# anyway, and it is 'cheaper' to determine.
1895
# file_id = self._new_id[trans_id]
1896
self._tree._observed_sha1(None, path, observed)
1971
1899
class TransformPreview(DiskTreeTransform):
2033
1958
self._all_children_cache = {}
2034
1959
self._path2trans_id_cache = {}
2035
1960
self._final_name_cache = {}
2036
self._iter_changes_cache = dict((c.file_id, c) for c in
1961
self._iter_changes_cache = dict((c[0], c) for c in
2037
1962
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
1964
def _content_change(self, file_id):
2045
1965
"""Return True if the content of this file changed"""
2046
1966
changes = self._iter_changes_cache.get(file_id)
2047
return (changes is not None and changes.changed_content)
1967
# changes[2] is true if the file content changed. See
1968
# InterTree.iter_changes.
1969
return (changes is not None and changes[2])
2049
1971
def _get_repository(self):
2050
1972
repo = getattr(self._transform._tree, '_repository', None)
2062
1984
def _get_file_revision(self, path, file_id, vf, tree_revision):
2063
1985
parent_keys = [
2064
(file_id, t.get_file_revision(t.id2path(file_id)))
2065
for t in self._iter_parent_trees()]
1986
(file_id, t.get_file_revision(t.id2path(file_id), file_id))
1987
for t in self._iter_parent_trees()]
2066
1988
vf.add_lines((file_id, tree_revision), parent_keys,
2067
self.get_file_lines(path))
1989
self.get_file_lines(path, file_id))
2068
1990
repo = self._get_repository()
2069
1991
base_vf = repo.texts
2070
1992
if base_vf not in vf.fallback_versionedfiles:
2114
2039
return tree_ids
2116
2041
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)
2042
return {self.id2path(fid) for fid in self.all_file_ids()}
2044
def _has_id(self, file_id, fallback_check):
2045
if file_id in self._transform._r_new_id:
2047
elif file_id in {self._transform.tree_file_id(trans_id) for
2048
trans_id in self._transform._removed_id}:
2051
return fallback_check(file_id)
2053
def has_id(self, file_id):
2054
return self._has_id(file_id, self._transform._tree.has_id)
2056
def has_or_had_id(self, file_id):
2057
return self._has_id(file_id, self._transform._tree.has_or_had_id)
2129
2059
def _path2trans_id(self, path):
2130
2060
# We must not use None here, because that is a valid value to store.
2187
2122
file_id = self._transform.final_file_id(trans_id)
2188
2123
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):
2125
if (specific_files is not None and
2126
self._final_paths.get_path(trans_id) not in specific_files):
2193
2128
kind = self._transform.final_kind(trans_id)
2194
2129
if kind is None:
2195
2130
kind = self._transform._tree.stored_kind(
2196
self._transform._tree.id2path(file_id))
2131
self._transform._tree.id2path(file_id),
2197
2133
new_entry = inventory.make_entry(
2199
2135
self._transform.final_name(trans_id),
2223
2159
for entry, trans_id in self._make_inv_entries(todo):
2226
def iter_entries_by_dir(self, specific_files=None, recurse_nested=False):
2228
raise NotImplementedError(
2229
'follow tree references not yet supported')
2162
def iter_entries_by_dir(self, specific_files=None):
2231
2163
# This may not be a maximally efficient implementation, but it is
2232
2164
# reasonably straightforward. An implementation that grafts the
2233
2165
# TreeTransform changes onto the tree's iter_entries_by_dir results
2245
2177
dir_id = self._transform.final_file_id(dir_trans_id)
2246
2178
for child_trans_id in self._all_children(dir_trans_id):
2247
2179
ordered_ids.append((child_trans_id, dir_id))
2249
2180
for entry, trans_id in self._make_inv_entries(ordered_ids):
2250
path_entries.append((self._final_paths.get_path(trans_id), entry))
2181
yield 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):
2183
def list_files(self, include_root=False, from_dir=None, recursive=True):
2256
2184
"""See WorkingTree.list_files."""
2258
raise NotImplementedError(
2259
'follow tree references not yet supported')
2261
2185
# XXX This should behave like WorkingTree.list_files, but is really
2262
2186
# more like RevisionTree.list_files.
2274
2196
if not path.startswith(prefix):
2276
2198
path = path[len(prefix):]
2277
yield path, 'V', entry.kind, entry
2199
yield path, 'V', entry.kind, entry.file_id, entry
2279
2201
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
2202
root_entry = inventory.make_entry('directory', '',
2203
ROOT_PARENT, self.get_root_id())
2204
yield '', 'V', 'directory', root_entry.file_id, root_entry
2283
2205
entries = self._iter_entries_for_dir(from_dir or '')
2284
2206
for path, entry in entries:
2285
yield path, 'V', entry.kind, entry
2207
yield path, 'V', entry.kind, entry.file_id, entry
2287
def kind(self, path):
2209
def kind(self, path, file_id=None):
2288
2210
trans_id = self._path2trans_id(path)
2289
2211
if trans_id is None:
2290
2212
raise errors.NoSuchFile(path)
2291
2213
return self._transform.final_kind(trans_id)
2293
def stored_kind(self, path):
2215
def stored_kind(self, path, file_id=None):
2294
2216
trans_id = self._path2trans_id(path)
2295
2217
if trans_id is None:
2296
2218
raise errors.NoSuchFile(path)
2298
2220
return self._transform._new_contents[trans_id]
2299
2221
except KeyError:
2300
return self._transform._tree.stored_kind(path)
2222
return self._transform._tree.stored_kind(path, file_id)
2302
def get_file_mtime(self, path):
2224
def get_file_mtime(self, path, file_id=None):
2303
2225
"""See Tree.get_file_mtime"""
2304
file_id = self.path2id(path)
2227
file_id = self.path2id(path)
2305
2228
if file_id is None:
2306
2229
raise errors.NoSuchFile(path)
2307
2230
if not self._content_change(file_id):
2308
2231
return self._transform._tree.get_file_mtime(
2309
self._transform._tree.id2path(file_id))
2232
self._transform._tree.id2path(file_id), file_id)
2310
2233
trans_id = self._path2trans_id(path)
2311
2234
return self._stat_limbo_file(trans_id).st_mtime
2313
def get_file_size(self, path):
2236
def get_file_size(self, path, file_id=None):
2314
2237
"""See Tree.get_file_size"""
2315
2238
trans_id = self._path2trans_id(path)
2316
2239
if trans_id is None:
2321
2244
if trans_id in self._transform._new_contents:
2322
2245
return self._stat_limbo_file(trans_id).st_size
2323
if self.kind(path) == 'file':
2324
return self._transform._tree.get_file_size(path)
2246
if self.kind(path, file_id) == 'file':
2247
return self._transform._tree.get_file_size(path, file_id)
2328
def get_file_verifier(self, path, stat_value=None):
2251
def get_file_verifier(self, path, file_id=None, stat_value=None):
2329
2252
trans_id = self._path2trans_id(path)
2330
2253
if trans_id is None:
2331
2254
raise errors.NoSuchFile(path)
2332
2255
kind = self._transform._new_contents.get(trans_id)
2333
2256
if kind is None:
2334
return self._transform._tree.get_file_verifier(path)
2257
return self._transform._tree.get_file_verifier(path, file_id)
2335
2258
if kind == 'file':
2336
with self.get_file(path) as fileobj:
2259
with self.get_file(path, file_id) as fileobj:
2337
2260
return ("SHA1", sha_file(fileobj))
2339
def get_file_sha1(self, path, stat_value=None):
2262
def get_file_sha1(self, path, file_id=None, stat_value=None):
2340
2263
trans_id = self._path2trans_id(path)
2341
2264
if trans_id is None:
2342
2265
raise errors.NoSuchFile(path)
2343
2266
kind = self._transform._new_contents.get(trans_id)
2344
2267
if kind is None:
2345
return self._transform._tree.get_file_sha1(path)
2268
return self._transform._tree.get_file_sha1(path, file_id)
2346
2269
if kind == 'file':
2347
with self.get_file(path) as fileobj:
2270
with self.get_file(path, file_id) as fileobj:
2348
2271
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):
2273
def is_executable(self, path, file_id=None):
2360
2274
trans_id = self._path2trans_id(path)
2361
2275
if trans_id is None:
2408
2322
executable = None
2409
2323
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)
2324
link_or_sha1 = os.readlink(limbo_name).decode(osutils._fs_enc)
2413
2325
executable = tt._new_executability.get(trans_id, executable)
2414
2326
return kind, size, executable, link_or_sha1
2416
2328
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):
2329
specific_files=None, pb=None, extra_trees=None,
2330
require_versioned=True, want_unversioned=False):
2419
2331
"""See InterTree.iter_changes.
2421
2333
This has a fast path that is only used when the from_tree matches
2422
2334
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):
2336
if (from_tree is not self._transform._tree or include_unchanged or
2337
specific_files or want_unversioned):
2426
2338
return tree.InterTree(from_tree, self).iter_changes(
2427
2339
include_unchanged=include_unchanged,
2428
2340
specific_files=specific_files,
2434
2346
raise ValueError('want_unversioned is not supported')
2435
2347
return self._transform.iter_changes()
2437
def get_file(self, path):
2349
def get_file(self, path, file_id=None):
2438
2350
"""See Tree.get_file"""
2439
file_id = self.path2id(path)
2352
file_id = self.path2id(path)
2440
2353
if not self._content_change(file_id):
2441
return self._transform._tree.get_file(path)
2354
return self._transform._tree.get_file(path, file_id)
2442
2355
trans_id = self._path2trans_id(path)
2443
2356
name = self._transform._limbo_name(trans_id)
2444
2357
return open(name, 'rb')
2446
def get_file_with_stat(self, path):
2447
return self.get_file(path), None
2359
def get_file_with_stat(self, path, file_id=None):
2360
return self.get_file(path, file_id), None
2449
def annotate_iter(self, path,
2362
def annotate_iter(self, path, file_id=None,
2450
2363
default_revision=_mod_revision.CURRENT_REVISION):
2451
file_id = self.path2id(path)
2365
file_id = self.path2id(path)
2452
2366
changes = self._iter_changes_cache.get(file_id)
2453
2367
if changes is None:
2456
changed_content, versioned, kind = (
2457
changes.changed_content, changes.versioned, changes.kind)
2370
changed_content, versioned, kind = (changes[2], changes[3],
2458
2372
if kind[1] is None:
2460
2374
get_old = (kind[0] == 'file' and versioned[0])
2462
2376
old_annotation = self._transform._tree.annotate_iter(
2463
path, default_revision=default_revision)
2377
path, file_id=file_id, default_revision=default_revision)
2465
2379
old_annotation = []
2466
2380
if changes is None:
2475
2389
# It would be nice to be able to use the new Annotator based
2476
2390
# approach, as well.
2477
2391
return annotate.reannotate([old_annotation],
2478
self.get_file(path).readlines(),
2392
self.get_file(path, file_id).readlines(),
2479
2393
default_revision)
2481
def get_symlink_target(self, path):
2395
def get_symlink_target(self, path, file_id=None):
2482
2396
"""See Tree.get_symlink_target"""
2483
file_id = self.path2id(path)
2398
file_id = self.path2id(path)
2484
2399
if not self._content_change(file_id):
2485
2400
return self._transform._tree.get_symlink_target(path)
2486
2401
trans_id = self._path2trans_id(path)
2500
2415
path_from_root = self._final_paths.get_path(child_id)
2501
2416
basename = self._transform.final_name(child_id)
2502
2417
file_id = self._transform.final_file_id(child_id)
2503
kind = self._transform.final_kind(child_id)
2418
kind = self._transform.final_kind(child_id)
2504
2419
if kind is not None:
2505
2420
versioned_kind = kind
2507
2422
kind = 'unknown'
2508
2423
versioned_kind = self._transform._tree.stored_kind(
2509
self._transform._tree.id2path(file_id))
2424
self._transform._tree.id2path(file_id),
2510
2426
if versioned_kind == 'directory':
2511
2427
subdirs.append(child_id)
2512
2428
children.append((path_from_root, basename, kind, None,
2567
2482
return [(self.get_path(t), t) for t in trans_ids]
2486
def topology_sorted_ids(tree):
2487
"""Determine the topological order of the ids in a tree"""
2488
file_ids = list(tree)
2489
file_ids.sort(key=tree.id2path)
2570
2493
def build_tree(tree, wt, accelerator_tree=None, hardlink=False,
2571
2494
delta_from_tree=False):
2572
2495
"""Create working tree for a branch, using a TreeTransform.
2593
2516
:param delta_from_tree: If true, build_tree may use the input Tree to
2594
2517
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())
2519
with wt.lock_tree_write(), tree.lock_read():
2599
2520
if accelerator_tree is not None:
2600
exit_stack.enter_context(accelerator_tree.lock_read())
2601
return _build_tree(tree, wt, accelerator_tree, hardlink,
2521
accelerator_tree.lock_read()
2523
return _build_tree(tree, wt, accelerator_tree, hardlink,
2526
if accelerator_tree is not None:
2527
accelerator_tree.unlock()
2605
2530
def _build_tree(tree, wt, accelerator_tree, hardlink, delta_from_tree):
2618
2543
# is set within the tree, nor setting the root and thus
2619
2544
# marking the tree as dirty, because we use two different
2620
2545
# idioms here: tree interfaces and inventory interfaces.
2621
if wt.path2id('') != tree.path2id(''):
2622
wt.set_root_id(tree.path2id(''))
2546
if wt.get_root_id() != tree.get_root_id():
2547
wt.set_root_id(tree.get_root_id())
2624
tt = wt.get_transform()
2549
tt = TreeTransform(wt)
2627
2552
pp.next_phase()
2628
file_trans_id[wt.path2id('')] = tt.trans_id_tree_path('')
2553
file_trans_id[wt.get_root_id()] = tt.trans_id_tree_path('')
2629
2554
with ui.ui_factory.nested_progress_bar() as pb:
2630
2555
deferred_contents = []
2644
2569
for dir, files in wt.walkdirs():
2645
2570
existing_files.update(f[0] for f in files)
2646
2571
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)
2572
enumerate(tree.iter_entries_by_dir()):
2573
pb.update(gettext("Building tree"), num - len(deferred_contents), total)
2650
2574
if entry.parent_id is None:
2652
2576
reparent = False
2665
2589
divert.add(file_id)
2666
if (file_id not in divert
2668
tree, entry, tree_path, kind, target_path)):
2590
if (file_id not in divert and
2591
_content_match(tree, entry, tree_path, file_id, kind,
2669
2593
tt.delete_contents(tt.trans_id_tree_path(tree_path))
2670
2594
if kind == 'directory':
2671
2595
reparent = True
2676
2600
trans_id = tt.create_path(entry.name, parent_id)
2677
2601
file_trans_id[file_id] = trans_id
2678
2602
tt.version_file(file_id, trans_id)
2679
executable = tree.is_executable(tree_path)
2603
executable = tree.is_executable(tree_path, file_id)
2681
2605
tt.set_executability(executable, trans_id)
2682
trans_data = (trans_id, file_id,
2683
tree_path, entry.text_sha1)
2606
trans_data = (trans_id, file_id, tree_path, entry.text_sha1)
2684
2607
deferred_contents.append((tree_path, trans_data))
2686
2609
file_trans_id[file_id] = new_by_entry(
2687
tree_path, tt, entry, parent_id, tree)
2610
tree_path, tt, entry, parent_id, tree)
2689
2612
new_trans_id = file_trans_id[file_id]
2690
2613
old_parent = tt.trans_id_tree_path(tree_path)
2694
2617
accelerator_tree, hardlink)
2695
2618
pp.next_phase()
2696
2619
divert_trans = set(file_trans_id[f] for f in divert)
2699
return resolve_checkout(t, c, divert_trans)
2620
resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
2700
2621
raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
2701
2622
if len(raw_conflicts) > 0:
2702
2623
precomputed_delta = None
2723
2644
new_desired_files = desired_files
2725
2646
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])]
2647
unchanged = [(p[0], p[1]) for (f, p, c, v, d, n, k, e)
2648
in iter if not (c or e[0] != e[1])]
2729
2649
if accelerator_tree.supports_content_filtering():
2730
2650
unchanged = [(tp, ap) for (tp, ap) in unchanged
2731
2651
if not next(accelerator_tree.iter_search_rules([ap]))]
2736
2656
accelerator_path = unchanged.get(tree_path)
2737
2657
if accelerator_path is None:
2738
2658
new_desired_files.append((tree_path,
2739
(trans_id, file_id, tree_path, text_sha1)))
2659
(trans_id, file_id, tree_path, text_sha1)))
2741
2661
pb.update(gettext('Adding file contents'), count + offset, total)
2743
2663
tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
2746
with accelerator_tree.get_file(accelerator_path) as f:
2666
with accelerator_tree.get_file(accelerator_path, file_id) as f:
2747
2667
chunks = osutils.file_iterator(f)
2748
2668
if wt.supports_content_filtering():
2749
2669
filters = wt._content_filter_stack(tree_path)
2750
2670
chunks = filtered_output_bytes(chunks, filters,
2751
ContentFilterContext(tree_path, tree))
2671
ContentFilterContext(tree_path, tree))
2752
2672
tt.create_file(chunks, trans_id, sha1=text_sha1)
2754
2674
offset += count
2757
2677
if wt.supports_content_filtering():
2758
2678
filters = wt._content_filter_stack(tree_path)
2759
2679
contents = filtered_output_bytes(contents, filters,
2760
ContentFilterContext(tree_path, tree))
2680
ContentFilterContext(tree_path, tree))
2761
2681
tt.create_file(contents, trans_id, sha1=text_sha1)
2762
2682
pb.update(gettext('Adding file contents'), count + offset, total)
2774
2694
return by_parent[old_parent]
2777
def _content_match(tree, entry, tree_path, kind, target_path):
2697
def _content_match(tree, entry, tree_path, file_id, kind, target_path):
2778
2698
if entry.kind != kind:
2780
2700
if entry.kind == "directory":
2782
2702
if entry.kind == "file":
2783
2703
with open(target_path, 'rb') as f1, \
2784
tree.get_file(tree_path) as f2:
2704
tree.get_file(tree_path, file_id) as f2:
2785
2705
if osutils.compare_files(f1, f2):
2787
2707
elif entry.kind == "symlink":
2788
if tree.get_symlink_target(tree_path) == os.readlink(target_path):
2708
if tree.get_symlink_target(tree_path, file_id) == os.readlink(target_path):
2809
2729
final_parent = tt.final_parent(old_file)
2810
2730
if new_file in divert:
2811
new_name = tt.final_name(old_file) + '.diverted'
2731
new_name = tt.final_name(old_file)+'.diverted'
2812
2732
tt.adjust_path(new_name, final_parent, new_file)
2813
2733
new_conflicts.add((c_type, 'Diverted to',
2814
2734
new_file, old_file))
2816
new_name = tt.final_name(old_file) + '.moved'
2736
new_name = tt.final_name(old_file)+'.moved'
2817
2737
tt.adjust_path(new_name, final_parent, old_file)
2818
2738
new_conflicts.add((c_type, 'Moved existing file to',
2819
2739
old_file, new_file))
2825
2745
name = entry.name
2826
2746
kind = entry.kind
2827
2747
if kind == 'file':
2828
with tree.get_file(path) as f:
2829
executable = tree.is_executable(path)
2748
with tree.get_file(path, entry.file_id) as f:
2749
executable = tree.is_executable(path, entry.file_id)
2830
2750
return tt.new_file(
2831
name, parent_id, osutils.file_iterator(f), entry.file_id,
2751
name, parent_id, osutils.file_iterator(f), entry.file_id,
2833
2753
elif kind in ('directory', 'tree-reference'):
2834
2754
trans_id = tt.new_directory(name, parent_id, entry.file_id)
2835
2755
if kind == 'tree-reference':
2836
2756
tt.set_tree_reference(entry.reference_revision, trans_id)
2837
2757
return trans_id
2838
2758
elif kind == 'symlink':
2839
target = tree.get_symlink_target(path)
2759
target = tree.get_symlink_target(path, entry.file_id)
2840
2760
return tt.new_symlink(name, parent_id, target, entry.file_id)
2842
2762
raise errors.BadFileKindError(name, kind)
2845
def create_from_tree(tt, trans_id, tree, path, chunks=None,
2846
filter_tree_path=None):
2765
def create_from_tree(tt, trans_id, tree, path, file_id=None, chunks=None,
2766
filter_tree_path=None):
2847
2767
"""Create new file contents according to tree contents.
2849
2769
:param filter_tree_path: the tree path to use to lookup
2850
2770
content filters to apply to the bytes output in the working tree.
2851
2771
This only applies if the working tree supports content filtering.
2853
kind = tree.kind(path)
2773
kind = tree.kind(path, file_id)
2854
2774
if kind == 'directory':
2855
2775
tt.create_directory(trans_id)
2856
2776
elif kind == "file":
2857
2777
if chunks is None:
2858
f = tree.get_file(path)
2778
f = tree.get_file(path, file_id)
2859
2779
chunks = osutils.file_iterator(f)
2864
2784
if wt.supports_content_filtering() and filter_tree_path is not None:
2865
2785
filters = wt._content_filter_stack(filter_tree_path)
2866
chunks = filtered_output_bytes(
2786
chunks = filtered_output_bytes(chunks, filters,
2868
2787
ContentFilterContext(filter_tree_path, tree))
2869
2788
tt.create_file(chunks, trans_id)
2871
2790
if f is not None:
2873
2792
elif kind == "symlink":
2874
tt.create_symlink(tree.get_symlink_target(path), trans_id)
2793
tt.create_symlink(tree.get_symlink_target(path, file_id), trans_id)
2876
2795
raise AssertionError('Unknown kind %r' % kind)
2887
2806
"""Revert a working tree's contents to those of a target tree."""
2888
2807
pb = ui.ui_factory.nested_progress_bar()
2890
with target_tree.lock_read(), working_tree.get_transform(pb) as tt:
2809
with target_tree.lock_read(), TreeTransform(working_tree, pb) as tt:
2891
2810
pp = ProgressPhase("Revert phase", 3, pb)
2892
2811
conflicts, merge_modified = _prepare_revert_transform(
2893
2812
working_tree, target_tree, tt, filenames, backups, pp)
2916
2835
child_pb, filenames, backups,
2917
2836
merge_modified, basis_tree)
2918
2837
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))
2838
raw_conflicts = resolve_conflicts(tt, child_pb,
2839
lambda t, c: conflict_pass(t, c, target_tree))
2921
2840
conflicts = cook_conflicts(raw_conflicts, tt)
2922
2841
return conflicts, merge_modified
2930
2849
# than the target changes relative to the working tree. Because WT4 has an
2931
2850
# 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)
2852
change_list = working_tree.iter_changes(target_tree,
2853
specific_files=specific_files, pb=pb)
2935
2854
if not target_tree.is_versioned(u''):
2936
2855
skip_root = True
2938
2857
skip_root = False
2940
2859
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
2860
for id_num, (file_id, path, changed_content, versioned, parent, name,
2861
kind, executable) in enumerate(change_list):
2862
target_path, wt_path = path
2863
target_versioned, wt_versioned = versioned
2864
target_parent, wt_parent = parent
2865
target_name, wt_name = name
2866
target_kind, wt_kind = kind
2867
target_executable, wt_executable = executable
2949
2868
if skip_root and wt_parent is None:
2951
2870
trans_id = tt.trans_id_file_id(file_id)
2953
if change.changed_content:
2954
2873
keep_content = False
2955
2874
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:
2875
wt_sha1 = working_tree.get_file_sha1(wt_path, file_id)
2876
if merge_modified.get(file_id) != wt_sha1:
2958
2877
# acquire the basis tree lazily to prevent the
2959
2878
# expense of accessing it when it's not needed ?
2960
2879
# (Guessing, RBC, 200702)
2961
2880
if basis_tree is None:
2962
2881
basis_tree = working_tree.basis_tree()
2963
2882
basis_tree.lock_read()
2964
basis_inter = InterTree.get(basis_tree, working_tree)
2965
basis_path = basis_inter.find_source_path(wt_path)
2883
basis_path = find_previous_path(working_tree, basis_tree, wt_path)
2966
2884
if basis_path is None:
2967
2885
if target_kind is None and not target_versioned:
2968
2886
keep_content = True
2970
if wt_sha1 != basis_tree.get_file_sha1(basis_path):
2888
if wt_sha1 != basis_tree.get_file_sha1(basis_path, file_id):
2971
2889
keep_content = True
2972
2890
if wt_kind is not None:
2973
2891
if not keep_content:
2989
2907
tt.create_directory(trans_id)
2990
2908
if target_kind == 'tree-reference':
2991
2909
revision = target_tree.get_reference_revision(
2910
target_path, file_id)
2993
2911
tt.set_tree_reference(revision, trans_id)
2994
2912
elif target_kind == 'symlink':
2995
2913
tt.create_symlink(target_tree.get_symlink_target(
2996
target_path), trans_id)
2914
target_path, file_id), trans_id)
2997
2915
elif target_kind == 'file':
2998
deferred_files.append(
2999
(target_path, (trans_id, mode_id, file_id)))
2916
deferred_files.append((target_path, (trans_id, mode_id, file_id)))
3000
2917
if basis_tree is None:
3001
2918
basis_tree = working_tree.basis_tree()
3002
2919
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)
2920
new_sha1 = target_tree.get_file_sha1(target_path, file_id)
2921
basis_path = find_previous_path(target_tree, basis_tree, target_path)
3006
2922
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]
2923
new_sha1 == basis_tree.get_file_sha1(basis_path, file_id)):
2924
if file_id in merge_modified:
2925
del merge_modified[file_id]
3013
merge_modified[target_path] = new_sha1
2927
merge_modified[file_id] = new_sha1
3015
2929
# preserve the execute bit when backing up
3016
2930
if keep_content and wt_executable == target_executable:
3021
2935
tt.version_file(file_id, trans_id)
3022
2936
if wt_versioned and not target_versioned:
3023
2937
tt.unversion_file(trans_id)
3024
if (target_name is not None
3025
and (wt_name != target_name or wt_parent != target_parent)):
2938
if (target_name is not None and
2939
(wt_name != target_name or wt_parent != target_parent)):
3026
2940
if target_name == '' and target_parent is None:
3027
2941
parent_trans = ROOT_PARENT
3035
2949
tt.set_executability(target_executable, trans_id)
3036
2950
if working_tree.supports_content_filtering():
3037
2951
for (trans_id, mode_id, file_id), bytes in (
3038
target_tree.iter_files_bytes(deferred_files)):
2952
target_tree.iter_files_bytes(deferred_files)):
3039
2953
# We're reverting a tree to the target tree so using the
3040
2954
# target tree to find the file path seems the best choice
3041
2955
# here IMO - Ian C 27/Oct/2009
3042
2956
filter_tree_path = target_tree.id2path(file_id)
3043
2957
filters = working_tree._content_filter_stack(filter_tree_path)
3044
bytes = filtered_output_bytes(
2958
bytes = filtered_output_bytes(bytes, filters,
3046
2959
ContentFilterContext(filter_tree_path, working_tree))
3047
2960
tt.create_file(bytes, trans_id, mode_id)
3049
2962
for (trans_id, mode_id, file_id), bytes in target_tree.iter_files_bytes(
3051
2964
tt.create_file(bytes, trans_id, mode_id)
3052
2965
tt.fixup_new_roots()
3161
3074
file_id = tt.inactive_file_id(conflict[1])
3162
3075
# special-case the other tree root (move its children instead)
3163
3076
if path_tree and path_tree.path2id('') == file_id:
3164
# This is the root entry, skip it
3077
# This is the root entry, skip it
3166
3079
tt.version_file(file_id, conflict[1])
3167
3080
new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
3168
3081
elif c_type == 'non-directory parent':
3171
3084
parent_name = tt.final_name(parent_id)
3172
3085
parent_file_id = tt.final_file_id(parent_id)
3173
3086
new_parent_id = tt.new_directory(parent_name + '.new',
3174
parent_parent, parent_file_id)
3087
parent_parent, parent_file_id)
3175
3088
_reparent_transform_children(tt, parent_id, new_parent_id)
3176
3089
if parent_file_id is not None:
3177
3090
tt.unversion_file(parent_id)
3245
3158
except OSError as e:
3246
3159
raise errors.TransformRenameFailed(to, from_, str(e), e.errno)
3247
3160
# after rollback, don't reuse _FileMover
3248
self.past_renames = None
3249
self.pending_deletions = None
3162
pending_deletions = None
3251
3164
def apply_deletions(self):
3252
3165
"""Apply all marked deletions"""
3253
3166
for path in self.pending_deletions:
3254
3167
delete_any(path)
3255
3168
# after apply_deletions, don't reuse _FileMover
3256
self.past_renames = None
3257
self.pending_deletions = None
3170
pending_deletions = None
3260
3173
def link_tree(target_tree, source_tree):
3263
3176
:param target_tree: Tree to change
3264
3177
: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])
3179
tt = TreeTransform(target_tree)
3181
for (file_id, paths, changed_content, versioned, parent, name, kind,
3182
executable) in target_tree.iter_changes(source_tree,
3183
include_unchanged=True):
3186
if kind != ('file', 'file'):
3188
if executable[0] != executable[1]:
3190
trans_id = tt.trans_id_tree_path(paths[1])
3275
3191
tt.delete_contents(trans_id)
3276
tt.create_hardlink(source_tree.abspath(change.path[0]), trans_id)
3192
tt.create_hardlink(source_tree.abspath(paths[0]), trans_id)