31
32
lazy_import.lazy_import(globals(), """
42
43
revision as _mod_revision,
46
from breezy.bzr import (
50
from breezy.i18n import gettext
47
from brzlib.i18n import gettext
52
from .errors import (DuplicateKey, MalformedTransform,
53
ReusingTransform, CantMoveRoot,
54
ImmortalLimbo, NoFinalPath)
55
from .filters import filtered_output_bytes, ContentFilterContext
56
from .mutabletree import MutableTree
57
from .osutils import (
49
from brzlib.errors import (DuplicateKey, MalformedTransform,
50
ReusingTransform, CantMoveRoot,
51
ImmortalLimbo, NoFinalPath,
53
from brzlib.filters import filtered_output_bytes, ContentFilterContext
54
from brzlib.mutabletree import MutableTree
55
from brzlib.osutils import (
65
from .progress import ProgressPhase
63
from brzlib.progress import ProgressPhase
64
from brzlib.symbol_versioning import (
72
71
ROOT_PARENT = "root-parent"
75
73
def unique_add(map, key, value):
77
75
raise DuplicateKey(key=key)
81
80
class _TransformResults(object):
82
81
def __init__(self, modified_paths, rename_count):
83
82
object.__init__(self)
717
725
def _duplicate_ids(self):
718
726
"""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.
725
728
removed_tree_ids = set((self.tree_file_id(trans_id) for trans_id in
726
729
self._removed_id))
730
all_ids = self._tree.all_file_ids()
727
731
active_tree_ids = all_ids.difference(removed_tree_ids)
728
for trans_id, file_id in self._new_id.items():
732
for trans_id, file_id in self._new_id.iteritems():
729
733
if file_id in active_tree_ids:
730
path = self._tree.id2path(file_id)
731
old_trans_id = self.trans_id_tree_path(path)
734
old_trans_id = self.trans_id_tree_file_id(file_id)
732
735
conflicts.append(('duplicate id', old_trans_id, trans_id))
735
738
def _parent_type_conflicts(self, by_parent):
736
739
"""Children must have a directory parent"""
738
for parent_id, children in by_parent.items():
741
for parent_id, children in by_parent.iteritems():
739
742
if parent_id == ROOT_PARENT:
741
744
no_children = True
982
990
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)
990
991
if from_kind != to_kind:
992
993
elif to_kind in ('file', 'symlink') and (
993
to_trans_id != from_trans_id
994
or to_trans_id in self._new_contents):
994
to_trans_id != from_trans_id or
995
to_trans_id in self._new_contents):
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):
997
if (not modified and from_versioned == to_versioned and
998
from_parent==to_parent and from_name == to_name and
999
from_executable == to_executable):
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))
1001
results.append((file_id, (from_path, to_path), modified,
1002
(from_versioned, to_versioned),
1003
(from_parent, to_parent),
1004
(from_name, to_name),
1005
(from_kind, to_kind),
1006
(from_executable, to_executable)))
1007
return iter(sorted(results, key=lambda x:x[1]))
1013
1009
def get_preview_tree(self):
1014
1010
"""Return a tree representing the result of the transform.
1077
1073
return revision_id
1079
1075
def _text_parent(self, trans_id):
1080
path = self.tree_path(trans_id)
1076
file_id = self.tree_file_id(trans_id)
1082
if path is None or self._tree.kind(path) != 'file':
1078
if file_id is None or self._tree.kind(file_id) != 'file':
1084
1080
except errors.NoSuchFile:
1088
1084
def _get_parents_texts(self, trans_id):
1089
1085
"""Get texts for compression parents of this file."""
1090
path = self._text_parent(trans_id)
1086
file_id = self._text_parent(trans_id)
1093
return (self._tree.get_file_text(path),)
1089
return (self._tree.get_file_text(file_id),)
1095
1091
def _get_parents_lines(self, trans_id):
1096
1092
"""Get lines for compression parents of this file."""
1097
path = self._text_parent(trans_id)
1093
file_id = self._text_parent(trans_id)
1100
return (self._tree.get_file_lines(path),)
1096
return (self._tree.get_file_lines(file_id),)
1102
1098
def serialize(self, serializer):
1103
1099
"""Serialize this TreeTransform.
1105
1101
:param serializer: A Serialiser like pack.ContainerSerializer.
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]
1103
new_name = dict((k, v.encode('utf-8')) for k, v in
1104
self._new_name.items())
1105
new_executability = dict((k, int(v)) for k, v in
1106
self._new_executability.items())
1107
tree_path_ids = dict((k.encode('utf-8'), v)
1108
for k, v in self._tree_path_ids.items())
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,
1110
'_id_number': self._id_number,
1111
'_new_name': new_name,
1112
'_new_parent': self._new_parent,
1113
'_new_executability': new_executability,
1114
'_new_id': self._new_id,
1115
'_tree_path_ids': tree_path_ids,
1116
'_removed_id': list(self._removed_id),
1117
'_removed_contents': list(self._removed_contents),
1118
'_non_present_ids': self._non_present_ids,
1134
1120
yield serializer.bytes_record(bencode.bencode(attribs),
1136
for trans_id, kind in sorted(self._new_contents.items()):
1122
for trans_id, kind in self._new_contents.items():
1137
1123
if kind == 'file':
1138
with open(self._limbo_name(trans_id), 'rb') as cur_file:
1139
lines = cur_file.readlines()
1124
lines = osutils.chunks_to_lines(
1125
self._read_file_chunks(trans_id))
1140
1126
parents = self._get_parents_lines(trans_id)
1141
1127
mpdiff = multiparent.MultiParent.from_lines(lines, parents)
1142
content = b''.join(mpdiff.to_patch())
1128
content = ''.join(mpdiff.to_patch())
1143
1129
if kind == 'directory':
1145
1131
if kind == 'symlink':
1146
1132
content = self._read_symlink_target(trans_id)
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')),))
1133
yield serializer.bytes_record(content, ((trans_id, kind),))
1152
1135
def deserialize(self, records):
1153
1136
"""Deserialize a stored TreeTransform.
1155
1138
:param records: An iterable of (names, content) tuples, as per
1156
1139
pack.ContainerPushParser.
1158
names, content = next(records)
1141
names, content = records.next()
1159
1142
attribs = bencode.bdecode(content)
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()}
1143
self._id_number = attribs['_id_number']
1144
self._new_name = dict((k, v.decode('utf-8'))
1145
for k, v in attribs['_new_name'].items())
1146
self._new_parent = attribs['_new_parent']
1147
self._new_executability = dict((k, bool(v)) for k, v in
1148
attribs['_new_executability'].items())
1149
self._new_id = attribs['_new_id']
1150
self._r_new_id = dict((v, k) for k, v in self._new_id.items())
1171
1151
self._tree_path_ids = {}
1172
1152
self._tree_id_paths = {}
1173
for bytepath, trans_id in attribs[b'_tree_path_ids'].items():
1153
for bytepath, trans_id in attribs['_tree_path_ids'].items():
1174
1154
path = bytepath.decode('utf-8')
1175
trans_id = trans_id.decode('utf-8')
1176
1155
self._tree_path_ids[path] = trans_id
1177
1156
self._tree_id_paths[trans_id] = path
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()}
1157
self._removed_id = set(attribs['_removed_id'])
1158
self._removed_contents = set(attribs['_removed_contents'])
1159
self._non_present_ids = attribs['_non_present_ids']
1186
1160
for ((trans_id, kind),), content in records:
1187
trans_id = trans_id.decode('utf-8')
1188
kind = kind.decode('ascii')
1189
1161
if kind == 'file':
1190
1162
mpdiff = multiparent.MultiParent.from_patch(content)
1191
1163
lines = mpdiff.to_lines(self._get_parents_texts(trans_id))
1195
1167
if kind == 'symlink':
1196
1168
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)
1238
1171
class DiskTreeTransform(TreeTransformBase):
1239
1172
"""Tree transform storing its contents on disk."""
1241
def __init__(self, tree, limbodir, pb=None, case_sensitive=True):
1174
def __init__(self, tree, limbodir, pb=None,
1175
case_sensitive=True):
1242
1176
"""Constructor.
1243
1177
:param tree: The tree that will be transformed, but not necessarily
1244
1178
the output tree.
2026
1984
self._all_children_cache = {}
2027
1985
self._path2trans_id_cache = {}
2028
1986
self._final_name_cache = {}
2029
self._iter_changes_cache = dict((c.file_id, c) for c in
1987
self._iter_changes_cache = dict((c[0], c) for c in
2030
1988
self._transform.iter_changes())
2032
def supports_tree_reference(self):
2033
# TODO(jelmer): Support tree references in _PreviewTree.
2034
# return self._transform._tree.supports_tree_reference()
2037
1990
def _content_change(self, file_id):
2038
1991
"""Return True if the content of this file changed"""
2039
1992
changes = self._iter_changes_cache.get(file_id)
2040
return (changes is not None and changes.changed_content)
1993
# changes[2] is true if the file content changed. See
1994
# InterTree.iter_changes.
1995
return (changes is not None and changes[2])
2042
1997
def _get_repository(self):
2043
1998
repo = getattr(self._transform._tree, '_repository', None)
2052
2007
except errors.NoSuchRevisionInTree:
2053
2008
yield self._get_repository().revision_tree(revision_id)
2055
def _get_file_revision(self, path, file_id, vf, tree_revision):
2057
(file_id, t.get_file_revision(t.id2path(file_id)))
2058
for t in self._iter_parent_trees()]
2010
def _get_file_revision(self, file_id, vf, tree_revision):
2011
parent_keys = [(file_id, t.get_file_revision(file_id)) for t in
2012
self._iter_parent_trees()]
2059
2013
vf.add_lines((file_id, tree_revision), parent_keys,
2060
self.get_file_lines(path))
2014
self.get_file_lines(file_id))
2061
2015
repo = self._get_repository()
2062
2016
base_vf = repo.texts
2063
2017
if base_vf not in vf.fallback_versionedfiles:
2064
2018
vf.fallback_versionedfiles.append(base_vf)
2065
2019
return tree_revision
2067
def _stat_limbo_file(self, trans_id):
2021
def _stat_limbo_file(self, file_id=None, trans_id=None):
2022
if trans_id is None:
2023
trans_id = self._transform.trans_id_file_id(file_id)
2068
2024
name = self._transform._limbo_name(trans_id)
2069
2025
return os.lstat(name)
2106
2071
tree_ids.update(self._transform._new_id.values())
2107
2072
return tree_ids
2109
def all_versioned_paths(self):
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)
2075
return iter(self.all_file_ids())
2077
def _has_id(self, file_id, fallback_check):
2078
if file_id in self._transform._r_new_id:
2080
elif file_id in set([self._transform.tree_file_id(trans_id) for
2081
trans_id in self._transform._removed_id]):
2084
return fallback_check(file_id)
2086
def has_id(self, file_id):
2087
return self._has_id(file_id, self._transform._tree.has_id)
2089
def has_or_had_id(self, file_id):
2090
return self._has_id(file_id, self._transform._tree.has_or_had_id)
2122
2092
def _path2trans_id(self, path):
2123
2093
# We must not use None here, because that is a valid value to store.
2207
2182
ordered_ids.append((trans_id, parent_file_id))
2208
2183
return ordered_ids
2210
def iter_child_entries(self, path):
2211
trans_id = self._path2trans_id(path)
2212
if trans_id is None:
2213
raise errors.NoSuchFile(path)
2185
def iter_child_entries(self, file_id, path=None):
2186
self.id2path(file_id)
2187
trans_id = self._transform.trans_id_file_id(file_id)
2214
2188
todo = [(child_trans_id, trans_id) for child_trans_id in
2215
2189
self._all_children(trans_id)]
2216
2190
for entry, trans_id in self._make_inv_entries(todo):
2219
def iter_entries_by_dir(self, specific_files=None, recurse_nested=False):
2221
raise NotImplementedError(
2222
'follow tree references not yet supported')
2193
def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
2224
2194
# This may not be a maximally efficient implementation, but it is
2225
2195
# reasonably straightforward. An implementation that grafts the
2226
2196
# TreeTransform changes onto the tree's iter_entries_by_dir results
2229
2199
ordered_ids = self._list_files_by_dir()
2230
2200
for entry, trans_id in self._make_inv_entries(ordered_ids,
2232
yield self._final_paths.get_path(trans_id), entry
2201
specific_file_ids, yield_parents=yield_parents):
2202
yield unicode(self._final_paths.get_path(trans_id)), entry
2234
2204
def _iter_entries_for_dir(self, dir_path):
2235
2205
"""Return path, entry for items in a directory without recursing down."""
2206
dir_file_id = self.path2id(dir_path)
2236
2207
ordered_ids = []
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))
2208
for file_id in self.iter_children(dir_file_id):
2209
trans_id = self._transform.trans_id_file_id(file_id)
2210
ordered_ids.append((trans_id, file_id))
2242
2211
for entry, trans_id in self._make_inv_entries(ordered_ids):
2243
path_entries.append((self._final_paths.get_path(trans_id), entry))
2212
yield unicode(self._final_paths.get_path(trans_id)), entry
2247
def list_files(self, include_root=False, from_dir=None, recursive=True,
2248
recurse_nested=False):
2214
def list_files(self, include_root=False, from_dir=None, recursive=True):
2249
2215
"""See WorkingTree.list_files."""
2251
raise NotImplementedError(
2252
'follow tree references not yet supported')
2254
2216
# XXX This should behave like WorkingTree.list_files, but is really
2255
2217
# more like RevisionTree.list_files.
2267
2227
if not path.startswith(prefix):
2269
2229
path = path[len(prefix):]
2270
yield path, 'V', entry.kind, entry
2230
yield path, 'V', entry.kind, entry.file_id, entry
2272
2232
if from_dir is None and include_root is True:
2273
root_entry = inventory.make_entry(
2274
'directory', '', ROOT_PARENT, self.path2id(''))
2275
yield '', 'V', 'directory', root_entry
2233
root_entry = inventory.make_entry('directory', '',
2234
ROOT_PARENT, self.get_root_id())
2235
yield '', 'V', 'directory', root_entry.file_id, root_entry
2276
2236
entries = self._iter_entries_for_dir(from_dir or '')
2277
2237
for path, entry in entries:
2278
yield path, 'V', entry.kind, entry
2238
yield path, 'V', entry.kind, entry.file_id, entry
2280
def kind(self, path):
2281
trans_id = self._path2trans_id(path)
2282
if trans_id is None:
2283
raise errors.NoSuchFile(path)
2240
def kind(self, file_id):
2241
trans_id = self._transform.trans_id_file_id(file_id)
2284
2242
return self._transform.final_kind(trans_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)
2244
def stored_kind(self, file_id):
2245
trans_id = self._transform.trans_id_file_id(file_id)
2291
2247
return self._transform._new_contents[trans_id]
2292
2248
except KeyError:
2293
return self._transform._tree.stored_kind(path)
2249
return self._transform._tree.stored_kind(file_id)
2295
def get_file_mtime(self, path):
2251
def get_file_mtime(self, file_id, path=None):
2296
2252
"""See Tree.get_file_mtime"""
2297
file_id = self.path2id(path)
2299
raise errors.NoSuchFile(path)
2300
2253
if not self._content_change(file_id):
2301
return self._transform._tree.get_file_mtime(
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):
2254
return self._transform._tree.get_file_mtime(file_id)
2255
return self._stat_limbo_file(file_id).st_mtime
2257
def _file_size(self, entry, stat_value):
2258
return self.get_file_size(entry.file_id)
2260
def get_file_size(self, file_id):
2307
2261
"""See Tree.get_file_size"""
2308
trans_id = self._path2trans_id(path)
2309
if trans_id is None:
2310
raise errors.NoSuchFile(path)
2262
trans_id = self._transform.trans_id_file_id(file_id)
2311
2263
kind = self._transform.final_kind(trans_id)
2312
2264
if kind != 'file':
2314
2266
if trans_id in self._transform._new_contents:
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)
2267
return self._stat_limbo_file(trans_id=trans_id).st_size
2268
if self.kind(file_id) == 'file':
2269
return self._transform._tree.get_file_size(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)
2273
def get_file_verifier(self, file_id, path=None, stat_value=None):
2274
trans_id = self._transform.trans_id_file_id(file_id)
2325
2275
kind = self._transform._new_contents.get(trans_id)
2326
2276
if kind is None:
2327
return self._transform._tree.get_file_verifier(path)
2277
return self._transform._tree.get_file_verifier(file_id)
2328
2278
if kind == 'file':
2329
with self.get_file(path) as fileobj:
2279
fileobj = self.get_file(file_id)
2330
2281
return ("SHA1", sha_file(fileobj))
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)
2285
def get_file_sha1(self, file_id, path=None, stat_value=None):
2286
trans_id = self._transform.trans_id_file_id(file_id)
2336
2287
kind = self._transform._new_contents.get(trans_id)
2337
2288
if kind is None:
2338
return self._transform._tree.get_file_sha1(path)
2289
return self._transform._tree.get_file_sha1(file_id)
2339
2290
if kind == 'file':
2340
with self.get_file(path) as fileobj:
2291
fileobj = self.get_file(file_id)
2341
2293
return sha_file(fileobj)
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:
2297
def is_executable(self, file_id, path=None):
2300
trans_id = self._transform.trans_id_file_id(file_id)
2357
2302
return self._transform._new_executability[trans_id]
2358
2303
except KeyError:
2360
return self._transform._tree.is_executable(path)
2361
except OSError as e:
2305
return self._transform._tree.is_executable(file_id, path)
2362
2307
if e.errno == errno.ENOENT:
2365
except errors.NoSuchFile:
2310
except errors.NoSuchId:
2368
2313
def has_filename(self, path):
2401
2346
executable = None
2402
2347
if kind == 'symlink':
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)
2348
link_or_sha1 = os.readlink(limbo_name).decode(osutils._fs_enc)
2406
2349
executable = tt._new_executability.get(trans_id, executable)
2407
2350
return kind, size, executable, link_or_sha1
2409
2352
def iter_changes(self, from_tree, include_unchanged=False,
2410
specific_files=None, pb=None, extra_trees=None,
2411
require_versioned=True, want_unversioned=False):
2353
specific_files=None, pb=None, extra_trees=None,
2354
require_versioned=True, want_unversioned=False):
2412
2355
"""See InterTree.iter_changes.
2414
2357
This has a fast path that is only used when the from_tree matches
2415
2358
the transform tree, and no fancy options are supplied.
2417
if (from_tree is not self._transform._tree or include_unchanged
2418
or specific_files or want_unversioned):
2360
if (from_tree is not self._transform._tree or include_unchanged or
2361
specific_files or want_unversioned):
2419
2362
return tree.InterTree(from_tree, self).iter_changes(
2420
2363
include_unchanged=include_unchanged,
2421
2364
specific_files=specific_files,
2427
2370
raise ValueError('want_unversioned is not supported')
2428
2371
return self._transform.iter_changes()
2430
def get_file(self, path):
2373
def get_file(self, file_id, path=None):
2431
2374
"""See Tree.get_file"""
2432
file_id = self.path2id(path)
2433
2375
if not self._content_change(file_id):
2434
return self._transform._tree.get_file(path)
2435
trans_id = self._path2trans_id(path)
2376
return self._transform._tree.get_file(file_id, path)
2377
trans_id = self._transform.trans_id_file_id(file_id)
2436
2378
name = self._transform._limbo_name(trans_id)
2437
2379
return open(name, 'rb')
2439
def get_file_with_stat(self, path):
2440
return self.get_file(path), None
2381
def get_file_with_stat(self, file_id, path=None):
2382
return self.get_file(file_id, path), None
2442
def annotate_iter(self, path,
2384
def annotate_iter(self, file_id,
2443
2385
default_revision=_mod_revision.CURRENT_REVISION):
2444
file_id = self.path2id(path)
2445
2386
changes = self._iter_changes_cache.get(file_id)
2446
2387
if changes is None:
2449
changed_content, versioned, kind = (
2450
changes.changed_content, changes.versioned, changes.kind)
2390
changed_content, versioned, kind = (changes[2], changes[3],
2451
2392
if kind[1] is None:
2453
2394
get_old = (kind[0] == 'file' and versioned[0])
2455
old_annotation = self._transform._tree.annotate_iter(
2456
path, default_revision=default_revision)
2396
old_annotation = self._transform._tree.annotate_iter(file_id,
2397
default_revision=default_revision)
2458
2399
old_annotation = []
2459
2400
if changes is None:
2586
2532
:param delta_from_tree: If true, build_tree may use the input Tree to
2587
2533
generate the inventory delta.
2589
with contextlib.ExitStack() as exit_stack:
2590
exit_stack.enter_context(wt.lock_tree_write())
2591
exit_stack.enter_context(tree.lock_read())
2592
if accelerator_tree is not None:
2593
exit_stack.enter_context(accelerator_tree.lock_read())
2594
return _build_tree(tree, wt, accelerator_tree, hardlink,
2535
wt.lock_tree_write()
2539
if accelerator_tree is not None:
2540
accelerator_tree.lock_read()
2542
return _build_tree(tree, wt, accelerator_tree, hardlink,
2545
if accelerator_tree is not None:
2546
accelerator_tree.unlock()
2598
2553
def _build_tree(tree, wt, accelerator_tree, hardlink, delta_from_tree):
2599
2554
"""See build_tree."""
2600
for num, _unused in enumerate(wt.all_versioned_paths()):
2555
for num, _unused in enumerate(wt.all_file_ids()):
2601
2556
if num > 0: # more than just a root
2602
2557
raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
2603
2558
file_trans_id = {}
2604
2559
top_pb = ui.ui_factory.nested_progress_bar()
2605
2560
pp = ProgressPhase("Build phase", 2, top_pb)
2606
if tree.path2id('') is not None:
2561
if tree.get_root_id() is not None:
2607
2562
# This is kind of a hack: we should be altering the root
2608
2563
# as part of the regular tree shape diff logic.
2609
2564
# The conditional test here is to avoid doing an
2669
2625
trans_id = tt.create_path(entry.name, parent_id)
2670
2626
file_trans_id[file_id] = trans_id
2671
2627
tt.version_file(file_id, trans_id)
2672
executable = tree.is_executable(tree_path)
2628
executable = tree.is_executable(file_id, tree_path)
2674
2630
tt.set_executability(executable, trans_id)
2675
trans_data = (trans_id, file_id,
2676
tree_path, entry.text_sha1)
2677
deferred_contents.append((tree_path, trans_data))
2631
trans_data = (trans_id, tree_path, entry.text_sha1)
2632
deferred_contents.append((file_id, trans_data))
2679
file_trans_id[file_id] = new_by_entry(
2680
tree_path, tt, entry, parent_id, tree)
2634
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
2682
2637
new_trans_id = file_trans_id[file_id]
2683
2638
old_parent = tt.trans_id_tree_path(tree_path)
2716
2671
new_desired_files = desired_files
2718
2673
iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
2720
change.path for change in iter
2721
if not (change.changed_content or change.executable[0] != change.executable[1])]
2674
unchanged = [(f, p[1]) for (f, p, c, v, d, n, k, e)
2675
in iter if not (c or e[0] != e[1])]
2722
2676
if accelerator_tree.supports_content_filtering():
2723
unchanged = [(tp, ap) for (tp, ap) in unchanged
2724
if not next(accelerator_tree.iter_search_rules([ap]))]
2677
unchanged = [(f, p) for (f, p) in unchanged
2678
if not accelerator_tree.iter_search_rules([p]).next()]
2725
2679
unchanged = dict(unchanged)
2726
2680
new_desired_files = []
2728
for unused_tree_path, (trans_id, file_id, tree_path, text_sha1) in desired_files:
2729
accelerator_path = unchanged.get(tree_path)
2682
for file_id, (trans_id, tree_path, text_sha1) in desired_files:
2683
accelerator_path = unchanged.get(file_id)
2730
2684
if accelerator_path is None:
2731
new_desired_files.append((tree_path,
2732
(trans_id, file_id, tree_path, text_sha1)))
2685
new_desired_files.append((file_id,
2686
(trans_id, tree_path, text_sha1)))
2734
2688
pb.update(gettext('Adding file contents'), count + offset, total)
2736
2690
tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
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)
2693
contents = accelerator_tree.get_file(file_id, accelerator_path)
2694
if wt.supports_content_filtering():
2695
filters = wt._content_filter_stack(tree_path)
2696
contents = filtered_output_bytes(contents, filters,
2697
ContentFilterContext(tree_path, tree))
2699
tt.create_file(contents, trans_id, sha1=text_sha1)
2703
except AttributeError:
2704
# after filtering, contents may no longer be file-like
2747
2707
offset += count
2748
for count, ((trans_id, file_id, tree_path, text_sha1), contents) in enumerate(
2708
for count, ((trans_id, tree_path, text_sha1), contents) in enumerate(
2749
2709
tree.iter_files_bytes(new_desired_files)):
2750
2710
if wt.supports_content_filtering():
2751
2711
filters = wt._content_filter_stack(tree_path)
2752
2712
contents = filtered_output_bytes(contents, filters,
2753
ContentFilterContext(tree_path, tree))
2713
ContentFilterContext(tree_path, tree))
2754
2714
tt.create_file(contents, trans_id, sha1=text_sha1)
2755
2715
pb.update(gettext('Adding file contents'), count + offset, total)
2802
2764
final_parent = tt.final_parent(old_file)
2803
2765
if new_file in divert:
2804
new_name = tt.final_name(old_file) + '.diverted'
2766
new_name = tt.final_name(old_file)+'.diverted'
2805
2767
tt.adjust_path(new_name, final_parent, new_file)
2806
2768
new_conflicts.add((c_type, 'Diverted to',
2807
2769
new_file, old_file))
2809
new_name = tt.final_name(old_file) + '.moved'
2771
new_name = tt.final_name(old_file)+'.moved'
2810
2772
tt.adjust_path(new_name, final_parent, old_file)
2811
2773
new_conflicts.add((c_type, 'Moved existing file to',
2812
2774
old_file, new_file))
2813
2775
return new_conflicts
2816
def new_by_entry(path, tt, entry, parent_id, tree):
2778
def new_by_entry(tt, entry, parent_id, tree):
2817
2779
"""Create a new file according to its inventory entry"""
2818
2780
name = entry.name
2819
2781
kind = entry.kind
2820
2782
if kind == 'file':
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,
2783
contents = tree.get_file(entry.file_id).readlines()
2784
executable = tree.is_executable(entry.file_id)
2785
return tt.new_file(name, parent_id, contents, entry.file_id,
2826
2787
elif kind in ('directory', 'tree-reference'):
2827
2788
trans_id = tt.new_directory(name, parent_id, entry.file_id)
2828
2789
if kind == 'tree-reference':
2829
2790
tt.set_tree_reference(entry.reference_revision, trans_id)
2830
2791
return trans_id
2831
2792
elif kind == 'symlink':
2832
target = tree.get_symlink_target(path)
2793
target = tree.get_symlink_target(entry.file_id)
2833
2794
return tt.new_symlink(name, parent_id, target, entry.file_id)
2835
2796
raise errors.BadFileKindError(name, kind)
2838
def create_from_tree(tt, trans_id, tree, path, chunks=None,
2839
filter_tree_path=None):
2799
def create_from_tree(tt, trans_id, tree, file_id, bytes=None,
2800
filter_tree_path=None):
2840
2801
"""Create new file contents according to tree contents.
2842
2803
:param filter_tree_path: the tree path to use to lookup
2843
2804
content filters to apply to the bytes output in the working tree.
2844
2805
This only applies if the working tree supports content filtering.
2846
kind = tree.kind(path)
2807
kind = tree.kind(file_id)
2847
2808
if kind == 'directory':
2848
2809
tt.create_directory(trans_id)
2849
2810
elif kind == "file":
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)
2812
tree_file = tree.get_file(file_id)
2814
bytes = tree_file.readlines()
2818
if wt.supports_content_filtering() and filter_tree_path is not None:
2819
filters = wt._content_filter_stack(filter_tree_path)
2820
bytes = filtered_output_bytes(bytes, filters,
2821
ContentFilterContext(filter_tree_path, tree))
2822
tt.create_file(bytes, trans_id)
2866
2823
elif kind == "symlink":
2867
tt.create_symlink(tree.get_symlink_target(path), trans_id)
2824
tt.create_symlink(tree.get_symlink_target(file_id), trans_id)
2869
2826
raise AssertionError('Unknown kind %r' % kind)
2878
2835
def revert(working_tree, target_tree, filenames, backups=False,
2879
2836
pb=None, change_reporter=None):
2880
2837
"""Revert a working tree's contents to those of a target tree."""
2838
target_tree.lock_read()
2881
2839
pb = ui.ui_factory.nested_progress_bar()
2840
tt = TreeTransform(working_tree, pb)
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)
2842
pp = ProgressPhase("Revert phase", 3, pb)
2843
conflicts, merge_modified = _prepare_revert_transform(
2844
working_tree, target_tree, tt, filenames, backups, pp)
2846
change_reporter = delta._ChangeReporter(
2847
unversioned_filter=working_tree.is_ignored)
2848
delta.report_changes(tt.iter_changes(), change_reporter)
2849
for conflict in conflicts:
2850
trace.warning(unicode(conflict))
2853
working_tree.set_merge_modified(merge_modified)
2855
target_tree.unlock()
2899
2858
return conflicts
2902
2861
def _prepare_revert_transform(working_tree, target_tree, tt, filenames,
2903
2862
backups, pp, basis_tree=None,
2904
2863
merge_modified=None):
2905
with ui.ui_factory.nested_progress_bar() as child_pb:
2864
child_pb = ui.ui_factory.nested_progress_bar()
2906
2866
if merge_modified is None:
2907
2867
merge_modified = working_tree.merge_modified()
2908
2868
merge_modified = _alter_files(working_tree, target_tree, tt,
2909
2869
child_pb, filenames, backups,
2910
2870
merge_modified, basis_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))
2873
child_pb = ui.ui_factory.nested_progress_bar()
2875
raw_conflicts = resolve_conflicts(tt, child_pb,
2876
lambda t, c: conflict_pass(t, c, target_tree))
2914
2879
conflicts = cook_conflicts(raw_conflicts, tt)
2915
2880
return conflicts, merge_modified
2923
2888
# than the target changes relative to the working tree. Because WT4 has an
2924
2889
# optimizer to compare itself to a target, but no optimizer for the
2926
change_list = working_tree.iter_changes(
2927
target_tree, specific_files=specific_files, pb=pb)
2928
if not target_tree.is_versioned(u''):
2891
change_list = working_tree.iter_changes(target_tree,
2892
specific_files=specific_files, pb=pb)
2893
if target_tree.get_root_id() is None:
2929
2894
skip_root = True
2931
2896
skip_root = False
2933
2898
deferred_files = []
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
2899
for id_num, (file_id, path, changed_content, versioned, parent, name,
2900
kind, executable) in enumerate(change_list):
2901
target_path, wt_path = path
2902
target_versioned, wt_versioned = versioned
2903
target_parent, wt_parent = parent
2904
target_name, wt_name = name
2905
target_kind, wt_kind = kind
2906
target_executable, wt_executable = executable
2942
2907
if skip_root and wt_parent is None:
2944
2909
trans_id = tt.trans_id_file_id(file_id)
2946
if change.changed_content:
2947
2912
keep_content = False
2948
2913
if wt_kind == 'file' and (backups or target_kind is None):
2949
wt_sha1 = working_tree.get_file_sha1(wt_path)
2950
if merge_modified.get(wt_path) != wt_sha1:
2914
wt_sha1 = working_tree.get_file_sha1(file_id)
2915
if merge_modified.get(file_id) != wt_sha1:
2951
2916
# acquire the basis tree lazily to prevent the
2952
2917
# expense of accessing it when it's not needed ?
2953
2918
# (Guessing, RBC, 200702)
2954
2919
if basis_tree is None:
2955
2920
basis_tree = working_tree.basis_tree()
2956
2921
basis_tree.lock_read()
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:
2960
if target_kind is None and not target_versioned:
2963
if wt_sha1 != basis_tree.get_file_sha1(basis_path):
2922
if basis_tree.has_id(file_id):
2923
if wt_sha1 != basis_tree.get_file_sha1(file_id):
2925
elif target_kind is None and not target_versioned:
2965
2927
if wt_kind is not None:
2966
2928
if not keep_content:
2967
2929
tt.delete_contents(trans_id)
2981
2943
if target_kind in ('directory', 'tree-reference'):
2982
2944
tt.create_directory(trans_id)
2983
2945
if target_kind == 'tree-reference':
2984
revision = target_tree.get_reference_revision(
2946
revision = target_tree.get_reference_revision(file_id,
2986
2948
tt.set_tree_reference(revision, trans_id)
2987
2949
elif target_kind == 'symlink':
2988
tt.create_symlink(target_tree.get_symlink_target(
2989
target_path), trans_id)
2950
tt.create_symlink(target_tree.get_symlink_target(file_id),
2990
2952
elif target_kind == 'file':
2991
deferred_files.append(
2992
(target_path, (trans_id, mode_id, file_id)))
2953
deferred_files.append((file_id, (trans_id, mode_id)))
2993
2954
if basis_tree is None:
2994
2955
basis_tree = working_tree.basis_tree()
2995
2956
basis_tree.lock_read()
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)
2999
if (basis_path is not None and
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]
2957
new_sha1 = target_tree.get_file_sha1(file_id)
2958
if (basis_tree.has_id(file_id) and
2959
new_sha1 == basis_tree.get_file_sha1(file_id)):
2960
if file_id in merge_modified:
2961
del merge_modified[file_id]
3006
merge_modified[target_path] = new_sha1
2963
merge_modified[file_id] = new_sha1
3008
2965
# preserve the execute bit when backing up
3009
2966
if keep_content and wt_executable == target_executable:
3027
2984
if wt_executable != target_executable and target_kind == "file":
3028
2985
tt.set_executability(target_executable, trans_id)
3029
2986
if working_tree.supports_content_filtering():
3030
for (trans_id, mode_id, file_id), bytes in (
3031
target_tree.iter_files_bytes(deferred_files)):
2987
for index, ((trans_id, mode_id), bytes) in enumerate(
2988
target_tree.iter_files_bytes(deferred_files)):
2989
file_id = deferred_files[index][0]
3032
2990
# We're reverting a tree to the target tree so using the
3033
2991
# target tree to find the file path seems the best choice
3034
2992
# here IMO - Ian C 27/Oct/2009
3035
2993
filter_tree_path = target_tree.id2path(file_id)
3036
2994
filters = working_tree._content_filter_stack(filter_tree_path)
3037
bytes = filtered_output_bytes(
2995
bytes = filtered_output_bytes(bytes, filters,
3039
2996
ContentFilterContext(filter_tree_path, working_tree))
3040
2997
tt.create_file(bytes, trans_id, mode_id)
3042
for (trans_id, mode_id, file_id), bytes in target_tree.iter_files_bytes(
2999
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
3044
3001
tt.create_file(bytes, trans_id, mode_id)
3045
3002
tt.fixup_new_roots()
3235
3196
for from_, to in reversed(self.past_renames):
3237
3198
os.rename(to, from_)
3238
except OSError as e:
3239
3200
raise errors.TransformRenameFailed(to, from_, str(e), e.errno)
3240
3201
# after rollback, don't reuse _FileMover
3241
self.past_renames = None
3242
self.pending_deletions = None
3203
pending_deletions = None
3244
3205
def apply_deletions(self):
3245
3206
"""Apply all marked deletions"""
3246
3207
for path in self.pending_deletions:
3247
3208
delete_any(path)
3248
3209
# after apply_deletions, don't reuse _FileMover
3249
self.past_renames = None
3250
self.pending_deletions = None
3253
def link_tree(target_tree, source_tree):
3254
"""Where possible, hard-link files in a tree to those in another tree.
3256
:param target_tree: Tree to change
3257
:param source_tree: Tree to hard-link from
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])
3268
tt.delete_contents(trans_id)
3269
tt.create_hardlink(source_tree.abspath(change.path[0]), trans_id)
3211
pending_deletions = None