##// END OF EJS Templates
typing: (mostly) align the signatures of `imanifestrevisionstored` overrides...
Matt Harbison -
r53371:048c1199 default
parent child Browse files
Show More
@@ -1,370 +1,370
1 from __future__ import annotations
1 from __future__ import annotations
2
2
3 from mercurial import (
3 from mercurial import (
4 match as matchmod,
4 match as matchmod,
5 pathutil,
5 pathutil,
6 pycompat,
6 pycompat,
7 util,
7 util,
8 )
8 )
9 from mercurial.interfaces import (
9 from mercurial.interfaces import (
10 repository,
10 repository,
11 util as interfaceutil,
11 util as interfaceutil,
12 )
12 )
13 from . import gitutil
13 from . import gitutil
14
14
15
15
16 pygit2 = gitutil.get_pygit2()
16 pygit2 = gitutil.get_pygit2()
17
17
18
18
19 @interfaceutil.implementer(repository.imanifestdict)
19 @interfaceutil.implementer(repository.imanifestdict)
20 class gittreemanifest:
20 class gittreemanifest:
21 """Expose git trees (and optionally a builder's overlay) as a manifestdict.
21 """Expose git trees (and optionally a builder's overlay) as a manifestdict.
22
22
23 Very similar to mercurial.manifest.treemanifest.
23 Very similar to mercurial.manifest.treemanifest.
24 """
24 """
25
25
26 def __init__(self, git_repo, root_tree, pending_changes):
26 def __init__(self, git_repo, root_tree, pending_changes):
27 """Initializer.
27 """Initializer.
28
28
29 Args:
29 Args:
30 git_repo: The git_repo we're walking (required to look up child
30 git_repo: The git_repo we're walking (required to look up child
31 trees).
31 trees).
32 root_tree: The root Git tree object for this manifest.
32 root_tree: The root Git tree object for this manifest.
33 pending_changes: A dict in which pending changes will be
33 pending_changes: A dict in which pending changes will be
34 tracked. The enclosing memgittreemanifestctx will use this to
34 tracked. The enclosing memgittreemanifestctx will use this to
35 construct any required Tree objects in Git during it's
35 construct any required Tree objects in Git during it's
36 `write()` method.
36 `write()` method.
37 """
37 """
38 self._git_repo = git_repo
38 self._git_repo = git_repo
39 self._tree = root_tree
39 self._tree = root_tree
40 if pending_changes is None:
40 if pending_changes is None:
41 pending_changes = {}
41 pending_changes = {}
42 # dict of path: Optional[Tuple(node, flags)]
42 # dict of path: Optional[Tuple(node, flags)]
43 self._pending_changes = pending_changes
43 self._pending_changes = pending_changes
44
44
45 def _resolve_entry(self, path):
45 def _resolve_entry(self, path):
46 """Given a path, load its node and flags, or raise KeyError if missing.
46 """Given a path, load its node and flags, or raise KeyError if missing.
47
47
48 This takes into account any pending writes in the builder.
48 This takes into account any pending writes in the builder.
49 """
49 """
50 upath = pycompat.fsdecode(path)
50 upath = pycompat.fsdecode(path)
51 ent = None
51 ent = None
52 if path in self._pending_changes:
52 if path in self._pending_changes:
53 val = self._pending_changes[path]
53 val = self._pending_changes[path]
54 if val is None:
54 if val is None:
55 raise KeyError
55 raise KeyError
56 return val
56 return val
57 t = self._tree
57 t = self._tree
58 comps = upath.split('/')
58 comps = upath.split('/')
59 te = self._tree
59 te = self._tree
60 for comp in comps[:-1]:
60 for comp in comps[:-1]:
61 te = te[comp]
61 te = te[comp]
62 t = self._git_repo[te.id]
62 t = self._git_repo[te.id]
63 ent = t[comps[-1]]
63 ent = t[comps[-1]]
64 if ent.filemode == pygit2.GIT_FILEMODE_BLOB:
64 if ent.filemode == pygit2.GIT_FILEMODE_BLOB:
65 flags = b''
65 flags = b''
66 elif ent.filemode == pygit2.GIT_FILEMODE_BLOB_EXECUTABLE:
66 elif ent.filemode == pygit2.GIT_FILEMODE_BLOB_EXECUTABLE:
67 flags = b'x'
67 flags = b'x'
68 elif ent.filemode == pygit2.GIT_FILEMODE_LINK:
68 elif ent.filemode == pygit2.GIT_FILEMODE_LINK:
69 flags = b'l'
69 flags = b'l'
70 else:
70 else:
71 raise ValueError('unsupported mode %s' % oct(ent.filemode))
71 raise ValueError('unsupported mode %s' % oct(ent.filemode))
72 return ent.id.raw, flags
72 return ent.id.raw, flags
73
73
74 def __getitem__(self, path):
74 def __getitem__(self, path):
75 return self._resolve_entry(path)[0]
75 return self._resolve_entry(path)[0]
76
76
77 def find(self, path):
77 def find(self, path):
78 return self._resolve_entry(path)
78 return self._resolve_entry(path)
79
79
80 def __len__(self):
80 def __len__(self):
81 return len(list(self.walk(matchmod.always())))
81 return len(list(self.walk(matchmod.always())))
82
82
83 def __nonzero__(self):
83 def __nonzero__(self):
84 try:
84 try:
85 next(iter(self))
85 next(iter(self))
86 return True
86 return True
87 except StopIteration:
87 except StopIteration:
88 return False
88 return False
89
89
90 __bool__ = __nonzero__
90 __bool__ = __nonzero__
91
91
92 def __contains__(self, path):
92 def __contains__(self, path):
93 try:
93 try:
94 self._resolve_entry(path)
94 self._resolve_entry(path)
95 return True
95 return True
96 except KeyError:
96 except KeyError:
97 return False
97 return False
98
98
99 def iterkeys(self):
99 def iterkeys(self):
100 return self.walk(matchmod.always())
100 return self.walk(matchmod.always())
101
101
102 def keys(self):
102 def keys(self):
103 return list(self.iterkeys())
103 return list(self.iterkeys())
104
104
105 def __iter__(self):
105 def __iter__(self):
106 return self.iterkeys()
106 return self.iterkeys()
107
107
108 def __setitem__(self, path, node):
108 def __setitem__(self, path, node):
109 self._pending_changes[path] = node, self.flags(path)
109 self._pending_changes[path] = node, self.flags(path)
110
110
111 def __delitem__(self, path):
111 def __delitem__(self, path):
112 # TODO: should probably KeyError for already-deleted files?
112 # TODO: should probably KeyError for already-deleted files?
113 self._pending_changes[path] = None
113 self._pending_changes[path] = None
114
114
115 def filesnotin(self, other, match=None):
115 def filesnotin(self, other, match=None):
116 if match is not None:
116 if match is not None:
117 match = matchmod.badmatch(match, lambda path, msg: None)
117 match = matchmod.badmatch(match, lambda path, msg: None)
118 sm2 = set(other.walk(match))
118 sm2 = set(other.walk(match))
119 return {f for f in self.walk(match) if f not in sm2}
119 return {f for f in self.walk(match) if f not in sm2}
120 return {f for f in self if f not in other}
120 return {f for f in self if f not in other}
121
121
122 @util.propertycache
122 @util.propertycache
123 def _dirs(self):
123 def _dirs(self):
124 return pathutil.dirs(self)
124 return pathutil.dirs(self)
125
125
126 def hasdir(self, dir):
126 def hasdir(self, dir):
127 return dir in self._dirs
127 return dir in self._dirs
128
128
129 def diff(self, other, match=lambda x: True, clean=False):
129 def diff(self, other, match=lambda x: True, clean=False):
130 """Finds changes between the current manifest and m2.
130 """Finds changes between the current manifest and m2.
131
131
132 The result is returned as a dict with filename as key and
132 The result is returned as a dict with filename as key and
133 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
133 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
134 nodeid in the current/other manifest and fl1/fl2 is the flag
134 nodeid in the current/other manifest and fl1/fl2 is the flag
135 in the current/other manifest. Where the file does not exist,
135 in the current/other manifest. Where the file does not exist,
136 the nodeid will be None and the flags will be the empty
136 the nodeid will be None and the flags will be the empty
137 string.
137 string.
138 """
138 """
139 result = {}
139 result = {}
140
140
141 def _iterativediff(t1, t2, subdir):
141 def _iterativediff(t1, t2, subdir):
142 """compares two trees and appends new tree nodes to examine to
142 """compares two trees and appends new tree nodes to examine to
143 the stack"""
143 the stack"""
144 if t1 is None:
144 if t1 is None:
145 t1 = {}
145 t1 = {}
146 if t2 is None:
146 if t2 is None:
147 t2 = {}
147 t2 = {}
148
148
149 for e1 in t1:
149 for e1 in t1:
150 realname = subdir + pycompat.fsencode(e1.name)
150 realname = subdir + pycompat.fsencode(e1.name)
151
151
152 if e1.type == pygit2.GIT_OBJ_TREE:
152 if e1.type == pygit2.GIT_OBJ_TREE:
153 try:
153 try:
154 e2 = t2[e1.name]
154 e2 = t2[e1.name]
155 if e2.type != pygit2.GIT_OBJ_TREE:
155 if e2.type != pygit2.GIT_OBJ_TREE:
156 e2 = None
156 e2 = None
157 except KeyError:
157 except KeyError:
158 e2 = None
158 e2 = None
159
159
160 stack.append((realname + b'/', e1, e2))
160 stack.append((realname + b'/', e1, e2))
161 else:
161 else:
162 n1, fl1 = self.find(realname)
162 n1, fl1 = self.find(realname)
163
163
164 try:
164 try:
165 e2 = t2[e1.name]
165 e2 = t2[e1.name]
166 n2, fl2 = other.find(realname)
166 n2, fl2 = other.find(realname)
167 except KeyError:
167 except KeyError:
168 e2 = None
168 e2 = None
169 n2, fl2 = (None, b'')
169 n2, fl2 = (None, b'')
170
170
171 if e2 is not None and e2.type == pygit2.GIT_OBJ_TREE:
171 if e2 is not None and e2.type == pygit2.GIT_OBJ_TREE:
172 stack.append((realname + b'/', None, e2))
172 stack.append((realname + b'/', None, e2))
173
173
174 if not match(realname):
174 if not match(realname):
175 continue
175 continue
176
176
177 if n1 != n2 or fl1 != fl2:
177 if n1 != n2 or fl1 != fl2:
178 result[realname] = ((n1, fl1), (n2, fl2))
178 result[realname] = ((n1, fl1), (n2, fl2))
179 elif clean:
179 elif clean:
180 result[realname] = None
180 result[realname] = None
181
181
182 for e2 in t2:
182 for e2 in t2:
183 if e2.name in t1:
183 if e2.name in t1:
184 continue
184 continue
185
185
186 realname = subdir + pycompat.fsencode(e2.name)
186 realname = subdir + pycompat.fsencode(e2.name)
187
187
188 if e2.type == pygit2.GIT_OBJ_TREE:
188 if e2.type == pygit2.GIT_OBJ_TREE:
189 stack.append((realname + b'/', None, e2))
189 stack.append((realname + b'/', None, e2))
190 elif match(realname):
190 elif match(realname):
191 n2, fl2 = other.find(realname)
191 n2, fl2 = other.find(realname)
192 result[realname] = ((None, b''), (n2, fl2))
192 result[realname] = ((None, b''), (n2, fl2))
193
193
194 stack = []
194 stack = []
195 _iterativediff(self._tree, other._tree, b'')
195 _iterativediff(self._tree, other._tree, b'')
196 while stack:
196 while stack:
197 subdir, t1, t2 = stack.pop()
197 subdir, t1, t2 = stack.pop()
198 # stack is populated in the function call
198 # stack is populated in the function call
199 _iterativediff(t1, t2, subdir)
199 _iterativediff(t1, t2, subdir)
200
200
201 return result
201 return result
202
202
203 def setflag(self, path, flag):
203 def setflag(self, path, flag):
204 node, unused_flag = self._resolve_entry(path)
204 node, unused_flag = self._resolve_entry(path)
205 self._pending_changes[path] = node, flag
205 self._pending_changes[path] = node, flag
206
206
207 def get(self, path, default=None):
207 def get(self, path, default=None):
208 try:
208 try:
209 return self._resolve_entry(path)[0]
209 return self._resolve_entry(path)[0]
210 except KeyError:
210 except KeyError:
211 return default
211 return default
212
212
213 def flags(self, path):
213 def flags(self, path):
214 try:
214 try:
215 return self._resolve_entry(path)[1]
215 return self._resolve_entry(path)[1]
216 except KeyError:
216 except KeyError:
217 return b''
217 return b''
218
218
219 def copy(self):
219 def copy(self):
220 return gittreemanifest(
220 return gittreemanifest(
221 self._git_repo, self._tree, dict(self._pending_changes)
221 self._git_repo, self._tree, dict(self._pending_changes)
222 )
222 )
223
223
224 def items(self):
224 def items(self):
225 for f in self:
225 for f in self:
226 # TODO: build a proper iterator version of this
226 # TODO: build a proper iterator version of this
227 yield self[f]
227 yield self[f]
228
228
229 def iteritems(self):
229 def iteritems(self):
230 return self.items()
230 return self.items()
231
231
232 def iterentries(self):
232 def iterentries(self):
233 for f in self:
233 for f in self:
234 # TODO: build a proper iterator version of this
234 # TODO: build a proper iterator version of this
235 yield self._resolve_entry(f)
235 yield self._resolve_entry(f)
236
236
237 def text(self):
237 def text(self):
238 assert False # TODO can this method move out of the manifest iface?
238 assert False # TODO can this method move out of the manifest iface?
239
239
240 def _walkonetree(self, tree, match, subdir):
240 def _walkonetree(self, tree, match, subdir):
241 for te in tree:
241 for te in tree:
242 # TODO: can we prune dir walks with the matcher?
242 # TODO: can we prune dir walks with the matcher?
243 realname = subdir + pycompat.fsencode(te.name)
243 realname = subdir + pycompat.fsencode(te.name)
244 if te.type == pygit2.GIT_OBJ_TREE:
244 if te.type == pygit2.GIT_OBJ_TREE:
245 for inner in self._walkonetree(
245 for inner in self._walkonetree(
246 self._git_repo[te.id], match, realname + b'/'
246 self._git_repo[te.id], match, realname + b'/'
247 ):
247 ):
248 yield inner
248 yield inner
249 elif match(realname):
249 elif match(realname):
250 yield pycompat.fsencode(realname)
250 yield pycompat.fsencode(realname)
251
251
252 def walk(self, match):
252 def walk(self, match):
253 # TODO: this is a very lazy way to merge in the pending
253 # TODO: this is a very lazy way to merge in the pending
254 # changes. There is absolutely room for optimization here by
254 # changes. There is absolutely room for optimization here by
255 # being clever about walking over the sets...
255 # being clever about walking over the sets...
256 baseline = set(self._walkonetree(self._tree, match, b''))
256 baseline = set(self._walkonetree(self._tree, match, b''))
257 deleted = {p for p, v in self._pending_changes.items() if v is None}
257 deleted = {p for p, v in self._pending_changes.items() if v is None}
258 pend = {p for p in self._pending_changes if match(p)}
258 pend = {p for p in self._pending_changes if match(p)}
259 return iter(sorted((baseline | pend) - deleted))
259 return iter(sorted((baseline | pend) - deleted))
260
260
261
261
262 @interfaceutil.implementer(repository.imanifestrevisionstored)
262 @interfaceutil.implementer(repository.imanifestrevisionstored)
263 class gittreemanifestctx:
263 class gittreemanifestctx:
264 def __init__(self, repo, gittree):
264 def __init__(self, repo, gittree):
265 self._repo = repo
265 self._repo = repo
266 self._tree = gittree
266 self._tree = gittree
267
267
268 def read(self):
268 def read(self):
269 return gittreemanifest(self._repo, self._tree, None)
269 return gittreemanifest(self._repo, self._tree, None)
270
270
271 def readfast(self, shallow=False):
271 def readfast(self, shallow: bool = False):
272 return self.read()
272 return self.read()
273
273
274 def copy(self):
274 def copy(self):
275 # NB: it's important that we return a memgittreemanifestctx
275 # NB: it's important that we return a memgittreemanifestctx
276 # because the caller expects a mutable manifest.
276 # because the caller expects a mutable manifest.
277 return memgittreemanifestctx(self._repo, self._tree)
277 return memgittreemanifestctx(self._repo, self._tree)
278
278
279 def find(self, path):
279 def find(self, path: bytes) -> tuple[bytes, bytes]:
280 return self.read()[path]
280 return self.read()[path]
281
281
282
282
283 @interfaceutil.implementer(repository.imanifestrevisionwritable)
283 @interfaceutil.implementer(repository.imanifestrevisionwritable)
284 class memgittreemanifestctx:
284 class memgittreemanifestctx:
285 def __init__(self, repo, tree):
285 def __init__(self, repo, tree):
286 self._repo = repo
286 self._repo = repo
287 self._tree = tree
287 self._tree = tree
288 # dict of path: Optional[Tuple(node, flags)]
288 # dict of path: Optional[Tuple(node, flags)]
289 self._pending_changes = {}
289 self._pending_changes = {}
290
290
291 def read(self):
291 def read(self):
292 return gittreemanifest(self._repo, self._tree, self._pending_changes)
292 return gittreemanifest(self._repo, self._tree, self._pending_changes)
293
293
294 def copy(self):
294 def copy(self):
295 # TODO: if we have a builder in play, what should happen here?
295 # TODO: if we have a builder in play, what should happen here?
296 # Maybe we can shuffle copy() into the immutable interface.
296 # Maybe we can shuffle copy() into the immutable interface.
297 return memgittreemanifestctx(self._repo, self._tree)
297 return memgittreemanifestctx(self._repo, self._tree)
298
298
299 def write(self, transaction, link, p1, p2, added, removed, match=None):
299 def write(self, transaction, link, p1, p2, added, removed, match=None):
300 # We're not (for now, anyway) going to audit filenames, so we
300 # We're not (for now, anyway) going to audit filenames, so we
301 # can ignore added and removed.
301 # can ignore added and removed.
302
302
303 # TODO what does this match argument get used for? hopefully
303 # TODO what does this match argument get used for? hopefully
304 # just narrow?
304 # just narrow?
305 assert not match or isinstance(match, matchmod.alwaysmatcher)
305 assert not match or isinstance(match, matchmod.alwaysmatcher)
306
306
307 touched_dirs = pathutil.dirs(list(self._pending_changes))
307 touched_dirs = pathutil.dirs(list(self._pending_changes))
308 trees = {
308 trees = {
309 b'': self._tree,
309 b'': self._tree,
310 }
310 }
311 # path: treebuilder
311 # path: treebuilder
312 builders = {
312 builders = {
313 b'': self._repo.TreeBuilder(self._tree),
313 b'': self._repo.TreeBuilder(self._tree),
314 }
314 }
315 # get a TreeBuilder for every tree in the touched_dirs set
315 # get a TreeBuilder for every tree in the touched_dirs set
316 for d in sorted(touched_dirs, key=lambda x: (len(x), x)):
316 for d in sorted(touched_dirs, key=lambda x: (len(x), x)):
317 if d == b'':
317 if d == b'':
318 # loaded root tree above
318 # loaded root tree above
319 continue
319 continue
320 comps = d.split(b'/')
320 comps = d.split(b'/')
321 full = b''
321 full = b''
322 for part in comps:
322 for part in comps:
323 parent = trees[full]
323 parent = trees[full]
324 try:
324 try:
325 parent_tree_id = parent[pycompat.fsdecode(part)].id
325 parent_tree_id = parent[pycompat.fsdecode(part)].id
326 new = self._repo[parent_tree_id]
326 new = self._repo[parent_tree_id]
327 except KeyError:
327 except KeyError:
328 # new directory
328 # new directory
329 new = None
329 new = None
330 full += b'/' + part
330 full += b'/' + part
331 if new is not None:
331 if new is not None:
332 # existing directory
332 # existing directory
333 trees[full] = new
333 trees[full] = new
334 builders[full] = self._repo.TreeBuilder(new)
334 builders[full] = self._repo.TreeBuilder(new)
335 else:
335 else:
336 # new directory, use an empty dict to easily
336 # new directory, use an empty dict to easily
337 # generate KeyError as any nested new dirs get
337 # generate KeyError as any nested new dirs get
338 # created.
338 # created.
339 trees[full] = {}
339 trees[full] = {}
340 builders[full] = self._repo.TreeBuilder()
340 builders[full] = self._repo.TreeBuilder()
341 for f, info in self._pending_changes.items():
341 for f, info in self._pending_changes.items():
342 if b'/' not in f:
342 if b'/' not in f:
343 dirname = b''
343 dirname = b''
344 basename = f
344 basename = f
345 else:
345 else:
346 dirname, basename = f.rsplit(b'/', 1)
346 dirname, basename = f.rsplit(b'/', 1)
347 dirname = b'/' + dirname
347 dirname = b'/' + dirname
348 if info is None:
348 if info is None:
349 builders[dirname].remove(pycompat.fsdecode(basename))
349 builders[dirname].remove(pycompat.fsdecode(basename))
350 else:
350 else:
351 n, fl = info
351 n, fl = info
352 mode = {
352 mode = {
353 b'': pygit2.GIT_FILEMODE_BLOB,
353 b'': pygit2.GIT_FILEMODE_BLOB,
354 b'x': pygit2.GIT_FILEMODE_BLOB_EXECUTABLE,
354 b'x': pygit2.GIT_FILEMODE_BLOB_EXECUTABLE,
355 b'l': pygit2.GIT_FILEMODE_LINK,
355 b'l': pygit2.GIT_FILEMODE_LINK,
356 }[fl]
356 }[fl]
357 builders[dirname].insert(
357 builders[dirname].insert(
358 pycompat.fsdecode(basename), gitutil.togitnode(n), mode
358 pycompat.fsdecode(basename), gitutil.togitnode(n), mode
359 )
359 )
360 # This visits the buffered TreeBuilders in deepest-first
360 # This visits the buffered TreeBuilders in deepest-first
361 # order, bubbling up the edits.
361 # order, bubbling up the edits.
362 for b in sorted(builders, key=len, reverse=True):
362 for b in sorted(builders, key=len, reverse=True):
363 if b == b'':
363 if b == b'':
364 break
364 break
365 cb = builders[b]
365 cb = builders[b]
366 dn, bn = b.rsplit(b'/', 1)
366 dn, bn = b.rsplit(b'/', 1)
367 builders[dn].insert(
367 builders[dn].insert(
368 pycompat.fsdecode(bn), cb.write(), pygit2.GIT_FILEMODE_TREE
368 pycompat.fsdecode(bn), cb.write(), pygit2.GIT_FILEMODE_TREE
369 )
369 )
370 return builders[b''].write().raw
370 return builders[b''].write().raw
@@ -1,2166 +1,2172
1 # repository.py - Interfaces and base classes for repositories and peers.
1 # repository.py - Interfaces and base classes for repositories and peers.
2 # coding: utf-8
2 # coding: utf-8
3 #
3 #
4 # Copyright 2017 Gregory Szorc <gregory.szorc@gmail.com>
4 # Copyright 2017 Gregory Szorc <gregory.szorc@gmail.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from __future__ import annotations
9 from __future__ import annotations
10
10
11 import typing
11 import typing
12
12
13 from typing import (
13 from typing import (
14 Any,
14 Any,
15 Collection,
15 Protocol,
16 Protocol,
16 )
17 )
17
18
18 from ..i18n import _
19 from ..i18n import _
19 from .. import error
20 from .. import error
20 from . import util as interfaceutil
21 from . import util as interfaceutil
21
22
22 if typing.TYPE_CHECKING:
23 if typing.TYPE_CHECKING:
23 # Almost all mercurial modules are only imported in the type checking phase
24 # Almost all mercurial modules are only imported in the type checking phase
24 # to avoid circular imports
25 # to avoid circular imports
25 from ..utils import (
26 from ..utils import (
26 urlutil,
27 urlutil,
27 )
28 )
28
29
29 # TODO: create a Protocol class, since importing uimod here causes a cycle
30 # TODO: create a Protocol class, since importing uimod here causes a cycle
30 # that confuses pytype.
31 # that confuses pytype.
31 Ui = Any
32 Ui = Any
32
33
33 # Local repository feature string.
34 # Local repository feature string.
34
35
35 # Revlogs are being used for file storage.
36 # Revlogs are being used for file storage.
36 REPO_FEATURE_REVLOG_FILE_STORAGE = b'revlogfilestorage'
37 REPO_FEATURE_REVLOG_FILE_STORAGE = b'revlogfilestorage'
37 # The storage part of the repository is shared from an external source.
38 # The storage part of the repository is shared from an external source.
38 REPO_FEATURE_SHARED_STORAGE = b'sharedstore'
39 REPO_FEATURE_SHARED_STORAGE = b'sharedstore'
39 # LFS supported for backing file storage.
40 # LFS supported for backing file storage.
40 REPO_FEATURE_LFS = b'lfs'
41 REPO_FEATURE_LFS = b'lfs'
41 # Repository supports being stream cloned.
42 # Repository supports being stream cloned.
42 REPO_FEATURE_STREAM_CLONE = b'streamclone'
43 REPO_FEATURE_STREAM_CLONE = b'streamclone'
43 # Repository supports (at least) some sidedata to be stored
44 # Repository supports (at least) some sidedata to be stored
44 REPO_FEATURE_SIDE_DATA = b'side-data'
45 REPO_FEATURE_SIDE_DATA = b'side-data'
45 # Files storage may lack data for all ancestors.
46 # Files storage may lack data for all ancestors.
46 REPO_FEATURE_SHALLOW_FILE_STORAGE = b'shallowfilestorage'
47 REPO_FEATURE_SHALLOW_FILE_STORAGE = b'shallowfilestorage'
47
48
48 REVISION_FLAG_CENSORED = 1 << 15
49 REVISION_FLAG_CENSORED = 1 << 15
49 REVISION_FLAG_ELLIPSIS = 1 << 14
50 REVISION_FLAG_ELLIPSIS = 1 << 14
50 REVISION_FLAG_EXTSTORED = 1 << 13
51 REVISION_FLAG_EXTSTORED = 1 << 13
51 REVISION_FLAG_HASCOPIESINFO = 1 << 12
52 REVISION_FLAG_HASCOPIESINFO = 1 << 12
52
53
53 REVISION_FLAGS_KNOWN = (
54 REVISION_FLAGS_KNOWN = (
54 REVISION_FLAG_CENSORED
55 REVISION_FLAG_CENSORED
55 | REVISION_FLAG_ELLIPSIS
56 | REVISION_FLAG_ELLIPSIS
56 | REVISION_FLAG_EXTSTORED
57 | REVISION_FLAG_EXTSTORED
57 | REVISION_FLAG_HASCOPIESINFO
58 | REVISION_FLAG_HASCOPIESINFO
58 )
59 )
59
60
60 CG_DELTAMODE_STD = b'default'
61 CG_DELTAMODE_STD = b'default'
61 CG_DELTAMODE_PREV = b'previous'
62 CG_DELTAMODE_PREV = b'previous'
62 CG_DELTAMODE_FULL = b'fulltext'
63 CG_DELTAMODE_FULL = b'fulltext'
63 CG_DELTAMODE_P1 = b'p1'
64 CG_DELTAMODE_P1 = b'p1'
64
65
65
66
66 ## Cache related constants:
67 ## Cache related constants:
67 #
68 #
68 # Used to control which cache should be warmed in a repo.updatecaches(…) call.
69 # Used to control which cache should be warmed in a repo.updatecaches(…) call.
69
70
70 # Warm branchmaps of all known repoview's filter-level
71 # Warm branchmaps of all known repoview's filter-level
71 CACHE_BRANCHMAP_ALL = b"branchmap-all"
72 CACHE_BRANCHMAP_ALL = b"branchmap-all"
72 # Warm branchmaps of repoview's filter-level used by server
73 # Warm branchmaps of repoview's filter-level used by server
73 CACHE_BRANCHMAP_SERVED = b"branchmap-served"
74 CACHE_BRANCHMAP_SERVED = b"branchmap-served"
74 # Warm internal changelog cache (eg: persistent nodemap)
75 # Warm internal changelog cache (eg: persistent nodemap)
75 CACHE_CHANGELOG_CACHE = b"changelog-cache"
76 CACHE_CHANGELOG_CACHE = b"changelog-cache"
76 # check of a branchmap can use the "pure topo" mode
77 # check of a branchmap can use the "pure topo" mode
77 CACHE_BRANCHMAP_DETECT_PURE_TOPO = b"branchmap-detect-pure-topo"
78 CACHE_BRANCHMAP_DETECT_PURE_TOPO = b"branchmap-detect-pure-topo"
78 # Warm full manifest cache
79 # Warm full manifest cache
79 CACHE_FULL_MANIFEST = b"full-manifest"
80 CACHE_FULL_MANIFEST = b"full-manifest"
80 # Warm file-node-tags cache
81 # Warm file-node-tags cache
81 CACHE_FILE_NODE_TAGS = b"file-node-tags"
82 CACHE_FILE_NODE_TAGS = b"file-node-tags"
82 # Warm internal manifestlog cache (eg: persistent nodemap)
83 # Warm internal manifestlog cache (eg: persistent nodemap)
83 CACHE_MANIFESTLOG_CACHE = b"manifestlog-cache"
84 CACHE_MANIFESTLOG_CACHE = b"manifestlog-cache"
84 # Warn rev branch cache
85 # Warn rev branch cache
85 CACHE_REV_BRANCH = b"rev-branch-cache"
86 CACHE_REV_BRANCH = b"rev-branch-cache"
86 # Warm tags' cache for default repoview'
87 # Warm tags' cache for default repoview'
87 CACHE_TAGS_DEFAULT = b"tags-default"
88 CACHE_TAGS_DEFAULT = b"tags-default"
88 # Warm tags' cache for repoview's filter-level used by server
89 # Warm tags' cache for repoview's filter-level used by server
89 CACHE_TAGS_SERVED = b"tags-served"
90 CACHE_TAGS_SERVED = b"tags-served"
90
91
91 # the cache to warm by default after a simple transaction
92 # the cache to warm by default after a simple transaction
92 # (this is a mutable set to let extension update it)
93 # (this is a mutable set to let extension update it)
93 CACHES_DEFAULT = {
94 CACHES_DEFAULT = {
94 CACHE_BRANCHMAP_SERVED,
95 CACHE_BRANCHMAP_SERVED,
95 }
96 }
96
97
97 # the caches to warm when warming all of them
98 # the caches to warm when warming all of them
98 # (this is a mutable set to let extension update it)
99 # (this is a mutable set to let extension update it)
99 CACHES_ALL = {
100 CACHES_ALL = {
100 CACHE_BRANCHMAP_SERVED,
101 CACHE_BRANCHMAP_SERVED,
101 CACHE_BRANCHMAP_ALL,
102 CACHE_BRANCHMAP_ALL,
102 CACHE_BRANCHMAP_DETECT_PURE_TOPO,
103 CACHE_BRANCHMAP_DETECT_PURE_TOPO,
103 CACHE_REV_BRANCH,
104 CACHE_REV_BRANCH,
104 CACHE_CHANGELOG_CACHE,
105 CACHE_CHANGELOG_CACHE,
105 CACHE_FILE_NODE_TAGS,
106 CACHE_FILE_NODE_TAGS,
106 CACHE_FULL_MANIFEST,
107 CACHE_FULL_MANIFEST,
107 CACHE_MANIFESTLOG_CACHE,
108 CACHE_MANIFESTLOG_CACHE,
108 CACHE_TAGS_DEFAULT,
109 CACHE_TAGS_DEFAULT,
109 CACHE_TAGS_SERVED,
110 CACHE_TAGS_SERVED,
110 }
111 }
111
112
112 # the cache to warm by default on simple call
113 # the cache to warm by default on simple call
113 # (this is a mutable set to let extension update it)
114 # (this is a mutable set to let extension update it)
114 CACHES_POST_CLONE = CACHES_ALL.copy()
115 CACHES_POST_CLONE = CACHES_ALL.copy()
115 CACHES_POST_CLONE.discard(CACHE_FILE_NODE_TAGS)
116 CACHES_POST_CLONE.discard(CACHE_FILE_NODE_TAGS)
116 CACHES_POST_CLONE.discard(CACHE_REV_BRANCH)
117 CACHES_POST_CLONE.discard(CACHE_REV_BRANCH)
117
118
118
119
119 class _ipeerconnection(Protocol):
120 class _ipeerconnection(Protocol):
120 """Represents a "connection" to a repository.
121 """Represents a "connection" to a repository.
121
122
122 This is the base interface for representing a connection to a repository.
123 This is the base interface for representing a connection to a repository.
123 It holds basic properties and methods applicable to all peer types.
124 It holds basic properties and methods applicable to all peer types.
124
125
125 This is not a complete interface definition and should not be used
126 This is not a complete interface definition and should not be used
126 outside of this module.
127 outside of this module.
127 """
128 """
128
129
129 ui: Ui
130 ui: Ui
130 """ui.ui instance"""
131 """ui.ui instance"""
131
132
132 path: urlutil.path | None
133 path: urlutil.path | None
133 """a urlutil.path instance or None"""
134 """a urlutil.path instance or None"""
134
135
135 def url(self):
136 def url(self):
136 """Returns a URL string representing this peer.
137 """Returns a URL string representing this peer.
137
138
138 Currently, implementations expose the raw URL used to construct the
139 Currently, implementations expose the raw URL used to construct the
139 instance. It may contain credentials as part of the URL. The
140 instance. It may contain credentials as part of the URL. The
140 expectations of the value aren't well-defined and this could lead to
141 expectations of the value aren't well-defined and this could lead to
141 data leakage.
142 data leakage.
142
143
143 TODO audit/clean consumers and more clearly define the contents of this
144 TODO audit/clean consumers and more clearly define the contents of this
144 value.
145 value.
145 """
146 """
146
147
147 def local(self):
148 def local(self):
148 """Returns a local repository instance.
149 """Returns a local repository instance.
149
150
150 If the peer represents a local repository, returns an object that
151 If the peer represents a local repository, returns an object that
151 can be used to interface with it. Otherwise returns ``None``.
152 can be used to interface with it. Otherwise returns ``None``.
152 """
153 """
153
154
154 def canpush(self):
155 def canpush(self):
155 """Returns a boolean indicating if this peer can be pushed to."""
156 """Returns a boolean indicating if this peer can be pushed to."""
156
157
157 def close(self):
158 def close(self):
158 """Close the connection to this peer.
159 """Close the connection to this peer.
159
160
160 This is called when the peer will no longer be used. Resources
161 This is called when the peer will no longer be used. Resources
161 associated with the peer should be cleaned up.
162 associated with the peer should be cleaned up.
162 """
163 """
163
164
164
165
165 class ipeercapabilities(Protocol):
166 class ipeercapabilities(Protocol):
166 """Peer sub-interface related to capabilities."""
167 """Peer sub-interface related to capabilities."""
167
168
168 def capable(self, name):
169 def capable(self, name):
169 """Determine support for a named capability.
170 """Determine support for a named capability.
170
171
171 Returns ``False`` if capability not supported.
172 Returns ``False`` if capability not supported.
172
173
173 Returns ``True`` if boolean capability is supported. Returns a string
174 Returns ``True`` if boolean capability is supported. Returns a string
174 if capability support is non-boolean.
175 if capability support is non-boolean.
175
176
176 Capability strings may or may not map to wire protocol capabilities.
177 Capability strings may or may not map to wire protocol capabilities.
177 """
178 """
178
179
179 def requirecap(self, name, purpose):
180 def requirecap(self, name, purpose):
180 """Require a capability to be present.
181 """Require a capability to be present.
181
182
182 Raises a ``CapabilityError`` if the capability isn't present.
183 Raises a ``CapabilityError`` if the capability isn't present.
183 """
184 """
184
185
185
186
186 class ipeercommands(Protocol):
187 class ipeercommands(Protocol):
187 """Client-side interface for communicating over the wire protocol.
188 """Client-side interface for communicating over the wire protocol.
188
189
189 This interface is used as a gateway to the Mercurial wire protocol.
190 This interface is used as a gateway to the Mercurial wire protocol.
190 methods commonly call wire protocol commands of the same name.
191 methods commonly call wire protocol commands of the same name.
191 """
192 """
192
193
193 def branchmap(self):
194 def branchmap(self):
194 """Obtain heads in named branches.
195 """Obtain heads in named branches.
195
196
196 Returns a dict mapping branch name to an iterable of nodes that are
197 Returns a dict mapping branch name to an iterable of nodes that are
197 heads on that branch.
198 heads on that branch.
198 """
199 """
199
200
200 def capabilities(self):
201 def capabilities(self):
201 """Obtain capabilities of the peer.
202 """Obtain capabilities of the peer.
202
203
203 Returns a set of string capabilities.
204 Returns a set of string capabilities.
204 """
205 """
205
206
206 def get_cached_bundle_inline(self, path):
207 def get_cached_bundle_inline(self, path):
207 """Retrieve a clonebundle across the wire.
208 """Retrieve a clonebundle across the wire.
208
209
209 Returns a chunkbuffer
210 Returns a chunkbuffer
210 """
211 """
211
212
212 def clonebundles(self):
213 def clonebundles(self):
213 """Obtains the clone bundles manifest for the repo.
214 """Obtains the clone bundles manifest for the repo.
214
215
215 Returns the manifest as unparsed bytes.
216 Returns the manifest as unparsed bytes.
216 """
217 """
217
218
218 def debugwireargs(self, one, two, three=None, four=None, five=None):
219 def debugwireargs(self, one, two, three=None, four=None, five=None):
219 """Used to facilitate debugging of arguments passed over the wire."""
220 """Used to facilitate debugging of arguments passed over the wire."""
220
221
221 def getbundle(self, source, **kwargs):
222 def getbundle(self, source, **kwargs):
222 """Obtain remote repository data as a bundle.
223 """Obtain remote repository data as a bundle.
223
224
224 This command is how the bulk of repository data is transferred from
225 This command is how the bulk of repository data is transferred from
225 the peer to the local repository
226 the peer to the local repository
226
227
227 Returns a generator of bundle data.
228 Returns a generator of bundle data.
228 """
229 """
229
230
230 def heads(self):
231 def heads(self):
231 """Determine all known head revisions in the peer.
232 """Determine all known head revisions in the peer.
232
233
233 Returns an iterable of binary nodes.
234 Returns an iterable of binary nodes.
234 """
235 """
235
236
236 def known(self, nodes):
237 def known(self, nodes):
237 """Determine whether multiple nodes are known.
238 """Determine whether multiple nodes are known.
238
239
239 Accepts an iterable of nodes whose presence to check for.
240 Accepts an iterable of nodes whose presence to check for.
240
241
241 Returns an iterable of booleans indicating of the corresponding node
242 Returns an iterable of booleans indicating of the corresponding node
242 at that index is known to the peer.
243 at that index is known to the peer.
243 """
244 """
244
245
245 def listkeys(self, namespace):
246 def listkeys(self, namespace):
246 """Obtain all keys in a pushkey namespace.
247 """Obtain all keys in a pushkey namespace.
247
248
248 Returns an iterable of key names.
249 Returns an iterable of key names.
249 """
250 """
250
251
251 def lookup(self, key):
252 def lookup(self, key):
252 """Resolve a value to a known revision.
253 """Resolve a value to a known revision.
253
254
254 Returns a binary node of the resolved revision on success.
255 Returns a binary node of the resolved revision on success.
255 """
256 """
256
257
257 def pushkey(self, namespace, key, old, new):
258 def pushkey(self, namespace, key, old, new):
258 """Set a value using the ``pushkey`` protocol.
259 """Set a value using the ``pushkey`` protocol.
259
260
260 Arguments correspond to the pushkey namespace and key to operate on and
261 Arguments correspond to the pushkey namespace and key to operate on and
261 the old and new values for that key.
262 the old and new values for that key.
262
263
263 Returns a string with the peer result. The value inside varies by the
264 Returns a string with the peer result. The value inside varies by the
264 namespace.
265 namespace.
265 """
266 """
266
267
267 def stream_out(self):
268 def stream_out(self):
268 """Obtain streaming clone data.
269 """Obtain streaming clone data.
269
270
270 Successful result should be a generator of data chunks.
271 Successful result should be a generator of data chunks.
271 """
272 """
272
273
273 def unbundle(self, bundle, heads, url):
274 def unbundle(self, bundle, heads, url):
274 """Transfer repository data to the peer.
275 """Transfer repository data to the peer.
275
276
276 This is how the bulk of data during a push is transferred.
277 This is how the bulk of data during a push is transferred.
277
278
278 Returns the integer number of heads added to the peer.
279 Returns the integer number of heads added to the peer.
279 """
280 """
280
281
281
282
282 class ipeerlegacycommands(Protocol):
283 class ipeerlegacycommands(Protocol):
283 """Interface for implementing support for legacy wire protocol commands.
284 """Interface for implementing support for legacy wire protocol commands.
284
285
285 Wire protocol commands transition to legacy status when they are no longer
286 Wire protocol commands transition to legacy status when they are no longer
286 used by modern clients. To facilitate identifying which commands are
287 used by modern clients. To facilitate identifying which commands are
287 legacy, the interfaces are split.
288 legacy, the interfaces are split.
288 """
289 """
289
290
290 def between(self, pairs):
291 def between(self, pairs):
291 """Obtain nodes between pairs of nodes.
292 """Obtain nodes between pairs of nodes.
292
293
293 ``pairs`` is an iterable of node pairs.
294 ``pairs`` is an iterable of node pairs.
294
295
295 Returns an iterable of iterables of nodes corresponding to each
296 Returns an iterable of iterables of nodes corresponding to each
296 requested pair.
297 requested pair.
297 """
298 """
298
299
299 def branches(self, nodes):
300 def branches(self, nodes):
300 """Obtain ancestor changesets of specific nodes back to a branch point.
301 """Obtain ancestor changesets of specific nodes back to a branch point.
301
302
302 For each requested node, the peer finds the first ancestor node that is
303 For each requested node, the peer finds the first ancestor node that is
303 a DAG root or is a merge.
304 a DAG root or is a merge.
304
305
305 Returns an iterable of iterables with the resolved values for each node.
306 Returns an iterable of iterables with the resolved values for each node.
306 """
307 """
307
308
308 def changegroup(self, nodes, source):
309 def changegroup(self, nodes, source):
309 """Obtain a changegroup with data for descendants of specified nodes."""
310 """Obtain a changegroup with data for descendants of specified nodes."""
310
311
311 def changegroupsubset(self, bases, heads, source):
312 def changegroupsubset(self, bases, heads, source):
312 pass
313 pass
313
314
314
315
315 class ipeercommandexecutor(Protocol):
316 class ipeercommandexecutor(Protocol):
316 """Represents a mechanism to execute remote commands.
317 """Represents a mechanism to execute remote commands.
317
318
318 This is the primary interface for requesting that wire protocol commands
319 This is the primary interface for requesting that wire protocol commands
319 be executed. Instances of this interface are active in a context manager
320 be executed. Instances of this interface are active in a context manager
320 and have a well-defined lifetime. When the context manager exits, all
321 and have a well-defined lifetime. When the context manager exits, all
321 outstanding requests are waited on.
322 outstanding requests are waited on.
322 """
323 """
323
324
324 def callcommand(self, name, args):
325 def callcommand(self, name, args):
325 """Request that a named command be executed.
326 """Request that a named command be executed.
326
327
327 Receives the command name and a dictionary of command arguments.
328 Receives the command name and a dictionary of command arguments.
328
329
329 Returns a ``concurrent.futures.Future`` that will resolve to the
330 Returns a ``concurrent.futures.Future`` that will resolve to the
330 result of that command request. That exact value is left up to
331 result of that command request. That exact value is left up to
331 the implementation and possibly varies by command.
332 the implementation and possibly varies by command.
332
333
333 Not all commands can coexist with other commands in an executor
334 Not all commands can coexist with other commands in an executor
334 instance: it depends on the underlying wire protocol transport being
335 instance: it depends on the underlying wire protocol transport being
335 used and the command itself.
336 used and the command itself.
336
337
337 Implementations MAY call ``sendcommands()`` automatically if the
338 Implementations MAY call ``sendcommands()`` automatically if the
338 requested command can not coexist with other commands in this executor.
339 requested command can not coexist with other commands in this executor.
339
340
340 Implementations MAY call ``sendcommands()`` automatically when the
341 Implementations MAY call ``sendcommands()`` automatically when the
341 future's ``result()`` is called. So, consumers using multiple
342 future's ``result()`` is called. So, consumers using multiple
342 commands with an executor MUST ensure that ``result()`` is not called
343 commands with an executor MUST ensure that ``result()`` is not called
343 until all command requests have been issued.
344 until all command requests have been issued.
344 """
345 """
345
346
346 def sendcommands(self):
347 def sendcommands(self):
347 """Trigger submission of queued command requests.
348 """Trigger submission of queued command requests.
348
349
349 Not all transports submit commands as soon as they are requested to
350 Not all transports submit commands as soon as they are requested to
350 run. When called, this method forces queued command requests to be
351 run. When called, this method forces queued command requests to be
351 issued. It will no-op if all commands have already been sent.
352 issued. It will no-op if all commands have already been sent.
352
353
353 When called, no more new commands may be issued with this executor.
354 When called, no more new commands may be issued with this executor.
354 """
355 """
355
356
356 def close(self):
357 def close(self):
357 """Signal that this command request is finished.
358 """Signal that this command request is finished.
358
359
359 When called, no more new commands may be issued. All outstanding
360 When called, no more new commands may be issued. All outstanding
360 commands that have previously been issued are waited on before
361 commands that have previously been issued are waited on before
361 returning. This not only includes waiting for the futures to resolve,
362 returning. This not only includes waiting for the futures to resolve,
362 but also waiting for all response data to arrive. In other words,
363 but also waiting for all response data to arrive. In other words,
363 calling this waits for all on-wire state for issued command requests
364 calling this waits for all on-wire state for issued command requests
364 to finish.
365 to finish.
365
366
366 When used as a context manager, this method is called when exiting the
367 When used as a context manager, this method is called when exiting the
367 context manager.
368 context manager.
368
369
369 This method may call ``sendcommands()`` if there are buffered commands.
370 This method may call ``sendcommands()`` if there are buffered commands.
370 """
371 """
371
372
372
373
373 class ipeerrequests(Protocol):
374 class ipeerrequests(Protocol):
374 """Interface for executing commands on a peer."""
375 """Interface for executing commands on a peer."""
375
376
376 limitedarguments: bool
377 limitedarguments: bool
377 """True if the peer cannot receive large argument value for commands."""
378 """True if the peer cannot receive large argument value for commands."""
378
379
379 def commandexecutor(self):
380 def commandexecutor(self):
380 """A context manager that resolves to an ipeercommandexecutor.
381 """A context manager that resolves to an ipeercommandexecutor.
381
382
382 The object this resolves to can be used to issue command requests
383 The object this resolves to can be used to issue command requests
383 to the peer.
384 to the peer.
384
385
385 Callers should call its ``callcommand`` method to issue command
386 Callers should call its ``callcommand`` method to issue command
386 requests.
387 requests.
387
388
388 A new executor should be obtained for each distinct set of commands
389 A new executor should be obtained for each distinct set of commands
389 (possibly just a single command) that the consumer wants to execute
390 (possibly just a single command) that the consumer wants to execute
390 as part of a single operation or round trip. This is because some
391 as part of a single operation or round trip. This is because some
391 peers are half-duplex and/or don't support persistent connections.
392 peers are half-duplex and/or don't support persistent connections.
392 e.g. in the case of HTTP peers, commands sent to an executor represent
393 e.g. in the case of HTTP peers, commands sent to an executor represent
393 a single HTTP request. While some peers may support multiple command
394 a single HTTP request. While some peers may support multiple command
394 sends over the wire per executor, consumers need to code to the least
395 sends over the wire per executor, consumers need to code to the least
395 capable peer. So it should be assumed that command executors buffer
396 capable peer. So it should be assumed that command executors buffer
396 called commands until they are told to send them and that each
397 called commands until they are told to send them and that each
397 command executor could result in a new connection or wire-level request
398 command executor could result in a new connection or wire-level request
398 being issued.
399 being issued.
399 """
400 """
400
401
401
402
402 class peer(_ipeerconnection, ipeercapabilities, ipeerrequests, Protocol):
403 class peer(_ipeerconnection, ipeercapabilities, ipeerrequests, Protocol):
403 """Unified interface for peer repositories.
404 """Unified interface for peer repositories.
404
405
405 All peer instances must conform to this interface.
406 All peer instances must conform to this interface.
406 """
407 """
407
408
408 limitedarguments: bool = False
409 limitedarguments: bool = False
409
410
410 def __init__(self, ui, path=None, remotehidden=False):
411 def __init__(self, ui, path=None, remotehidden=False):
411 self.ui = ui
412 self.ui = ui
412 self.path = path
413 self.path = path
413
414
414 def capable(self, name):
415 def capable(self, name):
415 # TODO: this class should maybe subclass ipeercommands too, otherwise it
416 # TODO: this class should maybe subclass ipeercommands too, otherwise it
416 # is assuming whatever uses this as a mixin also has this interface.
417 # is assuming whatever uses this as a mixin also has this interface.
417 caps = self.capabilities() # pytype: disable=attribute-error
418 caps = self.capabilities() # pytype: disable=attribute-error
418 if name in caps:
419 if name in caps:
419 return True
420 return True
420
421
421 name = b'%s=' % name
422 name = b'%s=' % name
422 for cap in caps:
423 for cap in caps:
423 if cap.startswith(name):
424 if cap.startswith(name):
424 return cap[len(name) :]
425 return cap[len(name) :]
425
426
426 return False
427 return False
427
428
428 def requirecap(self, name, purpose):
429 def requirecap(self, name, purpose):
429 if self.capable(name):
430 if self.capable(name):
430 return
431 return
431
432
432 raise error.CapabilityError(
433 raise error.CapabilityError(
433 _(
434 _(
434 b'cannot %s; remote repository does not support the '
435 b'cannot %s; remote repository does not support the '
435 b'\'%s\' capability'
436 b'\'%s\' capability'
436 )
437 )
437 % (purpose, name)
438 % (purpose, name)
438 )
439 )
439
440
440
441
441 class iverifyproblem(Protocol):
442 class iverifyproblem(Protocol):
442 """Represents a problem with the integrity of the repository.
443 """Represents a problem with the integrity of the repository.
443
444
444 Instances of this interface are emitted to describe an integrity issue
445 Instances of this interface are emitted to describe an integrity issue
445 with a repository (e.g. corrupt storage, missing data, etc).
446 with a repository (e.g. corrupt storage, missing data, etc).
446
447
447 Instances are essentially messages associated with severity.
448 Instances are essentially messages associated with severity.
448 """
449 """
449
450
450 warning: bytes | None
451 warning: bytes | None
451 """Message indicating a non-fatal problem."""
452 """Message indicating a non-fatal problem."""
452
453
453 error: bytes | None
454 error: bytes | None
454 """Message indicating a fatal problem."""
455 """Message indicating a fatal problem."""
455
456
456 node: bytes | None
457 node: bytes | None
457 """Revision encountering the problem.
458 """Revision encountering the problem.
458
459
459 ``None`` means the problem doesn't apply to a single revision.
460 ``None`` means the problem doesn't apply to a single revision.
460 """
461 """
461
462
462
463
463 class irevisiondelta(Protocol):
464 class irevisiondelta(Protocol):
464 """Represents a delta between one revision and another.
465 """Represents a delta between one revision and another.
465
466
466 Instances convey enough information to allow a revision to be exchanged
467 Instances convey enough information to allow a revision to be exchanged
467 with another repository.
468 with another repository.
468
469
469 Instances represent the fulltext revision data or a delta against
470 Instances represent the fulltext revision data or a delta against
470 another revision. Therefore the ``revision`` and ``delta`` attributes
471 another revision. Therefore the ``revision`` and ``delta`` attributes
471 are mutually exclusive.
472 are mutually exclusive.
472
473
473 Typically used for changegroup generation.
474 Typically used for changegroup generation.
474 """
475 """
475
476
476 node: bytes
477 node: bytes
477 """20 byte node of this revision."""
478 """20 byte node of this revision."""
478
479
479 p1node: bytes
480 p1node: bytes
480 """20 byte node of 1st parent of this revision."""
481 """20 byte node of 1st parent of this revision."""
481
482
482 p2node: bytes
483 p2node: bytes
483 """20 byte node of 2nd parent of this revision."""
484 """20 byte node of 2nd parent of this revision."""
484
485
485 # TODO: is this really optional? revlog.revlogrevisiondelta defaults to None
486 # TODO: is this really optional? revlog.revlogrevisiondelta defaults to None
486 linknode: bytes | None
487 linknode: bytes | None
487 """20 byte node of the changelog revision this node is linked to."""
488 """20 byte node of the changelog revision this node is linked to."""
488
489
489 flags: int
490 flags: int
490 """2 bytes of integer flags that apply to this revision.
491 """2 bytes of integer flags that apply to this revision.
491
492
492 This is a bitwise composition of the ``REVISION_FLAG_*`` constants.
493 This is a bitwise composition of the ``REVISION_FLAG_*`` constants.
493 """
494 """
494
495
495 basenode: bytes
496 basenode: bytes
496 """20 byte node of the revision this data is a delta against.
497 """20 byte node of the revision this data is a delta against.
497
498
498 ``nullid`` indicates that the revision is a full revision and not
499 ``nullid`` indicates that the revision is a full revision and not
499 a delta.
500 a delta.
500 """
501 """
501
502
502 baserevisionsize: int | None
503 baserevisionsize: int | None
503 """Size of base revision this delta is against.
504 """Size of base revision this delta is against.
504
505
505 May be ``None`` if ``basenode`` is ``nullid``.
506 May be ``None`` if ``basenode`` is ``nullid``.
506 """
507 """
507
508
508 # TODO: is this really optional? (Seems possible in
509 # TODO: is this really optional? (Seems possible in
509 # storageutil.emitrevisions()).
510 # storageutil.emitrevisions()).
510 revision: bytes | None
511 revision: bytes | None
511 """Raw fulltext of revision data for this node."""
512 """Raw fulltext of revision data for this node."""
512
513
513 delta: bytes | None
514 delta: bytes | None
514 """Delta between ``basenode`` and ``node``.
515 """Delta between ``basenode`` and ``node``.
515
516
516 Stored in the bdiff delta format.
517 Stored in the bdiff delta format.
517 """
518 """
518
519
519 sidedata: bytes | None
520 sidedata: bytes | None
520 """Raw sidedata bytes for the given revision."""
521 """Raw sidedata bytes for the given revision."""
521
522
522 protocol_flags: int
523 protocol_flags: int
523 """Single byte of integer flags that can influence the protocol.
524 """Single byte of integer flags that can influence the protocol.
524
525
525 This is a bitwise composition of the ``storageutil.CG_FLAG*`` constants.
526 This is a bitwise composition of the ``storageutil.CG_FLAG*`` constants.
526 """
527 """
527
528
528
529
529 class ifilerevisionssequence(Protocol):
530 class ifilerevisionssequence(Protocol):
530 """Contains index data for all revisions of a file.
531 """Contains index data for all revisions of a file.
531
532
532 Types implementing this behave like lists of tuples. The index
533 Types implementing this behave like lists of tuples. The index
533 in the list corresponds to the revision number. The values contain
534 in the list corresponds to the revision number. The values contain
534 index metadata.
535 index metadata.
535
536
536 The *null* revision (revision number -1) is always the last item
537 The *null* revision (revision number -1) is always the last item
537 in the index.
538 in the index.
538 """
539 """
539
540
540 def __len__(self):
541 def __len__(self):
541 """The total number of revisions."""
542 """The total number of revisions."""
542
543
543 def __getitem__(self, rev):
544 def __getitem__(self, rev):
544 """Returns the object having a specific revision number.
545 """Returns the object having a specific revision number.
545
546
546 Returns an 8-tuple with the following fields:
547 Returns an 8-tuple with the following fields:
547
548
548 offset+flags
549 offset+flags
549 Contains the offset and flags for the revision. 64-bit unsigned
550 Contains the offset and flags for the revision. 64-bit unsigned
550 integer where first 6 bytes are the offset and the next 2 bytes
551 integer where first 6 bytes are the offset and the next 2 bytes
551 are flags. The offset can be 0 if it is not used by the store.
552 are flags. The offset can be 0 if it is not used by the store.
552 compressed size
553 compressed size
553 Size of the revision data in the store. It can be 0 if it isn't
554 Size of the revision data in the store. It can be 0 if it isn't
554 needed by the store.
555 needed by the store.
555 uncompressed size
556 uncompressed size
556 Fulltext size. It can be 0 if it isn't needed by the store.
557 Fulltext size. It can be 0 if it isn't needed by the store.
557 base revision
558 base revision
558 Revision number of revision the delta for storage is encoded
559 Revision number of revision the delta for storage is encoded
559 against. -1 indicates not encoded against a base revision.
560 against. -1 indicates not encoded against a base revision.
560 link revision
561 link revision
561 Revision number of changelog revision this entry is related to.
562 Revision number of changelog revision this entry is related to.
562 p1 revision
563 p1 revision
563 Revision number of 1st parent. -1 if no 1st parent.
564 Revision number of 1st parent. -1 if no 1st parent.
564 p2 revision
565 p2 revision
565 Revision number of 2nd parent. -1 if no 1st parent.
566 Revision number of 2nd parent. -1 if no 1st parent.
566 node
567 node
567 Binary node value for this revision number.
568 Binary node value for this revision number.
568
569
569 Negative values should index off the end of the sequence. ``-1``
570 Negative values should index off the end of the sequence. ``-1``
570 should return the null revision. ``-2`` should return the most
571 should return the null revision. ``-2`` should return the most
571 recent revision.
572 recent revision.
572 """
573 """
573
574
574 def __contains__(self, rev):
575 def __contains__(self, rev):
575 """Whether a revision number exists."""
576 """Whether a revision number exists."""
576
577
577 def insert(self, i, entry):
578 def insert(self, i, entry):
578 """Add an item to the index at specific revision."""
579 """Add an item to the index at specific revision."""
579
580
580
581
581 class ifileindex(Protocol):
582 class ifileindex(Protocol):
582 """Storage interface for index data of a single file.
583 """Storage interface for index data of a single file.
583
584
584 File storage data is divided into index metadata and data storage.
585 File storage data is divided into index metadata and data storage.
585 This interface defines the index portion of the interface.
586 This interface defines the index portion of the interface.
586
587
587 The index logically consists of:
588 The index logically consists of:
588
589
589 * A mapping between revision numbers and nodes.
590 * A mapping between revision numbers and nodes.
590 * DAG data (storing and querying the relationship between nodes).
591 * DAG data (storing and querying the relationship between nodes).
591 * Metadata to facilitate storage.
592 * Metadata to facilitate storage.
592 """
593 """
593
594
594 nullid: bytes
595 nullid: bytes
595 """node for the null revision for use as delta base."""
596 """node for the null revision for use as delta base."""
596
597
597 def __len__(self):
598 def __len__(self):
598 """Obtain the number of revisions stored for this file."""
599 """Obtain the number of revisions stored for this file."""
599
600
600 def __iter__(self):
601 def __iter__(self):
601 """Iterate over revision numbers for this file."""
602 """Iterate over revision numbers for this file."""
602
603
603 def hasnode(self, node):
604 def hasnode(self, node):
604 """Returns a bool indicating if a node is known to this store.
605 """Returns a bool indicating if a node is known to this store.
605
606
606 Implementations must only return True for full, binary node values:
607 Implementations must only return True for full, binary node values:
607 hex nodes, revision numbers, and partial node matches must be
608 hex nodes, revision numbers, and partial node matches must be
608 rejected.
609 rejected.
609
610
610 The null node is never present.
611 The null node is never present.
611 """
612 """
612
613
613 def revs(self, start=0, stop=None):
614 def revs(self, start=0, stop=None):
614 """Iterate over revision numbers for this file, with control."""
615 """Iterate over revision numbers for this file, with control."""
615
616
616 def parents(self, node):
617 def parents(self, node):
617 """Returns a 2-tuple of parent nodes for a revision.
618 """Returns a 2-tuple of parent nodes for a revision.
618
619
619 Values will be ``nullid`` if the parent is empty.
620 Values will be ``nullid`` if the parent is empty.
620 """
621 """
621
622
622 def parentrevs(self, rev):
623 def parentrevs(self, rev):
623 """Like parents() but operates on revision numbers."""
624 """Like parents() but operates on revision numbers."""
624
625
625 def rev(self, node):
626 def rev(self, node):
626 """Obtain the revision number given a node.
627 """Obtain the revision number given a node.
627
628
628 Raises ``error.LookupError`` if the node is not known.
629 Raises ``error.LookupError`` if the node is not known.
629 """
630 """
630
631
631 def node(self, rev):
632 def node(self, rev):
632 """Obtain the node value given a revision number.
633 """Obtain the node value given a revision number.
633
634
634 Raises ``IndexError`` if the node is not known.
635 Raises ``IndexError`` if the node is not known.
635 """
636 """
636
637
637 def lookup(self, node):
638 def lookup(self, node):
638 """Attempt to resolve a value to a node.
639 """Attempt to resolve a value to a node.
639
640
640 Value can be a binary node, hex node, revision number, or a string
641 Value can be a binary node, hex node, revision number, or a string
641 that can be converted to an integer.
642 that can be converted to an integer.
642
643
643 Raises ``error.LookupError`` if a node could not be resolved.
644 Raises ``error.LookupError`` if a node could not be resolved.
644 """
645 """
645
646
646 def linkrev(self, rev):
647 def linkrev(self, rev):
647 """Obtain the changeset revision number a revision is linked to."""
648 """Obtain the changeset revision number a revision is linked to."""
648
649
649 def iscensored(self, rev):
650 def iscensored(self, rev):
650 """Return whether a revision's content has been censored."""
651 """Return whether a revision's content has been censored."""
651
652
652 def commonancestorsheads(self, node1, node2):
653 def commonancestorsheads(self, node1, node2):
653 """Obtain an iterable of nodes containing heads of common ancestors.
654 """Obtain an iterable of nodes containing heads of common ancestors.
654
655
655 See ``ancestor.commonancestorsheads()``.
656 See ``ancestor.commonancestorsheads()``.
656 """
657 """
657
658
658 def descendants(self, revs):
659 def descendants(self, revs):
659 """Obtain descendant revision numbers for a set of revision numbers.
660 """Obtain descendant revision numbers for a set of revision numbers.
660
661
661 If ``nullrev`` is in the set, this is equivalent to ``revs()``.
662 If ``nullrev`` is in the set, this is equivalent to ``revs()``.
662 """
663 """
663
664
664 def heads(self, start=None, stop=None):
665 def heads(self, start=None, stop=None):
665 """Obtain a list of nodes that are DAG heads, with control.
666 """Obtain a list of nodes that are DAG heads, with control.
666
667
667 The set of revisions examined can be limited by specifying
668 The set of revisions examined can be limited by specifying
668 ``start`` and ``stop``. ``start`` is a node. ``stop`` is an
669 ``start`` and ``stop``. ``start`` is a node. ``stop`` is an
669 iterable of nodes. DAG traversal starts at earlier revision
670 iterable of nodes. DAG traversal starts at earlier revision
670 ``start`` and iterates forward until any node in ``stop`` is
671 ``start`` and iterates forward until any node in ``stop`` is
671 encountered.
672 encountered.
672 """
673 """
673
674
674 def children(self, node):
675 def children(self, node):
675 """Obtain nodes that are children of a node.
676 """Obtain nodes that are children of a node.
676
677
677 Returns a list of nodes.
678 Returns a list of nodes.
678 """
679 """
679
680
680
681
681 class ifiledata(Protocol):
682 class ifiledata(Protocol):
682 """Storage interface for data storage of a specific file.
683 """Storage interface for data storage of a specific file.
683
684
684 This complements ``ifileindex`` and provides an interface for accessing
685 This complements ``ifileindex`` and provides an interface for accessing
685 data for a tracked file.
686 data for a tracked file.
686 """
687 """
687
688
688 def size(self, rev):
689 def size(self, rev):
689 """Obtain the fulltext size of file data.
690 """Obtain the fulltext size of file data.
690
691
691 Any metadata is excluded from size measurements.
692 Any metadata is excluded from size measurements.
692 """
693 """
693
694
694 def revision(self, node):
695 def revision(self, node):
695 """Obtain fulltext data for a node.
696 """Obtain fulltext data for a node.
696
697
697 By default, any storage transformations are applied before the data
698 By default, any storage transformations are applied before the data
698 is returned. If ``raw`` is True, non-raw storage transformations
699 is returned. If ``raw`` is True, non-raw storage transformations
699 are not applied.
700 are not applied.
700
701
701 The fulltext data may contain a header containing metadata. Most
702 The fulltext data may contain a header containing metadata. Most
702 consumers should use ``read()`` to obtain the actual file data.
703 consumers should use ``read()`` to obtain the actual file data.
703 """
704 """
704
705
705 def rawdata(self, node):
706 def rawdata(self, node):
706 """Obtain raw data for a node."""
707 """Obtain raw data for a node."""
707
708
708 def read(self, node):
709 def read(self, node):
709 """Resolve file fulltext data.
710 """Resolve file fulltext data.
710
711
711 This is similar to ``revision()`` except any metadata in the data
712 This is similar to ``revision()`` except any metadata in the data
712 headers is stripped.
713 headers is stripped.
713 """
714 """
714
715
715 def renamed(self, node):
716 def renamed(self, node):
716 """Obtain copy metadata for a node.
717 """Obtain copy metadata for a node.
717
718
718 Returns ``False`` if no copy metadata is stored or a 2-tuple of
719 Returns ``False`` if no copy metadata is stored or a 2-tuple of
719 (path, node) from which this revision was copied.
720 (path, node) from which this revision was copied.
720 """
721 """
721
722
722 def cmp(self, node, fulltext):
723 def cmp(self, node, fulltext):
723 """Compare fulltext to another revision.
724 """Compare fulltext to another revision.
724
725
725 Returns True if the fulltext is different from what is stored.
726 Returns True if the fulltext is different from what is stored.
726
727
727 This takes copy metadata into account.
728 This takes copy metadata into account.
728
729
729 TODO better document the copy metadata and censoring logic.
730 TODO better document the copy metadata and censoring logic.
730 """
731 """
731
732
732 def emitrevisions(
733 def emitrevisions(
733 self,
734 self,
734 nodes,
735 nodes,
735 nodesorder=None,
736 nodesorder=None,
736 revisiondata=False,
737 revisiondata=False,
737 assumehaveparentrevisions=False,
738 assumehaveparentrevisions=False,
738 deltamode=CG_DELTAMODE_STD,
739 deltamode=CG_DELTAMODE_STD,
739 ):
740 ):
740 """Produce ``irevisiondelta`` for revisions.
741 """Produce ``irevisiondelta`` for revisions.
741
742
742 Given an iterable of nodes, emits objects conforming to the
743 Given an iterable of nodes, emits objects conforming to the
743 ``irevisiondelta`` interface that describe revisions in storage.
744 ``irevisiondelta`` interface that describe revisions in storage.
744
745
745 This method is a generator.
746 This method is a generator.
746
747
747 The input nodes may be unordered. Implementations must ensure that a
748 The input nodes may be unordered. Implementations must ensure that a
748 node's parents are emitted before the node itself. Transitively, this
749 node's parents are emitted before the node itself. Transitively, this
749 means that a node may only be emitted once all its ancestors in
750 means that a node may only be emitted once all its ancestors in
750 ``nodes`` have also been emitted.
751 ``nodes`` have also been emitted.
751
752
752 By default, emits "index" data (the ``node``, ``p1node``, and
753 By default, emits "index" data (the ``node``, ``p1node``, and
753 ``p2node`` attributes). If ``revisiondata`` is set, revision data
754 ``p2node`` attributes). If ``revisiondata`` is set, revision data
754 will also be present on the emitted objects.
755 will also be present on the emitted objects.
755
756
756 With default argument values, implementations can choose to emit
757 With default argument values, implementations can choose to emit
757 either fulltext revision data or a delta. When emitting deltas,
758 either fulltext revision data or a delta. When emitting deltas,
758 implementations must consider whether the delta's base revision
759 implementations must consider whether the delta's base revision
759 fulltext is available to the receiver.
760 fulltext is available to the receiver.
760
761
761 The base revision fulltext is guaranteed to be available if any of
762 The base revision fulltext is guaranteed to be available if any of
762 the following are met:
763 the following are met:
763
764
764 * Its fulltext revision was emitted by this method call.
765 * Its fulltext revision was emitted by this method call.
765 * A delta for that revision was emitted by this method call.
766 * A delta for that revision was emitted by this method call.
766 * ``assumehaveparentrevisions`` is True and the base revision is a
767 * ``assumehaveparentrevisions`` is True and the base revision is a
767 parent of the node.
768 parent of the node.
768
769
769 ``nodesorder`` can be used to control the order that revisions are
770 ``nodesorder`` can be used to control the order that revisions are
770 emitted. By default, revisions can be reordered as long as they are
771 emitted. By default, revisions can be reordered as long as they are
771 in DAG topological order (see above). If the value is ``nodes``,
772 in DAG topological order (see above). If the value is ``nodes``,
772 the iteration order from ``nodes`` should be used. If the value is
773 the iteration order from ``nodes`` should be used. If the value is
773 ``storage``, then the native order from the backing storage layer
774 ``storage``, then the native order from the backing storage layer
774 is used. (Not all storage layers will have strong ordering and behavior
775 is used. (Not all storage layers will have strong ordering and behavior
775 of this mode is storage-dependent.) ``nodes`` ordering can force
776 of this mode is storage-dependent.) ``nodes`` ordering can force
776 revisions to be emitted before their ancestors, so consumers should
777 revisions to be emitted before their ancestors, so consumers should
777 use it with care.
778 use it with care.
778
779
779 The ``linknode`` attribute on the returned ``irevisiondelta`` may not
780 The ``linknode`` attribute on the returned ``irevisiondelta`` may not
780 be set and it is the caller's responsibility to resolve it, if needed.
781 be set and it is the caller's responsibility to resolve it, if needed.
781
782
782 If ``deltamode`` is CG_DELTAMODE_PREV and revision data is requested,
783 If ``deltamode`` is CG_DELTAMODE_PREV and revision data is requested,
783 all revision data should be emitted as deltas against the revision
784 all revision data should be emitted as deltas against the revision
784 emitted just prior. The initial revision should be a delta against its
785 emitted just prior. The initial revision should be a delta against its
785 1st parent.
786 1st parent.
786 """
787 """
787
788
788
789
789 class ifilemutation(Protocol):
790 class ifilemutation(Protocol):
790 """Storage interface for mutation events of a tracked file."""
791 """Storage interface for mutation events of a tracked file."""
791
792
792 def add(self, filedata, meta, transaction, linkrev, p1, p2):
793 def add(self, filedata, meta, transaction, linkrev, p1, p2):
793 """Add a new revision to the store.
794 """Add a new revision to the store.
794
795
795 Takes file data, dictionary of metadata, a transaction, linkrev,
796 Takes file data, dictionary of metadata, a transaction, linkrev,
796 and parent nodes.
797 and parent nodes.
797
798
798 Returns the node that was added.
799 Returns the node that was added.
799
800
800 May no-op if a revision matching the supplied data is already stored.
801 May no-op if a revision matching the supplied data is already stored.
801 """
802 """
802
803
803 def addrevision(
804 def addrevision(
804 self,
805 self,
805 revisiondata,
806 revisiondata,
806 transaction,
807 transaction,
807 linkrev,
808 linkrev,
808 p1,
809 p1,
809 p2,
810 p2,
810 node=None,
811 node=None,
811 flags=0,
812 flags=0,
812 cachedelta=None,
813 cachedelta=None,
813 ):
814 ):
814 """Add a new revision to the store and return its number.
815 """Add a new revision to the store and return its number.
815
816
816 This is similar to ``add()`` except it operates at a lower level.
817 This is similar to ``add()`` except it operates at a lower level.
817
818
818 The data passed in already contains a metadata header, if any.
819 The data passed in already contains a metadata header, if any.
819
820
820 ``node`` and ``flags`` can be used to define the expected node and
821 ``node`` and ``flags`` can be used to define the expected node and
821 the flags to use with storage. ``flags`` is a bitwise value composed
822 the flags to use with storage. ``flags`` is a bitwise value composed
822 of the various ``REVISION_FLAG_*`` constants.
823 of the various ``REVISION_FLAG_*`` constants.
823
824
824 ``add()`` is usually called when adding files from e.g. the working
825 ``add()`` is usually called when adding files from e.g. the working
825 directory. ``addrevision()`` is often called by ``add()`` and for
826 directory. ``addrevision()`` is often called by ``add()`` and for
826 scenarios where revision data has already been computed, such as when
827 scenarios where revision data has already been computed, such as when
827 applying raw data from a peer repo.
828 applying raw data from a peer repo.
828 """
829 """
829
830
830 def addgroup(
831 def addgroup(
831 self,
832 self,
832 deltas,
833 deltas,
833 linkmapper,
834 linkmapper,
834 transaction,
835 transaction,
835 addrevisioncb=None,
836 addrevisioncb=None,
836 duplicaterevisioncb=None,
837 duplicaterevisioncb=None,
837 maybemissingparents=False,
838 maybemissingparents=False,
838 ):
839 ):
839 """Process a series of deltas for storage.
840 """Process a series of deltas for storage.
840
841
841 ``deltas`` is an iterable of 7-tuples of
842 ``deltas`` is an iterable of 7-tuples of
842 (node, p1, p2, linknode, deltabase, delta, flags) defining revisions
843 (node, p1, p2, linknode, deltabase, delta, flags) defining revisions
843 to add.
844 to add.
844
845
845 The ``delta`` field contains ``mpatch`` data to apply to a base
846 The ``delta`` field contains ``mpatch`` data to apply to a base
846 revision, identified by ``deltabase``. The base node can be
847 revision, identified by ``deltabase``. The base node can be
847 ``nullid``, in which case the header from the delta can be ignored
848 ``nullid``, in which case the header from the delta can be ignored
848 and the delta used as the fulltext.
849 and the delta used as the fulltext.
849
850
850 ``alwayscache`` instructs the lower layers to cache the content of the
851 ``alwayscache`` instructs the lower layers to cache the content of the
851 newly added revision, even if it needs to be explicitly computed.
852 newly added revision, even if it needs to be explicitly computed.
852 This used to be the default when ``addrevisioncb`` was provided up to
853 This used to be the default when ``addrevisioncb`` was provided up to
853 Mercurial 5.8.
854 Mercurial 5.8.
854
855
855 ``addrevisioncb`` should be called for each new rev as it is committed.
856 ``addrevisioncb`` should be called for each new rev as it is committed.
856 ``duplicaterevisioncb`` should be called for all revs with a
857 ``duplicaterevisioncb`` should be called for all revs with a
857 pre-existing node.
858 pre-existing node.
858
859
859 ``maybemissingparents`` is a bool indicating whether the incoming
860 ``maybemissingparents`` is a bool indicating whether the incoming
860 data may reference parents/ancestor revisions that aren't present.
861 data may reference parents/ancestor revisions that aren't present.
861 This flag is set when receiving data into a "shallow" store that
862 This flag is set when receiving data into a "shallow" store that
862 doesn't hold all history.
863 doesn't hold all history.
863
864
864 Returns a list of nodes that were processed. A node will be in the list
865 Returns a list of nodes that were processed. A node will be in the list
865 even if it existed in the store previously.
866 even if it existed in the store previously.
866 """
867 """
867
868
868 def censorrevision(self, tr, node, tombstone=b''):
869 def censorrevision(self, tr, node, tombstone=b''):
869 """Remove the content of a single revision.
870 """Remove the content of a single revision.
870
871
871 The specified ``node`` will have its content purged from storage.
872 The specified ``node`` will have its content purged from storage.
872 Future attempts to access the revision data for this node will
873 Future attempts to access the revision data for this node will
873 result in failure.
874 result in failure.
874
875
875 A ``tombstone`` message can optionally be stored. This message may be
876 A ``tombstone`` message can optionally be stored. This message may be
876 displayed to users when they attempt to access the missing revision
877 displayed to users when they attempt to access the missing revision
877 data.
878 data.
878
879
879 Storage backends may have stored deltas against the previous content
880 Storage backends may have stored deltas against the previous content
880 in this revision. As part of censoring a revision, these storage
881 in this revision. As part of censoring a revision, these storage
881 backends are expected to rewrite any internally stored deltas such
882 backends are expected to rewrite any internally stored deltas such
882 that they no longer reference the deleted content.
883 that they no longer reference the deleted content.
883 """
884 """
884
885
885 def getstrippoint(self, minlink):
886 def getstrippoint(self, minlink):
886 """Find the minimum revision that must be stripped to strip a linkrev.
887 """Find the minimum revision that must be stripped to strip a linkrev.
887
888
888 Returns a 2-tuple containing the minimum revision number and a set
889 Returns a 2-tuple containing the minimum revision number and a set
889 of all revisions numbers that would be broken by this strip.
890 of all revisions numbers that would be broken by this strip.
890
891
891 TODO this is highly revlog centric and should be abstracted into
892 TODO this is highly revlog centric and should be abstracted into
892 a higher-level deletion API. ``repair.strip()`` relies on this.
893 a higher-level deletion API. ``repair.strip()`` relies on this.
893 """
894 """
894
895
895 def strip(self, minlink, transaction):
896 def strip(self, minlink, transaction):
896 """Remove storage of items starting at a linkrev.
897 """Remove storage of items starting at a linkrev.
897
898
898 This uses ``getstrippoint()`` to determine the first node to remove.
899 This uses ``getstrippoint()`` to determine the first node to remove.
899 Then it effectively truncates storage for all revisions after that.
900 Then it effectively truncates storage for all revisions after that.
900
901
901 TODO this is highly revlog centric and should be abstracted into a
902 TODO this is highly revlog centric and should be abstracted into a
902 higher-level deletion API.
903 higher-level deletion API.
903 """
904 """
904
905
905
906
906 class ifilestorage(ifileindex, ifiledata, ifilemutation):
907 class ifilestorage(ifileindex, ifiledata, ifilemutation):
907 """Complete storage interface for a single tracked file."""
908 """Complete storage interface for a single tracked file."""
908
909
909 def files(self):
910 def files(self):
910 """Obtain paths that are backing storage for this file.
911 """Obtain paths that are backing storage for this file.
911
912
912 TODO this is used heavily by verify code and there should probably
913 TODO this is used heavily by verify code and there should probably
913 be a better API for that.
914 be a better API for that.
914 """
915 """
915
916
916 def storageinfo(
917 def storageinfo(
917 self,
918 self,
918 exclusivefiles=False,
919 exclusivefiles=False,
919 sharedfiles=False,
920 sharedfiles=False,
920 revisionscount=False,
921 revisionscount=False,
921 trackedsize=False,
922 trackedsize=False,
922 storedsize=False,
923 storedsize=False,
923 ):
924 ):
924 """Obtain information about storage for this file's data.
925 """Obtain information about storage for this file's data.
925
926
926 Returns a dict describing storage for this tracked path. The keys
927 Returns a dict describing storage for this tracked path. The keys
927 in the dict map to arguments of the same. The arguments are bools
928 in the dict map to arguments of the same. The arguments are bools
928 indicating whether to calculate and obtain that data.
929 indicating whether to calculate and obtain that data.
929
930
930 exclusivefiles
931 exclusivefiles
931 Iterable of (vfs, path) describing files that are exclusively
932 Iterable of (vfs, path) describing files that are exclusively
932 used to back storage for this tracked path.
933 used to back storage for this tracked path.
933
934
934 sharedfiles
935 sharedfiles
935 Iterable of (vfs, path) describing files that are used to back
936 Iterable of (vfs, path) describing files that are used to back
936 storage for this tracked path. Those files may also provide storage
937 storage for this tracked path. Those files may also provide storage
937 for other stored entities.
938 for other stored entities.
938
939
939 revisionscount
940 revisionscount
940 Number of revisions available for retrieval.
941 Number of revisions available for retrieval.
941
942
942 trackedsize
943 trackedsize
943 Total size in bytes of all tracked revisions. This is a sum of the
944 Total size in bytes of all tracked revisions. This is a sum of the
944 length of the fulltext of all revisions.
945 length of the fulltext of all revisions.
945
946
946 storedsize
947 storedsize
947 Total size in bytes used to store data for all tracked revisions.
948 Total size in bytes used to store data for all tracked revisions.
948 This is commonly less than ``trackedsize`` due to internal usage
949 This is commonly less than ``trackedsize`` due to internal usage
949 of deltas rather than fulltext revisions.
950 of deltas rather than fulltext revisions.
950
951
951 Not all storage backends may support all queries are have a reasonable
952 Not all storage backends may support all queries are have a reasonable
952 value to use. In that case, the value should be set to ``None`` and
953 value to use. In that case, the value should be set to ``None`` and
953 callers are expected to handle this special value.
954 callers are expected to handle this special value.
954 """
955 """
955
956
956 def verifyintegrity(self, state):
957 def verifyintegrity(self, state):
957 """Verifies the integrity of file storage.
958 """Verifies the integrity of file storage.
958
959
959 ``state`` is a dict holding state of the verifier process. It can be
960 ``state`` is a dict holding state of the verifier process. It can be
960 used to communicate data between invocations of multiple storage
961 used to communicate data between invocations of multiple storage
961 primitives.
962 primitives.
962
963
963 If individual revisions cannot have their revision content resolved,
964 If individual revisions cannot have their revision content resolved,
964 the method is expected to set the ``skipread`` key to a set of nodes
965 the method is expected to set the ``skipread`` key to a set of nodes
965 that encountered problems. If set, the method can also add the node(s)
966 that encountered problems. If set, the method can also add the node(s)
966 to ``safe_renamed`` in order to indicate nodes that may perform the
967 to ``safe_renamed`` in order to indicate nodes that may perform the
967 rename checks with currently accessible data.
968 rename checks with currently accessible data.
968
969
969 The method yields objects conforming to the ``iverifyproblem``
970 The method yields objects conforming to the ``iverifyproblem``
970 interface.
971 interface.
971 """
972 """
972
973
973
974
974 class idirs(Protocol):
975 class idirs(Protocol):
975 """Interface representing a collection of directories from paths.
976 """Interface representing a collection of directories from paths.
976
977
977 This interface is essentially a derived data structure representing
978 This interface is essentially a derived data structure representing
978 directories from a collection of paths.
979 directories from a collection of paths.
979 """
980 """
980
981
981 def addpath(self, path):
982 def addpath(self, path):
982 """Add a path to the collection.
983 """Add a path to the collection.
983
984
984 All directories in the path will be added to the collection.
985 All directories in the path will be added to the collection.
985 """
986 """
986
987
987 def delpath(self, path):
988 def delpath(self, path):
988 """Remove a path from the collection.
989 """Remove a path from the collection.
989
990
990 If the removal was the last path in a particular directory, the
991 If the removal was the last path in a particular directory, the
991 directory is removed from the collection.
992 directory is removed from the collection.
992 """
993 """
993
994
994 def __iter__(self):
995 def __iter__(self):
995 """Iterate over the directories in this collection of paths."""
996 """Iterate over the directories in this collection of paths."""
996
997
997 def __contains__(self, path):
998 def __contains__(self, path):
998 """Whether a specific directory is in this collection."""
999 """Whether a specific directory is in this collection."""
999
1000
1000
1001
1001 class imanifestdict(Protocol):
1002 class imanifestdict(Protocol):
1002 """Interface representing a manifest data structure.
1003 """Interface representing a manifest data structure.
1003
1004
1004 A manifest is effectively a dict mapping paths to entries. Each entry
1005 A manifest is effectively a dict mapping paths to entries. Each entry
1005 consists of a binary node and extra flags affecting that entry.
1006 consists of a binary node and extra flags affecting that entry.
1006 """
1007 """
1007
1008
1008 def __getitem__(self, path):
1009 def __getitem__(self, path):
1009 """Returns the binary node value for a path in the manifest.
1010 """Returns the binary node value for a path in the manifest.
1010
1011
1011 Raises ``KeyError`` if the path does not exist in the manifest.
1012 Raises ``KeyError`` if the path does not exist in the manifest.
1012
1013
1013 Equivalent to ``self.find(path)[0]``.
1014 Equivalent to ``self.find(path)[0]``.
1014 """
1015 """
1015
1016
1016 def find(self, path):
1017 def find(self, path):
1017 """Returns the entry for a path in the manifest.
1018 """Returns the entry for a path in the manifest.
1018
1019
1019 Returns a 2-tuple of (node, flags).
1020 Returns a 2-tuple of (node, flags).
1020
1021
1021 Raises ``KeyError`` if the path does not exist in the manifest.
1022 Raises ``KeyError`` if the path does not exist in the manifest.
1022 """
1023 """
1023
1024
1024 def __len__(self):
1025 def __len__(self):
1025 """Return the number of entries in the manifest."""
1026 """Return the number of entries in the manifest."""
1026
1027
1027 def __nonzero__(self):
1028 def __nonzero__(self):
1028 """Returns True if the manifest has entries, False otherwise."""
1029 """Returns True if the manifest has entries, False otherwise."""
1029
1030
1030 __bool__ = __nonzero__
1031 __bool__ = __nonzero__
1031
1032
1032 def set(self, path, node, flags):
1033 def set(self, path, node, flags):
1033 """Define the node value and flags for a path in the manifest.
1034 """Define the node value and flags for a path in the manifest.
1034
1035
1035 Equivalent to __setitem__ followed by setflag, but can be more efficient.
1036 Equivalent to __setitem__ followed by setflag, but can be more efficient.
1036 """
1037 """
1037
1038
1038 def __setitem__(self, path, node):
1039 def __setitem__(self, path, node):
1039 """Define the node value for a path in the manifest.
1040 """Define the node value for a path in the manifest.
1040
1041
1041 If the path is already in the manifest, its flags will be copied to
1042 If the path is already in the manifest, its flags will be copied to
1042 the new entry.
1043 the new entry.
1043 """
1044 """
1044
1045
1045 def __contains__(self, path):
1046 def __contains__(self, path):
1046 """Whether a path exists in the manifest."""
1047 """Whether a path exists in the manifest."""
1047
1048
1048 def __delitem__(self, path):
1049 def __delitem__(self, path):
1049 """Remove a path from the manifest.
1050 """Remove a path from the manifest.
1050
1051
1051 Raises ``KeyError`` if the path is not in the manifest.
1052 Raises ``KeyError`` if the path is not in the manifest.
1052 """
1053 """
1053
1054
1054 def __iter__(self):
1055 def __iter__(self):
1055 """Iterate over paths in the manifest."""
1056 """Iterate over paths in the manifest."""
1056
1057
1057 def iterkeys(self):
1058 def iterkeys(self):
1058 """Iterate over paths in the manifest."""
1059 """Iterate over paths in the manifest."""
1059
1060
1060 def keys(self):
1061 def keys(self):
1061 """Obtain a list of paths in the manifest."""
1062 """Obtain a list of paths in the manifest."""
1062
1063
1063 def filesnotin(self, other, match=None):
1064 def filesnotin(self, other, match=None):
1064 """Obtain the set of paths in this manifest but not in another.
1065 """Obtain the set of paths in this manifest but not in another.
1065
1066
1066 ``match`` is an optional matcher function to be applied to both
1067 ``match`` is an optional matcher function to be applied to both
1067 manifests.
1068 manifests.
1068
1069
1069 Returns a set of paths.
1070 Returns a set of paths.
1070 """
1071 """
1071
1072
1072 def dirs(self):
1073 def dirs(self):
1073 """Returns an object implementing the ``idirs`` interface."""
1074 """Returns an object implementing the ``idirs`` interface."""
1074
1075
1075 def hasdir(self, dir):
1076 def hasdir(self, dir):
1076 """Returns a bool indicating if a directory is in this manifest."""
1077 """Returns a bool indicating if a directory is in this manifest."""
1077
1078
1078 def walk(self, match):
1079 def walk(self, match):
1079 """Generator of paths in manifest satisfying a matcher.
1080 """Generator of paths in manifest satisfying a matcher.
1080
1081
1081 If the matcher has explicit files listed and they don't exist in
1082 If the matcher has explicit files listed and they don't exist in
1082 the manifest, ``match.bad()`` is called for each missing file.
1083 the manifest, ``match.bad()`` is called for each missing file.
1083 """
1084 """
1084
1085
1085 def diff(self, other, match=None, clean=False):
1086 def diff(self, other, match=None, clean=False):
1086 """Find differences between this manifest and another.
1087 """Find differences between this manifest and another.
1087
1088
1088 This manifest is compared to ``other``.
1089 This manifest is compared to ``other``.
1089
1090
1090 If ``match`` is provided, the two manifests are filtered against this
1091 If ``match`` is provided, the two manifests are filtered against this
1091 matcher and only entries satisfying the matcher are compared.
1092 matcher and only entries satisfying the matcher are compared.
1092
1093
1093 If ``clean`` is True, unchanged files are included in the returned
1094 If ``clean`` is True, unchanged files are included in the returned
1094 object.
1095 object.
1095
1096
1096 Returns a dict with paths as keys and values of 2-tuples of 2-tuples of
1097 Returns a dict with paths as keys and values of 2-tuples of 2-tuples of
1097 the form ``((node1, flag1), (node2, flag2))`` where ``(node1, flag1)``
1098 the form ``((node1, flag1), (node2, flag2))`` where ``(node1, flag1)``
1098 represents the node and flags for this manifest and ``(node2, flag2)``
1099 represents the node and flags for this manifest and ``(node2, flag2)``
1099 are the same for the other manifest.
1100 are the same for the other manifest.
1100 """
1101 """
1101
1102
1102 def setflag(self, path, flag):
1103 def setflag(self, path, flag):
1103 """Set the flag value for a given path.
1104 """Set the flag value for a given path.
1104
1105
1105 Raises ``KeyError`` if the path is not already in the manifest.
1106 Raises ``KeyError`` if the path is not already in the manifest.
1106 """
1107 """
1107
1108
1108 def get(self, path, default=None):
1109 def get(self, path, default=None):
1109 """Obtain the node value for a path or a default value if missing."""
1110 """Obtain the node value for a path or a default value if missing."""
1110
1111
1111 def flags(self, path):
1112 def flags(self, path):
1112 """Return the flags value for a path (default: empty bytestring)."""
1113 """Return the flags value for a path (default: empty bytestring)."""
1113
1114
1114 def copy(self):
1115 def copy(self):
1115 """Return a copy of this manifest."""
1116 """Return a copy of this manifest."""
1116
1117
1117 def items(self):
1118 def items(self):
1118 """Returns an iterable of (path, node) for items in this manifest."""
1119 """Returns an iterable of (path, node) for items in this manifest."""
1119
1120
1120 def iteritems(self):
1121 def iteritems(self):
1121 """Identical to items()."""
1122 """Identical to items()."""
1122
1123
1123 def iterentries(self):
1124 def iterentries(self):
1124 """Returns an iterable of (path, node, flags) for this manifest.
1125 """Returns an iterable of (path, node, flags) for this manifest.
1125
1126
1126 Similar to ``iteritems()`` except items are a 3-tuple and include
1127 Similar to ``iteritems()`` except items are a 3-tuple and include
1127 flags.
1128 flags.
1128 """
1129 """
1129
1130
1130 def text(self):
1131 def text(self):
1131 """Obtain the raw data representation for this manifest.
1132 """Obtain the raw data representation for this manifest.
1132
1133
1133 Result is used to create a manifest revision.
1134 Result is used to create a manifest revision.
1134 """
1135 """
1135
1136
1136 def fastdelta(self, base, changes):
1137 def fastdelta(self, base, changes):
1137 """Obtain a delta between this manifest and another given changes.
1138 """Obtain a delta between this manifest and another given changes.
1138
1139
1139 ``base`` in the raw data representation for another manifest.
1140 ``base`` in the raw data representation for another manifest.
1140
1141
1141 ``changes`` is an iterable of ``(path, to_delete)``.
1142 ``changes`` is an iterable of ``(path, to_delete)``.
1142
1143
1143 Returns a 2-tuple containing ``bytearray(self.text())`` and the
1144 Returns a 2-tuple containing ``bytearray(self.text())`` and the
1144 delta between ``base`` and this manifest.
1145 delta between ``base`` and this manifest.
1145
1146
1146 If this manifest implementation can't support ``fastdelta()``,
1147 If this manifest implementation can't support ``fastdelta()``,
1147 raise ``mercurial.manifest.FastdeltaUnavailable``.
1148 raise ``mercurial.manifest.FastdeltaUnavailable``.
1148 """
1149 """
1149
1150
1150
1151
1151 class imanifestrevisionbase(Protocol):
1152 class imanifestrevisionbase(Protocol):
1152 """Base interface representing a single revision of a manifest.
1153 """Base interface representing a single revision of a manifest.
1153
1154
1154 Should not be used as a primary interface: should always be inherited
1155 Should not be used as a primary interface: should always be inherited
1155 as part of a larger interface.
1156 as part of a larger interface.
1156 """
1157 """
1157
1158
1158 def copy(self):
1159 def copy(self):
1159 """Obtain a copy of this manifest instance.
1160 """Obtain a copy of this manifest instance.
1160
1161
1161 Returns an object conforming to the ``imanifestrevisionwritable``
1162 Returns an object conforming to the ``imanifestrevisionwritable``
1162 interface. The instance will be associated with the same
1163 interface. The instance will be associated with the same
1163 ``imanifestlog`` collection as this instance.
1164 ``imanifestlog`` collection as this instance.
1164 """
1165 """
1165
1166
1166 def read(self):
1167 def read(self):
1167 """Obtain the parsed manifest data structure.
1168 """Obtain the parsed manifest data structure.
1168
1169
1169 The returned object conforms to the ``imanifestdict`` interface.
1170 The returned object conforms to the ``imanifestdict`` interface.
1170 """
1171 """
1171
1172
1172
1173
1173 class imanifestrevisionstored(imanifestrevisionbase):
1174 class imanifestrevisionstored(imanifestrevisionbase):
1174 """Interface representing a manifest revision committed to storage."""
1175 """Interface representing a manifest revision committed to storage."""
1175
1176
1176 def node(self):
1177 def node(self) -> bytes:
1177 """The binary node for this manifest."""
1178 """The binary node for this manifest."""
1178
1179
1179 parents: list[bytes]
1180 parents: list[bytes]
1180 """List of binary nodes that are parents for this manifest revision."""
1181 """List of binary nodes that are parents for this manifest revision."""
1181
1182
1182 def readdelta(self, shallow=False):
1183 def readdelta(self, shallow: bool = False):
1183 """Obtain the manifest data structure representing changes from parent.
1184 """Obtain the manifest data structure representing changes from parent.
1184
1185
1185 This manifest is compared to its 1st parent. A new manifest
1186 This manifest is compared to its 1st parent. A new manifest
1186 representing those differences is constructed.
1187 representing those differences is constructed.
1187
1188
1188 If `shallow` is True, this will read the delta for this directory,
1189 If `shallow` is True, this will read the delta for this directory,
1189 without recursively reading subdirectory manifests. Instead, any
1190 without recursively reading subdirectory manifests. Instead, any
1190 subdirectory entry will be reported as it appears in the manifest, i.e.
1191 subdirectory entry will be reported as it appears in the manifest, i.e.
1191 the subdirectory will be reported among files and distinguished only by
1192 the subdirectory will be reported among files and distinguished only by
1192 its 't' flag. This only apply if the underlying manifest support it.
1193 its 't' flag. This only apply if the underlying manifest support it.
1193
1194
1194 The returned object conforms to the ``imanifestdict`` interface.
1195 The returned object conforms to the ``imanifestdict`` interface.
1195 """
1196 """
1196
1197
1197 def read_any_fast_delta(self, valid_bases=None, *, shallow=False):
1198 def read_any_fast_delta(
1199 self,
1200 valid_bases: Collection[int] | None = None,
1201 *,
1202 shallow: bool = False,
1203 ):
1198 """read some manifest information as fast if possible
1204 """read some manifest information as fast if possible
1199
1205
1200 This might return a "delta", a manifest object containing only file
1206 This might return a "delta", a manifest object containing only file
1201 changed compared to another revisions. The `valid_bases` argument
1207 changed compared to another revisions. The `valid_bases` argument
1202 control the set of revision that might be used as a base.
1208 control the set of revision that might be used as a base.
1203
1209
1204 If no delta can be retrieved quickly, a full read of the manifest will
1210 If no delta can be retrieved quickly, a full read of the manifest will
1205 be performed instead.
1211 be performed instead.
1206
1212
1207 The function return a tuple with two elements. The first one is the
1213 The function return a tuple with two elements. The first one is the
1208 delta base used (or None if we did a full read), the second one is the
1214 delta base used (or None if we did a full read), the second one is the
1209 manifest information.
1215 manifest information.
1210
1216
1211 If `shallow` is True, this will read the delta for this directory,
1217 If `shallow` is True, this will read the delta for this directory,
1212 without recursively reading subdirectory manifests. Instead, any
1218 without recursively reading subdirectory manifests. Instead, any
1213 subdirectory entry will be reported as it appears in the manifest, i.e.
1219 subdirectory entry will be reported as it appears in the manifest, i.e.
1214 the subdirectory will be reported among files and distinguished only by
1220 the subdirectory will be reported among files and distinguished only by
1215 its 't' flag. This only apply if the underlying manifest support it.
1221 its 't' flag. This only apply if the underlying manifest support it.
1216
1222
1217 The returned object conforms to the ``imanifestdict`` interface.
1223 The returned object conforms to the ``imanifestdict`` interface.
1218 """
1224 """
1219
1225
1220 def read_delta_parents(self, *, shallow=False, exact=True):
1226 def read_delta_parents(self, *, shallow: bool = False, exact: bool = True):
1221 """return a diff from this revision against both parents.
1227 """return a diff from this revision against both parents.
1222
1228
1223 If `exact` is False, this might return a superset of the diff, containing
1229 If `exact` is False, this might return a superset of the diff, containing
1224 files that are actually present as is in one of the parents.
1230 files that are actually present as is in one of the parents.
1225
1231
1226 If `shallow` is True, this will read the delta for this directory,
1232 If `shallow` is True, this will read the delta for this directory,
1227 without recursively reading subdirectory manifests. Instead, any
1233 without recursively reading subdirectory manifests. Instead, any
1228 subdirectory entry will be reported as it appears in the manifest, i.e.
1234 subdirectory entry will be reported as it appears in the manifest, i.e.
1229 the subdirectory will be reported among files and distinguished only by
1235 the subdirectory will be reported among files and distinguished only by
1230 its 't' flag. This only apply if the underlying manifest support it.
1236 its 't' flag. This only apply if the underlying manifest support it.
1231
1237
1232 The returned object conforms to the ``imanifestdict`` interface."""
1238 The returned object conforms to the ``imanifestdict`` interface."""
1233
1239
1234 def read_delta_new_entries(self, *, shallow=False):
1240 def read_delta_new_entries(self, *, shallow: bool = False):
1235 """Return a manifest containing just the entries that might be new to
1241 """Return a manifest containing just the entries that might be new to
1236 the repository.
1242 the repository.
1237
1243
1238 This is often equivalent to a diff against both parents, but without
1244 This is often equivalent to a diff against both parents, but without
1239 garantee. For performance reason, It might contains more files in some cases.
1245 garantee. For performance reason, It might contains more files in some cases.
1240
1246
1241 If `shallow` is True, this will read the delta for this directory,
1247 If `shallow` is True, this will read the delta for this directory,
1242 without recursively reading subdirectory manifests. Instead, any
1248 without recursively reading subdirectory manifests. Instead, any
1243 subdirectory entry will be reported as it appears in the manifest, i.e.
1249 subdirectory entry will be reported as it appears in the manifest, i.e.
1244 the subdirectory will be reported among files and distinguished only by
1250 the subdirectory will be reported among files and distinguished only by
1245 its 't' flag. This only apply if the underlying manifest support it.
1251 its 't' flag. This only apply if the underlying manifest support it.
1246
1252
1247 The returned object conforms to the ``imanifestdict`` interface."""
1253 The returned object conforms to the ``imanifestdict`` interface."""
1248
1254
1249 def readfast(self, shallow=False):
1255 def readfast(self, shallow: bool = False):
1250 """Calls either ``read()`` or ``readdelta()``.
1256 """Calls either ``read()`` or ``readdelta()``.
1251
1257
1252 The faster of the two options is called.
1258 The faster of the two options is called.
1253 """
1259 """
1254
1260
1255 def find(self, key):
1261 def find(self, key: bytes) -> tuple[bytes, bytes]:
1256 """Calls self.read().find(key)``.
1262 """Calls self.read().find(key)``.
1257
1263
1258 Returns a 2-tuple of ``(node, flags)`` or raises ``KeyError``.
1264 Returns a 2-tuple of ``(node, flags)`` or raises ``KeyError``.
1259 """
1265 """
1260
1266
1261
1267
1262 class imanifestrevisionwritable(imanifestrevisionbase):
1268 class imanifestrevisionwritable(imanifestrevisionbase):
1263 """Interface representing a manifest revision that can be committed."""
1269 """Interface representing a manifest revision that can be committed."""
1264
1270
1265 def write(
1271 def write(
1266 self, transaction, linkrev, p1node, p2node, added, removed, match=None
1272 self, transaction, linkrev, p1node, p2node, added, removed, match=None
1267 ):
1273 ):
1268 """Add this revision to storage.
1274 """Add this revision to storage.
1269
1275
1270 Takes a transaction object, the changeset revision number it will
1276 Takes a transaction object, the changeset revision number it will
1271 be associated with, its parent nodes, and lists of added and
1277 be associated with, its parent nodes, and lists of added and
1272 removed paths.
1278 removed paths.
1273
1279
1274 If match is provided, storage can choose not to inspect or write out
1280 If match is provided, storage can choose not to inspect or write out
1275 items that do not match. Storage is still required to be able to provide
1281 items that do not match. Storage is still required to be able to provide
1276 the full manifest in the future for any directories written (these
1282 the full manifest in the future for any directories written (these
1277 manifests should not be "narrowed on disk").
1283 manifests should not be "narrowed on disk").
1278
1284
1279 Returns the binary node of the created revision.
1285 Returns the binary node of the created revision.
1280 """
1286 """
1281
1287
1282
1288
1283 class imanifeststorage(Protocol):
1289 class imanifeststorage(Protocol):
1284 """Storage interface for manifest data."""
1290 """Storage interface for manifest data."""
1285
1291
1286 nodeconstants = interfaceutil.Attribute(
1292 nodeconstants = interfaceutil.Attribute(
1287 """nodeconstants used by the current repository."""
1293 """nodeconstants used by the current repository."""
1288 )
1294 )
1289
1295
1290 tree = interfaceutil.Attribute(
1296 tree = interfaceutil.Attribute(
1291 """The path to the directory this manifest tracks.
1297 """The path to the directory this manifest tracks.
1292
1298
1293 The empty bytestring represents the root manifest.
1299 The empty bytestring represents the root manifest.
1294 """
1300 """
1295 )
1301 )
1296
1302
1297 index = interfaceutil.Attribute(
1303 index = interfaceutil.Attribute(
1298 """An ``ifilerevisionssequence`` instance."""
1304 """An ``ifilerevisionssequence`` instance."""
1299 )
1305 )
1300
1306
1301 opener = interfaceutil.Attribute(
1307 opener = interfaceutil.Attribute(
1302 """VFS opener to use to access underlying files used for storage.
1308 """VFS opener to use to access underlying files used for storage.
1303
1309
1304 TODO this is revlog specific and should not be exposed.
1310 TODO this is revlog specific and should not be exposed.
1305 """
1311 """
1306 )
1312 )
1307
1313
1308 fulltextcache = interfaceutil.Attribute(
1314 fulltextcache = interfaceutil.Attribute(
1309 """Dict with cache of fulltexts.
1315 """Dict with cache of fulltexts.
1310
1316
1311 TODO this doesn't feel appropriate for the storage interface.
1317 TODO this doesn't feel appropriate for the storage interface.
1312 """
1318 """
1313 )
1319 )
1314
1320
1315 def __len__(self):
1321 def __len__(self):
1316 """Obtain the number of revisions stored for this manifest."""
1322 """Obtain the number of revisions stored for this manifest."""
1317
1323
1318 def __iter__(self):
1324 def __iter__(self):
1319 """Iterate over revision numbers for this manifest."""
1325 """Iterate over revision numbers for this manifest."""
1320
1326
1321 def rev(self, node):
1327 def rev(self, node):
1322 """Obtain the revision number given a binary node.
1328 """Obtain the revision number given a binary node.
1323
1329
1324 Raises ``error.LookupError`` if the node is not known.
1330 Raises ``error.LookupError`` if the node is not known.
1325 """
1331 """
1326
1332
1327 def node(self, rev):
1333 def node(self, rev):
1328 """Obtain the node value given a revision number.
1334 """Obtain the node value given a revision number.
1329
1335
1330 Raises ``error.LookupError`` if the revision is not known.
1336 Raises ``error.LookupError`` if the revision is not known.
1331 """
1337 """
1332
1338
1333 def lookup(self, value):
1339 def lookup(self, value):
1334 """Attempt to resolve a value to a node.
1340 """Attempt to resolve a value to a node.
1335
1341
1336 Value can be a binary node, hex node, revision number, or a bytes
1342 Value can be a binary node, hex node, revision number, or a bytes
1337 that can be converted to an integer.
1343 that can be converted to an integer.
1338
1344
1339 Raises ``error.LookupError`` if a ndoe could not be resolved.
1345 Raises ``error.LookupError`` if a ndoe could not be resolved.
1340 """
1346 """
1341
1347
1342 def parents(self, node):
1348 def parents(self, node):
1343 """Returns a 2-tuple of parent nodes for a node.
1349 """Returns a 2-tuple of parent nodes for a node.
1344
1350
1345 Values will be ``nullid`` if the parent is empty.
1351 Values will be ``nullid`` if the parent is empty.
1346 """
1352 """
1347
1353
1348 def parentrevs(self, rev):
1354 def parentrevs(self, rev):
1349 """Like parents() but operates on revision numbers."""
1355 """Like parents() but operates on revision numbers."""
1350
1356
1351 def linkrev(self, rev):
1357 def linkrev(self, rev):
1352 """Obtain the changeset revision number a revision is linked to."""
1358 """Obtain the changeset revision number a revision is linked to."""
1353
1359
1354 def revision(self, node):
1360 def revision(self, node):
1355 """Obtain fulltext data for a node."""
1361 """Obtain fulltext data for a node."""
1356
1362
1357 def rawdata(self, node):
1363 def rawdata(self, node):
1358 """Obtain raw data for a node."""
1364 """Obtain raw data for a node."""
1359
1365
1360 def revdiff(self, rev1, rev2):
1366 def revdiff(self, rev1, rev2):
1361 """Obtain a delta between two revision numbers.
1367 """Obtain a delta between two revision numbers.
1362
1368
1363 The returned data is the result of ``bdiff.bdiff()`` on the raw
1369 The returned data is the result of ``bdiff.bdiff()`` on the raw
1364 revision data.
1370 revision data.
1365 """
1371 """
1366
1372
1367 def cmp(self, node, fulltext):
1373 def cmp(self, node, fulltext):
1368 """Compare fulltext to another revision.
1374 """Compare fulltext to another revision.
1369
1375
1370 Returns True if the fulltext is different from what is stored.
1376 Returns True if the fulltext is different from what is stored.
1371 """
1377 """
1372
1378
1373 def emitrevisions(
1379 def emitrevisions(
1374 self,
1380 self,
1375 nodes,
1381 nodes,
1376 nodesorder=None,
1382 nodesorder=None,
1377 revisiondata=False,
1383 revisiondata=False,
1378 assumehaveparentrevisions=False,
1384 assumehaveparentrevisions=False,
1379 ):
1385 ):
1380 """Produce ``irevisiondelta`` describing revisions.
1386 """Produce ``irevisiondelta`` describing revisions.
1381
1387
1382 See the documentation for ``ifiledata`` for more.
1388 See the documentation for ``ifiledata`` for more.
1383 """
1389 """
1384
1390
1385 def addgroup(
1391 def addgroup(
1386 self,
1392 self,
1387 deltas,
1393 deltas,
1388 linkmapper,
1394 linkmapper,
1389 transaction,
1395 transaction,
1390 addrevisioncb=None,
1396 addrevisioncb=None,
1391 duplicaterevisioncb=None,
1397 duplicaterevisioncb=None,
1392 ):
1398 ):
1393 """Process a series of deltas for storage.
1399 """Process a series of deltas for storage.
1394
1400
1395 See the documentation in ``ifilemutation`` for more.
1401 See the documentation in ``ifilemutation`` for more.
1396 """
1402 """
1397
1403
1398 def rawsize(self, rev):
1404 def rawsize(self, rev):
1399 """Obtain the size of tracked data.
1405 """Obtain the size of tracked data.
1400
1406
1401 Is equivalent to ``len(m.rawdata(node))``.
1407 Is equivalent to ``len(m.rawdata(node))``.
1402
1408
1403 TODO this method is only used by upgrade code and may be removed.
1409 TODO this method is only used by upgrade code and may be removed.
1404 """
1410 """
1405
1411
1406 def getstrippoint(self, minlink):
1412 def getstrippoint(self, minlink):
1407 """Find minimum revision that must be stripped to strip a linkrev.
1413 """Find minimum revision that must be stripped to strip a linkrev.
1408
1414
1409 See the documentation in ``ifilemutation`` for more.
1415 See the documentation in ``ifilemutation`` for more.
1410 """
1416 """
1411
1417
1412 def strip(self, minlink, transaction):
1418 def strip(self, minlink, transaction):
1413 """Remove storage of items starting at a linkrev.
1419 """Remove storage of items starting at a linkrev.
1414
1420
1415 See the documentation in ``ifilemutation`` for more.
1421 See the documentation in ``ifilemutation`` for more.
1416 """
1422 """
1417
1423
1418 def checksize(self):
1424 def checksize(self):
1419 """Obtain the expected sizes of backing files.
1425 """Obtain the expected sizes of backing files.
1420
1426
1421 TODO this is used by verify and it should not be part of the interface.
1427 TODO this is used by verify and it should not be part of the interface.
1422 """
1428 """
1423
1429
1424 def files(self):
1430 def files(self):
1425 """Obtain paths that are backing storage for this manifest.
1431 """Obtain paths that are backing storage for this manifest.
1426
1432
1427 TODO this is used by verify and there should probably be a better API
1433 TODO this is used by verify and there should probably be a better API
1428 for this functionality.
1434 for this functionality.
1429 """
1435 """
1430
1436
1431 def deltaparent(self, rev):
1437 def deltaparent(self, rev):
1432 """Obtain the revision that a revision is delta'd against.
1438 """Obtain the revision that a revision is delta'd against.
1433
1439
1434 TODO delta encoding is an implementation detail of storage and should
1440 TODO delta encoding is an implementation detail of storage and should
1435 not be exposed to the storage interface.
1441 not be exposed to the storage interface.
1436 """
1442 """
1437
1443
1438 def clone(self, tr, dest, **kwargs):
1444 def clone(self, tr, dest, **kwargs):
1439 """Clone this instance to another."""
1445 """Clone this instance to another."""
1440
1446
1441 def clearcaches(self, clear_persisted_data=False):
1447 def clearcaches(self, clear_persisted_data=False):
1442 """Clear any caches associated with this instance."""
1448 """Clear any caches associated with this instance."""
1443
1449
1444 def dirlog(self, d):
1450 def dirlog(self, d):
1445 """Obtain a manifest storage instance for a tree."""
1451 """Obtain a manifest storage instance for a tree."""
1446
1452
1447 def add(
1453 def add(
1448 self,
1454 self,
1449 m,
1455 m,
1450 transaction,
1456 transaction,
1451 link,
1457 link,
1452 p1,
1458 p1,
1453 p2,
1459 p2,
1454 added,
1460 added,
1455 removed,
1461 removed,
1456 readtree=None,
1462 readtree=None,
1457 match=None,
1463 match=None,
1458 ):
1464 ):
1459 """Add a revision to storage.
1465 """Add a revision to storage.
1460
1466
1461 ``m`` is an object conforming to ``imanifestdict``.
1467 ``m`` is an object conforming to ``imanifestdict``.
1462
1468
1463 ``link`` is the linkrev revision number.
1469 ``link`` is the linkrev revision number.
1464
1470
1465 ``p1`` and ``p2`` are the parent revision numbers.
1471 ``p1`` and ``p2`` are the parent revision numbers.
1466
1472
1467 ``added`` and ``removed`` are iterables of added and removed paths,
1473 ``added`` and ``removed`` are iterables of added and removed paths,
1468 respectively.
1474 respectively.
1469
1475
1470 ``readtree`` is a function that can be used to read the child tree(s)
1476 ``readtree`` is a function that can be used to read the child tree(s)
1471 when recursively writing the full tree structure when using
1477 when recursively writing the full tree structure when using
1472 treemanifets.
1478 treemanifets.
1473
1479
1474 ``match`` is a matcher that can be used to hint to storage that not all
1480 ``match`` is a matcher that can be used to hint to storage that not all
1475 paths must be inspected; this is an optimization and can be safely
1481 paths must be inspected; this is an optimization and can be safely
1476 ignored. Note that the storage must still be able to reproduce a full
1482 ignored. Note that the storage must still be able to reproduce a full
1477 manifest including files that did not match.
1483 manifest including files that did not match.
1478 """
1484 """
1479
1485
1480 def storageinfo(
1486 def storageinfo(
1481 self,
1487 self,
1482 exclusivefiles=False,
1488 exclusivefiles=False,
1483 sharedfiles=False,
1489 sharedfiles=False,
1484 revisionscount=False,
1490 revisionscount=False,
1485 trackedsize=False,
1491 trackedsize=False,
1486 storedsize=False,
1492 storedsize=False,
1487 ):
1493 ):
1488 """Obtain information about storage for this manifest's data.
1494 """Obtain information about storage for this manifest's data.
1489
1495
1490 See ``ifilestorage.storageinfo()`` for a description of this method.
1496 See ``ifilestorage.storageinfo()`` for a description of this method.
1491 This one behaves the same way, except for manifest data.
1497 This one behaves the same way, except for manifest data.
1492 """
1498 """
1493
1499
1494 def get_revlog(self):
1500 def get_revlog(self):
1495 """return an actual revlog instance if any
1501 """return an actual revlog instance if any
1496
1502
1497 This exist because a lot of code leverage the fact the underlying
1503 This exist because a lot of code leverage the fact the underlying
1498 storage is a revlog for optimization, so giving simple way to access
1504 storage is a revlog for optimization, so giving simple way to access
1499 the revlog instance helps such code.
1505 the revlog instance helps such code.
1500 """
1506 """
1501
1507
1502
1508
1503 class imanifestlog(Protocol):
1509 class imanifestlog(Protocol):
1504 """Interface representing a collection of manifest snapshots.
1510 """Interface representing a collection of manifest snapshots.
1505
1511
1506 Represents the root manifest in a repository.
1512 Represents the root manifest in a repository.
1507
1513
1508 Also serves as a means to access nested tree manifests and to cache
1514 Also serves as a means to access nested tree manifests and to cache
1509 tree manifests.
1515 tree manifests.
1510 """
1516 """
1511
1517
1512 nodeconstants = interfaceutil.Attribute(
1518 nodeconstants = interfaceutil.Attribute(
1513 """nodeconstants used by the current repository."""
1519 """nodeconstants used by the current repository."""
1514 )
1520 )
1515
1521
1516 narrowed = interfaceutil.Attribute(
1522 narrowed = interfaceutil.Attribute(
1517 """True, is the manifest is narrowed by a matcher"""
1523 """True, is the manifest is narrowed by a matcher"""
1518 )
1524 )
1519
1525
1520 def __getitem__(self, node):
1526 def __getitem__(self, node):
1521 """Obtain a manifest instance for a given binary node.
1527 """Obtain a manifest instance for a given binary node.
1522
1528
1523 Equivalent to calling ``self.get('', node)``.
1529 Equivalent to calling ``self.get('', node)``.
1524
1530
1525 The returned object conforms to the ``imanifestrevisionstored``
1531 The returned object conforms to the ``imanifestrevisionstored``
1526 interface.
1532 interface.
1527 """
1533 """
1528
1534
1529 def get(self, tree, node, verify=True):
1535 def get(self, tree, node, verify=True):
1530 """Retrieve the manifest instance for a given directory and binary node.
1536 """Retrieve the manifest instance for a given directory and binary node.
1531
1537
1532 ``node`` always refers to the node of the root manifest (which will be
1538 ``node`` always refers to the node of the root manifest (which will be
1533 the only manifest if flat manifests are being used).
1539 the only manifest if flat manifests are being used).
1534
1540
1535 If ``tree`` is the empty string, the root manifest is returned.
1541 If ``tree`` is the empty string, the root manifest is returned.
1536 Otherwise the manifest for the specified directory will be returned
1542 Otherwise the manifest for the specified directory will be returned
1537 (requires tree manifests).
1543 (requires tree manifests).
1538
1544
1539 If ``verify`` is True, ``LookupError`` is raised if the node is not
1545 If ``verify`` is True, ``LookupError`` is raised if the node is not
1540 known.
1546 known.
1541
1547
1542 The returned object conforms to the ``imanifestrevisionstored``
1548 The returned object conforms to the ``imanifestrevisionstored``
1543 interface.
1549 interface.
1544 """
1550 """
1545
1551
1546 def getstorage(self, tree):
1552 def getstorage(self, tree):
1547 """Retrieve an interface to storage for a particular tree.
1553 """Retrieve an interface to storage for a particular tree.
1548
1554
1549 If ``tree`` is the empty bytestring, storage for the root manifest will
1555 If ``tree`` is the empty bytestring, storage for the root manifest will
1550 be returned. Otherwise storage for a tree manifest is returned.
1556 be returned. Otherwise storage for a tree manifest is returned.
1551
1557
1552 TODO formalize interface for returned object.
1558 TODO formalize interface for returned object.
1553 """
1559 """
1554
1560
1555 def clearcaches(self, clear_persisted_data: bool = False) -> None:
1561 def clearcaches(self, clear_persisted_data: bool = False) -> None:
1556 """Clear caches associated with this collection."""
1562 """Clear caches associated with this collection."""
1557
1563
1558 def rev(self, node):
1564 def rev(self, node):
1559 """Obtain the revision number for a binary node.
1565 """Obtain the revision number for a binary node.
1560
1566
1561 Raises ``error.LookupError`` if the node is not known.
1567 Raises ``error.LookupError`` if the node is not known.
1562 """
1568 """
1563
1569
1564 def update_caches(self, transaction):
1570 def update_caches(self, transaction):
1565 """update whatever cache are relevant for the used storage."""
1571 """update whatever cache are relevant for the used storage."""
1566
1572
1567
1573
1568 class ilocalrepositoryfilestorage(Protocol):
1574 class ilocalrepositoryfilestorage(Protocol):
1569 """Local repository sub-interface providing access to tracked file storage.
1575 """Local repository sub-interface providing access to tracked file storage.
1570
1576
1571 This interface defines how a repository accesses storage for a single
1577 This interface defines how a repository accesses storage for a single
1572 tracked file path.
1578 tracked file path.
1573 """
1579 """
1574
1580
1575 def file(self, f):
1581 def file(self, f):
1576 """Obtain a filelog for a tracked path.
1582 """Obtain a filelog for a tracked path.
1577
1583
1578 The returned type conforms to the ``ifilestorage`` interface.
1584 The returned type conforms to the ``ifilestorage`` interface.
1579 """
1585 """
1580
1586
1581
1587
1582 class ilocalrepositorymain(Protocol):
1588 class ilocalrepositorymain(Protocol):
1583 """Main interface for local repositories.
1589 """Main interface for local repositories.
1584
1590
1585 This currently captures the reality of things - not how things should be.
1591 This currently captures the reality of things - not how things should be.
1586 """
1592 """
1587
1593
1588 nodeconstants = interfaceutil.Attribute(
1594 nodeconstants = interfaceutil.Attribute(
1589 """Constant nodes matching the hash function used by the repository."""
1595 """Constant nodes matching the hash function used by the repository."""
1590 )
1596 )
1591 nullid = interfaceutil.Attribute(
1597 nullid = interfaceutil.Attribute(
1592 """null revision for the hash function used by the repository."""
1598 """null revision for the hash function used by the repository."""
1593 )
1599 )
1594
1600
1595 supported = interfaceutil.Attribute(
1601 supported = interfaceutil.Attribute(
1596 """Set of requirements that this repo is capable of opening."""
1602 """Set of requirements that this repo is capable of opening."""
1597 )
1603 )
1598
1604
1599 requirements = interfaceutil.Attribute(
1605 requirements = interfaceutil.Attribute(
1600 """Set of requirements this repo uses."""
1606 """Set of requirements this repo uses."""
1601 )
1607 )
1602
1608
1603 features = interfaceutil.Attribute(
1609 features = interfaceutil.Attribute(
1604 """Set of "features" this repository supports.
1610 """Set of "features" this repository supports.
1605
1611
1606 A "feature" is a loosely-defined term. It can refer to a feature
1612 A "feature" is a loosely-defined term. It can refer to a feature
1607 in the classical sense or can describe an implementation detail
1613 in the classical sense or can describe an implementation detail
1608 of the repository. For example, a ``readonly`` feature may denote
1614 of the repository. For example, a ``readonly`` feature may denote
1609 the repository as read-only. Or a ``revlogfilestore`` feature may
1615 the repository as read-only. Or a ``revlogfilestore`` feature may
1610 denote that the repository is using revlogs for file storage.
1616 denote that the repository is using revlogs for file storage.
1611
1617
1612 The intent of features is to provide a machine-queryable mechanism
1618 The intent of features is to provide a machine-queryable mechanism
1613 for repo consumers to test for various repository characteristics.
1619 for repo consumers to test for various repository characteristics.
1614
1620
1615 Features are similar to ``requirements``. The main difference is that
1621 Features are similar to ``requirements``. The main difference is that
1616 requirements are stored on-disk and represent requirements to open the
1622 requirements are stored on-disk and represent requirements to open the
1617 repository. Features are more run-time capabilities of the repository
1623 repository. Features are more run-time capabilities of the repository
1618 and more granular capabilities (which may be derived from requirements).
1624 and more granular capabilities (which may be derived from requirements).
1619 """
1625 """
1620 )
1626 )
1621
1627
1622 filtername = interfaceutil.Attribute(
1628 filtername = interfaceutil.Attribute(
1623 """Name of the repoview that is active on this repo."""
1629 """Name of the repoview that is active on this repo."""
1624 )
1630 )
1625
1631
1626 vfs_map = interfaceutil.Attribute(
1632 vfs_map = interfaceutil.Attribute(
1627 """a bytes-key β†’ vfs mapping used by transaction and others"""
1633 """a bytes-key β†’ vfs mapping used by transaction and others"""
1628 )
1634 )
1629
1635
1630 wvfs = interfaceutil.Attribute(
1636 wvfs = interfaceutil.Attribute(
1631 """VFS used to access the working directory."""
1637 """VFS used to access the working directory."""
1632 )
1638 )
1633
1639
1634 vfs = interfaceutil.Attribute(
1640 vfs = interfaceutil.Attribute(
1635 """VFS rooted at the .hg directory.
1641 """VFS rooted at the .hg directory.
1636
1642
1637 Used to access repository data not in the store.
1643 Used to access repository data not in the store.
1638 """
1644 """
1639 )
1645 )
1640
1646
1641 svfs = interfaceutil.Attribute(
1647 svfs = interfaceutil.Attribute(
1642 """VFS rooted at the store.
1648 """VFS rooted at the store.
1643
1649
1644 Used to access repository data in the store. Typically .hg/store.
1650 Used to access repository data in the store. Typically .hg/store.
1645 But can point elsewhere if the store is shared.
1651 But can point elsewhere if the store is shared.
1646 """
1652 """
1647 )
1653 )
1648
1654
1649 root = interfaceutil.Attribute(
1655 root = interfaceutil.Attribute(
1650 """Path to the root of the working directory."""
1656 """Path to the root of the working directory."""
1651 )
1657 )
1652
1658
1653 path = interfaceutil.Attribute("""Path to the .hg directory.""")
1659 path = interfaceutil.Attribute("""Path to the .hg directory.""")
1654
1660
1655 origroot = interfaceutil.Attribute(
1661 origroot = interfaceutil.Attribute(
1656 """The filesystem path that was used to construct the repo."""
1662 """The filesystem path that was used to construct the repo."""
1657 )
1663 )
1658
1664
1659 auditor = interfaceutil.Attribute(
1665 auditor = interfaceutil.Attribute(
1660 """A pathauditor for the working directory.
1666 """A pathauditor for the working directory.
1661
1667
1662 This checks if a path refers to a nested repository.
1668 This checks if a path refers to a nested repository.
1663
1669
1664 Operates on the filesystem.
1670 Operates on the filesystem.
1665 """
1671 """
1666 )
1672 )
1667
1673
1668 nofsauditor = interfaceutil.Attribute(
1674 nofsauditor = interfaceutil.Attribute(
1669 """A pathauditor for the working directory.
1675 """A pathauditor for the working directory.
1670
1676
1671 This is like ``auditor`` except it doesn't do filesystem checks.
1677 This is like ``auditor`` except it doesn't do filesystem checks.
1672 """
1678 """
1673 )
1679 )
1674
1680
1675 baseui = interfaceutil.Attribute(
1681 baseui = interfaceutil.Attribute(
1676 """Original ui instance passed into constructor."""
1682 """Original ui instance passed into constructor."""
1677 )
1683 )
1678
1684
1679 ui = interfaceutil.Attribute("""Main ui instance for this instance.""")
1685 ui = interfaceutil.Attribute("""Main ui instance for this instance.""")
1680
1686
1681 sharedpath = interfaceutil.Attribute(
1687 sharedpath = interfaceutil.Attribute(
1682 """Path to the .hg directory of the repo this repo was shared from."""
1688 """Path to the .hg directory of the repo this repo was shared from."""
1683 )
1689 )
1684
1690
1685 store = interfaceutil.Attribute("""A store instance.""")
1691 store = interfaceutil.Attribute("""A store instance.""")
1686
1692
1687 spath = interfaceutil.Attribute("""Path to the store.""")
1693 spath = interfaceutil.Attribute("""Path to the store.""")
1688
1694
1689 sjoin = interfaceutil.Attribute("""Alias to self.store.join.""")
1695 sjoin = interfaceutil.Attribute("""Alias to self.store.join.""")
1690
1696
1691 cachevfs = interfaceutil.Attribute(
1697 cachevfs = interfaceutil.Attribute(
1692 """A VFS used to access the cache directory.
1698 """A VFS used to access the cache directory.
1693
1699
1694 Typically .hg/cache.
1700 Typically .hg/cache.
1695 """
1701 """
1696 )
1702 )
1697
1703
1698 wcachevfs = interfaceutil.Attribute(
1704 wcachevfs = interfaceutil.Attribute(
1699 """A VFS used to access the cache directory dedicated to working copy
1705 """A VFS used to access the cache directory dedicated to working copy
1700
1706
1701 Typically .hg/wcache.
1707 Typically .hg/wcache.
1702 """
1708 """
1703 )
1709 )
1704
1710
1705 filteredrevcache = interfaceutil.Attribute(
1711 filteredrevcache = interfaceutil.Attribute(
1706 """Holds sets of revisions to be filtered."""
1712 """Holds sets of revisions to be filtered."""
1707 )
1713 )
1708
1714
1709 names = interfaceutil.Attribute("""A ``namespaces`` instance.""")
1715 names = interfaceutil.Attribute("""A ``namespaces`` instance.""")
1710
1716
1711 filecopiesmode = interfaceutil.Attribute(
1717 filecopiesmode = interfaceutil.Attribute(
1712 """The way files copies should be dealt with in this repo."""
1718 """The way files copies should be dealt with in this repo."""
1713 )
1719 )
1714
1720
1715 def close(self):
1721 def close(self):
1716 """Close the handle on this repository."""
1722 """Close the handle on this repository."""
1717
1723
1718 def peer(self, path=None):
1724 def peer(self, path=None):
1719 """Obtain an object conforming to the ``peer`` interface."""
1725 """Obtain an object conforming to the ``peer`` interface."""
1720
1726
1721 def unfiltered(self):
1727 def unfiltered(self):
1722 """Obtain an unfiltered/raw view of this repo."""
1728 """Obtain an unfiltered/raw view of this repo."""
1723
1729
1724 def filtered(self, name, visibilityexceptions=None):
1730 def filtered(self, name, visibilityexceptions=None):
1725 """Obtain a named view of this repository."""
1731 """Obtain a named view of this repository."""
1726
1732
1727 obsstore = interfaceutil.Attribute("""A store of obsolescence data.""")
1733 obsstore = interfaceutil.Attribute("""A store of obsolescence data.""")
1728
1734
1729 changelog = interfaceutil.Attribute("""A handle on the changelog revlog.""")
1735 changelog = interfaceutil.Attribute("""A handle on the changelog revlog.""")
1730
1736
1731 manifestlog = interfaceutil.Attribute(
1737 manifestlog = interfaceutil.Attribute(
1732 """An instance conforming to the ``imanifestlog`` interface.
1738 """An instance conforming to the ``imanifestlog`` interface.
1733
1739
1734 Provides access to manifests for the repository.
1740 Provides access to manifests for the repository.
1735 """
1741 """
1736 )
1742 )
1737
1743
1738 dirstate = interfaceutil.Attribute("""Working directory state.""")
1744 dirstate = interfaceutil.Attribute("""Working directory state.""")
1739
1745
1740 narrowpats = interfaceutil.Attribute(
1746 narrowpats = interfaceutil.Attribute(
1741 """Matcher patterns for this repository's narrowspec."""
1747 """Matcher patterns for this repository's narrowspec."""
1742 )
1748 )
1743
1749
1744 def narrowmatch(self, match=None, includeexact=False):
1750 def narrowmatch(self, match=None, includeexact=False):
1745 """Obtain a matcher for the narrowspec."""
1751 """Obtain a matcher for the narrowspec."""
1746
1752
1747 def setnarrowpats(self, newincludes, newexcludes):
1753 def setnarrowpats(self, newincludes, newexcludes):
1748 """Define the narrowspec for this repository."""
1754 """Define the narrowspec for this repository."""
1749
1755
1750 def __getitem__(self, changeid):
1756 def __getitem__(self, changeid):
1751 """Try to resolve a changectx."""
1757 """Try to resolve a changectx."""
1752
1758
1753 def __contains__(self, changeid):
1759 def __contains__(self, changeid):
1754 """Whether a changeset exists."""
1760 """Whether a changeset exists."""
1755
1761
1756 def __nonzero__(self):
1762 def __nonzero__(self):
1757 """Always returns True."""
1763 """Always returns True."""
1758 return True
1764 return True
1759
1765
1760 __bool__ = __nonzero__
1766 __bool__ = __nonzero__
1761
1767
1762 def __len__(self):
1768 def __len__(self):
1763 """Returns the number of changesets in the repo."""
1769 """Returns the number of changesets in the repo."""
1764
1770
1765 def __iter__(self):
1771 def __iter__(self):
1766 """Iterate over revisions in the changelog."""
1772 """Iterate over revisions in the changelog."""
1767
1773
1768 def revs(self, expr, *args):
1774 def revs(self, expr, *args):
1769 """Evaluate a revset.
1775 """Evaluate a revset.
1770
1776
1771 Emits revisions.
1777 Emits revisions.
1772 """
1778 """
1773
1779
1774 def set(self, expr, *args):
1780 def set(self, expr, *args):
1775 """Evaluate a revset.
1781 """Evaluate a revset.
1776
1782
1777 Emits changectx instances.
1783 Emits changectx instances.
1778 """
1784 """
1779
1785
1780 def anyrevs(self, specs, user=False, localalias=None):
1786 def anyrevs(self, specs, user=False, localalias=None):
1781 """Find revisions matching one of the given revsets."""
1787 """Find revisions matching one of the given revsets."""
1782
1788
1783 def url(self):
1789 def url(self):
1784 """Returns a string representing the location of this repo."""
1790 """Returns a string representing the location of this repo."""
1785
1791
1786 def hook(self, name, throw=False, **args):
1792 def hook(self, name, throw=False, **args):
1787 """Call a hook."""
1793 """Call a hook."""
1788
1794
1789 def tags(self):
1795 def tags(self):
1790 """Return a mapping of tag to node."""
1796 """Return a mapping of tag to node."""
1791
1797
1792 def tagtype(self, tagname):
1798 def tagtype(self, tagname):
1793 """Return the type of a given tag."""
1799 """Return the type of a given tag."""
1794
1800
1795 def tagslist(self):
1801 def tagslist(self):
1796 """Return a list of tags ordered by revision."""
1802 """Return a list of tags ordered by revision."""
1797
1803
1798 def nodetags(self, node):
1804 def nodetags(self, node):
1799 """Return the tags associated with a node."""
1805 """Return the tags associated with a node."""
1800
1806
1801 def nodebookmarks(self, node):
1807 def nodebookmarks(self, node):
1802 """Return the list of bookmarks pointing to the specified node."""
1808 """Return the list of bookmarks pointing to the specified node."""
1803
1809
1804 def branchmap(self):
1810 def branchmap(self):
1805 """Return a mapping of branch to heads in that branch."""
1811 """Return a mapping of branch to heads in that branch."""
1806
1812
1807 def revbranchcache(self):
1813 def revbranchcache(self):
1808 pass
1814 pass
1809
1815
1810 def register_changeset(self, rev, changelogrevision):
1816 def register_changeset(self, rev, changelogrevision):
1811 """Extension point for caches for new nodes.
1817 """Extension point for caches for new nodes.
1812
1818
1813 Multiple consumers are expected to need parts of the changelogrevision,
1819 Multiple consumers are expected to need parts of the changelogrevision,
1814 so it is provided as optimization to avoid duplicate lookups. A simple
1820 so it is provided as optimization to avoid duplicate lookups. A simple
1815 cache would be fragile when other revisions are accessed, too."""
1821 cache would be fragile when other revisions are accessed, too."""
1816 pass
1822 pass
1817
1823
1818 def branchtip(self, branchtip, ignoremissing=False):
1824 def branchtip(self, branchtip, ignoremissing=False):
1819 """Return the tip node for a given branch."""
1825 """Return the tip node for a given branch."""
1820
1826
1821 def lookup(self, key):
1827 def lookup(self, key):
1822 """Resolve the node for a revision."""
1828 """Resolve the node for a revision."""
1823
1829
1824 def lookupbranch(self, key):
1830 def lookupbranch(self, key):
1825 """Look up the branch name of the given revision or branch name."""
1831 """Look up the branch name of the given revision or branch name."""
1826
1832
1827 def known(self, nodes):
1833 def known(self, nodes):
1828 """Determine whether a series of nodes is known.
1834 """Determine whether a series of nodes is known.
1829
1835
1830 Returns a list of bools.
1836 Returns a list of bools.
1831 """
1837 """
1832
1838
1833 def local(self):
1839 def local(self):
1834 """Whether the repository is local."""
1840 """Whether the repository is local."""
1835 return True
1841 return True
1836
1842
1837 def publishing(self):
1843 def publishing(self):
1838 """Whether the repository is a publishing repository."""
1844 """Whether the repository is a publishing repository."""
1839
1845
1840 def cancopy(self):
1846 def cancopy(self):
1841 pass
1847 pass
1842
1848
1843 def shared(self):
1849 def shared(self):
1844 """The type of shared repository or None."""
1850 """The type of shared repository or None."""
1845
1851
1846 def wjoin(self, f, *insidef):
1852 def wjoin(self, f, *insidef):
1847 """Calls self.vfs.reljoin(self.root, f, *insidef)"""
1853 """Calls self.vfs.reljoin(self.root, f, *insidef)"""
1848
1854
1849 def setparents(self, p1, p2):
1855 def setparents(self, p1, p2):
1850 """Set the parent nodes of the working directory."""
1856 """Set the parent nodes of the working directory."""
1851
1857
1852 def filectx(self, path, changeid=None, fileid=None):
1858 def filectx(self, path, changeid=None, fileid=None):
1853 """Obtain a filectx for the given file revision."""
1859 """Obtain a filectx for the given file revision."""
1854
1860
1855 def getcwd(self):
1861 def getcwd(self):
1856 """Obtain the current working directory from the dirstate."""
1862 """Obtain the current working directory from the dirstate."""
1857
1863
1858 def pathto(self, f, cwd=None):
1864 def pathto(self, f, cwd=None):
1859 """Obtain the relative path to a file."""
1865 """Obtain the relative path to a file."""
1860
1866
1861 def adddatafilter(self, name, fltr):
1867 def adddatafilter(self, name, fltr):
1862 pass
1868 pass
1863
1869
1864 def wread(self, filename):
1870 def wread(self, filename):
1865 """Read a file from wvfs, using data filters."""
1871 """Read a file from wvfs, using data filters."""
1866
1872
1867 def wwrite(self, filename, data, flags, backgroundclose=False, **kwargs):
1873 def wwrite(self, filename, data, flags, backgroundclose=False, **kwargs):
1868 """Write data to a file in the wvfs, using data filters."""
1874 """Write data to a file in the wvfs, using data filters."""
1869
1875
1870 def wwritedata(self, filename, data):
1876 def wwritedata(self, filename, data):
1871 """Resolve data for writing to the wvfs, using data filters."""
1877 """Resolve data for writing to the wvfs, using data filters."""
1872
1878
1873 def currenttransaction(self):
1879 def currenttransaction(self):
1874 """Obtain the current transaction instance or None."""
1880 """Obtain the current transaction instance or None."""
1875
1881
1876 def transaction(self, desc, report=None):
1882 def transaction(self, desc, report=None):
1877 """Open a new transaction to write to the repository."""
1883 """Open a new transaction to write to the repository."""
1878
1884
1879 def undofiles(self):
1885 def undofiles(self):
1880 """Returns a list of (vfs, path) for files to undo transactions."""
1886 """Returns a list of (vfs, path) for files to undo transactions."""
1881
1887
1882 def recover(self):
1888 def recover(self):
1883 """Roll back an interrupted transaction."""
1889 """Roll back an interrupted transaction."""
1884
1890
1885 def rollback(self, dryrun=False, force=False):
1891 def rollback(self, dryrun=False, force=False):
1886 """Undo the last transaction.
1892 """Undo the last transaction.
1887
1893
1888 DANGEROUS.
1894 DANGEROUS.
1889 """
1895 """
1890
1896
1891 def updatecaches(self, tr=None, full=False, caches=None):
1897 def updatecaches(self, tr=None, full=False, caches=None):
1892 """Warm repo caches."""
1898 """Warm repo caches."""
1893
1899
1894 def invalidatecaches(self):
1900 def invalidatecaches(self):
1895 """Invalidate cached data due to the repository mutating."""
1901 """Invalidate cached data due to the repository mutating."""
1896
1902
1897 def invalidatevolatilesets(self):
1903 def invalidatevolatilesets(self):
1898 pass
1904 pass
1899
1905
1900 def invalidatedirstate(self):
1906 def invalidatedirstate(self):
1901 """Invalidate the dirstate."""
1907 """Invalidate the dirstate."""
1902
1908
1903 def invalidate(self, clearfilecache=False):
1909 def invalidate(self, clearfilecache=False):
1904 pass
1910 pass
1905
1911
1906 def invalidateall(self):
1912 def invalidateall(self):
1907 pass
1913 pass
1908
1914
1909 def lock(self, wait=True):
1915 def lock(self, wait=True):
1910 """Lock the repository store and return a lock instance."""
1916 """Lock the repository store and return a lock instance."""
1911
1917
1912 def currentlock(self):
1918 def currentlock(self):
1913 """Return the lock if it's held or None."""
1919 """Return the lock if it's held or None."""
1914
1920
1915 def wlock(self, wait=True):
1921 def wlock(self, wait=True):
1916 """Lock the non-store parts of the repository."""
1922 """Lock the non-store parts of the repository."""
1917
1923
1918 def currentwlock(self):
1924 def currentwlock(self):
1919 """Return the wlock if it's held or None."""
1925 """Return the wlock if it's held or None."""
1920
1926
1921 def checkcommitpatterns(self, wctx, match, status, fail):
1927 def checkcommitpatterns(self, wctx, match, status, fail):
1922 pass
1928 pass
1923
1929
1924 def commit(
1930 def commit(
1925 self,
1931 self,
1926 text=b'',
1932 text=b'',
1927 user=None,
1933 user=None,
1928 date=None,
1934 date=None,
1929 match=None,
1935 match=None,
1930 force=False,
1936 force=False,
1931 editor=False,
1937 editor=False,
1932 extra=None,
1938 extra=None,
1933 ):
1939 ):
1934 """Add a new revision to the repository."""
1940 """Add a new revision to the repository."""
1935
1941
1936 def commitctx(self, ctx, error=False, origctx=None):
1942 def commitctx(self, ctx, error=False, origctx=None):
1937 """Commit a commitctx instance to the repository."""
1943 """Commit a commitctx instance to the repository."""
1938
1944
1939 def destroying(self):
1945 def destroying(self):
1940 """Inform the repository that nodes are about to be destroyed."""
1946 """Inform the repository that nodes are about to be destroyed."""
1941
1947
1942 def destroyed(self):
1948 def destroyed(self):
1943 """Inform the repository that nodes have been destroyed."""
1949 """Inform the repository that nodes have been destroyed."""
1944
1950
1945 def status(
1951 def status(
1946 self,
1952 self,
1947 node1=b'.',
1953 node1=b'.',
1948 node2=None,
1954 node2=None,
1949 match=None,
1955 match=None,
1950 ignored=False,
1956 ignored=False,
1951 clean=False,
1957 clean=False,
1952 unknown=False,
1958 unknown=False,
1953 listsubrepos=False,
1959 listsubrepos=False,
1954 ):
1960 ):
1955 """Convenience method to call repo[x].status()."""
1961 """Convenience method to call repo[x].status()."""
1956
1962
1957 def addpostdsstatus(self, ps):
1963 def addpostdsstatus(self, ps):
1958 pass
1964 pass
1959
1965
1960 def postdsstatus(self):
1966 def postdsstatus(self):
1961 pass
1967 pass
1962
1968
1963 def clearpostdsstatus(self):
1969 def clearpostdsstatus(self):
1964 pass
1970 pass
1965
1971
1966 def heads(self, start=None):
1972 def heads(self, start=None):
1967 """Obtain list of nodes that are DAG heads."""
1973 """Obtain list of nodes that are DAG heads."""
1968
1974
1969 def branchheads(self, branch=None, start=None, closed=False):
1975 def branchheads(self, branch=None, start=None, closed=False):
1970 pass
1976 pass
1971
1977
1972 def branches(self, nodes):
1978 def branches(self, nodes):
1973 pass
1979 pass
1974
1980
1975 def between(self, pairs):
1981 def between(self, pairs):
1976 pass
1982 pass
1977
1983
1978 def checkpush(self, pushop):
1984 def checkpush(self, pushop):
1979 pass
1985 pass
1980
1986
1981 prepushoutgoinghooks = interfaceutil.Attribute("""util.hooks instance.""")
1987 prepushoutgoinghooks = interfaceutil.Attribute("""util.hooks instance.""")
1982
1988
1983 def pushkey(self, namespace, key, old, new):
1989 def pushkey(self, namespace, key, old, new):
1984 pass
1990 pass
1985
1991
1986 def listkeys(self, namespace):
1992 def listkeys(self, namespace):
1987 pass
1993 pass
1988
1994
1989 def debugwireargs(self, one, two, three=None, four=None, five=None):
1995 def debugwireargs(self, one, two, three=None, four=None, five=None):
1990 pass
1996 pass
1991
1997
1992 def savecommitmessage(self, text):
1998 def savecommitmessage(self, text):
1993 pass
1999 pass
1994
2000
1995 def register_sidedata_computer(
2001 def register_sidedata_computer(
1996 self, kind, category, keys, computer, flags, replace=False
2002 self, kind, category, keys, computer, flags, replace=False
1997 ):
2003 ):
1998 pass
2004 pass
1999
2005
2000 def register_wanted_sidedata(self, category):
2006 def register_wanted_sidedata(self, category):
2001 pass
2007 pass
2002
2008
2003
2009
2004 class completelocalrepository(
2010 class completelocalrepository(
2005 ilocalrepositorymain, ilocalrepositoryfilestorage
2011 ilocalrepositorymain, ilocalrepositoryfilestorage
2006 ):
2012 ):
2007 """Complete interface for a local repository."""
2013 """Complete interface for a local repository."""
2008
2014
2009
2015
2010 class iwireprotocolcommandcacher(Protocol):
2016 class iwireprotocolcommandcacher(Protocol):
2011 """Represents a caching backend for wire protocol commands.
2017 """Represents a caching backend for wire protocol commands.
2012
2018
2013 Wire protocol version 2 supports transparent caching of many commands.
2019 Wire protocol version 2 supports transparent caching of many commands.
2014 To leverage this caching, servers can activate objects that cache
2020 To leverage this caching, servers can activate objects that cache
2015 command responses. Objects handle both cache writing and reading.
2021 command responses. Objects handle both cache writing and reading.
2016 This interface defines how that response caching mechanism works.
2022 This interface defines how that response caching mechanism works.
2017
2023
2018 Wire protocol version 2 commands emit a series of objects that are
2024 Wire protocol version 2 commands emit a series of objects that are
2019 serialized and sent to the client. The caching layer exists between
2025 serialized and sent to the client. The caching layer exists between
2020 the invocation of the command function and the sending of its output
2026 the invocation of the command function and the sending of its output
2021 objects to an output layer.
2027 objects to an output layer.
2022
2028
2023 Instances of this interface represent a binding to a cache that
2029 Instances of this interface represent a binding to a cache that
2024 can serve a response (in place of calling a command function) and/or
2030 can serve a response (in place of calling a command function) and/or
2025 write responses to a cache for subsequent use.
2031 write responses to a cache for subsequent use.
2026
2032
2027 When a command request arrives, the following happens with regards
2033 When a command request arrives, the following happens with regards
2028 to this interface:
2034 to this interface:
2029
2035
2030 1. The server determines whether the command request is cacheable.
2036 1. The server determines whether the command request is cacheable.
2031 2. If it is, an instance of this interface is spawned.
2037 2. If it is, an instance of this interface is spawned.
2032 3. The cacher is activated in a context manager (``__enter__`` is called).
2038 3. The cacher is activated in a context manager (``__enter__`` is called).
2033 4. A cache *key* for that request is derived. This will call the
2039 4. A cache *key* for that request is derived. This will call the
2034 instance's ``adjustcachekeystate()`` method so the derivation
2040 instance's ``adjustcachekeystate()`` method so the derivation
2035 can be influenced.
2041 can be influenced.
2036 5. The cacher is informed of the derived cache key via a call to
2042 5. The cacher is informed of the derived cache key via a call to
2037 ``setcachekey()``.
2043 ``setcachekey()``.
2038 6. The cacher's ``lookup()`` method is called to test for presence of
2044 6. The cacher's ``lookup()`` method is called to test for presence of
2039 the derived key in the cache.
2045 the derived key in the cache.
2040 7. If ``lookup()`` returns a hit, that cached result is used in place
2046 7. If ``lookup()`` returns a hit, that cached result is used in place
2041 of invoking the command function. ``__exit__`` is called and the instance
2047 of invoking the command function. ``__exit__`` is called and the instance
2042 is discarded.
2048 is discarded.
2043 8. The command function is invoked.
2049 8. The command function is invoked.
2044 9. ``onobject()`` is called for each object emitted by the command
2050 9. ``onobject()`` is called for each object emitted by the command
2045 function.
2051 function.
2046 10. After the final object is seen, ``onfinished()`` is called.
2052 10. After the final object is seen, ``onfinished()`` is called.
2047 11. ``__exit__`` is called to signal the end of use of the instance.
2053 11. ``__exit__`` is called to signal the end of use of the instance.
2048
2054
2049 Cache *key* derivation can be influenced by the instance.
2055 Cache *key* derivation can be influenced by the instance.
2050
2056
2051 Cache keys are initially derived by a deterministic representation of
2057 Cache keys are initially derived by a deterministic representation of
2052 the command request. This includes the command name, arguments, protocol
2058 the command request. This includes the command name, arguments, protocol
2053 version, etc. This initial key derivation is performed by CBOR-encoding a
2059 version, etc. This initial key derivation is performed by CBOR-encoding a
2054 data structure and feeding that output into a hasher.
2060 data structure and feeding that output into a hasher.
2055
2061
2056 Instances of this interface can influence this initial key derivation
2062 Instances of this interface can influence this initial key derivation
2057 via ``adjustcachekeystate()``.
2063 via ``adjustcachekeystate()``.
2058
2064
2059 The instance is informed of the derived cache key via a call to
2065 The instance is informed of the derived cache key via a call to
2060 ``setcachekey()``. The instance must store the key locally so it can
2066 ``setcachekey()``. The instance must store the key locally so it can
2061 be consulted on subsequent operations that may require it.
2067 be consulted on subsequent operations that may require it.
2062
2068
2063 When constructed, the instance has access to a callable that can be used
2069 When constructed, the instance has access to a callable that can be used
2064 for encoding response objects. This callable receives as its single
2070 for encoding response objects. This callable receives as its single
2065 argument an object emitted by a command function. It returns an iterable
2071 argument an object emitted by a command function. It returns an iterable
2066 of bytes chunks representing the encoded object. Unless the cacher is
2072 of bytes chunks representing the encoded object. Unless the cacher is
2067 caching native Python objects in memory or has a way of reconstructing
2073 caching native Python objects in memory or has a way of reconstructing
2068 the original Python objects, implementations typically call this function
2074 the original Python objects, implementations typically call this function
2069 to produce bytes from the output objects and then store those bytes in
2075 to produce bytes from the output objects and then store those bytes in
2070 the cache. When it comes time to re-emit those bytes, they are wrapped
2076 the cache. When it comes time to re-emit those bytes, they are wrapped
2071 in a ``wireprototypes.encodedresponse`` instance to tell the output
2077 in a ``wireprototypes.encodedresponse`` instance to tell the output
2072 layer that they are pre-encoded.
2078 layer that they are pre-encoded.
2073
2079
2074 When receiving the objects emitted by the command function, instances
2080 When receiving the objects emitted by the command function, instances
2075 can choose what to do with those objects. The simplest thing to do is
2081 can choose what to do with those objects. The simplest thing to do is
2076 re-emit the original objects. They will be forwarded to the output
2082 re-emit the original objects. They will be forwarded to the output
2077 layer and will be processed as if the cacher did not exist.
2083 layer and will be processed as if the cacher did not exist.
2078
2084
2079 Implementations could also choose to not emit objects - instead locally
2085 Implementations could also choose to not emit objects - instead locally
2080 buffering objects or their encoded representation. They could then emit
2086 buffering objects or their encoded representation. They could then emit
2081 a single "coalesced" object when ``onfinished()`` is called. In
2087 a single "coalesced" object when ``onfinished()`` is called. In
2082 this way, the implementation would function as a filtering layer of
2088 this way, the implementation would function as a filtering layer of
2083 sorts.
2089 sorts.
2084
2090
2085 When caching objects, typically the encoded form of the object will
2091 When caching objects, typically the encoded form of the object will
2086 be stored. Keep in mind that if the original object is forwarded to
2092 be stored. Keep in mind that if the original object is forwarded to
2087 the output layer, it will need to be encoded there as well. For large
2093 the output layer, it will need to be encoded there as well. For large
2088 output, this redundant encoding could add overhead. Implementations
2094 output, this redundant encoding could add overhead. Implementations
2089 could wrap the encoded object data in ``wireprototypes.encodedresponse``
2095 could wrap the encoded object data in ``wireprototypes.encodedresponse``
2090 instances to avoid this overhead.
2096 instances to avoid this overhead.
2091 """
2097 """
2092
2098
2093 def __enter__(self):
2099 def __enter__(self):
2094 """Marks the instance as active.
2100 """Marks the instance as active.
2095
2101
2096 Should return self.
2102 Should return self.
2097 """
2103 """
2098
2104
2099 def __exit__(self, exctype, excvalue, exctb):
2105 def __exit__(self, exctype, excvalue, exctb):
2100 """Called when cacher is no longer used.
2106 """Called when cacher is no longer used.
2101
2107
2102 This can be used by implementations to perform cleanup actions (e.g.
2108 This can be used by implementations to perform cleanup actions (e.g.
2103 disconnecting network sockets, aborting a partially cached response.
2109 disconnecting network sockets, aborting a partially cached response.
2104 """
2110 """
2105
2111
2106 def adjustcachekeystate(self, state):
2112 def adjustcachekeystate(self, state):
2107 """Influences cache key derivation by adjusting state to derive key.
2113 """Influences cache key derivation by adjusting state to derive key.
2108
2114
2109 A dict defining the state used to derive the cache key is passed.
2115 A dict defining the state used to derive the cache key is passed.
2110
2116
2111 Implementations can modify this dict to record additional state that
2117 Implementations can modify this dict to record additional state that
2112 is wanted to influence key derivation.
2118 is wanted to influence key derivation.
2113
2119
2114 Implementations are *highly* encouraged to not modify or delete
2120 Implementations are *highly* encouraged to not modify or delete
2115 existing keys.
2121 existing keys.
2116 """
2122 """
2117
2123
2118 def setcachekey(self, key):
2124 def setcachekey(self, key):
2119 """Record the derived cache key for this request.
2125 """Record the derived cache key for this request.
2120
2126
2121 Instances may mutate the key for internal usage, as desired. e.g.
2127 Instances may mutate the key for internal usage, as desired. e.g.
2122 instances may wish to prepend the repo name, introduce path
2128 instances may wish to prepend the repo name, introduce path
2123 components for filesystem or URL addressing, etc. Behavior is up to
2129 components for filesystem or URL addressing, etc. Behavior is up to
2124 the cache.
2130 the cache.
2125
2131
2126 Returns a bool indicating if the request is cacheable by this
2132 Returns a bool indicating if the request is cacheable by this
2127 instance.
2133 instance.
2128 """
2134 """
2129
2135
2130 def lookup(self):
2136 def lookup(self):
2131 """Attempt to resolve an entry in the cache.
2137 """Attempt to resolve an entry in the cache.
2132
2138
2133 The instance is instructed to look for the cache key that it was
2139 The instance is instructed to look for the cache key that it was
2134 informed about via the call to ``setcachekey()``.
2140 informed about via the call to ``setcachekey()``.
2135
2141
2136 If there's no cache hit or the cacher doesn't wish to use the cached
2142 If there's no cache hit or the cacher doesn't wish to use the cached
2137 entry, ``None`` should be returned.
2143 entry, ``None`` should be returned.
2138
2144
2139 Else, a dict defining the cached result should be returned. The
2145 Else, a dict defining the cached result should be returned. The
2140 dict may have the following keys:
2146 dict may have the following keys:
2141
2147
2142 objs
2148 objs
2143 An iterable of objects that should be sent to the client. That
2149 An iterable of objects that should be sent to the client. That
2144 iterable of objects is expected to be what the command function
2150 iterable of objects is expected to be what the command function
2145 would return if invoked or an equivalent representation thereof.
2151 would return if invoked or an equivalent representation thereof.
2146 """
2152 """
2147
2153
2148 def onobject(self, obj):
2154 def onobject(self, obj):
2149 """Called when a new object is emitted from the command function.
2155 """Called when a new object is emitted from the command function.
2150
2156
2151 Receives as its argument the object that was emitted from the
2157 Receives as its argument the object that was emitted from the
2152 command function.
2158 command function.
2153
2159
2154 This method returns an iterator of objects to forward to the output
2160 This method returns an iterator of objects to forward to the output
2155 layer. The easiest implementation is a generator that just
2161 layer. The easiest implementation is a generator that just
2156 ``yield obj``.
2162 ``yield obj``.
2157 """
2163 """
2158
2164
2159 def onfinished(self):
2165 def onfinished(self):
2160 """Called after all objects have been emitted from the command function.
2166 """Called after all objects have been emitted from the command function.
2161
2167
2162 Implementations should return an iterator of objects to forward to
2168 Implementations should return an iterator of objects to forward to
2163 the output layer.
2169 the output layer.
2164
2170
2165 This method can be a generator.
2171 This method can be a generator.
2166 """
2172 """
@@ -1,2747 +1,2747
1 # manifest.py - manifest revision class for mercurial
1 # manifest.py - manifest revision class for mercurial
2 #
2 #
3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import annotations
8 from __future__ import annotations
9
9
10 import heapq
10 import heapq
11 import itertools
11 import itertools
12 import struct
12 import struct
13 import typing
13 import typing
14 import weakref
14 import weakref
15
15
16 from typing import (
16 from typing import (
17 Callable,
17 Callable,
18 Collection,
18 Collection,
19 Dict,
19 Dict,
20 Iterable,
20 Iterable,
21 Iterator,
21 Iterator,
22 List,
22 List,
23 Optional,
23 Optional,
24 Set,
24 Set,
25 Tuple,
25 Tuple,
26 Union,
26 Union,
27 cast,
27 cast,
28 )
28 )
29
29
30 from .i18n import _
30 from .i18n import _
31 from .node import (
31 from .node import (
32 bin,
32 bin,
33 hex,
33 hex,
34 nullrev,
34 nullrev,
35 )
35 )
36 from . import (
36 from . import (
37 encoding,
37 encoding,
38 error,
38 error,
39 match as matchmod,
39 match as matchmod,
40 mdiff,
40 mdiff,
41 pathutil,
41 pathutil,
42 policy,
42 policy,
43 pycompat,
43 pycompat,
44 revlog,
44 revlog,
45 util,
45 util,
46 )
46 )
47 from .interfaces import (
47 from .interfaces import (
48 repository,
48 repository,
49 )
49 )
50 from .revlogutils import (
50 from .revlogutils import (
51 constants as revlog_constants,
51 constants as revlog_constants,
52 )
52 )
53
53
54 if typing.TYPE_CHECKING:
54 if typing.TYPE_CHECKING:
55 from typing import (
55 from typing import (
56 ByteString,
56 ByteString,
57 )
57 )
58
58
59
59
60 parsers = policy.importmod('parsers')
60 parsers = policy.importmod('parsers')
61 propertycache = util.propertycache
61 propertycache = util.propertycache
62
62
63 # Allow tests to more easily test the alternate path in manifestdict.fastdelta()
63 # Allow tests to more easily test the alternate path in manifestdict.fastdelta()
64 FASTDELTA_TEXTDIFF_THRESHOLD = 1000
64 FASTDELTA_TEXTDIFF_THRESHOLD = 1000
65
65
66
66
67 def _parse(nodelen, data: bytes):
67 def _parse(nodelen, data: bytes):
68 # This method does a little bit of excessive-looking
68 # This method does a little bit of excessive-looking
69 # precondition checking. This is so that the behavior of this
69 # precondition checking. This is so that the behavior of this
70 # class exactly matches its C counterpart to try and help
70 # class exactly matches its C counterpart to try and help
71 # prevent surprise breakage for anyone that develops against
71 # prevent surprise breakage for anyone that develops against
72 # the pure version.
72 # the pure version.
73 if data and data[-1:] != b'\n':
73 if data and data[-1:] != b'\n':
74 raise ValueError(b'Manifest did not end in a newline.')
74 raise ValueError(b'Manifest did not end in a newline.')
75 prev = None
75 prev = None
76 for l in data.splitlines():
76 for l in data.splitlines():
77 if prev is not None and prev > l:
77 if prev is not None and prev > l:
78 raise ValueError(b'Manifest lines not in sorted order.')
78 raise ValueError(b'Manifest lines not in sorted order.')
79 prev = l
79 prev = l
80 f, n = l.split(b'\0')
80 f, n = l.split(b'\0')
81 nl = len(n)
81 nl = len(n)
82 flags = n[-1:]
82 flags = n[-1:]
83 if flags in _manifestflags:
83 if flags in _manifestflags:
84 n = n[:-1]
84 n = n[:-1]
85 nl -= 1
85 nl -= 1
86 else:
86 else:
87 flags = b''
87 flags = b''
88 if nl != 2 * nodelen:
88 if nl != 2 * nodelen:
89 raise ValueError(b'Invalid manifest line')
89 raise ValueError(b'Invalid manifest line')
90
90
91 yield f, bin(n), flags
91 yield f, bin(n), flags
92
92
93
93
94 def _text(it):
94 def _text(it):
95 files = []
95 files = []
96 lines = []
96 lines = []
97 for f, n, fl in it:
97 for f, n, fl in it:
98 files.append(f)
98 files.append(f)
99 # if this is changed to support newlines in filenames,
99 # if this is changed to support newlines in filenames,
100 # be sure to check the templates/ dir again (especially *-raw.tmpl)
100 # be sure to check the templates/ dir again (especially *-raw.tmpl)
101 lines.append(b"%s\0%s%s\n" % (f, hex(n), fl))
101 lines.append(b"%s\0%s%s\n" % (f, hex(n), fl))
102
102
103 _checkforbidden(files)
103 _checkforbidden(files)
104 return b''.join(lines)
104 return b''.join(lines)
105
105
106
106
107 class lazymanifestiter:
107 class lazymanifestiter:
108 def __init__(self, lm: '_LazyManifest') -> None:
108 def __init__(self, lm: '_LazyManifest') -> None:
109 self.pos = 0
109 self.pos = 0
110 self.lm = lm
110 self.lm = lm
111
111
112 def __iter__(self) -> 'lazymanifestiter':
112 def __iter__(self) -> 'lazymanifestiter':
113 return self
113 return self
114
114
115 def next(self) -> bytes:
115 def next(self) -> bytes:
116 try:
116 try:
117 data, pos = self.lm._get(self.pos)
117 data, pos = self.lm._get(self.pos)
118 except IndexError:
118 except IndexError:
119 raise StopIteration
119 raise StopIteration
120 if pos == -1:
120 if pos == -1:
121 assert isinstance(data, tuple)
121 assert isinstance(data, tuple)
122 self.pos += 1
122 self.pos += 1
123 return data[0]
123 return data[0]
124 assert isinstance(data, bytes)
124 assert isinstance(data, bytes)
125 self.pos += 1
125 self.pos += 1
126 zeropos = data.find(b'\x00', pos)
126 zeropos = data.find(b'\x00', pos)
127 return data[pos:zeropos]
127 return data[pos:zeropos]
128
128
129 __next__ = next
129 __next__ = next
130
130
131
131
132 class lazymanifestiterentries:
132 class lazymanifestiterentries:
133 def __init__(self, lm: '_LazyManifest') -> None:
133 def __init__(self, lm: '_LazyManifest') -> None:
134 self.lm = lm
134 self.lm = lm
135 self.pos = 0
135 self.pos = 0
136
136
137 def __iter__(self) -> 'lazymanifestiterentries':
137 def __iter__(self) -> 'lazymanifestiterentries':
138 return self
138 return self
139
139
140 def next(self) -> Tuple[bytes, bytes, bytes]:
140 def next(self) -> Tuple[bytes, bytes, bytes]:
141 try:
141 try:
142 data, pos = self.lm._get(self.pos)
142 data, pos = self.lm._get(self.pos)
143 except IndexError:
143 except IndexError:
144 raise StopIteration
144 raise StopIteration
145 if pos == -1:
145 if pos == -1:
146 assert isinstance(data, tuple)
146 assert isinstance(data, tuple)
147 self.pos += 1
147 self.pos += 1
148 return data
148 return data
149 assert isinstance(data, bytes)
149 assert isinstance(data, bytes)
150 zeropos = data.find(b'\x00', pos)
150 zeropos = data.find(b'\x00', pos)
151 nlpos = data.find(b'\n', pos)
151 nlpos = data.find(b'\n', pos)
152 if zeropos == -1 or nlpos == -1 or nlpos < zeropos:
152 if zeropos == -1 or nlpos == -1 or nlpos < zeropos:
153 raise error.StorageError(b'Invalid manifest line')
153 raise error.StorageError(b'Invalid manifest line')
154 flags = data[nlpos - 1 : nlpos]
154 flags = data[nlpos - 1 : nlpos]
155 if flags in _manifestflags:
155 if flags in _manifestflags:
156 hlen = nlpos - zeropos - 2
156 hlen = nlpos - zeropos - 2
157 else:
157 else:
158 hlen = nlpos - zeropos - 1
158 hlen = nlpos - zeropos - 1
159 flags = b''
159 flags = b''
160 if hlen != 2 * self.lm._nodelen:
160 if hlen != 2 * self.lm._nodelen:
161 raise error.StorageError(b'Invalid manifest line')
161 raise error.StorageError(b'Invalid manifest line')
162 hashval = unhexlify(
162 hashval = unhexlify(
163 data, self.lm.extrainfo[self.pos], zeropos + 1, hlen
163 data, self.lm.extrainfo[self.pos], zeropos + 1, hlen
164 )
164 )
165 self.pos += 1
165 self.pos += 1
166 return (data[pos:zeropos], hashval, flags)
166 return (data[pos:zeropos], hashval, flags)
167
167
168 __next__ = next
168 __next__ = next
169
169
170
170
171 def unhexlify(data: bytes, extra: int, pos, length: int):
171 def unhexlify(data: bytes, extra: int, pos, length: int):
172 s = bin(data[pos : pos + length])
172 s = bin(data[pos : pos + length])
173 if extra:
173 if extra:
174 s += bytes([extra & 0xFF])
174 s += bytes([extra & 0xFF])
175 return s
175 return s
176
176
177
177
178 def _cmp(a, b):
178 def _cmp(a, b):
179 return (a > b) - (a < b)
179 return (a > b) - (a < b)
180
180
181
181
182 _manifestflags = {b'', b'l', b't', b'x'}
182 _manifestflags = {b'', b'l', b't', b'x'}
183
183
184
184
185 class _LazyManifest:
185 class _LazyManifest:
186 """A pure python manifest backed by a byte string. It is supplimented with
186 """A pure python manifest backed by a byte string. It is supplimented with
187 internal lists as it is modified, until it is compacted back to a pure byte
187 internal lists as it is modified, until it is compacted back to a pure byte
188 string.
188 string.
189
189
190 ``data`` is the initial manifest data.
190 ``data`` is the initial manifest data.
191
191
192 ``positions`` is a list of offsets, one per manifest entry. Positive
192 ``positions`` is a list of offsets, one per manifest entry. Positive
193 values are offsets into ``data``, negative values are offsets into the
193 values are offsets into ``data``, negative values are offsets into the
194 ``extradata`` list. When an entry is removed, its entry is dropped from
194 ``extradata`` list. When an entry is removed, its entry is dropped from
195 ``positions``. The values are encoded such that when walking the list and
195 ``positions``. The values are encoded such that when walking the list and
196 indexing into ``data`` or ``extradata`` as appropriate, the entries are
196 indexing into ``data`` or ``extradata`` as appropriate, the entries are
197 sorted by filename.
197 sorted by filename.
198
198
199 ``extradata`` is a list of (key, hash, flags) for entries that were added or
199 ``extradata`` is a list of (key, hash, flags) for entries that were added or
200 modified since the manifest was created or compacted.
200 modified since the manifest was created or compacted.
201 """
201 """
202
202
203 def __init__(
203 def __init__(
204 self,
204 self,
205 nodelen: int,
205 nodelen: int,
206 data: bytes,
206 data: bytes,
207 positions=None,
207 positions=None,
208 extrainfo=None,
208 extrainfo=None,
209 extradata=None,
209 extradata=None,
210 hasremovals: bool = False,
210 hasremovals: bool = False,
211 ):
211 ):
212 self._nodelen = nodelen
212 self._nodelen = nodelen
213 if positions is None:
213 if positions is None:
214 self.positions = self.findlines(data)
214 self.positions = self.findlines(data)
215 self.extrainfo = [0] * len(self.positions)
215 self.extrainfo = [0] * len(self.positions)
216 self.data = data
216 self.data = data
217 self.extradata = []
217 self.extradata = []
218 self.hasremovals = False
218 self.hasremovals = False
219 else:
219 else:
220 self.positions = positions[:]
220 self.positions = positions[:]
221 self.extrainfo = extrainfo[:]
221 self.extrainfo = extrainfo[:]
222 self.extradata = extradata[:]
222 self.extradata = extradata[:]
223 self.data = data
223 self.data = data
224 self.hasremovals = hasremovals
224 self.hasremovals = hasremovals
225
225
226 def findlines(self, data: bytes) -> List[int]:
226 def findlines(self, data: bytes) -> List[int]:
227 if not data:
227 if not data:
228 return []
228 return []
229 pos = data.find(b"\n")
229 pos = data.find(b"\n")
230 if pos == -1 or data[-1:] != b'\n':
230 if pos == -1 or data[-1:] != b'\n':
231 raise ValueError(b"Manifest did not end in a newline.")
231 raise ValueError(b"Manifest did not end in a newline.")
232 positions = [0]
232 positions = [0]
233 prev = data[: data.find(b'\x00')]
233 prev = data[: data.find(b'\x00')]
234 while pos < len(data) - 1 and pos != -1:
234 while pos < len(data) - 1 and pos != -1:
235 positions.append(pos + 1)
235 positions.append(pos + 1)
236 nexts = data[pos + 1 : data.find(b'\x00', pos + 1)]
236 nexts = data[pos + 1 : data.find(b'\x00', pos + 1)]
237 if nexts < prev:
237 if nexts < prev:
238 raise ValueError(b"Manifest lines not in sorted order.")
238 raise ValueError(b"Manifest lines not in sorted order.")
239 prev = nexts
239 prev = nexts
240 pos = data.find(b"\n", pos + 1)
240 pos = data.find(b"\n", pos + 1)
241 return positions
241 return positions
242
242
243 def _get(
243 def _get(
244 self, index: int
244 self, index: int
245 ) -> Tuple[Union[bytes, Tuple[bytes, bytes, bytes]], int]:
245 ) -> Tuple[Union[bytes, Tuple[bytes, bytes, bytes]], int]:
246 # get the position encoded in pos:
246 # get the position encoded in pos:
247 # positive number is an index in 'data'
247 # positive number is an index in 'data'
248 # negative number is in extrapieces
248 # negative number is in extrapieces
249 pos = self.positions[index]
249 pos = self.positions[index]
250 if pos >= 0:
250 if pos >= 0:
251 return self.data, pos
251 return self.data, pos
252 return self.extradata[-pos - 1], -1
252 return self.extradata[-pos - 1], -1
253
253
254 def _getkey(self, pos) -> bytes:
254 def _getkey(self, pos) -> bytes:
255 if pos >= 0:
255 if pos >= 0:
256 return self.data[pos : self.data.find(b'\x00', pos + 1)]
256 return self.data[pos : self.data.find(b'\x00', pos + 1)]
257 return self.extradata[-pos - 1][0]
257 return self.extradata[-pos - 1][0]
258
258
259 def bsearch(self, key: bytes) -> int:
259 def bsearch(self, key: bytes) -> int:
260 first = 0
260 first = 0
261 last = len(self.positions) - 1
261 last = len(self.positions) - 1
262
262
263 while first <= last:
263 while first <= last:
264 midpoint = (first + last) // 2
264 midpoint = (first + last) // 2
265 nextpos = self.positions[midpoint]
265 nextpos = self.positions[midpoint]
266 candidate = self._getkey(nextpos)
266 candidate = self._getkey(nextpos)
267 r = _cmp(key, candidate)
267 r = _cmp(key, candidate)
268 if r == 0:
268 if r == 0:
269 return midpoint
269 return midpoint
270 else:
270 else:
271 if r < 0:
271 if r < 0:
272 last = midpoint - 1
272 last = midpoint - 1
273 else:
273 else:
274 first = midpoint + 1
274 first = midpoint + 1
275 return -1
275 return -1
276
276
277 def bsearch2(self, key: bytes) -> Tuple[int, bool]:
277 def bsearch2(self, key: bytes) -> Tuple[int, bool]:
278 # same as the above, but will always return the position
278 # same as the above, but will always return the position
279 # done for performance reasons
279 # done for performance reasons
280 first = 0
280 first = 0
281 last = len(self.positions) - 1
281 last = len(self.positions) - 1
282
282
283 while first <= last:
283 while first <= last:
284 midpoint = (first + last) // 2
284 midpoint = (first + last) // 2
285 nextpos = self.positions[midpoint]
285 nextpos = self.positions[midpoint]
286 candidate = self._getkey(nextpos)
286 candidate = self._getkey(nextpos)
287 r = _cmp(key, candidate)
287 r = _cmp(key, candidate)
288 if r == 0:
288 if r == 0:
289 return (midpoint, True)
289 return (midpoint, True)
290 else:
290 else:
291 if r < 0:
291 if r < 0:
292 last = midpoint - 1
292 last = midpoint - 1
293 else:
293 else:
294 first = midpoint + 1
294 first = midpoint + 1
295 return (first, False)
295 return (first, False)
296
296
297 def __contains__(self, key: bytes) -> bool:
297 def __contains__(self, key: bytes) -> bool:
298 return self.bsearch(key) != -1
298 return self.bsearch(key) != -1
299
299
300 def __getitem__(self, key: bytes) -> Tuple[bytes, bytes]:
300 def __getitem__(self, key: bytes) -> Tuple[bytes, bytes]:
301 if not isinstance(key, bytes):
301 if not isinstance(key, bytes):
302 raise TypeError(b"getitem: manifest keys must be a bytes.")
302 raise TypeError(b"getitem: manifest keys must be a bytes.")
303 needle = self.bsearch(key)
303 needle = self.bsearch(key)
304 if needle == -1:
304 if needle == -1:
305 raise KeyError
305 raise KeyError
306 data, pos = self._get(needle)
306 data, pos = self._get(needle)
307 if pos == -1:
307 if pos == -1:
308 assert isinstance(data, tuple)
308 assert isinstance(data, tuple)
309 return (data[1], data[2])
309 return (data[1], data[2])
310
310
311 assert isinstance(data, bytes)
311 assert isinstance(data, bytes)
312 zeropos = data.find(b'\x00', pos)
312 zeropos = data.find(b'\x00', pos)
313 nlpos = data.find(b'\n', zeropos)
313 nlpos = data.find(b'\n', zeropos)
314 assert 0 <= needle <= len(self.positions)
314 assert 0 <= needle <= len(self.positions)
315 assert len(self.extrainfo) == len(self.positions)
315 assert len(self.extrainfo) == len(self.positions)
316 if zeropos == -1 or nlpos == -1 or nlpos < zeropos:
316 if zeropos == -1 or nlpos == -1 or nlpos < zeropos:
317 raise error.StorageError(b'Invalid manifest line')
317 raise error.StorageError(b'Invalid manifest line')
318 hlen = nlpos - zeropos - 1
318 hlen = nlpos - zeropos - 1
319 flags = data[nlpos - 1 : nlpos]
319 flags = data[nlpos - 1 : nlpos]
320 if flags in _manifestflags:
320 if flags in _manifestflags:
321 hlen -= 1
321 hlen -= 1
322 else:
322 else:
323 flags = b''
323 flags = b''
324 if hlen != 2 * self._nodelen:
324 if hlen != 2 * self._nodelen:
325 raise error.StorageError(b'Invalid manifest line')
325 raise error.StorageError(b'Invalid manifest line')
326 hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, hlen)
326 hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, hlen)
327 return (hashval, flags)
327 return (hashval, flags)
328
328
329 def __delitem__(self, key: bytes) -> None:
329 def __delitem__(self, key: bytes) -> None:
330 needle, found = self.bsearch2(key)
330 needle, found = self.bsearch2(key)
331 if not found:
331 if not found:
332 raise KeyError
332 raise KeyError
333 cur = self.positions[needle]
333 cur = self.positions[needle]
334 self.positions = self.positions[:needle] + self.positions[needle + 1 :]
334 self.positions = self.positions[:needle] + self.positions[needle + 1 :]
335 self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1 :]
335 self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1 :]
336 if cur >= 0:
336 if cur >= 0:
337 # This does NOT unsort the list as far as the search functions are
337 # This does NOT unsort the list as far as the search functions are
338 # concerned, as they only examine lines mapped by self.positions.
338 # concerned, as they only examine lines mapped by self.positions.
339 self.data = self.data[:cur] + b'\x00' + self.data[cur + 1 :]
339 self.data = self.data[:cur] + b'\x00' + self.data[cur + 1 :]
340 self.hasremovals = True
340 self.hasremovals = True
341
341
342 def __setitem__(self, key: bytes, value: Tuple[bytes, bytes]):
342 def __setitem__(self, key: bytes, value: Tuple[bytes, bytes]):
343 if not isinstance(key, bytes):
343 if not isinstance(key, bytes):
344 raise TypeError(b"setitem: manifest keys must be a byte string.")
344 raise TypeError(b"setitem: manifest keys must be a byte string.")
345 if not isinstance(value, tuple) or len(value) != 2:
345 if not isinstance(value, tuple) or len(value) != 2:
346 raise TypeError(
346 raise TypeError(
347 b"Manifest values must be a tuple of (node, flags)."
347 b"Manifest values must be a tuple of (node, flags)."
348 )
348 )
349 hashval = value[0]
349 hashval = value[0]
350 if not isinstance(hashval, bytes) or len(hashval) not in (20, 32):
350 if not isinstance(hashval, bytes) or len(hashval) not in (20, 32):
351 raise TypeError(b"node must be a 20-byte or 32-byte byte string")
351 raise TypeError(b"node must be a 20-byte or 32-byte byte string")
352 flags = value[1]
352 flags = value[1]
353 if not isinstance(flags, bytes) or len(flags) > 1:
353 if not isinstance(flags, bytes) or len(flags) > 1:
354 raise TypeError(b"flags must a 0 or 1 byte string, got %r", flags)
354 raise TypeError(b"flags must a 0 or 1 byte string, got %r", flags)
355 needle, found = self.bsearch2(key)
355 needle, found = self.bsearch2(key)
356 if found:
356 if found:
357 # put the item
357 # put the item
358 pos = self.positions[needle]
358 pos = self.positions[needle]
359 if pos < 0:
359 if pos < 0:
360 self.extradata[-pos - 1] = (key, hashval, value[1])
360 self.extradata[-pos - 1] = (key, hashval, value[1])
361 else:
361 else:
362 # just don't bother
362 # just don't bother
363 self.extradata.append((key, hashval, value[1]))
363 self.extradata.append((key, hashval, value[1]))
364 self.positions[needle] = -len(self.extradata)
364 self.positions[needle] = -len(self.extradata)
365 else:
365 else:
366 # not found, put it in with extra positions
366 # not found, put it in with extra positions
367 self.extradata.append((key, hashval, value[1]))
367 self.extradata.append((key, hashval, value[1]))
368 self.positions = (
368 self.positions = (
369 self.positions[:needle]
369 self.positions[:needle]
370 + [-len(self.extradata)]
370 + [-len(self.extradata)]
371 + self.positions[needle:]
371 + self.positions[needle:]
372 )
372 )
373 self.extrainfo = (
373 self.extrainfo = (
374 self.extrainfo[:needle] + [0] + self.extrainfo[needle:]
374 self.extrainfo[:needle] + [0] + self.extrainfo[needle:]
375 )
375 )
376
376
377 def copy(self) -> '_LazyManifest':
377 def copy(self) -> '_LazyManifest':
378 # XXX call _compact like in C?
378 # XXX call _compact like in C?
379 return _lazymanifest(
379 return _lazymanifest(
380 self._nodelen,
380 self._nodelen,
381 self.data,
381 self.data,
382 self.positions,
382 self.positions,
383 self.extrainfo,
383 self.extrainfo,
384 self.extradata,
384 self.extradata,
385 self.hasremovals,
385 self.hasremovals,
386 )
386 )
387
387
388 def _compact(self) -> None:
388 def _compact(self) -> None:
389 # hopefully not called TOO often
389 # hopefully not called TOO often
390 if len(self.extradata) == 0 and not self.hasremovals:
390 if len(self.extradata) == 0 and not self.hasremovals:
391 return
391 return
392 l = []
392 l = []
393 i = 0
393 i = 0
394 offset = 0
394 offset = 0
395 self.extrainfo = [0] * len(self.positions)
395 self.extrainfo = [0] * len(self.positions)
396 while i < len(self.positions):
396 while i < len(self.positions):
397 if self.positions[i] >= 0:
397 if self.positions[i] >= 0:
398 cur = self.positions[i]
398 cur = self.positions[i]
399 last_cut = cur
399 last_cut = cur
400
400
401 # Collect all contiguous entries in the buffer at the current
401 # Collect all contiguous entries in the buffer at the current
402 # offset, breaking out only for added/modified items held in
402 # offset, breaking out only for added/modified items held in
403 # extradata, or a deleted line prior to the next position.
403 # extradata, or a deleted line prior to the next position.
404 while True:
404 while True:
405 self.positions[i] = offset
405 self.positions[i] = offset
406 i += 1
406 i += 1
407 if i == len(self.positions) or self.positions[i] < 0:
407 if i == len(self.positions) or self.positions[i] < 0:
408 break
408 break
409
409
410 # A removed file has no positions[] entry, but does have an
410 # A removed file has no positions[] entry, but does have an
411 # overwritten first byte. Break out and find the end of the
411 # overwritten first byte. Break out and find the end of the
412 # current good entry/entries if there is a removed file
412 # current good entry/entries if there is a removed file
413 # before the next position.
413 # before the next position.
414 if (
414 if (
415 self.hasremovals
415 self.hasremovals
416 and self.data.find(b'\n\x00', cur, self.positions[i])
416 and self.data.find(b'\n\x00', cur, self.positions[i])
417 != -1
417 != -1
418 ):
418 ):
419 break
419 break
420
420
421 offset += self.positions[i] - cur
421 offset += self.positions[i] - cur
422 cur = self.positions[i]
422 cur = self.positions[i]
423 end_cut = self.data.find(b'\n', cur)
423 end_cut = self.data.find(b'\n', cur)
424 if end_cut != -1:
424 if end_cut != -1:
425 end_cut += 1
425 end_cut += 1
426 offset += end_cut - cur
426 offset += end_cut - cur
427 l.append(self.data[last_cut:end_cut])
427 l.append(self.data[last_cut:end_cut])
428 else:
428 else:
429 while i < len(self.positions) and self.positions[i] < 0:
429 while i < len(self.positions) and self.positions[i] < 0:
430 cur = self.positions[i]
430 cur = self.positions[i]
431 t = self.extradata[-cur - 1]
431 t = self.extradata[-cur - 1]
432 l.append(self._pack(t))
432 l.append(self._pack(t))
433 self.positions[i] = offset
433 self.positions[i] = offset
434 # Hashes are either 20 bytes (old sha1s) or 32
434 # Hashes are either 20 bytes (old sha1s) or 32
435 # bytes (new non-sha1).
435 # bytes (new non-sha1).
436 hlen = 20
436 hlen = 20
437 if len(t[1]) > 25:
437 if len(t[1]) > 25:
438 hlen = 32
438 hlen = 32
439 if len(t[1]) > hlen:
439 if len(t[1]) > hlen:
440 self.extrainfo[i] = ord(t[1][hlen + 1])
440 self.extrainfo[i] = ord(t[1][hlen + 1])
441 offset += len(l[-1])
441 offset += len(l[-1])
442 i += 1
442 i += 1
443 self.data = b''.join(l)
443 self.data = b''.join(l)
444 self.hasremovals = False
444 self.hasremovals = False
445 self.extradata = []
445 self.extradata = []
446
446
447 def _pack(self, d: Tuple[bytes, bytes, bytes]) -> bytes:
447 def _pack(self, d: Tuple[bytes, bytes, bytes]) -> bytes:
448 n = d[1]
448 n = d[1]
449 assert len(n) in (20, 32)
449 assert len(n) in (20, 32)
450 return d[0] + b'\x00' + hex(n) + d[2] + b'\n'
450 return d[0] + b'\x00' + hex(n) + d[2] + b'\n'
451
451
452 def text(self) -> ByteString:
452 def text(self) -> ByteString:
453 self._compact()
453 self._compact()
454 return self.data
454 return self.data
455
455
456 def diff(
456 def diff(
457 self, m2: '_LazyManifest', clean: bool = False
457 self, m2: '_LazyManifest', clean: bool = False
458 ) -> Dict[
458 ) -> Dict[
459 bytes,
459 bytes,
460 Optional[
460 Optional[
461 Tuple[Tuple[Optional[bytes], bytes], Tuple[Optional[bytes], bytes]]
461 Tuple[Tuple[Optional[bytes], bytes], Tuple[Optional[bytes], bytes]]
462 ],
462 ],
463 ]:
463 ]:
464 '''Finds changes between the current manifest and m2.'''
464 '''Finds changes between the current manifest and m2.'''
465 # XXX think whether efficiency matters here
465 # XXX think whether efficiency matters here
466 diff = {}
466 diff = {}
467
467
468 for fn, e1, flags in self.iterentries():
468 for fn, e1, flags in self.iterentries():
469 if fn not in m2:
469 if fn not in m2:
470 diff[fn] = (e1, flags), (None, b'')
470 diff[fn] = (e1, flags), (None, b'')
471 else:
471 else:
472 e2 = m2[fn]
472 e2 = m2[fn]
473 if (e1, flags) != e2:
473 if (e1, flags) != e2:
474 diff[fn] = (e1, flags), e2
474 diff[fn] = (e1, flags), e2
475 elif clean:
475 elif clean:
476 diff[fn] = None
476 diff[fn] = None
477
477
478 for fn, e2, flags in m2.iterentries():
478 for fn, e2, flags in m2.iterentries():
479 if fn not in self:
479 if fn not in self:
480 diff[fn] = (None, b''), (e2, flags)
480 diff[fn] = (None, b''), (e2, flags)
481
481
482 return diff
482 return diff
483
483
484 def iterentries(self) -> lazymanifestiterentries:
484 def iterentries(self) -> lazymanifestiterentries:
485 return lazymanifestiterentries(self)
485 return lazymanifestiterentries(self)
486
486
487 def iterkeys(self) -> lazymanifestiter:
487 def iterkeys(self) -> lazymanifestiter:
488 return lazymanifestiter(self)
488 return lazymanifestiter(self)
489
489
490 def __iter__(self) -> lazymanifestiter:
490 def __iter__(self) -> lazymanifestiter:
491 return lazymanifestiter(self)
491 return lazymanifestiter(self)
492
492
493 def __len__(self) -> int:
493 def __len__(self) -> int:
494 return len(self.positions)
494 return len(self.positions)
495
495
496 def filtercopy(self, filterfn: Callable[[bytes], bool]) -> '_LazyManifest':
496 def filtercopy(self, filterfn: Callable[[bytes], bool]) -> '_LazyManifest':
497 # XXX should be optimized
497 # XXX should be optimized
498 c = _lazymanifest(self._nodelen, b'')
498 c = _lazymanifest(self._nodelen, b'')
499 for f, n, fl in self.iterentries():
499 for f, n, fl in self.iterentries():
500 if filterfn(f):
500 if filterfn(f):
501 c[f] = n, fl
501 c[f] = n, fl
502 return c
502 return c
503
503
504
504
505 try:
505 try:
506 _lazymanifest = parsers.lazymanifest
506 _lazymanifest = parsers.lazymanifest
507 except AttributeError:
507 except AttributeError:
508 _lazymanifest = _LazyManifest
508 _lazymanifest = _LazyManifest
509
509
510
510
511 class manifestdict: # (repository.imanifestdict)
511 class manifestdict: # (repository.imanifestdict)
512 def __init__(self, nodelen: int, data: ByteString = b''):
512 def __init__(self, nodelen: int, data: ByteString = b''):
513 self._nodelen = nodelen
513 self._nodelen = nodelen
514 self._lm = _lazymanifest(nodelen, data)
514 self._lm = _lazymanifest(nodelen, data)
515
515
516 def __getitem__(self, key: bytes) -> bytes:
516 def __getitem__(self, key: bytes) -> bytes:
517 return self._lm[key][0]
517 return self._lm[key][0]
518
518
519 def find(self, key: bytes) -> Tuple[bytes, bytes]:
519 def find(self, key: bytes) -> Tuple[bytes, bytes]:
520 return self._lm[key]
520 return self._lm[key]
521
521
522 def __len__(self) -> int:
522 def __len__(self) -> int:
523 return len(self._lm)
523 return len(self._lm)
524
524
525 def __nonzero__(self) -> bool:
525 def __nonzero__(self) -> bool:
526 # nonzero is covered by the __len__ function, but implementing it here
526 # nonzero is covered by the __len__ function, but implementing it here
527 # makes it easier for extensions to override.
527 # makes it easier for extensions to override.
528 return len(self._lm) != 0
528 return len(self._lm) != 0
529
529
530 __bool__ = __nonzero__
530 __bool__ = __nonzero__
531
531
532 def set(self, key: bytes, node: bytes, flags: bytes) -> None:
532 def set(self, key: bytes, node: bytes, flags: bytes) -> None:
533 self._lm[key] = node, flags
533 self._lm[key] = node, flags
534
534
535 def __setitem__(self, key: bytes, node: bytes) -> None:
535 def __setitem__(self, key: bytes, node: bytes) -> None:
536 self._lm[key] = node, self.flags(key)
536 self._lm[key] = node, self.flags(key)
537
537
538 def __contains__(self, key: bytes) -> bool:
538 def __contains__(self, key: bytes) -> bool:
539 if key is None:
539 if key is None:
540 return False
540 return False
541 return key in self._lm
541 return key in self._lm
542
542
543 def __delitem__(self, key: bytes) -> None:
543 def __delitem__(self, key: bytes) -> None:
544 del self._lm[key]
544 del self._lm[key]
545
545
546 def __iter__(self) -> Iterator[bytes]:
546 def __iter__(self) -> Iterator[bytes]:
547 return self._lm.__iter__()
547 return self._lm.__iter__()
548
548
549 def iterkeys(self) -> Iterator[bytes]:
549 def iterkeys(self) -> Iterator[bytes]:
550 return self._lm.iterkeys()
550 return self._lm.iterkeys()
551
551
552 def keys(self) -> List[bytes]:
552 def keys(self) -> List[bytes]:
553 return list(self.iterkeys())
553 return list(self.iterkeys())
554
554
555 def filesnotin(self, m2, match=None) -> Set[bytes]:
555 def filesnotin(self, m2, match=None) -> Set[bytes]:
556 '''Set of files in this manifest that are not in the other'''
556 '''Set of files in this manifest that are not in the other'''
557 if match is not None:
557 if match is not None:
558 match = matchmod.badmatch(match, lambda path, msg: None)
558 match = matchmod.badmatch(match, lambda path, msg: None)
559 sm2 = set(m2.walk(match))
559 sm2 = set(m2.walk(match))
560 return {f for f in self.walk(match) if f not in sm2}
560 return {f for f in self.walk(match) if f not in sm2}
561 return {f for f in self if f not in m2}
561 return {f for f in self if f not in m2}
562
562
563 @propertycache
563 @propertycache
564 def _dirs(self) -> pathutil.dirs:
564 def _dirs(self) -> pathutil.dirs:
565 return pathutil.dirs(self)
565 return pathutil.dirs(self)
566
566
567 def dirs(self) -> pathutil.dirs:
567 def dirs(self) -> pathutil.dirs:
568 return self._dirs
568 return self._dirs
569
569
570 def hasdir(self, dir: bytes) -> bool:
570 def hasdir(self, dir: bytes) -> bool:
571 return dir in self._dirs
571 return dir in self._dirs
572
572
573 def _filesfastpath(self, match: matchmod.basematcher) -> bool:
573 def _filesfastpath(self, match: matchmod.basematcher) -> bool:
574 """Checks whether we can correctly and quickly iterate over matcher
574 """Checks whether we can correctly and quickly iterate over matcher
575 files instead of over manifest files."""
575 files instead of over manifest files."""
576 files = match.files()
576 files = match.files()
577 return len(files) < 100 and (
577 return len(files) < 100 and (
578 match.isexact()
578 match.isexact()
579 or (match.prefix() and all(fn in self for fn in files))
579 or (match.prefix() and all(fn in self for fn in files))
580 )
580 )
581
581
582 def walk(self, match: matchmod.basematcher) -> Iterator[bytes]:
582 def walk(self, match: matchmod.basematcher) -> Iterator[bytes]:
583 """Generates matching file names.
583 """Generates matching file names.
584
584
585 Equivalent to manifest.matches(match).iterkeys(), but without creating
585 Equivalent to manifest.matches(match).iterkeys(), but without creating
586 an entirely new manifest.
586 an entirely new manifest.
587
587
588 It also reports nonexistent files by marking them bad with match.bad().
588 It also reports nonexistent files by marking them bad with match.bad().
589 """
589 """
590 if match.always():
590 if match.always():
591 for f in iter(self):
591 for f in iter(self):
592 yield f
592 yield f
593 return
593 return
594
594
595 fset = set(match.files())
595 fset = set(match.files())
596
596
597 # avoid the entire walk if we're only looking for specific files
597 # avoid the entire walk if we're only looking for specific files
598 if self._filesfastpath(match):
598 if self._filesfastpath(match):
599 for fn in sorted(fset):
599 for fn in sorted(fset):
600 if fn in self:
600 if fn in self:
601 yield fn
601 yield fn
602 return
602 return
603
603
604 for fn in self:
604 for fn in self:
605 if fn in fset:
605 if fn in fset:
606 # specified pattern is the exact name
606 # specified pattern is the exact name
607 fset.remove(fn)
607 fset.remove(fn)
608 if match(fn):
608 if match(fn):
609 yield fn
609 yield fn
610
610
611 # for dirstate.walk, files=[''] means "walk the whole tree".
611 # for dirstate.walk, files=[''] means "walk the whole tree".
612 # follow that here, too
612 # follow that here, too
613 fset.discard(b'')
613 fset.discard(b'')
614
614
615 for fn in sorted(fset):
615 for fn in sorted(fset):
616 if not self.hasdir(fn):
616 if not self.hasdir(fn):
617 match.bad(fn, None)
617 match.bad(fn, None)
618
618
619 def _matches(self, match: matchmod.basematcher) -> 'manifestdict':
619 def _matches(self, match: matchmod.basematcher) -> 'manifestdict':
620 '''generate a new manifest filtered by the match argument'''
620 '''generate a new manifest filtered by the match argument'''
621 if match.always():
621 if match.always():
622 return self.copy()
622 return self.copy()
623
623
624 if self._filesfastpath(match):
624 if self._filesfastpath(match):
625 m = manifestdict(self._nodelen)
625 m = manifestdict(self._nodelen)
626 lm = self._lm
626 lm = self._lm
627 for fn in match.files():
627 for fn in match.files():
628 if fn in lm:
628 if fn in lm:
629 m._lm[fn] = lm[fn]
629 m._lm[fn] = lm[fn]
630 return m
630 return m
631
631
632 m = manifestdict(self._nodelen)
632 m = manifestdict(self._nodelen)
633 m._lm = self._lm.filtercopy(match)
633 m._lm = self._lm.filtercopy(match)
634 return m
634 return m
635
635
636 def diff(
636 def diff(
637 self,
637 self,
638 m2: 'manifestdict',
638 m2: 'manifestdict',
639 match: Optional[matchmod.basematcher] = None,
639 match: Optional[matchmod.basematcher] = None,
640 clean: bool = False,
640 clean: bool = False,
641 ) -> Dict[
641 ) -> Dict[
642 bytes,
642 bytes,
643 Optional[
643 Optional[
644 Tuple[Tuple[Optional[bytes], bytes], Tuple[Optional[bytes], bytes]]
644 Tuple[Tuple[Optional[bytes], bytes], Tuple[Optional[bytes], bytes]]
645 ],
645 ],
646 ]:
646 ]:
647 """Finds changes between the current manifest and m2.
647 """Finds changes between the current manifest and m2.
648
648
649 Args:
649 Args:
650 m2: the manifest to which this manifest should be compared.
650 m2: the manifest to which this manifest should be compared.
651 clean: if true, include files unchanged between these manifests
651 clean: if true, include files unchanged between these manifests
652 with a None value in the returned dictionary.
652 with a None value in the returned dictionary.
653
653
654 The result is returned as a dict with filename as key and
654 The result is returned as a dict with filename as key and
655 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
655 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
656 nodeid in the current/other manifest and fl1/fl2 is the flag
656 nodeid in the current/other manifest and fl1/fl2 is the flag
657 in the current/other manifest. Where the file does not exist,
657 in the current/other manifest. Where the file does not exist,
658 the nodeid will be None and the flags will be the empty
658 the nodeid will be None and the flags will be the empty
659 string.
659 string.
660 """
660 """
661 if match:
661 if match:
662 m1 = self._matches(match)
662 m1 = self._matches(match)
663 m2 = m2._matches(match)
663 m2 = m2._matches(match)
664 return m1.diff(m2, clean=clean)
664 return m1.diff(m2, clean=clean)
665 return self._lm.diff(m2._lm, clean)
665 return self._lm.diff(m2._lm, clean)
666
666
667 def setflag(self, key: bytes, flag: bytes) -> None:
667 def setflag(self, key: bytes, flag: bytes) -> None:
668 if flag not in _manifestflags:
668 if flag not in _manifestflags:
669 raise TypeError(b"Invalid manifest flag set.")
669 raise TypeError(b"Invalid manifest flag set.")
670 self._lm[key] = self[key], flag
670 self._lm[key] = self[key], flag
671
671
672 def get(self, key: bytes, default=None) -> Optional[bytes]:
672 def get(self, key: bytes, default=None) -> Optional[bytes]:
673 try:
673 try:
674 return self._lm[key][0]
674 return self._lm[key][0]
675 except KeyError:
675 except KeyError:
676 return default
676 return default
677
677
678 def flags(self, key: bytes) -> bytes:
678 def flags(self, key: bytes) -> bytes:
679 try:
679 try:
680 return self._lm[key][1]
680 return self._lm[key][1]
681 except KeyError:
681 except KeyError:
682 return b''
682 return b''
683
683
684 def copy(self) -> 'manifestdict':
684 def copy(self) -> 'manifestdict':
685 c = manifestdict(self._nodelen)
685 c = manifestdict(self._nodelen)
686 c._lm = self._lm.copy()
686 c._lm = self._lm.copy()
687 return c
687 return c
688
688
689 def items(self) -> Iterator[Tuple[bytes, bytes]]:
689 def items(self) -> Iterator[Tuple[bytes, bytes]]:
690 return (x[:2] for x in self._lm.iterentries())
690 return (x[:2] for x in self._lm.iterentries())
691
691
692 def iteritems(self) -> Iterator[Tuple[bytes, bytes]]:
692 def iteritems(self) -> Iterator[Tuple[bytes, bytes]]:
693 return (x[:2] for x in self._lm.iterentries())
693 return (x[:2] for x in self._lm.iterentries())
694
694
695 def iterentries(self) -> Iterator[Tuple[bytes, bytes, bytes]]:
695 def iterentries(self) -> Iterator[Tuple[bytes, bytes, bytes]]:
696 return self._lm.iterentries()
696 return self._lm.iterentries()
697
697
698 def text(self) -> ByteString:
698 def text(self) -> ByteString:
699 # most likely uses native version
699 # most likely uses native version
700 return self._lm.text()
700 return self._lm.text()
701
701
702 def fastdelta(
702 def fastdelta(
703 self, base: ByteString, changes: Iterable[Tuple[bytes, bool]]
703 self, base: ByteString, changes: Iterable[Tuple[bytes, bool]]
704 ) -> Tuple[ByteString, ByteString]:
704 ) -> Tuple[ByteString, ByteString]:
705 """Given a base manifest text as a bytearray and a list of changes
705 """Given a base manifest text as a bytearray and a list of changes
706 relative to that text, compute a delta that can be used by revlog.
706 relative to that text, compute a delta that can be used by revlog.
707 """
707 """
708 delta = []
708 delta = []
709 dstart = None
709 dstart = None
710 dend = None
710 dend = None
711 dline = [b""]
711 dline = [b""]
712 start = 0
712 start = 0
713 # zero copy representation of base as a buffer
713 # zero copy representation of base as a buffer
714 addbuf = util.buffer(base)
714 addbuf = util.buffer(base)
715
715
716 changes = list(changes)
716 changes = list(changes)
717 if len(changes) < FASTDELTA_TEXTDIFF_THRESHOLD:
717 if len(changes) < FASTDELTA_TEXTDIFF_THRESHOLD:
718 # start with a readonly loop that finds the offset of
718 # start with a readonly loop that finds the offset of
719 # each line and creates the deltas
719 # each line and creates the deltas
720 for f, todelete in changes:
720 for f, todelete in changes:
721 # bs will either be the index of the item or the insert point
721 # bs will either be the index of the item or the insert point
722 start, end = _msearch(addbuf, f, start)
722 start, end = _msearch(addbuf, f, start)
723 if not todelete:
723 if not todelete:
724 h, fl = self._lm[f]
724 h, fl = self._lm[f]
725 l = b"%s\0%s%s\n" % (f, hex(h), fl)
725 l = b"%s\0%s%s\n" % (f, hex(h), fl)
726 else:
726 else:
727 if start == end:
727 if start == end:
728 # item we want to delete was not found, error out
728 # item we want to delete was not found, error out
729 raise AssertionError(
729 raise AssertionError(
730 _(b"failed to remove %s from manifest") % f
730 _(b"failed to remove %s from manifest") % f
731 )
731 )
732 l = b""
732 l = b""
733 if dstart is not None and dstart <= start and dend >= start:
733 if dstart is not None and dstart <= start and dend >= start:
734 if dend < end:
734 if dend < end:
735 dend = end
735 dend = end
736 if l:
736 if l:
737 dline.append(l)
737 dline.append(l)
738 else:
738 else:
739 if dstart is not None:
739 if dstart is not None:
740 delta.append((dstart, dend, b"".join(dline)))
740 delta.append((dstart, dend, b"".join(dline)))
741 dstart = start
741 dstart = start
742 dend = end
742 dend = end
743 dline = [l]
743 dline = [l]
744
744
745 if dstart is not None:
745 if dstart is not None:
746 delta.append((dstart, dend, b"".join(dline)))
746 delta.append((dstart, dend, b"".join(dline)))
747 # apply the delta to the base, and get a delta for addrevision
747 # apply the delta to the base, and get a delta for addrevision
748 deltatext, arraytext = _addlistdelta(base, delta)
748 deltatext, arraytext = _addlistdelta(base, delta)
749 else:
749 else:
750 # For large changes, it's much cheaper to just build the text and
750 # For large changes, it's much cheaper to just build the text and
751 # diff it.
751 # diff it.
752 arraytext = bytearray(self.text())
752 arraytext = bytearray(self.text())
753 deltatext = mdiff.textdiff(
753 deltatext = mdiff.textdiff(
754 util.buffer(base), util.buffer(arraytext)
754 util.buffer(base), util.buffer(arraytext)
755 )
755 )
756
756
757 return arraytext, deltatext
757 return arraytext, deltatext
758
758
759
759
760 def _msearch(
760 def _msearch(
761 m: ByteString, s: bytes, lo: int = 0, hi: Optional[int] = None
761 m: ByteString, s: bytes, lo: int = 0, hi: Optional[int] = None
762 ) -> Tuple[int, int]:
762 ) -> Tuple[int, int]:
763 """return a tuple (start, end) that says where to find s within m.
763 """return a tuple (start, end) that says where to find s within m.
764
764
765 If the string is found m[start:end] are the line containing
765 If the string is found m[start:end] are the line containing
766 that string. If start == end the string was not found and
766 that string. If start == end the string was not found and
767 they indicate the proper sorted insertion point.
767 they indicate the proper sorted insertion point.
768 """
768 """
769
769
770 def advance(i: int, c: bytes):
770 def advance(i: int, c: bytes):
771 while i < lenm and m[i : i + 1] != c:
771 while i < lenm and m[i : i + 1] != c:
772 i += 1
772 i += 1
773 return i
773 return i
774
774
775 if not s:
775 if not s:
776 return (lo, lo)
776 return (lo, lo)
777 lenm = len(m)
777 lenm = len(m)
778 if not hi:
778 if not hi:
779 hi = lenm
779 hi = lenm
780 while lo < hi:
780 while lo < hi:
781 mid = (lo + hi) // 2
781 mid = (lo + hi) // 2
782 start = mid
782 start = mid
783 while start > 0 and m[start - 1 : start] != b'\n':
783 while start > 0 and m[start - 1 : start] != b'\n':
784 start -= 1
784 start -= 1
785 end = advance(start, b'\0')
785 end = advance(start, b'\0')
786 if bytes(m[start:end]) < s:
786 if bytes(m[start:end]) < s:
787 # we know that after the null there are 40 bytes of sha1
787 # we know that after the null there are 40 bytes of sha1
788 # this translates to the bisect lo = mid + 1
788 # this translates to the bisect lo = mid + 1
789 lo = advance(end + 40, b'\n') + 1
789 lo = advance(end + 40, b'\n') + 1
790 else:
790 else:
791 # this translates to the bisect hi = mid
791 # this translates to the bisect hi = mid
792 hi = start
792 hi = start
793 end = advance(lo, b'\0')
793 end = advance(lo, b'\0')
794 found = m[lo:end]
794 found = m[lo:end]
795 if s == found:
795 if s == found:
796 # we know that after the null there are 40 bytes of sha1
796 # we know that after the null there are 40 bytes of sha1
797 end = advance(end + 40, b'\n')
797 end = advance(end + 40, b'\n')
798 return (lo, end + 1)
798 return (lo, end + 1)
799 else:
799 else:
800 return (lo, lo)
800 return (lo, lo)
801
801
802
802
803 def _checkforbidden(l: Iterable[bytes]) -> None:
803 def _checkforbidden(l: Iterable[bytes]) -> None:
804 """Check filenames for illegal characters."""
804 """Check filenames for illegal characters."""
805 for f in l:
805 for f in l:
806 if b'\n' in f or b'\r' in f:
806 if b'\n' in f or b'\r' in f:
807 raise error.StorageError(
807 raise error.StorageError(
808 _(b"'\\n' and '\\r' disallowed in filenames: %r")
808 _(b"'\\n' and '\\r' disallowed in filenames: %r")
809 % pycompat.bytestr(f)
809 % pycompat.bytestr(f)
810 )
810 )
811
811
812
812
813 # apply the changes collected during the bisect loop to our addlist
813 # apply the changes collected during the bisect loop to our addlist
814 # return a delta suitable for addrevision
814 # return a delta suitable for addrevision
815 def _addlistdelta(
815 def _addlistdelta(
816 addlist: ByteString,
816 addlist: ByteString,
817 x: Iterable[Tuple[int, int, bytes]],
817 x: Iterable[Tuple[int, int, bytes]],
818 ) -> Tuple[bytes, ByteString]:
818 ) -> Tuple[bytes, ByteString]:
819 # for large addlist arrays, building a new array is cheaper
819 # for large addlist arrays, building a new array is cheaper
820 # than repeatedly modifying the existing one
820 # than repeatedly modifying the existing one
821 currentposition = 0
821 currentposition = 0
822 newaddlist = bytearray()
822 newaddlist = bytearray()
823
823
824 for start, end, content in x:
824 for start, end, content in x:
825 newaddlist += addlist[currentposition:start]
825 newaddlist += addlist[currentposition:start]
826 if content:
826 if content:
827 newaddlist += bytearray(content)
827 newaddlist += bytearray(content)
828
828
829 currentposition = end
829 currentposition = end
830
830
831 newaddlist += addlist[currentposition:]
831 newaddlist += addlist[currentposition:]
832
832
833 deltatext = b"".join(
833 deltatext = b"".join(
834 struct.pack(b">lll", start, end, len(content)) + content
834 struct.pack(b">lll", start, end, len(content)) + content
835 for start, end, content in x
835 for start, end, content in x
836 )
836 )
837 return deltatext, newaddlist
837 return deltatext, newaddlist
838
838
839
839
840 def _splittopdir(f: bytes) -> Tuple[bytes, bytes]:
840 def _splittopdir(f: bytes) -> Tuple[bytes, bytes]:
841 if b'/' in f:
841 if b'/' in f:
842 dir, subpath = f.split(b'/', 1)
842 dir, subpath = f.split(b'/', 1)
843 return dir + b'/', subpath
843 return dir + b'/', subpath
844 else:
844 else:
845 return b'', f
845 return b'', f
846
846
847
847
848 _noop = lambda s: None
848 _noop = lambda s: None
849
849
850
850
851 class treemanifest: # (repository.imanifestdict)
851 class treemanifest: # (repository.imanifestdict)
852 _dir: bytes
852 _dir: bytes
853 _dirs: Dict[bytes, 'treemanifest']
853 _dirs: Dict[bytes, 'treemanifest']
854 _dirty: bool
854 _dirty: bool
855 _files: Dict[bytes, bytes]
855 _files: Dict[bytes, bytes]
856 _flags: Dict[bytes, bytes]
856 _flags: Dict[bytes, bytes]
857
857
858 def __init__(self, nodeconstants, dir: bytes = b'', text: bytes = b''):
858 def __init__(self, nodeconstants, dir: bytes = b'', text: bytes = b''):
859 self._dir = dir
859 self._dir = dir
860 self.nodeconstants = nodeconstants
860 self.nodeconstants = nodeconstants
861 self._node = self.nodeconstants.nullid
861 self._node = self.nodeconstants.nullid
862 self._nodelen = self.nodeconstants.nodelen
862 self._nodelen = self.nodeconstants.nodelen
863 self._loadfunc = _noop
863 self._loadfunc = _noop
864 self._copyfunc = _noop
864 self._copyfunc = _noop
865 self._dirty = False
865 self._dirty = False
866 self._dirs = {}
866 self._dirs = {}
867 self._lazydirs: Dict[
867 self._lazydirs: Dict[
868 bytes,
868 bytes,
869 Tuple[bytes, Callable[[bytes, bytes], 'treemanifest'], bool],
869 Tuple[bytes, Callable[[bytes, bytes], 'treemanifest'], bool],
870 ] = {}
870 ] = {}
871 # Using _lazymanifest here is a little slower than plain old dicts
871 # Using _lazymanifest here is a little slower than plain old dicts
872 self._files = {}
872 self._files = {}
873 self._flags = {}
873 self._flags = {}
874 if text:
874 if text:
875
875
876 def readsubtree(subdir, subm):
876 def readsubtree(subdir, subm):
877 raise AssertionError(
877 raise AssertionError(
878 b'treemanifest constructor only accepts flat manifests'
878 b'treemanifest constructor only accepts flat manifests'
879 )
879 )
880
880
881 self.parse(text, readsubtree)
881 self.parse(text, readsubtree)
882 self._dirty = True # Mark flat manifest dirty after parsing
882 self._dirty = True # Mark flat manifest dirty after parsing
883
883
884 def _subpath(self, path: bytes) -> bytes:
884 def _subpath(self, path: bytes) -> bytes:
885 return self._dir + path
885 return self._dir + path
886
886
887 def _loadalllazy(self) -> None:
887 def _loadalllazy(self) -> None:
888 selfdirs = self._dirs
888 selfdirs = self._dirs
889 subpath = self._subpath
889 subpath = self._subpath
890 for d, (node, readsubtree, docopy) in self._lazydirs.items():
890 for d, (node, readsubtree, docopy) in self._lazydirs.items():
891 if docopy:
891 if docopy:
892 selfdirs[d] = readsubtree(subpath(d), node).copy()
892 selfdirs[d] = readsubtree(subpath(d), node).copy()
893 else:
893 else:
894 selfdirs[d] = readsubtree(subpath(d), node)
894 selfdirs[d] = readsubtree(subpath(d), node)
895 self._lazydirs.clear()
895 self._lazydirs.clear()
896
896
897 def _loadlazy(self, d: bytes) -> None:
897 def _loadlazy(self, d: bytes) -> None:
898 v = self._lazydirs.get(d)
898 v = self._lazydirs.get(d)
899 if v is not None:
899 if v is not None:
900 node, readsubtree, docopy = v
900 node, readsubtree, docopy = v
901 if docopy:
901 if docopy:
902 self._dirs[d] = readsubtree(self._subpath(d), node).copy()
902 self._dirs[d] = readsubtree(self._subpath(d), node).copy()
903 else:
903 else:
904 self._dirs[d] = readsubtree(self._subpath(d), node)
904 self._dirs[d] = readsubtree(self._subpath(d), node)
905 del self._lazydirs[d]
905 del self._lazydirs[d]
906
906
907 def _loadchildrensetlazy(
907 def _loadchildrensetlazy(
908 self, visit: Union[Set[bytes], bytes]
908 self, visit: Union[Set[bytes], bytes]
909 ) -> Optional[Set[bytes]]:
909 ) -> Optional[Set[bytes]]:
910 if not visit:
910 if not visit:
911 return None
911 return None
912 if visit == b'all' or visit == b'this':
912 if visit == b'all' or visit == b'this':
913 self._loadalllazy()
913 self._loadalllazy()
914 return None
914 return None
915
915
916 visit = cast(Set[bytes], visit)
916 visit = cast(Set[bytes], visit)
917
917
918 loadlazy = self._loadlazy
918 loadlazy = self._loadlazy
919 for k in visit:
919 for k in visit:
920 loadlazy(k + b'/')
920 loadlazy(k + b'/')
921 return visit
921 return visit
922
922
923 def _loaddifflazy(self, t1: 'treemanifest', t2: 'treemanifest'):
923 def _loaddifflazy(self, t1: 'treemanifest', t2: 'treemanifest'):
924 """load items in t1 and t2 if they're needed for diffing.
924 """load items in t1 and t2 if they're needed for diffing.
925
925
926 The criteria currently is:
926 The criteria currently is:
927 - if it's not present in _lazydirs in either t1 or t2, load it in the
927 - if it's not present in _lazydirs in either t1 or t2, load it in the
928 other (it may already be loaded or it may not exist, doesn't matter)
928 other (it may already be loaded or it may not exist, doesn't matter)
929 - if it's present in _lazydirs in both, compare the nodeid; if it
929 - if it's present in _lazydirs in both, compare the nodeid; if it
930 differs, load it in both
930 differs, load it in both
931 """
931 """
932 toloadlazy = []
932 toloadlazy = []
933 for d, v1 in t1._lazydirs.items():
933 for d, v1 in t1._lazydirs.items():
934 v2 = t2._lazydirs.get(d)
934 v2 = t2._lazydirs.get(d)
935 if v2 is None or v2[0] != v1[0]:
935 if v2 is None or v2[0] != v1[0]:
936 toloadlazy.append(d)
936 toloadlazy.append(d)
937 for d, v1 in t2._lazydirs.items():
937 for d, v1 in t2._lazydirs.items():
938 if d not in t1._lazydirs:
938 if d not in t1._lazydirs:
939 toloadlazy.append(d)
939 toloadlazy.append(d)
940
940
941 for d in toloadlazy:
941 for d in toloadlazy:
942 t1._loadlazy(d)
942 t1._loadlazy(d)
943 t2._loadlazy(d)
943 t2._loadlazy(d)
944
944
945 def __len__(self) -> int:
945 def __len__(self) -> int:
946 self._load()
946 self._load()
947 size = len(self._files)
947 size = len(self._files)
948 self._loadalllazy()
948 self._loadalllazy()
949 for m in self._dirs.values():
949 for m in self._dirs.values():
950 size += m.__len__()
950 size += m.__len__()
951 return size
951 return size
952
952
953 def __nonzero__(self) -> bool:
953 def __nonzero__(self) -> bool:
954 # Faster than "__len__() != 0" since it avoids loading sub-manifests
954 # Faster than "__len__() != 0" since it avoids loading sub-manifests
955 return not self._isempty()
955 return not self._isempty()
956
956
957 __bool__ = __nonzero__
957 __bool__ = __nonzero__
958
958
959 def _isempty(self) -> bool:
959 def _isempty(self) -> bool:
960 self._load() # for consistency; already loaded by all callers
960 self._load() # for consistency; already loaded by all callers
961 # See if we can skip loading everything.
961 # See if we can skip loading everything.
962 if self._files or (
962 if self._files or (
963 self._dirs and any(not m._isempty() for m in self._dirs.values())
963 self._dirs and any(not m._isempty() for m in self._dirs.values())
964 ):
964 ):
965 return False
965 return False
966 self._loadalllazy()
966 self._loadalllazy()
967 return not self._dirs or all(m._isempty() for m in self._dirs.values())
967 return not self._dirs or all(m._isempty() for m in self._dirs.values())
968
968
969 @encoding.strmethod
969 @encoding.strmethod
970 def __repr__(self) -> bytes:
970 def __repr__(self) -> bytes:
971 return (
971 return (
972 b'<treemanifest dir=%s, node=%s, loaded=%r, dirty=%r at 0x%x>'
972 b'<treemanifest dir=%s, node=%s, loaded=%r, dirty=%r at 0x%x>'
973 % (
973 % (
974 self._dir,
974 self._dir,
975 hex(self._node),
975 hex(self._node),
976 bool(self._loadfunc is _noop),
976 bool(self._loadfunc is _noop),
977 self._dirty,
977 self._dirty,
978 id(self),
978 id(self),
979 )
979 )
980 )
980 )
981
981
982 def dir(self) -> bytes:
982 def dir(self) -> bytes:
983 """The directory that this tree manifest represents, including a
983 """The directory that this tree manifest represents, including a
984 trailing '/'. Empty string for the repo root directory."""
984 trailing '/'. Empty string for the repo root directory."""
985 return self._dir
985 return self._dir
986
986
987 def node(self) -> bytes:
987 def node(self) -> bytes:
988 """This node of this instance. nullid for unsaved instances. Should
988 """This node of this instance. nullid for unsaved instances. Should
989 be updated when the instance is read or written from a revlog.
989 be updated when the instance is read or written from a revlog.
990 """
990 """
991 assert not self._dirty
991 assert not self._dirty
992 return self._node
992 return self._node
993
993
994 def setnode(self, node: bytes) -> None:
994 def setnode(self, node: bytes) -> None:
995 self._node = node
995 self._node = node
996 self._dirty = False
996 self._dirty = False
997
997
998 def iterentries(
998 def iterentries(
999 self,
999 self,
1000 ) -> Iterator[Tuple[bytes, Union[bytes, 'treemanifest'], bytes]]:
1000 ) -> Iterator[Tuple[bytes, Union[bytes, 'treemanifest'], bytes]]:
1001 self._load()
1001 self._load()
1002 self._loadalllazy()
1002 self._loadalllazy()
1003 for p, n in sorted(
1003 for p, n in sorted(
1004 itertools.chain(self._dirs.items(), self._files.items())
1004 itertools.chain(self._dirs.items(), self._files.items())
1005 ):
1005 ):
1006 if p in self._files:
1006 if p in self._files:
1007 yield self._subpath(p), n, self._flags.get(p, b'')
1007 yield self._subpath(p), n, self._flags.get(p, b'')
1008 else:
1008 else:
1009 for x in n.iterentries():
1009 for x in n.iterentries():
1010 yield x
1010 yield x
1011
1011
1012 def items(self) -> Iterator[Tuple[bytes, Union[bytes, 'treemanifest']]]:
1012 def items(self) -> Iterator[Tuple[bytes, Union[bytes, 'treemanifest']]]:
1013 self._load()
1013 self._load()
1014 self._loadalllazy()
1014 self._loadalllazy()
1015 for p, n in sorted(
1015 for p, n in sorted(
1016 itertools.chain(self._dirs.items(), self._files.items())
1016 itertools.chain(self._dirs.items(), self._files.items())
1017 ):
1017 ):
1018 if p in self._files:
1018 if p in self._files:
1019 yield self._subpath(p), n
1019 yield self._subpath(p), n
1020 else:
1020 else:
1021 for f, sn in n.items():
1021 for f, sn in n.items():
1022 yield f, sn
1022 yield f, sn
1023
1023
1024 iteritems = items
1024 iteritems = items
1025
1025
1026 def iterkeys(self) -> Iterator[bytes]:
1026 def iterkeys(self) -> Iterator[bytes]:
1027 self._load()
1027 self._load()
1028 self._loadalllazy()
1028 self._loadalllazy()
1029 for p in sorted(itertools.chain(self._dirs, self._files)):
1029 for p in sorted(itertools.chain(self._dirs, self._files)):
1030 if p in self._files:
1030 if p in self._files:
1031 yield self._subpath(p)
1031 yield self._subpath(p)
1032 else:
1032 else:
1033 for f in self._dirs[p]:
1033 for f in self._dirs[p]:
1034 yield f
1034 yield f
1035
1035
1036 def keys(self) -> List[bytes]:
1036 def keys(self) -> List[bytes]:
1037 return list(self.iterkeys())
1037 return list(self.iterkeys())
1038
1038
1039 def __iter__(self) -> Iterator[bytes]:
1039 def __iter__(self) -> Iterator[bytes]:
1040 return self.iterkeys()
1040 return self.iterkeys()
1041
1041
1042 def __contains__(self, f: bytes) -> bool:
1042 def __contains__(self, f: bytes) -> bool:
1043 if f is None:
1043 if f is None:
1044 return False
1044 return False
1045 self._load()
1045 self._load()
1046 dir, subpath = _splittopdir(f)
1046 dir, subpath = _splittopdir(f)
1047 if dir:
1047 if dir:
1048 self._loadlazy(dir)
1048 self._loadlazy(dir)
1049
1049
1050 if dir not in self._dirs:
1050 if dir not in self._dirs:
1051 return False
1051 return False
1052
1052
1053 return self._dirs[dir].__contains__(subpath)
1053 return self._dirs[dir].__contains__(subpath)
1054 else:
1054 else:
1055 return f in self._files
1055 return f in self._files
1056
1056
1057 def get(self, f: bytes, default: Optional[bytes] = None) -> Optional[bytes]:
1057 def get(self, f: bytes, default: Optional[bytes] = None) -> Optional[bytes]:
1058 self._load()
1058 self._load()
1059 dir, subpath = _splittopdir(f)
1059 dir, subpath = _splittopdir(f)
1060 if dir:
1060 if dir:
1061 self._loadlazy(dir)
1061 self._loadlazy(dir)
1062
1062
1063 if dir not in self._dirs:
1063 if dir not in self._dirs:
1064 return default
1064 return default
1065 return self._dirs[dir].get(subpath, default)
1065 return self._dirs[dir].get(subpath, default)
1066 else:
1066 else:
1067 return self._files.get(f, default)
1067 return self._files.get(f, default)
1068
1068
1069 def __getitem__(self, f: bytes) -> bytes:
1069 def __getitem__(self, f: bytes) -> bytes:
1070 self._load()
1070 self._load()
1071 dir, subpath = _splittopdir(f)
1071 dir, subpath = _splittopdir(f)
1072 if dir:
1072 if dir:
1073 self._loadlazy(dir)
1073 self._loadlazy(dir)
1074
1074
1075 return self._dirs[dir].__getitem__(subpath)
1075 return self._dirs[dir].__getitem__(subpath)
1076 else:
1076 else:
1077 return self._files[f]
1077 return self._files[f]
1078
1078
1079 def flags(self, f: bytes) -> bytes:
1079 def flags(self, f: bytes) -> bytes:
1080 self._load()
1080 self._load()
1081 dir, subpath = _splittopdir(f)
1081 dir, subpath = _splittopdir(f)
1082 if dir:
1082 if dir:
1083 self._loadlazy(dir)
1083 self._loadlazy(dir)
1084
1084
1085 if dir not in self._dirs:
1085 if dir not in self._dirs:
1086 return b''
1086 return b''
1087 return self._dirs[dir].flags(subpath)
1087 return self._dirs[dir].flags(subpath)
1088 else:
1088 else:
1089 if f in self._lazydirs or f in self._dirs:
1089 if f in self._lazydirs or f in self._dirs:
1090 return b''
1090 return b''
1091 return self._flags.get(f, b'')
1091 return self._flags.get(f, b'')
1092
1092
1093 def find(self, f: bytes) -> Tuple[bytes, bytes]:
1093 def find(self, f: bytes) -> Tuple[bytes, bytes]:
1094 self._load()
1094 self._load()
1095 dir, subpath = _splittopdir(f)
1095 dir, subpath = _splittopdir(f)
1096 if dir:
1096 if dir:
1097 self._loadlazy(dir)
1097 self._loadlazy(dir)
1098
1098
1099 return self._dirs[dir].find(subpath)
1099 return self._dirs[dir].find(subpath)
1100 else:
1100 else:
1101 return self._files[f], self._flags.get(f, b'')
1101 return self._files[f], self._flags.get(f, b'')
1102
1102
1103 def __delitem__(self, f: bytes) -> None:
1103 def __delitem__(self, f: bytes) -> None:
1104 self._load()
1104 self._load()
1105 dir, subpath = _splittopdir(f)
1105 dir, subpath = _splittopdir(f)
1106 if dir:
1106 if dir:
1107 self._loadlazy(dir)
1107 self._loadlazy(dir)
1108
1108
1109 self._dirs[dir].__delitem__(subpath)
1109 self._dirs[dir].__delitem__(subpath)
1110 # If the directory is now empty, remove it
1110 # If the directory is now empty, remove it
1111 if self._dirs[dir]._isempty():
1111 if self._dirs[dir]._isempty():
1112 del self._dirs[dir]
1112 del self._dirs[dir]
1113 else:
1113 else:
1114 del self._files[f]
1114 del self._files[f]
1115 if f in self._flags:
1115 if f in self._flags:
1116 del self._flags[f]
1116 del self._flags[f]
1117 self._dirty = True
1117 self._dirty = True
1118
1118
1119 def set(self, f: bytes, node: bytes, flags: bytes) -> None:
1119 def set(self, f: bytes, node: bytes, flags: bytes) -> None:
1120 """Set both the node and the flags for path f."""
1120 """Set both the node and the flags for path f."""
1121 assert node is not None
1121 assert node is not None
1122 if flags not in _manifestflags:
1122 if flags not in _manifestflags:
1123 raise TypeError(b"Invalid manifest flag set.")
1123 raise TypeError(b"Invalid manifest flag set.")
1124 self._load()
1124 self._load()
1125 dir, subpath = _splittopdir(f)
1125 dir, subpath = _splittopdir(f)
1126 if dir:
1126 if dir:
1127 self._loadlazy(dir)
1127 self._loadlazy(dir)
1128 if dir not in self._dirs:
1128 if dir not in self._dirs:
1129 self._dirs[dir] = treemanifest(
1129 self._dirs[dir] = treemanifest(
1130 self.nodeconstants, self._subpath(dir)
1130 self.nodeconstants, self._subpath(dir)
1131 )
1131 )
1132 self._dirs[dir].set(subpath, node, flags)
1132 self._dirs[dir].set(subpath, node, flags)
1133 else:
1133 else:
1134 assert len(node) in (20, 32)
1134 assert len(node) in (20, 32)
1135 self._files[f] = node
1135 self._files[f] = node
1136 self._flags[f] = flags
1136 self._flags[f] = flags
1137 self._dirty = True
1137 self._dirty = True
1138
1138
1139 def __setitem__(self, f: bytes, n: bytes) -> None:
1139 def __setitem__(self, f: bytes, n: bytes) -> None:
1140 assert n is not None
1140 assert n is not None
1141 self._load()
1141 self._load()
1142 dir, subpath = _splittopdir(f)
1142 dir, subpath = _splittopdir(f)
1143 if dir:
1143 if dir:
1144 self._loadlazy(dir)
1144 self._loadlazy(dir)
1145 if dir not in self._dirs:
1145 if dir not in self._dirs:
1146 self._dirs[dir] = treemanifest(
1146 self._dirs[dir] = treemanifest(
1147 self.nodeconstants, self._subpath(dir)
1147 self.nodeconstants, self._subpath(dir)
1148 )
1148 )
1149 self._dirs[dir].__setitem__(subpath, n)
1149 self._dirs[dir].__setitem__(subpath, n)
1150 else:
1150 else:
1151 # manifest nodes are either 20 bytes or 32 bytes,
1151 # manifest nodes are either 20 bytes or 32 bytes,
1152 # depending on the hash in use. Assert this as historically
1152 # depending on the hash in use. Assert this as historically
1153 # sometimes extra bytes were added.
1153 # sometimes extra bytes were added.
1154 assert len(n) in (20, 32)
1154 assert len(n) in (20, 32)
1155 self._files[f] = n
1155 self._files[f] = n
1156 self._dirty = True
1156 self._dirty = True
1157
1157
1158 def _load(self) -> None:
1158 def _load(self) -> None:
1159 if self._loadfunc is not _noop:
1159 if self._loadfunc is not _noop:
1160 lf, self._loadfunc = self._loadfunc, _noop
1160 lf, self._loadfunc = self._loadfunc, _noop
1161 lf(self)
1161 lf(self)
1162 elif self._copyfunc is not _noop:
1162 elif self._copyfunc is not _noop:
1163 cf, self._copyfunc = self._copyfunc, _noop
1163 cf, self._copyfunc = self._copyfunc, _noop
1164 cf(self)
1164 cf(self)
1165
1165
1166 def setflag(self, f: bytes, flags: bytes) -> None:
1166 def setflag(self, f: bytes, flags: bytes) -> None:
1167 """Set the flags (symlink, executable) for path f."""
1167 """Set the flags (symlink, executable) for path f."""
1168 if flags not in _manifestflags:
1168 if flags not in _manifestflags:
1169 raise TypeError(b"Invalid manifest flag set.")
1169 raise TypeError(b"Invalid manifest flag set.")
1170 self._load()
1170 self._load()
1171 dir, subpath = _splittopdir(f)
1171 dir, subpath = _splittopdir(f)
1172 if dir:
1172 if dir:
1173 self._loadlazy(dir)
1173 self._loadlazy(dir)
1174 if dir not in self._dirs:
1174 if dir not in self._dirs:
1175 self._dirs[dir] = treemanifest(
1175 self._dirs[dir] = treemanifest(
1176 self.nodeconstants, self._subpath(dir)
1176 self.nodeconstants, self._subpath(dir)
1177 )
1177 )
1178 self._dirs[dir].setflag(subpath, flags)
1178 self._dirs[dir].setflag(subpath, flags)
1179 else:
1179 else:
1180 self._flags[f] = flags
1180 self._flags[f] = flags
1181 self._dirty = True
1181 self._dirty = True
1182
1182
1183 def copy(self) -> 'treemanifest':
1183 def copy(self) -> 'treemanifest':
1184 copy = treemanifest(self.nodeconstants, self._dir)
1184 copy = treemanifest(self.nodeconstants, self._dir)
1185 copy._node = self._node
1185 copy._node = self._node
1186 copy._dirty = self._dirty
1186 copy._dirty = self._dirty
1187 if self._copyfunc is _noop:
1187 if self._copyfunc is _noop:
1188
1188
1189 def _copyfunc(s):
1189 def _copyfunc(s):
1190 self._load()
1190 self._load()
1191 s._lazydirs = {
1191 s._lazydirs = {
1192 d: (n, r, True) for d, (n, r, c) in self._lazydirs.items()
1192 d: (n, r, True) for d, (n, r, c) in self._lazydirs.items()
1193 }
1193 }
1194 sdirs = s._dirs
1194 sdirs = s._dirs
1195 for d, v in self._dirs.items():
1195 for d, v in self._dirs.items():
1196 sdirs[d] = v.copy()
1196 sdirs[d] = v.copy()
1197 s._files = dict.copy(self._files)
1197 s._files = dict.copy(self._files)
1198 s._flags = dict.copy(self._flags)
1198 s._flags = dict.copy(self._flags)
1199
1199
1200 if self._loadfunc is _noop:
1200 if self._loadfunc is _noop:
1201 _copyfunc(copy)
1201 _copyfunc(copy)
1202 else:
1202 else:
1203 copy._copyfunc = _copyfunc
1203 copy._copyfunc = _copyfunc
1204 else:
1204 else:
1205 copy._copyfunc = self._copyfunc
1205 copy._copyfunc = self._copyfunc
1206 return copy
1206 return copy
1207
1207
1208 def filesnotin(
1208 def filesnotin(
1209 self, m2: 'treemanifest', match: Optional[matchmod.basematcher] = None
1209 self, m2: 'treemanifest', match: Optional[matchmod.basematcher] = None
1210 ) -> Set[bytes]:
1210 ) -> Set[bytes]:
1211 '''Set of files in this manifest that are not in the other'''
1211 '''Set of files in this manifest that are not in the other'''
1212 if match and not match.always():
1212 if match and not match.always():
1213 m1 = self._matches(match)
1213 m1 = self._matches(match)
1214 m2 = m2._matches(match)
1214 m2 = m2._matches(match)
1215 return m1.filesnotin(m2)
1215 return m1.filesnotin(m2)
1216
1216
1217 files = set()
1217 files = set()
1218
1218
1219 def _filesnotin(t1, t2):
1219 def _filesnotin(t1, t2):
1220 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1220 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1221 return
1221 return
1222 t1._load()
1222 t1._load()
1223 t2._load()
1223 t2._load()
1224 self._loaddifflazy(t1, t2)
1224 self._loaddifflazy(t1, t2)
1225 for d, m1 in t1._dirs.items():
1225 for d, m1 in t1._dirs.items():
1226 if d in t2._dirs:
1226 if d in t2._dirs:
1227 m2 = t2._dirs[d]
1227 m2 = t2._dirs[d]
1228 _filesnotin(m1, m2)
1228 _filesnotin(m1, m2)
1229 else:
1229 else:
1230 files.update(m1.iterkeys())
1230 files.update(m1.iterkeys())
1231
1231
1232 for fn in t1._files:
1232 for fn in t1._files:
1233 if fn not in t2._files:
1233 if fn not in t2._files:
1234 files.add(t1._subpath(fn))
1234 files.add(t1._subpath(fn))
1235
1235
1236 _filesnotin(self, m2)
1236 _filesnotin(self, m2)
1237 return files
1237 return files
1238
1238
1239 @propertycache
1239 @propertycache
1240 def _alldirs(self) -> pathutil.dirs:
1240 def _alldirs(self) -> pathutil.dirs:
1241 return pathutil.dirs(self)
1241 return pathutil.dirs(self)
1242
1242
1243 def dirs(self) -> pathutil.dirs:
1243 def dirs(self) -> pathutil.dirs:
1244 return self._alldirs
1244 return self._alldirs
1245
1245
1246 def hasdir(self, dir: bytes) -> bool:
1246 def hasdir(self, dir: bytes) -> bool:
1247 self._load()
1247 self._load()
1248 topdir, subdir = _splittopdir(dir)
1248 topdir, subdir = _splittopdir(dir)
1249 if topdir:
1249 if topdir:
1250 self._loadlazy(topdir)
1250 self._loadlazy(topdir)
1251 if topdir in self._dirs:
1251 if topdir in self._dirs:
1252 return self._dirs[topdir].hasdir(subdir)
1252 return self._dirs[topdir].hasdir(subdir)
1253 return False
1253 return False
1254 dirslash = dir + b'/'
1254 dirslash = dir + b'/'
1255 return dirslash in self._dirs or dirslash in self._lazydirs
1255 return dirslash in self._dirs or dirslash in self._lazydirs
1256
1256
1257 def walk(self, match: matchmod.basematcher) -> Iterator[bytes]:
1257 def walk(self, match: matchmod.basematcher) -> Iterator[bytes]:
1258 """Generates matching file names.
1258 """Generates matching file names.
1259
1259
1260 It also reports nonexistent files by marking them bad with match.bad().
1260 It also reports nonexistent files by marking them bad with match.bad().
1261 """
1261 """
1262 if match.always():
1262 if match.always():
1263 for f in iter(self):
1263 for f in iter(self):
1264 yield f
1264 yield f
1265 return
1265 return
1266
1266
1267 fset = set(match.files())
1267 fset = set(match.files())
1268
1268
1269 for fn in self._walk(match):
1269 for fn in self._walk(match):
1270 if fn in fset:
1270 if fn in fset:
1271 # specified pattern is the exact name
1271 # specified pattern is the exact name
1272 fset.remove(fn)
1272 fset.remove(fn)
1273 yield fn
1273 yield fn
1274
1274
1275 # for dirstate.walk, files=[''] means "walk the whole tree".
1275 # for dirstate.walk, files=[''] means "walk the whole tree".
1276 # follow that here, too
1276 # follow that here, too
1277 fset.discard(b'')
1277 fset.discard(b'')
1278
1278
1279 for fn in sorted(fset):
1279 for fn in sorted(fset):
1280 if not self.hasdir(fn):
1280 if not self.hasdir(fn):
1281 match.bad(fn, None)
1281 match.bad(fn, None)
1282
1282
1283 def _walk(self, match: matchmod.basematcher) -> Iterator[bytes]:
1283 def _walk(self, match: matchmod.basematcher) -> Iterator[bytes]:
1284 '''Recursively generates matching file names for walk().'''
1284 '''Recursively generates matching file names for walk().'''
1285 visit = match.visitchildrenset(self._dir[:-1])
1285 visit = match.visitchildrenset(self._dir[:-1])
1286 if not visit:
1286 if not visit:
1287 return
1287 return
1288
1288
1289 # yield this dir's files and walk its submanifests
1289 # yield this dir's files and walk its submanifests
1290 self._load()
1290 self._load()
1291 visit = self._loadchildrensetlazy(visit)
1291 visit = self._loadchildrensetlazy(visit)
1292 for p in sorted(list(self._dirs) + list(self._files)):
1292 for p in sorted(list(self._dirs) + list(self._files)):
1293 if p in self._files:
1293 if p in self._files:
1294 fullp = self._subpath(p)
1294 fullp = self._subpath(p)
1295 if match(fullp):
1295 if match(fullp):
1296 yield fullp
1296 yield fullp
1297 else:
1297 else:
1298 if not visit or p[:-1] in visit:
1298 if not visit or p[:-1] in visit:
1299 for f in self._dirs[p]._walk(match):
1299 for f in self._dirs[p]._walk(match):
1300 yield f
1300 yield f
1301
1301
1302 def _matches(self, match: matchmod.basematcher) -> 'treemanifest':
1302 def _matches(self, match: matchmod.basematcher) -> 'treemanifest':
1303 """recursively generate a new manifest filtered by the match argument."""
1303 """recursively generate a new manifest filtered by the match argument."""
1304 if match.always():
1304 if match.always():
1305 return self.copy()
1305 return self.copy()
1306 return self._matches_inner(match)
1306 return self._matches_inner(match)
1307
1307
1308 def _matches_inner(self, match: matchmod.basematcher) -> 'treemanifest':
1308 def _matches_inner(self, match: matchmod.basematcher) -> 'treemanifest':
1309 if match.always():
1309 if match.always():
1310 return self.copy()
1310 return self.copy()
1311
1311
1312 visit = match.visitchildrenset(self._dir[:-1])
1312 visit = match.visitchildrenset(self._dir[:-1])
1313 if visit == b'all':
1313 if visit == b'all':
1314 return self.copy()
1314 return self.copy()
1315 ret = treemanifest(self.nodeconstants, self._dir)
1315 ret = treemanifest(self.nodeconstants, self._dir)
1316 if not visit:
1316 if not visit:
1317 return ret
1317 return ret
1318
1318
1319 self._load()
1319 self._load()
1320 for fn in self._files:
1320 for fn in self._files:
1321 # While visitchildrenset *usually* lists only subdirs, this is
1321 # While visitchildrenset *usually* lists only subdirs, this is
1322 # actually up to the matcher and may have some files in the set().
1322 # actually up to the matcher and may have some files in the set().
1323 # If visit == 'this', we should obviously look at the files in this
1323 # If visit == 'this', we should obviously look at the files in this
1324 # directory; if visit is a set, and fn is in it, we should inspect
1324 # directory; if visit is a set, and fn is in it, we should inspect
1325 # fn (but no need to inspect things not in the set).
1325 # fn (but no need to inspect things not in the set).
1326 if visit != b'this' and fn not in visit:
1326 if visit != b'this' and fn not in visit:
1327 continue
1327 continue
1328 fullp = self._subpath(fn)
1328 fullp = self._subpath(fn)
1329 # visitchildrenset isn't perfect, we still need to call the regular
1329 # visitchildrenset isn't perfect, we still need to call the regular
1330 # matcher code to further filter results.
1330 # matcher code to further filter results.
1331 if not match(fullp):
1331 if not match(fullp):
1332 continue
1332 continue
1333 ret._files[fn] = self._files[fn]
1333 ret._files[fn] = self._files[fn]
1334 if fn in self._flags:
1334 if fn in self._flags:
1335 ret._flags[fn] = self._flags[fn]
1335 ret._flags[fn] = self._flags[fn]
1336
1336
1337 visit = self._loadchildrensetlazy(visit)
1337 visit = self._loadchildrensetlazy(visit)
1338 for dir, subm in self._dirs.items():
1338 for dir, subm in self._dirs.items():
1339 if visit and dir[:-1] not in visit:
1339 if visit and dir[:-1] not in visit:
1340 continue
1340 continue
1341 m = subm._matches_inner(match)
1341 m = subm._matches_inner(match)
1342 if not m._isempty():
1342 if not m._isempty():
1343 ret._dirs[dir] = m
1343 ret._dirs[dir] = m
1344
1344
1345 if not ret._isempty():
1345 if not ret._isempty():
1346 ret._dirty = True
1346 ret._dirty = True
1347 return ret
1347 return ret
1348
1348
1349 def fastdelta(
1349 def fastdelta(
1350 self, base: ByteString, changes: Iterable[Tuple[bytes, bool]]
1350 self, base: ByteString, changes: Iterable[Tuple[bytes, bool]]
1351 ) -> ByteString:
1351 ) -> ByteString:
1352 raise FastdeltaUnavailable()
1352 raise FastdeltaUnavailable()
1353
1353
1354 def diff(
1354 def diff(
1355 self,
1355 self,
1356 m2: 'treemanifest',
1356 m2: 'treemanifest',
1357 match: Optional[matchmod.basematcher] = None,
1357 match: Optional[matchmod.basematcher] = None,
1358 clean: bool = False,
1358 clean: bool = False,
1359 ) -> Dict[
1359 ) -> Dict[
1360 bytes,
1360 bytes,
1361 Optional[
1361 Optional[
1362 Tuple[Tuple[Optional[bytes], bytes], Tuple[Optional[bytes], bytes]]
1362 Tuple[Tuple[Optional[bytes], bytes], Tuple[Optional[bytes], bytes]]
1363 ],
1363 ],
1364 ]:
1364 ]:
1365 """Finds changes between the current manifest and m2.
1365 """Finds changes between the current manifest and m2.
1366
1366
1367 Args:
1367 Args:
1368 m2: the manifest to which this manifest should be compared.
1368 m2: the manifest to which this manifest should be compared.
1369 clean: if true, include files unchanged between these manifests
1369 clean: if true, include files unchanged between these manifests
1370 with a None value in the returned dictionary.
1370 with a None value in the returned dictionary.
1371
1371
1372 The result is returned as a dict with filename as key and
1372 The result is returned as a dict with filename as key and
1373 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1373 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1374 nodeid in the current/other manifest and fl1/fl2 is the flag
1374 nodeid in the current/other manifest and fl1/fl2 is the flag
1375 in the current/other manifest. Where the file does not exist,
1375 in the current/other manifest. Where the file does not exist,
1376 the nodeid will be None and the flags will be the empty
1376 the nodeid will be None and the flags will be the empty
1377 string.
1377 string.
1378 """
1378 """
1379 if match and not match.always():
1379 if match and not match.always():
1380 m1 = self._matches(match)
1380 m1 = self._matches(match)
1381 m2 = m2._matches(match)
1381 m2 = m2._matches(match)
1382 return m1.diff(m2, clean=clean)
1382 return m1.diff(m2, clean=clean)
1383 result = {}
1383 result = {}
1384 emptytree = treemanifest(self.nodeconstants)
1384 emptytree = treemanifest(self.nodeconstants)
1385
1385
1386 def _iterativediff(t1, t2, stack):
1386 def _iterativediff(t1, t2, stack):
1387 """compares two tree manifests and append new tree-manifests which
1387 """compares two tree manifests and append new tree-manifests which
1388 needs to be compared to stack"""
1388 needs to be compared to stack"""
1389 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1389 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1390 return
1390 return
1391 t1._load()
1391 t1._load()
1392 t2._load()
1392 t2._load()
1393 self._loaddifflazy(t1, t2)
1393 self._loaddifflazy(t1, t2)
1394
1394
1395 for d, m1 in t1._dirs.items():
1395 for d, m1 in t1._dirs.items():
1396 m2 = t2._dirs.get(d, emptytree)
1396 m2 = t2._dirs.get(d, emptytree)
1397 stack.append((m1, m2))
1397 stack.append((m1, m2))
1398
1398
1399 for d, m2 in t2._dirs.items():
1399 for d, m2 in t2._dirs.items():
1400 if d not in t1._dirs:
1400 if d not in t1._dirs:
1401 stack.append((emptytree, m2))
1401 stack.append((emptytree, m2))
1402
1402
1403 for fn, n1 in t1._files.items():
1403 for fn, n1 in t1._files.items():
1404 fl1 = t1._flags.get(fn, b'')
1404 fl1 = t1._flags.get(fn, b'')
1405 n2 = t2._files.get(fn, None)
1405 n2 = t2._files.get(fn, None)
1406 fl2 = t2._flags.get(fn, b'')
1406 fl2 = t2._flags.get(fn, b'')
1407 if n1 != n2 or fl1 != fl2:
1407 if n1 != n2 or fl1 != fl2:
1408 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1408 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1409 elif clean:
1409 elif clean:
1410 result[t1._subpath(fn)] = None
1410 result[t1._subpath(fn)] = None
1411
1411
1412 for fn, n2 in t2._files.items():
1412 for fn, n2 in t2._files.items():
1413 if fn not in t1._files:
1413 if fn not in t1._files:
1414 fl2 = t2._flags.get(fn, b'')
1414 fl2 = t2._flags.get(fn, b'')
1415 result[t2._subpath(fn)] = ((None, b''), (n2, fl2))
1415 result[t2._subpath(fn)] = ((None, b''), (n2, fl2))
1416
1416
1417 stackls = []
1417 stackls = []
1418 _iterativediff(self, m2, stackls)
1418 _iterativediff(self, m2, stackls)
1419 while stackls:
1419 while stackls:
1420 t1, t2 = stackls.pop()
1420 t1, t2 = stackls.pop()
1421 # stackls is populated in the function call
1421 # stackls is populated in the function call
1422 _iterativediff(t1, t2, stackls)
1422 _iterativediff(t1, t2, stackls)
1423 return result
1423 return result
1424
1424
1425 def unmodifiedsince(self, m2: 'treemanifest') -> bool:
1425 def unmodifiedsince(self, m2: 'treemanifest') -> bool:
1426 return not self._dirty and not m2._dirty and self._node == m2._node
1426 return not self._dirty and not m2._dirty and self._node == m2._node
1427
1427
1428 def parse(
1428 def parse(
1429 self,
1429 self,
1430 text: bytes,
1430 text: bytes,
1431 readsubtree: Callable[[bytes, bytes], 'treemanifest'],
1431 readsubtree: Callable[[bytes, bytes], 'treemanifest'],
1432 ) -> None:
1432 ) -> None:
1433 selflazy = self._lazydirs
1433 selflazy = self._lazydirs
1434 for f, n, fl in _parse(self._nodelen, text):
1434 for f, n, fl in _parse(self._nodelen, text):
1435 if fl == b't':
1435 if fl == b't':
1436 f = f + b'/'
1436 f = f + b'/'
1437 # False below means "doesn't need to be copied" and can use the
1437 # False below means "doesn't need to be copied" and can use the
1438 # cached value from readsubtree directly.
1438 # cached value from readsubtree directly.
1439 selflazy[f] = (n, readsubtree, False)
1439 selflazy[f] = (n, readsubtree, False)
1440 elif b'/' in f:
1440 elif b'/' in f:
1441 # This is a flat manifest, so use __setitem__ and setflag rather
1441 # This is a flat manifest, so use __setitem__ and setflag rather
1442 # than assigning directly to _files and _flags, so we can
1442 # than assigning directly to _files and _flags, so we can
1443 # assign a path in a subdirectory, and to mark dirty (compared
1443 # assign a path in a subdirectory, and to mark dirty (compared
1444 # to nullid).
1444 # to nullid).
1445 self[f] = n
1445 self[f] = n
1446 if fl:
1446 if fl:
1447 self.setflag(f, fl)
1447 self.setflag(f, fl)
1448 else:
1448 else:
1449 # Assigning to _files and _flags avoids marking as dirty,
1449 # Assigning to _files and _flags avoids marking as dirty,
1450 # and should be a little faster.
1450 # and should be a little faster.
1451 self._files[f] = n
1451 self._files[f] = n
1452 if fl:
1452 if fl:
1453 self._flags[f] = fl
1453 self._flags[f] = fl
1454
1454
1455 def text(self) -> ByteString:
1455 def text(self) -> ByteString:
1456 """Get the full data of this manifest as a bytestring."""
1456 """Get the full data of this manifest as a bytestring."""
1457 self._load()
1457 self._load()
1458 return _text(self.iterentries())
1458 return _text(self.iterentries())
1459
1459
1460 def dirtext(self) -> ByteString:
1460 def dirtext(self) -> ByteString:
1461 """Get the full data of this directory as a bytestring. Make sure that
1461 """Get the full data of this directory as a bytestring. Make sure that
1462 any submanifests have been written first, so their nodeids are correct.
1462 any submanifests have been written first, so their nodeids are correct.
1463 """
1463 """
1464 self._load()
1464 self._load()
1465 flags = self.flags
1465 flags = self.flags
1466 lazydirs = [(d[:-1], v[0], b't') for d, v in self._lazydirs.items()]
1466 lazydirs = [(d[:-1], v[0], b't') for d, v in self._lazydirs.items()]
1467 dirs = [(d[:-1], self._dirs[d]._node, b't') for d in self._dirs]
1467 dirs = [(d[:-1], self._dirs[d]._node, b't') for d in self._dirs]
1468 files = [(f, self._files[f], flags(f)) for f in self._files]
1468 files = [(f, self._files[f], flags(f)) for f in self._files]
1469 return _text(sorted(dirs + files + lazydirs))
1469 return _text(sorted(dirs + files + lazydirs))
1470
1470
1471 def read(
1471 def read(
1472 self,
1472 self,
1473 gettext: Callable[[], ByteString],
1473 gettext: Callable[[], ByteString],
1474 readsubtree: Callable[[bytes, bytes], 'treemanifest'],
1474 readsubtree: Callable[[bytes, bytes], 'treemanifest'],
1475 ) -> None:
1475 ) -> None:
1476 def _load_for_read(s):
1476 def _load_for_read(s):
1477 s.parse(gettext(), readsubtree)
1477 s.parse(gettext(), readsubtree)
1478 s._dirty = False
1478 s._dirty = False
1479
1479
1480 self._loadfunc = _load_for_read
1480 self._loadfunc = _load_for_read
1481
1481
1482 def writesubtrees(
1482 def writesubtrees(
1483 self,
1483 self,
1484 m1: 'treemanifest',
1484 m1: 'treemanifest',
1485 m2: 'treemanifest',
1485 m2: 'treemanifest',
1486 writesubtree: Callable[
1486 writesubtree: Callable[
1487 [
1487 [
1488 Callable[['treemanifest'], None],
1488 Callable[['treemanifest'], None],
1489 bytes,
1489 bytes,
1490 bytes,
1490 bytes,
1491 matchmod.basematcher,
1491 matchmod.basematcher,
1492 ],
1492 ],
1493 None,
1493 None,
1494 ],
1494 ],
1495 match: matchmod.basematcher,
1495 match: matchmod.basematcher,
1496 ) -> None:
1496 ) -> None:
1497 self._load() # for consistency; should never have any effect here
1497 self._load() # for consistency; should never have any effect here
1498 m1._load()
1498 m1._load()
1499 m2._load()
1499 m2._load()
1500 emptytree = treemanifest(self.nodeconstants)
1500 emptytree = treemanifest(self.nodeconstants)
1501
1501
1502 def getnode(m, d):
1502 def getnode(m, d):
1503 ld = m._lazydirs.get(d)
1503 ld = m._lazydirs.get(d)
1504 if ld:
1504 if ld:
1505 return ld[0]
1505 return ld[0]
1506 tree = m._dirs.get(d, emptytree)
1506 tree = m._dirs.get(d, emptytree)
1507 assert tree is not None # helps pytype
1507 assert tree is not None # helps pytype
1508 return tree._node
1508 return tree._node
1509
1509
1510 # let's skip investigating things that `match` says we do not need.
1510 # let's skip investigating things that `match` says we do not need.
1511 visit = match.visitchildrenset(self._dir[:-1])
1511 visit = match.visitchildrenset(self._dir[:-1])
1512 visit = self._loadchildrensetlazy(visit)
1512 visit = self._loadchildrensetlazy(visit)
1513 if visit == b'this' or visit == b'all':
1513 if visit == b'this' or visit == b'all':
1514 visit = None
1514 visit = None
1515 for d, subm in self._dirs.items():
1515 for d, subm in self._dirs.items():
1516 if visit and d[:-1] not in visit:
1516 if visit and d[:-1] not in visit:
1517 continue
1517 continue
1518 subp1 = getnode(m1, d)
1518 subp1 = getnode(m1, d)
1519 subp2 = getnode(m2, d)
1519 subp2 = getnode(m2, d)
1520 if subp1 == self.nodeconstants.nullid:
1520 if subp1 == self.nodeconstants.nullid:
1521 subp1, subp2 = subp2, subp1
1521 subp1, subp2 = subp2, subp1
1522 writesubtree(subm, subp1, subp2, match)
1522 writesubtree(subm, subp1, subp2, match)
1523
1523
1524 def walksubtrees(
1524 def walksubtrees(
1525 self, matcher: Optional[matchmod.basematcher] = None
1525 self, matcher: Optional[matchmod.basematcher] = None
1526 ) -> Iterator['treemanifest']:
1526 ) -> Iterator['treemanifest']:
1527 """Returns an iterator of the subtrees of this manifest, including this
1527 """Returns an iterator of the subtrees of this manifest, including this
1528 manifest itself.
1528 manifest itself.
1529
1529
1530 If `matcher` is provided, it only returns subtrees that match.
1530 If `matcher` is provided, it only returns subtrees that match.
1531 """
1531 """
1532 if matcher and not matcher.visitdir(self._dir[:-1]):
1532 if matcher and not matcher.visitdir(self._dir[:-1]):
1533 return
1533 return
1534 if not matcher or matcher(self._dir[:-1]):
1534 if not matcher or matcher(self._dir[:-1]):
1535 yield self
1535 yield self
1536
1536
1537 self._load()
1537 self._load()
1538 # OPT: use visitchildrenset to avoid loading everything.
1538 # OPT: use visitchildrenset to avoid loading everything.
1539 self._loadalllazy()
1539 self._loadalllazy()
1540 for d, subm in self._dirs.items():
1540 for d, subm in self._dirs.items():
1541 for subtree in subm.walksubtrees(matcher=matcher):
1541 for subtree in subm.walksubtrees(matcher=matcher):
1542 yield subtree
1542 yield subtree
1543
1543
1544
1544
1545 class manifestfulltextcache(util.lrucachedict):
1545 class manifestfulltextcache(util.lrucachedict):
1546 """File-backed LRU cache for the manifest cache
1546 """File-backed LRU cache for the manifest cache
1547
1547
1548 File consists of entries, up to EOF:
1548 File consists of entries, up to EOF:
1549
1549
1550 - 20 bytes node, 4 bytes length, <length> manifest data
1550 - 20 bytes node, 4 bytes length, <length> manifest data
1551
1551
1552 These are written in reverse cache order (oldest to newest).
1552 These are written in reverse cache order (oldest to newest).
1553
1553
1554 """
1554 """
1555
1555
1556 _file = b'manifestfulltextcache'
1556 _file = b'manifestfulltextcache'
1557
1557
1558 def __init__(self, max):
1558 def __init__(self, max):
1559 super(manifestfulltextcache, self).__init__(max)
1559 super(manifestfulltextcache, self).__init__(max)
1560 self._dirty = False
1560 self._dirty = False
1561 self._read = False
1561 self._read = False
1562 self._opener = None
1562 self._opener = None
1563
1563
1564 def read(self):
1564 def read(self):
1565 if self._read or self._opener is None:
1565 if self._read or self._opener is None:
1566 return
1566 return
1567
1567
1568 try:
1568 try:
1569 with self._opener(self._file) as fp:
1569 with self._opener(self._file) as fp:
1570 set = super(manifestfulltextcache, self).__setitem__
1570 set = super(manifestfulltextcache, self).__setitem__
1571 # ignore trailing data, this is a cache, corruption is skipped
1571 # ignore trailing data, this is a cache, corruption is skipped
1572 while True:
1572 while True:
1573 # TODO do we need to do work here for sha1 portability?
1573 # TODO do we need to do work here for sha1 portability?
1574 node = fp.read(20)
1574 node = fp.read(20)
1575 if len(node) < 20:
1575 if len(node) < 20:
1576 break
1576 break
1577 try:
1577 try:
1578 size = struct.unpack(b'>L', fp.read(4))[0]
1578 size = struct.unpack(b'>L', fp.read(4))[0]
1579 except struct.error:
1579 except struct.error:
1580 break
1580 break
1581 value = bytearray(fp.read(size))
1581 value = bytearray(fp.read(size))
1582 if len(value) != size:
1582 if len(value) != size:
1583 break
1583 break
1584 set(node, value)
1584 set(node, value)
1585 except IOError:
1585 except IOError:
1586 # the file is allowed to be missing
1586 # the file is allowed to be missing
1587 pass
1587 pass
1588
1588
1589 self._read = True
1589 self._read = True
1590 self._dirty = False
1590 self._dirty = False
1591
1591
1592 def write(self):
1592 def write(self):
1593 if not self._dirty or self._opener is None:
1593 if not self._dirty or self._opener is None:
1594 return
1594 return
1595 # rotate backwards to the first used node
1595 # rotate backwards to the first used node
1596 try:
1596 try:
1597 with self._opener(
1597 with self._opener(
1598 self._file, b'w', atomictemp=True, checkambig=True
1598 self._file, b'w', atomictemp=True, checkambig=True
1599 ) as fp:
1599 ) as fp:
1600 node = self._head.prev
1600 node = self._head.prev
1601 while True:
1601 while True:
1602 if node.key in self._cache:
1602 if node.key in self._cache:
1603 fp.write(node.key)
1603 fp.write(node.key)
1604 fp.write(struct.pack(b'>L', len(node.value)))
1604 fp.write(struct.pack(b'>L', len(node.value)))
1605 fp.write(node.value)
1605 fp.write(node.value)
1606 if node is self._head:
1606 if node is self._head:
1607 break
1607 break
1608 node = node.prev
1608 node = node.prev
1609 except IOError:
1609 except IOError:
1610 # We could not write the cache (eg: permission error)
1610 # We could not write the cache (eg: permission error)
1611 # the content can be missing.
1611 # the content can be missing.
1612 #
1612 #
1613 # We could try harder and see if we could recreate a wcache
1613 # We could try harder and see if we could recreate a wcache
1614 # directory were we coudl write too.
1614 # directory were we coudl write too.
1615 #
1615 #
1616 # XXX the error pass silently, having some way to issue an error
1616 # XXX the error pass silently, having some way to issue an error
1617 # log `ui.log` would be nice.
1617 # log `ui.log` would be nice.
1618 pass
1618 pass
1619
1619
1620 def __len__(self):
1620 def __len__(self):
1621 if not self._read:
1621 if not self._read:
1622 self.read()
1622 self.read()
1623 return super(manifestfulltextcache, self).__len__()
1623 return super(manifestfulltextcache, self).__len__()
1624
1624
1625 def __contains__(self, k):
1625 def __contains__(self, k):
1626 if not self._read:
1626 if not self._read:
1627 self.read()
1627 self.read()
1628 return super(manifestfulltextcache, self).__contains__(k)
1628 return super(manifestfulltextcache, self).__contains__(k)
1629
1629
1630 def __iter__(self):
1630 def __iter__(self):
1631 if not self._read:
1631 if not self._read:
1632 self.read()
1632 self.read()
1633 return super(manifestfulltextcache, self).__iter__()
1633 return super(manifestfulltextcache, self).__iter__()
1634
1634
1635 def __getitem__(self, k):
1635 def __getitem__(self, k):
1636 if not self._read:
1636 if not self._read:
1637 self.read()
1637 self.read()
1638 # the cache lru order can change on read
1638 # the cache lru order can change on read
1639 setdirty = self._cache.get(k) is not self._head
1639 setdirty = self._cache.get(k) is not self._head
1640 value = super(manifestfulltextcache, self).__getitem__(k)
1640 value = super(manifestfulltextcache, self).__getitem__(k)
1641 if setdirty:
1641 if setdirty:
1642 self._dirty = True
1642 self._dirty = True
1643 return value
1643 return value
1644
1644
1645 def __setitem__(self, k, v):
1645 def __setitem__(self, k, v):
1646 if not self._read:
1646 if not self._read:
1647 self.read()
1647 self.read()
1648 super(manifestfulltextcache, self).__setitem__(k, v)
1648 super(manifestfulltextcache, self).__setitem__(k, v)
1649 self._dirty = True
1649 self._dirty = True
1650
1650
1651 def __delitem__(self, k):
1651 def __delitem__(self, k):
1652 if not self._read:
1652 if not self._read:
1653 self.read()
1653 self.read()
1654 super(manifestfulltextcache, self).__delitem__(k)
1654 super(manifestfulltextcache, self).__delitem__(k)
1655 self._dirty = True
1655 self._dirty = True
1656
1656
1657 def get(self, k, default=None):
1657 def get(self, k, default=None):
1658 if not self._read:
1658 if not self._read:
1659 self.read()
1659 self.read()
1660 return super(manifestfulltextcache, self).get(k, default=default)
1660 return super(manifestfulltextcache, self).get(k, default=default)
1661
1661
1662 def clear(self, clear_persisted_data=False):
1662 def clear(self, clear_persisted_data=False):
1663 super(manifestfulltextcache, self).clear()
1663 super(manifestfulltextcache, self).clear()
1664 if clear_persisted_data:
1664 if clear_persisted_data:
1665 self._dirty = True
1665 self._dirty = True
1666 self.write()
1666 self.write()
1667 self._read = False
1667 self._read = False
1668
1668
1669
1669
1670 # and upper bound of what we expect from compression
1670 # and upper bound of what we expect from compression
1671 # (real live value seems to be "3")
1671 # (real live value seems to be "3")
1672 MAXCOMPRESSION = 3
1672 MAXCOMPRESSION = 3
1673
1673
1674
1674
1675 class FastdeltaUnavailable(Exception):
1675 class FastdeltaUnavailable(Exception):
1676 """Exception raised when fastdelta isn't usable on a manifest."""
1676 """Exception raised when fastdelta isn't usable on a manifest."""
1677
1677
1678
1678
1679 class manifestrevlog: # (repository.imanifeststorage)
1679 class manifestrevlog: # (repository.imanifeststorage)
1680 """A revlog that stores manifest texts. This is responsible for caching the
1680 """A revlog that stores manifest texts. This is responsible for caching the
1681 full-text manifest contents.
1681 full-text manifest contents.
1682 """
1682 """
1683
1683
1684 def __init__(
1684 def __init__(
1685 self,
1685 self,
1686 nodeconstants,
1686 nodeconstants,
1687 opener,
1687 opener,
1688 tree=b'',
1688 tree=b'',
1689 dirlogcache=None,
1689 dirlogcache=None,
1690 treemanifest=False,
1690 treemanifest=False,
1691 ):
1691 ):
1692 """Constructs a new manifest revlog
1692 """Constructs a new manifest revlog
1693
1693
1694 `indexfile` - used by extensions to have two manifests at once, like
1694 `indexfile` - used by extensions to have two manifests at once, like
1695 when transitioning between flatmanifeset and treemanifests.
1695 when transitioning between flatmanifeset and treemanifests.
1696
1696
1697 `treemanifest` - used to indicate this is a tree manifest revlog. Opener
1697 `treemanifest` - used to indicate this is a tree manifest revlog. Opener
1698 options can also be used to make this a tree manifest revlog. The opener
1698 options can also be used to make this a tree manifest revlog. The opener
1699 option takes precedence, so if it is set to True, we ignore whatever
1699 option takes precedence, so if it is set to True, we ignore whatever
1700 value is passed in to the constructor.
1700 value is passed in to the constructor.
1701 """
1701 """
1702 self.nodeconstants = nodeconstants
1702 self.nodeconstants = nodeconstants
1703 # During normal operations, we expect to deal with not more than four
1703 # During normal operations, we expect to deal with not more than four
1704 # revs at a time (such as during commit --amend). When rebasing large
1704 # revs at a time (such as during commit --amend). When rebasing large
1705 # stacks of commits, the number can go up, hence the config knob below.
1705 # stacks of commits, the number can go up, hence the config knob below.
1706 cachesize = 4
1706 cachesize = 4
1707 optiontreemanifest = False
1707 optiontreemanifest = False
1708 persistentnodemap = False
1708 persistentnodemap = False
1709 opts = getattr(opener, 'options', None)
1709 opts = getattr(opener, 'options', None)
1710 if opts is not None:
1710 if opts is not None:
1711 cachesize = opts.get(b'manifestcachesize', cachesize)
1711 cachesize = opts.get(b'manifestcachesize', cachesize)
1712 optiontreemanifest = opts.get(b'treemanifest', False)
1712 optiontreemanifest = opts.get(b'treemanifest', False)
1713 persistentnodemap = opts.get(b'persistent-nodemap', False)
1713 persistentnodemap = opts.get(b'persistent-nodemap', False)
1714
1714
1715 self._treeondisk = optiontreemanifest or treemanifest
1715 self._treeondisk = optiontreemanifest or treemanifest
1716
1716
1717 self._fulltextcache = manifestfulltextcache(cachesize)
1717 self._fulltextcache = manifestfulltextcache(cachesize)
1718
1718
1719 if tree:
1719 if tree:
1720 assert self._treeondisk, (tree, b'opts is %r' % opts)
1720 assert self._treeondisk, (tree, b'opts is %r' % opts)
1721
1721
1722 radix = b'00manifest'
1722 radix = b'00manifest'
1723 if tree:
1723 if tree:
1724 radix = b"meta/" + tree + radix
1724 radix = b"meta/" + tree + radix
1725
1725
1726 self.tree = tree
1726 self.tree = tree
1727
1727
1728 # The dirlogcache is kept on the root manifest log
1728 # The dirlogcache is kept on the root manifest log
1729 if tree:
1729 if tree:
1730 self._dirlogcache = dirlogcache
1730 self._dirlogcache = dirlogcache
1731 else:
1731 else:
1732 self._dirlogcache = {b'': self}
1732 self._dirlogcache = {b'': self}
1733
1733
1734 self._revlog = revlog.revlog(
1734 self._revlog = revlog.revlog(
1735 opener,
1735 opener,
1736 target=(revlog_constants.KIND_MANIFESTLOG, self.tree),
1736 target=(revlog_constants.KIND_MANIFESTLOG, self.tree),
1737 radix=radix,
1737 radix=radix,
1738 # only root indexfile is cached
1738 # only root indexfile is cached
1739 checkambig=not bool(tree),
1739 checkambig=not bool(tree),
1740 mmaplargeindex=True,
1740 mmaplargeindex=True,
1741 upperboundcomp=MAXCOMPRESSION,
1741 upperboundcomp=MAXCOMPRESSION,
1742 persistentnodemap=persistentnodemap,
1742 persistentnodemap=persistentnodemap,
1743 )
1743 )
1744
1744
1745 self.index = self._revlog.index
1745 self.index = self._revlog.index
1746
1746
1747 def get_revlog(self):
1747 def get_revlog(self):
1748 """return an actual revlog instance if any
1748 """return an actual revlog instance if any
1749
1749
1750 This exist because a lot of code leverage the fact the underlying
1750 This exist because a lot of code leverage the fact the underlying
1751 storage is a revlog for optimization, so giving simple way to access
1751 storage is a revlog for optimization, so giving simple way to access
1752 the revlog instance helps such code.
1752 the revlog instance helps such code.
1753 """
1753 """
1754 return self._revlog
1754 return self._revlog
1755
1755
1756 def _setupmanifestcachehooks(self, repo):
1756 def _setupmanifestcachehooks(self, repo):
1757 """Persist the manifestfulltextcache on lock release"""
1757 """Persist the manifestfulltextcache on lock release"""
1758 if not hasattr(repo, '_wlockref'):
1758 if not hasattr(repo, '_wlockref'):
1759 return
1759 return
1760
1760
1761 self._fulltextcache._opener = repo.wcachevfs
1761 self._fulltextcache._opener = repo.wcachevfs
1762 if repo._currentlock(repo._wlockref) is None:
1762 if repo._currentlock(repo._wlockref) is None:
1763 return
1763 return
1764
1764
1765 reporef = weakref.ref(repo)
1765 reporef = weakref.ref(repo)
1766 manifestrevlogref = weakref.ref(self)
1766 manifestrevlogref = weakref.ref(self)
1767
1767
1768 def persistmanifestcache(success):
1768 def persistmanifestcache(success):
1769 # Repo is in an unknown state, do not persist.
1769 # Repo is in an unknown state, do not persist.
1770 if not success:
1770 if not success:
1771 return
1771 return
1772
1772
1773 repo = reporef()
1773 repo = reporef()
1774 self = manifestrevlogref()
1774 self = manifestrevlogref()
1775 if repo is None or self is None:
1775 if repo is None or self is None:
1776 return
1776 return
1777 if repo.manifestlog.getstorage(b'') is not self:
1777 if repo.manifestlog.getstorage(b'') is not self:
1778 # there's a different manifest in play now, abort
1778 # there's a different manifest in play now, abort
1779 return
1779 return
1780 self._fulltextcache.write()
1780 self._fulltextcache.write()
1781
1781
1782 repo._afterlock(persistmanifestcache)
1782 repo._afterlock(persistmanifestcache)
1783
1783
1784 @property
1784 @property
1785 def fulltextcache(self):
1785 def fulltextcache(self):
1786 return self._fulltextcache
1786 return self._fulltextcache
1787
1787
1788 def clearcaches(self, clear_persisted_data: bool = False) -> None:
1788 def clearcaches(self, clear_persisted_data: bool = False) -> None:
1789 self._revlog.clearcaches()
1789 self._revlog.clearcaches()
1790 self._fulltextcache.clear(clear_persisted_data=clear_persisted_data)
1790 self._fulltextcache.clear(clear_persisted_data=clear_persisted_data)
1791 self._dirlogcache = {self.tree: self}
1791 self._dirlogcache = {self.tree: self}
1792
1792
1793 def dirlog(self, d):
1793 def dirlog(self, d):
1794 if d:
1794 if d:
1795 assert self._treeondisk
1795 assert self._treeondisk
1796 if d not in self._dirlogcache:
1796 if d not in self._dirlogcache:
1797 mfrevlog = manifestrevlog(
1797 mfrevlog = manifestrevlog(
1798 self.nodeconstants,
1798 self.nodeconstants,
1799 self.opener,
1799 self.opener,
1800 d,
1800 d,
1801 self._dirlogcache,
1801 self._dirlogcache,
1802 treemanifest=self._treeondisk,
1802 treemanifest=self._treeondisk,
1803 )
1803 )
1804 self._dirlogcache[d] = mfrevlog
1804 self._dirlogcache[d] = mfrevlog
1805 return self._dirlogcache[d]
1805 return self._dirlogcache[d]
1806
1806
1807 def add(
1807 def add(
1808 self,
1808 self,
1809 m,
1809 m,
1810 transaction,
1810 transaction,
1811 link,
1811 link,
1812 p1,
1812 p1,
1813 p2,
1813 p2,
1814 added: Iterable[bytes],
1814 added: Iterable[bytes],
1815 removed: Iterable[bytes],
1815 removed: Iterable[bytes],
1816 readtree=None,
1816 readtree=None,
1817 match=None,
1817 match=None,
1818 ):
1818 ):
1819 """add some manifest entry in to the manifest log
1819 """add some manifest entry in to the manifest log
1820
1820
1821 input:
1821 input:
1822
1822
1823 m: the manifest dict we want to store
1823 m: the manifest dict we want to store
1824 transaction: the open transaction
1824 transaction: the open transaction
1825 p1: manifest-node of p1
1825 p1: manifest-node of p1
1826 p2: manifest-node of p2
1826 p2: manifest-node of p2
1827 added: file added/changed compared to parent
1827 added: file added/changed compared to parent
1828 removed: file removed compared to parent
1828 removed: file removed compared to parent
1829
1829
1830 tree manifest input:
1830 tree manifest input:
1831
1831
1832 readtree: a function to read a subtree
1832 readtree: a function to read a subtree
1833 match: a filematcher for the subpart of the tree manifest
1833 match: a filematcher for the subpart of the tree manifest
1834 """
1834 """
1835 try:
1835 try:
1836 if p1 not in self.fulltextcache:
1836 if p1 not in self.fulltextcache:
1837 raise FastdeltaUnavailable()
1837 raise FastdeltaUnavailable()
1838 # If our first parent is in the manifest cache, we can
1838 # If our first parent is in the manifest cache, we can
1839 # compute a delta here using properties we know about the
1839 # compute a delta here using properties we know about the
1840 # manifest up-front, which may save time later for the
1840 # manifest up-front, which may save time later for the
1841 # revlog layer.
1841 # revlog layer.
1842
1842
1843 _checkforbidden(added)
1843 _checkforbidden(added)
1844 # combine the changed lists into one sorted iterator
1844 # combine the changed lists into one sorted iterator
1845 work = heapq.merge(
1845 work = heapq.merge(
1846 [(x, False) for x in sorted(added)],
1846 [(x, False) for x in sorted(added)],
1847 [(x, True) for x in sorted(removed)],
1847 [(x, True) for x in sorted(removed)],
1848 )
1848 )
1849
1849
1850 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1850 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1851 cachedelta = self._revlog.rev(p1), deltatext
1851 cachedelta = self._revlog.rev(p1), deltatext
1852 text = util.buffer(arraytext)
1852 text = util.buffer(arraytext)
1853 rev = self._revlog.addrevision(
1853 rev = self._revlog.addrevision(
1854 text, transaction, link, p1, p2, cachedelta
1854 text, transaction, link, p1, p2, cachedelta
1855 )
1855 )
1856 n = self._revlog.node(rev)
1856 n = self._revlog.node(rev)
1857 except FastdeltaUnavailable:
1857 except FastdeltaUnavailable:
1858 # The first parent manifest isn't already loaded or the
1858 # The first parent manifest isn't already loaded or the
1859 # manifest implementation doesn't support fastdelta, so
1859 # manifest implementation doesn't support fastdelta, so
1860 # we'll just encode a fulltext of the manifest and pass
1860 # we'll just encode a fulltext of the manifest and pass
1861 # that through to the revlog layer, and let it handle the
1861 # that through to the revlog layer, and let it handle the
1862 # delta process.
1862 # delta process.
1863 if self._treeondisk:
1863 if self._treeondisk:
1864 assert readtree, b"readtree must be set for treemanifest writes"
1864 assert readtree, b"readtree must be set for treemanifest writes"
1865 assert match, b"match must be specified for treemanifest writes"
1865 assert match, b"match must be specified for treemanifest writes"
1866 m1 = readtree(self.tree, p1)
1866 m1 = readtree(self.tree, p1)
1867 m2 = readtree(self.tree, p2)
1867 m2 = readtree(self.tree, p2)
1868 n = self._addtree(
1868 n = self._addtree(
1869 m, transaction, link, m1, m2, readtree, match=match
1869 m, transaction, link, m1, m2, readtree, match=match
1870 )
1870 )
1871 arraytext = None
1871 arraytext = None
1872 else:
1872 else:
1873 text = m.text()
1873 text = m.text()
1874 rev = self._revlog.addrevision(text, transaction, link, p1, p2)
1874 rev = self._revlog.addrevision(text, transaction, link, p1, p2)
1875 n = self._revlog.node(rev)
1875 n = self._revlog.node(rev)
1876 arraytext = bytearray(text)
1876 arraytext = bytearray(text)
1877
1877
1878 if arraytext is not None:
1878 if arraytext is not None:
1879 self.fulltextcache[n] = arraytext
1879 self.fulltextcache[n] = arraytext
1880
1880
1881 return n
1881 return n
1882
1882
1883 def _addtree(self, m, transaction, link, m1, m2, readtree, match):
1883 def _addtree(self, m, transaction, link, m1, m2, readtree, match):
1884 # If the manifest is unchanged compared to one parent,
1884 # If the manifest is unchanged compared to one parent,
1885 # don't write a new revision
1885 # don't write a new revision
1886 if self.tree != b'' and (
1886 if self.tree != b'' and (
1887 m.unmodifiedsince(m1) or m.unmodifiedsince(m2)
1887 m.unmodifiedsince(m1) or m.unmodifiedsince(m2)
1888 ):
1888 ):
1889 return m.node()
1889 return m.node()
1890
1890
1891 def writesubtree(subm, subp1, subp2, match):
1891 def writesubtree(subm, subp1, subp2, match):
1892 sublog = self.dirlog(subm.dir())
1892 sublog = self.dirlog(subm.dir())
1893 sublog.add(
1893 sublog.add(
1894 subm,
1894 subm,
1895 transaction,
1895 transaction,
1896 link,
1896 link,
1897 subp1,
1897 subp1,
1898 subp2,
1898 subp2,
1899 None,
1899 None,
1900 None,
1900 None,
1901 readtree=readtree,
1901 readtree=readtree,
1902 match=match,
1902 match=match,
1903 )
1903 )
1904
1904
1905 m.writesubtrees(m1, m2, writesubtree, match)
1905 m.writesubtrees(m1, m2, writesubtree, match)
1906 text = m.dirtext()
1906 text = m.dirtext()
1907 n = None
1907 n = None
1908 if self.tree != b'':
1908 if self.tree != b'':
1909 # Double-check whether contents are unchanged to one parent
1909 # Double-check whether contents are unchanged to one parent
1910 if text == m1.dirtext():
1910 if text == m1.dirtext():
1911 n = m1.node()
1911 n = m1.node()
1912 elif text == m2.dirtext():
1912 elif text == m2.dirtext():
1913 n = m2.node()
1913 n = m2.node()
1914
1914
1915 if not n:
1915 if not n:
1916 rev = self._revlog.addrevision(
1916 rev = self._revlog.addrevision(
1917 text, transaction, link, m1.node(), m2.node()
1917 text, transaction, link, m1.node(), m2.node()
1918 )
1918 )
1919 n = self._revlog.node(rev)
1919 n = self._revlog.node(rev)
1920
1920
1921 # Save nodeid so parent manifest can calculate its nodeid
1921 # Save nodeid so parent manifest can calculate its nodeid
1922 m.setnode(n)
1922 m.setnode(n)
1923 return n
1923 return n
1924
1924
1925 def __len__(self):
1925 def __len__(self):
1926 return len(self._revlog)
1926 return len(self._revlog)
1927
1927
1928 def __iter__(self):
1928 def __iter__(self):
1929 return self._revlog.__iter__()
1929 return self._revlog.__iter__()
1930
1930
1931 def rev(self, node):
1931 def rev(self, node):
1932 return self._revlog.rev(node)
1932 return self._revlog.rev(node)
1933
1933
1934 def node(self, rev):
1934 def node(self, rev):
1935 return self._revlog.node(rev)
1935 return self._revlog.node(rev)
1936
1936
1937 def lookup(self, value):
1937 def lookup(self, value):
1938 return self._revlog.lookup(value)
1938 return self._revlog.lookup(value)
1939
1939
1940 def parentrevs(self, rev):
1940 def parentrevs(self, rev):
1941 return self._revlog.parentrevs(rev)
1941 return self._revlog.parentrevs(rev)
1942
1942
1943 def parents(self, node):
1943 def parents(self, node):
1944 return self._revlog.parents(node)
1944 return self._revlog.parents(node)
1945
1945
1946 def linkrev(self, rev):
1946 def linkrev(self, rev):
1947 return self._revlog.linkrev(rev)
1947 return self._revlog.linkrev(rev)
1948
1948
1949 def checksize(self):
1949 def checksize(self):
1950 return self._revlog.checksize()
1950 return self._revlog.checksize()
1951
1951
1952 def revision(self, node):
1952 def revision(self, node):
1953 return self._revlog.revision(node)
1953 return self._revlog.revision(node)
1954
1954
1955 def rawdata(self, node):
1955 def rawdata(self, node):
1956 return self._revlog.rawdata(node)
1956 return self._revlog.rawdata(node)
1957
1957
1958 def revdiff(self, rev1, rev2):
1958 def revdiff(self, rev1, rev2):
1959 return self._revlog.revdiff(rev1, rev2)
1959 return self._revlog.revdiff(rev1, rev2)
1960
1960
1961 def cmp(self, node, text):
1961 def cmp(self, node, text):
1962 return self._revlog.cmp(node, text)
1962 return self._revlog.cmp(node, text)
1963
1963
1964 def deltaparent(self, rev):
1964 def deltaparent(self, rev):
1965 return self._revlog.deltaparent(rev)
1965 return self._revlog.deltaparent(rev)
1966
1966
1967 def emitrevisions(
1967 def emitrevisions(
1968 self,
1968 self,
1969 nodes,
1969 nodes,
1970 nodesorder=None,
1970 nodesorder=None,
1971 revisiondata=False,
1971 revisiondata=False,
1972 assumehaveparentrevisions=False,
1972 assumehaveparentrevisions=False,
1973 deltamode=repository.CG_DELTAMODE_STD,
1973 deltamode=repository.CG_DELTAMODE_STD,
1974 sidedata_helpers=None,
1974 sidedata_helpers=None,
1975 debug_info=None,
1975 debug_info=None,
1976 ):
1976 ):
1977 return self._revlog.emitrevisions(
1977 return self._revlog.emitrevisions(
1978 nodes,
1978 nodes,
1979 nodesorder=nodesorder,
1979 nodesorder=nodesorder,
1980 revisiondata=revisiondata,
1980 revisiondata=revisiondata,
1981 assumehaveparentrevisions=assumehaveparentrevisions,
1981 assumehaveparentrevisions=assumehaveparentrevisions,
1982 deltamode=deltamode,
1982 deltamode=deltamode,
1983 sidedata_helpers=sidedata_helpers,
1983 sidedata_helpers=sidedata_helpers,
1984 debug_info=debug_info,
1984 debug_info=debug_info,
1985 )
1985 )
1986
1986
1987 def addgroup(
1987 def addgroup(
1988 self,
1988 self,
1989 deltas,
1989 deltas,
1990 linkmapper,
1990 linkmapper,
1991 transaction,
1991 transaction,
1992 alwayscache=False,
1992 alwayscache=False,
1993 addrevisioncb=None,
1993 addrevisioncb=None,
1994 duplicaterevisioncb=None,
1994 duplicaterevisioncb=None,
1995 debug_info=None,
1995 debug_info=None,
1996 delta_base_reuse_policy=None,
1996 delta_base_reuse_policy=None,
1997 ):
1997 ):
1998 return self._revlog.addgroup(
1998 return self._revlog.addgroup(
1999 deltas,
1999 deltas,
2000 linkmapper,
2000 linkmapper,
2001 transaction,
2001 transaction,
2002 alwayscache=alwayscache,
2002 alwayscache=alwayscache,
2003 addrevisioncb=addrevisioncb,
2003 addrevisioncb=addrevisioncb,
2004 duplicaterevisioncb=duplicaterevisioncb,
2004 duplicaterevisioncb=duplicaterevisioncb,
2005 debug_info=debug_info,
2005 debug_info=debug_info,
2006 delta_base_reuse_policy=delta_base_reuse_policy,
2006 delta_base_reuse_policy=delta_base_reuse_policy,
2007 )
2007 )
2008
2008
2009 def rawsize(self, rev):
2009 def rawsize(self, rev):
2010 return self._revlog.rawsize(rev)
2010 return self._revlog.rawsize(rev)
2011
2011
2012 def getstrippoint(self, minlink):
2012 def getstrippoint(self, minlink):
2013 return self._revlog.getstrippoint(minlink)
2013 return self._revlog.getstrippoint(minlink)
2014
2014
2015 def strip(self, minlink, transaction):
2015 def strip(self, minlink, transaction):
2016 return self._revlog.strip(minlink, transaction)
2016 return self._revlog.strip(minlink, transaction)
2017
2017
2018 def files(self):
2018 def files(self):
2019 return self._revlog.files()
2019 return self._revlog.files()
2020
2020
2021 def clone(self, tr, destrevlog, **kwargs):
2021 def clone(self, tr, destrevlog, **kwargs):
2022 if not isinstance(destrevlog, manifestrevlog):
2022 if not isinstance(destrevlog, manifestrevlog):
2023 raise error.ProgrammingError(b'expected manifestrevlog to clone()')
2023 raise error.ProgrammingError(b'expected manifestrevlog to clone()')
2024
2024
2025 return self._revlog.clone(tr, destrevlog._revlog, **kwargs)
2025 return self._revlog.clone(tr, destrevlog._revlog, **kwargs)
2026
2026
2027 def storageinfo(
2027 def storageinfo(
2028 self,
2028 self,
2029 exclusivefiles=False,
2029 exclusivefiles=False,
2030 sharedfiles=False,
2030 sharedfiles=False,
2031 revisionscount=False,
2031 revisionscount=False,
2032 trackedsize=False,
2032 trackedsize=False,
2033 storedsize=False,
2033 storedsize=False,
2034 ):
2034 ):
2035 return self._revlog.storageinfo(
2035 return self._revlog.storageinfo(
2036 exclusivefiles=exclusivefiles,
2036 exclusivefiles=exclusivefiles,
2037 sharedfiles=sharedfiles,
2037 sharedfiles=sharedfiles,
2038 revisionscount=revisionscount,
2038 revisionscount=revisionscount,
2039 trackedsize=trackedsize,
2039 trackedsize=trackedsize,
2040 storedsize=storedsize,
2040 storedsize=storedsize,
2041 )
2041 )
2042
2042
2043 @property
2043 @property
2044 def opener(self):
2044 def opener(self):
2045 return self._revlog.opener
2045 return self._revlog.opener
2046
2046
2047 @opener.setter
2047 @opener.setter
2048 def opener(self, value):
2048 def opener(self, value):
2049 self._revlog.opener = value
2049 self._revlog.opener = value
2050
2050
2051
2051
2052 # TODO: drop this in favor of repository.imanifestrevisionstored?
2052 # TODO: drop this in favor of repository.imanifestrevisionstored?
2053 AnyManifestCtx = Union['manifestctx', 'treemanifestctx']
2053 AnyManifestCtx = Union['manifestctx', 'treemanifestctx']
2054 # TODO: drop this in favor of repository.imanifestdict
2054 # TODO: drop this in favor of repository.imanifestdict
2055 AnyManifestDict = Union[manifestdict, treemanifest]
2055 AnyManifestDict = Union[manifestdict, treemanifest]
2056
2056
2057
2057
2058 class manifestlog: # (repository.imanifestlog)
2058 class manifestlog: # (repository.imanifestlog)
2059 """A collection class representing the collection of manifest snapshots
2059 """A collection class representing the collection of manifest snapshots
2060 referenced by commits in the repository.
2060 referenced by commits in the repository.
2061
2061
2062 In this situation, 'manifest' refers to the abstract concept of a snapshot
2062 In this situation, 'manifest' refers to the abstract concept of a snapshot
2063 of the list of files in the given commit. Consumers of the output of this
2063 of the list of files in the given commit. Consumers of the output of this
2064 class do not care about the implementation details of the actual manifests
2064 class do not care about the implementation details of the actual manifests
2065 they receive (i.e. tree or flat or lazily loaded, etc)."""
2065 they receive (i.e. tree or flat or lazily loaded, etc)."""
2066
2066
2067 def __init__(self, opener, repo, rootstore, narrowmatch):
2067 def __init__(self, opener, repo, rootstore, narrowmatch):
2068 self.nodeconstants = repo.nodeconstants
2068 self.nodeconstants = repo.nodeconstants
2069 usetreemanifest = False
2069 usetreemanifest = False
2070 cachesize = 4
2070 cachesize = 4
2071
2071
2072 opts = getattr(opener, 'options', None)
2072 opts = getattr(opener, 'options', None)
2073 if opts is not None:
2073 if opts is not None:
2074 usetreemanifest = opts.get(b'treemanifest', usetreemanifest)
2074 usetreemanifest = opts.get(b'treemanifest', usetreemanifest)
2075 cachesize = opts.get(b'manifestcachesize', cachesize)
2075 cachesize = opts.get(b'manifestcachesize', cachesize)
2076
2076
2077 self._treemanifests = usetreemanifest
2077 self._treemanifests = usetreemanifest
2078
2078
2079 self._rootstore = rootstore
2079 self._rootstore = rootstore
2080 self._rootstore._setupmanifestcachehooks(repo)
2080 self._rootstore._setupmanifestcachehooks(repo)
2081 self._narrowmatch = narrowmatch
2081 self._narrowmatch = narrowmatch
2082
2082
2083 # A cache of the manifestctx or treemanifestctx for each directory
2083 # A cache of the manifestctx or treemanifestctx for each directory
2084 self._dirmancache = {}
2084 self._dirmancache = {}
2085 self._dirmancache[b''] = util.lrucachedict(cachesize)
2085 self._dirmancache[b''] = util.lrucachedict(cachesize)
2086
2086
2087 self._cachesize = cachesize
2087 self._cachesize = cachesize
2088
2088
2089 def __getitem__(self, node):
2089 def __getitem__(self, node):
2090 """Retrieves the manifest instance for the given node. Throws a
2090 """Retrieves the manifest instance for the given node. Throws a
2091 LookupError if not found.
2091 LookupError if not found.
2092 """
2092 """
2093 return self.get(b'', node)
2093 return self.get(b'', node)
2094
2094
2095 @property
2095 @property
2096 def narrowed(self):
2096 def narrowed(self):
2097 return not (self._narrowmatch is None or self._narrowmatch.always())
2097 return not (self._narrowmatch is None or self._narrowmatch.always())
2098
2098
2099 def get(
2099 def get(
2100 self, tree: bytes, node: bytes, verify: bool = True
2100 self, tree: bytes, node: bytes, verify: bool = True
2101 ) -> AnyManifestCtx:
2101 ) -> AnyManifestCtx:
2102 """Retrieves the manifest instance for the given node. Throws a
2102 """Retrieves the manifest instance for the given node. Throws a
2103 LookupError if not found.
2103 LookupError if not found.
2104
2104
2105 `verify` - if True an exception will be thrown if the node is not in
2105 `verify` - if True an exception will be thrown if the node is not in
2106 the revlog
2106 the revlog
2107 """
2107 """
2108 if node in self._dirmancache.get(tree, ()):
2108 if node in self._dirmancache.get(tree, ()):
2109 return self._dirmancache[tree][node]
2109 return self._dirmancache[tree][node]
2110
2110
2111 if not self._narrowmatch.always():
2111 if not self._narrowmatch.always():
2112 if not self._narrowmatch.visitdir(tree[:-1]):
2112 if not self._narrowmatch.visitdir(tree[:-1]):
2113 return excludeddirmanifestctx(self.nodeconstants, tree, node)
2113 return excludeddirmanifestctx(self.nodeconstants, tree, node)
2114 if tree:
2114 if tree:
2115 if self._rootstore._treeondisk:
2115 if self._rootstore._treeondisk:
2116 if verify:
2116 if verify:
2117 # Side-effect is LookupError is raised if node doesn't
2117 # Side-effect is LookupError is raised if node doesn't
2118 # exist.
2118 # exist.
2119 self.getstorage(tree).rev(node)
2119 self.getstorage(tree).rev(node)
2120
2120
2121 m = treemanifestctx(self, tree, node)
2121 m = treemanifestctx(self, tree, node)
2122 else:
2122 else:
2123 raise error.Abort(
2123 raise error.Abort(
2124 _(
2124 _(
2125 b"cannot ask for manifest directory '%s' in a flat "
2125 b"cannot ask for manifest directory '%s' in a flat "
2126 b"manifest"
2126 b"manifest"
2127 )
2127 )
2128 % tree
2128 % tree
2129 )
2129 )
2130 else:
2130 else:
2131 if verify:
2131 if verify:
2132 # Side-effect is LookupError is raised if node doesn't exist.
2132 # Side-effect is LookupError is raised if node doesn't exist.
2133 self._rootstore.rev(node)
2133 self._rootstore.rev(node)
2134
2134
2135 if self._treemanifests:
2135 if self._treemanifests:
2136 m = treemanifestctx(self, b'', node)
2136 m = treemanifestctx(self, b'', node)
2137 else:
2137 else:
2138 m = manifestctx(self, node)
2138 m = manifestctx(self, node)
2139
2139
2140 if node != self.nodeconstants.nullid:
2140 if node != self.nodeconstants.nullid:
2141 mancache = self._dirmancache.get(tree)
2141 mancache = self._dirmancache.get(tree)
2142 if not mancache:
2142 if not mancache:
2143 mancache = util.lrucachedict(self._cachesize)
2143 mancache = util.lrucachedict(self._cachesize)
2144 self._dirmancache[tree] = mancache
2144 self._dirmancache[tree] = mancache
2145 mancache[node] = m
2145 mancache[node] = m
2146 return m
2146 return m
2147
2147
2148 def getstorage(self, tree):
2148 def getstorage(self, tree):
2149 return self._rootstore.dirlog(tree)
2149 return self._rootstore.dirlog(tree)
2150
2150
2151 def clearcaches(self, clear_persisted_data: bool = False) -> None:
2151 def clearcaches(self, clear_persisted_data: bool = False) -> None:
2152 self._dirmancache.clear()
2152 self._dirmancache.clear()
2153 self._rootstore.clearcaches(clear_persisted_data=clear_persisted_data)
2153 self._rootstore.clearcaches(clear_persisted_data=clear_persisted_data)
2154
2154
2155 def rev(self, node) -> int:
2155 def rev(self, node) -> int:
2156 return self._rootstore.rev(node)
2156 return self._rootstore.rev(node)
2157
2157
2158 def update_caches(self, transaction) -> None:
2158 def update_caches(self, transaction) -> None:
2159 return self._rootstore._revlog.update_caches(transaction=transaction)
2159 return self._rootstore._revlog.update_caches(transaction=transaction)
2160
2160
2161
2161
2162 class memmanifestctx: # (repository.imanifestrevisionwritable)
2162 class memmanifestctx: # (repository.imanifestrevisionwritable)
2163 _manifestdict: manifestdict
2163 _manifestdict: manifestdict
2164
2164
2165 def __init__(self, manifestlog):
2165 def __init__(self, manifestlog):
2166 self._manifestlog = manifestlog
2166 self._manifestlog = manifestlog
2167 self._manifestdict = manifestdict(manifestlog.nodeconstants.nodelen)
2167 self._manifestdict = manifestdict(manifestlog.nodeconstants.nodelen)
2168
2168
2169 def _storage(self) -> manifestrevlog:
2169 def _storage(self) -> manifestrevlog:
2170 return self._manifestlog.getstorage(b'')
2170 return self._manifestlog.getstorage(b'')
2171
2171
2172 def copy(self) -> 'memmanifestctx':
2172 def copy(self) -> 'memmanifestctx':
2173 memmf = memmanifestctx(self._manifestlog)
2173 memmf = memmanifestctx(self._manifestlog)
2174 memmf._manifestdict = self.read().copy()
2174 memmf._manifestdict = self.read().copy()
2175 return memmf
2175 return memmf
2176
2176
2177 def read(self) -> 'manifestdict':
2177 def read(self) -> 'manifestdict':
2178 return self._manifestdict
2178 return self._manifestdict
2179
2179
2180 def write(self, transaction, link, p1, p2, added, removed, match=None):
2180 def write(self, transaction, link, p1, p2, added, removed, match=None):
2181 return self._storage().add(
2181 return self._storage().add(
2182 self._manifestdict,
2182 self._manifestdict,
2183 transaction,
2183 transaction,
2184 link,
2184 link,
2185 p1,
2185 p1,
2186 p2,
2186 p2,
2187 added,
2187 added,
2188 removed,
2188 removed,
2189 match=match,
2189 match=match,
2190 )
2190 )
2191
2191
2192
2192
2193 class manifestctx: # (repository.imanifestrevisionstored)
2193 class manifestctx: # (repository.imanifestrevisionstored)
2194 """A class representing a single revision of a manifest, including its
2194 """A class representing a single revision of a manifest, including its
2195 contents, its parent revs, and its linkrev.
2195 contents, its parent revs, and its linkrev.
2196 """
2196 """
2197
2197
2198 _data: Optional[manifestdict]
2198 _data: Optional[manifestdict]
2199
2199
2200 def __init__(self, manifestlog, node):
2200 def __init__(self, manifestlog, node):
2201 self._manifestlog = manifestlog
2201 self._manifestlog = manifestlog
2202 self._data = None
2202 self._data = None
2203
2203
2204 self._node = node
2204 self._node = node
2205
2205
2206 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
2206 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
2207 # but let's add it later when something needs it and we can load it
2207 # but let's add it later when something needs it and we can load it
2208 # lazily.
2208 # lazily.
2209 # self.p1, self.p2 = store.parents(node)
2209 # self.p1, self.p2 = store.parents(node)
2210 # rev = store.rev(node)
2210 # rev = store.rev(node)
2211 # self.linkrev = store.linkrev(rev)
2211 # self.linkrev = store.linkrev(rev)
2212
2212
2213 def _storage(self) -> 'manifestrevlog':
2213 def _storage(self) -> 'manifestrevlog':
2214 return self._manifestlog.getstorage(b'')
2214 return self._manifestlog.getstorage(b'')
2215
2215
2216 def node(self) -> bytes:
2216 def node(self) -> bytes:
2217 return self._node
2217 return self._node
2218
2218
2219 def copy(self) -> memmanifestctx:
2219 def copy(self) -> memmanifestctx:
2220 memmf = memmanifestctx(self._manifestlog)
2220 memmf = memmanifestctx(self._manifestlog)
2221 memmf._manifestdict = self.read().copy()
2221 memmf._manifestdict = self.read().copy()
2222 return memmf
2222 return memmf
2223
2223
2224 @propertycache
2224 @propertycache
2225 def parents(self) -> Tuple[bytes, bytes]:
2225 def parents(self) -> Tuple[bytes, bytes]:
2226 return self._storage().parents(self._node)
2226 return self._storage().parents(self._node)
2227
2227
2228 def read(self) -> 'manifestdict':
2228 def read(self) -> 'manifestdict':
2229 if self._data is None:
2229 if self._data is None:
2230 nc = self._manifestlog.nodeconstants
2230 nc = self._manifestlog.nodeconstants
2231 if self._node == nc.nullid:
2231 if self._node == nc.nullid:
2232 self._data = manifestdict(nc.nodelen)
2232 self._data = manifestdict(nc.nodelen)
2233 else:
2233 else:
2234 store = self._storage()
2234 store = self._storage()
2235 if self._node in store.fulltextcache:
2235 if self._node in store.fulltextcache:
2236 text = pycompat.bytestr(store.fulltextcache[self._node])
2236 text = pycompat.bytestr(store.fulltextcache[self._node])
2237 else:
2237 else:
2238 text = store.revision(self._node)
2238 text = store.revision(self._node)
2239 arraytext = bytearray(text)
2239 arraytext = bytearray(text)
2240 store.fulltextcache[self._node] = arraytext
2240 store.fulltextcache[self._node] = arraytext
2241 self._data = manifestdict(nc.nodelen, text)
2241 self._data = manifestdict(nc.nodelen, text)
2242 return self._data
2242 return self._data
2243
2243
2244 def readfast(self, shallow: bool = False) -> 'manifestdict':
2244 def readfast(self, shallow: bool = False) -> 'manifestdict':
2245 """Calls either readdelta or read, based on which would be less work.
2245 """Calls either readdelta or read, based on which would be less work.
2246 readdelta is called if the delta is against the p1, and therefore can be
2246 readdelta is called if the delta is against the p1, and therefore can be
2247 read quickly.
2247 read quickly.
2248
2248
2249 If `shallow` is True, nothing changes since this is a flat manifest.
2249 If `shallow` is True, nothing changes since this is a flat manifest.
2250 """
2250 """
2251 util.nouideprecwarn(
2251 util.nouideprecwarn(
2252 b'"readfast" is deprecated use "read_any_fast_delta" or "read_delta_parents"',
2252 b'"readfast" is deprecated use "read_any_fast_delta" or "read_delta_parents"',
2253 b"6.9",
2253 b"6.9",
2254 stacklevel=2,
2254 stacklevel=2,
2255 )
2255 )
2256 store = self._storage()
2256 store = self._storage()
2257 r = store.rev(self._node)
2257 r = store.rev(self._node)
2258 deltaparent = store.deltaparent(r)
2258 deltaparent = store.deltaparent(r)
2259 if deltaparent != nullrev and deltaparent in store.parentrevs(r):
2259 if deltaparent != nullrev and deltaparent in store.parentrevs(r):
2260 return self.readdelta()
2260 return self.readdelta()
2261 return self.read()
2261 return self.read()
2262
2262
2263 def readdelta(self, shallow: bool = False) -> 'manifestdict':
2263 def readdelta(self, shallow: bool = False) -> 'manifestdict':
2264 """Returns a manifest containing just the entries that are present
2264 """Returns a manifest containing just the entries that are present
2265 in this manifest, but not in its p1 manifest. This is efficient to read
2265 in this manifest, but not in its p1 manifest. This is efficient to read
2266 if the revlog delta is already p1.
2266 if the revlog delta is already p1.
2267
2267
2268 Changing the value of `shallow` has no effect on flat manifests.
2268 Changing the value of `shallow` has no effect on flat manifests.
2269 """
2269 """
2270 util.nouideprecwarn(
2270 util.nouideprecwarn(
2271 b'"readfast" is deprecated use "read_any_fast_delta" or "read_delta_new_entries"',
2271 b'"readfast" is deprecated use "read_any_fast_delta" or "read_delta_new_entries"',
2272 b"6.9",
2272 b"6.9",
2273 stacklevel=2,
2273 stacklevel=2,
2274 )
2274 )
2275 store = self._storage()
2275 store = self._storage()
2276 r = store.rev(self._node)
2276 r = store.rev(self._node)
2277 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2277 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2278 return manifestdict(store.nodeconstants.nodelen, d)
2278 return manifestdict(store.nodeconstants.nodelen, d)
2279
2279
2280 def read_any_fast_delta(
2280 def read_any_fast_delta(
2281 self,
2281 self,
2282 valid_bases: Optional[Collection[int]] = None,
2282 valid_bases: Optional[Collection[int]] = None,
2283 *,
2283 *,
2284 shallow: bool = False,
2284 shallow: bool = False,
2285 ) -> Tuple[Optional[int], manifestdict]:
2285 ) -> Tuple[Optional[int], manifestdict]:
2286 """see `imanifestrevisionstored` documentation"""
2286 """see `imanifestrevisionstored` documentation"""
2287 store = self._storage()
2287 store = self._storage()
2288 r = store.rev(self._node)
2288 r = store.rev(self._node)
2289 deltaparent = store.deltaparent(r)
2289 deltaparent = store.deltaparent(r)
2290 if valid_bases is None:
2290 if valid_bases is None:
2291 # make sure the next check is True
2291 # make sure the next check is True
2292 valid_bases = (deltaparent,)
2292 valid_bases = (deltaparent,)
2293 if deltaparent != nullrev and deltaparent in valid_bases:
2293 if deltaparent != nullrev and deltaparent in valid_bases:
2294 d = mdiff.patchtext(store.revdiff(deltaparent, r))
2294 d = mdiff.patchtext(store.revdiff(deltaparent, r))
2295 return (
2295 return (
2296 deltaparent,
2296 deltaparent,
2297 manifestdict(store.nodeconstants.nodelen, d),
2297 manifestdict(store.nodeconstants.nodelen, d),
2298 )
2298 )
2299 return (None, self.read())
2299 return (None, self.read())
2300
2300
2301 def read_delta_parents(
2301 def read_delta_parents(
2302 self,
2302 self,
2303 *,
2303 *,
2304 shallow: bool = False,
2304 shallow: bool = False,
2305 exact: bool = True,
2305 exact: bool = True,
2306 ) -> manifestdict:
2306 ) -> manifestdict:
2307 """see `interface.imanifestrevisionbase` documentations"""
2307 """see `interface.imanifestrevisionbase` documentations"""
2308 store = self._storage()
2308 store = self._storage()
2309 r = store.rev(self._node)
2309 r = store.rev(self._node)
2310 deltaparent = store.deltaparent(r)
2310 deltaparent = store.deltaparent(r)
2311 parents = [p for p in store.parentrevs(r) if p is not nullrev]
2311 parents = [p for p in store.parentrevs(r) if p is not nullrev]
2312 if not exact and deltaparent in parents:
2312 if not exact and deltaparent in parents:
2313 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2313 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2314 return manifestdict(store.nodeconstants.nodelen, d)
2314 return manifestdict(store.nodeconstants.nodelen, d)
2315 elif not exact or len(parents) == 0:
2315 elif not exact or len(parents) == 0:
2316 return self.read()
2316 return self.read()
2317 elif len(parents) == 1:
2317 elif len(parents) == 1:
2318 p = parents[0]
2318 p = parents[0]
2319 d = mdiff.patchtext(store.revdiff(p, r))
2319 d = mdiff.patchtext(store.revdiff(p, r))
2320 return manifestdict(store.nodeconstants.nodelen, d)
2320 return manifestdict(store.nodeconstants.nodelen, d)
2321 else:
2321 else:
2322 p1, p2 = parents
2322 p1, p2 = parents
2323 d1 = mdiff.patchtext(store.revdiff(p1, r))
2323 d1 = mdiff.patchtext(store.revdiff(p1, r))
2324 d2 = mdiff.patchtext(store.revdiff(p2, r))
2324 d2 = mdiff.patchtext(store.revdiff(p2, r))
2325 d1 = manifestdict(store.nodeconstants.nodelen, d1)
2325 d1 = manifestdict(store.nodeconstants.nodelen, d1)
2326 d2 = manifestdict(store.nodeconstants.nodelen, d2)
2326 d2 = manifestdict(store.nodeconstants.nodelen, d2)
2327 md = manifestdict(store.nodeconstants.nodelen)
2327 md = manifestdict(store.nodeconstants.nodelen)
2328 for f, new_node, new_flag in d1.iterentries():
2328 for f, new_node, new_flag in d1.iterentries():
2329 if f not in d2:
2329 if f not in d2:
2330 continue
2330 continue
2331 if new_node is not None:
2331 if new_node is not None:
2332 md.set(f, new_node, new_flag)
2332 md.set(f, new_node, new_flag)
2333 return md
2333 return md
2334
2334
2335 def read_delta_new_entries(self, *, shallow=False) -> manifestdict:
2335 def read_delta_new_entries(self, *, shallow: bool = False) -> manifestdict:
2336 """see `interface.imanifestrevisionbase` documentations"""
2336 """see `interface.imanifestrevisionbase` documentations"""
2337 # If we are using narrow, returning a delta against an arbitrary
2337 # If we are using narrow, returning a delta against an arbitrary
2338 # changeset might return file outside the narrowspec. This can create
2338 # changeset might return file outside the narrowspec. This can create
2339 # issue when running validation server side with strict security as
2339 # issue when running validation server side with strict security as
2340 # push from low priviledge usage might be seen as adding new revision
2340 # push from low priviledge usage might be seen as adding new revision
2341 # for files they cannot touch. So we are strict if narrow is involved.
2341 # for files they cannot touch. So we are strict if narrow is involved.
2342 if self._manifestlog.narrowed:
2342 if self._manifestlog.narrowed:
2343 return self.read_delta_parents(shallow=shallow, exact=True)
2343 return self.read_delta_parents(shallow=shallow, exact=True)
2344 store = self._storage()
2344 store = self._storage()
2345 r = store.rev(self._node)
2345 r = store.rev(self._node)
2346 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2346 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2347 return manifestdict(store.nodeconstants.nodelen, d)
2347 return manifestdict(store.nodeconstants.nodelen, d)
2348
2348
2349 def find(self, key: bytes) -> Tuple[bytes, bytes]:
2349 def find(self, key: bytes) -> Tuple[bytes, bytes]:
2350 return self.read().find(key)
2350 return self.read().find(key)
2351
2351
2352
2352
2353 class memtreemanifestctx: # (repository.imanifestrevisionwritable)
2353 class memtreemanifestctx: # (repository.imanifestrevisionwritable)
2354 _treemanifest: treemanifest
2354 _treemanifest: treemanifest
2355
2355
2356 def __init__(self, manifestlog, dir=b''):
2356 def __init__(self, manifestlog, dir=b''):
2357 self._manifestlog = manifestlog
2357 self._manifestlog = manifestlog
2358 self._dir = dir
2358 self._dir = dir
2359 self._treemanifest = treemanifest(manifestlog.nodeconstants)
2359 self._treemanifest = treemanifest(manifestlog.nodeconstants)
2360
2360
2361 def _storage(self) -> manifestrevlog:
2361 def _storage(self) -> manifestrevlog:
2362 return self._manifestlog.getstorage(b'')
2362 return self._manifestlog.getstorage(b'')
2363
2363
2364 def copy(self) -> 'memtreemanifestctx':
2364 def copy(self) -> 'memtreemanifestctx':
2365 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
2365 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
2366 memmf._treemanifest = self._treemanifest.copy()
2366 memmf._treemanifest = self._treemanifest.copy()
2367 return memmf
2367 return memmf
2368
2368
2369 def read(self) -> 'treemanifest':
2369 def read(self) -> 'treemanifest':
2370 return self._treemanifest
2370 return self._treemanifest
2371
2371
2372 def write(self, transaction, link, p1, p2, added, removed, match=None):
2372 def write(self, transaction, link, p1, p2, added, removed, match=None):
2373 def readtree(dir, node):
2373 def readtree(dir, node):
2374 return self._manifestlog.get(dir, node).read()
2374 return self._manifestlog.get(dir, node).read()
2375
2375
2376 return self._storage().add(
2376 return self._storage().add(
2377 self._treemanifest,
2377 self._treemanifest,
2378 transaction,
2378 transaction,
2379 link,
2379 link,
2380 p1,
2380 p1,
2381 p2,
2381 p2,
2382 added,
2382 added,
2383 removed,
2383 removed,
2384 readtree=readtree,
2384 readtree=readtree,
2385 match=match,
2385 match=match,
2386 )
2386 )
2387
2387
2388
2388
2389 class treemanifestctx: # (repository.imanifestrevisionstored)
2389 class treemanifestctx: # (repository.imanifestrevisionstored)
2390 _data: Optional[treemanifest]
2390 _data: Optional[treemanifest]
2391
2391
2392 def __init__(self, manifestlog, dir, node):
2392 def __init__(self, manifestlog, dir, node):
2393 self._manifestlog = manifestlog
2393 self._manifestlog = manifestlog
2394 self._dir = dir
2394 self._dir = dir
2395 self._data = None
2395 self._data = None
2396
2396
2397 self._node = node
2397 self._node = node
2398
2398
2399 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
2399 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
2400 # we can instantiate treemanifestctx objects for directories we don't
2400 # we can instantiate treemanifestctx objects for directories we don't
2401 # have on disk.
2401 # have on disk.
2402 # self.p1, self.p2 = store.parents(node)
2402 # self.p1, self.p2 = store.parents(node)
2403 # rev = store.rev(node)
2403 # rev = store.rev(node)
2404 # self.linkrev = store.linkrev(rev)
2404 # self.linkrev = store.linkrev(rev)
2405
2405
2406 def _storage(self) -> manifestrevlog:
2406 def _storage(self) -> manifestrevlog:
2407 narrowmatch = self._manifestlog._narrowmatch
2407 narrowmatch = self._manifestlog._narrowmatch
2408 if not narrowmatch.always():
2408 if not narrowmatch.always():
2409 if not narrowmatch.visitdir(self._dir[:-1]):
2409 if not narrowmatch.visitdir(self._dir[:-1]):
2410 return excludedmanifestrevlog(
2410 return excludedmanifestrevlog(
2411 self._manifestlog.nodeconstants, self._dir
2411 self._manifestlog.nodeconstants, self._dir
2412 )
2412 )
2413 return self._manifestlog.getstorage(self._dir)
2413 return self._manifestlog.getstorage(self._dir)
2414
2414
2415 def read(self) -> 'treemanifest':
2415 def read(self) -> 'treemanifest':
2416 if self._data is None:
2416 if self._data is None:
2417 store = self._storage()
2417 store = self._storage()
2418 if self._node == self._manifestlog.nodeconstants.nullid:
2418 if self._node == self._manifestlog.nodeconstants.nullid:
2419 self._data = treemanifest(self._manifestlog.nodeconstants)
2419 self._data = treemanifest(self._manifestlog.nodeconstants)
2420 # TODO accessing non-public API
2420 # TODO accessing non-public API
2421 elif store._treeondisk:
2421 elif store._treeondisk:
2422 m = treemanifest(self._manifestlog.nodeconstants, dir=self._dir)
2422 m = treemanifest(self._manifestlog.nodeconstants, dir=self._dir)
2423
2423
2424 def gettext():
2424 def gettext():
2425 return store.revision(self._node)
2425 return store.revision(self._node)
2426
2426
2427 def readsubtree(dir, subm):
2427 def readsubtree(dir, subm):
2428 # Set verify to False since we need to be able to create
2428 # Set verify to False since we need to be able to create
2429 # subtrees for trees that don't exist on disk.
2429 # subtrees for trees that don't exist on disk.
2430 return self._manifestlog.get(dir, subm, verify=False).read()
2430 return self._manifestlog.get(dir, subm, verify=False).read()
2431
2431
2432 m.read(gettext, readsubtree)
2432 m.read(gettext, readsubtree)
2433 m.setnode(self._node)
2433 m.setnode(self._node)
2434 self._data = m
2434 self._data = m
2435 else:
2435 else:
2436 if self._node in store.fulltextcache:
2436 if self._node in store.fulltextcache:
2437 text = pycompat.bytestr(store.fulltextcache[self._node])
2437 text = pycompat.bytestr(store.fulltextcache[self._node])
2438 else:
2438 else:
2439 text = store.revision(self._node)
2439 text = store.revision(self._node)
2440 arraytext = bytearray(text)
2440 arraytext = bytearray(text)
2441 store.fulltextcache[self._node] = arraytext
2441 store.fulltextcache[self._node] = arraytext
2442 self._data = treemanifest(
2442 self._data = treemanifest(
2443 self._manifestlog.nodeconstants, dir=self._dir, text=text
2443 self._manifestlog.nodeconstants, dir=self._dir, text=text
2444 )
2444 )
2445
2445
2446 return self._data
2446 return self._data
2447
2447
2448 def node(self) -> bytes:
2448 def node(self) -> bytes:
2449 return self._node
2449 return self._node
2450
2450
2451 def copy(self) -> 'memtreemanifestctx':
2451 def copy(self) -> 'memtreemanifestctx':
2452 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
2452 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
2453 memmf._treemanifest = self.read().copy()
2453 memmf._treemanifest = self.read().copy()
2454 return memmf
2454 return memmf
2455
2455
2456 @propertycache
2456 @propertycache
2457 def parents(self) -> Tuple[bytes, bytes]:
2457 def parents(self) -> Tuple[bytes, bytes]:
2458 return self._storage().parents(self._node)
2458 return self._storage().parents(self._node)
2459
2459
2460 def readdelta(self, shallow: bool = False) -> AnyManifestDict:
2460 def readdelta(self, shallow: bool = False) -> AnyManifestDict:
2461 """see `imanifestrevisionstored` documentation"""
2461 """see `imanifestrevisionstored` documentation"""
2462 util.nouideprecwarn(
2462 util.nouideprecwarn(
2463 b'"readdelta" is deprecated use "read_any_fast_delta" or "read_delta_new_entries"',
2463 b'"readdelta" is deprecated use "read_any_fast_delta" or "read_delta_new_entries"',
2464 b"6.9",
2464 b"6.9",
2465 stacklevel=2,
2465 stacklevel=2,
2466 )
2466 )
2467 store = self._storage()
2467 store = self._storage()
2468 if shallow:
2468 if shallow:
2469 r = store.rev(self._node)
2469 r = store.rev(self._node)
2470 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2470 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2471 return manifestdict(store.nodeconstants.nodelen, d)
2471 return manifestdict(store.nodeconstants.nodelen, d)
2472 else:
2472 else:
2473 # Need to perform a slow delta
2473 # Need to perform a slow delta
2474 r0 = store.deltaparent(store.rev(self._node))
2474 r0 = store.deltaparent(store.rev(self._node))
2475 m0 = self._manifestlog.get(self._dir, store.node(r0)).read()
2475 m0 = self._manifestlog.get(self._dir, store.node(r0)).read()
2476 m1 = self.read()
2476 m1 = self.read()
2477 md = treemanifest(self._manifestlog.nodeconstants, dir=self._dir)
2477 md = treemanifest(self._manifestlog.nodeconstants, dir=self._dir)
2478 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).items():
2478 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).items():
2479 if n1:
2479 if n1:
2480 md[f] = n1
2480 md[f] = n1
2481 if fl1:
2481 if fl1:
2482 md.setflag(f, fl1)
2482 md.setflag(f, fl1)
2483 return md
2483 return md
2484
2484
2485 def read_any_fast_delta(
2485 def read_any_fast_delta(
2486 self,
2486 self,
2487 valid_bases: Optional[Collection[int]] = None,
2487 valid_bases: Optional[Collection[int]] = None,
2488 *,
2488 *,
2489 shallow: bool = False,
2489 shallow: bool = False,
2490 ) -> Tuple[Optional[int], AnyManifestDict]:
2490 ) -> Tuple[Optional[int], AnyManifestDict]:
2491 """see `imanifestrevisionstored` documentation"""
2491 """see `imanifestrevisionstored` documentation"""
2492 store = self._storage()
2492 store = self._storage()
2493 r = store.rev(self._node)
2493 r = store.rev(self._node)
2494 deltaparent = store.deltaparent(r)
2494 deltaparent = store.deltaparent(r)
2495
2495
2496 if valid_bases is None:
2496 if valid_bases is None:
2497 # make sure the next check is True
2497 # make sure the next check is True
2498 valid_bases = (deltaparent,)
2498 valid_bases = (deltaparent,)
2499 can_use_delta = deltaparent != nullrev and deltaparent in valid_bases
2499 can_use_delta = deltaparent != nullrev and deltaparent in valid_bases
2500
2500
2501 if shallow:
2501 if shallow:
2502 if can_use_delta:
2502 if can_use_delta:
2503 return (deltaparent, self._read_storage_delta_shallow())
2503 return (deltaparent, self._read_storage_delta_shallow())
2504 else:
2504 else:
2505 d = store.revision(self._node)
2505 d = store.revision(self._node)
2506 return (None, manifestdict(store.nodeconstants.nodelen, d))
2506 return (None, manifestdict(store.nodeconstants.nodelen, d))
2507 else:
2507 else:
2508 # note: This use "slow_delta" here is cargo culted from the previous
2508 # note: This use "slow_delta" here is cargo culted from the previous
2509 # implementation. I am not sure it make sense since the goal here is to
2509 # implementation. I am not sure it make sense since the goal here is to
2510 # be fast, so why are we computing a delta? On the other hand, tree
2510 # be fast, so why are we computing a delta? On the other hand, tree
2511 # manifest delta as fairly "cheap" and allow for skipping whole part of
2511 # manifest delta as fairly "cheap" and allow for skipping whole part of
2512 # the tree that a full read would access. So it might be a good idea.
2512 # the tree that a full read would access. So it might be a good idea.
2513 #
2513 #
2514 # If we realize we don't need delta here, we should simply use:
2514 # If we realize we don't need delta here, we should simply use:
2515 #
2515 #
2516 # return (None, self.read())
2516 # return (None, self.read())
2517 if can_use_delta:
2517 if can_use_delta:
2518 return (None, self._read_storage_slow_delta(base=deltaparent))
2518 return (None, self._read_storage_slow_delta(base=deltaparent))
2519 else:
2519 else:
2520 parents = [
2520 parents = [
2521 p
2521 p
2522 for p in store.parentrevs(r)
2522 for p in store.parentrevs(r)
2523 if p is not nullrev and p in valid_bases
2523 if p is not nullrev and p in valid_bases
2524 ]
2524 ]
2525 if parents:
2525 if parents:
2526 best_base = max(parents)
2526 best_base = max(parents)
2527 else:
2527 else:
2528 best_base = max(valid_bases)
2528 best_base = max(valid_bases)
2529 return (None, self._read_storage_slow_delta(base=best_base))
2529 return (None, self._read_storage_slow_delta(base=best_base))
2530
2530
2531 def _read_storage_delta_shallow(self) -> manifestdict:
2531 def _read_storage_delta_shallow(self) -> manifestdict:
2532 store = self._storage()
2532 store = self._storage()
2533 r = store.rev(self._node)
2533 r = store.rev(self._node)
2534 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2534 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2535 return manifestdict(store.nodeconstants.nodelen, d)
2535 return manifestdict(store.nodeconstants.nodelen, d)
2536
2536
2537 def _read_storage_slow_delta(self, base) -> 'treemanifest':
2537 def _read_storage_slow_delta(self, base) -> 'treemanifest':
2538 store = self._storage()
2538 store = self._storage()
2539 if base is None:
2539 if base is None:
2540 base = store.deltaparent(store.rev(self._node))
2540 base = store.deltaparent(store.rev(self._node))
2541 m0 = self._manifestlog.get(self._dir, store.node(base)).read()
2541 m0 = self._manifestlog.get(self._dir, store.node(base)).read()
2542 m1 = self.read()
2542 m1 = self.read()
2543 md = treemanifest(self._manifestlog.nodeconstants, dir=self._dir)
2543 md = treemanifest(self._manifestlog.nodeconstants, dir=self._dir)
2544 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).items():
2544 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).items():
2545 if n1:
2545 if n1:
2546 md[f] = n1
2546 md[f] = n1
2547 if fl1:
2547 if fl1:
2548 md.setflag(f, fl1)
2548 md.setflag(f, fl1)
2549 return md
2549 return md
2550
2550
2551 def read_delta_parents(
2551 def read_delta_parents(
2552 self,
2552 self,
2553 *,
2553 *,
2554 shallow: bool = False,
2554 shallow: bool = False,
2555 exact: bool = True,
2555 exact: bool = True,
2556 ) -> AnyManifestDict:
2556 ) -> AnyManifestDict:
2557 """see `interface.imanifestrevisionbase` documentations"""
2557 """see `interface.imanifestrevisionbase` documentations"""
2558 store = self._storage()
2558 store = self._storage()
2559 r = store.rev(self._node)
2559 r = store.rev(self._node)
2560 parents = [p for p in store.parentrevs(r) if p is not nullrev]
2560 parents = [p for p in store.parentrevs(r) if p is not nullrev]
2561 if not exact:
2561 if not exact:
2562 return self.read_any_fast_delta(parents, shallow=shallow)[1]
2562 return self.read_any_fast_delta(parents, shallow=shallow)[1]
2563 elif len(parents) == 0:
2563 elif len(parents) == 0:
2564 if shallow:
2564 if shallow:
2565 d = store.revision(self._node)
2565 d = store.revision(self._node)
2566 return manifestdict(store.nodeconstants.nodelen, d)
2566 return manifestdict(store.nodeconstants.nodelen, d)
2567 else:
2567 else:
2568 return self.read()
2568 return self.read()
2569 elif len(parents) == 1:
2569 elif len(parents) == 1:
2570 p = parents[0]
2570 p = parents[0]
2571 if shallow:
2571 if shallow:
2572 d = mdiff.patchtext(store.revdiff(p, r))
2572 d = mdiff.patchtext(store.revdiff(p, r))
2573 return manifestdict(store.nodeconstants.nodelen, d)
2573 return manifestdict(store.nodeconstants.nodelen, d)
2574 else:
2574 else:
2575 return self._read_storage_slow_delta(base=p)
2575 return self._read_storage_slow_delta(base=p)
2576 else:
2576 else:
2577 p1, p2 = parents
2577 p1, p2 = parents
2578 if shallow:
2578 if shallow:
2579 d1 = mdiff.patchtext(store.revdiff(p1, r))
2579 d1 = mdiff.patchtext(store.revdiff(p1, r))
2580 d2 = mdiff.patchtext(store.revdiff(p2, r))
2580 d2 = mdiff.patchtext(store.revdiff(p2, r))
2581 d1 = manifestdict(store.nodeconstants.nodelen, d1)
2581 d1 = manifestdict(store.nodeconstants.nodelen, d1)
2582 d2 = manifestdict(store.nodeconstants.nodelen, d2)
2582 d2 = manifestdict(store.nodeconstants.nodelen, d2)
2583 md = manifestdict(store.nodeconstants.nodelen)
2583 md = manifestdict(store.nodeconstants.nodelen)
2584 for f, new_node, new_flag in d1.iterentries():
2584 for f, new_node, new_flag in d1.iterentries():
2585 if f not in d2:
2585 if f not in d2:
2586 continue
2586 continue
2587 if new_node is not None:
2587 if new_node is not None:
2588 md.set(f, new_node, new_flag)
2588 md.set(f, new_node, new_flag)
2589 return md
2589 return md
2590 else:
2590 else:
2591 m1 = self._manifestlog.get(self._dir, store.node(p1)).read()
2591 m1 = self._manifestlog.get(self._dir, store.node(p1)).read()
2592 m2 = self._manifestlog.get(self._dir, store.node(p2)).read()
2592 m2 = self._manifestlog.get(self._dir, store.node(p2)).read()
2593 mc = self.read()
2593 mc = self.read()
2594 d1 = m1.diff(mc)
2594 d1 = m1.diff(mc)
2595 d2 = m2.diff(mc)
2595 d2 = m2.diff(mc)
2596 md = treemanifest(
2596 md = treemanifest(
2597 self._manifestlog.nodeconstants,
2597 self._manifestlog.nodeconstants,
2598 dir=self._dir,
2598 dir=self._dir,
2599 )
2599 )
2600 for f, new_node, new_flag in d1.iterentries():
2600 for f, new_node, new_flag in d1.iterentries():
2601 if f not in d2:
2601 if f not in d2:
2602 continue
2602 continue
2603 if new_node is not None:
2603 if new_node is not None:
2604 md.set(f, new_node, new_flag)
2604 md.set(f, new_node, new_flag)
2605 return md
2605 return md
2606
2606
2607 def read_delta_new_entries(
2607 def read_delta_new_entries(
2608 self, *, shallow: bool = False
2608 self, *, shallow: bool = False
2609 ) -> AnyManifestDict:
2609 ) -> AnyManifestDict:
2610 """see `interface.imanifestrevisionbase` documentations"""
2610 """see `interface.imanifestrevisionbase` documentations"""
2611 # If we are using narrow, returning a delta against an arbitrary
2611 # If we are using narrow, returning a delta against an arbitrary
2612 # changeset might return file outside the narrowspec. This can create
2612 # changeset might return file outside the narrowspec. This can create
2613 # issue when running validation server side with strict security as
2613 # issue when running validation server side with strict security as
2614 # push from low priviledge usage might be seen as adding new revision
2614 # push from low priviledge usage might be seen as adding new revision
2615 # for files they cannot touch. So we are strict if narrow is involved.
2615 # for files they cannot touch. So we are strict if narrow is involved.
2616 if self._manifestlog.narrowed:
2616 if self._manifestlog.narrowed:
2617 return self.read_delta_parents(shallow=shallow, exact=True)
2617 return self.read_delta_parents(shallow=shallow, exact=True)
2618 # delegate to existing another existing method for simplicity
2618 # delegate to existing another existing method for simplicity
2619 store = self._storage()
2619 store = self._storage()
2620 r = store.rev(self._node)
2620 r = store.rev(self._node)
2621 bases = (store.deltaparent(r),)
2621 bases = (store.deltaparent(r),)
2622 return self.read_any_fast_delta(bases, shallow=shallow)[1]
2622 return self.read_any_fast_delta(bases, shallow=shallow)[1]
2623
2623
2624 def readfast(self, shallow=False) -> AnyManifestDict:
2624 def readfast(self, shallow: bool = False) -> AnyManifestDict:
2625 """Calls either readdelta or read, based on which would be less work.
2625 """Calls either readdelta or read, based on which would be less work.
2626 readdelta is called if the delta is against the p1, and therefore can be
2626 readdelta is called if the delta is against the p1, and therefore can be
2627 read quickly.
2627 read quickly.
2628
2628
2629 If `shallow` is True, it only returns the entries from this manifest,
2629 If `shallow` is True, it only returns the entries from this manifest,
2630 and not any submanifests.
2630 and not any submanifests.
2631 """
2631 """
2632 util.nouideprecwarn(
2632 util.nouideprecwarn(
2633 b'"readdelta" is deprecated use "read_any_fast_delta" or "read_delta_parents"',
2633 b'"readdelta" is deprecated use "read_any_fast_delta" or "read_delta_parents"',
2634 b"6.9",
2634 b"6.9",
2635 stacklevel=2,
2635 stacklevel=2,
2636 )
2636 )
2637 store = self._storage()
2637 store = self._storage()
2638 r = store.rev(self._node)
2638 r = store.rev(self._node)
2639 deltaparent = store.deltaparent(r)
2639 deltaparent = store.deltaparent(r)
2640 if deltaparent != nullrev and deltaparent in store.parentrevs(r):
2640 if deltaparent != nullrev and deltaparent in store.parentrevs(r):
2641 return self.readdelta(shallow=shallow)
2641 return self.readdelta(shallow=shallow)
2642
2642
2643 if shallow:
2643 if shallow:
2644 return manifestdict(
2644 return manifestdict(
2645 store.nodeconstants.nodelen, store.revision(self._node)
2645 store.nodeconstants.nodelen, store.revision(self._node)
2646 )
2646 )
2647 else:
2647 else:
2648 return self.read()
2648 return self.read()
2649
2649
2650 def find(self, key: bytes) -> Tuple[bytes, bytes]:
2650 def find(self, key: bytes) -> Tuple[bytes, bytes]:
2651 return self.read().find(key)
2651 return self.read().find(key)
2652
2652
2653
2653
2654 class excludeddir(treemanifest):
2654 class excludeddir(treemanifest):
2655 """Stand-in for a directory that is excluded from the repository.
2655 """Stand-in for a directory that is excluded from the repository.
2656
2656
2657 With narrowing active on a repository that uses treemanifests,
2657 With narrowing active on a repository that uses treemanifests,
2658 some of the directory revlogs will be excluded from the resulting
2658 some of the directory revlogs will be excluded from the resulting
2659 clone. This is a huge storage win for clients, but means we need
2659 clone. This is a huge storage win for clients, but means we need
2660 some sort of pseudo-manifest to surface to internals so we can
2660 some sort of pseudo-manifest to surface to internals so we can
2661 detect a merge conflict outside the narrowspec. That's what this
2661 detect a merge conflict outside the narrowspec. That's what this
2662 class is: it stands in for a directory whose node is known, but
2662 class is: it stands in for a directory whose node is known, but
2663 whose contents are unknown.
2663 whose contents are unknown.
2664 """
2664 """
2665
2665
2666 _files: Dict[bytes, bytes]
2666 _files: Dict[bytes, bytes]
2667 _flags: Dict[bytes, bytes]
2667 _flags: Dict[bytes, bytes]
2668
2668
2669 def __init__(self, nodeconstants, dir, node):
2669 def __init__(self, nodeconstants, dir, node):
2670 super(excludeddir, self).__init__(nodeconstants, dir)
2670 super(excludeddir, self).__init__(nodeconstants, dir)
2671 self._node = node
2671 self._node = node
2672 # Add an empty file, which will be included by iterators and such,
2672 # Add an empty file, which will be included by iterators and such,
2673 # appearing as the directory itself (i.e. something like "dir/")
2673 # appearing as the directory itself (i.e. something like "dir/")
2674 self._files[b''] = node
2674 self._files[b''] = node
2675 self._flags[b''] = b't'
2675 self._flags[b''] = b't'
2676
2676
2677 # Manifests outside the narrowspec should never be modified, so avoid
2677 # Manifests outside the narrowspec should never be modified, so avoid
2678 # copying. This makes a noticeable difference when there are very many
2678 # copying. This makes a noticeable difference when there are very many
2679 # directories outside the narrowspec. Also, it makes sense for the copy to
2679 # directories outside the narrowspec. Also, it makes sense for the copy to
2680 # be of the same type as the original, which would not happen with the
2680 # be of the same type as the original, which would not happen with the
2681 # super type's copy().
2681 # super type's copy().
2682 def copy(self):
2682 def copy(self):
2683 return self
2683 return self
2684
2684
2685
2685
2686 class excludeddirmanifestctx(treemanifestctx):
2686 class excludeddirmanifestctx(treemanifestctx):
2687 """context wrapper for excludeddir - see that docstring for rationale"""
2687 """context wrapper for excludeddir - see that docstring for rationale"""
2688
2688
2689 def __init__(self, nodeconstants, dir, node):
2689 def __init__(self, nodeconstants, dir, node):
2690 self.nodeconstants = nodeconstants
2690 self.nodeconstants = nodeconstants
2691 self._dir = dir
2691 self._dir = dir
2692 self._node = node
2692 self._node = node
2693
2693
2694 def read(self):
2694 def read(self):
2695 return excludeddir(self.nodeconstants, self._dir, self._node)
2695 return excludeddir(self.nodeconstants, self._dir, self._node)
2696
2696
2697 def readfast(self, shallow=False):
2697 def readfast(self, shallow: bool = False):
2698 # special version of readfast since we don't have underlying storage
2698 # special version of readfast since we don't have underlying storage
2699 return self.read()
2699 return self.read()
2700
2700
2701 def write(self, *args):
2701 def write(self, *args):
2702 raise error.ProgrammingError(
2702 raise error.ProgrammingError(
2703 b'attempt to write manifest from excluded dir %s' % self._dir
2703 b'attempt to write manifest from excluded dir %s' % self._dir
2704 )
2704 )
2705
2705
2706
2706
2707 class excludedmanifestrevlog(manifestrevlog):
2707 class excludedmanifestrevlog(manifestrevlog):
2708 """Stand-in for excluded treemanifest revlogs.
2708 """Stand-in for excluded treemanifest revlogs.
2709
2709
2710 When narrowing is active on a treemanifest repository, we'll have
2710 When narrowing is active on a treemanifest repository, we'll have
2711 references to directories we can't see due to the revlog being
2711 references to directories we can't see due to the revlog being
2712 skipped. This class exists to conform to the manifestrevlog
2712 skipped. This class exists to conform to the manifestrevlog
2713 interface for those directories and proactively prevent writes to
2713 interface for those directories and proactively prevent writes to
2714 outside the narrowspec.
2714 outside the narrowspec.
2715 """
2715 """
2716
2716
2717 def __init__(self, nodeconstants, dir):
2717 def __init__(self, nodeconstants, dir):
2718 self.nodeconstants = nodeconstants
2718 self.nodeconstants = nodeconstants
2719 self._dir = dir
2719 self._dir = dir
2720
2720
2721 def __len__(self):
2721 def __len__(self):
2722 raise error.ProgrammingError(
2722 raise error.ProgrammingError(
2723 b'attempt to get length of excluded dir %s' % self._dir
2723 b'attempt to get length of excluded dir %s' % self._dir
2724 )
2724 )
2725
2725
2726 def rev(self, node):
2726 def rev(self, node):
2727 raise error.ProgrammingError(
2727 raise error.ProgrammingError(
2728 b'attempt to get rev from excluded dir %s' % self._dir
2728 b'attempt to get rev from excluded dir %s' % self._dir
2729 )
2729 )
2730
2730
2731 def linkrev(self, node):
2731 def linkrev(self, node):
2732 raise error.ProgrammingError(
2732 raise error.ProgrammingError(
2733 b'attempt to get linkrev from excluded dir %s' % self._dir
2733 b'attempt to get linkrev from excluded dir %s' % self._dir
2734 )
2734 )
2735
2735
2736 def node(self, rev):
2736 def node(self, rev):
2737 raise error.ProgrammingError(
2737 raise error.ProgrammingError(
2738 b'attempt to get node from excluded dir %s' % self._dir
2738 b'attempt to get node from excluded dir %s' % self._dir
2739 )
2739 )
2740
2740
2741 def add(self, *args, **kwargs):
2741 def add(self, *args, **kwargs):
2742 # We should never write entries in dirlogs outside the narrow clone.
2742 # We should never write entries in dirlogs outside the narrow clone.
2743 # However, the method still gets called from writesubtree() in
2743 # However, the method still gets called from writesubtree() in
2744 # _addtree(), so we need to handle it. We should possibly make that
2744 # _addtree(), so we need to handle it. We should possibly make that
2745 # avoid calling add() with a clean manifest (_dirty is always False
2745 # avoid calling add() with a clean manifest (_dirty is always False
2746 # in excludeddir instances).
2746 # in excludeddir instances).
2747 pass
2747 pass
General Comments 0
You need to be logged in to leave comments. Login now