##// END OF EJS Templates
revlog: skip opener options to pass compression option values...
marmoute -
r51933:7d66621c default
parent child Browse files
Show More
@@ -1,4045 +1,4047 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 delta_config.max_deltachain_span = 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 delta_config.max_chain_len = maxchainlen
1143 delta_config.max_chain_len = 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 feature_config.compression_engine = r.split(b'-', 2)[2]
1152 feature_config.compression_engine = r.split(b'-', 2)[2]
1153
1153
1154 options[b'zlib.level'] = ui.configint(b'storage', b'revlog.zlib.level')
1154 zlib_level = ui.configint(b'storage', b'revlog.zlib.level')
1155 if options[b'zlib.level'] is not None:
1155 if zlib_level is not None:
1156 if not (0 <= options[b'zlib.level'] <= 9):
1156 if not (0 <= 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 % zlib_level)
1159 options[b'zstd.level'] = ui.configint(b'storage', b'revlog.zstd.level')
1159 feature_config.compression_engine_options[b'zlib.level'] = zlib_level
1160 if options[b'zstd.level'] is not None:
1160 zstd_level = ui.configint(b'storage', b'revlog.zstd.level')
1161 if not (0 <= options[b'zstd.level'] <= 22):
1161 if zstd_level is not None:
1162 if not (0 <= zstd_level <= 22):
1162 msg = _(b'invalid value for `storage.revlog.zstd.level` config: %d')
1163 msg = _(b'invalid value for `storage.revlog.zstd.level` config: %d')
1163 raise error.Abort(msg % options[b'zstd.level'])
1164 raise error.Abort(msg % zstd_level)
1165 feature_config.compression_engine_options[b'zstd.level'] = zstd_level
1164
1166
1165 if requirementsmod.NARROW_REQUIREMENT in requirements:
1167 if requirementsmod.NARROW_REQUIREMENT in requirements:
1166 options[b'enableellipsis'] = True
1168 options[b'enableellipsis'] = True
1167
1169
1168 if ui.configbool(b'experimental', b'rust.index'):
1170 if ui.configbool(b'experimental', b'rust.index'):
1169 options[b'rust.index'] = True
1171 options[b'rust.index'] = True
1170 if requirementsmod.NODEMAP_REQUIREMENT in requirements:
1172 if requirementsmod.NODEMAP_REQUIREMENT in requirements:
1171 slow_path = ui.config(
1173 slow_path = ui.config(
1172 b'storage', b'revlog.persistent-nodemap.slow-path'
1174 b'storage', b'revlog.persistent-nodemap.slow-path'
1173 )
1175 )
1174 if slow_path not in (b'allow', b'warn', b'abort'):
1176 if slow_path not in (b'allow', b'warn', b'abort'):
1175 default = ui.config_default(
1177 default = ui.config_default(
1176 b'storage', b'revlog.persistent-nodemap.slow-path'
1178 b'storage', b'revlog.persistent-nodemap.slow-path'
1177 )
1179 )
1178 msg = _(
1180 msg = _(
1179 b'unknown value for config '
1181 b'unknown value for config '
1180 b'"storage.revlog.persistent-nodemap.slow-path": "%s"\n'
1182 b'"storage.revlog.persistent-nodemap.slow-path": "%s"\n'
1181 )
1183 )
1182 ui.warn(msg % slow_path)
1184 ui.warn(msg % slow_path)
1183 if not ui.quiet:
1185 if not ui.quiet:
1184 ui.warn(_(b'falling back to default value: %s\n') % default)
1186 ui.warn(_(b'falling back to default value: %s\n') % default)
1185 slow_path = default
1187 slow_path = default
1186
1188
1187 msg = _(
1189 msg = _(
1188 b"accessing `persistent-nodemap` repository without associated "
1190 b"accessing `persistent-nodemap` repository without associated "
1189 b"fast implementation."
1191 b"fast implementation."
1190 )
1192 )
1191 hint = _(
1193 hint = _(
1192 b"check `hg help config.format.use-persistent-nodemap` "
1194 b"check `hg help config.format.use-persistent-nodemap` "
1193 b"for details"
1195 b"for details"
1194 )
1196 )
1195 if not revlog.HAS_FAST_PERSISTENT_NODEMAP:
1197 if not revlog.HAS_FAST_PERSISTENT_NODEMAP:
1196 if slow_path == b'warn':
1198 if slow_path == b'warn':
1197 msg = b"warning: " + msg + b'\n'
1199 msg = b"warning: " + msg + b'\n'
1198 ui.warn(msg)
1200 ui.warn(msg)
1199 if not ui.quiet:
1201 if not ui.quiet:
1200 hint = b'(' + hint + b')\n'
1202 hint = b'(' + hint + b')\n'
1201 ui.warn(hint)
1203 ui.warn(hint)
1202 if slow_path == b'abort':
1204 if slow_path == b'abort':
1203 raise error.Abort(msg, hint=hint)
1205 raise error.Abort(msg, hint=hint)
1204 options[b'persistent-nodemap'] = True
1206 options[b'persistent-nodemap'] = True
1205 if requirementsmod.DIRSTATE_V2_REQUIREMENT in requirements:
1207 if requirementsmod.DIRSTATE_V2_REQUIREMENT in requirements:
1206 slow_path = ui.config(b'storage', b'dirstate-v2.slow-path')
1208 slow_path = ui.config(b'storage', b'dirstate-v2.slow-path')
1207 if slow_path not in (b'allow', b'warn', b'abort'):
1209 if slow_path not in (b'allow', b'warn', b'abort'):
1208 default = ui.config_default(b'storage', b'dirstate-v2.slow-path')
1210 default = ui.config_default(b'storage', b'dirstate-v2.slow-path')
1209 msg = _(b'unknown value for config "dirstate-v2.slow-path": "%s"\n')
1211 msg = _(b'unknown value for config "dirstate-v2.slow-path": "%s"\n')
1210 ui.warn(msg % slow_path)
1212 ui.warn(msg % slow_path)
1211 if not ui.quiet:
1213 if not ui.quiet:
1212 ui.warn(_(b'falling back to default value: %s\n') % default)
1214 ui.warn(_(b'falling back to default value: %s\n') % default)
1213 slow_path = default
1215 slow_path = default
1214
1216
1215 msg = _(
1217 msg = _(
1216 b"accessing `dirstate-v2` repository without associated "
1218 b"accessing `dirstate-v2` repository without associated "
1217 b"fast implementation."
1219 b"fast implementation."
1218 )
1220 )
1219 hint = _(
1221 hint = _(
1220 b"check `hg help config.format.use-dirstate-v2` " b"for details"
1222 b"check `hg help config.format.use-dirstate-v2` " b"for details"
1221 )
1223 )
1222 if not dirstate.HAS_FAST_DIRSTATE_V2:
1224 if not dirstate.HAS_FAST_DIRSTATE_V2:
1223 if slow_path == b'warn':
1225 if slow_path == b'warn':
1224 msg = b"warning: " + msg + b'\n'
1226 msg = b"warning: " + msg + b'\n'
1225 ui.warn(msg)
1227 ui.warn(msg)
1226 if not ui.quiet:
1228 if not ui.quiet:
1227 hint = b'(' + hint + b')\n'
1229 hint = b'(' + hint + b')\n'
1228 ui.warn(hint)
1230 ui.warn(hint)
1229 if slow_path == b'abort':
1231 if slow_path == b'abort':
1230 raise error.Abort(msg, hint=hint)
1232 raise error.Abort(msg, hint=hint)
1231 if ui.configbool(b'storage', b'revlog.persistent-nodemap.mmap'):
1233 if ui.configbool(b'storage', b'revlog.persistent-nodemap.mmap'):
1232 options[b'persistent-nodemap.mmap'] = True
1234 options[b'persistent-nodemap.mmap'] = True
1233 if ui.configbool(b'devel', b'persistent-nodemap'):
1235 if ui.configbool(b'devel', b'persistent-nodemap'):
1234 options[b'devel-force-nodemap'] = True
1236 options[b'devel-force-nodemap'] = True
1235
1237
1236 return options
1238 return options
1237
1239
1238
1240
1239 def makemain(**kwargs):
1241 def makemain(**kwargs):
1240 """Produce a type conforming to ``ilocalrepositorymain``."""
1242 """Produce a type conforming to ``ilocalrepositorymain``."""
1241 return localrepository
1243 return localrepository
1242
1244
1243
1245
1244 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
1246 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
1245 class revlogfilestorage:
1247 class revlogfilestorage:
1246 """File storage when using revlogs."""
1248 """File storage when using revlogs."""
1247
1249
1248 def file(self, path):
1250 def file(self, path):
1249 if path.startswith(b'/'):
1251 if path.startswith(b'/'):
1250 path = path[1:]
1252 path = path[1:]
1251
1253
1252 try_split = (
1254 try_split = (
1253 self.currenttransaction() is not None
1255 self.currenttransaction() is not None
1254 or txnutil.mayhavepending(self.root)
1256 or txnutil.mayhavepending(self.root)
1255 )
1257 )
1256
1258
1257 return filelog.filelog(self.svfs, path, try_split=try_split)
1259 return filelog.filelog(self.svfs, path, try_split=try_split)
1258
1260
1259
1261
1260 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
1262 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
1261 class revlognarrowfilestorage:
1263 class revlognarrowfilestorage:
1262 """File storage when using revlogs and narrow files."""
1264 """File storage when using revlogs and narrow files."""
1263
1265
1264 def file(self, path):
1266 def file(self, path):
1265 if path.startswith(b'/'):
1267 if path.startswith(b'/'):
1266 path = path[1:]
1268 path = path[1:]
1267
1269
1268 try_split = (
1270 try_split = (
1269 self.currenttransaction() is not None
1271 self.currenttransaction() is not None
1270 or txnutil.mayhavepending(self.root)
1272 or txnutil.mayhavepending(self.root)
1271 )
1273 )
1272 return filelog.narrowfilelog(
1274 return filelog.narrowfilelog(
1273 self.svfs, path, self._storenarrowmatch, try_split=try_split
1275 self.svfs, path, self._storenarrowmatch, try_split=try_split
1274 )
1276 )
1275
1277
1276
1278
1277 def makefilestorage(requirements, features, **kwargs):
1279 def makefilestorage(requirements, features, **kwargs):
1278 """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
1280 """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
1279 features.add(repository.REPO_FEATURE_REVLOG_FILE_STORAGE)
1281 features.add(repository.REPO_FEATURE_REVLOG_FILE_STORAGE)
1280 features.add(repository.REPO_FEATURE_STREAM_CLONE)
1282 features.add(repository.REPO_FEATURE_STREAM_CLONE)
1281
1283
1282 if requirementsmod.NARROW_REQUIREMENT in requirements:
1284 if requirementsmod.NARROW_REQUIREMENT in requirements:
1283 return revlognarrowfilestorage
1285 return revlognarrowfilestorage
1284 else:
1286 else:
1285 return revlogfilestorage
1287 return revlogfilestorage
1286
1288
1287
1289
1288 # List of repository interfaces and factory functions for them. Each
1290 # List of repository interfaces and factory functions for them. Each
1289 # will be called in order during ``makelocalrepository()`` to iteratively
1291 # will be called in order during ``makelocalrepository()`` to iteratively
1290 # derive the final type for a local repository instance. We capture the
1292 # 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
1293 # function as a lambda so we don't hold a reference and the module-level
1292 # functions can be wrapped.
1294 # functions can be wrapped.
1293 REPO_INTERFACES = [
1295 REPO_INTERFACES = [
1294 (repository.ilocalrepositorymain, lambda: makemain),
1296 (repository.ilocalrepositorymain, lambda: makemain),
1295 (repository.ilocalrepositoryfilestorage, lambda: makefilestorage),
1297 (repository.ilocalrepositoryfilestorage, lambda: makefilestorage),
1296 ]
1298 ]
1297
1299
1298
1300
1299 @interfaceutil.implementer(repository.ilocalrepositorymain)
1301 @interfaceutil.implementer(repository.ilocalrepositorymain)
1300 class localrepository:
1302 class localrepository:
1301 """Main class for representing local repositories.
1303 """Main class for representing local repositories.
1302
1304
1303 All local repositories are instances of this class.
1305 All local repositories are instances of this class.
1304
1306
1305 Constructed on its own, instances of this class are not usable as
1307 Constructed on its own, instances of this class are not usable as
1306 repository objects. To obtain a usable repository object, call
1308 repository objects. To obtain a usable repository object, call
1307 ``hg.repository()``, ``localrepo.instance()``, or
1309 ``hg.repository()``, ``localrepo.instance()``, or
1308 ``localrepo.makelocalrepository()``. The latter is the lowest-level.
1310 ``localrepo.makelocalrepository()``. The latter is the lowest-level.
1309 ``instance()`` adds support for creating new repositories.
1311 ``instance()`` adds support for creating new repositories.
1310 ``hg.repository()`` adds more extension integration, including calling
1312 ``hg.repository()`` adds more extension integration, including calling
1311 ``reposetup()``. Generally speaking, ``hg.repository()`` should be
1313 ``reposetup()``. Generally speaking, ``hg.repository()`` should be
1312 used.
1314 used.
1313 """
1315 """
1314
1316
1315 _basesupported = {
1317 _basesupported = {
1316 requirementsmod.ARCHIVED_PHASE_REQUIREMENT,
1318 requirementsmod.ARCHIVED_PHASE_REQUIREMENT,
1317 requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT,
1319 requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT,
1318 requirementsmod.CHANGELOGV2_REQUIREMENT,
1320 requirementsmod.CHANGELOGV2_REQUIREMENT,
1319 requirementsmod.COPIESSDC_REQUIREMENT,
1321 requirementsmod.COPIESSDC_REQUIREMENT,
1320 requirementsmod.DIRSTATE_TRACKED_HINT_V1,
1322 requirementsmod.DIRSTATE_TRACKED_HINT_V1,
1321 requirementsmod.DIRSTATE_V2_REQUIREMENT,
1323 requirementsmod.DIRSTATE_V2_REQUIREMENT,
1322 requirementsmod.DOTENCODE_REQUIREMENT,
1324 requirementsmod.DOTENCODE_REQUIREMENT,
1323 requirementsmod.FNCACHE_REQUIREMENT,
1325 requirementsmod.FNCACHE_REQUIREMENT,
1324 requirementsmod.GENERALDELTA_REQUIREMENT,
1326 requirementsmod.GENERALDELTA_REQUIREMENT,
1325 requirementsmod.INTERNAL_PHASE_REQUIREMENT,
1327 requirementsmod.INTERNAL_PHASE_REQUIREMENT,
1326 requirementsmod.NODEMAP_REQUIREMENT,
1328 requirementsmod.NODEMAP_REQUIREMENT,
1327 requirementsmod.RELATIVE_SHARED_REQUIREMENT,
1329 requirementsmod.RELATIVE_SHARED_REQUIREMENT,
1328 requirementsmod.REVLOGV1_REQUIREMENT,
1330 requirementsmod.REVLOGV1_REQUIREMENT,
1329 requirementsmod.REVLOGV2_REQUIREMENT,
1331 requirementsmod.REVLOGV2_REQUIREMENT,
1330 requirementsmod.SHARED_REQUIREMENT,
1332 requirementsmod.SHARED_REQUIREMENT,
1331 requirementsmod.SHARESAFE_REQUIREMENT,
1333 requirementsmod.SHARESAFE_REQUIREMENT,
1332 requirementsmod.SPARSE_REQUIREMENT,
1334 requirementsmod.SPARSE_REQUIREMENT,
1333 requirementsmod.SPARSEREVLOG_REQUIREMENT,
1335 requirementsmod.SPARSEREVLOG_REQUIREMENT,
1334 requirementsmod.STORE_REQUIREMENT,
1336 requirementsmod.STORE_REQUIREMENT,
1335 requirementsmod.TREEMANIFEST_REQUIREMENT,
1337 requirementsmod.TREEMANIFEST_REQUIREMENT,
1336 }
1338 }
1337
1339
1338 # list of prefix for file which can be written without 'wlock'
1340 # list of prefix for file which can be written without 'wlock'
1339 # Extensions should extend this list when needed
1341 # Extensions should extend this list when needed
1340 _wlockfreeprefix = {
1342 _wlockfreeprefix = {
1341 # We migh consider requiring 'wlock' for the next
1343 # We migh consider requiring 'wlock' for the next
1342 # two, but pretty much all the existing code assume
1344 # two, but pretty much all the existing code assume
1343 # wlock is not needed so we keep them excluded for
1345 # wlock is not needed so we keep them excluded for
1344 # now.
1346 # now.
1345 b'hgrc',
1347 b'hgrc',
1346 b'requires',
1348 b'requires',
1347 # XXX cache is a complicatged business someone
1349 # XXX cache is a complicatged business someone
1348 # should investigate this in depth at some point
1350 # should investigate this in depth at some point
1349 b'cache/',
1351 b'cache/',
1350 # XXX bisect was still a bit too messy at the time
1352 # XXX bisect was still a bit too messy at the time
1351 # this changeset was introduced. Someone should fix
1353 # this changeset was introduced. Someone should fix
1352 # the remainig bit and drop this line
1354 # the remainig bit and drop this line
1353 b'bisect.state',
1355 b'bisect.state',
1354 }
1356 }
1355
1357
1356 def __init__(
1358 def __init__(
1357 self,
1359 self,
1358 baseui,
1360 baseui,
1359 ui,
1361 ui,
1360 origroot: bytes,
1362 origroot: bytes,
1361 wdirvfs: vfsmod.vfs,
1363 wdirvfs: vfsmod.vfs,
1362 hgvfs: vfsmod.vfs,
1364 hgvfs: vfsmod.vfs,
1363 requirements,
1365 requirements,
1364 supportedrequirements,
1366 supportedrequirements,
1365 sharedpath: bytes,
1367 sharedpath: bytes,
1366 store,
1368 store,
1367 cachevfs: vfsmod.vfs,
1369 cachevfs: vfsmod.vfs,
1368 wcachevfs: vfsmod.vfs,
1370 wcachevfs: vfsmod.vfs,
1369 features,
1371 features,
1370 intents=None,
1372 intents=None,
1371 ):
1373 ):
1372 """Create a new local repository instance.
1374 """Create a new local repository instance.
1373
1375
1374 Most callers should use ``hg.repository()``, ``localrepo.instance()``,
1376 Most callers should use ``hg.repository()``, ``localrepo.instance()``,
1375 or ``localrepo.makelocalrepository()`` for obtaining a new repository
1377 or ``localrepo.makelocalrepository()`` for obtaining a new repository
1376 object.
1378 object.
1377
1379
1378 Arguments:
1380 Arguments:
1379
1381
1380 baseui
1382 baseui
1381 ``ui.ui`` instance that ``ui`` argument was based off of.
1383 ``ui.ui`` instance that ``ui`` argument was based off of.
1382
1384
1383 ui
1385 ui
1384 ``ui.ui`` instance for use by the repository.
1386 ``ui.ui`` instance for use by the repository.
1385
1387
1386 origroot
1388 origroot
1387 ``bytes`` path to working directory root of this repository.
1389 ``bytes`` path to working directory root of this repository.
1388
1390
1389 wdirvfs
1391 wdirvfs
1390 ``vfs.vfs`` rooted at the working directory.
1392 ``vfs.vfs`` rooted at the working directory.
1391
1393
1392 hgvfs
1394 hgvfs
1393 ``vfs.vfs`` rooted at .hg/
1395 ``vfs.vfs`` rooted at .hg/
1394
1396
1395 requirements
1397 requirements
1396 ``set`` of bytestrings representing repository opening requirements.
1398 ``set`` of bytestrings representing repository opening requirements.
1397
1399
1398 supportedrequirements
1400 supportedrequirements
1399 ``set`` of bytestrings representing repository requirements that we
1401 ``set`` of bytestrings representing repository requirements that we
1400 know how to open. May be a supetset of ``requirements``.
1402 know how to open. May be a supetset of ``requirements``.
1401
1403
1402 sharedpath
1404 sharedpath
1403 ``bytes`` Defining path to storage base directory. Points to a
1405 ``bytes`` Defining path to storage base directory. Points to a
1404 ``.hg/`` directory somewhere.
1406 ``.hg/`` directory somewhere.
1405
1407
1406 store
1408 store
1407 ``store.basicstore`` (or derived) instance providing access to
1409 ``store.basicstore`` (or derived) instance providing access to
1408 versioned storage.
1410 versioned storage.
1409
1411
1410 cachevfs
1412 cachevfs
1411 ``vfs.vfs`` used for cache files.
1413 ``vfs.vfs`` used for cache files.
1412
1414
1413 wcachevfs
1415 wcachevfs
1414 ``vfs.vfs`` used for cache files related to the working copy.
1416 ``vfs.vfs`` used for cache files related to the working copy.
1415
1417
1416 features
1418 features
1417 ``set`` of bytestrings defining features/capabilities of this
1419 ``set`` of bytestrings defining features/capabilities of this
1418 instance.
1420 instance.
1419
1421
1420 intents
1422 intents
1421 ``set`` of system strings indicating what this repo will be used
1423 ``set`` of system strings indicating what this repo will be used
1422 for.
1424 for.
1423 """
1425 """
1424 self.baseui = baseui
1426 self.baseui = baseui
1425 self.ui = ui
1427 self.ui = ui
1426 self.origroot = origroot
1428 self.origroot = origroot
1427 # vfs rooted at working directory.
1429 # vfs rooted at working directory.
1428 self.wvfs = wdirvfs
1430 self.wvfs = wdirvfs
1429 self.root = wdirvfs.base
1431 self.root = wdirvfs.base
1430 # vfs rooted at .hg/. Used to access most non-store paths.
1432 # vfs rooted at .hg/. Used to access most non-store paths.
1431 self.vfs = hgvfs
1433 self.vfs = hgvfs
1432 self.path = hgvfs.base
1434 self.path = hgvfs.base
1433 self.requirements = requirements
1435 self.requirements = requirements
1434 self.nodeconstants = sha1nodeconstants
1436 self.nodeconstants = sha1nodeconstants
1435 self.nullid = self.nodeconstants.nullid
1437 self.nullid = self.nodeconstants.nullid
1436 self.supported = supportedrequirements
1438 self.supported = supportedrequirements
1437 self.sharedpath = sharedpath
1439 self.sharedpath = sharedpath
1438 self.store = store
1440 self.store = store
1439 self.cachevfs = cachevfs
1441 self.cachevfs = cachevfs
1440 self.wcachevfs = wcachevfs
1442 self.wcachevfs = wcachevfs
1441 self.features = features
1443 self.features = features
1442
1444
1443 self.filtername = None
1445 self.filtername = None
1444
1446
1445 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1447 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1446 b'devel', b'check-locks'
1448 b'devel', b'check-locks'
1447 ):
1449 ):
1448 self.vfs.audit = self._getvfsward(self.vfs.audit)
1450 self.vfs.audit = self._getvfsward(self.vfs.audit)
1449 # A list of callback to shape the phase if no data were found.
1451 # A list of callback to shape the phase if no data were found.
1450 # Callback are in the form: func(repo, roots) --> processed root.
1452 # Callback are in the form: func(repo, roots) --> processed root.
1451 # This list it to be filled by extension during repo setup
1453 # This list it to be filled by extension during repo setup
1452 self._phasedefaults = []
1454 self._phasedefaults = []
1453
1455
1454 color.setup(self.ui)
1456 color.setup(self.ui)
1455
1457
1456 self.spath = self.store.path
1458 self.spath = self.store.path
1457 self.svfs = self.store.vfs
1459 self.svfs = self.store.vfs
1458 self.sjoin = self.store.join
1460 self.sjoin = self.store.join
1459 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1461 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1460 b'devel', b'check-locks'
1462 b'devel', b'check-locks'
1461 ):
1463 ):
1462 if hasattr(self.svfs, 'vfs'): # this is filtervfs
1464 if hasattr(self.svfs, 'vfs'): # this is filtervfs
1463 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
1465 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
1464 else: # standard vfs
1466 else: # standard vfs
1465 self.svfs.audit = self._getsvfsward(self.svfs.audit)
1467 self.svfs.audit = self._getsvfsward(self.svfs.audit)
1466
1468
1467 self._dirstatevalidatewarned = False
1469 self._dirstatevalidatewarned = False
1468
1470
1469 self._branchcaches = branchmap.BranchMapCache()
1471 self._branchcaches = branchmap.BranchMapCache()
1470 self._revbranchcache = None
1472 self._revbranchcache = None
1471 self._filterpats = {}
1473 self._filterpats = {}
1472 self._datafilters = {}
1474 self._datafilters = {}
1473 self._transref = self._lockref = self._wlockref = None
1475 self._transref = self._lockref = self._wlockref = None
1474
1476
1475 # A cache for various files under .hg/ that tracks file changes,
1477 # A cache for various files under .hg/ that tracks file changes,
1476 # (used by the filecache decorator)
1478 # (used by the filecache decorator)
1477 #
1479 #
1478 # Maps a property name to its util.filecacheentry
1480 # Maps a property name to its util.filecacheentry
1479 self._filecache = {}
1481 self._filecache = {}
1480
1482
1481 # hold sets of revision to be filtered
1483 # hold sets of revision to be filtered
1482 # should be cleared when something might have changed the filter value:
1484 # should be cleared when something might have changed the filter value:
1483 # - new changesets,
1485 # - new changesets,
1484 # - phase change,
1486 # - phase change,
1485 # - new obsolescence marker,
1487 # - new obsolescence marker,
1486 # - working directory parent change,
1488 # - working directory parent change,
1487 # - bookmark changes
1489 # - bookmark changes
1488 self.filteredrevcache = {}
1490 self.filteredrevcache = {}
1489
1491
1490 self._dirstate = None
1492 self._dirstate = None
1491 # post-dirstate-status hooks
1493 # post-dirstate-status hooks
1492 self._postdsstatus = []
1494 self._postdsstatus = []
1493
1495
1494 self._pending_narrow_pats = None
1496 self._pending_narrow_pats = None
1495 self._pending_narrow_pats_dirstate = None
1497 self._pending_narrow_pats_dirstate = None
1496
1498
1497 # generic mapping between names and nodes
1499 # generic mapping between names and nodes
1498 self.names = namespaces.namespaces()
1500 self.names = namespaces.namespaces()
1499
1501
1500 # Key to signature value.
1502 # Key to signature value.
1501 self._sparsesignaturecache = {}
1503 self._sparsesignaturecache = {}
1502 # Signature to cached matcher instance.
1504 # Signature to cached matcher instance.
1503 self._sparsematchercache = {}
1505 self._sparsematchercache = {}
1504
1506
1505 self._extrafilterid = repoview.extrafilter(ui)
1507 self._extrafilterid = repoview.extrafilter(ui)
1506
1508
1507 self.filecopiesmode = None
1509 self.filecopiesmode = None
1508 if requirementsmod.COPIESSDC_REQUIREMENT in self.requirements:
1510 if requirementsmod.COPIESSDC_REQUIREMENT in self.requirements:
1509 self.filecopiesmode = b'changeset-sidedata'
1511 self.filecopiesmode = b'changeset-sidedata'
1510
1512
1511 self._wanted_sidedata = set()
1513 self._wanted_sidedata = set()
1512 self._sidedata_computers = {}
1514 self._sidedata_computers = {}
1513 sidedatamod.set_sidedata_spec_for_repo(self)
1515 sidedatamod.set_sidedata_spec_for_repo(self)
1514
1516
1515 def _getvfsward(self, origfunc):
1517 def _getvfsward(self, origfunc):
1516 """build a ward for self.vfs"""
1518 """build a ward for self.vfs"""
1517 rref = weakref.ref(self)
1519 rref = weakref.ref(self)
1518
1520
1519 def checkvfs(path, mode=None):
1521 def checkvfs(path, mode=None):
1520 ret = origfunc(path, mode=mode)
1522 ret = origfunc(path, mode=mode)
1521 repo = rref()
1523 repo = rref()
1522 if (
1524 if (
1523 repo is None
1525 repo is None
1524 or not hasattr(repo, '_wlockref')
1526 or not hasattr(repo, '_wlockref')
1525 or not hasattr(repo, '_lockref')
1527 or not hasattr(repo, '_lockref')
1526 ):
1528 ):
1527 return
1529 return
1528 if mode in (None, b'r', b'rb'):
1530 if mode in (None, b'r', b'rb'):
1529 return
1531 return
1530 if path.startswith(repo.path):
1532 if path.startswith(repo.path):
1531 # truncate name relative to the repository (.hg)
1533 # truncate name relative to the repository (.hg)
1532 path = path[len(repo.path) + 1 :]
1534 path = path[len(repo.path) + 1 :]
1533 if path.startswith(b'cache/'):
1535 if path.startswith(b'cache/'):
1534 msg = b'accessing cache with vfs instead of cachevfs: "%s"'
1536 msg = b'accessing cache with vfs instead of cachevfs: "%s"'
1535 repo.ui.develwarn(msg % path, stacklevel=3, config=b"cache-vfs")
1537 repo.ui.develwarn(msg % path, stacklevel=3, config=b"cache-vfs")
1536 # path prefixes covered by 'lock'
1538 # path prefixes covered by 'lock'
1537 vfs_path_prefixes = (
1539 vfs_path_prefixes = (
1538 b'journal.',
1540 b'journal.',
1539 b'undo.',
1541 b'undo.',
1540 b'strip-backup/',
1542 b'strip-backup/',
1541 b'cache/',
1543 b'cache/',
1542 )
1544 )
1543 if any(path.startswith(prefix) for prefix in vfs_path_prefixes):
1545 if any(path.startswith(prefix) for prefix in vfs_path_prefixes):
1544 if repo._currentlock(repo._lockref) is None:
1546 if repo._currentlock(repo._lockref) is None:
1545 repo.ui.develwarn(
1547 repo.ui.develwarn(
1546 b'write with no lock: "%s"' % path,
1548 b'write with no lock: "%s"' % path,
1547 stacklevel=3,
1549 stacklevel=3,
1548 config=b'check-locks',
1550 config=b'check-locks',
1549 )
1551 )
1550 elif repo._currentlock(repo._wlockref) is None:
1552 elif repo._currentlock(repo._wlockref) is None:
1551 # rest of vfs files are covered by 'wlock'
1553 # rest of vfs files are covered by 'wlock'
1552 #
1554 #
1553 # exclude special files
1555 # exclude special files
1554 for prefix in self._wlockfreeprefix:
1556 for prefix in self._wlockfreeprefix:
1555 if path.startswith(prefix):
1557 if path.startswith(prefix):
1556 return
1558 return
1557 repo.ui.develwarn(
1559 repo.ui.develwarn(
1558 b'write with no wlock: "%s"' % path,
1560 b'write with no wlock: "%s"' % path,
1559 stacklevel=3,
1561 stacklevel=3,
1560 config=b'check-locks',
1562 config=b'check-locks',
1561 )
1563 )
1562 return ret
1564 return ret
1563
1565
1564 return checkvfs
1566 return checkvfs
1565
1567
1566 def _getsvfsward(self, origfunc):
1568 def _getsvfsward(self, origfunc):
1567 """build a ward for self.svfs"""
1569 """build a ward for self.svfs"""
1568 rref = weakref.ref(self)
1570 rref = weakref.ref(self)
1569
1571
1570 def checksvfs(path, mode=None):
1572 def checksvfs(path, mode=None):
1571 ret = origfunc(path, mode=mode)
1573 ret = origfunc(path, mode=mode)
1572 repo = rref()
1574 repo = rref()
1573 if repo is None or not hasattr(repo, '_lockref'):
1575 if repo is None or not hasattr(repo, '_lockref'):
1574 return
1576 return
1575 if mode in (None, b'r', b'rb'):
1577 if mode in (None, b'r', b'rb'):
1576 return
1578 return
1577 if path.startswith(repo.sharedpath):
1579 if path.startswith(repo.sharedpath):
1578 # truncate name relative to the repository (.hg)
1580 # truncate name relative to the repository (.hg)
1579 path = path[len(repo.sharedpath) + 1 :]
1581 path = path[len(repo.sharedpath) + 1 :]
1580 if repo._currentlock(repo._lockref) is None:
1582 if repo._currentlock(repo._lockref) is None:
1581 repo.ui.develwarn(
1583 repo.ui.develwarn(
1582 b'write with no lock: "%s"' % path, stacklevel=4
1584 b'write with no lock: "%s"' % path, stacklevel=4
1583 )
1585 )
1584 return ret
1586 return ret
1585
1587
1586 return checksvfs
1588 return checksvfs
1587
1589
1588 @property
1590 @property
1589 def vfs_map(self):
1591 def vfs_map(self):
1590 return {
1592 return {
1591 b'': self.svfs,
1593 b'': self.svfs,
1592 b'plain': self.vfs,
1594 b'plain': self.vfs,
1593 b'store': self.svfs,
1595 b'store': self.svfs,
1594 }
1596 }
1595
1597
1596 def close(self):
1598 def close(self):
1597 self._writecaches()
1599 self._writecaches()
1598
1600
1599 def _writecaches(self):
1601 def _writecaches(self):
1600 if self._revbranchcache:
1602 if self._revbranchcache:
1601 self._revbranchcache.write()
1603 self._revbranchcache.write()
1602
1604
1603 def _restrictcapabilities(self, caps):
1605 def _restrictcapabilities(self, caps):
1604 if self.ui.configbool(b'experimental', b'bundle2-advertise'):
1606 if self.ui.configbool(b'experimental', b'bundle2-advertise'):
1605 caps = set(caps)
1607 caps = set(caps)
1606 capsblob = bundle2.encodecaps(
1608 capsblob = bundle2.encodecaps(
1607 bundle2.getrepocaps(self, role=b'client')
1609 bundle2.getrepocaps(self, role=b'client')
1608 )
1610 )
1609 caps.add(b'bundle2=' + urlreq.quote(capsblob))
1611 caps.add(b'bundle2=' + urlreq.quote(capsblob))
1610 if self.ui.configbool(b'experimental', b'narrow'):
1612 if self.ui.configbool(b'experimental', b'narrow'):
1611 caps.add(wireprototypes.NARROWCAP)
1613 caps.add(wireprototypes.NARROWCAP)
1612 return caps
1614 return caps
1613
1615
1614 # Don't cache auditor/nofsauditor, or you'll end up with reference cycle:
1616 # Don't cache auditor/nofsauditor, or you'll end up with reference cycle:
1615 # self -> auditor -> self._checknested -> self
1617 # self -> auditor -> self._checknested -> self
1616
1618
1617 @property
1619 @property
1618 def auditor(self):
1620 def auditor(self):
1619 # This is only used by context.workingctx.match in order to
1621 # This is only used by context.workingctx.match in order to
1620 # detect files in subrepos.
1622 # detect files in subrepos.
1621 return pathutil.pathauditor(self.root, callback=self._checknested)
1623 return pathutil.pathauditor(self.root, callback=self._checknested)
1622
1624
1623 @property
1625 @property
1624 def nofsauditor(self):
1626 def nofsauditor(self):
1625 # This is only used by context.basectx.match in order to detect
1627 # This is only used by context.basectx.match in order to detect
1626 # files in subrepos.
1628 # files in subrepos.
1627 return pathutil.pathauditor(
1629 return pathutil.pathauditor(
1628 self.root, callback=self._checknested, realfs=False, cached=True
1630 self.root, callback=self._checknested, realfs=False, cached=True
1629 )
1631 )
1630
1632
1631 def _checknested(self, path):
1633 def _checknested(self, path):
1632 """Determine if path is a legal nested repository."""
1634 """Determine if path is a legal nested repository."""
1633 if not path.startswith(self.root):
1635 if not path.startswith(self.root):
1634 return False
1636 return False
1635 subpath = path[len(self.root) + 1 :]
1637 subpath = path[len(self.root) + 1 :]
1636 normsubpath = util.pconvert(subpath)
1638 normsubpath = util.pconvert(subpath)
1637
1639
1638 # XXX: Checking against the current working copy is wrong in
1640 # XXX: Checking against the current working copy is wrong in
1639 # the sense that it can reject things like
1641 # the sense that it can reject things like
1640 #
1642 #
1641 # $ hg cat -r 10 sub/x.txt
1643 # $ hg cat -r 10 sub/x.txt
1642 #
1644 #
1643 # if sub/ is no longer a subrepository in the working copy
1645 # if sub/ is no longer a subrepository in the working copy
1644 # parent revision.
1646 # parent revision.
1645 #
1647 #
1646 # However, it can of course also allow things that would have
1648 # However, it can of course also allow things that would have
1647 # been rejected before, such as the above cat command if sub/
1649 # been rejected before, such as the above cat command if sub/
1648 # is a subrepository now, but was a normal directory before.
1650 # is a subrepository now, but was a normal directory before.
1649 # The old path auditor would have rejected by mistake since it
1651 # The old path auditor would have rejected by mistake since it
1650 # panics when it sees sub/.hg/.
1652 # panics when it sees sub/.hg/.
1651 #
1653 #
1652 # All in all, checking against the working copy seems sensible
1654 # All in all, checking against the working copy seems sensible
1653 # since we want to prevent access to nested repositories on
1655 # since we want to prevent access to nested repositories on
1654 # the filesystem *now*.
1656 # the filesystem *now*.
1655 ctx = self[None]
1657 ctx = self[None]
1656 parts = util.splitpath(subpath)
1658 parts = util.splitpath(subpath)
1657 while parts:
1659 while parts:
1658 prefix = b'/'.join(parts)
1660 prefix = b'/'.join(parts)
1659 if prefix in ctx.substate:
1661 if prefix in ctx.substate:
1660 if prefix == normsubpath:
1662 if prefix == normsubpath:
1661 return True
1663 return True
1662 else:
1664 else:
1663 sub = ctx.sub(prefix)
1665 sub = ctx.sub(prefix)
1664 return sub.checknested(subpath[len(prefix) + 1 :])
1666 return sub.checknested(subpath[len(prefix) + 1 :])
1665 else:
1667 else:
1666 parts.pop()
1668 parts.pop()
1667 return False
1669 return False
1668
1670
1669 def peer(self, path=None, remotehidden=False):
1671 def peer(self, path=None, remotehidden=False):
1670 return localpeer(
1672 return localpeer(
1671 self, path=path, remotehidden=remotehidden
1673 self, path=path, remotehidden=remotehidden
1672 ) # not cached to avoid reference cycle
1674 ) # not cached to avoid reference cycle
1673
1675
1674 def unfiltered(self):
1676 def unfiltered(self):
1675 """Return unfiltered version of the repository
1677 """Return unfiltered version of the repository
1676
1678
1677 Intended to be overwritten by filtered repo."""
1679 Intended to be overwritten by filtered repo."""
1678 return self
1680 return self
1679
1681
1680 def filtered(self, name, visibilityexceptions=None):
1682 def filtered(self, name, visibilityexceptions=None):
1681 """Return a filtered version of a repository
1683 """Return a filtered version of a repository
1682
1684
1683 The `name` parameter is the identifier of the requested view. This
1685 The `name` parameter is the identifier of the requested view. This
1684 will return a repoview object set "exactly" to the specified view.
1686 will return a repoview object set "exactly" to the specified view.
1685
1687
1686 This function does not apply recursive filtering to a repository. For
1688 This function does not apply recursive filtering to a repository. For
1687 example calling `repo.filtered("served")` will return a repoview using
1689 example calling `repo.filtered("served")` will return a repoview using
1688 the "served" view, regardless of the initial view used by `repo`.
1690 the "served" view, regardless of the initial view used by `repo`.
1689
1691
1690 In other word, there is always only one level of `repoview` "filtering".
1692 In other word, there is always only one level of `repoview` "filtering".
1691 """
1693 """
1692 if self._extrafilterid is not None and b'%' not in name:
1694 if self._extrafilterid is not None and b'%' not in name:
1693 name = name + b'%' + self._extrafilterid
1695 name = name + b'%' + self._extrafilterid
1694
1696
1695 cls = repoview.newtype(self.unfiltered().__class__)
1697 cls = repoview.newtype(self.unfiltered().__class__)
1696 return cls(self, name, visibilityexceptions)
1698 return cls(self, name, visibilityexceptions)
1697
1699
1698 @mixedrepostorecache(
1700 @mixedrepostorecache(
1699 (b'bookmarks', b'plain'),
1701 (b'bookmarks', b'plain'),
1700 (b'bookmarks.current', b'plain'),
1702 (b'bookmarks.current', b'plain'),
1701 (b'bookmarks', b''),
1703 (b'bookmarks', b''),
1702 (b'00changelog.i', b''),
1704 (b'00changelog.i', b''),
1703 )
1705 )
1704 def _bookmarks(self):
1706 def _bookmarks(self):
1705 # Since the multiple files involved in the transaction cannot be
1707 # Since the multiple files involved in the transaction cannot be
1706 # written atomically (with current repository format), there is a race
1708 # written atomically (with current repository format), there is a race
1707 # condition here.
1709 # condition here.
1708 #
1710 #
1709 # 1) changelog content A is read
1711 # 1) changelog content A is read
1710 # 2) outside transaction update changelog to content B
1712 # 2) outside transaction update changelog to content B
1711 # 3) outside transaction update bookmark file referring to content B
1713 # 3) outside transaction update bookmark file referring to content B
1712 # 4) bookmarks file content is read and filtered against changelog-A
1714 # 4) bookmarks file content is read and filtered against changelog-A
1713 #
1715 #
1714 # When this happens, bookmarks against nodes missing from A are dropped.
1716 # When this happens, bookmarks against nodes missing from A are dropped.
1715 #
1717 #
1716 # Having this happening during read is not great, but it become worse
1718 # Having this happening during read is not great, but it become worse
1717 # when this happen during write because the bookmarks to the "unknown"
1719 # when this happen during write because the bookmarks to the "unknown"
1718 # nodes will be dropped for good. However, writes happen within locks.
1720 # nodes will be dropped for good. However, writes happen within locks.
1719 # This locking makes it possible to have a race free consistent read.
1721 # This locking makes it possible to have a race free consistent read.
1720 # For this purpose data read from disc before locking are
1722 # For this purpose data read from disc before locking are
1721 # "invalidated" right after the locks are taken. This invalidations are
1723 # "invalidated" right after the locks are taken. This invalidations are
1722 # "light", the `filecache` mechanism keep the data in memory and will
1724 # "light", the `filecache` mechanism keep the data in memory and will
1723 # reuse them if the underlying files did not changed. Not parsing the
1725 # reuse them if the underlying files did not changed. Not parsing the
1724 # same data multiple times helps performances.
1726 # same data multiple times helps performances.
1725 #
1727 #
1726 # Unfortunately in the case describe above, the files tracked by the
1728 # Unfortunately in the case describe above, the files tracked by the
1727 # bookmarks file cache might not have changed, but the in-memory
1729 # bookmarks file cache might not have changed, but the in-memory
1728 # content is still "wrong" because we used an older changelog content
1730 # 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
1731 # to process the on-disk data. So after locking, the changelog would be
1730 # refreshed but `_bookmarks` would be preserved.
1732 # refreshed but `_bookmarks` would be preserved.
1731 # Adding `00changelog.i` to the list of tracked file is not
1733 # Adding `00changelog.i` to the list of tracked file is not
1732 # enough, because at the time we build the content for `_bookmarks` in
1734 # enough, because at the time we build the content for `_bookmarks` in
1733 # (4), the changelog file has already diverged from the content used
1735 # (4), the changelog file has already diverged from the content used
1734 # for loading `changelog` in (1)
1736 # for loading `changelog` in (1)
1735 #
1737 #
1736 # To prevent the issue, we force the changelog to be explicitly
1738 # To prevent the issue, we force the changelog to be explicitly
1737 # reloaded while computing `_bookmarks`. The data race can still happen
1739 # reloaded while computing `_bookmarks`. The data race can still happen
1738 # without the lock (with a narrower window), but it would no longer go
1740 # without the lock (with a narrower window), but it would no longer go
1739 # undetected during the lock time refresh.
1741 # undetected during the lock time refresh.
1740 #
1742 #
1741 # The new schedule is as follow
1743 # The new schedule is as follow
1742 #
1744 #
1743 # 1) filecache logic detect that `_bookmarks` needs to be computed
1745 # 1) filecache logic detect that `_bookmarks` needs to be computed
1744 # 2) cachestat for `bookmarks` and `changelog` are captured (for book)
1746 # 2) cachestat for `bookmarks` and `changelog` are captured (for book)
1745 # 3) We force `changelog` filecache to be tested
1747 # 3) We force `changelog` filecache to be tested
1746 # 4) cachestat for `changelog` are captured (for changelog)
1748 # 4) cachestat for `changelog` are captured (for changelog)
1747 # 5) `_bookmarks` is computed and cached
1749 # 5) `_bookmarks` is computed and cached
1748 #
1750 #
1749 # The step in (3) ensure we have a changelog at least as recent as the
1751 # 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:
1752 # 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
1753 # * if the changelog did not changed since (1) -> we can reuse the data
1752 # * otherwise -> the bookmarks get refreshed.
1754 # * otherwise -> the bookmarks get refreshed.
1753 self._refreshchangelog()
1755 self._refreshchangelog()
1754 return bookmarks.bmstore(self)
1756 return bookmarks.bmstore(self)
1755
1757
1756 def _refreshchangelog(self):
1758 def _refreshchangelog(self):
1757 """make sure the in memory changelog match the on-disk one"""
1759 """make sure the in memory changelog match the on-disk one"""
1758 if 'changelog' in vars(self) and self.currenttransaction() is None:
1760 if 'changelog' in vars(self) and self.currenttransaction() is None:
1759 del self.changelog
1761 del self.changelog
1760
1762
1761 @property
1763 @property
1762 def _activebookmark(self):
1764 def _activebookmark(self):
1763 return self._bookmarks.active
1765 return self._bookmarks.active
1764
1766
1765 # _phasesets depend on changelog. what we need is to call
1767 # _phasesets depend on changelog. what we need is to call
1766 # _phasecache.invalidate() if '00changelog.i' was changed, but it
1768 # _phasecache.invalidate() if '00changelog.i' was changed, but it
1767 # can't be easily expressed in filecache mechanism.
1769 # can't be easily expressed in filecache mechanism.
1768 @storecache(b'phaseroots', b'00changelog.i')
1770 @storecache(b'phaseroots', b'00changelog.i')
1769 def _phasecache(self):
1771 def _phasecache(self):
1770 return phases.phasecache(self, self._phasedefaults)
1772 return phases.phasecache(self, self._phasedefaults)
1771
1773
1772 @storecache(b'obsstore')
1774 @storecache(b'obsstore')
1773 def obsstore(self):
1775 def obsstore(self):
1774 return obsolete.makestore(self.ui, self)
1776 return obsolete.makestore(self.ui, self)
1775
1777
1776 @changelogcache()
1778 @changelogcache()
1777 def changelog(repo):
1779 def changelog(repo):
1778 # load dirstate before changelog to avoid race see issue6303
1780 # load dirstate before changelog to avoid race see issue6303
1779 repo.dirstate.prefetch_parents()
1781 repo.dirstate.prefetch_parents()
1780 return repo.store.changelog(
1782 return repo.store.changelog(
1781 txnutil.mayhavepending(repo.root),
1783 txnutil.mayhavepending(repo.root),
1782 concurrencychecker=revlogchecker.get_checker(repo.ui, b'changelog'),
1784 concurrencychecker=revlogchecker.get_checker(repo.ui, b'changelog'),
1783 )
1785 )
1784
1786
1785 @manifestlogcache()
1787 @manifestlogcache()
1786 def manifestlog(self):
1788 def manifestlog(self):
1787 return self.store.manifestlog(self, self._storenarrowmatch)
1789 return self.store.manifestlog(self, self._storenarrowmatch)
1788
1790
1789 @unfilteredpropertycache
1791 @unfilteredpropertycache
1790 def dirstate(self):
1792 def dirstate(self):
1791 if self._dirstate is None:
1793 if self._dirstate is None:
1792 self._dirstate = self._makedirstate()
1794 self._dirstate = self._makedirstate()
1793 else:
1795 else:
1794 self._dirstate.refresh()
1796 self._dirstate.refresh()
1795 return self._dirstate
1797 return self._dirstate
1796
1798
1797 def _makedirstate(self):
1799 def _makedirstate(self):
1798 """Extension point for wrapping the dirstate per-repo."""
1800 """Extension point for wrapping the dirstate per-repo."""
1799 sparsematchfn = None
1801 sparsematchfn = None
1800 if sparse.use_sparse(self):
1802 if sparse.use_sparse(self):
1801 sparsematchfn = lambda: sparse.matcher(self)
1803 sparsematchfn = lambda: sparse.matcher(self)
1802 v2_req = requirementsmod.DIRSTATE_V2_REQUIREMENT
1804 v2_req = requirementsmod.DIRSTATE_V2_REQUIREMENT
1803 th = requirementsmod.DIRSTATE_TRACKED_HINT_V1
1805 th = requirementsmod.DIRSTATE_TRACKED_HINT_V1
1804 use_dirstate_v2 = v2_req in self.requirements
1806 use_dirstate_v2 = v2_req in self.requirements
1805 use_tracked_hint = th in self.requirements
1807 use_tracked_hint = th in self.requirements
1806
1808
1807 return dirstate.dirstate(
1809 return dirstate.dirstate(
1808 self.vfs,
1810 self.vfs,
1809 self.ui,
1811 self.ui,
1810 self.root,
1812 self.root,
1811 self._dirstatevalidate,
1813 self._dirstatevalidate,
1812 sparsematchfn,
1814 sparsematchfn,
1813 self.nodeconstants,
1815 self.nodeconstants,
1814 use_dirstate_v2,
1816 use_dirstate_v2,
1815 use_tracked_hint=use_tracked_hint,
1817 use_tracked_hint=use_tracked_hint,
1816 )
1818 )
1817
1819
1818 def _dirstatevalidate(self, node):
1820 def _dirstatevalidate(self, node):
1819 okay = True
1821 okay = True
1820 try:
1822 try:
1821 self.changelog.rev(node)
1823 self.changelog.rev(node)
1822 except error.LookupError:
1824 except error.LookupError:
1823 # If the parent are unknown it might just be because the changelog
1825 # 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
1826 # in memory is lagging behind the dirstate in memory. So try to
1825 # refresh the changelog first.
1827 # refresh the changelog first.
1826 #
1828 #
1827 # We only do so if we don't hold the lock, if we do hold the lock
1829 # 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
1830 # the invalidation at that time should have taken care of this and
1829 # something is very fishy.
1831 # something is very fishy.
1830 if self.currentlock() is None:
1832 if self.currentlock() is None:
1831 self.invalidate()
1833 self.invalidate()
1832 try:
1834 try:
1833 self.changelog.rev(node)
1835 self.changelog.rev(node)
1834 except error.LookupError:
1836 except error.LookupError:
1835 okay = False
1837 okay = False
1836 else:
1838 else:
1837 # XXX we should consider raising an error here.
1839 # XXX we should consider raising an error here.
1838 okay = False
1840 okay = False
1839 if okay:
1841 if okay:
1840 return node
1842 return node
1841 else:
1843 else:
1842 if not self._dirstatevalidatewarned:
1844 if not self._dirstatevalidatewarned:
1843 self._dirstatevalidatewarned = True
1845 self._dirstatevalidatewarned = True
1844 self.ui.warn(
1846 self.ui.warn(
1845 _(b"warning: ignoring unknown working parent %s!\n")
1847 _(b"warning: ignoring unknown working parent %s!\n")
1846 % short(node)
1848 % short(node)
1847 )
1849 )
1848 return self.nullid
1850 return self.nullid
1849
1851
1850 @storecache(narrowspec.FILENAME)
1852 @storecache(narrowspec.FILENAME)
1851 def narrowpats(self):
1853 def narrowpats(self):
1852 """matcher patterns for this repository's narrowspec
1854 """matcher patterns for this repository's narrowspec
1853
1855
1854 A tuple of (includes, excludes).
1856 A tuple of (includes, excludes).
1855 """
1857 """
1856 # the narrow management should probably move into its own object
1858 # the narrow management should probably move into its own object
1857 val = self._pending_narrow_pats
1859 val = self._pending_narrow_pats
1858 if val is None:
1860 if val is None:
1859 val = narrowspec.load(self)
1861 val = narrowspec.load(self)
1860 return val
1862 return val
1861
1863
1862 @storecache(narrowspec.FILENAME)
1864 @storecache(narrowspec.FILENAME)
1863 def _storenarrowmatch(self):
1865 def _storenarrowmatch(self):
1864 if requirementsmod.NARROW_REQUIREMENT not in self.requirements:
1866 if requirementsmod.NARROW_REQUIREMENT not in self.requirements:
1865 return matchmod.always()
1867 return matchmod.always()
1866 include, exclude = self.narrowpats
1868 include, exclude = self.narrowpats
1867 return narrowspec.match(self.root, include=include, exclude=exclude)
1869 return narrowspec.match(self.root, include=include, exclude=exclude)
1868
1870
1869 @storecache(narrowspec.FILENAME)
1871 @storecache(narrowspec.FILENAME)
1870 def _narrowmatch(self):
1872 def _narrowmatch(self):
1871 if requirementsmod.NARROW_REQUIREMENT not in self.requirements:
1873 if requirementsmod.NARROW_REQUIREMENT not in self.requirements:
1872 return matchmod.always()
1874 return matchmod.always()
1873 narrowspec.checkworkingcopynarrowspec(self)
1875 narrowspec.checkworkingcopynarrowspec(self)
1874 include, exclude = self.narrowpats
1876 include, exclude = self.narrowpats
1875 return narrowspec.match(self.root, include=include, exclude=exclude)
1877 return narrowspec.match(self.root, include=include, exclude=exclude)
1876
1878
1877 def narrowmatch(self, match=None, includeexact=False):
1879 def narrowmatch(self, match=None, includeexact=False):
1878 """matcher corresponding the the repo's narrowspec
1880 """matcher corresponding the the repo's narrowspec
1879
1881
1880 If `match` is given, then that will be intersected with the narrow
1882 If `match` is given, then that will be intersected with the narrow
1881 matcher.
1883 matcher.
1882
1884
1883 If `includeexact` is True, then any exact matches from `match` will
1885 If `includeexact` is True, then any exact matches from `match` will
1884 be included even if they're outside the narrowspec.
1886 be included even if they're outside the narrowspec.
1885 """
1887 """
1886 if match:
1888 if match:
1887 if includeexact and not self._narrowmatch.always():
1889 if includeexact and not self._narrowmatch.always():
1888 # do not exclude explicitly-specified paths so that they can
1890 # do not exclude explicitly-specified paths so that they can
1889 # be warned later on
1891 # be warned later on
1890 em = matchmod.exact(match.files())
1892 em = matchmod.exact(match.files())
1891 nm = matchmod.unionmatcher([self._narrowmatch, em])
1893 nm = matchmod.unionmatcher([self._narrowmatch, em])
1892 return matchmod.intersectmatchers(match, nm)
1894 return matchmod.intersectmatchers(match, nm)
1893 return matchmod.intersectmatchers(match, self._narrowmatch)
1895 return matchmod.intersectmatchers(match, self._narrowmatch)
1894 return self._narrowmatch
1896 return self._narrowmatch
1895
1897
1896 def setnarrowpats(self, newincludes, newexcludes):
1898 def setnarrowpats(self, newincludes, newexcludes):
1897 narrowspec.save(self, newincludes, newexcludes)
1899 narrowspec.save(self, newincludes, newexcludes)
1898 self.invalidate(clearfilecache=True)
1900 self.invalidate(clearfilecache=True)
1899
1901
1900 @unfilteredpropertycache
1902 @unfilteredpropertycache
1901 def _quick_access_changeid_null(self):
1903 def _quick_access_changeid_null(self):
1902 return {
1904 return {
1903 b'null': (nullrev, self.nodeconstants.nullid),
1905 b'null': (nullrev, self.nodeconstants.nullid),
1904 nullrev: (nullrev, self.nodeconstants.nullid),
1906 nullrev: (nullrev, self.nodeconstants.nullid),
1905 self.nullid: (nullrev, self.nullid),
1907 self.nullid: (nullrev, self.nullid),
1906 }
1908 }
1907
1909
1908 @unfilteredpropertycache
1910 @unfilteredpropertycache
1909 def _quick_access_changeid_wc(self):
1911 def _quick_access_changeid_wc(self):
1910 # also fast path access to the working copy parents
1912 # also fast path access to the working copy parents
1911 # however, only do it for filter that ensure wc is visible.
1913 # however, only do it for filter that ensure wc is visible.
1912 quick = self._quick_access_changeid_null.copy()
1914 quick = self._quick_access_changeid_null.copy()
1913 cl = self.unfiltered().changelog
1915 cl = self.unfiltered().changelog
1914 for node in self.dirstate.parents():
1916 for node in self.dirstate.parents():
1915 if node == self.nullid:
1917 if node == self.nullid:
1916 continue
1918 continue
1917 rev = cl.index.get_rev(node)
1919 rev = cl.index.get_rev(node)
1918 if rev is None:
1920 if rev is None:
1919 # unknown working copy parent case:
1921 # unknown working copy parent case:
1920 #
1922 #
1921 # skip the fast path and let higher code deal with it
1923 # skip the fast path and let higher code deal with it
1922 continue
1924 continue
1923 pair = (rev, node)
1925 pair = (rev, node)
1924 quick[rev] = pair
1926 quick[rev] = pair
1925 quick[node] = pair
1927 quick[node] = pair
1926 # also add the parents of the parents
1928 # also add the parents of the parents
1927 for r in cl.parentrevs(rev):
1929 for r in cl.parentrevs(rev):
1928 if r == nullrev:
1930 if r == nullrev:
1929 continue
1931 continue
1930 n = cl.node(r)
1932 n = cl.node(r)
1931 pair = (r, n)
1933 pair = (r, n)
1932 quick[r] = pair
1934 quick[r] = pair
1933 quick[n] = pair
1935 quick[n] = pair
1934 p1node = self.dirstate.p1()
1936 p1node = self.dirstate.p1()
1935 if p1node != self.nullid:
1937 if p1node != self.nullid:
1936 quick[b'.'] = quick[p1node]
1938 quick[b'.'] = quick[p1node]
1937 return quick
1939 return quick
1938
1940
1939 @unfilteredmethod
1941 @unfilteredmethod
1940 def _quick_access_changeid_invalidate(self):
1942 def _quick_access_changeid_invalidate(self):
1941 if '_quick_access_changeid_wc' in vars(self):
1943 if '_quick_access_changeid_wc' in vars(self):
1942 del self.__dict__['_quick_access_changeid_wc']
1944 del self.__dict__['_quick_access_changeid_wc']
1943
1945
1944 @property
1946 @property
1945 def _quick_access_changeid(self):
1947 def _quick_access_changeid(self):
1946 """an helper dictionnary for __getitem__ calls
1948 """an helper dictionnary for __getitem__ calls
1947
1949
1948 This contains a list of symbol we can recognise right away without
1950 This contains a list of symbol we can recognise right away without
1949 further processing.
1951 further processing.
1950 """
1952 """
1951 if self.filtername in repoview.filter_has_wc:
1953 if self.filtername in repoview.filter_has_wc:
1952 return self._quick_access_changeid_wc
1954 return self._quick_access_changeid_wc
1953 return self._quick_access_changeid_null
1955 return self._quick_access_changeid_null
1954
1956
1955 def __getitem__(self, changeid):
1957 def __getitem__(self, changeid):
1956 # dealing with special cases
1958 # dealing with special cases
1957 if changeid is None:
1959 if changeid is None:
1958 return context.workingctx(self)
1960 return context.workingctx(self)
1959 if isinstance(changeid, context.basectx):
1961 if isinstance(changeid, context.basectx):
1960 return changeid
1962 return changeid
1961
1963
1962 # dealing with multiple revisions
1964 # dealing with multiple revisions
1963 if isinstance(changeid, slice):
1965 if isinstance(changeid, slice):
1964 # wdirrev isn't contiguous so the slice shouldn't include it
1966 # wdirrev isn't contiguous so the slice shouldn't include it
1965 return [
1967 return [
1966 self[i]
1968 self[i]
1967 for i in range(*changeid.indices(len(self)))
1969 for i in range(*changeid.indices(len(self)))
1968 if i not in self.changelog.filteredrevs
1970 if i not in self.changelog.filteredrevs
1969 ]
1971 ]
1970
1972
1971 # dealing with some special values
1973 # dealing with some special values
1972 quick_access = self._quick_access_changeid.get(changeid)
1974 quick_access = self._quick_access_changeid.get(changeid)
1973 if quick_access is not None:
1975 if quick_access is not None:
1974 rev, node = quick_access
1976 rev, node = quick_access
1975 return context.changectx(self, rev, node, maybe_filtered=False)
1977 return context.changectx(self, rev, node, maybe_filtered=False)
1976 if changeid == b'tip':
1978 if changeid == b'tip':
1977 node = self.changelog.tip()
1979 node = self.changelog.tip()
1978 rev = self.changelog.rev(node)
1980 rev = self.changelog.rev(node)
1979 return context.changectx(self, rev, node)
1981 return context.changectx(self, rev, node)
1980
1982
1981 # dealing with arbitrary values
1983 # dealing with arbitrary values
1982 try:
1984 try:
1983 if isinstance(changeid, int):
1985 if isinstance(changeid, int):
1984 node = self.changelog.node(changeid)
1986 node = self.changelog.node(changeid)
1985 rev = changeid
1987 rev = changeid
1986 elif changeid == b'.':
1988 elif changeid == b'.':
1987 # this is a hack to delay/avoid loading obsmarkers
1989 # this is a hack to delay/avoid loading obsmarkers
1988 # when we know that '.' won't be hidden
1990 # when we know that '.' won't be hidden
1989 node = self.dirstate.p1()
1991 node = self.dirstate.p1()
1990 rev = self.unfiltered().changelog.rev(node)
1992 rev = self.unfiltered().changelog.rev(node)
1991 elif len(changeid) == self.nodeconstants.nodelen:
1993 elif len(changeid) == self.nodeconstants.nodelen:
1992 try:
1994 try:
1993 node = changeid
1995 node = changeid
1994 rev = self.changelog.rev(changeid)
1996 rev = self.changelog.rev(changeid)
1995 except error.FilteredLookupError:
1997 except error.FilteredLookupError:
1996 changeid = hex(changeid) # for the error message
1998 changeid = hex(changeid) # for the error message
1997 raise
1999 raise
1998 except LookupError:
2000 except LookupError:
1999 # check if it might have come from damaged dirstate
2001 # check if it might have come from damaged dirstate
2000 #
2002 #
2001 # XXX we could avoid the unfiltered if we had a recognizable
2003 # XXX we could avoid the unfiltered if we had a recognizable
2002 # exception for filtered changeset access
2004 # exception for filtered changeset access
2003 if (
2005 if (
2004 self.local()
2006 self.local()
2005 and changeid in self.unfiltered().dirstate.parents()
2007 and changeid in self.unfiltered().dirstate.parents()
2006 ):
2008 ):
2007 msg = _(b"working directory has unknown parent '%s'!")
2009 msg = _(b"working directory has unknown parent '%s'!")
2008 raise error.Abort(msg % short(changeid))
2010 raise error.Abort(msg % short(changeid))
2009 changeid = hex(changeid) # for the error message
2011 changeid = hex(changeid) # for the error message
2010 raise
2012 raise
2011
2013
2012 elif len(changeid) == 2 * self.nodeconstants.nodelen:
2014 elif len(changeid) == 2 * self.nodeconstants.nodelen:
2013 node = bin(changeid)
2015 node = bin(changeid)
2014 rev = self.changelog.rev(node)
2016 rev = self.changelog.rev(node)
2015 else:
2017 else:
2016 raise error.ProgrammingError(
2018 raise error.ProgrammingError(
2017 b"unsupported changeid '%s' of type %s"
2019 b"unsupported changeid '%s' of type %s"
2018 % (changeid, pycompat.bytestr(type(changeid)))
2020 % (changeid, pycompat.bytestr(type(changeid)))
2019 )
2021 )
2020
2022
2021 return context.changectx(self, rev, node)
2023 return context.changectx(self, rev, node)
2022
2024
2023 except (error.FilteredIndexError, error.FilteredLookupError):
2025 except (error.FilteredIndexError, error.FilteredLookupError):
2024 raise error.FilteredRepoLookupError(
2026 raise error.FilteredRepoLookupError(
2025 _(b"filtered revision '%s'") % pycompat.bytestr(changeid)
2027 _(b"filtered revision '%s'") % pycompat.bytestr(changeid)
2026 )
2028 )
2027 except (IndexError, LookupError):
2029 except (IndexError, LookupError):
2028 raise error.RepoLookupError(
2030 raise error.RepoLookupError(
2029 _(b"unknown revision '%s'") % pycompat.bytestr(changeid)
2031 _(b"unknown revision '%s'") % pycompat.bytestr(changeid)
2030 )
2032 )
2031 except error.WdirUnsupported:
2033 except error.WdirUnsupported:
2032 return context.workingctx(self)
2034 return context.workingctx(self)
2033
2035
2034 def __contains__(self, changeid):
2036 def __contains__(self, changeid):
2035 """True if the given changeid exists"""
2037 """True if the given changeid exists"""
2036 try:
2038 try:
2037 self[changeid]
2039 self[changeid]
2038 return True
2040 return True
2039 except error.RepoLookupError:
2041 except error.RepoLookupError:
2040 return False
2042 return False
2041
2043
2042 def __nonzero__(self):
2044 def __nonzero__(self):
2043 return True
2045 return True
2044
2046
2045 __bool__ = __nonzero__
2047 __bool__ = __nonzero__
2046
2048
2047 def __len__(self):
2049 def __len__(self):
2048 # no need to pay the cost of repoview.changelog
2050 # no need to pay the cost of repoview.changelog
2049 unfi = self.unfiltered()
2051 unfi = self.unfiltered()
2050 return len(unfi.changelog)
2052 return len(unfi.changelog)
2051
2053
2052 def __iter__(self):
2054 def __iter__(self):
2053 return iter(self.changelog)
2055 return iter(self.changelog)
2054
2056
2055 def revs(self, expr: bytes, *args):
2057 def revs(self, expr: bytes, *args):
2056 """Find revisions matching a revset.
2058 """Find revisions matching a revset.
2057
2059
2058 The revset is specified as a string ``expr`` that may contain
2060 The revset is specified as a string ``expr`` that may contain
2059 %-formatting to escape certain types. See ``revsetlang.formatspec``.
2061 %-formatting to escape certain types. See ``revsetlang.formatspec``.
2060
2062
2061 Revset aliases from the configuration are not expanded. To expand
2063 Revset aliases from the configuration are not expanded. To expand
2062 user aliases, consider calling ``scmutil.revrange()`` or
2064 user aliases, consider calling ``scmutil.revrange()`` or
2063 ``repo.anyrevs([expr], user=True)``.
2065 ``repo.anyrevs([expr], user=True)``.
2064
2066
2065 Returns a smartset.abstractsmartset, which is a list-like interface
2067 Returns a smartset.abstractsmartset, which is a list-like interface
2066 that contains integer revisions.
2068 that contains integer revisions.
2067 """
2069 """
2068 tree = revsetlang.spectree(expr, *args)
2070 tree = revsetlang.spectree(expr, *args)
2069 return revset.makematcher(tree)(self)
2071 return revset.makematcher(tree)(self)
2070
2072
2071 def set(self, expr: bytes, *args):
2073 def set(self, expr: bytes, *args):
2072 """Find revisions matching a revset and emit changectx instances.
2074 """Find revisions matching a revset and emit changectx instances.
2073
2075
2074 This is a convenience wrapper around ``revs()`` that iterates the
2076 This is a convenience wrapper around ``revs()`` that iterates the
2075 result and is a generator of changectx instances.
2077 result and is a generator of changectx instances.
2076
2078
2077 Revset aliases from the configuration are not expanded. To expand
2079 Revset aliases from the configuration are not expanded. To expand
2078 user aliases, consider calling ``scmutil.revrange()``.
2080 user aliases, consider calling ``scmutil.revrange()``.
2079 """
2081 """
2080 for r in self.revs(expr, *args):
2082 for r in self.revs(expr, *args):
2081 yield self[r]
2083 yield self[r]
2082
2084
2083 def anyrevs(self, specs: bytes, user=False, localalias=None):
2085 def anyrevs(self, specs: bytes, user=False, localalias=None):
2084 """Find revisions matching one of the given revsets.
2086 """Find revisions matching one of the given revsets.
2085
2087
2086 Revset aliases from the configuration are not expanded by default. To
2088 Revset aliases from the configuration are not expanded by default. To
2087 expand user aliases, specify ``user=True``. To provide some local
2089 expand user aliases, specify ``user=True``. To provide some local
2088 definitions overriding user aliases, set ``localalias`` to
2090 definitions overriding user aliases, set ``localalias`` to
2089 ``{name: definitionstring}``.
2091 ``{name: definitionstring}``.
2090 """
2092 """
2091 if specs == [b'null']:
2093 if specs == [b'null']:
2092 return revset.baseset([nullrev])
2094 return revset.baseset([nullrev])
2093 if specs == [b'.']:
2095 if specs == [b'.']:
2094 quick_data = self._quick_access_changeid.get(b'.')
2096 quick_data = self._quick_access_changeid.get(b'.')
2095 if quick_data is not None:
2097 if quick_data is not None:
2096 return revset.baseset([quick_data[0]])
2098 return revset.baseset([quick_data[0]])
2097 if user:
2099 if user:
2098 m = revset.matchany(
2100 m = revset.matchany(
2099 self.ui,
2101 self.ui,
2100 specs,
2102 specs,
2101 lookup=revset.lookupfn(self),
2103 lookup=revset.lookupfn(self),
2102 localalias=localalias,
2104 localalias=localalias,
2103 )
2105 )
2104 else:
2106 else:
2105 m = revset.matchany(None, specs, localalias=localalias)
2107 m = revset.matchany(None, specs, localalias=localalias)
2106 return m(self)
2108 return m(self)
2107
2109
2108 def url(self) -> bytes:
2110 def url(self) -> bytes:
2109 return b'file:' + self.root
2111 return b'file:' + self.root
2110
2112
2111 def hook(self, name, throw=False, **args):
2113 def hook(self, name, throw=False, **args):
2112 """Call a hook, passing this repo instance.
2114 """Call a hook, passing this repo instance.
2113
2115
2114 This a convenience method to aid invoking hooks. Extensions likely
2116 This a convenience method to aid invoking hooks. Extensions likely
2115 won't call this unless they have registered a custom hook or are
2117 won't call this unless they have registered a custom hook or are
2116 replacing code that is expected to call a hook.
2118 replacing code that is expected to call a hook.
2117 """
2119 """
2118 return hook.hook(self.ui, self, name, throw, **args)
2120 return hook.hook(self.ui, self, name, throw, **args)
2119
2121
2120 @filteredpropertycache
2122 @filteredpropertycache
2121 def _tagscache(self):
2123 def _tagscache(self):
2122 """Returns a tagscache object that contains various tags related
2124 """Returns a tagscache object that contains various tags related
2123 caches."""
2125 caches."""
2124
2126
2125 # This simplifies its cache management by having one decorated
2127 # This simplifies its cache management by having one decorated
2126 # function (this one) and the rest simply fetch things from it.
2128 # function (this one) and the rest simply fetch things from it.
2127 class tagscache:
2129 class tagscache:
2128 def __init__(self):
2130 def __init__(self):
2129 # These two define the set of tags for this repository. tags
2131 # These two define the set of tags for this repository. tags
2130 # maps tag name to node; tagtypes maps tag name to 'global' or
2132 # maps tag name to node; tagtypes maps tag name to 'global' or
2131 # 'local'. (Global tags are defined by .hgtags across all
2133 # 'local'. (Global tags are defined by .hgtags across all
2132 # heads, and local tags are defined in .hg/localtags.)
2134 # heads, and local tags are defined in .hg/localtags.)
2133 # They constitute the in-memory cache of tags.
2135 # They constitute the in-memory cache of tags.
2134 self.tags = self.tagtypes = None
2136 self.tags = self.tagtypes = None
2135
2137
2136 self.nodetagscache = self.tagslist = None
2138 self.nodetagscache = self.tagslist = None
2137
2139
2138 cache = tagscache()
2140 cache = tagscache()
2139 cache.tags, cache.tagtypes = self._findtags()
2141 cache.tags, cache.tagtypes = self._findtags()
2140
2142
2141 return cache
2143 return cache
2142
2144
2143 def tags(self):
2145 def tags(self):
2144 '''return a mapping of tag to node'''
2146 '''return a mapping of tag to node'''
2145 t = {}
2147 t = {}
2146 if self.changelog.filteredrevs:
2148 if self.changelog.filteredrevs:
2147 tags, tt = self._findtags()
2149 tags, tt = self._findtags()
2148 else:
2150 else:
2149 tags = self._tagscache.tags
2151 tags = self._tagscache.tags
2150 rev = self.changelog.rev
2152 rev = self.changelog.rev
2151 for k, v in tags.items():
2153 for k, v in tags.items():
2152 try:
2154 try:
2153 # ignore tags to unknown nodes
2155 # ignore tags to unknown nodes
2154 rev(v)
2156 rev(v)
2155 t[k] = v
2157 t[k] = v
2156 except (error.LookupError, ValueError):
2158 except (error.LookupError, ValueError):
2157 pass
2159 pass
2158 return t
2160 return t
2159
2161
2160 def _findtags(self):
2162 def _findtags(self):
2161 """Do the hard work of finding tags. Return a pair of dicts
2163 """Do the hard work of finding tags. Return a pair of dicts
2162 (tags, tagtypes) where tags maps tag name to node, and tagtypes
2164 (tags, tagtypes) where tags maps tag name to node, and tagtypes
2163 maps tag name to a string like \'global\' or \'local\'.
2165 maps tag name to a string like \'global\' or \'local\'.
2164 Subclasses or extensions are free to add their own tags, but
2166 Subclasses or extensions are free to add their own tags, but
2165 should be aware that the returned dicts will be retained for the
2167 should be aware that the returned dicts will be retained for the
2166 duration of the localrepo object."""
2168 duration of the localrepo object."""
2167
2169
2168 # XXX what tagtype should subclasses/extensions use? Currently
2170 # XXX what tagtype should subclasses/extensions use? Currently
2169 # mq and bookmarks add tags, but do not set the tagtype at all.
2171 # mq and bookmarks add tags, but do not set the tagtype at all.
2170 # Should each extension invent its own tag type? Should there
2172 # Should each extension invent its own tag type? Should there
2171 # be one tagtype for all such "virtual" tags? Or is the status
2173 # be one tagtype for all such "virtual" tags? Or is the status
2172 # quo fine?
2174 # quo fine?
2173
2175
2174 # map tag name to (node, hist)
2176 # map tag name to (node, hist)
2175 alltags = tagsmod.findglobaltags(self.ui, self)
2177 alltags = tagsmod.findglobaltags(self.ui, self)
2176 # map tag name to tag type
2178 # map tag name to tag type
2177 tagtypes = {tag: b'global' for tag in alltags}
2179 tagtypes = {tag: b'global' for tag in alltags}
2178
2180
2179 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
2181 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
2180
2182
2181 # Build the return dicts. Have to re-encode tag names because
2183 # 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
2184 # 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
2185 # writing to the cache), but the rest of Mercurial wants them in
2184 # local encoding.
2186 # local encoding.
2185 tags = {}
2187 tags = {}
2186 for name, (node, hist) in alltags.items():
2188 for name, (node, hist) in alltags.items():
2187 if node != self.nullid:
2189 if node != self.nullid:
2188 tags[encoding.tolocal(name)] = node
2190 tags[encoding.tolocal(name)] = node
2189 tags[b'tip'] = self.changelog.tip()
2191 tags[b'tip'] = self.changelog.tip()
2190 tagtypes = {
2192 tagtypes = {
2191 encoding.tolocal(name): value for (name, value) in tagtypes.items()
2193 encoding.tolocal(name): value for (name, value) in tagtypes.items()
2192 }
2194 }
2193 return (tags, tagtypes)
2195 return (tags, tagtypes)
2194
2196
2195 def tagtype(self, tagname):
2197 def tagtype(self, tagname):
2196 """
2198 """
2197 return the type of the given tag. result can be:
2199 return the type of the given tag. result can be:
2198
2200
2199 'local' : a local tag
2201 'local' : a local tag
2200 'global' : a global tag
2202 'global' : a global tag
2201 None : tag does not exist
2203 None : tag does not exist
2202 """
2204 """
2203
2205
2204 return self._tagscache.tagtypes.get(tagname)
2206 return self._tagscache.tagtypes.get(tagname)
2205
2207
2206 def tagslist(self):
2208 def tagslist(self):
2207 '''return a list of tags ordered by revision'''
2209 '''return a list of tags ordered by revision'''
2208 if not self._tagscache.tagslist:
2210 if not self._tagscache.tagslist:
2209 l = []
2211 l = []
2210 for t, n in self.tags().items():
2212 for t, n in self.tags().items():
2211 l.append((self.changelog.rev(n), t, n))
2213 l.append((self.changelog.rev(n), t, n))
2212 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
2214 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
2213
2215
2214 return self._tagscache.tagslist
2216 return self._tagscache.tagslist
2215
2217
2216 def nodetags(self, node):
2218 def nodetags(self, node):
2217 '''return the tags associated with a node'''
2219 '''return the tags associated with a node'''
2218 if not self._tagscache.nodetagscache:
2220 if not self._tagscache.nodetagscache:
2219 nodetagscache = {}
2221 nodetagscache = {}
2220 for t, n in self._tagscache.tags.items():
2222 for t, n in self._tagscache.tags.items():
2221 nodetagscache.setdefault(n, []).append(t)
2223 nodetagscache.setdefault(n, []).append(t)
2222 for tags in nodetagscache.values():
2224 for tags in nodetagscache.values():
2223 tags.sort()
2225 tags.sort()
2224 self._tagscache.nodetagscache = nodetagscache
2226 self._tagscache.nodetagscache = nodetagscache
2225 return self._tagscache.nodetagscache.get(node, [])
2227 return self._tagscache.nodetagscache.get(node, [])
2226
2228
2227 def nodebookmarks(self, node):
2229 def nodebookmarks(self, node):
2228 """return the list of bookmarks pointing to the specified node"""
2230 """return the list of bookmarks pointing to the specified node"""
2229 return self._bookmarks.names(node)
2231 return self._bookmarks.names(node)
2230
2232
2231 def branchmap(self):
2233 def branchmap(self):
2232 """returns a dictionary {branch: [branchheads]} with branchheads
2234 """returns a dictionary {branch: [branchheads]} with branchheads
2233 ordered by increasing revision number"""
2235 ordered by increasing revision number"""
2234 return self._branchcaches[self]
2236 return self._branchcaches[self]
2235
2237
2236 @unfilteredmethod
2238 @unfilteredmethod
2237 def revbranchcache(self):
2239 def revbranchcache(self):
2238 if not self._revbranchcache:
2240 if not self._revbranchcache:
2239 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
2241 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
2240 return self._revbranchcache
2242 return self._revbranchcache
2241
2243
2242 def register_changeset(self, rev, changelogrevision):
2244 def register_changeset(self, rev, changelogrevision):
2243 self.revbranchcache().setdata(rev, changelogrevision)
2245 self.revbranchcache().setdata(rev, changelogrevision)
2244
2246
2245 def branchtip(self, branch, ignoremissing=False):
2247 def branchtip(self, branch, ignoremissing=False):
2246 """return the tip node for a given branch
2248 """return the tip node for a given branch
2247
2249
2248 If ignoremissing is True, then this method will not raise an error.
2250 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
2251 This is helpful for callers that only expect None for a missing branch
2250 (e.g. namespace).
2252 (e.g. namespace).
2251
2253
2252 """
2254 """
2253 try:
2255 try:
2254 return self.branchmap().branchtip(branch)
2256 return self.branchmap().branchtip(branch)
2255 except KeyError:
2257 except KeyError:
2256 if not ignoremissing:
2258 if not ignoremissing:
2257 raise error.RepoLookupError(_(b"unknown branch '%s'") % branch)
2259 raise error.RepoLookupError(_(b"unknown branch '%s'") % branch)
2258 else:
2260 else:
2259 pass
2261 pass
2260
2262
2261 def lookup(self, key):
2263 def lookup(self, key):
2262 node = scmutil.revsymbol(self, key).node()
2264 node = scmutil.revsymbol(self, key).node()
2263 if node is None:
2265 if node is None:
2264 raise error.RepoLookupError(_(b"unknown revision '%s'") % key)
2266 raise error.RepoLookupError(_(b"unknown revision '%s'") % key)
2265 return node
2267 return node
2266
2268
2267 def lookupbranch(self, key):
2269 def lookupbranch(self, key):
2268 if self.branchmap().hasbranch(key):
2270 if self.branchmap().hasbranch(key):
2269 return key
2271 return key
2270
2272
2271 return scmutil.revsymbol(self, key).branch()
2273 return scmutil.revsymbol(self, key).branch()
2272
2274
2273 def known(self, nodes):
2275 def known(self, nodes):
2274 cl = self.changelog
2276 cl = self.changelog
2275 get_rev = cl.index.get_rev
2277 get_rev = cl.index.get_rev
2276 filtered = cl.filteredrevs
2278 filtered = cl.filteredrevs
2277 result = []
2279 result = []
2278 for n in nodes:
2280 for n in nodes:
2279 r = get_rev(n)
2281 r = get_rev(n)
2280 resp = not (r is None or r in filtered)
2282 resp = not (r is None or r in filtered)
2281 result.append(resp)
2283 result.append(resp)
2282 return result
2284 return result
2283
2285
2284 def local(self):
2286 def local(self):
2285 return self
2287 return self
2286
2288
2287 def publishing(self):
2289 def publishing(self):
2288 # it's safe (and desirable) to trust the publish flag unconditionally
2290 # 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
2291 # 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)
2292 return self.ui.configbool(b'phases', b'publish', untrusted=True)
2291
2293
2292 def cancopy(self):
2294 def cancopy(self):
2293 # so statichttprepo's override of local() works
2295 # so statichttprepo's override of local() works
2294 if not self.local():
2296 if not self.local():
2295 return False
2297 return False
2296 if not self.publishing():
2298 if not self.publishing():
2297 return True
2299 return True
2298 # if publishing we can't copy if there is filtered content
2300 # if publishing we can't copy if there is filtered content
2299 return not self.filtered(b'visible').changelog.filteredrevs
2301 return not self.filtered(b'visible').changelog.filteredrevs
2300
2302
2301 def shared(self):
2303 def shared(self):
2302 '''the type of shared repository (None if not shared)'''
2304 '''the type of shared repository (None if not shared)'''
2303 if self.sharedpath != self.path:
2305 if self.sharedpath != self.path:
2304 return b'store'
2306 return b'store'
2305 return None
2307 return None
2306
2308
2307 def wjoin(self, f: bytes, *insidef: bytes) -> bytes:
2309 def wjoin(self, f: bytes, *insidef: bytes) -> bytes:
2308 return self.vfs.reljoin(self.root, f, *insidef)
2310 return self.vfs.reljoin(self.root, f, *insidef)
2309
2311
2310 def setparents(self, p1, p2=None):
2312 def setparents(self, p1, p2=None):
2311 if p2 is None:
2313 if p2 is None:
2312 p2 = self.nullid
2314 p2 = self.nullid
2313 self[None].setparents(p1, p2)
2315 self[None].setparents(p1, p2)
2314 self._quick_access_changeid_invalidate()
2316 self._quick_access_changeid_invalidate()
2315
2317
2316 def filectx(self, path: bytes, changeid=None, fileid=None, changectx=None):
2318 def filectx(self, path: bytes, changeid=None, fileid=None, changectx=None):
2317 """changeid must be a changeset revision, if specified.
2319 """changeid must be a changeset revision, if specified.
2318 fileid can be a file revision or node."""
2320 fileid can be a file revision or node."""
2319 return context.filectx(
2321 return context.filectx(
2320 self, path, changeid, fileid, changectx=changectx
2322 self, path, changeid, fileid, changectx=changectx
2321 )
2323 )
2322
2324
2323 def getcwd(self) -> bytes:
2325 def getcwd(self) -> bytes:
2324 return self.dirstate.getcwd()
2326 return self.dirstate.getcwd()
2325
2327
2326 def pathto(self, f: bytes, cwd: Optional[bytes] = None) -> bytes:
2328 def pathto(self, f: bytes, cwd: Optional[bytes] = None) -> bytes:
2327 return self.dirstate.pathto(f, cwd)
2329 return self.dirstate.pathto(f, cwd)
2328
2330
2329 def _loadfilter(self, filter):
2331 def _loadfilter(self, filter):
2330 if filter not in self._filterpats:
2332 if filter not in self._filterpats:
2331 l = []
2333 l = []
2332 for pat, cmd in self.ui.configitems(filter):
2334 for pat, cmd in self.ui.configitems(filter):
2333 if cmd == b'!':
2335 if cmd == b'!':
2334 continue
2336 continue
2335 mf = matchmod.match(self.root, b'', [pat])
2337 mf = matchmod.match(self.root, b'', [pat])
2336 fn = None
2338 fn = None
2337 params = cmd
2339 params = cmd
2338 for name, filterfn in self._datafilters.items():
2340 for name, filterfn in self._datafilters.items():
2339 if cmd.startswith(name):
2341 if cmd.startswith(name):
2340 fn = filterfn
2342 fn = filterfn
2341 params = cmd[len(name) :].lstrip()
2343 params = cmd[len(name) :].lstrip()
2342 break
2344 break
2343 if not fn:
2345 if not fn:
2344 fn = lambda s, c, **kwargs: procutil.filter(s, c)
2346 fn = lambda s, c, **kwargs: procutil.filter(s, c)
2345 fn.__name__ = 'commandfilter'
2347 fn.__name__ = 'commandfilter'
2346 # Wrap old filters not supporting keyword arguments
2348 # Wrap old filters not supporting keyword arguments
2347 if not pycompat.getargspec(fn)[2]:
2349 if not pycompat.getargspec(fn)[2]:
2348 oldfn = fn
2350 oldfn = fn
2349 fn = lambda s, c, oldfn=oldfn, **kwargs: oldfn(s, c)
2351 fn = lambda s, c, oldfn=oldfn, **kwargs: oldfn(s, c)
2350 fn.__name__ = 'compat-' + oldfn.__name__
2352 fn.__name__ = 'compat-' + oldfn.__name__
2351 l.append((mf, fn, params))
2353 l.append((mf, fn, params))
2352 self._filterpats[filter] = l
2354 self._filterpats[filter] = l
2353 return self._filterpats[filter]
2355 return self._filterpats[filter]
2354
2356
2355 def _filter(self, filterpats, filename, data):
2357 def _filter(self, filterpats, filename, data):
2356 for mf, fn, cmd in filterpats:
2358 for mf, fn, cmd in filterpats:
2357 if mf(filename):
2359 if mf(filename):
2358 self.ui.debug(
2360 self.ui.debug(
2359 b"filtering %s through %s\n"
2361 b"filtering %s through %s\n"
2360 % (filename, cmd or pycompat.sysbytes(fn.__name__))
2362 % (filename, cmd or pycompat.sysbytes(fn.__name__))
2361 )
2363 )
2362 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
2364 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
2363 break
2365 break
2364
2366
2365 return data
2367 return data
2366
2368
2367 @unfilteredpropertycache
2369 @unfilteredpropertycache
2368 def _encodefilterpats(self):
2370 def _encodefilterpats(self):
2369 return self._loadfilter(b'encode')
2371 return self._loadfilter(b'encode')
2370
2372
2371 @unfilteredpropertycache
2373 @unfilteredpropertycache
2372 def _decodefilterpats(self):
2374 def _decodefilterpats(self):
2373 return self._loadfilter(b'decode')
2375 return self._loadfilter(b'decode')
2374
2376
2375 def adddatafilter(self, name, filter):
2377 def adddatafilter(self, name, filter):
2376 self._datafilters[name] = filter
2378 self._datafilters[name] = filter
2377
2379
2378 def wread(self, filename: bytes) -> bytes:
2380 def wread(self, filename: bytes) -> bytes:
2379 if self.wvfs.islink(filename):
2381 if self.wvfs.islink(filename):
2380 data = self.wvfs.readlink(filename)
2382 data = self.wvfs.readlink(filename)
2381 else:
2383 else:
2382 data = self.wvfs.read(filename)
2384 data = self.wvfs.read(filename)
2383 return self._filter(self._encodefilterpats, filename, data)
2385 return self._filter(self._encodefilterpats, filename, data)
2384
2386
2385 def wwrite(
2387 def wwrite(
2386 self,
2388 self,
2387 filename: bytes,
2389 filename: bytes,
2388 data: bytes,
2390 data: bytes,
2389 flags: bytes,
2391 flags: bytes,
2390 backgroundclose=False,
2392 backgroundclose=False,
2391 **kwargs
2393 **kwargs
2392 ) -> int:
2394 ) -> int:
2393 """write ``data`` into ``filename`` in the working directory
2395 """write ``data`` into ``filename`` in the working directory
2394
2396
2395 This returns length of written (maybe decoded) data.
2397 This returns length of written (maybe decoded) data.
2396 """
2398 """
2397 data = self._filter(self._decodefilterpats, filename, data)
2399 data = self._filter(self._decodefilterpats, filename, data)
2398 if b'l' in flags:
2400 if b'l' in flags:
2399 self.wvfs.symlink(data, filename)
2401 self.wvfs.symlink(data, filename)
2400 else:
2402 else:
2401 self.wvfs.write(
2403 self.wvfs.write(
2402 filename, data, backgroundclose=backgroundclose, **kwargs
2404 filename, data, backgroundclose=backgroundclose, **kwargs
2403 )
2405 )
2404 if b'x' in flags:
2406 if b'x' in flags:
2405 self.wvfs.setflags(filename, False, True)
2407 self.wvfs.setflags(filename, False, True)
2406 else:
2408 else:
2407 self.wvfs.setflags(filename, False, False)
2409 self.wvfs.setflags(filename, False, False)
2408 return len(data)
2410 return len(data)
2409
2411
2410 def wwritedata(self, filename: bytes, data: bytes) -> bytes:
2412 def wwritedata(self, filename: bytes, data: bytes) -> bytes:
2411 return self._filter(self._decodefilterpats, filename, data)
2413 return self._filter(self._decodefilterpats, filename, data)
2412
2414
2413 def currenttransaction(self):
2415 def currenttransaction(self):
2414 """return the current transaction or None if non exists"""
2416 """return the current transaction or None if non exists"""
2415 if self._transref:
2417 if self._transref:
2416 tr = self._transref()
2418 tr = self._transref()
2417 else:
2419 else:
2418 tr = None
2420 tr = None
2419
2421
2420 if tr and tr.running():
2422 if tr and tr.running():
2421 return tr
2423 return tr
2422 return None
2424 return None
2423
2425
2424 def transaction(self, desc, report=None):
2426 def transaction(self, desc, report=None):
2425 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
2427 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
2426 b'devel', b'check-locks'
2428 b'devel', b'check-locks'
2427 ):
2429 ):
2428 if self._currentlock(self._lockref) is None:
2430 if self._currentlock(self._lockref) is None:
2429 raise error.ProgrammingError(b'transaction requires locking')
2431 raise error.ProgrammingError(b'transaction requires locking')
2430 tr = self.currenttransaction()
2432 tr = self.currenttransaction()
2431 if tr is not None:
2433 if tr is not None:
2432 return tr.nest(name=desc)
2434 return tr.nest(name=desc)
2433
2435
2434 # abort here if the journal already exists
2436 # abort here if the journal already exists
2435 if self.svfs.exists(b"journal"):
2437 if self.svfs.exists(b"journal"):
2436 raise error.RepoError(
2438 raise error.RepoError(
2437 _(b"abandoned transaction found"),
2439 _(b"abandoned transaction found"),
2438 hint=_(b"run 'hg recover' to clean up transaction"),
2440 hint=_(b"run 'hg recover' to clean up transaction"),
2439 )
2441 )
2440
2442
2441 # At that point your dirstate should be clean:
2443 # At that point your dirstate should be clean:
2442 #
2444 #
2443 # - If you don't have the wlock, why would you still have a dirty
2445 # - If you don't have the wlock, why would you still have a dirty
2444 # dirstate ?
2446 # dirstate ?
2445 #
2447 #
2446 # - If you hold the wlock, you should not be opening a transaction in
2448 # - 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
2449 # the middle of a `distate.changing_*` block. The transaction needs to
2448 # be open before that and wrap the change-context.
2450 # be open before that and wrap the change-context.
2449 #
2451 #
2450 # - If you are not within a `dirstate.changing_*` context, why is our
2452 # - If you are not within a `dirstate.changing_*` context, why is our
2451 # dirstate dirty?
2453 # dirstate dirty?
2452 if self.dirstate._dirty:
2454 if self.dirstate._dirty:
2453 m = "cannot open a transaction with a dirty dirstate"
2455 m = "cannot open a transaction with a dirty dirstate"
2454 raise error.ProgrammingError(m)
2456 raise error.ProgrammingError(m)
2455
2457
2456 idbase = b"%.40f#%f" % (random.random(), time.time())
2458 idbase = b"%.40f#%f" % (random.random(), time.time())
2457 ha = hex(hashutil.sha1(idbase).digest())
2459 ha = hex(hashutil.sha1(idbase).digest())
2458 txnid = b'TXN:' + ha
2460 txnid = b'TXN:' + ha
2459 self.hook(b'pretxnopen', throw=True, txnname=desc, txnid=txnid)
2461 self.hook(b'pretxnopen', throw=True, txnname=desc, txnid=txnid)
2460
2462
2461 self._writejournal(desc)
2463 self._writejournal(desc)
2462 if report:
2464 if report:
2463 rp = report
2465 rp = report
2464 else:
2466 else:
2465 rp = self.ui.warn
2467 rp = self.ui.warn
2466 vfsmap = self.vfs_map
2468 vfsmap = self.vfs_map
2467 # we must avoid cyclic reference between repo and transaction.
2469 # we must avoid cyclic reference between repo and transaction.
2468 reporef = weakref.ref(self)
2470 reporef = weakref.ref(self)
2469 # Code to track tag movement
2471 # Code to track tag movement
2470 #
2472 #
2471 # Since tags are all handled as file content, it is actually quite hard
2473 # 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
2474 # 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
2475 # tracking at the repository level. One could envision to track changes
2474 # to the '.hgtags' file through changegroup apply but that fails to
2476 # to the '.hgtags' file through changegroup apply but that fails to
2475 # cope with case where transaction expose new heads without changegroup
2477 # cope with case where transaction expose new heads without changegroup
2476 # being involved (eg: phase movement).
2478 # being involved (eg: phase movement).
2477 #
2479 #
2478 # For now, We gate the feature behind a flag since this likely comes
2480 # 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
2481 # 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
2482 # 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
2483 # 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.
2484 # will be removed when we are happy with the performance impact.
2483 #
2485 #
2484 # Once this feature is no longer experimental move the following
2486 # Once this feature is no longer experimental move the following
2485 # documentation to the appropriate help section:
2487 # documentation to the appropriate help section:
2486 #
2488 #
2487 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
2489 # 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
2490 # tags (new or changed or deleted tags). In addition the details of
2489 # these changes are made available in a file at:
2491 # these changes are made available in a file at:
2490 # ``REPOROOT/.hg/changes/tags.changes``.
2492 # ``REPOROOT/.hg/changes/tags.changes``.
2491 # Make sure you check for HG_TAG_MOVED before reading that file as it
2493 # 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
2494 # might exist from a previous transaction even if no tag were touched
2493 # in this one. Changes are recorded in a line base format::
2495 # in this one. Changes are recorded in a line base format::
2494 #
2496 #
2495 # <action> <hex-node> <tag-name>\n
2497 # <action> <hex-node> <tag-name>\n
2496 #
2498 #
2497 # Actions are defined as follow:
2499 # Actions are defined as follow:
2498 # "-R": tag is removed,
2500 # "-R": tag is removed,
2499 # "+A": tag is added,
2501 # "+A": tag is added,
2500 # "-M": tag is moved (old value),
2502 # "-M": tag is moved (old value),
2501 # "+M": tag is moved (new value),
2503 # "+M": tag is moved (new value),
2502 tracktags = lambda x: None
2504 tracktags = lambda x: None
2503 # experimental config: experimental.hook-track-tags
2505 # experimental config: experimental.hook-track-tags
2504 shouldtracktags = self.ui.configbool(
2506 shouldtracktags = self.ui.configbool(
2505 b'experimental', b'hook-track-tags'
2507 b'experimental', b'hook-track-tags'
2506 )
2508 )
2507 if desc != b'strip' and shouldtracktags:
2509 if desc != b'strip' and shouldtracktags:
2508 oldheads = self.changelog.headrevs()
2510 oldheads = self.changelog.headrevs()
2509
2511
2510 def tracktags(tr2):
2512 def tracktags(tr2):
2511 repo = reporef()
2513 repo = reporef()
2512 assert repo is not None # help pytype
2514 assert repo is not None # help pytype
2513 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
2515 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
2514 newheads = repo.changelog.headrevs()
2516 newheads = repo.changelog.headrevs()
2515 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
2517 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
2516 # notes: we compare lists here.
2518 # notes: we compare lists here.
2517 # As we do it only once buiding set would not be cheaper
2519 # As we do it only once buiding set would not be cheaper
2518 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
2520 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
2519 if changes:
2521 if changes:
2520 tr2.hookargs[b'tag_moved'] = b'1'
2522 tr2.hookargs[b'tag_moved'] = b'1'
2521 with repo.vfs(
2523 with repo.vfs(
2522 b'changes/tags.changes', b'w', atomictemp=True
2524 b'changes/tags.changes', b'w', atomictemp=True
2523 ) as changesfile:
2525 ) as changesfile:
2524 # note: we do not register the file to the transaction
2526 # note: we do not register the file to the transaction
2525 # because we needs it to still exist on the transaction
2527 # because we needs it to still exist on the transaction
2526 # is close (for txnclose hooks)
2528 # is close (for txnclose hooks)
2527 tagsmod.writediff(changesfile, changes)
2529 tagsmod.writediff(changesfile, changes)
2528
2530
2529 def validate(tr2):
2531 def validate(tr2):
2530 """will run pre-closing hooks"""
2532 """will run pre-closing hooks"""
2531 # XXX the transaction API is a bit lacking here so we take a hacky
2533 # XXX the transaction API is a bit lacking here so we take a hacky
2532 # path for now
2534 # path for now
2533 #
2535 #
2534 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
2536 # 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
2537 # dict is copied before these run. In addition we needs the data
2536 # available to in memory hooks too.
2538 # available to in memory hooks too.
2537 #
2539 #
2538 # Moreover, we also need to make sure this runs before txnclose
2540 # Moreover, we also need to make sure this runs before txnclose
2539 # hooks and there is no "pending" mechanism that would execute
2541 # hooks and there is no "pending" mechanism that would execute
2540 # logic only if hooks are about to run.
2542 # logic only if hooks are about to run.
2541 #
2543 #
2542 # Fixing this limitation of the transaction is also needed to track
2544 # Fixing this limitation of the transaction is also needed to track
2543 # other families of changes (bookmarks, phases, obsolescence).
2545 # other families of changes (bookmarks, phases, obsolescence).
2544 #
2546 #
2545 # This will have to be fixed before we remove the experimental
2547 # This will have to be fixed before we remove the experimental
2546 # gating.
2548 # gating.
2547 tracktags(tr2)
2549 tracktags(tr2)
2548 repo = reporef()
2550 repo = reporef()
2549 assert repo is not None # help pytype
2551 assert repo is not None # help pytype
2550
2552
2551 singleheadopt = (b'experimental', b'single-head-per-branch')
2553 singleheadopt = (b'experimental', b'single-head-per-branch')
2552 singlehead = repo.ui.configbool(*singleheadopt)
2554 singlehead = repo.ui.configbool(*singleheadopt)
2553 if singlehead:
2555 if singlehead:
2554 singleheadsub = repo.ui.configsuboptions(*singleheadopt)[1]
2556 singleheadsub = repo.ui.configsuboptions(*singleheadopt)[1]
2555 accountclosed = singleheadsub.get(
2557 accountclosed = singleheadsub.get(
2556 b"account-closed-heads", False
2558 b"account-closed-heads", False
2557 )
2559 )
2558 if singleheadsub.get(b"public-changes-only", False):
2560 if singleheadsub.get(b"public-changes-only", False):
2559 filtername = b"immutable"
2561 filtername = b"immutable"
2560 else:
2562 else:
2561 filtername = b"visible"
2563 filtername = b"visible"
2562 scmutil.enforcesinglehead(
2564 scmutil.enforcesinglehead(
2563 repo, tr2, desc, accountclosed, filtername
2565 repo, tr2, desc, accountclosed, filtername
2564 )
2566 )
2565 if hook.hashook(repo.ui, b'pretxnclose-bookmark'):
2567 if hook.hashook(repo.ui, b'pretxnclose-bookmark'):
2566 for name, (old, new) in sorted(
2568 for name, (old, new) in sorted(
2567 tr.changes[b'bookmarks'].items()
2569 tr.changes[b'bookmarks'].items()
2568 ):
2570 ):
2569 args = tr.hookargs.copy()
2571 args = tr.hookargs.copy()
2570 args.update(bookmarks.preparehookargs(name, old, new))
2572 args.update(bookmarks.preparehookargs(name, old, new))
2571 repo.hook(
2573 repo.hook(
2572 b'pretxnclose-bookmark',
2574 b'pretxnclose-bookmark',
2573 throw=True,
2575 throw=True,
2574 **pycompat.strkwargs(args)
2576 **pycompat.strkwargs(args)
2575 )
2577 )
2576 if hook.hashook(repo.ui, b'pretxnclose-phase'):
2578 if hook.hashook(repo.ui, b'pretxnclose-phase'):
2577 cl = repo.unfiltered().changelog
2579 cl = repo.unfiltered().changelog
2578 for revs, (old, new) in tr.changes[b'phases']:
2580 for revs, (old, new) in tr.changes[b'phases']:
2579 for rev in revs:
2581 for rev in revs:
2580 args = tr.hookargs.copy()
2582 args = tr.hookargs.copy()
2581 node = hex(cl.node(rev))
2583 node = hex(cl.node(rev))
2582 args.update(phases.preparehookargs(node, old, new))
2584 args.update(phases.preparehookargs(node, old, new))
2583 repo.hook(
2585 repo.hook(
2584 b'pretxnclose-phase',
2586 b'pretxnclose-phase',
2585 throw=True,
2587 throw=True,
2586 **pycompat.strkwargs(args)
2588 **pycompat.strkwargs(args)
2587 )
2589 )
2588
2590
2589 repo.hook(
2591 repo.hook(
2590 b'pretxnclose', throw=True, **pycompat.strkwargs(tr.hookargs)
2592 b'pretxnclose', throw=True, **pycompat.strkwargs(tr.hookargs)
2591 )
2593 )
2592
2594
2593 def releasefn(tr, success):
2595 def releasefn(tr, success):
2594 repo = reporef()
2596 repo = reporef()
2595 if repo is None:
2597 if repo is None:
2596 # If the repo has been GC'd (and this release function is being
2598 # 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,
2599 # called from transaction.__del__), there's not much we can do,
2598 # so just leave the unfinished transaction there and let the
2600 # so just leave the unfinished transaction there and let the
2599 # user run `hg recover`.
2601 # user run `hg recover`.
2600 return
2602 return
2601 if success:
2603 if success:
2602 # this should be explicitly invoked here, because
2604 # this should be explicitly invoked here, because
2603 # in-memory changes aren't written out at closing
2605 # in-memory changes aren't written out at closing
2604 # transaction, if tr.addfilegenerator (via
2606 # transaction, if tr.addfilegenerator (via
2605 # dirstate.write or so) isn't invoked while
2607 # dirstate.write or so) isn't invoked while
2606 # transaction running
2608 # transaction running
2607 repo.dirstate.write(None)
2609 repo.dirstate.write(None)
2608 else:
2610 else:
2609 # discard all changes (including ones already written
2611 # discard all changes (including ones already written
2610 # out) in this transaction
2612 # out) in this transaction
2611 repo.invalidate(clearfilecache=True)
2613 repo.invalidate(clearfilecache=True)
2612
2614
2613 tr = transaction.transaction(
2615 tr = transaction.transaction(
2614 rp,
2616 rp,
2615 self.svfs,
2617 self.svfs,
2616 vfsmap,
2618 vfsmap,
2617 b"journal",
2619 b"journal",
2618 b"undo",
2620 b"undo",
2619 lambda: None,
2621 lambda: None,
2620 self.store.createmode,
2622 self.store.createmode,
2621 validator=validate,
2623 validator=validate,
2622 releasefn=releasefn,
2624 releasefn=releasefn,
2623 checkambigfiles=_cachedfiles,
2625 checkambigfiles=_cachedfiles,
2624 name=desc,
2626 name=desc,
2625 )
2627 )
2626 for vfs_id, path in self._journalfiles():
2628 for vfs_id, path in self._journalfiles():
2627 tr.add_journal(vfs_id, path)
2629 tr.add_journal(vfs_id, path)
2628 tr.changes[b'origrepolen'] = len(self)
2630 tr.changes[b'origrepolen'] = len(self)
2629 tr.changes[b'obsmarkers'] = set()
2631 tr.changes[b'obsmarkers'] = set()
2630 tr.changes[b'phases'] = []
2632 tr.changes[b'phases'] = []
2631 tr.changes[b'bookmarks'] = {}
2633 tr.changes[b'bookmarks'] = {}
2632
2634
2633 tr.hookargs[b'txnid'] = txnid
2635 tr.hookargs[b'txnid'] = txnid
2634 tr.hookargs[b'txnname'] = desc
2636 tr.hookargs[b'txnname'] = desc
2635 tr.hookargs[b'changes'] = tr.changes
2637 tr.hookargs[b'changes'] = tr.changes
2636 # note: writing the fncache only during finalize mean that the file is
2638 # note: writing the fncache only during finalize mean that the file is
2637 # outdated when running hooks. As fncache is used for streaming clone,
2639 # outdated when running hooks. As fncache is used for streaming clone,
2638 # this is not expected to break anything that happen during the hooks.
2640 # this is not expected to break anything that happen during the hooks.
2639 tr.addfinalize(b'flush-fncache', self.store.write)
2641 tr.addfinalize(b'flush-fncache', self.store.write)
2640
2642
2641 def txnclosehook(tr2):
2643 def txnclosehook(tr2):
2642 """To be run if transaction is successful, will schedule a hook run"""
2644 """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.
2645 # Don't reference tr2 in hook() so we don't hold a reference.
2644 # This reduces memory consumption when there are multiple
2646 # This reduces memory consumption when there are multiple
2645 # transactions per lock. This can likely go away if issue5045
2647 # transactions per lock. This can likely go away if issue5045
2646 # fixes the function accumulation.
2648 # fixes the function accumulation.
2647 hookargs = tr2.hookargs
2649 hookargs = tr2.hookargs
2648
2650
2649 def hookfunc(unused_success):
2651 def hookfunc(unused_success):
2650 repo = reporef()
2652 repo = reporef()
2651 assert repo is not None # help pytype
2653 assert repo is not None # help pytype
2652
2654
2653 if hook.hashook(repo.ui, b'txnclose-bookmark'):
2655 if hook.hashook(repo.ui, b'txnclose-bookmark'):
2654 bmchanges = sorted(tr.changes[b'bookmarks'].items())
2656 bmchanges = sorted(tr.changes[b'bookmarks'].items())
2655 for name, (old, new) in bmchanges:
2657 for name, (old, new) in bmchanges:
2656 args = tr.hookargs.copy()
2658 args = tr.hookargs.copy()
2657 args.update(bookmarks.preparehookargs(name, old, new))
2659 args.update(bookmarks.preparehookargs(name, old, new))
2658 repo.hook(
2660 repo.hook(
2659 b'txnclose-bookmark',
2661 b'txnclose-bookmark',
2660 throw=False,
2662 throw=False,
2661 **pycompat.strkwargs(args)
2663 **pycompat.strkwargs(args)
2662 )
2664 )
2663
2665
2664 if hook.hashook(repo.ui, b'txnclose-phase'):
2666 if hook.hashook(repo.ui, b'txnclose-phase'):
2665 cl = repo.unfiltered().changelog
2667 cl = repo.unfiltered().changelog
2666 phasemv = sorted(
2668 phasemv = sorted(
2667 tr.changes[b'phases'], key=lambda r: r[0][0]
2669 tr.changes[b'phases'], key=lambda r: r[0][0]
2668 )
2670 )
2669 for revs, (old, new) in phasemv:
2671 for revs, (old, new) in phasemv:
2670 for rev in revs:
2672 for rev in revs:
2671 args = tr.hookargs.copy()
2673 args = tr.hookargs.copy()
2672 node = hex(cl.node(rev))
2674 node = hex(cl.node(rev))
2673 args.update(phases.preparehookargs(node, old, new))
2675 args.update(phases.preparehookargs(node, old, new))
2674 repo.hook(
2676 repo.hook(
2675 b'txnclose-phase',
2677 b'txnclose-phase',
2676 throw=False,
2678 throw=False,
2677 **pycompat.strkwargs(args)
2679 **pycompat.strkwargs(args)
2678 )
2680 )
2679
2681
2680 repo.hook(
2682 repo.hook(
2681 b'txnclose', throw=False, **pycompat.strkwargs(hookargs)
2683 b'txnclose', throw=False, **pycompat.strkwargs(hookargs)
2682 )
2684 )
2683
2685
2684 repo = reporef()
2686 repo = reporef()
2685 assert repo is not None # help pytype
2687 assert repo is not None # help pytype
2686 repo._afterlock(hookfunc)
2688 repo._afterlock(hookfunc)
2687
2689
2688 tr.addfinalize(b'txnclose-hook', txnclosehook)
2690 tr.addfinalize(b'txnclose-hook', txnclosehook)
2689 # Include a leading "-" to make it happen before the transaction summary
2691 # Include a leading "-" to make it happen before the transaction summary
2690 # reports registered via scmutil.registersummarycallback() whose names
2692 # reports registered via scmutil.registersummarycallback() whose names
2691 # are 00-txnreport etc. That way, the caches will be warm when the
2693 # are 00-txnreport etc. That way, the caches will be warm when the
2692 # callbacks run.
2694 # callbacks run.
2693 tr.addpostclose(b'-warm-cache', self._buildcacheupdater(tr))
2695 tr.addpostclose(b'-warm-cache', self._buildcacheupdater(tr))
2694
2696
2695 def txnaborthook(tr2):
2697 def txnaborthook(tr2):
2696 """To be run if transaction is aborted"""
2698 """To be run if transaction is aborted"""
2697 repo = reporef()
2699 repo = reporef()
2698 assert repo is not None # help pytype
2700 assert repo is not None # help pytype
2699 repo.hook(
2701 repo.hook(
2700 b'txnabort', throw=False, **pycompat.strkwargs(tr2.hookargs)
2702 b'txnabort', throw=False, **pycompat.strkwargs(tr2.hookargs)
2701 )
2703 )
2702
2704
2703 tr.addabort(b'txnabort-hook', txnaborthook)
2705 tr.addabort(b'txnabort-hook', txnaborthook)
2704 # avoid eager cache invalidation. in-memory data should be identical
2706 # avoid eager cache invalidation. in-memory data should be identical
2705 # to stored data if transaction has no error.
2707 # to stored data if transaction has no error.
2706 tr.addpostclose(b'refresh-filecachestats', self._refreshfilecachestats)
2708 tr.addpostclose(b'refresh-filecachestats', self._refreshfilecachestats)
2707 self._transref = weakref.ref(tr)
2709 self._transref = weakref.ref(tr)
2708 scmutil.registersummarycallback(self, tr, desc)
2710 scmutil.registersummarycallback(self, tr, desc)
2709 # This only exist to deal with the need of rollback to have viable
2711 # 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
2712 # parents at the end of the operation. So backup viable parents at the
2711 # time of this operation.
2713 # time of this operation.
2712 #
2714 #
2713 # We only do it when the `wlock` is taken, otherwise other might be
2715 # We only do it when the `wlock` is taken, otherwise other might be
2714 # altering the dirstate under us.
2716 # altering the dirstate under us.
2715 #
2717 #
2716 # This is really not a great way to do this (first, because we cannot
2718 # 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
2719 # always do it). There are more viable alternative that exists
2718 #
2720 #
2719 # - backing only the working copy parent in a dedicated files and doing
2721 # - backing only the working copy parent in a dedicated files and doing
2720 # a clean "keep-update" to them on `hg rollback`.
2722 # a clean "keep-update" to them on `hg rollback`.
2721 #
2723 #
2722 # - slightly changing the behavior an applying a logic similar to "hg
2724 # - slightly changing the behavior an applying a logic similar to "hg
2723 # strip" to pick a working copy destination on `hg rollback`
2725 # strip" to pick a working copy destination on `hg rollback`
2724 if self.currentwlock() is not None:
2726 if self.currentwlock() is not None:
2725 ds = self.dirstate
2727 ds = self.dirstate
2726 if not self.vfs.exists(b'branch'):
2728 if not self.vfs.exists(b'branch'):
2727 # force a file to be written if None exist
2729 # force a file to be written if None exist
2728 ds.setbranch(b'default', None)
2730 ds.setbranch(b'default', None)
2729
2731
2730 def backup_dirstate(tr):
2732 def backup_dirstate(tr):
2731 for f in ds.all_file_names():
2733 for f in ds.all_file_names():
2732 # hardlink backup is okay because `dirstate` is always
2734 # hardlink backup is okay because `dirstate` is always
2733 # atomically written and possible data file are append only
2735 # atomically written and possible data file are append only
2734 # and resistant to trailing data.
2736 # and resistant to trailing data.
2735 tr.addbackup(f, hardlink=True, location=b'plain')
2737 tr.addbackup(f, hardlink=True, location=b'plain')
2736
2738
2737 tr.addvalidator(b'dirstate-backup', backup_dirstate)
2739 tr.addvalidator(b'dirstate-backup', backup_dirstate)
2738 return tr
2740 return tr
2739
2741
2740 def _journalfiles(self):
2742 def _journalfiles(self):
2741 return (
2743 return (
2742 (self.svfs, b'journal'),
2744 (self.svfs, b'journal'),
2743 (self.vfs, b'journal.desc'),
2745 (self.vfs, b'journal.desc'),
2744 )
2746 )
2745
2747
2746 def undofiles(self):
2748 def undofiles(self):
2747 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
2749 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
2748
2750
2749 @unfilteredmethod
2751 @unfilteredmethod
2750 def _writejournal(self, desc):
2752 def _writejournal(self, desc):
2751 self.vfs.write(b"journal.desc", b"%d\n%s\n" % (len(self), desc))
2753 self.vfs.write(b"journal.desc", b"%d\n%s\n" % (len(self), desc))
2752
2754
2753 def recover(self):
2755 def recover(self):
2754 with self.lock():
2756 with self.lock():
2755 if self.svfs.exists(b"journal"):
2757 if self.svfs.exists(b"journal"):
2756 self.ui.status(_(b"rolling back interrupted transaction\n"))
2758 self.ui.status(_(b"rolling back interrupted transaction\n"))
2757 vfsmap = self.vfs_map
2759 vfsmap = self.vfs_map
2758 transaction.rollback(
2760 transaction.rollback(
2759 self.svfs,
2761 self.svfs,
2760 vfsmap,
2762 vfsmap,
2761 b"journal",
2763 b"journal",
2762 self.ui.warn,
2764 self.ui.warn,
2763 checkambigfiles=_cachedfiles,
2765 checkambigfiles=_cachedfiles,
2764 )
2766 )
2765 self.invalidate()
2767 self.invalidate()
2766 return True
2768 return True
2767 else:
2769 else:
2768 self.ui.warn(_(b"no interrupted transaction available\n"))
2770 self.ui.warn(_(b"no interrupted transaction available\n"))
2769 return False
2771 return False
2770
2772
2771 def rollback(self, dryrun=False, force=False):
2773 def rollback(self, dryrun=False, force=False):
2772 wlock = lock = None
2774 wlock = lock = None
2773 try:
2775 try:
2774 wlock = self.wlock()
2776 wlock = self.wlock()
2775 lock = self.lock()
2777 lock = self.lock()
2776 if self.svfs.exists(b"undo"):
2778 if self.svfs.exists(b"undo"):
2777 return self._rollback(dryrun, force)
2779 return self._rollback(dryrun, force)
2778 else:
2780 else:
2779 self.ui.warn(_(b"no rollback information available\n"))
2781 self.ui.warn(_(b"no rollback information available\n"))
2780 return 1
2782 return 1
2781 finally:
2783 finally:
2782 release(lock, wlock)
2784 release(lock, wlock)
2783
2785
2784 @unfilteredmethod # Until we get smarter cache management
2786 @unfilteredmethod # Until we get smarter cache management
2785 def _rollback(self, dryrun, force):
2787 def _rollback(self, dryrun, force):
2786 ui = self.ui
2788 ui = self.ui
2787
2789
2788 parents = self.dirstate.parents()
2790 parents = self.dirstate.parents()
2789 try:
2791 try:
2790 args = self.vfs.read(b'undo.desc').splitlines()
2792 args = self.vfs.read(b'undo.desc').splitlines()
2791 (oldlen, desc, detail) = (int(args[0]), args[1], None)
2793 (oldlen, desc, detail) = (int(args[0]), args[1], None)
2792 if len(args) >= 3:
2794 if len(args) >= 3:
2793 detail = args[2]
2795 detail = args[2]
2794 oldtip = oldlen - 1
2796 oldtip = oldlen - 1
2795
2797
2796 if detail and ui.verbose:
2798 if detail and ui.verbose:
2797 msg = _(
2799 msg = _(
2798 b'repository tip rolled back to revision %d'
2800 b'repository tip rolled back to revision %d'
2799 b' (undo %s: %s)\n'
2801 b' (undo %s: %s)\n'
2800 ) % (oldtip, desc, detail)
2802 ) % (oldtip, desc, detail)
2801 else:
2803 else:
2802 msg = _(
2804 msg = _(
2803 b'repository tip rolled back to revision %d (undo %s)\n'
2805 b'repository tip rolled back to revision %d (undo %s)\n'
2804 ) % (oldtip, desc)
2806 ) % (oldtip, desc)
2805 parentgone = any(self[p].rev() > oldtip for p in parents)
2807 parentgone = any(self[p].rev() > oldtip for p in parents)
2806 except IOError:
2808 except IOError:
2807 msg = _(b'rolling back unknown transaction\n')
2809 msg = _(b'rolling back unknown transaction\n')
2808 desc = None
2810 desc = None
2809 parentgone = True
2811 parentgone = True
2810
2812
2811 if not force and self[b'.'] != self[b'tip'] and desc == b'commit':
2813 if not force and self[b'.'] != self[b'tip'] and desc == b'commit':
2812 raise error.Abort(
2814 raise error.Abort(
2813 _(
2815 _(
2814 b'rollback of last commit while not checked out '
2816 b'rollback of last commit while not checked out '
2815 b'may lose data'
2817 b'may lose data'
2816 ),
2818 ),
2817 hint=_(b'use -f to force'),
2819 hint=_(b'use -f to force'),
2818 )
2820 )
2819
2821
2820 ui.status(msg)
2822 ui.status(msg)
2821 if dryrun:
2823 if dryrun:
2822 return 0
2824 return 0
2823
2825
2824 self.destroying()
2826 self.destroying()
2825 vfsmap = self.vfs_map
2827 vfsmap = self.vfs_map
2826 skip_journal_pattern = None
2828 skip_journal_pattern = None
2827 if not parentgone:
2829 if not parentgone:
2828 skip_journal_pattern = RE_SKIP_DIRSTATE_ROLLBACK
2830 skip_journal_pattern = RE_SKIP_DIRSTATE_ROLLBACK
2829 transaction.rollback(
2831 transaction.rollback(
2830 self.svfs,
2832 self.svfs,
2831 vfsmap,
2833 vfsmap,
2832 b'undo',
2834 b'undo',
2833 ui.warn,
2835 ui.warn,
2834 checkambigfiles=_cachedfiles,
2836 checkambigfiles=_cachedfiles,
2835 skip_journal_pattern=skip_journal_pattern,
2837 skip_journal_pattern=skip_journal_pattern,
2836 )
2838 )
2837 self.invalidate()
2839 self.invalidate()
2838 self.dirstate.invalidate()
2840 self.dirstate.invalidate()
2839
2841
2840 if parentgone:
2842 if parentgone:
2841 # replace this with some explicit parent update in the future.
2843 # replace this with some explicit parent update in the future.
2842 has_node = self.changelog.index.has_node
2844 has_node = self.changelog.index.has_node
2843 if not all(has_node(p) for p in self.dirstate._pl):
2845 if not all(has_node(p) for p in self.dirstate._pl):
2844 # There was no dirstate to backup initially, we need to drop
2846 # There was no dirstate to backup initially, we need to drop
2845 # the existing one.
2847 # the existing one.
2846 with self.dirstate.changing_parents(self):
2848 with self.dirstate.changing_parents(self):
2847 self.dirstate.setparents(self.nullid)
2849 self.dirstate.setparents(self.nullid)
2848 self.dirstate.clear()
2850 self.dirstate.clear()
2849
2851
2850 parents = tuple([p.rev() for p in self[None].parents()])
2852 parents = tuple([p.rev() for p in self[None].parents()])
2851 if len(parents) > 1:
2853 if len(parents) > 1:
2852 ui.status(
2854 ui.status(
2853 _(
2855 _(
2854 b'working directory now based on '
2856 b'working directory now based on '
2855 b'revisions %d and %d\n'
2857 b'revisions %d and %d\n'
2856 )
2858 )
2857 % parents
2859 % parents
2858 )
2860 )
2859 else:
2861 else:
2860 ui.status(
2862 ui.status(
2861 _(b'working directory now based on revision %d\n') % parents
2863 _(b'working directory now based on revision %d\n') % parents
2862 )
2864 )
2863 mergestatemod.mergestate.clean(self)
2865 mergestatemod.mergestate.clean(self)
2864
2866
2865 # TODO: if we know which new heads may result from this rollback, pass
2867 # 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
2868 # them to destroy(), which will prevent the branchhead cache from being
2867 # invalidated.
2869 # invalidated.
2868 self.destroyed()
2870 self.destroyed()
2869 return 0
2871 return 0
2870
2872
2871 def _buildcacheupdater(self, newtransaction):
2873 def _buildcacheupdater(self, newtransaction):
2872 """called during transaction to build the callback updating cache
2874 """called during transaction to build the callback updating cache
2873
2875
2874 Lives on the repository to help extension who might want to augment
2876 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
2877 this logic. For this purpose, the created transaction is passed to the
2876 method.
2878 method.
2877 """
2879 """
2878 # we must avoid cyclic reference between repo and transaction.
2880 # we must avoid cyclic reference between repo and transaction.
2879 reporef = weakref.ref(self)
2881 reporef = weakref.ref(self)
2880
2882
2881 def updater(tr):
2883 def updater(tr):
2882 repo = reporef()
2884 repo = reporef()
2883 assert repo is not None # help pytype
2885 assert repo is not None # help pytype
2884 repo.updatecaches(tr)
2886 repo.updatecaches(tr)
2885
2887
2886 return updater
2888 return updater
2887
2889
2888 @unfilteredmethod
2890 @unfilteredmethod
2889 def updatecaches(self, tr=None, full=False, caches=None):
2891 def updatecaches(self, tr=None, full=False, caches=None):
2890 """warm appropriate caches
2892 """warm appropriate caches
2891
2893
2892 If this function is called after a transaction closed. The transaction
2894 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
2895 will be available in the 'tr' argument. This can be used to selectively
2894 update caches relevant to the changes in that transaction.
2896 update caches relevant to the changes in that transaction.
2895
2897
2896 If 'full' is set, make sure all caches the function knows about have
2898 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.
2899 up-to-date data. Even the ones usually loaded more lazily.
2898
2900
2899 The `full` argument can take a special "post-clone" value. In this case
2901 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
2902 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
2903 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.
2904 as we plan for a cleaner way to deal with this for 5.9.
2903 """
2905 """
2904 if tr is not None and tr.hookargs.get(b'source') == b'strip':
2906 if tr is not None and tr.hookargs.get(b'source') == b'strip':
2905 # During strip, many caches are invalid but
2907 # During strip, many caches are invalid but
2906 # later call to `destroyed` will refresh them.
2908 # later call to `destroyed` will refresh them.
2907 return
2909 return
2908
2910
2909 unfi = self.unfiltered()
2911 unfi = self.unfiltered()
2910
2912
2911 if full:
2913 if full:
2912 msg = (
2914 msg = (
2913 "`full` argument for `repo.updatecaches` is deprecated\n"
2915 "`full` argument for `repo.updatecaches` is deprecated\n"
2914 "(use `caches=repository.CACHE_ALL` instead)"
2916 "(use `caches=repository.CACHE_ALL` instead)"
2915 )
2917 )
2916 self.ui.deprecwarn(msg, b"5.9")
2918 self.ui.deprecwarn(msg, b"5.9")
2917 caches = repository.CACHES_ALL
2919 caches = repository.CACHES_ALL
2918 if full == b"post-clone":
2920 if full == b"post-clone":
2919 caches = repository.CACHES_POST_CLONE
2921 caches = repository.CACHES_POST_CLONE
2920 caches = repository.CACHES_ALL
2922 caches = repository.CACHES_ALL
2921 elif caches is None:
2923 elif caches is None:
2922 caches = repository.CACHES_DEFAULT
2924 caches = repository.CACHES_DEFAULT
2923
2925
2924 if repository.CACHE_BRANCHMAP_SERVED in caches:
2926 if repository.CACHE_BRANCHMAP_SERVED in caches:
2925 if tr is None or tr.changes[b'origrepolen'] < len(self):
2927 if tr is None or tr.changes[b'origrepolen'] < len(self):
2926 # accessing the 'served' branchmap should refresh all the others,
2928 # accessing the 'served' branchmap should refresh all the others,
2927 self.ui.debug(b'updating the branch cache\n')
2929 self.ui.debug(b'updating the branch cache\n')
2928 self.filtered(b'served').branchmap()
2930 self.filtered(b'served').branchmap()
2929 self.filtered(b'served.hidden').branchmap()
2931 self.filtered(b'served.hidden').branchmap()
2930 # flush all possibly delayed write.
2932 # flush all possibly delayed write.
2931 self._branchcaches.write_delayed(self)
2933 self._branchcaches.write_delayed(self)
2932
2934
2933 if repository.CACHE_CHANGELOG_CACHE in caches:
2935 if repository.CACHE_CHANGELOG_CACHE in caches:
2934 self.changelog.update_caches(transaction=tr)
2936 self.changelog.update_caches(transaction=tr)
2935
2937
2936 if repository.CACHE_MANIFESTLOG_CACHE in caches:
2938 if repository.CACHE_MANIFESTLOG_CACHE in caches:
2937 self.manifestlog.update_caches(transaction=tr)
2939 self.manifestlog.update_caches(transaction=tr)
2938 for entry in self.store.walk():
2940 for entry in self.store.walk():
2939 if not entry.is_revlog:
2941 if not entry.is_revlog:
2940 continue
2942 continue
2941 if not entry.is_manifestlog:
2943 if not entry.is_manifestlog:
2942 continue
2944 continue
2943 manifestrevlog = entry.get_revlog_instance(self).get_revlog()
2945 manifestrevlog = entry.get_revlog_instance(self).get_revlog()
2944 if manifestrevlog is not None:
2946 if manifestrevlog is not None:
2945 manifestrevlog.update_caches(transaction=tr)
2947 manifestrevlog.update_caches(transaction=tr)
2946
2948
2947 if repository.CACHE_REV_BRANCH in caches:
2949 if repository.CACHE_REV_BRANCH in caches:
2948 rbc = unfi.revbranchcache()
2950 rbc = unfi.revbranchcache()
2949 for r in unfi.changelog:
2951 for r in unfi.changelog:
2950 rbc.branchinfo(r)
2952 rbc.branchinfo(r)
2951 rbc.write()
2953 rbc.write()
2952
2954
2953 if repository.CACHE_FULL_MANIFEST in caches:
2955 if repository.CACHE_FULL_MANIFEST in caches:
2954 # ensure the working copy parents are in the manifestfulltextcache
2956 # ensure the working copy parents are in the manifestfulltextcache
2955 for ctx in self[b'.'].parents():
2957 for ctx in self[b'.'].parents():
2956 ctx.manifest() # accessing the manifest is enough
2958 ctx.manifest() # accessing the manifest is enough
2957
2959
2958 if repository.CACHE_FILE_NODE_TAGS in caches:
2960 if repository.CACHE_FILE_NODE_TAGS in caches:
2959 # accessing fnode cache warms the cache
2961 # accessing fnode cache warms the cache
2960 tagsmod.fnoderevs(self.ui, unfi, unfi.changelog.revs())
2962 tagsmod.fnoderevs(self.ui, unfi, unfi.changelog.revs())
2961
2963
2962 if repository.CACHE_TAGS_DEFAULT in caches:
2964 if repository.CACHE_TAGS_DEFAULT in caches:
2963 # accessing tags warm the cache
2965 # accessing tags warm the cache
2964 self.tags()
2966 self.tags()
2965 if repository.CACHE_TAGS_SERVED in caches:
2967 if repository.CACHE_TAGS_SERVED in caches:
2966 self.filtered(b'served').tags()
2968 self.filtered(b'served').tags()
2967
2969
2968 if repository.CACHE_BRANCHMAP_ALL in caches:
2970 if repository.CACHE_BRANCHMAP_ALL in caches:
2969 # The CACHE_BRANCHMAP_ALL updates lazily-loaded caches immediately,
2971 # The CACHE_BRANCHMAP_ALL updates lazily-loaded caches immediately,
2970 # so we're forcing a write to cause these caches to be warmed up
2972 # 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
2973 # 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
2974 # 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).
2975 # they're a subset of another kind of cache that *has* been used).
2974 for filt in repoview.filtertable.keys():
2976 for filt in repoview.filtertable.keys():
2975 filtered = self.filtered(filt)
2977 filtered = self.filtered(filt)
2976 filtered.branchmap().write(filtered)
2978 filtered.branchmap().write(filtered)
2977
2979
2978 def invalidatecaches(self):
2980 def invalidatecaches(self):
2979 if '_tagscache' in vars(self):
2981 if '_tagscache' in vars(self):
2980 # can't use delattr on proxy
2982 # can't use delattr on proxy
2981 del self.__dict__['_tagscache']
2983 del self.__dict__['_tagscache']
2982
2984
2983 self._branchcaches.clear()
2985 self._branchcaches.clear()
2984 self.invalidatevolatilesets()
2986 self.invalidatevolatilesets()
2985 self._sparsesignaturecache.clear()
2987 self._sparsesignaturecache.clear()
2986
2988
2987 def invalidatevolatilesets(self):
2989 def invalidatevolatilesets(self):
2988 self.filteredrevcache.clear()
2990 self.filteredrevcache.clear()
2989 obsolete.clearobscaches(self)
2991 obsolete.clearobscaches(self)
2990 self._quick_access_changeid_invalidate()
2992 self._quick_access_changeid_invalidate()
2991
2993
2992 def invalidatedirstate(self):
2994 def invalidatedirstate(self):
2993 """Invalidates the dirstate, causing the next call to dirstate
2995 """Invalidates the dirstate, causing the next call to dirstate
2994 to check if it was modified since the last time it was read,
2996 to check if it was modified since the last time it was read,
2995 rereading it if it has.
2997 rereading it if it has.
2996
2998
2997 This is different to dirstate.invalidate() that it doesn't always
2999 This is different to dirstate.invalidate() that it doesn't always
2998 rereads the dirstate. Use dirstate.invalidate() if you want to
3000 rereads the dirstate. Use dirstate.invalidate() if you want to
2999 explicitly read the dirstate again (i.e. restoring it to a previous
3001 explicitly read the dirstate again (i.e. restoring it to a previous
3000 known good state)."""
3002 known good state)."""
3001 unfi = self.unfiltered()
3003 unfi = self.unfiltered()
3002 if 'dirstate' in unfi.__dict__:
3004 if 'dirstate' in unfi.__dict__:
3003 assert not self.dirstate.is_changing_any
3005 assert not self.dirstate.is_changing_any
3004 del unfi.__dict__['dirstate']
3006 del unfi.__dict__['dirstate']
3005
3007
3006 def invalidate(self, clearfilecache=False):
3008 def invalidate(self, clearfilecache=False):
3007 """Invalidates both store and non-store parts other than dirstate
3009 """Invalidates both store and non-store parts other than dirstate
3008
3010
3009 If a transaction is running, invalidation of store is omitted,
3011 If a transaction is running, invalidation of store is omitted,
3010 because discarding in-memory changes might cause inconsistency
3012 because discarding in-memory changes might cause inconsistency
3011 (e.g. incomplete fncache causes unintentional failure, but
3013 (e.g. incomplete fncache causes unintentional failure, but
3012 redundant one doesn't).
3014 redundant one doesn't).
3013 """
3015 """
3014 unfiltered = self.unfiltered() # all file caches are stored unfiltered
3016 unfiltered = self.unfiltered() # all file caches are stored unfiltered
3015 for k in list(self._filecache.keys()):
3017 for k in list(self._filecache.keys()):
3016 if (
3018 if (
3017 k == b'changelog'
3019 k == b'changelog'
3018 and self.currenttransaction()
3020 and self.currenttransaction()
3019 and self.changelog._delayed
3021 and self.changelog._delayed
3020 ):
3022 ):
3021 # The changelog object may store unwritten revisions. We don't
3023 # The changelog object may store unwritten revisions. We don't
3022 # want to lose them.
3024 # want to lose them.
3023 # TODO: Solve the problem instead of working around it.
3025 # TODO: Solve the problem instead of working around it.
3024 continue
3026 continue
3025
3027
3026 if clearfilecache:
3028 if clearfilecache:
3027 del self._filecache[k]
3029 del self._filecache[k]
3028 try:
3030 try:
3029 # XXX ideally, the key would be a unicode string to match the
3031 # XXX ideally, the key would be a unicode string to match the
3030 # fact it refers to an attribut name. However changing this was
3032 # fact it refers to an attribut name. However changing this was
3031 # a bit a scope creep compared to the series cleaning up
3033 # a bit a scope creep compared to the series cleaning up
3032 # del/set/getattr so we kept thing simple here.
3034 # del/set/getattr so we kept thing simple here.
3033 delattr(unfiltered, pycompat.sysstr(k))
3035 delattr(unfiltered, pycompat.sysstr(k))
3034 except AttributeError:
3036 except AttributeError:
3035 pass
3037 pass
3036 self.invalidatecaches()
3038 self.invalidatecaches()
3037 if not self.currenttransaction():
3039 if not self.currenttransaction():
3038 # TODO: Changing contents of store outside transaction
3040 # TODO: Changing contents of store outside transaction
3039 # causes inconsistency. We should make in-memory store
3041 # causes inconsistency. We should make in-memory store
3040 # changes detectable, and abort if changed.
3042 # changes detectable, and abort if changed.
3041 self.store.invalidatecaches()
3043 self.store.invalidatecaches()
3042
3044
3043 def invalidateall(self):
3045 def invalidateall(self):
3044 """Fully invalidates both store and non-store parts, causing the
3046 """Fully invalidates both store and non-store parts, causing the
3045 subsequent operation to reread any outside changes."""
3047 subsequent operation to reread any outside changes."""
3046 # extension should hook this to invalidate its caches
3048 # extension should hook this to invalidate its caches
3047 self.invalidate()
3049 self.invalidate()
3048 self.invalidatedirstate()
3050 self.invalidatedirstate()
3049
3051
3050 @unfilteredmethod
3052 @unfilteredmethod
3051 def _refreshfilecachestats(self, tr):
3053 def _refreshfilecachestats(self, tr):
3052 """Reload stats of cached files so that they are flagged as valid"""
3054 """Reload stats of cached files so that they are flagged as valid"""
3053 for k, ce in self._filecache.items():
3055 for k, ce in self._filecache.items():
3054 k = pycompat.sysstr(k)
3056 k = pycompat.sysstr(k)
3055 if k == 'dirstate' or k not in self.__dict__:
3057 if k == 'dirstate' or k not in self.__dict__:
3056 continue
3058 continue
3057 ce.refresh()
3059 ce.refresh()
3058
3060
3059 def _lock(
3061 def _lock(
3060 self,
3062 self,
3061 vfs,
3063 vfs,
3062 lockname,
3064 lockname,
3063 wait,
3065 wait,
3064 releasefn,
3066 releasefn,
3065 acquirefn,
3067 acquirefn,
3066 desc,
3068 desc,
3067 ):
3069 ):
3068 timeout = 0
3070 timeout = 0
3069 warntimeout = 0
3071 warntimeout = 0
3070 if wait:
3072 if wait:
3071 timeout = self.ui.configint(b"ui", b"timeout")
3073 timeout = self.ui.configint(b"ui", b"timeout")
3072 warntimeout = self.ui.configint(b"ui", b"timeout.warn")
3074 warntimeout = self.ui.configint(b"ui", b"timeout.warn")
3073 # internal config: ui.signal-safe-lock
3075 # internal config: ui.signal-safe-lock
3074 signalsafe = self.ui.configbool(b'ui', b'signal-safe-lock')
3076 signalsafe = self.ui.configbool(b'ui', b'signal-safe-lock')
3075
3077
3076 l = lockmod.trylock(
3078 l = lockmod.trylock(
3077 self.ui,
3079 self.ui,
3078 vfs,
3080 vfs,
3079 lockname,
3081 lockname,
3080 timeout,
3082 timeout,
3081 warntimeout,
3083 warntimeout,
3082 releasefn=releasefn,
3084 releasefn=releasefn,
3083 acquirefn=acquirefn,
3085 acquirefn=acquirefn,
3084 desc=desc,
3086 desc=desc,
3085 signalsafe=signalsafe,
3087 signalsafe=signalsafe,
3086 )
3088 )
3087 return l
3089 return l
3088
3090
3089 def _afterlock(self, callback):
3091 def _afterlock(self, callback):
3090 """add a callback to be run when the repository is fully unlocked
3092 """add a callback to be run when the repository is fully unlocked
3091
3093
3092 The callback will be executed when the outermost lock is released
3094 The callback will be executed when the outermost lock is released
3093 (with wlock being higher level than 'lock')."""
3095 (with wlock being higher level than 'lock')."""
3094 for ref in (self._wlockref, self._lockref):
3096 for ref in (self._wlockref, self._lockref):
3095 l = ref and ref()
3097 l = ref and ref()
3096 if l and l.held:
3098 if l and l.held:
3097 l.postrelease.append(callback)
3099 l.postrelease.append(callback)
3098 break
3100 break
3099 else: # no lock have been found.
3101 else: # no lock have been found.
3100 callback(True)
3102 callback(True)
3101
3103
3102 def lock(self, wait=True):
3104 def lock(self, wait=True):
3103 """Lock the repository store (.hg/store) and return a weak reference
3105 """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
3106 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.)
3107 stripping). If you are opening a transaction, get a lock as well.)
3106
3108
3107 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
3109 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
3108 'wlock' first to avoid a dead-lock hazard."""
3110 'wlock' first to avoid a dead-lock hazard."""
3109 l = self._currentlock(self._lockref)
3111 l = self._currentlock(self._lockref)
3110 if l is not None:
3112 if l is not None:
3111 l.lock()
3113 l.lock()
3112 return l
3114 return l
3113
3115
3114 l = self._lock(
3116 l = self._lock(
3115 vfs=self.svfs,
3117 vfs=self.svfs,
3116 lockname=b"lock",
3118 lockname=b"lock",
3117 wait=wait,
3119 wait=wait,
3118 releasefn=None,
3120 releasefn=None,
3119 acquirefn=self.invalidate,
3121 acquirefn=self.invalidate,
3120 desc=_(b'repository %s') % self.origroot,
3122 desc=_(b'repository %s') % self.origroot,
3121 )
3123 )
3122 self._lockref = weakref.ref(l)
3124 self._lockref = weakref.ref(l)
3123 return l
3125 return l
3124
3126
3125 def wlock(self, wait=True):
3127 def wlock(self, wait=True):
3126 """Lock the non-store parts of the repository (everything under
3128 """Lock the non-store parts of the repository (everything under
3127 .hg except .hg/store) and return a weak reference to the lock.
3129 .hg except .hg/store) and return a weak reference to the lock.
3128
3130
3129 Use this before modifying files in .hg.
3131 Use this before modifying files in .hg.
3130
3132
3131 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
3133 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
3132 'wlock' first to avoid a dead-lock hazard."""
3134 'wlock' first to avoid a dead-lock hazard."""
3133 l = self._wlockref() if self._wlockref else None
3135 l = self._wlockref() if self._wlockref else None
3134 if l is not None and l.held:
3136 if l is not None and l.held:
3135 l.lock()
3137 l.lock()
3136 return l
3138 return l
3137
3139
3138 # We do not need to check for non-waiting lock acquisition. Such
3140 # We do not need to check for non-waiting lock acquisition. Such
3139 # acquisition would not cause dead-lock as they would just fail.
3141 # acquisition would not cause dead-lock as they would just fail.
3140 if wait and (
3142 if wait and (
3141 self.ui.configbool(b'devel', b'all-warnings')
3143 self.ui.configbool(b'devel', b'all-warnings')
3142 or self.ui.configbool(b'devel', b'check-locks')
3144 or self.ui.configbool(b'devel', b'check-locks')
3143 ):
3145 ):
3144 if self._currentlock(self._lockref) is not None:
3146 if self._currentlock(self._lockref) is not None:
3145 self.ui.develwarn(b'"wlock" acquired after "lock"')
3147 self.ui.develwarn(b'"wlock" acquired after "lock"')
3146
3148
3147 def unlock():
3149 def unlock():
3148 if self.dirstate.is_changing_any:
3150 if self.dirstate.is_changing_any:
3149 msg = b"wlock release in the middle of a changing parents"
3151 msg = b"wlock release in the middle of a changing parents"
3150 self.ui.develwarn(msg)
3152 self.ui.develwarn(msg)
3151 self.dirstate.invalidate()
3153 self.dirstate.invalidate()
3152 else:
3154 else:
3153 if self.dirstate._dirty:
3155 if self.dirstate._dirty:
3154 msg = b"dirty dirstate on wlock release"
3156 msg = b"dirty dirstate on wlock release"
3155 self.ui.develwarn(msg)
3157 self.ui.develwarn(msg)
3156 self.dirstate.write(None)
3158 self.dirstate.write(None)
3157
3159
3158 unfi = self.unfiltered()
3160 unfi = self.unfiltered()
3159 if 'dirstate' in unfi.__dict__:
3161 if 'dirstate' in unfi.__dict__:
3160 del unfi.__dict__['dirstate']
3162 del unfi.__dict__['dirstate']
3161
3163
3162 l = self._lock(
3164 l = self._lock(
3163 self.vfs,
3165 self.vfs,
3164 b"wlock",
3166 b"wlock",
3165 wait,
3167 wait,
3166 unlock,
3168 unlock,
3167 self.invalidatedirstate,
3169 self.invalidatedirstate,
3168 _(b'working directory of %s') % self.origroot,
3170 _(b'working directory of %s') % self.origroot,
3169 )
3171 )
3170 self._wlockref = weakref.ref(l)
3172 self._wlockref = weakref.ref(l)
3171 return l
3173 return l
3172
3174
3173 def _currentlock(self, lockref):
3175 def _currentlock(self, lockref):
3174 """Returns the lock if it's held, or None if it's not."""
3176 """Returns the lock if it's held, or None if it's not."""
3175 if lockref is None:
3177 if lockref is None:
3176 return None
3178 return None
3177 l = lockref()
3179 l = lockref()
3178 if l is None or not l.held:
3180 if l is None or not l.held:
3179 return None
3181 return None
3180 return l
3182 return l
3181
3183
3182 def currentwlock(self):
3184 def currentwlock(self):
3183 """Returns the wlock if it's held, or None if it's not."""
3185 """Returns the wlock if it's held, or None if it's not."""
3184 return self._currentlock(self._wlockref)
3186 return self._currentlock(self._wlockref)
3185
3187
3186 def currentlock(self):
3188 def currentlock(self):
3187 """Returns the lock if it's held, or None if it's not."""
3189 """Returns the lock if it's held, or None if it's not."""
3188 return self._currentlock(self._lockref)
3190 return self._currentlock(self._lockref)
3189
3191
3190 def checkcommitpatterns(self, wctx, match, status, fail):
3192 def checkcommitpatterns(self, wctx, match, status, fail):
3191 """check for commit arguments that aren't committable"""
3193 """check for commit arguments that aren't committable"""
3192 if match.isexact() or match.prefix():
3194 if match.isexact() or match.prefix():
3193 matched = set(status.modified + status.added + status.removed)
3195 matched = set(status.modified + status.added + status.removed)
3194
3196
3195 for f in match.files():
3197 for f in match.files():
3196 f = self.dirstate.normalize(f)
3198 f = self.dirstate.normalize(f)
3197 if f == b'.' or f in matched or f in wctx.substate:
3199 if f == b'.' or f in matched or f in wctx.substate:
3198 continue
3200 continue
3199 if f in status.deleted:
3201 if f in status.deleted:
3200 fail(f, _(b'file not found!'))
3202 fail(f, _(b'file not found!'))
3201 # Is it a directory that exists or used to exist?
3203 # Is it a directory that exists or used to exist?
3202 if self.wvfs.isdir(f) or wctx.p1().hasdir(f):
3204 if self.wvfs.isdir(f) or wctx.p1().hasdir(f):
3203 d = f + b'/'
3205 d = f + b'/'
3204 for mf in matched:
3206 for mf in matched:
3205 if mf.startswith(d):
3207 if mf.startswith(d):
3206 break
3208 break
3207 else:
3209 else:
3208 fail(f, _(b"no match under directory!"))
3210 fail(f, _(b"no match under directory!"))
3209 elif f not in self.dirstate:
3211 elif f not in self.dirstate:
3210 fail(f, _(b"file not tracked!"))
3212 fail(f, _(b"file not tracked!"))
3211
3213
3212 @unfilteredmethod
3214 @unfilteredmethod
3213 def commit(
3215 def commit(
3214 self,
3216 self,
3215 text=b"",
3217 text=b"",
3216 user=None,
3218 user=None,
3217 date=None,
3219 date=None,
3218 match=None,
3220 match=None,
3219 force=False,
3221 force=False,
3220 editor=None,
3222 editor=None,
3221 extra=None,
3223 extra=None,
3222 ):
3224 ):
3223 """Add a new revision to current repository.
3225 """Add a new revision to current repository.
3224
3226
3225 Revision information is gathered from the working directory,
3227 Revision information is gathered from the working directory,
3226 match can be used to filter the committed files. If editor is
3228 match can be used to filter the committed files. If editor is
3227 supplied, it is called to get a commit message.
3229 supplied, it is called to get a commit message.
3228 """
3230 """
3229 if extra is None:
3231 if extra is None:
3230 extra = {}
3232 extra = {}
3231
3233
3232 def fail(f, msg):
3234 def fail(f, msg):
3233 raise error.InputError(b'%s: %s' % (f, msg))
3235 raise error.InputError(b'%s: %s' % (f, msg))
3234
3236
3235 if not match:
3237 if not match:
3236 match = matchmod.always()
3238 match = matchmod.always()
3237
3239
3238 if not force:
3240 if not force:
3239 match.bad = fail
3241 match.bad = fail
3240
3242
3241 # lock() for recent changelog (see issue4368)
3243 # lock() for recent changelog (see issue4368)
3242 with self.wlock(), self.lock():
3244 with self.wlock(), self.lock():
3243 wctx = self[None]
3245 wctx = self[None]
3244 merge = len(wctx.parents()) > 1
3246 merge = len(wctx.parents()) > 1
3245
3247
3246 if not force and merge and not match.always():
3248 if not force and merge and not match.always():
3247 raise error.Abort(
3249 raise error.Abort(
3248 _(
3250 _(
3249 b'cannot partially commit a merge '
3251 b'cannot partially commit a merge '
3250 b'(do not specify files or patterns)'
3252 b'(do not specify files or patterns)'
3251 )
3253 )
3252 )
3254 )
3253
3255
3254 status = self.status(match=match, clean=force)
3256 status = self.status(match=match, clean=force)
3255 if force:
3257 if force:
3256 status.modified.extend(
3258 status.modified.extend(
3257 status.clean
3259 status.clean
3258 ) # mq may commit clean files
3260 ) # mq may commit clean files
3259
3261
3260 # check subrepos
3262 # check subrepos
3261 subs, commitsubs, newstate = subrepoutil.precommit(
3263 subs, commitsubs, newstate = subrepoutil.precommit(
3262 self.ui, wctx, status, match, force=force
3264 self.ui, wctx, status, match, force=force
3263 )
3265 )
3264
3266
3265 # make sure all explicit patterns are matched
3267 # make sure all explicit patterns are matched
3266 if not force:
3268 if not force:
3267 self.checkcommitpatterns(wctx, match, status, fail)
3269 self.checkcommitpatterns(wctx, match, status, fail)
3268
3270
3269 cctx = context.workingcommitctx(
3271 cctx = context.workingcommitctx(
3270 self, status, text, user, date, extra
3272 self, status, text, user, date, extra
3271 )
3273 )
3272
3274
3273 ms = mergestatemod.mergestate.read(self)
3275 ms = mergestatemod.mergestate.read(self)
3274 mergeutil.checkunresolved(ms)
3276 mergeutil.checkunresolved(ms)
3275
3277
3276 # internal config: ui.allowemptycommit
3278 # internal config: ui.allowemptycommit
3277 if cctx.isempty() and not self.ui.configbool(
3279 if cctx.isempty() and not self.ui.configbool(
3278 b'ui', b'allowemptycommit'
3280 b'ui', b'allowemptycommit'
3279 ):
3281 ):
3280 self.ui.debug(b'nothing to commit, clearing merge state\n')
3282 self.ui.debug(b'nothing to commit, clearing merge state\n')
3281 ms.reset()
3283 ms.reset()
3282 return None
3284 return None
3283
3285
3284 if merge and cctx.deleted():
3286 if merge and cctx.deleted():
3285 raise error.Abort(_(b"cannot commit merge with missing files"))
3287 raise error.Abort(_(b"cannot commit merge with missing files"))
3286
3288
3287 if editor:
3289 if editor:
3288 cctx._text = editor(self, cctx, subs)
3290 cctx._text = editor(self, cctx, subs)
3289 edited = text != cctx._text
3291 edited = text != cctx._text
3290
3292
3291 # Save commit message in case this transaction gets rolled back
3293 # Save commit message in case this transaction gets rolled back
3292 # (e.g. by a pretxncommit hook). Leave the content alone on
3294 # (e.g. by a pretxncommit hook). Leave the content alone on
3293 # the assumption that the user will use the same editor again.
3295 # the assumption that the user will use the same editor again.
3294 msg_path = self.savecommitmessage(cctx._text)
3296 msg_path = self.savecommitmessage(cctx._text)
3295
3297
3296 # commit subs and write new state
3298 # commit subs and write new state
3297 if subs:
3299 if subs:
3298 uipathfn = scmutil.getuipathfn(self)
3300 uipathfn = scmutil.getuipathfn(self)
3299 for s in sorted(commitsubs):
3301 for s in sorted(commitsubs):
3300 sub = wctx.sub(s)
3302 sub = wctx.sub(s)
3301 self.ui.status(
3303 self.ui.status(
3302 _(b'committing subrepository %s\n')
3304 _(b'committing subrepository %s\n')
3303 % uipathfn(subrepoutil.subrelpath(sub))
3305 % uipathfn(subrepoutil.subrelpath(sub))
3304 )
3306 )
3305 sr = sub.commit(cctx._text, user, date)
3307 sr = sub.commit(cctx._text, user, date)
3306 newstate[s] = (newstate[s][0], sr)
3308 newstate[s] = (newstate[s][0], sr)
3307 subrepoutil.writestate(self, newstate)
3309 subrepoutil.writestate(self, newstate)
3308
3310
3309 p1, p2 = self.dirstate.parents()
3311 p1, p2 = self.dirstate.parents()
3310 hookp1, hookp2 = hex(p1), (p2 != self.nullid and hex(p2) or b'')
3312 hookp1, hookp2 = hex(p1), (p2 != self.nullid and hex(p2) or b'')
3311 try:
3313 try:
3312 self.hook(
3314 self.hook(
3313 b"precommit", throw=True, parent1=hookp1, parent2=hookp2
3315 b"precommit", throw=True, parent1=hookp1, parent2=hookp2
3314 )
3316 )
3315 with self.transaction(b'commit'):
3317 with self.transaction(b'commit'):
3316 ret = self.commitctx(cctx, True)
3318 ret = self.commitctx(cctx, True)
3317 # update bookmarks, dirstate and mergestate
3319 # update bookmarks, dirstate and mergestate
3318 bookmarks.update(self, [p1, p2], ret)
3320 bookmarks.update(self, [p1, p2], ret)
3319 cctx.markcommitted(ret)
3321 cctx.markcommitted(ret)
3320 ms.reset()
3322 ms.reset()
3321 except: # re-raises
3323 except: # re-raises
3322 if edited:
3324 if edited:
3323 self.ui.write(
3325 self.ui.write(
3324 _(b'note: commit message saved in %s\n') % msg_path
3326 _(b'note: commit message saved in %s\n') % msg_path
3325 )
3327 )
3326 self.ui.write(
3328 self.ui.write(
3327 _(
3329 _(
3328 b"note: use 'hg commit --logfile "
3330 b"note: use 'hg commit --logfile "
3329 b"%s --edit' to reuse it\n"
3331 b"%s --edit' to reuse it\n"
3330 )
3332 )
3331 % msg_path
3333 % msg_path
3332 )
3334 )
3333 raise
3335 raise
3334
3336
3335 def commithook(unused_success):
3337 def commithook(unused_success):
3336 # hack for command that use a temporary commit (eg: histedit)
3338 # hack for command that use a temporary commit (eg: histedit)
3337 # temporary commit got stripped before hook release
3339 # temporary commit got stripped before hook release
3338 if self.changelog.hasnode(ret):
3340 if self.changelog.hasnode(ret):
3339 self.hook(
3341 self.hook(
3340 b"commit", node=hex(ret), parent1=hookp1, parent2=hookp2
3342 b"commit", node=hex(ret), parent1=hookp1, parent2=hookp2
3341 )
3343 )
3342
3344
3343 self._afterlock(commithook)
3345 self._afterlock(commithook)
3344 return ret
3346 return ret
3345
3347
3346 @unfilteredmethod
3348 @unfilteredmethod
3347 def commitctx(self, ctx, error=False, origctx=None):
3349 def commitctx(self, ctx, error=False, origctx=None):
3348 return commit.commitctx(self, ctx, error=error, origctx=origctx)
3350 return commit.commitctx(self, ctx, error=error, origctx=origctx)
3349
3351
3350 @unfilteredmethod
3352 @unfilteredmethod
3351 def destroying(self):
3353 def destroying(self):
3352 """Inform the repository that nodes are about to be destroyed.
3354 """Inform the repository that nodes are about to be destroyed.
3353 Intended for use by strip and rollback, so there's a common
3355 Intended for use by strip and rollback, so there's a common
3354 place for anything that has to be done before destroying history.
3356 place for anything that has to be done before destroying history.
3355
3357
3356 This is mostly useful for saving state that is in memory and waiting
3358 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
3359 to be flushed when the current lock is released. Because a call to
3358 destroyed is imminent, the repo will be invalidated causing those
3360 destroyed is imminent, the repo will be invalidated causing those
3359 changes to stay in memory (waiting for the next unlock), or vanish
3361 changes to stay in memory (waiting for the next unlock), or vanish
3360 completely.
3362 completely.
3361 """
3363 """
3362 # When using the same lock to commit and strip, the phasecache is left
3364 # 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,
3365 # dirty after committing. Then when we strip, the repo is invalidated,
3364 # causing those changes to disappear.
3366 # causing those changes to disappear.
3365 if '_phasecache' in vars(self):
3367 if '_phasecache' in vars(self):
3366 self._phasecache.write()
3368 self._phasecache.write()
3367
3369
3368 @unfilteredmethod
3370 @unfilteredmethod
3369 def destroyed(self):
3371 def destroyed(self):
3370 """Inform the repository that nodes have been destroyed.
3372 """Inform the repository that nodes have been destroyed.
3371 Intended for use by strip and rollback, so there's a common
3373 Intended for use by strip and rollback, so there's a common
3372 place for anything that has to be done after destroying history.
3374 place for anything that has to be done after destroying history.
3373 """
3375 """
3374 # When one tries to:
3376 # When one tries to:
3375 # 1) destroy nodes thus calling this method (e.g. strip)
3377 # 1) destroy nodes thus calling this method (e.g. strip)
3376 # 2) use phasecache somewhere (e.g. commit)
3378 # 2) use phasecache somewhere (e.g. commit)
3377 #
3379 #
3378 # then 2) will fail because the phasecache contains nodes that were
3380 # then 2) will fail because the phasecache contains nodes that were
3379 # removed. We can either remove phasecache from the filecache,
3381 # removed. We can either remove phasecache from the filecache,
3380 # causing it to reload next time it is accessed, or simply filter
3382 # causing it to reload next time it is accessed, or simply filter
3381 # the removed nodes now and write the updated cache.
3383 # the removed nodes now and write the updated cache.
3382 self._phasecache.filterunknown(self)
3384 self._phasecache.filterunknown(self)
3383 self._phasecache.write()
3385 self._phasecache.write()
3384
3386
3385 # refresh all repository caches
3387 # refresh all repository caches
3386 self.updatecaches()
3388 self.updatecaches()
3387
3389
3388 # Ensure the persistent tag cache is updated. Doing it now
3390 # Ensure the persistent tag cache is updated. Doing it now
3389 # means that the tag cache only has to worry about destroyed
3391 # means that the tag cache only has to worry about destroyed
3390 # heads immediately after a strip/rollback. That in turn
3392 # heads immediately after a strip/rollback. That in turn
3391 # guarantees that "cachetip == currenttip" (comparing both rev
3393 # guarantees that "cachetip == currenttip" (comparing both rev
3392 # and node) always means no nodes have been added or destroyed.
3394 # and node) always means no nodes have been added or destroyed.
3393
3395
3394 # XXX this is suboptimal when qrefresh'ing: we strip the current
3396 # XXX this is suboptimal when qrefresh'ing: we strip the current
3395 # head, refresh the tag cache, then immediately add a new head.
3397 # head, refresh the tag cache, then immediately add a new head.
3396 # But I think doing it this way is necessary for the "instant
3398 # But I think doing it this way is necessary for the "instant
3397 # tag cache retrieval" case to work.
3399 # tag cache retrieval" case to work.
3398 self.invalidate()
3400 self.invalidate()
3399
3401
3400 def status(
3402 def status(
3401 self,
3403 self,
3402 node1=b'.',
3404 node1=b'.',
3403 node2=None,
3405 node2=None,
3404 match=None,
3406 match=None,
3405 ignored=False,
3407 ignored=False,
3406 clean=False,
3408 clean=False,
3407 unknown=False,
3409 unknown=False,
3408 listsubrepos=False,
3410 listsubrepos=False,
3409 ):
3411 ):
3410 '''a convenience method that calls node1.status(node2)'''
3412 '''a convenience method that calls node1.status(node2)'''
3411 return self[node1].status(
3413 return self[node1].status(
3412 node2, match, ignored, clean, unknown, listsubrepos
3414 node2, match, ignored, clean, unknown, listsubrepos
3413 )
3415 )
3414
3416
3415 def addpostdsstatus(self, ps):
3417 def addpostdsstatus(self, ps):
3416 """Add a callback to run within the wlock, at the point at which status
3418 """Add a callback to run within the wlock, at the point at which status
3417 fixups happen.
3419 fixups happen.
3418
3420
3419 On status completion, callback(wctx, status) will be called with the
3421 On status completion, callback(wctx, status) will be called with the
3420 wlock held, unless the dirstate has changed from underneath or the wlock
3422 wlock held, unless the dirstate has changed from underneath or the wlock
3421 couldn't be grabbed.
3423 couldn't be grabbed.
3422
3424
3423 Callbacks should not capture and use a cached copy of the dirstate --
3425 Callbacks should not capture and use a cached copy of the dirstate --
3424 it might change in the meanwhile. Instead, they should access the
3426 it might change in the meanwhile. Instead, they should access the
3425 dirstate via wctx.repo().dirstate.
3427 dirstate via wctx.repo().dirstate.
3426
3428
3427 This list is emptied out after each status run -- extensions should
3429 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.
3430 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
3431 Extensions should also make sure they don't call this for statuses
3430 that don't involve the dirstate.
3432 that don't involve the dirstate.
3431 """
3433 """
3432
3434
3433 # The list is located here for uniqueness reasons -- it is actually
3435 # The list is located here for uniqueness reasons -- it is actually
3434 # managed by the workingctx, but that isn't unique per-repo.
3436 # managed by the workingctx, but that isn't unique per-repo.
3435 self._postdsstatus.append(ps)
3437 self._postdsstatus.append(ps)
3436
3438
3437 def postdsstatus(self):
3439 def postdsstatus(self):
3438 """Used by workingctx to get the list of post-dirstate-status hooks."""
3440 """Used by workingctx to get the list of post-dirstate-status hooks."""
3439 return self._postdsstatus
3441 return self._postdsstatus
3440
3442
3441 def clearpostdsstatus(self):
3443 def clearpostdsstatus(self):
3442 """Used by workingctx to clear post-dirstate-status hooks."""
3444 """Used by workingctx to clear post-dirstate-status hooks."""
3443 del self._postdsstatus[:]
3445 del self._postdsstatus[:]
3444
3446
3445 def heads(self, start=None):
3447 def heads(self, start=None):
3446 if start is None:
3448 if start is None:
3447 cl = self.changelog
3449 cl = self.changelog
3448 headrevs = reversed(cl.headrevs())
3450 headrevs = reversed(cl.headrevs())
3449 return [cl.node(rev) for rev in headrevs]
3451 return [cl.node(rev) for rev in headrevs]
3450
3452
3451 heads = self.changelog.heads(start)
3453 heads = self.changelog.heads(start)
3452 # sort the output in rev descending order
3454 # sort the output in rev descending order
3453 return sorted(heads, key=self.changelog.rev, reverse=True)
3455 return sorted(heads, key=self.changelog.rev, reverse=True)
3454
3456
3455 def branchheads(self, branch=None, start=None, closed=False):
3457 def branchheads(self, branch=None, start=None, closed=False):
3456 """return a (possibly filtered) list of heads for the given branch
3458 """return a (possibly filtered) list of heads for the given branch
3457
3459
3458 Heads are returned in topological order, from newest to oldest.
3460 Heads are returned in topological order, from newest to oldest.
3459 If branch is None, use the dirstate branch.
3461 If branch is None, use the dirstate branch.
3460 If start is not None, return only heads reachable from start.
3462 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.
3463 If closed is True, return heads that are marked as closed as well.
3462 """
3464 """
3463 if branch is None:
3465 if branch is None:
3464 branch = self[None].branch()
3466 branch = self[None].branch()
3465 branches = self.branchmap()
3467 branches = self.branchmap()
3466 if not branches.hasbranch(branch):
3468 if not branches.hasbranch(branch):
3467 return []
3469 return []
3468 # the cache returns heads ordered lowest to highest
3470 # the cache returns heads ordered lowest to highest
3469 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
3471 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
3470 if start is not None:
3472 if start is not None:
3471 # filter out the heads that cannot be reached from startrev
3473 # filter out the heads that cannot be reached from startrev
3472 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
3474 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
3473 bheads = [h for h in bheads if h in fbheads]
3475 bheads = [h for h in bheads if h in fbheads]
3474 return bheads
3476 return bheads
3475
3477
3476 def branches(self, nodes):
3478 def branches(self, nodes):
3477 if not nodes:
3479 if not nodes:
3478 nodes = [self.changelog.tip()]
3480 nodes = [self.changelog.tip()]
3479 b = []
3481 b = []
3480 for n in nodes:
3482 for n in nodes:
3481 t = n
3483 t = n
3482 while True:
3484 while True:
3483 p = self.changelog.parents(n)
3485 p = self.changelog.parents(n)
3484 if p[1] != self.nullid or p[0] == self.nullid:
3486 if p[1] != self.nullid or p[0] == self.nullid:
3485 b.append((t, n, p[0], p[1]))
3487 b.append((t, n, p[0], p[1]))
3486 break
3488 break
3487 n = p[0]
3489 n = p[0]
3488 return b
3490 return b
3489
3491
3490 def between(self, pairs):
3492 def between(self, pairs):
3491 r = []
3493 r = []
3492
3494
3493 for top, bottom in pairs:
3495 for top, bottom in pairs:
3494 n, l, i = top, [], 0
3496 n, l, i = top, [], 0
3495 f = 1
3497 f = 1
3496
3498
3497 while n != bottom and n != self.nullid:
3499 while n != bottom and n != self.nullid:
3498 p = self.changelog.parents(n)[0]
3500 p = self.changelog.parents(n)[0]
3499 if i == f:
3501 if i == f:
3500 l.append(n)
3502 l.append(n)
3501 f = f * 2
3503 f = f * 2
3502 n = p
3504 n = p
3503 i += 1
3505 i += 1
3504
3506
3505 r.append(l)
3507 r.append(l)
3506
3508
3507 return r
3509 return r
3508
3510
3509 def checkpush(self, pushop):
3511 def checkpush(self, pushop):
3510 """Extensions can override this function if additional checks have
3512 """Extensions can override this function if additional checks have
3511 to be performed before pushing, or call it if they override push
3513 to be performed before pushing, or call it if they override push
3512 command.
3514 command.
3513 """
3515 """
3514
3516
3515 @unfilteredpropertycache
3517 @unfilteredpropertycache
3516 def prepushoutgoinghooks(self):
3518 def prepushoutgoinghooks(self):
3517 """Return util.hooks consists of a pushop with repo, remote, outgoing
3519 """Return util.hooks consists of a pushop with repo, remote, outgoing
3518 methods, which are called before pushing changesets.
3520 methods, which are called before pushing changesets.
3519 """
3521 """
3520 return util.hooks()
3522 return util.hooks()
3521
3523
3522 def pushkey(self, namespace, key, old, new):
3524 def pushkey(self, namespace, key, old, new):
3523 try:
3525 try:
3524 tr = self.currenttransaction()
3526 tr = self.currenttransaction()
3525 hookargs = {}
3527 hookargs = {}
3526 if tr is not None:
3528 if tr is not None:
3527 hookargs.update(tr.hookargs)
3529 hookargs.update(tr.hookargs)
3528 hookargs = pycompat.strkwargs(hookargs)
3530 hookargs = pycompat.strkwargs(hookargs)
3529 hookargs['namespace'] = namespace
3531 hookargs['namespace'] = namespace
3530 hookargs['key'] = key
3532 hookargs['key'] = key
3531 hookargs['old'] = old
3533 hookargs['old'] = old
3532 hookargs['new'] = new
3534 hookargs['new'] = new
3533 self.hook(b'prepushkey', throw=True, **hookargs)
3535 self.hook(b'prepushkey', throw=True, **hookargs)
3534 except error.HookAbort as exc:
3536 except error.HookAbort as exc:
3535 self.ui.write_err(_(b"pushkey-abort: %s\n") % exc)
3537 self.ui.write_err(_(b"pushkey-abort: %s\n") % exc)
3536 if exc.hint:
3538 if exc.hint:
3537 self.ui.write_err(_(b"(%s)\n") % exc.hint)
3539 self.ui.write_err(_(b"(%s)\n") % exc.hint)
3538 return False
3540 return False
3539 self.ui.debug(b'pushing key for "%s:%s"\n' % (namespace, key))
3541 self.ui.debug(b'pushing key for "%s:%s"\n' % (namespace, key))
3540 ret = pushkey.push(self, namespace, key, old, new)
3542 ret = pushkey.push(self, namespace, key, old, new)
3541
3543
3542 def runhook(unused_success):
3544 def runhook(unused_success):
3543 self.hook(
3545 self.hook(
3544 b'pushkey',
3546 b'pushkey',
3545 namespace=namespace,
3547 namespace=namespace,
3546 key=key,
3548 key=key,
3547 old=old,
3549 old=old,
3548 new=new,
3550 new=new,
3549 ret=ret,
3551 ret=ret,
3550 )
3552 )
3551
3553
3552 self._afterlock(runhook)
3554 self._afterlock(runhook)
3553 return ret
3555 return ret
3554
3556
3555 def listkeys(self, namespace):
3557 def listkeys(self, namespace):
3556 self.hook(b'prelistkeys', throw=True, namespace=namespace)
3558 self.hook(b'prelistkeys', throw=True, namespace=namespace)
3557 self.ui.debug(b'listing keys for "%s"\n' % namespace)
3559 self.ui.debug(b'listing keys for "%s"\n' % namespace)
3558 values = pushkey.list(self, namespace)
3560 values = pushkey.list(self, namespace)
3559 self.hook(b'listkeys', namespace=namespace, values=values)
3561 self.hook(b'listkeys', namespace=namespace, values=values)
3560 return values
3562 return values
3561
3563
3562 def debugwireargs(self, one, two, three=None, four=None, five=None):
3564 def debugwireargs(self, one, two, three=None, four=None, five=None):
3563 '''used to test argument passing over the wire'''
3565 '''used to test argument passing over the wire'''
3564 return b"%s %s %s %s %s" % (
3566 return b"%s %s %s %s %s" % (
3565 one,
3567 one,
3566 two,
3568 two,
3567 pycompat.bytestr(three),
3569 pycompat.bytestr(three),
3568 pycompat.bytestr(four),
3570 pycompat.bytestr(four),
3569 pycompat.bytestr(five),
3571 pycompat.bytestr(five),
3570 )
3572 )
3571
3573
3572 def savecommitmessage(self, text):
3574 def savecommitmessage(self, text):
3573 fp = self.vfs(b'last-message.txt', b'wb')
3575 fp = self.vfs(b'last-message.txt', b'wb')
3574 try:
3576 try:
3575 fp.write(text)
3577 fp.write(text)
3576 finally:
3578 finally:
3577 fp.close()
3579 fp.close()
3578 return self.pathto(fp.name[len(self.root) + 1 :])
3580 return self.pathto(fp.name[len(self.root) + 1 :])
3579
3581
3580 def register_wanted_sidedata(self, category):
3582 def register_wanted_sidedata(self, category):
3581 if repository.REPO_FEATURE_SIDE_DATA not in self.features:
3583 if repository.REPO_FEATURE_SIDE_DATA not in self.features:
3582 # Only revlogv2 repos can want sidedata.
3584 # Only revlogv2 repos can want sidedata.
3583 return
3585 return
3584 self._wanted_sidedata.add(pycompat.bytestr(category))
3586 self._wanted_sidedata.add(pycompat.bytestr(category))
3585
3587
3586 def register_sidedata_computer(
3588 def register_sidedata_computer(
3587 self, kind, category, keys, computer, flags, replace=False
3589 self, kind, category, keys, computer, flags, replace=False
3588 ):
3590 ):
3589 if kind not in revlogconst.ALL_KINDS:
3591 if kind not in revlogconst.ALL_KINDS:
3590 msg = _(b"unexpected revlog kind '%s'.")
3592 msg = _(b"unexpected revlog kind '%s'.")
3591 raise error.ProgrammingError(msg % kind)
3593 raise error.ProgrammingError(msg % kind)
3592 category = pycompat.bytestr(category)
3594 category = pycompat.bytestr(category)
3593 already_registered = category in self._sidedata_computers.get(kind, [])
3595 already_registered = category in self._sidedata_computers.get(kind, [])
3594 if already_registered and not replace:
3596 if already_registered and not replace:
3595 msg = _(
3597 msg = _(
3596 b"cannot register a sidedata computer twice for category '%s'."
3598 b"cannot register a sidedata computer twice for category '%s'."
3597 )
3599 )
3598 raise error.ProgrammingError(msg % category)
3600 raise error.ProgrammingError(msg % category)
3599 if replace and not already_registered:
3601 if replace and not already_registered:
3600 msg = _(
3602 msg = _(
3601 b"cannot replace a sidedata computer that isn't registered "
3603 b"cannot replace a sidedata computer that isn't registered "
3602 b"for category '%s'."
3604 b"for category '%s'."
3603 )
3605 )
3604 raise error.ProgrammingError(msg % category)
3606 raise error.ProgrammingError(msg % category)
3605 self._sidedata_computers.setdefault(kind, {})
3607 self._sidedata_computers.setdefault(kind, {})
3606 self._sidedata_computers[kind][category] = (keys, computer, flags)
3608 self._sidedata_computers[kind][category] = (keys, computer, flags)
3607
3609
3608
3610
3609 def undoname(fn: bytes) -> bytes:
3611 def undoname(fn: bytes) -> bytes:
3610 base, name = os.path.split(fn)
3612 base, name = os.path.split(fn)
3611 assert name.startswith(b'journal')
3613 assert name.startswith(b'journal')
3612 return os.path.join(base, name.replace(b'journal', b'undo', 1))
3614 return os.path.join(base, name.replace(b'journal', b'undo', 1))
3613
3615
3614
3616
3615 def instance(ui, path: bytes, create, intents=None, createopts=None):
3617 def instance(ui, path: bytes, create, intents=None, createopts=None):
3616 # prevent cyclic import localrepo -> upgrade -> localrepo
3618 # prevent cyclic import localrepo -> upgrade -> localrepo
3617 from . import upgrade
3619 from . import upgrade
3618
3620
3619 localpath = urlutil.urllocalpath(path)
3621 localpath = urlutil.urllocalpath(path)
3620 if create:
3622 if create:
3621 createrepository(ui, localpath, createopts=createopts)
3623 createrepository(ui, localpath, createopts=createopts)
3622
3624
3623 def repo_maker():
3625 def repo_maker():
3624 return makelocalrepository(ui, localpath, intents=intents)
3626 return makelocalrepository(ui, localpath, intents=intents)
3625
3627
3626 repo = repo_maker()
3628 repo = repo_maker()
3627 repo = upgrade.may_auto_upgrade(repo, repo_maker)
3629 repo = upgrade.may_auto_upgrade(repo, repo_maker)
3628 return repo
3630 return repo
3629
3631
3630
3632
3631 def islocal(path: bytes) -> bool:
3633 def islocal(path: bytes) -> bool:
3632 return True
3634 return True
3633
3635
3634
3636
3635 def defaultcreateopts(ui, createopts=None):
3637 def defaultcreateopts(ui, createopts=None):
3636 """Populate the default creation options for a repository.
3638 """Populate the default creation options for a repository.
3637
3639
3638 A dictionary of explicitly requested creation options can be passed
3640 A dictionary of explicitly requested creation options can be passed
3639 in. Missing keys will be populated.
3641 in. Missing keys will be populated.
3640 """
3642 """
3641 createopts = dict(createopts or {})
3643 createopts = dict(createopts or {})
3642
3644
3643 if b'backend' not in createopts:
3645 if b'backend' not in createopts:
3644 # experimental config: storage.new-repo-backend
3646 # experimental config: storage.new-repo-backend
3645 createopts[b'backend'] = ui.config(b'storage', b'new-repo-backend')
3647 createopts[b'backend'] = ui.config(b'storage', b'new-repo-backend')
3646
3648
3647 return createopts
3649 return createopts
3648
3650
3649
3651
3650 def clone_requirements(ui, createopts, srcrepo):
3652 def clone_requirements(ui, createopts, srcrepo):
3651 """clone the requirements of a local repo for a local clone
3653 """clone the requirements of a local repo for a local clone
3652
3654
3653 The store requirements are unchanged while the working copy requirements
3655 The store requirements are unchanged while the working copy requirements
3654 depends on the configuration
3656 depends on the configuration
3655 """
3657 """
3656 target_requirements = set()
3658 target_requirements = set()
3657 if not srcrepo.requirements:
3659 if not srcrepo.requirements:
3658 # this is a legacy revlog "v0" repository, we cannot do anything fancy
3660 # this is a legacy revlog "v0" repository, we cannot do anything fancy
3659 # with it.
3661 # with it.
3660 return target_requirements
3662 return target_requirements
3661 createopts = defaultcreateopts(ui, createopts=createopts)
3663 createopts = defaultcreateopts(ui, createopts=createopts)
3662 for r in newreporequirements(ui, createopts):
3664 for r in newreporequirements(ui, createopts):
3663 if r in requirementsmod.WORKING_DIR_REQUIREMENTS:
3665 if r in requirementsmod.WORKING_DIR_REQUIREMENTS:
3664 target_requirements.add(r)
3666 target_requirements.add(r)
3665
3667
3666 for r in srcrepo.requirements:
3668 for r in srcrepo.requirements:
3667 if r not in requirementsmod.WORKING_DIR_REQUIREMENTS:
3669 if r not in requirementsmod.WORKING_DIR_REQUIREMENTS:
3668 target_requirements.add(r)
3670 target_requirements.add(r)
3669 return target_requirements
3671 return target_requirements
3670
3672
3671
3673
3672 def newreporequirements(ui, createopts):
3674 def newreporequirements(ui, createopts):
3673 """Determine the set of requirements for a new local repository.
3675 """Determine the set of requirements for a new local repository.
3674
3676
3675 Extensions can wrap this function to specify custom requirements for
3677 Extensions can wrap this function to specify custom requirements for
3676 new repositories.
3678 new repositories.
3677 """
3679 """
3678
3680
3679 if b'backend' not in createopts:
3681 if b'backend' not in createopts:
3680 raise error.ProgrammingError(
3682 raise error.ProgrammingError(
3681 b'backend key not present in createopts; '
3683 b'backend key not present in createopts; '
3682 b'was defaultcreateopts() called?'
3684 b'was defaultcreateopts() called?'
3683 )
3685 )
3684
3686
3685 if createopts[b'backend'] != b'revlogv1':
3687 if createopts[b'backend'] != b'revlogv1':
3686 raise error.Abort(
3688 raise error.Abort(
3687 _(
3689 _(
3688 b'unable to determine repository requirements for '
3690 b'unable to determine repository requirements for '
3689 b'storage backend: %s'
3691 b'storage backend: %s'
3690 )
3692 )
3691 % createopts[b'backend']
3693 % createopts[b'backend']
3692 )
3694 )
3693
3695
3694 requirements = {requirementsmod.REVLOGV1_REQUIREMENT}
3696 requirements = {requirementsmod.REVLOGV1_REQUIREMENT}
3695 if ui.configbool(b'format', b'usestore'):
3697 if ui.configbool(b'format', b'usestore'):
3696 requirements.add(requirementsmod.STORE_REQUIREMENT)
3698 requirements.add(requirementsmod.STORE_REQUIREMENT)
3697 if ui.configbool(b'format', b'usefncache'):
3699 if ui.configbool(b'format', b'usefncache'):
3698 requirements.add(requirementsmod.FNCACHE_REQUIREMENT)
3700 requirements.add(requirementsmod.FNCACHE_REQUIREMENT)
3699 if ui.configbool(b'format', b'dotencode'):
3701 if ui.configbool(b'format', b'dotencode'):
3700 requirements.add(requirementsmod.DOTENCODE_REQUIREMENT)
3702 requirements.add(requirementsmod.DOTENCODE_REQUIREMENT)
3701
3703
3702 compengines = ui.configlist(b'format', b'revlog-compression')
3704 compengines = ui.configlist(b'format', b'revlog-compression')
3703 for compengine in compengines:
3705 for compengine in compengines:
3704 if compengine in util.compengines:
3706 if compengine in util.compengines:
3705 engine = util.compengines[compengine]
3707 engine = util.compengines[compengine]
3706 if engine.available() and engine.revlogheader():
3708 if engine.available() and engine.revlogheader():
3707 break
3709 break
3708 else:
3710 else:
3709 raise error.Abort(
3711 raise error.Abort(
3710 _(
3712 _(
3711 b'compression engines %s defined by '
3713 b'compression engines %s defined by '
3712 b'format.revlog-compression not available'
3714 b'format.revlog-compression not available'
3713 )
3715 )
3714 % b', '.join(b'"%s"' % e for e in compengines),
3716 % b', '.join(b'"%s"' % e for e in compengines),
3715 hint=_(
3717 hint=_(
3716 b'run "hg debuginstall" to list available '
3718 b'run "hg debuginstall" to list available '
3717 b'compression engines'
3719 b'compression engines'
3718 ),
3720 ),
3719 )
3721 )
3720
3722
3721 # zlib is the historical default and doesn't need an explicit requirement.
3723 # zlib is the historical default and doesn't need an explicit requirement.
3722 if compengine == b'zstd':
3724 if compengine == b'zstd':
3723 requirements.add(b'revlog-compression-zstd')
3725 requirements.add(b'revlog-compression-zstd')
3724 elif compengine != b'zlib':
3726 elif compengine != b'zlib':
3725 requirements.add(b'exp-compression-%s' % compengine)
3727 requirements.add(b'exp-compression-%s' % compengine)
3726
3728
3727 if scmutil.gdinitconfig(ui):
3729 if scmutil.gdinitconfig(ui):
3728 requirements.add(requirementsmod.GENERALDELTA_REQUIREMENT)
3730 requirements.add(requirementsmod.GENERALDELTA_REQUIREMENT)
3729 if ui.configbool(b'format', b'sparse-revlog'):
3731 if ui.configbool(b'format', b'sparse-revlog'):
3730 requirements.add(requirementsmod.SPARSEREVLOG_REQUIREMENT)
3732 requirements.add(requirementsmod.SPARSEREVLOG_REQUIREMENT)
3731
3733
3732 # experimental config: format.use-dirstate-v2
3734 # experimental config: format.use-dirstate-v2
3733 # Keep this logic in sync with `has_dirstate_v2()` in `tests/hghave.py`
3735 # Keep this logic in sync with `has_dirstate_v2()` in `tests/hghave.py`
3734 if ui.configbool(b'format', b'use-dirstate-v2'):
3736 if ui.configbool(b'format', b'use-dirstate-v2'):
3735 requirements.add(requirementsmod.DIRSTATE_V2_REQUIREMENT)
3737 requirements.add(requirementsmod.DIRSTATE_V2_REQUIREMENT)
3736
3738
3737 # experimental config: format.exp-use-copies-side-data-changeset
3739 # experimental config: format.exp-use-copies-side-data-changeset
3738 if ui.configbool(b'format', b'exp-use-copies-side-data-changeset'):
3740 if ui.configbool(b'format', b'exp-use-copies-side-data-changeset'):
3739 requirements.add(requirementsmod.CHANGELOGV2_REQUIREMENT)
3741 requirements.add(requirementsmod.CHANGELOGV2_REQUIREMENT)
3740 requirements.add(requirementsmod.COPIESSDC_REQUIREMENT)
3742 requirements.add(requirementsmod.COPIESSDC_REQUIREMENT)
3741 if ui.configbool(b'experimental', b'treemanifest'):
3743 if ui.configbool(b'experimental', b'treemanifest'):
3742 requirements.add(requirementsmod.TREEMANIFEST_REQUIREMENT)
3744 requirements.add(requirementsmod.TREEMANIFEST_REQUIREMENT)
3743
3745
3744 changelogv2 = ui.config(b'format', b'exp-use-changelog-v2')
3746 changelogv2 = ui.config(b'format', b'exp-use-changelog-v2')
3745 if changelogv2 == b'enable-unstable-format-and-corrupt-my-data':
3747 if changelogv2 == b'enable-unstable-format-and-corrupt-my-data':
3746 requirements.add(requirementsmod.CHANGELOGV2_REQUIREMENT)
3748 requirements.add(requirementsmod.CHANGELOGV2_REQUIREMENT)
3747
3749
3748 revlogv2 = ui.config(b'experimental', b'revlogv2')
3750 revlogv2 = ui.config(b'experimental', b'revlogv2')
3749 if revlogv2 == b'enable-unstable-format-and-corrupt-my-data':
3751 if revlogv2 == b'enable-unstable-format-and-corrupt-my-data':
3750 requirements.discard(requirementsmod.REVLOGV1_REQUIREMENT)
3752 requirements.discard(requirementsmod.REVLOGV1_REQUIREMENT)
3751 requirements.add(requirementsmod.REVLOGV2_REQUIREMENT)
3753 requirements.add(requirementsmod.REVLOGV2_REQUIREMENT)
3752 # experimental config: format.internal-phase
3754 # experimental config: format.internal-phase
3753 if ui.configbool(b'format', b'use-internal-phase'):
3755 if ui.configbool(b'format', b'use-internal-phase'):
3754 requirements.add(requirementsmod.INTERNAL_PHASE_REQUIREMENT)
3756 requirements.add(requirementsmod.INTERNAL_PHASE_REQUIREMENT)
3755
3757
3756 # experimental config: format.exp-archived-phase
3758 # experimental config: format.exp-archived-phase
3757 if ui.configbool(b'format', b'exp-archived-phase'):
3759 if ui.configbool(b'format', b'exp-archived-phase'):
3758 requirements.add(requirementsmod.ARCHIVED_PHASE_REQUIREMENT)
3760 requirements.add(requirementsmod.ARCHIVED_PHASE_REQUIREMENT)
3759
3761
3760 if createopts.get(b'narrowfiles'):
3762 if createopts.get(b'narrowfiles'):
3761 requirements.add(requirementsmod.NARROW_REQUIREMENT)
3763 requirements.add(requirementsmod.NARROW_REQUIREMENT)
3762
3764
3763 if createopts.get(b'lfs'):
3765 if createopts.get(b'lfs'):
3764 requirements.add(b'lfs')
3766 requirements.add(b'lfs')
3765
3767
3766 if ui.configbool(b'format', b'bookmarks-in-store'):
3768 if ui.configbool(b'format', b'bookmarks-in-store'):
3767 requirements.add(requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT)
3769 requirements.add(requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT)
3768
3770
3769 # The feature is disabled unless a fast implementation is available.
3771 # The feature is disabled unless a fast implementation is available.
3770 persistent_nodemap_default = policy.importrust('revlog') is not None
3772 persistent_nodemap_default = policy.importrust('revlog') is not None
3771 if ui.configbool(
3773 if ui.configbool(
3772 b'format', b'use-persistent-nodemap', persistent_nodemap_default
3774 b'format', b'use-persistent-nodemap', persistent_nodemap_default
3773 ):
3775 ):
3774 requirements.add(requirementsmod.NODEMAP_REQUIREMENT)
3776 requirements.add(requirementsmod.NODEMAP_REQUIREMENT)
3775
3777
3776 # if share-safe is enabled, let's create the new repository with the new
3778 # if share-safe is enabled, let's create the new repository with the new
3777 # requirement
3779 # requirement
3778 if ui.configbool(b'format', b'use-share-safe'):
3780 if ui.configbool(b'format', b'use-share-safe'):
3779 requirements.add(requirementsmod.SHARESAFE_REQUIREMENT)
3781 requirements.add(requirementsmod.SHARESAFE_REQUIREMENT)
3780
3782
3781 # if we are creating a share-repoΒΉ we have to handle requirement
3783 # if we are creating a share-repoΒΉ we have to handle requirement
3782 # differently.
3784 # differently.
3783 #
3785 #
3784 # [1] (i.e. reusing the store from another repository, just having a
3786 # [1] (i.e. reusing the store from another repository, just having a
3785 # working copy)
3787 # working copy)
3786 if b'sharedrepo' in createopts:
3788 if b'sharedrepo' in createopts:
3787 source_requirements = set(createopts[b'sharedrepo'].requirements)
3789 source_requirements = set(createopts[b'sharedrepo'].requirements)
3788
3790
3789 if requirementsmod.SHARESAFE_REQUIREMENT not in source_requirements:
3791 if requirementsmod.SHARESAFE_REQUIREMENT not in source_requirements:
3790 # share to an old school repository, we have to copy the
3792 # share to an old school repository, we have to copy the
3791 # requirements and hope for the best.
3793 # requirements and hope for the best.
3792 requirements = source_requirements
3794 requirements = source_requirements
3793 else:
3795 else:
3794 # We have control on the working copy only, so "copy" the non
3796 # We have control on the working copy only, so "copy" the non
3795 # working copy part over, ignoring previous logic.
3797 # working copy part over, ignoring previous logic.
3796 to_drop = set()
3798 to_drop = set()
3797 for req in requirements:
3799 for req in requirements:
3798 if req in requirementsmod.WORKING_DIR_REQUIREMENTS:
3800 if req in requirementsmod.WORKING_DIR_REQUIREMENTS:
3799 continue
3801 continue
3800 if req in source_requirements:
3802 if req in source_requirements:
3801 continue
3803 continue
3802 to_drop.add(req)
3804 to_drop.add(req)
3803 requirements -= to_drop
3805 requirements -= to_drop
3804 requirements |= source_requirements
3806 requirements |= source_requirements
3805
3807
3806 if createopts.get(b'sharedrelative'):
3808 if createopts.get(b'sharedrelative'):
3807 requirements.add(requirementsmod.RELATIVE_SHARED_REQUIREMENT)
3809 requirements.add(requirementsmod.RELATIVE_SHARED_REQUIREMENT)
3808 else:
3810 else:
3809 requirements.add(requirementsmod.SHARED_REQUIREMENT)
3811 requirements.add(requirementsmod.SHARED_REQUIREMENT)
3810
3812
3811 if ui.configbool(b'format', b'use-dirstate-tracked-hint'):
3813 if ui.configbool(b'format', b'use-dirstate-tracked-hint'):
3812 version = ui.configint(b'format', b'use-dirstate-tracked-hint.version')
3814 version = ui.configint(b'format', b'use-dirstate-tracked-hint.version')
3813 msg = _(b"ignoring unknown tracked key version: %d\n")
3815 msg = _(b"ignoring unknown tracked key version: %d\n")
3814 hint = _(
3816 hint = _(
3815 b"see `hg help config.format.use-dirstate-tracked-hint-version"
3817 b"see `hg help config.format.use-dirstate-tracked-hint-version"
3816 )
3818 )
3817 if version != 1:
3819 if version != 1:
3818 ui.warn(msg % version, hint=hint)
3820 ui.warn(msg % version, hint=hint)
3819 else:
3821 else:
3820 requirements.add(requirementsmod.DIRSTATE_TRACKED_HINT_V1)
3822 requirements.add(requirementsmod.DIRSTATE_TRACKED_HINT_V1)
3821
3823
3822 return requirements
3824 return requirements
3823
3825
3824
3826
3825 def checkrequirementscompat(ui, requirements):
3827 def checkrequirementscompat(ui, requirements):
3826 """Checks compatibility of repository requirements enabled and disabled.
3828 """Checks compatibility of repository requirements enabled and disabled.
3827
3829
3828 Returns a set of requirements which needs to be dropped because dependend
3830 Returns a set of requirements which needs to be dropped because dependend
3829 requirements are not enabled. Also warns users about it"""
3831 requirements are not enabled. Also warns users about it"""
3830
3832
3831 dropped = set()
3833 dropped = set()
3832
3834
3833 if requirementsmod.STORE_REQUIREMENT not in requirements:
3835 if requirementsmod.STORE_REQUIREMENT not in requirements:
3834 if requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT in requirements:
3836 if requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT in requirements:
3835 ui.warn(
3837 ui.warn(
3836 _(
3838 _(
3837 b'ignoring enabled \'format.bookmarks-in-store\' config '
3839 b'ignoring enabled \'format.bookmarks-in-store\' config '
3838 b'beacuse it is incompatible with disabled '
3840 b'beacuse it is incompatible with disabled '
3839 b'\'format.usestore\' config\n'
3841 b'\'format.usestore\' config\n'
3840 )
3842 )
3841 )
3843 )
3842 dropped.add(requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT)
3844 dropped.add(requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT)
3843
3845
3844 if (
3846 if (
3845 requirementsmod.SHARED_REQUIREMENT in requirements
3847 requirementsmod.SHARED_REQUIREMENT in requirements
3846 or requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements
3848 or requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements
3847 ):
3849 ):
3848 raise error.Abort(
3850 raise error.Abort(
3849 _(
3851 _(
3850 b"cannot create shared repository as source was created"
3852 b"cannot create shared repository as source was created"
3851 b" with 'format.usestore' config disabled"
3853 b" with 'format.usestore' config disabled"
3852 )
3854 )
3853 )
3855 )
3854
3856
3855 if requirementsmod.SHARESAFE_REQUIREMENT in requirements:
3857 if requirementsmod.SHARESAFE_REQUIREMENT in requirements:
3856 if ui.hasconfig(b'format', b'use-share-safe'):
3858 if ui.hasconfig(b'format', b'use-share-safe'):
3857 msg = _(
3859 msg = _(
3858 b"ignoring enabled 'format.use-share-safe' config because "
3860 b"ignoring enabled 'format.use-share-safe' config because "
3859 b"it is incompatible with disabled 'format.usestore'"
3861 b"it is incompatible with disabled 'format.usestore'"
3860 b" config\n"
3862 b" config\n"
3861 )
3863 )
3862 ui.warn(msg)
3864 ui.warn(msg)
3863 dropped.add(requirementsmod.SHARESAFE_REQUIREMENT)
3865 dropped.add(requirementsmod.SHARESAFE_REQUIREMENT)
3864
3866
3865 return dropped
3867 return dropped
3866
3868
3867
3869
3868 def filterknowncreateopts(ui, createopts):
3870 def filterknowncreateopts(ui, createopts):
3869 """Filters a dict of repo creation options against options that are known.
3871 """Filters a dict of repo creation options against options that are known.
3870
3872
3871 Receives a dict of repo creation options and returns a dict of those
3873 Receives a dict of repo creation options and returns a dict of those
3872 options that we don't know how to handle.
3874 options that we don't know how to handle.
3873
3875
3874 This function is called as part of repository creation. If the
3876 This function is called as part of repository creation. If the
3875 returned dict contains any items, repository creation will not
3877 returned dict contains any items, repository creation will not
3876 be allowed, as it means there was a request to create a repository
3878 be allowed, as it means there was a request to create a repository
3877 with options not recognized by loaded code.
3879 with options not recognized by loaded code.
3878
3880
3879 Extensions can wrap this function to filter out creation options
3881 Extensions can wrap this function to filter out creation options
3880 they know how to handle.
3882 they know how to handle.
3881 """
3883 """
3882 known = {
3884 known = {
3883 b'backend',
3885 b'backend',
3884 b'lfs',
3886 b'lfs',
3885 b'narrowfiles',
3887 b'narrowfiles',
3886 b'sharedrepo',
3888 b'sharedrepo',
3887 b'sharedrelative',
3889 b'sharedrelative',
3888 b'shareditems',
3890 b'shareditems',
3889 b'shallowfilestore',
3891 b'shallowfilestore',
3890 }
3892 }
3891
3893
3892 return {k: v for k, v in createopts.items() if k not in known}
3894 return {k: v for k, v in createopts.items() if k not in known}
3893
3895
3894
3896
3895 def createrepository(ui, path: bytes, createopts=None, requirements=None):
3897 def createrepository(ui, path: bytes, createopts=None, requirements=None):
3896 """Create a new repository in a vfs.
3898 """Create a new repository in a vfs.
3897
3899
3898 ``path`` path to the new repo's working directory.
3900 ``path`` path to the new repo's working directory.
3899 ``createopts`` options for the new repository.
3901 ``createopts`` options for the new repository.
3900 ``requirement`` predefined set of requirements.
3902 ``requirement`` predefined set of requirements.
3901 (incompatible with ``createopts``)
3903 (incompatible with ``createopts``)
3902
3904
3903 The following keys for ``createopts`` are recognized:
3905 The following keys for ``createopts`` are recognized:
3904
3906
3905 backend
3907 backend
3906 The storage backend to use.
3908 The storage backend to use.
3907 lfs
3909 lfs
3908 Repository will be created with ``lfs`` requirement. The lfs extension
3910 Repository will be created with ``lfs`` requirement. The lfs extension
3909 will automatically be loaded when the repository is accessed.
3911 will automatically be loaded when the repository is accessed.
3910 narrowfiles
3912 narrowfiles
3911 Set up repository to support narrow file storage.
3913 Set up repository to support narrow file storage.
3912 sharedrepo
3914 sharedrepo
3913 Repository object from which storage should be shared.
3915 Repository object from which storage should be shared.
3914 sharedrelative
3916 sharedrelative
3915 Boolean indicating if the path to the shared repo should be
3917 Boolean indicating if the path to the shared repo should be
3916 stored as relative. By default, the pointer to the "parent" repo
3918 stored as relative. By default, the pointer to the "parent" repo
3917 is stored as an absolute path.
3919 is stored as an absolute path.
3918 shareditems
3920 shareditems
3919 Set of items to share to the new repository (in addition to storage).
3921 Set of items to share to the new repository (in addition to storage).
3920 shallowfilestore
3922 shallowfilestore
3921 Indicates that storage for files should be shallow (not all ancestor
3923 Indicates that storage for files should be shallow (not all ancestor
3922 revisions are known).
3924 revisions are known).
3923 """
3925 """
3924
3926
3925 if requirements is not None:
3927 if requirements is not None:
3926 if createopts is not None:
3928 if createopts is not None:
3927 msg = b'cannot specify both createopts and requirements'
3929 msg = b'cannot specify both createopts and requirements'
3928 raise error.ProgrammingError(msg)
3930 raise error.ProgrammingError(msg)
3929 createopts = {}
3931 createopts = {}
3930 else:
3932 else:
3931 createopts = defaultcreateopts(ui, createopts=createopts)
3933 createopts = defaultcreateopts(ui, createopts=createopts)
3932
3934
3933 unknownopts = filterknowncreateopts(ui, createopts)
3935 unknownopts = filterknowncreateopts(ui, createopts)
3934
3936
3935 if not isinstance(unknownopts, dict):
3937 if not isinstance(unknownopts, dict):
3936 raise error.ProgrammingError(
3938 raise error.ProgrammingError(
3937 b'filterknowncreateopts() did not return a dict'
3939 b'filterknowncreateopts() did not return a dict'
3938 )
3940 )
3939
3941
3940 if unknownopts:
3942 if unknownopts:
3941 raise error.Abort(
3943 raise error.Abort(
3942 _(
3944 _(
3943 b'unable to create repository because of unknown '
3945 b'unable to create repository because of unknown '
3944 b'creation option: %s'
3946 b'creation option: %s'
3945 )
3947 )
3946 % b', '.join(sorted(unknownopts)),
3948 % b', '.join(sorted(unknownopts)),
3947 hint=_(b'is a required extension not loaded?'),
3949 hint=_(b'is a required extension not loaded?'),
3948 )
3950 )
3949
3951
3950 requirements = newreporequirements(ui, createopts=createopts)
3952 requirements = newreporequirements(ui, createopts=createopts)
3951 requirements -= checkrequirementscompat(ui, requirements)
3953 requirements -= checkrequirementscompat(ui, requirements)
3952
3954
3953 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
3955 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
3954
3956
3955 hgvfs = vfsmod.vfs(wdirvfs.join(b'.hg'))
3957 hgvfs = vfsmod.vfs(wdirvfs.join(b'.hg'))
3956 if hgvfs.exists():
3958 if hgvfs.exists():
3957 raise error.RepoError(_(b'repository %s already exists') % path)
3959 raise error.RepoError(_(b'repository %s already exists') % path)
3958
3960
3959 if b'sharedrepo' in createopts:
3961 if b'sharedrepo' in createopts:
3960 sharedpath = createopts[b'sharedrepo'].sharedpath
3962 sharedpath = createopts[b'sharedrepo'].sharedpath
3961
3963
3962 if createopts.get(b'sharedrelative'):
3964 if createopts.get(b'sharedrelative'):
3963 try:
3965 try:
3964 sharedpath = os.path.relpath(sharedpath, hgvfs.base)
3966 sharedpath = os.path.relpath(sharedpath, hgvfs.base)
3965 sharedpath = util.pconvert(sharedpath)
3967 sharedpath = util.pconvert(sharedpath)
3966 except (IOError, ValueError) as e:
3968 except (IOError, ValueError) as e:
3967 # ValueError is raised on Windows if the drive letters differ
3969 # ValueError is raised on Windows if the drive letters differ
3968 # on each path.
3970 # on each path.
3969 raise error.Abort(
3971 raise error.Abort(
3970 _(b'cannot calculate relative path'),
3972 _(b'cannot calculate relative path'),
3971 hint=stringutil.forcebytestr(e),
3973 hint=stringutil.forcebytestr(e),
3972 )
3974 )
3973
3975
3974 if not wdirvfs.exists():
3976 if not wdirvfs.exists():
3975 wdirvfs.makedirs()
3977 wdirvfs.makedirs()
3976
3978
3977 hgvfs.makedir(notindexed=True)
3979 hgvfs.makedir(notindexed=True)
3978 if b'sharedrepo' not in createopts:
3980 if b'sharedrepo' not in createopts:
3979 hgvfs.mkdir(b'cache')
3981 hgvfs.mkdir(b'cache')
3980 hgvfs.mkdir(b'wcache')
3982 hgvfs.mkdir(b'wcache')
3981
3983
3982 has_store = requirementsmod.STORE_REQUIREMENT in requirements
3984 has_store = requirementsmod.STORE_REQUIREMENT in requirements
3983 if has_store and b'sharedrepo' not in createopts:
3985 if has_store and b'sharedrepo' not in createopts:
3984 hgvfs.mkdir(b'store')
3986 hgvfs.mkdir(b'store')
3985
3987
3986 # We create an invalid changelog outside the store so very old
3988 # We create an invalid changelog outside the store so very old
3987 # Mercurial versions (which didn't know about the requirements
3989 # Mercurial versions (which didn't know about the requirements
3988 # file) encounter an error on reading the changelog. This
3990 # file) encounter an error on reading the changelog. This
3989 # effectively locks out old clients and prevents them from
3991 # effectively locks out old clients and prevents them from
3990 # mucking with a repo in an unknown format.
3992 # mucking with a repo in an unknown format.
3991 #
3993 #
3992 # The revlog header has version 65535, which won't be recognized by
3994 # The revlog header has version 65535, which won't be recognized by
3993 # such old clients.
3995 # such old clients.
3994 hgvfs.append(
3996 hgvfs.append(
3995 b'00changelog.i',
3997 b'00changelog.i',
3996 b'\0\0\xFF\xFF dummy changelog to prevent using the old repo '
3998 b'\0\0\xFF\xFF dummy changelog to prevent using the old repo '
3997 b'layout',
3999 b'layout',
3998 )
4000 )
3999
4001
4000 # Filter the requirements into working copy and store ones
4002 # Filter the requirements into working copy and store ones
4001 wcreq, storereq = scmutil.filterrequirements(requirements)
4003 wcreq, storereq = scmutil.filterrequirements(requirements)
4002 # write working copy ones
4004 # write working copy ones
4003 scmutil.writerequires(hgvfs, wcreq)
4005 scmutil.writerequires(hgvfs, wcreq)
4004 # If there are store requirements and the current repository
4006 # If there are store requirements and the current repository
4005 # is not a shared one, write stored requirements
4007 # is not a shared one, write stored requirements
4006 # For new shared repository, we don't need to write the store
4008 # For new shared repository, we don't need to write the store
4007 # requirements as they are already present in store requires
4009 # requirements as they are already present in store requires
4008 if storereq and b'sharedrepo' not in createopts:
4010 if storereq and b'sharedrepo' not in createopts:
4009 storevfs = vfsmod.vfs(hgvfs.join(b'store'), cacheaudited=True)
4011 storevfs = vfsmod.vfs(hgvfs.join(b'store'), cacheaudited=True)
4010 scmutil.writerequires(storevfs, storereq)
4012 scmutil.writerequires(storevfs, storereq)
4011
4013
4012 # Write out file telling readers where to find the shared store.
4014 # Write out file telling readers where to find the shared store.
4013 if b'sharedrepo' in createopts:
4015 if b'sharedrepo' in createopts:
4014 hgvfs.write(b'sharedpath', sharedpath)
4016 hgvfs.write(b'sharedpath', sharedpath)
4015
4017
4016 if createopts.get(b'shareditems'):
4018 if createopts.get(b'shareditems'):
4017 shared = b'\n'.join(sorted(createopts[b'shareditems'])) + b'\n'
4019 shared = b'\n'.join(sorted(createopts[b'shareditems'])) + b'\n'
4018 hgvfs.write(b'shared', shared)
4020 hgvfs.write(b'shared', shared)
4019
4021
4020
4022
4021 def poisonrepository(repo):
4023 def poisonrepository(repo):
4022 """Poison a repository instance so it can no longer be used."""
4024 """Poison a repository instance so it can no longer be used."""
4023 # Perform any cleanup on the instance.
4025 # Perform any cleanup on the instance.
4024 repo.close()
4026 repo.close()
4025
4027
4026 # Our strategy is to replace the type of the object with one that
4028 # Our strategy is to replace the type of the object with one that
4027 # has all attribute lookups result in error.
4029 # has all attribute lookups result in error.
4028 #
4030 #
4029 # But we have to allow the close() method because some constructors
4031 # But we have to allow the close() method because some constructors
4030 # of repos call close() on repo references.
4032 # of repos call close() on repo references.
4031 class poisonedrepository:
4033 class poisonedrepository:
4032 def __getattribute__(self, item):
4034 def __getattribute__(self, item):
4033 if item == 'close':
4035 if item == 'close':
4034 return object.__getattribute__(self, item)
4036 return object.__getattribute__(self, item)
4035
4037
4036 raise error.ProgrammingError(
4038 raise error.ProgrammingError(
4037 b'repo instances should not be used after unshare'
4039 b'repo instances should not be used after unshare'
4038 )
4040 )
4039
4041
4040 def close(self):
4042 def close(self):
4041 pass
4043 pass
4042
4044
4043 # We may have a repoview, which intercepts __setattr__. So be sure
4045 # We may have a repoview, which intercepts __setattr__. So be sure
4044 # we operate at the lowest level possible.
4046 # we operate at the lowest level possible.
4045 object.__setattr__(repo, '__class__', poisonedrepository)
4047 object.__setattr__(repo, '__class__', poisonedrepository)
@@ -1,3728 +1,3723 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 comp_engine_opts = self.feature_config.compression_engine_options
632 if b'zlib.level' in opts:
633 comp_engine_opts[b'zlib.level'] = opts[b'zlib.level']
634 if b'zstd.level' in opts:
635 comp_engine_opts[b'zstd.level'] = opts[b'zstd.level']
636 if self._mmaplargeindex and b'mmapindexthreshold' in opts:
631 if self._mmaplargeindex and b'mmapindexthreshold' in opts:
637 mmapindexthreshold = opts[b'mmapindexthreshold']
632 mmapindexthreshold = opts[b'mmapindexthreshold']
638 self.data_config.mmap_index_threshold = mmapindexthreshold
633 self.data_config.mmap_index_threshold = mmapindexthreshold
639 if b'sparse-revlog' in opts:
634 if b'sparse-revlog' in opts:
640 self.delta_config.sparse_revlog = bool(opts[b'sparse-revlog'])
635 self.delta_config.sparse_revlog = bool(opts[b'sparse-revlog'])
641 if self.delta_config.sparse_revlog:
636 if self.delta_config.sparse_revlog:
642 # sparse-revlog forces sparse-read
637 # sparse-revlog forces sparse-read
643 self.data_config.with_sparse_read = True
638 self.data_config.with_sparse_read = True
644 elif b'with-sparse-read' in opts:
639 elif b'with-sparse-read' in opts:
645 self.data_config.with_sparse_read = bool(opts[b'with-sparse-read'])
640 self.data_config.with_sparse_read = bool(opts[b'with-sparse-read'])
646 if b'sparse-read-density-threshold' in opts:
641 if b'sparse-read-density-threshold' in opts:
647 self.data_config.sr_density_threshold = opts[
642 self.data_config.sr_density_threshold = opts[
648 b'sparse-read-density-threshold'
643 b'sparse-read-density-threshold'
649 ]
644 ]
650 if b'sparse-read-min-gap-size' in opts:
645 if b'sparse-read-min-gap-size' in opts:
651 self.data_config.sr_min_gap_size = opts[b'sparse-read-min-gap-size']
646 self.data_config.sr_min_gap_size = opts[b'sparse-read-min-gap-size']
652 if opts.get(b'enableellipsis'):
647 if opts.get(b'enableellipsis'):
653 self.feature_config.enable_ellipsis = True
648 self.feature_config.enable_ellipsis = True
654 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
649 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
655
650
656 # revlog v0 doesn't have flag processors
651 # revlog v0 doesn't have flag processors
657 for flag, processor in opts.get(b'flagprocessors', {}).items():
652 for flag, processor in opts.get(b'flagprocessors', {}).items():
658 flagutil.insertflagprocessor(flag, processor, self._flagprocessors)
653 flagutil.insertflagprocessor(flag, processor, self._flagprocessors)
659
654
660 chunk_cache_size = self.data_config.chunk_cache_size
655 chunk_cache_size = self.data_config.chunk_cache_size
661 if chunk_cache_size <= 0:
656 if chunk_cache_size <= 0:
662 raise error.RevlogError(
657 raise error.RevlogError(
663 _(b'revlog chunk cache size %r is not greater than 0')
658 _(b'revlog chunk cache size %r is not greater than 0')
664 % chunk_cache_size
659 % chunk_cache_size
665 )
660 )
666 elif chunk_cache_size & (chunk_cache_size - 1):
661 elif chunk_cache_size & (chunk_cache_size - 1):
667 raise error.RevlogError(
662 raise error.RevlogError(
668 _(b'revlog chunk cache size %r is not a power of 2')
663 _(b'revlog chunk cache size %r is not a power of 2')
669 % chunk_cache_size
664 % chunk_cache_size
670 )
665 )
671 force_nodemap = opts.get(b'devel-force-nodemap', False)
666 force_nodemap = opts.get(b'devel-force-nodemap', False)
672 return new_header, mmapindexthreshold, force_nodemap
667 return new_header, mmapindexthreshold, force_nodemap
673
668
674 def _get_data(self, filepath, mmap_threshold, size=None):
669 def _get_data(self, filepath, mmap_threshold, size=None):
675 """return a file content with or without mmap
670 """return a file content with or without mmap
676
671
677 If the file is missing return the empty string"""
672 If the file is missing return the empty string"""
678 try:
673 try:
679 with self.opener(filepath) as fp:
674 with self.opener(filepath) as fp:
680 if mmap_threshold is not None:
675 if mmap_threshold is not None:
681 file_size = self.opener.fstat(fp).st_size
676 file_size = self.opener.fstat(fp).st_size
682 if file_size >= mmap_threshold:
677 if file_size >= mmap_threshold:
683 if size is not None:
678 if size is not None:
684 # avoid potentiel mmap crash
679 # avoid potentiel mmap crash
685 size = min(file_size, size)
680 size = min(file_size, size)
686 # TODO: should .close() to release resources without
681 # TODO: should .close() to release resources without
687 # relying on Python GC
682 # relying on Python GC
688 if size is None:
683 if size is None:
689 return util.buffer(util.mmapread(fp))
684 return util.buffer(util.mmapread(fp))
690 else:
685 else:
691 return util.buffer(util.mmapread(fp, size))
686 return util.buffer(util.mmapread(fp, size))
692 if size is None:
687 if size is None:
693 return fp.read()
688 return fp.read()
694 else:
689 else:
695 return fp.read(size)
690 return fp.read(size)
696 except FileNotFoundError:
691 except FileNotFoundError:
697 return b''
692 return b''
698
693
699 def get_streams(self, max_linkrev, force_inline=False):
694 def get_streams(self, max_linkrev, force_inline=False):
700 """return a list of streams that represent this revlog
695 """return a list of streams that represent this revlog
701
696
702 This is used by stream-clone to do bytes to bytes copies of a repository.
697 This is used by stream-clone to do bytes to bytes copies of a repository.
703
698
704 This streams data for all revisions that refer to a changelog revision up
699 This streams data for all revisions that refer to a changelog revision up
705 to `max_linkrev`.
700 to `max_linkrev`.
706
701
707 If `force_inline` is set, it enforces that the stream will represent an inline revlog.
702 If `force_inline` is set, it enforces that the stream will represent an inline revlog.
708
703
709 It returns is a list of three-tuple:
704 It returns is a list of three-tuple:
710
705
711 [
706 [
712 (filename, bytes_stream, stream_size),
707 (filename, bytes_stream, stream_size),
713 …
708 …
714 ]
709 ]
715 """
710 """
716 n = len(self)
711 n = len(self)
717 index = self.index
712 index = self.index
718 while n > 0:
713 while n > 0:
719 linkrev = index[n - 1][4]
714 linkrev = index[n - 1][4]
720 if linkrev < max_linkrev:
715 if linkrev < max_linkrev:
721 break
716 break
722 # note: this loop will rarely go through multiple iterations, since
717 # note: this loop will rarely go through multiple iterations, since
723 # it only traverses commits created during the current streaming
718 # it only traverses commits created during the current streaming
724 # pull operation.
719 # pull operation.
725 #
720 #
726 # If this become a problem, using a binary search should cap the
721 # If this become a problem, using a binary search should cap the
727 # runtime of this.
722 # runtime of this.
728 n = n - 1
723 n = n - 1
729 if n == 0:
724 if n == 0:
730 # no data to send
725 # no data to send
731 return []
726 return []
732 index_size = n * index.entry_size
727 index_size = n * index.entry_size
733 data_size = self.end(n - 1)
728 data_size = self.end(n - 1)
734
729
735 # XXX we might have been split (or stripped) since the object
730 # XXX we might have been split (or stripped) since the object
736 # initialization, We need to close this race too, but having a way to
731 # initialization, We need to close this race too, but having a way to
737 # pre-open the file we feed to the revlog and never closing them before
732 # pre-open the file we feed to the revlog and never closing them before
738 # we are done streaming.
733 # we are done streaming.
739
734
740 if self._inline:
735 if self._inline:
741
736
742 def get_stream():
737 def get_stream():
743 with self._indexfp() as fp:
738 with self._indexfp() as fp:
744 yield None
739 yield None
745 size = index_size + data_size
740 size = index_size + data_size
746 if size <= 65536:
741 if size <= 65536:
747 yield fp.read(size)
742 yield fp.read(size)
748 else:
743 else:
749 yield from util.filechunkiter(fp, limit=size)
744 yield from util.filechunkiter(fp, limit=size)
750
745
751 inline_stream = get_stream()
746 inline_stream = get_stream()
752 next(inline_stream)
747 next(inline_stream)
753 return [
748 return [
754 (self._indexfile, inline_stream, index_size + data_size),
749 (self._indexfile, inline_stream, index_size + data_size),
755 ]
750 ]
756 elif force_inline:
751 elif force_inline:
757
752
758 def get_stream():
753 def get_stream():
759 with self.reading():
754 with self.reading():
760 yield None
755 yield None
761
756
762 for rev in range(n):
757 for rev in range(n):
763 idx = self.index.entry_binary(rev)
758 idx = self.index.entry_binary(rev)
764 if rev == 0 and self._docket is None:
759 if rev == 0 and self._docket is None:
765 # re-inject the inline flag
760 # re-inject the inline flag
766 header = self._format_flags
761 header = self._format_flags
767 header |= self._format_version
762 header |= self._format_version
768 header |= FLAG_INLINE_DATA
763 header |= FLAG_INLINE_DATA
769 header = self.index.pack_header(header)
764 header = self.index.pack_header(header)
770 idx = header + idx
765 idx = header + idx
771 yield idx
766 yield idx
772 yield self._getsegmentforrevs(rev, rev)[1]
767 yield self._getsegmentforrevs(rev, rev)[1]
773
768
774 inline_stream = get_stream()
769 inline_stream = get_stream()
775 next(inline_stream)
770 next(inline_stream)
776 return [
771 return [
777 (self._indexfile, inline_stream, index_size + data_size),
772 (self._indexfile, inline_stream, index_size + data_size),
778 ]
773 ]
779 else:
774 else:
780
775
781 def get_index_stream():
776 def get_index_stream():
782 with self._indexfp() as fp:
777 with self._indexfp() as fp:
783 yield None
778 yield None
784 if index_size <= 65536:
779 if index_size <= 65536:
785 yield fp.read(index_size)
780 yield fp.read(index_size)
786 else:
781 else:
787 yield from util.filechunkiter(fp, limit=index_size)
782 yield from util.filechunkiter(fp, limit=index_size)
788
783
789 def get_data_stream():
784 def get_data_stream():
790 with self._datafp() as fp:
785 with self._datafp() as fp:
791 yield None
786 yield None
792 if data_size <= 65536:
787 if data_size <= 65536:
793 yield fp.read(data_size)
788 yield fp.read(data_size)
794 else:
789 else:
795 yield from util.filechunkiter(fp, limit=data_size)
790 yield from util.filechunkiter(fp, limit=data_size)
796
791
797 index_stream = get_index_stream()
792 index_stream = get_index_stream()
798 next(index_stream)
793 next(index_stream)
799 data_stream = get_data_stream()
794 data_stream = get_data_stream()
800 next(data_stream)
795 next(data_stream)
801 return [
796 return [
802 (self._datafile, data_stream, data_size),
797 (self._datafile, data_stream, data_size),
803 (self._indexfile, index_stream, index_size),
798 (self._indexfile, index_stream, index_size),
804 ]
799 ]
805
800
806 def _loadindex(self, docket=None):
801 def _loadindex(self, docket=None):
807
802
808 new_header, mmapindexthreshold, force_nodemap = self._init_opts()
803 new_header, mmapindexthreshold, force_nodemap = self._init_opts()
809
804
810 if self.postfix is not None:
805 if self.postfix is not None:
811 entry_point = b'%s.i.%s' % (self.radix, self.postfix)
806 entry_point = b'%s.i.%s' % (self.radix, self.postfix)
812 elif self._trypending and self.opener.exists(b'%s.i.a' % self.radix):
807 elif self._trypending and self.opener.exists(b'%s.i.a' % self.radix):
813 entry_point = b'%s.i.a' % self.radix
808 entry_point = b'%s.i.a' % self.radix
814 elif self._try_split and self.opener.exists(self._split_index_file):
809 elif self._try_split and self.opener.exists(self._split_index_file):
815 entry_point = self._split_index_file
810 entry_point = self._split_index_file
816 else:
811 else:
817 entry_point = b'%s.i' % self.radix
812 entry_point = b'%s.i' % self.radix
818
813
819 if docket is not None:
814 if docket is not None:
820 self._docket = docket
815 self._docket = docket
821 self._docket_file = entry_point
816 self._docket_file = entry_point
822 else:
817 else:
823 self._initempty = True
818 self._initempty = True
824 entry_data = self._get_data(entry_point, mmapindexthreshold)
819 entry_data = self._get_data(entry_point, mmapindexthreshold)
825 if len(entry_data) > 0:
820 if len(entry_data) > 0:
826 header = INDEX_HEADER.unpack(entry_data[:4])[0]
821 header = INDEX_HEADER.unpack(entry_data[:4])[0]
827 self._initempty = False
822 self._initempty = False
828 else:
823 else:
829 header = new_header
824 header = new_header
830
825
831 self._format_flags = header & ~0xFFFF
826 self._format_flags = header & ~0xFFFF
832 self._format_version = header & 0xFFFF
827 self._format_version = header & 0xFFFF
833
828
834 supported_flags = SUPPORTED_FLAGS.get(self._format_version)
829 supported_flags = SUPPORTED_FLAGS.get(self._format_version)
835 if supported_flags is None:
830 if supported_flags is None:
836 msg = _(b'unknown version (%d) in revlog %s')
831 msg = _(b'unknown version (%d) in revlog %s')
837 msg %= (self._format_version, self.display_id)
832 msg %= (self._format_version, self.display_id)
838 raise error.RevlogError(msg)
833 raise error.RevlogError(msg)
839 elif self._format_flags & ~supported_flags:
834 elif self._format_flags & ~supported_flags:
840 msg = _(b'unknown flags (%#04x) in version %d revlog %s')
835 msg = _(b'unknown flags (%#04x) in version %d revlog %s')
841 display_flag = self._format_flags >> 16
836 display_flag = self._format_flags >> 16
842 msg %= (display_flag, self._format_version, self.display_id)
837 msg %= (display_flag, self._format_version, self.display_id)
843 raise error.RevlogError(msg)
838 raise error.RevlogError(msg)
844
839
845 features = FEATURES_BY_VERSION[self._format_version]
840 features = FEATURES_BY_VERSION[self._format_version]
846 self._inline = features[b'inline'](self._format_flags)
841 self._inline = features[b'inline'](self._format_flags)
847 self.delta_config.general_delta = features[b'generaldelta'](
842 self.delta_config.general_delta = features[b'generaldelta'](
848 self._format_flags
843 self._format_flags
849 )
844 )
850 self.feature_config.has_side_data = features[b'sidedata']
845 self.feature_config.has_side_data = features[b'sidedata']
851
846
852 if not features[b'docket']:
847 if not features[b'docket']:
853 self._indexfile = entry_point
848 self._indexfile = entry_point
854 index_data = entry_data
849 index_data = entry_data
855 else:
850 else:
856 self._docket_file = entry_point
851 self._docket_file = entry_point
857 if self._initempty:
852 if self._initempty:
858 self._docket = docketutil.default_docket(self, header)
853 self._docket = docketutil.default_docket(self, header)
859 else:
854 else:
860 self._docket = docketutil.parse_docket(
855 self._docket = docketutil.parse_docket(
861 self, entry_data, use_pending=self._trypending
856 self, entry_data, use_pending=self._trypending
862 )
857 )
863
858
864 if self._docket is not None:
859 if self._docket is not None:
865 self._indexfile = self._docket.index_filepath()
860 self._indexfile = self._docket.index_filepath()
866 index_data = b''
861 index_data = b''
867 index_size = self._docket.index_end
862 index_size = self._docket.index_end
868 if index_size > 0:
863 if index_size > 0:
869 index_data = self._get_data(
864 index_data = self._get_data(
870 self._indexfile, mmapindexthreshold, size=index_size
865 self._indexfile, mmapindexthreshold, size=index_size
871 )
866 )
872 if len(index_data) < index_size:
867 if len(index_data) < index_size:
873 msg = _(b'too few index data for %s: got %d, expected %d')
868 msg = _(b'too few index data for %s: got %d, expected %d')
874 msg %= (self.display_id, len(index_data), index_size)
869 msg %= (self.display_id, len(index_data), index_size)
875 raise error.RevlogError(msg)
870 raise error.RevlogError(msg)
876
871
877 self._inline = False
872 self._inline = False
878 # generaldelta implied by version 2 revlogs.
873 # generaldelta implied by version 2 revlogs.
879 self.delta_config.general_delta = True
874 self.delta_config.general_delta = True
880 # the logic for persistent nodemap will be dealt with within the
875 # the logic for persistent nodemap will be dealt with within the
881 # main docket, so disable it for now.
876 # main docket, so disable it for now.
882 self._nodemap_file = None
877 self._nodemap_file = None
883
878
884 if self._docket is not None:
879 if self._docket is not None:
885 self._datafile = self._docket.data_filepath()
880 self._datafile = self._docket.data_filepath()
886 self._sidedatafile = self._docket.sidedata_filepath()
881 self._sidedatafile = self._docket.sidedata_filepath()
887 elif self.postfix is None:
882 elif self.postfix is None:
888 self._datafile = b'%s.d' % self.radix
883 self._datafile = b'%s.d' % self.radix
889 else:
884 else:
890 self._datafile = b'%s.d.%s' % (self.radix, self.postfix)
885 self._datafile = b'%s.d.%s' % (self.radix, self.postfix)
891
886
892 self.nodeconstants = sha1nodeconstants
887 self.nodeconstants = sha1nodeconstants
893 self.nullid = self.nodeconstants.nullid
888 self.nullid = self.nodeconstants.nullid
894
889
895 # sparse-revlog can't be on without general-delta (issue6056)
890 # sparse-revlog can't be on without general-delta (issue6056)
896 if not self._generaldelta:
891 if not self._generaldelta:
897 self.delta_config.sparse_revlog = False
892 self.delta_config.sparse_revlog = False
898
893
899 self._storedeltachains = True
894 self._storedeltachains = True
900
895
901 devel_nodemap = (
896 devel_nodemap = (
902 self._nodemap_file
897 self._nodemap_file
903 and force_nodemap
898 and force_nodemap
904 and parse_index_v1_nodemap is not None
899 and parse_index_v1_nodemap is not None
905 )
900 )
906
901
907 use_rust_index = False
902 use_rust_index = False
908 if rustrevlog is not None:
903 if rustrevlog is not None:
909 if self._nodemap_file is not None:
904 if self._nodemap_file is not None:
910 use_rust_index = True
905 use_rust_index = True
911 else:
906 else:
912 use_rust_index = self.opener.options.get(b'rust.index')
907 use_rust_index = self.opener.options.get(b'rust.index')
913
908
914 self._parse_index = parse_index_v1
909 self._parse_index = parse_index_v1
915 if self._format_version == REVLOGV0:
910 if self._format_version == REVLOGV0:
916 self._parse_index = revlogv0.parse_index_v0
911 self._parse_index = revlogv0.parse_index_v0
917 elif self._format_version == REVLOGV2:
912 elif self._format_version == REVLOGV2:
918 self._parse_index = parse_index_v2
913 self._parse_index = parse_index_v2
919 elif self._format_version == CHANGELOGV2:
914 elif self._format_version == CHANGELOGV2:
920 self._parse_index = parse_index_cl_v2
915 self._parse_index = parse_index_cl_v2
921 elif devel_nodemap:
916 elif devel_nodemap:
922 self._parse_index = parse_index_v1_nodemap
917 self._parse_index = parse_index_v1_nodemap
923 elif use_rust_index:
918 elif use_rust_index:
924 self._parse_index = parse_index_v1_mixed
919 self._parse_index = parse_index_v1_mixed
925 try:
920 try:
926 d = self._parse_index(index_data, self._inline)
921 d = self._parse_index(index_data, self._inline)
927 index, chunkcache = d
922 index, chunkcache = d
928 use_nodemap = (
923 use_nodemap = (
929 not self._inline
924 not self._inline
930 and self._nodemap_file is not None
925 and self._nodemap_file is not None
931 and hasattr(index, 'update_nodemap_data')
926 and hasattr(index, 'update_nodemap_data')
932 )
927 )
933 if use_nodemap:
928 if use_nodemap:
934 nodemap_data = nodemaputil.persisted_data(self)
929 nodemap_data = nodemaputil.persisted_data(self)
935 if nodemap_data is not None:
930 if nodemap_data is not None:
936 docket = nodemap_data[0]
931 docket = nodemap_data[0]
937 if (
932 if (
938 len(d[0]) > docket.tip_rev
933 len(d[0]) > docket.tip_rev
939 and d[0][docket.tip_rev][7] == docket.tip_node
934 and d[0][docket.tip_rev][7] == docket.tip_node
940 ):
935 ):
941 # no changelog tampering
936 # no changelog tampering
942 self._nodemap_docket = docket
937 self._nodemap_docket = docket
943 index.update_nodemap_data(*nodemap_data)
938 index.update_nodemap_data(*nodemap_data)
944 except (ValueError, IndexError):
939 except (ValueError, IndexError):
945 raise error.RevlogError(
940 raise error.RevlogError(
946 _(b"index %s is corrupted") % self.display_id
941 _(b"index %s is corrupted") % self.display_id
947 )
942 )
948 self.index = index
943 self.index = index
949 self._segmentfile = randomaccessfile.randomaccessfile(
944 self._segmentfile = randomaccessfile.randomaccessfile(
950 self.opener,
945 self.opener,
951 (self._indexfile if self._inline else self._datafile),
946 (self._indexfile if self._inline else self._datafile),
952 self._chunkcachesize,
947 self._chunkcachesize,
953 chunkcache,
948 chunkcache,
954 )
949 )
955 self._segmentfile_sidedata = randomaccessfile.randomaccessfile(
950 self._segmentfile_sidedata = randomaccessfile.randomaccessfile(
956 self.opener,
951 self.opener,
957 self._sidedatafile,
952 self._sidedatafile,
958 self._chunkcachesize,
953 self._chunkcachesize,
959 )
954 )
960 # revnum -> (chain-length, sum-delta-length)
955 # revnum -> (chain-length, sum-delta-length)
961 self._chaininfocache = util.lrucachedict(500)
956 self._chaininfocache = util.lrucachedict(500)
962 # revlog header -> revlog compressor
957 # revlog header -> revlog compressor
963 self._decompressors = {}
958 self._decompressors = {}
964
959
965 def get_revlog(self):
960 def get_revlog(self):
966 """simple function to mirror API of other not-really-revlog API"""
961 """simple function to mirror API of other not-really-revlog API"""
967 return self
962 return self
968
963
969 @util.propertycache
964 @util.propertycache
970 def revlog_kind(self):
965 def revlog_kind(self):
971 return self.target[0]
966 return self.target[0]
972
967
973 @util.propertycache
968 @util.propertycache
974 def display_id(self):
969 def display_id(self):
975 """The public facing "ID" of the revlog that we use in message"""
970 """The public facing "ID" of the revlog that we use in message"""
976 if self.revlog_kind == KIND_FILELOG:
971 if self.revlog_kind == KIND_FILELOG:
977 # Reference the file without the "data/" prefix, so it is familiar
972 # Reference the file without the "data/" prefix, so it is familiar
978 # to the user.
973 # to the user.
979 return self.target[1]
974 return self.target[1]
980 else:
975 else:
981 return self.radix
976 return self.radix
982
977
983 def _get_decompressor(self, t):
978 def _get_decompressor(self, t):
984 try:
979 try:
985 compressor = self._decompressors[t]
980 compressor = self._decompressors[t]
986 except KeyError:
981 except KeyError:
987 try:
982 try:
988 engine = util.compengines.forrevlogheader(t)
983 engine = util.compengines.forrevlogheader(t)
989 compressor = engine.revlogcompressor(self._compengineopts)
984 compressor = engine.revlogcompressor(self._compengineopts)
990 self._decompressors[t] = compressor
985 self._decompressors[t] = compressor
991 except KeyError:
986 except KeyError:
992 raise error.RevlogError(
987 raise error.RevlogError(
993 _(b'unknown compression type %s') % binascii.hexlify(t)
988 _(b'unknown compression type %s') % binascii.hexlify(t)
994 )
989 )
995 return compressor
990 return compressor
996
991
997 @util.propertycache
992 @util.propertycache
998 def _compressor(self):
993 def _compressor(self):
999 engine = util.compengines[self._compengine]
994 engine = util.compengines[self._compengine]
1000 return engine.revlogcompressor(self._compengineopts)
995 return engine.revlogcompressor(self._compengineopts)
1001
996
1002 @util.propertycache
997 @util.propertycache
1003 def _decompressor(self):
998 def _decompressor(self):
1004 """the default decompressor"""
999 """the default decompressor"""
1005 if self._docket is None:
1000 if self._docket is None:
1006 return None
1001 return None
1007 t = self._docket.default_compression_header
1002 t = self._docket.default_compression_header
1008 c = self._get_decompressor(t)
1003 c = self._get_decompressor(t)
1009 return c.decompress
1004 return c.decompress
1010
1005
1011 def _indexfp(self):
1006 def _indexfp(self):
1012 """file object for the revlog's index file"""
1007 """file object for the revlog's index file"""
1013 return self.opener(self._indexfile, mode=b"r")
1008 return self.opener(self._indexfile, mode=b"r")
1014
1009
1015 def __index_write_fp(self):
1010 def __index_write_fp(self):
1016 # You should not use this directly and use `_writing` instead
1011 # You should not use this directly and use `_writing` instead
1017 try:
1012 try:
1018 f = self.opener(
1013 f = self.opener(
1019 self._indexfile, mode=b"r+", checkambig=self._checkambig
1014 self._indexfile, mode=b"r+", checkambig=self._checkambig
1020 )
1015 )
1021 if self._docket is None:
1016 if self._docket is None:
1022 f.seek(0, os.SEEK_END)
1017 f.seek(0, os.SEEK_END)
1023 else:
1018 else:
1024 f.seek(self._docket.index_end, os.SEEK_SET)
1019 f.seek(self._docket.index_end, os.SEEK_SET)
1025 return f
1020 return f
1026 except FileNotFoundError:
1021 except FileNotFoundError:
1027 return self.opener(
1022 return self.opener(
1028 self._indexfile, mode=b"w+", checkambig=self._checkambig
1023 self._indexfile, mode=b"w+", checkambig=self._checkambig
1029 )
1024 )
1030
1025
1031 def __index_new_fp(self):
1026 def __index_new_fp(self):
1032 # You should not use this unless you are upgrading from inline revlog
1027 # You should not use this unless you are upgrading from inline revlog
1033 return self.opener(
1028 return self.opener(
1034 self._indexfile,
1029 self._indexfile,
1035 mode=b"w",
1030 mode=b"w",
1036 checkambig=self._checkambig,
1031 checkambig=self._checkambig,
1037 atomictemp=True,
1032 atomictemp=True,
1038 )
1033 )
1039
1034
1040 def _datafp(self, mode=b'r'):
1035 def _datafp(self, mode=b'r'):
1041 """file object for the revlog's data file"""
1036 """file object for the revlog's data file"""
1042 return self.opener(self._datafile, mode=mode)
1037 return self.opener(self._datafile, mode=mode)
1043
1038
1044 @contextlib.contextmanager
1039 @contextlib.contextmanager
1045 def _sidedatareadfp(self):
1040 def _sidedatareadfp(self):
1046 """file object suitable to read sidedata"""
1041 """file object suitable to read sidedata"""
1047 if self._writinghandles:
1042 if self._writinghandles:
1048 yield self._writinghandles[2]
1043 yield self._writinghandles[2]
1049 else:
1044 else:
1050 with self.opener(self._sidedatafile) as fp:
1045 with self.opener(self._sidedatafile) as fp:
1051 yield fp
1046 yield fp
1052
1047
1053 def tiprev(self):
1048 def tiprev(self):
1054 return len(self.index) - 1
1049 return len(self.index) - 1
1055
1050
1056 def tip(self):
1051 def tip(self):
1057 return self.node(self.tiprev())
1052 return self.node(self.tiprev())
1058
1053
1059 def __contains__(self, rev):
1054 def __contains__(self, rev):
1060 return 0 <= rev < len(self)
1055 return 0 <= rev < len(self)
1061
1056
1062 def __len__(self):
1057 def __len__(self):
1063 return len(self.index)
1058 return len(self.index)
1064
1059
1065 def __iter__(self):
1060 def __iter__(self):
1066 return iter(range(len(self)))
1061 return iter(range(len(self)))
1067
1062
1068 def revs(self, start=0, stop=None):
1063 def revs(self, start=0, stop=None):
1069 """iterate over all rev in this revlog (from start to stop)"""
1064 """iterate over all rev in this revlog (from start to stop)"""
1070 return storageutil.iterrevs(len(self), start=start, stop=stop)
1065 return storageutil.iterrevs(len(self), start=start, stop=stop)
1071
1066
1072 def hasnode(self, node):
1067 def hasnode(self, node):
1073 try:
1068 try:
1074 self.rev(node)
1069 self.rev(node)
1075 return True
1070 return True
1076 except KeyError:
1071 except KeyError:
1077 return False
1072 return False
1078
1073
1079 def _candelta(self, baserev, rev):
1074 def _candelta(self, baserev, rev):
1080 """whether two revisions (baserev, rev) can be delta-ed or not"""
1075 """whether two revisions (baserev, rev) can be delta-ed or not"""
1081 # Disable delta if either rev requires a content-changing flag
1076 # Disable delta if either rev requires a content-changing flag
1082 # processor (ex. LFS). This is because such flag processor can alter
1077 # processor (ex. LFS). This is because such flag processor can alter
1083 # the rawtext content that the delta will be based on, and two clients
1078 # the rawtext content that the delta will be based on, and two clients
1084 # could have a same revlog node with different flags (i.e. different
1079 # could have a same revlog node with different flags (i.e. different
1085 # rawtext contents) and the delta could be incompatible.
1080 # rawtext contents) and the delta could be incompatible.
1086 if (self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS) or (
1081 if (self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS) or (
1087 self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS
1082 self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS
1088 ):
1083 ):
1089 return False
1084 return False
1090 return True
1085 return True
1091
1086
1092 def update_caches(self, transaction):
1087 def update_caches(self, transaction):
1093 """update on disk cache
1088 """update on disk cache
1094
1089
1095 If a transaction is passed, the update may be delayed to transaction
1090 If a transaction is passed, the update may be delayed to transaction
1096 commit."""
1091 commit."""
1097 if self._nodemap_file is not None:
1092 if self._nodemap_file is not None:
1098 if transaction is None:
1093 if transaction is None:
1099 nodemaputil.update_persistent_nodemap(self)
1094 nodemaputil.update_persistent_nodemap(self)
1100 else:
1095 else:
1101 nodemaputil.setup_persistent_nodemap(transaction, self)
1096 nodemaputil.setup_persistent_nodemap(transaction, self)
1102
1097
1103 def clearcaches(self):
1098 def clearcaches(self):
1104 """Clear in-memory caches"""
1099 """Clear in-memory caches"""
1105 self._revisioncache = None
1100 self._revisioncache = None
1106 self._chainbasecache.clear()
1101 self._chainbasecache.clear()
1107 self._segmentfile.clear_cache()
1102 self._segmentfile.clear_cache()
1108 self._segmentfile_sidedata.clear_cache()
1103 self._segmentfile_sidedata.clear_cache()
1109 self._pcache = {}
1104 self._pcache = {}
1110 self._nodemap_docket = None
1105 self._nodemap_docket = None
1111 self.index.clearcaches()
1106 self.index.clearcaches()
1112 # The python code is the one responsible for validating the docket, we
1107 # The python code is the one responsible for validating the docket, we
1113 # end up having to refresh it here.
1108 # end up having to refresh it here.
1114 use_nodemap = (
1109 use_nodemap = (
1115 not self._inline
1110 not self._inline
1116 and self._nodemap_file is not None
1111 and self._nodemap_file is not None
1117 and hasattr(self.index, 'update_nodemap_data')
1112 and hasattr(self.index, 'update_nodemap_data')
1118 )
1113 )
1119 if use_nodemap:
1114 if use_nodemap:
1120 nodemap_data = nodemaputil.persisted_data(self)
1115 nodemap_data = nodemaputil.persisted_data(self)
1121 if nodemap_data is not None:
1116 if nodemap_data is not None:
1122 self._nodemap_docket = nodemap_data[0]
1117 self._nodemap_docket = nodemap_data[0]
1123 self.index.update_nodemap_data(*nodemap_data)
1118 self.index.update_nodemap_data(*nodemap_data)
1124
1119
1125 def rev(self, node):
1120 def rev(self, node):
1126 """return the revision number associated with a <nodeid>"""
1121 """return the revision number associated with a <nodeid>"""
1127 try:
1122 try:
1128 return self.index.rev(node)
1123 return self.index.rev(node)
1129 except TypeError:
1124 except TypeError:
1130 raise
1125 raise
1131 except error.RevlogError:
1126 except error.RevlogError:
1132 # parsers.c radix tree lookup failed
1127 # parsers.c radix tree lookup failed
1133 if (
1128 if (
1134 node == self.nodeconstants.wdirid
1129 node == self.nodeconstants.wdirid
1135 or node in self.nodeconstants.wdirfilenodeids
1130 or node in self.nodeconstants.wdirfilenodeids
1136 ):
1131 ):
1137 raise error.WdirUnsupported
1132 raise error.WdirUnsupported
1138 raise error.LookupError(node, self.display_id, _(b'no node'))
1133 raise error.LookupError(node, self.display_id, _(b'no node'))
1139
1134
1140 # Accessors for index entries.
1135 # Accessors for index entries.
1141
1136
1142 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
1137 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
1143 # are flags.
1138 # are flags.
1144 def start(self, rev):
1139 def start(self, rev):
1145 return int(self.index[rev][0] >> 16)
1140 return int(self.index[rev][0] >> 16)
1146
1141
1147 def sidedata_cut_off(self, rev):
1142 def sidedata_cut_off(self, rev):
1148 sd_cut_off = self.index[rev][8]
1143 sd_cut_off = self.index[rev][8]
1149 if sd_cut_off != 0:
1144 if sd_cut_off != 0:
1150 return sd_cut_off
1145 return sd_cut_off
1151 # This is some annoying dance, because entries without sidedata
1146 # This is some annoying dance, because entries without sidedata
1152 # currently use 0 as their ofsset. (instead of previous-offset +
1147 # currently use 0 as their ofsset. (instead of previous-offset +
1153 # previous-size)
1148 # previous-size)
1154 #
1149 #
1155 # We should reconsider this sidedata β†’ 0 sidata_offset policy.
1150 # We should reconsider this sidedata β†’ 0 sidata_offset policy.
1156 # In the meantime, we need this.
1151 # In the meantime, we need this.
1157 while 0 <= rev:
1152 while 0 <= rev:
1158 e = self.index[rev]
1153 e = self.index[rev]
1159 if e[9] != 0:
1154 if e[9] != 0:
1160 return e[8] + e[9]
1155 return e[8] + e[9]
1161 rev -= 1
1156 rev -= 1
1162 return 0
1157 return 0
1163
1158
1164 def flags(self, rev):
1159 def flags(self, rev):
1165 return self.index[rev][0] & 0xFFFF
1160 return self.index[rev][0] & 0xFFFF
1166
1161
1167 def length(self, rev):
1162 def length(self, rev):
1168 return self.index[rev][1]
1163 return self.index[rev][1]
1169
1164
1170 def sidedata_length(self, rev):
1165 def sidedata_length(self, rev):
1171 if not self.hassidedata:
1166 if not self.hassidedata:
1172 return 0
1167 return 0
1173 return self.index[rev][9]
1168 return self.index[rev][9]
1174
1169
1175 def rawsize(self, rev):
1170 def rawsize(self, rev):
1176 """return the length of the uncompressed text for a given revision"""
1171 """return the length of the uncompressed text for a given revision"""
1177 l = self.index[rev][2]
1172 l = self.index[rev][2]
1178 if l >= 0:
1173 if l >= 0:
1179 return l
1174 return l
1180
1175
1181 t = self.rawdata(rev)
1176 t = self.rawdata(rev)
1182 return len(t)
1177 return len(t)
1183
1178
1184 def size(self, rev):
1179 def size(self, rev):
1185 """length of non-raw text (processed by a "read" flag processor)"""
1180 """length of non-raw text (processed by a "read" flag processor)"""
1186 # fast path: if no "read" flag processor could change the content,
1181 # fast path: if no "read" flag processor could change the content,
1187 # size is rawsize. note: ELLIPSIS is known to not change the content.
1182 # size is rawsize. note: ELLIPSIS is known to not change the content.
1188 flags = self.flags(rev)
1183 flags = self.flags(rev)
1189 if flags & (flagutil.REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
1184 if flags & (flagutil.REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
1190 return self.rawsize(rev)
1185 return self.rawsize(rev)
1191
1186
1192 return len(self.revision(rev))
1187 return len(self.revision(rev))
1193
1188
1194 def fast_rank(self, rev):
1189 def fast_rank(self, rev):
1195 """Return the rank of a revision if already known, or None otherwise.
1190 """Return the rank of a revision if already known, or None otherwise.
1196
1191
1197 The rank of a revision is the size of the sub-graph it defines as a
1192 The rank of a revision is the size of the sub-graph it defines as a
1198 head. Equivalently, the rank of a revision `r` is the size of the set
1193 head. Equivalently, the rank of a revision `r` is the size of the set
1199 `ancestors(r)`, `r` included.
1194 `ancestors(r)`, `r` included.
1200
1195
1201 This method returns the rank retrieved from the revlog in constant
1196 This method returns the rank retrieved from the revlog in constant
1202 time. It makes no attempt at computing unknown values for versions of
1197 time. It makes no attempt at computing unknown values for versions of
1203 the revlog which do not persist the rank.
1198 the revlog which do not persist the rank.
1204 """
1199 """
1205 rank = self.index[rev][ENTRY_RANK]
1200 rank = self.index[rev][ENTRY_RANK]
1206 if self._format_version != CHANGELOGV2 or rank == RANK_UNKNOWN:
1201 if self._format_version != CHANGELOGV2 or rank == RANK_UNKNOWN:
1207 return None
1202 return None
1208 if rev == nullrev:
1203 if rev == nullrev:
1209 return 0 # convention
1204 return 0 # convention
1210 return rank
1205 return rank
1211
1206
1212 def chainbase(self, rev):
1207 def chainbase(self, rev):
1213 base = self._chainbasecache.get(rev)
1208 base = self._chainbasecache.get(rev)
1214 if base is not None:
1209 if base is not None:
1215 return base
1210 return base
1216
1211
1217 index = self.index
1212 index = self.index
1218 iterrev = rev
1213 iterrev = rev
1219 base = index[iterrev][3]
1214 base = index[iterrev][3]
1220 while base != iterrev:
1215 while base != iterrev:
1221 iterrev = base
1216 iterrev = base
1222 base = index[iterrev][3]
1217 base = index[iterrev][3]
1223
1218
1224 self._chainbasecache[rev] = base
1219 self._chainbasecache[rev] = base
1225 return base
1220 return base
1226
1221
1227 def linkrev(self, rev):
1222 def linkrev(self, rev):
1228 return self.index[rev][4]
1223 return self.index[rev][4]
1229
1224
1230 def parentrevs(self, rev):
1225 def parentrevs(self, rev):
1231 try:
1226 try:
1232 entry = self.index[rev]
1227 entry = self.index[rev]
1233 except IndexError:
1228 except IndexError:
1234 if rev == wdirrev:
1229 if rev == wdirrev:
1235 raise error.WdirUnsupported
1230 raise error.WdirUnsupported
1236 raise
1231 raise
1237
1232
1238 if self.canonical_parent_order and entry[5] == nullrev:
1233 if self.canonical_parent_order and entry[5] == nullrev:
1239 return entry[6], entry[5]
1234 return entry[6], entry[5]
1240 else:
1235 else:
1241 return entry[5], entry[6]
1236 return entry[5], entry[6]
1242
1237
1243 # fast parentrevs(rev) where rev isn't filtered
1238 # fast parentrevs(rev) where rev isn't filtered
1244 _uncheckedparentrevs = parentrevs
1239 _uncheckedparentrevs = parentrevs
1245
1240
1246 def node(self, rev):
1241 def node(self, rev):
1247 try:
1242 try:
1248 return self.index[rev][7]
1243 return self.index[rev][7]
1249 except IndexError:
1244 except IndexError:
1250 if rev == wdirrev:
1245 if rev == wdirrev:
1251 raise error.WdirUnsupported
1246 raise error.WdirUnsupported
1252 raise
1247 raise
1253
1248
1254 # Derived from index values.
1249 # Derived from index values.
1255
1250
1256 def end(self, rev):
1251 def end(self, rev):
1257 return self.start(rev) + self.length(rev)
1252 return self.start(rev) + self.length(rev)
1258
1253
1259 def parents(self, node):
1254 def parents(self, node):
1260 i = self.index
1255 i = self.index
1261 d = i[self.rev(node)]
1256 d = i[self.rev(node)]
1262 # inline node() to avoid function call overhead
1257 # inline node() to avoid function call overhead
1263 if self.canonical_parent_order and d[5] == self.nullid:
1258 if self.canonical_parent_order and d[5] == self.nullid:
1264 return i[d[6]][7], i[d[5]][7]
1259 return i[d[6]][7], i[d[5]][7]
1265 else:
1260 else:
1266 return i[d[5]][7], i[d[6]][7]
1261 return i[d[5]][7], i[d[6]][7]
1267
1262
1268 def chainlen(self, rev):
1263 def chainlen(self, rev):
1269 return self._chaininfo(rev)[0]
1264 return self._chaininfo(rev)[0]
1270
1265
1271 def _chaininfo(self, rev):
1266 def _chaininfo(self, rev):
1272 chaininfocache = self._chaininfocache
1267 chaininfocache = self._chaininfocache
1273 if rev in chaininfocache:
1268 if rev in chaininfocache:
1274 return chaininfocache[rev]
1269 return chaininfocache[rev]
1275 index = self.index
1270 index = self.index
1276 generaldelta = self._generaldelta
1271 generaldelta = self._generaldelta
1277 iterrev = rev
1272 iterrev = rev
1278 e = index[iterrev]
1273 e = index[iterrev]
1279 clen = 0
1274 clen = 0
1280 compresseddeltalen = 0
1275 compresseddeltalen = 0
1281 while iterrev != e[3]:
1276 while iterrev != e[3]:
1282 clen += 1
1277 clen += 1
1283 compresseddeltalen += e[1]
1278 compresseddeltalen += e[1]
1284 if generaldelta:
1279 if generaldelta:
1285 iterrev = e[3]
1280 iterrev = e[3]
1286 else:
1281 else:
1287 iterrev -= 1
1282 iterrev -= 1
1288 if iterrev in chaininfocache:
1283 if iterrev in chaininfocache:
1289 t = chaininfocache[iterrev]
1284 t = chaininfocache[iterrev]
1290 clen += t[0]
1285 clen += t[0]
1291 compresseddeltalen += t[1]
1286 compresseddeltalen += t[1]
1292 break
1287 break
1293 e = index[iterrev]
1288 e = index[iterrev]
1294 else:
1289 else:
1295 # Add text length of base since decompressing that also takes
1290 # Add text length of base since decompressing that also takes
1296 # work. For cache hits the length is already included.
1291 # work. For cache hits the length is already included.
1297 compresseddeltalen += e[1]
1292 compresseddeltalen += e[1]
1298 r = (clen, compresseddeltalen)
1293 r = (clen, compresseddeltalen)
1299 chaininfocache[rev] = r
1294 chaininfocache[rev] = r
1300 return r
1295 return r
1301
1296
1302 def _deltachain(self, rev, stoprev=None):
1297 def _deltachain(self, rev, stoprev=None):
1303 """Obtain the delta chain for a revision.
1298 """Obtain the delta chain for a revision.
1304
1299
1305 ``stoprev`` specifies a revision to stop at. If not specified, we
1300 ``stoprev`` specifies a revision to stop at. If not specified, we
1306 stop at the base of the chain.
1301 stop at the base of the chain.
1307
1302
1308 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
1303 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
1309 revs in ascending order and ``stopped`` is a bool indicating whether
1304 revs in ascending order and ``stopped`` is a bool indicating whether
1310 ``stoprev`` was hit.
1305 ``stoprev`` was hit.
1311 """
1306 """
1312 # Try C implementation.
1307 # Try C implementation.
1313 try:
1308 try:
1314 return self.index.deltachain(rev, stoprev, self._generaldelta)
1309 return self.index.deltachain(rev, stoprev, self._generaldelta)
1315 except AttributeError:
1310 except AttributeError:
1316 pass
1311 pass
1317
1312
1318 chain = []
1313 chain = []
1319
1314
1320 # Alias to prevent attribute lookup in tight loop.
1315 # Alias to prevent attribute lookup in tight loop.
1321 index = self.index
1316 index = self.index
1322 generaldelta = self._generaldelta
1317 generaldelta = self._generaldelta
1323
1318
1324 iterrev = rev
1319 iterrev = rev
1325 e = index[iterrev]
1320 e = index[iterrev]
1326 while iterrev != e[3] and iterrev != stoprev:
1321 while iterrev != e[3] and iterrev != stoprev:
1327 chain.append(iterrev)
1322 chain.append(iterrev)
1328 if generaldelta:
1323 if generaldelta:
1329 iterrev = e[3]
1324 iterrev = e[3]
1330 else:
1325 else:
1331 iterrev -= 1
1326 iterrev -= 1
1332 e = index[iterrev]
1327 e = index[iterrev]
1333
1328
1334 if iterrev == stoprev:
1329 if iterrev == stoprev:
1335 stopped = True
1330 stopped = True
1336 else:
1331 else:
1337 chain.append(iterrev)
1332 chain.append(iterrev)
1338 stopped = False
1333 stopped = False
1339
1334
1340 chain.reverse()
1335 chain.reverse()
1341 return chain, stopped
1336 return chain, stopped
1342
1337
1343 def ancestors(self, revs, stoprev=0, inclusive=False):
1338 def ancestors(self, revs, stoprev=0, inclusive=False):
1344 """Generate the ancestors of 'revs' in reverse revision order.
1339 """Generate the ancestors of 'revs' in reverse revision order.
1345 Does not generate revs lower than stoprev.
1340 Does not generate revs lower than stoprev.
1346
1341
1347 See the documentation for ancestor.lazyancestors for more details."""
1342 See the documentation for ancestor.lazyancestors for more details."""
1348
1343
1349 # first, make sure start revisions aren't filtered
1344 # first, make sure start revisions aren't filtered
1350 revs = list(revs)
1345 revs = list(revs)
1351 checkrev = self.node
1346 checkrev = self.node
1352 for r in revs:
1347 for r in revs:
1353 checkrev(r)
1348 checkrev(r)
1354 # and we're sure ancestors aren't filtered as well
1349 # and we're sure ancestors aren't filtered as well
1355
1350
1356 if rustancestor is not None and self.index.rust_ext_compat:
1351 if rustancestor is not None and self.index.rust_ext_compat:
1357 lazyancestors = rustancestor.LazyAncestors
1352 lazyancestors = rustancestor.LazyAncestors
1358 arg = self.index
1353 arg = self.index
1359 else:
1354 else:
1360 lazyancestors = ancestor.lazyancestors
1355 lazyancestors = ancestor.lazyancestors
1361 arg = self._uncheckedparentrevs
1356 arg = self._uncheckedparentrevs
1362 return lazyancestors(arg, revs, stoprev=stoprev, inclusive=inclusive)
1357 return lazyancestors(arg, revs, stoprev=stoprev, inclusive=inclusive)
1363
1358
1364 def descendants(self, revs):
1359 def descendants(self, revs):
1365 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
1360 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
1366
1361
1367 def findcommonmissing(self, common=None, heads=None):
1362 def findcommonmissing(self, common=None, heads=None):
1368 """Return a tuple of the ancestors of common and the ancestors of heads
1363 """Return a tuple of the ancestors of common and the ancestors of heads
1369 that are not ancestors of common. In revset terminology, we return the
1364 that are not ancestors of common. In revset terminology, we return the
1370 tuple:
1365 tuple:
1371
1366
1372 ::common, (::heads) - (::common)
1367 ::common, (::heads) - (::common)
1373
1368
1374 The list is sorted by revision number, meaning it is
1369 The list is sorted by revision number, meaning it is
1375 topologically sorted.
1370 topologically sorted.
1376
1371
1377 'heads' and 'common' are both lists of node IDs. If heads is
1372 'heads' and 'common' are both lists of node IDs. If heads is
1378 not supplied, uses all of the revlog's heads. If common is not
1373 not supplied, uses all of the revlog's heads. If common is not
1379 supplied, uses nullid."""
1374 supplied, uses nullid."""
1380 if common is None:
1375 if common is None:
1381 common = [self.nullid]
1376 common = [self.nullid]
1382 if heads is None:
1377 if heads is None:
1383 heads = self.heads()
1378 heads = self.heads()
1384
1379
1385 common = [self.rev(n) for n in common]
1380 common = [self.rev(n) for n in common]
1386 heads = [self.rev(n) for n in heads]
1381 heads = [self.rev(n) for n in heads]
1387
1382
1388 # we want the ancestors, but inclusive
1383 # we want the ancestors, but inclusive
1389 class lazyset:
1384 class lazyset:
1390 def __init__(self, lazyvalues):
1385 def __init__(self, lazyvalues):
1391 self.addedvalues = set()
1386 self.addedvalues = set()
1392 self.lazyvalues = lazyvalues
1387 self.lazyvalues = lazyvalues
1393
1388
1394 def __contains__(self, value):
1389 def __contains__(self, value):
1395 return value in self.addedvalues or value in self.lazyvalues
1390 return value in self.addedvalues or value in self.lazyvalues
1396
1391
1397 def __iter__(self):
1392 def __iter__(self):
1398 added = self.addedvalues
1393 added = self.addedvalues
1399 for r in added:
1394 for r in added:
1400 yield r
1395 yield r
1401 for r in self.lazyvalues:
1396 for r in self.lazyvalues:
1402 if not r in added:
1397 if not r in added:
1403 yield r
1398 yield r
1404
1399
1405 def add(self, value):
1400 def add(self, value):
1406 self.addedvalues.add(value)
1401 self.addedvalues.add(value)
1407
1402
1408 def update(self, values):
1403 def update(self, values):
1409 self.addedvalues.update(values)
1404 self.addedvalues.update(values)
1410
1405
1411 has = lazyset(self.ancestors(common))
1406 has = lazyset(self.ancestors(common))
1412 has.add(nullrev)
1407 has.add(nullrev)
1413 has.update(common)
1408 has.update(common)
1414
1409
1415 # take all ancestors from heads that aren't in has
1410 # take all ancestors from heads that aren't in has
1416 missing = set()
1411 missing = set()
1417 visit = collections.deque(r for r in heads if r not in has)
1412 visit = collections.deque(r for r in heads if r not in has)
1418 while visit:
1413 while visit:
1419 r = visit.popleft()
1414 r = visit.popleft()
1420 if r in missing:
1415 if r in missing:
1421 continue
1416 continue
1422 else:
1417 else:
1423 missing.add(r)
1418 missing.add(r)
1424 for p in self.parentrevs(r):
1419 for p in self.parentrevs(r):
1425 if p not in has:
1420 if p not in has:
1426 visit.append(p)
1421 visit.append(p)
1427 missing = list(missing)
1422 missing = list(missing)
1428 missing.sort()
1423 missing.sort()
1429 return has, [self.node(miss) for miss in missing]
1424 return has, [self.node(miss) for miss in missing]
1430
1425
1431 def incrementalmissingrevs(self, common=None):
1426 def incrementalmissingrevs(self, common=None):
1432 """Return an object that can be used to incrementally compute the
1427 """Return an object that can be used to incrementally compute the
1433 revision numbers of the ancestors of arbitrary sets that are not
1428 revision numbers of the ancestors of arbitrary sets that are not
1434 ancestors of common. This is an ancestor.incrementalmissingancestors
1429 ancestors of common. This is an ancestor.incrementalmissingancestors
1435 object.
1430 object.
1436
1431
1437 'common' is a list of revision numbers. If common is not supplied, uses
1432 'common' is a list of revision numbers. If common is not supplied, uses
1438 nullrev.
1433 nullrev.
1439 """
1434 """
1440 if common is None:
1435 if common is None:
1441 common = [nullrev]
1436 common = [nullrev]
1442
1437
1443 if rustancestor is not None and self.index.rust_ext_compat:
1438 if rustancestor is not None and self.index.rust_ext_compat:
1444 return rustancestor.MissingAncestors(self.index, common)
1439 return rustancestor.MissingAncestors(self.index, common)
1445 return ancestor.incrementalmissingancestors(self.parentrevs, common)
1440 return ancestor.incrementalmissingancestors(self.parentrevs, common)
1446
1441
1447 def findmissingrevs(self, common=None, heads=None):
1442 def findmissingrevs(self, common=None, heads=None):
1448 """Return the revision numbers of the ancestors of heads that
1443 """Return the revision numbers of the ancestors of heads that
1449 are not ancestors of common.
1444 are not ancestors of common.
1450
1445
1451 More specifically, return a list of revision numbers corresponding to
1446 More specifically, return a list of revision numbers corresponding to
1452 nodes N such that every N satisfies the following constraints:
1447 nodes N such that every N satisfies the following constraints:
1453
1448
1454 1. N is an ancestor of some node in 'heads'
1449 1. N is an ancestor of some node in 'heads'
1455 2. N is not an ancestor of any node in 'common'
1450 2. N is not an ancestor of any node in 'common'
1456
1451
1457 The list is sorted by revision number, meaning it is
1452 The list is sorted by revision number, meaning it is
1458 topologically sorted.
1453 topologically sorted.
1459
1454
1460 'heads' and 'common' are both lists of revision numbers. If heads is
1455 'heads' and 'common' are both lists of revision numbers. If heads is
1461 not supplied, uses all of the revlog's heads. If common is not
1456 not supplied, uses all of the revlog's heads. If common is not
1462 supplied, uses nullid."""
1457 supplied, uses nullid."""
1463 if common is None:
1458 if common is None:
1464 common = [nullrev]
1459 common = [nullrev]
1465 if heads is None:
1460 if heads is None:
1466 heads = self.headrevs()
1461 heads = self.headrevs()
1467
1462
1468 inc = self.incrementalmissingrevs(common=common)
1463 inc = self.incrementalmissingrevs(common=common)
1469 return inc.missingancestors(heads)
1464 return inc.missingancestors(heads)
1470
1465
1471 def findmissing(self, common=None, heads=None):
1466 def findmissing(self, common=None, heads=None):
1472 """Return the ancestors of heads that are not ancestors of common.
1467 """Return the ancestors of heads that are not ancestors of common.
1473
1468
1474 More specifically, return a list of nodes N such that every N
1469 More specifically, return a list of nodes N such that every N
1475 satisfies the following constraints:
1470 satisfies the following constraints:
1476
1471
1477 1. N is an ancestor of some node in 'heads'
1472 1. N is an ancestor of some node in 'heads'
1478 2. N is not an ancestor of any node in 'common'
1473 2. N is not an ancestor of any node in 'common'
1479
1474
1480 The list is sorted by revision number, meaning it is
1475 The list is sorted by revision number, meaning it is
1481 topologically sorted.
1476 topologically sorted.
1482
1477
1483 'heads' and 'common' are both lists of node IDs. If heads is
1478 'heads' and 'common' are both lists of node IDs. If heads is
1484 not supplied, uses all of the revlog's heads. If common is not
1479 not supplied, uses all of the revlog's heads. If common is not
1485 supplied, uses nullid."""
1480 supplied, uses nullid."""
1486 if common is None:
1481 if common is None:
1487 common = [self.nullid]
1482 common = [self.nullid]
1488 if heads is None:
1483 if heads is None:
1489 heads = self.heads()
1484 heads = self.heads()
1490
1485
1491 common = [self.rev(n) for n in common]
1486 common = [self.rev(n) for n in common]
1492 heads = [self.rev(n) for n in heads]
1487 heads = [self.rev(n) for n in heads]
1493
1488
1494 inc = self.incrementalmissingrevs(common=common)
1489 inc = self.incrementalmissingrevs(common=common)
1495 return [self.node(r) for r in inc.missingancestors(heads)]
1490 return [self.node(r) for r in inc.missingancestors(heads)]
1496
1491
1497 def nodesbetween(self, roots=None, heads=None):
1492 def nodesbetween(self, roots=None, heads=None):
1498 """Return a topological path from 'roots' to 'heads'.
1493 """Return a topological path from 'roots' to 'heads'.
1499
1494
1500 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
1495 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
1501 topologically sorted list of all nodes N that satisfy both of
1496 topologically sorted list of all nodes N that satisfy both of
1502 these constraints:
1497 these constraints:
1503
1498
1504 1. N is a descendant of some node in 'roots'
1499 1. N is a descendant of some node in 'roots'
1505 2. N is an ancestor of some node in 'heads'
1500 2. N is an ancestor of some node in 'heads'
1506
1501
1507 Every node is considered to be both a descendant and an ancestor
1502 Every node is considered to be both a descendant and an ancestor
1508 of itself, so every reachable node in 'roots' and 'heads' will be
1503 of itself, so every reachable node in 'roots' and 'heads' will be
1509 included in 'nodes'.
1504 included in 'nodes'.
1510
1505
1511 'outroots' is the list of reachable nodes in 'roots', i.e., the
1506 'outroots' is the list of reachable nodes in 'roots', i.e., the
1512 subset of 'roots' that is returned in 'nodes'. Likewise,
1507 subset of 'roots' that is returned in 'nodes'. Likewise,
1513 'outheads' is the subset of 'heads' that is also in 'nodes'.
1508 'outheads' is the subset of 'heads' that is also in 'nodes'.
1514
1509
1515 'roots' and 'heads' are both lists of node IDs. If 'roots' is
1510 'roots' and 'heads' are both lists of node IDs. If 'roots' is
1516 unspecified, uses nullid as the only root. If 'heads' is
1511 unspecified, uses nullid as the only root. If 'heads' is
1517 unspecified, uses list of all of the revlog's heads."""
1512 unspecified, uses list of all of the revlog's heads."""
1518 nonodes = ([], [], [])
1513 nonodes = ([], [], [])
1519 if roots is not None:
1514 if roots is not None:
1520 roots = list(roots)
1515 roots = list(roots)
1521 if not roots:
1516 if not roots:
1522 return nonodes
1517 return nonodes
1523 lowestrev = min([self.rev(n) for n in roots])
1518 lowestrev = min([self.rev(n) for n in roots])
1524 else:
1519 else:
1525 roots = [self.nullid] # Everybody's a descendant of nullid
1520 roots = [self.nullid] # Everybody's a descendant of nullid
1526 lowestrev = nullrev
1521 lowestrev = nullrev
1527 if (lowestrev == nullrev) and (heads is None):
1522 if (lowestrev == nullrev) and (heads is None):
1528 # We want _all_ the nodes!
1523 # We want _all_ the nodes!
1529 return (
1524 return (
1530 [self.node(r) for r in self],
1525 [self.node(r) for r in self],
1531 [self.nullid],
1526 [self.nullid],
1532 list(self.heads()),
1527 list(self.heads()),
1533 )
1528 )
1534 if heads is None:
1529 if heads is None:
1535 # All nodes are ancestors, so the latest ancestor is the last
1530 # All nodes are ancestors, so the latest ancestor is the last
1536 # node.
1531 # node.
1537 highestrev = len(self) - 1
1532 highestrev = len(self) - 1
1538 # Set ancestors to None to signal that every node is an ancestor.
1533 # Set ancestors to None to signal that every node is an ancestor.
1539 ancestors = None
1534 ancestors = None
1540 # Set heads to an empty dictionary for later discovery of heads
1535 # Set heads to an empty dictionary for later discovery of heads
1541 heads = {}
1536 heads = {}
1542 else:
1537 else:
1543 heads = list(heads)
1538 heads = list(heads)
1544 if not heads:
1539 if not heads:
1545 return nonodes
1540 return nonodes
1546 ancestors = set()
1541 ancestors = set()
1547 # Turn heads into a dictionary so we can remove 'fake' heads.
1542 # Turn heads into a dictionary so we can remove 'fake' heads.
1548 # Also, later we will be using it to filter out the heads we can't
1543 # Also, later we will be using it to filter out the heads we can't
1549 # find from roots.
1544 # find from roots.
1550 heads = dict.fromkeys(heads, False)
1545 heads = dict.fromkeys(heads, False)
1551 # Start at the top and keep marking parents until we're done.
1546 # Start at the top and keep marking parents until we're done.
1552 nodestotag = set(heads)
1547 nodestotag = set(heads)
1553 # Remember where the top was so we can use it as a limit later.
1548 # Remember where the top was so we can use it as a limit later.
1554 highestrev = max([self.rev(n) for n in nodestotag])
1549 highestrev = max([self.rev(n) for n in nodestotag])
1555 while nodestotag:
1550 while nodestotag:
1556 # grab a node to tag
1551 # grab a node to tag
1557 n = nodestotag.pop()
1552 n = nodestotag.pop()
1558 # Never tag nullid
1553 # Never tag nullid
1559 if n == self.nullid:
1554 if n == self.nullid:
1560 continue
1555 continue
1561 # A node's revision number represents its place in a
1556 # A node's revision number represents its place in a
1562 # topologically sorted list of nodes.
1557 # topologically sorted list of nodes.
1563 r = self.rev(n)
1558 r = self.rev(n)
1564 if r >= lowestrev:
1559 if r >= lowestrev:
1565 if n not in ancestors:
1560 if n not in ancestors:
1566 # If we are possibly a descendant of one of the roots
1561 # If we are possibly a descendant of one of the roots
1567 # and we haven't already been marked as an ancestor
1562 # and we haven't already been marked as an ancestor
1568 ancestors.add(n) # Mark as ancestor
1563 ancestors.add(n) # Mark as ancestor
1569 # Add non-nullid parents to list of nodes to tag.
1564 # Add non-nullid parents to list of nodes to tag.
1570 nodestotag.update(
1565 nodestotag.update(
1571 [p for p in self.parents(n) if p != self.nullid]
1566 [p for p in self.parents(n) if p != self.nullid]
1572 )
1567 )
1573 elif n in heads: # We've seen it before, is it a fake head?
1568 elif n in heads: # We've seen it before, is it a fake head?
1574 # So it is, real heads should not be the ancestors of
1569 # So it is, real heads should not be the ancestors of
1575 # any other heads.
1570 # any other heads.
1576 heads.pop(n)
1571 heads.pop(n)
1577 if not ancestors:
1572 if not ancestors:
1578 return nonodes
1573 return nonodes
1579 # Now that we have our set of ancestors, we want to remove any
1574 # Now that we have our set of ancestors, we want to remove any
1580 # roots that are not ancestors.
1575 # roots that are not ancestors.
1581
1576
1582 # If one of the roots was nullid, everything is included anyway.
1577 # If one of the roots was nullid, everything is included anyway.
1583 if lowestrev > nullrev:
1578 if lowestrev > nullrev:
1584 # But, since we weren't, let's recompute the lowest rev to not
1579 # But, since we weren't, let's recompute the lowest rev to not
1585 # include roots that aren't ancestors.
1580 # include roots that aren't ancestors.
1586
1581
1587 # Filter out roots that aren't ancestors of heads
1582 # Filter out roots that aren't ancestors of heads
1588 roots = [root for root in roots if root in ancestors]
1583 roots = [root for root in roots if root in ancestors]
1589 # Recompute the lowest revision
1584 # Recompute the lowest revision
1590 if roots:
1585 if roots:
1591 lowestrev = min([self.rev(root) for root in roots])
1586 lowestrev = min([self.rev(root) for root in roots])
1592 else:
1587 else:
1593 # No more roots? Return empty list
1588 # No more roots? Return empty list
1594 return nonodes
1589 return nonodes
1595 else:
1590 else:
1596 # We are descending from nullid, and don't need to care about
1591 # We are descending from nullid, and don't need to care about
1597 # any other roots.
1592 # any other roots.
1598 lowestrev = nullrev
1593 lowestrev = nullrev
1599 roots = [self.nullid]
1594 roots = [self.nullid]
1600 # Transform our roots list into a set.
1595 # Transform our roots list into a set.
1601 descendants = set(roots)
1596 descendants = set(roots)
1602 # Also, keep the original roots so we can filter out roots that aren't
1597 # Also, keep the original roots so we can filter out roots that aren't
1603 # 'real' roots (i.e. are descended from other roots).
1598 # 'real' roots (i.e. are descended from other roots).
1604 roots = descendants.copy()
1599 roots = descendants.copy()
1605 # Our topologically sorted list of output nodes.
1600 # Our topologically sorted list of output nodes.
1606 orderedout = []
1601 orderedout = []
1607 # Don't start at nullid since we don't want nullid in our output list,
1602 # Don't start at nullid since we don't want nullid in our output list,
1608 # and if nullid shows up in descendants, empty parents will look like
1603 # and if nullid shows up in descendants, empty parents will look like
1609 # they're descendants.
1604 # they're descendants.
1610 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1605 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1611 n = self.node(r)
1606 n = self.node(r)
1612 isdescendant = False
1607 isdescendant = False
1613 if lowestrev == nullrev: # Everybody is a descendant of nullid
1608 if lowestrev == nullrev: # Everybody is a descendant of nullid
1614 isdescendant = True
1609 isdescendant = True
1615 elif n in descendants:
1610 elif n in descendants:
1616 # n is already a descendant
1611 # n is already a descendant
1617 isdescendant = True
1612 isdescendant = True
1618 # This check only needs to be done here because all the roots
1613 # This check only needs to be done here because all the roots
1619 # will start being marked is descendants before the loop.
1614 # will start being marked is descendants before the loop.
1620 if n in roots:
1615 if n in roots:
1621 # If n was a root, check if it's a 'real' root.
1616 # If n was a root, check if it's a 'real' root.
1622 p = tuple(self.parents(n))
1617 p = tuple(self.parents(n))
1623 # If any of its parents are descendants, it's not a root.
1618 # If any of its parents are descendants, it's not a root.
1624 if (p[0] in descendants) or (p[1] in descendants):
1619 if (p[0] in descendants) or (p[1] in descendants):
1625 roots.remove(n)
1620 roots.remove(n)
1626 else:
1621 else:
1627 p = tuple(self.parents(n))
1622 p = tuple(self.parents(n))
1628 # A node is a descendant if either of its parents are
1623 # A node is a descendant if either of its parents are
1629 # descendants. (We seeded the dependents list with the roots
1624 # descendants. (We seeded the dependents list with the roots
1630 # up there, remember?)
1625 # up there, remember?)
1631 if (p[0] in descendants) or (p[1] in descendants):
1626 if (p[0] in descendants) or (p[1] in descendants):
1632 descendants.add(n)
1627 descendants.add(n)
1633 isdescendant = True
1628 isdescendant = True
1634 if isdescendant and ((ancestors is None) or (n in ancestors)):
1629 if isdescendant and ((ancestors is None) or (n in ancestors)):
1635 # Only include nodes that are both descendants and ancestors.
1630 # Only include nodes that are both descendants and ancestors.
1636 orderedout.append(n)
1631 orderedout.append(n)
1637 if (ancestors is not None) and (n in heads):
1632 if (ancestors is not None) and (n in heads):
1638 # We're trying to figure out which heads are reachable
1633 # We're trying to figure out which heads are reachable
1639 # from roots.
1634 # from roots.
1640 # Mark this head as having been reached
1635 # Mark this head as having been reached
1641 heads[n] = True
1636 heads[n] = True
1642 elif ancestors is None:
1637 elif ancestors is None:
1643 # Otherwise, we're trying to discover the heads.
1638 # Otherwise, we're trying to discover the heads.
1644 # Assume this is a head because if it isn't, the next step
1639 # Assume this is a head because if it isn't, the next step
1645 # will eventually remove it.
1640 # will eventually remove it.
1646 heads[n] = True
1641 heads[n] = True
1647 # But, obviously its parents aren't.
1642 # But, obviously its parents aren't.
1648 for p in self.parents(n):
1643 for p in self.parents(n):
1649 heads.pop(p, None)
1644 heads.pop(p, None)
1650 heads = [head for head, flag in heads.items() if flag]
1645 heads = [head for head, flag in heads.items() if flag]
1651 roots = list(roots)
1646 roots = list(roots)
1652 assert orderedout
1647 assert orderedout
1653 assert roots
1648 assert roots
1654 assert heads
1649 assert heads
1655 return (orderedout, roots, heads)
1650 return (orderedout, roots, heads)
1656
1651
1657 def headrevs(self, revs=None):
1652 def headrevs(self, revs=None):
1658 if revs is None:
1653 if revs is None:
1659 try:
1654 try:
1660 return self.index.headrevs()
1655 return self.index.headrevs()
1661 except AttributeError:
1656 except AttributeError:
1662 return self._headrevs()
1657 return self._headrevs()
1663 if rustdagop is not None and self.index.rust_ext_compat:
1658 if rustdagop is not None and self.index.rust_ext_compat:
1664 return rustdagop.headrevs(self.index, revs)
1659 return rustdagop.headrevs(self.index, revs)
1665 return dagop.headrevs(revs, self._uncheckedparentrevs)
1660 return dagop.headrevs(revs, self._uncheckedparentrevs)
1666
1661
1667 def computephases(self, roots):
1662 def computephases(self, roots):
1668 return self.index.computephasesmapsets(roots)
1663 return self.index.computephasesmapsets(roots)
1669
1664
1670 def _headrevs(self):
1665 def _headrevs(self):
1671 count = len(self)
1666 count = len(self)
1672 if not count:
1667 if not count:
1673 return [nullrev]
1668 return [nullrev]
1674 # we won't iter over filtered rev so nobody is a head at start
1669 # we won't iter over filtered rev so nobody is a head at start
1675 ishead = [0] * (count + 1)
1670 ishead = [0] * (count + 1)
1676 index = self.index
1671 index = self.index
1677 for r in self:
1672 for r in self:
1678 ishead[r] = 1 # I may be an head
1673 ishead[r] = 1 # I may be an head
1679 e = index[r]
1674 e = index[r]
1680 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1675 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1681 return [r for r, val in enumerate(ishead) if val]
1676 return [r for r, val in enumerate(ishead) if val]
1682
1677
1683 def heads(self, start=None, stop=None):
1678 def heads(self, start=None, stop=None):
1684 """return the list of all nodes that have no children
1679 """return the list of all nodes that have no children
1685
1680
1686 if start is specified, only heads that are descendants of
1681 if start is specified, only heads that are descendants of
1687 start will be returned
1682 start will be returned
1688 if stop is specified, it will consider all the revs from stop
1683 if stop is specified, it will consider all the revs from stop
1689 as if they had no children
1684 as if they had no children
1690 """
1685 """
1691 if start is None and stop is None:
1686 if start is None and stop is None:
1692 if not len(self):
1687 if not len(self):
1693 return [self.nullid]
1688 return [self.nullid]
1694 return [self.node(r) for r in self.headrevs()]
1689 return [self.node(r) for r in self.headrevs()]
1695
1690
1696 if start is None:
1691 if start is None:
1697 start = nullrev
1692 start = nullrev
1698 else:
1693 else:
1699 start = self.rev(start)
1694 start = self.rev(start)
1700
1695
1701 stoprevs = {self.rev(n) for n in stop or []}
1696 stoprevs = {self.rev(n) for n in stop or []}
1702
1697
1703 revs = dagop.headrevssubset(
1698 revs = dagop.headrevssubset(
1704 self.revs, self.parentrevs, startrev=start, stoprevs=stoprevs
1699 self.revs, self.parentrevs, startrev=start, stoprevs=stoprevs
1705 )
1700 )
1706
1701
1707 return [self.node(rev) for rev in revs]
1702 return [self.node(rev) for rev in revs]
1708
1703
1709 def children(self, node):
1704 def children(self, node):
1710 """find the children of a given node"""
1705 """find the children of a given node"""
1711 c = []
1706 c = []
1712 p = self.rev(node)
1707 p = self.rev(node)
1713 for r in self.revs(start=p + 1):
1708 for r in self.revs(start=p + 1):
1714 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1709 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1715 if prevs:
1710 if prevs:
1716 for pr in prevs:
1711 for pr in prevs:
1717 if pr == p:
1712 if pr == p:
1718 c.append(self.node(r))
1713 c.append(self.node(r))
1719 elif p == nullrev:
1714 elif p == nullrev:
1720 c.append(self.node(r))
1715 c.append(self.node(r))
1721 return c
1716 return c
1722
1717
1723 def commonancestorsheads(self, a, b):
1718 def commonancestorsheads(self, a, b):
1724 """calculate all the heads of the common ancestors of nodes a and b"""
1719 """calculate all the heads of the common ancestors of nodes a and b"""
1725 a, b = self.rev(a), self.rev(b)
1720 a, b = self.rev(a), self.rev(b)
1726 ancs = self._commonancestorsheads(a, b)
1721 ancs = self._commonancestorsheads(a, b)
1727 return pycompat.maplist(self.node, ancs)
1722 return pycompat.maplist(self.node, ancs)
1728
1723
1729 def _commonancestorsheads(self, *revs):
1724 def _commonancestorsheads(self, *revs):
1730 """calculate all the heads of the common ancestors of revs"""
1725 """calculate all the heads of the common ancestors of revs"""
1731 try:
1726 try:
1732 ancs = self.index.commonancestorsheads(*revs)
1727 ancs = self.index.commonancestorsheads(*revs)
1733 except (AttributeError, OverflowError): # C implementation failed
1728 except (AttributeError, OverflowError): # C implementation failed
1734 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1729 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1735 return ancs
1730 return ancs
1736
1731
1737 def isancestor(self, a, b):
1732 def isancestor(self, a, b):
1738 """return True if node a is an ancestor of node b
1733 """return True if node a is an ancestor of node b
1739
1734
1740 A revision is considered an ancestor of itself."""
1735 A revision is considered an ancestor of itself."""
1741 a, b = self.rev(a), self.rev(b)
1736 a, b = self.rev(a), self.rev(b)
1742 return self.isancestorrev(a, b)
1737 return self.isancestorrev(a, b)
1743
1738
1744 def isancestorrev(self, a, b):
1739 def isancestorrev(self, a, b):
1745 """return True if revision a is an ancestor of revision b
1740 """return True if revision a is an ancestor of revision b
1746
1741
1747 A revision is considered an ancestor of itself.
1742 A revision is considered an ancestor of itself.
1748
1743
1749 The implementation of this is trivial but the use of
1744 The implementation of this is trivial but the use of
1750 reachableroots is not."""
1745 reachableroots is not."""
1751 if a == nullrev:
1746 if a == nullrev:
1752 return True
1747 return True
1753 elif a == b:
1748 elif a == b:
1754 return True
1749 return True
1755 elif a > b:
1750 elif a > b:
1756 return False
1751 return False
1757 return bool(self.reachableroots(a, [b], [a], includepath=False))
1752 return bool(self.reachableroots(a, [b], [a], includepath=False))
1758
1753
1759 def reachableroots(self, minroot, heads, roots, includepath=False):
1754 def reachableroots(self, minroot, heads, roots, includepath=False):
1760 """return (heads(::(<roots> and <roots>::<heads>)))
1755 """return (heads(::(<roots> and <roots>::<heads>)))
1761
1756
1762 If includepath is True, return (<roots>::<heads>)."""
1757 If includepath is True, return (<roots>::<heads>)."""
1763 try:
1758 try:
1764 return self.index.reachableroots2(
1759 return self.index.reachableroots2(
1765 minroot, heads, roots, includepath
1760 minroot, heads, roots, includepath
1766 )
1761 )
1767 except AttributeError:
1762 except AttributeError:
1768 return dagop._reachablerootspure(
1763 return dagop._reachablerootspure(
1769 self.parentrevs, minroot, roots, heads, includepath
1764 self.parentrevs, minroot, roots, heads, includepath
1770 )
1765 )
1771
1766
1772 def ancestor(self, a, b):
1767 def ancestor(self, a, b):
1773 """calculate the "best" common ancestor of nodes a and b"""
1768 """calculate the "best" common ancestor of nodes a and b"""
1774
1769
1775 a, b = self.rev(a), self.rev(b)
1770 a, b = self.rev(a), self.rev(b)
1776 try:
1771 try:
1777 ancs = self.index.ancestors(a, b)
1772 ancs = self.index.ancestors(a, b)
1778 except (AttributeError, OverflowError):
1773 except (AttributeError, OverflowError):
1779 ancs = ancestor.ancestors(self.parentrevs, a, b)
1774 ancs = ancestor.ancestors(self.parentrevs, a, b)
1780 if ancs:
1775 if ancs:
1781 # choose a consistent winner when there's a tie
1776 # choose a consistent winner when there's a tie
1782 return min(map(self.node, ancs))
1777 return min(map(self.node, ancs))
1783 return self.nullid
1778 return self.nullid
1784
1779
1785 def _match(self, id):
1780 def _match(self, id):
1786 if isinstance(id, int):
1781 if isinstance(id, int):
1787 # rev
1782 # rev
1788 return self.node(id)
1783 return self.node(id)
1789 if len(id) == self.nodeconstants.nodelen:
1784 if len(id) == self.nodeconstants.nodelen:
1790 # possibly a binary node
1785 # possibly a binary node
1791 # odds of a binary node being all hex in ASCII are 1 in 10**25
1786 # odds of a binary node being all hex in ASCII are 1 in 10**25
1792 try:
1787 try:
1793 node = id
1788 node = id
1794 self.rev(node) # quick search the index
1789 self.rev(node) # quick search the index
1795 return node
1790 return node
1796 except error.LookupError:
1791 except error.LookupError:
1797 pass # may be partial hex id
1792 pass # may be partial hex id
1798 try:
1793 try:
1799 # str(rev)
1794 # str(rev)
1800 rev = int(id)
1795 rev = int(id)
1801 if b"%d" % rev != id:
1796 if b"%d" % rev != id:
1802 raise ValueError
1797 raise ValueError
1803 if rev < 0:
1798 if rev < 0:
1804 rev = len(self) + rev
1799 rev = len(self) + rev
1805 if rev < 0 or rev >= len(self):
1800 if rev < 0 or rev >= len(self):
1806 raise ValueError
1801 raise ValueError
1807 return self.node(rev)
1802 return self.node(rev)
1808 except (ValueError, OverflowError):
1803 except (ValueError, OverflowError):
1809 pass
1804 pass
1810 if len(id) == 2 * self.nodeconstants.nodelen:
1805 if len(id) == 2 * self.nodeconstants.nodelen:
1811 try:
1806 try:
1812 # a full hex nodeid?
1807 # a full hex nodeid?
1813 node = bin(id)
1808 node = bin(id)
1814 self.rev(node)
1809 self.rev(node)
1815 return node
1810 return node
1816 except (binascii.Error, error.LookupError):
1811 except (binascii.Error, error.LookupError):
1817 pass
1812 pass
1818
1813
1819 def _partialmatch(self, id):
1814 def _partialmatch(self, id):
1820 # we don't care wdirfilenodeids as they should be always full hash
1815 # we don't care wdirfilenodeids as they should be always full hash
1821 maybewdir = self.nodeconstants.wdirhex.startswith(id)
1816 maybewdir = self.nodeconstants.wdirhex.startswith(id)
1822 ambiguous = False
1817 ambiguous = False
1823 try:
1818 try:
1824 partial = self.index.partialmatch(id)
1819 partial = self.index.partialmatch(id)
1825 if partial and self.hasnode(partial):
1820 if partial and self.hasnode(partial):
1826 if maybewdir:
1821 if maybewdir:
1827 # single 'ff...' match in radix tree, ambiguous with wdir
1822 # single 'ff...' match in radix tree, ambiguous with wdir
1828 ambiguous = True
1823 ambiguous = True
1829 else:
1824 else:
1830 return partial
1825 return partial
1831 elif maybewdir:
1826 elif maybewdir:
1832 # no 'ff...' match in radix tree, wdir identified
1827 # no 'ff...' match in radix tree, wdir identified
1833 raise error.WdirUnsupported
1828 raise error.WdirUnsupported
1834 else:
1829 else:
1835 return None
1830 return None
1836 except error.RevlogError:
1831 except error.RevlogError:
1837 # parsers.c radix tree lookup gave multiple matches
1832 # parsers.c radix tree lookup gave multiple matches
1838 # fast path: for unfiltered changelog, radix tree is accurate
1833 # fast path: for unfiltered changelog, radix tree is accurate
1839 if not getattr(self, 'filteredrevs', None):
1834 if not getattr(self, 'filteredrevs', None):
1840 ambiguous = True
1835 ambiguous = True
1841 # fall through to slow path that filters hidden revisions
1836 # fall through to slow path that filters hidden revisions
1842 except (AttributeError, ValueError):
1837 except (AttributeError, ValueError):
1843 # we are pure python, or key is not hex
1838 # we are pure python, or key is not hex
1844 pass
1839 pass
1845 if ambiguous:
1840 if ambiguous:
1846 raise error.AmbiguousPrefixLookupError(
1841 raise error.AmbiguousPrefixLookupError(
1847 id, self.display_id, _(b'ambiguous identifier')
1842 id, self.display_id, _(b'ambiguous identifier')
1848 )
1843 )
1849
1844
1850 if id in self._pcache:
1845 if id in self._pcache:
1851 return self._pcache[id]
1846 return self._pcache[id]
1852
1847
1853 if len(id) <= 40:
1848 if len(id) <= 40:
1854 # hex(node)[:...]
1849 # hex(node)[:...]
1855 l = len(id) // 2 * 2 # grab an even number of digits
1850 l = len(id) // 2 * 2 # grab an even number of digits
1856 try:
1851 try:
1857 # we're dropping the last digit, so let's check that it's hex,
1852 # we're dropping the last digit, so let's check that it's hex,
1858 # to avoid the expensive computation below if it's not
1853 # to avoid the expensive computation below if it's not
1859 if len(id) % 2 > 0:
1854 if len(id) % 2 > 0:
1860 if not (id[-1] in hexdigits):
1855 if not (id[-1] in hexdigits):
1861 return None
1856 return None
1862 prefix = bin(id[:l])
1857 prefix = bin(id[:l])
1863 except binascii.Error:
1858 except binascii.Error:
1864 pass
1859 pass
1865 else:
1860 else:
1866 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1861 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1867 nl = [
1862 nl = [
1868 n for n in nl if hex(n).startswith(id) and self.hasnode(n)
1863 n for n in nl if hex(n).startswith(id) and self.hasnode(n)
1869 ]
1864 ]
1870 if self.nodeconstants.nullhex.startswith(id):
1865 if self.nodeconstants.nullhex.startswith(id):
1871 nl.append(self.nullid)
1866 nl.append(self.nullid)
1872 if len(nl) > 0:
1867 if len(nl) > 0:
1873 if len(nl) == 1 and not maybewdir:
1868 if len(nl) == 1 and not maybewdir:
1874 self._pcache[id] = nl[0]
1869 self._pcache[id] = nl[0]
1875 return nl[0]
1870 return nl[0]
1876 raise error.AmbiguousPrefixLookupError(
1871 raise error.AmbiguousPrefixLookupError(
1877 id, self.display_id, _(b'ambiguous identifier')
1872 id, self.display_id, _(b'ambiguous identifier')
1878 )
1873 )
1879 if maybewdir:
1874 if maybewdir:
1880 raise error.WdirUnsupported
1875 raise error.WdirUnsupported
1881 return None
1876 return None
1882
1877
1883 def lookup(self, id):
1878 def lookup(self, id):
1884 """locate a node based on:
1879 """locate a node based on:
1885 - revision number or str(revision number)
1880 - revision number or str(revision number)
1886 - nodeid or subset of hex nodeid
1881 - nodeid or subset of hex nodeid
1887 """
1882 """
1888 n = self._match(id)
1883 n = self._match(id)
1889 if n is not None:
1884 if n is not None:
1890 return n
1885 return n
1891 n = self._partialmatch(id)
1886 n = self._partialmatch(id)
1892 if n:
1887 if n:
1893 return n
1888 return n
1894
1889
1895 raise error.LookupError(id, self.display_id, _(b'no match found'))
1890 raise error.LookupError(id, self.display_id, _(b'no match found'))
1896
1891
1897 def shortest(self, node, minlength=1):
1892 def shortest(self, node, minlength=1):
1898 """Find the shortest unambiguous prefix that matches node."""
1893 """Find the shortest unambiguous prefix that matches node."""
1899
1894
1900 def isvalid(prefix):
1895 def isvalid(prefix):
1901 try:
1896 try:
1902 matchednode = self._partialmatch(prefix)
1897 matchednode = self._partialmatch(prefix)
1903 except error.AmbiguousPrefixLookupError:
1898 except error.AmbiguousPrefixLookupError:
1904 return False
1899 return False
1905 except error.WdirUnsupported:
1900 except error.WdirUnsupported:
1906 # single 'ff...' match
1901 # single 'ff...' match
1907 return True
1902 return True
1908 if matchednode is None:
1903 if matchednode is None:
1909 raise error.LookupError(node, self.display_id, _(b'no node'))
1904 raise error.LookupError(node, self.display_id, _(b'no node'))
1910 return True
1905 return True
1911
1906
1912 def maybewdir(prefix):
1907 def maybewdir(prefix):
1913 return all(c == b'f' for c in pycompat.iterbytestr(prefix))
1908 return all(c == b'f' for c in pycompat.iterbytestr(prefix))
1914
1909
1915 hexnode = hex(node)
1910 hexnode = hex(node)
1916
1911
1917 def disambiguate(hexnode, minlength):
1912 def disambiguate(hexnode, minlength):
1918 """Disambiguate against wdirid."""
1913 """Disambiguate against wdirid."""
1919 for length in range(minlength, len(hexnode) + 1):
1914 for length in range(minlength, len(hexnode) + 1):
1920 prefix = hexnode[:length]
1915 prefix = hexnode[:length]
1921 if not maybewdir(prefix):
1916 if not maybewdir(prefix):
1922 return prefix
1917 return prefix
1923
1918
1924 if not getattr(self, 'filteredrevs', None):
1919 if not getattr(self, 'filteredrevs', None):
1925 try:
1920 try:
1926 length = max(self.index.shortest(node), minlength)
1921 length = max(self.index.shortest(node), minlength)
1927 return disambiguate(hexnode, length)
1922 return disambiguate(hexnode, length)
1928 except error.RevlogError:
1923 except error.RevlogError:
1929 if node != self.nodeconstants.wdirid:
1924 if node != self.nodeconstants.wdirid:
1930 raise error.LookupError(
1925 raise error.LookupError(
1931 node, self.display_id, _(b'no node')
1926 node, self.display_id, _(b'no node')
1932 )
1927 )
1933 except AttributeError:
1928 except AttributeError:
1934 # Fall through to pure code
1929 # Fall through to pure code
1935 pass
1930 pass
1936
1931
1937 if node == self.nodeconstants.wdirid:
1932 if node == self.nodeconstants.wdirid:
1938 for length in range(minlength, len(hexnode) + 1):
1933 for length in range(minlength, len(hexnode) + 1):
1939 prefix = hexnode[:length]
1934 prefix = hexnode[:length]
1940 if isvalid(prefix):
1935 if isvalid(prefix):
1941 return prefix
1936 return prefix
1942
1937
1943 for length in range(minlength, len(hexnode) + 1):
1938 for length in range(minlength, len(hexnode) + 1):
1944 prefix = hexnode[:length]
1939 prefix = hexnode[:length]
1945 if isvalid(prefix):
1940 if isvalid(prefix):
1946 return disambiguate(hexnode, length)
1941 return disambiguate(hexnode, length)
1947
1942
1948 def cmp(self, node, text):
1943 def cmp(self, node, text):
1949 """compare text with a given file revision
1944 """compare text with a given file revision
1950
1945
1951 returns True if text is different than what is stored.
1946 returns True if text is different than what is stored.
1952 """
1947 """
1953 p1, p2 = self.parents(node)
1948 p1, p2 = self.parents(node)
1954 return storageutil.hashrevisionsha1(text, p1, p2) != node
1949 return storageutil.hashrevisionsha1(text, p1, p2) != node
1955
1950
1956 def _getsegmentforrevs(self, startrev, endrev):
1951 def _getsegmentforrevs(self, startrev, endrev):
1957 """Obtain a segment of raw data corresponding to a range of revisions.
1952 """Obtain a segment of raw data corresponding to a range of revisions.
1958
1953
1959 Accepts the start and end revisions and an optional already-open
1954 Accepts the start and end revisions and an optional already-open
1960 file handle to be used for reading. If the file handle is read, its
1955 file handle to be used for reading. If the file handle is read, its
1961 seek position will not be preserved.
1956 seek position will not be preserved.
1962
1957
1963 Requests for data may be satisfied by a cache.
1958 Requests for data may be satisfied by a cache.
1964
1959
1965 Returns a 2-tuple of (offset, data) for the requested range of
1960 Returns a 2-tuple of (offset, data) for the requested range of
1966 revisions. Offset is the integer offset from the beginning of the
1961 revisions. Offset is the integer offset from the beginning of the
1967 revlog and data is a str or buffer of the raw byte data.
1962 revlog and data is a str or buffer of the raw byte data.
1968
1963
1969 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1964 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1970 to determine where each revision's data begins and ends.
1965 to determine where each revision's data begins and ends.
1971 """
1966 """
1972 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1967 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1973 # (functions are expensive).
1968 # (functions are expensive).
1974 index = self.index
1969 index = self.index
1975 istart = index[startrev]
1970 istart = index[startrev]
1976 start = int(istart[0] >> 16)
1971 start = int(istart[0] >> 16)
1977 if startrev == endrev:
1972 if startrev == endrev:
1978 end = start + istart[1]
1973 end = start + istart[1]
1979 else:
1974 else:
1980 iend = index[endrev]
1975 iend = index[endrev]
1981 end = int(iend[0] >> 16) + iend[1]
1976 end = int(iend[0] >> 16) + iend[1]
1982
1977
1983 if self._inline:
1978 if self._inline:
1984 start += (startrev + 1) * self.index.entry_size
1979 start += (startrev + 1) * self.index.entry_size
1985 end += (endrev + 1) * self.index.entry_size
1980 end += (endrev + 1) * self.index.entry_size
1986 length = end - start
1981 length = end - start
1987
1982
1988 return start, self._segmentfile.read_chunk(start, length)
1983 return start, self._segmentfile.read_chunk(start, length)
1989
1984
1990 def _chunk(self, rev):
1985 def _chunk(self, rev):
1991 """Obtain a single decompressed chunk for a revision.
1986 """Obtain a single decompressed chunk for a revision.
1992
1987
1993 Accepts an integer revision and an optional already-open file handle
1988 Accepts an integer revision and an optional already-open file handle
1994 to be used for reading. If used, the seek position of the file will not
1989 to be used for reading. If used, the seek position of the file will not
1995 be preserved.
1990 be preserved.
1996
1991
1997 Returns a str holding uncompressed data for the requested revision.
1992 Returns a str holding uncompressed data for the requested revision.
1998 """
1993 """
1999 compression_mode = self.index[rev][10]
1994 compression_mode = self.index[rev][10]
2000 data = self._getsegmentforrevs(rev, rev)[1]
1995 data = self._getsegmentforrevs(rev, rev)[1]
2001 if compression_mode == COMP_MODE_PLAIN:
1996 if compression_mode == COMP_MODE_PLAIN:
2002 return data
1997 return data
2003 elif compression_mode == COMP_MODE_DEFAULT:
1998 elif compression_mode == COMP_MODE_DEFAULT:
2004 return self._decompressor(data)
1999 return self._decompressor(data)
2005 elif compression_mode == COMP_MODE_INLINE:
2000 elif compression_mode == COMP_MODE_INLINE:
2006 return self.decompress(data)
2001 return self.decompress(data)
2007 else:
2002 else:
2008 msg = b'unknown compression mode %d'
2003 msg = b'unknown compression mode %d'
2009 msg %= compression_mode
2004 msg %= compression_mode
2010 raise error.RevlogError(msg)
2005 raise error.RevlogError(msg)
2011
2006
2012 def _chunks(self, revs, targetsize=None):
2007 def _chunks(self, revs, targetsize=None):
2013 """Obtain decompressed chunks for the specified revisions.
2008 """Obtain decompressed chunks for the specified revisions.
2014
2009
2015 Accepts an iterable of numeric revisions that are assumed to be in
2010 Accepts an iterable of numeric revisions that are assumed to be in
2016 ascending order. Also accepts an optional already-open file handle
2011 ascending order. Also accepts an optional already-open file handle
2017 to be used for reading. If used, the seek position of the file will
2012 to be used for reading. If used, the seek position of the file will
2018 not be preserved.
2013 not be preserved.
2019
2014
2020 This function is similar to calling ``self._chunk()`` multiple times,
2015 This function is similar to calling ``self._chunk()`` multiple times,
2021 but is faster.
2016 but is faster.
2022
2017
2023 Returns a list with decompressed data for each requested revision.
2018 Returns a list with decompressed data for each requested revision.
2024 """
2019 """
2025 if not revs:
2020 if not revs:
2026 return []
2021 return []
2027 start = self.start
2022 start = self.start
2028 length = self.length
2023 length = self.length
2029 inline = self._inline
2024 inline = self._inline
2030 iosize = self.index.entry_size
2025 iosize = self.index.entry_size
2031 buffer = util.buffer
2026 buffer = util.buffer
2032
2027
2033 l = []
2028 l = []
2034 ladd = l.append
2029 ladd = l.append
2035
2030
2036 if not self._withsparseread:
2031 if not self._withsparseread:
2037 slicedchunks = (revs,)
2032 slicedchunks = (revs,)
2038 else:
2033 else:
2039 slicedchunks = deltautil.slicechunk(
2034 slicedchunks = deltautil.slicechunk(
2040 self, revs, targetsize=targetsize
2035 self, revs, targetsize=targetsize
2041 )
2036 )
2042
2037
2043 for revschunk in slicedchunks:
2038 for revschunk in slicedchunks:
2044 firstrev = revschunk[0]
2039 firstrev = revschunk[0]
2045 # Skip trailing revisions with empty diff
2040 # Skip trailing revisions with empty diff
2046 for lastrev in revschunk[::-1]:
2041 for lastrev in revschunk[::-1]:
2047 if length(lastrev) != 0:
2042 if length(lastrev) != 0:
2048 break
2043 break
2049
2044
2050 try:
2045 try:
2051 offset, data = self._getsegmentforrevs(firstrev, lastrev)
2046 offset, data = self._getsegmentforrevs(firstrev, lastrev)
2052 except OverflowError:
2047 except OverflowError:
2053 # issue4215 - we can't cache a run of chunks greater than
2048 # issue4215 - we can't cache a run of chunks greater than
2054 # 2G on Windows
2049 # 2G on Windows
2055 return [self._chunk(rev) for rev in revschunk]
2050 return [self._chunk(rev) for rev in revschunk]
2056
2051
2057 decomp = self.decompress
2052 decomp = self.decompress
2058 # self._decompressor might be None, but will not be used in that case
2053 # self._decompressor might be None, but will not be used in that case
2059 def_decomp = self._decompressor
2054 def_decomp = self._decompressor
2060 for rev in revschunk:
2055 for rev in revschunk:
2061 chunkstart = start(rev)
2056 chunkstart = start(rev)
2062 if inline:
2057 if inline:
2063 chunkstart += (rev + 1) * iosize
2058 chunkstart += (rev + 1) * iosize
2064 chunklength = length(rev)
2059 chunklength = length(rev)
2065 comp_mode = self.index[rev][10]
2060 comp_mode = self.index[rev][10]
2066 c = buffer(data, chunkstart - offset, chunklength)
2061 c = buffer(data, chunkstart - offset, chunklength)
2067 if comp_mode == COMP_MODE_PLAIN:
2062 if comp_mode == COMP_MODE_PLAIN:
2068 ladd(c)
2063 ladd(c)
2069 elif comp_mode == COMP_MODE_INLINE:
2064 elif comp_mode == COMP_MODE_INLINE:
2070 ladd(decomp(c))
2065 ladd(decomp(c))
2071 elif comp_mode == COMP_MODE_DEFAULT:
2066 elif comp_mode == COMP_MODE_DEFAULT:
2072 ladd(def_decomp(c))
2067 ladd(def_decomp(c))
2073 else:
2068 else:
2074 msg = b'unknown compression mode %d'
2069 msg = b'unknown compression mode %d'
2075 msg %= comp_mode
2070 msg %= comp_mode
2076 raise error.RevlogError(msg)
2071 raise error.RevlogError(msg)
2077
2072
2078 return l
2073 return l
2079
2074
2080 def deltaparent(self, rev):
2075 def deltaparent(self, rev):
2081 """return deltaparent of the given revision"""
2076 """return deltaparent of the given revision"""
2082 base = self.index[rev][3]
2077 base = self.index[rev][3]
2083 if base == rev:
2078 if base == rev:
2084 return nullrev
2079 return nullrev
2085 elif self._generaldelta:
2080 elif self._generaldelta:
2086 return base
2081 return base
2087 else:
2082 else:
2088 return rev - 1
2083 return rev - 1
2089
2084
2090 def issnapshot(self, rev):
2085 def issnapshot(self, rev):
2091 """tells whether rev is a snapshot"""
2086 """tells whether rev is a snapshot"""
2092 if not self._sparserevlog:
2087 if not self._sparserevlog:
2093 return self.deltaparent(rev) == nullrev
2088 return self.deltaparent(rev) == nullrev
2094 elif hasattr(self.index, 'issnapshot'):
2089 elif hasattr(self.index, 'issnapshot'):
2095 # directly assign the method to cache the testing and access
2090 # directly assign the method to cache the testing and access
2096 self.issnapshot = self.index.issnapshot
2091 self.issnapshot = self.index.issnapshot
2097 return self.issnapshot(rev)
2092 return self.issnapshot(rev)
2098 if rev == nullrev:
2093 if rev == nullrev:
2099 return True
2094 return True
2100 entry = self.index[rev]
2095 entry = self.index[rev]
2101 base = entry[3]
2096 base = entry[3]
2102 if base == rev:
2097 if base == rev:
2103 return True
2098 return True
2104 if base == nullrev:
2099 if base == nullrev:
2105 return True
2100 return True
2106 p1 = entry[5]
2101 p1 = entry[5]
2107 while self.length(p1) == 0:
2102 while self.length(p1) == 0:
2108 b = self.deltaparent(p1)
2103 b = self.deltaparent(p1)
2109 if b == p1:
2104 if b == p1:
2110 break
2105 break
2111 p1 = b
2106 p1 = b
2112 p2 = entry[6]
2107 p2 = entry[6]
2113 while self.length(p2) == 0:
2108 while self.length(p2) == 0:
2114 b = self.deltaparent(p2)
2109 b = self.deltaparent(p2)
2115 if b == p2:
2110 if b == p2:
2116 break
2111 break
2117 p2 = b
2112 p2 = b
2118 if base == p1 or base == p2:
2113 if base == p1 or base == p2:
2119 return False
2114 return False
2120 return self.issnapshot(base)
2115 return self.issnapshot(base)
2121
2116
2122 def snapshotdepth(self, rev):
2117 def snapshotdepth(self, rev):
2123 """number of snapshot in the chain before this one"""
2118 """number of snapshot in the chain before this one"""
2124 if not self.issnapshot(rev):
2119 if not self.issnapshot(rev):
2125 raise error.ProgrammingError(b'revision %d not a snapshot')
2120 raise error.ProgrammingError(b'revision %d not a snapshot')
2126 return len(self._deltachain(rev)[0]) - 1
2121 return len(self._deltachain(rev)[0]) - 1
2127
2122
2128 def revdiff(self, rev1, rev2):
2123 def revdiff(self, rev1, rev2):
2129 """return or calculate a delta between two revisions
2124 """return or calculate a delta between two revisions
2130
2125
2131 The delta calculated is in binary form and is intended to be written to
2126 The delta calculated is in binary form and is intended to be written to
2132 revlog data directly. So this function needs raw revision data.
2127 revlog data directly. So this function needs raw revision data.
2133 """
2128 """
2134 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
2129 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
2135 return bytes(self._chunk(rev2))
2130 return bytes(self._chunk(rev2))
2136
2131
2137 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
2132 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
2138
2133
2139 def revision(self, nodeorrev):
2134 def revision(self, nodeorrev):
2140 """return an uncompressed revision of a given node or revision
2135 """return an uncompressed revision of a given node or revision
2141 number.
2136 number.
2142 """
2137 """
2143 return self._revisiondata(nodeorrev)
2138 return self._revisiondata(nodeorrev)
2144
2139
2145 def sidedata(self, nodeorrev):
2140 def sidedata(self, nodeorrev):
2146 """a map of extra data related to the changeset but not part of the hash
2141 """a map of extra data related to the changeset but not part of the hash
2147
2142
2148 This function currently return a dictionary. However, more advanced
2143 This function currently return a dictionary. However, more advanced
2149 mapping object will likely be used in the future for a more
2144 mapping object will likely be used in the future for a more
2150 efficient/lazy code.
2145 efficient/lazy code.
2151 """
2146 """
2152 # deal with <nodeorrev> argument type
2147 # deal with <nodeorrev> argument type
2153 if isinstance(nodeorrev, int):
2148 if isinstance(nodeorrev, int):
2154 rev = nodeorrev
2149 rev = nodeorrev
2155 else:
2150 else:
2156 rev = self.rev(nodeorrev)
2151 rev = self.rev(nodeorrev)
2157 return self._sidedata(rev)
2152 return self._sidedata(rev)
2158
2153
2159 def _revisiondata(self, nodeorrev, raw=False):
2154 def _revisiondata(self, nodeorrev, raw=False):
2160 # deal with <nodeorrev> argument type
2155 # deal with <nodeorrev> argument type
2161 if isinstance(nodeorrev, int):
2156 if isinstance(nodeorrev, int):
2162 rev = nodeorrev
2157 rev = nodeorrev
2163 node = self.node(rev)
2158 node = self.node(rev)
2164 else:
2159 else:
2165 node = nodeorrev
2160 node = nodeorrev
2166 rev = None
2161 rev = None
2167
2162
2168 # fast path the special `nullid` rev
2163 # fast path the special `nullid` rev
2169 if node == self.nullid:
2164 if node == self.nullid:
2170 return b""
2165 return b""
2171
2166
2172 # ``rawtext`` is the text as stored inside the revlog. Might be the
2167 # ``rawtext`` is the text as stored inside the revlog. Might be the
2173 # revision or might need to be processed to retrieve the revision.
2168 # revision or might need to be processed to retrieve the revision.
2174 rev, rawtext, validated = self._rawtext(node, rev)
2169 rev, rawtext, validated = self._rawtext(node, rev)
2175
2170
2176 if raw and validated:
2171 if raw and validated:
2177 # if we don't want to process the raw text and that raw
2172 # if we don't want to process the raw text and that raw
2178 # text is cached, we can exit early.
2173 # text is cached, we can exit early.
2179 return rawtext
2174 return rawtext
2180 if rev is None:
2175 if rev is None:
2181 rev = self.rev(node)
2176 rev = self.rev(node)
2182 # the revlog's flag for this revision
2177 # the revlog's flag for this revision
2183 # (usually alter its state or content)
2178 # (usually alter its state or content)
2184 flags = self.flags(rev)
2179 flags = self.flags(rev)
2185
2180
2186 if validated and flags == REVIDX_DEFAULT_FLAGS:
2181 if validated and flags == REVIDX_DEFAULT_FLAGS:
2187 # no extra flags set, no flag processor runs, text = rawtext
2182 # no extra flags set, no flag processor runs, text = rawtext
2188 return rawtext
2183 return rawtext
2189
2184
2190 if raw:
2185 if raw:
2191 validatehash = flagutil.processflagsraw(self, rawtext, flags)
2186 validatehash = flagutil.processflagsraw(self, rawtext, flags)
2192 text = rawtext
2187 text = rawtext
2193 else:
2188 else:
2194 r = flagutil.processflagsread(self, rawtext, flags)
2189 r = flagutil.processflagsread(self, rawtext, flags)
2195 text, validatehash = r
2190 text, validatehash = r
2196 if validatehash:
2191 if validatehash:
2197 self.checkhash(text, node, rev=rev)
2192 self.checkhash(text, node, rev=rev)
2198 if not validated:
2193 if not validated:
2199 self._revisioncache = (node, rev, rawtext)
2194 self._revisioncache = (node, rev, rawtext)
2200
2195
2201 return text
2196 return text
2202
2197
2203 def _rawtext(self, node, rev):
2198 def _rawtext(self, node, rev):
2204 """return the possibly unvalidated rawtext for a revision
2199 """return the possibly unvalidated rawtext for a revision
2205
2200
2206 returns (rev, rawtext, validated)
2201 returns (rev, rawtext, validated)
2207 """
2202 """
2208
2203
2209 # revision in the cache (could be useful to apply delta)
2204 # revision in the cache (could be useful to apply delta)
2210 cachedrev = None
2205 cachedrev = None
2211 # An intermediate text to apply deltas to
2206 # An intermediate text to apply deltas to
2212 basetext = None
2207 basetext = None
2213
2208
2214 # Check if we have the entry in cache
2209 # Check if we have the entry in cache
2215 # The cache entry looks like (node, rev, rawtext)
2210 # The cache entry looks like (node, rev, rawtext)
2216 if self._revisioncache:
2211 if self._revisioncache:
2217 if self._revisioncache[0] == node:
2212 if self._revisioncache[0] == node:
2218 return (rev, self._revisioncache[2], True)
2213 return (rev, self._revisioncache[2], True)
2219 cachedrev = self._revisioncache[1]
2214 cachedrev = self._revisioncache[1]
2220
2215
2221 if rev is None:
2216 if rev is None:
2222 rev = self.rev(node)
2217 rev = self.rev(node)
2223
2218
2224 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
2219 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
2225 if stopped:
2220 if stopped:
2226 basetext = self._revisioncache[2]
2221 basetext = self._revisioncache[2]
2227
2222
2228 # drop cache to save memory, the caller is expected to
2223 # drop cache to save memory, the caller is expected to
2229 # update self._revisioncache after validating the text
2224 # update self._revisioncache after validating the text
2230 self._revisioncache = None
2225 self._revisioncache = None
2231
2226
2232 targetsize = None
2227 targetsize = None
2233 rawsize = self.index[rev][2]
2228 rawsize = self.index[rev][2]
2234 if 0 <= rawsize:
2229 if 0 <= rawsize:
2235 targetsize = 4 * rawsize
2230 targetsize = 4 * rawsize
2236
2231
2237 bins = self._chunks(chain, targetsize=targetsize)
2232 bins = self._chunks(chain, targetsize=targetsize)
2238 if basetext is None:
2233 if basetext is None:
2239 basetext = bytes(bins[0])
2234 basetext = bytes(bins[0])
2240 bins = bins[1:]
2235 bins = bins[1:]
2241
2236
2242 rawtext = mdiff.patches(basetext, bins)
2237 rawtext = mdiff.patches(basetext, bins)
2243 del basetext # let us have a chance to free memory early
2238 del basetext # let us have a chance to free memory early
2244 return (rev, rawtext, False)
2239 return (rev, rawtext, False)
2245
2240
2246 def _sidedata(self, rev):
2241 def _sidedata(self, rev):
2247 """Return the sidedata for a given revision number."""
2242 """Return the sidedata for a given revision number."""
2248 index_entry = self.index[rev]
2243 index_entry = self.index[rev]
2249 sidedata_offset = index_entry[8]
2244 sidedata_offset = index_entry[8]
2250 sidedata_size = index_entry[9]
2245 sidedata_size = index_entry[9]
2251
2246
2252 if self._inline:
2247 if self._inline:
2253 sidedata_offset += self.index.entry_size * (1 + rev)
2248 sidedata_offset += self.index.entry_size * (1 + rev)
2254 if sidedata_size == 0:
2249 if sidedata_size == 0:
2255 return {}
2250 return {}
2256
2251
2257 if self._docket.sidedata_end < sidedata_offset + sidedata_size:
2252 if self._docket.sidedata_end < sidedata_offset + sidedata_size:
2258 filename = self._sidedatafile
2253 filename = self._sidedatafile
2259 end = self._docket.sidedata_end
2254 end = self._docket.sidedata_end
2260 offset = sidedata_offset
2255 offset = sidedata_offset
2261 length = sidedata_size
2256 length = sidedata_size
2262 m = FILE_TOO_SHORT_MSG % (filename, length, offset, end)
2257 m = FILE_TOO_SHORT_MSG % (filename, length, offset, end)
2263 raise error.RevlogError(m)
2258 raise error.RevlogError(m)
2264
2259
2265 comp_segment = self._segmentfile_sidedata.read_chunk(
2260 comp_segment = self._segmentfile_sidedata.read_chunk(
2266 sidedata_offset, sidedata_size
2261 sidedata_offset, sidedata_size
2267 )
2262 )
2268
2263
2269 comp = self.index[rev][11]
2264 comp = self.index[rev][11]
2270 if comp == COMP_MODE_PLAIN:
2265 if comp == COMP_MODE_PLAIN:
2271 segment = comp_segment
2266 segment = comp_segment
2272 elif comp == COMP_MODE_DEFAULT:
2267 elif comp == COMP_MODE_DEFAULT:
2273 segment = self._decompressor(comp_segment)
2268 segment = self._decompressor(comp_segment)
2274 elif comp == COMP_MODE_INLINE:
2269 elif comp == COMP_MODE_INLINE:
2275 segment = self.decompress(comp_segment)
2270 segment = self.decompress(comp_segment)
2276 else:
2271 else:
2277 msg = b'unknown compression mode %d'
2272 msg = b'unknown compression mode %d'
2278 msg %= comp
2273 msg %= comp
2279 raise error.RevlogError(msg)
2274 raise error.RevlogError(msg)
2280
2275
2281 sidedata = sidedatautil.deserialize_sidedata(segment)
2276 sidedata = sidedatautil.deserialize_sidedata(segment)
2282 return sidedata
2277 return sidedata
2283
2278
2284 def rawdata(self, nodeorrev):
2279 def rawdata(self, nodeorrev):
2285 """return an uncompressed raw data of a given node or revision number."""
2280 """return an uncompressed raw data of a given node or revision number."""
2286 return self._revisiondata(nodeorrev, raw=True)
2281 return self._revisiondata(nodeorrev, raw=True)
2287
2282
2288 def hash(self, text, p1, p2):
2283 def hash(self, text, p1, p2):
2289 """Compute a node hash.
2284 """Compute a node hash.
2290
2285
2291 Available as a function so that subclasses can replace the hash
2286 Available as a function so that subclasses can replace the hash
2292 as needed.
2287 as needed.
2293 """
2288 """
2294 return storageutil.hashrevisionsha1(text, p1, p2)
2289 return storageutil.hashrevisionsha1(text, p1, p2)
2295
2290
2296 def checkhash(self, text, node, p1=None, p2=None, rev=None):
2291 def checkhash(self, text, node, p1=None, p2=None, rev=None):
2297 """Check node hash integrity.
2292 """Check node hash integrity.
2298
2293
2299 Available as a function so that subclasses can extend hash mismatch
2294 Available as a function so that subclasses can extend hash mismatch
2300 behaviors as needed.
2295 behaviors as needed.
2301 """
2296 """
2302 try:
2297 try:
2303 if p1 is None and p2 is None:
2298 if p1 is None and p2 is None:
2304 p1, p2 = self.parents(node)
2299 p1, p2 = self.parents(node)
2305 if node != self.hash(text, p1, p2):
2300 if node != self.hash(text, p1, p2):
2306 # Clear the revision cache on hash failure. The revision cache
2301 # Clear the revision cache on hash failure. The revision cache
2307 # only stores the raw revision and clearing the cache does have
2302 # only stores the raw revision and clearing the cache does have
2308 # the side-effect that we won't have a cache hit when the raw
2303 # the side-effect that we won't have a cache hit when the raw
2309 # revision data is accessed. But this case should be rare and
2304 # revision data is accessed. But this case should be rare and
2310 # it is extra work to teach the cache about the hash
2305 # it is extra work to teach the cache about the hash
2311 # verification state.
2306 # verification state.
2312 if self._revisioncache and self._revisioncache[0] == node:
2307 if self._revisioncache and self._revisioncache[0] == node:
2313 self._revisioncache = None
2308 self._revisioncache = None
2314
2309
2315 revornode = rev
2310 revornode = rev
2316 if revornode is None:
2311 if revornode is None:
2317 revornode = templatefilters.short(hex(node))
2312 revornode = templatefilters.short(hex(node))
2318 raise error.RevlogError(
2313 raise error.RevlogError(
2319 _(b"integrity check failed on %s:%s")
2314 _(b"integrity check failed on %s:%s")
2320 % (self.display_id, pycompat.bytestr(revornode))
2315 % (self.display_id, pycompat.bytestr(revornode))
2321 )
2316 )
2322 except error.RevlogError:
2317 except error.RevlogError:
2323 if self._censorable and storageutil.iscensoredtext(text):
2318 if self._censorable and storageutil.iscensoredtext(text):
2324 raise error.CensoredNodeError(self.display_id, node, text)
2319 raise error.CensoredNodeError(self.display_id, node, text)
2325 raise
2320 raise
2326
2321
2327 @property
2322 @property
2328 def _split_index_file(self):
2323 def _split_index_file(self):
2329 """the path where to expect the index of an ongoing splitting operation
2324 """the path where to expect the index of an ongoing splitting operation
2330
2325
2331 The file will only exist if a splitting operation is in progress, but
2326 The file will only exist if a splitting operation is in progress, but
2332 it is always expected at the same location."""
2327 it is always expected at the same location."""
2333 parts = self.radix.split(b'/')
2328 parts = self.radix.split(b'/')
2334 if len(parts) > 1:
2329 if len(parts) > 1:
2335 # adds a '-s' prefix to the ``data/` or `meta/` base
2330 # adds a '-s' prefix to the ``data/` or `meta/` base
2336 head = parts[0] + b'-s'
2331 head = parts[0] + b'-s'
2337 mids = parts[1:-1]
2332 mids = parts[1:-1]
2338 tail = parts[-1] + b'.i'
2333 tail = parts[-1] + b'.i'
2339 pieces = [head] + mids + [tail]
2334 pieces = [head] + mids + [tail]
2340 return b'/'.join(pieces)
2335 return b'/'.join(pieces)
2341 else:
2336 else:
2342 # the revlog is stored at the root of the store (changelog or
2337 # the revlog is stored at the root of the store (changelog or
2343 # manifest), no risk of collision.
2338 # manifest), no risk of collision.
2344 return self.radix + b'.i.s'
2339 return self.radix + b'.i.s'
2345
2340
2346 def _enforceinlinesize(self, tr, side_write=True):
2341 def _enforceinlinesize(self, tr, side_write=True):
2347 """Check if the revlog is too big for inline and convert if so.
2342 """Check if the revlog is too big for inline and convert if so.
2348
2343
2349 This should be called after revisions are added to the revlog. If the
2344 This should be called after revisions are added to the revlog. If the
2350 revlog has grown too large to be an inline revlog, it will convert it
2345 revlog has grown too large to be an inline revlog, it will convert it
2351 to use multiple index and data files.
2346 to use multiple index and data files.
2352 """
2347 """
2353 tiprev = len(self) - 1
2348 tiprev = len(self) - 1
2354 total_size = self.start(tiprev) + self.length(tiprev)
2349 total_size = self.start(tiprev) + self.length(tiprev)
2355 if not self._inline or total_size < _maxinline:
2350 if not self._inline or total_size < _maxinline:
2356 return
2351 return
2357
2352
2358 troffset = tr.findoffset(self._indexfile)
2353 troffset = tr.findoffset(self._indexfile)
2359 if troffset is None:
2354 if troffset is None:
2360 raise error.RevlogError(
2355 raise error.RevlogError(
2361 _(b"%s not found in the transaction") % self._indexfile
2356 _(b"%s not found in the transaction") % self._indexfile
2362 )
2357 )
2363 if troffset:
2358 if troffset:
2364 tr.addbackup(self._indexfile, for_offset=True)
2359 tr.addbackup(self._indexfile, for_offset=True)
2365 tr.add(self._datafile, 0)
2360 tr.add(self._datafile, 0)
2366
2361
2367 existing_handles = False
2362 existing_handles = False
2368 if self._writinghandles is not None:
2363 if self._writinghandles is not None:
2369 existing_handles = True
2364 existing_handles = True
2370 fp = self._writinghandles[0]
2365 fp = self._writinghandles[0]
2371 fp.flush()
2366 fp.flush()
2372 fp.close()
2367 fp.close()
2373 # We can't use the cached file handle after close(). So prevent
2368 # We can't use the cached file handle after close(). So prevent
2374 # its usage.
2369 # its usage.
2375 self._writinghandles = None
2370 self._writinghandles = None
2376 self._segmentfile.writing_handle = None
2371 self._segmentfile.writing_handle = None
2377 # No need to deal with sidedata writing handle as it is only
2372 # No need to deal with sidedata writing handle as it is only
2378 # relevant with revlog-v2 which is never inline, not reaching
2373 # relevant with revlog-v2 which is never inline, not reaching
2379 # this code
2374 # this code
2380 if side_write:
2375 if side_write:
2381 old_index_file_path = self._indexfile
2376 old_index_file_path = self._indexfile
2382 new_index_file_path = self._split_index_file
2377 new_index_file_path = self._split_index_file
2383 opener = self.opener
2378 opener = self.opener
2384 weak_self = weakref.ref(self)
2379 weak_self = weakref.ref(self)
2385
2380
2386 # the "split" index replace the real index when the transaction is finalized
2381 # the "split" index replace the real index when the transaction is finalized
2387 def finalize_callback(tr):
2382 def finalize_callback(tr):
2388 opener.rename(
2383 opener.rename(
2389 new_index_file_path,
2384 new_index_file_path,
2390 old_index_file_path,
2385 old_index_file_path,
2391 checkambig=True,
2386 checkambig=True,
2392 )
2387 )
2393 maybe_self = weak_self()
2388 maybe_self = weak_self()
2394 if maybe_self is not None:
2389 if maybe_self is not None:
2395 maybe_self._indexfile = old_index_file_path
2390 maybe_self._indexfile = old_index_file_path
2396
2391
2397 def abort_callback(tr):
2392 def abort_callback(tr):
2398 maybe_self = weak_self()
2393 maybe_self = weak_self()
2399 if maybe_self is not None:
2394 if maybe_self is not None:
2400 maybe_self._indexfile = old_index_file_path
2395 maybe_self._indexfile = old_index_file_path
2401
2396
2402 tr.registertmp(new_index_file_path)
2397 tr.registertmp(new_index_file_path)
2403 if self.target[1] is not None:
2398 if self.target[1] is not None:
2404 callback_id = b'000-revlog-split-%d-%s' % self.target
2399 callback_id = b'000-revlog-split-%d-%s' % self.target
2405 else:
2400 else:
2406 callback_id = b'000-revlog-split-%d' % self.target[0]
2401 callback_id = b'000-revlog-split-%d' % self.target[0]
2407 tr.addfinalize(callback_id, finalize_callback)
2402 tr.addfinalize(callback_id, finalize_callback)
2408 tr.addabort(callback_id, abort_callback)
2403 tr.addabort(callback_id, abort_callback)
2409
2404
2410 new_dfh = self._datafp(b'w+')
2405 new_dfh = self._datafp(b'w+')
2411 new_dfh.truncate(0) # drop any potentially existing data
2406 new_dfh.truncate(0) # drop any potentially existing data
2412 try:
2407 try:
2413 with self.reading():
2408 with self.reading():
2414 for r in self:
2409 for r in self:
2415 new_dfh.write(self._getsegmentforrevs(r, r)[1])
2410 new_dfh.write(self._getsegmentforrevs(r, r)[1])
2416 new_dfh.flush()
2411 new_dfh.flush()
2417
2412
2418 if side_write:
2413 if side_write:
2419 self._indexfile = new_index_file_path
2414 self._indexfile = new_index_file_path
2420 with self.__index_new_fp() as fp:
2415 with self.__index_new_fp() as fp:
2421 self._format_flags &= ~FLAG_INLINE_DATA
2416 self._format_flags &= ~FLAG_INLINE_DATA
2422 self._inline = False
2417 self._inline = False
2423 for i in self:
2418 for i in self:
2424 e = self.index.entry_binary(i)
2419 e = self.index.entry_binary(i)
2425 if i == 0 and self._docket is None:
2420 if i == 0 and self._docket is None:
2426 header = self._format_flags | self._format_version
2421 header = self._format_flags | self._format_version
2427 header = self.index.pack_header(header)
2422 header = self.index.pack_header(header)
2428 e = header + e
2423 e = header + e
2429 fp.write(e)
2424 fp.write(e)
2430 if self._docket is not None:
2425 if self._docket is not None:
2431 self._docket.index_end = fp.tell()
2426 self._docket.index_end = fp.tell()
2432
2427
2433 # If we don't use side-write, the temp file replace the real
2428 # If we don't use side-write, the temp file replace the real
2434 # index when we exit the context manager
2429 # index when we exit the context manager
2435
2430
2436 nodemaputil.setup_persistent_nodemap(tr, self)
2431 nodemaputil.setup_persistent_nodemap(tr, self)
2437 self._segmentfile = randomaccessfile.randomaccessfile(
2432 self._segmentfile = randomaccessfile.randomaccessfile(
2438 self.opener,
2433 self.opener,
2439 self._datafile,
2434 self._datafile,
2440 self._chunkcachesize,
2435 self._chunkcachesize,
2441 )
2436 )
2442
2437
2443 if existing_handles:
2438 if existing_handles:
2444 # switched from inline to conventional reopen the index
2439 # switched from inline to conventional reopen the index
2445 ifh = self.__index_write_fp()
2440 ifh = self.__index_write_fp()
2446 self._writinghandles = (ifh, new_dfh, None)
2441 self._writinghandles = (ifh, new_dfh, None)
2447 self._segmentfile.writing_handle = new_dfh
2442 self._segmentfile.writing_handle = new_dfh
2448 new_dfh = None
2443 new_dfh = None
2449 # No need to deal with sidedata writing handle as it is only
2444 # No need to deal with sidedata writing handle as it is only
2450 # relevant with revlog-v2 which is never inline, not reaching
2445 # relevant with revlog-v2 which is never inline, not reaching
2451 # this code
2446 # this code
2452 finally:
2447 finally:
2453 if new_dfh is not None:
2448 if new_dfh is not None:
2454 new_dfh.close()
2449 new_dfh.close()
2455
2450
2456 def _nodeduplicatecallback(self, transaction, node):
2451 def _nodeduplicatecallback(self, transaction, node):
2457 """called when trying to add a node already stored."""
2452 """called when trying to add a node already stored."""
2458
2453
2459 @contextlib.contextmanager
2454 @contextlib.contextmanager
2460 def reading(self):
2455 def reading(self):
2461 """Context manager that keeps data and sidedata files open for reading"""
2456 """Context manager that keeps data and sidedata files open for reading"""
2462 if len(self.index) == 0:
2457 if len(self.index) == 0:
2463 yield # nothing to be read
2458 yield # nothing to be read
2464 else:
2459 else:
2465 with self._segmentfile.reading():
2460 with self._segmentfile.reading():
2466 with self._segmentfile_sidedata.reading():
2461 with self._segmentfile_sidedata.reading():
2467 yield
2462 yield
2468
2463
2469 @contextlib.contextmanager
2464 @contextlib.contextmanager
2470 def _writing(self, transaction):
2465 def _writing(self, transaction):
2471 if self._trypending:
2466 if self._trypending:
2472 msg = b'try to write in a `trypending` revlog: %s'
2467 msg = b'try to write in a `trypending` revlog: %s'
2473 msg %= self.display_id
2468 msg %= self.display_id
2474 raise error.ProgrammingError(msg)
2469 raise error.ProgrammingError(msg)
2475 if self._writinghandles is not None:
2470 if self._writinghandles is not None:
2476 yield
2471 yield
2477 else:
2472 else:
2478 ifh = dfh = sdfh = None
2473 ifh = dfh = sdfh = None
2479 try:
2474 try:
2480 r = len(self)
2475 r = len(self)
2481 # opening the data file.
2476 # opening the data file.
2482 dsize = 0
2477 dsize = 0
2483 if r:
2478 if r:
2484 dsize = self.end(r - 1)
2479 dsize = self.end(r - 1)
2485 dfh = None
2480 dfh = None
2486 if not self._inline:
2481 if not self._inline:
2487 try:
2482 try:
2488 dfh = self._datafp(b"r+")
2483 dfh = self._datafp(b"r+")
2489 if self._docket is None:
2484 if self._docket is None:
2490 dfh.seek(0, os.SEEK_END)
2485 dfh.seek(0, os.SEEK_END)
2491 else:
2486 else:
2492 dfh.seek(self._docket.data_end, os.SEEK_SET)
2487 dfh.seek(self._docket.data_end, os.SEEK_SET)
2493 except FileNotFoundError:
2488 except FileNotFoundError:
2494 dfh = self._datafp(b"w+")
2489 dfh = self._datafp(b"w+")
2495 transaction.add(self._datafile, dsize)
2490 transaction.add(self._datafile, dsize)
2496 if self._sidedatafile is not None:
2491 if self._sidedatafile is not None:
2497 # revlog-v2 does not inline, help Pytype
2492 # revlog-v2 does not inline, help Pytype
2498 assert dfh is not None
2493 assert dfh is not None
2499 try:
2494 try:
2500 sdfh = self.opener(self._sidedatafile, mode=b"r+")
2495 sdfh = self.opener(self._sidedatafile, mode=b"r+")
2501 dfh.seek(self._docket.sidedata_end, os.SEEK_SET)
2496 dfh.seek(self._docket.sidedata_end, os.SEEK_SET)
2502 except FileNotFoundError:
2497 except FileNotFoundError:
2503 sdfh = self.opener(self._sidedatafile, mode=b"w+")
2498 sdfh = self.opener(self._sidedatafile, mode=b"w+")
2504 transaction.add(
2499 transaction.add(
2505 self._sidedatafile, self._docket.sidedata_end
2500 self._sidedatafile, self._docket.sidedata_end
2506 )
2501 )
2507
2502
2508 # opening the index file.
2503 # opening the index file.
2509 isize = r * self.index.entry_size
2504 isize = r * self.index.entry_size
2510 ifh = self.__index_write_fp()
2505 ifh = self.__index_write_fp()
2511 if self._inline:
2506 if self._inline:
2512 transaction.add(self._indexfile, dsize + isize)
2507 transaction.add(self._indexfile, dsize + isize)
2513 else:
2508 else:
2514 transaction.add(self._indexfile, isize)
2509 transaction.add(self._indexfile, isize)
2515 # exposing all file handle for writing.
2510 # exposing all file handle for writing.
2516 self._writinghandles = (ifh, dfh, sdfh)
2511 self._writinghandles = (ifh, dfh, sdfh)
2517 self._segmentfile.writing_handle = ifh if self._inline else dfh
2512 self._segmentfile.writing_handle = ifh if self._inline else dfh
2518 self._segmentfile_sidedata.writing_handle = sdfh
2513 self._segmentfile_sidedata.writing_handle = sdfh
2519 yield
2514 yield
2520 if self._docket is not None:
2515 if self._docket is not None:
2521 self._write_docket(transaction)
2516 self._write_docket(transaction)
2522 finally:
2517 finally:
2523 self._writinghandles = None
2518 self._writinghandles = None
2524 self._segmentfile.writing_handle = None
2519 self._segmentfile.writing_handle = None
2525 self._segmentfile_sidedata.writing_handle = None
2520 self._segmentfile_sidedata.writing_handle = None
2526 if dfh is not None:
2521 if dfh is not None:
2527 dfh.close()
2522 dfh.close()
2528 if sdfh is not None:
2523 if sdfh is not None:
2529 sdfh.close()
2524 sdfh.close()
2530 # closing the index file last to avoid exposing referent to
2525 # closing the index file last to avoid exposing referent to
2531 # potential unflushed data content.
2526 # potential unflushed data content.
2532 if ifh is not None:
2527 if ifh is not None:
2533 ifh.close()
2528 ifh.close()
2534
2529
2535 def _write_docket(self, transaction):
2530 def _write_docket(self, transaction):
2536 """write the current docket on disk
2531 """write the current docket on disk
2537
2532
2538 Exist as a method to help changelog to implement transaction logic
2533 Exist as a method to help changelog to implement transaction logic
2539
2534
2540 We could also imagine using the same transaction logic for all revlog
2535 We could also imagine using the same transaction logic for all revlog
2541 since docket are cheap."""
2536 since docket are cheap."""
2542 self._docket.write(transaction)
2537 self._docket.write(transaction)
2543
2538
2544 def addrevision(
2539 def addrevision(
2545 self,
2540 self,
2546 text,
2541 text,
2547 transaction,
2542 transaction,
2548 link,
2543 link,
2549 p1,
2544 p1,
2550 p2,
2545 p2,
2551 cachedelta=None,
2546 cachedelta=None,
2552 node=None,
2547 node=None,
2553 flags=REVIDX_DEFAULT_FLAGS,
2548 flags=REVIDX_DEFAULT_FLAGS,
2554 deltacomputer=None,
2549 deltacomputer=None,
2555 sidedata=None,
2550 sidedata=None,
2556 ):
2551 ):
2557 """add a revision to the log
2552 """add a revision to the log
2558
2553
2559 text - the revision data to add
2554 text - the revision data to add
2560 transaction - the transaction object used for rollback
2555 transaction - the transaction object used for rollback
2561 link - the linkrev data to add
2556 link - the linkrev data to add
2562 p1, p2 - the parent nodeids of the revision
2557 p1, p2 - the parent nodeids of the revision
2563 cachedelta - an optional precomputed delta
2558 cachedelta - an optional precomputed delta
2564 node - nodeid of revision; typically node is not specified, and it is
2559 node - nodeid of revision; typically node is not specified, and it is
2565 computed by default as hash(text, p1, p2), however subclasses might
2560 computed by default as hash(text, p1, p2), however subclasses might
2566 use different hashing method (and override checkhash() in such case)
2561 use different hashing method (and override checkhash() in such case)
2567 flags - the known flags to set on the revision
2562 flags - the known flags to set on the revision
2568 deltacomputer - an optional deltacomputer instance shared between
2563 deltacomputer - an optional deltacomputer instance shared between
2569 multiple calls
2564 multiple calls
2570 """
2565 """
2571 if link == nullrev:
2566 if link == nullrev:
2572 raise error.RevlogError(
2567 raise error.RevlogError(
2573 _(b"attempted to add linkrev -1 to %s") % self.display_id
2568 _(b"attempted to add linkrev -1 to %s") % self.display_id
2574 )
2569 )
2575
2570
2576 if sidedata is None:
2571 if sidedata is None:
2577 sidedata = {}
2572 sidedata = {}
2578 elif sidedata and not self.hassidedata:
2573 elif sidedata and not self.hassidedata:
2579 raise error.ProgrammingError(
2574 raise error.ProgrammingError(
2580 _(b"trying to add sidedata to a revlog who don't support them")
2575 _(b"trying to add sidedata to a revlog who don't support them")
2581 )
2576 )
2582
2577
2583 if flags:
2578 if flags:
2584 node = node or self.hash(text, p1, p2)
2579 node = node or self.hash(text, p1, p2)
2585
2580
2586 rawtext, validatehash = flagutil.processflagswrite(self, text, flags)
2581 rawtext, validatehash = flagutil.processflagswrite(self, text, flags)
2587
2582
2588 # If the flag processor modifies the revision data, ignore any provided
2583 # If the flag processor modifies the revision data, ignore any provided
2589 # cachedelta.
2584 # cachedelta.
2590 if rawtext != text:
2585 if rawtext != text:
2591 cachedelta = None
2586 cachedelta = None
2592
2587
2593 if len(rawtext) > _maxentrysize:
2588 if len(rawtext) > _maxentrysize:
2594 raise error.RevlogError(
2589 raise error.RevlogError(
2595 _(
2590 _(
2596 b"%s: size of %d bytes exceeds maximum revlog storage of 2GiB"
2591 b"%s: size of %d bytes exceeds maximum revlog storage of 2GiB"
2597 )
2592 )
2598 % (self.display_id, len(rawtext))
2593 % (self.display_id, len(rawtext))
2599 )
2594 )
2600
2595
2601 node = node or self.hash(rawtext, p1, p2)
2596 node = node or self.hash(rawtext, p1, p2)
2602 rev = self.index.get_rev(node)
2597 rev = self.index.get_rev(node)
2603 if rev is not None:
2598 if rev is not None:
2604 return rev
2599 return rev
2605
2600
2606 if validatehash:
2601 if validatehash:
2607 self.checkhash(rawtext, node, p1=p1, p2=p2)
2602 self.checkhash(rawtext, node, p1=p1, p2=p2)
2608
2603
2609 return self.addrawrevision(
2604 return self.addrawrevision(
2610 rawtext,
2605 rawtext,
2611 transaction,
2606 transaction,
2612 link,
2607 link,
2613 p1,
2608 p1,
2614 p2,
2609 p2,
2615 node,
2610 node,
2616 flags,
2611 flags,
2617 cachedelta=cachedelta,
2612 cachedelta=cachedelta,
2618 deltacomputer=deltacomputer,
2613 deltacomputer=deltacomputer,
2619 sidedata=sidedata,
2614 sidedata=sidedata,
2620 )
2615 )
2621
2616
2622 def addrawrevision(
2617 def addrawrevision(
2623 self,
2618 self,
2624 rawtext,
2619 rawtext,
2625 transaction,
2620 transaction,
2626 link,
2621 link,
2627 p1,
2622 p1,
2628 p2,
2623 p2,
2629 node,
2624 node,
2630 flags,
2625 flags,
2631 cachedelta=None,
2626 cachedelta=None,
2632 deltacomputer=None,
2627 deltacomputer=None,
2633 sidedata=None,
2628 sidedata=None,
2634 ):
2629 ):
2635 """add a raw revision with known flags, node and parents
2630 """add a raw revision with known flags, node and parents
2636 useful when reusing a revision not stored in this revlog (ex: received
2631 useful when reusing a revision not stored in this revlog (ex: received
2637 over wire, or read from an external bundle).
2632 over wire, or read from an external bundle).
2638 """
2633 """
2639 with self._writing(transaction):
2634 with self._writing(transaction):
2640 return self._addrevision(
2635 return self._addrevision(
2641 node,
2636 node,
2642 rawtext,
2637 rawtext,
2643 transaction,
2638 transaction,
2644 link,
2639 link,
2645 p1,
2640 p1,
2646 p2,
2641 p2,
2647 flags,
2642 flags,
2648 cachedelta,
2643 cachedelta,
2649 deltacomputer=deltacomputer,
2644 deltacomputer=deltacomputer,
2650 sidedata=sidedata,
2645 sidedata=sidedata,
2651 )
2646 )
2652
2647
2653 def compress(self, data):
2648 def compress(self, data):
2654 """Generate a possibly-compressed representation of data."""
2649 """Generate a possibly-compressed representation of data."""
2655 if not data:
2650 if not data:
2656 return b'', data
2651 return b'', data
2657
2652
2658 compressed = self._compressor.compress(data)
2653 compressed = self._compressor.compress(data)
2659
2654
2660 if compressed:
2655 if compressed:
2661 # The revlog compressor added the header in the returned data.
2656 # The revlog compressor added the header in the returned data.
2662 return b'', compressed
2657 return b'', compressed
2663
2658
2664 if data[0:1] == b'\0':
2659 if data[0:1] == b'\0':
2665 return b'', data
2660 return b'', data
2666 return b'u', data
2661 return b'u', data
2667
2662
2668 def decompress(self, data):
2663 def decompress(self, data):
2669 """Decompress a revlog chunk.
2664 """Decompress a revlog chunk.
2670
2665
2671 The chunk is expected to begin with a header identifying the
2666 The chunk is expected to begin with a header identifying the
2672 format type so it can be routed to an appropriate decompressor.
2667 format type so it can be routed to an appropriate decompressor.
2673 """
2668 """
2674 if not data:
2669 if not data:
2675 return data
2670 return data
2676
2671
2677 # Revlogs are read much more frequently than they are written and many
2672 # Revlogs are read much more frequently than they are written and many
2678 # chunks only take microseconds to decompress, so performance is
2673 # chunks only take microseconds to decompress, so performance is
2679 # important here.
2674 # important here.
2680 #
2675 #
2681 # We can make a few assumptions about revlogs:
2676 # We can make a few assumptions about revlogs:
2682 #
2677 #
2683 # 1) the majority of chunks will be compressed (as opposed to inline
2678 # 1) the majority of chunks will be compressed (as opposed to inline
2684 # raw data).
2679 # raw data).
2685 # 2) decompressing *any* data will likely by at least 10x slower than
2680 # 2) decompressing *any* data will likely by at least 10x slower than
2686 # returning raw inline data.
2681 # returning raw inline data.
2687 # 3) we want to prioritize common and officially supported compression
2682 # 3) we want to prioritize common and officially supported compression
2688 # engines
2683 # engines
2689 #
2684 #
2690 # It follows that we want to optimize for "decompress compressed data
2685 # It follows that we want to optimize for "decompress compressed data
2691 # when encoded with common and officially supported compression engines"
2686 # when encoded with common and officially supported compression engines"
2692 # case over "raw data" and "data encoded by less common or non-official
2687 # case over "raw data" and "data encoded by less common or non-official
2693 # compression engines." That is why we have the inline lookup first
2688 # compression engines." That is why we have the inline lookup first
2694 # followed by the compengines lookup.
2689 # followed by the compengines lookup.
2695 #
2690 #
2696 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
2691 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
2697 # compressed chunks. And this matters for changelog and manifest reads.
2692 # compressed chunks. And this matters for changelog and manifest reads.
2698 t = data[0:1]
2693 t = data[0:1]
2699
2694
2700 if t == b'x':
2695 if t == b'x':
2701 try:
2696 try:
2702 return _zlibdecompress(data)
2697 return _zlibdecompress(data)
2703 except zlib.error as e:
2698 except zlib.error as e:
2704 raise error.RevlogError(
2699 raise error.RevlogError(
2705 _(b'revlog decompress error: %s')
2700 _(b'revlog decompress error: %s')
2706 % stringutil.forcebytestr(e)
2701 % stringutil.forcebytestr(e)
2707 )
2702 )
2708 # '\0' is more common than 'u' so it goes first.
2703 # '\0' is more common than 'u' so it goes first.
2709 elif t == b'\0':
2704 elif t == b'\0':
2710 return data
2705 return data
2711 elif t == b'u':
2706 elif t == b'u':
2712 return util.buffer(data, 1)
2707 return util.buffer(data, 1)
2713
2708
2714 compressor = self._get_decompressor(t)
2709 compressor = self._get_decompressor(t)
2715
2710
2716 return compressor.decompress(data)
2711 return compressor.decompress(data)
2717
2712
2718 def _addrevision(
2713 def _addrevision(
2719 self,
2714 self,
2720 node,
2715 node,
2721 rawtext,
2716 rawtext,
2722 transaction,
2717 transaction,
2723 link,
2718 link,
2724 p1,
2719 p1,
2725 p2,
2720 p2,
2726 flags,
2721 flags,
2727 cachedelta,
2722 cachedelta,
2728 alwayscache=False,
2723 alwayscache=False,
2729 deltacomputer=None,
2724 deltacomputer=None,
2730 sidedata=None,
2725 sidedata=None,
2731 ):
2726 ):
2732 """internal function to add revisions to the log
2727 """internal function to add revisions to the log
2733
2728
2734 see addrevision for argument descriptions.
2729 see addrevision for argument descriptions.
2735
2730
2736 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2731 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2737
2732
2738 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2733 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2739 be used.
2734 be used.
2740
2735
2741 invariants:
2736 invariants:
2742 - rawtext is optional (can be None); if not set, cachedelta must be set.
2737 - rawtext is optional (can be None); if not set, cachedelta must be set.
2743 if both are set, they must correspond to each other.
2738 if both are set, they must correspond to each other.
2744 """
2739 """
2745 if node == self.nullid:
2740 if node == self.nullid:
2746 raise error.RevlogError(
2741 raise error.RevlogError(
2747 _(b"%s: attempt to add null revision") % self.display_id
2742 _(b"%s: attempt to add null revision") % self.display_id
2748 )
2743 )
2749 if (
2744 if (
2750 node == self.nodeconstants.wdirid
2745 node == self.nodeconstants.wdirid
2751 or node in self.nodeconstants.wdirfilenodeids
2746 or node in self.nodeconstants.wdirfilenodeids
2752 ):
2747 ):
2753 raise error.RevlogError(
2748 raise error.RevlogError(
2754 _(b"%s: attempt to add wdir revision") % self.display_id
2749 _(b"%s: attempt to add wdir revision") % self.display_id
2755 )
2750 )
2756 if self._writinghandles is None:
2751 if self._writinghandles is None:
2757 msg = b'adding revision outside `revlog._writing` context'
2752 msg = b'adding revision outside `revlog._writing` context'
2758 raise error.ProgrammingError(msg)
2753 raise error.ProgrammingError(msg)
2759
2754
2760 btext = [rawtext]
2755 btext = [rawtext]
2761
2756
2762 curr = len(self)
2757 curr = len(self)
2763 prev = curr - 1
2758 prev = curr - 1
2764
2759
2765 offset = self._get_data_offset(prev)
2760 offset = self._get_data_offset(prev)
2766
2761
2767 if self._concurrencychecker:
2762 if self._concurrencychecker:
2768 ifh, dfh, sdfh = self._writinghandles
2763 ifh, dfh, sdfh = self._writinghandles
2769 # XXX no checking for the sidedata file
2764 # XXX no checking for the sidedata file
2770 if self._inline:
2765 if self._inline:
2771 # offset is "as if" it were in the .d file, so we need to add on
2766 # offset is "as if" it were in the .d file, so we need to add on
2772 # the size of the entry metadata.
2767 # the size of the entry metadata.
2773 self._concurrencychecker(
2768 self._concurrencychecker(
2774 ifh, self._indexfile, offset + curr * self.index.entry_size
2769 ifh, self._indexfile, offset + curr * self.index.entry_size
2775 )
2770 )
2776 else:
2771 else:
2777 # Entries in the .i are a consistent size.
2772 # Entries in the .i are a consistent size.
2778 self._concurrencychecker(
2773 self._concurrencychecker(
2779 ifh, self._indexfile, curr * self.index.entry_size
2774 ifh, self._indexfile, curr * self.index.entry_size
2780 )
2775 )
2781 self._concurrencychecker(dfh, self._datafile, offset)
2776 self._concurrencychecker(dfh, self._datafile, offset)
2782
2777
2783 p1r, p2r = self.rev(p1), self.rev(p2)
2778 p1r, p2r = self.rev(p1), self.rev(p2)
2784
2779
2785 # full versions are inserted when the needed deltas
2780 # full versions are inserted when the needed deltas
2786 # become comparable to the uncompressed text
2781 # become comparable to the uncompressed text
2787 if rawtext is None:
2782 if rawtext is None:
2788 # need rawtext size, before changed by flag processors, which is
2783 # need rawtext size, before changed by flag processors, which is
2789 # the non-raw size. use revlog explicitly to avoid filelog's extra
2784 # the non-raw size. use revlog explicitly to avoid filelog's extra
2790 # logic that might remove metadata size.
2785 # logic that might remove metadata size.
2791 textlen = mdiff.patchedsize(
2786 textlen = mdiff.patchedsize(
2792 revlog.size(self, cachedelta[0]), cachedelta[1]
2787 revlog.size(self, cachedelta[0]), cachedelta[1]
2793 )
2788 )
2794 else:
2789 else:
2795 textlen = len(rawtext)
2790 textlen = len(rawtext)
2796
2791
2797 if deltacomputer is None:
2792 if deltacomputer is None:
2798 write_debug = None
2793 write_debug = None
2799 if self._debug_delta:
2794 if self._debug_delta:
2800 write_debug = transaction._report
2795 write_debug = transaction._report
2801 deltacomputer = deltautil.deltacomputer(
2796 deltacomputer = deltautil.deltacomputer(
2802 self, write_debug=write_debug
2797 self, write_debug=write_debug
2803 )
2798 )
2804
2799
2805 if cachedelta is not None and len(cachedelta) == 2:
2800 if cachedelta is not None and len(cachedelta) == 2:
2806 # If the cached delta has no information about how it should be
2801 # If the cached delta has no information about how it should be
2807 # reused, add the default reuse instruction according to the
2802 # reused, add the default reuse instruction according to the
2808 # revlog's configuration.
2803 # revlog's configuration.
2809 if self._generaldelta and self._lazydeltabase:
2804 if self._generaldelta and self._lazydeltabase:
2810 delta_base_reuse = DELTA_BASE_REUSE_TRY
2805 delta_base_reuse = DELTA_BASE_REUSE_TRY
2811 else:
2806 else:
2812 delta_base_reuse = DELTA_BASE_REUSE_NO
2807 delta_base_reuse = DELTA_BASE_REUSE_NO
2813 cachedelta = (cachedelta[0], cachedelta[1], delta_base_reuse)
2808 cachedelta = (cachedelta[0], cachedelta[1], delta_base_reuse)
2814
2809
2815 revinfo = revlogutils.revisioninfo(
2810 revinfo = revlogutils.revisioninfo(
2816 node,
2811 node,
2817 p1,
2812 p1,
2818 p2,
2813 p2,
2819 btext,
2814 btext,
2820 textlen,
2815 textlen,
2821 cachedelta,
2816 cachedelta,
2822 flags,
2817 flags,
2823 )
2818 )
2824
2819
2825 deltainfo = deltacomputer.finddeltainfo(revinfo)
2820 deltainfo = deltacomputer.finddeltainfo(revinfo)
2826
2821
2827 compression_mode = COMP_MODE_INLINE
2822 compression_mode = COMP_MODE_INLINE
2828 if self._docket is not None:
2823 if self._docket is not None:
2829 default_comp = self._docket.default_compression_header
2824 default_comp = self._docket.default_compression_header
2830 r = deltautil.delta_compression(default_comp, deltainfo)
2825 r = deltautil.delta_compression(default_comp, deltainfo)
2831 compression_mode, deltainfo = r
2826 compression_mode, deltainfo = r
2832
2827
2833 sidedata_compression_mode = COMP_MODE_INLINE
2828 sidedata_compression_mode = COMP_MODE_INLINE
2834 if sidedata and self.hassidedata:
2829 if sidedata and self.hassidedata:
2835 sidedata_compression_mode = COMP_MODE_PLAIN
2830 sidedata_compression_mode = COMP_MODE_PLAIN
2836 serialized_sidedata = sidedatautil.serialize_sidedata(sidedata)
2831 serialized_sidedata = sidedatautil.serialize_sidedata(sidedata)
2837 sidedata_offset = self._docket.sidedata_end
2832 sidedata_offset = self._docket.sidedata_end
2838 h, comp_sidedata = self.compress(serialized_sidedata)
2833 h, comp_sidedata = self.compress(serialized_sidedata)
2839 if (
2834 if (
2840 h != b'u'
2835 h != b'u'
2841 and comp_sidedata[0:1] != b'\0'
2836 and comp_sidedata[0:1] != b'\0'
2842 and len(comp_sidedata) < len(serialized_sidedata)
2837 and len(comp_sidedata) < len(serialized_sidedata)
2843 ):
2838 ):
2844 assert not h
2839 assert not h
2845 if (
2840 if (
2846 comp_sidedata[0:1]
2841 comp_sidedata[0:1]
2847 == self._docket.default_compression_header
2842 == self._docket.default_compression_header
2848 ):
2843 ):
2849 sidedata_compression_mode = COMP_MODE_DEFAULT
2844 sidedata_compression_mode = COMP_MODE_DEFAULT
2850 serialized_sidedata = comp_sidedata
2845 serialized_sidedata = comp_sidedata
2851 else:
2846 else:
2852 sidedata_compression_mode = COMP_MODE_INLINE
2847 sidedata_compression_mode = COMP_MODE_INLINE
2853 serialized_sidedata = comp_sidedata
2848 serialized_sidedata = comp_sidedata
2854 else:
2849 else:
2855 serialized_sidedata = b""
2850 serialized_sidedata = b""
2856 # Don't store the offset if the sidedata is empty, that way
2851 # Don't store the offset if the sidedata is empty, that way
2857 # we can easily detect empty sidedata and they will be no different
2852 # we can easily detect empty sidedata and they will be no different
2858 # than ones we manually add.
2853 # than ones we manually add.
2859 sidedata_offset = 0
2854 sidedata_offset = 0
2860
2855
2861 rank = RANK_UNKNOWN
2856 rank = RANK_UNKNOWN
2862 if self._compute_rank:
2857 if self._compute_rank:
2863 if (p1r, p2r) == (nullrev, nullrev):
2858 if (p1r, p2r) == (nullrev, nullrev):
2864 rank = 1
2859 rank = 1
2865 elif p1r != nullrev and p2r == nullrev:
2860 elif p1r != nullrev and p2r == nullrev:
2866 rank = 1 + self.fast_rank(p1r)
2861 rank = 1 + self.fast_rank(p1r)
2867 elif p1r == nullrev and p2r != nullrev:
2862 elif p1r == nullrev and p2r != nullrev:
2868 rank = 1 + self.fast_rank(p2r)
2863 rank = 1 + self.fast_rank(p2r)
2869 else: # merge node
2864 else: # merge node
2870 if rustdagop is not None and self.index.rust_ext_compat:
2865 if rustdagop is not None and self.index.rust_ext_compat:
2871 rank = rustdagop.rank(self.index, p1r, p2r)
2866 rank = rustdagop.rank(self.index, p1r, p2r)
2872 else:
2867 else:
2873 pmin, pmax = sorted((p1r, p2r))
2868 pmin, pmax = sorted((p1r, p2r))
2874 rank = 1 + self.fast_rank(pmax)
2869 rank = 1 + self.fast_rank(pmax)
2875 rank += sum(1 for _ in self.findmissingrevs([pmax], [pmin]))
2870 rank += sum(1 for _ in self.findmissingrevs([pmax], [pmin]))
2876
2871
2877 e = revlogutils.entry(
2872 e = revlogutils.entry(
2878 flags=flags,
2873 flags=flags,
2879 data_offset=offset,
2874 data_offset=offset,
2880 data_compressed_length=deltainfo.deltalen,
2875 data_compressed_length=deltainfo.deltalen,
2881 data_uncompressed_length=textlen,
2876 data_uncompressed_length=textlen,
2882 data_compression_mode=compression_mode,
2877 data_compression_mode=compression_mode,
2883 data_delta_base=deltainfo.base,
2878 data_delta_base=deltainfo.base,
2884 link_rev=link,
2879 link_rev=link,
2885 parent_rev_1=p1r,
2880 parent_rev_1=p1r,
2886 parent_rev_2=p2r,
2881 parent_rev_2=p2r,
2887 node_id=node,
2882 node_id=node,
2888 sidedata_offset=sidedata_offset,
2883 sidedata_offset=sidedata_offset,
2889 sidedata_compressed_length=len(serialized_sidedata),
2884 sidedata_compressed_length=len(serialized_sidedata),
2890 sidedata_compression_mode=sidedata_compression_mode,
2885 sidedata_compression_mode=sidedata_compression_mode,
2891 rank=rank,
2886 rank=rank,
2892 )
2887 )
2893
2888
2894 self.index.append(e)
2889 self.index.append(e)
2895 entry = self.index.entry_binary(curr)
2890 entry = self.index.entry_binary(curr)
2896 if curr == 0 and self._docket is None:
2891 if curr == 0 and self._docket is None:
2897 header = self._format_flags | self._format_version
2892 header = self._format_flags | self._format_version
2898 header = self.index.pack_header(header)
2893 header = self.index.pack_header(header)
2899 entry = header + entry
2894 entry = header + entry
2900 self._writeentry(
2895 self._writeentry(
2901 transaction,
2896 transaction,
2902 entry,
2897 entry,
2903 deltainfo.data,
2898 deltainfo.data,
2904 link,
2899 link,
2905 offset,
2900 offset,
2906 serialized_sidedata,
2901 serialized_sidedata,
2907 sidedata_offset,
2902 sidedata_offset,
2908 )
2903 )
2909
2904
2910 rawtext = btext[0]
2905 rawtext = btext[0]
2911
2906
2912 if alwayscache and rawtext is None:
2907 if alwayscache and rawtext is None:
2913 rawtext = deltacomputer.buildtext(revinfo)
2908 rawtext = deltacomputer.buildtext(revinfo)
2914
2909
2915 if type(rawtext) == bytes: # only accept immutable objects
2910 if type(rawtext) == bytes: # only accept immutable objects
2916 self._revisioncache = (node, curr, rawtext)
2911 self._revisioncache = (node, curr, rawtext)
2917 self._chainbasecache[curr] = deltainfo.chainbase
2912 self._chainbasecache[curr] = deltainfo.chainbase
2918 return curr
2913 return curr
2919
2914
2920 def _get_data_offset(self, prev):
2915 def _get_data_offset(self, prev):
2921 """Returns the current offset in the (in-transaction) data file.
2916 """Returns the current offset in the (in-transaction) data file.
2922 Versions < 2 of the revlog can get this 0(1), revlog v2 needs a docket
2917 Versions < 2 of the revlog can get this 0(1), revlog v2 needs a docket
2923 file to store that information: since sidedata can be rewritten to the
2918 file to store that information: since sidedata can be rewritten to the
2924 end of the data file within a transaction, you can have cases where, for
2919 end of the data file within a transaction, you can have cases where, for
2925 example, rev `n` does not have sidedata while rev `n - 1` does, leading
2920 example, rev `n` does not have sidedata while rev `n - 1` does, leading
2926 to `n - 1`'s sidedata being written after `n`'s data.
2921 to `n - 1`'s sidedata being written after `n`'s data.
2927
2922
2928 TODO cache this in a docket file before getting out of experimental."""
2923 TODO cache this in a docket file before getting out of experimental."""
2929 if self._docket is None:
2924 if self._docket is None:
2930 return self.end(prev)
2925 return self.end(prev)
2931 else:
2926 else:
2932 return self._docket.data_end
2927 return self._docket.data_end
2933
2928
2934 def _writeentry(
2929 def _writeentry(
2935 self, transaction, entry, data, link, offset, sidedata, sidedata_offset
2930 self, transaction, entry, data, link, offset, sidedata, sidedata_offset
2936 ):
2931 ):
2937 # Files opened in a+ mode have inconsistent behavior on various
2932 # Files opened in a+ mode have inconsistent behavior on various
2938 # platforms. Windows requires that a file positioning call be made
2933 # platforms. Windows requires that a file positioning call be made
2939 # when the file handle transitions between reads and writes. See
2934 # when the file handle transitions between reads and writes. See
2940 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2935 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2941 # platforms, Python or the platform itself can be buggy. Some versions
2936 # platforms, Python or the platform itself can be buggy. Some versions
2942 # of Solaris have been observed to not append at the end of the file
2937 # of Solaris have been observed to not append at the end of the file
2943 # if the file was seeked to before the end. See issue4943 for more.
2938 # if the file was seeked to before the end. See issue4943 for more.
2944 #
2939 #
2945 # We work around this issue by inserting a seek() before writing.
2940 # We work around this issue by inserting a seek() before writing.
2946 # Note: This is likely not necessary on Python 3. However, because
2941 # Note: This is likely not necessary on Python 3. However, because
2947 # the file handle is reused for reads and may be seeked there, we need
2942 # the file handle is reused for reads and may be seeked there, we need
2948 # to be careful before changing this.
2943 # to be careful before changing this.
2949 if self._writinghandles is None:
2944 if self._writinghandles is None:
2950 msg = b'adding revision outside `revlog._writing` context'
2945 msg = b'adding revision outside `revlog._writing` context'
2951 raise error.ProgrammingError(msg)
2946 raise error.ProgrammingError(msg)
2952 ifh, dfh, sdfh = self._writinghandles
2947 ifh, dfh, sdfh = self._writinghandles
2953 if self._docket is None:
2948 if self._docket is None:
2954 ifh.seek(0, os.SEEK_END)
2949 ifh.seek(0, os.SEEK_END)
2955 else:
2950 else:
2956 ifh.seek(self._docket.index_end, os.SEEK_SET)
2951 ifh.seek(self._docket.index_end, os.SEEK_SET)
2957 if dfh:
2952 if dfh:
2958 if self._docket is None:
2953 if self._docket is None:
2959 dfh.seek(0, os.SEEK_END)
2954 dfh.seek(0, os.SEEK_END)
2960 else:
2955 else:
2961 dfh.seek(self._docket.data_end, os.SEEK_SET)
2956 dfh.seek(self._docket.data_end, os.SEEK_SET)
2962 if sdfh:
2957 if sdfh:
2963 sdfh.seek(self._docket.sidedata_end, os.SEEK_SET)
2958 sdfh.seek(self._docket.sidedata_end, os.SEEK_SET)
2964
2959
2965 curr = len(self) - 1
2960 curr = len(self) - 1
2966 if not self._inline:
2961 if not self._inline:
2967 transaction.add(self._datafile, offset)
2962 transaction.add(self._datafile, offset)
2968 if self._sidedatafile:
2963 if self._sidedatafile:
2969 transaction.add(self._sidedatafile, sidedata_offset)
2964 transaction.add(self._sidedatafile, sidedata_offset)
2970 transaction.add(self._indexfile, curr * len(entry))
2965 transaction.add(self._indexfile, curr * len(entry))
2971 if data[0]:
2966 if data[0]:
2972 dfh.write(data[0])
2967 dfh.write(data[0])
2973 dfh.write(data[1])
2968 dfh.write(data[1])
2974 if sidedata:
2969 if sidedata:
2975 sdfh.write(sidedata)
2970 sdfh.write(sidedata)
2976 ifh.write(entry)
2971 ifh.write(entry)
2977 else:
2972 else:
2978 offset += curr * self.index.entry_size
2973 offset += curr * self.index.entry_size
2979 transaction.add(self._indexfile, offset)
2974 transaction.add(self._indexfile, offset)
2980 ifh.write(entry)
2975 ifh.write(entry)
2981 ifh.write(data[0])
2976 ifh.write(data[0])
2982 ifh.write(data[1])
2977 ifh.write(data[1])
2983 assert not sidedata
2978 assert not sidedata
2984 self._enforceinlinesize(transaction)
2979 self._enforceinlinesize(transaction)
2985 if self._docket is not None:
2980 if self._docket is not None:
2986 # revlog-v2 always has 3 writing handles, help Pytype
2981 # revlog-v2 always has 3 writing handles, help Pytype
2987 wh1 = self._writinghandles[0]
2982 wh1 = self._writinghandles[0]
2988 wh2 = self._writinghandles[1]
2983 wh2 = self._writinghandles[1]
2989 wh3 = self._writinghandles[2]
2984 wh3 = self._writinghandles[2]
2990 assert wh1 is not None
2985 assert wh1 is not None
2991 assert wh2 is not None
2986 assert wh2 is not None
2992 assert wh3 is not None
2987 assert wh3 is not None
2993 self._docket.index_end = wh1.tell()
2988 self._docket.index_end = wh1.tell()
2994 self._docket.data_end = wh2.tell()
2989 self._docket.data_end = wh2.tell()
2995 self._docket.sidedata_end = wh3.tell()
2990 self._docket.sidedata_end = wh3.tell()
2996
2991
2997 nodemaputil.setup_persistent_nodemap(transaction, self)
2992 nodemaputil.setup_persistent_nodemap(transaction, self)
2998
2993
2999 def addgroup(
2994 def addgroup(
3000 self,
2995 self,
3001 deltas,
2996 deltas,
3002 linkmapper,
2997 linkmapper,
3003 transaction,
2998 transaction,
3004 alwayscache=False,
2999 alwayscache=False,
3005 addrevisioncb=None,
3000 addrevisioncb=None,
3006 duplicaterevisioncb=None,
3001 duplicaterevisioncb=None,
3007 debug_info=None,
3002 debug_info=None,
3008 delta_base_reuse_policy=None,
3003 delta_base_reuse_policy=None,
3009 ):
3004 ):
3010 """
3005 """
3011 add a delta group
3006 add a delta group
3012
3007
3013 given a set of deltas, add them to the revision log. the
3008 given a set of deltas, add them to the revision log. the
3014 first delta is against its parent, which should be in our
3009 first delta is against its parent, which should be in our
3015 log, the rest are against the previous delta.
3010 log, the rest are against the previous delta.
3016
3011
3017 If ``addrevisioncb`` is defined, it will be called with arguments of
3012 If ``addrevisioncb`` is defined, it will be called with arguments of
3018 this revlog and the node that was added.
3013 this revlog and the node that was added.
3019 """
3014 """
3020
3015
3021 if self._adding_group:
3016 if self._adding_group:
3022 raise error.ProgrammingError(b'cannot nest addgroup() calls')
3017 raise error.ProgrammingError(b'cannot nest addgroup() calls')
3023
3018
3024 # read the default delta-base reuse policy from revlog config if the
3019 # read the default delta-base reuse policy from revlog config if the
3025 # group did not specify one.
3020 # group did not specify one.
3026 if delta_base_reuse_policy is None:
3021 if delta_base_reuse_policy is None:
3027 if self._generaldelta and self._lazydeltabase:
3022 if self._generaldelta and self._lazydeltabase:
3028 delta_base_reuse_policy = DELTA_BASE_REUSE_TRY
3023 delta_base_reuse_policy = DELTA_BASE_REUSE_TRY
3029 else:
3024 else:
3030 delta_base_reuse_policy = DELTA_BASE_REUSE_NO
3025 delta_base_reuse_policy = DELTA_BASE_REUSE_NO
3031
3026
3032 self._adding_group = True
3027 self._adding_group = True
3033 empty = True
3028 empty = True
3034 try:
3029 try:
3035 with self._writing(transaction):
3030 with self._writing(transaction):
3036 write_debug = None
3031 write_debug = None
3037 if self._debug_delta:
3032 if self._debug_delta:
3038 write_debug = transaction._report
3033 write_debug = transaction._report
3039 deltacomputer = deltautil.deltacomputer(
3034 deltacomputer = deltautil.deltacomputer(
3040 self,
3035 self,
3041 write_debug=write_debug,
3036 write_debug=write_debug,
3042 debug_info=debug_info,
3037 debug_info=debug_info,
3043 )
3038 )
3044 # loop through our set of deltas
3039 # loop through our set of deltas
3045 for data in deltas:
3040 for data in deltas:
3046 (
3041 (
3047 node,
3042 node,
3048 p1,
3043 p1,
3049 p2,
3044 p2,
3050 linknode,
3045 linknode,
3051 deltabase,
3046 deltabase,
3052 delta,
3047 delta,
3053 flags,
3048 flags,
3054 sidedata,
3049 sidedata,
3055 ) = data
3050 ) = data
3056 link = linkmapper(linknode)
3051 link = linkmapper(linknode)
3057 flags = flags or REVIDX_DEFAULT_FLAGS
3052 flags = flags or REVIDX_DEFAULT_FLAGS
3058
3053
3059 rev = self.index.get_rev(node)
3054 rev = self.index.get_rev(node)
3060 if rev is not None:
3055 if rev is not None:
3061 # this can happen if two branches make the same change
3056 # this can happen if two branches make the same change
3062 self._nodeduplicatecallback(transaction, rev)
3057 self._nodeduplicatecallback(transaction, rev)
3063 if duplicaterevisioncb:
3058 if duplicaterevisioncb:
3064 duplicaterevisioncb(self, rev)
3059 duplicaterevisioncb(self, rev)
3065 empty = False
3060 empty = False
3066 continue
3061 continue
3067
3062
3068 for p in (p1, p2):
3063 for p in (p1, p2):
3069 if not self.index.has_node(p):
3064 if not self.index.has_node(p):
3070 raise error.LookupError(
3065 raise error.LookupError(
3071 p, self.radix, _(b'unknown parent')
3066 p, self.radix, _(b'unknown parent')
3072 )
3067 )
3073
3068
3074 if not self.index.has_node(deltabase):
3069 if not self.index.has_node(deltabase):
3075 raise error.LookupError(
3070 raise error.LookupError(
3076 deltabase, self.display_id, _(b'unknown delta base')
3071 deltabase, self.display_id, _(b'unknown delta base')
3077 )
3072 )
3078
3073
3079 baserev = self.rev(deltabase)
3074 baserev = self.rev(deltabase)
3080
3075
3081 if baserev != nullrev and self.iscensored(baserev):
3076 if baserev != nullrev and self.iscensored(baserev):
3082 # if base is censored, delta must be full replacement in a
3077 # if base is censored, delta must be full replacement in a
3083 # single patch operation
3078 # single patch operation
3084 hlen = struct.calcsize(b">lll")
3079 hlen = struct.calcsize(b">lll")
3085 oldlen = self.rawsize(baserev)
3080 oldlen = self.rawsize(baserev)
3086 newlen = len(delta) - hlen
3081 newlen = len(delta) - hlen
3087 if delta[:hlen] != mdiff.replacediffheader(
3082 if delta[:hlen] != mdiff.replacediffheader(
3088 oldlen, newlen
3083 oldlen, newlen
3089 ):
3084 ):
3090 raise error.CensoredBaseError(
3085 raise error.CensoredBaseError(
3091 self.display_id, self.node(baserev)
3086 self.display_id, self.node(baserev)
3092 )
3087 )
3093
3088
3094 if not flags and self._peek_iscensored(baserev, delta):
3089 if not flags and self._peek_iscensored(baserev, delta):
3095 flags |= REVIDX_ISCENSORED
3090 flags |= REVIDX_ISCENSORED
3096
3091
3097 # We assume consumers of addrevisioncb will want to retrieve
3092 # We assume consumers of addrevisioncb will want to retrieve
3098 # the added revision, which will require a call to
3093 # the added revision, which will require a call to
3099 # revision(). revision() will fast path if there is a cache
3094 # revision(). revision() will fast path if there is a cache
3100 # hit. So, we tell _addrevision() to always cache in this case.
3095 # hit. So, we tell _addrevision() to always cache in this case.
3101 # We're only using addgroup() in the context of changegroup
3096 # We're only using addgroup() in the context of changegroup
3102 # generation so the revision data can always be handled as raw
3097 # generation so the revision data can always be handled as raw
3103 # by the flagprocessor.
3098 # by the flagprocessor.
3104 rev = self._addrevision(
3099 rev = self._addrevision(
3105 node,
3100 node,
3106 None,
3101 None,
3107 transaction,
3102 transaction,
3108 link,
3103 link,
3109 p1,
3104 p1,
3110 p2,
3105 p2,
3111 flags,
3106 flags,
3112 (baserev, delta, delta_base_reuse_policy),
3107 (baserev, delta, delta_base_reuse_policy),
3113 alwayscache=alwayscache,
3108 alwayscache=alwayscache,
3114 deltacomputer=deltacomputer,
3109 deltacomputer=deltacomputer,
3115 sidedata=sidedata,
3110 sidedata=sidedata,
3116 )
3111 )
3117
3112
3118 if addrevisioncb:
3113 if addrevisioncb:
3119 addrevisioncb(self, rev)
3114 addrevisioncb(self, rev)
3120 empty = False
3115 empty = False
3121 finally:
3116 finally:
3122 self._adding_group = False
3117 self._adding_group = False
3123 return not empty
3118 return not empty
3124
3119
3125 def iscensored(self, rev):
3120 def iscensored(self, rev):
3126 """Check if a file revision is censored."""
3121 """Check if a file revision is censored."""
3127 if not self._censorable:
3122 if not self._censorable:
3128 return False
3123 return False
3129
3124
3130 return self.flags(rev) & REVIDX_ISCENSORED
3125 return self.flags(rev) & REVIDX_ISCENSORED
3131
3126
3132 def _peek_iscensored(self, baserev, delta):
3127 def _peek_iscensored(self, baserev, delta):
3133 """Quickly check if a delta produces a censored revision."""
3128 """Quickly check if a delta produces a censored revision."""
3134 if not self._censorable:
3129 if not self._censorable:
3135 return False
3130 return False
3136
3131
3137 return storageutil.deltaiscensored(delta, baserev, self.rawsize)
3132 return storageutil.deltaiscensored(delta, baserev, self.rawsize)
3138
3133
3139 def getstrippoint(self, minlink):
3134 def getstrippoint(self, minlink):
3140 """find the minimum rev that must be stripped to strip the linkrev
3135 """find the minimum rev that must be stripped to strip the linkrev
3141
3136
3142 Returns a tuple containing the minimum rev and a set of all revs that
3137 Returns a tuple containing the minimum rev and a set of all revs that
3143 have linkrevs that will be broken by this strip.
3138 have linkrevs that will be broken by this strip.
3144 """
3139 """
3145 return storageutil.resolvestripinfo(
3140 return storageutil.resolvestripinfo(
3146 minlink,
3141 minlink,
3147 len(self) - 1,
3142 len(self) - 1,
3148 self.headrevs(),
3143 self.headrevs(),
3149 self.linkrev,
3144 self.linkrev,
3150 self.parentrevs,
3145 self.parentrevs,
3151 )
3146 )
3152
3147
3153 def strip(self, minlink, transaction):
3148 def strip(self, minlink, transaction):
3154 """truncate the revlog on the first revision with a linkrev >= minlink
3149 """truncate the revlog on the first revision with a linkrev >= minlink
3155
3150
3156 This function is called when we're stripping revision minlink and
3151 This function is called when we're stripping revision minlink and
3157 its descendants from the repository.
3152 its descendants from the repository.
3158
3153
3159 We have to remove all revisions with linkrev >= minlink, because
3154 We have to remove all revisions with linkrev >= minlink, because
3160 the equivalent changelog revisions will be renumbered after the
3155 the equivalent changelog revisions will be renumbered after the
3161 strip.
3156 strip.
3162
3157
3163 So we truncate the revlog on the first of these revisions, and
3158 So we truncate the revlog on the first of these revisions, and
3164 trust that the caller has saved the revisions that shouldn't be
3159 trust that the caller has saved the revisions that shouldn't be
3165 removed and that it'll re-add them after this truncation.
3160 removed and that it'll re-add them after this truncation.
3166 """
3161 """
3167 if len(self) == 0:
3162 if len(self) == 0:
3168 return
3163 return
3169
3164
3170 rev, _ = self.getstrippoint(minlink)
3165 rev, _ = self.getstrippoint(minlink)
3171 if rev == len(self):
3166 if rev == len(self):
3172 return
3167 return
3173
3168
3174 # first truncate the files on disk
3169 # first truncate the files on disk
3175 data_end = self.start(rev)
3170 data_end = self.start(rev)
3176 if not self._inline:
3171 if not self._inline:
3177 transaction.add(self._datafile, data_end)
3172 transaction.add(self._datafile, data_end)
3178 end = rev * self.index.entry_size
3173 end = rev * self.index.entry_size
3179 else:
3174 else:
3180 end = data_end + (rev * self.index.entry_size)
3175 end = data_end + (rev * self.index.entry_size)
3181
3176
3182 if self._sidedatafile:
3177 if self._sidedatafile:
3183 sidedata_end = self.sidedata_cut_off(rev)
3178 sidedata_end = self.sidedata_cut_off(rev)
3184 transaction.add(self._sidedatafile, sidedata_end)
3179 transaction.add(self._sidedatafile, sidedata_end)
3185
3180
3186 transaction.add(self._indexfile, end)
3181 transaction.add(self._indexfile, end)
3187 if self._docket is not None:
3182 if self._docket is not None:
3188 # XXX we could, leverage the docket while stripping. However it is
3183 # XXX we could, leverage the docket while stripping. However it is
3189 # not powerfull enough at the time of this comment
3184 # not powerfull enough at the time of this comment
3190 self._docket.index_end = end
3185 self._docket.index_end = end
3191 self._docket.data_end = data_end
3186 self._docket.data_end = data_end
3192 self._docket.sidedata_end = sidedata_end
3187 self._docket.sidedata_end = sidedata_end
3193 self._docket.write(transaction, stripping=True)
3188 self._docket.write(transaction, stripping=True)
3194
3189
3195 # then reset internal state in memory to forget those revisions
3190 # then reset internal state in memory to forget those revisions
3196 self._revisioncache = None
3191 self._revisioncache = None
3197 self._chaininfocache = util.lrucachedict(500)
3192 self._chaininfocache = util.lrucachedict(500)
3198 self._segmentfile.clear_cache()
3193 self._segmentfile.clear_cache()
3199 self._segmentfile_sidedata.clear_cache()
3194 self._segmentfile_sidedata.clear_cache()
3200
3195
3201 del self.index[rev:-1]
3196 del self.index[rev:-1]
3202
3197
3203 def checksize(self):
3198 def checksize(self):
3204 """Check size of index and data files
3199 """Check size of index and data files
3205
3200
3206 return a (dd, di) tuple.
3201 return a (dd, di) tuple.
3207 - dd: extra bytes for the "data" file
3202 - dd: extra bytes for the "data" file
3208 - di: extra bytes for the "index" file
3203 - di: extra bytes for the "index" file
3209
3204
3210 A healthy revlog will return (0, 0).
3205 A healthy revlog will return (0, 0).
3211 """
3206 """
3212 expected = 0
3207 expected = 0
3213 if len(self):
3208 if len(self):
3214 expected = max(0, self.end(len(self) - 1))
3209 expected = max(0, self.end(len(self) - 1))
3215
3210
3216 try:
3211 try:
3217 with self._datafp() as f:
3212 with self._datafp() as f:
3218 f.seek(0, io.SEEK_END)
3213 f.seek(0, io.SEEK_END)
3219 actual = f.tell()
3214 actual = f.tell()
3220 dd = actual - expected
3215 dd = actual - expected
3221 except FileNotFoundError:
3216 except FileNotFoundError:
3222 dd = 0
3217 dd = 0
3223
3218
3224 try:
3219 try:
3225 f = self.opener(self._indexfile)
3220 f = self.opener(self._indexfile)
3226 f.seek(0, io.SEEK_END)
3221 f.seek(0, io.SEEK_END)
3227 actual = f.tell()
3222 actual = f.tell()
3228 f.close()
3223 f.close()
3229 s = self.index.entry_size
3224 s = self.index.entry_size
3230 i = max(0, actual // s)
3225 i = max(0, actual // s)
3231 di = actual - (i * s)
3226 di = actual - (i * s)
3232 if self._inline:
3227 if self._inline:
3233 databytes = 0
3228 databytes = 0
3234 for r in self:
3229 for r in self:
3235 databytes += max(0, self.length(r))
3230 databytes += max(0, self.length(r))
3236 dd = 0
3231 dd = 0
3237 di = actual - len(self) * s - databytes
3232 di = actual - len(self) * s - databytes
3238 except FileNotFoundError:
3233 except FileNotFoundError:
3239 di = 0
3234 di = 0
3240
3235
3241 return (dd, di)
3236 return (dd, di)
3242
3237
3243 def files(self):
3238 def files(self):
3244 res = [self._indexfile]
3239 res = [self._indexfile]
3245 if self._docket_file is None:
3240 if self._docket_file is None:
3246 if not self._inline:
3241 if not self._inline:
3247 res.append(self._datafile)
3242 res.append(self._datafile)
3248 else:
3243 else:
3249 res.append(self._docket_file)
3244 res.append(self._docket_file)
3250 res.extend(self._docket.old_index_filepaths(include_empty=False))
3245 res.extend(self._docket.old_index_filepaths(include_empty=False))
3251 if self._docket.data_end:
3246 if self._docket.data_end:
3252 res.append(self._datafile)
3247 res.append(self._datafile)
3253 res.extend(self._docket.old_data_filepaths(include_empty=False))
3248 res.extend(self._docket.old_data_filepaths(include_empty=False))
3254 if self._docket.sidedata_end:
3249 if self._docket.sidedata_end:
3255 res.append(self._sidedatafile)
3250 res.append(self._sidedatafile)
3256 res.extend(self._docket.old_sidedata_filepaths(include_empty=False))
3251 res.extend(self._docket.old_sidedata_filepaths(include_empty=False))
3257 return res
3252 return res
3258
3253
3259 def emitrevisions(
3254 def emitrevisions(
3260 self,
3255 self,
3261 nodes,
3256 nodes,
3262 nodesorder=None,
3257 nodesorder=None,
3263 revisiondata=False,
3258 revisiondata=False,
3264 assumehaveparentrevisions=False,
3259 assumehaveparentrevisions=False,
3265 deltamode=repository.CG_DELTAMODE_STD,
3260 deltamode=repository.CG_DELTAMODE_STD,
3266 sidedata_helpers=None,
3261 sidedata_helpers=None,
3267 debug_info=None,
3262 debug_info=None,
3268 ):
3263 ):
3269 if nodesorder not in (b'nodes', b'storage', b'linear', None):
3264 if nodesorder not in (b'nodes', b'storage', b'linear', None):
3270 raise error.ProgrammingError(
3265 raise error.ProgrammingError(
3271 b'unhandled value for nodesorder: %s' % nodesorder
3266 b'unhandled value for nodesorder: %s' % nodesorder
3272 )
3267 )
3273
3268
3274 if nodesorder is None and not self._generaldelta:
3269 if nodesorder is None and not self._generaldelta:
3275 nodesorder = b'storage'
3270 nodesorder = b'storage'
3276
3271
3277 if (
3272 if (
3278 not self._storedeltachains
3273 not self._storedeltachains
3279 and deltamode != repository.CG_DELTAMODE_PREV
3274 and deltamode != repository.CG_DELTAMODE_PREV
3280 ):
3275 ):
3281 deltamode = repository.CG_DELTAMODE_FULL
3276 deltamode = repository.CG_DELTAMODE_FULL
3282
3277
3283 return storageutil.emitrevisions(
3278 return storageutil.emitrevisions(
3284 self,
3279 self,
3285 nodes,
3280 nodes,
3286 nodesorder,
3281 nodesorder,
3287 revlogrevisiondelta,
3282 revlogrevisiondelta,
3288 deltaparentfn=self.deltaparent,
3283 deltaparentfn=self.deltaparent,
3289 candeltafn=self._candelta,
3284 candeltafn=self._candelta,
3290 rawsizefn=self.rawsize,
3285 rawsizefn=self.rawsize,
3291 revdifffn=self.revdiff,
3286 revdifffn=self.revdiff,
3292 flagsfn=self.flags,
3287 flagsfn=self.flags,
3293 deltamode=deltamode,
3288 deltamode=deltamode,
3294 revisiondata=revisiondata,
3289 revisiondata=revisiondata,
3295 assumehaveparentrevisions=assumehaveparentrevisions,
3290 assumehaveparentrevisions=assumehaveparentrevisions,
3296 sidedata_helpers=sidedata_helpers,
3291 sidedata_helpers=sidedata_helpers,
3297 debug_info=debug_info,
3292 debug_info=debug_info,
3298 )
3293 )
3299
3294
3300 DELTAREUSEALWAYS = b'always'
3295 DELTAREUSEALWAYS = b'always'
3301 DELTAREUSESAMEREVS = b'samerevs'
3296 DELTAREUSESAMEREVS = b'samerevs'
3302 DELTAREUSENEVER = b'never'
3297 DELTAREUSENEVER = b'never'
3303
3298
3304 DELTAREUSEFULLADD = b'fulladd'
3299 DELTAREUSEFULLADD = b'fulladd'
3305
3300
3306 DELTAREUSEALL = {b'always', b'samerevs', b'never', b'fulladd'}
3301 DELTAREUSEALL = {b'always', b'samerevs', b'never', b'fulladd'}
3307
3302
3308 def clone(
3303 def clone(
3309 self,
3304 self,
3310 tr,
3305 tr,
3311 destrevlog,
3306 destrevlog,
3312 addrevisioncb=None,
3307 addrevisioncb=None,
3313 deltareuse=DELTAREUSESAMEREVS,
3308 deltareuse=DELTAREUSESAMEREVS,
3314 forcedeltabothparents=None,
3309 forcedeltabothparents=None,
3315 sidedata_helpers=None,
3310 sidedata_helpers=None,
3316 ):
3311 ):
3317 """Copy this revlog to another, possibly with format changes.
3312 """Copy this revlog to another, possibly with format changes.
3318
3313
3319 The destination revlog will contain the same revisions and nodes.
3314 The destination revlog will contain the same revisions and nodes.
3320 However, it may not be bit-for-bit identical due to e.g. delta encoding
3315 However, it may not be bit-for-bit identical due to e.g. delta encoding
3321 differences.
3316 differences.
3322
3317
3323 The ``deltareuse`` argument control how deltas from the existing revlog
3318 The ``deltareuse`` argument control how deltas from the existing revlog
3324 are preserved in the destination revlog. The argument can have the
3319 are preserved in the destination revlog. The argument can have the
3325 following values:
3320 following values:
3326
3321
3327 DELTAREUSEALWAYS
3322 DELTAREUSEALWAYS
3328 Deltas will always be reused (if possible), even if the destination
3323 Deltas will always be reused (if possible), even if the destination
3329 revlog would not select the same revisions for the delta. This is the
3324 revlog would not select the same revisions for the delta. This is the
3330 fastest mode of operation.
3325 fastest mode of operation.
3331 DELTAREUSESAMEREVS
3326 DELTAREUSESAMEREVS
3332 Deltas will be reused if the destination revlog would pick the same
3327 Deltas will be reused if the destination revlog would pick the same
3333 revisions for the delta. This mode strikes a balance between speed
3328 revisions for the delta. This mode strikes a balance between speed
3334 and optimization.
3329 and optimization.
3335 DELTAREUSENEVER
3330 DELTAREUSENEVER
3336 Deltas will never be reused. This is the slowest mode of execution.
3331 Deltas will never be reused. This is the slowest mode of execution.
3337 This mode can be used to recompute deltas (e.g. if the diff/delta
3332 This mode can be used to recompute deltas (e.g. if the diff/delta
3338 algorithm changes).
3333 algorithm changes).
3339 DELTAREUSEFULLADD
3334 DELTAREUSEFULLADD
3340 Revision will be re-added as if their were new content. This is
3335 Revision will be re-added as if their were new content. This is
3341 slower than DELTAREUSEALWAYS but allow more mechanism to kicks in.
3336 slower than DELTAREUSEALWAYS but allow more mechanism to kicks in.
3342 eg: large file detection and handling.
3337 eg: large file detection and handling.
3343
3338
3344 Delta computation can be slow, so the choice of delta reuse policy can
3339 Delta computation can be slow, so the choice of delta reuse policy can
3345 significantly affect run time.
3340 significantly affect run time.
3346
3341
3347 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
3342 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
3348 two extremes. Deltas will be reused if they are appropriate. But if the
3343 two extremes. Deltas will be reused if they are appropriate. But if the
3349 delta could choose a better revision, it will do so. This means if you
3344 delta could choose a better revision, it will do so. This means if you
3350 are converting a non-generaldelta revlog to a generaldelta revlog,
3345 are converting a non-generaldelta revlog to a generaldelta revlog,
3351 deltas will be recomputed if the delta's parent isn't a parent of the
3346 deltas will be recomputed if the delta's parent isn't a parent of the
3352 revision.
3347 revision.
3353
3348
3354 In addition to the delta policy, the ``forcedeltabothparents``
3349 In addition to the delta policy, the ``forcedeltabothparents``
3355 argument controls whether to force compute deltas against both parents
3350 argument controls whether to force compute deltas against both parents
3356 for merges. By default, the current default is used.
3351 for merges. By default, the current default is used.
3357
3352
3358 See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
3353 See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
3359 `sidedata_helpers`.
3354 `sidedata_helpers`.
3360 """
3355 """
3361 if deltareuse not in self.DELTAREUSEALL:
3356 if deltareuse not in self.DELTAREUSEALL:
3362 raise ValueError(
3357 raise ValueError(
3363 _(b'value for deltareuse invalid: %s') % deltareuse
3358 _(b'value for deltareuse invalid: %s') % deltareuse
3364 )
3359 )
3365
3360
3366 if len(destrevlog):
3361 if len(destrevlog):
3367 raise ValueError(_(b'destination revlog is not empty'))
3362 raise ValueError(_(b'destination revlog is not empty'))
3368
3363
3369 if getattr(self, 'filteredrevs', None):
3364 if getattr(self, 'filteredrevs', None):
3370 raise ValueError(_(b'source revlog has filtered revisions'))
3365 raise ValueError(_(b'source revlog has filtered revisions'))
3371 if getattr(destrevlog, 'filteredrevs', None):
3366 if getattr(destrevlog, 'filteredrevs', None):
3372 raise ValueError(_(b'destination revlog has filtered revisions'))
3367 raise ValueError(_(b'destination revlog has filtered revisions'))
3373
3368
3374 # lazydelta and lazydeltabase controls whether to reuse a cached delta,
3369 # lazydelta and lazydeltabase controls whether to reuse a cached delta,
3375 # if possible.
3370 # if possible.
3376 old_delta_config = destrevlog.delta_config
3371 old_delta_config = destrevlog.delta_config
3377 destrevlog.delta_config = destrevlog.delta_config.copy()
3372 destrevlog.delta_config = destrevlog.delta_config.copy()
3378
3373
3379 try:
3374 try:
3380 if deltareuse == self.DELTAREUSEALWAYS:
3375 if deltareuse == self.DELTAREUSEALWAYS:
3381 destrevlog.delta_config.lazy_delta_base = True
3376 destrevlog.delta_config.lazy_delta_base = True
3382 destrevlog.delta_config.lazy_delta = True
3377 destrevlog.delta_config.lazy_delta = True
3383 elif deltareuse == self.DELTAREUSESAMEREVS:
3378 elif deltareuse == self.DELTAREUSESAMEREVS:
3384 destrevlog.delta_config.lazy_delta_base = False
3379 destrevlog.delta_config.lazy_delta_base = False
3385 destrevlog.delta_config.lazy_delta = True
3380 destrevlog.delta_config.lazy_delta = True
3386 elif deltareuse == self.DELTAREUSENEVER:
3381 elif deltareuse == self.DELTAREUSENEVER:
3387 destrevlog.delta_config.lazy_delta_base = False
3382 destrevlog.delta_config.lazy_delta_base = False
3388 destrevlog.delta_config.lazy_delta = False
3383 destrevlog.delta_config.lazy_delta = False
3389
3384
3390 delta_both_parents = (
3385 delta_both_parents = (
3391 forcedeltabothparents or old_delta_config.delta_both_parents
3386 forcedeltabothparents or old_delta_config.delta_both_parents
3392 )
3387 )
3393 destrevlog.delta_config.delta_both_parents = delta_both_parents
3388 destrevlog.delta_config.delta_both_parents = delta_both_parents
3394
3389
3395 with self.reading():
3390 with self.reading():
3396 self._clone(
3391 self._clone(
3397 tr,
3392 tr,
3398 destrevlog,
3393 destrevlog,
3399 addrevisioncb,
3394 addrevisioncb,
3400 deltareuse,
3395 deltareuse,
3401 forcedeltabothparents,
3396 forcedeltabothparents,
3402 sidedata_helpers,
3397 sidedata_helpers,
3403 )
3398 )
3404
3399
3405 finally:
3400 finally:
3406 destrevlog.delta_config = old_delta_config
3401 destrevlog.delta_config = old_delta_config
3407
3402
3408 def _clone(
3403 def _clone(
3409 self,
3404 self,
3410 tr,
3405 tr,
3411 destrevlog,
3406 destrevlog,
3412 addrevisioncb,
3407 addrevisioncb,
3413 deltareuse,
3408 deltareuse,
3414 forcedeltabothparents,
3409 forcedeltabothparents,
3415 sidedata_helpers,
3410 sidedata_helpers,
3416 ):
3411 ):
3417 """perform the core duty of `revlog.clone` after parameter processing"""
3412 """perform the core duty of `revlog.clone` after parameter processing"""
3418 write_debug = None
3413 write_debug = None
3419 if self._debug_delta:
3414 if self._debug_delta:
3420 write_debug = tr._report
3415 write_debug = tr._report
3421 deltacomputer = deltautil.deltacomputer(
3416 deltacomputer = deltautil.deltacomputer(
3422 destrevlog,
3417 destrevlog,
3423 write_debug=write_debug,
3418 write_debug=write_debug,
3424 )
3419 )
3425 index = self.index
3420 index = self.index
3426 for rev in self:
3421 for rev in self:
3427 entry = index[rev]
3422 entry = index[rev]
3428
3423
3429 # Some classes override linkrev to take filtered revs into
3424 # Some classes override linkrev to take filtered revs into
3430 # account. Use raw entry from index.
3425 # account. Use raw entry from index.
3431 flags = entry[0] & 0xFFFF
3426 flags = entry[0] & 0xFFFF
3432 linkrev = entry[4]
3427 linkrev = entry[4]
3433 p1 = index[entry[5]][7]
3428 p1 = index[entry[5]][7]
3434 p2 = index[entry[6]][7]
3429 p2 = index[entry[6]][7]
3435 node = entry[7]
3430 node = entry[7]
3436
3431
3437 # (Possibly) reuse the delta from the revlog if allowed and
3432 # (Possibly) reuse the delta from the revlog if allowed and
3438 # the revlog chunk is a delta.
3433 # the revlog chunk is a delta.
3439 cachedelta = None
3434 cachedelta = None
3440 rawtext = None
3435 rawtext = None
3441 if deltareuse == self.DELTAREUSEFULLADD:
3436 if deltareuse == self.DELTAREUSEFULLADD:
3442 text = self._revisiondata(rev)
3437 text = self._revisiondata(rev)
3443 sidedata = self.sidedata(rev)
3438 sidedata = self.sidedata(rev)
3444
3439
3445 if sidedata_helpers is not None:
3440 if sidedata_helpers is not None:
3446 (sidedata, new_flags) = sidedatautil.run_sidedata_helpers(
3441 (sidedata, new_flags) = sidedatautil.run_sidedata_helpers(
3447 self, sidedata_helpers, sidedata, rev
3442 self, sidedata_helpers, sidedata, rev
3448 )
3443 )
3449 flags = flags | new_flags[0] & ~new_flags[1]
3444 flags = flags | new_flags[0] & ~new_flags[1]
3450
3445
3451 destrevlog.addrevision(
3446 destrevlog.addrevision(
3452 text,
3447 text,
3453 tr,
3448 tr,
3454 linkrev,
3449 linkrev,
3455 p1,
3450 p1,
3456 p2,
3451 p2,
3457 cachedelta=cachedelta,
3452 cachedelta=cachedelta,
3458 node=node,
3453 node=node,
3459 flags=flags,
3454 flags=flags,
3460 deltacomputer=deltacomputer,
3455 deltacomputer=deltacomputer,
3461 sidedata=sidedata,
3456 sidedata=sidedata,
3462 )
3457 )
3463 else:
3458 else:
3464 if destrevlog._lazydelta:
3459 if destrevlog._lazydelta:
3465 dp = self.deltaparent(rev)
3460 dp = self.deltaparent(rev)
3466 if dp != nullrev:
3461 if dp != nullrev:
3467 cachedelta = (dp, bytes(self._chunk(rev)))
3462 cachedelta = (dp, bytes(self._chunk(rev)))
3468
3463
3469 sidedata = None
3464 sidedata = None
3470 if not cachedelta:
3465 if not cachedelta:
3471 rawtext = self._revisiondata(rev)
3466 rawtext = self._revisiondata(rev)
3472 sidedata = self.sidedata(rev)
3467 sidedata = self.sidedata(rev)
3473 if sidedata is None:
3468 if sidedata is None:
3474 sidedata = self.sidedata(rev)
3469 sidedata = self.sidedata(rev)
3475
3470
3476 if sidedata_helpers is not None:
3471 if sidedata_helpers is not None:
3477 (sidedata, new_flags) = sidedatautil.run_sidedata_helpers(
3472 (sidedata, new_flags) = sidedatautil.run_sidedata_helpers(
3478 self, sidedata_helpers, sidedata, rev
3473 self, sidedata_helpers, sidedata, rev
3479 )
3474 )
3480 flags = flags | new_flags[0] & ~new_flags[1]
3475 flags = flags | new_flags[0] & ~new_flags[1]
3481
3476
3482 with destrevlog._writing(tr):
3477 with destrevlog._writing(tr):
3483 destrevlog._addrevision(
3478 destrevlog._addrevision(
3484 node,
3479 node,
3485 rawtext,
3480 rawtext,
3486 tr,
3481 tr,
3487 linkrev,
3482 linkrev,
3488 p1,
3483 p1,
3489 p2,
3484 p2,
3490 flags,
3485 flags,
3491 cachedelta,
3486 cachedelta,
3492 deltacomputer=deltacomputer,
3487 deltacomputer=deltacomputer,
3493 sidedata=sidedata,
3488 sidedata=sidedata,
3494 )
3489 )
3495
3490
3496 if addrevisioncb:
3491 if addrevisioncb:
3497 addrevisioncb(self, rev, node)
3492 addrevisioncb(self, rev, node)
3498
3493
3499 def censorrevision(self, tr, censornode, tombstone=b''):
3494 def censorrevision(self, tr, censornode, tombstone=b''):
3500 if self._format_version == REVLOGV0:
3495 if self._format_version == REVLOGV0:
3501 raise error.RevlogError(
3496 raise error.RevlogError(
3502 _(b'cannot censor with version %d revlogs')
3497 _(b'cannot censor with version %d revlogs')
3503 % self._format_version
3498 % self._format_version
3504 )
3499 )
3505 elif self._format_version == REVLOGV1:
3500 elif self._format_version == REVLOGV1:
3506 rewrite.v1_censor(self, tr, censornode, tombstone)
3501 rewrite.v1_censor(self, tr, censornode, tombstone)
3507 else:
3502 else:
3508 rewrite.v2_censor(self, tr, censornode, tombstone)
3503 rewrite.v2_censor(self, tr, censornode, tombstone)
3509
3504
3510 def verifyintegrity(self, state):
3505 def verifyintegrity(self, state):
3511 """Verifies the integrity of the revlog.
3506 """Verifies the integrity of the revlog.
3512
3507
3513 Yields ``revlogproblem`` instances describing problems that are
3508 Yields ``revlogproblem`` instances describing problems that are
3514 found.
3509 found.
3515 """
3510 """
3516 dd, di = self.checksize()
3511 dd, di = self.checksize()
3517 if dd:
3512 if dd:
3518 yield revlogproblem(error=_(b'data length off by %d bytes') % dd)
3513 yield revlogproblem(error=_(b'data length off by %d bytes') % dd)
3519 if di:
3514 if di:
3520 yield revlogproblem(error=_(b'index contains %d extra bytes') % di)
3515 yield revlogproblem(error=_(b'index contains %d extra bytes') % di)
3521
3516
3522 version = self._format_version
3517 version = self._format_version
3523
3518
3524 # The verifier tells us what version revlog we should be.
3519 # The verifier tells us what version revlog we should be.
3525 if version != state[b'expectedversion']:
3520 if version != state[b'expectedversion']:
3526 yield revlogproblem(
3521 yield revlogproblem(
3527 warning=_(b"warning: '%s' uses revlog format %d; expected %d")
3522 warning=_(b"warning: '%s' uses revlog format %d; expected %d")
3528 % (self.display_id, version, state[b'expectedversion'])
3523 % (self.display_id, version, state[b'expectedversion'])
3529 )
3524 )
3530
3525
3531 state[b'skipread'] = set()
3526 state[b'skipread'] = set()
3532 state[b'safe_renamed'] = set()
3527 state[b'safe_renamed'] = set()
3533
3528
3534 for rev in self:
3529 for rev in self:
3535 node = self.node(rev)
3530 node = self.node(rev)
3536
3531
3537 # Verify contents. 4 cases to care about:
3532 # Verify contents. 4 cases to care about:
3538 #
3533 #
3539 # common: the most common case
3534 # common: the most common case
3540 # rename: with a rename
3535 # rename: with a rename
3541 # meta: file content starts with b'\1\n', the metadata
3536 # meta: file content starts with b'\1\n', the metadata
3542 # header defined in filelog.py, but without a rename
3537 # header defined in filelog.py, but without a rename
3543 # ext: content stored externally
3538 # ext: content stored externally
3544 #
3539 #
3545 # More formally, their differences are shown below:
3540 # More formally, their differences are shown below:
3546 #
3541 #
3547 # | common | rename | meta | ext
3542 # | common | rename | meta | ext
3548 # -------------------------------------------------------
3543 # -------------------------------------------------------
3549 # flags() | 0 | 0 | 0 | not 0
3544 # flags() | 0 | 0 | 0 | not 0
3550 # renamed() | False | True | False | ?
3545 # renamed() | False | True | False | ?
3551 # rawtext[0:2]=='\1\n'| False | True | True | ?
3546 # rawtext[0:2]=='\1\n'| False | True | True | ?
3552 #
3547 #
3553 # "rawtext" means the raw text stored in revlog data, which
3548 # "rawtext" means the raw text stored in revlog data, which
3554 # could be retrieved by "rawdata(rev)". "text"
3549 # could be retrieved by "rawdata(rev)". "text"
3555 # mentioned below is "revision(rev)".
3550 # mentioned below is "revision(rev)".
3556 #
3551 #
3557 # There are 3 different lengths stored physically:
3552 # There are 3 different lengths stored physically:
3558 # 1. L1: rawsize, stored in revlog index
3553 # 1. L1: rawsize, stored in revlog index
3559 # 2. L2: len(rawtext), stored in revlog data
3554 # 2. L2: len(rawtext), stored in revlog data
3560 # 3. L3: len(text), stored in revlog data if flags==0, or
3555 # 3. L3: len(text), stored in revlog data if flags==0, or
3561 # possibly somewhere else if flags!=0
3556 # possibly somewhere else if flags!=0
3562 #
3557 #
3563 # L1 should be equal to L2. L3 could be different from them.
3558 # L1 should be equal to L2. L3 could be different from them.
3564 # "text" may or may not affect commit hash depending on flag
3559 # "text" may or may not affect commit hash depending on flag
3565 # processors (see flagutil.addflagprocessor).
3560 # processors (see flagutil.addflagprocessor).
3566 #
3561 #
3567 # | common | rename | meta | ext
3562 # | common | rename | meta | ext
3568 # -------------------------------------------------
3563 # -------------------------------------------------
3569 # rawsize() | L1 | L1 | L1 | L1
3564 # rawsize() | L1 | L1 | L1 | L1
3570 # size() | L1 | L2-LM | L1(*) | L1 (?)
3565 # size() | L1 | L2-LM | L1(*) | L1 (?)
3571 # len(rawtext) | L2 | L2 | L2 | L2
3566 # len(rawtext) | L2 | L2 | L2 | L2
3572 # len(text) | L2 | L2 | L2 | L3
3567 # len(text) | L2 | L2 | L2 | L3
3573 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
3568 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
3574 #
3569 #
3575 # LM: length of metadata, depending on rawtext
3570 # LM: length of metadata, depending on rawtext
3576 # (*): not ideal, see comment in filelog.size
3571 # (*): not ideal, see comment in filelog.size
3577 # (?): could be "- len(meta)" if the resolved content has
3572 # (?): could be "- len(meta)" if the resolved content has
3578 # rename metadata
3573 # rename metadata
3579 #
3574 #
3580 # Checks needed to be done:
3575 # Checks needed to be done:
3581 # 1. length check: L1 == L2, in all cases.
3576 # 1. length check: L1 == L2, in all cases.
3582 # 2. hash check: depending on flag processor, we may need to
3577 # 2. hash check: depending on flag processor, we may need to
3583 # use either "text" (external), or "rawtext" (in revlog).
3578 # use either "text" (external), or "rawtext" (in revlog).
3584
3579
3585 try:
3580 try:
3586 skipflags = state.get(b'skipflags', 0)
3581 skipflags = state.get(b'skipflags', 0)
3587 if skipflags:
3582 if skipflags:
3588 skipflags &= self.flags(rev)
3583 skipflags &= self.flags(rev)
3589
3584
3590 _verify_revision(self, skipflags, state, node)
3585 _verify_revision(self, skipflags, state, node)
3591
3586
3592 l1 = self.rawsize(rev)
3587 l1 = self.rawsize(rev)
3593 l2 = len(self.rawdata(node))
3588 l2 = len(self.rawdata(node))
3594
3589
3595 if l1 != l2:
3590 if l1 != l2:
3596 yield revlogproblem(
3591 yield revlogproblem(
3597 error=_(b'unpacked size is %d, %d expected') % (l2, l1),
3592 error=_(b'unpacked size is %d, %d expected') % (l2, l1),
3598 node=node,
3593 node=node,
3599 )
3594 )
3600
3595
3601 except error.CensoredNodeError:
3596 except error.CensoredNodeError:
3602 if state[b'erroroncensored']:
3597 if state[b'erroroncensored']:
3603 yield revlogproblem(
3598 yield revlogproblem(
3604 error=_(b'censored file data'), node=node
3599 error=_(b'censored file data'), node=node
3605 )
3600 )
3606 state[b'skipread'].add(node)
3601 state[b'skipread'].add(node)
3607 except Exception as e:
3602 except Exception as e:
3608 yield revlogproblem(
3603 yield revlogproblem(
3609 error=_(b'unpacking %s: %s')
3604 error=_(b'unpacking %s: %s')
3610 % (short(node), stringutil.forcebytestr(e)),
3605 % (short(node), stringutil.forcebytestr(e)),
3611 node=node,
3606 node=node,
3612 )
3607 )
3613 state[b'skipread'].add(node)
3608 state[b'skipread'].add(node)
3614
3609
3615 def storageinfo(
3610 def storageinfo(
3616 self,
3611 self,
3617 exclusivefiles=False,
3612 exclusivefiles=False,
3618 sharedfiles=False,
3613 sharedfiles=False,
3619 revisionscount=False,
3614 revisionscount=False,
3620 trackedsize=False,
3615 trackedsize=False,
3621 storedsize=False,
3616 storedsize=False,
3622 ):
3617 ):
3623 d = {}
3618 d = {}
3624
3619
3625 if exclusivefiles:
3620 if exclusivefiles:
3626 d[b'exclusivefiles'] = [(self.opener, self._indexfile)]
3621 d[b'exclusivefiles'] = [(self.opener, self._indexfile)]
3627 if not self._inline:
3622 if not self._inline:
3628 d[b'exclusivefiles'].append((self.opener, self._datafile))
3623 d[b'exclusivefiles'].append((self.opener, self._datafile))
3629
3624
3630 if sharedfiles:
3625 if sharedfiles:
3631 d[b'sharedfiles'] = []
3626 d[b'sharedfiles'] = []
3632
3627
3633 if revisionscount:
3628 if revisionscount:
3634 d[b'revisionscount'] = len(self)
3629 d[b'revisionscount'] = len(self)
3635
3630
3636 if trackedsize:
3631 if trackedsize:
3637 d[b'trackedsize'] = sum(map(self.rawsize, iter(self)))
3632 d[b'trackedsize'] = sum(map(self.rawsize, iter(self)))
3638
3633
3639 if storedsize:
3634 if storedsize:
3640 d[b'storedsize'] = sum(
3635 d[b'storedsize'] = sum(
3641 self.opener.stat(path).st_size for path in self.files()
3636 self.opener.stat(path).st_size for path in self.files()
3642 )
3637 )
3643
3638
3644 return d
3639 return d
3645
3640
3646 def rewrite_sidedata(self, transaction, helpers, startrev, endrev):
3641 def rewrite_sidedata(self, transaction, helpers, startrev, endrev):
3647 if not self.hassidedata:
3642 if not self.hassidedata:
3648 return
3643 return
3649 # revlog formats with sidedata support does not support inline
3644 # revlog formats with sidedata support does not support inline
3650 assert not self._inline
3645 assert not self._inline
3651 if not helpers[1] and not helpers[2]:
3646 if not helpers[1] and not helpers[2]:
3652 # Nothing to generate or remove
3647 # Nothing to generate or remove
3653 return
3648 return
3654
3649
3655 new_entries = []
3650 new_entries = []
3656 # append the new sidedata
3651 # append the new sidedata
3657 with self._writing(transaction):
3652 with self._writing(transaction):
3658 ifh, dfh, sdfh = self._writinghandles
3653 ifh, dfh, sdfh = self._writinghandles
3659 dfh.seek(self._docket.sidedata_end, os.SEEK_SET)
3654 dfh.seek(self._docket.sidedata_end, os.SEEK_SET)
3660
3655
3661 current_offset = sdfh.tell()
3656 current_offset = sdfh.tell()
3662 for rev in range(startrev, endrev + 1):
3657 for rev in range(startrev, endrev + 1):
3663 entry = self.index[rev]
3658 entry = self.index[rev]
3664 new_sidedata, flags = sidedatautil.run_sidedata_helpers(
3659 new_sidedata, flags = sidedatautil.run_sidedata_helpers(
3665 store=self,
3660 store=self,
3666 sidedata_helpers=helpers,
3661 sidedata_helpers=helpers,
3667 sidedata={},
3662 sidedata={},
3668 rev=rev,
3663 rev=rev,
3669 )
3664 )
3670
3665
3671 serialized_sidedata = sidedatautil.serialize_sidedata(
3666 serialized_sidedata = sidedatautil.serialize_sidedata(
3672 new_sidedata
3667 new_sidedata
3673 )
3668 )
3674
3669
3675 sidedata_compression_mode = COMP_MODE_INLINE
3670 sidedata_compression_mode = COMP_MODE_INLINE
3676 if serialized_sidedata and self.hassidedata:
3671 if serialized_sidedata and self.hassidedata:
3677 sidedata_compression_mode = COMP_MODE_PLAIN
3672 sidedata_compression_mode = COMP_MODE_PLAIN
3678 h, comp_sidedata = self.compress(serialized_sidedata)
3673 h, comp_sidedata = self.compress(serialized_sidedata)
3679 if (
3674 if (
3680 h != b'u'
3675 h != b'u'
3681 and comp_sidedata[0] != b'\0'
3676 and comp_sidedata[0] != b'\0'
3682 and len(comp_sidedata) < len(serialized_sidedata)
3677 and len(comp_sidedata) < len(serialized_sidedata)
3683 ):
3678 ):
3684 assert not h
3679 assert not h
3685 if (
3680 if (
3686 comp_sidedata[0]
3681 comp_sidedata[0]
3687 == self._docket.default_compression_header
3682 == self._docket.default_compression_header
3688 ):
3683 ):
3689 sidedata_compression_mode = COMP_MODE_DEFAULT
3684 sidedata_compression_mode = COMP_MODE_DEFAULT
3690 serialized_sidedata = comp_sidedata
3685 serialized_sidedata = comp_sidedata
3691 else:
3686 else:
3692 sidedata_compression_mode = COMP_MODE_INLINE
3687 sidedata_compression_mode = COMP_MODE_INLINE
3693 serialized_sidedata = comp_sidedata
3688 serialized_sidedata = comp_sidedata
3694 if entry[8] != 0 or entry[9] != 0:
3689 if entry[8] != 0 or entry[9] != 0:
3695 # rewriting entries that already have sidedata is not
3690 # rewriting entries that already have sidedata is not
3696 # supported yet, because it introduces garbage data in the
3691 # supported yet, because it introduces garbage data in the
3697 # revlog.
3692 # revlog.
3698 msg = b"rewriting existing sidedata is not supported yet"
3693 msg = b"rewriting existing sidedata is not supported yet"
3699 raise error.Abort(msg)
3694 raise error.Abort(msg)
3700
3695
3701 # Apply (potential) flags to add and to remove after running
3696 # Apply (potential) flags to add and to remove after running
3702 # the sidedata helpers
3697 # the sidedata helpers
3703 new_offset_flags = entry[0] | flags[0] & ~flags[1]
3698 new_offset_flags = entry[0] | flags[0] & ~flags[1]
3704 entry_update = (
3699 entry_update = (
3705 current_offset,
3700 current_offset,
3706 len(serialized_sidedata),
3701 len(serialized_sidedata),
3707 new_offset_flags,
3702 new_offset_flags,
3708 sidedata_compression_mode,
3703 sidedata_compression_mode,
3709 )
3704 )
3710
3705
3711 # the sidedata computation might have move the file cursors around
3706 # the sidedata computation might have move the file cursors around
3712 sdfh.seek(current_offset, os.SEEK_SET)
3707 sdfh.seek(current_offset, os.SEEK_SET)
3713 sdfh.write(serialized_sidedata)
3708 sdfh.write(serialized_sidedata)
3714 new_entries.append(entry_update)
3709 new_entries.append(entry_update)
3715 current_offset += len(serialized_sidedata)
3710 current_offset += len(serialized_sidedata)
3716 self._docket.sidedata_end = sdfh.tell()
3711 self._docket.sidedata_end = sdfh.tell()
3717
3712
3718 # rewrite the new index entries
3713 # rewrite the new index entries
3719 ifh.seek(startrev * self.index.entry_size)
3714 ifh.seek(startrev * self.index.entry_size)
3720 for i, e in enumerate(new_entries):
3715 for i, e in enumerate(new_entries):
3721 rev = startrev + i
3716 rev = startrev + i
3722 self.index.replace_sidedata_info(rev, *e)
3717 self.index.replace_sidedata_info(rev, *e)
3723 packed = self.index.entry_binary(rev)
3718 packed = self.index.entry_binary(rev)
3724 if rev == 0 and self._docket is None:
3719 if rev == 0 and self._docket is None:
3725 header = self._format_flags | self._format_version
3720 header = self._format_flags | self._format_version
3726 header = self.index.pack_header(header)
3721 header = self.index.pack_header(header)
3727 packed = header + packed
3722 packed = header + packed
3728 ifh.write(packed)
3723 ifh.write(packed)
General Comments 0
You need to be logged in to leave comments. Login now