##// END OF EJS Templates
revlog: skip opener options to pass max_deltachain_span...
marmoute -
r51930:d900f744 default
parent child Browse files
Show More
@@ -1,4045 +1,4045 b''
1 # localrepo.py - read/write repository class for mercurial
1 # localrepo.py - read/write repository class for mercurial
2 # coding: utf-8
2 # coding: utf-8
3 #
3 #
4 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
4 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.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
9
10 import functools
10 import functools
11 import os
11 import os
12 import random
12 import random
13 import re
13 import re
14 import sys
14 import sys
15 import time
15 import time
16 import weakref
16 import weakref
17
17
18 from concurrent import futures
18 from concurrent import futures
19 from typing import (
19 from typing import (
20 Optional,
20 Optional,
21 )
21 )
22
22
23 from .i18n import _
23 from .i18n import _
24 from .node import (
24 from .node import (
25 bin,
25 bin,
26 hex,
26 hex,
27 nullrev,
27 nullrev,
28 sha1nodeconstants,
28 sha1nodeconstants,
29 short,
29 short,
30 )
30 )
31 from . import (
31 from . import (
32 bookmarks,
32 bookmarks,
33 branchmap,
33 branchmap,
34 bundle2,
34 bundle2,
35 bundlecaches,
35 bundlecaches,
36 changegroup,
36 changegroup,
37 color,
37 color,
38 commit,
38 commit,
39 context,
39 context,
40 dirstate,
40 dirstate,
41 discovery,
41 discovery,
42 encoding,
42 encoding,
43 error,
43 error,
44 exchange,
44 exchange,
45 extensions,
45 extensions,
46 filelog,
46 filelog,
47 hook,
47 hook,
48 lock as lockmod,
48 lock as lockmod,
49 match as matchmod,
49 match as matchmod,
50 mergestate as mergestatemod,
50 mergestate as mergestatemod,
51 mergeutil,
51 mergeutil,
52 namespaces,
52 namespaces,
53 narrowspec,
53 narrowspec,
54 obsolete,
54 obsolete,
55 pathutil,
55 pathutil,
56 phases,
56 phases,
57 policy,
57 policy,
58 pushkey,
58 pushkey,
59 pycompat,
59 pycompat,
60 rcutil,
60 rcutil,
61 repoview,
61 repoview,
62 requirements as requirementsmod,
62 requirements as requirementsmod,
63 revlog,
63 revlog,
64 revset,
64 revset,
65 revsetlang,
65 revsetlang,
66 scmutil,
66 scmutil,
67 sparse,
67 sparse,
68 store as storemod,
68 store as storemod,
69 subrepoutil,
69 subrepoutil,
70 tags as tagsmod,
70 tags as tagsmod,
71 transaction,
71 transaction,
72 txnutil,
72 txnutil,
73 util,
73 util,
74 vfs as vfsmod,
74 vfs as vfsmod,
75 wireprototypes,
75 wireprototypes,
76 )
76 )
77
77
78 from .interfaces import (
78 from .interfaces import (
79 repository,
79 repository,
80 util as interfaceutil,
80 util as interfaceutil,
81 )
81 )
82
82
83 from .utils import (
83 from .utils import (
84 hashutil,
84 hashutil,
85 procutil,
85 procutil,
86 stringutil,
86 stringutil,
87 urlutil,
87 urlutil,
88 )
88 )
89
89
90 from .revlogutils import (
90 from .revlogutils import (
91 concurrency_checker as revlogchecker,
91 concurrency_checker as revlogchecker,
92 constants as revlogconst,
92 constants as revlogconst,
93 sidedata as sidedatamod,
93 sidedata as sidedatamod,
94 )
94 )
95
95
96 release = lockmod.release
96 release = lockmod.release
97 urlerr = util.urlerr
97 urlerr = util.urlerr
98 urlreq = util.urlreq
98 urlreq = util.urlreq
99
99
100 RE_SKIP_DIRSTATE_ROLLBACK = re.compile(
100 RE_SKIP_DIRSTATE_ROLLBACK = re.compile(
101 b"^((dirstate|narrowspec.dirstate).*|branch$)"
101 b"^((dirstate|narrowspec.dirstate).*|branch$)"
102 )
102 )
103
103
104 # set of (path, vfs-location) tuples. vfs-location is:
104 # set of (path, vfs-location) tuples. vfs-location is:
105 # - 'plain for vfs relative paths
105 # - 'plain for vfs relative paths
106 # - '' for svfs relative paths
106 # - '' for svfs relative paths
107 _cachedfiles = set()
107 _cachedfiles = set()
108
108
109
109
110 class _basefilecache(scmutil.filecache):
110 class _basefilecache(scmutil.filecache):
111 """All filecache usage on repo are done for logic that should be unfiltered"""
111 """All filecache usage on repo are done for logic that should be unfiltered"""
112
112
113 def __get__(self, repo, type=None):
113 def __get__(self, repo, type=None):
114 if repo is None:
114 if repo is None:
115 return self
115 return self
116 # proxy to unfiltered __dict__ since filtered repo has no entry
116 # proxy to unfiltered __dict__ since filtered repo has no entry
117 unfi = repo.unfiltered()
117 unfi = repo.unfiltered()
118 try:
118 try:
119 return unfi.__dict__[self.sname]
119 return unfi.__dict__[self.sname]
120 except KeyError:
120 except KeyError:
121 pass
121 pass
122 return super(_basefilecache, self).__get__(unfi, type)
122 return super(_basefilecache, self).__get__(unfi, type)
123
123
124 def set(self, repo, value):
124 def set(self, repo, value):
125 return super(_basefilecache, self).set(repo.unfiltered(), value)
125 return super(_basefilecache, self).set(repo.unfiltered(), value)
126
126
127
127
128 class repofilecache(_basefilecache):
128 class repofilecache(_basefilecache):
129 """filecache for files in .hg but outside of .hg/store"""
129 """filecache for files in .hg but outside of .hg/store"""
130
130
131 def __init__(self, *paths):
131 def __init__(self, *paths):
132 super(repofilecache, self).__init__(*paths)
132 super(repofilecache, self).__init__(*paths)
133 for path in paths:
133 for path in paths:
134 _cachedfiles.add((path, b'plain'))
134 _cachedfiles.add((path, b'plain'))
135
135
136 def join(self, obj, fname):
136 def join(self, obj, fname):
137 return obj.vfs.join(fname)
137 return obj.vfs.join(fname)
138
138
139
139
140 class storecache(_basefilecache):
140 class storecache(_basefilecache):
141 """filecache for files in the store"""
141 """filecache for files in the store"""
142
142
143 def __init__(self, *paths):
143 def __init__(self, *paths):
144 super(storecache, self).__init__(*paths)
144 super(storecache, self).__init__(*paths)
145 for path in paths:
145 for path in paths:
146 _cachedfiles.add((path, b''))
146 _cachedfiles.add((path, b''))
147
147
148 def join(self, obj, fname):
148 def join(self, obj, fname):
149 return obj.sjoin(fname)
149 return obj.sjoin(fname)
150
150
151
151
152 class changelogcache(storecache):
152 class changelogcache(storecache):
153 """filecache for the changelog"""
153 """filecache for the changelog"""
154
154
155 def __init__(self):
155 def __init__(self):
156 super(changelogcache, self).__init__()
156 super(changelogcache, self).__init__()
157 _cachedfiles.add((b'00changelog.i', b''))
157 _cachedfiles.add((b'00changelog.i', b''))
158 _cachedfiles.add((b'00changelog.n', b''))
158 _cachedfiles.add((b'00changelog.n', b''))
159
159
160 def tracked_paths(self, obj):
160 def tracked_paths(self, obj):
161 paths = [self.join(obj, b'00changelog.i')]
161 paths = [self.join(obj, b'00changelog.i')]
162 if obj.store.opener.options.get(b'persistent-nodemap', False):
162 if obj.store.opener.options.get(b'persistent-nodemap', False):
163 paths.append(self.join(obj, b'00changelog.n'))
163 paths.append(self.join(obj, b'00changelog.n'))
164 return paths
164 return paths
165
165
166
166
167 class manifestlogcache(storecache):
167 class manifestlogcache(storecache):
168 """filecache for the manifestlog"""
168 """filecache for the manifestlog"""
169
169
170 def __init__(self):
170 def __init__(self):
171 super(manifestlogcache, self).__init__()
171 super(manifestlogcache, self).__init__()
172 _cachedfiles.add((b'00manifest.i', b''))
172 _cachedfiles.add((b'00manifest.i', b''))
173 _cachedfiles.add((b'00manifest.n', b''))
173 _cachedfiles.add((b'00manifest.n', b''))
174
174
175 def tracked_paths(self, obj):
175 def tracked_paths(self, obj):
176 paths = [self.join(obj, b'00manifest.i')]
176 paths = [self.join(obj, b'00manifest.i')]
177 if obj.store.opener.options.get(b'persistent-nodemap', False):
177 if obj.store.opener.options.get(b'persistent-nodemap', False):
178 paths.append(self.join(obj, b'00manifest.n'))
178 paths.append(self.join(obj, b'00manifest.n'))
179 return paths
179 return paths
180
180
181
181
182 class mixedrepostorecache(_basefilecache):
182 class mixedrepostorecache(_basefilecache):
183 """filecache for a mix files in .hg/store and outside"""
183 """filecache for a mix files in .hg/store and outside"""
184
184
185 def __init__(self, *pathsandlocations):
185 def __init__(self, *pathsandlocations):
186 # scmutil.filecache only uses the path for passing back into our
186 # scmutil.filecache only uses the path for passing back into our
187 # join(), so we can safely pass a list of paths and locations
187 # join(), so we can safely pass a list of paths and locations
188 super(mixedrepostorecache, self).__init__(*pathsandlocations)
188 super(mixedrepostorecache, self).__init__(*pathsandlocations)
189 _cachedfiles.update(pathsandlocations)
189 _cachedfiles.update(pathsandlocations)
190
190
191 def join(self, obj, fnameandlocation):
191 def join(self, obj, fnameandlocation):
192 fname, location = fnameandlocation
192 fname, location = fnameandlocation
193 if location == b'plain':
193 if location == b'plain':
194 return obj.vfs.join(fname)
194 return obj.vfs.join(fname)
195 else:
195 else:
196 if location != b'':
196 if location != b'':
197 raise error.ProgrammingError(
197 raise error.ProgrammingError(
198 b'unexpected location: %s' % location
198 b'unexpected location: %s' % location
199 )
199 )
200 return obj.sjoin(fname)
200 return obj.sjoin(fname)
201
201
202
202
203 def isfilecached(repo, name):
203 def isfilecached(repo, name):
204 """check if a repo has already cached "name" filecache-ed property
204 """check if a repo has already cached "name" filecache-ed property
205
205
206 This returns (cachedobj-or-None, iscached) tuple.
206 This returns (cachedobj-or-None, iscached) tuple.
207 """
207 """
208 cacheentry = repo.unfiltered()._filecache.get(name, None)
208 cacheentry = repo.unfiltered()._filecache.get(name, None)
209 if not cacheentry:
209 if not cacheentry:
210 return None, False
210 return None, False
211 return cacheentry.obj, True
211 return cacheentry.obj, True
212
212
213
213
214 class unfilteredpropertycache(util.propertycache):
214 class unfilteredpropertycache(util.propertycache):
215 """propertycache that apply to unfiltered repo only"""
215 """propertycache that apply to unfiltered repo only"""
216
216
217 def __get__(self, repo, type=None):
217 def __get__(self, repo, type=None):
218 unfi = repo.unfiltered()
218 unfi = repo.unfiltered()
219 if unfi is repo:
219 if unfi is repo:
220 return super(unfilteredpropertycache, self).__get__(unfi)
220 return super(unfilteredpropertycache, self).__get__(unfi)
221 return getattr(unfi, self.name)
221 return getattr(unfi, self.name)
222
222
223
223
224 class filteredpropertycache(util.propertycache):
224 class filteredpropertycache(util.propertycache):
225 """propertycache that must take filtering in account"""
225 """propertycache that must take filtering in account"""
226
226
227 def cachevalue(self, obj, value):
227 def cachevalue(self, obj, value):
228 object.__setattr__(obj, self.name, value)
228 object.__setattr__(obj, self.name, value)
229
229
230
230
231 def hasunfilteredcache(repo, name):
231 def hasunfilteredcache(repo, name):
232 """check if a repo has an unfilteredpropertycache value for <name>"""
232 """check if a repo has an unfilteredpropertycache value for <name>"""
233 return name in vars(repo.unfiltered())
233 return name in vars(repo.unfiltered())
234
234
235
235
236 def unfilteredmethod(orig):
236 def unfilteredmethod(orig):
237 """decorate method that always need to be run on unfiltered version"""
237 """decorate method that always need to be run on unfiltered version"""
238
238
239 @functools.wraps(orig)
239 @functools.wraps(orig)
240 def wrapper(repo, *args, **kwargs):
240 def wrapper(repo, *args, **kwargs):
241 return orig(repo.unfiltered(), *args, **kwargs)
241 return orig(repo.unfiltered(), *args, **kwargs)
242
242
243 return wrapper
243 return wrapper
244
244
245
245
246 moderncaps = {
246 moderncaps = {
247 b'lookup',
247 b'lookup',
248 b'branchmap',
248 b'branchmap',
249 b'pushkey',
249 b'pushkey',
250 b'known',
250 b'known',
251 b'getbundle',
251 b'getbundle',
252 b'unbundle',
252 b'unbundle',
253 }
253 }
254 legacycaps = moderncaps.union({b'changegroupsubset'})
254 legacycaps = moderncaps.union({b'changegroupsubset'})
255
255
256
256
257 @interfaceutil.implementer(repository.ipeercommandexecutor)
257 @interfaceutil.implementer(repository.ipeercommandexecutor)
258 class localcommandexecutor:
258 class localcommandexecutor:
259 def __init__(self, peer):
259 def __init__(self, peer):
260 self._peer = peer
260 self._peer = peer
261 self._sent = False
261 self._sent = False
262 self._closed = False
262 self._closed = False
263
263
264 def __enter__(self):
264 def __enter__(self):
265 return self
265 return self
266
266
267 def __exit__(self, exctype, excvalue, exctb):
267 def __exit__(self, exctype, excvalue, exctb):
268 self.close()
268 self.close()
269
269
270 def callcommand(self, command, args):
270 def callcommand(self, command, args):
271 if self._sent:
271 if self._sent:
272 raise error.ProgrammingError(
272 raise error.ProgrammingError(
273 b'callcommand() cannot be used after sendcommands()'
273 b'callcommand() cannot be used after sendcommands()'
274 )
274 )
275
275
276 if self._closed:
276 if self._closed:
277 raise error.ProgrammingError(
277 raise error.ProgrammingError(
278 b'callcommand() cannot be used after close()'
278 b'callcommand() cannot be used after close()'
279 )
279 )
280
280
281 # We don't need to support anything fancy. Just call the named
281 # We don't need to support anything fancy. Just call the named
282 # method on the peer and return a resolved future.
282 # method on the peer and return a resolved future.
283 fn = getattr(self._peer, pycompat.sysstr(command))
283 fn = getattr(self._peer, pycompat.sysstr(command))
284
284
285 f = futures.Future()
285 f = futures.Future()
286
286
287 try:
287 try:
288 result = fn(**pycompat.strkwargs(args))
288 result = fn(**pycompat.strkwargs(args))
289 except Exception:
289 except Exception:
290 pycompat.future_set_exception_info(f, sys.exc_info()[1:])
290 pycompat.future_set_exception_info(f, sys.exc_info()[1:])
291 else:
291 else:
292 f.set_result(result)
292 f.set_result(result)
293
293
294 return f
294 return f
295
295
296 def sendcommands(self):
296 def sendcommands(self):
297 self._sent = True
297 self._sent = True
298
298
299 def close(self):
299 def close(self):
300 self._closed = True
300 self._closed = True
301
301
302
302
303 @interfaceutil.implementer(repository.ipeercommands)
303 @interfaceutil.implementer(repository.ipeercommands)
304 class localpeer(repository.peer):
304 class localpeer(repository.peer):
305 '''peer for a local repo; reflects only the most recent API'''
305 '''peer for a local repo; reflects only the most recent API'''
306
306
307 def __init__(self, repo, caps=None, path=None, remotehidden=False):
307 def __init__(self, repo, caps=None, path=None, remotehidden=False):
308 super(localpeer, self).__init__(
308 super(localpeer, self).__init__(
309 repo.ui, path=path, remotehidden=remotehidden
309 repo.ui, path=path, remotehidden=remotehidden
310 )
310 )
311
311
312 if caps is None:
312 if caps is None:
313 caps = moderncaps.copy()
313 caps = moderncaps.copy()
314 if remotehidden:
314 if remotehidden:
315 self._repo = repo.filtered(b'served.hidden')
315 self._repo = repo.filtered(b'served.hidden')
316 else:
316 else:
317 self._repo = repo.filtered(b'served')
317 self._repo = repo.filtered(b'served')
318 if repo._wanted_sidedata:
318 if repo._wanted_sidedata:
319 formatted = bundle2.format_remote_wanted_sidedata(repo)
319 formatted = bundle2.format_remote_wanted_sidedata(repo)
320 caps.add(b'exp-wanted-sidedata=' + formatted)
320 caps.add(b'exp-wanted-sidedata=' + formatted)
321
321
322 self._caps = repo._restrictcapabilities(caps)
322 self._caps = repo._restrictcapabilities(caps)
323
323
324 # Begin of _basepeer interface.
324 # Begin of _basepeer interface.
325
325
326 def url(self):
326 def url(self):
327 return self._repo.url()
327 return self._repo.url()
328
328
329 def local(self):
329 def local(self):
330 return self._repo
330 return self._repo
331
331
332 def canpush(self):
332 def canpush(self):
333 return True
333 return True
334
334
335 def close(self):
335 def close(self):
336 self._repo.close()
336 self._repo.close()
337
337
338 # End of _basepeer interface.
338 # End of _basepeer interface.
339
339
340 # Begin of _basewirecommands interface.
340 # Begin of _basewirecommands interface.
341
341
342 def branchmap(self):
342 def branchmap(self):
343 return self._repo.branchmap()
343 return self._repo.branchmap()
344
344
345 def capabilities(self):
345 def capabilities(self):
346 return self._caps
346 return self._caps
347
347
348 def get_cached_bundle_inline(self, path):
348 def get_cached_bundle_inline(self, path):
349 # not needed with local peer
349 # not needed with local peer
350 raise NotImplementedError
350 raise NotImplementedError
351
351
352 def clonebundles(self):
352 def clonebundles(self):
353 return bundlecaches.get_manifest(self._repo)
353 return bundlecaches.get_manifest(self._repo)
354
354
355 def debugwireargs(self, one, two, three=None, four=None, five=None):
355 def debugwireargs(self, one, two, three=None, four=None, five=None):
356 """Used to test argument passing over the wire"""
356 """Used to test argument passing over the wire"""
357 return b"%s %s %s %s %s" % (
357 return b"%s %s %s %s %s" % (
358 one,
358 one,
359 two,
359 two,
360 pycompat.bytestr(three),
360 pycompat.bytestr(three),
361 pycompat.bytestr(four),
361 pycompat.bytestr(four),
362 pycompat.bytestr(five),
362 pycompat.bytestr(five),
363 )
363 )
364
364
365 def getbundle(
365 def getbundle(
366 self,
366 self,
367 source,
367 source,
368 heads=None,
368 heads=None,
369 common=None,
369 common=None,
370 bundlecaps=None,
370 bundlecaps=None,
371 remote_sidedata=None,
371 remote_sidedata=None,
372 **kwargs
372 **kwargs
373 ):
373 ):
374 chunks = exchange.getbundlechunks(
374 chunks = exchange.getbundlechunks(
375 self._repo,
375 self._repo,
376 source,
376 source,
377 heads=heads,
377 heads=heads,
378 common=common,
378 common=common,
379 bundlecaps=bundlecaps,
379 bundlecaps=bundlecaps,
380 remote_sidedata=remote_sidedata,
380 remote_sidedata=remote_sidedata,
381 **kwargs
381 **kwargs
382 )[1]
382 )[1]
383 cb = util.chunkbuffer(chunks)
383 cb = util.chunkbuffer(chunks)
384
384
385 if exchange.bundle2requested(bundlecaps):
385 if exchange.bundle2requested(bundlecaps):
386 # When requesting a bundle2, getbundle returns a stream to make the
386 # When requesting a bundle2, getbundle returns a stream to make the
387 # wire level function happier. We need to build a proper object
387 # wire level function happier. We need to build a proper object
388 # from it in local peer.
388 # from it in local peer.
389 return bundle2.getunbundler(self.ui, cb)
389 return bundle2.getunbundler(self.ui, cb)
390 else:
390 else:
391 return changegroup.getunbundler(b'01', cb, None)
391 return changegroup.getunbundler(b'01', cb, None)
392
392
393 def heads(self):
393 def heads(self):
394 return self._repo.heads()
394 return self._repo.heads()
395
395
396 def known(self, nodes):
396 def known(self, nodes):
397 return self._repo.known(nodes)
397 return self._repo.known(nodes)
398
398
399 def listkeys(self, namespace):
399 def listkeys(self, namespace):
400 return self._repo.listkeys(namespace)
400 return self._repo.listkeys(namespace)
401
401
402 def lookup(self, key):
402 def lookup(self, key):
403 return self._repo.lookup(key)
403 return self._repo.lookup(key)
404
404
405 def pushkey(self, namespace, key, old, new):
405 def pushkey(self, namespace, key, old, new):
406 return self._repo.pushkey(namespace, key, old, new)
406 return self._repo.pushkey(namespace, key, old, new)
407
407
408 def stream_out(self):
408 def stream_out(self):
409 raise error.Abort(_(b'cannot perform stream clone against local peer'))
409 raise error.Abort(_(b'cannot perform stream clone against local peer'))
410
410
411 def unbundle(self, bundle, heads, url):
411 def unbundle(self, bundle, heads, url):
412 """apply a bundle on a repo
412 """apply a bundle on a repo
413
413
414 This function handles the repo locking itself."""
414 This function handles the repo locking itself."""
415 try:
415 try:
416 try:
416 try:
417 bundle = exchange.readbundle(self.ui, bundle, None)
417 bundle = exchange.readbundle(self.ui, bundle, None)
418 ret = exchange.unbundle(self._repo, bundle, heads, b'push', url)
418 ret = exchange.unbundle(self._repo, bundle, heads, b'push', url)
419 if hasattr(ret, 'getchunks'):
419 if hasattr(ret, 'getchunks'):
420 # This is a bundle20 object, turn it into an unbundler.
420 # This is a bundle20 object, turn it into an unbundler.
421 # This little dance should be dropped eventually when the
421 # This little dance should be dropped eventually when the
422 # API is finally improved.
422 # API is finally improved.
423 stream = util.chunkbuffer(ret.getchunks())
423 stream = util.chunkbuffer(ret.getchunks())
424 ret = bundle2.getunbundler(self.ui, stream)
424 ret = bundle2.getunbundler(self.ui, stream)
425 return ret
425 return ret
426 except Exception as exc:
426 except Exception as exc:
427 # If the exception contains output salvaged from a bundle2
427 # If the exception contains output salvaged from a bundle2
428 # reply, we need to make sure it is printed before continuing
428 # reply, we need to make sure it is printed before continuing
429 # to fail. So we build a bundle2 with such output and consume
429 # to fail. So we build a bundle2 with such output and consume
430 # it directly.
430 # it directly.
431 #
431 #
432 # This is not very elegant but allows a "simple" solution for
432 # This is not very elegant but allows a "simple" solution for
433 # issue4594
433 # issue4594
434 output = getattr(exc, '_bundle2salvagedoutput', ())
434 output = getattr(exc, '_bundle2salvagedoutput', ())
435 if output:
435 if output:
436 bundler = bundle2.bundle20(self._repo.ui)
436 bundler = bundle2.bundle20(self._repo.ui)
437 for out in output:
437 for out in output:
438 bundler.addpart(out)
438 bundler.addpart(out)
439 stream = util.chunkbuffer(bundler.getchunks())
439 stream = util.chunkbuffer(bundler.getchunks())
440 b = bundle2.getunbundler(self.ui, stream)
440 b = bundle2.getunbundler(self.ui, stream)
441 bundle2.processbundle(self._repo, b)
441 bundle2.processbundle(self._repo, b)
442 raise
442 raise
443 except error.PushRaced as exc:
443 except error.PushRaced as exc:
444 raise error.ResponseError(
444 raise error.ResponseError(
445 _(b'push failed:'), stringutil.forcebytestr(exc)
445 _(b'push failed:'), stringutil.forcebytestr(exc)
446 )
446 )
447
447
448 # End of _basewirecommands interface.
448 # End of _basewirecommands interface.
449
449
450 # Begin of peer interface.
450 # Begin of peer interface.
451
451
452 def commandexecutor(self):
452 def commandexecutor(self):
453 return localcommandexecutor(self)
453 return localcommandexecutor(self)
454
454
455 # End of peer interface.
455 # End of peer interface.
456
456
457
457
458 @interfaceutil.implementer(repository.ipeerlegacycommands)
458 @interfaceutil.implementer(repository.ipeerlegacycommands)
459 class locallegacypeer(localpeer):
459 class locallegacypeer(localpeer):
460 """peer extension which implements legacy methods too; used for tests with
460 """peer extension which implements legacy methods too; used for tests with
461 restricted capabilities"""
461 restricted capabilities"""
462
462
463 def __init__(self, repo, path=None, remotehidden=False):
463 def __init__(self, repo, path=None, remotehidden=False):
464 super(locallegacypeer, self).__init__(
464 super(locallegacypeer, self).__init__(
465 repo, caps=legacycaps, path=path, remotehidden=remotehidden
465 repo, caps=legacycaps, path=path, remotehidden=remotehidden
466 )
466 )
467
467
468 # Begin of baselegacywirecommands interface.
468 # Begin of baselegacywirecommands interface.
469
469
470 def between(self, pairs):
470 def between(self, pairs):
471 return self._repo.between(pairs)
471 return self._repo.between(pairs)
472
472
473 def branches(self, nodes):
473 def branches(self, nodes):
474 return self._repo.branches(nodes)
474 return self._repo.branches(nodes)
475
475
476 def changegroup(self, nodes, source):
476 def changegroup(self, nodes, source):
477 outgoing = discovery.outgoing(
477 outgoing = discovery.outgoing(
478 self._repo, missingroots=nodes, ancestorsof=self._repo.heads()
478 self._repo, missingroots=nodes, ancestorsof=self._repo.heads()
479 )
479 )
480 return changegroup.makechangegroup(self._repo, outgoing, b'01', source)
480 return changegroup.makechangegroup(self._repo, outgoing, b'01', source)
481
481
482 def changegroupsubset(self, bases, heads, source):
482 def changegroupsubset(self, bases, heads, source):
483 outgoing = discovery.outgoing(
483 outgoing = discovery.outgoing(
484 self._repo, missingroots=bases, ancestorsof=heads
484 self._repo, missingroots=bases, ancestorsof=heads
485 )
485 )
486 return changegroup.makechangegroup(self._repo, outgoing, b'01', source)
486 return changegroup.makechangegroup(self._repo, outgoing, b'01', source)
487
487
488 # End of baselegacywirecommands interface.
488 # End of baselegacywirecommands interface.
489
489
490
490
491 # Functions receiving (ui, features) that extensions can register to impact
491 # Functions receiving (ui, features) that extensions can register to impact
492 # the ability to load repositories with custom requirements. Only
492 # the ability to load repositories with custom requirements. Only
493 # functions defined in loaded extensions are called.
493 # functions defined in loaded extensions are called.
494 #
494 #
495 # The function receives a set of requirement strings that the repository
495 # The function receives a set of requirement strings that the repository
496 # is capable of opening. Functions will typically add elements to the
496 # is capable of opening. Functions will typically add elements to the
497 # set to reflect that the extension knows how to handle that requirements.
497 # set to reflect that the extension knows how to handle that requirements.
498 featuresetupfuncs = set()
498 featuresetupfuncs = set()
499
499
500
500
501 def _getsharedvfs(hgvfs, requirements):
501 def _getsharedvfs(hgvfs, requirements):
502 """returns the vfs object pointing to root of shared source
502 """returns the vfs object pointing to root of shared source
503 repo for a shared repository
503 repo for a shared repository
504
504
505 hgvfs is vfs pointing at .hg/ of current repo (shared one)
505 hgvfs is vfs pointing at .hg/ of current repo (shared one)
506 requirements is a set of requirements of current repo (shared one)
506 requirements is a set of requirements of current repo (shared one)
507 """
507 """
508 # The ``shared`` or ``relshared`` requirements indicate the
508 # The ``shared`` or ``relshared`` requirements indicate the
509 # store lives in the path contained in the ``.hg/sharedpath`` file.
509 # store lives in the path contained in the ``.hg/sharedpath`` file.
510 # This is an absolute path for ``shared`` and relative to
510 # This is an absolute path for ``shared`` and relative to
511 # ``.hg/`` for ``relshared``.
511 # ``.hg/`` for ``relshared``.
512 sharedpath = hgvfs.read(b'sharedpath').rstrip(b'\n')
512 sharedpath = hgvfs.read(b'sharedpath').rstrip(b'\n')
513 if requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements:
513 if requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements:
514 sharedpath = util.normpath(hgvfs.join(sharedpath))
514 sharedpath = util.normpath(hgvfs.join(sharedpath))
515
515
516 sharedvfs = vfsmod.vfs(sharedpath, realpath=True)
516 sharedvfs = vfsmod.vfs(sharedpath, realpath=True)
517
517
518 if not sharedvfs.exists():
518 if not sharedvfs.exists():
519 raise error.RepoError(
519 raise error.RepoError(
520 _(b'.hg/sharedpath points to nonexistent directory %s')
520 _(b'.hg/sharedpath points to nonexistent directory %s')
521 % sharedvfs.base
521 % sharedvfs.base
522 )
522 )
523 return sharedvfs
523 return sharedvfs
524
524
525
525
526 def _readrequires(vfs, allowmissing):
526 def _readrequires(vfs, allowmissing):
527 """reads the require file present at root of this vfs
527 """reads the require file present at root of this vfs
528 and return a set of requirements
528 and return a set of requirements
529
529
530 If allowmissing is True, we suppress FileNotFoundError if raised"""
530 If allowmissing is True, we suppress FileNotFoundError if raised"""
531 # requires file contains a newline-delimited list of
531 # requires file contains a newline-delimited list of
532 # features/capabilities the opener (us) must have in order to use
532 # features/capabilities the opener (us) must have in order to use
533 # the repository. This file was introduced in Mercurial 0.9.2,
533 # the repository. This file was introduced in Mercurial 0.9.2,
534 # which means very old repositories may not have one. We assume
534 # which means very old repositories may not have one. We assume
535 # a missing file translates to no requirements.
535 # a missing file translates to no requirements.
536 read = vfs.tryread if allowmissing else vfs.read
536 read = vfs.tryread if allowmissing else vfs.read
537 return set(read(b'requires').splitlines())
537 return set(read(b'requires').splitlines())
538
538
539
539
540 def makelocalrepository(baseui, path: bytes, intents=None):
540 def makelocalrepository(baseui, path: bytes, intents=None):
541 """Create a local repository object.
541 """Create a local repository object.
542
542
543 Given arguments needed to construct a local repository, this function
543 Given arguments needed to construct a local repository, this function
544 performs various early repository loading functionality (such as
544 performs various early repository loading functionality (such as
545 reading the ``.hg/requires`` and ``.hg/hgrc`` files), validates that
545 reading the ``.hg/requires`` and ``.hg/hgrc`` files), validates that
546 the repository can be opened, derives a type suitable for representing
546 the repository can be opened, derives a type suitable for representing
547 that repository, and returns an instance of it.
547 that repository, and returns an instance of it.
548
548
549 The returned object conforms to the ``repository.completelocalrepository``
549 The returned object conforms to the ``repository.completelocalrepository``
550 interface.
550 interface.
551
551
552 The repository type is derived by calling a series of factory functions
552 The repository type is derived by calling a series of factory functions
553 for each aspect/interface of the final repository. These are defined by
553 for each aspect/interface of the final repository. These are defined by
554 ``REPO_INTERFACES``.
554 ``REPO_INTERFACES``.
555
555
556 Each factory function is called to produce a type implementing a specific
556 Each factory function is called to produce a type implementing a specific
557 interface. The cumulative list of returned types will be combined into a
557 interface. The cumulative list of returned types will be combined into a
558 new type and that type will be instantiated to represent the local
558 new type and that type will be instantiated to represent the local
559 repository.
559 repository.
560
560
561 The factory functions each receive various state that may be consulted
561 The factory functions each receive various state that may be consulted
562 as part of deriving a type.
562 as part of deriving a type.
563
563
564 Extensions should wrap these factory functions to customize repository type
564 Extensions should wrap these factory functions to customize repository type
565 creation. Note that an extension's wrapped function may be called even if
565 creation. Note that an extension's wrapped function may be called even if
566 that extension is not loaded for the repo being constructed. Extensions
566 that extension is not loaded for the repo being constructed. Extensions
567 should check if their ``__name__`` appears in the
567 should check if their ``__name__`` appears in the
568 ``extensionmodulenames`` set passed to the factory function and no-op if
568 ``extensionmodulenames`` set passed to the factory function and no-op if
569 not.
569 not.
570 """
570 """
571 ui = baseui.copy()
571 ui = baseui.copy()
572 # Prevent copying repo configuration.
572 # Prevent copying repo configuration.
573 ui.copy = baseui.copy
573 ui.copy = baseui.copy
574
574
575 # Working directory VFS rooted at repository root.
575 # Working directory VFS rooted at repository root.
576 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
576 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
577
577
578 # Main VFS for .hg/ directory.
578 # Main VFS for .hg/ directory.
579 hgpath = wdirvfs.join(b'.hg')
579 hgpath = wdirvfs.join(b'.hg')
580 hgvfs = vfsmod.vfs(hgpath, cacheaudited=True)
580 hgvfs = vfsmod.vfs(hgpath, cacheaudited=True)
581 # Whether this repository is shared one or not
581 # Whether this repository is shared one or not
582 shared = False
582 shared = False
583 # If this repository is shared, vfs pointing to shared repo
583 # If this repository is shared, vfs pointing to shared repo
584 sharedvfs = None
584 sharedvfs = None
585
585
586 # The .hg/ path should exist and should be a directory. All other
586 # The .hg/ path should exist and should be a directory. All other
587 # cases are errors.
587 # cases are errors.
588 if not hgvfs.isdir():
588 if not hgvfs.isdir():
589 try:
589 try:
590 hgvfs.stat()
590 hgvfs.stat()
591 except FileNotFoundError:
591 except FileNotFoundError:
592 pass
592 pass
593 except ValueError as e:
593 except ValueError as e:
594 # Can be raised on Python 3.8 when path is invalid.
594 # Can be raised on Python 3.8 when path is invalid.
595 raise error.Abort(
595 raise error.Abort(
596 _(b'invalid path %s: %s') % (path, stringutil.forcebytestr(e))
596 _(b'invalid path %s: %s') % (path, stringutil.forcebytestr(e))
597 )
597 )
598
598
599 raise error.RepoError(_(b'repository %s not found') % path)
599 raise error.RepoError(_(b'repository %s not found') % path)
600
600
601 requirements = _readrequires(hgvfs, True)
601 requirements = _readrequires(hgvfs, True)
602 shared = (
602 shared = (
603 requirementsmod.SHARED_REQUIREMENT in requirements
603 requirementsmod.SHARED_REQUIREMENT in requirements
604 or requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements
604 or requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements
605 )
605 )
606 storevfs = None
606 storevfs = None
607 if shared:
607 if shared:
608 # This is a shared repo
608 # This is a shared repo
609 sharedvfs = _getsharedvfs(hgvfs, requirements)
609 sharedvfs = _getsharedvfs(hgvfs, requirements)
610 storevfs = vfsmod.vfs(sharedvfs.join(b'store'))
610 storevfs = vfsmod.vfs(sharedvfs.join(b'store'))
611 else:
611 else:
612 storevfs = vfsmod.vfs(hgvfs.join(b'store'))
612 storevfs = vfsmod.vfs(hgvfs.join(b'store'))
613
613
614 # if .hg/requires contains the sharesafe requirement, it means
614 # if .hg/requires contains the sharesafe requirement, it means
615 # there exists a `.hg/store/requires` too and we should read it
615 # there exists a `.hg/store/requires` too and we should read it
616 # NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
616 # NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
617 # is present. We never write SHARESAFE_REQUIREMENT for a repo if store
617 # is present. We never write SHARESAFE_REQUIREMENT for a repo if store
618 # is not present, refer checkrequirementscompat() for that
618 # is not present, refer checkrequirementscompat() for that
619 #
619 #
620 # However, if SHARESAFE_REQUIREMENT is not present, it means that the
620 # However, if SHARESAFE_REQUIREMENT is not present, it means that the
621 # repository was shared the old way. We check the share source .hg/requires
621 # repository was shared the old way. We check the share source .hg/requires
622 # for SHARESAFE_REQUIREMENT to detect whether the current repository needs
622 # for SHARESAFE_REQUIREMENT to detect whether the current repository needs
623 # to be reshared
623 # to be reshared
624 hint = _(b"see `hg help config.format.use-share-safe` for more information")
624 hint = _(b"see `hg help config.format.use-share-safe` for more information")
625 if requirementsmod.SHARESAFE_REQUIREMENT in requirements:
625 if requirementsmod.SHARESAFE_REQUIREMENT in requirements:
626 if (
626 if (
627 shared
627 shared
628 and requirementsmod.SHARESAFE_REQUIREMENT
628 and requirementsmod.SHARESAFE_REQUIREMENT
629 not in _readrequires(sharedvfs, True)
629 not in _readrequires(sharedvfs, True)
630 ):
630 ):
631 mismatch_warn = ui.configbool(
631 mismatch_warn = ui.configbool(
632 b'share', b'safe-mismatch.source-not-safe.warn'
632 b'share', b'safe-mismatch.source-not-safe.warn'
633 )
633 )
634 mismatch_config = ui.config(
634 mismatch_config = ui.config(
635 b'share', b'safe-mismatch.source-not-safe'
635 b'share', b'safe-mismatch.source-not-safe'
636 )
636 )
637 mismatch_verbose_upgrade = ui.configbool(
637 mismatch_verbose_upgrade = ui.configbool(
638 b'share', b'safe-mismatch.source-not-safe:verbose-upgrade'
638 b'share', b'safe-mismatch.source-not-safe:verbose-upgrade'
639 )
639 )
640 if mismatch_config in (
640 if mismatch_config in (
641 b'downgrade-allow',
641 b'downgrade-allow',
642 b'allow',
642 b'allow',
643 b'downgrade-abort',
643 b'downgrade-abort',
644 ):
644 ):
645 # prevent cyclic import localrepo -> upgrade -> localrepo
645 # prevent cyclic import localrepo -> upgrade -> localrepo
646 from . import upgrade
646 from . import upgrade
647
647
648 upgrade.downgrade_share_to_non_safe(
648 upgrade.downgrade_share_to_non_safe(
649 ui,
649 ui,
650 hgvfs,
650 hgvfs,
651 sharedvfs,
651 sharedvfs,
652 requirements,
652 requirements,
653 mismatch_config,
653 mismatch_config,
654 mismatch_warn,
654 mismatch_warn,
655 mismatch_verbose_upgrade,
655 mismatch_verbose_upgrade,
656 )
656 )
657 elif mismatch_config == b'abort':
657 elif mismatch_config == b'abort':
658 raise error.Abort(
658 raise error.Abort(
659 _(b"share source does not support share-safe requirement"),
659 _(b"share source does not support share-safe requirement"),
660 hint=hint,
660 hint=hint,
661 )
661 )
662 else:
662 else:
663 raise error.Abort(
663 raise error.Abort(
664 _(
664 _(
665 b"share-safe mismatch with source.\nUnrecognized"
665 b"share-safe mismatch with source.\nUnrecognized"
666 b" value '%s' of `share.safe-mismatch.source-not-safe`"
666 b" value '%s' of `share.safe-mismatch.source-not-safe`"
667 b" set."
667 b" set."
668 )
668 )
669 % mismatch_config,
669 % mismatch_config,
670 hint=hint,
670 hint=hint,
671 )
671 )
672 else:
672 else:
673 requirements |= _readrequires(storevfs, False)
673 requirements |= _readrequires(storevfs, False)
674 elif shared:
674 elif shared:
675 sourcerequires = _readrequires(sharedvfs, False)
675 sourcerequires = _readrequires(sharedvfs, False)
676 if requirementsmod.SHARESAFE_REQUIREMENT in sourcerequires:
676 if requirementsmod.SHARESAFE_REQUIREMENT in sourcerequires:
677 mismatch_config = ui.config(b'share', b'safe-mismatch.source-safe')
677 mismatch_config = ui.config(b'share', b'safe-mismatch.source-safe')
678 mismatch_warn = ui.configbool(
678 mismatch_warn = ui.configbool(
679 b'share', b'safe-mismatch.source-safe.warn'
679 b'share', b'safe-mismatch.source-safe.warn'
680 )
680 )
681 mismatch_verbose_upgrade = ui.configbool(
681 mismatch_verbose_upgrade = ui.configbool(
682 b'share', b'safe-mismatch.source-safe:verbose-upgrade'
682 b'share', b'safe-mismatch.source-safe:verbose-upgrade'
683 )
683 )
684 if mismatch_config in (
684 if mismatch_config in (
685 b'upgrade-allow',
685 b'upgrade-allow',
686 b'allow',
686 b'allow',
687 b'upgrade-abort',
687 b'upgrade-abort',
688 ):
688 ):
689 # prevent cyclic import localrepo -> upgrade -> localrepo
689 # prevent cyclic import localrepo -> upgrade -> localrepo
690 from . import upgrade
690 from . import upgrade
691
691
692 upgrade.upgrade_share_to_safe(
692 upgrade.upgrade_share_to_safe(
693 ui,
693 ui,
694 hgvfs,
694 hgvfs,
695 storevfs,
695 storevfs,
696 requirements,
696 requirements,
697 mismatch_config,
697 mismatch_config,
698 mismatch_warn,
698 mismatch_warn,
699 mismatch_verbose_upgrade,
699 mismatch_verbose_upgrade,
700 )
700 )
701 elif mismatch_config == b'abort':
701 elif mismatch_config == b'abort':
702 raise error.Abort(
702 raise error.Abort(
703 _(
703 _(
704 b'version mismatch: source uses share-safe'
704 b'version mismatch: source uses share-safe'
705 b' functionality while the current share does not'
705 b' functionality while the current share does not'
706 ),
706 ),
707 hint=hint,
707 hint=hint,
708 )
708 )
709 else:
709 else:
710 raise error.Abort(
710 raise error.Abort(
711 _(
711 _(
712 b"share-safe mismatch with source.\nUnrecognized"
712 b"share-safe mismatch with source.\nUnrecognized"
713 b" value '%s' of `share.safe-mismatch.source-safe` set."
713 b" value '%s' of `share.safe-mismatch.source-safe` set."
714 )
714 )
715 % mismatch_config,
715 % mismatch_config,
716 hint=hint,
716 hint=hint,
717 )
717 )
718
718
719 # The .hg/hgrc file may load extensions or contain config options
719 # The .hg/hgrc file may load extensions or contain config options
720 # that influence repository construction. Attempt to load it and
720 # that influence repository construction. Attempt to load it and
721 # process any new extensions that it may have pulled in.
721 # process any new extensions that it may have pulled in.
722 if loadhgrc(ui, wdirvfs, hgvfs, requirements, sharedvfs):
722 if loadhgrc(ui, wdirvfs, hgvfs, requirements, sharedvfs):
723 afterhgrcload(ui, wdirvfs, hgvfs, requirements)
723 afterhgrcload(ui, wdirvfs, hgvfs, requirements)
724 extensions.loadall(ui)
724 extensions.loadall(ui)
725 extensions.populateui(ui)
725 extensions.populateui(ui)
726
726
727 # Set of module names of extensions loaded for this repository.
727 # Set of module names of extensions loaded for this repository.
728 extensionmodulenames = {m.__name__ for n, m in extensions.extensions(ui)}
728 extensionmodulenames = {m.__name__ for n, m in extensions.extensions(ui)}
729
729
730 supportedrequirements = gathersupportedrequirements(ui)
730 supportedrequirements = gathersupportedrequirements(ui)
731
731
732 # We first validate the requirements are known.
732 # We first validate the requirements are known.
733 ensurerequirementsrecognized(requirements, supportedrequirements)
733 ensurerequirementsrecognized(requirements, supportedrequirements)
734
734
735 # Then we validate that the known set is reasonable to use together.
735 # Then we validate that the known set is reasonable to use together.
736 ensurerequirementscompatible(ui, requirements)
736 ensurerequirementscompatible(ui, requirements)
737
737
738 # TODO there are unhandled edge cases related to opening repositories with
738 # TODO there are unhandled edge cases related to opening repositories with
739 # shared storage. If storage is shared, we should also test for requirements
739 # shared storage. If storage is shared, we should also test for requirements
740 # compatibility in the pointed-to repo. This entails loading the .hg/hgrc in
740 # compatibility in the pointed-to repo. This entails loading the .hg/hgrc in
741 # that repo, as that repo may load extensions needed to open it. This is a
741 # that repo, as that repo may load extensions needed to open it. This is a
742 # bit complicated because we don't want the other hgrc to overwrite settings
742 # bit complicated because we don't want the other hgrc to overwrite settings
743 # in this hgrc.
743 # in this hgrc.
744 #
744 #
745 # This bug is somewhat mitigated by the fact that we copy the .hg/requires
745 # This bug is somewhat mitigated by the fact that we copy the .hg/requires
746 # file when sharing repos. But if a requirement is added after the share is
746 # file when sharing repos. But if a requirement is added after the share is
747 # performed, thereby introducing a new requirement for the opener, we may
747 # performed, thereby introducing a new requirement for the opener, we may
748 # will not see that and could encounter a run-time error interacting with
748 # will not see that and could encounter a run-time error interacting with
749 # that shared store since it has an unknown-to-us requirement.
749 # that shared store since it has an unknown-to-us requirement.
750
750
751 # At this point, we know we should be capable of opening the repository.
751 # At this point, we know we should be capable of opening the repository.
752 # Now get on with doing that.
752 # Now get on with doing that.
753
753
754 features = set()
754 features = set()
755
755
756 # The "store" part of the repository holds versioned data. How it is
756 # The "store" part of the repository holds versioned data. How it is
757 # accessed is determined by various requirements. If `shared` or
757 # accessed is determined by various requirements. If `shared` or
758 # `relshared` requirements are present, this indicates current repository
758 # `relshared` requirements are present, this indicates current repository
759 # is a share and store exists in path mentioned in `.hg/sharedpath`
759 # is a share and store exists in path mentioned in `.hg/sharedpath`
760 if shared:
760 if shared:
761 storebasepath = sharedvfs.base
761 storebasepath = sharedvfs.base
762 cachepath = sharedvfs.join(b'cache')
762 cachepath = sharedvfs.join(b'cache')
763 features.add(repository.REPO_FEATURE_SHARED_STORAGE)
763 features.add(repository.REPO_FEATURE_SHARED_STORAGE)
764 else:
764 else:
765 storebasepath = hgvfs.base
765 storebasepath = hgvfs.base
766 cachepath = hgvfs.join(b'cache')
766 cachepath = hgvfs.join(b'cache')
767 wcachepath = hgvfs.join(b'wcache')
767 wcachepath = hgvfs.join(b'wcache')
768
768
769 # The store has changed over time and the exact layout is dictated by
769 # The store has changed over time and the exact layout is dictated by
770 # requirements. The store interface abstracts differences across all
770 # requirements. The store interface abstracts differences across all
771 # of them.
771 # of them.
772 store = makestore(
772 store = makestore(
773 requirements,
773 requirements,
774 storebasepath,
774 storebasepath,
775 lambda base: vfsmod.vfs(base, cacheaudited=True),
775 lambda base: vfsmod.vfs(base, cacheaudited=True),
776 )
776 )
777 hgvfs.createmode = store.createmode
777 hgvfs.createmode = store.createmode
778
778
779 storevfs = store.vfs
779 storevfs = store.vfs
780 storevfs.options = resolvestorevfsoptions(ui, requirements, features)
780 storevfs.options = resolvestorevfsoptions(ui, requirements, features)
781
781
782 if (
782 if (
783 requirementsmod.REVLOGV2_REQUIREMENT in requirements
783 requirementsmod.REVLOGV2_REQUIREMENT in requirements
784 or requirementsmod.CHANGELOGV2_REQUIREMENT in requirements
784 or requirementsmod.CHANGELOGV2_REQUIREMENT in requirements
785 ):
785 ):
786 features.add(repository.REPO_FEATURE_SIDE_DATA)
786 features.add(repository.REPO_FEATURE_SIDE_DATA)
787 # the revlogv2 docket introduced race condition that we need to fix
787 # the revlogv2 docket introduced race condition that we need to fix
788 features.discard(repository.REPO_FEATURE_STREAM_CLONE)
788 features.discard(repository.REPO_FEATURE_STREAM_CLONE)
789
789
790 # The cache vfs is used to manage cache files.
790 # The cache vfs is used to manage cache files.
791 cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
791 cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
792 cachevfs.createmode = store.createmode
792 cachevfs.createmode = store.createmode
793 # The cache vfs is used to manage cache files related to the working copy
793 # The cache vfs is used to manage cache files related to the working copy
794 wcachevfs = vfsmod.vfs(wcachepath, cacheaudited=True)
794 wcachevfs = vfsmod.vfs(wcachepath, cacheaudited=True)
795 wcachevfs.createmode = store.createmode
795 wcachevfs.createmode = store.createmode
796
796
797 # Now resolve the type for the repository object. We do this by repeatedly
797 # Now resolve the type for the repository object. We do this by repeatedly
798 # calling a factory function to produces types for specific aspects of the
798 # calling a factory function to produces types for specific aspects of the
799 # repo's operation. The aggregate returned types are used as base classes
799 # repo's operation. The aggregate returned types are used as base classes
800 # for a dynamically-derived type, which will represent our new repository.
800 # for a dynamically-derived type, which will represent our new repository.
801
801
802 bases = []
802 bases = []
803 extrastate = {}
803 extrastate = {}
804
804
805 for iface, fn in REPO_INTERFACES:
805 for iface, fn in REPO_INTERFACES:
806 # We pass all potentially useful state to give extensions tons of
806 # We pass all potentially useful state to give extensions tons of
807 # flexibility.
807 # flexibility.
808 typ = fn()(
808 typ = fn()(
809 ui=ui,
809 ui=ui,
810 intents=intents,
810 intents=intents,
811 requirements=requirements,
811 requirements=requirements,
812 features=features,
812 features=features,
813 wdirvfs=wdirvfs,
813 wdirvfs=wdirvfs,
814 hgvfs=hgvfs,
814 hgvfs=hgvfs,
815 store=store,
815 store=store,
816 storevfs=storevfs,
816 storevfs=storevfs,
817 storeoptions=storevfs.options,
817 storeoptions=storevfs.options,
818 cachevfs=cachevfs,
818 cachevfs=cachevfs,
819 wcachevfs=wcachevfs,
819 wcachevfs=wcachevfs,
820 extensionmodulenames=extensionmodulenames,
820 extensionmodulenames=extensionmodulenames,
821 extrastate=extrastate,
821 extrastate=extrastate,
822 baseclasses=bases,
822 baseclasses=bases,
823 )
823 )
824
824
825 if not isinstance(typ, type):
825 if not isinstance(typ, type):
826 raise error.ProgrammingError(
826 raise error.ProgrammingError(
827 b'unable to construct type for %s' % iface
827 b'unable to construct type for %s' % iface
828 )
828 )
829
829
830 bases.append(typ)
830 bases.append(typ)
831
831
832 # type() allows you to use characters in type names that wouldn't be
832 # type() allows you to use characters in type names that wouldn't be
833 # recognized as Python symbols in source code. We abuse that to add
833 # recognized as Python symbols in source code. We abuse that to add
834 # rich information about our constructed repo.
834 # rich information about our constructed repo.
835 name = pycompat.sysstr(
835 name = pycompat.sysstr(
836 b'derivedrepo:%s<%s>' % (wdirvfs.base, b','.join(sorted(requirements)))
836 b'derivedrepo:%s<%s>' % (wdirvfs.base, b','.join(sorted(requirements)))
837 )
837 )
838
838
839 cls = type(name, tuple(bases), {})
839 cls = type(name, tuple(bases), {})
840
840
841 return cls(
841 return cls(
842 baseui=baseui,
842 baseui=baseui,
843 ui=ui,
843 ui=ui,
844 origroot=path,
844 origroot=path,
845 wdirvfs=wdirvfs,
845 wdirvfs=wdirvfs,
846 hgvfs=hgvfs,
846 hgvfs=hgvfs,
847 requirements=requirements,
847 requirements=requirements,
848 supportedrequirements=supportedrequirements,
848 supportedrequirements=supportedrequirements,
849 sharedpath=storebasepath,
849 sharedpath=storebasepath,
850 store=store,
850 store=store,
851 cachevfs=cachevfs,
851 cachevfs=cachevfs,
852 wcachevfs=wcachevfs,
852 wcachevfs=wcachevfs,
853 features=features,
853 features=features,
854 intents=intents,
854 intents=intents,
855 )
855 )
856
856
857
857
858 def loadhgrc(
858 def loadhgrc(
859 ui,
859 ui,
860 wdirvfs: vfsmod.vfs,
860 wdirvfs: vfsmod.vfs,
861 hgvfs: vfsmod.vfs,
861 hgvfs: vfsmod.vfs,
862 requirements,
862 requirements,
863 sharedvfs: Optional[vfsmod.vfs] = None,
863 sharedvfs: Optional[vfsmod.vfs] = None,
864 ):
864 ):
865 """Load hgrc files/content into a ui instance.
865 """Load hgrc files/content into a ui instance.
866
866
867 This is called during repository opening to load any additional
867 This is called during repository opening to load any additional
868 config files or settings relevant to the current repository.
868 config files or settings relevant to the current repository.
869
869
870 Returns a bool indicating whether any additional configs were loaded.
870 Returns a bool indicating whether any additional configs were loaded.
871
871
872 Extensions should monkeypatch this function to modify how per-repo
872 Extensions should monkeypatch this function to modify how per-repo
873 configs are loaded. For example, an extension may wish to pull in
873 configs are loaded. For example, an extension may wish to pull in
874 configs from alternate files or sources.
874 configs from alternate files or sources.
875
875
876 sharedvfs is vfs object pointing to source repo if the current one is a
876 sharedvfs is vfs object pointing to source repo if the current one is a
877 shared one
877 shared one
878 """
878 """
879 if not rcutil.use_repo_hgrc():
879 if not rcutil.use_repo_hgrc():
880 return False
880 return False
881
881
882 ret = False
882 ret = False
883 # first load config from shared source if we has to
883 # first load config from shared source if we has to
884 if requirementsmod.SHARESAFE_REQUIREMENT in requirements and sharedvfs:
884 if requirementsmod.SHARESAFE_REQUIREMENT in requirements and sharedvfs:
885 try:
885 try:
886 ui.readconfig(sharedvfs.join(b'hgrc'), root=sharedvfs.base)
886 ui.readconfig(sharedvfs.join(b'hgrc'), root=sharedvfs.base)
887 ret = True
887 ret = True
888 except IOError:
888 except IOError:
889 pass
889 pass
890
890
891 try:
891 try:
892 ui.readconfig(hgvfs.join(b'hgrc'), root=wdirvfs.base)
892 ui.readconfig(hgvfs.join(b'hgrc'), root=wdirvfs.base)
893 ret = True
893 ret = True
894 except IOError:
894 except IOError:
895 pass
895 pass
896
896
897 try:
897 try:
898 ui.readconfig(hgvfs.join(b'hgrc-not-shared'), root=wdirvfs.base)
898 ui.readconfig(hgvfs.join(b'hgrc-not-shared'), root=wdirvfs.base)
899 ret = True
899 ret = True
900 except IOError:
900 except IOError:
901 pass
901 pass
902
902
903 return ret
903 return ret
904
904
905
905
906 def afterhgrcload(ui, wdirvfs, hgvfs, requirements):
906 def afterhgrcload(ui, wdirvfs, hgvfs, requirements):
907 """Perform additional actions after .hg/hgrc is loaded.
907 """Perform additional actions after .hg/hgrc is loaded.
908
908
909 This function is called during repository loading immediately after
909 This function is called during repository loading immediately after
910 the .hg/hgrc file is loaded and before per-repo extensions are loaded.
910 the .hg/hgrc file is loaded and before per-repo extensions are loaded.
911
911
912 The function can be used to validate configs, automatically add
912 The function can be used to validate configs, automatically add
913 options (including extensions) based on requirements, etc.
913 options (including extensions) based on requirements, etc.
914 """
914 """
915
915
916 # Map of requirements to list of extensions to load automatically when
916 # Map of requirements to list of extensions to load automatically when
917 # requirement is present.
917 # requirement is present.
918 autoextensions = {
918 autoextensions = {
919 b'git': [b'git'],
919 b'git': [b'git'],
920 b'largefiles': [b'largefiles'],
920 b'largefiles': [b'largefiles'],
921 b'lfs': [b'lfs'],
921 b'lfs': [b'lfs'],
922 }
922 }
923
923
924 for requirement, names in sorted(autoextensions.items()):
924 for requirement, names in sorted(autoextensions.items()):
925 if requirement not in requirements:
925 if requirement not in requirements:
926 continue
926 continue
927
927
928 for name in names:
928 for name in names:
929 if not ui.hasconfig(b'extensions', name):
929 if not ui.hasconfig(b'extensions', name):
930 ui.setconfig(b'extensions', name, b'', source=b'autoload')
930 ui.setconfig(b'extensions', name, b'', source=b'autoload')
931
931
932
932
933 def gathersupportedrequirements(ui):
933 def gathersupportedrequirements(ui):
934 """Determine the complete set of recognized requirements."""
934 """Determine the complete set of recognized requirements."""
935 # Start with all requirements supported by this file.
935 # Start with all requirements supported by this file.
936 supported = set(localrepository._basesupported)
936 supported = set(localrepository._basesupported)
937
937
938 # Execute ``featuresetupfuncs`` entries if they belong to an extension
938 # Execute ``featuresetupfuncs`` entries if they belong to an extension
939 # relevant to this ui instance.
939 # relevant to this ui instance.
940 modules = {m.__name__ for n, m in extensions.extensions(ui)}
940 modules = {m.__name__ for n, m in extensions.extensions(ui)}
941
941
942 for fn in featuresetupfuncs:
942 for fn in featuresetupfuncs:
943 if fn.__module__ in modules:
943 if fn.__module__ in modules:
944 fn(ui, supported)
944 fn(ui, supported)
945
945
946 # Add derived requirements from registered compression engines.
946 # Add derived requirements from registered compression engines.
947 for name in util.compengines:
947 for name in util.compengines:
948 engine = util.compengines[name]
948 engine = util.compengines[name]
949 if engine.available() and engine.revlogheader():
949 if engine.available() and engine.revlogheader():
950 supported.add(b'exp-compression-%s' % name)
950 supported.add(b'exp-compression-%s' % name)
951 if engine.name() == b'zstd':
951 if engine.name() == b'zstd':
952 supported.add(requirementsmod.REVLOG_COMPRESSION_ZSTD)
952 supported.add(requirementsmod.REVLOG_COMPRESSION_ZSTD)
953
953
954 return supported
954 return supported
955
955
956
956
957 def ensurerequirementsrecognized(requirements, supported):
957 def ensurerequirementsrecognized(requirements, supported):
958 """Validate that a set of local requirements is recognized.
958 """Validate that a set of local requirements is recognized.
959
959
960 Receives a set of requirements. Raises an ``error.RepoError`` if there
960 Receives a set of requirements. Raises an ``error.RepoError`` if there
961 exists any requirement in that set that currently loaded code doesn't
961 exists any requirement in that set that currently loaded code doesn't
962 recognize.
962 recognize.
963
963
964 Returns a set of supported requirements.
964 Returns a set of supported requirements.
965 """
965 """
966 missing = set()
966 missing = set()
967
967
968 for requirement in requirements:
968 for requirement in requirements:
969 if requirement in supported:
969 if requirement in supported:
970 continue
970 continue
971
971
972 if not requirement or not requirement[0:1].isalnum():
972 if not requirement or not requirement[0:1].isalnum():
973 raise error.RequirementError(_(b'.hg/requires file is corrupt'))
973 raise error.RequirementError(_(b'.hg/requires file is corrupt'))
974
974
975 missing.add(requirement)
975 missing.add(requirement)
976
976
977 if missing:
977 if missing:
978 raise error.RequirementError(
978 raise error.RequirementError(
979 _(b'repository requires features unknown to this Mercurial: %s')
979 _(b'repository requires features unknown to this Mercurial: %s')
980 % b' '.join(sorted(missing)),
980 % b' '.join(sorted(missing)),
981 hint=_(
981 hint=_(
982 b'see https://mercurial-scm.org/wiki/MissingRequirement '
982 b'see https://mercurial-scm.org/wiki/MissingRequirement '
983 b'for more information'
983 b'for more information'
984 ),
984 ),
985 )
985 )
986
986
987
987
988 def ensurerequirementscompatible(ui, requirements):
988 def ensurerequirementscompatible(ui, requirements):
989 """Validates that a set of recognized requirements is mutually compatible.
989 """Validates that a set of recognized requirements is mutually compatible.
990
990
991 Some requirements may not be compatible with others or require
991 Some requirements may not be compatible with others or require
992 config options that aren't enabled. This function is called during
992 config options that aren't enabled. This function is called during
993 repository opening to ensure that the set of requirements needed
993 repository opening to ensure that the set of requirements needed
994 to open a repository is sane and compatible with config options.
994 to open a repository is sane and compatible with config options.
995
995
996 Extensions can monkeypatch this function to perform additional
996 Extensions can monkeypatch this function to perform additional
997 checking.
997 checking.
998
998
999 ``error.RepoError`` should be raised on failure.
999 ``error.RepoError`` should be raised on failure.
1000 """
1000 """
1001 if (
1001 if (
1002 requirementsmod.SPARSE_REQUIREMENT in requirements
1002 requirementsmod.SPARSE_REQUIREMENT in requirements
1003 and not sparse.enabled
1003 and not sparse.enabled
1004 ):
1004 ):
1005 raise error.RepoError(
1005 raise error.RepoError(
1006 _(
1006 _(
1007 b'repository is using sparse feature but '
1007 b'repository is using sparse feature but '
1008 b'sparse is not enabled; enable the '
1008 b'sparse is not enabled; enable the '
1009 b'"sparse" extensions to access'
1009 b'"sparse" extensions to access'
1010 )
1010 )
1011 )
1011 )
1012
1012
1013
1013
1014 def makestore(requirements, path, vfstype):
1014 def makestore(requirements, path, vfstype):
1015 """Construct a storage object for a repository."""
1015 """Construct a storage object for a repository."""
1016 if requirementsmod.STORE_REQUIREMENT in requirements:
1016 if requirementsmod.STORE_REQUIREMENT in requirements:
1017 if requirementsmod.FNCACHE_REQUIREMENT in requirements:
1017 if requirementsmod.FNCACHE_REQUIREMENT in requirements:
1018 dotencode = requirementsmod.DOTENCODE_REQUIREMENT in requirements
1018 dotencode = requirementsmod.DOTENCODE_REQUIREMENT in requirements
1019 return storemod.fncachestore(path, vfstype, dotencode)
1019 return storemod.fncachestore(path, vfstype, dotencode)
1020
1020
1021 return storemod.encodedstore(path, vfstype)
1021 return storemod.encodedstore(path, vfstype)
1022
1022
1023 return storemod.basicstore(path, vfstype)
1023 return storemod.basicstore(path, vfstype)
1024
1024
1025
1025
1026 def resolvestorevfsoptions(ui, requirements, features):
1026 def resolvestorevfsoptions(ui, requirements, features):
1027 """Resolve the options to pass to the store vfs opener.
1027 """Resolve the options to pass to the store vfs opener.
1028
1028
1029 The returned dict is used to influence behavior of the storage layer.
1029 The returned dict is used to influence behavior of the storage layer.
1030 """
1030 """
1031 options = {}
1031 options = {}
1032
1032
1033 if requirementsmod.TREEMANIFEST_REQUIREMENT in requirements:
1033 if requirementsmod.TREEMANIFEST_REQUIREMENT in requirements:
1034 options[b'treemanifest'] = True
1034 options[b'treemanifest'] = True
1035
1035
1036 # experimental config: format.manifestcachesize
1036 # experimental config: format.manifestcachesize
1037 manifestcachesize = ui.configint(b'format', b'manifestcachesize')
1037 manifestcachesize = ui.configint(b'format', b'manifestcachesize')
1038 if manifestcachesize is not None:
1038 if manifestcachesize is not None:
1039 options[b'manifestcachesize'] = manifestcachesize
1039 options[b'manifestcachesize'] = manifestcachesize
1040
1040
1041 # In the absence of another requirement superseding a revlog-related
1041 # In the absence of another requirement superseding a revlog-related
1042 # requirement, we have to assume the repo is using revlog version 0.
1042 # requirement, we have to assume the repo is using revlog version 0.
1043 # This revlog format is super old and we don't bother trying to parse
1043 # This revlog format is super old and we don't bother trying to parse
1044 # opener options for it because those options wouldn't do anything
1044 # opener options for it because those options wouldn't do anything
1045 # meaningful on such old repos.
1045 # meaningful on such old repos.
1046 if (
1046 if (
1047 requirementsmod.REVLOGV1_REQUIREMENT in requirements
1047 requirementsmod.REVLOGV1_REQUIREMENT in requirements
1048 or requirementsmod.REVLOGV2_REQUIREMENT in requirements
1048 or requirementsmod.REVLOGV2_REQUIREMENT in requirements
1049 ):
1049 ):
1050 options.update(resolverevlogstorevfsoptions(ui, requirements, features))
1050 options.update(resolverevlogstorevfsoptions(ui, requirements, features))
1051 else: # explicitly mark repo as using revlogv0
1051 else: # explicitly mark repo as using revlogv0
1052 options[b'revlogv0'] = True
1052 options[b'revlogv0'] = True
1053
1053
1054 if requirementsmod.COPIESSDC_REQUIREMENT in requirements:
1054 if requirementsmod.COPIESSDC_REQUIREMENT in requirements:
1055 options[b'copies-storage'] = b'changeset-sidedata'
1055 options[b'copies-storage'] = b'changeset-sidedata'
1056 else:
1056 else:
1057 writecopiesto = ui.config(b'experimental', b'copies.write-to')
1057 writecopiesto = ui.config(b'experimental', b'copies.write-to')
1058 copiesextramode = (b'changeset-only', b'compatibility')
1058 copiesextramode = (b'changeset-only', b'compatibility')
1059 if writecopiesto in copiesextramode:
1059 if writecopiesto in copiesextramode:
1060 options[b'copies-storage'] = b'extra'
1060 options[b'copies-storage'] = b'extra'
1061
1061
1062 return options
1062 return options
1063
1063
1064
1064
1065 def resolverevlogstorevfsoptions(ui, requirements, features):
1065 def resolverevlogstorevfsoptions(ui, requirements, features):
1066 """Resolve opener options specific to revlogs."""
1066 """Resolve opener options specific to revlogs."""
1067
1067
1068 options = {}
1068 options = {}
1069 options[b'flagprocessors'] = {}
1069 options[b'flagprocessors'] = {}
1070
1070
1071 feature_config = options[b'feature-config'] = revlog.FeatureConfig()
1071 feature_config = options[b'feature-config'] = revlog.FeatureConfig()
1072 data_config = options[b'data-config'] = revlog.DataConfig()
1072 data_config = options[b'data-config'] = revlog.DataConfig()
1073 delta_config = options[b'delta-config'] = revlog.DeltaConfig()
1073 delta_config = options[b'delta-config'] = revlog.DeltaConfig()
1074
1074
1075 if requirementsmod.REVLOGV1_REQUIREMENT in requirements:
1075 if requirementsmod.REVLOGV1_REQUIREMENT in requirements:
1076 options[b'revlogv1'] = True
1076 options[b'revlogv1'] = True
1077 if requirementsmod.REVLOGV2_REQUIREMENT in requirements:
1077 if requirementsmod.REVLOGV2_REQUIREMENT in requirements:
1078 options[b'revlogv2'] = True
1078 options[b'revlogv2'] = True
1079 if requirementsmod.CHANGELOGV2_REQUIREMENT in requirements:
1079 if requirementsmod.CHANGELOGV2_REQUIREMENT in requirements:
1080 options[b'changelogv2'] = True
1080 options[b'changelogv2'] = True
1081 cmp_rank = ui.configbool(b'experimental', b'changelog-v2.compute-rank')
1081 cmp_rank = ui.configbool(b'experimental', b'changelog-v2.compute-rank')
1082 options[b'changelogv2.compute-rank'] = cmp_rank
1082 options[b'changelogv2.compute-rank'] = cmp_rank
1083
1083
1084 if requirementsmod.GENERALDELTA_REQUIREMENT in requirements:
1084 if requirementsmod.GENERALDELTA_REQUIREMENT in requirements:
1085 options[b'generaldelta'] = True
1085 options[b'generaldelta'] = True
1086
1086
1087 # experimental config: format.chunkcachesize
1087 # experimental config: format.chunkcachesize
1088 chunkcachesize = ui.configint(b'format', b'chunkcachesize')
1088 chunkcachesize = ui.configint(b'format', b'chunkcachesize')
1089 if chunkcachesize is not None:
1089 if chunkcachesize is not None:
1090 data_config.chunk_cache_size = chunkcachesize
1090 data_config.chunk_cache_size = chunkcachesize
1091
1091
1092 delta_config.delta_both_parents = ui.configbool(
1092 delta_config.delta_both_parents = ui.configbool(
1093 b'storage', b'revlog.optimize-delta-parent-choice'
1093 b'storage', b'revlog.optimize-delta-parent-choice'
1094 )
1094 )
1095 delta_config.candidate_group_chunk_size = ui.configint(
1095 delta_config.candidate_group_chunk_size = ui.configint(
1096 b'storage',
1096 b'storage',
1097 b'revlog.delta-parent-search.candidate-group-chunk-size',
1097 b'revlog.delta-parent-search.candidate-group-chunk-size',
1098 )
1098 )
1099 delta_config.debug_delta = ui.configbool(b'debug', b'revlog.debug-delta')
1099 delta_config.debug_delta = ui.configbool(b'debug', b'revlog.debug-delta')
1100
1100
1101 issue6528 = ui.configbool(b'storage', b'revlog.issue6528.fix-incoming')
1101 issue6528 = ui.configbool(b'storage', b'revlog.issue6528.fix-incoming')
1102 options[b'issue6528.fix-incoming'] = issue6528
1102 options[b'issue6528.fix-incoming'] = issue6528
1103
1103
1104 lazydelta = ui.configbool(b'storage', b'revlog.reuse-external-delta')
1104 lazydelta = ui.configbool(b'storage', b'revlog.reuse-external-delta')
1105 lazydeltabase = False
1105 lazydeltabase = False
1106 if lazydelta:
1106 if lazydelta:
1107 lazydeltabase = ui.configbool(
1107 lazydeltabase = ui.configbool(
1108 b'storage', b'revlog.reuse-external-delta-parent'
1108 b'storage', b'revlog.reuse-external-delta-parent'
1109 )
1109 )
1110 if lazydeltabase is None:
1110 if lazydeltabase is None:
1111 lazydeltabase = not scmutil.gddeltaconfig(ui)
1111 lazydeltabase = not scmutil.gddeltaconfig(ui)
1112 delta_config.lazy_delta = lazydelta
1112 delta_config.lazy_delta = lazydelta
1113 delta_config.lazy_delta_base = lazydeltabase
1113 delta_config.lazy_delta_base = lazydeltabase
1114
1114
1115 chainspan = ui.configbytes(b'experimental', b'maxdeltachainspan')
1115 chainspan = ui.configbytes(b'experimental', b'maxdeltachainspan')
1116 if 0 <= chainspan:
1116 if 0 <= chainspan:
1117 options[b'maxdeltachainspan'] = chainspan
1117 delta_config.max_deltachain_span = chainspan
1118
1118
1119 mmapindexthreshold = ui.configbytes(b'experimental', b'mmapindexthreshold')
1119 mmapindexthreshold = ui.configbytes(b'experimental', b'mmapindexthreshold')
1120 if mmapindexthreshold is not None:
1120 if mmapindexthreshold is not None:
1121 options[b'mmapindexthreshold'] = mmapindexthreshold
1121 options[b'mmapindexthreshold'] = mmapindexthreshold
1122
1122
1123 withsparseread = ui.configbool(b'experimental', b'sparse-read')
1123 withsparseread = ui.configbool(b'experimental', b'sparse-read')
1124 srdensitythres = float(
1124 srdensitythres = float(
1125 ui.config(b'experimental', b'sparse-read.density-threshold')
1125 ui.config(b'experimental', b'sparse-read.density-threshold')
1126 )
1126 )
1127 srmingapsize = ui.configbytes(b'experimental', b'sparse-read.min-gap-size')
1127 srmingapsize = ui.configbytes(b'experimental', b'sparse-read.min-gap-size')
1128 options[b'with-sparse-read'] = withsparseread
1128 options[b'with-sparse-read'] = withsparseread
1129 options[b'sparse-read-density-threshold'] = srdensitythres
1129 options[b'sparse-read-density-threshold'] = srdensitythres
1130 options[b'sparse-read-min-gap-size'] = srmingapsize
1130 options[b'sparse-read-min-gap-size'] = srmingapsize
1131
1131
1132 sparserevlog = requirementsmod.SPARSEREVLOG_REQUIREMENT in requirements
1132 sparserevlog = requirementsmod.SPARSEREVLOG_REQUIREMENT in requirements
1133 options[b'sparse-revlog'] = sparserevlog
1133 options[b'sparse-revlog'] = sparserevlog
1134 if sparserevlog:
1134 if sparserevlog:
1135 options[b'generaldelta'] = True
1135 options[b'generaldelta'] = True
1136
1136
1137 maxchainlen = None
1137 maxchainlen = None
1138 if sparserevlog:
1138 if sparserevlog:
1139 maxchainlen = revlogconst.SPARSE_REVLOG_MAX_CHAIN_LENGTH
1139 maxchainlen = revlogconst.SPARSE_REVLOG_MAX_CHAIN_LENGTH
1140 # experimental config: format.maxchainlen
1140 # experimental config: format.maxchainlen
1141 maxchainlen = ui.configint(b'format', b'maxchainlen', maxchainlen)
1141 maxchainlen = ui.configint(b'format', b'maxchainlen', maxchainlen)
1142 if maxchainlen is not None:
1142 if maxchainlen is not None:
1143 options[b'maxchainlen'] = maxchainlen
1143 options[b'maxchainlen'] = maxchainlen
1144
1144
1145 for r in requirements:
1145 for r in requirements:
1146 # we allow multiple compression engine requirement to co-exist because
1146 # we allow multiple compression engine requirement to co-exist because
1147 # strickly speaking, revlog seems to support mixed compression style.
1147 # strickly speaking, revlog seems to support mixed compression style.
1148 #
1148 #
1149 # The compression used for new entries will be "the last one"
1149 # The compression used for new entries will be "the last one"
1150 prefix = r.startswith
1150 prefix = r.startswith
1151 if prefix(b'revlog-compression-') or prefix(b'exp-compression-'):
1151 if prefix(b'revlog-compression-') or prefix(b'exp-compression-'):
1152 options[b'compengine'] = r.split(b'-', 2)[2]
1152 options[b'compengine'] = r.split(b'-', 2)[2]
1153
1153
1154 options[b'zlib.level'] = ui.configint(b'storage', b'revlog.zlib.level')
1154 options[b'zlib.level'] = ui.configint(b'storage', b'revlog.zlib.level')
1155 if options[b'zlib.level'] is not None:
1155 if options[b'zlib.level'] is not None:
1156 if not (0 <= options[b'zlib.level'] <= 9):
1156 if not (0 <= options[b'zlib.level'] <= 9):
1157 msg = _(b'invalid value for `storage.revlog.zlib.level` config: %d')
1157 msg = _(b'invalid value for `storage.revlog.zlib.level` config: %d')
1158 raise error.Abort(msg % options[b'zlib.level'])
1158 raise error.Abort(msg % options[b'zlib.level'])
1159 options[b'zstd.level'] = ui.configint(b'storage', b'revlog.zstd.level')
1159 options[b'zstd.level'] = ui.configint(b'storage', b'revlog.zstd.level')
1160 if options[b'zstd.level'] is not None:
1160 if options[b'zstd.level'] is not None:
1161 if not (0 <= options[b'zstd.level'] <= 22):
1161 if not (0 <= options[b'zstd.level'] <= 22):
1162 msg = _(b'invalid value for `storage.revlog.zstd.level` config: %d')
1162 msg = _(b'invalid value for `storage.revlog.zstd.level` config: %d')
1163 raise error.Abort(msg % options[b'zstd.level'])
1163 raise error.Abort(msg % options[b'zstd.level'])
1164
1164
1165 if requirementsmod.NARROW_REQUIREMENT in requirements:
1165 if requirementsmod.NARROW_REQUIREMENT in requirements:
1166 options[b'enableellipsis'] = True
1166 options[b'enableellipsis'] = True
1167
1167
1168 if ui.configbool(b'experimental', b'rust.index'):
1168 if ui.configbool(b'experimental', b'rust.index'):
1169 options[b'rust.index'] = True
1169 options[b'rust.index'] = True
1170 if requirementsmod.NODEMAP_REQUIREMENT in requirements:
1170 if requirementsmod.NODEMAP_REQUIREMENT in requirements:
1171 slow_path = ui.config(
1171 slow_path = ui.config(
1172 b'storage', b'revlog.persistent-nodemap.slow-path'
1172 b'storage', b'revlog.persistent-nodemap.slow-path'
1173 )
1173 )
1174 if slow_path not in (b'allow', b'warn', b'abort'):
1174 if slow_path not in (b'allow', b'warn', b'abort'):
1175 default = ui.config_default(
1175 default = ui.config_default(
1176 b'storage', b'revlog.persistent-nodemap.slow-path'
1176 b'storage', b'revlog.persistent-nodemap.slow-path'
1177 )
1177 )
1178 msg = _(
1178 msg = _(
1179 b'unknown value for config '
1179 b'unknown value for config '
1180 b'"storage.revlog.persistent-nodemap.slow-path": "%s"\n'
1180 b'"storage.revlog.persistent-nodemap.slow-path": "%s"\n'
1181 )
1181 )
1182 ui.warn(msg % slow_path)
1182 ui.warn(msg % slow_path)
1183 if not ui.quiet:
1183 if not ui.quiet:
1184 ui.warn(_(b'falling back to default value: %s\n') % default)
1184 ui.warn(_(b'falling back to default value: %s\n') % default)
1185 slow_path = default
1185 slow_path = default
1186
1186
1187 msg = _(
1187 msg = _(
1188 b"accessing `persistent-nodemap` repository without associated "
1188 b"accessing `persistent-nodemap` repository without associated "
1189 b"fast implementation."
1189 b"fast implementation."
1190 )
1190 )
1191 hint = _(
1191 hint = _(
1192 b"check `hg help config.format.use-persistent-nodemap` "
1192 b"check `hg help config.format.use-persistent-nodemap` "
1193 b"for details"
1193 b"for details"
1194 )
1194 )
1195 if not revlog.HAS_FAST_PERSISTENT_NODEMAP:
1195 if not revlog.HAS_FAST_PERSISTENT_NODEMAP:
1196 if slow_path == b'warn':
1196 if slow_path == b'warn':
1197 msg = b"warning: " + msg + b'\n'
1197 msg = b"warning: " + msg + b'\n'
1198 ui.warn(msg)
1198 ui.warn(msg)
1199 if not ui.quiet:
1199 if not ui.quiet:
1200 hint = b'(' + hint + b')\n'
1200 hint = b'(' + hint + b')\n'
1201 ui.warn(hint)
1201 ui.warn(hint)
1202 if slow_path == b'abort':
1202 if slow_path == b'abort':
1203 raise error.Abort(msg, hint=hint)
1203 raise error.Abort(msg, hint=hint)
1204 options[b'persistent-nodemap'] = True
1204 options[b'persistent-nodemap'] = True
1205 if requirementsmod.DIRSTATE_V2_REQUIREMENT in requirements:
1205 if requirementsmod.DIRSTATE_V2_REQUIREMENT in requirements:
1206 slow_path = ui.config(b'storage', b'dirstate-v2.slow-path')
1206 slow_path = ui.config(b'storage', b'dirstate-v2.slow-path')
1207 if slow_path not in (b'allow', b'warn', b'abort'):
1207 if slow_path not in (b'allow', b'warn', b'abort'):
1208 default = ui.config_default(b'storage', b'dirstate-v2.slow-path')
1208 default = ui.config_default(b'storage', b'dirstate-v2.slow-path')
1209 msg = _(b'unknown value for config "dirstate-v2.slow-path": "%s"\n')
1209 msg = _(b'unknown value for config "dirstate-v2.slow-path": "%s"\n')
1210 ui.warn(msg % slow_path)
1210 ui.warn(msg % slow_path)
1211 if not ui.quiet:
1211 if not ui.quiet:
1212 ui.warn(_(b'falling back to default value: %s\n') % default)
1212 ui.warn(_(b'falling back to default value: %s\n') % default)
1213 slow_path = default
1213 slow_path = default
1214
1214
1215 msg = _(
1215 msg = _(
1216 b"accessing `dirstate-v2` repository without associated "
1216 b"accessing `dirstate-v2` repository without associated "
1217 b"fast implementation."
1217 b"fast implementation."
1218 )
1218 )
1219 hint = _(
1219 hint = _(
1220 b"check `hg help config.format.use-dirstate-v2` " b"for details"
1220 b"check `hg help config.format.use-dirstate-v2` " b"for details"
1221 )
1221 )
1222 if not dirstate.HAS_FAST_DIRSTATE_V2:
1222 if not dirstate.HAS_FAST_DIRSTATE_V2:
1223 if slow_path == b'warn':
1223 if slow_path == b'warn':
1224 msg = b"warning: " + msg + b'\n'
1224 msg = b"warning: " + msg + b'\n'
1225 ui.warn(msg)
1225 ui.warn(msg)
1226 if not ui.quiet:
1226 if not ui.quiet:
1227 hint = b'(' + hint + b')\n'
1227 hint = b'(' + hint + b')\n'
1228 ui.warn(hint)
1228 ui.warn(hint)
1229 if slow_path == b'abort':
1229 if slow_path == b'abort':
1230 raise error.Abort(msg, hint=hint)
1230 raise error.Abort(msg, hint=hint)
1231 if ui.configbool(b'storage', b'revlog.persistent-nodemap.mmap'):
1231 if ui.configbool(b'storage', b'revlog.persistent-nodemap.mmap'):
1232 options[b'persistent-nodemap.mmap'] = True
1232 options[b'persistent-nodemap.mmap'] = True
1233 if ui.configbool(b'devel', b'persistent-nodemap'):
1233 if ui.configbool(b'devel', b'persistent-nodemap'):
1234 options[b'devel-force-nodemap'] = True
1234 options[b'devel-force-nodemap'] = True
1235
1235
1236 return options
1236 return options
1237
1237
1238
1238
1239 def makemain(**kwargs):
1239 def makemain(**kwargs):
1240 """Produce a type conforming to ``ilocalrepositorymain``."""
1240 """Produce a type conforming to ``ilocalrepositorymain``."""
1241 return localrepository
1241 return localrepository
1242
1242
1243
1243
1244 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
1244 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
1245 class revlogfilestorage:
1245 class revlogfilestorage:
1246 """File storage when using revlogs."""
1246 """File storage when using revlogs."""
1247
1247
1248 def file(self, path):
1248 def file(self, path):
1249 if path.startswith(b'/'):
1249 if path.startswith(b'/'):
1250 path = path[1:]
1250 path = path[1:]
1251
1251
1252 try_split = (
1252 try_split = (
1253 self.currenttransaction() is not None
1253 self.currenttransaction() is not None
1254 or txnutil.mayhavepending(self.root)
1254 or txnutil.mayhavepending(self.root)
1255 )
1255 )
1256
1256
1257 return filelog.filelog(self.svfs, path, try_split=try_split)
1257 return filelog.filelog(self.svfs, path, try_split=try_split)
1258
1258
1259
1259
1260 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
1260 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
1261 class revlognarrowfilestorage:
1261 class revlognarrowfilestorage:
1262 """File storage when using revlogs and narrow files."""
1262 """File storage when using revlogs and narrow files."""
1263
1263
1264 def file(self, path):
1264 def file(self, path):
1265 if path.startswith(b'/'):
1265 if path.startswith(b'/'):
1266 path = path[1:]
1266 path = path[1:]
1267
1267
1268 try_split = (
1268 try_split = (
1269 self.currenttransaction() is not None
1269 self.currenttransaction() is not None
1270 or txnutil.mayhavepending(self.root)
1270 or txnutil.mayhavepending(self.root)
1271 )
1271 )
1272 return filelog.narrowfilelog(
1272 return filelog.narrowfilelog(
1273 self.svfs, path, self._storenarrowmatch, try_split=try_split
1273 self.svfs, path, self._storenarrowmatch, try_split=try_split
1274 )
1274 )
1275
1275
1276
1276
1277 def makefilestorage(requirements, features, **kwargs):
1277 def makefilestorage(requirements, features, **kwargs):
1278 """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
1278 """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
1279 features.add(repository.REPO_FEATURE_REVLOG_FILE_STORAGE)
1279 features.add(repository.REPO_FEATURE_REVLOG_FILE_STORAGE)
1280 features.add(repository.REPO_FEATURE_STREAM_CLONE)
1280 features.add(repository.REPO_FEATURE_STREAM_CLONE)
1281
1281
1282 if requirementsmod.NARROW_REQUIREMENT in requirements:
1282 if requirementsmod.NARROW_REQUIREMENT in requirements:
1283 return revlognarrowfilestorage
1283 return revlognarrowfilestorage
1284 else:
1284 else:
1285 return revlogfilestorage
1285 return revlogfilestorage
1286
1286
1287
1287
1288 # List of repository interfaces and factory functions for them. Each
1288 # List of repository interfaces and factory functions for them. Each
1289 # will be called in order during ``makelocalrepository()`` to iteratively
1289 # will be called in order during ``makelocalrepository()`` to iteratively
1290 # derive the final type for a local repository instance. We capture the
1290 # derive the final type for a local repository instance. We capture the
1291 # function as a lambda so we don't hold a reference and the module-level
1291 # function as a lambda so we don't hold a reference and the module-level
1292 # functions can be wrapped.
1292 # functions can be wrapped.
1293 REPO_INTERFACES = [
1293 REPO_INTERFACES = [
1294 (repository.ilocalrepositorymain, lambda: makemain),
1294 (repository.ilocalrepositorymain, lambda: makemain),
1295 (repository.ilocalrepositoryfilestorage, lambda: makefilestorage),
1295 (repository.ilocalrepositoryfilestorage, lambda: makefilestorage),
1296 ]
1296 ]
1297
1297
1298
1298
1299 @interfaceutil.implementer(repository.ilocalrepositorymain)
1299 @interfaceutil.implementer(repository.ilocalrepositorymain)
1300 class localrepository:
1300 class localrepository:
1301 """Main class for representing local repositories.
1301 """Main class for representing local repositories.
1302
1302
1303 All local repositories are instances of this class.
1303 All local repositories are instances of this class.
1304
1304
1305 Constructed on its own, instances of this class are not usable as
1305 Constructed on its own, instances of this class are not usable as
1306 repository objects. To obtain a usable repository object, call
1306 repository objects. To obtain a usable repository object, call
1307 ``hg.repository()``, ``localrepo.instance()``, or
1307 ``hg.repository()``, ``localrepo.instance()``, or
1308 ``localrepo.makelocalrepository()``. The latter is the lowest-level.
1308 ``localrepo.makelocalrepository()``. The latter is the lowest-level.
1309 ``instance()`` adds support for creating new repositories.
1309 ``instance()`` adds support for creating new repositories.
1310 ``hg.repository()`` adds more extension integration, including calling
1310 ``hg.repository()`` adds more extension integration, including calling
1311 ``reposetup()``. Generally speaking, ``hg.repository()`` should be
1311 ``reposetup()``. Generally speaking, ``hg.repository()`` should be
1312 used.
1312 used.
1313 """
1313 """
1314
1314
1315 _basesupported = {
1315 _basesupported = {
1316 requirementsmod.ARCHIVED_PHASE_REQUIREMENT,
1316 requirementsmod.ARCHIVED_PHASE_REQUIREMENT,
1317 requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT,
1317 requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT,
1318 requirementsmod.CHANGELOGV2_REQUIREMENT,
1318 requirementsmod.CHANGELOGV2_REQUIREMENT,
1319 requirementsmod.COPIESSDC_REQUIREMENT,
1319 requirementsmod.COPIESSDC_REQUIREMENT,
1320 requirementsmod.DIRSTATE_TRACKED_HINT_V1,
1320 requirementsmod.DIRSTATE_TRACKED_HINT_V1,
1321 requirementsmod.DIRSTATE_V2_REQUIREMENT,
1321 requirementsmod.DIRSTATE_V2_REQUIREMENT,
1322 requirementsmod.DOTENCODE_REQUIREMENT,
1322 requirementsmod.DOTENCODE_REQUIREMENT,
1323 requirementsmod.FNCACHE_REQUIREMENT,
1323 requirementsmod.FNCACHE_REQUIREMENT,
1324 requirementsmod.GENERALDELTA_REQUIREMENT,
1324 requirementsmod.GENERALDELTA_REQUIREMENT,
1325 requirementsmod.INTERNAL_PHASE_REQUIREMENT,
1325 requirementsmod.INTERNAL_PHASE_REQUIREMENT,
1326 requirementsmod.NODEMAP_REQUIREMENT,
1326 requirementsmod.NODEMAP_REQUIREMENT,
1327 requirementsmod.RELATIVE_SHARED_REQUIREMENT,
1327 requirementsmod.RELATIVE_SHARED_REQUIREMENT,
1328 requirementsmod.REVLOGV1_REQUIREMENT,
1328 requirementsmod.REVLOGV1_REQUIREMENT,
1329 requirementsmod.REVLOGV2_REQUIREMENT,
1329 requirementsmod.REVLOGV2_REQUIREMENT,
1330 requirementsmod.SHARED_REQUIREMENT,
1330 requirementsmod.SHARED_REQUIREMENT,
1331 requirementsmod.SHARESAFE_REQUIREMENT,
1331 requirementsmod.SHARESAFE_REQUIREMENT,
1332 requirementsmod.SPARSE_REQUIREMENT,
1332 requirementsmod.SPARSE_REQUIREMENT,
1333 requirementsmod.SPARSEREVLOG_REQUIREMENT,
1333 requirementsmod.SPARSEREVLOG_REQUIREMENT,
1334 requirementsmod.STORE_REQUIREMENT,
1334 requirementsmod.STORE_REQUIREMENT,
1335 requirementsmod.TREEMANIFEST_REQUIREMENT,
1335 requirementsmod.TREEMANIFEST_REQUIREMENT,
1336 }
1336 }
1337
1337
1338 # list of prefix for file which can be written without 'wlock'
1338 # list of prefix for file which can be written without 'wlock'
1339 # Extensions should extend this list when needed
1339 # Extensions should extend this list when needed
1340 _wlockfreeprefix = {
1340 _wlockfreeprefix = {
1341 # We migh consider requiring 'wlock' for the next
1341 # We migh consider requiring 'wlock' for the next
1342 # two, but pretty much all the existing code assume
1342 # two, but pretty much all the existing code assume
1343 # wlock is not needed so we keep them excluded for
1343 # wlock is not needed so we keep them excluded for
1344 # now.
1344 # now.
1345 b'hgrc',
1345 b'hgrc',
1346 b'requires',
1346 b'requires',
1347 # XXX cache is a complicatged business someone
1347 # XXX cache is a complicatged business someone
1348 # should investigate this in depth at some point
1348 # should investigate this in depth at some point
1349 b'cache/',
1349 b'cache/',
1350 # XXX bisect was still a bit too messy at the time
1350 # XXX bisect was still a bit too messy at the time
1351 # this changeset was introduced. Someone should fix
1351 # this changeset was introduced. Someone should fix
1352 # the remainig bit and drop this line
1352 # the remainig bit and drop this line
1353 b'bisect.state',
1353 b'bisect.state',
1354 }
1354 }
1355
1355
1356 def __init__(
1356 def __init__(
1357 self,
1357 self,
1358 baseui,
1358 baseui,
1359 ui,
1359 ui,
1360 origroot: bytes,
1360 origroot: bytes,
1361 wdirvfs: vfsmod.vfs,
1361 wdirvfs: vfsmod.vfs,
1362 hgvfs: vfsmod.vfs,
1362 hgvfs: vfsmod.vfs,
1363 requirements,
1363 requirements,
1364 supportedrequirements,
1364 supportedrequirements,
1365 sharedpath: bytes,
1365 sharedpath: bytes,
1366 store,
1366 store,
1367 cachevfs: vfsmod.vfs,
1367 cachevfs: vfsmod.vfs,
1368 wcachevfs: vfsmod.vfs,
1368 wcachevfs: vfsmod.vfs,
1369 features,
1369 features,
1370 intents=None,
1370 intents=None,
1371 ):
1371 ):
1372 """Create a new local repository instance.
1372 """Create a new local repository instance.
1373
1373
1374 Most callers should use ``hg.repository()``, ``localrepo.instance()``,
1374 Most callers should use ``hg.repository()``, ``localrepo.instance()``,
1375 or ``localrepo.makelocalrepository()`` for obtaining a new repository
1375 or ``localrepo.makelocalrepository()`` for obtaining a new repository
1376 object.
1376 object.
1377
1377
1378 Arguments:
1378 Arguments:
1379
1379
1380 baseui
1380 baseui
1381 ``ui.ui`` instance that ``ui`` argument was based off of.
1381 ``ui.ui`` instance that ``ui`` argument was based off of.
1382
1382
1383 ui
1383 ui
1384 ``ui.ui`` instance for use by the repository.
1384 ``ui.ui`` instance for use by the repository.
1385
1385
1386 origroot
1386 origroot
1387 ``bytes`` path to working directory root of this repository.
1387 ``bytes`` path to working directory root of this repository.
1388
1388
1389 wdirvfs
1389 wdirvfs
1390 ``vfs.vfs`` rooted at the working directory.
1390 ``vfs.vfs`` rooted at the working directory.
1391
1391
1392 hgvfs
1392 hgvfs
1393 ``vfs.vfs`` rooted at .hg/
1393 ``vfs.vfs`` rooted at .hg/
1394
1394
1395 requirements
1395 requirements
1396 ``set`` of bytestrings representing repository opening requirements.
1396 ``set`` of bytestrings representing repository opening requirements.
1397
1397
1398 supportedrequirements
1398 supportedrequirements
1399 ``set`` of bytestrings representing repository requirements that we
1399 ``set`` of bytestrings representing repository requirements that we
1400 know how to open. May be a supetset of ``requirements``.
1400 know how to open. May be a supetset of ``requirements``.
1401
1401
1402 sharedpath
1402 sharedpath
1403 ``bytes`` Defining path to storage base directory. Points to a
1403 ``bytes`` Defining path to storage base directory. Points to a
1404 ``.hg/`` directory somewhere.
1404 ``.hg/`` directory somewhere.
1405
1405
1406 store
1406 store
1407 ``store.basicstore`` (or derived) instance providing access to
1407 ``store.basicstore`` (or derived) instance providing access to
1408 versioned storage.
1408 versioned storage.
1409
1409
1410 cachevfs
1410 cachevfs
1411 ``vfs.vfs`` used for cache files.
1411 ``vfs.vfs`` used for cache files.
1412
1412
1413 wcachevfs
1413 wcachevfs
1414 ``vfs.vfs`` used for cache files related to the working copy.
1414 ``vfs.vfs`` used for cache files related to the working copy.
1415
1415
1416 features
1416 features
1417 ``set`` of bytestrings defining features/capabilities of this
1417 ``set`` of bytestrings defining features/capabilities of this
1418 instance.
1418 instance.
1419
1419
1420 intents
1420 intents
1421 ``set`` of system strings indicating what this repo will be used
1421 ``set`` of system strings indicating what this repo will be used
1422 for.
1422 for.
1423 """
1423 """
1424 self.baseui = baseui
1424 self.baseui = baseui
1425 self.ui = ui
1425 self.ui = ui
1426 self.origroot = origroot
1426 self.origroot = origroot
1427 # vfs rooted at working directory.
1427 # vfs rooted at working directory.
1428 self.wvfs = wdirvfs
1428 self.wvfs = wdirvfs
1429 self.root = wdirvfs.base
1429 self.root = wdirvfs.base
1430 # vfs rooted at .hg/. Used to access most non-store paths.
1430 # vfs rooted at .hg/. Used to access most non-store paths.
1431 self.vfs = hgvfs
1431 self.vfs = hgvfs
1432 self.path = hgvfs.base
1432 self.path = hgvfs.base
1433 self.requirements = requirements
1433 self.requirements = requirements
1434 self.nodeconstants = sha1nodeconstants
1434 self.nodeconstants = sha1nodeconstants
1435 self.nullid = self.nodeconstants.nullid
1435 self.nullid = self.nodeconstants.nullid
1436 self.supported = supportedrequirements
1436 self.supported = supportedrequirements
1437 self.sharedpath = sharedpath
1437 self.sharedpath = sharedpath
1438 self.store = store
1438 self.store = store
1439 self.cachevfs = cachevfs
1439 self.cachevfs = cachevfs
1440 self.wcachevfs = wcachevfs
1440 self.wcachevfs = wcachevfs
1441 self.features = features
1441 self.features = features
1442
1442
1443 self.filtername = None
1443 self.filtername = None
1444
1444
1445 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1445 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1446 b'devel', b'check-locks'
1446 b'devel', b'check-locks'
1447 ):
1447 ):
1448 self.vfs.audit = self._getvfsward(self.vfs.audit)
1448 self.vfs.audit = self._getvfsward(self.vfs.audit)
1449 # A list of callback to shape the phase if no data were found.
1449 # A list of callback to shape the phase if no data were found.
1450 # Callback are in the form: func(repo, roots) --> processed root.
1450 # Callback are in the form: func(repo, roots) --> processed root.
1451 # This list it to be filled by extension during repo setup
1451 # This list it to be filled by extension during repo setup
1452 self._phasedefaults = []
1452 self._phasedefaults = []
1453
1453
1454 color.setup(self.ui)
1454 color.setup(self.ui)
1455
1455
1456 self.spath = self.store.path
1456 self.spath = self.store.path
1457 self.svfs = self.store.vfs
1457 self.svfs = self.store.vfs
1458 self.sjoin = self.store.join
1458 self.sjoin = self.store.join
1459 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1459 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1460 b'devel', b'check-locks'
1460 b'devel', b'check-locks'
1461 ):
1461 ):
1462 if hasattr(self.svfs, 'vfs'): # this is filtervfs
1462 if hasattr(self.svfs, 'vfs'): # this is filtervfs
1463 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
1463 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
1464 else: # standard vfs
1464 else: # standard vfs
1465 self.svfs.audit = self._getsvfsward(self.svfs.audit)
1465 self.svfs.audit = self._getsvfsward(self.svfs.audit)
1466
1466
1467 self._dirstatevalidatewarned = False
1467 self._dirstatevalidatewarned = False
1468
1468
1469 self._branchcaches = branchmap.BranchMapCache()
1469 self._branchcaches = branchmap.BranchMapCache()
1470 self._revbranchcache = None
1470 self._revbranchcache = None
1471 self._filterpats = {}
1471 self._filterpats = {}
1472 self._datafilters = {}
1472 self._datafilters = {}
1473 self._transref = self._lockref = self._wlockref = None
1473 self._transref = self._lockref = self._wlockref = None
1474
1474
1475 # A cache for various files under .hg/ that tracks file changes,
1475 # A cache for various files under .hg/ that tracks file changes,
1476 # (used by the filecache decorator)
1476 # (used by the filecache decorator)
1477 #
1477 #
1478 # Maps a property name to its util.filecacheentry
1478 # Maps a property name to its util.filecacheentry
1479 self._filecache = {}
1479 self._filecache = {}
1480
1480
1481 # hold sets of revision to be filtered
1481 # hold sets of revision to be filtered
1482 # should be cleared when something might have changed the filter value:
1482 # should be cleared when something might have changed the filter value:
1483 # - new changesets,
1483 # - new changesets,
1484 # - phase change,
1484 # - phase change,
1485 # - new obsolescence marker,
1485 # - new obsolescence marker,
1486 # - working directory parent change,
1486 # - working directory parent change,
1487 # - bookmark changes
1487 # - bookmark changes
1488 self.filteredrevcache = {}
1488 self.filteredrevcache = {}
1489
1489
1490 self._dirstate = None
1490 self._dirstate = None
1491 # post-dirstate-status hooks
1491 # post-dirstate-status hooks
1492 self._postdsstatus = []
1492 self._postdsstatus = []
1493
1493
1494 self._pending_narrow_pats = None
1494 self._pending_narrow_pats = None
1495 self._pending_narrow_pats_dirstate = None
1495 self._pending_narrow_pats_dirstate = None
1496
1496
1497 # generic mapping between names and nodes
1497 # generic mapping between names and nodes
1498 self.names = namespaces.namespaces()
1498 self.names = namespaces.namespaces()
1499
1499
1500 # Key to signature value.
1500 # Key to signature value.
1501 self._sparsesignaturecache = {}
1501 self._sparsesignaturecache = {}
1502 # Signature to cached matcher instance.
1502 # Signature to cached matcher instance.
1503 self._sparsematchercache = {}
1503 self._sparsematchercache = {}
1504
1504
1505 self._extrafilterid = repoview.extrafilter(ui)
1505 self._extrafilterid = repoview.extrafilter(ui)
1506
1506
1507 self.filecopiesmode = None
1507 self.filecopiesmode = None
1508 if requirementsmod.COPIESSDC_REQUIREMENT in self.requirements:
1508 if requirementsmod.COPIESSDC_REQUIREMENT in self.requirements:
1509 self.filecopiesmode = b'changeset-sidedata'
1509 self.filecopiesmode = b'changeset-sidedata'
1510
1510
1511 self._wanted_sidedata = set()
1511 self._wanted_sidedata = set()
1512 self._sidedata_computers = {}
1512 self._sidedata_computers = {}
1513 sidedatamod.set_sidedata_spec_for_repo(self)
1513 sidedatamod.set_sidedata_spec_for_repo(self)
1514
1514
1515 def _getvfsward(self, origfunc):
1515 def _getvfsward(self, origfunc):
1516 """build a ward for self.vfs"""
1516 """build a ward for self.vfs"""
1517 rref = weakref.ref(self)
1517 rref = weakref.ref(self)
1518
1518
1519 def checkvfs(path, mode=None):
1519 def checkvfs(path, mode=None):
1520 ret = origfunc(path, mode=mode)
1520 ret = origfunc(path, mode=mode)
1521 repo = rref()
1521 repo = rref()
1522 if (
1522 if (
1523 repo is None
1523 repo is None
1524 or not hasattr(repo, '_wlockref')
1524 or not hasattr(repo, '_wlockref')
1525 or not hasattr(repo, '_lockref')
1525 or not hasattr(repo, '_lockref')
1526 ):
1526 ):
1527 return
1527 return
1528 if mode in (None, b'r', b'rb'):
1528 if mode in (None, b'r', b'rb'):
1529 return
1529 return
1530 if path.startswith(repo.path):
1530 if path.startswith(repo.path):
1531 # truncate name relative to the repository (.hg)
1531 # truncate name relative to the repository (.hg)
1532 path = path[len(repo.path) + 1 :]
1532 path = path[len(repo.path) + 1 :]
1533 if path.startswith(b'cache/'):
1533 if path.startswith(b'cache/'):
1534 msg = b'accessing cache with vfs instead of cachevfs: "%s"'
1534 msg = b'accessing cache with vfs instead of cachevfs: "%s"'
1535 repo.ui.develwarn(msg % path, stacklevel=3, config=b"cache-vfs")
1535 repo.ui.develwarn(msg % path, stacklevel=3, config=b"cache-vfs")
1536 # path prefixes covered by 'lock'
1536 # path prefixes covered by 'lock'
1537 vfs_path_prefixes = (
1537 vfs_path_prefixes = (
1538 b'journal.',
1538 b'journal.',
1539 b'undo.',
1539 b'undo.',
1540 b'strip-backup/',
1540 b'strip-backup/',
1541 b'cache/',
1541 b'cache/',
1542 )
1542 )
1543 if any(path.startswith(prefix) for prefix in vfs_path_prefixes):
1543 if any(path.startswith(prefix) for prefix in vfs_path_prefixes):
1544 if repo._currentlock(repo._lockref) is None:
1544 if repo._currentlock(repo._lockref) is None:
1545 repo.ui.develwarn(
1545 repo.ui.develwarn(
1546 b'write with no lock: "%s"' % path,
1546 b'write with no lock: "%s"' % path,
1547 stacklevel=3,
1547 stacklevel=3,
1548 config=b'check-locks',
1548 config=b'check-locks',
1549 )
1549 )
1550 elif repo._currentlock(repo._wlockref) is None:
1550 elif repo._currentlock(repo._wlockref) is None:
1551 # rest of vfs files are covered by 'wlock'
1551 # rest of vfs files are covered by 'wlock'
1552 #
1552 #
1553 # exclude special files
1553 # exclude special files
1554 for prefix in self._wlockfreeprefix:
1554 for prefix in self._wlockfreeprefix:
1555 if path.startswith(prefix):
1555 if path.startswith(prefix):
1556 return
1556 return
1557 repo.ui.develwarn(
1557 repo.ui.develwarn(
1558 b'write with no wlock: "%s"' % path,
1558 b'write with no wlock: "%s"' % path,
1559 stacklevel=3,
1559 stacklevel=3,
1560 config=b'check-locks',
1560 config=b'check-locks',
1561 )
1561 )
1562 return ret
1562 return ret
1563
1563
1564 return checkvfs
1564 return checkvfs
1565
1565
1566 def _getsvfsward(self, origfunc):
1566 def _getsvfsward(self, origfunc):
1567 """build a ward for self.svfs"""
1567 """build a ward for self.svfs"""
1568 rref = weakref.ref(self)
1568 rref = weakref.ref(self)
1569
1569
1570 def checksvfs(path, mode=None):
1570 def checksvfs(path, mode=None):
1571 ret = origfunc(path, mode=mode)
1571 ret = origfunc(path, mode=mode)
1572 repo = rref()
1572 repo = rref()
1573 if repo is None or not hasattr(repo, '_lockref'):
1573 if repo is None or not hasattr(repo, '_lockref'):
1574 return
1574 return
1575 if mode in (None, b'r', b'rb'):
1575 if mode in (None, b'r', b'rb'):
1576 return
1576 return
1577 if path.startswith(repo.sharedpath):
1577 if path.startswith(repo.sharedpath):
1578 # truncate name relative to the repository (.hg)
1578 # truncate name relative to the repository (.hg)
1579 path = path[len(repo.sharedpath) + 1 :]
1579 path = path[len(repo.sharedpath) + 1 :]
1580 if repo._currentlock(repo._lockref) is None:
1580 if repo._currentlock(repo._lockref) is None:
1581 repo.ui.develwarn(
1581 repo.ui.develwarn(
1582 b'write with no lock: "%s"' % path, stacklevel=4
1582 b'write with no lock: "%s"' % path, stacklevel=4
1583 )
1583 )
1584 return ret
1584 return ret
1585
1585
1586 return checksvfs
1586 return checksvfs
1587
1587
1588 @property
1588 @property
1589 def vfs_map(self):
1589 def vfs_map(self):
1590 return {
1590 return {
1591 b'': self.svfs,
1591 b'': self.svfs,
1592 b'plain': self.vfs,
1592 b'plain': self.vfs,
1593 b'store': self.svfs,
1593 b'store': self.svfs,
1594 }
1594 }
1595
1595
1596 def close(self):
1596 def close(self):
1597 self._writecaches()
1597 self._writecaches()
1598
1598
1599 def _writecaches(self):
1599 def _writecaches(self):
1600 if self._revbranchcache:
1600 if self._revbranchcache:
1601 self._revbranchcache.write()
1601 self._revbranchcache.write()
1602
1602
1603 def _restrictcapabilities(self, caps):
1603 def _restrictcapabilities(self, caps):
1604 if self.ui.configbool(b'experimental', b'bundle2-advertise'):
1604 if self.ui.configbool(b'experimental', b'bundle2-advertise'):
1605 caps = set(caps)
1605 caps = set(caps)
1606 capsblob = bundle2.encodecaps(
1606 capsblob = bundle2.encodecaps(
1607 bundle2.getrepocaps(self, role=b'client')
1607 bundle2.getrepocaps(self, role=b'client')
1608 )
1608 )
1609 caps.add(b'bundle2=' + urlreq.quote(capsblob))
1609 caps.add(b'bundle2=' + urlreq.quote(capsblob))
1610 if self.ui.configbool(b'experimental', b'narrow'):
1610 if self.ui.configbool(b'experimental', b'narrow'):
1611 caps.add(wireprototypes.NARROWCAP)
1611 caps.add(wireprototypes.NARROWCAP)
1612 return caps
1612 return caps
1613
1613
1614 # Don't cache auditor/nofsauditor, or you'll end up with reference cycle:
1614 # Don't cache auditor/nofsauditor, or you'll end up with reference cycle:
1615 # self -> auditor -> self._checknested -> self
1615 # self -> auditor -> self._checknested -> self
1616
1616
1617 @property
1617 @property
1618 def auditor(self):
1618 def auditor(self):
1619 # This is only used by context.workingctx.match in order to
1619 # This is only used by context.workingctx.match in order to
1620 # detect files in subrepos.
1620 # detect files in subrepos.
1621 return pathutil.pathauditor(self.root, callback=self._checknested)
1621 return pathutil.pathauditor(self.root, callback=self._checknested)
1622
1622
1623 @property
1623 @property
1624 def nofsauditor(self):
1624 def nofsauditor(self):
1625 # This is only used by context.basectx.match in order to detect
1625 # This is only used by context.basectx.match in order to detect
1626 # files in subrepos.
1626 # files in subrepos.
1627 return pathutil.pathauditor(
1627 return pathutil.pathauditor(
1628 self.root, callback=self._checknested, realfs=False, cached=True
1628 self.root, callback=self._checknested, realfs=False, cached=True
1629 )
1629 )
1630
1630
1631 def _checknested(self, path):
1631 def _checknested(self, path):
1632 """Determine if path is a legal nested repository."""
1632 """Determine if path is a legal nested repository."""
1633 if not path.startswith(self.root):
1633 if not path.startswith(self.root):
1634 return False
1634 return False
1635 subpath = path[len(self.root) + 1 :]
1635 subpath = path[len(self.root) + 1 :]
1636 normsubpath = util.pconvert(subpath)
1636 normsubpath = util.pconvert(subpath)
1637
1637
1638 # XXX: Checking against the current working copy is wrong in
1638 # XXX: Checking against the current working copy is wrong in
1639 # the sense that it can reject things like
1639 # the sense that it can reject things like
1640 #
1640 #
1641 # $ hg cat -r 10 sub/x.txt
1641 # $ hg cat -r 10 sub/x.txt
1642 #
1642 #
1643 # if sub/ is no longer a subrepository in the working copy
1643 # if sub/ is no longer a subrepository in the working copy
1644 # parent revision.
1644 # parent revision.
1645 #
1645 #
1646 # However, it can of course also allow things that would have
1646 # However, it can of course also allow things that would have
1647 # been rejected before, such as the above cat command if sub/
1647 # been rejected before, such as the above cat command if sub/
1648 # is a subrepository now, but was a normal directory before.
1648 # is a subrepository now, but was a normal directory before.
1649 # The old path auditor would have rejected by mistake since it
1649 # The old path auditor would have rejected by mistake since it
1650 # panics when it sees sub/.hg/.
1650 # panics when it sees sub/.hg/.
1651 #
1651 #
1652 # All in all, checking against the working copy seems sensible
1652 # All in all, checking against the working copy seems sensible
1653 # since we want to prevent access to nested repositories on
1653 # since we want to prevent access to nested repositories on
1654 # the filesystem *now*.
1654 # the filesystem *now*.
1655 ctx = self[None]
1655 ctx = self[None]
1656 parts = util.splitpath(subpath)
1656 parts = util.splitpath(subpath)
1657 while parts:
1657 while parts:
1658 prefix = b'/'.join(parts)
1658 prefix = b'/'.join(parts)
1659 if prefix in ctx.substate:
1659 if prefix in ctx.substate:
1660 if prefix == normsubpath:
1660 if prefix == normsubpath:
1661 return True
1661 return True
1662 else:
1662 else:
1663 sub = ctx.sub(prefix)
1663 sub = ctx.sub(prefix)
1664 return sub.checknested(subpath[len(prefix) + 1 :])
1664 return sub.checknested(subpath[len(prefix) + 1 :])
1665 else:
1665 else:
1666 parts.pop()
1666 parts.pop()
1667 return False
1667 return False
1668
1668
1669 def peer(self, path=None, remotehidden=False):
1669 def peer(self, path=None, remotehidden=False):
1670 return localpeer(
1670 return localpeer(
1671 self, path=path, remotehidden=remotehidden
1671 self, path=path, remotehidden=remotehidden
1672 ) # not cached to avoid reference cycle
1672 ) # not cached to avoid reference cycle
1673
1673
1674 def unfiltered(self):
1674 def unfiltered(self):
1675 """Return unfiltered version of the repository
1675 """Return unfiltered version of the repository
1676
1676
1677 Intended to be overwritten by filtered repo."""
1677 Intended to be overwritten by filtered repo."""
1678 return self
1678 return self
1679
1679
1680 def filtered(self, name, visibilityexceptions=None):
1680 def filtered(self, name, visibilityexceptions=None):
1681 """Return a filtered version of a repository
1681 """Return a filtered version of a repository
1682
1682
1683 The `name` parameter is the identifier of the requested view. This
1683 The `name` parameter is the identifier of the requested view. This
1684 will return a repoview object set "exactly" to the specified view.
1684 will return a repoview object set "exactly" to the specified view.
1685
1685
1686 This function does not apply recursive filtering to a repository. For
1686 This function does not apply recursive filtering to a repository. For
1687 example calling `repo.filtered("served")` will return a repoview using
1687 example calling `repo.filtered("served")` will return a repoview using
1688 the "served" view, regardless of the initial view used by `repo`.
1688 the "served" view, regardless of the initial view used by `repo`.
1689
1689
1690 In other word, there is always only one level of `repoview` "filtering".
1690 In other word, there is always only one level of `repoview` "filtering".
1691 """
1691 """
1692 if self._extrafilterid is not None and b'%' not in name:
1692 if self._extrafilterid is not None and b'%' not in name:
1693 name = name + b'%' + self._extrafilterid
1693 name = name + b'%' + self._extrafilterid
1694
1694
1695 cls = repoview.newtype(self.unfiltered().__class__)
1695 cls = repoview.newtype(self.unfiltered().__class__)
1696 return cls(self, name, visibilityexceptions)
1696 return cls(self, name, visibilityexceptions)
1697
1697
1698 @mixedrepostorecache(
1698 @mixedrepostorecache(
1699 (b'bookmarks', b'plain'),
1699 (b'bookmarks', b'plain'),
1700 (b'bookmarks.current', b'plain'),
1700 (b'bookmarks.current', b'plain'),
1701 (b'bookmarks', b''),
1701 (b'bookmarks', b''),
1702 (b'00changelog.i', b''),
1702 (b'00changelog.i', b''),
1703 )
1703 )
1704 def _bookmarks(self):
1704 def _bookmarks(self):
1705 # Since the multiple files involved in the transaction cannot be
1705 # Since the multiple files involved in the transaction cannot be
1706 # written atomically (with current repository format), there is a race
1706 # written atomically (with current repository format), there is a race
1707 # condition here.
1707 # condition here.
1708 #
1708 #
1709 # 1) changelog content A is read
1709 # 1) changelog content A is read
1710 # 2) outside transaction update changelog to content B
1710 # 2) outside transaction update changelog to content B
1711 # 3) outside transaction update bookmark file referring to content B
1711 # 3) outside transaction update bookmark file referring to content B
1712 # 4) bookmarks file content is read and filtered against changelog-A
1712 # 4) bookmarks file content is read and filtered against changelog-A
1713 #
1713 #
1714 # When this happens, bookmarks against nodes missing from A are dropped.
1714 # When this happens, bookmarks against nodes missing from A are dropped.
1715 #
1715 #
1716 # Having this happening during read is not great, but it become worse
1716 # Having this happening during read is not great, but it become worse
1717 # when this happen during write because the bookmarks to the "unknown"
1717 # when this happen during write because the bookmarks to the "unknown"
1718 # nodes will be dropped for good. However, writes happen within locks.
1718 # nodes will be dropped for good. However, writes happen within locks.
1719 # This locking makes it possible to have a race free consistent read.
1719 # This locking makes it possible to have a race free consistent read.
1720 # For this purpose data read from disc before locking are
1720 # For this purpose data read from disc before locking are
1721 # "invalidated" right after the locks are taken. This invalidations are
1721 # "invalidated" right after the locks are taken. This invalidations are
1722 # "light", the `filecache` mechanism keep the data in memory and will
1722 # "light", the `filecache` mechanism keep the data in memory and will
1723 # reuse them if the underlying files did not changed. Not parsing the
1723 # reuse them if the underlying files did not changed. Not parsing the
1724 # same data multiple times helps performances.
1724 # same data multiple times helps performances.
1725 #
1725 #
1726 # Unfortunately in the case describe above, the files tracked by the
1726 # Unfortunately in the case describe above, the files tracked by the
1727 # bookmarks file cache might not have changed, but the in-memory
1727 # bookmarks file cache might not have changed, but the in-memory
1728 # content is still "wrong" because we used an older changelog content
1728 # content is still "wrong" because we used an older changelog content
1729 # to process the on-disk data. So after locking, the changelog would be
1729 # to process the on-disk data. So after locking, the changelog would be
1730 # refreshed but `_bookmarks` would be preserved.
1730 # refreshed but `_bookmarks` would be preserved.
1731 # Adding `00changelog.i` to the list of tracked file is not
1731 # Adding `00changelog.i` to the list of tracked file is not
1732 # enough, because at the time we build the content for `_bookmarks` in
1732 # enough, because at the time we build the content for `_bookmarks` in
1733 # (4), the changelog file has already diverged from the content used
1733 # (4), the changelog file has already diverged from the content used
1734 # for loading `changelog` in (1)
1734 # for loading `changelog` in (1)
1735 #
1735 #
1736 # To prevent the issue, we force the changelog to be explicitly
1736 # To prevent the issue, we force the changelog to be explicitly
1737 # reloaded while computing `_bookmarks`. The data race can still happen
1737 # reloaded while computing `_bookmarks`. The data race can still happen
1738 # without the lock (with a narrower window), but it would no longer go
1738 # without the lock (with a narrower window), but it would no longer go
1739 # undetected during the lock time refresh.
1739 # undetected during the lock time refresh.
1740 #
1740 #
1741 # The new schedule is as follow
1741 # The new schedule is as follow
1742 #
1742 #
1743 # 1) filecache logic detect that `_bookmarks` needs to be computed
1743 # 1) filecache logic detect that `_bookmarks` needs to be computed
1744 # 2) cachestat for `bookmarks` and `changelog` are captured (for book)
1744 # 2) cachestat for `bookmarks` and `changelog` are captured (for book)
1745 # 3) We force `changelog` filecache to be tested
1745 # 3) We force `changelog` filecache to be tested
1746 # 4) cachestat for `changelog` are captured (for changelog)
1746 # 4) cachestat for `changelog` are captured (for changelog)
1747 # 5) `_bookmarks` is computed and cached
1747 # 5) `_bookmarks` is computed and cached
1748 #
1748 #
1749 # The step in (3) ensure we have a changelog at least as recent as the
1749 # The step in (3) ensure we have a changelog at least as recent as the
1750 # cache stat computed in (1). As a result at locking time:
1750 # cache stat computed in (1). As a result at locking time:
1751 # * if the changelog did not changed since (1) -> we can reuse the data
1751 # * if the changelog did not changed since (1) -> we can reuse the data
1752 # * otherwise -> the bookmarks get refreshed.
1752 # * otherwise -> the bookmarks get refreshed.
1753 self._refreshchangelog()
1753 self._refreshchangelog()
1754 return bookmarks.bmstore(self)
1754 return bookmarks.bmstore(self)
1755
1755
1756 def _refreshchangelog(self):
1756 def _refreshchangelog(self):
1757 """make sure the in memory changelog match the on-disk one"""
1757 """make sure the in memory changelog match the on-disk one"""
1758 if 'changelog' in vars(self) and self.currenttransaction() is None:
1758 if 'changelog' in vars(self) and self.currenttransaction() is None:
1759 del self.changelog
1759 del self.changelog
1760
1760
1761 @property
1761 @property
1762 def _activebookmark(self):
1762 def _activebookmark(self):
1763 return self._bookmarks.active
1763 return self._bookmarks.active
1764
1764
1765 # _phasesets depend on changelog. what we need is to call
1765 # _phasesets depend on changelog. what we need is to call
1766 # _phasecache.invalidate() if '00changelog.i' was changed, but it
1766 # _phasecache.invalidate() if '00changelog.i' was changed, but it
1767 # can't be easily expressed in filecache mechanism.
1767 # can't be easily expressed in filecache mechanism.
1768 @storecache(b'phaseroots', b'00changelog.i')
1768 @storecache(b'phaseroots', b'00changelog.i')
1769 def _phasecache(self):
1769 def _phasecache(self):
1770 return phases.phasecache(self, self._phasedefaults)
1770 return phases.phasecache(self, self._phasedefaults)
1771
1771
1772 @storecache(b'obsstore')
1772 @storecache(b'obsstore')
1773 def obsstore(self):
1773 def obsstore(self):
1774 return obsolete.makestore(self.ui, self)
1774 return obsolete.makestore(self.ui, self)
1775
1775
1776 @changelogcache()
1776 @changelogcache()
1777 def changelog(repo):
1777 def changelog(repo):
1778 # load dirstate before changelog to avoid race see issue6303
1778 # load dirstate before changelog to avoid race see issue6303
1779 repo.dirstate.prefetch_parents()
1779 repo.dirstate.prefetch_parents()
1780 return repo.store.changelog(
1780 return repo.store.changelog(
1781 txnutil.mayhavepending(repo.root),
1781 txnutil.mayhavepending(repo.root),
1782 concurrencychecker=revlogchecker.get_checker(repo.ui, b'changelog'),
1782 concurrencychecker=revlogchecker.get_checker(repo.ui, b'changelog'),
1783 )
1783 )
1784
1784
1785 @manifestlogcache()
1785 @manifestlogcache()
1786 def manifestlog(self):
1786 def manifestlog(self):
1787 return self.store.manifestlog(self, self._storenarrowmatch)
1787 return self.store.manifestlog(self, self._storenarrowmatch)
1788
1788
1789 @unfilteredpropertycache
1789 @unfilteredpropertycache
1790 def dirstate(self):
1790 def dirstate(self):
1791 if self._dirstate is None:
1791 if self._dirstate is None:
1792 self._dirstate = self._makedirstate()
1792 self._dirstate = self._makedirstate()
1793 else:
1793 else:
1794 self._dirstate.refresh()
1794 self._dirstate.refresh()
1795 return self._dirstate
1795 return self._dirstate
1796
1796
1797 def _makedirstate(self):
1797 def _makedirstate(self):
1798 """Extension point for wrapping the dirstate per-repo."""
1798 """Extension point for wrapping the dirstate per-repo."""
1799 sparsematchfn = None
1799 sparsematchfn = None
1800 if sparse.use_sparse(self):
1800 if sparse.use_sparse(self):
1801 sparsematchfn = lambda: sparse.matcher(self)
1801 sparsematchfn = lambda: sparse.matcher(self)
1802 v2_req = requirementsmod.DIRSTATE_V2_REQUIREMENT
1802 v2_req = requirementsmod.DIRSTATE_V2_REQUIREMENT
1803 th = requirementsmod.DIRSTATE_TRACKED_HINT_V1
1803 th = requirementsmod.DIRSTATE_TRACKED_HINT_V1
1804 use_dirstate_v2 = v2_req in self.requirements
1804 use_dirstate_v2 = v2_req in self.requirements
1805 use_tracked_hint = th in self.requirements
1805 use_tracked_hint = th in self.requirements
1806
1806
1807 return dirstate.dirstate(
1807 return dirstate.dirstate(
1808 self.vfs,
1808 self.vfs,
1809 self.ui,
1809 self.ui,
1810 self.root,
1810 self.root,
1811 self._dirstatevalidate,
1811 self._dirstatevalidate,
1812 sparsematchfn,
1812 sparsematchfn,
1813 self.nodeconstants,
1813 self.nodeconstants,
1814 use_dirstate_v2,
1814 use_dirstate_v2,
1815 use_tracked_hint=use_tracked_hint,
1815 use_tracked_hint=use_tracked_hint,
1816 )
1816 )
1817
1817
1818 def _dirstatevalidate(self, node):
1818 def _dirstatevalidate(self, node):
1819 okay = True
1819 okay = True
1820 try:
1820 try:
1821 self.changelog.rev(node)
1821 self.changelog.rev(node)
1822 except error.LookupError:
1822 except error.LookupError:
1823 # If the parent are unknown it might just be because the changelog
1823 # If the parent are unknown it might just be because the changelog
1824 # in memory is lagging behind the dirstate in memory. So try to
1824 # in memory is lagging behind the dirstate in memory. So try to
1825 # refresh the changelog first.
1825 # refresh the changelog first.
1826 #
1826 #
1827 # We only do so if we don't hold the lock, if we do hold the lock
1827 # We only do so if we don't hold the lock, if we do hold the lock
1828 # the invalidation at that time should have taken care of this and
1828 # the invalidation at that time should have taken care of this and
1829 # something is very fishy.
1829 # something is very fishy.
1830 if self.currentlock() is None:
1830 if self.currentlock() is None:
1831 self.invalidate()
1831 self.invalidate()
1832 try:
1832 try:
1833 self.changelog.rev(node)
1833 self.changelog.rev(node)
1834 except error.LookupError:
1834 except error.LookupError:
1835 okay = False
1835 okay = False
1836 else:
1836 else:
1837 # XXX we should consider raising an error here.
1837 # XXX we should consider raising an error here.
1838 okay = False
1838 okay = False
1839 if okay:
1839 if okay:
1840 return node
1840 return node
1841 else:
1841 else:
1842 if not self._dirstatevalidatewarned:
1842 if not self._dirstatevalidatewarned:
1843 self._dirstatevalidatewarned = True
1843 self._dirstatevalidatewarned = True
1844 self.ui.warn(
1844 self.ui.warn(
1845 _(b"warning: ignoring unknown working parent %s!\n")
1845 _(b"warning: ignoring unknown working parent %s!\n")
1846 % short(node)
1846 % short(node)
1847 )
1847 )
1848 return self.nullid
1848 return self.nullid
1849
1849
1850 @storecache(narrowspec.FILENAME)
1850 @storecache(narrowspec.FILENAME)
1851 def narrowpats(self):
1851 def narrowpats(self):
1852 """matcher patterns for this repository's narrowspec
1852 """matcher patterns for this repository's narrowspec
1853
1853
1854 A tuple of (includes, excludes).
1854 A tuple of (includes, excludes).
1855 """
1855 """
1856 # the narrow management should probably move into its own object
1856 # the narrow management should probably move into its own object
1857 val = self._pending_narrow_pats
1857 val = self._pending_narrow_pats
1858 if val is None:
1858 if val is None:
1859 val = narrowspec.load(self)
1859 val = narrowspec.load(self)
1860 return val
1860 return val
1861
1861
1862 @storecache(narrowspec.FILENAME)
1862 @storecache(narrowspec.FILENAME)
1863 def _storenarrowmatch(self):
1863 def _storenarrowmatch(self):
1864 if requirementsmod.NARROW_REQUIREMENT not in self.requirements:
1864 if requirementsmod.NARROW_REQUIREMENT not in self.requirements:
1865 return matchmod.always()
1865 return matchmod.always()
1866 include, exclude = self.narrowpats
1866 include, exclude = self.narrowpats
1867 return narrowspec.match(self.root, include=include, exclude=exclude)
1867 return narrowspec.match(self.root, include=include, exclude=exclude)
1868
1868
1869 @storecache(narrowspec.FILENAME)
1869 @storecache(narrowspec.FILENAME)
1870 def _narrowmatch(self):
1870 def _narrowmatch(self):
1871 if requirementsmod.NARROW_REQUIREMENT not in self.requirements:
1871 if requirementsmod.NARROW_REQUIREMENT not in self.requirements:
1872 return matchmod.always()
1872 return matchmod.always()
1873 narrowspec.checkworkingcopynarrowspec(self)
1873 narrowspec.checkworkingcopynarrowspec(self)
1874 include, exclude = self.narrowpats
1874 include, exclude = self.narrowpats
1875 return narrowspec.match(self.root, include=include, exclude=exclude)
1875 return narrowspec.match(self.root, include=include, exclude=exclude)
1876
1876
1877 def narrowmatch(self, match=None, includeexact=False):
1877 def narrowmatch(self, match=None, includeexact=False):
1878 """matcher corresponding the the repo's narrowspec
1878 """matcher corresponding the the repo's narrowspec
1879
1879
1880 If `match` is given, then that will be intersected with the narrow
1880 If `match` is given, then that will be intersected with the narrow
1881 matcher.
1881 matcher.
1882
1882
1883 If `includeexact` is True, then any exact matches from `match` will
1883 If `includeexact` is True, then any exact matches from `match` will
1884 be included even if they're outside the narrowspec.
1884 be included even if they're outside the narrowspec.
1885 """
1885 """
1886 if match:
1886 if match:
1887 if includeexact and not self._narrowmatch.always():
1887 if includeexact and not self._narrowmatch.always():
1888 # do not exclude explicitly-specified paths so that they can
1888 # do not exclude explicitly-specified paths so that they can
1889 # be warned later on
1889 # be warned later on
1890 em = matchmod.exact(match.files())
1890 em = matchmod.exact(match.files())
1891 nm = matchmod.unionmatcher([self._narrowmatch, em])
1891 nm = matchmod.unionmatcher([self._narrowmatch, em])
1892 return matchmod.intersectmatchers(match, nm)
1892 return matchmod.intersectmatchers(match, nm)
1893 return matchmod.intersectmatchers(match, self._narrowmatch)
1893 return matchmod.intersectmatchers(match, self._narrowmatch)
1894 return self._narrowmatch
1894 return self._narrowmatch
1895
1895
1896 def setnarrowpats(self, newincludes, newexcludes):
1896 def setnarrowpats(self, newincludes, newexcludes):
1897 narrowspec.save(self, newincludes, newexcludes)
1897 narrowspec.save(self, newincludes, newexcludes)
1898 self.invalidate(clearfilecache=True)
1898 self.invalidate(clearfilecache=True)
1899
1899
1900 @unfilteredpropertycache
1900 @unfilteredpropertycache
1901 def _quick_access_changeid_null(self):
1901 def _quick_access_changeid_null(self):
1902 return {
1902 return {
1903 b'null': (nullrev, self.nodeconstants.nullid),
1903 b'null': (nullrev, self.nodeconstants.nullid),
1904 nullrev: (nullrev, self.nodeconstants.nullid),
1904 nullrev: (nullrev, self.nodeconstants.nullid),
1905 self.nullid: (nullrev, self.nullid),
1905 self.nullid: (nullrev, self.nullid),
1906 }
1906 }
1907
1907
1908 @unfilteredpropertycache
1908 @unfilteredpropertycache
1909 def _quick_access_changeid_wc(self):
1909 def _quick_access_changeid_wc(self):
1910 # also fast path access to the working copy parents
1910 # also fast path access to the working copy parents
1911 # however, only do it for filter that ensure wc is visible.
1911 # however, only do it for filter that ensure wc is visible.
1912 quick = self._quick_access_changeid_null.copy()
1912 quick = self._quick_access_changeid_null.copy()
1913 cl = self.unfiltered().changelog
1913 cl = self.unfiltered().changelog
1914 for node in self.dirstate.parents():
1914 for node in self.dirstate.parents():
1915 if node == self.nullid:
1915 if node == self.nullid:
1916 continue
1916 continue
1917 rev = cl.index.get_rev(node)
1917 rev = cl.index.get_rev(node)
1918 if rev is None:
1918 if rev is None:
1919 # unknown working copy parent case:
1919 # unknown working copy parent case:
1920 #
1920 #
1921 # skip the fast path and let higher code deal with it
1921 # skip the fast path and let higher code deal with it
1922 continue
1922 continue
1923 pair = (rev, node)
1923 pair = (rev, node)
1924 quick[rev] = pair
1924 quick[rev] = pair
1925 quick[node] = pair
1925 quick[node] = pair
1926 # also add the parents of the parents
1926 # also add the parents of the parents
1927 for r in cl.parentrevs(rev):
1927 for r in cl.parentrevs(rev):
1928 if r == nullrev:
1928 if r == nullrev:
1929 continue
1929 continue
1930 n = cl.node(r)
1930 n = cl.node(r)
1931 pair = (r, n)
1931 pair = (r, n)
1932 quick[r] = pair
1932 quick[r] = pair
1933 quick[n] = pair
1933 quick[n] = pair
1934 p1node = self.dirstate.p1()
1934 p1node = self.dirstate.p1()
1935 if p1node != self.nullid:
1935 if p1node != self.nullid:
1936 quick[b'.'] = quick[p1node]
1936 quick[b'.'] = quick[p1node]
1937 return quick
1937 return quick
1938
1938
1939 @unfilteredmethod
1939 @unfilteredmethod
1940 def _quick_access_changeid_invalidate(self):
1940 def _quick_access_changeid_invalidate(self):
1941 if '_quick_access_changeid_wc' in vars(self):
1941 if '_quick_access_changeid_wc' in vars(self):
1942 del self.__dict__['_quick_access_changeid_wc']
1942 del self.__dict__['_quick_access_changeid_wc']
1943
1943
1944 @property
1944 @property
1945 def _quick_access_changeid(self):
1945 def _quick_access_changeid(self):
1946 """an helper dictionnary for __getitem__ calls
1946 """an helper dictionnary for __getitem__ calls
1947
1947
1948 This contains a list of symbol we can recognise right away without
1948 This contains a list of symbol we can recognise right away without
1949 further processing.
1949 further processing.
1950 """
1950 """
1951 if self.filtername in repoview.filter_has_wc:
1951 if self.filtername in repoview.filter_has_wc:
1952 return self._quick_access_changeid_wc
1952 return self._quick_access_changeid_wc
1953 return self._quick_access_changeid_null
1953 return self._quick_access_changeid_null
1954
1954
1955 def __getitem__(self, changeid):
1955 def __getitem__(self, changeid):
1956 # dealing with special cases
1956 # dealing with special cases
1957 if changeid is None:
1957 if changeid is None:
1958 return context.workingctx(self)
1958 return context.workingctx(self)
1959 if isinstance(changeid, context.basectx):
1959 if isinstance(changeid, context.basectx):
1960 return changeid
1960 return changeid
1961
1961
1962 # dealing with multiple revisions
1962 # dealing with multiple revisions
1963 if isinstance(changeid, slice):
1963 if isinstance(changeid, slice):
1964 # wdirrev isn't contiguous so the slice shouldn't include it
1964 # wdirrev isn't contiguous so the slice shouldn't include it
1965 return [
1965 return [
1966 self[i]
1966 self[i]
1967 for i in range(*changeid.indices(len(self)))
1967 for i in range(*changeid.indices(len(self)))
1968 if i not in self.changelog.filteredrevs
1968 if i not in self.changelog.filteredrevs
1969 ]
1969 ]
1970
1970
1971 # dealing with some special values
1971 # dealing with some special values
1972 quick_access = self._quick_access_changeid.get(changeid)
1972 quick_access = self._quick_access_changeid.get(changeid)
1973 if quick_access is not None:
1973 if quick_access is not None:
1974 rev, node = quick_access
1974 rev, node = quick_access
1975 return context.changectx(self, rev, node, maybe_filtered=False)
1975 return context.changectx(self, rev, node, maybe_filtered=False)
1976 if changeid == b'tip':
1976 if changeid == b'tip':
1977 node = self.changelog.tip()
1977 node = self.changelog.tip()
1978 rev = self.changelog.rev(node)
1978 rev = self.changelog.rev(node)
1979 return context.changectx(self, rev, node)
1979 return context.changectx(self, rev, node)
1980
1980
1981 # dealing with arbitrary values
1981 # dealing with arbitrary values
1982 try:
1982 try:
1983 if isinstance(changeid, int):
1983 if isinstance(changeid, int):
1984 node = self.changelog.node(changeid)
1984 node = self.changelog.node(changeid)
1985 rev = changeid
1985 rev = changeid
1986 elif changeid == b'.':
1986 elif changeid == b'.':
1987 # this is a hack to delay/avoid loading obsmarkers
1987 # this is a hack to delay/avoid loading obsmarkers
1988 # when we know that '.' won't be hidden
1988 # when we know that '.' won't be hidden
1989 node = self.dirstate.p1()
1989 node = self.dirstate.p1()
1990 rev = self.unfiltered().changelog.rev(node)
1990 rev = self.unfiltered().changelog.rev(node)
1991 elif len(changeid) == self.nodeconstants.nodelen:
1991 elif len(changeid) == self.nodeconstants.nodelen:
1992 try:
1992 try:
1993 node = changeid
1993 node = changeid
1994 rev = self.changelog.rev(changeid)
1994 rev = self.changelog.rev(changeid)
1995 except error.FilteredLookupError:
1995 except error.FilteredLookupError:
1996 changeid = hex(changeid) # for the error message
1996 changeid = hex(changeid) # for the error message
1997 raise
1997 raise
1998 except LookupError:
1998 except LookupError:
1999 # check if it might have come from damaged dirstate
1999 # check if it might have come from damaged dirstate
2000 #
2000 #
2001 # XXX we could avoid the unfiltered if we had a recognizable
2001 # XXX we could avoid the unfiltered if we had a recognizable
2002 # exception for filtered changeset access
2002 # exception for filtered changeset access
2003 if (
2003 if (
2004 self.local()
2004 self.local()
2005 and changeid in self.unfiltered().dirstate.parents()
2005 and changeid in self.unfiltered().dirstate.parents()
2006 ):
2006 ):
2007 msg = _(b"working directory has unknown parent '%s'!")
2007 msg = _(b"working directory has unknown parent '%s'!")
2008 raise error.Abort(msg % short(changeid))
2008 raise error.Abort(msg % short(changeid))
2009 changeid = hex(changeid) # for the error message
2009 changeid = hex(changeid) # for the error message
2010 raise
2010 raise
2011
2011
2012 elif len(changeid) == 2 * self.nodeconstants.nodelen:
2012 elif len(changeid) == 2 * self.nodeconstants.nodelen:
2013 node = bin(changeid)
2013 node = bin(changeid)
2014 rev = self.changelog.rev(node)
2014 rev = self.changelog.rev(node)
2015 else:
2015 else:
2016 raise error.ProgrammingError(
2016 raise error.ProgrammingError(
2017 b"unsupported changeid '%s' of type %s"
2017 b"unsupported changeid '%s' of type %s"
2018 % (changeid, pycompat.bytestr(type(changeid)))
2018 % (changeid, pycompat.bytestr(type(changeid)))
2019 )
2019 )
2020
2020
2021 return context.changectx(self, rev, node)
2021 return context.changectx(self, rev, node)
2022
2022
2023 except (error.FilteredIndexError, error.FilteredLookupError):
2023 except (error.FilteredIndexError, error.FilteredLookupError):
2024 raise error.FilteredRepoLookupError(
2024 raise error.FilteredRepoLookupError(
2025 _(b"filtered revision '%s'") % pycompat.bytestr(changeid)
2025 _(b"filtered revision '%s'") % pycompat.bytestr(changeid)
2026 )
2026 )
2027 except (IndexError, LookupError):
2027 except (IndexError, LookupError):
2028 raise error.RepoLookupError(
2028 raise error.RepoLookupError(
2029 _(b"unknown revision '%s'") % pycompat.bytestr(changeid)
2029 _(b"unknown revision '%s'") % pycompat.bytestr(changeid)
2030 )
2030 )
2031 except error.WdirUnsupported:
2031 except error.WdirUnsupported:
2032 return context.workingctx(self)
2032 return context.workingctx(self)
2033
2033
2034 def __contains__(self, changeid):
2034 def __contains__(self, changeid):
2035 """True if the given changeid exists"""
2035 """True if the given changeid exists"""
2036 try:
2036 try:
2037 self[changeid]
2037 self[changeid]
2038 return True
2038 return True
2039 except error.RepoLookupError:
2039 except error.RepoLookupError:
2040 return False
2040 return False
2041
2041
2042 def __nonzero__(self):
2042 def __nonzero__(self):
2043 return True
2043 return True
2044
2044
2045 __bool__ = __nonzero__
2045 __bool__ = __nonzero__
2046
2046
2047 def __len__(self):
2047 def __len__(self):
2048 # no need to pay the cost of repoview.changelog
2048 # no need to pay the cost of repoview.changelog
2049 unfi = self.unfiltered()
2049 unfi = self.unfiltered()
2050 return len(unfi.changelog)
2050 return len(unfi.changelog)
2051
2051
2052 def __iter__(self):
2052 def __iter__(self):
2053 return iter(self.changelog)
2053 return iter(self.changelog)
2054
2054
2055 def revs(self, expr: bytes, *args):
2055 def revs(self, expr: bytes, *args):
2056 """Find revisions matching a revset.
2056 """Find revisions matching a revset.
2057
2057
2058 The revset is specified as a string ``expr`` that may contain
2058 The revset is specified as a string ``expr`` that may contain
2059 %-formatting to escape certain types. See ``revsetlang.formatspec``.
2059 %-formatting to escape certain types. See ``revsetlang.formatspec``.
2060
2060
2061 Revset aliases from the configuration are not expanded. To expand
2061 Revset aliases from the configuration are not expanded. To expand
2062 user aliases, consider calling ``scmutil.revrange()`` or
2062 user aliases, consider calling ``scmutil.revrange()`` or
2063 ``repo.anyrevs([expr], user=True)``.
2063 ``repo.anyrevs([expr], user=True)``.
2064
2064
2065 Returns a smartset.abstractsmartset, which is a list-like interface
2065 Returns a smartset.abstractsmartset, which is a list-like interface
2066 that contains integer revisions.
2066 that contains integer revisions.
2067 """
2067 """
2068 tree = revsetlang.spectree(expr, *args)
2068 tree = revsetlang.spectree(expr, *args)
2069 return revset.makematcher(tree)(self)
2069 return revset.makematcher(tree)(self)
2070
2070
2071 def set(self, expr: bytes, *args):
2071 def set(self, expr: bytes, *args):
2072 """Find revisions matching a revset and emit changectx instances.
2072 """Find revisions matching a revset and emit changectx instances.
2073
2073
2074 This is a convenience wrapper around ``revs()`` that iterates the
2074 This is a convenience wrapper around ``revs()`` that iterates the
2075 result and is a generator of changectx instances.
2075 result and is a generator of changectx instances.
2076
2076
2077 Revset aliases from the configuration are not expanded. To expand
2077 Revset aliases from the configuration are not expanded. To expand
2078 user aliases, consider calling ``scmutil.revrange()``.
2078 user aliases, consider calling ``scmutil.revrange()``.
2079 """
2079 """
2080 for r in self.revs(expr, *args):
2080 for r in self.revs(expr, *args):
2081 yield self[r]
2081 yield self[r]
2082
2082
2083 def anyrevs(self, specs: bytes, user=False, localalias=None):
2083 def anyrevs(self, specs: bytes, user=False, localalias=None):
2084 """Find revisions matching one of the given revsets.
2084 """Find revisions matching one of the given revsets.
2085
2085
2086 Revset aliases from the configuration are not expanded by default. To
2086 Revset aliases from the configuration are not expanded by default. To
2087 expand user aliases, specify ``user=True``. To provide some local
2087 expand user aliases, specify ``user=True``. To provide some local
2088 definitions overriding user aliases, set ``localalias`` to
2088 definitions overriding user aliases, set ``localalias`` to
2089 ``{name: definitionstring}``.
2089 ``{name: definitionstring}``.
2090 """
2090 """
2091 if specs == [b'null']:
2091 if specs == [b'null']:
2092 return revset.baseset([nullrev])
2092 return revset.baseset([nullrev])
2093 if specs == [b'.']:
2093 if specs == [b'.']:
2094 quick_data = self._quick_access_changeid.get(b'.')
2094 quick_data = self._quick_access_changeid.get(b'.')
2095 if quick_data is not None:
2095 if quick_data is not None:
2096 return revset.baseset([quick_data[0]])
2096 return revset.baseset([quick_data[0]])
2097 if user:
2097 if user:
2098 m = revset.matchany(
2098 m = revset.matchany(
2099 self.ui,
2099 self.ui,
2100 specs,
2100 specs,
2101 lookup=revset.lookupfn(self),
2101 lookup=revset.lookupfn(self),
2102 localalias=localalias,
2102 localalias=localalias,
2103 )
2103 )
2104 else:
2104 else:
2105 m = revset.matchany(None, specs, localalias=localalias)
2105 m = revset.matchany(None, specs, localalias=localalias)
2106 return m(self)
2106 return m(self)
2107
2107
2108 def url(self) -> bytes:
2108 def url(self) -> bytes:
2109 return b'file:' + self.root
2109 return b'file:' + self.root
2110
2110
2111 def hook(self, name, throw=False, **args):
2111 def hook(self, name, throw=False, **args):
2112 """Call a hook, passing this repo instance.
2112 """Call a hook, passing this repo instance.
2113
2113
2114 This a convenience method to aid invoking hooks. Extensions likely
2114 This a convenience method to aid invoking hooks. Extensions likely
2115 won't call this unless they have registered a custom hook or are
2115 won't call this unless they have registered a custom hook or are
2116 replacing code that is expected to call a hook.
2116 replacing code that is expected to call a hook.
2117 """
2117 """
2118 return hook.hook(self.ui, self, name, throw, **args)
2118 return hook.hook(self.ui, self, name, throw, **args)
2119
2119
2120 @filteredpropertycache
2120 @filteredpropertycache
2121 def _tagscache(self):
2121 def _tagscache(self):
2122 """Returns a tagscache object that contains various tags related
2122 """Returns a tagscache object that contains various tags related
2123 caches."""
2123 caches."""
2124
2124
2125 # This simplifies its cache management by having one decorated
2125 # This simplifies its cache management by having one decorated
2126 # function (this one) and the rest simply fetch things from it.
2126 # function (this one) and the rest simply fetch things from it.
2127 class tagscache:
2127 class tagscache:
2128 def __init__(self):
2128 def __init__(self):
2129 # These two define the set of tags for this repository. tags
2129 # These two define the set of tags for this repository. tags
2130 # maps tag name to node; tagtypes maps tag name to 'global' or
2130 # maps tag name to node; tagtypes maps tag name to 'global' or
2131 # 'local'. (Global tags are defined by .hgtags across all
2131 # 'local'. (Global tags are defined by .hgtags across all
2132 # heads, and local tags are defined in .hg/localtags.)
2132 # heads, and local tags are defined in .hg/localtags.)
2133 # They constitute the in-memory cache of tags.
2133 # They constitute the in-memory cache of tags.
2134 self.tags = self.tagtypes = None
2134 self.tags = self.tagtypes = None
2135
2135
2136 self.nodetagscache = self.tagslist = None
2136 self.nodetagscache = self.tagslist = None
2137
2137
2138 cache = tagscache()
2138 cache = tagscache()
2139 cache.tags, cache.tagtypes = self._findtags()
2139 cache.tags, cache.tagtypes = self._findtags()
2140
2140
2141 return cache
2141 return cache
2142
2142
2143 def tags(self):
2143 def tags(self):
2144 '''return a mapping of tag to node'''
2144 '''return a mapping of tag to node'''
2145 t = {}
2145 t = {}
2146 if self.changelog.filteredrevs:
2146 if self.changelog.filteredrevs:
2147 tags, tt = self._findtags()
2147 tags, tt = self._findtags()
2148 else:
2148 else:
2149 tags = self._tagscache.tags
2149 tags = self._tagscache.tags
2150 rev = self.changelog.rev
2150 rev = self.changelog.rev
2151 for k, v in tags.items():
2151 for k, v in tags.items():
2152 try:
2152 try:
2153 # ignore tags to unknown nodes
2153 # ignore tags to unknown nodes
2154 rev(v)
2154 rev(v)
2155 t[k] = v
2155 t[k] = v
2156 except (error.LookupError, ValueError):
2156 except (error.LookupError, ValueError):
2157 pass
2157 pass
2158 return t
2158 return t
2159
2159
2160 def _findtags(self):
2160 def _findtags(self):
2161 """Do the hard work of finding tags. Return a pair of dicts
2161 """Do the hard work of finding tags. Return a pair of dicts
2162 (tags, tagtypes) where tags maps tag name to node, and tagtypes
2162 (tags, tagtypes) where tags maps tag name to node, and tagtypes
2163 maps tag name to a string like \'global\' or \'local\'.
2163 maps tag name to a string like \'global\' or \'local\'.
2164 Subclasses or extensions are free to add their own tags, but
2164 Subclasses or extensions are free to add their own tags, but
2165 should be aware that the returned dicts will be retained for the
2165 should be aware that the returned dicts will be retained for the
2166 duration of the localrepo object."""
2166 duration of the localrepo object."""
2167
2167
2168 # XXX what tagtype should subclasses/extensions use? Currently
2168 # XXX what tagtype should subclasses/extensions use? Currently
2169 # mq and bookmarks add tags, but do not set the tagtype at all.
2169 # mq and bookmarks add tags, but do not set the tagtype at all.
2170 # Should each extension invent its own tag type? Should there
2170 # Should each extension invent its own tag type? Should there
2171 # be one tagtype for all such "virtual" tags? Or is the status
2171 # be one tagtype for all such "virtual" tags? Or is the status
2172 # quo fine?
2172 # quo fine?
2173
2173
2174 # map tag name to (node, hist)
2174 # map tag name to (node, hist)
2175 alltags = tagsmod.findglobaltags(self.ui, self)
2175 alltags = tagsmod.findglobaltags(self.ui, self)
2176 # map tag name to tag type
2176 # map tag name to tag type
2177 tagtypes = {tag: b'global' for tag in alltags}
2177 tagtypes = {tag: b'global' for tag in alltags}
2178
2178
2179 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
2179 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
2180
2180
2181 # Build the return dicts. Have to re-encode tag names because
2181 # Build the return dicts. Have to re-encode tag names because
2182 # the tags module always uses UTF-8 (in order not to lose info
2182 # the tags module always uses UTF-8 (in order not to lose info
2183 # writing to the cache), but the rest of Mercurial wants them in
2183 # writing to the cache), but the rest of Mercurial wants them in
2184 # local encoding.
2184 # local encoding.
2185 tags = {}
2185 tags = {}
2186 for name, (node, hist) in alltags.items():
2186 for name, (node, hist) in alltags.items():
2187 if node != self.nullid:
2187 if node != self.nullid:
2188 tags[encoding.tolocal(name)] = node
2188 tags[encoding.tolocal(name)] = node
2189 tags[b'tip'] = self.changelog.tip()
2189 tags[b'tip'] = self.changelog.tip()
2190 tagtypes = {
2190 tagtypes = {
2191 encoding.tolocal(name): value for (name, value) in tagtypes.items()
2191 encoding.tolocal(name): value for (name, value) in tagtypes.items()
2192 }
2192 }
2193 return (tags, tagtypes)
2193 return (tags, tagtypes)
2194
2194
2195 def tagtype(self, tagname):
2195 def tagtype(self, tagname):
2196 """
2196 """
2197 return the type of the given tag. result can be:
2197 return the type of the given tag. result can be:
2198
2198
2199 'local' : a local tag
2199 'local' : a local tag
2200 'global' : a global tag
2200 'global' : a global tag
2201 None : tag does not exist
2201 None : tag does not exist
2202 """
2202 """
2203
2203
2204 return self._tagscache.tagtypes.get(tagname)
2204 return self._tagscache.tagtypes.get(tagname)
2205
2205
2206 def tagslist(self):
2206 def tagslist(self):
2207 '''return a list of tags ordered by revision'''
2207 '''return a list of tags ordered by revision'''
2208 if not self._tagscache.tagslist:
2208 if not self._tagscache.tagslist:
2209 l = []
2209 l = []
2210 for t, n in self.tags().items():
2210 for t, n in self.tags().items():
2211 l.append((self.changelog.rev(n), t, n))
2211 l.append((self.changelog.rev(n), t, n))
2212 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
2212 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
2213
2213
2214 return self._tagscache.tagslist
2214 return self._tagscache.tagslist
2215
2215
2216 def nodetags(self, node):
2216 def nodetags(self, node):
2217 '''return the tags associated with a node'''
2217 '''return the tags associated with a node'''
2218 if not self._tagscache.nodetagscache:
2218 if not self._tagscache.nodetagscache:
2219 nodetagscache = {}
2219 nodetagscache = {}
2220 for t, n in self._tagscache.tags.items():
2220 for t, n in self._tagscache.tags.items():
2221 nodetagscache.setdefault(n, []).append(t)
2221 nodetagscache.setdefault(n, []).append(t)
2222 for tags in nodetagscache.values():
2222 for tags in nodetagscache.values():
2223 tags.sort()
2223 tags.sort()
2224 self._tagscache.nodetagscache = nodetagscache
2224 self._tagscache.nodetagscache = nodetagscache
2225 return self._tagscache.nodetagscache.get(node, [])
2225 return self._tagscache.nodetagscache.get(node, [])
2226
2226
2227 def nodebookmarks(self, node):
2227 def nodebookmarks(self, node):
2228 """return the list of bookmarks pointing to the specified node"""
2228 """return the list of bookmarks pointing to the specified node"""
2229 return self._bookmarks.names(node)
2229 return self._bookmarks.names(node)
2230
2230
2231 def branchmap(self):
2231 def branchmap(self):
2232 """returns a dictionary {branch: [branchheads]} with branchheads
2232 """returns a dictionary {branch: [branchheads]} with branchheads
2233 ordered by increasing revision number"""
2233 ordered by increasing revision number"""
2234 return self._branchcaches[self]
2234 return self._branchcaches[self]
2235
2235
2236 @unfilteredmethod
2236 @unfilteredmethod
2237 def revbranchcache(self):
2237 def revbranchcache(self):
2238 if not self._revbranchcache:
2238 if not self._revbranchcache:
2239 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
2239 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
2240 return self._revbranchcache
2240 return self._revbranchcache
2241
2241
2242 def register_changeset(self, rev, changelogrevision):
2242 def register_changeset(self, rev, changelogrevision):
2243 self.revbranchcache().setdata(rev, changelogrevision)
2243 self.revbranchcache().setdata(rev, changelogrevision)
2244
2244
2245 def branchtip(self, branch, ignoremissing=False):
2245 def branchtip(self, branch, ignoremissing=False):
2246 """return the tip node for a given branch
2246 """return the tip node for a given branch
2247
2247
2248 If ignoremissing is True, then this method will not raise an error.
2248 If ignoremissing is True, then this method will not raise an error.
2249 This is helpful for callers that only expect None for a missing branch
2249 This is helpful for callers that only expect None for a missing branch
2250 (e.g. namespace).
2250 (e.g. namespace).
2251
2251
2252 """
2252 """
2253 try:
2253 try:
2254 return self.branchmap().branchtip(branch)
2254 return self.branchmap().branchtip(branch)
2255 except KeyError:
2255 except KeyError:
2256 if not ignoremissing:
2256 if not ignoremissing:
2257 raise error.RepoLookupError(_(b"unknown branch '%s'") % branch)
2257 raise error.RepoLookupError(_(b"unknown branch '%s'") % branch)
2258 else:
2258 else:
2259 pass
2259 pass
2260
2260
2261 def lookup(self, key):
2261 def lookup(self, key):
2262 node = scmutil.revsymbol(self, key).node()
2262 node = scmutil.revsymbol(self, key).node()
2263 if node is None:
2263 if node is None:
2264 raise error.RepoLookupError(_(b"unknown revision '%s'") % key)
2264 raise error.RepoLookupError(_(b"unknown revision '%s'") % key)
2265 return node
2265 return node
2266
2266
2267 def lookupbranch(self, key):
2267 def lookupbranch(self, key):
2268 if self.branchmap().hasbranch(key):
2268 if self.branchmap().hasbranch(key):
2269 return key
2269 return key
2270
2270
2271 return scmutil.revsymbol(self, key).branch()
2271 return scmutil.revsymbol(self, key).branch()
2272
2272
2273 def known(self, nodes):
2273 def known(self, nodes):
2274 cl = self.changelog
2274 cl = self.changelog
2275 get_rev = cl.index.get_rev
2275 get_rev = cl.index.get_rev
2276 filtered = cl.filteredrevs
2276 filtered = cl.filteredrevs
2277 result = []
2277 result = []
2278 for n in nodes:
2278 for n in nodes:
2279 r = get_rev(n)
2279 r = get_rev(n)
2280 resp = not (r is None or r in filtered)
2280 resp = not (r is None or r in filtered)
2281 result.append(resp)
2281 result.append(resp)
2282 return result
2282 return result
2283
2283
2284 def local(self):
2284 def local(self):
2285 return self
2285 return self
2286
2286
2287 def publishing(self):
2287 def publishing(self):
2288 # it's safe (and desirable) to trust the publish flag unconditionally
2288 # it's safe (and desirable) to trust the publish flag unconditionally
2289 # so that we don't finalize changes shared between users via ssh or nfs
2289 # so that we don't finalize changes shared between users via ssh or nfs
2290 return self.ui.configbool(b'phases', b'publish', untrusted=True)
2290 return self.ui.configbool(b'phases', b'publish', untrusted=True)
2291
2291
2292 def cancopy(self):
2292 def cancopy(self):
2293 # so statichttprepo's override of local() works
2293 # so statichttprepo's override of local() works
2294 if not self.local():
2294 if not self.local():
2295 return False
2295 return False
2296 if not self.publishing():
2296 if not self.publishing():
2297 return True
2297 return True
2298 # if publishing we can't copy if there is filtered content
2298 # if publishing we can't copy if there is filtered content
2299 return not self.filtered(b'visible').changelog.filteredrevs
2299 return not self.filtered(b'visible').changelog.filteredrevs
2300
2300
2301 def shared(self):
2301 def shared(self):
2302 '''the type of shared repository (None if not shared)'''
2302 '''the type of shared repository (None if not shared)'''
2303 if self.sharedpath != self.path:
2303 if self.sharedpath != self.path:
2304 return b'store'
2304 return b'store'
2305 return None
2305 return None
2306
2306
2307 def wjoin(self, f: bytes, *insidef: bytes) -> bytes:
2307 def wjoin(self, f: bytes, *insidef: bytes) -> bytes:
2308 return self.vfs.reljoin(self.root, f, *insidef)
2308 return self.vfs.reljoin(self.root, f, *insidef)
2309
2309
2310 def setparents(self, p1, p2=None):
2310 def setparents(self, p1, p2=None):
2311 if p2 is None:
2311 if p2 is None:
2312 p2 = self.nullid
2312 p2 = self.nullid
2313 self[None].setparents(p1, p2)
2313 self[None].setparents(p1, p2)
2314 self._quick_access_changeid_invalidate()
2314 self._quick_access_changeid_invalidate()
2315
2315
2316 def filectx(self, path: bytes, changeid=None, fileid=None, changectx=None):
2316 def filectx(self, path: bytes, changeid=None, fileid=None, changectx=None):
2317 """changeid must be a changeset revision, if specified.
2317 """changeid must be a changeset revision, if specified.
2318 fileid can be a file revision or node."""
2318 fileid can be a file revision or node."""
2319 return context.filectx(
2319 return context.filectx(
2320 self, path, changeid, fileid, changectx=changectx
2320 self, path, changeid, fileid, changectx=changectx
2321 )
2321 )
2322
2322
2323 def getcwd(self) -> bytes:
2323 def getcwd(self) -> bytes:
2324 return self.dirstate.getcwd()
2324 return self.dirstate.getcwd()
2325
2325
2326 def pathto(self, f: bytes, cwd: Optional[bytes] = None) -> bytes:
2326 def pathto(self, f: bytes, cwd: Optional[bytes] = None) -> bytes:
2327 return self.dirstate.pathto(f, cwd)
2327 return self.dirstate.pathto(f, cwd)
2328
2328
2329 def _loadfilter(self, filter):
2329 def _loadfilter(self, filter):
2330 if filter not in self._filterpats:
2330 if filter not in self._filterpats:
2331 l = []
2331 l = []
2332 for pat, cmd in self.ui.configitems(filter):
2332 for pat, cmd in self.ui.configitems(filter):
2333 if cmd == b'!':
2333 if cmd == b'!':
2334 continue
2334 continue
2335 mf = matchmod.match(self.root, b'', [pat])
2335 mf = matchmod.match(self.root, b'', [pat])
2336 fn = None
2336 fn = None
2337 params = cmd
2337 params = cmd
2338 for name, filterfn in self._datafilters.items():
2338 for name, filterfn in self._datafilters.items():
2339 if cmd.startswith(name):
2339 if cmd.startswith(name):
2340 fn = filterfn
2340 fn = filterfn
2341 params = cmd[len(name) :].lstrip()
2341 params = cmd[len(name) :].lstrip()
2342 break
2342 break
2343 if not fn:
2343 if not fn:
2344 fn = lambda s, c, **kwargs: procutil.filter(s, c)
2344 fn = lambda s, c, **kwargs: procutil.filter(s, c)
2345 fn.__name__ = 'commandfilter'
2345 fn.__name__ = 'commandfilter'
2346 # Wrap old filters not supporting keyword arguments
2346 # Wrap old filters not supporting keyword arguments
2347 if not pycompat.getargspec(fn)[2]:
2347 if not pycompat.getargspec(fn)[2]:
2348 oldfn = fn
2348 oldfn = fn
2349 fn = lambda s, c, oldfn=oldfn, **kwargs: oldfn(s, c)
2349 fn = lambda s, c, oldfn=oldfn, **kwargs: oldfn(s, c)
2350 fn.__name__ = 'compat-' + oldfn.__name__
2350 fn.__name__ = 'compat-' + oldfn.__name__
2351 l.append((mf, fn, params))
2351 l.append((mf, fn, params))
2352 self._filterpats[filter] = l
2352 self._filterpats[filter] = l
2353 return self._filterpats[filter]
2353 return self._filterpats[filter]
2354
2354
2355 def _filter(self, filterpats, filename, data):
2355 def _filter(self, filterpats, filename, data):
2356 for mf, fn, cmd in filterpats:
2356 for mf, fn, cmd in filterpats:
2357 if mf(filename):
2357 if mf(filename):
2358 self.ui.debug(
2358 self.ui.debug(
2359 b"filtering %s through %s\n"
2359 b"filtering %s through %s\n"
2360 % (filename, cmd or pycompat.sysbytes(fn.__name__))
2360 % (filename, cmd or pycompat.sysbytes(fn.__name__))
2361 )
2361 )
2362 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
2362 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
2363 break
2363 break
2364
2364
2365 return data
2365 return data
2366
2366
2367 @unfilteredpropertycache
2367 @unfilteredpropertycache
2368 def _encodefilterpats(self):
2368 def _encodefilterpats(self):
2369 return self._loadfilter(b'encode')
2369 return self._loadfilter(b'encode')
2370
2370
2371 @unfilteredpropertycache
2371 @unfilteredpropertycache
2372 def _decodefilterpats(self):
2372 def _decodefilterpats(self):
2373 return self._loadfilter(b'decode')
2373 return self._loadfilter(b'decode')
2374
2374
2375 def adddatafilter(self, name, filter):
2375 def adddatafilter(self, name, filter):
2376 self._datafilters[name] = filter
2376 self._datafilters[name] = filter
2377
2377
2378 def wread(self, filename: bytes) -> bytes:
2378 def wread(self, filename: bytes) -> bytes:
2379 if self.wvfs.islink(filename):
2379 if self.wvfs.islink(filename):
2380 data = self.wvfs.readlink(filename)
2380 data = self.wvfs.readlink(filename)
2381 else:
2381 else:
2382 data = self.wvfs.read(filename)
2382 data = self.wvfs.read(filename)
2383 return self._filter(self._encodefilterpats, filename, data)
2383 return self._filter(self._encodefilterpats, filename, data)
2384
2384
2385 def wwrite(
2385 def wwrite(
2386 self,
2386 self,
2387 filename: bytes,
2387 filename: bytes,
2388 data: bytes,
2388 data: bytes,
2389 flags: bytes,
2389 flags: bytes,
2390 backgroundclose=False,
2390 backgroundclose=False,
2391 **kwargs
2391 **kwargs
2392 ) -> int:
2392 ) -> int:
2393 """write ``data`` into ``filename`` in the working directory
2393 """write ``data`` into ``filename`` in the working directory
2394
2394
2395 This returns length of written (maybe decoded) data.
2395 This returns length of written (maybe decoded) data.
2396 """
2396 """
2397 data = self._filter(self._decodefilterpats, filename, data)
2397 data = self._filter(self._decodefilterpats, filename, data)
2398 if b'l' in flags:
2398 if b'l' in flags:
2399 self.wvfs.symlink(data, filename)
2399 self.wvfs.symlink(data, filename)
2400 else:
2400 else:
2401 self.wvfs.write(
2401 self.wvfs.write(
2402 filename, data, backgroundclose=backgroundclose, **kwargs
2402 filename, data, backgroundclose=backgroundclose, **kwargs
2403 )
2403 )
2404 if b'x' in flags:
2404 if b'x' in flags:
2405 self.wvfs.setflags(filename, False, True)
2405 self.wvfs.setflags(filename, False, True)
2406 else:
2406 else:
2407 self.wvfs.setflags(filename, False, False)
2407 self.wvfs.setflags(filename, False, False)
2408 return len(data)
2408 return len(data)
2409
2409
2410 def wwritedata(self, filename: bytes, data: bytes) -> bytes:
2410 def wwritedata(self, filename: bytes, data: bytes) -> bytes:
2411 return self._filter(self._decodefilterpats, filename, data)
2411 return self._filter(self._decodefilterpats, filename, data)
2412
2412
2413 def currenttransaction(self):
2413 def currenttransaction(self):
2414 """return the current transaction or None if non exists"""
2414 """return the current transaction or None if non exists"""
2415 if self._transref:
2415 if self._transref:
2416 tr = self._transref()
2416 tr = self._transref()
2417 else:
2417 else:
2418 tr = None
2418 tr = None
2419
2419
2420 if tr and tr.running():
2420 if tr and tr.running():
2421 return tr
2421 return tr
2422 return None
2422 return None
2423
2423
2424 def transaction(self, desc, report=None):
2424 def transaction(self, desc, report=None):
2425 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
2425 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
2426 b'devel', b'check-locks'
2426 b'devel', b'check-locks'
2427 ):
2427 ):
2428 if self._currentlock(self._lockref) is None:
2428 if self._currentlock(self._lockref) is None:
2429 raise error.ProgrammingError(b'transaction requires locking')
2429 raise error.ProgrammingError(b'transaction requires locking')
2430 tr = self.currenttransaction()
2430 tr = self.currenttransaction()
2431 if tr is not None:
2431 if tr is not None:
2432 return tr.nest(name=desc)
2432 return tr.nest(name=desc)
2433
2433
2434 # abort here if the journal already exists
2434 # abort here if the journal already exists
2435 if self.svfs.exists(b"journal"):
2435 if self.svfs.exists(b"journal"):
2436 raise error.RepoError(
2436 raise error.RepoError(
2437 _(b"abandoned transaction found"),
2437 _(b"abandoned transaction found"),
2438 hint=_(b"run 'hg recover' to clean up transaction"),
2438 hint=_(b"run 'hg recover' to clean up transaction"),
2439 )
2439 )
2440
2440
2441 # At that point your dirstate should be clean:
2441 # At that point your dirstate should be clean:
2442 #
2442 #
2443 # - If you don't have the wlock, why would you still have a dirty
2443 # - If you don't have the wlock, why would you still have a dirty
2444 # dirstate ?
2444 # dirstate ?
2445 #
2445 #
2446 # - If you hold the wlock, you should not be opening a transaction in
2446 # - If you hold the wlock, you should not be opening a transaction in
2447 # the middle of a `distate.changing_*` block. The transaction needs to
2447 # the middle of a `distate.changing_*` block. The transaction needs to
2448 # be open before that and wrap the change-context.
2448 # be open before that and wrap the change-context.
2449 #
2449 #
2450 # - If you are not within a `dirstate.changing_*` context, why is our
2450 # - If you are not within a `dirstate.changing_*` context, why is our
2451 # dirstate dirty?
2451 # dirstate dirty?
2452 if self.dirstate._dirty:
2452 if self.dirstate._dirty:
2453 m = "cannot open a transaction with a dirty dirstate"
2453 m = "cannot open a transaction with a dirty dirstate"
2454 raise error.ProgrammingError(m)
2454 raise error.ProgrammingError(m)
2455
2455
2456 idbase = b"%.40f#%f" % (random.random(), time.time())
2456 idbase = b"%.40f#%f" % (random.random(), time.time())
2457 ha = hex(hashutil.sha1(idbase).digest())
2457 ha = hex(hashutil.sha1(idbase).digest())
2458 txnid = b'TXN:' + ha
2458 txnid = b'TXN:' + ha
2459 self.hook(b'pretxnopen', throw=True, txnname=desc, txnid=txnid)
2459 self.hook(b'pretxnopen', throw=True, txnname=desc, txnid=txnid)
2460
2460
2461 self._writejournal(desc)
2461 self._writejournal(desc)
2462 if report:
2462 if report:
2463 rp = report
2463 rp = report
2464 else:
2464 else:
2465 rp = self.ui.warn
2465 rp = self.ui.warn
2466 vfsmap = self.vfs_map
2466 vfsmap = self.vfs_map
2467 # we must avoid cyclic reference between repo and transaction.
2467 # we must avoid cyclic reference between repo and transaction.
2468 reporef = weakref.ref(self)
2468 reporef = weakref.ref(self)
2469 # Code to track tag movement
2469 # Code to track tag movement
2470 #
2470 #
2471 # Since tags are all handled as file content, it is actually quite hard
2471 # Since tags are all handled as file content, it is actually quite hard
2472 # to track these movement from a code perspective. So we fallback to a
2472 # to track these movement from a code perspective. So we fallback to a
2473 # tracking at the repository level. One could envision to track changes
2473 # tracking at the repository level. One could envision to track changes
2474 # to the '.hgtags' file through changegroup apply but that fails to
2474 # to the '.hgtags' file through changegroup apply but that fails to
2475 # cope with case where transaction expose new heads without changegroup
2475 # cope with case where transaction expose new heads without changegroup
2476 # being involved (eg: phase movement).
2476 # being involved (eg: phase movement).
2477 #
2477 #
2478 # For now, We gate the feature behind a flag since this likely comes
2478 # For now, We gate the feature behind a flag since this likely comes
2479 # with performance impacts. The current code run more often than needed
2479 # with performance impacts. The current code run more often than needed
2480 # and do not use caches as much as it could. The current focus is on
2480 # and do not use caches as much as it could. The current focus is on
2481 # the behavior of the feature so we disable it by default. The flag
2481 # the behavior of the feature so we disable it by default. The flag
2482 # will be removed when we are happy with the performance impact.
2482 # will be removed when we are happy with the performance impact.
2483 #
2483 #
2484 # Once this feature is no longer experimental move the following
2484 # Once this feature is no longer experimental move the following
2485 # documentation to the appropriate help section:
2485 # documentation to the appropriate help section:
2486 #
2486 #
2487 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
2487 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
2488 # tags (new or changed or deleted tags). In addition the details of
2488 # tags (new or changed or deleted tags). In addition the details of
2489 # these changes are made available in a file at:
2489 # these changes are made available in a file at:
2490 # ``REPOROOT/.hg/changes/tags.changes``.
2490 # ``REPOROOT/.hg/changes/tags.changes``.
2491 # Make sure you check for HG_TAG_MOVED before reading that file as it
2491 # Make sure you check for HG_TAG_MOVED before reading that file as it
2492 # might exist from a previous transaction even if no tag were touched
2492 # might exist from a previous transaction even if no tag were touched
2493 # in this one. Changes are recorded in a line base format::
2493 # in this one. Changes are recorded in a line base format::
2494 #
2494 #
2495 # <action> <hex-node> <tag-name>\n
2495 # <action> <hex-node> <tag-name>\n
2496 #
2496 #
2497 # Actions are defined as follow:
2497 # Actions are defined as follow:
2498 # "-R": tag is removed,
2498 # "-R": tag is removed,
2499 # "+A": tag is added,
2499 # "+A": tag is added,
2500 # "-M": tag is moved (old value),
2500 # "-M": tag is moved (old value),
2501 # "+M": tag is moved (new value),
2501 # "+M": tag is moved (new value),
2502 tracktags = lambda x: None
2502 tracktags = lambda x: None
2503 # experimental config: experimental.hook-track-tags
2503 # experimental config: experimental.hook-track-tags
2504 shouldtracktags = self.ui.configbool(
2504 shouldtracktags = self.ui.configbool(
2505 b'experimental', b'hook-track-tags'
2505 b'experimental', b'hook-track-tags'
2506 )
2506 )
2507 if desc != b'strip' and shouldtracktags:
2507 if desc != b'strip' and shouldtracktags:
2508 oldheads = self.changelog.headrevs()
2508 oldheads = self.changelog.headrevs()
2509
2509
2510 def tracktags(tr2):
2510 def tracktags(tr2):
2511 repo = reporef()
2511 repo = reporef()
2512 assert repo is not None # help pytype
2512 assert repo is not None # help pytype
2513 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
2513 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
2514 newheads = repo.changelog.headrevs()
2514 newheads = repo.changelog.headrevs()
2515 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
2515 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
2516 # notes: we compare lists here.
2516 # notes: we compare lists here.
2517 # As we do it only once buiding set would not be cheaper
2517 # As we do it only once buiding set would not be cheaper
2518 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
2518 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
2519 if changes:
2519 if changes:
2520 tr2.hookargs[b'tag_moved'] = b'1'
2520 tr2.hookargs[b'tag_moved'] = b'1'
2521 with repo.vfs(
2521 with repo.vfs(
2522 b'changes/tags.changes', b'w', atomictemp=True
2522 b'changes/tags.changes', b'w', atomictemp=True
2523 ) as changesfile:
2523 ) as changesfile:
2524 # note: we do not register the file to the transaction
2524 # note: we do not register the file to the transaction
2525 # because we needs it to still exist on the transaction
2525 # because we needs it to still exist on the transaction
2526 # is close (for txnclose hooks)
2526 # is close (for txnclose hooks)
2527 tagsmod.writediff(changesfile, changes)
2527 tagsmod.writediff(changesfile, changes)
2528
2528
2529 def validate(tr2):
2529 def validate(tr2):
2530 """will run pre-closing hooks"""
2530 """will run pre-closing hooks"""
2531 # XXX the transaction API is a bit lacking here so we take a hacky
2531 # XXX the transaction API is a bit lacking here so we take a hacky
2532 # path for now
2532 # path for now
2533 #
2533 #
2534 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
2534 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
2535 # dict is copied before these run. In addition we needs the data
2535 # dict is copied before these run. In addition we needs the data
2536 # available to in memory hooks too.
2536 # available to in memory hooks too.
2537 #
2537 #
2538 # Moreover, we also need to make sure this runs before txnclose
2538 # Moreover, we also need to make sure this runs before txnclose
2539 # hooks and there is no "pending" mechanism that would execute
2539 # hooks and there is no "pending" mechanism that would execute
2540 # logic only if hooks are about to run.
2540 # logic only if hooks are about to run.
2541 #
2541 #
2542 # Fixing this limitation of the transaction is also needed to track
2542 # Fixing this limitation of the transaction is also needed to track
2543 # other families of changes (bookmarks, phases, obsolescence).
2543 # other families of changes (bookmarks, phases, obsolescence).
2544 #
2544 #
2545 # This will have to be fixed before we remove the experimental
2545 # This will have to be fixed before we remove the experimental
2546 # gating.
2546 # gating.
2547 tracktags(tr2)
2547 tracktags(tr2)
2548 repo = reporef()
2548 repo = reporef()
2549 assert repo is not None # help pytype
2549 assert repo is not None # help pytype
2550
2550
2551 singleheadopt = (b'experimental', b'single-head-per-branch')
2551 singleheadopt = (b'experimental', b'single-head-per-branch')
2552 singlehead = repo.ui.configbool(*singleheadopt)
2552 singlehead = repo.ui.configbool(*singleheadopt)
2553 if singlehead:
2553 if singlehead:
2554 singleheadsub = repo.ui.configsuboptions(*singleheadopt)[1]
2554 singleheadsub = repo.ui.configsuboptions(*singleheadopt)[1]
2555 accountclosed = singleheadsub.get(
2555 accountclosed = singleheadsub.get(
2556 b"account-closed-heads", False
2556 b"account-closed-heads", False
2557 )
2557 )
2558 if singleheadsub.get(b"public-changes-only", False):
2558 if singleheadsub.get(b"public-changes-only", False):
2559 filtername = b"immutable"
2559 filtername = b"immutable"
2560 else:
2560 else:
2561 filtername = b"visible"
2561 filtername = b"visible"
2562 scmutil.enforcesinglehead(
2562 scmutil.enforcesinglehead(
2563 repo, tr2, desc, accountclosed, filtername
2563 repo, tr2, desc, accountclosed, filtername
2564 )
2564 )
2565 if hook.hashook(repo.ui, b'pretxnclose-bookmark'):
2565 if hook.hashook(repo.ui, b'pretxnclose-bookmark'):
2566 for name, (old, new) in sorted(
2566 for name, (old, new) in sorted(
2567 tr.changes[b'bookmarks'].items()
2567 tr.changes[b'bookmarks'].items()
2568 ):
2568 ):
2569 args = tr.hookargs.copy()
2569 args = tr.hookargs.copy()
2570 args.update(bookmarks.preparehookargs(name, old, new))
2570 args.update(bookmarks.preparehookargs(name, old, new))
2571 repo.hook(
2571 repo.hook(
2572 b'pretxnclose-bookmark',
2572 b'pretxnclose-bookmark',
2573 throw=True,
2573 throw=True,
2574 **pycompat.strkwargs(args)
2574 **pycompat.strkwargs(args)
2575 )
2575 )
2576 if hook.hashook(repo.ui, b'pretxnclose-phase'):
2576 if hook.hashook(repo.ui, b'pretxnclose-phase'):
2577 cl = repo.unfiltered().changelog
2577 cl = repo.unfiltered().changelog
2578 for revs, (old, new) in tr.changes[b'phases']:
2578 for revs, (old, new) in tr.changes[b'phases']:
2579 for rev in revs:
2579 for rev in revs:
2580 args = tr.hookargs.copy()
2580 args = tr.hookargs.copy()
2581 node = hex(cl.node(rev))
2581 node = hex(cl.node(rev))
2582 args.update(phases.preparehookargs(node, old, new))
2582 args.update(phases.preparehookargs(node, old, new))
2583 repo.hook(
2583 repo.hook(
2584 b'pretxnclose-phase',
2584 b'pretxnclose-phase',
2585 throw=True,
2585 throw=True,
2586 **pycompat.strkwargs(args)
2586 **pycompat.strkwargs(args)
2587 )
2587 )
2588
2588
2589 repo.hook(
2589 repo.hook(
2590 b'pretxnclose', throw=True, **pycompat.strkwargs(tr.hookargs)
2590 b'pretxnclose', throw=True, **pycompat.strkwargs(tr.hookargs)
2591 )
2591 )
2592
2592
2593 def releasefn(tr, success):
2593 def releasefn(tr, success):
2594 repo = reporef()
2594 repo = reporef()
2595 if repo is None:
2595 if repo is None:
2596 # If the repo has been GC'd (and this release function is being
2596 # If the repo has been GC'd (and this release function is being
2597 # called from transaction.__del__), there's not much we can do,
2597 # called from transaction.__del__), there's not much we can do,
2598 # so just leave the unfinished transaction there and let the
2598 # so just leave the unfinished transaction there and let the
2599 # user run `hg recover`.
2599 # user run `hg recover`.
2600 return
2600 return
2601 if success:
2601 if success:
2602 # this should be explicitly invoked here, because
2602 # this should be explicitly invoked here, because
2603 # in-memory changes aren't written out at closing
2603 # in-memory changes aren't written out at closing
2604 # transaction, if tr.addfilegenerator (via
2604 # transaction, if tr.addfilegenerator (via
2605 # dirstate.write or so) isn't invoked while
2605 # dirstate.write or so) isn't invoked while
2606 # transaction running
2606 # transaction running
2607 repo.dirstate.write(None)
2607 repo.dirstate.write(None)
2608 else:
2608 else:
2609 # discard all changes (including ones already written
2609 # discard all changes (including ones already written
2610 # out) in this transaction
2610 # out) in this transaction
2611 repo.invalidate(clearfilecache=True)
2611 repo.invalidate(clearfilecache=True)
2612
2612
2613 tr = transaction.transaction(
2613 tr = transaction.transaction(
2614 rp,
2614 rp,
2615 self.svfs,
2615 self.svfs,
2616 vfsmap,
2616 vfsmap,
2617 b"journal",
2617 b"journal",
2618 b"undo",
2618 b"undo",
2619 lambda: None,
2619 lambda: None,
2620 self.store.createmode,
2620 self.store.createmode,
2621 validator=validate,
2621 validator=validate,
2622 releasefn=releasefn,
2622 releasefn=releasefn,
2623 checkambigfiles=_cachedfiles,
2623 checkambigfiles=_cachedfiles,
2624 name=desc,
2624 name=desc,
2625 )
2625 )
2626 for vfs_id, path in self._journalfiles():
2626 for vfs_id, path in self._journalfiles():
2627 tr.add_journal(vfs_id, path)
2627 tr.add_journal(vfs_id, path)
2628 tr.changes[b'origrepolen'] = len(self)
2628 tr.changes[b'origrepolen'] = len(self)
2629 tr.changes[b'obsmarkers'] = set()
2629 tr.changes[b'obsmarkers'] = set()
2630 tr.changes[b'phases'] = []
2630 tr.changes[b'phases'] = []
2631 tr.changes[b'bookmarks'] = {}
2631 tr.changes[b'bookmarks'] = {}
2632
2632
2633 tr.hookargs[b'txnid'] = txnid
2633 tr.hookargs[b'txnid'] = txnid
2634 tr.hookargs[b'txnname'] = desc
2634 tr.hookargs[b'txnname'] = desc
2635 tr.hookargs[b'changes'] = tr.changes
2635 tr.hookargs[b'changes'] = tr.changes
2636 # note: writing the fncache only during finalize mean that the file is
2636 # note: writing the fncache only during finalize mean that the file is
2637 # outdated when running hooks. As fncache is used for streaming clone,
2637 # outdated when running hooks. As fncache is used for streaming clone,
2638 # this is not expected to break anything that happen during the hooks.
2638 # this is not expected to break anything that happen during the hooks.
2639 tr.addfinalize(b'flush-fncache', self.store.write)
2639 tr.addfinalize(b'flush-fncache', self.store.write)
2640
2640
2641 def txnclosehook(tr2):
2641 def txnclosehook(tr2):
2642 """To be run if transaction is successful, will schedule a hook run"""
2642 """To be run if transaction is successful, will schedule a hook run"""
2643 # Don't reference tr2 in hook() so we don't hold a reference.
2643 # Don't reference tr2 in hook() so we don't hold a reference.
2644 # This reduces memory consumption when there are multiple
2644 # This reduces memory consumption when there are multiple
2645 # transactions per lock. This can likely go away if issue5045
2645 # transactions per lock. This can likely go away if issue5045
2646 # fixes the function accumulation.
2646 # fixes the function accumulation.
2647 hookargs = tr2.hookargs
2647 hookargs = tr2.hookargs
2648
2648
2649 def hookfunc(unused_success):
2649 def hookfunc(unused_success):
2650 repo = reporef()
2650 repo = reporef()
2651 assert repo is not None # help pytype
2651 assert repo is not None # help pytype
2652
2652
2653 if hook.hashook(repo.ui, b'txnclose-bookmark'):
2653 if hook.hashook(repo.ui, b'txnclose-bookmark'):
2654 bmchanges = sorted(tr.changes[b'bookmarks'].items())
2654 bmchanges = sorted(tr.changes[b'bookmarks'].items())
2655 for name, (old, new) in bmchanges:
2655 for name, (old, new) in bmchanges:
2656 args = tr.hookargs.copy()
2656 args = tr.hookargs.copy()
2657 args.update(bookmarks.preparehookargs(name, old, new))
2657 args.update(bookmarks.preparehookargs(name, old, new))
2658 repo.hook(
2658 repo.hook(
2659 b'txnclose-bookmark',
2659 b'txnclose-bookmark',
2660 throw=False,
2660 throw=False,
2661 **pycompat.strkwargs(args)
2661 **pycompat.strkwargs(args)
2662 )
2662 )
2663
2663
2664 if hook.hashook(repo.ui, b'txnclose-phase'):
2664 if hook.hashook(repo.ui, b'txnclose-phase'):
2665 cl = repo.unfiltered().changelog
2665 cl = repo.unfiltered().changelog
2666 phasemv = sorted(
2666 phasemv = sorted(
2667 tr.changes[b'phases'], key=lambda r: r[0][0]
2667 tr.changes[b'phases'], key=lambda r: r[0][0]
2668 )
2668 )
2669 for revs, (old, new) in phasemv:
2669 for revs, (old, new) in phasemv:
2670 for rev in revs:
2670 for rev in revs:
2671 args = tr.hookargs.copy()
2671 args = tr.hookargs.copy()
2672 node = hex(cl.node(rev))
2672 node = hex(cl.node(rev))
2673 args.update(phases.preparehookargs(node, old, new))
2673 args.update(phases.preparehookargs(node, old, new))
2674 repo.hook(
2674 repo.hook(
2675 b'txnclose-phase',
2675 b'txnclose-phase',
2676 throw=False,
2676 throw=False,
2677 **pycompat.strkwargs(args)
2677 **pycompat.strkwargs(args)
2678 )
2678 )
2679
2679
2680 repo.hook(
2680 repo.hook(
2681 b'txnclose', throw=False, **pycompat.strkwargs(hookargs)
2681 b'txnclose', throw=False, **pycompat.strkwargs(hookargs)
2682 )
2682 )
2683
2683
2684 repo = reporef()
2684 repo = reporef()
2685 assert repo is not None # help pytype
2685 assert repo is not None # help pytype
2686 repo._afterlock(hookfunc)
2686 repo._afterlock(hookfunc)
2687
2687
2688 tr.addfinalize(b'txnclose-hook', txnclosehook)
2688 tr.addfinalize(b'txnclose-hook', txnclosehook)
2689 # Include a leading "-" to make it happen before the transaction summary
2689 # Include a leading "-" to make it happen before the transaction summary
2690 # reports registered via scmutil.registersummarycallback() whose names
2690 # reports registered via scmutil.registersummarycallback() whose names
2691 # are 00-txnreport etc. That way, the caches will be warm when the
2691 # are 00-txnreport etc. That way, the caches will be warm when the
2692 # callbacks run.
2692 # callbacks run.
2693 tr.addpostclose(b'-warm-cache', self._buildcacheupdater(tr))
2693 tr.addpostclose(b'-warm-cache', self._buildcacheupdater(tr))
2694
2694
2695 def txnaborthook(tr2):
2695 def txnaborthook(tr2):
2696 """To be run if transaction is aborted"""
2696 """To be run if transaction is aborted"""
2697 repo = reporef()
2697 repo = reporef()
2698 assert repo is not None # help pytype
2698 assert repo is not None # help pytype
2699 repo.hook(
2699 repo.hook(
2700 b'txnabort', throw=False, **pycompat.strkwargs(tr2.hookargs)
2700 b'txnabort', throw=False, **pycompat.strkwargs(tr2.hookargs)
2701 )
2701 )
2702
2702
2703 tr.addabort(b'txnabort-hook', txnaborthook)
2703 tr.addabort(b'txnabort-hook', txnaborthook)
2704 # avoid eager cache invalidation. in-memory data should be identical
2704 # avoid eager cache invalidation. in-memory data should be identical
2705 # to stored data if transaction has no error.
2705 # to stored data if transaction has no error.
2706 tr.addpostclose(b'refresh-filecachestats', self._refreshfilecachestats)
2706 tr.addpostclose(b'refresh-filecachestats', self._refreshfilecachestats)
2707 self._transref = weakref.ref(tr)
2707 self._transref = weakref.ref(tr)
2708 scmutil.registersummarycallback(self, tr, desc)
2708 scmutil.registersummarycallback(self, tr, desc)
2709 # This only exist to deal with the need of rollback to have viable
2709 # This only exist to deal with the need of rollback to have viable
2710 # parents at the end of the operation. So backup viable parents at the
2710 # parents at the end of the operation. So backup viable parents at the
2711 # time of this operation.
2711 # time of this operation.
2712 #
2712 #
2713 # We only do it when the `wlock` is taken, otherwise other might be
2713 # We only do it when the `wlock` is taken, otherwise other might be
2714 # altering the dirstate under us.
2714 # altering the dirstate under us.
2715 #
2715 #
2716 # This is really not a great way to do this (first, because we cannot
2716 # This is really not a great way to do this (first, because we cannot
2717 # always do it). There are more viable alternative that exists
2717 # always do it). There are more viable alternative that exists
2718 #
2718 #
2719 # - backing only the working copy parent in a dedicated files and doing
2719 # - backing only the working copy parent in a dedicated files and doing
2720 # a clean "keep-update" to them on `hg rollback`.
2720 # a clean "keep-update" to them on `hg rollback`.
2721 #
2721 #
2722 # - slightly changing the behavior an applying a logic similar to "hg
2722 # - slightly changing the behavior an applying a logic similar to "hg
2723 # strip" to pick a working copy destination on `hg rollback`
2723 # strip" to pick a working copy destination on `hg rollback`
2724 if self.currentwlock() is not None:
2724 if self.currentwlock() is not None:
2725 ds = self.dirstate
2725 ds = self.dirstate
2726 if not self.vfs.exists(b'branch'):
2726 if not self.vfs.exists(b'branch'):
2727 # force a file to be written if None exist
2727 # force a file to be written if None exist
2728 ds.setbranch(b'default', None)
2728 ds.setbranch(b'default', None)
2729
2729
2730 def backup_dirstate(tr):
2730 def backup_dirstate(tr):
2731 for f in ds.all_file_names():
2731 for f in ds.all_file_names():
2732 # hardlink backup is okay because `dirstate` is always
2732 # hardlink backup is okay because `dirstate` is always
2733 # atomically written and possible data file are append only
2733 # atomically written and possible data file are append only
2734 # and resistant to trailing data.
2734 # and resistant to trailing data.
2735 tr.addbackup(f, hardlink=True, location=b'plain')
2735 tr.addbackup(f, hardlink=True, location=b'plain')
2736
2736
2737 tr.addvalidator(b'dirstate-backup', backup_dirstate)
2737 tr.addvalidator(b'dirstate-backup', backup_dirstate)
2738 return tr
2738 return tr
2739
2739
2740 def _journalfiles(self):
2740 def _journalfiles(self):
2741 return (
2741 return (
2742 (self.svfs, b'journal'),
2742 (self.svfs, b'journal'),
2743 (self.vfs, b'journal.desc'),
2743 (self.vfs, b'journal.desc'),
2744 )
2744 )
2745
2745
2746 def undofiles(self):
2746 def undofiles(self):
2747 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
2747 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
2748
2748
2749 @unfilteredmethod
2749 @unfilteredmethod
2750 def _writejournal(self, desc):
2750 def _writejournal(self, desc):
2751 self.vfs.write(b"journal.desc", b"%d\n%s\n" % (len(self), desc))
2751 self.vfs.write(b"journal.desc", b"%d\n%s\n" % (len(self), desc))
2752
2752
2753 def recover(self):
2753 def recover(self):
2754 with self.lock():
2754 with self.lock():
2755 if self.svfs.exists(b"journal"):
2755 if self.svfs.exists(b"journal"):
2756 self.ui.status(_(b"rolling back interrupted transaction\n"))
2756 self.ui.status(_(b"rolling back interrupted transaction\n"))
2757 vfsmap = self.vfs_map
2757 vfsmap = self.vfs_map
2758 transaction.rollback(
2758 transaction.rollback(
2759 self.svfs,
2759 self.svfs,
2760 vfsmap,
2760 vfsmap,
2761 b"journal",
2761 b"journal",
2762 self.ui.warn,
2762 self.ui.warn,
2763 checkambigfiles=_cachedfiles,
2763 checkambigfiles=_cachedfiles,
2764 )
2764 )
2765 self.invalidate()
2765 self.invalidate()
2766 return True
2766 return True
2767 else:
2767 else:
2768 self.ui.warn(_(b"no interrupted transaction available\n"))
2768 self.ui.warn(_(b"no interrupted transaction available\n"))
2769 return False
2769 return False
2770
2770
2771 def rollback(self, dryrun=False, force=False):
2771 def rollback(self, dryrun=False, force=False):
2772 wlock = lock = None
2772 wlock = lock = None
2773 try:
2773 try:
2774 wlock = self.wlock()
2774 wlock = self.wlock()
2775 lock = self.lock()
2775 lock = self.lock()
2776 if self.svfs.exists(b"undo"):
2776 if self.svfs.exists(b"undo"):
2777 return self._rollback(dryrun, force)
2777 return self._rollback(dryrun, force)
2778 else:
2778 else:
2779 self.ui.warn(_(b"no rollback information available\n"))
2779 self.ui.warn(_(b"no rollback information available\n"))
2780 return 1
2780 return 1
2781 finally:
2781 finally:
2782 release(lock, wlock)
2782 release(lock, wlock)
2783
2783
2784 @unfilteredmethod # Until we get smarter cache management
2784 @unfilteredmethod # Until we get smarter cache management
2785 def _rollback(self, dryrun, force):
2785 def _rollback(self, dryrun, force):
2786 ui = self.ui
2786 ui = self.ui
2787
2787
2788 parents = self.dirstate.parents()
2788 parents = self.dirstate.parents()
2789 try:
2789 try:
2790 args = self.vfs.read(b'undo.desc').splitlines()
2790 args = self.vfs.read(b'undo.desc').splitlines()
2791 (oldlen, desc, detail) = (int(args[0]), args[1], None)
2791 (oldlen, desc, detail) = (int(args[0]), args[1], None)
2792 if len(args) >= 3:
2792 if len(args) >= 3:
2793 detail = args[2]
2793 detail = args[2]
2794 oldtip = oldlen - 1
2794 oldtip = oldlen - 1
2795
2795
2796 if detail and ui.verbose:
2796 if detail and ui.verbose:
2797 msg = _(
2797 msg = _(
2798 b'repository tip rolled back to revision %d'
2798 b'repository tip rolled back to revision %d'
2799 b' (undo %s: %s)\n'
2799 b' (undo %s: %s)\n'
2800 ) % (oldtip, desc, detail)
2800 ) % (oldtip, desc, detail)
2801 else:
2801 else:
2802 msg = _(
2802 msg = _(
2803 b'repository tip rolled back to revision %d (undo %s)\n'
2803 b'repository tip rolled back to revision %d (undo %s)\n'
2804 ) % (oldtip, desc)
2804 ) % (oldtip, desc)
2805 parentgone = any(self[p].rev() > oldtip for p in parents)
2805 parentgone = any(self[p].rev() > oldtip for p in parents)
2806 except IOError:
2806 except IOError:
2807 msg = _(b'rolling back unknown transaction\n')
2807 msg = _(b'rolling back unknown transaction\n')
2808 desc = None
2808 desc = None
2809 parentgone = True
2809 parentgone = True
2810
2810
2811 if not force and self[b'.'] != self[b'tip'] and desc == b'commit':
2811 if not force and self[b'.'] != self[b'tip'] and desc == b'commit':
2812 raise error.Abort(
2812 raise error.Abort(
2813 _(
2813 _(
2814 b'rollback of last commit while not checked out '
2814 b'rollback of last commit while not checked out '
2815 b'may lose data'
2815 b'may lose data'
2816 ),
2816 ),
2817 hint=_(b'use -f to force'),
2817 hint=_(b'use -f to force'),
2818 )
2818 )
2819
2819
2820 ui.status(msg)
2820 ui.status(msg)
2821 if dryrun:
2821 if dryrun:
2822 return 0
2822 return 0
2823
2823
2824 self.destroying()
2824 self.destroying()
2825 vfsmap = self.vfs_map
2825 vfsmap = self.vfs_map
2826 skip_journal_pattern = None
2826 skip_journal_pattern = None
2827 if not parentgone:
2827 if not parentgone:
2828 skip_journal_pattern = RE_SKIP_DIRSTATE_ROLLBACK
2828 skip_journal_pattern = RE_SKIP_DIRSTATE_ROLLBACK
2829 transaction.rollback(
2829 transaction.rollback(
2830 self.svfs,
2830 self.svfs,
2831 vfsmap,
2831 vfsmap,
2832 b'undo',
2832 b'undo',
2833 ui.warn,
2833 ui.warn,
2834 checkambigfiles=_cachedfiles,
2834 checkambigfiles=_cachedfiles,
2835 skip_journal_pattern=skip_journal_pattern,
2835 skip_journal_pattern=skip_journal_pattern,
2836 )
2836 )
2837 self.invalidate()
2837 self.invalidate()
2838 self.dirstate.invalidate()
2838 self.dirstate.invalidate()
2839
2839
2840 if parentgone:
2840 if parentgone:
2841 # replace this with some explicit parent update in the future.
2841 # replace this with some explicit parent update in the future.
2842 has_node = self.changelog.index.has_node
2842 has_node = self.changelog.index.has_node
2843 if not all(has_node(p) for p in self.dirstate._pl):
2843 if not all(has_node(p) for p in self.dirstate._pl):
2844 # There was no dirstate to backup initially, we need to drop
2844 # There was no dirstate to backup initially, we need to drop
2845 # the existing one.
2845 # the existing one.
2846 with self.dirstate.changing_parents(self):
2846 with self.dirstate.changing_parents(self):
2847 self.dirstate.setparents(self.nullid)
2847 self.dirstate.setparents(self.nullid)
2848 self.dirstate.clear()
2848 self.dirstate.clear()
2849
2849
2850 parents = tuple([p.rev() for p in self[None].parents()])
2850 parents = tuple([p.rev() for p in self[None].parents()])
2851 if len(parents) > 1:
2851 if len(parents) > 1:
2852 ui.status(
2852 ui.status(
2853 _(
2853 _(
2854 b'working directory now based on '
2854 b'working directory now based on '
2855 b'revisions %d and %d\n'
2855 b'revisions %d and %d\n'
2856 )
2856 )
2857 % parents
2857 % parents
2858 )
2858 )
2859 else:
2859 else:
2860 ui.status(
2860 ui.status(
2861 _(b'working directory now based on revision %d\n') % parents
2861 _(b'working directory now based on revision %d\n') % parents
2862 )
2862 )
2863 mergestatemod.mergestate.clean(self)
2863 mergestatemod.mergestate.clean(self)
2864
2864
2865 # TODO: if we know which new heads may result from this rollback, pass
2865 # TODO: if we know which new heads may result from this rollback, pass
2866 # them to destroy(), which will prevent the branchhead cache from being
2866 # them to destroy(), which will prevent the branchhead cache from being
2867 # invalidated.
2867 # invalidated.
2868 self.destroyed()
2868 self.destroyed()
2869 return 0
2869 return 0
2870
2870
2871 def _buildcacheupdater(self, newtransaction):
2871 def _buildcacheupdater(self, newtransaction):
2872 """called during transaction to build the callback updating cache
2872 """called during transaction to build the callback updating cache
2873
2873
2874 Lives on the repository to help extension who might want to augment
2874 Lives on the repository to help extension who might want to augment
2875 this logic. For this purpose, the created transaction is passed to the
2875 this logic. For this purpose, the created transaction is passed to the
2876 method.
2876 method.
2877 """
2877 """
2878 # we must avoid cyclic reference between repo and transaction.
2878 # we must avoid cyclic reference between repo and transaction.
2879 reporef = weakref.ref(self)
2879 reporef = weakref.ref(self)
2880
2880
2881 def updater(tr):
2881 def updater(tr):
2882 repo = reporef()
2882 repo = reporef()
2883 assert repo is not None # help pytype
2883 assert repo is not None # help pytype
2884 repo.updatecaches(tr)
2884 repo.updatecaches(tr)
2885
2885
2886 return updater
2886 return updater
2887
2887
2888 @unfilteredmethod
2888 @unfilteredmethod
2889 def updatecaches(self, tr=None, full=False, caches=None):
2889 def updatecaches(self, tr=None, full=False, caches=None):
2890 """warm appropriate caches
2890 """warm appropriate caches
2891
2891
2892 If this function is called after a transaction closed. The transaction
2892 If this function is called after a transaction closed. The transaction
2893 will be available in the 'tr' argument. This can be used to selectively
2893 will be available in the 'tr' argument. This can be used to selectively
2894 update caches relevant to the changes in that transaction.
2894 update caches relevant to the changes in that transaction.
2895
2895
2896 If 'full' is set, make sure all caches the function knows about have
2896 If 'full' is set, make sure all caches the function knows about have
2897 up-to-date data. Even the ones usually loaded more lazily.
2897 up-to-date data. Even the ones usually loaded more lazily.
2898
2898
2899 The `full` argument can take a special "post-clone" value. In this case
2899 The `full` argument can take a special "post-clone" value. In this case
2900 the cache warming is made after a clone and of the slower cache might
2900 the cache warming is made after a clone and of the slower cache might
2901 be skipped, namely the `.fnodetags` one. This argument is 5.8 specific
2901 be skipped, namely the `.fnodetags` one. This argument is 5.8 specific
2902 as we plan for a cleaner way to deal with this for 5.9.
2902 as we plan for a cleaner way to deal with this for 5.9.
2903 """
2903 """
2904 if tr is not None and tr.hookargs.get(b'source') == b'strip':
2904 if tr is not None and tr.hookargs.get(b'source') == b'strip':
2905 # During strip, many caches are invalid but
2905 # During strip, many caches are invalid but
2906 # later call to `destroyed` will refresh them.
2906 # later call to `destroyed` will refresh them.
2907 return
2907 return
2908
2908
2909 unfi = self.unfiltered()
2909 unfi = self.unfiltered()
2910
2910
2911 if full:
2911 if full:
2912 msg = (
2912 msg = (
2913 "`full` argument for `repo.updatecaches` is deprecated\n"
2913 "`full` argument for `repo.updatecaches` is deprecated\n"
2914 "(use `caches=repository.CACHE_ALL` instead)"
2914 "(use `caches=repository.CACHE_ALL` instead)"
2915 )
2915 )
2916 self.ui.deprecwarn(msg, b"5.9")
2916 self.ui.deprecwarn(msg, b"5.9")
2917 caches = repository.CACHES_ALL
2917 caches = repository.CACHES_ALL
2918 if full == b"post-clone":
2918 if full == b"post-clone":
2919 caches = repository.CACHES_POST_CLONE
2919 caches = repository.CACHES_POST_CLONE
2920 caches = repository.CACHES_ALL
2920 caches = repository.CACHES_ALL
2921 elif caches is None:
2921 elif caches is None:
2922 caches = repository.CACHES_DEFAULT
2922 caches = repository.CACHES_DEFAULT
2923
2923
2924 if repository.CACHE_BRANCHMAP_SERVED in caches:
2924 if repository.CACHE_BRANCHMAP_SERVED in caches:
2925 if tr is None or tr.changes[b'origrepolen'] < len(self):
2925 if tr is None or tr.changes[b'origrepolen'] < len(self):
2926 # accessing the 'served' branchmap should refresh all the others,
2926 # accessing the 'served' branchmap should refresh all the others,
2927 self.ui.debug(b'updating the branch cache\n')
2927 self.ui.debug(b'updating the branch cache\n')
2928 self.filtered(b'served').branchmap()
2928 self.filtered(b'served').branchmap()
2929 self.filtered(b'served.hidden').branchmap()
2929 self.filtered(b'served.hidden').branchmap()
2930 # flush all possibly delayed write.
2930 # flush all possibly delayed write.
2931 self._branchcaches.write_delayed(self)
2931 self._branchcaches.write_delayed(self)
2932
2932
2933 if repository.CACHE_CHANGELOG_CACHE in caches:
2933 if repository.CACHE_CHANGELOG_CACHE in caches:
2934 self.changelog.update_caches(transaction=tr)
2934 self.changelog.update_caches(transaction=tr)
2935
2935
2936 if repository.CACHE_MANIFESTLOG_CACHE in caches:
2936 if repository.CACHE_MANIFESTLOG_CACHE in caches:
2937 self.manifestlog.update_caches(transaction=tr)
2937 self.manifestlog.update_caches(transaction=tr)
2938 for entry in self.store.walk():
2938 for entry in self.store.walk():
2939 if not entry.is_revlog:
2939 if not entry.is_revlog:
2940 continue
2940 continue
2941 if not entry.is_manifestlog:
2941 if not entry.is_manifestlog:
2942 continue
2942 continue
2943 manifestrevlog = entry.get_revlog_instance(self).get_revlog()
2943 manifestrevlog = entry.get_revlog_instance(self).get_revlog()
2944 if manifestrevlog is not None:
2944 if manifestrevlog is not None:
2945 manifestrevlog.update_caches(transaction=tr)
2945 manifestrevlog.update_caches(transaction=tr)
2946
2946
2947 if repository.CACHE_REV_BRANCH in caches:
2947 if repository.CACHE_REV_BRANCH in caches:
2948 rbc = unfi.revbranchcache()
2948 rbc = unfi.revbranchcache()
2949 for r in unfi.changelog:
2949 for r in unfi.changelog:
2950 rbc.branchinfo(r)
2950 rbc.branchinfo(r)
2951 rbc.write()
2951 rbc.write()
2952
2952
2953 if repository.CACHE_FULL_MANIFEST in caches:
2953 if repository.CACHE_FULL_MANIFEST in caches:
2954 # ensure the working copy parents are in the manifestfulltextcache
2954 # ensure the working copy parents are in the manifestfulltextcache
2955 for ctx in self[b'.'].parents():
2955 for ctx in self[b'.'].parents():
2956 ctx.manifest() # accessing the manifest is enough
2956 ctx.manifest() # accessing the manifest is enough
2957
2957
2958 if repository.CACHE_FILE_NODE_TAGS in caches:
2958 if repository.CACHE_FILE_NODE_TAGS in caches:
2959 # accessing fnode cache warms the cache
2959 # accessing fnode cache warms the cache
2960 tagsmod.fnoderevs(self.ui, unfi, unfi.changelog.revs())
2960 tagsmod.fnoderevs(self.ui, unfi, unfi.changelog.revs())
2961
2961
2962 if repository.CACHE_TAGS_DEFAULT in caches:
2962 if repository.CACHE_TAGS_DEFAULT in caches:
2963 # accessing tags warm the cache
2963 # accessing tags warm the cache
2964 self.tags()
2964 self.tags()
2965 if repository.CACHE_TAGS_SERVED in caches:
2965 if repository.CACHE_TAGS_SERVED in caches:
2966 self.filtered(b'served').tags()
2966 self.filtered(b'served').tags()
2967
2967
2968 if repository.CACHE_BRANCHMAP_ALL in caches:
2968 if repository.CACHE_BRANCHMAP_ALL in caches:
2969 # The CACHE_BRANCHMAP_ALL updates lazily-loaded caches immediately,
2969 # The CACHE_BRANCHMAP_ALL updates lazily-loaded caches immediately,
2970 # so we're forcing a write to cause these caches to be warmed up
2970 # so we're forcing a write to cause these caches to be warmed up
2971 # even if they haven't explicitly been requested yet (if they've
2971 # even if they haven't explicitly been requested yet (if they've
2972 # never been used by hg, they won't ever have been written, even if
2972 # never been used by hg, they won't ever have been written, even if
2973 # they're a subset of another kind of cache that *has* been used).
2973 # they're a subset of another kind of cache that *has* been used).
2974 for filt in repoview.filtertable.keys():
2974 for filt in repoview.filtertable.keys():
2975 filtered = self.filtered(filt)
2975 filtered = self.filtered(filt)
2976 filtered.branchmap().write(filtered)
2976 filtered.branchmap().write(filtered)
2977
2977
2978 def invalidatecaches(self):
2978 def invalidatecaches(self):
2979 if '_tagscache' in vars(self):
2979 if '_tagscache' in vars(self):
2980 # can't use delattr on proxy
2980 # can't use delattr on proxy
2981 del self.__dict__['_tagscache']
2981 del self.__dict__['_tagscache']
2982
2982
2983 self._branchcaches.clear()
2983 self._branchcaches.clear()
2984 self.invalidatevolatilesets()
2984 self.invalidatevolatilesets()
2985 self._sparsesignaturecache.clear()
2985 self._sparsesignaturecache.clear()
2986
2986
2987 def invalidatevolatilesets(self):
2987 def invalidatevolatilesets(self):
2988 self.filteredrevcache.clear()
2988 self.filteredrevcache.clear()
2989 obsolete.clearobscaches(self)
2989 obsolete.clearobscaches(self)
2990 self._quick_access_changeid_invalidate()
2990 self._quick_access_changeid_invalidate()
2991
2991
2992 def invalidatedirstate(self):
2992 def invalidatedirstate(self):
2993 """Invalidates the dirstate, causing the next call to dirstate
2993 """Invalidates the dirstate, causing the next call to dirstate
2994 to check if it was modified since the last time it was read,
2994 to check if it was modified since the last time it was read,
2995 rereading it if it has.
2995 rereading it if it has.
2996
2996
2997 This is different to dirstate.invalidate() that it doesn't always
2997 This is different to dirstate.invalidate() that it doesn't always
2998 rereads the dirstate. Use dirstate.invalidate() if you want to
2998 rereads the dirstate. Use dirstate.invalidate() if you want to
2999 explicitly read the dirstate again (i.e. restoring it to a previous
2999 explicitly read the dirstate again (i.e. restoring it to a previous
3000 known good state)."""
3000 known good state)."""
3001 unfi = self.unfiltered()
3001 unfi = self.unfiltered()
3002 if 'dirstate' in unfi.__dict__:
3002 if 'dirstate' in unfi.__dict__:
3003 assert not self.dirstate.is_changing_any
3003 assert not self.dirstate.is_changing_any
3004 del unfi.__dict__['dirstate']
3004 del unfi.__dict__['dirstate']
3005
3005
3006 def invalidate(self, clearfilecache=False):
3006 def invalidate(self, clearfilecache=False):
3007 """Invalidates both store and non-store parts other than dirstate
3007 """Invalidates both store and non-store parts other than dirstate
3008
3008
3009 If a transaction is running, invalidation of store is omitted,
3009 If a transaction is running, invalidation of store is omitted,
3010 because discarding in-memory changes might cause inconsistency
3010 because discarding in-memory changes might cause inconsistency
3011 (e.g. incomplete fncache causes unintentional failure, but
3011 (e.g. incomplete fncache causes unintentional failure, but
3012 redundant one doesn't).
3012 redundant one doesn't).
3013 """
3013 """
3014 unfiltered = self.unfiltered() # all file caches are stored unfiltered
3014 unfiltered = self.unfiltered() # all file caches are stored unfiltered
3015 for k in list(self._filecache.keys()):
3015 for k in list(self._filecache.keys()):
3016 if (
3016 if (
3017 k == b'changelog'
3017 k == b'changelog'
3018 and self.currenttransaction()
3018 and self.currenttransaction()
3019 and self.changelog._delayed
3019 and self.changelog._delayed
3020 ):
3020 ):
3021 # The changelog object may store unwritten revisions. We don't
3021 # The changelog object may store unwritten revisions. We don't
3022 # want to lose them.
3022 # want to lose them.
3023 # TODO: Solve the problem instead of working around it.
3023 # TODO: Solve the problem instead of working around it.
3024 continue
3024 continue
3025
3025
3026 if clearfilecache:
3026 if clearfilecache:
3027 del self._filecache[k]
3027 del self._filecache[k]
3028 try:
3028 try:
3029 # XXX ideally, the key would be a unicode string to match the
3029 # XXX ideally, the key would be a unicode string to match the
3030 # fact it refers to an attribut name. However changing this was
3030 # fact it refers to an attribut name. However changing this was
3031 # a bit a scope creep compared to the series cleaning up
3031 # a bit a scope creep compared to the series cleaning up
3032 # del/set/getattr so we kept thing simple here.
3032 # del/set/getattr so we kept thing simple here.
3033 delattr(unfiltered, pycompat.sysstr(k))
3033 delattr(unfiltered, pycompat.sysstr(k))
3034 except AttributeError:
3034 except AttributeError:
3035 pass
3035 pass
3036 self.invalidatecaches()
3036 self.invalidatecaches()
3037 if not self.currenttransaction():
3037 if not self.currenttransaction():
3038 # TODO: Changing contents of store outside transaction
3038 # TODO: Changing contents of store outside transaction
3039 # causes inconsistency. We should make in-memory store
3039 # causes inconsistency. We should make in-memory store
3040 # changes detectable, and abort if changed.
3040 # changes detectable, and abort if changed.
3041 self.store.invalidatecaches()
3041 self.store.invalidatecaches()
3042
3042
3043 def invalidateall(self):
3043 def invalidateall(self):
3044 """Fully invalidates both store and non-store parts, causing the
3044 """Fully invalidates both store and non-store parts, causing the
3045 subsequent operation to reread any outside changes."""
3045 subsequent operation to reread any outside changes."""
3046 # extension should hook this to invalidate its caches
3046 # extension should hook this to invalidate its caches
3047 self.invalidate()
3047 self.invalidate()
3048 self.invalidatedirstate()
3048 self.invalidatedirstate()
3049
3049
3050 @unfilteredmethod
3050 @unfilteredmethod
3051 def _refreshfilecachestats(self, tr):
3051 def _refreshfilecachestats(self, tr):
3052 """Reload stats of cached files so that they are flagged as valid"""
3052 """Reload stats of cached files so that they are flagged as valid"""
3053 for k, ce in self._filecache.items():
3053 for k, ce in self._filecache.items():
3054 k = pycompat.sysstr(k)
3054 k = pycompat.sysstr(k)
3055 if k == 'dirstate' or k not in self.__dict__:
3055 if k == 'dirstate' or k not in self.__dict__:
3056 continue
3056 continue
3057 ce.refresh()
3057 ce.refresh()
3058
3058
3059 def _lock(
3059 def _lock(
3060 self,
3060 self,
3061 vfs,
3061 vfs,
3062 lockname,
3062 lockname,
3063 wait,
3063 wait,
3064 releasefn,
3064 releasefn,
3065 acquirefn,
3065 acquirefn,
3066 desc,
3066 desc,
3067 ):
3067 ):
3068 timeout = 0
3068 timeout = 0
3069 warntimeout = 0
3069 warntimeout = 0
3070 if wait:
3070 if wait:
3071 timeout = self.ui.configint(b"ui", b"timeout")
3071 timeout = self.ui.configint(b"ui", b"timeout")
3072 warntimeout = self.ui.configint(b"ui", b"timeout.warn")
3072 warntimeout = self.ui.configint(b"ui", b"timeout.warn")
3073 # internal config: ui.signal-safe-lock
3073 # internal config: ui.signal-safe-lock
3074 signalsafe = self.ui.configbool(b'ui', b'signal-safe-lock')
3074 signalsafe = self.ui.configbool(b'ui', b'signal-safe-lock')
3075
3075
3076 l = lockmod.trylock(
3076 l = lockmod.trylock(
3077 self.ui,
3077 self.ui,
3078 vfs,
3078 vfs,
3079 lockname,
3079 lockname,
3080 timeout,
3080 timeout,
3081 warntimeout,
3081 warntimeout,
3082 releasefn=releasefn,
3082 releasefn=releasefn,
3083 acquirefn=acquirefn,
3083 acquirefn=acquirefn,
3084 desc=desc,
3084 desc=desc,
3085 signalsafe=signalsafe,
3085 signalsafe=signalsafe,
3086 )
3086 )
3087 return l
3087 return l
3088
3088
3089 def _afterlock(self, callback):
3089 def _afterlock(self, callback):
3090 """add a callback to be run when the repository is fully unlocked
3090 """add a callback to be run when the repository is fully unlocked
3091
3091
3092 The callback will be executed when the outermost lock is released
3092 The callback will be executed when the outermost lock is released
3093 (with wlock being higher level than 'lock')."""
3093 (with wlock being higher level than 'lock')."""
3094 for ref in (self._wlockref, self._lockref):
3094 for ref in (self._wlockref, self._lockref):
3095 l = ref and ref()
3095 l = ref and ref()
3096 if l and l.held:
3096 if l and l.held:
3097 l.postrelease.append(callback)
3097 l.postrelease.append(callback)
3098 break
3098 break
3099 else: # no lock have been found.
3099 else: # no lock have been found.
3100 callback(True)
3100 callback(True)
3101
3101
3102 def lock(self, wait=True):
3102 def lock(self, wait=True):
3103 """Lock the repository store (.hg/store) and return a weak reference
3103 """Lock the repository store (.hg/store) and return a weak reference
3104 to the lock. Use this before modifying the store (e.g. committing or
3104 to the lock. Use this before modifying the store (e.g. committing or
3105 stripping). If you are opening a transaction, get a lock as well.)
3105 stripping). If you are opening a transaction, get a lock as well.)
3106
3106
3107 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
3107 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
3108 'wlock' first to avoid a dead-lock hazard."""
3108 'wlock' first to avoid a dead-lock hazard."""
3109 l = self._currentlock(self._lockref)
3109 l = self._currentlock(self._lockref)
3110 if l is not None:
3110 if l is not None:
3111 l.lock()
3111 l.lock()
3112 return l
3112 return l
3113
3113
3114 l = self._lock(
3114 l = self._lock(
3115 vfs=self.svfs,
3115 vfs=self.svfs,
3116 lockname=b"lock",
3116 lockname=b"lock",
3117 wait=wait,
3117 wait=wait,
3118 releasefn=None,
3118 releasefn=None,
3119 acquirefn=self.invalidate,
3119 acquirefn=self.invalidate,
3120 desc=_(b'repository %s') % self.origroot,
3120 desc=_(b'repository %s') % self.origroot,
3121 )
3121 )
3122 self._lockref = weakref.ref(l)
3122 self._lockref = weakref.ref(l)
3123 return l
3123 return l
3124
3124
3125 def wlock(self, wait=True):
3125 def wlock(self, wait=True):
3126 """Lock the non-store parts of the repository (everything under
3126 """Lock the non-store parts of the repository (everything under
3127 .hg except .hg/store) and return a weak reference to the lock.
3127 .hg except .hg/store) and return a weak reference to the lock.
3128
3128
3129 Use this before modifying files in .hg.
3129 Use this before modifying files in .hg.
3130
3130
3131 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
3131 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
3132 'wlock' first to avoid a dead-lock hazard."""
3132 'wlock' first to avoid a dead-lock hazard."""
3133 l = self._wlockref() if self._wlockref else None
3133 l = self._wlockref() if self._wlockref else None
3134 if l is not None and l.held:
3134 if l is not None and l.held:
3135 l.lock()
3135 l.lock()
3136 return l
3136 return l
3137
3137
3138 # We do not need to check for non-waiting lock acquisition. Such
3138 # We do not need to check for non-waiting lock acquisition. Such
3139 # acquisition would not cause dead-lock as they would just fail.
3139 # acquisition would not cause dead-lock as they would just fail.
3140 if wait and (
3140 if wait and (
3141 self.ui.configbool(b'devel', b'all-warnings')
3141 self.ui.configbool(b'devel', b'all-warnings')
3142 or self.ui.configbool(b'devel', b'check-locks')
3142 or self.ui.configbool(b'devel', b'check-locks')
3143 ):
3143 ):
3144 if self._currentlock(self._lockref) is not None:
3144 if self._currentlock(self._lockref) is not None:
3145 self.ui.develwarn(b'"wlock" acquired after "lock"')
3145 self.ui.develwarn(b'"wlock" acquired after "lock"')
3146
3146
3147 def unlock():
3147 def unlock():
3148 if self.dirstate.is_changing_any:
3148 if self.dirstate.is_changing_any:
3149 msg = b"wlock release in the middle of a changing parents"
3149 msg = b"wlock release in the middle of a changing parents"
3150 self.ui.develwarn(msg)
3150 self.ui.develwarn(msg)
3151 self.dirstate.invalidate()
3151 self.dirstate.invalidate()
3152 else:
3152 else:
3153 if self.dirstate._dirty:
3153 if self.dirstate._dirty:
3154 msg = b"dirty dirstate on wlock release"
3154 msg = b"dirty dirstate on wlock release"
3155 self.ui.develwarn(msg)
3155 self.ui.develwarn(msg)
3156 self.dirstate.write(None)
3156 self.dirstate.write(None)
3157
3157
3158 unfi = self.unfiltered()
3158 unfi = self.unfiltered()
3159 if 'dirstate' in unfi.__dict__:
3159 if 'dirstate' in unfi.__dict__:
3160 del unfi.__dict__['dirstate']
3160 del unfi.__dict__['dirstate']
3161
3161
3162 l = self._lock(
3162 l = self._lock(
3163 self.vfs,
3163 self.vfs,
3164 b"wlock",
3164 b"wlock",
3165 wait,
3165 wait,
3166 unlock,
3166 unlock,
3167 self.invalidatedirstate,
3167 self.invalidatedirstate,
3168 _(b'working directory of %s') % self.origroot,
3168 _(b'working directory of %s') % self.origroot,
3169 )
3169 )
3170 self._wlockref = weakref.ref(l)
3170 self._wlockref = weakref.ref(l)
3171 return l
3171 return l
3172
3172
3173 def _currentlock(self, lockref):
3173 def _currentlock(self, lockref):
3174 """Returns the lock if it's held, or None if it's not."""
3174 """Returns the lock if it's held, or None if it's not."""
3175 if lockref is None:
3175 if lockref is None:
3176 return None
3176 return None
3177 l = lockref()
3177 l = lockref()
3178 if l is None or not l.held:
3178 if l is None or not l.held:
3179 return None
3179 return None
3180 return l
3180 return l
3181
3181
3182 def currentwlock(self):
3182 def currentwlock(self):
3183 """Returns the wlock if it's held, or None if it's not."""
3183 """Returns the wlock if it's held, or None if it's not."""
3184 return self._currentlock(self._wlockref)
3184 return self._currentlock(self._wlockref)
3185
3185
3186 def currentlock(self):
3186 def currentlock(self):
3187 """Returns the lock if it's held, or None if it's not."""
3187 """Returns the lock if it's held, or None if it's not."""
3188 return self._currentlock(self._lockref)
3188 return self._currentlock(self._lockref)
3189
3189
3190 def checkcommitpatterns(self, wctx, match, status, fail):
3190 def checkcommitpatterns(self, wctx, match, status, fail):
3191 """check for commit arguments that aren't committable"""
3191 """check for commit arguments that aren't committable"""
3192 if match.isexact() or match.prefix():
3192 if match.isexact() or match.prefix():
3193 matched = set(status.modified + status.added + status.removed)
3193 matched = set(status.modified + status.added + status.removed)
3194
3194
3195 for f in match.files():
3195 for f in match.files():
3196 f = self.dirstate.normalize(f)
3196 f = self.dirstate.normalize(f)
3197 if f == b'.' or f in matched or f in wctx.substate:
3197 if f == b'.' or f in matched or f in wctx.substate:
3198 continue
3198 continue
3199 if f in status.deleted:
3199 if f in status.deleted:
3200 fail(f, _(b'file not found!'))
3200 fail(f, _(b'file not found!'))
3201 # Is it a directory that exists or used to exist?
3201 # Is it a directory that exists or used to exist?
3202 if self.wvfs.isdir(f) or wctx.p1().hasdir(f):
3202 if self.wvfs.isdir(f) or wctx.p1().hasdir(f):
3203 d = f + b'/'
3203 d = f + b'/'
3204 for mf in matched:
3204 for mf in matched:
3205 if mf.startswith(d):
3205 if mf.startswith(d):
3206 break
3206 break
3207 else:
3207 else:
3208 fail(f, _(b"no match under directory!"))
3208 fail(f, _(b"no match under directory!"))
3209 elif f not in self.dirstate:
3209 elif f not in self.dirstate:
3210 fail(f, _(b"file not tracked!"))
3210 fail(f, _(b"file not tracked!"))
3211
3211
3212 @unfilteredmethod
3212 @unfilteredmethod
3213 def commit(
3213 def commit(
3214 self,
3214 self,
3215 text=b"",
3215 text=b"",
3216 user=None,
3216 user=None,
3217 date=None,
3217 date=None,
3218 match=None,
3218 match=None,
3219 force=False,
3219 force=False,
3220 editor=None,
3220 editor=None,
3221 extra=None,
3221 extra=None,
3222 ):
3222 ):
3223 """Add a new revision to current repository.
3223 """Add a new revision to current repository.
3224
3224
3225 Revision information is gathered from the working directory,
3225 Revision information is gathered from the working directory,
3226 match can be used to filter the committed files. If editor is
3226 match can be used to filter the committed files. If editor is
3227 supplied, it is called to get a commit message.
3227 supplied, it is called to get a commit message.
3228 """
3228 """
3229 if extra is None:
3229 if extra is None:
3230 extra = {}
3230 extra = {}
3231
3231
3232 def fail(f, msg):
3232 def fail(f, msg):
3233 raise error.InputError(b'%s: %s' % (f, msg))
3233 raise error.InputError(b'%s: %s' % (f, msg))
3234
3234
3235 if not match:
3235 if not match:
3236 match = matchmod.always()
3236 match = matchmod.always()
3237
3237
3238 if not force:
3238 if not force:
3239 match.bad = fail
3239 match.bad = fail
3240
3240
3241 # lock() for recent changelog (see issue4368)
3241 # lock() for recent changelog (see issue4368)
3242 with self.wlock(), self.lock():
3242 with self.wlock(), self.lock():
3243 wctx = self[None]
3243 wctx = self[None]
3244 merge = len(wctx.parents()) > 1
3244 merge = len(wctx.parents()) > 1
3245
3245
3246 if not force and merge and not match.always():
3246 if not force and merge and not match.always():
3247 raise error.Abort(
3247 raise error.Abort(
3248 _(
3248 _(
3249 b'cannot partially commit a merge '
3249 b'cannot partially commit a merge '
3250 b'(do not specify files or patterns)'
3250 b'(do not specify files or patterns)'
3251 )
3251 )
3252 )
3252 )
3253
3253
3254 status = self.status(match=match, clean=force)
3254 status = self.status(match=match, clean=force)
3255 if force:
3255 if force:
3256 status.modified.extend(
3256 status.modified.extend(
3257 status.clean
3257 status.clean
3258 ) # mq may commit clean files
3258 ) # mq may commit clean files
3259
3259
3260 # check subrepos
3260 # check subrepos
3261 subs, commitsubs, newstate = subrepoutil.precommit(
3261 subs, commitsubs, newstate = subrepoutil.precommit(
3262 self.ui, wctx, status, match, force=force
3262 self.ui, wctx, status, match, force=force
3263 )
3263 )
3264
3264
3265 # make sure all explicit patterns are matched
3265 # make sure all explicit patterns are matched
3266 if not force:
3266 if not force:
3267 self.checkcommitpatterns(wctx, match, status, fail)
3267 self.checkcommitpatterns(wctx, match, status, fail)
3268
3268
3269 cctx = context.workingcommitctx(
3269 cctx = context.workingcommitctx(
3270 self, status, text, user, date, extra
3270 self, status, text, user, date, extra
3271 )
3271 )
3272
3272
3273 ms = mergestatemod.mergestate.read(self)
3273 ms = mergestatemod.mergestate.read(self)
3274 mergeutil.checkunresolved(ms)
3274 mergeutil.checkunresolved(ms)
3275
3275
3276 # internal config: ui.allowemptycommit
3276 # internal config: ui.allowemptycommit
3277 if cctx.isempty() and not self.ui.configbool(
3277 if cctx.isempty() and not self.ui.configbool(
3278 b'ui', b'allowemptycommit'
3278 b'ui', b'allowemptycommit'
3279 ):
3279 ):
3280 self.ui.debug(b'nothing to commit, clearing merge state\n')
3280 self.ui.debug(b'nothing to commit, clearing merge state\n')
3281 ms.reset()
3281 ms.reset()
3282 return None
3282 return None
3283
3283
3284 if merge and cctx.deleted():
3284 if merge and cctx.deleted():
3285 raise error.Abort(_(b"cannot commit merge with missing files"))
3285 raise error.Abort(_(b"cannot commit merge with missing files"))
3286
3286
3287 if editor:
3287 if editor:
3288 cctx._text = editor(self, cctx, subs)
3288 cctx._text = editor(self, cctx, subs)
3289 edited = text != cctx._text
3289 edited = text != cctx._text
3290
3290
3291 # Save commit message in case this transaction gets rolled back
3291 # Save commit message in case this transaction gets rolled back
3292 # (e.g. by a pretxncommit hook). Leave the content alone on
3292 # (e.g. by a pretxncommit hook). Leave the content alone on
3293 # the assumption that the user will use the same editor again.
3293 # the assumption that the user will use the same editor again.
3294 msg_path = self.savecommitmessage(cctx._text)
3294 msg_path = self.savecommitmessage(cctx._text)
3295
3295
3296 # commit subs and write new state
3296 # commit subs and write new state
3297 if subs:
3297 if subs:
3298 uipathfn = scmutil.getuipathfn(self)
3298 uipathfn = scmutil.getuipathfn(self)
3299 for s in sorted(commitsubs):
3299 for s in sorted(commitsubs):
3300 sub = wctx.sub(s)
3300 sub = wctx.sub(s)
3301 self.ui.status(
3301 self.ui.status(
3302 _(b'committing subrepository %s\n')
3302 _(b'committing subrepository %s\n')
3303 % uipathfn(subrepoutil.subrelpath(sub))
3303 % uipathfn(subrepoutil.subrelpath(sub))
3304 )
3304 )
3305 sr = sub.commit(cctx._text, user, date)
3305 sr = sub.commit(cctx._text, user, date)
3306 newstate[s] = (newstate[s][0], sr)
3306 newstate[s] = (newstate[s][0], sr)
3307 subrepoutil.writestate(self, newstate)
3307 subrepoutil.writestate(self, newstate)
3308
3308
3309 p1, p2 = self.dirstate.parents()
3309 p1, p2 = self.dirstate.parents()
3310 hookp1, hookp2 = hex(p1), (p2 != self.nullid and hex(p2) or b'')
3310 hookp1, hookp2 = hex(p1), (p2 != self.nullid and hex(p2) or b'')
3311 try:
3311 try:
3312 self.hook(
3312 self.hook(
3313 b"precommit", throw=True, parent1=hookp1, parent2=hookp2
3313 b"precommit", throw=True, parent1=hookp1, parent2=hookp2
3314 )
3314 )
3315 with self.transaction(b'commit'):
3315 with self.transaction(b'commit'):
3316 ret = self.commitctx(cctx, True)
3316 ret = self.commitctx(cctx, True)
3317 # update bookmarks, dirstate and mergestate
3317 # update bookmarks, dirstate and mergestate
3318 bookmarks.update(self, [p1, p2], ret)
3318 bookmarks.update(self, [p1, p2], ret)
3319 cctx.markcommitted(ret)
3319 cctx.markcommitted(ret)
3320 ms.reset()
3320 ms.reset()
3321 except: # re-raises
3321 except: # re-raises
3322 if edited:
3322 if edited:
3323 self.ui.write(
3323 self.ui.write(
3324 _(b'note: commit message saved in %s\n') % msg_path
3324 _(b'note: commit message saved in %s\n') % msg_path
3325 )
3325 )
3326 self.ui.write(
3326 self.ui.write(
3327 _(
3327 _(
3328 b"note: use 'hg commit --logfile "
3328 b"note: use 'hg commit --logfile "
3329 b"%s --edit' to reuse it\n"
3329 b"%s --edit' to reuse it\n"
3330 )
3330 )
3331 % msg_path
3331 % msg_path
3332 )
3332 )
3333 raise
3333 raise
3334
3334
3335 def commithook(unused_success):
3335 def commithook(unused_success):
3336 # hack for command that use a temporary commit (eg: histedit)
3336 # hack for command that use a temporary commit (eg: histedit)
3337 # temporary commit got stripped before hook release
3337 # temporary commit got stripped before hook release
3338 if self.changelog.hasnode(ret):
3338 if self.changelog.hasnode(ret):
3339 self.hook(
3339 self.hook(
3340 b"commit", node=hex(ret), parent1=hookp1, parent2=hookp2
3340 b"commit", node=hex(ret), parent1=hookp1, parent2=hookp2
3341 )
3341 )
3342
3342
3343 self._afterlock(commithook)
3343 self._afterlock(commithook)
3344 return ret
3344 return ret
3345
3345
3346 @unfilteredmethod
3346 @unfilteredmethod
3347 def commitctx(self, ctx, error=False, origctx=None):
3347 def commitctx(self, ctx, error=False, origctx=None):
3348 return commit.commitctx(self, ctx, error=error, origctx=origctx)
3348 return commit.commitctx(self, ctx, error=error, origctx=origctx)
3349
3349
3350 @unfilteredmethod
3350 @unfilteredmethod
3351 def destroying(self):
3351 def destroying(self):
3352 """Inform the repository that nodes are about to be destroyed.
3352 """Inform the repository that nodes are about to be destroyed.
3353 Intended for use by strip and rollback, so there's a common
3353 Intended for use by strip and rollback, so there's a common
3354 place for anything that has to be done before destroying history.
3354 place for anything that has to be done before destroying history.
3355
3355
3356 This is mostly useful for saving state that is in memory and waiting
3356 This is mostly useful for saving state that is in memory and waiting
3357 to be flushed when the current lock is released. Because a call to
3357 to be flushed when the current lock is released. Because a call to
3358 destroyed is imminent, the repo will be invalidated causing those
3358 destroyed is imminent, the repo will be invalidated causing those
3359 changes to stay in memory (waiting for the next unlock), or vanish
3359 changes to stay in memory (waiting for the next unlock), or vanish
3360 completely.
3360 completely.
3361 """
3361 """
3362 # When using the same lock to commit and strip, the phasecache is left
3362 # When using the same lock to commit and strip, the phasecache is left
3363 # dirty after committing. Then when we strip, the repo is invalidated,
3363 # dirty after committing. Then when we strip, the repo is invalidated,
3364 # causing those changes to disappear.
3364 # causing those changes to disappear.
3365 if '_phasecache' in vars(self):
3365 if '_phasecache' in vars(self):
3366 self._phasecache.write()
3366 self._phasecache.write()
3367
3367
3368 @unfilteredmethod
3368 @unfilteredmethod
3369 def destroyed(self):
3369 def destroyed(self):
3370 """Inform the repository that nodes have been destroyed.
3370 """Inform the repository that nodes have been destroyed.
3371 Intended for use by strip and rollback, so there's a common
3371 Intended for use by strip and rollback, so there's a common
3372 place for anything that has to be done after destroying history.
3372 place for anything that has to be done after destroying history.
3373 """
3373 """
3374 # When one tries to:
3374 # When one tries to:
3375 # 1) destroy nodes thus calling this method (e.g. strip)
3375 # 1) destroy nodes thus calling this method (e.g. strip)
3376 # 2) use phasecache somewhere (e.g. commit)
3376 # 2) use phasecache somewhere (e.g. commit)
3377 #
3377 #
3378 # then 2) will fail because the phasecache contains nodes that were
3378 # then 2) will fail because the phasecache contains nodes that were
3379 # removed. We can either remove phasecache from the filecache,
3379 # removed. We can either remove phasecache from the filecache,
3380 # causing it to reload next time it is accessed, or simply filter
3380 # causing it to reload next time it is accessed, or simply filter
3381 # the removed nodes now and write the updated cache.
3381 # the removed nodes now and write the updated cache.
3382 self._phasecache.filterunknown(self)
3382 self._phasecache.filterunknown(self)
3383 self._phasecache.write()
3383 self._phasecache.write()
3384
3384
3385 # refresh all repository caches
3385 # refresh all repository caches
3386 self.updatecaches()
3386 self.updatecaches()
3387
3387
3388 # Ensure the persistent tag cache is updated. Doing it now
3388 # Ensure the persistent tag cache is updated. Doing it now
3389 # means that the tag cache only has to worry about destroyed
3389 # means that the tag cache only has to worry about destroyed
3390 # heads immediately after a strip/rollback. That in turn
3390 # heads immediately after a strip/rollback. That in turn
3391 # guarantees that "cachetip == currenttip" (comparing both rev
3391 # guarantees that "cachetip == currenttip" (comparing both rev
3392 # and node) always means no nodes have been added or destroyed.
3392 # and node) always means no nodes have been added or destroyed.
3393
3393
3394 # XXX this is suboptimal when qrefresh'ing: we strip the current
3394 # XXX this is suboptimal when qrefresh'ing: we strip the current
3395 # head, refresh the tag cache, then immediately add a new head.
3395 # head, refresh the tag cache, then immediately add a new head.
3396 # But I think doing it this way is necessary for the "instant
3396 # But I think doing it this way is necessary for the "instant
3397 # tag cache retrieval" case to work.
3397 # tag cache retrieval" case to work.
3398 self.invalidate()
3398 self.invalidate()
3399
3399
3400 def status(
3400 def status(
3401 self,
3401 self,
3402 node1=b'.',
3402 node1=b'.',
3403 node2=None,
3403 node2=None,
3404 match=None,
3404 match=None,
3405 ignored=False,
3405 ignored=False,
3406 clean=False,
3406 clean=False,
3407 unknown=False,
3407 unknown=False,
3408 listsubrepos=False,
3408 listsubrepos=False,
3409 ):
3409 ):
3410 '''a convenience method that calls node1.status(node2)'''
3410 '''a convenience method that calls node1.status(node2)'''
3411 return self[node1].status(
3411 return self[node1].status(
3412 node2, match, ignored, clean, unknown, listsubrepos
3412 node2, match, ignored, clean, unknown, listsubrepos
3413 )
3413 )
3414
3414
3415 def addpostdsstatus(self, ps):
3415 def addpostdsstatus(self, ps):
3416 """Add a callback to run within the wlock, at the point at which status
3416 """Add a callback to run within the wlock, at the point at which status
3417 fixups happen.
3417 fixups happen.
3418
3418
3419 On status completion, callback(wctx, status) will be called with the
3419 On status completion, callback(wctx, status) will be called with the
3420 wlock held, unless the dirstate has changed from underneath or the wlock
3420 wlock held, unless the dirstate has changed from underneath or the wlock
3421 couldn't be grabbed.
3421 couldn't be grabbed.
3422
3422
3423 Callbacks should not capture and use a cached copy of the dirstate --
3423 Callbacks should not capture and use a cached copy of the dirstate --
3424 it might change in the meanwhile. Instead, they should access the
3424 it might change in the meanwhile. Instead, they should access the
3425 dirstate via wctx.repo().dirstate.
3425 dirstate via wctx.repo().dirstate.
3426
3426
3427 This list is emptied out after each status run -- extensions should
3427 This list is emptied out after each status run -- extensions should
3428 make sure it adds to this list each time dirstate.status is called.
3428 make sure it adds to this list each time dirstate.status is called.
3429 Extensions should also make sure they don't call this for statuses
3429 Extensions should also make sure they don't call this for statuses
3430 that don't involve the dirstate.
3430 that don't involve the dirstate.
3431 """
3431 """
3432
3432
3433 # The list is located here for uniqueness reasons -- it is actually
3433 # The list is located here for uniqueness reasons -- it is actually
3434 # managed by the workingctx, but that isn't unique per-repo.
3434 # managed by the workingctx, but that isn't unique per-repo.
3435 self._postdsstatus.append(ps)
3435 self._postdsstatus.append(ps)
3436
3436
3437 def postdsstatus(self):
3437 def postdsstatus(self):
3438 """Used by workingctx to get the list of post-dirstate-status hooks."""
3438 """Used by workingctx to get the list of post-dirstate-status hooks."""
3439 return self._postdsstatus
3439 return self._postdsstatus
3440
3440
3441 def clearpostdsstatus(self):
3441 def clearpostdsstatus(self):
3442 """Used by workingctx to clear post-dirstate-status hooks."""
3442 """Used by workingctx to clear post-dirstate-status hooks."""
3443 del self._postdsstatus[:]
3443 del self._postdsstatus[:]
3444
3444
3445 def heads(self, start=None):
3445 def heads(self, start=None):
3446 if start is None:
3446 if start is None:
3447 cl = self.changelog
3447 cl = self.changelog
3448 headrevs = reversed(cl.headrevs())
3448 headrevs = reversed(cl.headrevs())
3449 return [cl.node(rev) for rev in headrevs]
3449 return [cl.node(rev) for rev in headrevs]
3450
3450
3451 heads = self.changelog.heads(start)
3451 heads = self.changelog.heads(start)
3452 # sort the output in rev descending order
3452 # sort the output in rev descending order
3453 return sorted(heads, key=self.changelog.rev, reverse=True)
3453 return sorted(heads, key=self.changelog.rev, reverse=True)
3454
3454
3455 def branchheads(self, branch=None, start=None, closed=False):
3455 def branchheads(self, branch=None, start=None, closed=False):
3456 """return a (possibly filtered) list of heads for the given branch
3456 """return a (possibly filtered) list of heads for the given branch
3457
3457
3458 Heads are returned in topological order, from newest to oldest.
3458 Heads are returned in topological order, from newest to oldest.
3459 If branch is None, use the dirstate branch.
3459 If branch is None, use the dirstate branch.
3460 If start is not None, return only heads reachable from start.
3460 If start is not None, return only heads reachable from start.
3461 If closed is True, return heads that are marked as closed as well.
3461 If closed is True, return heads that are marked as closed as well.
3462 """
3462 """
3463 if branch is None:
3463 if branch is None:
3464 branch = self[None].branch()
3464 branch = self[None].branch()
3465 branches = self.branchmap()
3465 branches = self.branchmap()
3466 if not branches.hasbranch(branch):
3466 if not branches.hasbranch(branch):
3467 return []
3467 return []
3468 # the cache returns heads ordered lowest to highest
3468 # the cache returns heads ordered lowest to highest
3469 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
3469 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
3470 if start is not None:
3470 if start is not None:
3471 # filter out the heads that cannot be reached from startrev
3471 # filter out the heads that cannot be reached from startrev
3472 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
3472 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
3473 bheads = [h for h in bheads if h in fbheads]
3473 bheads = [h for h in bheads if h in fbheads]
3474 return bheads
3474 return bheads
3475
3475
3476 def branches(self, nodes):
3476 def branches(self, nodes):
3477 if not nodes:
3477 if not nodes:
3478 nodes = [self.changelog.tip()]
3478 nodes = [self.changelog.tip()]
3479 b = []
3479 b = []
3480 for n in nodes:
3480 for n in nodes:
3481 t = n
3481 t = n
3482 while True:
3482 while True:
3483 p = self.changelog.parents(n)
3483 p = self.changelog.parents(n)
3484 if p[1] != self.nullid or p[0] == self.nullid:
3484 if p[1] != self.nullid or p[0] == self.nullid:
3485 b.append((t, n, p[0], p[1]))
3485 b.append((t, n, p[0], p[1]))
3486 break
3486 break
3487 n = p[0]
3487 n = p[0]
3488 return b
3488 return b
3489
3489
3490 def between(self, pairs):
3490 def between(self, pairs):
3491 r = []
3491 r = []
3492
3492
3493 for top, bottom in pairs:
3493 for top, bottom in pairs:
3494 n, l, i = top, [], 0
3494 n, l, i = top, [], 0
3495 f = 1
3495 f = 1
3496
3496
3497 while n != bottom and n != self.nullid:
3497 while n != bottom and n != self.nullid:
3498 p = self.changelog.parents(n)[0]
3498 p = self.changelog.parents(n)[0]
3499 if i == f:
3499 if i == f:
3500 l.append(n)
3500 l.append(n)
3501 f = f * 2
3501 f = f * 2
3502 n = p
3502 n = p
3503 i += 1
3503 i += 1
3504
3504
3505 r.append(l)
3505 r.append(l)
3506
3506
3507 return r
3507 return r
3508
3508
3509 def checkpush(self, pushop):
3509 def checkpush(self, pushop):
3510 """Extensions can override this function if additional checks have
3510 """Extensions can override this function if additional checks have
3511 to be performed before pushing, or call it if they override push
3511 to be performed before pushing, or call it if they override push
3512 command.
3512 command.
3513 """
3513 """
3514
3514
3515 @unfilteredpropertycache
3515 @unfilteredpropertycache
3516 def prepushoutgoinghooks(self):
3516 def prepushoutgoinghooks(self):
3517 """Return util.hooks consists of a pushop with repo, remote, outgoing
3517 """Return util.hooks consists of a pushop with repo, remote, outgoing
3518 methods, which are called before pushing changesets.
3518 methods, which are called before pushing changesets.
3519 """
3519 """
3520 return util.hooks()
3520 return util.hooks()
3521
3521
3522 def pushkey(self, namespace, key, old, new):
3522 def pushkey(self, namespace, key, old, new):
3523 try:
3523 try:
3524 tr = self.currenttransaction()
3524 tr = self.currenttransaction()
3525 hookargs = {}
3525 hookargs = {}
3526 if tr is not None:
3526 if tr is not None:
3527 hookargs.update(tr.hookargs)
3527 hookargs.update(tr.hookargs)
3528 hookargs = pycompat.strkwargs(hookargs)
3528 hookargs = pycompat.strkwargs(hookargs)
3529 hookargs['namespace'] = namespace
3529 hookargs['namespace'] = namespace
3530 hookargs['key'] = key
3530 hookargs['key'] = key
3531 hookargs['old'] = old
3531 hookargs['old'] = old
3532 hookargs['new'] = new
3532 hookargs['new'] = new
3533 self.hook(b'prepushkey', throw=True, **hookargs)
3533 self.hook(b'prepushkey', throw=True, **hookargs)
3534 except error.HookAbort as exc:
3534 except error.HookAbort as exc:
3535 self.ui.write_err(_(b"pushkey-abort: %s\n") % exc)
3535 self.ui.write_err(_(b"pushkey-abort: %s\n") % exc)
3536 if exc.hint:
3536 if exc.hint:
3537 self.ui.write_err(_(b"(%s)\n") % exc.hint)
3537 self.ui.write_err(_(b"(%s)\n") % exc.hint)
3538 return False
3538 return False
3539 self.ui.debug(b'pushing key for "%s:%s"\n' % (namespace, key))
3539 self.ui.debug(b'pushing key for "%s:%s"\n' % (namespace, key))
3540 ret = pushkey.push(self, namespace, key, old, new)
3540 ret = pushkey.push(self, namespace, key, old, new)
3541
3541
3542 def runhook(unused_success):
3542 def runhook(unused_success):
3543 self.hook(
3543 self.hook(
3544 b'pushkey',
3544 b'pushkey',
3545 namespace=namespace,
3545 namespace=namespace,
3546 key=key,
3546 key=key,
3547 old=old,
3547 old=old,
3548 new=new,
3548 new=new,
3549 ret=ret,
3549 ret=ret,
3550 )
3550 )
3551
3551
3552 self._afterlock(runhook)
3552 self._afterlock(runhook)
3553 return ret
3553 return ret
3554
3554
3555 def listkeys(self, namespace):
3555 def listkeys(self, namespace):
3556 self.hook(b'prelistkeys', throw=True, namespace=namespace)
3556 self.hook(b'prelistkeys', throw=True, namespace=namespace)
3557 self.ui.debug(b'listing keys for "%s"\n' % namespace)
3557 self.ui.debug(b'listing keys for "%s"\n' % namespace)
3558 values = pushkey.list(self, namespace)
3558 values = pushkey.list(self, namespace)
3559 self.hook(b'listkeys', namespace=namespace, values=values)
3559 self.hook(b'listkeys', namespace=namespace, values=values)
3560 return values
3560 return values
3561
3561
3562 def debugwireargs(self, one, two, three=None, four=None, five=None):
3562 def debugwireargs(self, one, two, three=None, four=None, five=None):
3563 '''used to test argument passing over the wire'''
3563 '''used to test argument passing over the wire'''
3564 return b"%s %s %s %s %s" % (
3564 return b"%s %s %s %s %s" % (
3565 one,
3565 one,
3566 two,
3566 two,
3567 pycompat.bytestr(three),
3567 pycompat.bytestr(three),
3568 pycompat.bytestr(four),
3568 pycompat.bytestr(four),
3569 pycompat.bytestr(five),
3569 pycompat.bytestr(five),
3570 )
3570 )
3571
3571
3572 def savecommitmessage(self, text):
3572 def savecommitmessage(self, text):
3573 fp = self.vfs(b'last-message.txt', b'wb')
3573 fp = self.vfs(b'last-message.txt', b'wb')
3574 try:
3574 try:
3575 fp.write(text)
3575 fp.write(text)
3576 finally:
3576 finally:
3577 fp.close()
3577 fp.close()
3578 return self.pathto(fp.name[len(self.root) + 1 :])
3578 return self.pathto(fp.name[len(self.root) + 1 :])
3579
3579
3580 def register_wanted_sidedata(self, category):
3580 def register_wanted_sidedata(self, category):
3581 if repository.REPO_FEATURE_SIDE_DATA not in self.features:
3581 if repository.REPO_FEATURE_SIDE_DATA not in self.features:
3582 # Only revlogv2 repos can want sidedata.
3582 # Only revlogv2 repos can want sidedata.
3583 return
3583 return
3584 self._wanted_sidedata.add(pycompat.bytestr(category))
3584 self._wanted_sidedata.add(pycompat.bytestr(category))
3585
3585
3586 def register_sidedata_computer(
3586 def register_sidedata_computer(
3587 self, kind, category, keys, computer, flags, replace=False
3587 self, kind, category, keys, computer, flags, replace=False
3588 ):
3588 ):
3589 if kind not in revlogconst.ALL_KINDS:
3589 if kind not in revlogconst.ALL_KINDS:
3590 msg = _(b"unexpected revlog kind '%s'.")
3590 msg = _(b"unexpected revlog kind '%s'.")
3591 raise error.ProgrammingError(msg % kind)
3591 raise error.ProgrammingError(msg % kind)
3592 category = pycompat.bytestr(category)
3592 category = pycompat.bytestr(category)
3593 already_registered = category in self._sidedata_computers.get(kind, [])
3593 already_registered = category in self._sidedata_computers.get(kind, [])
3594 if already_registered and not replace:
3594 if already_registered and not replace:
3595 msg = _(
3595 msg = _(
3596 b"cannot register a sidedata computer twice for category '%s'."
3596 b"cannot register a sidedata computer twice for category '%s'."
3597 )
3597 )
3598 raise error.ProgrammingError(msg % category)
3598 raise error.ProgrammingError(msg % category)
3599 if replace and not already_registered:
3599 if replace and not already_registered:
3600 msg = _(
3600 msg = _(
3601 b"cannot replace a sidedata computer that isn't registered "
3601 b"cannot replace a sidedata computer that isn't registered "
3602 b"for category '%s'."
3602 b"for category '%s'."
3603 )
3603 )
3604 raise error.ProgrammingError(msg % category)
3604 raise error.ProgrammingError(msg % category)
3605 self._sidedata_computers.setdefault(kind, {})
3605 self._sidedata_computers.setdefault(kind, {})
3606 self._sidedata_computers[kind][category] = (keys, computer, flags)
3606 self._sidedata_computers[kind][category] = (keys, computer, flags)
3607
3607
3608
3608
3609 def undoname(fn: bytes) -> bytes:
3609 def undoname(fn: bytes) -> bytes:
3610 base, name = os.path.split(fn)
3610 base, name = os.path.split(fn)
3611 assert name.startswith(b'journal')
3611 assert name.startswith(b'journal')
3612 return os.path.join(base, name.replace(b'journal', b'undo', 1))
3612 return os.path.join(base, name.replace(b'journal', b'undo', 1))
3613
3613
3614
3614
3615 def instance(ui, path: bytes, create, intents=None, createopts=None):
3615 def instance(ui, path: bytes, create, intents=None, createopts=None):
3616 # prevent cyclic import localrepo -> upgrade -> localrepo
3616 # prevent cyclic import localrepo -> upgrade -> localrepo
3617 from . import upgrade
3617 from . import upgrade
3618
3618
3619 localpath = urlutil.urllocalpath(path)
3619 localpath = urlutil.urllocalpath(path)
3620 if create:
3620 if create:
3621 createrepository(ui, localpath, createopts=createopts)
3621 createrepository(ui, localpath, createopts=createopts)
3622
3622
3623 def repo_maker():
3623 def repo_maker():
3624 return makelocalrepository(ui, localpath, intents=intents)
3624 return makelocalrepository(ui, localpath, intents=intents)
3625
3625
3626 repo = repo_maker()
3626 repo = repo_maker()
3627 repo = upgrade.may_auto_upgrade(repo, repo_maker)
3627 repo = upgrade.may_auto_upgrade(repo, repo_maker)
3628 return repo
3628 return repo
3629
3629
3630
3630
3631 def islocal(path: bytes) -> bool:
3631 def islocal(path: bytes) -> bool:
3632 return True
3632 return True
3633
3633
3634
3634
3635 def defaultcreateopts(ui, createopts=None):
3635 def defaultcreateopts(ui, createopts=None):
3636 """Populate the default creation options for a repository.
3636 """Populate the default creation options for a repository.
3637
3637
3638 A dictionary of explicitly requested creation options can be passed
3638 A dictionary of explicitly requested creation options can be passed
3639 in. Missing keys will be populated.
3639 in. Missing keys will be populated.
3640 """
3640 """
3641 createopts = dict(createopts or {})
3641 createopts = dict(createopts or {})
3642
3642
3643 if b'backend' not in createopts:
3643 if b'backend' not in createopts:
3644 # experimental config: storage.new-repo-backend
3644 # experimental config: storage.new-repo-backend
3645 createopts[b'backend'] = ui.config(b'storage', b'new-repo-backend')
3645 createopts[b'backend'] = ui.config(b'storage', b'new-repo-backend')
3646
3646
3647 return createopts
3647 return createopts
3648
3648
3649
3649
3650 def clone_requirements(ui, createopts, srcrepo):
3650 def clone_requirements(ui, createopts, srcrepo):
3651 """clone the requirements of a local repo for a local clone
3651 """clone the requirements of a local repo for a local clone
3652
3652
3653 The store requirements are unchanged while the working copy requirements
3653 The store requirements are unchanged while the working copy requirements
3654 depends on the configuration
3654 depends on the configuration
3655 """
3655 """
3656 target_requirements = set()
3656 target_requirements = set()
3657 if not srcrepo.requirements:
3657 if not srcrepo.requirements:
3658 # this is a legacy revlog "v0" repository, we cannot do anything fancy
3658 # this is a legacy revlog "v0" repository, we cannot do anything fancy
3659 # with it.
3659 # with it.
3660 return target_requirements
3660 return target_requirements
3661 createopts = defaultcreateopts(ui, createopts=createopts)
3661 createopts = defaultcreateopts(ui, createopts=createopts)
3662 for r in newreporequirements(ui, createopts):
3662 for r in newreporequirements(ui, createopts):
3663 if r in requirementsmod.WORKING_DIR_REQUIREMENTS:
3663 if r in requirementsmod.WORKING_DIR_REQUIREMENTS:
3664 target_requirements.add(r)
3664 target_requirements.add(r)
3665
3665
3666 for r in srcrepo.requirements:
3666 for r in srcrepo.requirements:
3667 if r not in requirementsmod.WORKING_DIR_REQUIREMENTS:
3667 if r not in requirementsmod.WORKING_DIR_REQUIREMENTS:
3668 target_requirements.add(r)
3668 target_requirements.add(r)
3669 return target_requirements
3669 return target_requirements
3670
3670
3671
3671
3672 def newreporequirements(ui, createopts):
3672 def newreporequirements(ui, createopts):
3673 """Determine the set of requirements for a new local repository.
3673 """Determine the set of requirements for a new local repository.
3674
3674
3675 Extensions can wrap this function to specify custom requirements for
3675 Extensions can wrap this function to specify custom requirements for
3676 new repositories.
3676 new repositories.
3677 """
3677 """
3678
3678
3679 if b'backend' not in createopts:
3679 if b'backend' not in createopts:
3680 raise error.ProgrammingError(
3680 raise error.ProgrammingError(
3681 b'backend key not present in createopts; '
3681 b'backend key not present in createopts; '
3682 b'was defaultcreateopts() called?'
3682 b'was defaultcreateopts() called?'
3683 )
3683 )
3684
3684
3685 if createopts[b'backend'] != b'revlogv1':
3685 if createopts[b'backend'] != b'revlogv1':
3686 raise error.Abort(
3686 raise error.Abort(
3687 _(
3687 _(
3688 b'unable to determine repository requirements for '
3688 b'unable to determine repository requirements for '
3689 b'storage backend: %s'
3689 b'storage backend: %s'
3690 )
3690 )
3691 % createopts[b'backend']
3691 % createopts[b'backend']
3692 )
3692 )
3693
3693
3694 requirements = {requirementsmod.REVLOGV1_REQUIREMENT}
3694 requirements = {requirementsmod.REVLOGV1_REQUIREMENT}
3695 if ui.configbool(b'format', b'usestore'):
3695 if ui.configbool(b'format', b'usestore'):
3696 requirements.add(requirementsmod.STORE_REQUIREMENT)
3696 requirements.add(requirementsmod.STORE_REQUIREMENT)
3697 if ui.configbool(b'format', b'usefncache'):
3697 if ui.configbool(b'format', b'usefncache'):
3698 requirements.add(requirementsmod.FNCACHE_REQUIREMENT)
3698 requirements.add(requirementsmod.FNCACHE_REQUIREMENT)
3699 if ui.configbool(b'format', b'dotencode'):
3699 if ui.configbool(b'format', b'dotencode'):
3700 requirements.add(requirementsmod.DOTENCODE_REQUIREMENT)
3700 requirements.add(requirementsmod.DOTENCODE_REQUIREMENT)
3701
3701
3702 compengines = ui.configlist(b'format', b'revlog-compression')
3702 compengines = ui.configlist(b'format', b'revlog-compression')
3703 for compengine in compengines:
3703 for compengine in compengines:
3704 if compengine in util.compengines:
3704 if compengine in util.compengines:
3705 engine = util.compengines[compengine]
3705 engine = util.compengines[compengine]
3706 if engine.available() and engine.revlogheader():
3706 if engine.available() and engine.revlogheader():
3707 break
3707 break
3708 else:
3708 else:
3709 raise error.Abort(
3709 raise error.Abort(
3710 _(
3710 _(
3711 b'compression engines %s defined by '
3711 b'compression engines %s defined by '
3712 b'format.revlog-compression not available'
3712 b'format.revlog-compression not available'
3713 )
3713 )
3714 % b', '.join(b'"%s"' % e for e in compengines),
3714 % b', '.join(b'"%s"' % e for e in compengines),
3715 hint=_(
3715 hint=_(
3716 b'run "hg debuginstall" to list available '
3716 b'run "hg debuginstall" to list available '
3717 b'compression engines'
3717 b'compression engines'
3718 ),
3718 ),
3719 )
3719 )
3720
3720
3721 # zlib is the historical default and doesn't need an explicit requirement.
3721 # zlib is the historical default and doesn't need an explicit requirement.
3722 if compengine == b'zstd':
3722 if compengine == b'zstd':
3723 requirements.add(b'revlog-compression-zstd')
3723 requirements.add(b'revlog-compression-zstd')
3724 elif compengine != b'zlib':
3724 elif compengine != b'zlib':
3725 requirements.add(b'exp-compression-%s' % compengine)
3725 requirements.add(b'exp-compression-%s' % compengine)
3726
3726
3727 if scmutil.gdinitconfig(ui):
3727 if scmutil.gdinitconfig(ui):
3728 requirements.add(requirementsmod.GENERALDELTA_REQUIREMENT)
3728 requirements.add(requirementsmod.GENERALDELTA_REQUIREMENT)
3729 if ui.configbool(b'format', b'sparse-revlog'):
3729 if ui.configbool(b'format', b'sparse-revlog'):
3730 requirements.add(requirementsmod.SPARSEREVLOG_REQUIREMENT)
3730 requirements.add(requirementsmod.SPARSEREVLOG_REQUIREMENT)
3731
3731
3732 # experimental config: format.use-dirstate-v2
3732 # experimental config: format.use-dirstate-v2
3733 # Keep this logic in sync with `has_dirstate_v2()` in `tests/hghave.py`
3733 # Keep this logic in sync with `has_dirstate_v2()` in `tests/hghave.py`
3734 if ui.configbool(b'format', b'use-dirstate-v2'):
3734 if ui.configbool(b'format', b'use-dirstate-v2'):
3735 requirements.add(requirementsmod.DIRSTATE_V2_REQUIREMENT)
3735 requirements.add(requirementsmod.DIRSTATE_V2_REQUIREMENT)
3736
3736
3737 # experimental config: format.exp-use-copies-side-data-changeset
3737 # experimental config: format.exp-use-copies-side-data-changeset
3738 if ui.configbool(b'format', b'exp-use-copies-side-data-changeset'):
3738 if ui.configbool(b'format', b'exp-use-copies-side-data-changeset'):
3739 requirements.add(requirementsmod.CHANGELOGV2_REQUIREMENT)
3739 requirements.add(requirementsmod.CHANGELOGV2_REQUIREMENT)
3740 requirements.add(requirementsmod.COPIESSDC_REQUIREMENT)
3740 requirements.add(requirementsmod.COPIESSDC_REQUIREMENT)
3741 if ui.configbool(b'experimental', b'treemanifest'):
3741 if ui.configbool(b'experimental', b'treemanifest'):
3742 requirements.add(requirementsmod.TREEMANIFEST_REQUIREMENT)
3742 requirements.add(requirementsmod.TREEMANIFEST_REQUIREMENT)
3743
3743
3744 changelogv2 = ui.config(b'format', b'exp-use-changelog-v2')
3744 changelogv2 = ui.config(b'format', b'exp-use-changelog-v2')
3745 if changelogv2 == b'enable-unstable-format-and-corrupt-my-data':
3745 if changelogv2 == b'enable-unstable-format-and-corrupt-my-data':
3746 requirements.add(requirementsmod.CHANGELOGV2_REQUIREMENT)
3746 requirements.add(requirementsmod.CHANGELOGV2_REQUIREMENT)
3747
3747
3748 revlogv2 = ui.config(b'experimental', b'revlogv2')
3748 revlogv2 = ui.config(b'experimental', b'revlogv2')
3749 if revlogv2 == b'enable-unstable-format-and-corrupt-my-data':
3749 if revlogv2 == b'enable-unstable-format-and-corrupt-my-data':
3750 requirements.discard(requirementsmod.REVLOGV1_REQUIREMENT)
3750 requirements.discard(requirementsmod.REVLOGV1_REQUIREMENT)
3751 requirements.add(requirementsmod.REVLOGV2_REQUIREMENT)
3751 requirements.add(requirementsmod.REVLOGV2_REQUIREMENT)
3752 # experimental config: format.internal-phase
3752 # experimental config: format.internal-phase
3753 if ui.configbool(b'format', b'use-internal-phase'):
3753 if ui.configbool(b'format', b'use-internal-phase'):
3754 requirements.add(requirementsmod.INTERNAL_PHASE_REQUIREMENT)
3754 requirements.add(requirementsmod.INTERNAL_PHASE_REQUIREMENT)
3755
3755
3756 # experimental config: format.exp-archived-phase
3756 # experimental config: format.exp-archived-phase
3757 if ui.configbool(b'format', b'exp-archived-phase'):
3757 if ui.configbool(b'format', b'exp-archived-phase'):
3758 requirements.add(requirementsmod.ARCHIVED_PHASE_REQUIREMENT)
3758 requirements.add(requirementsmod.ARCHIVED_PHASE_REQUIREMENT)
3759
3759
3760 if createopts.get(b'narrowfiles'):
3760 if createopts.get(b'narrowfiles'):
3761 requirements.add(requirementsmod.NARROW_REQUIREMENT)
3761 requirements.add(requirementsmod.NARROW_REQUIREMENT)
3762
3762
3763 if createopts.get(b'lfs'):
3763 if createopts.get(b'lfs'):
3764 requirements.add(b'lfs')
3764 requirements.add(b'lfs')
3765
3765
3766 if ui.configbool(b'format', b'bookmarks-in-store'):
3766 if ui.configbool(b'format', b'bookmarks-in-store'):
3767 requirements.add(requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT)
3767 requirements.add(requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT)
3768
3768
3769 # The feature is disabled unless a fast implementation is available.
3769 # The feature is disabled unless a fast implementation is available.
3770 persistent_nodemap_default = policy.importrust('revlog') is not None
3770 persistent_nodemap_default = policy.importrust('revlog') is not None
3771 if ui.configbool(
3771 if ui.configbool(
3772 b'format', b'use-persistent-nodemap', persistent_nodemap_default
3772 b'format', b'use-persistent-nodemap', persistent_nodemap_default
3773 ):
3773 ):
3774 requirements.add(requirementsmod.NODEMAP_REQUIREMENT)
3774 requirements.add(requirementsmod.NODEMAP_REQUIREMENT)
3775
3775
3776 # if share-safe is enabled, let's create the new repository with the new
3776 # if share-safe is enabled, let's create the new repository with the new
3777 # requirement
3777 # requirement
3778 if ui.configbool(b'format', b'use-share-safe'):
3778 if ui.configbool(b'format', b'use-share-safe'):
3779 requirements.add(requirementsmod.SHARESAFE_REQUIREMENT)
3779 requirements.add(requirementsmod.SHARESAFE_REQUIREMENT)
3780
3780
3781 # if we are creating a share-repoΒΉ we have to handle requirement
3781 # if we are creating a share-repoΒΉ we have to handle requirement
3782 # differently.
3782 # differently.
3783 #
3783 #
3784 # [1] (i.e. reusing the store from another repository, just having a
3784 # [1] (i.e. reusing the store from another repository, just having a
3785 # working copy)
3785 # working copy)
3786 if b'sharedrepo' in createopts:
3786 if b'sharedrepo' in createopts:
3787 source_requirements = set(createopts[b'sharedrepo'].requirements)
3787 source_requirements = set(createopts[b'sharedrepo'].requirements)
3788
3788
3789 if requirementsmod.SHARESAFE_REQUIREMENT not in source_requirements:
3789 if requirementsmod.SHARESAFE_REQUIREMENT not in source_requirements:
3790 # share to an old school repository, we have to copy the
3790 # share to an old school repository, we have to copy the
3791 # requirements and hope for the best.
3791 # requirements and hope for the best.
3792 requirements = source_requirements
3792 requirements = source_requirements
3793 else:
3793 else:
3794 # We have control on the working copy only, so "copy" the non
3794 # We have control on the working copy only, so "copy" the non
3795 # working copy part over, ignoring previous logic.
3795 # working copy part over, ignoring previous logic.
3796 to_drop = set()
3796 to_drop = set()
3797 for req in requirements:
3797 for req in requirements:
3798 if req in requirementsmod.WORKING_DIR_REQUIREMENTS:
3798 if req in requirementsmod.WORKING_DIR_REQUIREMENTS:
3799 continue
3799 continue
3800 if req in source_requirements:
3800 if req in source_requirements:
3801 continue
3801 continue
3802 to_drop.add(req)
3802 to_drop.add(req)
3803 requirements -= to_drop
3803 requirements -= to_drop
3804 requirements |= source_requirements
3804 requirements |= source_requirements
3805
3805
3806 if createopts.get(b'sharedrelative'):
3806 if createopts.get(b'sharedrelative'):
3807 requirements.add(requirementsmod.RELATIVE_SHARED_REQUIREMENT)
3807 requirements.add(requirementsmod.RELATIVE_SHARED_REQUIREMENT)
3808 else:
3808 else:
3809 requirements.add(requirementsmod.SHARED_REQUIREMENT)
3809 requirements.add(requirementsmod.SHARED_REQUIREMENT)
3810
3810
3811 if ui.configbool(b'format', b'use-dirstate-tracked-hint'):
3811 if ui.configbool(b'format', b'use-dirstate-tracked-hint'):
3812 version = ui.configint(b'format', b'use-dirstate-tracked-hint.version')
3812 version = ui.configint(b'format', b'use-dirstate-tracked-hint.version')
3813 msg = _(b"ignoring unknown tracked key version: %d\n")
3813 msg = _(b"ignoring unknown tracked key version: %d\n")
3814 hint = _(
3814 hint = _(
3815 b"see `hg help config.format.use-dirstate-tracked-hint-version"
3815 b"see `hg help config.format.use-dirstate-tracked-hint-version"
3816 )
3816 )
3817 if version != 1:
3817 if version != 1:
3818 ui.warn(msg % version, hint=hint)
3818 ui.warn(msg % version, hint=hint)
3819 else:
3819 else:
3820 requirements.add(requirementsmod.DIRSTATE_TRACKED_HINT_V1)
3820 requirements.add(requirementsmod.DIRSTATE_TRACKED_HINT_V1)
3821
3821
3822 return requirements
3822 return requirements
3823
3823
3824
3824
3825 def checkrequirementscompat(ui, requirements):
3825 def checkrequirementscompat(ui, requirements):
3826 """Checks compatibility of repository requirements enabled and disabled.
3826 """Checks compatibility of repository requirements enabled and disabled.
3827
3827
3828 Returns a set of requirements which needs to be dropped because dependend
3828 Returns a set of requirements which needs to be dropped because dependend
3829 requirements are not enabled. Also warns users about it"""
3829 requirements are not enabled. Also warns users about it"""
3830
3830
3831 dropped = set()
3831 dropped = set()
3832
3832
3833 if requirementsmod.STORE_REQUIREMENT not in requirements:
3833 if requirementsmod.STORE_REQUIREMENT not in requirements:
3834 if requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT in requirements:
3834 if requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT in requirements:
3835 ui.warn(
3835 ui.warn(
3836 _(
3836 _(
3837 b'ignoring enabled \'format.bookmarks-in-store\' config '
3837 b'ignoring enabled \'format.bookmarks-in-store\' config '
3838 b'beacuse it is incompatible with disabled '
3838 b'beacuse it is incompatible with disabled '
3839 b'\'format.usestore\' config\n'
3839 b'\'format.usestore\' config\n'
3840 )
3840 )
3841 )
3841 )
3842 dropped.add(requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT)
3842 dropped.add(requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT)
3843
3843
3844 if (
3844 if (
3845 requirementsmod.SHARED_REQUIREMENT in requirements
3845 requirementsmod.SHARED_REQUIREMENT in requirements
3846 or requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements
3846 or requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements
3847 ):
3847 ):
3848 raise error.Abort(
3848 raise error.Abort(
3849 _(
3849 _(
3850 b"cannot create shared repository as source was created"
3850 b"cannot create shared repository as source was created"
3851 b" with 'format.usestore' config disabled"
3851 b" with 'format.usestore' config disabled"
3852 )
3852 )
3853 )
3853 )
3854
3854
3855 if requirementsmod.SHARESAFE_REQUIREMENT in requirements:
3855 if requirementsmod.SHARESAFE_REQUIREMENT in requirements:
3856 if ui.hasconfig(b'format', b'use-share-safe'):
3856 if ui.hasconfig(b'format', b'use-share-safe'):
3857 msg = _(
3857 msg = _(
3858 b"ignoring enabled 'format.use-share-safe' config because "
3858 b"ignoring enabled 'format.use-share-safe' config because "
3859 b"it is incompatible with disabled 'format.usestore'"
3859 b"it is incompatible with disabled 'format.usestore'"
3860 b" config\n"
3860 b" config\n"
3861 )
3861 )
3862 ui.warn(msg)
3862 ui.warn(msg)
3863 dropped.add(requirementsmod.SHARESAFE_REQUIREMENT)
3863 dropped.add(requirementsmod.SHARESAFE_REQUIREMENT)
3864
3864
3865 return dropped
3865 return dropped
3866
3866
3867
3867
3868 def filterknowncreateopts(ui, createopts):
3868 def filterknowncreateopts(ui, createopts):
3869 """Filters a dict of repo creation options against options that are known.
3869 """Filters a dict of repo creation options against options that are known.
3870
3870
3871 Receives a dict of repo creation options and returns a dict of those
3871 Receives a dict of repo creation options and returns a dict of those
3872 options that we don't know how to handle.
3872 options that we don't know how to handle.
3873
3873
3874 This function is called as part of repository creation. If the
3874 This function is called as part of repository creation. If the
3875 returned dict contains any items, repository creation will not
3875 returned dict contains any items, repository creation will not
3876 be allowed, as it means there was a request to create a repository
3876 be allowed, as it means there was a request to create a repository
3877 with options not recognized by loaded code.
3877 with options not recognized by loaded code.
3878
3878
3879 Extensions can wrap this function to filter out creation options
3879 Extensions can wrap this function to filter out creation options
3880 they know how to handle.
3880 they know how to handle.
3881 """
3881 """
3882 known = {
3882 known = {
3883 b'backend',
3883 b'backend',
3884 b'lfs',
3884 b'lfs',
3885 b'narrowfiles',
3885 b'narrowfiles',
3886 b'sharedrepo',
3886 b'sharedrepo',
3887 b'sharedrelative',
3887 b'sharedrelative',
3888 b'shareditems',
3888 b'shareditems',
3889 b'shallowfilestore',
3889 b'shallowfilestore',
3890 }
3890 }
3891
3891
3892 return {k: v for k, v in createopts.items() if k not in known}
3892 return {k: v for k, v in createopts.items() if k not in known}
3893
3893
3894
3894
3895 def createrepository(ui, path: bytes, createopts=None, requirements=None):
3895 def createrepository(ui, path: bytes, createopts=None, requirements=None):
3896 """Create a new repository in a vfs.
3896 """Create a new repository in a vfs.
3897
3897
3898 ``path`` path to the new repo's working directory.
3898 ``path`` path to the new repo's working directory.
3899 ``createopts`` options for the new repository.
3899 ``createopts`` options for the new repository.
3900 ``requirement`` predefined set of requirements.
3900 ``requirement`` predefined set of requirements.
3901 (incompatible with ``createopts``)
3901 (incompatible with ``createopts``)
3902
3902
3903 The following keys for ``createopts`` are recognized:
3903 The following keys for ``createopts`` are recognized:
3904
3904
3905 backend
3905 backend
3906 The storage backend to use.
3906 The storage backend to use.
3907 lfs
3907 lfs
3908 Repository will be created with ``lfs`` requirement. The lfs extension
3908 Repository will be created with ``lfs`` requirement. The lfs extension
3909 will automatically be loaded when the repository is accessed.
3909 will automatically be loaded when the repository is accessed.
3910 narrowfiles
3910 narrowfiles
3911 Set up repository to support narrow file storage.
3911 Set up repository to support narrow file storage.
3912 sharedrepo
3912 sharedrepo
3913 Repository object from which storage should be shared.
3913 Repository object from which storage should be shared.
3914 sharedrelative
3914 sharedrelative
3915 Boolean indicating if the path to the shared repo should be
3915 Boolean indicating if the path to the shared repo should be
3916 stored as relative. By default, the pointer to the "parent" repo
3916 stored as relative. By default, the pointer to the "parent" repo
3917 is stored as an absolute path.
3917 is stored as an absolute path.
3918 shareditems
3918 shareditems
3919 Set of items to share to the new repository (in addition to storage).
3919 Set of items to share to the new repository (in addition to storage).
3920 shallowfilestore
3920 shallowfilestore
3921 Indicates that storage for files should be shallow (not all ancestor
3921 Indicates that storage for files should be shallow (not all ancestor
3922 revisions are known).
3922 revisions are known).
3923 """
3923 """
3924
3924
3925 if requirements is not None:
3925 if requirements is not None:
3926 if createopts is not None:
3926 if createopts is not None:
3927 msg = b'cannot specify both createopts and requirements'
3927 msg = b'cannot specify both createopts and requirements'
3928 raise error.ProgrammingError(msg)
3928 raise error.ProgrammingError(msg)
3929 createopts = {}
3929 createopts = {}
3930 else:
3930 else:
3931 createopts = defaultcreateopts(ui, createopts=createopts)
3931 createopts = defaultcreateopts(ui, createopts=createopts)
3932
3932
3933 unknownopts = filterknowncreateopts(ui, createopts)
3933 unknownopts = filterknowncreateopts(ui, createopts)
3934
3934
3935 if not isinstance(unknownopts, dict):
3935 if not isinstance(unknownopts, dict):
3936 raise error.ProgrammingError(
3936 raise error.ProgrammingError(
3937 b'filterknowncreateopts() did not return a dict'
3937 b'filterknowncreateopts() did not return a dict'
3938 )
3938 )
3939
3939
3940 if unknownopts:
3940 if unknownopts:
3941 raise error.Abort(
3941 raise error.Abort(
3942 _(
3942 _(
3943 b'unable to create repository because of unknown '
3943 b'unable to create repository because of unknown '
3944 b'creation option: %s'
3944 b'creation option: %s'
3945 )
3945 )
3946 % b', '.join(sorted(unknownopts)),
3946 % b', '.join(sorted(unknownopts)),
3947 hint=_(b'is a required extension not loaded?'),
3947 hint=_(b'is a required extension not loaded?'),
3948 )
3948 )
3949
3949
3950 requirements = newreporequirements(ui, createopts=createopts)
3950 requirements = newreporequirements(ui, createopts=createopts)
3951 requirements -= checkrequirementscompat(ui, requirements)
3951 requirements -= checkrequirementscompat(ui, requirements)
3952
3952
3953 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
3953 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
3954
3954
3955 hgvfs = vfsmod.vfs(wdirvfs.join(b'.hg'))
3955 hgvfs = vfsmod.vfs(wdirvfs.join(b'.hg'))
3956 if hgvfs.exists():
3956 if hgvfs.exists():
3957 raise error.RepoError(_(b'repository %s already exists') % path)
3957 raise error.RepoError(_(b'repository %s already exists') % path)
3958
3958
3959 if b'sharedrepo' in createopts:
3959 if b'sharedrepo' in createopts:
3960 sharedpath = createopts[b'sharedrepo'].sharedpath
3960 sharedpath = createopts[b'sharedrepo'].sharedpath
3961
3961
3962 if createopts.get(b'sharedrelative'):
3962 if createopts.get(b'sharedrelative'):
3963 try:
3963 try:
3964 sharedpath = os.path.relpath(sharedpath, hgvfs.base)
3964 sharedpath = os.path.relpath(sharedpath, hgvfs.base)
3965 sharedpath = util.pconvert(sharedpath)
3965 sharedpath = util.pconvert(sharedpath)
3966 except (IOError, ValueError) as e:
3966 except (IOError, ValueError) as e:
3967 # ValueError is raised on Windows if the drive letters differ
3967 # ValueError is raised on Windows if the drive letters differ
3968 # on each path.
3968 # on each path.
3969 raise error.Abort(
3969 raise error.Abort(
3970 _(b'cannot calculate relative path'),
3970 _(b'cannot calculate relative path'),
3971 hint=stringutil.forcebytestr(e),
3971 hint=stringutil.forcebytestr(e),
3972 )
3972 )
3973
3973
3974 if not wdirvfs.exists():
3974 if not wdirvfs.exists():
3975 wdirvfs.makedirs()
3975 wdirvfs.makedirs()
3976
3976
3977 hgvfs.makedir(notindexed=True)
3977 hgvfs.makedir(notindexed=True)
3978 if b'sharedrepo' not in createopts:
3978 if b'sharedrepo' not in createopts:
3979 hgvfs.mkdir(b'cache')
3979 hgvfs.mkdir(b'cache')
3980 hgvfs.mkdir(b'wcache')
3980 hgvfs.mkdir(b'wcache')
3981
3981
3982 has_store = requirementsmod.STORE_REQUIREMENT in requirements
3982 has_store = requirementsmod.STORE_REQUIREMENT in requirements
3983 if has_store and b'sharedrepo' not in createopts:
3983 if has_store and b'sharedrepo' not in createopts:
3984 hgvfs.mkdir(b'store')
3984 hgvfs.mkdir(b'store')
3985
3985
3986 # We create an invalid changelog outside the store so very old
3986 # We create an invalid changelog outside the store so very old
3987 # Mercurial versions (which didn't know about the requirements
3987 # Mercurial versions (which didn't know about the requirements
3988 # file) encounter an error on reading the changelog. This
3988 # file) encounter an error on reading the changelog. This
3989 # effectively locks out old clients and prevents them from
3989 # effectively locks out old clients and prevents them from
3990 # mucking with a repo in an unknown format.
3990 # mucking with a repo in an unknown format.
3991 #
3991 #
3992 # The revlog header has version 65535, which won't be recognized by
3992 # The revlog header has version 65535, which won't be recognized by
3993 # such old clients.
3993 # such old clients.
3994 hgvfs.append(
3994 hgvfs.append(
3995 b'00changelog.i',
3995 b'00changelog.i',
3996 b'\0\0\xFF\xFF dummy changelog to prevent using the old repo '
3996 b'\0\0\xFF\xFF dummy changelog to prevent using the old repo '
3997 b'layout',
3997 b'layout',
3998 )
3998 )
3999
3999
4000 # Filter the requirements into working copy and store ones
4000 # Filter the requirements into working copy and store ones
4001 wcreq, storereq = scmutil.filterrequirements(requirements)
4001 wcreq, storereq = scmutil.filterrequirements(requirements)
4002 # write working copy ones
4002 # write working copy ones
4003 scmutil.writerequires(hgvfs, wcreq)
4003 scmutil.writerequires(hgvfs, wcreq)
4004 # If there are store requirements and the current repository
4004 # If there are store requirements and the current repository
4005 # is not a shared one, write stored requirements
4005 # is not a shared one, write stored requirements
4006 # For new shared repository, we don't need to write the store
4006 # For new shared repository, we don't need to write the store
4007 # requirements as they are already present in store requires
4007 # requirements as they are already present in store requires
4008 if storereq and b'sharedrepo' not in createopts:
4008 if storereq and b'sharedrepo' not in createopts:
4009 storevfs = vfsmod.vfs(hgvfs.join(b'store'), cacheaudited=True)
4009 storevfs = vfsmod.vfs(hgvfs.join(b'store'), cacheaudited=True)
4010 scmutil.writerequires(storevfs, storereq)
4010 scmutil.writerequires(storevfs, storereq)
4011
4011
4012 # Write out file telling readers where to find the shared store.
4012 # Write out file telling readers where to find the shared store.
4013 if b'sharedrepo' in createopts:
4013 if b'sharedrepo' in createopts:
4014 hgvfs.write(b'sharedpath', sharedpath)
4014 hgvfs.write(b'sharedpath', sharedpath)
4015
4015
4016 if createopts.get(b'shareditems'):
4016 if createopts.get(b'shareditems'):
4017 shared = b'\n'.join(sorted(createopts[b'shareditems'])) + b'\n'
4017 shared = b'\n'.join(sorted(createopts[b'shareditems'])) + b'\n'
4018 hgvfs.write(b'shared', shared)
4018 hgvfs.write(b'shared', shared)
4019
4019
4020
4020
4021 def poisonrepository(repo):
4021 def poisonrepository(repo):
4022 """Poison a repository instance so it can no longer be used."""
4022 """Poison a repository instance so it can no longer be used."""
4023 # Perform any cleanup on the instance.
4023 # Perform any cleanup on the instance.
4024 repo.close()
4024 repo.close()
4025
4025
4026 # Our strategy is to replace the type of the object with one that
4026 # Our strategy is to replace the type of the object with one that
4027 # has all attribute lookups result in error.
4027 # has all attribute lookups result in error.
4028 #
4028 #
4029 # But we have to allow the close() method because some constructors
4029 # But we have to allow the close() method because some constructors
4030 # of repos call close() on repo references.
4030 # of repos call close() on repo references.
4031 class poisonedrepository:
4031 class poisonedrepository:
4032 def __getattribute__(self, item):
4032 def __getattribute__(self, item):
4033 if item == 'close':
4033 if item == 'close':
4034 return object.__getattribute__(self, item)
4034 return object.__getattribute__(self, item)
4035
4035
4036 raise error.ProgrammingError(
4036 raise error.ProgrammingError(
4037 b'repo instances should not be used after unshare'
4037 b'repo instances should not be used after unshare'
4038 )
4038 )
4039
4039
4040 def close(self):
4040 def close(self):
4041 pass
4041 pass
4042
4042
4043 # We may have a repoview, which intercepts __setattr__. So be sure
4043 # We may have a repoview, which intercepts __setattr__. So be sure
4044 # we operate at the lowest level possible.
4044 # we operate at the lowest level possible.
4045 object.__setattr__(repo, '__class__', poisonedrepository)
4045 object.__setattr__(repo, '__class__', poisonedrepository)
@@ -1,3734 +1,3732 b''
1 # revlog.py - storage back-end for mercurial
1 # revlog.py - storage back-end for mercurial
2 # coding: utf8
2 # coding: utf8
3 #
3 #
4 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
4 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.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 """Storage back-end for Mercurial.
9 """Storage back-end for Mercurial.
10
10
11 This provides efficient delta storage with O(1) retrieve and append
11 This provides efficient delta storage with O(1) retrieve and append
12 and O(changes) merge between branches.
12 and O(changes) merge between branches.
13 """
13 """
14
14
15
15
16 import binascii
16 import binascii
17 import collections
17 import collections
18 import contextlib
18 import contextlib
19 import io
19 import io
20 import os
20 import os
21 import struct
21 import struct
22 import weakref
22 import weakref
23 import zlib
23 import zlib
24
24
25 # import stuff from node for others to import from revlog
25 # import stuff from node for others to import from revlog
26 from .node import (
26 from .node import (
27 bin,
27 bin,
28 hex,
28 hex,
29 nullrev,
29 nullrev,
30 sha1nodeconstants,
30 sha1nodeconstants,
31 short,
31 short,
32 wdirrev,
32 wdirrev,
33 )
33 )
34 from .i18n import _
34 from .i18n import _
35 from .revlogutils.constants import (
35 from .revlogutils.constants import (
36 ALL_KINDS,
36 ALL_KINDS,
37 CHANGELOGV2,
37 CHANGELOGV2,
38 COMP_MODE_DEFAULT,
38 COMP_MODE_DEFAULT,
39 COMP_MODE_INLINE,
39 COMP_MODE_INLINE,
40 COMP_MODE_PLAIN,
40 COMP_MODE_PLAIN,
41 DELTA_BASE_REUSE_NO,
41 DELTA_BASE_REUSE_NO,
42 DELTA_BASE_REUSE_TRY,
42 DELTA_BASE_REUSE_TRY,
43 ENTRY_RANK,
43 ENTRY_RANK,
44 FEATURES_BY_VERSION,
44 FEATURES_BY_VERSION,
45 FLAG_GENERALDELTA,
45 FLAG_GENERALDELTA,
46 FLAG_INLINE_DATA,
46 FLAG_INLINE_DATA,
47 INDEX_HEADER,
47 INDEX_HEADER,
48 KIND_CHANGELOG,
48 KIND_CHANGELOG,
49 KIND_FILELOG,
49 KIND_FILELOG,
50 RANK_UNKNOWN,
50 RANK_UNKNOWN,
51 REVLOGV0,
51 REVLOGV0,
52 REVLOGV1,
52 REVLOGV1,
53 REVLOGV1_FLAGS,
53 REVLOGV1_FLAGS,
54 REVLOGV2,
54 REVLOGV2,
55 REVLOGV2_FLAGS,
55 REVLOGV2_FLAGS,
56 REVLOG_DEFAULT_FLAGS,
56 REVLOG_DEFAULT_FLAGS,
57 REVLOG_DEFAULT_FORMAT,
57 REVLOG_DEFAULT_FORMAT,
58 REVLOG_DEFAULT_VERSION,
58 REVLOG_DEFAULT_VERSION,
59 SUPPORTED_FLAGS,
59 SUPPORTED_FLAGS,
60 )
60 )
61 from .revlogutils.flagutil import (
61 from .revlogutils.flagutil import (
62 REVIDX_DEFAULT_FLAGS,
62 REVIDX_DEFAULT_FLAGS,
63 REVIDX_ELLIPSIS,
63 REVIDX_ELLIPSIS,
64 REVIDX_EXTSTORED,
64 REVIDX_EXTSTORED,
65 REVIDX_FLAGS_ORDER,
65 REVIDX_FLAGS_ORDER,
66 REVIDX_HASCOPIESINFO,
66 REVIDX_HASCOPIESINFO,
67 REVIDX_ISCENSORED,
67 REVIDX_ISCENSORED,
68 REVIDX_RAWTEXT_CHANGING_FLAGS,
68 REVIDX_RAWTEXT_CHANGING_FLAGS,
69 )
69 )
70 from .thirdparty import attr
70 from .thirdparty import attr
71 from . import (
71 from . import (
72 ancestor,
72 ancestor,
73 dagop,
73 dagop,
74 error,
74 error,
75 mdiff,
75 mdiff,
76 policy,
76 policy,
77 pycompat,
77 pycompat,
78 revlogutils,
78 revlogutils,
79 templatefilters,
79 templatefilters,
80 util,
80 util,
81 )
81 )
82 from .interfaces import (
82 from .interfaces import (
83 repository,
83 repository,
84 util as interfaceutil,
84 util as interfaceutil,
85 )
85 )
86 from .revlogutils import (
86 from .revlogutils import (
87 deltas as deltautil,
87 deltas as deltautil,
88 docket as docketutil,
88 docket as docketutil,
89 flagutil,
89 flagutil,
90 nodemap as nodemaputil,
90 nodemap as nodemaputil,
91 randomaccessfile,
91 randomaccessfile,
92 revlogv0,
92 revlogv0,
93 rewrite,
93 rewrite,
94 sidedata as sidedatautil,
94 sidedata as sidedatautil,
95 )
95 )
96 from .utils import (
96 from .utils import (
97 storageutil,
97 storageutil,
98 stringutil,
98 stringutil,
99 )
99 )
100
100
101 # blanked usage of all the name to prevent pyflakes constraints
101 # blanked usage of all the name to prevent pyflakes constraints
102 # We need these name available in the module for extensions.
102 # We need these name available in the module for extensions.
103
103
104 REVLOGV0
104 REVLOGV0
105 REVLOGV1
105 REVLOGV1
106 REVLOGV2
106 REVLOGV2
107 CHANGELOGV2
107 CHANGELOGV2
108 FLAG_INLINE_DATA
108 FLAG_INLINE_DATA
109 FLAG_GENERALDELTA
109 FLAG_GENERALDELTA
110 REVLOG_DEFAULT_FLAGS
110 REVLOG_DEFAULT_FLAGS
111 REVLOG_DEFAULT_FORMAT
111 REVLOG_DEFAULT_FORMAT
112 REVLOG_DEFAULT_VERSION
112 REVLOG_DEFAULT_VERSION
113 REVLOGV1_FLAGS
113 REVLOGV1_FLAGS
114 REVLOGV2_FLAGS
114 REVLOGV2_FLAGS
115 REVIDX_ISCENSORED
115 REVIDX_ISCENSORED
116 REVIDX_ELLIPSIS
116 REVIDX_ELLIPSIS
117 REVIDX_HASCOPIESINFO
117 REVIDX_HASCOPIESINFO
118 REVIDX_EXTSTORED
118 REVIDX_EXTSTORED
119 REVIDX_DEFAULT_FLAGS
119 REVIDX_DEFAULT_FLAGS
120 REVIDX_FLAGS_ORDER
120 REVIDX_FLAGS_ORDER
121 REVIDX_RAWTEXT_CHANGING_FLAGS
121 REVIDX_RAWTEXT_CHANGING_FLAGS
122
122
123 parsers = policy.importmod('parsers')
123 parsers = policy.importmod('parsers')
124 rustancestor = policy.importrust('ancestor')
124 rustancestor = policy.importrust('ancestor')
125 rustdagop = policy.importrust('dagop')
125 rustdagop = policy.importrust('dagop')
126 rustrevlog = policy.importrust('revlog')
126 rustrevlog = policy.importrust('revlog')
127
127
128 # Aliased for performance.
128 # Aliased for performance.
129 _zlibdecompress = zlib.decompress
129 _zlibdecompress = zlib.decompress
130
130
131 # max size of inline data embedded into a revlog
131 # max size of inline data embedded into a revlog
132 _maxinline = 131072
132 _maxinline = 131072
133
133
134 # Flag processors for REVIDX_ELLIPSIS.
134 # Flag processors for REVIDX_ELLIPSIS.
135 def ellipsisreadprocessor(rl, text):
135 def ellipsisreadprocessor(rl, text):
136 return text, False
136 return text, False
137
137
138
138
139 def ellipsiswriteprocessor(rl, text):
139 def ellipsiswriteprocessor(rl, text):
140 return text, False
140 return text, False
141
141
142
142
143 def ellipsisrawprocessor(rl, text):
143 def ellipsisrawprocessor(rl, text):
144 return False
144 return False
145
145
146
146
147 ellipsisprocessor = (
147 ellipsisprocessor = (
148 ellipsisreadprocessor,
148 ellipsisreadprocessor,
149 ellipsiswriteprocessor,
149 ellipsiswriteprocessor,
150 ellipsisrawprocessor,
150 ellipsisrawprocessor,
151 )
151 )
152
152
153
153
154 def _verify_revision(rl, skipflags, state, node):
154 def _verify_revision(rl, skipflags, state, node):
155 """Verify the integrity of the given revlog ``node`` while providing a hook
155 """Verify the integrity of the given revlog ``node`` while providing a hook
156 point for extensions to influence the operation."""
156 point for extensions to influence the operation."""
157 if skipflags:
157 if skipflags:
158 state[b'skipread'].add(node)
158 state[b'skipread'].add(node)
159 else:
159 else:
160 # Side-effect: read content and verify hash.
160 # Side-effect: read content and verify hash.
161 rl.revision(node)
161 rl.revision(node)
162
162
163
163
164 # True if a fast implementation for persistent-nodemap is available
164 # True if a fast implementation for persistent-nodemap is available
165 #
165 #
166 # We also consider we have a "fast" implementation in "pure" python because
166 # We also consider we have a "fast" implementation in "pure" python because
167 # people using pure don't really have performance consideration (and a
167 # people using pure don't really have performance consideration (and a
168 # wheelbarrow of other slowness source)
168 # wheelbarrow of other slowness source)
169 HAS_FAST_PERSISTENT_NODEMAP = rustrevlog is not None or hasattr(
169 HAS_FAST_PERSISTENT_NODEMAP = rustrevlog is not None or hasattr(
170 parsers, 'BaseIndexObject'
170 parsers, 'BaseIndexObject'
171 )
171 )
172
172
173
173
174 @interfaceutil.implementer(repository.irevisiondelta)
174 @interfaceutil.implementer(repository.irevisiondelta)
175 @attr.s(slots=True)
175 @attr.s(slots=True)
176 class revlogrevisiondelta:
176 class revlogrevisiondelta:
177 node = attr.ib()
177 node = attr.ib()
178 p1node = attr.ib()
178 p1node = attr.ib()
179 p2node = attr.ib()
179 p2node = attr.ib()
180 basenode = attr.ib()
180 basenode = attr.ib()
181 flags = attr.ib()
181 flags = attr.ib()
182 baserevisionsize = attr.ib()
182 baserevisionsize = attr.ib()
183 revision = attr.ib()
183 revision = attr.ib()
184 delta = attr.ib()
184 delta = attr.ib()
185 sidedata = attr.ib()
185 sidedata = attr.ib()
186 protocol_flags = attr.ib()
186 protocol_flags = attr.ib()
187 linknode = attr.ib(default=None)
187 linknode = attr.ib(default=None)
188
188
189
189
190 @interfaceutil.implementer(repository.iverifyproblem)
190 @interfaceutil.implementer(repository.iverifyproblem)
191 @attr.s(frozen=True)
191 @attr.s(frozen=True)
192 class revlogproblem:
192 class revlogproblem:
193 warning = attr.ib(default=None)
193 warning = attr.ib(default=None)
194 error = attr.ib(default=None)
194 error = attr.ib(default=None)
195 node = attr.ib(default=None)
195 node = attr.ib(default=None)
196
196
197
197
198 def parse_index_v1(data, inline):
198 def parse_index_v1(data, inline):
199 # call the C implementation to parse the index data
199 # call the C implementation to parse the index data
200 index, cache = parsers.parse_index2(data, inline)
200 index, cache = parsers.parse_index2(data, inline)
201 return index, cache
201 return index, cache
202
202
203
203
204 def parse_index_v2(data, inline):
204 def parse_index_v2(data, inline):
205 # call the C implementation to parse the index data
205 # call the C implementation to parse the index data
206 index, cache = parsers.parse_index2(data, inline, format=REVLOGV2)
206 index, cache = parsers.parse_index2(data, inline, format=REVLOGV2)
207 return index, cache
207 return index, cache
208
208
209
209
210 def parse_index_cl_v2(data, inline):
210 def parse_index_cl_v2(data, inline):
211 # call the C implementation to parse the index data
211 # call the C implementation to parse the index data
212 index, cache = parsers.parse_index2(data, inline, format=CHANGELOGV2)
212 index, cache = parsers.parse_index2(data, inline, format=CHANGELOGV2)
213 return index, cache
213 return index, cache
214
214
215
215
216 if hasattr(parsers, 'parse_index_devel_nodemap'):
216 if hasattr(parsers, 'parse_index_devel_nodemap'):
217
217
218 def parse_index_v1_nodemap(data, inline):
218 def parse_index_v1_nodemap(data, inline):
219 index, cache = parsers.parse_index_devel_nodemap(data, inline)
219 index, cache = parsers.parse_index_devel_nodemap(data, inline)
220 return index, cache
220 return index, cache
221
221
222
222
223 else:
223 else:
224 parse_index_v1_nodemap = None
224 parse_index_v1_nodemap = None
225
225
226
226
227 def parse_index_v1_mixed(data, inline):
227 def parse_index_v1_mixed(data, inline):
228 index, cache = parse_index_v1(data, inline)
228 index, cache = parse_index_v1(data, inline)
229 return rustrevlog.MixedIndex(index), cache
229 return rustrevlog.MixedIndex(index), cache
230
230
231
231
232 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
232 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
233 # signed integer)
233 # signed integer)
234 _maxentrysize = 0x7FFFFFFF
234 _maxentrysize = 0x7FFFFFFF
235
235
236 FILE_TOO_SHORT_MSG = _(
236 FILE_TOO_SHORT_MSG = _(
237 b'cannot read from revlog %s;'
237 b'cannot read from revlog %s;'
238 b' expected %d bytes from offset %d, data size is %d'
238 b' expected %d bytes from offset %d, data size is %d'
239 )
239 )
240
240
241 hexdigits = b'0123456789abcdefABCDEF'
241 hexdigits = b'0123456789abcdefABCDEF'
242
242
243
243
244 class _Config:
244 class _Config:
245 def copy(self):
245 def copy(self):
246 return self.__class__(**self.__dict__)
246 return self.__class__(**self.__dict__)
247
247
248
248
249 @attr.s()
249 @attr.s()
250 class FeatureConfig(_Config):
250 class FeatureConfig(_Config):
251 """Hold configuration values about the available revlog features"""
251 """Hold configuration values about the available revlog features"""
252
252
253 # the default compression engine
253 # the default compression engine
254 compression_engine = attr.ib(default=b'zlib')
254 compression_engine = attr.ib(default=b'zlib')
255 # compression engines options
255 # compression engines options
256 compression_engine_options = attr.ib(default=attr.Factory(dict))
256 compression_engine_options = attr.ib(default=attr.Factory(dict))
257
257
258 # can we use censor on this revlog
258 # can we use censor on this revlog
259 censorable = attr.ib(default=False)
259 censorable = attr.ib(default=False)
260 # does this revlog use the "side data" feature
260 # does this revlog use the "side data" feature
261 has_side_data = attr.ib(default=False)
261 has_side_data = attr.ib(default=False)
262 # might remove rank configuration once the computation has no impact
262 # might remove rank configuration once the computation has no impact
263 compute_rank = attr.ib(default=False)
263 compute_rank = attr.ib(default=False)
264 # parent order is supposed to be semantically irrelevant, so we
264 # parent order is supposed to be semantically irrelevant, so we
265 # normally resort parents to ensure that the first parent is non-null,
265 # normally resort parents to ensure that the first parent is non-null,
266 # if there is a non-null parent at all.
266 # if there is a non-null parent at all.
267 # filelog abuses the parent order as flag to mark some instances of
267 # filelog abuses the parent order as flag to mark some instances of
268 # meta-encoded files, so allow it to disable this behavior.
268 # meta-encoded files, so allow it to disable this behavior.
269 canonical_parent_order = attr.ib(default=False)
269 canonical_parent_order = attr.ib(default=False)
270 # can ellipsis commit be used
270 # can ellipsis commit be used
271 enable_ellipsis = attr.ib(default=False)
271 enable_ellipsis = attr.ib(default=False)
272
272
273 def copy(self):
273 def copy(self):
274 new = super().copy()
274 new = super().copy()
275 new.compression_engine_options = self.compression_engine_options.copy()
275 new.compression_engine_options = self.compression_engine_options.copy()
276 return new
276 return new
277
277
278
278
279 @attr.s()
279 @attr.s()
280 class DataConfig(_Config):
280 class DataConfig(_Config):
281 """Hold configuration value about how the revlog data are read"""
281 """Hold configuration value about how the revlog data are read"""
282
282
283 # should we try to open the "pending" version of the revlog
283 # should we try to open the "pending" version of the revlog
284 try_pending = attr.ib(default=False)
284 try_pending = attr.ib(default=False)
285 # should we try to open the "splitted" version of the revlog
285 # should we try to open the "splitted" version of the revlog
286 try_split = attr.ib(default=False)
286 try_split = attr.ib(default=False)
287 # When True, indexfile should be opened with checkambig=True at writing,
287 # When True, indexfile should be opened with checkambig=True at writing,
288 # to avoid file stat ambiguity.
288 # to avoid file stat ambiguity.
289 check_ambig = attr.ib(default=False)
289 check_ambig = attr.ib(default=False)
290
290
291 # If true, use mmap instead of reading to deal with large index
291 # If true, use mmap instead of reading to deal with large index
292 mmap_large_index = attr.ib(default=False)
292 mmap_large_index = attr.ib(default=False)
293 # how much data is large
293 # how much data is large
294 mmap_index_threshold = attr.ib(default=None)
294 mmap_index_threshold = attr.ib(default=None)
295 # How much data to read and cache into the raw revlog data cache.
295 # How much data to read and cache into the raw revlog data cache.
296 chunk_cache_size = attr.ib(default=65536)
296 chunk_cache_size = attr.ib(default=65536)
297
297
298 # Allow sparse reading of the revlog data
298 # Allow sparse reading of the revlog data
299 with_sparse_read = attr.ib(default=False)
299 with_sparse_read = attr.ib(default=False)
300 # minimal density of a sparse read chunk
300 # minimal density of a sparse read chunk
301 sr_density_threshold = attr.ib(default=0.50)
301 sr_density_threshold = attr.ib(default=0.50)
302 # minimal size of data we skip when performing sparse read
302 # minimal size of data we skip when performing sparse read
303 sr_min_gap_size = attr.ib(default=262144)
303 sr_min_gap_size = attr.ib(default=262144)
304
304
305 # are delta encoded against arbitrary bases.
305 # are delta encoded against arbitrary bases.
306 generaldelta = attr.ib(default=False)
306 generaldelta = attr.ib(default=False)
307
307
308
308
309 @attr.s()
309 @attr.s()
310 class DeltaConfig(_Config):
310 class DeltaConfig(_Config):
311 """Hold configuration value about how new delta are computed
311 """Hold configuration value about how new delta are computed
312
312
313 Some attributes are duplicated from DataConfig to help havign each object
313 Some attributes are duplicated from DataConfig to help havign each object
314 self contained.
314 self contained.
315 """
315 """
316
316
317 # can delta be encoded against arbitrary bases.
317 # can delta be encoded against arbitrary bases.
318 general_delta = attr.ib(default=False)
318 general_delta = attr.ib(default=False)
319 # Allow sparse writing of the revlog data
319 # Allow sparse writing of the revlog data
320 sparse_revlog = attr.ib(default=False)
320 sparse_revlog = attr.ib(default=False)
321 # maximum length of a delta chain
321 # maximum length of a delta chain
322 max_chain_len = attr.ib(default=None)
322 max_chain_len = attr.ib(default=None)
323 # Maximum distance between delta chain base start and end
323 # Maximum distance between delta chain base start and end
324 max_deltachain_span = attr.ib(default=-1)
324 max_deltachain_span = attr.ib(default=-1)
325 # If `upper_bound_comp` is not None, this is the expected maximal gain from
325 # If `upper_bound_comp` is not None, this is the expected maximal gain from
326 # compression for the data content.
326 # compression for the data content.
327 upper_bound_comp = attr.ib(default=None)
327 upper_bound_comp = attr.ib(default=None)
328 # Should we try a delta against both parent
328 # Should we try a delta against both parent
329 delta_both_parents = attr.ib(default=True)
329 delta_both_parents = attr.ib(default=True)
330 # Test delta base candidate group by chunk of this maximal size.
330 # Test delta base candidate group by chunk of this maximal size.
331 candidate_group_chunk_size = attr.ib(default=0)
331 candidate_group_chunk_size = attr.ib(default=0)
332 # Should we display debug information about delta computation
332 # Should we display debug information about delta computation
333 debug_delta = attr.ib(default=False)
333 debug_delta = attr.ib(default=False)
334 # trust incoming delta by default
334 # trust incoming delta by default
335 lazy_delta = attr.ib(default=True)
335 lazy_delta = attr.ib(default=True)
336 # trust the base of incoming delta by default
336 # trust the base of incoming delta by default
337 lazy_delta_base = attr.ib(default=False)
337 lazy_delta_base = attr.ib(default=False)
338
338
339
339
340 class revlog:
340 class revlog:
341 """
341 """
342 the underlying revision storage object
342 the underlying revision storage object
343
343
344 A revlog consists of two parts, an index and the revision data.
344 A revlog consists of two parts, an index and the revision data.
345
345
346 The index is a file with a fixed record size containing
346 The index is a file with a fixed record size containing
347 information on each revision, including its nodeid (hash), the
347 information on each revision, including its nodeid (hash), the
348 nodeids of its parents, the position and offset of its data within
348 nodeids of its parents, the position and offset of its data within
349 the data file, and the revision it's based on. Finally, each entry
349 the data file, and the revision it's based on. Finally, each entry
350 contains a linkrev entry that can serve as a pointer to external
350 contains a linkrev entry that can serve as a pointer to external
351 data.
351 data.
352
352
353 The revision data itself is a linear collection of data chunks.
353 The revision data itself is a linear collection of data chunks.
354 Each chunk represents a revision and is usually represented as a
354 Each chunk represents a revision and is usually represented as a
355 delta against the previous chunk. To bound lookup time, runs of
355 delta against the previous chunk. To bound lookup time, runs of
356 deltas are limited to about 2 times the length of the original
356 deltas are limited to about 2 times the length of the original
357 version data. This makes retrieval of a version proportional to
357 version data. This makes retrieval of a version proportional to
358 its size, or O(1) relative to the number of revisions.
358 its size, or O(1) relative to the number of revisions.
359
359
360 Both pieces of the revlog are written to in an append-only
360 Both pieces of the revlog are written to in an append-only
361 fashion, which means we never need to rewrite a file to insert or
361 fashion, which means we never need to rewrite a file to insert or
362 remove data, and can use some simple techniques to avoid the need
362 remove data, and can use some simple techniques to avoid the need
363 for locking while reading.
363 for locking while reading.
364
364
365 If checkambig, indexfile is opened with checkambig=True at
365 If checkambig, indexfile is opened with checkambig=True at
366 writing, to avoid file stat ambiguity.
366 writing, to avoid file stat ambiguity.
367
367
368 If mmaplargeindex is True, and an mmapindexthreshold is set, the
368 If mmaplargeindex is True, and an mmapindexthreshold is set, the
369 index will be mmapped rather than read if it is larger than the
369 index will be mmapped rather than read if it is larger than the
370 configured threshold.
370 configured threshold.
371
371
372 If censorable is True, the revlog can have censored revisions.
372 If censorable is True, the revlog can have censored revisions.
373
373
374 If `upperboundcomp` is not None, this is the expected maximal gain from
374 If `upperboundcomp` is not None, this is the expected maximal gain from
375 compression for the data content.
375 compression for the data content.
376
376
377 `concurrencychecker` is an optional function that receives 3 arguments: a
377 `concurrencychecker` is an optional function that receives 3 arguments: a
378 file handle, a filename, and an expected position. It should check whether
378 file handle, a filename, and an expected position. It should check whether
379 the current position in the file handle is valid, and log/warn/fail (by
379 the current position in the file handle is valid, and log/warn/fail (by
380 raising).
380 raising).
381
381
382 See mercurial/revlogutils/contants.py for details about the content of an
382 See mercurial/revlogutils/contants.py for details about the content of an
383 index entry.
383 index entry.
384 """
384 """
385
385
386 _flagserrorclass = error.RevlogError
386 _flagserrorclass = error.RevlogError
387
387
388 @staticmethod
388 @staticmethod
389 def is_inline_index(header_bytes):
389 def is_inline_index(header_bytes):
390 """Determine if a revlog is inline from the initial bytes of the index"""
390 """Determine if a revlog is inline from the initial bytes of the index"""
391 header = INDEX_HEADER.unpack(header_bytes)[0]
391 header = INDEX_HEADER.unpack(header_bytes)[0]
392
392
393 _format_flags = header & ~0xFFFF
393 _format_flags = header & ~0xFFFF
394 _format_version = header & 0xFFFF
394 _format_version = header & 0xFFFF
395
395
396 features = FEATURES_BY_VERSION[_format_version]
396 features = FEATURES_BY_VERSION[_format_version]
397 return features[b'inline'](_format_flags)
397 return features[b'inline'](_format_flags)
398
398
399 def __init__(
399 def __init__(
400 self,
400 self,
401 opener,
401 opener,
402 target,
402 target,
403 radix,
403 radix,
404 postfix=None, # only exist for `tmpcensored` now
404 postfix=None, # only exist for `tmpcensored` now
405 checkambig=False,
405 checkambig=False,
406 mmaplargeindex=False,
406 mmaplargeindex=False,
407 censorable=False,
407 censorable=False,
408 upperboundcomp=None,
408 upperboundcomp=None,
409 persistentnodemap=False,
409 persistentnodemap=False,
410 concurrencychecker=None,
410 concurrencychecker=None,
411 trypending=False,
411 trypending=False,
412 try_split=False,
412 try_split=False,
413 canonical_parent_order=True,
413 canonical_parent_order=True,
414 ):
414 ):
415 """
415 """
416 create a revlog object
416 create a revlog object
417
417
418 opener is a function that abstracts the file opening operation
418 opener is a function that abstracts the file opening operation
419 and can be used to implement COW semantics or the like.
419 and can be used to implement COW semantics or the like.
420
420
421 `target`: a (KIND, ID) tuple that identify the content stored in
421 `target`: a (KIND, ID) tuple that identify the content stored in
422 this revlog. It help the rest of the code to understand what the revlog
422 this revlog. It help the rest of the code to understand what the revlog
423 is about without having to resort to heuristic and index filename
423 is about without having to resort to heuristic and index filename
424 analysis. Note: that this must be reliably be set by normal code, but
424 analysis. Note: that this must be reliably be set by normal code, but
425 that test, debug, or performance measurement code might not set this to
425 that test, debug, or performance measurement code might not set this to
426 accurate value.
426 accurate value.
427 """
427 """
428 self.upperboundcomp = upperboundcomp
428 self.upperboundcomp = upperboundcomp
429
429
430 self.radix = radix
430 self.radix = radix
431
431
432 self._docket_file = None
432 self._docket_file = None
433 self._indexfile = None
433 self._indexfile = None
434 self._datafile = None
434 self._datafile = None
435 self._sidedatafile = None
435 self._sidedatafile = None
436 self._nodemap_file = None
436 self._nodemap_file = None
437 self.postfix = postfix
437 self.postfix = postfix
438 self._trypending = trypending
438 self._trypending = trypending
439 self._try_split = try_split
439 self._try_split = try_split
440 self.opener = opener
440 self.opener = opener
441 if persistentnodemap:
441 if persistentnodemap:
442 self._nodemap_file = nodemaputil.get_nodemap_file(self)
442 self._nodemap_file = nodemaputil.get_nodemap_file(self)
443
443
444 assert target[0] in ALL_KINDS
444 assert target[0] in ALL_KINDS
445 assert len(target) == 2
445 assert len(target) == 2
446 self.target = target
446 self.target = target
447 if b'feature-config' in self.opener.options:
447 if b'feature-config' in self.opener.options:
448 self.feature_config = self.opener.options[b'feature-config'].copy()
448 self.feature_config = self.opener.options[b'feature-config'].copy()
449 else:
449 else:
450 self.feature_config = FeatureConfig()
450 self.feature_config = FeatureConfig()
451 self.feature_config.censorable = censorable
451 self.feature_config.censorable = censorable
452 self.feature_config.canonical_parent_order = canonical_parent_order
452 self.feature_config.canonical_parent_order = canonical_parent_order
453 if b'data-config' in self.opener.options:
453 if b'data-config' in self.opener.options:
454 self.data_config = self.opener.options[b'data-config'].copy()
454 self.data_config = self.opener.options[b'data-config'].copy()
455 else:
455 else:
456 self.data_config = DataConfig()
456 self.data_config = DataConfig()
457 self.data_config.check_ambig = checkambig
457 self.data_config.check_ambig = checkambig
458 self.data_config.mmap_large_index = mmaplargeindex
458 self.data_config.mmap_large_index = mmaplargeindex
459 if b'delta-config' in self.opener.options:
459 if b'delta-config' in self.opener.options:
460 self.delta_config = self.opener.options[b'delta-config'].copy()
460 self.delta_config = self.opener.options[b'delta-config'].copy()
461 else:
461 else:
462 self.delta_config = DeltaConfig()
462 self.delta_config = DeltaConfig()
463
463
464 # 3-tuple of (node, rev, text) for a raw revision.
464 # 3-tuple of (node, rev, text) for a raw revision.
465 self._revisioncache = None
465 self._revisioncache = None
466 # Maps rev to chain base rev.
466 # Maps rev to chain base rev.
467 self._chainbasecache = util.lrucachedict(100)
467 self._chainbasecache = util.lrucachedict(100)
468 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
468 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
469 self._chunkcache = (0, b'')
469 self._chunkcache = (0, b'')
470
470
471 self.index = None
471 self.index = None
472 self._docket = None
472 self._docket = None
473 self._nodemap_docket = None
473 self._nodemap_docket = None
474 # Mapping of partial identifiers to full nodes.
474 # Mapping of partial identifiers to full nodes.
475 self._pcache = {}
475 self._pcache = {}
476
476
477 # other optionnals features
477 # other optionnals features
478
478
479 # Make copy of flag processors so each revlog instance can support
479 # Make copy of flag processors so each revlog instance can support
480 # custom flags.
480 # custom flags.
481 self._flagprocessors = dict(flagutil.flagprocessors)
481 self._flagprocessors = dict(flagutil.flagprocessors)
482
482
483 # 3-tuple of file handles being used for active writing.
483 # 3-tuple of file handles being used for active writing.
484 self._writinghandles = None
484 self._writinghandles = None
485 # prevent nesting of addgroup
485 # prevent nesting of addgroup
486 self._adding_group = None
486 self._adding_group = None
487
487
488 self._loadindex()
488 self._loadindex()
489
489
490 self._concurrencychecker = concurrencychecker
490 self._concurrencychecker = concurrencychecker
491
491
492 @property
492 @property
493 def _generaldelta(self):
493 def _generaldelta(self):
494 """temporary compatibility proxy"""
494 """temporary compatibility proxy"""
495 return self.delta_config.general_delta
495 return self.delta_config.general_delta
496
496
497 @property
497 @property
498 def _checkambig(self):
498 def _checkambig(self):
499 """temporary compatibility proxy"""
499 """temporary compatibility proxy"""
500 return self.data_config.check_ambig
500 return self.data_config.check_ambig
501
501
502 @property
502 @property
503 def _mmaplargeindex(self):
503 def _mmaplargeindex(self):
504 """temporary compatibility proxy"""
504 """temporary compatibility proxy"""
505 return self.data_config.mmap_large_index
505 return self.data_config.mmap_large_index
506
506
507 @property
507 @property
508 def _censorable(self):
508 def _censorable(self):
509 """temporary compatibility proxy"""
509 """temporary compatibility proxy"""
510 return self.feature_config.censorable
510 return self.feature_config.censorable
511
511
512 @property
512 @property
513 def _chunkcachesize(self):
513 def _chunkcachesize(self):
514 """temporary compatibility proxy"""
514 """temporary compatibility proxy"""
515 return self.data_config.chunk_cache_size
515 return self.data_config.chunk_cache_size
516
516
517 @property
517 @property
518 def _maxchainlen(self):
518 def _maxchainlen(self):
519 """temporary compatibility proxy"""
519 """temporary compatibility proxy"""
520 return self.delta_config.max_chain_len
520 return self.delta_config.max_chain_len
521
521
522 @property
522 @property
523 def _deltabothparents(self):
523 def _deltabothparents(self):
524 """temporary compatibility proxy"""
524 """temporary compatibility proxy"""
525 return self.delta_config.delta_both_parents
525 return self.delta_config.delta_both_parents
526
526
527 @property
527 @property
528 def _candidate_group_chunk_size(self):
528 def _candidate_group_chunk_size(self):
529 """temporary compatibility proxy"""
529 """temporary compatibility proxy"""
530 return self.delta_config.candidate_group_chunk_size
530 return self.delta_config.candidate_group_chunk_size
531
531
532 @property
532 @property
533 def _debug_delta(self):
533 def _debug_delta(self):
534 """temporary compatibility proxy"""
534 """temporary compatibility proxy"""
535 return self.delta_config.debug_delta
535 return self.delta_config.debug_delta
536
536
537 @property
537 @property
538 def _compengine(self):
538 def _compengine(self):
539 """temporary compatibility proxy"""
539 """temporary compatibility proxy"""
540 return self.feature_config.compression_engine
540 return self.feature_config.compression_engine
541
541
542 @property
542 @property
543 def _compengineopts(self):
543 def _compengineopts(self):
544 """temporary compatibility proxy"""
544 """temporary compatibility proxy"""
545 return self.feature_config.compression_engine_options
545 return self.feature_config.compression_engine_options
546
546
547 @property
547 @property
548 def _maxdeltachainspan(self):
548 def _maxdeltachainspan(self):
549 """temporary compatibility proxy"""
549 """temporary compatibility proxy"""
550 return self.delta_config.max_deltachain_span
550 return self.delta_config.max_deltachain_span
551
551
552 @property
552 @property
553 def _withsparseread(self):
553 def _withsparseread(self):
554 """temporary compatibility proxy"""
554 """temporary compatibility proxy"""
555 return self.data_config.with_sparse_read
555 return self.data_config.with_sparse_read
556
556
557 @property
557 @property
558 def _sparserevlog(self):
558 def _sparserevlog(self):
559 """temporary compatibility proxy"""
559 """temporary compatibility proxy"""
560 return self.delta_config.sparse_revlog
560 return self.delta_config.sparse_revlog
561
561
562 @property
562 @property
563 def hassidedata(self):
563 def hassidedata(self):
564 """temporary compatibility proxy"""
564 """temporary compatibility proxy"""
565 return self.feature_config.has_side_data
565 return self.feature_config.has_side_data
566
566
567 @property
567 @property
568 def _srdensitythreshold(self):
568 def _srdensitythreshold(self):
569 """temporary compatibility proxy"""
569 """temporary compatibility proxy"""
570 return self.data_config.sr_density_threshold
570 return self.data_config.sr_density_threshold
571
571
572 @property
572 @property
573 def _srmingapsize(self):
573 def _srmingapsize(self):
574 """temporary compatibility proxy"""
574 """temporary compatibility proxy"""
575 return self.data_config.sr_min_gap_size
575 return self.data_config.sr_min_gap_size
576
576
577 @property
577 @property
578 def _compute_rank(self):
578 def _compute_rank(self):
579 """temporary compatibility proxy"""
579 """temporary compatibility proxy"""
580 return self.feature_config.compute_rank
580 return self.feature_config.compute_rank
581
581
582 @property
582 @property
583 def canonical_parent_order(self):
583 def canonical_parent_order(self):
584 """temporary compatibility proxy"""
584 """temporary compatibility proxy"""
585 return self.feature_config.canonical_parent_order
585 return self.feature_config.canonical_parent_order
586
586
587 @property
587 @property
588 def _lazydelta(self):
588 def _lazydelta(self):
589 """temporary compatibility proxy"""
589 """temporary compatibility proxy"""
590 return self.delta_config.lazy_delta
590 return self.delta_config.lazy_delta
591
591
592 @property
592 @property
593 def _lazydeltabase(self):
593 def _lazydeltabase(self):
594 """temporary compatibility proxy"""
594 """temporary compatibility proxy"""
595 return self.delta_config.lazy_delta_base
595 return self.delta_config.lazy_delta_base
596
596
597 def _init_opts(self):
597 def _init_opts(self):
598 """process options (from above/config) to setup associated default revlog mode
598 """process options (from above/config) to setup associated default revlog mode
599
599
600 These values might be affected when actually reading on disk information.
600 These values might be affected when actually reading on disk information.
601
601
602 The relevant values are returned for use in _loadindex().
602 The relevant values are returned for use in _loadindex().
603
603
604 * newversionflags:
604 * newversionflags:
605 version header to use if we need to create a new revlog
605 version header to use if we need to create a new revlog
606
606
607 * mmapindexthreshold:
607 * mmapindexthreshold:
608 minimal index size for start to use mmap
608 minimal index size for start to use mmap
609
609
610 * force_nodemap:
610 * force_nodemap:
611 force the usage of a "development" version of the nodemap code
611 force the usage of a "development" version of the nodemap code
612 """
612 """
613 mmapindexthreshold = None
613 mmapindexthreshold = None
614 opts = self.opener.options
614 opts = self.opener.options
615
615
616 if b'changelogv2' in opts and self.revlog_kind == KIND_CHANGELOG:
616 if b'changelogv2' in opts and self.revlog_kind == KIND_CHANGELOG:
617 new_header = CHANGELOGV2
617 new_header = CHANGELOGV2
618 compute_rank = opts.get(b'changelogv2.compute-rank', True)
618 compute_rank = opts.get(b'changelogv2.compute-rank', True)
619 self.feature_config.compute_rank = compute_rank
619 self.feature_config.compute_rank = compute_rank
620 elif b'revlogv2' in opts:
620 elif b'revlogv2' in opts:
621 new_header = REVLOGV2
621 new_header = REVLOGV2
622 elif b'revlogv1' in opts:
622 elif b'revlogv1' in opts:
623 new_header = REVLOGV1 | FLAG_INLINE_DATA
623 new_header = REVLOGV1 | FLAG_INLINE_DATA
624 if b'generaldelta' in opts:
624 if b'generaldelta' in opts:
625 new_header |= FLAG_GENERALDELTA
625 new_header |= FLAG_GENERALDELTA
626 elif b'revlogv0' in self.opener.options:
626 elif b'revlogv0' in self.opener.options:
627 new_header = REVLOGV0
627 new_header = REVLOGV0
628 else:
628 else:
629 new_header = REVLOG_DEFAULT_VERSION
629 new_header = REVLOG_DEFAULT_VERSION
630
630
631 if b'maxchainlen' in opts:
631 if b'maxchainlen' in opts:
632 self.delta_config.max_chain_len = opts[b'maxchainlen']
632 self.delta_config.max_chain_len = opts[b'maxchainlen']
633 if b'compengine' in opts:
633 if b'compengine' in opts:
634 self.feature_config.compression_engine = opts[b'compengine']
634 self.feature_config.compression_engine = opts[b'compengine']
635 comp_engine_opts = self.feature_config.compression_engine_options
635 comp_engine_opts = self.feature_config.compression_engine_options
636 if b'zlib.level' in opts:
636 if b'zlib.level' in opts:
637 comp_engine_opts[b'zlib.level'] = opts[b'zlib.level']
637 comp_engine_opts[b'zlib.level'] = opts[b'zlib.level']
638 if b'zstd.level' in opts:
638 if b'zstd.level' in opts:
639 comp_engine_opts[b'zstd.level'] = opts[b'zstd.level']
639 comp_engine_opts[b'zstd.level'] = opts[b'zstd.level']
640 if b'maxdeltachainspan' in opts:
641 self.delta_config.max_deltachain_span = opts[b'maxdeltachainspan']
642 if self._mmaplargeindex and b'mmapindexthreshold' in opts:
640 if self._mmaplargeindex and b'mmapindexthreshold' in opts:
643 mmapindexthreshold = opts[b'mmapindexthreshold']
641 mmapindexthreshold = opts[b'mmapindexthreshold']
644 self.data_config.mmap_index_threshold = mmapindexthreshold
642 self.data_config.mmap_index_threshold = mmapindexthreshold
645 if b'sparse-revlog' in opts:
643 if b'sparse-revlog' in opts:
646 self.delta_config.sparse_revlog = bool(opts[b'sparse-revlog'])
644 self.delta_config.sparse_revlog = bool(opts[b'sparse-revlog'])
647 if self.delta_config.sparse_revlog:
645 if self.delta_config.sparse_revlog:
648 # sparse-revlog forces sparse-read
646 # sparse-revlog forces sparse-read
649 self.data_config.with_sparse_read = True
647 self.data_config.with_sparse_read = True
650 elif b'with-sparse-read' in opts:
648 elif b'with-sparse-read' in opts:
651 self.data_config.with_sparse_read = bool(opts[b'with-sparse-read'])
649 self.data_config.with_sparse_read = bool(opts[b'with-sparse-read'])
652 if b'sparse-read-density-threshold' in opts:
650 if b'sparse-read-density-threshold' in opts:
653 self.data_config.sr_density_threshold = opts[
651 self.data_config.sr_density_threshold = opts[
654 b'sparse-read-density-threshold'
652 b'sparse-read-density-threshold'
655 ]
653 ]
656 if b'sparse-read-min-gap-size' in opts:
654 if b'sparse-read-min-gap-size' in opts:
657 self.data_config.sr_min_gap_size = opts[b'sparse-read-min-gap-size']
655 self.data_config.sr_min_gap_size = opts[b'sparse-read-min-gap-size']
658 if opts.get(b'enableellipsis'):
656 if opts.get(b'enableellipsis'):
659 self.feature_config.enable_ellipsis = True
657 self.feature_config.enable_ellipsis = True
660 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
658 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
661
659
662 # revlog v0 doesn't have flag processors
660 # revlog v0 doesn't have flag processors
663 for flag, processor in opts.get(b'flagprocessors', {}).items():
661 for flag, processor in opts.get(b'flagprocessors', {}).items():
664 flagutil.insertflagprocessor(flag, processor, self._flagprocessors)
662 flagutil.insertflagprocessor(flag, processor, self._flagprocessors)
665
663
666 chunk_cache_size = self.data_config.chunk_cache_size
664 chunk_cache_size = self.data_config.chunk_cache_size
667 if chunk_cache_size <= 0:
665 if chunk_cache_size <= 0:
668 raise error.RevlogError(
666 raise error.RevlogError(
669 _(b'revlog chunk cache size %r is not greater than 0')
667 _(b'revlog chunk cache size %r is not greater than 0')
670 % chunk_cache_size
668 % chunk_cache_size
671 )
669 )
672 elif chunk_cache_size & (chunk_cache_size - 1):
670 elif chunk_cache_size & (chunk_cache_size - 1):
673 raise error.RevlogError(
671 raise error.RevlogError(
674 _(b'revlog chunk cache size %r is not a power of 2')
672 _(b'revlog chunk cache size %r is not a power of 2')
675 % chunk_cache_size
673 % chunk_cache_size
676 )
674 )
677 force_nodemap = opts.get(b'devel-force-nodemap', False)
675 force_nodemap = opts.get(b'devel-force-nodemap', False)
678 return new_header, mmapindexthreshold, force_nodemap
676 return new_header, mmapindexthreshold, force_nodemap
679
677
680 def _get_data(self, filepath, mmap_threshold, size=None):
678 def _get_data(self, filepath, mmap_threshold, size=None):
681 """return a file content with or without mmap
679 """return a file content with or without mmap
682
680
683 If the file is missing return the empty string"""
681 If the file is missing return the empty string"""
684 try:
682 try:
685 with self.opener(filepath) as fp:
683 with self.opener(filepath) as fp:
686 if mmap_threshold is not None:
684 if mmap_threshold is not None:
687 file_size = self.opener.fstat(fp).st_size
685 file_size = self.opener.fstat(fp).st_size
688 if file_size >= mmap_threshold:
686 if file_size >= mmap_threshold:
689 if size is not None:
687 if size is not None:
690 # avoid potentiel mmap crash
688 # avoid potentiel mmap crash
691 size = min(file_size, size)
689 size = min(file_size, size)
692 # TODO: should .close() to release resources without
690 # TODO: should .close() to release resources without
693 # relying on Python GC
691 # relying on Python GC
694 if size is None:
692 if size is None:
695 return util.buffer(util.mmapread(fp))
693 return util.buffer(util.mmapread(fp))
696 else:
694 else:
697 return util.buffer(util.mmapread(fp, size))
695 return util.buffer(util.mmapread(fp, size))
698 if size is None:
696 if size is None:
699 return fp.read()
697 return fp.read()
700 else:
698 else:
701 return fp.read(size)
699 return fp.read(size)
702 except FileNotFoundError:
700 except FileNotFoundError:
703 return b''
701 return b''
704
702
705 def get_streams(self, max_linkrev, force_inline=False):
703 def get_streams(self, max_linkrev, force_inline=False):
706 """return a list of streams that represent this revlog
704 """return a list of streams that represent this revlog
707
705
708 This is used by stream-clone to do bytes to bytes copies of a repository.
706 This is used by stream-clone to do bytes to bytes copies of a repository.
709
707
710 This streams data for all revisions that refer to a changelog revision up
708 This streams data for all revisions that refer to a changelog revision up
711 to `max_linkrev`.
709 to `max_linkrev`.
712
710
713 If `force_inline` is set, it enforces that the stream will represent an inline revlog.
711 If `force_inline` is set, it enforces that the stream will represent an inline revlog.
714
712
715 It returns is a list of three-tuple:
713 It returns is a list of three-tuple:
716
714
717 [
715 [
718 (filename, bytes_stream, stream_size),
716 (filename, bytes_stream, stream_size),
719 …
717 …
720 ]
718 ]
721 """
719 """
722 n = len(self)
720 n = len(self)
723 index = self.index
721 index = self.index
724 while n > 0:
722 while n > 0:
725 linkrev = index[n - 1][4]
723 linkrev = index[n - 1][4]
726 if linkrev < max_linkrev:
724 if linkrev < max_linkrev:
727 break
725 break
728 # note: this loop will rarely go through multiple iterations, since
726 # note: this loop will rarely go through multiple iterations, since
729 # it only traverses commits created during the current streaming
727 # it only traverses commits created during the current streaming
730 # pull operation.
728 # pull operation.
731 #
729 #
732 # If this become a problem, using a binary search should cap the
730 # If this become a problem, using a binary search should cap the
733 # runtime of this.
731 # runtime of this.
734 n = n - 1
732 n = n - 1
735 if n == 0:
733 if n == 0:
736 # no data to send
734 # no data to send
737 return []
735 return []
738 index_size = n * index.entry_size
736 index_size = n * index.entry_size
739 data_size = self.end(n - 1)
737 data_size = self.end(n - 1)
740
738
741 # XXX we might have been split (or stripped) since the object
739 # XXX we might have been split (or stripped) since the object
742 # initialization, We need to close this race too, but having a way to
740 # initialization, We need to close this race too, but having a way to
743 # pre-open the file we feed to the revlog and never closing them before
741 # pre-open the file we feed to the revlog and never closing them before
744 # we are done streaming.
742 # we are done streaming.
745
743
746 if self._inline:
744 if self._inline:
747
745
748 def get_stream():
746 def get_stream():
749 with self._indexfp() as fp:
747 with self._indexfp() as fp:
750 yield None
748 yield None
751 size = index_size + data_size
749 size = index_size + data_size
752 if size <= 65536:
750 if size <= 65536:
753 yield fp.read(size)
751 yield fp.read(size)
754 else:
752 else:
755 yield from util.filechunkiter(fp, limit=size)
753 yield from util.filechunkiter(fp, limit=size)
756
754
757 inline_stream = get_stream()
755 inline_stream = get_stream()
758 next(inline_stream)
756 next(inline_stream)
759 return [
757 return [
760 (self._indexfile, inline_stream, index_size + data_size),
758 (self._indexfile, inline_stream, index_size + data_size),
761 ]
759 ]
762 elif force_inline:
760 elif force_inline:
763
761
764 def get_stream():
762 def get_stream():
765 with self.reading():
763 with self.reading():
766 yield None
764 yield None
767
765
768 for rev in range(n):
766 for rev in range(n):
769 idx = self.index.entry_binary(rev)
767 idx = self.index.entry_binary(rev)
770 if rev == 0 and self._docket is None:
768 if rev == 0 and self._docket is None:
771 # re-inject the inline flag
769 # re-inject the inline flag
772 header = self._format_flags
770 header = self._format_flags
773 header |= self._format_version
771 header |= self._format_version
774 header |= FLAG_INLINE_DATA
772 header |= FLAG_INLINE_DATA
775 header = self.index.pack_header(header)
773 header = self.index.pack_header(header)
776 idx = header + idx
774 idx = header + idx
777 yield idx
775 yield idx
778 yield self._getsegmentforrevs(rev, rev)[1]
776 yield self._getsegmentforrevs(rev, rev)[1]
779
777
780 inline_stream = get_stream()
778 inline_stream = get_stream()
781 next(inline_stream)
779 next(inline_stream)
782 return [
780 return [
783 (self._indexfile, inline_stream, index_size + data_size),
781 (self._indexfile, inline_stream, index_size + data_size),
784 ]
782 ]
785 else:
783 else:
786
784
787 def get_index_stream():
785 def get_index_stream():
788 with self._indexfp() as fp:
786 with self._indexfp() as fp:
789 yield None
787 yield None
790 if index_size <= 65536:
788 if index_size <= 65536:
791 yield fp.read(index_size)
789 yield fp.read(index_size)
792 else:
790 else:
793 yield from util.filechunkiter(fp, limit=index_size)
791 yield from util.filechunkiter(fp, limit=index_size)
794
792
795 def get_data_stream():
793 def get_data_stream():
796 with self._datafp() as fp:
794 with self._datafp() as fp:
797 yield None
795 yield None
798 if data_size <= 65536:
796 if data_size <= 65536:
799 yield fp.read(data_size)
797 yield fp.read(data_size)
800 else:
798 else:
801 yield from util.filechunkiter(fp, limit=data_size)
799 yield from util.filechunkiter(fp, limit=data_size)
802
800
803 index_stream = get_index_stream()
801 index_stream = get_index_stream()
804 next(index_stream)
802 next(index_stream)
805 data_stream = get_data_stream()
803 data_stream = get_data_stream()
806 next(data_stream)
804 next(data_stream)
807 return [
805 return [
808 (self._datafile, data_stream, data_size),
806 (self._datafile, data_stream, data_size),
809 (self._indexfile, index_stream, index_size),
807 (self._indexfile, index_stream, index_size),
810 ]
808 ]
811
809
812 def _loadindex(self, docket=None):
810 def _loadindex(self, docket=None):
813
811
814 new_header, mmapindexthreshold, force_nodemap = self._init_opts()
812 new_header, mmapindexthreshold, force_nodemap = self._init_opts()
815
813
816 if self.postfix is not None:
814 if self.postfix is not None:
817 entry_point = b'%s.i.%s' % (self.radix, self.postfix)
815 entry_point = b'%s.i.%s' % (self.radix, self.postfix)
818 elif self._trypending and self.opener.exists(b'%s.i.a' % self.radix):
816 elif self._trypending and self.opener.exists(b'%s.i.a' % self.radix):
819 entry_point = b'%s.i.a' % self.radix
817 entry_point = b'%s.i.a' % self.radix
820 elif self._try_split and self.opener.exists(self._split_index_file):
818 elif self._try_split and self.opener.exists(self._split_index_file):
821 entry_point = self._split_index_file
819 entry_point = self._split_index_file
822 else:
820 else:
823 entry_point = b'%s.i' % self.radix
821 entry_point = b'%s.i' % self.radix
824
822
825 if docket is not None:
823 if docket is not None:
826 self._docket = docket
824 self._docket = docket
827 self._docket_file = entry_point
825 self._docket_file = entry_point
828 else:
826 else:
829 self._initempty = True
827 self._initempty = True
830 entry_data = self._get_data(entry_point, mmapindexthreshold)
828 entry_data = self._get_data(entry_point, mmapindexthreshold)
831 if len(entry_data) > 0:
829 if len(entry_data) > 0:
832 header = INDEX_HEADER.unpack(entry_data[:4])[0]
830 header = INDEX_HEADER.unpack(entry_data[:4])[0]
833 self._initempty = False
831 self._initempty = False
834 else:
832 else:
835 header = new_header
833 header = new_header
836
834
837 self._format_flags = header & ~0xFFFF
835 self._format_flags = header & ~0xFFFF
838 self._format_version = header & 0xFFFF
836 self._format_version = header & 0xFFFF
839
837
840 supported_flags = SUPPORTED_FLAGS.get(self._format_version)
838 supported_flags = SUPPORTED_FLAGS.get(self._format_version)
841 if supported_flags is None:
839 if supported_flags is None:
842 msg = _(b'unknown version (%d) in revlog %s')
840 msg = _(b'unknown version (%d) in revlog %s')
843 msg %= (self._format_version, self.display_id)
841 msg %= (self._format_version, self.display_id)
844 raise error.RevlogError(msg)
842 raise error.RevlogError(msg)
845 elif self._format_flags & ~supported_flags:
843 elif self._format_flags & ~supported_flags:
846 msg = _(b'unknown flags (%#04x) in version %d revlog %s')
844 msg = _(b'unknown flags (%#04x) in version %d revlog %s')
847 display_flag = self._format_flags >> 16
845 display_flag = self._format_flags >> 16
848 msg %= (display_flag, self._format_version, self.display_id)
846 msg %= (display_flag, self._format_version, self.display_id)
849 raise error.RevlogError(msg)
847 raise error.RevlogError(msg)
850
848
851 features = FEATURES_BY_VERSION[self._format_version]
849 features = FEATURES_BY_VERSION[self._format_version]
852 self._inline = features[b'inline'](self._format_flags)
850 self._inline = features[b'inline'](self._format_flags)
853 self.delta_config.general_delta = features[b'generaldelta'](
851 self.delta_config.general_delta = features[b'generaldelta'](
854 self._format_flags
852 self._format_flags
855 )
853 )
856 self.feature_config.has_side_data = features[b'sidedata']
854 self.feature_config.has_side_data = features[b'sidedata']
857
855
858 if not features[b'docket']:
856 if not features[b'docket']:
859 self._indexfile = entry_point
857 self._indexfile = entry_point
860 index_data = entry_data
858 index_data = entry_data
861 else:
859 else:
862 self._docket_file = entry_point
860 self._docket_file = entry_point
863 if self._initempty:
861 if self._initempty:
864 self._docket = docketutil.default_docket(self, header)
862 self._docket = docketutil.default_docket(self, header)
865 else:
863 else:
866 self._docket = docketutil.parse_docket(
864 self._docket = docketutil.parse_docket(
867 self, entry_data, use_pending=self._trypending
865 self, entry_data, use_pending=self._trypending
868 )
866 )
869
867
870 if self._docket is not None:
868 if self._docket is not None:
871 self._indexfile = self._docket.index_filepath()
869 self._indexfile = self._docket.index_filepath()
872 index_data = b''
870 index_data = b''
873 index_size = self._docket.index_end
871 index_size = self._docket.index_end
874 if index_size > 0:
872 if index_size > 0:
875 index_data = self._get_data(
873 index_data = self._get_data(
876 self._indexfile, mmapindexthreshold, size=index_size
874 self._indexfile, mmapindexthreshold, size=index_size
877 )
875 )
878 if len(index_data) < index_size:
876 if len(index_data) < index_size:
879 msg = _(b'too few index data for %s: got %d, expected %d')
877 msg = _(b'too few index data for %s: got %d, expected %d')
880 msg %= (self.display_id, len(index_data), index_size)
878 msg %= (self.display_id, len(index_data), index_size)
881 raise error.RevlogError(msg)
879 raise error.RevlogError(msg)
882
880
883 self._inline = False
881 self._inline = False
884 # generaldelta implied by version 2 revlogs.
882 # generaldelta implied by version 2 revlogs.
885 self.delta_config.general_delta = True
883 self.delta_config.general_delta = True
886 # the logic for persistent nodemap will be dealt with within the
884 # the logic for persistent nodemap will be dealt with within the
887 # main docket, so disable it for now.
885 # main docket, so disable it for now.
888 self._nodemap_file = None
886 self._nodemap_file = None
889
887
890 if self._docket is not None:
888 if self._docket is not None:
891 self._datafile = self._docket.data_filepath()
889 self._datafile = self._docket.data_filepath()
892 self._sidedatafile = self._docket.sidedata_filepath()
890 self._sidedatafile = self._docket.sidedata_filepath()
893 elif self.postfix is None:
891 elif self.postfix is None:
894 self._datafile = b'%s.d' % self.radix
892 self._datafile = b'%s.d' % self.radix
895 else:
893 else:
896 self._datafile = b'%s.d.%s' % (self.radix, self.postfix)
894 self._datafile = b'%s.d.%s' % (self.radix, self.postfix)
897
895
898 self.nodeconstants = sha1nodeconstants
896 self.nodeconstants = sha1nodeconstants
899 self.nullid = self.nodeconstants.nullid
897 self.nullid = self.nodeconstants.nullid
900
898
901 # sparse-revlog can't be on without general-delta (issue6056)
899 # sparse-revlog can't be on without general-delta (issue6056)
902 if not self._generaldelta:
900 if not self._generaldelta:
903 self.delta_config.sparse_revlog = False
901 self.delta_config.sparse_revlog = False
904
902
905 self._storedeltachains = True
903 self._storedeltachains = True
906
904
907 devel_nodemap = (
905 devel_nodemap = (
908 self._nodemap_file
906 self._nodemap_file
909 and force_nodemap
907 and force_nodemap
910 and parse_index_v1_nodemap is not None
908 and parse_index_v1_nodemap is not None
911 )
909 )
912
910
913 use_rust_index = False
911 use_rust_index = False
914 if rustrevlog is not None:
912 if rustrevlog is not None:
915 if self._nodemap_file is not None:
913 if self._nodemap_file is not None:
916 use_rust_index = True
914 use_rust_index = True
917 else:
915 else:
918 use_rust_index = self.opener.options.get(b'rust.index')
916 use_rust_index = self.opener.options.get(b'rust.index')
919
917
920 self._parse_index = parse_index_v1
918 self._parse_index = parse_index_v1
921 if self._format_version == REVLOGV0:
919 if self._format_version == REVLOGV0:
922 self._parse_index = revlogv0.parse_index_v0
920 self._parse_index = revlogv0.parse_index_v0
923 elif self._format_version == REVLOGV2:
921 elif self._format_version == REVLOGV2:
924 self._parse_index = parse_index_v2
922 self._parse_index = parse_index_v2
925 elif self._format_version == CHANGELOGV2:
923 elif self._format_version == CHANGELOGV2:
926 self._parse_index = parse_index_cl_v2
924 self._parse_index = parse_index_cl_v2
927 elif devel_nodemap:
925 elif devel_nodemap:
928 self._parse_index = parse_index_v1_nodemap
926 self._parse_index = parse_index_v1_nodemap
929 elif use_rust_index:
927 elif use_rust_index:
930 self._parse_index = parse_index_v1_mixed
928 self._parse_index = parse_index_v1_mixed
931 try:
929 try:
932 d = self._parse_index(index_data, self._inline)
930 d = self._parse_index(index_data, self._inline)
933 index, chunkcache = d
931 index, chunkcache = d
934 use_nodemap = (
932 use_nodemap = (
935 not self._inline
933 not self._inline
936 and self._nodemap_file is not None
934 and self._nodemap_file is not None
937 and hasattr(index, 'update_nodemap_data')
935 and hasattr(index, 'update_nodemap_data')
938 )
936 )
939 if use_nodemap:
937 if use_nodemap:
940 nodemap_data = nodemaputil.persisted_data(self)
938 nodemap_data = nodemaputil.persisted_data(self)
941 if nodemap_data is not None:
939 if nodemap_data is not None:
942 docket = nodemap_data[0]
940 docket = nodemap_data[0]
943 if (
941 if (
944 len(d[0]) > docket.tip_rev
942 len(d[0]) > docket.tip_rev
945 and d[0][docket.tip_rev][7] == docket.tip_node
943 and d[0][docket.tip_rev][7] == docket.tip_node
946 ):
944 ):
947 # no changelog tampering
945 # no changelog tampering
948 self._nodemap_docket = docket
946 self._nodemap_docket = docket
949 index.update_nodemap_data(*nodemap_data)
947 index.update_nodemap_data(*nodemap_data)
950 except (ValueError, IndexError):
948 except (ValueError, IndexError):
951 raise error.RevlogError(
949 raise error.RevlogError(
952 _(b"index %s is corrupted") % self.display_id
950 _(b"index %s is corrupted") % self.display_id
953 )
951 )
954 self.index = index
952 self.index = index
955 self._segmentfile = randomaccessfile.randomaccessfile(
953 self._segmentfile = randomaccessfile.randomaccessfile(
956 self.opener,
954 self.opener,
957 (self._indexfile if self._inline else self._datafile),
955 (self._indexfile if self._inline else self._datafile),
958 self._chunkcachesize,
956 self._chunkcachesize,
959 chunkcache,
957 chunkcache,
960 )
958 )
961 self._segmentfile_sidedata = randomaccessfile.randomaccessfile(
959 self._segmentfile_sidedata = randomaccessfile.randomaccessfile(
962 self.opener,
960 self.opener,
963 self._sidedatafile,
961 self._sidedatafile,
964 self._chunkcachesize,
962 self._chunkcachesize,
965 )
963 )
966 # revnum -> (chain-length, sum-delta-length)
964 # revnum -> (chain-length, sum-delta-length)
967 self._chaininfocache = util.lrucachedict(500)
965 self._chaininfocache = util.lrucachedict(500)
968 # revlog header -> revlog compressor
966 # revlog header -> revlog compressor
969 self._decompressors = {}
967 self._decompressors = {}
970
968
971 def get_revlog(self):
969 def get_revlog(self):
972 """simple function to mirror API of other not-really-revlog API"""
970 """simple function to mirror API of other not-really-revlog API"""
973 return self
971 return self
974
972
975 @util.propertycache
973 @util.propertycache
976 def revlog_kind(self):
974 def revlog_kind(self):
977 return self.target[0]
975 return self.target[0]
978
976
979 @util.propertycache
977 @util.propertycache
980 def display_id(self):
978 def display_id(self):
981 """The public facing "ID" of the revlog that we use in message"""
979 """The public facing "ID" of the revlog that we use in message"""
982 if self.revlog_kind == KIND_FILELOG:
980 if self.revlog_kind == KIND_FILELOG:
983 # Reference the file without the "data/" prefix, so it is familiar
981 # Reference the file without the "data/" prefix, so it is familiar
984 # to the user.
982 # to the user.
985 return self.target[1]
983 return self.target[1]
986 else:
984 else:
987 return self.radix
985 return self.radix
988
986
989 def _get_decompressor(self, t):
987 def _get_decompressor(self, t):
990 try:
988 try:
991 compressor = self._decompressors[t]
989 compressor = self._decompressors[t]
992 except KeyError:
990 except KeyError:
993 try:
991 try:
994 engine = util.compengines.forrevlogheader(t)
992 engine = util.compengines.forrevlogheader(t)
995 compressor = engine.revlogcompressor(self._compengineopts)
993 compressor = engine.revlogcompressor(self._compengineopts)
996 self._decompressors[t] = compressor
994 self._decompressors[t] = compressor
997 except KeyError:
995 except KeyError:
998 raise error.RevlogError(
996 raise error.RevlogError(
999 _(b'unknown compression type %s') % binascii.hexlify(t)
997 _(b'unknown compression type %s') % binascii.hexlify(t)
1000 )
998 )
1001 return compressor
999 return compressor
1002
1000
1003 @util.propertycache
1001 @util.propertycache
1004 def _compressor(self):
1002 def _compressor(self):
1005 engine = util.compengines[self._compengine]
1003 engine = util.compengines[self._compengine]
1006 return engine.revlogcompressor(self._compengineopts)
1004 return engine.revlogcompressor(self._compengineopts)
1007
1005
1008 @util.propertycache
1006 @util.propertycache
1009 def _decompressor(self):
1007 def _decompressor(self):
1010 """the default decompressor"""
1008 """the default decompressor"""
1011 if self._docket is None:
1009 if self._docket is None:
1012 return None
1010 return None
1013 t = self._docket.default_compression_header
1011 t = self._docket.default_compression_header
1014 c = self._get_decompressor(t)
1012 c = self._get_decompressor(t)
1015 return c.decompress
1013 return c.decompress
1016
1014
1017 def _indexfp(self):
1015 def _indexfp(self):
1018 """file object for the revlog's index file"""
1016 """file object for the revlog's index file"""
1019 return self.opener(self._indexfile, mode=b"r")
1017 return self.opener(self._indexfile, mode=b"r")
1020
1018
1021 def __index_write_fp(self):
1019 def __index_write_fp(self):
1022 # You should not use this directly and use `_writing` instead
1020 # You should not use this directly and use `_writing` instead
1023 try:
1021 try:
1024 f = self.opener(
1022 f = self.opener(
1025 self._indexfile, mode=b"r+", checkambig=self._checkambig
1023 self._indexfile, mode=b"r+", checkambig=self._checkambig
1026 )
1024 )
1027 if self._docket is None:
1025 if self._docket is None:
1028 f.seek(0, os.SEEK_END)
1026 f.seek(0, os.SEEK_END)
1029 else:
1027 else:
1030 f.seek(self._docket.index_end, os.SEEK_SET)
1028 f.seek(self._docket.index_end, os.SEEK_SET)
1031 return f
1029 return f
1032 except FileNotFoundError:
1030 except FileNotFoundError:
1033 return self.opener(
1031 return self.opener(
1034 self._indexfile, mode=b"w+", checkambig=self._checkambig
1032 self._indexfile, mode=b"w+", checkambig=self._checkambig
1035 )
1033 )
1036
1034
1037 def __index_new_fp(self):
1035 def __index_new_fp(self):
1038 # You should not use this unless you are upgrading from inline revlog
1036 # You should not use this unless you are upgrading from inline revlog
1039 return self.opener(
1037 return self.opener(
1040 self._indexfile,
1038 self._indexfile,
1041 mode=b"w",
1039 mode=b"w",
1042 checkambig=self._checkambig,
1040 checkambig=self._checkambig,
1043 atomictemp=True,
1041 atomictemp=True,
1044 )
1042 )
1045
1043
1046 def _datafp(self, mode=b'r'):
1044 def _datafp(self, mode=b'r'):
1047 """file object for the revlog's data file"""
1045 """file object for the revlog's data file"""
1048 return self.opener(self._datafile, mode=mode)
1046 return self.opener(self._datafile, mode=mode)
1049
1047
1050 @contextlib.contextmanager
1048 @contextlib.contextmanager
1051 def _sidedatareadfp(self):
1049 def _sidedatareadfp(self):
1052 """file object suitable to read sidedata"""
1050 """file object suitable to read sidedata"""
1053 if self._writinghandles:
1051 if self._writinghandles:
1054 yield self._writinghandles[2]
1052 yield self._writinghandles[2]
1055 else:
1053 else:
1056 with self.opener(self._sidedatafile) as fp:
1054 with self.opener(self._sidedatafile) as fp:
1057 yield fp
1055 yield fp
1058
1056
1059 def tiprev(self):
1057 def tiprev(self):
1060 return len(self.index) - 1
1058 return len(self.index) - 1
1061
1059
1062 def tip(self):
1060 def tip(self):
1063 return self.node(self.tiprev())
1061 return self.node(self.tiprev())
1064
1062
1065 def __contains__(self, rev):
1063 def __contains__(self, rev):
1066 return 0 <= rev < len(self)
1064 return 0 <= rev < len(self)
1067
1065
1068 def __len__(self):
1066 def __len__(self):
1069 return len(self.index)
1067 return len(self.index)
1070
1068
1071 def __iter__(self):
1069 def __iter__(self):
1072 return iter(range(len(self)))
1070 return iter(range(len(self)))
1073
1071
1074 def revs(self, start=0, stop=None):
1072 def revs(self, start=0, stop=None):
1075 """iterate over all rev in this revlog (from start to stop)"""
1073 """iterate over all rev in this revlog (from start to stop)"""
1076 return storageutil.iterrevs(len(self), start=start, stop=stop)
1074 return storageutil.iterrevs(len(self), start=start, stop=stop)
1077
1075
1078 def hasnode(self, node):
1076 def hasnode(self, node):
1079 try:
1077 try:
1080 self.rev(node)
1078 self.rev(node)
1081 return True
1079 return True
1082 except KeyError:
1080 except KeyError:
1083 return False
1081 return False
1084
1082
1085 def _candelta(self, baserev, rev):
1083 def _candelta(self, baserev, rev):
1086 """whether two revisions (baserev, rev) can be delta-ed or not"""
1084 """whether two revisions (baserev, rev) can be delta-ed or not"""
1087 # Disable delta if either rev requires a content-changing flag
1085 # Disable delta if either rev requires a content-changing flag
1088 # processor (ex. LFS). This is because such flag processor can alter
1086 # processor (ex. LFS). This is because such flag processor can alter
1089 # the rawtext content that the delta will be based on, and two clients
1087 # the rawtext content that the delta will be based on, and two clients
1090 # could have a same revlog node with different flags (i.e. different
1088 # could have a same revlog node with different flags (i.e. different
1091 # rawtext contents) and the delta could be incompatible.
1089 # rawtext contents) and the delta could be incompatible.
1092 if (self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS) or (
1090 if (self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS) or (
1093 self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS
1091 self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS
1094 ):
1092 ):
1095 return False
1093 return False
1096 return True
1094 return True
1097
1095
1098 def update_caches(self, transaction):
1096 def update_caches(self, transaction):
1099 """update on disk cache
1097 """update on disk cache
1100
1098
1101 If a transaction is passed, the update may be delayed to transaction
1099 If a transaction is passed, the update may be delayed to transaction
1102 commit."""
1100 commit."""
1103 if self._nodemap_file is not None:
1101 if self._nodemap_file is not None:
1104 if transaction is None:
1102 if transaction is None:
1105 nodemaputil.update_persistent_nodemap(self)
1103 nodemaputil.update_persistent_nodemap(self)
1106 else:
1104 else:
1107 nodemaputil.setup_persistent_nodemap(transaction, self)
1105 nodemaputil.setup_persistent_nodemap(transaction, self)
1108
1106
1109 def clearcaches(self):
1107 def clearcaches(self):
1110 """Clear in-memory caches"""
1108 """Clear in-memory caches"""
1111 self._revisioncache = None
1109 self._revisioncache = None
1112 self._chainbasecache.clear()
1110 self._chainbasecache.clear()
1113 self._segmentfile.clear_cache()
1111 self._segmentfile.clear_cache()
1114 self._segmentfile_sidedata.clear_cache()
1112 self._segmentfile_sidedata.clear_cache()
1115 self._pcache = {}
1113 self._pcache = {}
1116 self._nodemap_docket = None
1114 self._nodemap_docket = None
1117 self.index.clearcaches()
1115 self.index.clearcaches()
1118 # The python code is the one responsible for validating the docket, we
1116 # The python code is the one responsible for validating the docket, we
1119 # end up having to refresh it here.
1117 # end up having to refresh it here.
1120 use_nodemap = (
1118 use_nodemap = (
1121 not self._inline
1119 not self._inline
1122 and self._nodemap_file is not None
1120 and self._nodemap_file is not None
1123 and hasattr(self.index, 'update_nodemap_data')
1121 and hasattr(self.index, 'update_nodemap_data')
1124 )
1122 )
1125 if use_nodemap:
1123 if use_nodemap:
1126 nodemap_data = nodemaputil.persisted_data(self)
1124 nodemap_data = nodemaputil.persisted_data(self)
1127 if nodemap_data is not None:
1125 if nodemap_data is not None:
1128 self._nodemap_docket = nodemap_data[0]
1126 self._nodemap_docket = nodemap_data[0]
1129 self.index.update_nodemap_data(*nodemap_data)
1127 self.index.update_nodemap_data(*nodemap_data)
1130
1128
1131 def rev(self, node):
1129 def rev(self, node):
1132 """return the revision number associated with a <nodeid>"""
1130 """return the revision number associated with a <nodeid>"""
1133 try:
1131 try:
1134 return self.index.rev(node)
1132 return self.index.rev(node)
1135 except TypeError:
1133 except TypeError:
1136 raise
1134 raise
1137 except error.RevlogError:
1135 except error.RevlogError:
1138 # parsers.c radix tree lookup failed
1136 # parsers.c radix tree lookup failed
1139 if (
1137 if (
1140 node == self.nodeconstants.wdirid
1138 node == self.nodeconstants.wdirid
1141 or node in self.nodeconstants.wdirfilenodeids
1139 or node in self.nodeconstants.wdirfilenodeids
1142 ):
1140 ):
1143 raise error.WdirUnsupported
1141 raise error.WdirUnsupported
1144 raise error.LookupError(node, self.display_id, _(b'no node'))
1142 raise error.LookupError(node, self.display_id, _(b'no node'))
1145
1143
1146 # Accessors for index entries.
1144 # Accessors for index entries.
1147
1145
1148 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
1146 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
1149 # are flags.
1147 # are flags.
1150 def start(self, rev):
1148 def start(self, rev):
1151 return int(self.index[rev][0] >> 16)
1149 return int(self.index[rev][0] >> 16)
1152
1150
1153 def sidedata_cut_off(self, rev):
1151 def sidedata_cut_off(self, rev):
1154 sd_cut_off = self.index[rev][8]
1152 sd_cut_off = self.index[rev][8]
1155 if sd_cut_off != 0:
1153 if sd_cut_off != 0:
1156 return sd_cut_off
1154 return sd_cut_off
1157 # This is some annoying dance, because entries without sidedata
1155 # This is some annoying dance, because entries without sidedata
1158 # currently use 0 as their ofsset. (instead of previous-offset +
1156 # currently use 0 as their ofsset. (instead of previous-offset +
1159 # previous-size)
1157 # previous-size)
1160 #
1158 #
1161 # We should reconsider this sidedata β†’ 0 sidata_offset policy.
1159 # We should reconsider this sidedata β†’ 0 sidata_offset policy.
1162 # In the meantime, we need this.
1160 # In the meantime, we need this.
1163 while 0 <= rev:
1161 while 0 <= rev:
1164 e = self.index[rev]
1162 e = self.index[rev]
1165 if e[9] != 0:
1163 if e[9] != 0:
1166 return e[8] + e[9]
1164 return e[8] + e[9]
1167 rev -= 1
1165 rev -= 1
1168 return 0
1166 return 0
1169
1167
1170 def flags(self, rev):
1168 def flags(self, rev):
1171 return self.index[rev][0] & 0xFFFF
1169 return self.index[rev][0] & 0xFFFF
1172
1170
1173 def length(self, rev):
1171 def length(self, rev):
1174 return self.index[rev][1]
1172 return self.index[rev][1]
1175
1173
1176 def sidedata_length(self, rev):
1174 def sidedata_length(self, rev):
1177 if not self.hassidedata:
1175 if not self.hassidedata:
1178 return 0
1176 return 0
1179 return self.index[rev][9]
1177 return self.index[rev][9]
1180
1178
1181 def rawsize(self, rev):
1179 def rawsize(self, rev):
1182 """return the length of the uncompressed text for a given revision"""
1180 """return the length of the uncompressed text for a given revision"""
1183 l = self.index[rev][2]
1181 l = self.index[rev][2]
1184 if l >= 0:
1182 if l >= 0:
1185 return l
1183 return l
1186
1184
1187 t = self.rawdata(rev)
1185 t = self.rawdata(rev)
1188 return len(t)
1186 return len(t)
1189
1187
1190 def size(self, rev):
1188 def size(self, rev):
1191 """length of non-raw text (processed by a "read" flag processor)"""
1189 """length of non-raw text (processed by a "read" flag processor)"""
1192 # fast path: if no "read" flag processor could change the content,
1190 # fast path: if no "read" flag processor could change the content,
1193 # size is rawsize. note: ELLIPSIS is known to not change the content.
1191 # size is rawsize. note: ELLIPSIS is known to not change the content.
1194 flags = self.flags(rev)
1192 flags = self.flags(rev)
1195 if flags & (flagutil.REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
1193 if flags & (flagutil.REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
1196 return self.rawsize(rev)
1194 return self.rawsize(rev)
1197
1195
1198 return len(self.revision(rev))
1196 return len(self.revision(rev))
1199
1197
1200 def fast_rank(self, rev):
1198 def fast_rank(self, rev):
1201 """Return the rank of a revision if already known, or None otherwise.
1199 """Return the rank of a revision if already known, or None otherwise.
1202
1200
1203 The rank of a revision is the size of the sub-graph it defines as a
1201 The rank of a revision is the size of the sub-graph it defines as a
1204 head. Equivalently, the rank of a revision `r` is the size of the set
1202 head. Equivalently, the rank of a revision `r` is the size of the set
1205 `ancestors(r)`, `r` included.
1203 `ancestors(r)`, `r` included.
1206
1204
1207 This method returns the rank retrieved from the revlog in constant
1205 This method returns the rank retrieved from the revlog in constant
1208 time. It makes no attempt at computing unknown values for versions of
1206 time. It makes no attempt at computing unknown values for versions of
1209 the revlog which do not persist the rank.
1207 the revlog which do not persist the rank.
1210 """
1208 """
1211 rank = self.index[rev][ENTRY_RANK]
1209 rank = self.index[rev][ENTRY_RANK]
1212 if self._format_version != CHANGELOGV2 or rank == RANK_UNKNOWN:
1210 if self._format_version != CHANGELOGV2 or rank == RANK_UNKNOWN:
1213 return None
1211 return None
1214 if rev == nullrev:
1212 if rev == nullrev:
1215 return 0 # convention
1213 return 0 # convention
1216 return rank
1214 return rank
1217
1215
1218 def chainbase(self, rev):
1216 def chainbase(self, rev):
1219 base = self._chainbasecache.get(rev)
1217 base = self._chainbasecache.get(rev)
1220 if base is not None:
1218 if base is not None:
1221 return base
1219 return base
1222
1220
1223 index = self.index
1221 index = self.index
1224 iterrev = rev
1222 iterrev = rev
1225 base = index[iterrev][3]
1223 base = index[iterrev][3]
1226 while base != iterrev:
1224 while base != iterrev:
1227 iterrev = base
1225 iterrev = base
1228 base = index[iterrev][3]
1226 base = index[iterrev][3]
1229
1227
1230 self._chainbasecache[rev] = base
1228 self._chainbasecache[rev] = base
1231 return base
1229 return base
1232
1230
1233 def linkrev(self, rev):
1231 def linkrev(self, rev):
1234 return self.index[rev][4]
1232 return self.index[rev][4]
1235
1233
1236 def parentrevs(self, rev):
1234 def parentrevs(self, rev):
1237 try:
1235 try:
1238 entry = self.index[rev]
1236 entry = self.index[rev]
1239 except IndexError:
1237 except IndexError:
1240 if rev == wdirrev:
1238 if rev == wdirrev:
1241 raise error.WdirUnsupported
1239 raise error.WdirUnsupported
1242 raise
1240 raise
1243
1241
1244 if self.canonical_parent_order and entry[5] == nullrev:
1242 if self.canonical_parent_order and entry[5] == nullrev:
1245 return entry[6], entry[5]
1243 return entry[6], entry[5]
1246 else:
1244 else:
1247 return entry[5], entry[6]
1245 return entry[5], entry[6]
1248
1246
1249 # fast parentrevs(rev) where rev isn't filtered
1247 # fast parentrevs(rev) where rev isn't filtered
1250 _uncheckedparentrevs = parentrevs
1248 _uncheckedparentrevs = parentrevs
1251
1249
1252 def node(self, rev):
1250 def node(self, rev):
1253 try:
1251 try:
1254 return self.index[rev][7]
1252 return self.index[rev][7]
1255 except IndexError:
1253 except IndexError:
1256 if rev == wdirrev:
1254 if rev == wdirrev:
1257 raise error.WdirUnsupported
1255 raise error.WdirUnsupported
1258 raise
1256 raise
1259
1257
1260 # Derived from index values.
1258 # Derived from index values.
1261
1259
1262 def end(self, rev):
1260 def end(self, rev):
1263 return self.start(rev) + self.length(rev)
1261 return self.start(rev) + self.length(rev)
1264
1262
1265 def parents(self, node):
1263 def parents(self, node):
1266 i = self.index
1264 i = self.index
1267 d = i[self.rev(node)]
1265 d = i[self.rev(node)]
1268 # inline node() to avoid function call overhead
1266 # inline node() to avoid function call overhead
1269 if self.canonical_parent_order and d[5] == self.nullid:
1267 if self.canonical_parent_order and d[5] == self.nullid:
1270 return i[d[6]][7], i[d[5]][7]
1268 return i[d[6]][7], i[d[5]][7]
1271 else:
1269 else:
1272 return i[d[5]][7], i[d[6]][7]
1270 return i[d[5]][7], i[d[6]][7]
1273
1271
1274 def chainlen(self, rev):
1272 def chainlen(self, rev):
1275 return self._chaininfo(rev)[0]
1273 return self._chaininfo(rev)[0]
1276
1274
1277 def _chaininfo(self, rev):
1275 def _chaininfo(self, rev):
1278 chaininfocache = self._chaininfocache
1276 chaininfocache = self._chaininfocache
1279 if rev in chaininfocache:
1277 if rev in chaininfocache:
1280 return chaininfocache[rev]
1278 return chaininfocache[rev]
1281 index = self.index
1279 index = self.index
1282 generaldelta = self._generaldelta
1280 generaldelta = self._generaldelta
1283 iterrev = rev
1281 iterrev = rev
1284 e = index[iterrev]
1282 e = index[iterrev]
1285 clen = 0
1283 clen = 0
1286 compresseddeltalen = 0
1284 compresseddeltalen = 0
1287 while iterrev != e[3]:
1285 while iterrev != e[3]:
1288 clen += 1
1286 clen += 1
1289 compresseddeltalen += e[1]
1287 compresseddeltalen += e[1]
1290 if generaldelta:
1288 if generaldelta:
1291 iterrev = e[3]
1289 iterrev = e[3]
1292 else:
1290 else:
1293 iterrev -= 1
1291 iterrev -= 1
1294 if iterrev in chaininfocache:
1292 if iterrev in chaininfocache:
1295 t = chaininfocache[iterrev]
1293 t = chaininfocache[iterrev]
1296 clen += t[0]
1294 clen += t[0]
1297 compresseddeltalen += t[1]
1295 compresseddeltalen += t[1]
1298 break
1296 break
1299 e = index[iterrev]
1297 e = index[iterrev]
1300 else:
1298 else:
1301 # Add text length of base since decompressing that also takes
1299 # Add text length of base since decompressing that also takes
1302 # work. For cache hits the length is already included.
1300 # work. For cache hits the length is already included.
1303 compresseddeltalen += e[1]
1301 compresseddeltalen += e[1]
1304 r = (clen, compresseddeltalen)
1302 r = (clen, compresseddeltalen)
1305 chaininfocache[rev] = r
1303 chaininfocache[rev] = r
1306 return r
1304 return r
1307
1305
1308 def _deltachain(self, rev, stoprev=None):
1306 def _deltachain(self, rev, stoprev=None):
1309 """Obtain the delta chain for a revision.
1307 """Obtain the delta chain for a revision.
1310
1308
1311 ``stoprev`` specifies a revision to stop at. If not specified, we
1309 ``stoprev`` specifies a revision to stop at. If not specified, we
1312 stop at the base of the chain.
1310 stop at the base of the chain.
1313
1311
1314 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
1312 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
1315 revs in ascending order and ``stopped`` is a bool indicating whether
1313 revs in ascending order and ``stopped`` is a bool indicating whether
1316 ``stoprev`` was hit.
1314 ``stoprev`` was hit.
1317 """
1315 """
1318 # Try C implementation.
1316 # Try C implementation.
1319 try:
1317 try:
1320 return self.index.deltachain(rev, stoprev, self._generaldelta)
1318 return self.index.deltachain(rev, stoprev, self._generaldelta)
1321 except AttributeError:
1319 except AttributeError:
1322 pass
1320 pass
1323
1321
1324 chain = []
1322 chain = []
1325
1323
1326 # Alias to prevent attribute lookup in tight loop.
1324 # Alias to prevent attribute lookup in tight loop.
1327 index = self.index
1325 index = self.index
1328 generaldelta = self._generaldelta
1326 generaldelta = self._generaldelta
1329
1327
1330 iterrev = rev
1328 iterrev = rev
1331 e = index[iterrev]
1329 e = index[iterrev]
1332 while iterrev != e[3] and iterrev != stoprev:
1330 while iterrev != e[3] and iterrev != stoprev:
1333 chain.append(iterrev)
1331 chain.append(iterrev)
1334 if generaldelta:
1332 if generaldelta:
1335 iterrev = e[3]
1333 iterrev = e[3]
1336 else:
1334 else:
1337 iterrev -= 1
1335 iterrev -= 1
1338 e = index[iterrev]
1336 e = index[iterrev]
1339
1337
1340 if iterrev == stoprev:
1338 if iterrev == stoprev:
1341 stopped = True
1339 stopped = True
1342 else:
1340 else:
1343 chain.append(iterrev)
1341 chain.append(iterrev)
1344 stopped = False
1342 stopped = False
1345
1343
1346 chain.reverse()
1344 chain.reverse()
1347 return chain, stopped
1345 return chain, stopped
1348
1346
1349 def ancestors(self, revs, stoprev=0, inclusive=False):
1347 def ancestors(self, revs, stoprev=0, inclusive=False):
1350 """Generate the ancestors of 'revs' in reverse revision order.
1348 """Generate the ancestors of 'revs' in reverse revision order.
1351 Does not generate revs lower than stoprev.
1349 Does not generate revs lower than stoprev.
1352
1350
1353 See the documentation for ancestor.lazyancestors for more details."""
1351 See the documentation for ancestor.lazyancestors for more details."""
1354
1352
1355 # first, make sure start revisions aren't filtered
1353 # first, make sure start revisions aren't filtered
1356 revs = list(revs)
1354 revs = list(revs)
1357 checkrev = self.node
1355 checkrev = self.node
1358 for r in revs:
1356 for r in revs:
1359 checkrev(r)
1357 checkrev(r)
1360 # and we're sure ancestors aren't filtered as well
1358 # and we're sure ancestors aren't filtered as well
1361
1359
1362 if rustancestor is not None and self.index.rust_ext_compat:
1360 if rustancestor is not None and self.index.rust_ext_compat:
1363 lazyancestors = rustancestor.LazyAncestors
1361 lazyancestors = rustancestor.LazyAncestors
1364 arg = self.index
1362 arg = self.index
1365 else:
1363 else:
1366 lazyancestors = ancestor.lazyancestors
1364 lazyancestors = ancestor.lazyancestors
1367 arg = self._uncheckedparentrevs
1365 arg = self._uncheckedparentrevs
1368 return lazyancestors(arg, revs, stoprev=stoprev, inclusive=inclusive)
1366 return lazyancestors(arg, revs, stoprev=stoprev, inclusive=inclusive)
1369
1367
1370 def descendants(self, revs):
1368 def descendants(self, revs):
1371 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
1369 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
1372
1370
1373 def findcommonmissing(self, common=None, heads=None):
1371 def findcommonmissing(self, common=None, heads=None):
1374 """Return a tuple of the ancestors of common and the ancestors of heads
1372 """Return a tuple of the ancestors of common and the ancestors of heads
1375 that are not ancestors of common. In revset terminology, we return the
1373 that are not ancestors of common. In revset terminology, we return the
1376 tuple:
1374 tuple:
1377
1375
1378 ::common, (::heads) - (::common)
1376 ::common, (::heads) - (::common)
1379
1377
1380 The list is sorted by revision number, meaning it is
1378 The list is sorted by revision number, meaning it is
1381 topologically sorted.
1379 topologically sorted.
1382
1380
1383 'heads' and 'common' are both lists of node IDs. If heads is
1381 'heads' and 'common' are both lists of node IDs. If heads is
1384 not supplied, uses all of the revlog's heads. If common is not
1382 not supplied, uses all of the revlog's heads. If common is not
1385 supplied, uses nullid."""
1383 supplied, uses nullid."""
1386 if common is None:
1384 if common is None:
1387 common = [self.nullid]
1385 common = [self.nullid]
1388 if heads is None:
1386 if heads is None:
1389 heads = self.heads()
1387 heads = self.heads()
1390
1388
1391 common = [self.rev(n) for n in common]
1389 common = [self.rev(n) for n in common]
1392 heads = [self.rev(n) for n in heads]
1390 heads = [self.rev(n) for n in heads]
1393
1391
1394 # we want the ancestors, but inclusive
1392 # we want the ancestors, but inclusive
1395 class lazyset:
1393 class lazyset:
1396 def __init__(self, lazyvalues):
1394 def __init__(self, lazyvalues):
1397 self.addedvalues = set()
1395 self.addedvalues = set()
1398 self.lazyvalues = lazyvalues
1396 self.lazyvalues = lazyvalues
1399
1397
1400 def __contains__(self, value):
1398 def __contains__(self, value):
1401 return value in self.addedvalues or value in self.lazyvalues
1399 return value in self.addedvalues or value in self.lazyvalues
1402
1400
1403 def __iter__(self):
1401 def __iter__(self):
1404 added = self.addedvalues
1402 added = self.addedvalues
1405 for r in added:
1403 for r in added:
1406 yield r
1404 yield r
1407 for r in self.lazyvalues:
1405 for r in self.lazyvalues:
1408 if not r in added:
1406 if not r in added:
1409 yield r
1407 yield r
1410
1408
1411 def add(self, value):
1409 def add(self, value):
1412 self.addedvalues.add(value)
1410 self.addedvalues.add(value)
1413
1411
1414 def update(self, values):
1412 def update(self, values):
1415 self.addedvalues.update(values)
1413 self.addedvalues.update(values)
1416
1414
1417 has = lazyset(self.ancestors(common))
1415 has = lazyset(self.ancestors(common))
1418 has.add(nullrev)
1416 has.add(nullrev)
1419 has.update(common)
1417 has.update(common)
1420
1418
1421 # take all ancestors from heads that aren't in has
1419 # take all ancestors from heads that aren't in has
1422 missing = set()
1420 missing = set()
1423 visit = collections.deque(r for r in heads if r not in has)
1421 visit = collections.deque(r for r in heads if r not in has)
1424 while visit:
1422 while visit:
1425 r = visit.popleft()
1423 r = visit.popleft()
1426 if r in missing:
1424 if r in missing:
1427 continue
1425 continue
1428 else:
1426 else:
1429 missing.add(r)
1427 missing.add(r)
1430 for p in self.parentrevs(r):
1428 for p in self.parentrevs(r):
1431 if p not in has:
1429 if p not in has:
1432 visit.append(p)
1430 visit.append(p)
1433 missing = list(missing)
1431 missing = list(missing)
1434 missing.sort()
1432 missing.sort()
1435 return has, [self.node(miss) for miss in missing]
1433 return has, [self.node(miss) for miss in missing]
1436
1434
1437 def incrementalmissingrevs(self, common=None):
1435 def incrementalmissingrevs(self, common=None):
1438 """Return an object that can be used to incrementally compute the
1436 """Return an object that can be used to incrementally compute the
1439 revision numbers of the ancestors of arbitrary sets that are not
1437 revision numbers of the ancestors of arbitrary sets that are not
1440 ancestors of common. This is an ancestor.incrementalmissingancestors
1438 ancestors of common. This is an ancestor.incrementalmissingancestors
1441 object.
1439 object.
1442
1440
1443 'common' is a list of revision numbers. If common is not supplied, uses
1441 'common' is a list of revision numbers. If common is not supplied, uses
1444 nullrev.
1442 nullrev.
1445 """
1443 """
1446 if common is None:
1444 if common is None:
1447 common = [nullrev]
1445 common = [nullrev]
1448
1446
1449 if rustancestor is not None and self.index.rust_ext_compat:
1447 if rustancestor is not None and self.index.rust_ext_compat:
1450 return rustancestor.MissingAncestors(self.index, common)
1448 return rustancestor.MissingAncestors(self.index, common)
1451 return ancestor.incrementalmissingancestors(self.parentrevs, common)
1449 return ancestor.incrementalmissingancestors(self.parentrevs, common)
1452
1450
1453 def findmissingrevs(self, common=None, heads=None):
1451 def findmissingrevs(self, common=None, heads=None):
1454 """Return the revision numbers of the ancestors of heads that
1452 """Return the revision numbers of the ancestors of heads that
1455 are not ancestors of common.
1453 are not ancestors of common.
1456
1454
1457 More specifically, return a list of revision numbers corresponding to
1455 More specifically, return a list of revision numbers corresponding to
1458 nodes N such that every N satisfies the following constraints:
1456 nodes N such that every N satisfies the following constraints:
1459
1457
1460 1. N is an ancestor of some node in 'heads'
1458 1. N is an ancestor of some node in 'heads'
1461 2. N is not an ancestor of any node in 'common'
1459 2. N is not an ancestor of any node in 'common'
1462
1460
1463 The list is sorted by revision number, meaning it is
1461 The list is sorted by revision number, meaning it is
1464 topologically sorted.
1462 topologically sorted.
1465
1463
1466 'heads' and 'common' are both lists of revision numbers. If heads is
1464 'heads' and 'common' are both lists of revision numbers. If heads is
1467 not supplied, uses all of the revlog's heads. If common is not
1465 not supplied, uses all of the revlog's heads. If common is not
1468 supplied, uses nullid."""
1466 supplied, uses nullid."""
1469 if common is None:
1467 if common is None:
1470 common = [nullrev]
1468 common = [nullrev]
1471 if heads is None:
1469 if heads is None:
1472 heads = self.headrevs()
1470 heads = self.headrevs()
1473
1471
1474 inc = self.incrementalmissingrevs(common=common)
1472 inc = self.incrementalmissingrevs(common=common)
1475 return inc.missingancestors(heads)
1473 return inc.missingancestors(heads)
1476
1474
1477 def findmissing(self, common=None, heads=None):
1475 def findmissing(self, common=None, heads=None):
1478 """Return the ancestors of heads that are not ancestors of common.
1476 """Return the ancestors of heads that are not ancestors of common.
1479
1477
1480 More specifically, return a list of nodes N such that every N
1478 More specifically, return a list of nodes N such that every N
1481 satisfies the following constraints:
1479 satisfies the following constraints:
1482
1480
1483 1. N is an ancestor of some node in 'heads'
1481 1. N is an ancestor of some node in 'heads'
1484 2. N is not an ancestor of any node in 'common'
1482 2. N is not an ancestor of any node in 'common'
1485
1483
1486 The list is sorted by revision number, meaning it is
1484 The list is sorted by revision number, meaning it is
1487 topologically sorted.
1485 topologically sorted.
1488
1486
1489 'heads' and 'common' are both lists of node IDs. If heads is
1487 'heads' and 'common' are both lists of node IDs. If heads is
1490 not supplied, uses all of the revlog's heads. If common is not
1488 not supplied, uses all of the revlog's heads. If common is not
1491 supplied, uses nullid."""
1489 supplied, uses nullid."""
1492 if common is None:
1490 if common is None:
1493 common = [self.nullid]
1491 common = [self.nullid]
1494 if heads is None:
1492 if heads is None:
1495 heads = self.heads()
1493 heads = self.heads()
1496
1494
1497 common = [self.rev(n) for n in common]
1495 common = [self.rev(n) for n in common]
1498 heads = [self.rev(n) for n in heads]
1496 heads = [self.rev(n) for n in heads]
1499
1497
1500 inc = self.incrementalmissingrevs(common=common)
1498 inc = self.incrementalmissingrevs(common=common)
1501 return [self.node(r) for r in inc.missingancestors(heads)]
1499 return [self.node(r) for r in inc.missingancestors(heads)]
1502
1500
1503 def nodesbetween(self, roots=None, heads=None):
1501 def nodesbetween(self, roots=None, heads=None):
1504 """Return a topological path from 'roots' to 'heads'.
1502 """Return a topological path from 'roots' to 'heads'.
1505
1503
1506 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
1504 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
1507 topologically sorted list of all nodes N that satisfy both of
1505 topologically sorted list of all nodes N that satisfy both of
1508 these constraints:
1506 these constraints:
1509
1507
1510 1. N is a descendant of some node in 'roots'
1508 1. N is a descendant of some node in 'roots'
1511 2. N is an ancestor of some node in 'heads'
1509 2. N is an ancestor of some node in 'heads'
1512
1510
1513 Every node is considered to be both a descendant and an ancestor
1511 Every node is considered to be both a descendant and an ancestor
1514 of itself, so every reachable node in 'roots' and 'heads' will be
1512 of itself, so every reachable node in 'roots' and 'heads' will be
1515 included in 'nodes'.
1513 included in 'nodes'.
1516
1514
1517 'outroots' is the list of reachable nodes in 'roots', i.e., the
1515 'outroots' is the list of reachable nodes in 'roots', i.e., the
1518 subset of 'roots' that is returned in 'nodes'. Likewise,
1516 subset of 'roots' that is returned in 'nodes'. Likewise,
1519 'outheads' is the subset of 'heads' that is also in 'nodes'.
1517 'outheads' is the subset of 'heads' that is also in 'nodes'.
1520
1518
1521 'roots' and 'heads' are both lists of node IDs. If 'roots' is
1519 'roots' and 'heads' are both lists of node IDs. If 'roots' is
1522 unspecified, uses nullid as the only root. If 'heads' is
1520 unspecified, uses nullid as the only root. If 'heads' is
1523 unspecified, uses list of all of the revlog's heads."""
1521 unspecified, uses list of all of the revlog's heads."""
1524 nonodes = ([], [], [])
1522 nonodes = ([], [], [])
1525 if roots is not None:
1523 if roots is not None:
1526 roots = list(roots)
1524 roots = list(roots)
1527 if not roots:
1525 if not roots:
1528 return nonodes
1526 return nonodes
1529 lowestrev = min([self.rev(n) for n in roots])
1527 lowestrev = min([self.rev(n) for n in roots])
1530 else:
1528 else:
1531 roots = [self.nullid] # Everybody's a descendant of nullid
1529 roots = [self.nullid] # Everybody's a descendant of nullid
1532 lowestrev = nullrev
1530 lowestrev = nullrev
1533 if (lowestrev == nullrev) and (heads is None):
1531 if (lowestrev == nullrev) and (heads is None):
1534 # We want _all_ the nodes!
1532 # We want _all_ the nodes!
1535 return (
1533 return (
1536 [self.node(r) for r in self],
1534 [self.node(r) for r in self],
1537 [self.nullid],
1535 [self.nullid],
1538 list(self.heads()),
1536 list(self.heads()),
1539 )
1537 )
1540 if heads is None:
1538 if heads is None:
1541 # All nodes are ancestors, so the latest ancestor is the last
1539 # All nodes are ancestors, so the latest ancestor is the last
1542 # node.
1540 # node.
1543 highestrev = len(self) - 1
1541 highestrev = len(self) - 1
1544 # Set ancestors to None to signal that every node is an ancestor.
1542 # Set ancestors to None to signal that every node is an ancestor.
1545 ancestors = None
1543 ancestors = None
1546 # Set heads to an empty dictionary for later discovery of heads
1544 # Set heads to an empty dictionary for later discovery of heads
1547 heads = {}
1545 heads = {}
1548 else:
1546 else:
1549 heads = list(heads)
1547 heads = list(heads)
1550 if not heads:
1548 if not heads:
1551 return nonodes
1549 return nonodes
1552 ancestors = set()
1550 ancestors = set()
1553 # Turn heads into a dictionary so we can remove 'fake' heads.
1551 # Turn heads into a dictionary so we can remove 'fake' heads.
1554 # Also, later we will be using it to filter out the heads we can't
1552 # Also, later we will be using it to filter out the heads we can't
1555 # find from roots.
1553 # find from roots.
1556 heads = dict.fromkeys(heads, False)
1554 heads = dict.fromkeys(heads, False)
1557 # Start at the top and keep marking parents until we're done.
1555 # Start at the top and keep marking parents until we're done.
1558 nodestotag = set(heads)
1556 nodestotag = set(heads)
1559 # Remember where the top was so we can use it as a limit later.
1557 # Remember where the top was so we can use it as a limit later.
1560 highestrev = max([self.rev(n) for n in nodestotag])
1558 highestrev = max([self.rev(n) for n in nodestotag])
1561 while nodestotag:
1559 while nodestotag:
1562 # grab a node to tag
1560 # grab a node to tag
1563 n = nodestotag.pop()
1561 n = nodestotag.pop()
1564 # Never tag nullid
1562 # Never tag nullid
1565 if n == self.nullid:
1563 if n == self.nullid:
1566 continue
1564 continue
1567 # A node's revision number represents its place in a
1565 # A node's revision number represents its place in a
1568 # topologically sorted list of nodes.
1566 # topologically sorted list of nodes.
1569 r = self.rev(n)
1567 r = self.rev(n)
1570 if r >= lowestrev:
1568 if r >= lowestrev:
1571 if n not in ancestors:
1569 if n not in ancestors:
1572 # If we are possibly a descendant of one of the roots
1570 # If we are possibly a descendant of one of the roots
1573 # and we haven't already been marked as an ancestor
1571 # and we haven't already been marked as an ancestor
1574 ancestors.add(n) # Mark as ancestor
1572 ancestors.add(n) # Mark as ancestor
1575 # Add non-nullid parents to list of nodes to tag.
1573 # Add non-nullid parents to list of nodes to tag.
1576 nodestotag.update(
1574 nodestotag.update(
1577 [p for p in self.parents(n) if p != self.nullid]
1575 [p for p in self.parents(n) if p != self.nullid]
1578 )
1576 )
1579 elif n in heads: # We've seen it before, is it a fake head?
1577 elif n in heads: # We've seen it before, is it a fake head?
1580 # So it is, real heads should not be the ancestors of
1578 # So it is, real heads should not be the ancestors of
1581 # any other heads.
1579 # any other heads.
1582 heads.pop(n)
1580 heads.pop(n)
1583 if not ancestors:
1581 if not ancestors:
1584 return nonodes
1582 return nonodes
1585 # Now that we have our set of ancestors, we want to remove any
1583 # Now that we have our set of ancestors, we want to remove any
1586 # roots that are not ancestors.
1584 # roots that are not ancestors.
1587
1585
1588 # If one of the roots was nullid, everything is included anyway.
1586 # If one of the roots was nullid, everything is included anyway.
1589 if lowestrev > nullrev:
1587 if lowestrev > nullrev:
1590 # But, since we weren't, let's recompute the lowest rev to not
1588 # But, since we weren't, let's recompute the lowest rev to not
1591 # include roots that aren't ancestors.
1589 # include roots that aren't ancestors.
1592
1590
1593 # Filter out roots that aren't ancestors of heads
1591 # Filter out roots that aren't ancestors of heads
1594 roots = [root for root in roots if root in ancestors]
1592 roots = [root for root in roots if root in ancestors]
1595 # Recompute the lowest revision
1593 # Recompute the lowest revision
1596 if roots:
1594 if roots:
1597 lowestrev = min([self.rev(root) for root in roots])
1595 lowestrev = min([self.rev(root) for root in roots])
1598 else:
1596 else:
1599 # No more roots? Return empty list
1597 # No more roots? Return empty list
1600 return nonodes
1598 return nonodes
1601 else:
1599 else:
1602 # We are descending from nullid, and don't need to care about
1600 # We are descending from nullid, and don't need to care about
1603 # any other roots.
1601 # any other roots.
1604 lowestrev = nullrev
1602 lowestrev = nullrev
1605 roots = [self.nullid]
1603 roots = [self.nullid]
1606 # Transform our roots list into a set.
1604 # Transform our roots list into a set.
1607 descendants = set(roots)
1605 descendants = set(roots)
1608 # Also, keep the original roots so we can filter out roots that aren't
1606 # Also, keep the original roots so we can filter out roots that aren't
1609 # 'real' roots (i.e. are descended from other roots).
1607 # 'real' roots (i.e. are descended from other roots).
1610 roots = descendants.copy()
1608 roots = descendants.copy()
1611 # Our topologically sorted list of output nodes.
1609 # Our topologically sorted list of output nodes.
1612 orderedout = []
1610 orderedout = []
1613 # Don't start at nullid since we don't want nullid in our output list,
1611 # Don't start at nullid since we don't want nullid in our output list,
1614 # and if nullid shows up in descendants, empty parents will look like
1612 # and if nullid shows up in descendants, empty parents will look like
1615 # they're descendants.
1613 # they're descendants.
1616 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1614 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1617 n = self.node(r)
1615 n = self.node(r)
1618 isdescendant = False
1616 isdescendant = False
1619 if lowestrev == nullrev: # Everybody is a descendant of nullid
1617 if lowestrev == nullrev: # Everybody is a descendant of nullid
1620 isdescendant = True
1618 isdescendant = True
1621 elif n in descendants:
1619 elif n in descendants:
1622 # n is already a descendant
1620 # n is already a descendant
1623 isdescendant = True
1621 isdescendant = True
1624 # This check only needs to be done here because all the roots
1622 # This check only needs to be done here because all the roots
1625 # will start being marked is descendants before the loop.
1623 # will start being marked is descendants before the loop.
1626 if n in roots:
1624 if n in roots:
1627 # If n was a root, check if it's a 'real' root.
1625 # If n was a root, check if it's a 'real' root.
1628 p = tuple(self.parents(n))
1626 p = tuple(self.parents(n))
1629 # If any of its parents are descendants, it's not a root.
1627 # If any of its parents are descendants, it's not a root.
1630 if (p[0] in descendants) or (p[1] in descendants):
1628 if (p[0] in descendants) or (p[1] in descendants):
1631 roots.remove(n)
1629 roots.remove(n)
1632 else:
1630 else:
1633 p = tuple(self.parents(n))
1631 p = tuple(self.parents(n))
1634 # A node is a descendant if either of its parents are
1632 # A node is a descendant if either of its parents are
1635 # descendants. (We seeded the dependents list with the roots
1633 # descendants. (We seeded the dependents list with the roots
1636 # up there, remember?)
1634 # up there, remember?)
1637 if (p[0] in descendants) or (p[1] in descendants):
1635 if (p[0] in descendants) or (p[1] in descendants):
1638 descendants.add(n)
1636 descendants.add(n)
1639 isdescendant = True
1637 isdescendant = True
1640 if isdescendant and ((ancestors is None) or (n in ancestors)):
1638 if isdescendant and ((ancestors is None) or (n in ancestors)):
1641 # Only include nodes that are both descendants and ancestors.
1639 # Only include nodes that are both descendants and ancestors.
1642 orderedout.append(n)
1640 orderedout.append(n)
1643 if (ancestors is not None) and (n in heads):
1641 if (ancestors is not None) and (n in heads):
1644 # We're trying to figure out which heads are reachable
1642 # We're trying to figure out which heads are reachable
1645 # from roots.
1643 # from roots.
1646 # Mark this head as having been reached
1644 # Mark this head as having been reached
1647 heads[n] = True
1645 heads[n] = True
1648 elif ancestors is None:
1646 elif ancestors is None:
1649 # Otherwise, we're trying to discover the heads.
1647 # Otherwise, we're trying to discover the heads.
1650 # Assume this is a head because if it isn't, the next step
1648 # Assume this is a head because if it isn't, the next step
1651 # will eventually remove it.
1649 # will eventually remove it.
1652 heads[n] = True
1650 heads[n] = True
1653 # But, obviously its parents aren't.
1651 # But, obviously its parents aren't.
1654 for p in self.parents(n):
1652 for p in self.parents(n):
1655 heads.pop(p, None)
1653 heads.pop(p, None)
1656 heads = [head for head, flag in heads.items() if flag]
1654 heads = [head for head, flag in heads.items() if flag]
1657 roots = list(roots)
1655 roots = list(roots)
1658 assert orderedout
1656 assert orderedout
1659 assert roots
1657 assert roots
1660 assert heads
1658 assert heads
1661 return (orderedout, roots, heads)
1659 return (orderedout, roots, heads)
1662
1660
1663 def headrevs(self, revs=None):
1661 def headrevs(self, revs=None):
1664 if revs is None:
1662 if revs is None:
1665 try:
1663 try:
1666 return self.index.headrevs()
1664 return self.index.headrevs()
1667 except AttributeError:
1665 except AttributeError:
1668 return self._headrevs()
1666 return self._headrevs()
1669 if rustdagop is not None and self.index.rust_ext_compat:
1667 if rustdagop is not None and self.index.rust_ext_compat:
1670 return rustdagop.headrevs(self.index, revs)
1668 return rustdagop.headrevs(self.index, revs)
1671 return dagop.headrevs(revs, self._uncheckedparentrevs)
1669 return dagop.headrevs(revs, self._uncheckedparentrevs)
1672
1670
1673 def computephases(self, roots):
1671 def computephases(self, roots):
1674 return self.index.computephasesmapsets(roots)
1672 return self.index.computephasesmapsets(roots)
1675
1673
1676 def _headrevs(self):
1674 def _headrevs(self):
1677 count = len(self)
1675 count = len(self)
1678 if not count:
1676 if not count:
1679 return [nullrev]
1677 return [nullrev]
1680 # we won't iter over filtered rev so nobody is a head at start
1678 # we won't iter over filtered rev so nobody is a head at start
1681 ishead = [0] * (count + 1)
1679 ishead = [0] * (count + 1)
1682 index = self.index
1680 index = self.index
1683 for r in self:
1681 for r in self:
1684 ishead[r] = 1 # I may be an head
1682 ishead[r] = 1 # I may be an head
1685 e = index[r]
1683 e = index[r]
1686 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1684 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1687 return [r for r, val in enumerate(ishead) if val]
1685 return [r for r, val in enumerate(ishead) if val]
1688
1686
1689 def heads(self, start=None, stop=None):
1687 def heads(self, start=None, stop=None):
1690 """return the list of all nodes that have no children
1688 """return the list of all nodes that have no children
1691
1689
1692 if start is specified, only heads that are descendants of
1690 if start is specified, only heads that are descendants of
1693 start will be returned
1691 start will be returned
1694 if stop is specified, it will consider all the revs from stop
1692 if stop is specified, it will consider all the revs from stop
1695 as if they had no children
1693 as if they had no children
1696 """
1694 """
1697 if start is None and stop is None:
1695 if start is None and stop is None:
1698 if not len(self):
1696 if not len(self):
1699 return [self.nullid]
1697 return [self.nullid]
1700 return [self.node(r) for r in self.headrevs()]
1698 return [self.node(r) for r in self.headrevs()]
1701
1699
1702 if start is None:
1700 if start is None:
1703 start = nullrev
1701 start = nullrev
1704 else:
1702 else:
1705 start = self.rev(start)
1703 start = self.rev(start)
1706
1704
1707 stoprevs = {self.rev(n) for n in stop or []}
1705 stoprevs = {self.rev(n) for n in stop or []}
1708
1706
1709 revs = dagop.headrevssubset(
1707 revs = dagop.headrevssubset(
1710 self.revs, self.parentrevs, startrev=start, stoprevs=stoprevs
1708 self.revs, self.parentrevs, startrev=start, stoprevs=stoprevs
1711 )
1709 )
1712
1710
1713 return [self.node(rev) for rev in revs]
1711 return [self.node(rev) for rev in revs]
1714
1712
1715 def children(self, node):
1713 def children(self, node):
1716 """find the children of a given node"""
1714 """find the children of a given node"""
1717 c = []
1715 c = []
1718 p = self.rev(node)
1716 p = self.rev(node)
1719 for r in self.revs(start=p + 1):
1717 for r in self.revs(start=p + 1):
1720 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1718 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1721 if prevs:
1719 if prevs:
1722 for pr in prevs:
1720 for pr in prevs:
1723 if pr == p:
1721 if pr == p:
1724 c.append(self.node(r))
1722 c.append(self.node(r))
1725 elif p == nullrev:
1723 elif p == nullrev:
1726 c.append(self.node(r))
1724 c.append(self.node(r))
1727 return c
1725 return c
1728
1726
1729 def commonancestorsheads(self, a, b):
1727 def commonancestorsheads(self, a, b):
1730 """calculate all the heads of the common ancestors of nodes a and b"""
1728 """calculate all the heads of the common ancestors of nodes a and b"""
1731 a, b = self.rev(a), self.rev(b)
1729 a, b = self.rev(a), self.rev(b)
1732 ancs = self._commonancestorsheads(a, b)
1730 ancs = self._commonancestorsheads(a, b)
1733 return pycompat.maplist(self.node, ancs)
1731 return pycompat.maplist(self.node, ancs)
1734
1732
1735 def _commonancestorsheads(self, *revs):
1733 def _commonancestorsheads(self, *revs):
1736 """calculate all the heads of the common ancestors of revs"""
1734 """calculate all the heads of the common ancestors of revs"""
1737 try:
1735 try:
1738 ancs = self.index.commonancestorsheads(*revs)
1736 ancs = self.index.commonancestorsheads(*revs)
1739 except (AttributeError, OverflowError): # C implementation failed
1737 except (AttributeError, OverflowError): # C implementation failed
1740 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1738 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1741 return ancs
1739 return ancs
1742
1740
1743 def isancestor(self, a, b):
1741 def isancestor(self, a, b):
1744 """return True if node a is an ancestor of node b
1742 """return True if node a is an ancestor of node b
1745
1743
1746 A revision is considered an ancestor of itself."""
1744 A revision is considered an ancestor of itself."""
1747 a, b = self.rev(a), self.rev(b)
1745 a, b = self.rev(a), self.rev(b)
1748 return self.isancestorrev(a, b)
1746 return self.isancestorrev(a, b)
1749
1747
1750 def isancestorrev(self, a, b):
1748 def isancestorrev(self, a, b):
1751 """return True if revision a is an ancestor of revision b
1749 """return True if revision a is an ancestor of revision b
1752
1750
1753 A revision is considered an ancestor of itself.
1751 A revision is considered an ancestor of itself.
1754
1752
1755 The implementation of this is trivial but the use of
1753 The implementation of this is trivial but the use of
1756 reachableroots is not."""
1754 reachableroots is not."""
1757 if a == nullrev:
1755 if a == nullrev:
1758 return True
1756 return True
1759 elif a == b:
1757 elif a == b:
1760 return True
1758 return True
1761 elif a > b:
1759 elif a > b:
1762 return False
1760 return False
1763 return bool(self.reachableroots(a, [b], [a], includepath=False))
1761 return bool(self.reachableroots(a, [b], [a], includepath=False))
1764
1762
1765 def reachableroots(self, minroot, heads, roots, includepath=False):
1763 def reachableroots(self, minroot, heads, roots, includepath=False):
1766 """return (heads(::(<roots> and <roots>::<heads>)))
1764 """return (heads(::(<roots> and <roots>::<heads>)))
1767
1765
1768 If includepath is True, return (<roots>::<heads>)."""
1766 If includepath is True, return (<roots>::<heads>)."""
1769 try:
1767 try:
1770 return self.index.reachableroots2(
1768 return self.index.reachableroots2(
1771 minroot, heads, roots, includepath
1769 minroot, heads, roots, includepath
1772 )
1770 )
1773 except AttributeError:
1771 except AttributeError:
1774 return dagop._reachablerootspure(
1772 return dagop._reachablerootspure(
1775 self.parentrevs, minroot, roots, heads, includepath
1773 self.parentrevs, minroot, roots, heads, includepath
1776 )
1774 )
1777
1775
1778 def ancestor(self, a, b):
1776 def ancestor(self, a, b):
1779 """calculate the "best" common ancestor of nodes a and b"""
1777 """calculate the "best" common ancestor of nodes a and b"""
1780
1778
1781 a, b = self.rev(a), self.rev(b)
1779 a, b = self.rev(a), self.rev(b)
1782 try:
1780 try:
1783 ancs = self.index.ancestors(a, b)
1781 ancs = self.index.ancestors(a, b)
1784 except (AttributeError, OverflowError):
1782 except (AttributeError, OverflowError):
1785 ancs = ancestor.ancestors(self.parentrevs, a, b)
1783 ancs = ancestor.ancestors(self.parentrevs, a, b)
1786 if ancs:
1784 if ancs:
1787 # choose a consistent winner when there's a tie
1785 # choose a consistent winner when there's a tie
1788 return min(map(self.node, ancs))
1786 return min(map(self.node, ancs))
1789 return self.nullid
1787 return self.nullid
1790
1788
1791 def _match(self, id):
1789 def _match(self, id):
1792 if isinstance(id, int):
1790 if isinstance(id, int):
1793 # rev
1791 # rev
1794 return self.node(id)
1792 return self.node(id)
1795 if len(id) == self.nodeconstants.nodelen:
1793 if len(id) == self.nodeconstants.nodelen:
1796 # possibly a binary node
1794 # possibly a binary node
1797 # odds of a binary node being all hex in ASCII are 1 in 10**25
1795 # odds of a binary node being all hex in ASCII are 1 in 10**25
1798 try:
1796 try:
1799 node = id
1797 node = id
1800 self.rev(node) # quick search the index
1798 self.rev(node) # quick search the index
1801 return node
1799 return node
1802 except error.LookupError:
1800 except error.LookupError:
1803 pass # may be partial hex id
1801 pass # may be partial hex id
1804 try:
1802 try:
1805 # str(rev)
1803 # str(rev)
1806 rev = int(id)
1804 rev = int(id)
1807 if b"%d" % rev != id:
1805 if b"%d" % rev != id:
1808 raise ValueError
1806 raise ValueError
1809 if rev < 0:
1807 if rev < 0:
1810 rev = len(self) + rev
1808 rev = len(self) + rev
1811 if rev < 0 or rev >= len(self):
1809 if rev < 0 or rev >= len(self):
1812 raise ValueError
1810 raise ValueError
1813 return self.node(rev)
1811 return self.node(rev)
1814 except (ValueError, OverflowError):
1812 except (ValueError, OverflowError):
1815 pass
1813 pass
1816 if len(id) == 2 * self.nodeconstants.nodelen:
1814 if len(id) == 2 * self.nodeconstants.nodelen:
1817 try:
1815 try:
1818 # a full hex nodeid?
1816 # a full hex nodeid?
1819 node = bin(id)
1817 node = bin(id)
1820 self.rev(node)
1818 self.rev(node)
1821 return node
1819 return node
1822 except (binascii.Error, error.LookupError):
1820 except (binascii.Error, error.LookupError):
1823 pass
1821 pass
1824
1822
1825 def _partialmatch(self, id):
1823 def _partialmatch(self, id):
1826 # we don't care wdirfilenodeids as they should be always full hash
1824 # we don't care wdirfilenodeids as they should be always full hash
1827 maybewdir = self.nodeconstants.wdirhex.startswith(id)
1825 maybewdir = self.nodeconstants.wdirhex.startswith(id)
1828 ambiguous = False
1826 ambiguous = False
1829 try:
1827 try:
1830 partial = self.index.partialmatch(id)
1828 partial = self.index.partialmatch(id)
1831 if partial and self.hasnode(partial):
1829 if partial and self.hasnode(partial):
1832 if maybewdir:
1830 if maybewdir:
1833 # single 'ff...' match in radix tree, ambiguous with wdir
1831 # single 'ff...' match in radix tree, ambiguous with wdir
1834 ambiguous = True
1832 ambiguous = True
1835 else:
1833 else:
1836 return partial
1834 return partial
1837 elif maybewdir:
1835 elif maybewdir:
1838 # no 'ff...' match in radix tree, wdir identified
1836 # no 'ff...' match in radix tree, wdir identified
1839 raise error.WdirUnsupported
1837 raise error.WdirUnsupported
1840 else:
1838 else:
1841 return None
1839 return None
1842 except error.RevlogError:
1840 except error.RevlogError:
1843 # parsers.c radix tree lookup gave multiple matches
1841 # parsers.c radix tree lookup gave multiple matches
1844 # fast path: for unfiltered changelog, radix tree is accurate
1842 # fast path: for unfiltered changelog, radix tree is accurate
1845 if not getattr(self, 'filteredrevs', None):
1843 if not getattr(self, 'filteredrevs', None):
1846 ambiguous = True
1844 ambiguous = True
1847 # fall through to slow path that filters hidden revisions
1845 # fall through to slow path that filters hidden revisions
1848 except (AttributeError, ValueError):
1846 except (AttributeError, ValueError):
1849 # we are pure python, or key is not hex
1847 # we are pure python, or key is not hex
1850 pass
1848 pass
1851 if ambiguous:
1849 if ambiguous:
1852 raise error.AmbiguousPrefixLookupError(
1850 raise error.AmbiguousPrefixLookupError(
1853 id, self.display_id, _(b'ambiguous identifier')
1851 id, self.display_id, _(b'ambiguous identifier')
1854 )
1852 )
1855
1853
1856 if id in self._pcache:
1854 if id in self._pcache:
1857 return self._pcache[id]
1855 return self._pcache[id]
1858
1856
1859 if len(id) <= 40:
1857 if len(id) <= 40:
1860 # hex(node)[:...]
1858 # hex(node)[:...]
1861 l = len(id) // 2 * 2 # grab an even number of digits
1859 l = len(id) // 2 * 2 # grab an even number of digits
1862 try:
1860 try:
1863 # we're dropping the last digit, so let's check that it's hex,
1861 # we're dropping the last digit, so let's check that it's hex,
1864 # to avoid the expensive computation below if it's not
1862 # to avoid the expensive computation below if it's not
1865 if len(id) % 2 > 0:
1863 if len(id) % 2 > 0:
1866 if not (id[-1] in hexdigits):
1864 if not (id[-1] in hexdigits):
1867 return None
1865 return None
1868 prefix = bin(id[:l])
1866 prefix = bin(id[:l])
1869 except binascii.Error:
1867 except binascii.Error:
1870 pass
1868 pass
1871 else:
1869 else:
1872 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1870 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1873 nl = [
1871 nl = [
1874 n for n in nl if hex(n).startswith(id) and self.hasnode(n)
1872 n for n in nl if hex(n).startswith(id) and self.hasnode(n)
1875 ]
1873 ]
1876 if self.nodeconstants.nullhex.startswith(id):
1874 if self.nodeconstants.nullhex.startswith(id):
1877 nl.append(self.nullid)
1875 nl.append(self.nullid)
1878 if len(nl) > 0:
1876 if len(nl) > 0:
1879 if len(nl) == 1 and not maybewdir:
1877 if len(nl) == 1 and not maybewdir:
1880 self._pcache[id] = nl[0]
1878 self._pcache[id] = nl[0]
1881 return nl[0]
1879 return nl[0]
1882 raise error.AmbiguousPrefixLookupError(
1880 raise error.AmbiguousPrefixLookupError(
1883 id, self.display_id, _(b'ambiguous identifier')
1881 id, self.display_id, _(b'ambiguous identifier')
1884 )
1882 )
1885 if maybewdir:
1883 if maybewdir:
1886 raise error.WdirUnsupported
1884 raise error.WdirUnsupported
1887 return None
1885 return None
1888
1886
1889 def lookup(self, id):
1887 def lookup(self, id):
1890 """locate a node based on:
1888 """locate a node based on:
1891 - revision number or str(revision number)
1889 - revision number or str(revision number)
1892 - nodeid or subset of hex nodeid
1890 - nodeid or subset of hex nodeid
1893 """
1891 """
1894 n = self._match(id)
1892 n = self._match(id)
1895 if n is not None:
1893 if n is not None:
1896 return n
1894 return n
1897 n = self._partialmatch(id)
1895 n = self._partialmatch(id)
1898 if n:
1896 if n:
1899 return n
1897 return n
1900
1898
1901 raise error.LookupError(id, self.display_id, _(b'no match found'))
1899 raise error.LookupError(id, self.display_id, _(b'no match found'))
1902
1900
1903 def shortest(self, node, minlength=1):
1901 def shortest(self, node, minlength=1):
1904 """Find the shortest unambiguous prefix that matches node."""
1902 """Find the shortest unambiguous prefix that matches node."""
1905
1903
1906 def isvalid(prefix):
1904 def isvalid(prefix):
1907 try:
1905 try:
1908 matchednode = self._partialmatch(prefix)
1906 matchednode = self._partialmatch(prefix)
1909 except error.AmbiguousPrefixLookupError:
1907 except error.AmbiguousPrefixLookupError:
1910 return False
1908 return False
1911 except error.WdirUnsupported:
1909 except error.WdirUnsupported:
1912 # single 'ff...' match
1910 # single 'ff...' match
1913 return True
1911 return True
1914 if matchednode is None:
1912 if matchednode is None:
1915 raise error.LookupError(node, self.display_id, _(b'no node'))
1913 raise error.LookupError(node, self.display_id, _(b'no node'))
1916 return True
1914 return True
1917
1915
1918 def maybewdir(prefix):
1916 def maybewdir(prefix):
1919 return all(c == b'f' for c in pycompat.iterbytestr(prefix))
1917 return all(c == b'f' for c in pycompat.iterbytestr(prefix))
1920
1918
1921 hexnode = hex(node)
1919 hexnode = hex(node)
1922
1920
1923 def disambiguate(hexnode, minlength):
1921 def disambiguate(hexnode, minlength):
1924 """Disambiguate against wdirid."""
1922 """Disambiguate against wdirid."""
1925 for length in range(minlength, len(hexnode) + 1):
1923 for length in range(minlength, len(hexnode) + 1):
1926 prefix = hexnode[:length]
1924 prefix = hexnode[:length]
1927 if not maybewdir(prefix):
1925 if not maybewdir(prefix):
1928 return prefix
1926 return prefix
1929
1927
1930 if not getattr(self, 'filteredrevs', None):
1928 if not getattr(self, 'filteredrevs', None):
1931 try:
1929 try:
1932 length = max(self.index.shortest(node), minlength)
1930 length = max(self.index.shortest(node), minlength)
1933 return disambiguate(hexnode, length)
1931 return disambiguate(hexnode, length)
1934 except error.RevlogError:
1932 except error.RevlogError:
1935 if node != self.nodeconstants.wdirid:
1933 if node != self.nodeconstants.wdirid:
1936 raise error.LookupError(
1934 raise error.LookupError(
1937 node, self.display_id, _(b'no node')
1935 node, self.display_id, _(b'no node')
1938 )
1936 )
1939 except AttributeError:
1937 except AttributeError:
1940 # Fall through to pure code
1938 # Fall through to pure code
1941 pass
1939 pass
1942
1940
1943 if node == self.nodeconstants.wdirid:
1941 if node == self.nodeconstants.wdirid:
1944 for length in range(minlength, len(hexnode) + 1):
1942 for length in range(minlength, len(hexnode) + 1):
1945 prefix = hexnode[:length]
1943 prefix = hexnode[:length]
1946 if isvalid(prefix):
1944 if isvalid(prefix):
1947 return prefix
1945 return prefix
1948
1946
1949 for length in range(minlength, len(hexnode) + 1):
1947 for length in range(minlength, len(hexnode) + 1):
1950 prefix = hexnode[:length]
1948 prefix = hexnode[:length]
1951 if isvalid(prefix):
1949 if isvalid(prefix):
1952 return disambiguate(hexnode, length)
1950 return disambiguate(hexnode, length)
1953
1951
1954 def cmp(self, node, text):
1952 def cmp(self, node, text):
1955 """compare text with a given file revision
1953 """compare text with a given file revision
1956
1954
1957 returns True if text is different than what is stored.
1955 returns True if text is different than what is stored.
1958 """
1956 """
1959 p1, p2 = self.parents(node)
1957 p1, p2 = self.parents(node)
1960 return storageutil.hashrevisionsha1(text, p1, p2) != node
1958 return storageutil.hashrevisionsha1(text, p1, p2) != node
1961
1959
1962 def _getsegmentforrevs(self, startrev, endrev):
1960 def _getsegmentforrevs(self, startrev, endrev):
1963 """Obtain a segment of raw data corresponding to a range of revisions.
1961 """Obtain a segment of raw data corresponding to a range of revisions.
1964
1962
1965 Accepts the start and end revisions and an optional already-open
1963 Accepts the start and end revisions and an optional already-open
1966 file handle to be used for reading. If the file handle is read, its
1964 file handle to be used for reading. If the file handle is read, its
1967 seek position will not be preserved.
1965 seek position will not be preserved.
1968
1966
1969 Requests for data may be satisfied by a cache.
1967 Requests for data may be satisfied by a cache.
1970
1968
1971 Returns a 2-tuple of (offset, data) for the requested range of
1969 Returns a 2-tuple of (offset, data) for the requested range of
1972 revisions. Offset is the integer offset from the beginning of the
1970 revisions. Offset is the integer offset from the beginning of the
1973 revlog and data is a str or buffer of the raw byte data.
1971 revlog and data is a str or buffer of the raw byte data.
1974
1972
1975 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1973 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1976 to determine where each revision's data begins and ends.
1974 to determine where each revision's data begins and ends.
1977 """
1975 """
1978 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1976 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1979 # (functions are expensive).
1977 # (functions are expensive).
1980 index = self.index
1978 index = self.index
1981 istart = index[startrev]
1979 istart = index[startrev]
1982 start = int(istart[0] >> 16)
1980 start = int(istart[0] >> 16)
1983 if startrev == endrev:
1981 if startrev == endrev:
1984 end = start + istart[1]
1982 end = start + istart[1]
1985 else:
1983 else:
1986 iend = index[endrev]
1984 iend = index[endrev]
1987 end = int(iend[0] >> 16) + iend[1]
1985 end = int(iend[0] >> 16) + iend[1]
1988
1986
1989 if self._inline:
1987 if self._inline:
1990 start += (startrev + 1) * self.index.entry_size
1988 start += (startrev + 1) * self.index.entry_size
1991 end += (endrev + 1) * self.index.entry_size
1989 end += (endrev + 1) * self.index.entry_size
1992 length = end - start
1990 length = end - start
1993
1991
1994 return start, self._segmentfile.read_chunk(start, length)
1992 return start, self._segmentfile.read_chunk(start, length)
1995
1993
1996 def _chunk(self, rev):
1994 def _chunk(self, rev):
1997 """Obtain a single decompressed chunk for a revision.
1995 """Obtain a single decompressed chunk for a revision.
1998
1996
1999 Accepts an integer revision and an optional already-open file handle
1997 Accepts an integer revision and an optional already-open file handle
2000 to be used for reading. If used, the seek position of the file will not
1998 to be used for reading. If used, the seek position of the file will not
2001 be preserved.
1999 be preserved.
2002
2000
2003 Returns a str holding uncompressed data for the requested revision.
2001 Returns a str holding uncompressed data for the requested revision.
2004 """
2002 """
2005 compression_mode = self.index[rev][10]
2003 compression_mode = self.index[rev][10]
2006 data = self._getsegmentforrevs(rev, rev)[1]
2004 data = self._getsegmentforrevs(rev, rev)[1]
2007 if compression_mode == COMP_MODE_PLAIN:
2005 if compression_mode == COMP_MODE_PLAIN:
2008 return data
2006 return data
2009 elif compression_mode == COMP_MODE_DEFAULT:
2007 elif compression_mode == COMP_MODE_DEFAULT:
2010 return self._decompressor(data)
2008 return self._decompressor(data)
2011 elif compression_mode == COMP_MODE_INLINE:
2009 elif compression_mode == COMP_MODE_INLINE:
2012 return self.decompress(data)
2010 return self.decompress(data)
2013 else:
2011 else:
2014 msg = b'unknown compression mode %d'
2012 msg = b'unknown compression mode %d'
2015 msg %= compression_mode
2013 msg %= compression_mode
2016 raise error.RevlogError(msg)
2014 raise error.RevlogError(msg)
2017
2015
2018 def _chunks(self, revs, targetsize=None):
2016 def _chunks(self, revs, targetsize=None):
2019 """Obtain decompressed chunks for the specified revisions.
2017 """Obtain decompressed chunks for the specified revisions.
2020
2018
2021 Accepts an iterable of numeric revisions that are assumed to be in
2019 Accepts an iterable of numeric revisions that are assumed to be in
2022 ascending order. Also accepts an optional already-open file handle
2020 ascending order. Also accepts an optional already-open file handle
2023 to be used for reading. If used, the seek position of the file will
2021 to be used for reading. If used, the seek position of the file will
2024 not be preserved.
2022 not be preserved.
2025
2023
2026 This function is similar to calling ``self._chunk()`` multiple times,
2024 This function is similar to calling ``self._chunk()`` multiple times,
2027 but is faster.
2025 but is faster.
2028
2026
2029 Returns a list with decompressed data for each requested revision.
2027 Returns a list with decompressed data for each requested revision.
2030 """
2028 """
2031 if not revs:
2029 if not revs:
2032 return []
2030 return []
2033 start = self.start
2031 start = self.start
2034 length = self.length
2032 length = self.length
2035 inline = self._inline
2033 inline = self._inline
2036 iosize = self.index.entry_size
2034 iosize = self.index.entry_size
2037 buffer = util.buffer
2035 buffer = util.buffer
2038
2036
2039 l = []
2037 l = []
2040 ladd = l.append
2038 ladd = l.append
2041
2039
2042 if not self._withsparseread:
2040 if not self._withsparseread:
2043 slicedchunks = (revs,)
2041 slicedchunks = (revs,)
2044 else:
2042 else:
2045 slicedchunks = deltautil.slicechunk(
2043 slicedchunks = deltautil.slicechunk(
2046 self, revs, targetsize=targetsize
2044 self, revs, targetsize=targetsize
2047 )
2045 )
2048
2046
2049 for revschunk in slicedchunks:
2047 for revschunk in slicedchunks:
2050 firstrev = revschunk[0]
2048 firstrev = revschunk[0]
2051 # Skip trailing revisions with empty diff
2049 # Skip trailing revisions with empty diff
2052 for lastrev in revschunk[::-1]:
2050 for lastrev in revschunk[::-1]:
2053 if length(lastrev) != 0:
2051 if length(lastrev) != 0:
2054 break
2052 break
2055
2053
2056 try:
2054 try:
2057 offset, data = self._getsegmentforrevs(firstrev, lastrev)
2055 offset, data = self._getsegmentforrevs(firstrev, lastrev)
2058 except OverflowError:
2056 except OverflowError:
2059 # issue4215 - we can't cache a run of chunks greater than
2057 # issue4215 - we can't cache a run of chunks greater than
2060 # 2G on Windows
2058 # 2G on Windows
2061 return [self._chunk(rev) for rev in revschunk]
2059 return [self._chunk(rev) for rev in revschunk]
2062
2060
2063 decomp = self.decompress
2061 decomp = self.decompress
2064 # self._decompressor might be None, but will not be used in that case
2062 # self._decompressor might be None, but will not be used in that case
2065 def_decomp = self._decompressor
2063 def_decomp = self._decompressor
2066 for rev in revschunk:
2064 for rev in revschunk:
2067 chunkstart = start(rev)
2065 chunkstart = start(rev)
2068 if inline:
2066 if inline:
2069 chunkstart += (rev + 1) * iosize
2067 chunkstart += (rev + 1) * iosize
2070 chunklength = length(rev)
2068 chunklength = length(rev)
2071 comp_mode = self.index[rev][10]
2069 comp_mode = self.index[rev][10]
2072 c = buffer(data, chunkstart - offset, chunklength)
2070 c = buffer(data, chunkstart - offset, chunklength)
2073 if comp_mode == COMP_MODE_PLAIN:
2071 if comp_mode == COMP_MODE_PLAIN:
2074 ladd(c)
2072 ladd(c)
2075 elif comp_mode == COMP_MODE_INLINE:
2073 elif comp_mode == COMP_MODE_INLINE:
2076 ladd(decomp(c))
2074 ladd(decomp(c))
2077 elif comp_mode == COMP_MODE_DEFAULT:
2075 elif comp_mode == COMP_MODE_DEFAULT:
2078 ladd(def_decomp(c))
2076 ladd(def_decomp(c))
2079 else:
2077 else:
2080 msg = b'unknown compression mode %d'
2078 msg = b'unknown compression mode %d'
2081 msg %= comp_mode
2079 msg %= comp_mode
2082 raise error.RevlogError(msg)
2080 raise error.RevlogError(msg)
2083
2081
2084 return l
2082 return l
2085
2083
2086 def deltaparent(self, rev):
2084 def deltaparent(self, rev):
2087 """return deltaparent of the given revision"""
2085 """return deltaparent of the given revision"""
2088 base = self.index[rev][3]
2086 base = self.index[rev][3]
2089 if base == rev:
2087 if base == rev:
2090 return nullrev
2088 return nullrev
2091 elif self._generaldelta:
2089 elif self._generaldelta:
2092 return base
2090 return base
2093 else:
2091 else:
2094 return rev - 1
2092 return rev - 1
2095
2093
2096 def issnapshot(self, rev):
2094 def issnapshot(self, rev):
2097 """tells whether rev is a snapshot"""
2095 """tells whether rev is a snapshot"""
2098 if not self._sparserevlog:
2096 if not self._sparserevlog:
2099 return self.deltaparent(rev) == nullrev
2097 return self.deltaparent(rev) == nullrev
2100 elif hasattr(self.index, 'issnapshot'):
2098 elif hasattr(self.index, 'issnapshot'):
2101 # directly assign the method to cache the testing and access
2099 # directly assign the method to cache the testing and access
2102 self.issnapshot = self.index.issnapshot
2100 self.issnapshot = self.index.issnapshot
2103 return self.issnapshot(rev)
2101 return self.issnapshot(rev)
2104 if rev == nullrev:
2102 if rev == nullrev:
2105 return True
2103 return True
2106 entry = self.index[rev]
2104 entry = self.index[rev]
2107 base = entry[3]
2105 base = entry[3]
2108 if base == rev:
2106 if base == rev:
2109 return True
2107 return True
2110 if base == nullrev:
2108 if base == nullrev:
2111 return True
2109 return True
2112 p1 = entry[5]
2110 p1 = entry[5]
2113 while self.length(p1) == 0:
2111 while self.length(p1) == 0:
2114 b = self.deltaparent(p1)
2112 b = self.deltaparent(p1)
2115 if b == p1:
2113 if b == p1:
2116 break
2114 break
2117 p1 = b
2115 p1 = b
2118 p2 = entry[6]
2116 p2 = entry[6]
2119 while self.length(p2) == 0:
2117 while self.length(p2) == 0:
2120 b = self.deltaparent(p2)
2118 b = self.deltaparent(p2)
2121 if b == p2:
2119 if b == p2:
2122 break
2120 break
2123 p2 = b
2121 p2 = b
2124 if base == p1 or base == p2:
2122 if base == p1 or base == p2:
2125 return False
2123 return False
2126 return self.issnapshot(base)
2124 return self.issnapshot(base)
2127
2125
2128 def snapshotdepth(self, rev):
2126 def snapshotdepth(self, rev):
2129 """number of snapshot in the chain before this one"""
2127 """number of snapshot in the chain before this one"""
2130 if not self.issnapshot(rev):
2128 if not self.issnapshot(rev):
2131 raise error.ProgrammingError(b'revision %d not a snapshot')
2129 raise error.ProgrammingError(b'revision %d not a snapshot')
2132 return len(self._deltachain(rev)[0]) - 1
2130 return len(self._deltachain(rev)[0]) - 1
2133
2131
2134 def revdiff(self, rev1, rev2):
2132 def revdiff(self, rev1, rev2):
2135 """return or calculate a delta between two revisions
2133 """return or calculate a delta between two revisions
2136
2134
2137 The delta calculated is in binary form and is intended to be written to
2135 The delta calculated is in binary form and is intended to be written to
2138 revlog data directly. So this function needs raw revision data.
2136 revlog data directly. So this function needs raw revision data.
2139 """
2137 """
2140 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
2138 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
2141 return bytes(self._chunk(rev2))
2139 return bytes(self._chunk(rev2))
2142
2140
2143 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
2141 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
2144
2142
2145 def revision(self, nodeorrev):
2143 def revision(self, nodeorrev):
2146 """return an uncompressed revision of a given node or revision
2144 """return an uncompressed revision of a given node or revision
2147 number.
2145 number.
2148 """
2146 """
2149 return self._revisiondata(nodeorrev)
2147 return self._revisiondata(nodeorrev)
2150
2148
2151 def sidedata(self, nodeorrev):
2149 def sidedata(self, nodeorrev):
2152 """a map of extra data related to the changeset but not part of the hash
2150 """a map of extra data related to the changeset but not part of the hash
2153
2151
2154 This function currently return a dictionary. However, more advanced
2152 This function currently return a dictionary. However, more advanced
2155 mapping object will likely be used in the future for a more
2153 mapping object will likely be used in the future for a more
2156 efficient/lazy code.
2154 efficient/lazy code.
2157 """
2155 """
2158 # deal with <nodeorrev> argument type
2156 # deal with <nodeorrev> argument type
2159 if isinstance(nodeorrev, int):
2157 if isinstance(nodeorrev, int):
2160 rev = nodeorrev
2158 rev = nodeorrev
2161 else:
2159 else:
2162 rev = self.rev(nodeorrev)
2160 rev = self.rev(nodeorrev)
2163 return self._sidedata(rev)
2161 return self._sidedata(rev)
2164
2162
2165 def _revisiondata(self, nodeorrev, raw=False):
2163 def _revisiondata(self, nodeorrev, raw=False):
2166 # deal with <nodeorrev> argument type
2164 # deal with <nodeorrev> argument type
2167 if isinstance(nodeorrev, int):
2165 if isinstance(nodeorrev, int):
2168 rev = nodeorrev
2166 rev = nodeorrev
2169 node = self.node(rev)
2167 node = self.node(rev)
2170 else:
2168 else:
2171 node = nodeorrev
2169 node = nodeorrev
2172 rev = None
2170 rev = None
2173
2171
2174 # fast path the special `nullid` rev
2172 # fast path the special `nullid` rev
2175 if node == self.nullid:
2173 if node == self.nullid:
2176 return b""
2174 return b""
2177
2175
2178 # ``rawtext`` is the text as stored inside the revlog. Might be the
2176 # ``rawtext`` is the text as stored inside the revlog. Might be the
2179 # revision or might need to be processed to retrieve the revision.
2177 # revision or might need to be processed to retrieve the revision.
2180 rev, rawtext, validated = self._rawtext(node, rev)
2178 rev, rawtext, validated = self._rawtext(node, rev)
2181
2179
2182 if raw and validated:
2180 if raw and validated:
2183 # if we don't want to process the raw text and that raw
2181 # if we don't want to process the raw text and that raw
2184 # text is cached, we can exit early.
2182 # text is cached, we can exit early.
2185 return rawtext
2183 return rawtext
2186 if rev is None:
2184 if rev is None:
2187 rev = self.rev(node)
2185 rev = self.rev(node)
2188 # the revlog's flag for this revision
2186 # the revlog's flag for this revision
2189 # (usually alter its state or content)
2187 # (usually alter its state or content)
2190 flags = self.flags(rev)
2188 flags = self.flags(rev)
2191
2189
2192 if validated and flags == REVIDX_DEFAULT_FLAGS:
2190 if validated and flags == REVIDX_DEFAULT_FLAGS:
2193 # no extra flags set, no flag processor runs, text = rawtext
2191 # no extra flags set, no flag processor runs, text = rawtext
2194 return rawtext
2192 return rawtext
2195
2193
2196 if raw:
2194 if raw:
2197 validatehash = flagutil.processflagsraw(self, rawtext, flags)
2195 validatehash = flagutil.processflagsraw(self, rawtext, flags)
2198 text = rawtext
2196 text = rawtext
2199 else:
2197 else:
2200 r = flagutil.processflagsread(self, rawtext, flags)
2198 r = flagutil.processflagsread(self, rawtext, flags)
2201 text, validatehash = r
2199 text, validatehash = r
2202 if validatehash:
2200 if validatehash:
2203 self.checkhash(text, node, rev=rev)
2201 self.checkhash(text, node, rev=rev)
2204 if not validated:
2202 if not validated:
2205 self._revisioncache = (node, rev, rawtext)
2203 self._revisioncache = (node, rev, rawtext)
2206
2204
2207 return text
2205 return text
2208
2206
2209 def _rawtext(self, node, rev):
2207 def _rawtext(self, node, rev):
2210 """return the possibly unvalidated rawtext for a revision
2208 """return the possibly unvalidated rawtext for a revision
2211
2209
2212 returns (rev, rawtext, validated)
2210 returns (rev, rawtext, validated)
2213 """
2211 """
2214
2212
2215 # revision in the cache (could be useful to apply delta)
2213 # revision in the cache (could be useful to apply delta)
2216 cachedrev = None
2214 cachedrev = None
2217 # An intermediate text to apply deltas to
2215 # An intermediate text to apply deltas to
2218 basetext = None
2216 basetext = None
2219
2217
2220 # Check if we have the entry in cache
2218 # Check if we have the entry in cache
2221 # The cache entry looks like (node, rev, rawtext)
2219 # The cache entry looks like (node, rev, rawtext)
2222 if self._revisioncache:
2220 if self._revisioncache:
2223 if self._revisioncache[0] == node:
2221 if self._revisioncache[0] == node:
2224 return (rev, self._revisioncache[2], True)
2222 return (rev, self._revisioncache[2], True)
2225 cachedrev = self._revisioncache[1]
2223 cachedrev = self._revisioncache[1]
2226
2224
2227 if rev is None:
2225 if rev is None:
2228 rev = self.rev(node)
2226 rev = self.rev(node)
2229
2227
2230 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
2228 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
2231 if stopped:
2229 if stopped:
2232 basetext = self._revisioncache[2]
2230 basetext = self._revisioncache[2]
2233
2231
2234 # drop cache to save memory, the caller is expected to
2232 # drop cache to save memory, the caller is expected to
2235 # update self._revisioncache after validating the text
2233 # update self._revisioncache after validating the text
2236 self._revisioncache = None
2234 self._revisioncache = None
2237
2235
2238 targetsize = None
2236 targetsize = None
2239 rawsize = self.index[rev][2]
2237 rawsize = self.index[rev][2]
2240 if 0 <= rawsize:
2238 if 0 <= rawsize:
2241 targetsize = 4 * rawsize
2239 targetsize = 4 * rawsize
2242
2240
2243 bins = self._chunks(chain, targetsize=targetsize)
2241 bins = self._chunks(chain, targetsize=targetsize)
2244 if basetext is None:
2242 if basetext is None:
2245 basetext = bytes(bins[0])
2243 basetext = bytes(bins[0])
2246 bins = bins[1:]
2244 bins = bins[1:]
2247
2245
2248 rawtext = mdiff.patches(basetext, bins)
2246 rawtext = mdiff.patches(basetext, bins)
2249 del basetext # let us have a chance to free memory early
2247 del basetext # let us have a chance to free memory early
2250 return (rev, rawtext, False)
2248 return (rev, rawtext, False)
2251
2249
2252 def _sidedata(self, rev):
2250 def _sidedata(self, rev):
2253 """Return the sidedata for a given revision number."""
2251 """Return the sidedata for a given revision number."""
2254 index_entry = self.index[rev]
2252 index_entry = self.index[rev]
2255 sidedata_offset = index_entry[8]
2253 sidedata_offset = index_entry[8]
2256 sidedata_size = index_entry[9]
2254 sidedata_size = index_entry[9]
2257
2255
2258 if self._inline:
2256 if self._inline:
2259 sidedata_offset += self.index.entry_size * (1 + rev)
2257 sidedata_offset += self.index.entry_size * (1 + rev)
2260 if sidedata_size == 0:
2258 if sidedata_size == 0:
2261 return {}
2259 return {}
2262
2260
2263 if self._docket.sidedata_end < sidedata_offset + sidedata_size:
2261 if self._docket.sidedata_end < sidedata_offset + sidedata_size:
2264 filename = self._sidedatafile
2262 filename = self._sidedatafile
2265 end = self._docket.sidedata_end
2263 end = self._docket.sidedata_end
2266 offset = sidedata_offset
2264 offset = sidedata_offset
2267 length = sidedata_size
2265 length = sidedata_size
2268 m = FILE_TOO_SHORT_MSG % (filename, length, offset, end)
2266 m = FILE_TOO_SHORT_MSG % (filename, length, offset, end)
2269 raise error.RevlogError(m)
2267 raise error.RevlogError(m)
2270
2268
2271 comp_segment = self._segmentfile_sidedata.read_chunk(
2269 comp_segment = self._segmentfile_sidedata.read_chunk(
2272 sidedata_offset, sidedata_size
2270 sidedata_offset, sidedata_size
2273 )
2271 )
2274
2272
2275 comp = self.index[rev][11]
2273 comp = self.index[rev][11]
2276 if comp == COMP_MODE_PLAIN:
2274 if comp == COMP_MODE_PLAIN:
2277 segment = comp_segment
2275 segment = comp_segment
2278 elif comp == COMP_MODE_DEFAULT:
2276 elif comp == COMP_MODE_DEFAULT:
2279 segment = self._decompressor(comp_segment)
2277 segment = self._decompressor(comp_segment)
2280 elif comp == COMP_MODE_INLINE:
2278 elif comp == COMP_MODE_INLINE:
2281 segment = self.decompress(comp_segment)
2279 segment = self.decompress(comp_segment)
2282 else:
2280 else:
2283 msg = b'unknown compression mode %d'
2281 msg = b'unknown compression mode %d'
2284 msg %= comp
2282 msg %= comp
2285 raise error.RevlogError(msg)
2283 raise error.RevlogError(msg)
2286
2284
2287 sidedata = sidedatautil.deserialize_sidedata(segment)
2285 sidedata = sidedatautil.deserialize_sidedata(segment)
2288 return sidedata
2286 return sidedata
2289
2287
2290 def rawdata(self, nodeorrev):
2288 def rawdata(self, nodeorrev):
2291 """return an uncompressed raw data of a given node or revision number."""
2289 """return an uncompressed raw data of a given node or revision number."""
2292 return self._revisiondata(nodeorrev, raw=True)
2290 return self._revisiondata(nodeorrev, raw=True)
2293
2291
2294 def hash(self, text, p1, p2):
2292 def hash(self, text, p1, p2):
2295 """Compute a node hash.
2293 """Compute a node hash.
2296
2294
2297 Available as a function so that subclasses can replace the hash
2295 Available as a function so that subclasses can replace the hash
2298 as needed.
2296 as needed.
2299 """
2297 """
2300 return storageutil.hashrevisionsha1(text, p1, p2)
2298 return storageutil.hashrevisionsha1(text, p1, p2)
2301
2299
2302 def checkhash(self, text, node, p1=None, p2=None, rev=None):
2300 def checkhash(self, text, node, p1=None, p2=None, rev=None):
2303 """Check node hash integrity.
2301 """Check node hash integrity.
2304
2302
2305 Available as a function so that subclasses can extend hash mismatch
2303 Available as a function so that subclasses can extend hash mismatch
2306 behaviors as needed.
2304 behaviors as needed.
2307 """
2305 """
2308 try:
2306 try:
2309 if p1 is None and p2 is None:
2307 if p1 is None and p2 is None:
2310 p1, p2 = self.parents(node)
2308 p1, p2 = self.parents(node)
2311 if node != self.hash(text, p1, p2):
2309 if node != self.hash(text, p1, p2):
2312 # Clear the revision cache on hash failure. The revision cache
2310 # Clear the revision cache on hash failure. The revision cache
2313 # only stores the raw revision and clearing the cache does have
2311 # only stores the raw revision and clearing the cache does have
2314 # the side-effect that we won't have a cache hit when the raw
2312 # the side-effect that we won't have a cache hit when the raw
2315 # revision data is accessed. But this case should be rare and
2313 # revision data is accessed. But this case should be rare and
2316 # it is extra work to teach the cache about the hash
2314 # it is extra work to teach the cache about the hash
2317 # verification state.
2315 # verification state.
2318 if self._revisioncache and self._revisioncache[0] == node:
2316 if self._revisioncache and self._revisioncache[0] == node:
2319 self._revisioncache = None
2317 self._revisioncache = None
2320
2318
2321 revornode = rev
2319 revornode = rev
2322 if revornode is None:
2320 if revornode is None:
2323 revornode = templatefilters.short(hex(node))
2321 revornode = templatefilters.short(hex(node))
2324 raise error.RevlogError(
2322 raise error.RevlogError(
2325 _(b"integrity check failed on %s:%s")
2323 _(b"integrity check failed on %s:%s")
2326 % (self.display_id, pycompat.bytestr(revornode))
2324 % (self.display_id, pycompat.bytestr(revornode))
2327 )
2325 )
2328 except error.RevlogError:
2326 except error.RevlogError:
2329 if self._censorable and storageutil.iscensoredtext(text):
2327 if self._censorable and storageutil.iscensoredtext(text):
2330 raise error.CensoredNodeError(self.display_id, node, text)
2328 raise error.CensoredNodeError(self.display_id, node, text)
2331 raise
2329 raise
2332
2330
2333 @property
2331 @property
2334 def _split_index_file(self):
2332 def _split_index_file(self):
2335 """the path where to expect the index of an ongoing splitting operation
2333 """the path where to expect the index of an ongoing splitting operation
2336
2334
2337 The file will only exist if a splitting operation is in progress, but
2335 The file will only exist if a splitting operation is in progress, but
2338 it is always expected at the same location."""
2336 it is always expected at the same location."""
2339 parts = self.radix.split(b'/')
2337 parts = self.radix.split(b'/')
2340 if len(parts) > 1:
2338 if len(parts) > 1:
2341 # adds a '-s' prefix to the ``data/` or `meta/` base
2339 # adds a '-s' prefix to the ``data/` or `meta/` base
2342 head = parts[0] + b'-s'
2340 head = parts[0] + b'-s'
2343 mids = parts[1:-1]
2341 mids = parts[1:-1]
2344 tail = parts[-1] + b'.i'
2342 tail = parts[-1] + b'.i'
2345 pieces = [head] + mids + [tail]
2343 pieces = [head] + mids + [tail]
2346 return b'/'.join(pieces)
2344 return b'/'.join(pieces)
2347 else:
2345 else:
2348 # the revlog is stored at the root of the store (changelog or
2346 # the revlog is stored at the root of the store (changelog or
2349 # manifest), no risk of collision.
2347 # manifest), no risk of collision.
2350 return self.radix + b'.i.s'
2348 return self.radix + b'.i.s'
2351
2349
2352 def _enforceinlinesize(self, tr, side_write=True):
2350 def _enforceinlinesize(self, tr, side_write=True):
2353 """Check if the revlog is too big for inline and convert if so.
2351 """Check if the revlog is too big for inline and convert if so.
2354
2352
2355 This should be called after revisions are added to the revlog. If the
2353 This should be called after revisions are added to the revlog. If the
2356 revlog has grown too large to be an inline revlog, it will convert it
2354 revlog has grown too large to be an inline revlog, it will convert it
2357 to use multiple index and data files.
2355 to use multiple index and data files.
2358 """
2356 """
2359 tiprev = len(self) - 1
2357 tiprev = len(self) - 1
2360 total_size = self.start(tiprev) + self.length(tiprev)
2358 total_size = self.start(tiprev) + self.length(tiprev)
2361 if not self._inline or total_size < _maxinline:
2359 if not self._inline or total_size < _maxinline:
2362 return
2360 return
2363
2361
2364 troffset = tr.findoffset(self._indexfile)
2362 troffset = tr.findoffset(self._indexfile)
2365 if troffset is None:
2363 if troffset is None:
2366 raise error.RevlogError(
2364 raise error.RevlogError(
2367 _(b"%s not found in the transaction") % self._indexfile
2365 _(b"%s not found in the transaction") % self._indexfile
2368 )
2366 )
2369 if troffset:
2367 if troffset:
2370 tr.addbackup(self._indexfile, for_offset=True)
2368 tr.addbackup(self._indexfile, for_offset=True)
2371 tr.add(self._datafile, 0)
2369 tr.add(self._datafile, 0)
2372
2370
2373 existing_handles = False
2371 existing_handles = False
2374 if self._writinghandles is not None:
2372 if self._writinghandles is not None:
2375 existing_handles = True
2373 existing_handles = True
2376 fp = self._writinghandles[0]
2374 fp = self._writinghandles[0]
2377 fp.flush()
2375 fp.flush()
2378 fp.close()
2376 fp.close()
2379 # We can't use the cached file handle after close(). So prevent
2377 # We can't use the cached file handle after close(). So prevent
2380 # its usage.
2378 # its usage.
2381 self._writinghandles = None
2379 self._writinghandles = None
2382 self._segmentfile.writing_handle = None
2380 self._segmentfile.writing_handle = None
2383 # No need to deal with sidedata writing handle as it is only
2381 # No need to deal with sidedata writing handle as it is only
2384 # relevant with revlog-v2 which is never inline, not reaching
2382 # relevant with revlog-v2 which is never inline, not reaching
2385 # this code
2383 # this code
2386 if side_write:
2384 if side_write:
2387 old_index_file_path = self._indexfile
2385 old_index_file_path = self._indexfile
2388 new_index_file_path = self._split_index_file
2386 new_index_file_path = self._split_index_file
2389 opener = self.opener
2387 opener = self.opener
2390 weak_self = weakref.ref(self)
2388 weak_self = weakref.ref(self)
2391
2389
2392 # the "split" index replace the real index when the transaction is finalized
2390 # the "split" index replace the real index when the transaction is finalized
2393 def finalize_callback(tr):
2391 def finalize_callback(tr):
2394 opener.rename(
2392 opener.rename(
2395 new_index_file_path,
2393 new_index_file_path,
2396 old_index_file_path,
2394 old_index_file_path,
2397 checkambig=True,
2395 checkambig=True,
2398 )
2396 )
2399 maybe_self = weak_self()
2397 maybe_self = weak_self()
2400 if maybe_self is not None:
2398 if maybe_self is not None:
2401 maybe_self._indexfile = old_index_file_path
2399 maybe_self._indexfile = old_index_file_path
2402
2400
2403 def abort_callback(tr):
2401 def abort_callback(tr):
2404 maybe_self = weak_self()
2402 maybe_self = weak_self()
2405 if maybe_self is not None:
2403 if maybe_self is not None:
2406 maybe_self._indexfile = old_index_file_path
2404 maybe_self._indexfile = old_index_file_path
2407
2405
2408 tr.registertmp(new_index_file_path)
2406 tr.registertmp(new_index_file_path)
2409 if self.target[1] is not None:
2407 if self.target[1] is not None:
2410 callback_id = b'000-revlog-split-%d-%s' % self.target
2408 callback_id = b'000-revlog-split-%d-%s' % self.target
2411 else:
2409 else:
2412 callback_id = b'000-revlog-split-%d' % self.target[0]
2410 callback_id = b'000-revlog-split-%d' % self.target[0]
2413 tr.addfinalize(callback_id, finalize_callback)
2411 tr.addfinalize(callback_id, finalize_callback)
2414 tr.addabort(callback_id, abort_callback)
2412 tr.addabort(callback_id, abort_callback)
2415
2413
2416 new_dfh = self._datafp(b'w+')
2414 new_dfh = self._datafp(b'w+')
2417 new_dfh.truncate(0) # drop any potentially existing data
2415 new_dfh.truncate(0) # drop any potentially existing data
2418 try:
2416 try:
2419 with self.reading():
2417 with self.reading():
2420 for r in self:
2418 for r in self:
2421 new_dfh.write(self._getsegmentforrevs(r, r)[1])
2419 new_dfh.write(self._getsegmentforrevs(r, r)[1])
2422 new_dfh.flush()
2420 new_dfh.flush()
2423
2421
2424 if side_write:
2422 if side_write:
2425 self._indexfile = new_index_file_path
2423 self._indexfile = new_index_file_path
2426 with self.__index_new_fp() as fp:
2424 with self.__index_new_fp() as fp:
2427 self._format_flags &= ~FLAG_INLINE_DATA
2425 self._format_flags &= ~FLAG_INLINE_DATA
2428 self._inline = False
2426 self._inline = False
2429 for i in self:
2427 for i in self:
2430 e = self.index.entry_binary(i)
2428 e = self.index.entry_binary(i)
2431 if i == 0 and self._docket is None:
2429 if i == 0 and self._docket is None:
2432 header = self._format_flags | self._format_version
2430 header = self._format_flags | self._format_version
2433 header = self.index.pack_header(header)
2431 header = self.index.pack_header(header)
2434 e = header + e
2432 e = header + e
2435 fp.write(e)
2433 fp.write(e)
2436 if self._docket is not None:
2434 if self._docket is not None:
2437 self._docket.index_end = fp.tell()
2435 self._docket.index_end = fp.tell()
2438
2436
2439 # If we don't use side-write, the temp file replace the real
2437 # If we don't use side-write, the temp file replace the real
2440 # index when we exit the context manager
2438 # index when we exit the context manager
2441
2439
2442 nodemaputil.setup_persistent_nodemap(tr, self)
2440 nodemaputil.setup_persistent_nodemap(tr, self)
2443 self._segmentfile = randomaccessfile.randomaccessfile(
2441 self._segmentfile = randomaccessfile.randomaccessfile(
2444 self.opener,
2442 self.opener,
2445 self._datafile,
2443 self._datafile,
2446 self._chunkcachesize,
2444 self._chunkcachesize,
2447 )
2445 )
2448
2446
2449 if existing_handles:
2447 if existing_handles:
2450 # switched from inline to conventional reopen the index
2448 # switched from inline to conventional reopen the index
2451 ifh = self.__index_write_fp()
2449 ifh = self.__index_write_fp()
2452 self._writinghandles = (ifh, new_dfh, None)
2450 self._writinghandles = (ifh, new_dfh, None)
2453 self._segmentfile.writing_handle = new_dfh
2451 self._segmentfile.writing_handle = new_dfh
2454 new_dfh = None
2452 new_dfh = None
2455 # No need to deal with sidedata writing handle as it is only
2453 # No need to deal with sidedata writing handle as it is only
2456 # relevant with revlog-v2 which is never inline, not reaching
2454 # relevant with revlog-v2 which is never inline, not reaching
2457 # this code
2455 # this code
2458 finally:
2456 finally:
2459 if new_dfh is not None:
2457 if new_dfh is not None:
2460 new_dfh.close()
2458 new_dfh.close()
2461
2459
2462 def _nodeduplicatecallback(self, transaction, node):
2460 def _nodeduplicatecallback(self, transaction, node):
2463 """called when trying to add a node already stored."""
2461 """called when trying to add a node already stored."""
2464
2462
2465 @contextlib.contextmanager
2463 @contextlib.contextmanager
2466 def reading(self):
2464 def reading(self):
2467 """Context manager that keeps data and sidedata files open for reading"""
2465 """Context manager that keeps data and sidedata files open for reading"""
2468 if len(self.index) == 0:
2466 if len(self.index) == 0:
2469 yield # nothing to be read
2467 yield # nothing to be read
2470 else:
2468 else:
2471 with self._segmentfile.reading():
2469 with self._segmentfile.reading():
2472 with self._segmentfile_sidedata.reading():
2470 with self._segmentfile_sidedata.reading():
2473 yield
2471 yield
2474
2472
2475 @contextlib.contextmanager
2473 @contextlib.contextmanager
2476 def _writing(self, transaction):
2474 def _writing(self, transaction):
2477 if self._trypending:
2475 if self._trypending:
2478 msg = b'try to write in a `trypending` revlog: %s'
2476 msg = b'try to write in a `trypending` revlog: %s'
2479 msg %= self.display_id
2477 msg %= self.display_id
2480 raise error.ProgrammingError(msg)
2478 raise error.ProgrammingError(msg)
2481 if self._writinghandles is not None:
2479 if self._writinghandles is not None:
2482 yield
2480 yield
2483 else:
2481 else:
2484 ifh = dfh = sdfh = None
2482 ifh = dfh = sdfh = None
2485 try:
2483 try:
2486 r = len(self)
2484 r = len(self)
2487 # opening the data file.
2485 # opening the data file.
2488 dsize = 0
2486 dsize = 0
2489 if r:
2487 if r:
2490 dsize = self.end(r - 1)
2488 dsize = self.end(r - 1)
2491 dfh = None
2489 dfh = None
2492 if not self._inline:
2490 if not self._inline:
2493 try:
2491 try:
2494 dfh = self._datafp(b"r+")
2492 dfh = self._datafp(b"r+")
2495 if self._docket is None:
2493 if self._docket is None:
2496 dfh.seek(0, os.SEEK_END)
2494 dfh.seek(0, os.SEEK_END)
2497 else:
2495 else:
2498 dfh.seek(self._docket.data_end, os.SEEK_SET)
2496 dfh.seek(self._docket.data_end, os.SEEK_SET)
2499 except FileNotFoundError:
2497 except FileNotFoundError:
2500 dfh = self._datafp(b"w+")
2498 dfh = self._datafp(b"w+")
2501 transaction.add(self._datafile, dsize)
2499 transaction.add(self._datafile, dsize)
2502 if self._sidedatafile is not None:
2500 if self._sidedatafile is not None:
2503 # revlog-v2 does not inline, help Pytype
2501 # revlog-v2 does not inline, help Pytype
2504 assert dfh is not None
2502 assert dfh is not None
2505 try:
2503 try:
2506 sdfh = self.opener(self._sidedatafile, mode=b"r+")
2504 sdfh = self.opener(self._sidedatafile, mode=b"r+")
2507 dfh.seek(self._docket.sidedata_end, os.SEEK_SET)
2505 dfh.seek(self._docket.sidedata_end, os.SEEK_SET)
2508 except FileNotFoundError:
2506 except FileNotFoundError:
2509 sdfh = self.opener(self._sidedatafile, mode=b"w+")
2507 sdfh = self.opener(self._sidedatafile, mode=b"w+")
2510 transaction.add(
2508 transaction.add(
2511 self._sidedatafile, self._docket.sidedata_end
2509 self._sidedatafile, self._docket.sidedata_end
2512 )
2510 )
2513
2511
2514 # opening the index file.
2512 # opening the index file.
2515 isize = r * self.index.entry_size
2513 isize = r * self.index.entry_size
2516 ifh = self.__index_write_fp()
2514 ifh = self.__index_write_fp()
2517 if self._inline:
2515 if self._inline:
2518 transaction.add(self._indexfile, dsize + isize)
2516 transaction.add(self._indexfile, dsize + isize)
2519 else:
2517 else:
2520 transaction.add(self._indexfile, isize)
2518 transaction.add(self._indexfile, isize)
2521 # exposing all file handle for writing.
2519 # exposing all file handle for writing.
2522 self._writinghandles = (ifh, dfh, sdfh)
2520 self._writinghandles = (ifh, dfh, sdfh)
2523 self._segmentfile.writing_handle = ifh if self._inline else dfh
2521 self._segmentfile.writing_handle = ifh if self._inline else dfh
2524 self._segmentfile_sidedata.writing_handle = sdfh
2522 self._segmentfile_sidedata.writing_handle = sdfh
2525 yield
2523 yield
2526 if self._docket is not None:
2524 if self._docket is not None:
2527 self._write_docket(transaction)
2525 self._write_docket(transaction)
2528 finally:
2526 finally:
2529 self._writinghandles = None
2527 self._writinghandles = None
2530 self._segmentfile.writing_handle = None
2528 self._segmentfile.writing_handle = None
2531 self._segmentfile_sidedata.writing_handle = None
2529 self._segmentfile_sidedata.writing_handle = None
2532 if dfh is not None:
2530 if dfh is not None:
2533 dfh.close()
2531 dfh.close()
2534 if sdfh is not None:
2532 if sdfh is not None:
2535 sdfh.close()
2533 sdfh.close()
2536 # closing the index file last to avoid exposing referent to
2534 # closing the index file last to avoid exposing referent to
2537 # potential unflushed data content.
2535 # potential unflushed data content.
2538 if ifh is not None:
2536 if ifh is not None:
2539 ifh.close()
2537 ifh.close()
2540
2538
2541 def _write_docket(self, transaction):
2539 def _write_docket(self, transaction):
2542 """write the current docket on disk
2540 """write the current docket on disk
2543
2541
2544 Exist as a method to help changelog to implement transaction logic
2542 Exist as a method to help changelog to implement transaction logic
2545
2543
2546 We could also imagine using the same transaction logic for all revlog
2544 We could also imagine using the same transaction logic for all revlog
2547 since docket are cheap."""
2545 since docket are cheap."""
2548 self._docket.write(transaction)
2546 self._docket.write(transaction)
2549
2547
2550 def addrevision(
2548 def addrevision(
2551 self,
2549 self,
2552 text,
2550 text,
2553 transaction,
2551 transaction,
2554 link,
2552 link,
2555 p1,
2553 p1,
2556 p2,
2554 p2,
2557 cachedelta=None,
2555 cachedelta=None,
2558 node=None,
2556 node=None,
2559 flags=REVIDX_DEFAULT_FLAGS,
2557 flags=REVIDX_DEFAULT_FLAGS,
2560 deltacomputer=None,
2558 deltacomputer=None,
2561 sidedata=None,
2559 sidedata=None,
2562 ):
2560 ):
2563 """add a revision to the log
2561 """add a revision to the log
2564
2562
2565 text - the revision data to add
2563 text - the revision data to add
2566 transaction - the transaction object used for rollback
2564 transaction - the transaction object used for rollback
2567 link - the linkrev data to add
2565 link - the linkrev data to add
2568 p1, p2 - the parent nodeids of the revision
2566 p1, p2 - the parent nodeids of the revision
2569 cachedelta - an optional precomputed delta
2567 cachedelta - an optional precomputed delta
2570 node - nodeid of revision; typically node is not specified, and it is
2568 node - nodeid of revision; typically node is not specified, and it is
2571 computed by default as hash(text, p1, p2), however subclasses might
2569 computed by default as hash(text, p1, p2), however subclasses might
2572 use different hashing method (and override checkhash() in such case)
2570 use different hashing method (and override checkhash() in such case)
2573 flags - the known flags to set on the revision
2571 flags - the known flags to set on the revision
2574 deltacomputer - an optional deltacomputer instance shared between
2572 deltacomputer - an optional deltacomputer instance shared between
2575 multiple calls
2573 multiple calls
2576 """
2574 """
2577 if link == nullrev:
2575 if link == nullrev:
2578 raise error.RevlogError(
2576 raise error.RevlogError(
2579 _(b"attempted to add linkrev -1 to %s") % self.display_id
2577 _(b"attempted to add linkrev -1 to %s") % self.display_id
2580 )
2578 )
2581
2579
2582 if sidedata is None:
2580 if sidedata is None:
2583 sidedata = {}
2581 sidedata = {}
2584 elif sidedata and not self.hassidedata:
2582 elif sidedata and not self.hassidedata:
2585 raise error.ProgrammingError(
2583 raise error.ProgrammingError(
2586 _(b"trying to add sidedata to a revlog who don't support them")
2584 _(b"trying to add sidedata to a revlog who don't support them")
2587 )
2585 )
2588
2586
2589 if flags:
2587 if flags:
2590 node = node or self.hash(text, p1, p2)
2588 node = node or self.hash(text, p1, p2)
2591
2589
2592 rawtext, validatehash = flagutil.processflagswrite(self, text, flags)
2590 rawtext, validatehash = flagutil.processflagswrite(self, text, flags)
2593
2591
2594 # If the flag processor modifies the revision data, ignore any provided
2592 # If the flag processor modifies the revision data, ignore any provided
2595 # cachedelta.
2593 # cachedelta.
2596 if rawtext != text:
2594 if rawtext != text:
2597 cachedelta = None
2595 cachedelta = None
2598
2596
2599 if len(rawtext) > _maxentrysize:
2597 if len(rawtext) > _maxentrysize:
2600 raise error.RevlogError(
2598 raise error.RevlogError(
2601 _(
2599 _(
2602 b"%s: size of %d bytes exceeds maximum revlog storage of 2GiB"
2600 b"%s: size of %d bytes exceeds maximum revlog storage of 2GiB"
2603 )
2601 )
2604 % (self.display_id, len(rawtext))
2602 % (self.display_id, len(rawtext))
2605 )
2603 )
2606
2604
2607 node = node or self.hash(rawtext, p1, p2)
2605 node = node or self.hash(rawtext, p1, p2)
2608 rev = self.index.get_rev(node)
2606 rev = self.index.get_rev(node)
2609 if rev is not None:
2607 if rev is not None:
2610 return rev
2608 return rev
2611
2609
2612 if validatehash:
2610 if validatehash:
2613 self.checkhash(rawtext, node, p1=p1, p2=p2)
2611 self.checkhash(rawtext, node, p1=p1, p2=p2)
2614
2612
2615 return self.addrawrevision(
2613 return self.addrawrevision(
2616 rawtext,
2614 rawtext,
2617 transaction,
2615 transaction,
2618 link,
2616 link,
2619 p1,
2617 p1,
2620 p2,
2618 p2,
2621 node,
2619 node,
2622 flags,
2620 flags,
2623 cachedelta=cachedelta,
2621 cachedelta=cachedelta,
2624 deltacomputer=deltacomputer,
2622 deltacomputer=deltacomputer,
2625 sidedata=sidedata,
2623 sidedata=sidedata,
2626 )
2624 )
2627
2625
2628 def addrawrevision(
2626 def addrawrevision(
2629 self,
2627 self,
2630 rawtext,
2628 rawtext,
2631 transaction,
2629 transaction,
2632 link,
2630 link,
2633 p1,
2631 p1,
2634 p2,
2632 p2,
2635 node,
2633 node,
2636 flags,
2634 flags,
2637 cachedelta=None,
2635 cachedelta=None,
2638 deltacomputer=None,
2636 deltacomputer=None,
2639 sidedata=None,
2637 sidedata=None,
2640 ):
2638 ):
2641 """add a raw revision with known flags, node and parents
2639 """add a raw revision with known flags, node and parents
2642 useful when reusing a revision not stored in this revlog (ex: received
2640 useful when reusing a revision not stored in this revlog (ex: received
2643 over wire, or read from an external bundle).
2641 over wire, or read from an external bundle).
2644 """
2642 """
2645 with self._writing(transaction):
2643 with self._writing(transaction):
2646 return self._addrevision(
2644 return self._addrevision(
2647 node,
2645 node,
2648 rawtext,
2646 rawtext,
2649 transaction,
2647 transaction,
2650 link,
2648 link,
2651 p1,
2649 p1,
2652 p2,
2650 p2,
2653 flags,
2651 flags,
2654 cachedelta,
2652 cachedelta,
2655 deltacomputer=deltacomputer,
2653 deltacomputer=deltacomputer,
2656 sidedata=sidedata,
2654 sidedata=sidedata,
2657 )
2655 )
2658
2656
2659 def compress(self, data):
2657 def compress(self, data):
2660 """Generate a possibly-compressed representation of data."""
2658 """Generate a possibly-compressed representation of data."""
2661 if not data:
2659 if not data:
2662 return b'', data
2660 return b'', data
2663
2661
2664 compressed = self._compressor.compress(data)
2662 compressed = self._compressor.compress(data)
2665
2663
2666 if compressed:
2664 if compressed:
2667 # The revlog compressor added the header in the returned data.
2665 # The revlog compressor added the header in the returned data.
2668 return b'', compressed
2666 return b'', compressed
2669
2667
2670 if data[0:1] == b'\0':
2668 if data[0:1] == b'\0':
2671 return b'', data
2669 return b'', data
2672 return b'u', data
2670 return b'u', data
2673
2671
2674 def decompress(self, data):
2672 def decompress(self, data):
2675 """Decompress a revlog chunk.
2673 """Decompress a revlog chunk.
2676
2674
2677 The chunk is expected to begin with a header identifying the
2675 The chunk is expected to begin with a header identifying the
2678 format type so it can be routed to an appropriate decompressor.
2676 format type so it can be routed to an appropriate decompressor.
2679 """
2677 """
2680 if not data:
2678 if not data:
2681 return data
2679 return data
2682
2680
2683 # Revlogs are read much more frequently than they are written and many
2681 # Revlogs are read much more frequently than they are written and many
2684 # chunks only take microseconds to decompress, so performance is
2682 # chunks only take microseconds to decompress, so performance is
2685 # important here.
2683 # important here.
2686 #
2684 #
2687 # We can make a few assumptions about revlogs:
2685 # We can make a few assumptions about revlogs:
2688 #
2686 #
2689 # 1) the majority of chunks will be compressed (as opposed to inline
2687 # 1) the majority of chunks will be compressed (as opposed to inline
2690 # raw data).
2688 # raw data).
2691 # 2) decompressing *any* data will likely by at least 10x slower than
2689 # 2) decompressing *any* data will likely by at least 10x slower than
2692 # returning raw inline data.
2690 # returning raw inline data.
2693 # 3) we want to prioritize common and officially supported compression
2691 # 3) we want to prioritize common and officially supported compression
2694 # engines
2692 # engines
2695 #
2693 #
2696 # It follows that we want to optimize for "decompress compressed data
2694 # It follows that we want to optimize for "decompress compressed data
2697 # when encoded with common and officially supported compression engines"
2695 # when encoded with common and officially supported compression engines"
2698 # case over "raw data" and "data encoded by less common or non-official
2696 # case over "raw data" and "data encoded by less common or non-official
2699 # compression engines." That is why we have the inline lookup first
2697 # compression engines." That is why we have the inline lookup first
2700 # followed by the compengines lookup.
2698 # followed by the compengines lookup.
2701 #
2699 #
2702 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
2700 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
2703 # compressed chunks. And this matters for changelog and manifest reads.
2701 # compressed chunks. And this matters for changelog and manifest reads.
2704 t = data[0:1]
2702 t = data[0:1]
2705
2703
2706 if t == b'x':
2704 if t == b'x':
2707 try:
2705 try:
2708 return _zlibdecompress(data)
2706 return _zlibdecompress(data)
2709 except zlib.error as e:
2707 except zlib.error as e:
2710 raise error.RevlogError(
2708 raise error.RevlogError(
2711 _(b'revlog decompress error: %s')
2709 _(b'revlog decompress error: %s')
2712 % stringutil.forcebytestr(e)
2710 % stringutil.forcebytestr(e)
2713 )
2711 )
2714 # '\0' is more common than 'u' so it goes first.
2712 # '\0' is more common than 'u' so it goes first.
2715 elif t == b'\0':
2713 elif t == b'\0':
2716 return data
2714 return data
2717 elif t == b'u':
2715 elif t == b'u':
2718 return util.buffer(data, 1)
2716 return util.buffer(data, 1)
2719
2717
2720 compressor = self._get_decompressor(t)
2718 compressor = self._get_decompressor(t)
2721
2719
2722 return compressor.decompress(data)
2720 return compressor.decompress(data)
2723
2721
2724 def _addrevision(
2722 def _addrevision(
2725 self,
2723 self,
2726 node,
2724 node,
2727 rawtext,
2725 rawtext,
2728 transaction,
2726 transaction,
2729 link,
2727 link,
2730 p1,
2728 p1,
2731 p2,
2729 p2,
2732 flags,
2730 flags,
2733 cachedelta,
2731 cachedelta,
2734 alwayscache=False,
2732 alwayscache=False,
2735 deltacomputer=None,
2733 deltacomputer=None,
2736 sidedata=None,
2734 sidedata=None,
2737 ):
2735 ):
2738 """internal function to add revisions to the log
2736 """internal function to add revisions to the log
2739
2737
2740 see addrevision for argument descriptions.
2738 see addrevision for argument descriptions.
2741
2739
2742 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2740 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2743
2741
2744 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2742 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2745 be used.
2743 be used.
2746
2744
2747 invariants:
2745 invariants:
2748 - rawtext is optional (can be None); if not set, cachedelta must be set.
2746 - rawtext is optional (can be None); if not set, cachedelta must be set.
2749 if both are set, they must correspond to each other.
2747 if both are set, they must correspond to each other.
2750 """
2748 """
2751 if node == self.nullid:
2749 if node == self.nullid:
2752 raise error.RevlogError(
2750 raise error.RevlogError(
2753 _(b"%s: attempt to add null revision") % self.display_id
2751 _(b"%s: attempt to add null revision") % self.display_id
2754 )
2752 )
2755 if (
2753 if (
2756 node == self.nodeconstants.wdirid
2754 node == self.nodeconstants.wdirid
2757 or node in self.nodeconstants.wdirfilenodeids
2755 or node in self.nodeconstants.wdirfilenodeids
2758 ):
2756 ):
2759 raise error.RevlogError(
2757 raise error.RevlogError(
2760 _(b"%s: attempt to add wdir revision") % self.display_id
2758 _(b"%s: attempt to add wdir revision") % self.display_id
2761 )
2759 )
2762 if self._writinghandles is None:
2760 if self._writinghandles is None:
2763 msg = b'adding revision outside `revlog._writing` context'
2761 msg = b'adding revision outside `revlog._writing` context'
2764 raise error.ProgrammingError(msg)
2762 raise error.ProgrammingError(msg)
2765
2763
2766 btext = [rawtext]
2764 btext = [rawtext]
2767
2765
2768 curr = len(self)
2766 curr = len(self)
2769 prev = curr - 1
2767 prev = curr - 1
2770
2768
2771 offset = self._get_data_offset(prev)
2769 offset = self._get_data_offset(prev)
2772
2770
2773 if self._concurrencychecker:
2771 if self._concurrencychecker:
2774 ifh, dfh, sdfh = self._writinghandles
2772 ifh, dfh, sdfh = self._writinghandles
2775 # XXX no checking for the sidedata file
2773 # XXX no checking for the sidedata file
2776 if self._inline:
2774 if self._inline:
2777 # offset is "as if" it were in the .d file, so we need to add on
2775 # offset is "as if" it were in the .d file, so we need to add on
2778 # the size of the entry metadata.
2776 # the size of the entry metadata.
2779 self._concurrencychecker(
2777 self._concurrencychecker(
2780 ifh, self._indexfile, offset + curr * self.index.entry_size
2778 ifh, self._indexfile, offset + curr * self.index.entry_size
2781 )
2779 )
2782 else:
2780 else:
2783 # Entries in the .i are a consistent size.
2781 # Entries in the .i are a consistent size.
2784 self._concurrencychecker(
2782 self._concurrencychecker(
2785 ifh, self._indexfile, curr * self.index.entry_size
2783 ifh, self._indexfile, curr * self.index.entry_size
2786 )
2784 )
2787 self._concurrencychecker(dfh, self._datafile, offset)
2785 self._concurrencychecker(dfh, self._datafile, offset)
2788
2786
2789 p1r, p2r = self.rev(p1), self.rev(p2)
2787 p1r, p2r = self.rev(p1), self.rev(p2)
2790
2788
2791 # full versions are inserted when the needed deltas
2789 # full versions are inserted when the needed deltas
2792 # become comparable to the uncompressed text
2790 # become comparable to the uncompressed text
2793 if rawtext is None:
2791 if rawtext is None:
2794 # need rawtext size, before changed by flag processors, which is
2792 # need rawtext size, before changed by flag processors, which is
2795 # the non-raw size. use revlog explicitly to avoid filelog's extra
2793 # the non-raw size. use revlog explicitly to avoid filelog's extra
2796 # logic that might remove metadata size.
2794 # logic that might remove metadata size.
2797 textlen = mdiff.patchedsize(
2795 textlen = mdiff.patchedsize(
2798 revlog.size(self, cachedelta[0]), cachedelta[1]
2796 revlog.size(self, cachedelta[0]), cachedelta[1]
2799 )
2797 )
2800 else:
2798 else:
2801 textlen = len(rawtext)
2799 textlen = len(rawtext)
2802
2800
2803 if deltacomputer is None:
2801 if deltacomputer is None:
2804 write_debug = None
2802 write_debug = None
2805 if self._debug_delta:
2803 if self._debug_delta:
2806 write_debug = transaction._report
2804 write_debug = transaction._report
2807 deltacomputer = deltautil.deltacomputer(
2805 deltacomputer = deltautil.deltacomputer(
2808 self, write_debug=write_debug
2806 self, write_debug=write_debug
2809 )
2807 )
2810
2808
2811 if cachedelta is not None and len(cachedelta) == 2:
2809 if cachedelta is not None and len(cachedelta) == 2:
2812 # If the cached delta has no information about how it should be
2810 # If the cached delta has no information about how it should be
2813 # reused, add the default reuse instruction according to the
2811 # reused, add the default reuse instruction according to the
2814 # revlog's configuration.
2812 # revlog's configuration.
2815 if self._generaldelta and self._lazydeltabase:
2813 if self._generaldelta and self._lazydeltabase:
2816 delta_base_reuse = DELTA_BASE_REUSE_TRY
2814 delta_base_reuse = DELTA_BASE_REUSE_TRY
2817 else:
2815 else:
2818 delta_base_reuse = DELTA_BASE_REUSE_NO
2816 delta_base_reuse = DELTA_BASE_REUSE_NO
2819 cachedelta = (cachedelta[0], cachedelta[1], delta_base_reuse)
2817 cachedelta = (cachedelta[0], cachedelta[1], delta_base_reuse)
2820
2818
2821 revinfo = revlogutils.revisioninfo(
2819 revinfo = revlogutils.revisioninfo(
2822 node,
2820 node,
2823 p1,
2821 p1,
2824 p2,
2822 p2,
2825 btext,
2823 btext,
2826 textlen,
2824 textlen,
2827 cachedelta,
2825 cachedelta,
2828 flags,
2826 flags,
2829 )
2827 )
2830
2828
2831 deltainfo = deltacomputer.finddeltainfo(revinfo)
2829 deltainfo = deltacomputer.finddeltainfo(revinfo)
2832
2830
2833 compression_mode = COMP_MODE_INLINE
2831 compression_mode = COMP_MODE_INLINE
2834 if self._docket is not None:
2832 if self._docket is not None:
2835 default_comp = self._docket.default_compression_header
2833 default_comp = self._docket.default_compression_header
2836 r = deltautil.delta_compression(default_comp, deltainfo)
2834 r = deltautil.delta_compression(default_comp, deltainfo)
2837 compression_mode, deltainfo = r
2835 compression_mode, deltainfo = r
2838
2836
2839 sidedata_compression_mode = COMP_MODE_INLINE
2837 sidedata_compression_mode = COMP_MODE_INLINE
2840 if sidedata and self.hassidedata:
2838 if sidedata and self.hassidedata:
2841 sidedata_compression_mode = COMP_MODE_PLAIN
2839 sidedata_compression_mode = COMP_MODE_PLAIN
2842 serialized_sidedata = sidedatautil.serialize_sidedata(sidedata)
2840 serialized_sidedata = sidedatautil.serialize_sidedata(sidedata)
2843 sidedata_offset = self._docket.sidedata_end
2841 sidedata_offset = self._docket.sidedata_end
2844 h, comp_sidedata = self.compress(serialized_sidedata)
2842 h, comp_sidedata = self.compress(serialized_sidedata)
2845 if (
2843 if (
2846 h != b'u'
2844 h != b'u'
2847 and comp_sidedata[0:1] != b'\0'
2845 and comp_sidedata[0:1] != b'\0'
2848 and len(comp_sidedata) < len(serialized_sidedata)
2846 and len(comp_sidedata) < len(serialized_sidedata)
2849 ):
2847 ):
2850 assert not h
2848 assert not h
2851 if (
2849 if (
2852 comp_sidedata[0:1]
2850 comp_sidedata[0:1]
2853 == self._docket.default_compression_header
2851 == self._docket.default_compression_header
2854 ):
2852 ):
2855 sidedata_compression_mode = COMP_MODE_DEFAULT
2853 sidedata_compression_mode = COMP_MODE_DEFAULT
2856 serialized_sidedata = comp_sidedata
2854 serialized_sidedata = comp_sidedata
2857 else:
2855 else:
2858 sidedata_compression_mode = COMP_MODE_INLINE
2856 sidedata_compression_mode = COMP_MODE_INLINE
2859 serialized_sidedata = comp_sidedata
2857 serialized_sidedata = comp_sidedata
2860 else:
2858 else:
2861 serialized_sidedata = b""
2859 serialized_sidedata = b""
2862 # Don't store the offset if the sidedata is empty, that way
2860 # Don't store the offset if the sidedata is empty, that way
2863 # we can easily detect empty sidedata and they will be no different
2861 # we can easily detect empty sidedata and they will be no different
2864 # than ones we manually add.
2862 # than ones we manually add.
2865 sidedata_offset = 0
2863 sidedata_offset = 0
2866
2864
2867 rank = RANK_UNKNOWN
2865 rank = RANK_UNKNOWN
2868 if self._compute_rank:
2866 if self._compute_rank:
2869 if (p1r, p2r) == (nullrev, nullrev):
2867 if (p1r, p2r) == (nullrev, nullrev):
2870 rank = 1
2868 rank = 1
2871 elif p1r != nullrev and p2r == nullrev:
2869 elif p1r != nullrev and p2r == nullrev:
2872 rank = 1 + self.fast_rank(p1r)
2870 rank = 1 + self.fast_rank(p1r)
2873 elif p1r == nullrev and p2r != nullrev:
2871 elif p1r == nullrev and p2r != nullrev:
2874 rank = 1 + self.fast_rank(p2r)
2872 rank = 1 + self.fast_rank(p2r)
2875 else: # merge node
2873 else: # merge node
2876 if rustdagop is not None and self.index.rust_ext_compat:
2874 if rustdagop is not None and self.index.rust_ext_compat:
2877 rank = rustdagop.rank(self.index, p1r, p2r)
2875 rank = rustdagop.rank(self.index, p1r, p2r)
2878 else:
2876 else:
2879 pmin, pmax = sorted((p1r, p2r))
2877 pmin, pmax = sorted((p1r, p2r))
2880 rank = 1 + self.fast_rank(pmax)
2878 rank = 1 + self.fast_rank(pmax)
2881 rank += sum(1 for _ in self.findmissingrevs([pmax], [pmin]))
2879 rank += sum(1 for _ in self.findmissingrevs([pmax], [pmin]))
2882
2880
2883 e = revlogutils.entry(
2881 e = revlogutils.entry(
2884 flags=flags,
2882 flags=flags,
2885 data_offset=offset,
2883 data_offset=offset,
2886 data_compressed_length=deltainfo.deltalen,
2884 data_compressed_length=deltainfo.deltalen,
2887 data_uncompressed_length=textlen,
2885 data_uncompressed_length=textlen,
2888 data_compression_mode=compression_mode,
2886 data_compression_mode=compression_mode,
2889 data_delta_base=deltainfo.base,
2887 data_delta_base=deltainfo.base,
2890 link_rev=link,
2888 link_rev=link,
2891 parent_rev_1=p1r,
2889 parent_rev_1=p1r,
2892 parent_rev_2=p2r,
2890 parent_rev_2=p2r,
2893 node_id=node,
2891 node_id=node,
2894 sidedata_offset=sidedata_offset,
2892 sidedata_offset=sidedata_offset,
2895 sidedata_compressed_length=len(serialized_sidedata),
2893 sidedata_compressed_length=len(serialized_sidedata),
2896 sidedata_compression_mode=sidedata_compression_mode,
2894 sidedata_compression_mode=sidedata_compression_mode,
2897 rank=rank,
2895 rank=rank,
2898 )
2896 )
2899
2897
2900 self.index.append(e)
2898 self.index.append(e)
2901 entry = self.index.entry_binary(curr)
2899 entry = self.index.entry_binary(curr)
2902 if curr == 0 and self._docket is None:
2900 if curr == 0 and self._docket is None:
2903 header = self._format_flags | self._format_version
2901 header = self._format_flags | self._format_version
2904 header = self.index.pack_header(header)
2902 header = self.index.pack_header(header)
2905 entry = header + entry
2903 entry = header + entry
2906 self._writeentry(
2904 self._writeentry(
2907 transaction,
2905 transaction,
2908 entry,
2906 entry,
2909 deltainfo.data,
2907 deltainfo.data,
2910 link,
2908 link,
2911 offset,
2909 offset,
2912 serialized_sidedata,
2910 serialized_sidedata,
2913 sidedata_offset,
2911 sidedata_offset,
2914 )
2912 )
2915
2913
2916 rawtext = btext[0]
2914 rawtext = btext[0]
2917
2915
2918 if alwayscache and rawtext is None:
2916 if alwayscache and rawtext is None:
2919 rawtext = deltacomputer.buildtext(revinfo)
2917 rawtext = deltacomputer.buildtext(revinfo)
2920
2918
2921 if type(rawtext) == bytes: # only accept immutable objects
2919 if type(rawtext) == bytes: # only accept immutable objects
2922 self._revisioncache = (node, curr, rawtext)
2920 self._revisioncache = (node, curr, rawtext)
2923 self._chainbasecache[curr] = deltainfo.chainbase
2921 self._chainbasecache[curr] = deltainfo.chainbase
2924 return curr
2922 return curr
2925
2923
2926 def _get_data_offset(self, prev):
2924 def _get_data_offset(self, prev):
2927 """Returns the current offset in the (in-transaction) data file.
2925 """Returns the current offset in the (in-transaction) data file.
2928 Versions < 2 of the revlog can get this 0(1), revlog v2 needs a docket
2926 Versions < 2 of the revlog can get this 0(1), revlog v2 needs a docket
2929 file to store that information: since sidedata can be rewritten to the
2927 file to store that information: since sidedata can be rewritten to the
2930 end of the data file within a transaction, you can have cases where, for
2928 end of the data file within a transaction, you can have cases where, for
2931 example, rev `n` does not have sidedata while rev `n - 1` does, leading
2929 example, rev `n` does not have sidedata while rev `n - 1` does, leading
2932 to `n - 1`'s sidedata being written after `n`'s data.
2930 to `n - 1`'s sidedata being written after `n`'s data.
2933
2931
2934 TODO cache this in a docket file before getting out of experimental."""
2932 TODO cache this in a docket file before getting out of experimental."""
2935 if self._docket is None:
2933 if self._docket is None:
2936 return self.end(prev)
2934 return self.end(prev)
2937 else:
2935 else:
2938 return self._docket.data_end
2936 return self._docket.data_end
2939
2937
2940 def _writeentry(
2938 def _writeentry(
2941 self, transaction, entry, data, link, offset, sidedata, sidedata_offset
2939 self, transaction, entry, data, link, offset, sidedata, sidedata_offset
2942 ):
2940 ):
2943 # Files opened in a+ mode have inconsistent behavior on various
2941 # Files opened in a+ mode have inconsistent behavior on various
2944 # platforms. Windows requires that a file positioning call be made
2942 # platforms. Windows requires that a file positioning call be made
2945 # when the file handle transitions between reads and writes. See
2943 # when the file handle transitions between reads and writes. See
2946 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2944 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2947 # platforms, Python or the platform itself can be buggy. Some versions
2945 # platforms, Python or the platform itself can be buggy. Some versions
2948 # of Solaris have been observed to not append at the end of the file
2946 # of Solaris have been observed to not append at the end of the file
2949 # if the file was seeked to before the end. See issue4943 for more.
2947 # if the file was seeked to before the end. See issue4943 for more.
2950 #
2948 #
2951 # We work around this issue by inserting a seek() before writing.
2949 # We work around this issue by inserting a seek() before writing.
2952 # Note: This is likely not necessary on Python 3. However, because
2950 # Note: This is likely not necessary on Python 3. However, because
2953 # the file handle is reused for reads and may be seeked there, we need
2951 # the file handle is reused for reads and may be seeked there, we need
2954 # to be careful before changing this.
2952 # to be careful before changing this.
2955 if self._writinghandles is None:
2953 if self._writinghandles is None:
2956 msg = b'adding revision outside `revlog._writing` context'
2954 msg = b'adding revision outside `revlog._writing` context'
2957 raise error.ProgrammingError(msg)
2955 raise error.ProgrammingError(msg)
2958 ifh, dfh, sdfh = self._writinghandles
2956 ifh, dfh, sdfh = self._writinghandles
2959 if self._docket is None:
2957 if self._docket is None:
2960 ifh.seek(0, os.SEEK_END)
2958 ifh.seek(0, os.SEEK_END)
2961 else:
2959 else:
2962 ifh.seek(self._docket.index_end, os.SEEK_SET)
2960 ifh.seek(self._docket.index_end, os.SEEK_SET)
2963 if dfh:
2961 if dfh:
2964 if self._docket is None:
2962 if self._docket is None:
2965 dfh.seek(0, os.SEEK_END)
2963 dfh.seek(0, os.SEEK_END)
2966 else:
2964 else:
2967 dfh.seek(self._docket.data_end, os.SEEK_SET)
2965 dfh.seek(self._docket.data_end, os.SEEK_SET)
2968 if sdfh:
2966 if sdfh:
2969 sdfh.seek(self._docket.sidedata_end, os.SEEK_SET)
2967 sdfh.seek(self._docket.sidedata_end, os.SEEK_SET)
2970
2968
2971 curr = len(self) - 1
2969 curr = len(self) - 1
2972 if not self._inline:
2970 if not self._inline:
2973 transaction.add(self._datafile, offset)
2971 transaction.add(self._datafile, offset)
2974 if self._sidedatafile:
2972 if self._sidedatafile:
2975 transaction.add(self._sidedatafile, sidedata_offset)
2973 transaction.add(self._sidedatafile, sidedata_offset)
2976 transaction.add(self._indexfile, curr * len(entry))
2974 transaction.add(self._indexfile, curr * len(entry))
2977 if data[0]:
2975 if data[0]:
2978 dfh.write(data[0])
2976 dfh.write(data[0])
2979 dfh.write(data[1])
2977 dfh.write(data[1])
2980 if sidedata:
2978 if sidedata:
2981 sdfh.write(sidedata)
2979 sdfh.write(sidedata)
2982 ifh.write(entry)
2980 ifh.write(entry)
2983 else:
2981 else:
2984 offset += curr * self.index.entry_size
2982 offset += curr * self.index.entry_size
2985 transaction.add(self._indexfile, offset)
2983 transaction.add(self._indexfile, offset)
2986 ifh.write(entry)
2984 ifh.write(entry)
2987 ifh.write(data[0])
2985 ifh.write(data[0])
2988 ifh.write(data[1])
2986 ifh.write(data[1])
2989 assert not sidedata
2987 assert not sidedata
2990 self._enforceinlinesize(transaction)
2988 self._enforceinlinesize(transaction)
2991 if self._docket is not None:
2989 if self._docket is not None:
2992 # revlog-v2 always has 3 writing handles, help Pytype
2990 # revlog-v2 always has 3 writing handles, help Pytype
2993 wh1 = self._writinghandles[0]
2991 wh1 = self._writinghandles[0]
2994 wh2 = self._writinghandles[1]
2992 wh2 = self._writinghandles[1]
2995 wh3 = self._writinghandles[2]
2993 wh3 = self._writinghandles[2]
2996 assert wh1 is not None
2994 assert wh1 is not None
2997 assert wh2 is not None
2995 assert wh2 is not None
2998 assert wh3 is not None
2996 assert wh3 is not None
2999 self._docket.index_end = wh1.tell()
2997 self._docket.index_end = wh1.tell()
3000 self._docket.data_end = wh2.tell()
2998 self._docket.data_end = wh2.tell()
3001 self._docket.sidedata_end = wh3.tell()
2999 self._docket.sidedata_end = wh3.tell()
3002
3000
3003 nodemaputil.setup_persistent_nodemap(transaction, self)
3001 nodemaputil.setup_persistent_nodemap(transaction, self)
3004
3002
3005 def addgroup(
3003 def addgroup(
3006 self,
3004 self,
3007 deltas,
3005 deltas,
3008 linkmapper,
3006 linkmapper,
3009 transaction,
3007 transaction,
3010 alwayscache=False,
3008 alwayscache=False,
3011 addrevisioncb=None,
3009 addrevisioncb=None,
3012 duplicaterevisioncb=None,
3010 duplicaterevisioncb=None,
3013 debug_info=None,
3011 debug_info=None,
3014 delta_base_reuse_policy=None,
3012 delta_base_reuse_policy=None,
3015 ):
3013 ):
3016 """
3014 """
3017 add a delta group
3015 add a delta group
3018
3016
3019 given a set of deltas, add them to the revision log. the
3017 given a set of deltas, add them to the revision log. the
3020 first delta is against its parent, which should be in our
3018 first delta is against its parent, which should be in our
3021 log, the rest are against the previous delta.
3019 log, the rest are against the previous delta.
3022
3020
3023 If ``addrevisioncb`` is defined, it will be called with arguments of
3021 If ``addrevisioncb`` is defined, it will be called with arguments of
3024 this revlog and the node that was added.
3022 this revlog and the node that was added.
3025 """
3023 """
3026
3024
3027 if self._adding_group:
3025 if self._adding_group:
3028 raise error.ProgrammingError(b'cannot nest addgroup() calls')
3026 raise error.ProgrammingError(b'cannot nest addgroup() calls')
3029
3027
3030 # read the default delta-base reuse policy from revlog config if the
3028 # read the default delta-base reuse policy from revlog config if the
3031 # group did not specify one.
3029 # group did not specify one.
3032 if delta_base_reuse_policy is None:
3030 if delta_base_reuse_policy is None:
3033 if self._generaldelta and self._lazydeltabase:
3031 if self._generaldelta and self._lazydeltabase:
3034 delta_base_reuse_policy = DELTA_BASE_REUSE_TRY
3032 delta_base_reuse_policy = DELTA_BASE_REUSE_TRY
3035 else:
3033 else:
3036 delta_base_reuse_policy = DELTA_BASE_REUSE_NO
3034 delta_base_reuse_policy = DELTA_BASE_REUSE_NO
3037
3035
3038 self._adding_group = True
3036 self._adding_group = True
3039 empty = True
3037 empty = True
3040 try:
3038 try:
3041 with self._writing(transaction):
3039 with self._writing(transaction):
3042 write_debug = None
3040 write_debug = None
3043 if self._debug_delta:
3041 if self._debug_delta:
3044 write_debug = transaction._report
3042 write_debug = transaction._report
3045 deltacomputer = deltautil.deltacomputer(
3043 deltacomputer = deltautil.deltacomputer(
3046 self,
3044 self,
3047 write_debug=write_debug,
3045 write_debug=write_debug,
3048 debug_info=debug_info,
3046 debug_info=debug_info,
3049 )
3047 )
3050 # loop through our set of deltas
3048 # loop through our set of deltas
3051 for data in deltas:
3049 for data in deltas:
3052 (
3050 (
3053 node,
3051 node,
3054 p1,
3052 p1,
3055 p2,
3053 p2,
3056 linknode,
3054 linknode,
3057 deltabase,
3055 deltabase,
3058 delta,
3056 delta,
3059 flags,
3057 flags,
3060 sidedata,
3058 sidedata,
3061 ) = data
3059 ) = data
3062 link = linkmapper(linknode)
3060 link = linkmapper(linknode)
3063 flags = flags or REVIDX_DEFAULT_FLAGS
3061 flags = flags or REVIDX_DEFAULT_FLAGS
3064
3062
3065 rev = self.index.get_rev(node)
3063 rev = self.index.get_rev(node)
3066 if rev is not None:
3064 if rev is not None:
3067 # this can happen if two branches make the same change
3065 # this can happen if two branches make the same change
3068 self._nodeduplicatecallback(transaction, rev)
3066 self._nodeduplicatecallback(transaction, rev)
3069 if duplicaterevisioncb:
3067 if duplicaterevisioncb:
3070 duplicaterevisioncb(self, rev)
3068 duplicaterevisioncb(self, rev)
3071 empty = False
3069 empty = False
3072 continue
3070 continue
3073
3071
3074 for p in (p1, p2):
3072 for p in (p1, p2):
3075 if not self.index.has_node(p):
3073 if not self.index.has_node(p):
3076 raise error.LookupError(
3074 raise error.LookupError(
3077 p, self.radix, _(b'unknown parent')
3075 p, self.radix, _(b'unknown parent')
3078 )
3076 )
3079
3077
3080 if not self.index.has_node(deltabase):
3078 if not self.index.has_node(deltabase):
3081 raise error.LookupError(
3079 raise error.LookupError(
3082 deltabase, self.display_id, _(b'unknown delta base')
3080 deltabase, self.display_id, _(b'unknown delta base')
3083 )
3081 )
3084
3082
3085 baserev = self.rev(deltabase)
3083 baserev = self.rev(deltabase)
3086
3084
3087 if baserev != nullrev and self.iscensored(baserev):
3085 if baserev != nullrev and self.iscensored(baserev):
3088 # if base is censored, delta must be full replacement in a
3086 # if base is censored, delta must be full replacement in a
3089 # single patch operation
3087 # single patch operation
3090 hlen = struct.calcsize(b">lll")
3088 hlen = struct.calcsize(b">lll")
3091 oldlen = self.rawsize(baserev)
3089 oldlen = self.rawsize(baserev)
3092 newlen = len(delta) - hlen
3090 newlen = len(delta) - hlen
3093 if delta[:hlen] != mdiff.replacediffheader(
3091 if delta[:hlen] != mdiff.replacediffheader(
3094 oldlen, newlen
3092 oldlen, newlen
3095 ):
3093 ):
3096 raise error.CensoredBaseError(
3094 raise error.CensoredBaseError(
3097 self.display_id, self.node(baserev)
3095 self.display_id, self.node(baserev)
3098 )
3096 )
3099
3097
3100 if not flags and self._peek_iscensored(baserev, delta):
3098 if not flags and self._peek_iscensored(baserev, delta):
3101 flags |= REVIDX_ISCENSORED
3099 flags |= REVIDX_ISCENSORED
3102
3100
3103 # We assume consumers of addrevisioncb will want to retrieve
3101 # We assume consumers of addrevisioncb will want to retrieve
3104 # the added revision, which will require a call to
3102 # the added revision, which will require a call to
3105 # revision(). revision() will fast path if there is a cache
3103 # revision(). revision() will fast path if there is a cache
3106 # hit. So, we tell _addrevision() to always cache in this case.
3104 # hit. So, we tell _addrevision() to always cache in this case.
3107 # We're only using addgroup() in the context of changegroup
3105 # We're only using addgroup() in the context of changegroup
3108 # generation so the revision data can always be handled as raw
3106 # generation so the revision data can always be handled as raw
3109 # by the flagprocessor.
3107 # by the flagprocessor.
3110 rev = self._addrevision(
3108 rev = self._addrevision(
3111 node,
3109 node,
3112 None,
3110 None,
3113 transaction,
3111 transaction,
3114 link,
3112 link,
3115 p1,
3113 p1,
3116 p2,
3114 p2,
3117 flags,
3115 flags,
3118 (baserev, delta, delta_base_reuse_policy),
3116 (baserev, delta, delta_base_reuse_policy),
3119 alwayscache=alwayscache,
3117 alwayscache=alwayscache,
3120 deltacomputer=deltacomputer,
3118 deltacomputer=deltacomputer,
3121 sidedata=sidedata,
3119 sidedata=sidedata,
3122 )
3120 )
3123
3121
3124 if addrevisioncb:
3122 if addrevisioncb:
3125 addrevisioncb(self, rev)
3123 addrevisioncb(self, rev)
3126 empty = False
3124 empty = False
3127 finally:
3125 finally:
3128 self._adding_group = False
3126 self._adding_group = False
3129 return not empty
3127 return not empty
3130
3128
3131 def iscensored(self, rev):
3129 def iscensored(self, rev):
3132 """Check if a file revision is censored."""
3130 """Check if a file revision is censored."""
3133 if not self._censorable:
3131 if not self._censorable:
3134 return False
3132 return False
3135
3133
3136 return self.flags(rev) & REVIDX_ISCENSORED
3134 return self.flags(rev) & REVIDX_ISCENSORED
3137
3135
3138 def _peek_iscensored(self, baserev, delta):
3136 def _peek_iscensored(self, baserev, delta):
3139 """Quickly check if a delta produces a censored revision."""
3137 """Quickly check if a delta produces a censored revision."""
3140 if not self._censorable:
3138 if not self._censorable:
3141 return False
3139 return False
3142
3140
3143 return storageutil.deltaiscensored(delta, baserev, self.rawsize)
3141 return storageutil.deltaiscensored(delta, baserev, self.rawsize)
3144
3142
3145 def getstrippoint(self, minlink):
3143 def getstrippoint(self, minlink):
3146 """find the minimum rev that must be stripped to strip the linkrev
3144 """find the minimum rev that must be stripped to strip the linkrev
3147
3145
3148 Returns a tuple containing the minimum rev and a set of all revs that
3146 Returns a tuple containing the minimum rev and a set of all revs that
3149 have linkrevs that will be broken by this strip.
3147 have linkrevs that will be broken by this strip.
3150 """
3148 """
3151 return storageutil.resolvestripinfo(
3149 return storageutil.resolvestripinfo(
3152 minlink,
3150 minlink,
3153 len(self) - 1,
3151 len(self) - 1,
3154 self.headrevs(),
3152 self.headrevs(),
3155 self.linkrev,
3153 self.linkrev,
3156 self.parentrevs,
3154 self.parentrevs,
3157 )
3155 )
3158
3156
3159 def strip(self, minlink, transaction):
3157 def strip(self, minlink, transaction):
3160 """truncate the revlog on the first revision with a linkrev >= minlink
3158 """truncate the revlog on the first revision with a linkrev >= minlink
3161
3159
3162 This function is called when we're stripping revision minlink and
3160 This function is called when we're stripping revision minlink and
3163 its descendants from the repository.
3161 its descendants from the repository.
3164
3162
3165 We have to remove all revisions with linkrev >= minlink, because
3163 We have to remove all revisions with linkrev >= minlink, because
3166 the equivalent changelog revisions will be renumbered after the
3164 the equivalent changelog revisions will be renumbered after the
3167 strip.
3165 strip.
3168
3166
3169 So we truncate the revlog on the first of these revisions, and
3167 So we truncate the revlog on the first of these revisions, and
3170 trust that the caller has saved the revisions that shouldn't be
3168 trust that the caller has saved the revisions that shouldn't be
3171 removed and that it'll re-add them after this truncation.
3169 removed and that it'll re-add them after this truncation.
3172 """
3170 """
3173 if len(self) == 0:
3171 if len(self) == 0:
3174 return
3172 return
3175
3173
3176 rev, _ = self.getstrippoint(minlink)
3174 rev, _ = self.getstrippoint(minlink)
3177 if rev == len(self):
3175 if rev == len(self):
3178 return
3176 return
3179
3177
3180 # first truncate the files on disk
3178 # first truncate the files on disk
3181 data_end = self.start(rev)
3179 data_end = self.start(rev)
3182 if not self._inline:
3180 if not self._inline:
3183 transaction.add(self._datafile, data_end)
3181 transaction.add(self._datafile, data_end)
3184 end = rev * self.index.entry_size
3182 end = rev * self.index.entry_size
3185 else:
3183 else:
3186 end = data_end + (rev * self.index.entry_size)
3184 end = data_end + (rev * self.index.entry_size)
3187
3185
3188 if self._sidedatafile:
3186 if self._sidedatafile:
3189 sidedata_end = self.sidedata_cut_off(rev)
3187 sidedata_end = self.sidedata_cut_off(rev)
3190 transaction.add(self._sidedatafile, sidedata_end)
3188 transaction.add(self._sidedatafile, sidedata_end)
3191
3189
3192 transaction.add(self._indexfile, end)
3190 transaction.add(self._indexfile, end)
3193 if self._docket is not None:
3191 if self._docket is not None:
3194 # XXX we could, leverage the docket while stripping. However it is
3192 # XXX we could, leverage the docket while stripping. However it is
3195 # not powerfull enough at the time of this comment
3193 # not powerfull enough at the time of this comment
3196 self._docket.index_end = end
3194 self._docket.index_end = end
3197 self._docket.data_end = data_end
3195 self._docket.data_end = data_end
3198 self._docket.sidedata_end = sidedata_end
3196 self._docket.sidedata_end = sidedata_end
3199 self._docket.write(transaction, stripping=True)
3197 self._docket.write(transaction, stripping=True)
3200
3198
3201 # then reset internal state in memory to forget those revisions
3199 # then reset internal state in memory to forget those revisions
3202 self._revisioncache = None
3200 self._revisioncache = None
3203 self._chaininfocache = util.lrucachedict(500)
3201 self._chaininfocache = util.lrucachedict(500)
3204 self._segmentfile.clear_cache()
3202 self._segmentfile.clear_cache()
3205 self._segmentfile_sidedata.clear_cache()
3203 self._segmentfile_sidedata.clear_cache()
3206
3204
3207 del self.index[rev:-1]
3205 del self.index[rev:-1]
3208
3206
3209 def checksize(self):
3207 def checksize(self):
3210 """Check size of index and data files
3208 """Check size of index and data files
3211
3209
3212 return a (dd, di) tuple.
3210 return a (dd, di) tuple.
3213 - dd: extra bytes for the "data" file
3211 - dd: extra bytes for the "data" file
3214 - di: extra bytes for the "index" file
3212 - di: extra bytes for the "index" file
3215
3213
3216 A healthy revlog will return (0, 0).
3214 A healthy revlog will return (0, 0).
3217 """
3215 """
3218 expected = 0
3216 expected = 0
3219 if len(self):
3217 if len(self):
3220 expected = max(0, self.end(len(self) - 1))
3218 expected = max(0, self.end(len(self) - 1))
3221
3219
3222 try:
3220 try:
3223 with self._datafp() as f:
3221 with self._datafp() as f:
3224 f.seek(0, io.SEEK_END)
3222 f.seek(0, io.SEEK_END)
3225 actual = f.tell()
3223 actual = f.tell()
3226 dd = actual - expected
3224 dd = actual - expected
3227 except FileNotFoundError:
3225 except FileNotFoundError:
3228 dd = 0
3226 dd = 0
3229
3227
3230 try:
3228 try:
3231 f = self.opener(self._indexfile)
3229 f = self.opener(self._indexfile)
3232 f.seek(0, io.SEEK_END)
3230 f.seek(0, io.SEEK_END)
3233 actual = f.tell()
3231 actual = f.tell()
3234 f.close()
3232 f.close()
3235 s = self.index.entry_size
3233 s = self.index.entry_size
3236 i = max(0, actual // s)
3234 i = max(0, actual // s)
3237 di = actual - (i * s)
3235 di = actual - (i * s)
3238 if self._inline:
3236 if self._inline:
3239 databytes = 0
3237 databytes = 0
3240 for r in self:
3238 for r in self:
3241 databytes += max(0, self.length(r))
3239 databytes += max(0, self.length(r))
3242 dd = 0
3240 dd = 0
3243 di = actual - len(self) * s - databytes
3241 di = actual - len(self) * s - databytes
3244 except FileNotFoundError:
3242 except FileNotFoundError:
3245 di = 0
3243 di = 0
3246
3244
3247 return (dd, di)
3245 return (dd, di)
3248
3246
3249 def files(self):
3247 def files(self):
3250 res = [self._indexfile]
3248 res = [self._indexfile]
3251 if self._docket_file is None:
3249 if self._docket_file is None:
3252 if not self._inline:
3250 if not self._inline:
3253 res.append(self._datafile)
3251 res.append(self._datafile)
3254 else:
3252 else:
3255 res.append(self._docket_file)
3253 res.append(self._docket_file)
3256 res.extend(self._docket.old_index_filepaths(include_empty=False))
3254 res.extend(self._docket.old_index_filepaths(include_empty=False))
3257 if self._docket.data_end:
3255 if self._docket.data_end:
3258 res.append(self._datafile)
3256 res.append(self._datafile)
3259 res.extend(self._docket.old_data_filepaths(include_empty=False))
3257 res.extend(self._docket.old_data_filepaths(include_empty=False))
3260 if self._docket.sidedata_end:
3258 if self._docket.sidedata_end:
3261 res.append(self._sidedatafile)
3259 res.append(self._sidedatafile)
3262 res.extend(self._docket.old_sidedata_filepaths(include_empty=False))
3260 res.extend(self._docket.old_sidedata_filepaths(include_empty=False))
3263 return res
3261 return res
3264
3262
3265 def emitrevisions(
3263 def emitrevisions(
3266 self,
3264 self,
3267 nodes,
3265 nodes,
3268 nodesorder=None,
3266 nodesorder=None,
3269 revisiondata=False,
3267 revisiondata=False,
3270 assumehaveparentrevisions=False,
3268 assumehaveparentrevisions=False,
3271 deltamode=repository.CG_DELTAMODE_STD,
3269 deltamode=repository.CG_DELTAMODE_STD,
3272 sidedata_helpers=None,
3270 sidedata_helpers=None,
3273 debug_info=None,
3271 debug_info=None,
3274 ):
3272 ):
3275 if nodesorder not in (b'nodes', b'storage', b'linear', None):
3273 if nodesorder not in (b'nodes', b'storage', b'linear', None):
3276 raise error.ProgrammingError(
3274 raise error.ProgrammingError(
3277 b'unhandled value for nodesorder: %s' % nodesorder
3275 b'unhandled value for nodesorder: %s' % nodesorder
3278 )
3276 )
3279
3277
3280 if nodesorder is None and not self._generaldelta:
3278 if nodesorder is None and not self._generaldelta:
3281 nodesorder = b'storage'
3279 nodesorder = b'storage'
3282
3280
3283 if (
3281 if (
3284 not self._storedeltachains
3282 not self._storedeltachains
3285 and deltamode != repository.CG_DELTAMODE_PREV
3283 and deltamode != repository.CG_DELTAMODE_PREV
3286 ):
3284 ):
3287 deltamode = repository.CG_DELTAMODE_FULL
3285 deltamode = repository.CG_DELTAMODE_FULL
3288
3286
3289 return storageutil.emitrevisions(
3287 return storageutil.emitrevisions(
3290 self,
3288 self,
3291 nodes,
3289 nodes,
3292 nodesorder,
3290 nodesorder,
3293 revlogrevisiondelta,
3291 revlogrevisiondelta,
3294 deltaparentfn=self.deltaparent,
3292 deltaparentfn=self.deltaparent,
3295 candeltafn=self._candelta,
3293 candeltafn=self._candelta,
3296 rawsizefn=self.rawsize,
3294 rawsizefn=self.rawsize,
3297 revdifffn=self.revdiff,
3295 revdifffn=self.revdiff,
3298 flagsfn=self.flags,
3296 flagsfn=self.flags,
3299 deltamode=deltamode,
3297 deltamode=deltamode,
3300 revisiondata=revisiondata,
3298 revisiondata=revisiondata,
3301 assumehaveparentrevisions=assumehaveparentrevisions,
3299 assumehaveparentrevisions=assumehaveparentrevisions,
3302 sidedata_helpers=sidedata_helpers,
3300 sidedata_helpers=sidedata_helpers,
3303 debug_info=debug_info,
3301 debug_info=debug_info,
3304 )
3302 )
3305
3303
3306 DELTAREUSEALWAYS = b'always'
3304 DELTAREUSEALWAYS = b'always'
3307 DELTAREUSESAMEREVS = b'samerevs'
3305 DELTAREUSESAMEREVS = b'samerevs'
3308 DELTAREUSENEVER = b'never'
3306 DELTAREUSENEVER = b'never'
3309
3307
3310 DELTAREUSEFULLADD = b'fulladd'
3308 DELTAREUSEFULLADD = b'fulladd'
3311
3309
3312 DELTAREUSEALL = {b'always', b'samerevs', b'never', b'fulladd'}
3310 DELTAREUSEALL = {b'always', b'samerevs', b'never', b'fulladd'}
3313
3311
3314 def clone(
3312 def clone(
3315 self,
3313 self,
3316 tr,
3314 tr,
3317 destrevlog,
3315 destrevlog,
3318 addrevisioncb=None,
3316 addrevisioncb=None,
3319 deltareuse=DELTAREUSESAMEREVS,
3317 deltareuse=DELTAREUSESAMEREVS,
3320 forcedeltabothparents=None,
3318 forcedeltabothparents=None,
3321 sidedata_helpers=None,
3319 sidedata_helpers=None,
3322 ):
3320 ):
3323 """Copy this revlog to another, possibly with format changes.
3321 """Copy this revlog to another, possibly with format changes.
3324
3322
3325 The destination revlog will contain the same revisions and nodes.
3323 The destination revlog will contain the same revisions and nodes.
3326 However, it may not be bit-for-bit identical due to e.g. delta encoding
3324 However, it may not be bit-for-bit identical due to e.g. delta encoding
3327 differences.
3325 differences.
3328
3326
3329 The ``deltareuse`` argument control how deltas from the existing revlog
3327 The ``deltareuse`` argument control how deltas from the existing revlog
3330 are preserved in the destination revlog. The argument can have the
3328 are preserved in the destination revlog. The argument can have the
3331 following values:
3329 following values:
3332
3330
3333 DELTAREUSEALWAYS
3331 DELTAREUSEALWAYS
3334 Deltas will always be reused (if possible), even if the destination
3332 Deltas will always be reused (if possible), even if the destination
3335 revlog would not select the same revisions for the delta. This is the
3333 revlog would not select the same revisions for the delta. This is the
3336 fastest mode of operation.
3334 fastest mode of operation.
3337 DELTAREUSESAMEREVS
3335 DELTAREUSESAMEREVS
3338 Deltas will be reused if the destination revlog would pick the same
3336 Deltas will be reused if the destination revlog would pick the same
3339 revisions for the delta. This mode strikes a balance between speed
3337 revisions for the delta. This mode strikes a balance between speed
3340 and optimization.
3338 and optimization.
3341 DELTAREUSENEVER
3339 DELTAREUSENEVER
3342 Deltas will never be reused. This is the slowest mode of execution.
3340 Deltas will never be reused. This is the slowest mode of execution.
3343 This mode can be used to recompute deltas (e.g. if the diff/delta
3341 This mode can be used to recompute deltas (e.g. if the diff/delta
3344 algorithm changes).
3342 algorithm changes).
3345 DELTAREUSEFULLADD
3343 DELTAREUSEFULLADD
3346 Revision will be re-added as if their were new content. This is
3344 Revision will be re-added as if their were new content. This is
3347 slower than DELTAREUSEALWAYS but allow more mechanism to kicks in.
3345 slower than DELTAREUSEALWAYS but allow more mechanism to kicks in.
3348 eg: large file detection and handling.
3346 eg: large file detection and handling.
3349
3347
3350 Delta computation can be slow, so the choice of delta reuse policy can
3348 Delta computation can be slow, so the choice of delta reuse policy can
3351 significantly affect run time.
3349 significantly affect run time.
3352
3350
3353 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
3351 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
3354 two extremes. Deltas will be reused if they are appropriate. But if the
3352 two extremes. Deltas will be reused if they are appropriate. But if the
3355 delta could choose a better revision, it will do so. This means if you
3353 delta could choose a better revision, it will do so. This means if you
3356 are converting a non-generaldelta revlog to a generaldelta revlog,
3354 are converting a non-generaldelta revlog to a generaldelta revlog,
3357 deltas will be recomputed if the delta's parent isn't a parent of the
3355 deltas will be recomputed if the delta's parent isn't a parent of the
3358 revision.
3356 revision.
3359
3357
3360 In addition to the delta policy, the ``forcedeltabothparents``
3358 In addition to the delta policy, the ``forcedeltabothparents``
3361 argument controls whether to force compute deltas against both parents
3359 argument controls whether to force compute deltas against both parents
3362 for merges. By default, the current default is used.
3360 for merges. By default, the current default is used.
3363
3361
3364 See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
3362 See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
3365 `sidedata_helpers`.
3363 `sidedata_helpers`.
3366 """
3364 """
3367 if deltareuse not in self.DELTAREUSEALL:
3365 if deltareuse not in self.DELTAREUSEALL:
3368 raise ValueError(
3366 raise ValueError(
3369 _(b'value for deltareuse invalid: %s') % deltareuse
3367 _(b'value for deltareuse invalid: %s') % deltareuse
3370 )
3368 )
3371
3369
3372 if len(destrevlog):
3370 if len(destrevlog):
3373 raise ValueError(_(b'destination revlog is not empty'))
3371 raise ValueError(_(b'destination revlog is not empty'))
3374
3372
3375 if getattr(self, 'filteredrevs', None):
3373 if getattr(self, 'filteredrevs', None):
3376 raise ValueError(_(b'source revlog has filtered revisions'))
3374 raise ValueError(_(b'source revlog has filtered revisions'))
3377 if getattr(destrevlog, 'filteredrevs', None):
3375 if getattr(destrevlog, 'filteredrevs', None):
3378 raise ValueError(_(b'destination revlog has filtered revisions'))
3376 raise ValueError(_(b'destination revlog has filtered revisions'))
3379
3377
3380 # lazydelta and lazydeltabase controls whether to reuse a cached delta,
3378 # lazydelta and lazydeltabase controls whether to reuse a cached delta,
3381 # if possible.
3379 # if possible.
3382 old_delta_config = destrevlog.delta_config
3380 old_delta_config = destrevlog.delta_config
3383 destrevlog.delta_config = destrevlog.delta_config.copy()
3381 destrevlog.delta_config = destrevlog.delta_config.copy()
3384
3382
3385 try:
3383 try:
3386 if deltareuse == self.DELTAREUSEALWAYS:
3384 if deltareuse == self.DELTAREUSEALWAYS:
3387 destrevlog.delta_config.lazy_delta_base = True
3385 destrevlog.delta_config.lazy_delta_base = True
3388 destrevlog.delta_config.lazy_delta = True
3386 destrevlog.delta_config.lazy_delta = True
3389 elif deltareuse == self.DELTAREUSESAMEREVS:
3387 elif deltareuse == self.DELTAREUSESAMEREVS:
3390 destrevlog.delta_config.lazy_delta_base = False
3388 destrevlog.delta_config.lazy_delta_base = False
3391 destrevlog.delta_config.lazy_delta = True
3389 destrevlog.delta_config.lazy_delta = True
3392 elif deltareuse == self.DELTAREUSENEVER:
3390 elif deltareuse == self.DELTAREUSENEVER:
3393 destrevlog.delta_config.lazy_delta_base = False
3391 destrevlog.delta_config.lazy_delta_base = False
3394 destrevlog.delta_config.lazy_delta = False
3392 destrevlog.delta_config.lazy_delta = False
3395
3393
3396 delta_both_parents = (
3394 delta_both_parents = (
3397 forcedeltabothparents or old_delta_config.delta_both_parents
3395 forcedeltabothparents or old_delta_config.delta_both_parents
3398 )
3396 )
3399 destrevlog.delta_config.delta_both_parents = delta_both_parents
3397 destrevlog.delta_config.delta_both_parents = delta_both_parents
3400
3398
3401 with self.reading():
3399 with self.reading():
3402 self._clone(
3400 self._clone(
3403 tr,
3401 tr,
3404 destrevlog,
3402 destrevlog,
3405 addrevisioncb,
3403 addrevisioncb,
3406 deltareuse,
3404 deltareuse,
3407 forcedeltabothparents,
3405 forcedeltabothparents,
3408 sidedata_helpers,
3406 sidedata_helpers,
3409 )
3407 )
3410
3408
3411 finally:
3409 finally:
3412 destrevlog.delta_config = old_delta_config
3410 destrevlog.delta_config = old_delta_config
3413
3411
3414 def _clone(
3412 def _clone(
3415 self,
3413 self,
3416 tr,
3414 tr,
3417 destrevlog,
3415 destrevlog,
3418 addrevisioncb,
3416 addrevisioncb,
3419 deltareuse,
3417 deltareuse,
3420 forcedeltabothparents,
3418 forcedeltabothparents,
3421 sidedata_helpers,
3419 sidedata_helpers,
3422 ):
3420 ):
3423 """perform the core duty of `revlog.clone` after parameter processing"""
3421 """perform the core duty of `revlog.clone` after parameter processing"""
3424 write_debug = None
3422 write_debug = None
3425 if self._debug_delta:
3423 if self._debug_delta:
3426 write_debug = tr._report
3424 write_debug = tr._report
3427 deltacomputer = deltautil.deltacomputer(
3425 deltacomputer = deltautil.deltacomputer(
3428 destrevlog,
3426 destrevlog,
3429 write_debug=write_debug,
3427 write_debug=write_debug,
3430 )
3428 )
3431 index = self.index
3429 index = self.index
3432 for rev in self:
3430 for rev in self:
3433 entry = index[rev]
3431 entry = index[rev]
3434
3432
3435 # Some classes override linkrev to take filtered revs into
3433 # Some classes override linkrev to take filtered revs into
3436 # account. Use raw entry from index.
3434 # account. Use raw entry from index.
3437 flags = entry[0] & 0xFFFF
3435 flags = entry[0] & 0xFFFF
3438 linkrev = entry[4]
3436 linkrev = entry[4]
3439 p1 = index[entry[5]][7]
3437 p1 = index[entry[5]][7]
3440 p2 = index[entry[6]][7]
3438 p2 = index[entry[6]][7]
3441 node = entry[7]
3439 node = entry[7]
3442
3440
3443 # (Possibly) reuse the delta from the revlog if allowed and
3441 # (Possibly) reuse the delta from the revlog if allowed and
3444 # the revlog chunk is a delta.
3442 # the revlog chunk is a delta.
3445 cachedelta = None
3443 cachedelta = None
3446 rawtext = None
3444 rawtext = None
3447 if deltareuse == self.DELTAREUSEFULLADD:
3445 if deltareuse == self.DELTAREUSEFULLADD:
3448 text = self._revisiondata(rev)
3446 text = self._revisiondata(rev)
3449 sidedata = self.sidedata(rev)
3447 sidedata = self.sidedata(rev)
3450
3448
3451 if sidedata_helpers is not None:
3449 if sidedata_helpers is not None:
3452 (sidedata, new_flags) = sidedatautil.run_sidedata_helpers(
3450 (sidedata, new_flags) = sidedatautil.run_sidedata_helpers(
3453 self, sidedata_helpers, sidedata, rev
3451 self, sidedata_helpers, sidedata, rev
3454 )
3452 )
3455 flags = flags | new_flags[0] & ~new_flags[1]
3453 flags = flags | new_flags[0] & ~new_flags[1]
3456
3454
3457 destrevlog.addrevision(
3455 destrevlog.addrevision(
3458 text,
3456 text,
3459 tr,
3457 tr,
3460 linkrev,
3458 linkrev,
3461 p1,
3459 p1,
3462 p2,
3460 p2,
3463 cachedelta=cachedelta,
3461 cachedelta=cachedelta,
3464 node=node,
3462 node=node,
3465 flags=flags,
3463 flags=flags,
3466 deltacomputer=deltacomputer,
3464 deltacomputer=deltacomputer,
3467 sidedata=sidedata,
3465 sidedata=sidedata,
3468 )
3466 )
3469 else:
3467 else:
3470 if destrevlog._lazydelta:
3468 if destrevlog._lazydelta:
3471 dp = self.deltaparent(rev)
3469 dp = self.deltaparent(rev)
3472 if dp != nullrev:
3470 if dp != nullrev:
3473 cachedelta = (dp, bytes(self._chunk(rev)))
3471 cachedelta = (dp, bytes(self._chunk(rev)))
3474
3472
3475 sidedata = None
3473 sidedata = None
3476 if not cachedelta:
3474 if not cachedelta:
3477 rawtext = self._revisiondata(rev)
3475 rawtext = self._revisiondata(rev)
3478 sidedata = self.sidedata(rev)
3476 sidedata = self.sidedata(rev)
3479 if sidedata is None:
3477 if sidedata is None:
3480 sidedata = self.sidedata(rev)
3478 sidedata = self.sidedata(rev)
3481
3479
3482 if sidedata_helpers is not None:
3480 if sidedata_helpers is not None:
3483 (sidedata, new_flags) = sidedatautil.run_sidedata_helpers(
3481 (sidedata, new_flags) = sidedatautil.run_sidedata_helpers(
3484 self, sidedata_helpers, sidedata, rev
3482 self, sidedata_helpers, sidedata, rev
3485 )
3483 )
3486 flags = flags | new_flags[0] & ~new_flags[1]
3484 flags = flags | new_flags[0] & ~new_flags[1]
3487
3485
3488 with destrevlog._writing(tr):
3486 with destrevlog._writing(tr):
3489 destrevlog._addrevision(
3487 destrevlog._addrevision(
3490 node,
3488 node,
3491 rawtext,
3489 rawtext,
3492 tr,
3490 tr,
3493 linkrev,
3491 linkrev,
3494 p1,
3492 p1,
3495 p2,
3493 p2,
3496 flags,
3494 flags,
3497 cachedelta,
3495 cachedelta,
3498 deltacomputer=deltacomputer,
3496 deltacomputer=deltacomputer,
3499 sidedata=sidedata,
3497 sidedata=sidedata,
3500 )
3498 )
3501
3499
3502 if addrevisioncb:
3500 if addrevisioncb:
3503 addrevisioncb(self, rev, node)
3501 addrevisioncb(self, rev, node)
3504
3502
3505 def censorrevision(self, tr, censornode, tombstone=b''):
3503 def censorrevision(self, tr, censornode, tombstone=b''):
3506 if self._format_version == REVLOGV0:
3504 if self._format_version == REVLOGV0:
3507 raise error.RevlogError(
3505 raise error.RevlogError(
3508 _(b'cannot censor with version %d revlogs')
3506 _(b'cannot censor with version %d revlogs')
3509 % self._format_version
3507 % self._format_version
3510 )
3508 )
3511 elif self._format_version == REVLOGV1:
3509 elif self._format_version == REVLOGV1:
3512 rewrite.v1_censor(self, tr, censornode, tombstone)
3510 rewrite.v1_censor(self, tr, censornode, tombstone)
3513 else:
3511 else:
3514 rewrite.v2_censor(self, tr, censornode, tombstone)
3512 rewrite.v2_censor(self, tr, censornode, tombstone)
3515
3513
3516 def verifyintegrity(self, state):
3514 def verifyintegrity(self, state):
3517 """Verifies the integrity of the revlog.
3515 """Verifies the integrity of the revlog.
3518
3516
3519 Yields ``revlogproblem`` instances describing problems that are
3517 Yields ``revlogproblem`` instances describing problems that are
3520 found.
3518 found.
3521 """
3519 """
3522 dd, di = self.checksize()
3520 dd, di = self.checksize()
3523 if dd:
3521 if dd:
3524 yield revlogproblem(error=_(b'data length off by %d bytes') % dd)
3522 yield revlogproblem(error=_(b'data length off by %d bytes') % dd)
3525 if di:
3523 if di:
3526 yield revlogproblem(error=_(b'index contains %d extra bytes') % di)
3524 yield revlogproblem(error=_(b'index contains %d extra bytes') % di)
3527
3525
3528 version = self._format_version
3526 version = self._format_version
3529
3527
3530 # The verifier tells us what version revlog we should be.
3528 # The verifier tells us what version revlog we should be.
3531 if version != state[b'expectedversion']:
3529 if version != state[b'expectedversion']:
3532 yield revlogproblem(
3530 yield revlogproblem(
3533 warning=_(b"warning: '%s' uses revlog format %d; expected %d")
3531 warning=_(b"warning: '%s' uses revlog format %d; expected %d")
3534 % (self.display_id, version, state[b'expectedversion'])
3532 % (self.display_id, version, state[b'expectedversion'])
3535 )
3533 )
3536
3534
3537 state[b'skipread'] = set()
3535 state[b'skipread'] = set()
3538 state[b'safe_renamed'] = set()
3536 state[b'safe_renamed'] = set()
3539
3537
3540 for rev in self:
3538 for rev in self:
3541 node = self.node(rev)
3539 node = self.node(rev)
3542
3540
3543 # Verify contents. 4 cases to care about:
3541 # Verify contents. 4 cases to care about:
3544 #
3542 #
3545 # common: the most common case
3543 # common: the most common case
3546 # rename: with a rename
3544 # rename: with a rename
3547 # meta: file content starts with b'\1\n', the metadata
3545 # meta: file content starts with b'\1\n', the metadata
3548 # header defined in filelog.py, but without a rename
3546 # header defined in filelog.py, but without a rename
3549 # ext: content stored externally
3547 # ext: content stored externally
3550 #
3548 #
3551 # More formally, their differences are shown below:
3549 # More formally, their differences are shown below:
3552 #
3550 #
3553 # | common | rename | meta | ext
3551 # | common | rename | meta | ext
3554 # -------------------------------------------------------
3552 # -------------------------------------------------------
3555 # flags() | 0 | 0 | 0 | not 0
3553 # flags() | 0 | 0 | 0 | not 0
3556 # renamed() | False | True | False | ?
3554 # renamed() | False | True | False | ?
3557 # rawtext[0:2]=='\1\n'| False | True | True | ?
3555 # rawtext[0:2]=='\1\n'| False | True | True | ?
3558 #
3556 #
3559 # "rawtext" means the raw text stored in revlog data, which
3557 # "rawtext" means the raw text stored in revlog data, which
3560 # could be retrieved by "rawdata(rev)". "text"
3558 # could be retrieved by "rawdata(rev)". "text"
3561 # mentioned below is "revision(rev)".
3559 # mentioned below is "revision(rev)".
3562 #
3560 #
3563 # There are 3 different lengths stored physically:
3561 # There are 3 different lengths stored physically:
3564 # 1. L1: rawsize, stored in revlog index
3562 # 1. L1: rawsize, stored in revlog index
3565 # 2. L2: len(rawtext), stored in revlog data
3563 # 2. L2: len(rawtext), stored in revlog data
3566 # 3. L3: len(text), stored in revlog data if flags==0, or
3564 # 3. L3: len(text), stored in revlog data if flags==0, or
3567 # possibly somewhere else if flags!=0
3565 # possibly somewhere else if flags!=0
3568 #
3566 #
3569 # L1 should be equal to L2. L3 could be different from them.
3567 # L1 should be equal to L2. L3 could be different from them.
3570 # "text" may or may not affect commit hash depending on flag
3568 # "text" may or may not affect commit hash depending on flag
3571 # processors (see flagutil.addflagprocessor).
3569 # processors (see flagutil.addflagprocessor).
3572 #
3570 #
3573 # | common | rename | meta | ext
3571 # | common | rename | meta | ext
3574 # -------------------------------------------------
3572 # -------------------------------------------------
3575 # rawsize() | L1 | L1 | L1 | L1
3573 # rawsize() | L1 | L1 | L1 | L1
3576 # size() | L1 | L2-LM | L1(*) | L1 (?)
3574 # size() | L1 | L2-LM | L1(*) | L1 (?)
3577 # len(rawtext) | L2 | L2 | L2 | L2
3575 # len(rawtext) | L2 | L2 | L2 | L2
3578 # len(text) | L2 | L2 | L2 | L3
3576 # len(text) | L2 | L2 | L2 | L3
3579 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
3577 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
3580 #
3578 #
3581 # LM: length of metadata, depending on rawtext
3579 # LM: length of metadata, depending on rawtext
3582 # (*): not ideal, see comment in filelog.size
3580 # (*): not ideal, see comment in filelog.size
3583 # (?): could be "- len(meta)" if the resolved content has
3581 # (?): could be "- len(meta)" if the resolved content has
3584 # rename metadata
3582 # rename metadata
3585 #
3583 #
3586 # Checks needed to be done:
3584 # Checks needed to be done:
3587 # 1. length check: L1 == L2, in all cases.
3585 # 1. length check: L1 == L2, in all cases.
3588 # 2. hash check: depending on flag processor, we may need to
3586 # 2. hash check: depending on flag processor, we may need to
3589 # use either "text" (external), or "rawtext" (in revlog).
3587 # use either "text" (external), or "rawtext" (in revlog).
3590
3588
3591 try:
3589 try:
3592 skipflags = state.get(b'skipflags', 0)
3590 skipflags = state.get(b'skipflags', 0)
3593 if skipflags:
3591 if skipflags:
3594 skipflags &= self.flags(rev)
3592 skipflags &= self.flags(rev)
3595
3593
3596 _verify_revision(self, skipflags, state, node)
3594 _verify_revision(self, skipflags, state, node)
3597
3595
3598 l1 = self.rawsize(rev)
3596 l1 = self.rawsize(rev)
3599 l2 = len(self.rawdata(node))
3597 l2 = len(self.rawdata(node))
3600
3598
3601 if l1 != l2:
3599 if l1 != l2:
3602 yield revlogproblem(
3600 yield revlogproblem(
3603 error=_(b'unpacked size is %d, %d expected') % (l2, l1),
3601 error=_(b'unpacked size is %d, %d expected') % (l2, l1),
3604 node=node,
3602 node=node,
3605 )
3603 )
3606
3604
3607 except error.CensoredNodeError:
3605 except error.CensoredNodeError:
3608 if state[b'erroroncensored']:
3606 if state[b'erroroncensored']:
3609 yield revlogproblem(
3607 yield revlogproblem(
3610 error=_(b'censored file data'), node=node
3608 error=_(b'censored file data'), node=node
3611 )
3609 )
3612 state[b'skipread'].add(node)
3610 state[b'skipread'].add(node)
3613 except Exception as e:
3611 except Exception as e:
3614 yield revlogproblem(
3612 yield revlogproblem(
3615 error=_(b'unpacking %s: %s')
3613 error=_(b'unpacking %s: %s')
3616 % (short(node), stringutil.forcebytestr(e)),
3614 % (short(node), stringutil.forcebytestr(e)),
3617 node=node,
3615 node=node,
3618 )
3616 )
3619 state[b'skipread'].add(node)
3617 state[b'skipread'].add(node)
3620
3618
3621 def storageinfo(
3619 def storageinfo(
3622 self,
3620 self,
3623 exclusivefiles=False,
3621 exclusivefiles=False,
3624 sharedfiles=False,
3622 sharedfiles=False,
3625 revisionscount=False,
3623 revisionscount=False,
3626 trackedsize=False,
3624 trackedsize=False,
3627 storedsize=False,
3625 storedsize=False,
3628 ):
3626 ):
3629 d = {}
3627 d = {}
3630
3628
3631 if exclusivefiles:
3629 if exclusivefiles:
3632 d[b'exclusivefiles'] = [(self.opener, self._indexfile)]
3630 d[b'exclusivefiles'] = [(self.opener, self._indexfile)]
3633 if not self._inline:
3631 if not self._inline:
3634 d[b'exclusivefiles'].append((self.opener, self._datafile))
3632 d[b'exclusivefiles'].append((self.opener, self._datafile))
3635
3633
3636 if sharedfiles:
3634 if sharedfiles:
3637 d[b'sharedfiles'] = []
3635 d[b'sharedfiles'] = []
3638
3636
3639 if revisionscount:
3637 if revisionscount:
3640 d[b'revisionscount'] = len(self)
3638 d[b'revisionscount'] = len(self)
3641
3639
3642 if trackedsize:
3640 if trackedsize:
3643 d[b'trackedsize'] = sum(map(self.rawsize, iter(self)))
3641 d[b'trackedsize'] = sum(map(self.rawsize, iter(self)))
3644
3642
3645 if storedsize:
3643 if storedsize:
3646 d[b'storedsize'] = sum(
3644 d[b'storedsize'] = sum(
3647 self.opener.stat(path).st_size for path in self.files()
3645 self.opener.stat(path).st_size for path in self.files()
3648 )
3646 )
3649
3647
3650 return d
3648 return d
3651
3649
3652 def rewrite_sidedata(self, transaction, helpers, startrev, endrev):
3650 def rewrite_sidedata(self, transaction, helpers, startrev, endrev):
3653 if not self.hassidedata:
3651 if not self.hassidedata:
3654 return
3652 return
3655 # revlog formats with sidedata support does not support inline
3653 # revlog formats with sidedata support does not support inline
3656 assert not self._inline
3654 assert not self._inline
3657 if not helpers[1] and not helpers[2]:
3655 if not helpers[1] and not helpers[2]:
3658 # Nothing to generate or remove
3656 # Nothing to generate or remove
3659 return
3657 return
3660
3658
3661 new_entries = []
3659 new_entries = []
3662 # append the new sidedata
3660 # append the new sidedata
3663 with self._writing(transaction):
3661 with self._writing(transaction):
3664 ifh, dfh, sdfh = self._writinghandles
3662 ifh, dfh, sdfh = self._writinghandles
3665 dfh.seek(self._docket.sidedata_end, os.SEEK_SET)
3663 dfh.seek(self._docket.sidedata_end, os.SEEK_SET)
3666
3664
3667 current_offset = sdfh.tell()
3665 current_offset = sdfh.tell()
3668 for rev in range(startrev, endrev + 1):
3666 for rev in range(startrev, endrev + 1):
3669 entry = self.index[rev]
3667 entry = self.index[rev]
3670 new_sidedata, flags = sidedatautil.run_sidedata_helpers(
3668 new_sidedata, flags = sidedatautil.run_sidedata_helpers(
3671 store=self,
3669 store=self,
3672 sidedata_helpers=helpers,
3670 sidedata_helpers=helpers,
3673 sidedata={},
3671 sidedata={},
3674 rev=rev,
3672 rev=rev,
3675 )
3673 )
3676
3674
3677 serialized_sidedata = sidedatautil.serialize_sidedata(
3675 serialized_sidedata = sidedatautil.serialize_sidedata(
3678 new_sidedata
3676 new_sidedata
3679 )
3677 )
3680
3678
3681 sidedata_compression_mode = COMP_MODE_INLINE
3679 sidedata_compression_mode = COMP_MODE_INLINE
3682 if serialized_sidedata and self.hassidedata:
3680 if serialized_sidedata and self.hassidedata:
3683 sidedata_compression_mode = COMP_MODE_PLAIN
3681 sidedata_compression_mode = COMP_MODE_PLAIN
3684 h, comp_sidedata = self.compress(serialized_sidedata)
3682 h, comp_sidedata = self.compress(serialized_sidedata)
3685 if (
3683 if (
3686 h != b'u'
3684 h != b'u'
3687 and comp_sidedata[0] != b'\0'
3685 and comp_sidedata[0] != b'\0'
3688 and len(comp_sidedata) < len(serialized_sidedata)
3686 and len(comp_sidedata) < len(serialized_sidedata)
3689 ):
3687 ):
3690 assert not h
3688 assert not h
3691 if (
3689 if (
3692 comp_sidedata[0]
3690 comp_sidedata[0]
3693 == self._docket.default_compression_header
3691 == self._docket.default_compression_header
3694 ):
3692 ):
3695 sidedata_compression_mode = COMP_MODE_DEFAULT
3693 sidedata_compression_mode = COMP_MODE_DEFAULT
3696 serialized_sidedata = comp_sidedata
3694 serialized_sidedata = comp_sidedata
3697 else:
3695 else:
3698 sidedata_compression_mode = COMP_MODE_INLINE
3696 sidedata_compression_mode = COMP_MODE_INLINE
3699 serialized_sidedata = comp_sidedata
3697 serialized_sidedata = comp_sidedata
3700 if entry[8] != 0 or entry[9] != 0:
3698 if entry[8] != 0 or entry[9] != 0:
3701 # rewriting entries that already have sidedata is not
3699 # rewriting entries that already have sidedata is not
3702 # supported yet, because it introduces garbage data in the
3700 # supported yet, because it introduces garbage data in the
3703 # revlog.
3701 # revlog.
3704 msg = b"rewriting existing sidedata is not supported yet"
3702 msg = b"rewriting existing sidedata is not supported yet"
3705 raise error.Abort(msg)
3703 raise error.Abort(msg)
3706
3704
3707 # Apply (potential) flags to add and to remove after running
3705 # Apply (potential) flags to add and to remove after running
3708 # the sidedata helpers
3706 # the sidedata helpers
3709 new_offset_flags = entry[0] | flags[0] & ~flags[1]
3707 new_offset_flags = entry[0] | flags[0] & ~flags[1]
3710 entry_update = (
3708 entry_update = (
3711 current_offset,
3709 current_offset,
3712 len(serialized_sidedata),
3710 len(serialized_sidedata),
3713 new_offset_flags,
3711 new_offset_flags,
3714 sidedata_compression_mode,
3712 sidedata_compression_mode,
3715 )
3713 )
3716
3714
3717 # the sidedata computation might have move the file cursors around
3715 # the sidedata computation might have move the file cursors around
3718 sdfh.seek(current_offset, os.SEEK_SET)
3716 sdfh.seek(current_offset, os.SEEK_SET)
3719 sdfh.write(serialized_sidedata)
3717 sdfh.write(serialized_sidedata)
3720 new_entries.append(entry_update)
3718 new_entries.append(entry_update)
3721 current_offset += len(serialized_sidedata)
3719 current_offset += len(serialized_sidedata)
3722 self._docket.sidedata_end = sdfh.tell()
3720 self._docket.sidedata_end = sdfh.tell()
3723
3721
3724 # rewrite the new index entries
3722 # rewrite the new index entries
3725 ifh.seek(startrev * self.index.entry_size)
3723 ifh.seek(startrev * self.index.entry_size)
3726 for i, e in enumerate(new_entries):
3724 for i, e in enumerate(new_entries):
3727 rev = startrev + i
3725 rev = startrev + i
3728 self.index.replace_sidedata_info(rev, *e)
3726 self.index.replace_sidedata_info(rev, *e)
3729 packed = self.index.entry_binary(rev)
3727 packed = self.index.entry_binary(rev)
3730 if rev == 0 and self._docket is None:
3728 if rev == 0 and self._docket is None:
3731 header = self._format_flags | self._format_version
3729 header = self._format_flags | self._format_version
3732 header = self.index.pack_header(header)
3730 header = self.index.pack_header(header)
3733 packed = header + packed
3731 packed = header + packed
3734 ifh.write(packed)
3732 ifh.write(packed)
General Comments 0
You need to be logged in to leave comments. Login now