##// END OF EJS Templates
revlog: skip opener option to pass chunk_cache_size value...
marmoute -
r51925:774c0034 default
parent child Browse files
Show More
@@ -1,4047 +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 options[b'chunkcachesize'] = chunkcachesize
1090 data_config.chunk_cache_size = chunkcachesize
1091
1091
1092 deltabothparents = ui.configbool(
1092 deltabothparents = ui.configbool(
1093 b'storage', b'revlog.optimize-delta-parent-choice'
1093 b'storage', b'revlog.optimize-delta-parent-choice'
1094 )
1094 )
1095 options[b'deltabothparents'] = deltabothparents
1095 options[b'deltabothparents'] = deltabothparents
1096 dps_cgds = ui.configint(
1096 dps_cgds = ui.configint(
1097 b'storage',
1097 b'storage',
1098 b'revlog.delta-parent-search.candidate-group-chunk-size',
1098 b'revlog.delta-parent-search.candidate-group-chunk-size',
1099 )
1099 )
1100 options[b'delta-parent-search.candidate-group-chunk-size'] = dps_cgds
1100 options[b'delta-parent-search.candidate-group-chunk-size'] = dps_cgds
1101 options[b'debug-delta'] = ui.configbool(b'debug', b'revlog.debug-delta')
1101 options[b'debug-delta'] = ui.configbool(b'debug', b'revlog.debug-delta')
1102
1102
1103 issue6528 = ui.configbool(b'storage', b'revlog.issue6528.fix-incoming')
1103 issue6528 = ui.configbool(b'storage', b'revlog.issue6528.fix-incoming')
1104 options[b'issue6528.fix-incoming'] = issue6528
1104 options[b'issue6528.fix-incoming'] = issue6528
1105
1105
1106 lazydelta = ui.configbool(b'storage', b'revlog.reuse-external-delta')
1106 lazydelta = ui.configbool(b'storage', b'revlog.reuse-external-delta')
1107 lazydeltabase = False
1107 lazydeltabase = False
1108 if lazydelta:
1108 if lazydelta:
1109 lazydeltabase = ui.configbool(
1109 lazydeltabase = ui.configbool(
1110 b'storage', b'revlog.reuse-external-delta-parent'
1110 b'storage', b'revlog.reuse-external-delta-parent'
1111 )
1111 )
1112 if lazydeltabase is None:
1112 if lazydeltabase is None:
1113 lazydeltabase = not scmutil.gddeltaconfig(ui)
1113 lazydeltabase = not scmutil.gddeltaconfig(ui)
1114 options[b'lazydelta'] = lazydelta
1114 options[b'lazydelta'] = lazydelta
1115 options[b'lazydeltabase'] = lazydeltabase
1115 options[b'lazydeltabase'] = lazydeltabase
1116
1116
1117 chainspan = ui.configbytes(b'experimental', b'maxdeltachainspan')
1117 chainspan = ui.configbytes(b'experimental', b'maxdeltachainspan')
1118 if 0 <= chainspan:
1118 if 0 <= chainspan:
1119 options[b'maxdeltachainspan'] = chainspan
1119 options[b'maxdeltachainspan'] = chainspan
1120
1120
1121 mmapindexthreshold = ui.configbytes(b'experimental', b'mmapindexthreshold')
1121 mmapindexthreshold = ui.configbytes(b'experimental', b'mmapindexthreshold')
1122 if mmapindexthreshold is not None:
1122 if mmapindexthreshold is not None:
1123 options[b'mmapindexthreshold'] = mmapindexthreshold
1123 options[b'mmapindexthreshold'] = mmapindexthreshold
1124
1124
1125 withsparseread = ui.configbool(b'experimental', b'sparse-read')
1125 withsparseread = ui.configbool(b'experimental', b'sparse-read')
1126 srdensitythres = float(
1126 srdensitythres = float(
1127 ui.config(b'experimental', b'sparse-read.density-threshold')
1127 ui.config(b'experimental', b'sparse-read.density-threshold')
1128 )
1128 )
1129 srmingapsize = ui.configbytes(b'experimental', b'sparse-read.min-gap-size')
1129 srmingapsize = ui.configbytes(b'experimental', b'sparse-read.min-gap-size')
1130 options[b'with-sparse-read'] = withsparseread
1130 options[b'with-sparse-read'] = withsparseread
1131 options[b'sparse-read-density-threshold'] = srdensitythres
1131 options[b'sparse-read-density-threshold'] = srdensitythres
1132 options[b'sparse-read-min-gap-size'] = srmingapsize
1132 options[b'sparse-read-min-gap-size'] = srmingapsize
1133
1133
1134 sparserevlog = requirementsmod.SPARSEREVLOG_REQUIREMENT in requirements
1134 sparserevlog = requirementsmod.SPARSEREVLOG_REQUIREMENT in requirements
1135 options[b'sparse-revlog'] = sparserevlog
1135 options[b'sparse-revlog'] = sparserevlog
1136 if sparserevlog:
1136 if sparserevlog:
1137 options[b'generaldelta'] = True
1137 options[b'generaldelta'] = True
1138
1138
1139 maxchainlen = None
1139 maxchainlen = None
1140 if sparserevlog:
1140 if sparserevlog:
1141 maxchainlen = revlogconst.SPARSE_REVLOG_MAX_CHAIN_LENGTH
1141 maxchainlen = revlogconst.SPARSE_REVLOG_MAX_CHAIN_LENGTH
1142 # experimental config: format.maxchainlen
1142 # experimental config: format.maxchainlen
1143 maxchainlen = ui.configint(b'format', b'maxchainlen', maxchainlen)
1143 maxchainlen = ui.configint(b'format', b'maxchainlen', maxchainlen)
1144 if maxchainlen is not None:
1144 if maxchainlen is not None:
1145 options[b'maxchainlen'] = maxchainlen
1145 options[b'maxchainlen'] = maxchainlen
1146
1146
1147 for r in requirements:
1147 for r in requirements:
1148 # we allow multiple compression engine requirement to co-exist because
1148 # we allow multiple compression engine requirement to co-exist because
1149 # strickly speaking, revlog seems to support mixed compression style.
1149 # strickly speaking, revlog seems to support mixed compression style.
1150 #
1150 #
1151 # The compression used for new entries will be "the last one"
1151 # The compression used for new entries will be "the last one"
1152 prefix = r.startswith
1152 prefix = r.startswith
1153 if prefix(b'revlog-compression-') or prefix(b'exp-compression-'):
1153 if prefix(b'revlog-compression-') or prefix(b'exp-compression-'):
1154 options[b'compengine'] = r.split(b'-', 2)[2]
1154 options[b'compengine'] = r.split(b'-', 2)[2]
1155
1155
1156 options[b'zlib.level'] = ui.configint(b'storage', b'revlog.zlib.level')
1156 options[b'zlib.level'] = ui.configint(b'storage', b'revlog.zlib.level')
1157 if options[b'zlib.level'] is not None:
1157 if options[b'zlib.level'] is not None:
1158 if not (0 <= options[b'zlib.level'] <= 9):
1158 if not (0 <= options[b'zlib.level'] <= 9):
1159 msg = _(b'invalid value for `storage.revlog.zlib.level` config: %d')
1159 msg = _(b'invalid value for `storage.revlog.zlib.level` config: %d')
1160 raise error.Abort(msg % options[b'zlib.level'])
1160 raise error.Abort(msg % options[b'zlib.level'])
1161 options[b'zstd.level'] = ui.configint(b'storage', b'revlog.zstd.level')
1161 options[b'zstd.level'] = ui.configint(b'storage', b'revlog.zstd.level')
1162 if options[b'zstd.level'] is not None:
1162 if options[b'zstd.level'] is not None:
1163 if not (0 <= options[b'zstd.level'] <= 22):
1163 if not (0 <= options[b'zstd.level'] <= 22):
1164 msg = _(b'invalid value for `storage.revlog.zstd.level` config: %d')
1164 msg = _(b'invalid value for `storage.revlog.zstd.level` config: %d')
1165 raise error.Abort(msg % options[b'zstd.level'])
1165 raise error.Abort(msg % options[b'zstd.level'])
1166
1166
1167 if requirementsmod.NARROW_REQUIREMENT in requirements:
1167 if requirementsmod.NARROW_REQUIREMENT in requirements:
1168 options[b'enableellipsis'] = True
1168 options[b'enableellipsis'] = True
1169
1169
1170 if ui.configbool(b'experimental', b'rust.index'):
1170 if ui.configbool(b'experimental', b'rust.index'):
1171 options[b'rust.index'] = True
1171 options[b'rust.index'] = True
1172 if requirementsmod.NODEMAP_REQUIREMENT in requirements:
1172 if requirementsmod.NODEMAP_REQUIREMENT in requirements:
1173 slow_path = ui.config(
1173 slow_path = ui.config(
1174 b'storage', b'revlog.persistent-nodemap.slow-path'
1174 b'storage', b'revlog.persistent-nodemap.slow-path'
1175 )
1175 )
1176 if slow_path not in (b'allow', b'warn', b'abort'):
1176 if slow_path not in (b'allow', b'warn', b'abort'):
1177 default = ui.config_default(
1177 default = ui.config_default(
1178 b'storage', b'revlog.persistent-nodemap.slow-path'
1178 b'storage', b'revlog.persistent-nodemap.slow-path'
1179 )
1179 )
1180 msg = _(
1180 msg = _(
1181 b'unknown value for config '
1181 b'unknown value for config '
1182 b'"storage.revlog.persistent-nodemap.slow-path": "%s"\n'
1182 b'"storage.revlog.persistent-nodemap.slow-path": "%s"\n'
1183 )
1183 )
1184 ui.warn(msg % slow_path)
1184 ui.warn(msg % slow_path)
1185 if not ui.quiet:
1185 if not ui.quiet:
1186 ui.warn(_(b'falling back to default value: %s\n') % default)
1186 ui.warn(_(b'falling back to default value: %s\n') % default)
1187 slow_path = default
1187 slow_path = default
1188
1188
1189 msg = _(
1189 msg = _(
1190 b"accessing `persistent-nodemap` repository without associated "
1190 b"accessing `persistent-nodemap` repository without associated "
1191 b"fast implementation."
1191 b"fast implementation."
1192 )
1192 )
1193 hint = _(
1193 hint = _(
1194 b"check `hg help config.format.use-persistent-nodemap` "
1194 b"check `hg help config.format.use-persistent-nodemap` "
1195 b"for details"
1195 b"for details"
1196 )
1196 )
1197 if not revlog.HAS_FAST_PERSISTENT_NODEMAP:
1197 if not revlog.HAS_FAST_PERSISTENT_NODEMAP:
1198 if slow_path == b'warn':
1198 if slow_path == b'warn':
1199 msg = b"warning: " + msg + b'\n'
1199 msg = b"warning: " + msg + b'\n'
1200 ui.warn(msg)
1200 ui.warn(msg)
1201 if not ui.quiet:
1201 if not ui.quiet:
1202 hint = b'(' + hint + b')\n'
1202 hint = b'(' + hint + b')\n'
1203 ui.warn(hint)
1203 ui.warn(hint)
1204 if slow_path == b'abort':
1204 if slow_path == b'abort':
1205 raise error.Abort(msg, hint=hint)
1205 raise error.Abort(msg, hint=hint)
1206 options[b'persistent-nodemap'] = True
1206 options[b'persistent-nodemap'] = True
1207 if requirementsmod.DIRSTATE_V2_REQUIREMENT in requirements:
1207 if requirementsmod.DIRSTATE_V2_REQUIREMENT in requirements:
1208 slow_path = ui.config(b'storage', b'dirstate-v2.slow-path')
1208 slow_path = ui.config(b'storage', b'dirstate-v2.slow-path')
1209 if slow_path not in (b'allow', b'warn', b'abort'):
1209 if slow_path not in (b'allow', b'warn', b'abort'):
1210 default = ui.config_default(b'storage', b'dirstate-v2.slow-path')
1210 default = ui.config_default(b'storage', b'dirstate-v2.slow-path')
1211 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')
1212 ui.warn(msg % slow_path)
1212 ui.warn(msg % slow_path)
1213 if not ui.quiet:
1213 if not ui.quiet:
1214 ui.warn(_(b'falling back to default value: %s\n') % default)
1214 ui.warn(_(b'falling back to default value: %s\n') % default)
1215 slow_path = default
1215 slow_path = default
1216
1216
1217 msg = _(
1217 msg = _(
1218 b"accessing `dirstate-v2` repository without associated "
1218 b"accessing `dirstate-v2` repository without associated "
1219 b"fast implementation."
1219 b"fast implementation."
1220 )
1220 )
1221 hint = _(
1221 hint = _(
1222 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"
1223 )
1223 )
1224 if not dirstate.HAS_FAST_DIRSTATE_V2:
1224 if not dirstate.HAS_FAST_DIRSTATE_V2:
1225 if slow_path == b'warn':
1225 if slow_path == b'warn':
1226 msg = b"warning: " + msg + b'\n'
1226 msg = b"warning: " + msg + b'\n'
1227 ui.warn(msg)
1227 ui.warn(msg)
1228 if not ui.quiet:
1228 if not ui.quiet:
1229 hint = b'(' + hint + b')\n'
1229 hint = b'(' + hint + b')\n'
1230 ui.warn(hint)
1230 ui.warn(hint)
1231 if slow_path == b'abort':
1231 if slow_path == b'abort':
1232 raise error.Abort(msg, hint=hint)
1232 raise error.Abort(msg, hint=hint)
1233 if ui.configbool(b'storage', b'revlog.persistent-nodemap.mmap'):
1233 if ui.configbool(b'storage', b'revlog.persistent-nodemap.mmap'):
1234 options[b'persistent-nodemap.mmap'] = True
1234 options[b'persistent-nodemap.mmap'] = True
1235 if ui.configbool(b'devel', b'persistent-nodemap'):
1235 if ui.configbool(b'devel', b'persistent-nodemap'):
1236 options[b'devel-force-nodemap'] = True
1236 options[b'devel-force-nodemap'] = True
1237
1237
1238 return options
1238 return options
1239
1239
1240
1240
1241 def makemain(**kwargs):
1241 def makemain(**kwargs):
1242 """Produce a type conforming to ``ilocalrepositorymain``."""
1242 """Produce a type conforming to ``ilocalrepositorymain``."""
1243 return localrepository
1243 return localrepository
1244
1244
1245
1245
1246 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
1246 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
1247 class revlogfilestorage:
1247 class revlogfilestorage:
1248 """File storage when using revlogs."""
1248 """File storage when using revlogs."""
1249
1249
1250 def file(self, path):
1250 def file(self, path):
1251 if path.startswith(b'/'):
1251 if path.startswith(b'/'):
1252 path = path[1:]
1252 path = path[1:]
1253
1253
1254 try_split = (
1254 try_split = (
1255 self.currenttransaction() is not None
1255 self.currenttransaction() is not None
1256 or txnutil.mayhavepending(self.root)
1256 or txnutil.mayhavepending(self.root)
1257 )
1257 )
1258
1258
1259 return filelog.filelog(self.svfs, path, try_split=try_split)
1259 return filelog.filelog(self.svfs, path, try_split=try_split)
1260
1260
1261
1261
1262 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
1262 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
1263 class revlognarrowfilestorage:
1263 class revlognarrowfilestorage:
1264 """File storage when using revlogs and narrow files."""
1264 """File storage when using revlogs and narrow files."""
1265
1265
1266 def file(self, path):
1266 def file(self, path):
1267 if path.startswith(b'/'):
1267 if path.startswith(b'/'):
1268 path = path[1:]
1268 path = path[1:]
1269
1269
1270 try_split = (
1270 try_split = (
1271 self.currenttransaction() is not None
1271 self.currenttransaction() is not None
1272 or txnutil.mayhavepending(self.root)
1272 or txnutil.mayhavepending(self.root)
1273 )
1273 )
1274 return filelog.narrowfilelog(
1274 return filelog.narrowfilelog(
1275 self.svfs, path, self._storenarrowmatch, try_split=try_split
1275 self.svfs, path, self._storenarrowmatch, try_split=try_split
1276 )
1276 )
1277
1277
1278
1278
1279 def makefilestorage(requirements, features, **kwargs):
1279 def makefilestorage(requirements, features, **kwargs):
1280 """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
1280 """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
1281 features.add(repository.REPO_FEATURE_REVLOG_FILE_STORAGE)
1281 features.add(repository.REPO_FEATURE_REVLOG_FILE_STORAGE)
1282 features.add(repository.REPO_FEATURE_STREAM_CLONE)
1282 features.add(repository.REPO_FEATURE_STREAM_CLONE)
1283
1283
1284 if requirementsmod.NARROW_REQUIREMENT in requirements:
1284 if requirementsmod.NARROW_REQUIREMENT in requirements:
1285 return revlognarrowfilestorage
1285 return revlognarrowfilestorage
1286 else:
1286 else:
1287 return revlogfilestorage
1287 return revlogfilestorage
1288
1288
1289
1289
1290 # List of repository interfaces and factory functions for them. Each
1290 # List of repository interfaces and factory functions for them. Each
1291 # will be called in order during ``makelocalrepository()`` to iteratively
1291 # will be called in order during ``makelocalrepository()`` to iteratively
1292 # 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
1293 # 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
1294 # functions can be wrapped.
1294 # functions can be wrapped.
1295 REPO_INTERFACES = [
1295 REPO_INTERFACES = [
1296 (repository.ilocalrepositorymain, lambda: makemain),
1296 (repository.ilocalrepositorymain, lambda: makemain),
1297 (repository.ilocalrepositoryfilestorage, lambda: makefilestorage),
1297 (repository.ilocalrepositoryfilestorage, lambda: makefilestorage),
1298 ]
1298 ]
1299
1299
1300
1300
1301 @interfaceutil.implementer(repository.ilocalrepositorymain)
1301 @interfaceutil.implementer(repository.ilocalrepositorymain)
1302 class localrepository:
1302 class localrepository:
1303 """Main class for representing local repositories.
1303 """Main class for representing local repositories.
1304
1304
1305 All local repositories are instances of this class.
1305 All local repositories are instances of this class.
1306
1306
1307 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
1308 repository objects. To obtain a usable repository object, call
1308 repository objects. To obtain a usable repository object, call
1309 ``hg.repository()``, ``localrepo.instance()``, or
1309 ``hg.repository()``, ``localrepo.instance()``, or
1310 ``localrepo.makelocalrepository()``. The latter is the lowest-level.
1310 ``localrepo.makelocalrepository()``. The latter is the lowest-level.
1311 ``instance()`` adds support for creating new repositories.
1311 ``instance()`` adds support for creating new repositories.
1312 ``hg.repository()`` adds more extension integration, including calling
1312 ``hg.repository()`` adds more extension integration, including calling
1313 ``reposetup()``. Generally speaking, ``hg.repository()`` should be
1313 ``reposetup()``. Generally speaking, ``hg.repository()`` should be
1314 used.
1314 used.
1315 """
1315 """
1316
1316
1317 _basesupported = {
1317 _basesupported = {
1318 requirementsmod.ARCHIVED_PHASE_REQUIREMENT,
1318 requirementsmod.ARCHIVED_PHASE_REQUIREMENT,
1319 requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT,
1319 requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT,
1320 requirementsmod.CHANGELOGV2_REQUIREMENT,
1320 requirementsmod.CHANGELOGV2_REQUIREMENT,
1321 requirementsmod.COPIESSDC_REQUIREMENT,
1321 requirementsmod.COPIESSDC_REQUIREMENT,
1322 requirementsmod.DIRSTATE_TRACKED_HINT_V1,
1322 requirementsmod.DIRSTATE_TRACKED_HINT_V1,
1323 requirementsmod.DIRSTATE_V2_REQUIREMENT,
1323 requirementsmod.DIRSTATE_V2_REQUIREMENT,
1324 requirementsmod.DOTENCODE_REQUIREMENT,
1324 requirementsmod.DOTENCODE_REQUIREMENT,
1325 requirementsmod.FNCACHE_REQUIREMENT,
1325 requirementsmod.FNCACHE_REQUIREMENT,
1326 requirementsmod.GENERALDELTA_REQUIREMENT,
1326 requirementsmod.GENERALDELTA_REQUIREMENT,
1327 requirementsmod.INTERNAL_PHASE_REQUIREMENT,
1327 requirementsmod.INTERNAL_PHASE_REQUIREMENT,
1328 requirementsmod.NODEMAP_REQUIREMENT,
1328 requirementsmod.NODEMAP_REQUIREMENT,
1329 requirementsmod.RELATIVE_SHARED_REQUIREMENT,
1329 requirementsmod.RELATIVE_SHARED_REQUIREMENT,
1330 requirementsmod.REVLOGV1_REQUIREMENT,
1330 requirementsmod.REVLOGV1_REQUIREMENT,
1331 requirementsmod.REVLOGV2_REQUIREMENT,
1331 requirementsmod.REVLOGV2_REQUIREMENT,
1332 requirementsmod.SHARED_REQUIREMENT,
1332 requirementsmod.SHARED_REQUIREMENT,
1333 requirementsmod.SHARESAFE_REQUIREMENT,
1333 requirementsmod.SHARESAFE_REQUIREMENT,
1334 requirementsmod.SPARSE_REQUIREMENT,
1334 requirementsmod.SPARSE_REQUIREMENT,
1335 requirementsmod.SPARSEREVLOG_REQUIREMENT,
1335 requirementsmod.SPARSEREVLOG_REQUIREMENT,
1336 requirementsmod.STORE_REQUIREMENT,
1336 requirementsmod.STORE_REQUIREMENT,
1337 requirementsmod.TREEMANIFEST_REQUIREMENT,
1337 requirementsmod.TREEMANIFEST_REQUIREMENT,
1338 }
1338 }
1339
1339
1340 # list of prefix for file which can be written without 'wlock'
1340 # list of prefix for file which can be written without 'wlock'
1341 # Extensions should extend this list when needed
1341 # Extensions should extend this list when needed
1342 _wlockfreeprefix = {
1342 _wlockfreeprefix = {
1343 # We migh consider requiring 'wlock' for the next
1343 # We migh consider requiring 'wlock' for the next
1344 # two, but pretty much all the existing code assume
1344 # two, but pretty much all the existing code assume
1345 # wlock is not needed so we keep them excluded for
1345 # wlock is not needed so we keep them excluded for
1346 # now.
1346 # now.
1347 b'hgrc',
1347 b'hgrc',
1348 b'requires',
1348 b'requires',
1349 # XXX cache is a complicatged business someone
1349 # XXX cache is a complicatged business someone
1350 # should investigate this in depth at some point
1350 # should investigate this in depth at some point
1351 b'cache/',
1351 b'cache/',
1352 # XXX bisect was still a bit too messy at the time
1352 # XXX bisect was still a bit too messy at the time
1353 # this changeset was introduced. Someone should fix
1353 # this changeset was introduced. Someone should fix
1354 # the remainig bit and drop this line
1354 # the remainig bit and drop this line
1355 b'bisect.state',
1355 b'bisect.state',
1356 }
1356 }
1357
1357
1358 def __init__(
1358 def __init__(
1359 self,
1359 self,
1360 baseui,
1360 baseui,
1361 ui,
1361 ui,
1362 origroot: bytes,
1362 origroot: bytes,
1363 wdirvfs: vfsmod.vfs,
1363 wdirvfs: vfsmod.vfs,
1364 hgvfs: vfsmod.vfs,
1364 hgvfs: vfsmod.vfs,
1365 requirements,
1365 requirements,
1366 supportedrequirements,
1366 supportedrequirements,
1367 sharedpath: bytes,
1367 sharedpath: bytes,
1368 store,
1368 store,
1369 cachevfs: vfsmod.vfs,
1369 cachevfs: vfsmod.vfs,
1370 wcachevfs: vfsmod.vfs,
1370 wcachevfs: vfsmod.vfs,
1371 features,
1371 features,
1372 intents=None,
1372 intents=None,
1373 ):
1373 ):
1374 """Create a new local repository instance.
1374 """Create a new local repository instance.
1375
1375
1376 Most callers should use ``hg.repository()``, ``localrepo.instance()``,
1376 Most callers should use ``hg.repository()``, ``localrepo.instance()``,
1377 or ``localrepo.makelocalrepository()`` for obtaining a new repository
1377 or ``localrepo.makelocalrepository()`` for obtaining a new repository
1378 object.
1378 object.
1379
1379
1380 Arguments:
1380 Arguments:
1381
1381
1382 baseui
1382 baseui
1383 ``ui.ui`` instance that ``ui`` argument was based off of.
1383 ``ui.ui`` instance that ``ui`` argument was based off of.
1384
1384
1385 ui
1385 ui
1386 ``ui.ui`` instance for use by the repository.
1386 ``ui.ui`` instance for use by the repository.
1387
1387
1388 origroot
1388 origroot
1389 ``bytes`` path to working directory root of this repository.
1389 ``bytes`` path to working directory root of this repository.
1390
1390
1391 wdirvfs
1391 wdirvfs
1392 ``vfs.vfs`` rooted at the working directory.
1392 ``vfs.vfs`` rooted at the working directory.
1393
1393
1394 hgvfs
1394 hgvfs
1395 ``vfs.vfs`` rooted at .hg/
1395 ``vfs.vfs`` rooted at .hg/
1396
1396
1397 requirements
1397 requirements
1398 ``set`` of bytestrings representing repository opening requirements.
1398 ``set`` of bytestrings representing repository opening requirements.
1399
1399
1400 supportedrequirements
1400 supportedrequirements
1401 ``set`` of bytestrings representing repository requirements that we
1401 ``set`` of bytestrings representing repository requirements that we
1402 know how to open. May be a supetset of ``requirements``.
1402 know how to open. May be a supetset of ``requirements``.
1403
1403
1404 sharedpath
1404 sharedpath
1405 ``bytes`` Defining path to storage base directory. Points to a
1405 ``bytes`` Defining path to storage base directory. Points to a
1406 ``.hg/`` directory somewhere.
1406 ``.hg/`` directory somewhere.
1407
1407
1408 store
1408 store
1409 ``store.basicstore`` (or derived) instance providing access to
1409 ``store.basicstore`` (or derived) instance providing access to
1410 versioned storage.
1410 versioned storage.
1411
1411
1412 cachevfs
1412 cachevfs
1413 ``vfs.vfs`` used for cache files.
1413 ``vfs.vfs`` used for cache files.
1414
1414
1415 wcachevfs
1415 wcachevfs
1416 ``vfs.vfs`` used for cache files related to the working copy.
1416 ``vfs.vfs`` used for cache files related to the working copy.
1417
1417
1418 features
1418 features
1419 ``set`` of bytestrings defining features/capabilities of this
1419 ``set`` of bytestrings defining features/capabilities of this
1420 instance.
1420 instance.
1421
1421
1422 intents
1422 intents
1423 ``set`` of system strings indicating what this repo will be used
1423 ``set`` of system strings indicating what this repo will be used
1424 for.
1424 for.
1425 """
1425 """
1426 self.baseui = baseui
1426 self.baseui = baseui
1427 self.ui = ui
1427 self.ui = ui
1428 self.origroot = origroot
1428 self.origroot = origroot
1429 # vfs rooted at working directory.
1429 # vfs rooted at working directory.
1430 self.wvfs = wdirvfs
1430 self.wvfs = wdirvfs
1431 self.root = wdirvfs.base
1431 self.root = wdirvfs.base
1432 # vfs rooted at .hg/. Used to access most non-store paths.
1432 # vfs rooted at .hg/. Used to access most non-store paths.
1433 self.vfs = hgvfs
1433 self.vfs = hgvfs
1434 self.path = hgvfs.base
1434 self.path = hgvfs.base
1435 self.requirements = requirements
1435 self.requirements = requirements
1436 self.nodeconstants = sha1nodeconstants
1436 self.nodeconstants = sha1nodeconstants
1437 self.nullid = self.nodeconstants.nullid
1437 self.nullid = self.nodeconstants.nullid
1438 self.supported = supportedrequirements
1438 self.supported = supportedrequirements
1439 self.sharedpath = sharedpath
1439 self.sharedpath = sharedpath
1440 self.store = store
1440 self.store = store
1441 self.cachevfs = cachevfs
1441 self.cachevfs = cachevfs
1442 self.wcachevfs = wcachevfs
1442 self.wcachevfs = wcachevfs
1443 self.features = features
1443 self.features = features
1444
1444
1445 self.filtername = None
1445 self.filtername = None
1446
1446
1447 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(
1448 b'devel', b'check-locks'
1448 b'devel', b'check-locks'
1449 ):
1449 ):
1450 self.vfs.audit = self._getvfsward(self.vfs.audit)
1450 self.vfs.audit = self._getvfsward(self.vfs.audit)
1451 # 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.
1452 # Callback are in the form: func(repo, roots) --> processed root.
1452 # Callback are in the form: func(repo, roots) --> processed root.
1453 # This list it to be filled by extension during repo setup
1453 # This list it to be filled by extension during repo setup
1454 self._phasedefaults = []
1454 self._phasedefaults = []
1455
1455
1456 color.setup(self.ui)
1456 color.setup(self.ui)
1457
1457
1458 self.spath = self.store.path
1458 self.spath = self.store.path
1459 self.svfs = self.store.vfs
1459 self.svfs = self.store.vfs
1460 self.sjoin = self.store.join
1460 self.sjoin = self.store.join
1461 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(
1462 b'devel', b'check-locks'
1462 b'devel', b'check-locks'
1463 ):
1463 ):
1464 if hasattr(self.svfs, 'vfs'): # this is filtervfs
1464 if hasattr(self.svfs, 'vfs'): # this is filtervfs
1465 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
1465 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
1466 else: # standard vfs
1466 else: # standard vfs
1467 self.svfs.audit = self._getsvfsward(self.svfs.audit)
1467 self.svfs.audit = self._getsvfsward(self.svfs.audit)
1468
1468
1469 self._dirstatevalidatewarned = False
1469 self._dirstatevalidatewarned = False
1470
1470
1471 self._branchcaches = branchmap.BranchMapCache()
1471 self._branchcaches = branchmap.BranchMapCache()
1472 self._revbranchcache = None
1472 self._revbranchcache = None
1473 self._filterpats = {}
1473 self._filterpats = {}
1474 self._datafilters = {}
1474 self._datafilters = {}
1475 self._transref = self._lockref = self._wlockref = None
1475 self._transref = self._lockref = self._wlockref = None
1476
1476
1477 # A cache for various files under .hg/ that tracks file changes,
1477 # A cache for various files under .hg/ that tracks file changes,
1478 # (used by the filecache decorator)
1478 # (used by the filecache decorator)
1479 #
1479 #
1480 # Maps a property name to its util.filecacheentry
1480 # Maps a property name to its util.filecacheentry
1481 self._filecache = {}
1481 self._filecache = {}
1482
1482
1483 # hold sets of revision to be filtered
1483 # hold sets of revision to be filtered
1484 # should be cleared when something might have changed the filter value:
1484 # should be cleared when something might have changed the filter value:
1485 # - new changesets,
1485 # - new changesets,
1486 # - phase change,
1486 # - phase change,
1487 # - new obsolescence marker,
1487 # - new obsolescence marker,
1488 # - working directory parent change,
1488 # - working directory parent change,
1489 # - bookmark changes
1489 # - bookmark changes
1490 self.filteredrevcache = {}
1490 self.filteredrevcache = {}
1491
1491
1492 self._dirstate = None
1492 self._dirstate = None
1493 # post-dirstate-status hooks
1493 # post-dirstate-status hooks
1494 self._postdsstatus = []
1494 self._postdsstatus = []
1495
1495
1496 self._pending_narrow_pats = None
1496 self._pending_narrow_pats = None
1497 self._pending_narrow_pats_dirstate = None
1497 self._pending_narrow_pats_dirstate = None
1498
1498
1499 # generic mapping between names and nodes
1499 # generic mapping between names and nodes
1500 self.names = namespaces.namespaces()
1500 self.names = namespaces.namespaces()
1501
1501
1502 # Key to signature value.
1502 # Key to signature value.
1503 self._sparsesignaturecache = {}
1503 self._sparsesignaturecache = {}
1504 # Signature to cached matcher instance.
1504 # Signature to cached matcher instance.
1505 self._sparsematchercache = {}
1505 self._sparsematchercache = {}
1506
1506
1507 self._extrafilterid = repoview.extrafilter(ui)
1507 self._extrafilterid = repoview.extrafilter(ui)
1508
1508
1509 self.filecopiesmode = None
1509 self.filecopiesmode = None
1510 if requirementsmod.COPIESSDC_REQUIREMENT in self.requirements:
1510 if requirementsmod.COPIESSDC_REQUIREMENT in self.requirements:
1511 self.filecopiesmode = b'changeset-sidedata'
1511 self.filecopiesmode = b'changeset-sidedata'
1512
1512
1513 self._wanted_sidedata = set()
1513 self._wanted_sidedata = set()
1514 self._sidedata_computers = {}
1514 self._sidedata_computers = {}
1515 sidedatamod.set_sidedata_spec_for_repo(self)
1515 sidedatamod.set_sidedata_spec_for_repo(self)
1516
1516
1517 def _getvfsward(self, origfunc):
1517 def _getvfsward(self, origfunc):
1518 """build a ward for self.vfs"""
1518 """build a ward for self.vfs"""
1519 rref = weakref.ref(self)
1519 rref = weakref.ref(self)
1520
1520
1521 def checkvfs(path, mode=None):
1521 def checkvfs(path, mode=None):
1522 ret = origfunc(path, mode=mode)
1522 ret = origfunc(path, mode=mode)
1523 repo = rref()
1523 repo = rref()
1524 if (
1524 if (
1525 repo is None
1525 repo is None
1526 or not hasattr(repo, '_wlockref')
1526 or not hasattr(repo, '_wlockref')
1527 or not hasattr(repo, '_lockref')
1527 or not hasattr(repo, '_lockref')
1528 ):
1528 ):
1529 return
1529 return
1530 if mode in (None, b'r', b'rb'):
1530 if mode in (None, b'r', b'rb'):
1531 return
1531 return
1532 if path.startswith(repo.path):
1532 if path.startswith(repo.path):
1533 # truncate name relative to the repository (.hg)
1533 # truncate name relative to the repository (.hg)
1534 path = path[len(repo.path) + 1 :]
1534 path = path[len(repo.path) + 1 :]
1535 if path.startswith(b'cache/'):
1535 if path.startswith(b'cache/'):
1536 msg = b'accessing cache with vfs instead of cachevfs: "%s"'
1536 msg = b'accessing cache with vfs instead of cachevfs: "%s"'
1537 repo.ui.develwarn(msg % path, stacklevel=3, config=b"cache-vfs")
1537 repo.ui.develwarn(msg % path, stacklevel=3, config=b"cache-vfs")
1538 # path prefixes covered by 'lock'
1538 # path prefixes covered by 'lock'
1539 vfs_path_prefixes = (
1539 vfs_path_prefixes = (
1540 b'journal.',
1540 b'journal.',
1541 b'undo.',
1541 b'undo.',
1542 b'strip-backup/',
1542 b'strip-backup/',
1543 b'cache/',
1543 b'cache/',
1544 )
1544 )
1545 if any(path.startswith(prefix) for prefix in vfs_path_prefixes):
1545 if any(path.startswith(prefix) for prefix in vfs_path_prefixes):
1546 if repo._currentlock(repo._lockref) is None:
1546 if repo._currentlock(repo._lockref) is None:
1547 repo.ui.develwarn(
1547 repo.ui.develwarn(
1548 b'write with no lock: "%s"' % path,
1548 b'write with no lock: "%s"' % path,
1549 stacklevel=3,
1549 stacklevel=3,
1550 config=b'check-locks',
1550 config=b'check-locks',
1551 )
1551 )
1552 elif repo._currentlock(repo._wlockref) is None:
1552 elif repo._currentlock(repo._wlockref) is None:
1553 # rest of vfs files are covered by 'wlock'
1553 # rest of vfs files are covered by 'wlock'
1554 #
1554 #
1555 # exclude special files
1555 # exclude special files
1556 for prefix in self._wlockfreeprefix:
1556 for prefix in self._wlockfreeprefix:
1557 if path.startswith(prefix):
1557 if path.startswith(prefix):
1558 return
1558 return
1559 repo.ui.develwarn(
1559 repo.ui.develwarn(
1560 b'write with no wlock: "%s"' % path,
1560 b'write with no wlock: "%s"' % path,
1561 stacklevel=3,
1561 stacklevel=3,
1562 config=b'check-locks',
1562 config=b'check-locks',
1563 )
1563 )
1564 return ret
1564 return ret
1565
1565
1566 return checkvfs
1566 return checkvfs
1567
1567
1568 def _getsvfsward(self, origfunc):
1568 def _getsvfsward(self, origfunc):
1569 """build a ward for self.svfs"""
1569 """build a ward for self.svfs"""
1570 rref = weakref.ref(self)
1570 rref = weakref.ref(self)
1571
1571
1572 def checksvfs(path, mode=None):
1572 def checksvfs(path, mode=None):
1573 ret = origfunc(path, mode=mode)
1573 ret = origfunc(path, mode=mode)
1574 repo = rref()
1574 repo = rref()
1575 if repo is None or not hasattr(repo, '_lockref'):
1575 if repo is None or not hasattr(repo, '_lockref'):
1576 return
1576 return
1577 if mode in (None, b'r', b'rb'):
1577 if mode in (None, b'r', b'rb'):
1578 return
1578 return
1579 if path.startswith(repo.sharedpath):
1579 if path.startswith(repo.sharedpath):
1580 # truncate name relative to the repository (.hg)
1580 # truncate name relative to the repository (.hg)
1581 path = path[len(repo.sharedpath) + 1 :]
1581 path = path[len(repo.sharedpath) + 1 :]
1582 if repo._currentlock(repo._lockref) is None:
1582 if repo._currentlock(repo._lockref) is None:
1583 repo.ui.develwarn(
1583 repo.ui.develwarn(
1584 b'write with no lock: "%s"' % path, stacklevel=4
1584 b'write with no lock: "%s"' % path, stacklevel=4
1585 )
1585 )
1586 return ret
1586 return ret
1587
1587
1588 return checksvfs
1588 return checksvfs
1589
1589
1590 @property
1590 @property
1591 def vfs_map(self):
1591 def vfs_map(self):
1592 return {
1592 return {
1593 b'': self.svfs,
1593 b'': self.svfs,
1594 b'plain': self.vfs,
1594 b'plain': self.vfs,
1595 b'store': self.svfs,
1595 b'store': self.svfs,
1596 }
1596 }
1597
1597
1598 def close(self):
1598 def close(self):
1599 self._writecaches()
1599 self._writecaches()
1600
1600
1601 def _writecaches(self):
1601 def _writecaches(self):
1602 if self._revbranchcache:
1602 if self._revbranchcache:
1603 self._revbranchcache.write()
1603 self._revbranchcache.write()
1604
1604
1605 def _restrictcapabilities(self, caps):
1605 def _restrictcapabilities(self, caps):
1606 if self.ui.configbool(b'experimental', b'bundle2-advertise'):
1606 if self.ui.configbool(b'experimental', b'bundle2-advertise'):
1607 caps = set(caps)
1607 caps = set(caps)
1608 capsblob = bundle2.encodecaps(
1608 capsblob = bundle2.encodecaps(
1609 bundle2.getrepocaps(self, role=b'client')
1609 bundle2.getrepocaps(self, role=b'client')
1610 )
1610 )
1611 caps.add(b'bundle2=' + urlreq.quote(capsblob))
1611 caps.add(b'bundle2=' + urlreq.quote(capsblob))
1612 if self.ui.configbool(b'experimental', b'narrow'):
1612 if self.ui.configbool(b'experimental', b'narrow'):
1613 caps.add(wireprototypes.NARROWCAP)
1613 caps.add(wireprototypes.NARROWCAP)
1614 return caps
1614 return caps
1615
1615
1616 # 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:
1617 # self -> auditor -> self._checknested -> self
1617 # self -> auditor -> self._checknested -> self
1618
1618
1619 @property
1619 @property
1620 def auditor(self):
1620 def auditor(self):
1621 # This is only used by context.workingctx.match in order to
1621 # This is only used by context.workingctx.match in order to
1622 # detect files in subrepos.
1622 # detect files in subrepos.
1623 return pathutil.pathauditor(self.root, callback=self._checknested)
1623 return pathutil.pathauditor(self.root, callback=self._checknested)
1624
1624
1625 @property
1625 @property
1626 def nofsauditor(self):
1626 def nofsauditor(self):
1627 # 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
1628 # files in subrepos.
1628 # files in subrepos.
1629 return pathutil.pathauditor(
1629 return pathutil.pathauditor(
1630 self.root, callback=self._checknested, realfs=False, cached=True
1630 self.root, callback=self._checknested, realfs=False, cached=True
1631 )
1631 )
1632
1632
1633 def _checknested(self, path):
1633 def _checknested(self, path):
1634 """Determine if path is a legal nested repository."""
1634 """Determine if path is a legal nested repository."""
1635 if not path.startswith(self.root):
1635 if not path.startswith(self.root):
1636 return False
1636 return False
1637 subpath = path[len(self.root) + 1 :]
1637 subpath = path[len(self.root) + 1 :]
1638 normsubpath = util.pconvert(subpath)
1638 normsubpath = util.pconvert(subpath)
1639
1639
1640 # XXX: Checking against the current working copy is wrong in
1640 # XXX: Checking against the current working copy is wrong in
1641 # the sense that it can reject things like
1641 # the sense that it can reject things like
1642 #
1642 #
1643 # $ hg cat -r 10 sub/x.txt
1643 # $ hg cat -r 10 sub/x.txt
1644 #
1644 #
1645 # if sub/ is no longer a subrepository in the working copy
1645 # if sub/ is no longer a subrepository in the working copy
1646 # parent revision.
1646 # parent revision.
1647 #
1647 #
1648 # However, it can of course also allow things that would have
1648 # However, it can of course also allow things that would have
1649 # been rejected before, such as the above cat command if sub/
1649 # been rejected before, such as the above cat command if sub/
1650 # is a subrepository now, but was a normal directory before.
1650 # is a subrepository now, but was a normal directory before.
1651 # The old path auditor would have rejected by mistake since it
1651 # The old path auditor would have rejected by mistake since it
1652 # panics when it sees sub/.hg/.
1652 # panics when it sees sub/.hg/.
1653 #
1653 #
1654 # All in all, checking against the working copy seems sensible
1654 # All in all, checking against the working copy seems sensible
1655 # since we want to prevent access to nested repositories on
1655 # since we want to prevent access to nested repositories on
1656 # the filesystem *now*.
1656 # the filesystem *now*.
1657 ctx = self[None]
1657 ctx = self[None]
1658 parts = util.splitpath(subpath)
1658 parts = util.splitpath(subpath)
1659 while parts:
1659 while parts:
1660 prefix = b'/'.join(parts)
1660 prefix = b'/'.join(parts)
1661 if prefix in ctx.substate:
1661 if prefix in ctx.substate:
1662 if prefix == normsubpath:
1662 if prefix == normsubpath:
1663 return True
1663 return True
1664 else:
1664 else:
1665 sub = ctx.sub(prefix)
1665 sub = ctx.sub(prefix)
1666 return sub.checknested(subpath[len(prefix) + 1 :])
1666 return sub.checknested(subpath[len(prefix) + 1 :])
1667 else:
1667 else:
1668 parts.pop()
1668 parts.pop()
1669 return False
1669 return False
1670
1670
1671 def peer(self, path=None, remotehidden=False):
1671 def peer(self, path=None, remotehidden=False):
1672 return localpeer(
1672 return localpeer(
1673 self, path=path, remotehidden=remotehidden
1673 self, path=path, remotehidden=remotehidden
1674 ) # not cached to avoid reference cycle
1674 ) # not cached to avoid reference cycle
1675
1675
1676 def unfiltered(self):
1676 def unfiltered(self):
1677 """Return unfiltered version of the repository
1677 """Return unfiltered version of the repository
1678
1678
1679 Intended to be overwritten by filtered repo."""
1679 Intended to be overwritten by filtered repo."""
1680 return self
1680 return self
1681
1681
1682 def filtered(self, name, visibilityexceptions=None):
1682 def filtered(self, name, visibilityexceptions=None):
1683 """Return a filtered version of a repository
1683 """Return a filtered version of a repository
1684
1684
1685 The `name` parameter is the identifier of the requested view. This
1685 The `name` parameter is the identifier of the requested view. This
1686 will return a repoview object set "exactly" to the specified view.
1686 will return a repoview object set "exactly" to the specified view.
1687
1687
1688 This function does not apply recursive filtering to a repository. For
1688 This function does not apply recursive filtering to a repository. For
1689 example calling `repo.filtered("served")` will return a repoview using
1689 example calling `repo.filtered("served")` will return a repoview using
1690 the "served" view, regardless of the initial view used by `repo`.
1690 the "served" view, regardless of the initial view used by `repo`.
1691
1691
1692 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".
1693 """
1693 """
1694 if self._extrafilterid is not None and b'%' not in name:
1694 if self._extrafilterid is not None and b'%' not in name:
1695 name = name + b'%' + self._extrafilterid
1695 name = name + b'%' + self._extrafilterid
1696
1696
1697 cls = repoview.newtype(self.unfiltered().__class__)
1697 cls = repoview.newtype(self.unfiltered().__class__)
1698 return cls(self, name, visibilityexceptions)
1698 return cls(self, name, visibilityexceptions)
1699
1699
1700 @mixedrepostorecache(
1700 @mixedrepostorecache(
1701 (b'bookmarks', b'plain'),
1701 (b'bookmarks', b'plain'),
1702 (b'bookmarks.current', b'plain'),
1702 (b'bookmarks.current', b'plain'),
1703 (b'bookmarks', b''),
1703 (b'bookmarks', b''),
1704 (b'00changelog.i', b''),
1704 (b'00changelog.i', b''),
1705 )
1705 )
1706 def _bookmarks(self):
1706 def _bookmarks(self):
1707 # Since the multiple files involved in the transaction cannot be
1707 # Since the multiple files involved in the transaction cannot be
1708 # written atomically (with current repository format), there is a race
1708 # written atomically (with current repository format), there is a race
1709 # condition here.
1709 # condition here.
1710 #
1710 #
1711 # 1) changelog content A is read
1711 # 1) changelog content A is read
1712 # 2) outside transaction update changelog to content B
1712 # 2) outside transaction update changelog to content B
1713 # 3) outside transaction update bookmark file referring to content B
1713 # 3) outside transaction update bookmark file referring to content B
1714 # 4) bookmarks file content is read and filtered against changelog-A
1714 # 4) bookmarks file content is read and filtered against changelog-A
1715 #
1715 #
1716 # When this happens, bookmarks against nodes missing from A are dropped.
1716 # When this happens, bookmarks against nodes missing from A are dropped.
1717 #
1717 #
1718 # 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
1719 # when this happen during write because the bookmarks to the "unknown"
1719 # when this happen during write because the bookmarks to the "unknown"
1720 # nodes will be dropped for good. However, writes happen within locks.
1720 # nodes will be dropped for good. However, writes happen within locks.
1721 # 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.
1722 # For this purpose data read from disc before locking are
1722 # For this purpose data read from disc before locking are
1723 # "invalidated" right after the locks are taken. This invalidations are
1723 # "invalidated" right after the locks are taken. This invalidations are
1724 # "light", the `filecache` mechanism keep the data in memory and will
1724 # "light", the `filecache` mechanism keep the data in memory and will
1725 # 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
1726 # same data multiple times helps performances.
1726 # same data multiple times helps performances.
1727 #
1727 #
1728 # Unfortunately in the case describe above, the files tracked by the
1728 # Unfortunately in the case describe above, the files tracked by the
1729 # bookmarks file cache might not have changed, but the in-memory
1729 # bookmarks file cache might not have changed, but the in-memory
1730 # content is still "wrong" because we used an older changelog content
1730 # content is still "wrong" because we used an older changelog content
1731 # 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
1732 # refreshed but `_bookmarks` would be preserved.
1732 # refreshed but `_bookmarks` would be preserved.
1733 # Adding `00changelog.i` to the list of tracked file is not
1733 # Adding `00changelog.i` to the list of tracked file is not
1734 # 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
1735 # (4), the changelog file has already diverged from the content used
1735 # (4), the changelog file has already diverged from the content used
1736 # for loading `changelog` in (1)
1736 # for loading `changelog` in (1)
1737 #
1737 #
1738 # To prevent the issue, we force the changelog to be explicitly
1738 # To prevent the issue, we force the changelog to be explicitly
1739 # reloaded while computing `_bookmarks`. The data race can still happen
1739 # reloaded while computing `_bookmarks`. The data race can still happen
1740 # 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
1741 # undetected during the lock time refresh.
1741 # undetected during the lock time refresh.
1742 #
1742 #
1743 # The new schedule is as follow
1743 # The new schedule is as follow
1744 #
1744 #
1745 # 1) filecache logic detect that `_bookmarks` needs to be computed
1745 # 1) filecache logic detect that `_bookmarks` needs to be computed
1746 # 2) cachestat for `bookmarks` and `changelog` are captured (for book)
1746 # 2) cachestat for `bookmarks` and `changelog` are captured (for book)
1747 # 3) We force `changelog` filecache to be tested
1747 # 3) We force `changelog` filecache to be tested
1748 # 4) cachestat for `changelog` are captured (for changelog)
1748 # 4) cachestat for `changelog` are captured (for changelog)
1749 # 5) `_bookmarks` is computed and cached
1749 # 5) `_bookmarks` is computed and cached
1750 #
1750 #
1751 # 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
1752 # cache stat computed in (1). As a result at locking time:
1752 # cache stat computed in (1). As a result at locking time:
1753 # * 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
1754 # * otherwise -> the bookmarks get refreshed.
1754 # * otherwise -> the bookmarks get refreshed.
1755 self._refreshchangelog()
1755 self._refreshchangelog()
1756 return bookmarks.bmstore(self)
1756 return bookmarks.bmstore(self)
1757
1757
1758 def _refreshchangelog(self):
1758 def _refreshchangelog(self):
1759 """make sure the in memory changelog match the on-disk one"""
1759 """make sure the in memory changelog match the on-disk one"""
1760 if 'changelog' in vars(self) and self.currenttransaction() is None:
1760 if 'changelog' in vars(self) and self.currenttransaction() is None:
1761 del self.changelog
1761 del self.changelog
1762
1762
1763 @property
1763 @property
1764 def _activebookmark(self):
1764 def _activebookmark(self):
1765 return self._bookmarks.active
1765 return self._bookmarks.active
1766
1766
1767 # _phasesets depend on changelog. what we need is to call
1767 # _phasesets depend on changelog. what we need is to call
1768 # _phasecache.invalidate() if '00changelog.i' was changed, but it
1768 # _phasecache.invalidate() if '00changelog.i' was changed, but it
1769 # can't be easily expressed in filecache mechanism.
1769 # can't be easily expressed in filecache mechanism.
1770 @storecache(b'phaseroots', b'00changelog.i')
1770 @storecache(b'phaseroots', b'00changelog.i')
1771 def _phasecache(self):
1771 def _phasecache(self):
1772 return phases.phasecache(self, self._phasedefaults)
1772 return phases.phasecache(self, self._phasedefaults)
1773
1773
1774 @storecache(b'obsstore')
1774 @storecache(b'obsstore')
1775 def obsstore(self):
1775 def obsstore(self):
1776 return obsolete.makestore(self.ui, self)
1776 return obsolete.makestore(self.ui, self)
1777
1777
1778 @changelogcache()
1778 @changelogcache()
1779 def changelog(repo):
1779 def changelog(repo):
1780 # load dirstate before changelog to avoid race see issue6303
1780 # load dirstate before changelog to avoid race see issue6303
1781 repo.dirstate.prefetch_parents()
1781 repo.dirstate.prefetch_parents()
1782 return repo.store.changelog(
1782 return repo.store.changelog(
1783 txnutil.mayhavepending(repo.root),
1783 txnutil.mayhavepending(repo.root),
1784 concurrencychecker=revlogchecker.get_checker(repo.ui, b'changelog'),
1784 concurrencychecker=revlogchecker.get_checker(repo.ui, b'changelog'),
1785 )
1785 )
1786
1786
1787 @manifestlogcache()
1787 @manifestlogcache()
1788 def manifestlog(self):
1788 def manifestlog(self):
1789 return self.store.manifestlog(self, self._storenarrowmatch)
1789 return self.store.manifestlog(self, self._storenarrowmatch)
1790
1790
1791 @unfilteredpropertycache
1791 @unfilteredpropertycache
1792 def dirstate(self):
1792 def dirstate(self):
1793 if self._dirstate is None:
1793 if self._dirstate is None:
1794 self._dirstate = self._makedirstate()
1794 self._dirstate = self._makedirstate()
1795 else:
1795 else:
1796 self._dirstate.refresh()
1796 self._dirstate.refresh()
1797 return self._dirstate
1797 return self._dirstate
1798
1798
1799 def _makedirstate(self):
1799 def _makedirstate(self):
1800 """Extension point for wrapping the dirstate per-repo."""
1800 """Extension point for wrapping the dirstate per-repo."""
1801 sparsematchfn = None
1801 sparsematchfn = None
1802 if sparse.use_sparse(self):
1802 if sparse.use_sparse(self):
1803 sparsematchfn = lambda: sparse.matcher(self)
1803 sparsematchfn = lambda: sparse.matcher(self)
1804 v2_req = requirementsmod.DIRSTATE_V2_REQUIREMENT
1804 v2_req = requirementsmod.DIRSTATE_V2_REQUIREMENT
1805 th = requirementsmod.DIRSTATE_TRACKED_HINT_V1
1805 th = requirementsmod.DIRSTATE_TRACKED_HINT_V1
1806 use_dirstate_v2 = v2_req in self.requirements
1806 use_dirstate_v2 = v2_req in self.requirements
1807 use_tracked_hint = th in self.requirements
1807 use_tracked_hint = th in self.requirements
1808
1808
1809 return dirstate.dirstate(
1809 return dirstate.dirstate(
1810 self.vfs,
1810 self.vfs,
1811 self.ui,
1811 self.ui,
1812 self.root,
1812 self.root,
1813 self._dirstatevalidate,
1813 self._dirstatevalidate,
1814 sparsematchfn,
1814 sparsematchfn,
1815 self.nodeconstants,
1815 self.nodeconstants,
1816 use_dirstate_v2,
1816 use_dirstate_v2,
1817 use_tracked_hint=use_tracked_hint,
1817 use_tracked_hint=use_tracked_hint,
1818 )
1818 )
1819
1819
1820 def _dirstatevalidate(self, node):
1820 def _dirstatevalidate(self, node):
1821 okay = True
1821 okay = True
1822 try:
1822 try:
1823 self.changelog.rev(node)
1823 self.changelog.rev(node)
1824 except error.LookupError:
1824 except error.LookupError:
1825 # 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
1826 # 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
1827 # refresh the changelog first.
1827 # refresh the changelog first.
1828 #
1828 #
1829 # 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
1830 # 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
1831 # something is very fishy.
1831 # something is very fishy.
1832 if self.currentlock() is None:
1832 if self.currentlock() is None:
1833 self.invalidate()
1833 self.invalidate()
1834 try:
1834 try:
1835 self.changelog.rev(node)
1835 self.changelog.rev(node)
1836 except error.LookupError:
1836 except error.LookupError:
1837 okay = False
1837 okay = False
1838 else:
1838 else:
1839 # XXX we should consider raising an error here.
1839 # XXX we should consider raising an error here.
1840 okay = False
1840 okay = False
1841 if okay:
1841 if okay:
1842 return node
1842 return node
1843 else:
1843 else:
1844 if not self._dirstatevalidatewarned:
1844 if not self._dirstatevalidatewarned:
1845 self._dirstatevalidatewarned = True
1845 self._dirstatevalidatewarned = True
1846 self.ui.warn(
1846 self.ui.warn(
1847 _(b"warning: ignoring unknown working parent %s!\n")
1847 _(b"warning: ignoring unknown working parent %s!\n")
1848 % short(node)
1848 % short(node)
1849 )
1849 )
1850 return self.nullid
1850 return self.nullid
1851
1851
1852 @storecache(narrowspec.FILENAME)
1852 @storecache(narrowspec.FILENAME)
1853 def narrowpats(self):
1853 def narrowpats(self):
1854 """matcher patterns for this repository's narrowspec
1854 """matcher patterns for this repository's narrowspec
1855
1855
1856 A tuple of (includes, excludes).
1856 A tuple of (includes, excludes).
1857 """
1857 """
1858 # the narrow management should probably move into its own object
1858 # the narrow management should probably move into its own object
1859 val = self._pending_narrow_pats
1859 val = self._pending_narrow_pats
1860 if val is None:
1860 if val is None:
1861 val = narrowspec.load(self)
1861 val = narrowspec.load(self)
1862 return val
1862 return val
1863
1863
1864 @storecache(narrowspec.FILENAME)
1864 @storecache(narrowspec.FILENAME)
1865 def _storenarrowmatch(self):
1865 def _storenarrowmatch(self):
1866 if requirementsmod.NARROW_REQUIREMENT not in self.requirements:
1866 if requirementsmod.NARROW_REQUIREMENT not in self.requirements:
1867 return matchmod.always()
1867 return matchmod.always()
1868 include, exclude = self.narrowpats
1868 include, exclude = self.narrowpats
1869 return narrowspec.match(self.root, include=include, exclude=exclude)
1869 return narrowspec.match(self.root, include=include, exclude=exclude)
1870
1870
1871 @storecache(narrowspec.FILENAME)
1871 @storecache(narrowspec.FILENAME)
1872 def _narrowmatch(self):
1872 def _narrowmatch(self):
1873 if requirementsmod.NARROW_REQUIREMENT not in self.requirements:
1873 if requirementsmod.NARROW_REQUIREMENT not in self.requirements:
1874 return matchmod.always()
1874 return matchmod.always()
1875 narrowspec.checkworkingcopynarrowspec(self)
1875 narrowspec.checkworkingcopynarrowspec(self)
1876 include, exclude = self.narrowpats
1876 include, exclude = self.narrowpats
1877 return narrowspec.match(self.root, include=include, exclude=exclude)
1877 return narrowspec.match(self.root, include=include, exclude=exclude)
1878
1878
1879 def narrowmatch(self, match=None, includeexact=False):
1879 def narrowmatch(self, match=None, includeexact=False):
1880 """matcher corresponding the the repo's narrowspec
1880 """matcher corresponding the the repo's narrowspec
1881
1881
1882 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
1883 matcher.
1883 matcher.
1884
1884
1885 If `includeexact` is True, then any exact matches from `match` will
1885 If `includeexact` is True, then any exact matches from `match` will
1886 be included even if they're outside the narrowspec.
1886 be included even if they're outside the narrowspec.
1887 """
1887 """
1888 if match:
1888 if match:
1889 if includeexact and not self._narrowmatch.always():
1889 if includeexact and not self._narrowmatch.always():
1890 # do not exclude explicitly-specified paths so that they can
1890 # do not exclude explicitly-specified paths so that they can
1891 # be warned later on
1891 # be warned later on
1892 em = matchmod.exact(match.files())
1892 em = matchmod.exact(match.files())
1893 nm = matchmod.unionmatcher([self._narrowmatch, em])
1893 nm = matchmod.unionmatcher([self._narrowmatch, em])
1894 return matchmod.intersectmatchers(match, nm)
1894 return matchmod.intersectmatchers(match, nm)
1895 return matchmod.intersectmatchers(match, self._narrowmatch)
1895 return matchmod.intersectmatchers(match, self._narrowmatch)
1896 return self._narrowmatch
1896 return self._narrowmatch
1897
1897
1898 def setnarrowpats(self, newincludes, newexcludes):
1898 def setnarrowpats(self, newincludes, newexcludes):
1899 narrowspec.save(self, newincludes, newexcludes)
1899 narrowspec.save(self, newincludes, newexcludes)
1900 self.invalidate(clearfilecache=True)
1900 self.invalidate(clearfilecache=True)
1901
1901
1902 @unfilteredpropertycache
1902 @unfilteredpropertycache
1903 def _quick_access_changeid_null(self):
1903 def _quick_access_changeid_null(self):
1904 return {
1904 return {
1905 b'null': (nullrev, self.nodeconstants.nullid),
1905 b'null': (nullrev, self.nodeconstants.nullid),
1906 nullrev: (nullrev, self.nodeconstants.nullid),
1906 nullrev: (nullrev, self.nodeconstants.nullid),
1907 self.nullid: (nullrev, self.nullid),
1907 self.nullid: (nullrev, self.nullid),
1908 }
1908 }
1909
1909
1910 @unfilteredpropertycache
1910 @unfilteredpropertycache
1911 def _quick_access_changeid_wc(self):
1911 def _quick_access_changeid_wc(self):
1912 # also fast path access to the working copy parents
1912 # also fast path access to the working copy parents
1913 # however, only do it for filter that ensure wc is visible.
1913 # however, only do it for filter that ensure wc is visible.
1914 quick = self._quick_access_changeid_null.copy()
1914 quick = self._quick_access_changeid_null.copy()
1915 cl = self.unfiltered().changelog
1915 cl = self.unfiltered().changelog
1916 for node in self.dirstate.parents():
1916 for node in self.dirstate.parents():
1917 if node == self.nullid:
1917 if node == self.nullid:
1918 continue
1918 continue
1919 rev = cl.index.get_rev(node)
1919 rev = cl.index.get_rev(node)
1920 if rev is None:
1920 if rev is None:
1921 # unknown working copy parent case:
1921 # unknown working copy parent case:
1922 #
1922 #
1923 # skip the fast path and let higher code deal with it
1923 # skip the fast path and let higher code deal with it
1924 continue
1924 continue
1925 pair = (rev, node)
1925 pair = (rev, node)
1926 quick[rev] = pair
1926 quick[rev] = pair
1927 quick[node] = pair
1927 quick[node] = pair
1928 # also add the parents of the parents
1928 # also add the parents of the parents
1929 for r in cl.parentrevs(rev):
1929 for r in cl.parentrevs(rev):
1930 if r == nullrev:
1930 if r == nullrev:
1931 continue
1931 continue
1932 n = cl.node(r)
1932 n = cl.node(r)
1933 pair = (r, n)
1933 pair = (r, n)
1934 quick[r] = pair
1934 quick[r] = pair
1935 quick[n] = pair
1935 quick[n] = pair
1936 p1node = self.dirstate.p1()
1936 p1node = self.dirstate.p1()
1937 if p1node != self.nullid:
1937 if p1node != self.nullid:
1938 quick[b'.'] = quick[p1node]
1938 quick[b'.'] = quick[p1node]
1939 return quick
1939 return quick
1940
1940
1941 @unfilteredmethod
1941 @unfilteredmethod
1942 def _quick_access_changeid_invalidate(self):
1942 def _quick_access_changeid_invalidate(self):
1943 if '_quick_access_changeid_wc' in vars(self):
1943 if '_quick_access_changeid_wc' in vars(self):
1944 del self.__dict__['_quick_access_changeid_wc']
1944 del self.__dict__['_quick_access_changeid_wc']
1945
1945
1946 @property
1946 @property
1947 def _quick_access_changeid(self):
1947 def _quick_access_changeid(self):
1948 """an helper dictionnary for __getitem__ calls
1948 """an helper dictionnary for __getitem__ calls
1949
1949
1950 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
1951 further processing.
1951 further processing.
1952 """
1952 """
1953 if self.filtername in repoview.filter_has_wc:
1953 if self.filtername in repoview.filter_has_wc:
1954 return self._quick_access_changeid_wc
1954 return self._quick_access_changeid_wc
1955 return self._quick_access_changeid_null
1955 return self._quick_access_changeid_null
1956
1956
1957 def __getitem__(self, changeid):
1957 def __getitem__(self, changeid):
1958 # dealing with special cases
1958 # dealing with special cases
1959 if changeid is None:
1959 if changeid is None:
1960 return context.workingctx(self)
1960 return context.workingctx(self)
1961 if isinstance(changeid, context.basectx):
1961 if isinstance(changeid, context.basectx):
1962 return changeid
1962 return changeid
1963
1963
1964 # dealing with multiple revisions
1964 # dealing with multiple revisions
1965 if isinstance(changeid, slice):
1965 if isinstance(changeid, slice):
1966 # wdirrev isn't contiguous so the slice shouldn't include it
1966 # wdirrev isn't contiguous so the slice shouldn't include it
1967 return [
1967 return [
1968 self[i]
1968 self[i]
1969 for i in range(*changeid.indices(len(self)))
1969 for i in range(*changeid.indices(len(self)))
1970 if i not in self.changelog.filteredrevs
1970 if i not in self.changelog.filteredrevs
1971 ]
1971 ]
1972
1972
1973 # dealing with some special values
1973 # dealing with some special values
1974 quick_access = self._quick_access_changeid.get(changeid)
1974 quick_access = self._quick_access_changeid.get(changeid)
1975 if quick_access is not None:
1975 if quick_access is not None:
1976 rev, node = quick_access
1976 rev, node = quick_access
1977 return context.changectx(self, rev, node, maybe_filtered=False)
1977 return context.changectx(self, rev, node, maybe_filtered=False)
1978 if changeid == b'tip':
1978 if changeid == b'tip':
1979 node = self.changelog.tip()
1979 node = self.changelog.tip()
1980 rev = self.changelog.rev(node)
1980 rev = self.changelog.rev(node)
1981 return context.changectx(self, rev, node)
1981 return context.changectx(self, rev, node)
1982
1982
1983 # dealing with arbitrary values
1983 # dealing with arbitrary values
1984 try:
1984 try:
1985 if isinstance(changeid, int):
1985 if isinstance(changeid, int):
1986 node = self.changelog.node(changeid)
1986 node = self.changelog.node(changeid)
1987 rev = changeid
1987 rev = changeid
1988 elif changeid == b'.':
1988 elif changeid == b'.':
1989 # this is a hack to delay/avoid loading obsmarkers
1989 # this is a hack to delay/avoid loading obsmarkers
1990 # when we know that '.' won't be hidden
1990 # when we know that '.' won't be hidden
1991 node = self.dirstate.p1()
1991 node = self.dirstate.p1()
1992 rev = self.unfiltered().changelog.rev(node)
1992 rev = self.unfiltered().changelog.rev(node)
1993 elif len(changeid) == self.nodeconstants.nodelen:
1993 elif len(changeid) == self.nodeconstants.nodelen:
1994 try:
1994 try:
1995 node = changeid
1995 node = changeid
1996 rev = self.changelog.rev(changeid)
1996 rev = self.changelog.rev(changeid)
1997 except error.FilteredLookupError:
1997 except error.FilteredLookupError:
1998 changeid = hex(changeid) # for the error message
1998 changeid = hex(changeid) # for the error message
1999 raise
1999 raise
2000 except LookupError:
2000 except LookupError:
2001 # check if it might have come from damaged dirstate
2001 # check if it might have come from damaged dirstate
2002 #
2002 #
2003 # XXX we could avoid the unfiltered if we had a recognizable
2003 # XXX we could avoid the unfiltered if we had a recognizable
2004 # exception for filtered changeset access
2004 # exception for filtered changeset access
2005 if (
2005 if (
2006 self.local()
2006 self.local()
2007 and changeid in self.unfiltered().dirstate.parents()
2007 and changeid in self.unfiltered().dirstate.parents()
2008 ):
2008 ):
2009 msg = _(b"working directory has unknown parent '%s'!")
2009 msg = _(b"working directory has unknown parent '%s'!")
2010 raise error.Abort(msg % short(changeid))
2010 raise error.Abort(msg % short(changeid))
2011 changeid = hex(changeid) # for the error message
2011 changeid = hex(changeid) # for the error message
2012 raise
2012 raise
2013
2013
2014 elif len(changeid) == 2 * self.nodeconstants.nodelen:
2014 elif len(changeid) == 2 * self.nodeconstants.nodelen:
2015 node = bin(changeid)
2015 node = bin(changeid)
2016 rev = self.changelog.rev(node)
2016 rev = self.changelog.rev(node)
2017 else:
2017 else:
2018 raise error.ProgrammingError(
2018 raise error.ProgrammingError(
2019 b"unsupported changeid '%s' of type %s"
2019 b"unsupported changeid '%s' of type %s"
2020 % (changeid, pycompat.bytestr(type(changeid)))
2020 % (changeid, pycompat.bytestr(type(changeid)))
2021 )
2021 )
2022
2022
2023 return context.changectx(self, rev, node)
2023 return context.changectx(self, rev, node)
2024
2024
2025 except (error.FilteredIndexError, error.FilteredLookupError):
2025 except (error.FilteredIndexError, error.FilteredLookupError):
2026 raise error.FilteredRepoLookupError(
2026 raise error.FilteredRepoLookupError(
2027 _(b"filtered revision '%s'") % pycompat.bytestr(changeid)
2027 _(b"filtered revision '%s'") % pycompat.bytestr(changeid)
2028 )
2028 )
2029 except (IndexError, LookupError):
2029 except (IndexError, LookupError):
2030 raise error.RepoLookupError(
2030 raise error.RepoLookupError(
2031 _(b"unknown revision '%s'") % pycompat.bytestr(changeid)
2031 _(b"unknown revision '%s'") % pycompat.bytestr(changeid)
2032 )
2032 )
2033 except error.WdirUnsupported:
2033 except error.WdirUnsupported:
2034 return context.workingctx(self)
2034 return context.workingctx(self)
2035
2035
2036 def __contains__(self, changeid):
2036 def __contains__(self, changeid):
2037 """True if the given changeid exists"""
2037 """True if the given changeid exists"""
2038 try:
2038 try:
2039 self[changeid]
2039 self[changeid]
2040 return True
2040 return True
2041 except error.RepoLookupError:
2041 except error.RepoLookupError:
2042 return False
2042 return False
2043
2043
2044 def __nonzero__(self):
2044 def __nonzero__(self):
2045 return True
2045 return True
2046
2046
2047 __bool__ = __nonzero__
2047 __bool__ = __nonzero__
2048
2048
2049 def __len__(self):
2049 def __len__(self):
2050 # no need to pay the cost of repoview.changelog
2050 # no need to pay the cost of repoview.changelog
2051 unfi = self.unfiltered()
2051 unfi = self.unfiltered()
2052 return len(unfi.changelog)
2052 return len(unfi.changelog)
2053
2053
2054 def __iter__(self):
2054 def __iter__(self):
2055 return iter(self.changelog)
2055 return iter(self.changelog)
2056
2056
2057 def revs(self, expr: bytes, *args):
2057 def revs(self, expr: bytes, *args):
2058 """Find revisions matching a revset.
2058 """Find revisions matching a revset.
2059
2059
2060 The revset is specified as a string ``expr`` that may contain
2060 The revset is specified as a string ``expr`` that may contain
2061 %-formatting to escape certain types. See ``revsetlang.formatspec``.
2061 %-formatting to escape certain types. See ``revsetlang.formatspec``.
2062
2062
2063 Revset aliases from the configuration are not expanded. To expand
2063 Revset aliases from the configuration are not expanded. To expand
2064 user aliases, consider calling ``scmutil.revrange()`` or
2064 user aliases, consider calling ``scmutil.revrange()`` or
2065 ``repo.anyrevs([expr], user=True)``.
2065 ``repo.anyrevs([expr], user=True)``.
2066
2066
2067 Returns a smartset.abstractsmartset, which is a list-like interface
2067 Returns a smartset.abstractsmartset, which is a list-like interface
2068 that contains integer revisions.
2068 that contains integer revisions.
2069 """
2069 """
2070 tree = revsetlang.spectree(expr, *args)
2070 tree = revsetlang.spectree(expr, *args)
2071 return revset.makematcher(tree)(self)
2071 return revset.makematcher(tree)(self)
2072
2072
2073 def set(self, expr: bytes, *args):
2073 def set(self, expr: bytes, *args):
2074 """Find revisions matching a revset and emit changectx instances.
2074 """Find revisions matching a revset and emit changectx instances.
2075
2075
2076 This is a convenience wrapper around ``revs()`` that iterates the
2076 This is a convenience wrapper around ``revs()`` that iterates the
2077 result and is a generator of changectx instances.
2077 result and is a generator of changectx instances.
2078
2078
2079 Revset aliases from the configuration are not expanded. To expand
2079 Revset aliases from the configuration are not expanded. To expand
2080 user aliases, consider calling ``scmutil.revrange()``.
2080 user aliases, consider calling ``scmutil.revrange()``.
2081 """
2081 """
2082 for r in self.revs(expr, *args):
2082 for r in self.revs(expr, *args):
2083 yield self[r]
2083 yield self[r]
2084
2084
2085 def anyrevs(self, specs: bytes, user=False, localalias=None):
2085 def anyrevs(self, specs: bytes, user=False, localalias=None):
2086 """Find revisions matching one of the given revsets.
2086 """Find revisions matching one of the given revsets.
2087
2087
2088 Revset aliases from the configuration are not expanded by default. To
2088 Revset aliases from the configuration are not expanded by default. To
2089 expand user aliases, specify ``user=True``. To provide some local
2089 expand user aliases, specify ``user=True``. To provide some local
2090 definitions overriding user aliases, set ``localalias`` to
2090 definitions overriding user aliases, set ``localalias`` to
2091 ``{name: definitionstring}``.
2091 ``{name: definitionstring}``.
2092 """
2092 """
2093 if specs == [b'null']:
2093 if specs == [b'null']:
2094 return revset.baseset([nullrev])
2094 return revset.baseset([nullrev])
2095 if specs == [b'.']:
2095 if specs == [b'.']:
2096 quick_data = self._quick_access_changeid.get(b'.')
2096 quick_data = self._quick_access_changeid.get(b'.')
2097 if quick_data is not None:
2097 if quick_data is not None:
2098 return revset.baseset([quick_data[0]])
2098 return revset.baseset([quick_data[0]])
2099 if user:
2099 if user:
2100 m = revset.matchany(
2100 m = revset.matchany(
2101 self.ui,
2101 self.ui,
2102 specs,
2102 specs,
2103 lookup=revset.lookupfn(self),
2103 lookup=revset.lookupfn(self),
2104 localalias=localalias,
2104 localalias=localalias,
2105 )
2105 )
2106 else:
2106 else:
2107 m = revset.matchany(None, specs, localalias=localalias)
2107 m = revset.matchany(None, specs, localalias=localalias)
2108 return m(self)
2108 return m(self)
2109
2109
2110 def url(self) -> bytes:
2110 def url(self) -> bytes:
2111 return b'file:' + self.root
2111 return b'file:' + self.root
2112
2112
2113 def hook(self, name, throw=False, **args):
2113 def hook(self, name, throw=False, **args):
2114 """Call a hook, passing this repo instance.
2114 """Call a hook, passing this repo instance.
2115
2115
2116 This a convenience method to aid invoking hooks. Extensions likely
2116 This a convenience method to aid invoking hooks. Extensions likely
2117 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
2118 replacing code that is expected to call a hook.
2118 replacing code that is expected to call a hook.
2119 """
2119 """
2120 return hook.hook(self.ui, self, name, throw, **args)
2120 return hook.hook(self.ui, self, name, throw, **args)
2121
2121
2122 @filteredpropertycache
2122 @filteredpropertycache
2123 def _tagscache(self):
2123 def _tagscache(self):
2124 """Returns a tagscache object that contains various tags related
2124 """Returns a tagscache object that contains various tags related
2125 caches."""
2125 caches."""
2126
2126
2127 # This simplifies its cache management by having one decorated
2127 # This simplifies its cache management by having one decorated
2128 # function (this one) and the rest simply fetch things from it.
2128 # function (this one) and the rest simply fetch things from it.
2129 class tagscache:
2129 class tagscache:
2130 def __init__(self):
2130 def __init__(self):
2131 # These two define the set of tags for this repository. tags
2131 # These two define the set of tags for this repository. tags
2132 # 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
2133 # 'local'. (Global tags are defined by .hgtags across all
2133 # 'local'. (Global tags are defined by .hgtags across all
2134 # heads, and local tags are defined in .hg/localtags.)
2134 # heads, and local tags are defined in .hg/localtags.)
2135 # They constitute the in-memory cache of tags.
2135 # They constitute the in-memory cache of tags.
2136 self.tags = self.tagtypes = None
2136 self.tags = self.tagtypes = None
2137
2137
2138 self.nodetagscache = self.tagslist = None
2138 self.nodetagscache = self.tagslist = None
2139
2139
2140 cache = tagscache()
2140 cache = tagscache()
2141 cache.tags, cache.tagtypes = self._findtags()
2141 cache.tags, cache.tagtypes = self._findtags()
2142
2142
2143 return cache
2143 return cache
2144
2144
2145 def tags(self):
2145 def tags(self):
2146 '''return a mapping of tag to node'''
2146 '''return a mapping of tag to node'''
2147 t = {}
2147 t = {}
2148 if self.changelog.filteredrevs:
2148 if self.changelog.filteredrevs:
2149 tags, tt = self._findtags()
2149 tags, tt = self._findtags()
2150 else:
2150 else:
2151 tags = self._tagscache.tags
2151 tags = self._tagscache.tags
2152 rev = self.changelog.rev
2152 rev = self.changelog.rev
2153 for k, v in tags.items():
2153 for k, v in tags.items():
2154 try:
2154 try:
2155 # ignore tags to unknown nodes
2155 # ignore tags to unknown nodes
2156 rev(v)
2156 rev(v)
2157 t[k] = v
2157 t[k] = v
2158 except (error.LookupError, ValueError):
2158 except (error.LookupError, ValueError):
2159 pass
2159 pass
2160 return t
2160 return t
2161
2161
2162 def _findtags(self):
2162 def _findtags(self):
2163 """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
2164 (tags, tagtypes) where tags maps tag name to node, and tagtypes
2164 (tags, tagtypes) where tags maps tag name to node, and tagtypes
2165 maps tag name to a string like \'global\' or \'local\'.
2165 maps tag name to a string like \'global\' or \'local\'.
2166 Subclasses or extensions are free to add their own tags, but
2166 Subclasses or extensions are free to add their own tags, but
2167 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
2168 duration of the localrepo object."""
2168 duration of the localrepo object."""
2169
2169
2170 # XXX what tagtype should subclasses/extensions use? Currently
2170 # XXX what tagtype should subclasses/extensions use? Currently
2171 # 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.
2172 # Should each extension invent its own tag type? Should there
2172 # Should each extension invent its own tag type? Should there
2173 # 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
2174 # quo fine?
2174 # quo fine?
2175
2175
2176 # map tag name to (node, hist)
2176 # map tag name to (node, hist)
2177 alltags = tagsmod.findglobaltags(self.ui, self)
2177 alltags = tagsmod.findglobaltags(self.ui, self)
2178 # map tag name to tag type
2178 # map tag name to tag type
2179 tagtypes = {tag: b'global' for tag in alltags}
2179 tagtypes = {tag: b'global' for tag in alltags}
2180
2180
2181 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
2181 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
2182
2182
2183 # Build the return dicts. Have to re-encode tag names because
2183 # Build the return dicts. Have to re-encode tag names because
2184 # 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
2185 # 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
2186 # local encoding.
2186 # local encoding.
2187 tags = {}
2187 tags = {}
2188 for name, (node, hist) in alltags.items():
2188 for name, (node, hist) in alltags.items():
2189 if node != self.nullid:
2189 if node != self.nullid:
2190 tags[encoding.tolocal(name)] = node
2190 tags[encoding.tolocal(name)] = node
2191 tags[b'tip'] = self.changelog.tip()
2191 tags[b'tip'] = self.changelog.tip()
2192 tagtypes = {
2192 tagtypes = {
2193 encoding.tolocal(name): value for (name, value) in tagtypes.items()
2193 encoding.tolocal(name): value for (name, value) in tagtypes.items()
2194 }
2194 }
2195 return (tags, tagtypes)
2195 return (tags, tagtypes)
2196
2196
2197 def tagtype(self, tagname):
2197 def tagtype(self, tagname):
2198 """
2198 """
2199 return the type of the given tag. result can be:
2199 return the type of the given tag. result can be:
2200
2200
2201 'local' : a local tag
2201 'local' : a local tag
2202 'global' : a global tag
2202 'global' : a global tag
2203 None : tag does not exist
2203 None : tag does not exist
2204 """
2204 """
2205
2205
2206 return self._tagscache.tagtypes.get(tagname)
2206 return self._tagscache.tagtypes.get(tagname)
2207
2207
2208 def tagslist(self):
2208 def tagslist(self):
2209 '''return a list of tags ordered by revision'''
2209 '''return a list of tags ordered by revision'''
2210 if not self._tagscache.tagslist:
2210 if not self._tagscache.tagslist:
2211 l = []
2211 l = []
2212 for t, n in self.tags().items():
2212 for t, n in self.tags().items():
2213 l.append((self.changelog.rev(n), t, n))
2213 l.append((self.changelog.rev(n), t, n))
2214 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)]
2215
2215
2216 return self._tagscache.tagslist
2216 return self._tagscache.tagslist
2217
2217
2218 def nodetags(self, node):
2218 def nodetags(self, node):
2219 '''return the tags associated with a node'''
2219 '''return the tags associated with a node'''
2220 if not self._tagscache.nodetagscache:
2220 if not self._tagscache.nodetagscache:
2221 nodetagscache = {}
2221 nodetagscache = {}
2222 for t, n in self._tagscache.tags.items():
2222 for t, n in self._tagscache.tags.items():
2223 nodetagscache.setdefault(n, []).append(t)
2223 nodetagscache.setdefault(n, []).append(t)
2224 for tags in nodetagscache.values():
2224 for tags in nodetagscache.values():
2225 tags.sort()
2225 tags.sort()
2226 self._tagscache.nodetagscache = nodetagscache
2226 self._tagscache.nodetagscache = nodetagscache
2227 return self._tagscache.nodetagscache.get(node, [])
2227 return self._tagscache.nodetagscache.get(node, [])
2228
2228
2229 def nodebookmarks(self, node):
2229 def nodebookmarks(self, node):
2230 """return the list of bookmarks pointing to the specified node"""
2230 """return the list of bookmarks pointing to the specified node"""
2231 return self._bookmarks.names(node)
2231 return self._bookmarks.names(node)
2232
2232
2233 def branchmap(self):
2233 def branchmap(self):
2234 """returns a dictionary {branch: [branchheads]} with branchheads
2234 """returns a dictionary {branch: [branchheads]} with branchheads
2235 ordered by increasing revision number"""
2235 ordered by increasing revision number"""
2236 return self._branchcaches[self]
2236 return self._branchcaches[self]
2237
2237
2238 @unfilteredmethod
2238 @unfilteredmethod
2239 def revbranchcache(self):
2239 def revbranchcache(self):
2240 if not self._revbranchcache:
2240 if not self._revbranchcache:
2241 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
2241 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
2242 return self._revbranchcache
2242 return self._revbranchcache
2243
2243
2244 def register_changeset(self, rev, changelogrevision):
2244 def register_changeset(self, rev, changelogrevision):
2245 self.revbranchcache().setdata(rev, changelogrevision)
2245 self.revbranchcache().setdata(rev, changelogrevision)
2246
2246
2247 def branchtip(self, branch, ignoremissing=False):
2247 def branchtip(self, branch, ignoremissing=False):
2248 """return the tip node for a given branch
2248 """return the tip node for a given branch
2249
2249
2250 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.
2251 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
2252 (e.g. namespace).
2252 (e.g. namespace).
2253
2253
2254 """
2254 """
2255 try:
2255 try:
2256 return self.branchmap().branchtip(branch)
2256 return self.branchmap().branchtip(branch)
2257 except KeyError:
2257 except KeyError:
2258 if not ignoremissing:
2258 if not ignoremissing:
2259 raise error.RepoLookupError(_(b"unknown branch '%s'") % branch)
2259 raise error.RepoLookupError(_(b"unknown branch '%s'") % branch)
2260 else:
2260 else:
2261 pass
2261 pass
2262
2262
2263 def lookup(self, key):
2263 def lookup(self, key):
2264 node = scmutil.revsymbol(self, key).node()
2264 node = scmutil.revsymbol(self, key).node()
2265 if node is None:
2265 if node is None:
2266 raise error.RepoLookupError(_(b"unknown revision '%s'") % key)
2266 raise error.RepoLookupError(_(b"unknown revision '%s'") % key)
2267 return node
2267 return node
2268
2268
2269 def lookupbranch(self, key):
2269 def lookupbranch(self, key):
2270 if self.branchmap().hasbranch(key):
2270 if self.branchmap().hasbranch(key):
2271 return key
2271 return key
2272
2272
2273 return scmutil.revsymbol(self, key).branch()
2273 return scmutil.revsymbol(self, key).branch()
2274
2274
2275 def known(self, nodes):
2275 def known(self, nodes):
2276 cl = self.changelog
2276 cl = self.changelog
2277 get_rev = cl.index.get_rev
2277 get_rev = cl.index.get_rev
2278 filtered = cl.filteredrevs
2278 filtered = cl.filteredrevs
2279 result = []
2279 result = []
2280 for n in nodes:
2280 for n in nodes:
2281 r = get_rev(n)
2281 r = get_rev(n)
2282 resp = not (r is None or r in filtered)
2282 resp = not (r is None or r in filtered)
2283 result.append(resp)
2283 result.append(resp)
2284 return result
2284 return result
2285
2285
2286 def local(self):
2286 def local(self):
2287 return self
2287 return self
2288
2288
2289 def publishing(self):
2289 def publishing(self):
2290 # it's safe (and desirable) to trust the publish flag unconditionally
2290 # it's safe (and desirable) to trust the publish flag unconditionally
2291 # 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
2292 return self.ui.configbool(b'phases', b'publish', untrusted=True)
2292 return self.ui.configbool(b'phases', b'publish', untrusted=True)
2293
2293
2294 def cancopy(self):
2294 def cancopy(self):
2295 # so statichttprepo's override of local() works
2295 # so statichttprepo's override of local() works
2296 if not self.local():
2296 if not self.local():
2297 return False
2297 return False
2298 if not self.publishing():
2298 if not self.publishing():
2299 return True
2299 return True
2300 # if publishing we can't copy if there is filtered content
2300 # if publishing we can't copy if there is filtered content
2301 return not self.filtered(b'visible').changelog.filteredrevs
2301 return not self.filtered(b'visible').changelog.filteredrevs
2302
2302
2303 def shared(self):
2303 def shared(self):
2304 '''the type of shared repository (None if not shared)'''
2304 '''the type of shared repository (None if not shared)'''
2305 if self.sharedpath != self.path:
2305 if self.sharedpath != self.path:
2306 return b'store'
2306 return b'store'
2307 return None
2307 return None
2308
2308
2309 def wjoin(self, f: bytes, *insidef: bytes) -> bytes:
2309 def wjoin(self, f: bytes, *insidef: bytes) -> bytes:
2310 return self.vfs.reljoin(self.root, f, *insidef)
2310 return self.vfs.reljoin(self.root, f, *insidef)
2311
2311
2312 def setparents(self, p1, p2=None):
2312 def setparents(self, p1, p2=None):
2313 if p2 is None:
2313 if p2 is None:
2314 p2 = self.nullid
2314 p2 = self.nullid
2315 self[None].setparents(p1, p2)
2315 self[None].setparents(p1, p2)
2316 self._quick_access_changeid_invalidate()
2316 self._quick_access_changeid_invalidate()
2317
2317
2318 def filectx(self, path: bytes, changeid=None, fileid=None, changectx=None):
2318 def filectx(self, path: bytes, changeid=None, fileid=None, changectx=None):
2319 """changeid must be a changeset revision, if specified.
2319 """changeid must be a changeset revision, if specified.
2320 fileid can be a file revision or node."""
2320 fileid can be a file revision or node."""
2321 return context.filectx(
2321 return context.filectx(
2322 self, path, changeid, fileid, changectx=changectx
2322 self, path, changeid, fileid, changectx=changectx
2323 )
2323 )
2324
2324
2325 def getcwd(self) -> bytes:
2325 def getcwd(self) -> bytes:
2326 return self.dirstate.getcwd()
2326 return self.dirstate.getcwd()
2327
2327
2328 def pathto(self, f: bytes, cwd: Optional[bytes] = None) -> bytes:
2328 def pathto(self, f: bytes, cwd: Optional[bytes] = None) -> bytes:
2329 return self.dirstate.pathto(f, cwd)
2329 return self.dirstate.pathto(f, cwd)
2330
2330
2331 def _loadfilter(self, filter):
2331 def _loadfilter(self, filter):
2332 if filter not in self._filterpats:
2332 if filter not in self._filterpats:
2333 l = []
2333 l = []
2334 for pat, cmd in self.ui.configitems(filter):
2334 for pat, cmd in self.ui.configitems(filter):
2335 if cmd == b'!':
2335 if cmd == b'!':
2336 continue
2336 continue
2337 mf = matchmod.match(self.root, b'', [pat])
2337 mf = matchmod.match(self.root, b'', [pat])
2338 fn = None
2338 fn = None
2339 params = cmd
2339 params = cmd
2340 for name, filterfn in self._datafilters.items():
2340 for name, filterfn in self._datafilters.items():
2341 if cmd.startswith(name):
2341 if cmd.startswith(name):
2342 fn = filterfn
2342 fn = filterfn
2343 params = cmd[len(name) :].lstrip()
2343 params = cmd[len(name) :].lstrip()
2344 break
2344 break
2345 if not fn:
2345 if not fn:
2346 fn = lambda s, c, **kwargs: procutil.filter(s, c)
2346 fn = lambda s, c, **kwargs: procutil.filter(s, c)
2347 fn.__name__ = 'commandfilter'
2347 fn.__name__ = 'commandfilter'
2348 # Wrap old filters not supporting keyword arguments
2348 # Wrap old filters not supporting keyword arguments
2349 if not pycompat.getargspec(fn)[2]:
2349 if not pycompat.getargspec(fn)[2]:
2350 oldfn = fn
2350 oldfn = fn
2351 fn = lambda s, c, oldfn=oldfn, **kwargs: oldfn(s, c)
2351 fn = lambda s, c, oldfn=oldfn, **kwargs: oldfn(s, c)
2352 fn.__name__ = 'compat-' + oldfn.__name__
2352 fn.__name__ = 'compat-' + oldfn.__name__
2353 l.append((mf, fn, params))
2353 l.append((mf, fn, params))
2354 self._filterpats[filter] = l
2354 self._filterpats[filter] = l
2355 return self._filterpats[filter]
2355 return self._filterpats[filter]
2356
2356
2357 def _filter(self, filterpats, filename, data):
2357 def _filter(self, filterpats, filename, data):
2358 for mf, fn, cmd in filterpats:
2358 for mf, fn, cmd in filterpats:
2359 if mf(filename):
2359 if mf(filename):
2360 self.ui.debug(
2360 self.ui.debug(
2361 b"filtering %s through %s\n"
2361 b"filtering %s through %s\n"
2362 % (filename, cmd or pycompat.sysbytes(fn.__name__))
2362 % (filename, cmd or pycompat.sysbytes(fn.__name__))
2363 )
2363 )
2364 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
2364 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
2365 break
2365 break
2366
2366
2367 return data
2367 return data
2368
2368
2369 @unfilteredpropertycache
2369 @unfilteredpropertycache
2370 def _encodefilterpats(self):
2370 def _encodefilterpats(self):
2371 return self._loadfilter(b'encode')
2371 return self._loadfilter(b'encode')
2372
2372
2373 @unfilteredpropertycache
2373 @unfilteredpropertycache
2374 def _decodefilterpats(self):
2374 def _decodefilterpats(self):
2375 return self._loadfilter(b'decode')
2375 return self._loadfilter(b'decode')
2376
2376
2377 def adddatafilter(self, name, filter):
2377 def adddatafilter(self, name, filter):
2378 self._datafilters[name] = filter
2378 self._datafilters[name] = filter
2379
2379
2380 def wread(self, filename: bytes) -> bytes:
2380 def wread(self, filename: bytes) -> bytes:
2381 if self.wvfs.islink(filename):
2381 if self.wvfs.islink(filename):
2382 data = self.wvfs.readlink(filename)
2382 data = self.wvfs.readlink(filename)
2383 else:
2383 else:
2384 data = self.wvfs.read(filename)
2384 data = self.wvfs.read(filename)
2385 return self._filter(self._encodefilterpats, filename, data)
2385 return self._filter(self._encodefilterpats, filename, data)
2386
2386
2387 def wwrite(
2387 def wwrite(
2388 self,
2388 self,
2389 filename: bytes,
2389 filename: bytes,
2390 data: bytes,
2390 data: bytes,
2391 flags: bytes,
2391 flags: bytes,
2392 backgroundclose=False,
2392 backgroundclose=False,
2393 **kwargs
2393 **kwargs
2394 ) -> int:
2394 ) -> int:
2395 """write ``data`` into ``filename`` in the working directory
2395 """write ``data`` into ``filename`` in the working directory
2396
2396
2397 This returns length of written (maybe decoded) data.
2397 This returns length of written (maybe decoded) data.
2398 """
2398 """
2399 data = self._filter(self._decodefilterpats, filename, data)
2399 data = self._filter(self._decodefilterpats, filename, data)
2400 if b'l' in flags:
2400 if b'l' in flags:
2401 self.wvfs.symlink(data, filename)
2401 self.wvfs.symlink(data, filename)
2402 else:
2402 else:
2403 self.wvfs.write(
2403 self.wvfs.write(
2404 filename, data, backgroundclose=backgroundclose, **kwargs
2404 filename, data, backgroundclose=backgroundclose, **kwargs
2405 )
2405 )
2406 if b'x' in flags:
2406 if b'x' in flags:
2407 self.wvfs.setflags(filename, False, True)
2407 self.wvfs.setflags(filename, False, True)
2408 else:
2408 else:
2409 self.wvfs.setflags(filename, False, False)
2409 self.wvfs.setflags(filename, False, False)
2410 return len(data)
2410 return len(data)
2411
2411
2412 def wwritedata(self, filename: bytes, data: bytes) -> bytes:
2412 def wwritedata(self, filename: bytes, data: bytes) -> bytes:
2413 return self._filter(self._decodefilterpats, filename, data)
2413 return self._filter(self._decodefilterpats, filename, data)
2414
2414
2415 def currenttransaction(self):
2415 def currenttransaction(self):
2416 """return the current transaction or None if non exists"""
2416 """return the current transaction or None if non exists"""
2417 if self._transref:
2417 if self._transref:
2418 tr = self._transref()
2418 tr = self._transref()
2419 else:
2419 else:
2420 tr = None
2420 tr = None
2421
2421
2422 if tr and tr.running():
2422 if tr and tr.running():
2423 return tr
2423 return tr
2424 return None
2424 return None
2425
2425
2426 def transaction(self, desc, report=None):
2426 def transaction(self, desc, report=None):
2427 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(
2428 b'devel', b'check-locks'
2428 b'devel', b'check-locks'
2429 ):
2429 ):
2430 if self._currentlock(self._lockref) is None:
2430 if self._currentlock(self._lockref) is None:
2431 raise error.ProgrammingError(b'transaction requires locking')
2431 raise error.ProgrammingError(b'transaction requires locking')
2432 tr = self.currenttransaction()
2432 tr = self.currenttransaction()
2433 if tr is not None:
2433 if tr is not None:
2434 return tr.nest(name=desc)
2434 return tr.nest(name=desc)
2435
2435
2436 # abort here if the journal already exists
2436 # abort here if the journal already exists
2437 if self.svfs.exists(b"journal"):
2437 if self.svfs.exists(b"journal"):
2438 raise error.RepoError(
2438 raise error.RepoError(
2439 _(b"abandoned transaction found"),
2439 _(b"abandoned transaction found"),
2440 hint=_(b"run 'hg recover' to clean up transaction"),
2440 hint=_(b"run 'hg recover' to clean up transaction"),
2441 )
2441 )
2442
2442
2443 # At that point your dirstate should be clean:
2443 # At that point your dirstate should be clean:
2444 #
2444 #
2445 # - 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
2446 # dirstate ?
2446 # dirstate ?
2447 #
2447 #
2448 # - 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
2449 # the middle of a `distate.changing_*` block. The transaction needs to
2449 # the middle of a `distate.changing_*` block. The transaction needs to
2450 # be open before that and wrap the change-context.
2450 # be open before that and wrap the change-context.
2451 #
2451 #
2452 # - 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
2453 # dirstate dirty?
2453 # dirstate dirty?
2454 if self.dirstate._dirty:
2454 if self.dirstate._dirty:
2455 m = "cannot open a transaction with a dirty dirstate"
2455 m = "cannot open a transaction with a dirty dirstate"
2456 raise error.ProgrammingError(m)
2456 raise error.ProgrammingError(m)
2457
2457
2458 idbase = b"%.40f#%f" % (random.random(), time.time())
2458 idbase = b"%.40f#%f" % (random.random(), time.time())
2459 ha = hex(hashutil.sha1(idbase).digest())
2459 ha = hex(hashutil.sha1(idbase).digest())
2460 txnid = b'TXN:' + ha
2460 txnid = b'TXN:' + ha
2461 self.hook(b'pretxnopen', throw=True, txnname=desc, txnid=txnid)
2461 self.hook(b'pretxnopen', throw=True, txnname=desc, txnid=txnid)
2462
2462
2463 self._writejournal(desc)
2463 self._writejournal(desc)
2464 if report:
2464 if report:
2465 rp = report
2465 rp = report
2466 else:
2466 else:
2467 rp = self.ui.warn
2467 rp = self.ui.warn
2468 vfsmap = self.vfs_map
2468 vfsmap = self.vfs_map
2469 # we must avoid cyclic reference between repo and transaction.
2469 # we must avoid cyclic reference between repo and transaction.
2470 reporef = weakref.ref(self)
2470 reporef = weakref.ref(self)
2471 # Code to track tag movement
2471 # Code to track tag movement
2472 #
2472 #
2473 # 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
2474 # 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
2475 # tracking at the repository level. One could envision to track changes
2475 # tracking at the repository level. One could envision to track changes
2476 # to the '.hgtags' file through changegroup apply but that fails to
2476 # to the '.hgtags' file through changegroup apply but that fails to
2477 # cope with case where transaction expose new heads without changegroup
2477 # cope with case where transaction expose new heads without changegroup
2478 # being involved (eg: phase movement).
2478 # being involved (eg: phase movement).
2479 #
2479 #
2480 # 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
2481 # with performance impacts. The current code run more often than needed
2481 # with performance impacts. The current code run more often than needed
2482 # 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
2483 # 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
2484 # will be removed when we are happy with the performance impact.
2484 # will be removed when we are happy with the performance impact.
2485 #
2485 #
2486 # Once this feature is no longer experimental move the following
2486 # Once this feature is no longer experimental move the following
2487 # documentation to the appropriate help section:
2487 # documentation to the appropriate help section:
2488 #
2488 #
2489 # 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
2490 # 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
2491 # these changes are made available in a file at:
2491 # these changes are made available in a file at:
2492 # ``REPOROOT/.hg/changes/tags.changes``.
2492 # ``REPOROOT/.hg/changes/tags.changes``.
2493 # 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
2494 # 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
2495 # in this one. Changes are recorded in a line base format::
2495 # in this one. Changes are recorded in a line base format::
2496 #
2496 #
2497 # <action> <hex-node> <tag-name>\n
2497 # <action> <hex-node> <tag-name>\n
2498 #
2498 #
2499 # Actions are defined as follow:
2499 # Actions are defined as follow:
2500 # "-R": tag is removed,
2500 # "-R": tag is removed,
2501 # "+A": tag is added,
2501 # "+A": tag is added,
2502 # "-M": tag is moved (old value),
2502 # "-M": tag is moved (old value),
2503 # "+M": tag is moved (new value),
2503 # "+M": tag is moved (new value),
2504 tracktags = lambda x: None
2504 tracktags = lambda x: None
2505 # experimental config: experimental.hook-track-tags
2505 # experimental config: experimental.hook-track-tags
2506 shouldtracktags = self.ui.configbool(
2506 shouldtracktags = self.ui.configbool(
2507 b'experimental', b'hook-track-tags'
2507 b'experimental', b'hook-track-tags'
2508 )
2508 )
2509 if desc != b'strip' and shouldtracktags:
2509 if desc != b'strip' and shouldtracktags:
2510 oldheads = self.changelog.headrevs()
2510 oldheads = self.changelog.headrevs()
2511
2511
2512 def tracktags(tr2):
2512 def tracktags(tr2):
2513 repo = reporef()
2513 repo = reporef()
2514 assert repo is not None # help pytype
2514 assert repo is not None # help pytype
2515 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
2515 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
2516 newheads = repo.changelog.headrevs()
2516 newheads = repo.changelog.headrevs()
2517 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
2517 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
2518 # notes: we compare lists here.
2518 # notes: we compare lists here.
2519 # 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
2520 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
2520 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
2521 if changes:
2521 if changes:
2522 tr2.hookargs[b'tag_moved'] = b'1'
2522 tr2.hookargs[b'tag_moved'] = b'1'
2523 with repo.vfs(
2523 with repo.vfs(
2524 b'changes/tags.changes', b'w', atomictemp=True
2524 b'changes/tags.changes', b'w', atomictemp=True
2525 ) as changesfile:
2525 ) as changesfile:
2526 # note: we do not register the file to the transaction
2526 # note: we do not register the file to the transaction
2527 # because we needs it to still exist on the transaction
2527 # because we needs it to still exist on the transaction
2528 # is close (for txnclose hooks)
2528 # is close (for txnclose hooks)
2529 tagsmod.writediff(changesfile, changes)
2529 tagsmod.writediff(changesfile, changes)
2530
2530
2531 def validate(tr2):
2531 def validate(tr2):
2532 """will run pre-closing hooks"""
2532 """will run pre-closing hooks"""
2533 # 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
2534 # path for now
2534 # path for now
2535 #
2535 #
2536 # 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'
2537 # 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
2538 # available to in memory hooks too.
2538 # available to in memory hooks too.
2539 #
2539 #
2540 # Moreover, we also need to make sure this runs before txnclose
2540 # Moreover, we also need to make sure this runs before txnclose
2541 # hooks and there is no "pending" mechanism that would execute
2541 # hooks and there is no "pending" mechanism that would execute
2542 # logic only if hooks are about to run.
2542 # logic only if hooks are about to run.
2543 #
2543 #
2544 # Fixing this limitation of the transaction is also needed to track
2544 # Fixing this limitation of the transaction is also needed to track
2545 # other families of changes (bookmarks, phases, obsolescence).
2545 # other families of changes (bookmarks, phases, obsolescence).
2546 #
2546 #
2547 # This will have to be fixed before we remove the experimental
2547 # This will have to be fixed before we remove the experimental
2548 # gating.
2548 # gating.
2549 tracktags(tr2)
2549 tracktags(tr2)
2550 repo = reporef()
2550 repo = reporef()
2551 assert repo is not None # help pytype
2551 assert repo is not None # help pytype
2552
2552
2553 singleheadopt = (b'experimental', b'single-head-per-branch')
2553 singleheadopt = (b'experimental', b'single-head-per-branch')
2554 singlehead = repo.ui.configbool(*singleheadopt)
2554 singlehead = repo.ui.configbool(*singleheadopt)
2555 if singlehead:
2555 if singlehead:
2556 singleheadsub = repo.ui.configsuboptions(*singleheadopt)[1]
2556 singleheadsub = repo.ui.configsuboptions(*singleheadopt)[1]
2557 accountclosed = singleheadsub.get(
2557 accountclosed = singleheadsub.get(
2558 b"account-closed-heads", False
2558 b"account-closed-heads", False
2559 )
2559 )
2560 if singleheadsub.get(b"public-changes-only", False):
2560 if singleheadsub.get(b"public-changes-only", False):
2561 filtername = b"immutable"
2561 filtername = b"immutable"
2562 else:
2562 else:
2563 filtername = b"visible"
2563 filtername = b"visible"
2564 scmutil.enforcesinglehead(
2564 scmutil.enforcesinglehead(
2565 repo, tr2, desc, accountclosed, filtername
2565 repo, tr2, desc, accountclosed, filtername
2566 )
2566 )
2567 if hook.hashook(repo.ui, b'pretxnclose-bookmark'):
2567 if hook.hashook(repo.ui, b'pretxnclose-bookmark'):
2568 for name, (old, new) in sorted(
2568 for name, (old, new) in sorted(
2569 tr.changes[b'bookmarks'].items()
2569 tr.changes[b'bookmarks'].items()
2570 ):
2570 ):
2571 args = tr.hookargs.copy()
2571 args = tr.hookargs.copy()
2572 args.update(bookmarks.preparehookargs(name, old, new))
2572 args.update(bookmarks.preparehookargs(name, old, new))
2573 repo.hook(
2573 repo.hook(
2574 b'pretxnclose-bookmark',
2574 b'pretxnclose-bookmark',
2575 throw=True,
2575 throw=True,
2576 **pycompat.strkwargs(args)
2576 **pycompat.strkwargs(args)
2577 )
2577 )
2578 if hook.hashook(repo.ui, b'pretxnclose-phase'):
2578 if hook.hashook(repo.ui, b'pretxnclose-phase'):
2579 cl = repo.unfiltered().changelog
2579 cl = repo.unfiltered().changelog
2580 for revs, (old, new) in tr.changes[b'phases']:
2580 for revs, (old, new) in tr.changes[b'phases']:
2581 for rev in revs:
2581 for rev in revs:
2582 args = tr.hookargs.copy()
2582 args = tr.hookargs.copy()
2583 node = hex(cl.node(rev))
2583 node = hex(cl.node(rev))
2584 args.update(phases.preparehookargs(node, old, new))
2584 args.update(phases.preparehookargs(node, old, new))
2585 repo.hook(
2585 repo.hook(
2586 b'pretxnclose-phase',
2586 b'pretxnclose-phase',
2587 throw=True,
2587 throw=True,
2588 **pycompat.strkwargs(args)
2588 **pycompat.strkwargs(args)
2589 )
2589 )
2590
2590
2591 repo.hook(
2591 repo.hook(
2592 b'pretxnclose', throw=True, **pycompat.strkwargs(tr.hookargs)
2592 b'pretxnclose', throw=True, **pycompat.strkwargs(tr.hookargs)
2593 )
2593 )
2594
2594
2595 def releasefn(tr, success):
2595 def releasefn(tr, success):
2596 repo = reporef()
2596 repo = reporef()
2597 if repo is None:
2597 if repo is None:
2598 # 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
2599 # called from transaction.__del__), there's not much we can do,
2599 # called from transaction.__del__), there's not much we can do,
2600 # so just leave the unfinished transaction there and let the
2600 # so just leave the unfinished transaction there and let the
2601 # user run `hg recover`.
2601 # user run `hg recover`.
2602 return
2602 return
2603 if success:
2603 if success:
2604 # this should be explicitly invoked here, because
2604 # this should be explicitly invoked here, because
2605 # in-memory changes aren't written out at closing
2605 # in-memory changes aren't written out at closing
2606 # transaction, if tr.addfilegenerator (via
2606 # transaction, if tr.addfilegenerator (via
2607 # dirstate.write or so) isn't invoked while
2607 # dirstate.write or so) isn't invoked while
2608 # transaction running
2608 # transaction running
2609 repo.dirstate.write(None)
2609 repo.dirstate.write(None)
2610 else:
2610 else:
2611 # discard all changes (including ones already written
2611 # discard all changes (including ones already written
2612 # out) in this transaction
2612 # out) in this transaction
2613 repo.invalidate(clearfilecache=True)
2613 repo.invalidate(clearfilecache=True)
2614
2614
2615 tr = transaction.transaction(
2615 tr = transaction.transaction(
2616 rp,
2616 rp,
2617 self.svfs,
2617 self.svfs,
2618 vfsmap,
2618 vfsmap,
2619 b"journal",
2619 b"journal",
2620 b"undo",
2620 b"undo",
2621 lambda: None,
2621 lambda: None,
2622 self.store.createmode,
2622 self.store.createmode,
2623 validator=validate,
2623 validator=validate,
2624 releasefn=releasefn,
2624 releasefn=releasefn,
2625 checkambigfiles=_cachedfiles,
2625 checkambigfiles=_cachedfiles,
2626 name=desc,
2626 name=desc,
2627 )
2627 )
2628 for vfs_id, path in self._journalfiles():
2628 for vfs_id, path in self._journalfiles():
2629 tr.add_journal(vfs_id, path)
2629 tr.add_journal(vfs_id, path)
2630 tr.changes[b'origrepolen'] = len(self)
2630 tr.changes[b'origrepolen'] = len(self)
2631 tr.changes[b'obsmarkers'] = set()
2631 tr.changes[b'obsmarkers'] = set()
2632 tr.changes[b'phases'] = []
2632 tr.changes[b'phases'] = []
2633 tr.changes[b'bookmarks'] = {}
2633 tr.changes[b'bookmarks'] = {}
2634
2634
2635 tr.hookargs[b'txnid'] = txnid
2635 tr.hookargs[b'txnid'] = txnid
2636 tr.hookargs[b'txnname'] = desc
2636 tr.hookargs[b'txnname'] = desc
2637 tr.hookargs[b'changes'] = tr.changes
2637 tr.hookargs[b'changes'] = tr.changes
2638 # 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
2639 # outdated when running hooks. As fncache is used for streaming clone,
2639 # outdated when running hooks. As fncache is used for streaming clone,
2640 # 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.
2641 tr.addfinalize(b'flush-fncache', self.store.write)
2641 tr.addfinalize(b'flush-fncache', self.store.write)
2642
2642
2643 def txnclosehook(tr2):
2643 def txnclosehook(tr2):
2644 """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"""
2645 # 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.
2646 # This reduces memory consumption when there are multiple
2646 # This reduces memory consumption when there are multiple
2647 # transactions per lock. This can likely go away if issue5045
2647 # transactions per lock. This can likely go away if issue5045
2648 # fixes the function accumulation.
2648 # fixes the function accumulation.
2649 hookargs = tr2.hookargs
2649 hookargs = tr2.hookargs
2650
2650
2651 def hookfunc(unused_success):
2651 def hookfunc(unused_success):
2652 repo = reporef()
2652 repo = reporef()
2653 assert repo is not None # help pytype
2653 assert repo is not None # help pytype
2654
2654
2655 if hook.hashook(repo.ui, b'txnclose-bookmark'):
2655 if hook.hashook(repo.ui, b'txnclose-bookmark'):
2656 bmchanges = sorted(tr.changes[b'bookmarks'].items())
2656 bmchanges = sorted(tr.changes[b'bookmarks'].items())
2657 for name, (old, new) in bmchanges:
2657 for name, (old, new) in bmchanges:
2658 args = tr.hookargs.copy()
2658 args = tr.hookargs.copy()
2659 args.update(bookmarks.preparehookargs(name, old, new))
2659 args.update(bookmarks.preparehookargs(name, old, new))
2660 repo.hook(
2660 repo.hook(
2661 b'txnclose-bookmark',
2661 b'txnclose-bookmark',
2662 throw=False,
2662 throw=False,
2663 **pycompat.strkwargs(args)
2663 **pycompat.strkwargs(args)
2664 )
2664 )
2665
2665
2666 if hook.hashook(repo.ui, b'txnclose-phase'):
2666 if hook.hashook(repo.ui, b'txnclose-phase'):
2667 cl = repo.unfiltered().changelog
2667 cl = repo.unfiltered().changelog
2668 phasemv = sorted(
2668 phasemv = sorted(
2669 tr.changes[b'phases'], key=lambda r: r[0][0]
2669 tr.changes[b'phases'], key=lambda r: r[0][0]
2670 )
2670 )
2671 for revs, (old, new) in phasemv:
2671 for revs, (old, new) in phasemv:
2672 for rev in revs:
2672 for rev in revs:
2673 args = tr.hookargs.copy()
2673 args = tr.hookargs.copy()
2674 node = hex(cl.node(rev))
2674 node = hex(cl.node(rev))
2675 args.update(phases.preparehookargs(node, old, new))
2675 args.update(phases.preparehookargs(node, old, new))
2676 repo.hook(
2676 repo.hook(
2677 b'txnclose-phase',
2677 b'txnclose-phase',
2678 throw=False,
2678 throw=False,
2679 **pycompat.strkwargs(args)
2679 **pycompat.strkwargs(args)
2680 )
2680 )
2681
2681
2682 repo.hook(
2682 repo.hook(
2683 b'txnclose', throw=False, **pycompat.strkwargs(hookargs)
2683 b'txnclose', throw=False, **pycompat.strkwargs(hookargs)
2684 )
2684 )
2685
2685
2686 repo = reporef()
2686 repo = reporef()
2687 assert repo is not None # help pytype
2687 assert repo is not None # help pytype
2688 repo._afterlock(hookfunc)
2688 repo._afterlock(hookfunc)
2689
2689
2690 tr.addfinalize(b'txnclose-hook', txnclosehook)
2690 tr.addfinalize(b'txnclose-hook', txnclosehook)
2691 # Include a leading "-" to make it happen before the transaction summary
2691 # Include a leading "-" to make it happen before the transaction summary
2692 # reports registered via scmutil.registersummarycallback() whose names
2692 # reports registered via scmutil.registersummarycallback() whose names
2693 # 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
2694 # callbacks run.
2694 # callbacks run.
2695 tr.addpostclose(b'-warm-cache', self._buildcacheupdater(tr))
2695 tr.addpostclose(b'-warm-cache', self._buildcacheupdater(tr))
2696
2696
2697 def txnaborthook(tr2):
2697 def txnaborthook(tr2):
2698 """To be run if transaction is aborted"""
2698 """To be run if transaction is aborted"""
2699 repo = reporef()
2699 repo = reporef()
2700 assert repo is not None # help pytype
2700 assert repo is not None # help pytype
2701 repo.hook(
2701 repo.hook(
2702 b'txnabort', throw=False, **pycompat.strkwargs(tr2.hookargs)
2702 b'txnabort', throw=False, **pycompat.strkwargs(tr2.hookargs)
2703 )
2703 )
2704
2704
2705 tr.addabort(b'txnabort-hook', txnaborthook)
2705 tr.addabort(b'txnabort-hook', txnaborthook)
2706 # avoid eager cache invalidation. in-memory data should be identical
2706 # avoid eager cache invalidation. in-memory data should be identical
2707 # to stored data if transaction has no error.
2707 # to stored data if transaction has no error.
2708 tr.addpostclose(b'refresh-filecachestats', self._refreshfilecachestats)
2708 tr.addpostclose(b'refresh-filecachestats', self._refreshfilecachestats)
2709 self._transref = weakref.ref(tr)
2709 self._transref = weakref.ref(tr)
2710 scmutil.registersummarycallback(self, tr, desc)
2710 scmutil.registersummarycallback(self, tr, desc)
2711 # 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
2712 # 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
2713 # time of this operation.
2713 # time of this operation.
2714 #
2714 #
2715 # 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
2716 # altering the dirstate under us.
2716 # altering the dirstate under us.
2717 #
2717 #
2718 # 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
2719 # always do it). There are more viable alternative that exists
2719 # always do it). There are more viable alternative that exists
2720 #
2720 #
2721 # - 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
2722 # a clean "keep-update" to them on `hg rollback`.
2722 # a clean "keep-update" to them on `hg rollback`.
2723 #
2723 #
2724 # - slightly changing the behavior an applying a logic similar to "hg
2724 # - slightly changing the behavior an applying a logic similar to "hg
2725 # strip" to pick a working copy destination on `hg rollback`
2725 # strip" to pick a working copy destination on `hg rollback`
2726 if self.currentwlock() is not None:
2726 if self.currentwlock() is not None:
2727 ds = self.dirstate
2727 ds = self.dirstate
2728 if not self.vfs.exists(b'branch'):
2728 if not self.vfs.exists(b'branch'):
2729 # force a file to be written if None exist
2729 # force a file to be written if None exist
2730 ds.setbranch(b'default', None)
2730 ds.setbranch(b'default', None)
2731
2731
2732 def backup_dirstate(tr):
2732 def backup_dirstate(tr):
2733 for f in ds.all_file_names():
2733 for f in ds.all_file_names():
2734 # hardlink backup is okay because `dirstate` is always
2734 # hardlink backup is okay because `dirstate` is always
2735 # atomically written and possible data file are append only
2735 # atomically written and possible data file are append only
2736 # and resistant to trailing data.
2736 # and resistant to trailing data.
2737 tr.addbackup(f, hardlink=True, location=b'plain')
2737 tr.addbackup(f, hardlink=True, location=b'plain')
2738
2738
2739 tr.addvalidator(b'dirstate-backup', backup_dirstate)
2739 tr.addvalidator(b'dirstate-backup', backup_dirstate)
2740 return tr
2740 return tr
2741
2741
2742 def _journalfiles(self):
2742 def _journalfiles(self):
2743 return (
2743 return (
2744 (self.svfs, b'journal'),
2744 (self.svfs, b'journal'),
2745 (self.vfs, b'journal.desc'),
2745 (self.vfs, b'journal.desc'),
2746 )
2746 )
2747
2747
2748 def undofiles(self):
2748 def undofiles(self):
2749 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
2749 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
2750
2750
2751 @unfilteredmethod
2751 @unfilteredmethod
2752 def _writejournal(self, desc):
2752 def _writejournal(self, desc):
2753 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))
2754
2754
2755 def recover(self):
2755 def recover(self):
2756 with self.lock():
2756 with self.lock():
2757 if self.svfs.exists(b"journal"):
2757 if self.svfs.exists(b"journal"):
2758 self.ui.status(_(b"rolling back interrupted transaction\n"))
2758 self.ui.status(_(b"rolling back interrupted transaction\n"))
2759 vfsmap = self.vfs_map
2759 vfsmap = self.vfs_map
2760 transaction.rollback(
2760 transaction.rollback(
2761 self.svfs,
2761 self.svfs,
2762 vfsmap,
2762 vfsmap,
2763 b"journal",
2763 b"journal",
2764 self.ui.warn,
2764 self.ui.warn,
2765 checkambigfiles=_cachedfiles,
2765 checkambigfiles=_cachedfiles,
2766 )
2766 )
2767 self.invalidate()
2767 self.invalidate()
2768 return True
2768 return True
2769 else:
2769 else:
2770 self.ui.warn(_(b"no interrupted transaction available\n"))
2770 self.ui.warn(_(b"no interrupted transaction available\n"))
2771 return False
2771 return False
2772
2772
2773 def rollback(self, dryrun=False, force=False):
2773 def rollback(self, dryrun=False, force=False):
2774 wlock = lock = None
2774 wlock = lock = None
2775 try:
2775 try:
2776 wlock = self.wlock()
2776 wlock = self.wlock()
2777 lock = self.lock()
2777 lock = self.lock()
2778 if self.svfs.exists(b"undo"):
2778 if self.svfs.exists(b"undo"):
2779 return self._rollback(dryrun, force)
2779 return self._rollback(dryrun, force)
2780 else:
2780 else:
2781 self.ui.warn(_(b"no rollback information available\n"))
2781 self.ui.warn(_(b"no rollback information available\n"))
2782 return 1
2782 return 1
2783 finally:
2783 finally:
2784 release(lock, wlock)
2784 release(lock, wlock)
2785
2785
2786 @unfilteredmethod # Until we get smarter cache management
2786 @unfilteredmethod # Until we get smarter cache management
2787 def _rollback(self, dryrun, force):
2787 def _rollback(self, dryrun, force):
2788 ui = self.ui
2788 ui = self.ui
2789
2789
2790 parents = self.dirstate.parents()
2790 parents = self.dirstate.parents()
2791 try:
2791 try:
2792 args = self.vfs.read(b'undo.desc').splitlines()
2792 args = self.vfs.read(b'undo.desc').splitlines()
2793 (oldlen, desc, detail) = (int(args[0]), args[1], None)
2793 (oldlen, desc, detail) = (int(args[0]), args[1], None)
2794 if len(args) >= 3:
2794 if len(args) >= 3:
2795 detail = args[2]
2795 detail = args[2]
2796 oldtip = oldlen - 1
2796 oldtip = oldlen - 1
2797
2797
2798 if detail and ui.verbose:
2798 if detail and ui.verbose:
2799 msg = _(
2799 msg = _(
2800 b'repository tip rolled back to revision %d'
2800 b'repository tip rolled back to revision %d'
2801 b' (undo %s: %s)\n'
2801 b' (undo %s: %s)\n'
2802 ) % (oldtip, desc, detail)
2802 ) % (oldtip, desc, detail)
2803 else:
2803 else:
2804 msg = _(
2804 msg = _(
2805 b'repository tip rolled back to revision %d (undo %s)\n'
2805 b'repository tip rolled back to revision %d (undo %s)\n'
2806 ) % (oldtip, desc)
2806 ) % (oldtip, desc)
2807 parentgone = any(self[p].rev() > oldtip for p in parents)
2807 parentgone = any(self[p].rev() > oldtip for p in parents)
2808 except IOError:
2808 except IOError:
2809 msg = _(b'rolling back unknown transaction\n')
2809 msg = _(b'rolling back unknown transaction\n')
2810 desc = None
2810 desc = None
2811 parentgone = True
2811 parentgone = True
2812
2812
2813 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':
2814 raise error.Abort(
2814 raise error.Abort(
2815 _(
2815 _(
2816 b'rollback of last commit while not checked out '
2816 b'rollback of last commit while not checked out '
2817 b'may lose data'
2817 b'may lose data'
2818 ),
2818 ),
2819 hint=_(b'use -f to force'),
2819 hint=_(b'use -f to force'),
2820 )
2820 )
2821
2821
2822 ui.status(msg)
2822 ui.status(msg)
2823 if dryrun:
2823 if dryrun:
2824 return 0
2824 return 0
2825
2825
2826 self.destroying()
2826 self.destroying()
2827 vfsmap = self.vfs_map
2827 vfsmap = self.vfs_map
2828 skip_journal_pattern = None
2828 skip_journal_pattern = None
2829 if not parentgone:
2829 if not parentgone:
2830 skip_journal_pattern = RE_SKIP_DIRSTATE_ROLLBACK
2830 skip_journal_pattern = RE_SKIP_DIRSTATE_ROLLBACK
2831 transaction.rollback(
2831 transaction.rollback(
2832 self.svfs,
2832 self.svfs,
2833 vfsmap,
2833 vfsmap,
2834 b'undo',
2834 b'undo',
2835 ui.warn,
2835 ui.warn,
2836 checkambigfiles=_cachedfiles,
2836 checkambigfiles=_cachedfiles,
2837 skip_journal_pattern=skip_journal_pattern,
2837 skip_journal_pattern=skip_journal_pattern,
2838 )
2838 )
2839 self.invalidate()
2839 self.invalidate()
2840 self.dirstate.invalidate()
2840 self.dirstate.invalidate()
2841
2841
2842 if parentgone:
2842 if parentgone:
2843 # replace this with some explicit parent update in the future.
2843 # replace this with some explicit parent update in the future.
2844 has_node = self.changelog.index.has_node
2844 has_node = self.changelog.index.has_node
2845 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):
2846 # There was no dirstate to backup initially, we need to drop
2846 # There was no dirstate to backup initially, we need to drop
2847 # the existing one.
2847 # the existing one.
2848 with self.dirstate.changing_parents(self):
2848 with self.dirstate.changing_parents(self):
2849 self.dirstate.setparents(self.nullid)
2849 self.dirstate.setparents(self.nullid)
2850 self.dirstate.clear()
2850 self.dirstate.clear()
2851
2851
2852 parents = tuple([p.rev() for p in self[None].parents()])
2852 parents = tuple([p.rev() for p in self[None].parents()])
2853 if len(parents) > 1:
2853 if len(parents) > 1:
2854 ui.status(
2854 ui.status(
2855 _(
2855 _(
2856 b'working directory now based on '
2856 b'working directory now based on '
2857 b'revisions %d and %d\n'
2857 b'revisions %d and %d\n'
2858 )
2858 )
2859 % parents
2859 % parents
2860 )
2860 )
2861 else:
2861 else:
2862 ui.status(
2862 ui.status(
2863 _(b'working directory now based on revision %d\n') % parents
2863 _(b'working directory now based on revision %d\n') % parents
2864 )
2864 )
2865 mergestatemod.mergestate.clean(self)
2865 mergestatemod.mergestate.clean(self)
2866
2866
2867 # 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
2868 # them to destroy(), which will prevent the branchhead cache from being
2868 # them to destroy(), which will prevent the branchhead cache from being
2869 # invalidated.
2869 # invalidated.
2870 self.destroyed()
2870 self.destroyed()
2871 return 0
2871 return 0
2872
2872
2873 def _buildcacheupdater(self, newtransaction):
2873 def _buildcacheupdater(self, newtransaction):
2874 """called during transaction to build the callback updating cache
2874 """called during transaction to build the callback updating cache
2875
2875
2876 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
2877 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
2878 method.
2878 method.
2879 """
2879 """
2880 # we must avoid cyclic reference between repo and transaction.
2880 # we must avoid cyclic reference between repo and transaction.
2881 reporef = weakref.ref(self)
2881 reporef = weakref.ref(self)
2882
2882
2883 def updater(tr):
2883 def updater(tr):
2884 repo = reporef()
2884 repo = reporef()
2885 assert repo is not None # help pytype
2885 assert repo is not None # help pytype
2886 repo.updatecaches(tr)
2886 repo.updatecaches(tr)
2887
2887
2888 return updater
2888 return updater
2889
2889
2890 @unfilteredmethod
2890 @unfilteredmethod
2891 def updatecaches(self, tr=None, full=False, caches=None):
2891 def updatecaches(self, tr=None, full=False, caches=None):
2892 """warm appropriate caches
2892 """warm appropriate caches
2893
2893
2894 If this function is called after a transaction closed. The transaction
2894 If this function is called after a transaction closed. The transaction
2895 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
2896 update caches relevant to the changes in that transaction.
2896 update caches relevant to the changes in that transaction.
2897
2897
2898 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
2899 up-to-date data. Even the ones usually loaded more lazily.
2899 up-to-date data. Even the ones usually loaded more lazily.
2900
2900
2901 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
2902 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
2903 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
2904 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.
2905 """
2905 """
2906 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':
2907 # During strip, many caches are invalid but
2907 # During strip, many caches are invalid but
2908 # later call to `destroyed` will refresh them.
2908 # later call to `destroyed` will refresh them.
2909 return
2909 return
2910
2910
2911 unfi = self.unfiltered()
2911 unfi = self.unfiltered()
2912
2912
2913 if full:
2913 if full:
2914 msg = (
2914 msg = (
2915 "`full` argument for `repo.updatecaches` is deprecated\n"
2915 "`full` argument for `repo.updatecaches` is deprecated\n"
2916 "(use `caches=repository.CACHE_ALL` instead)"
2916 "(use `caches=repository.CACHE_ALL` instead)"
2917 )
2917 )
2918 self.ui.deprecwarn(msg, b"5.9")
2918 self.ui.deprecwarn(msg, b"5.9")
2919 caches = repository.CACHES_ALL
2919 caches = repository.CACHES_ALL
2920 if full == b"post-clone":
2920 if full == b"post-clone":
2921 caches = repository.CACHES_POST_CLONE
2921 caches = repository.CACHES_POST_CLONE
2922 caches = repository.CACHES_ALL
2922 caches = repository.CACHES_ALL
2923 elif caches is None:
2923 elif caches is None:
2924 caches = repository.CACHES_DEFAULT
2924 caches = repository.CACHES_DEFAULT
2925
2925
2926 if repository.CACHE_BRANCHMAP_SERVED in caches:
2926 if repository.CACHE_BRANCHMAP_SERVED in caches:
2927 if tr is None or tr.changes[b'origrepolen'] < len(self):
2927 if tr is None or tr.changes[b'origrepolen'] < len(self):
2928 # accessing the 'served' branchmap should refresh all the others,
2928 # accessing the 'served' branchmap should refresh all the others,
2929 self.ui.debug(b'updating the branch cache\n')
2929 self.ui.debug(b'updating the branch cache\n')
2930 self.filtered(b'served').branchmap()
2930 self.filtered(b'served').branchmap()
2931 self.filtered(b'served.hidden').branchmap()
2931 self.filtered(b'served.hidden').branchmap()
2932 # flush all possibly delayed write.
2932 # flush all possibly delayed write.
2933 self._branchcaches.write_delayed(self)
2933 self._branchcaches.write_delayed(self)
2934
2934
2935 if repository.CACHE_CHANGELOG_CACHE in caches:
2935 if repository.CACHE_CHANGELOG_CACHE in caches:
2936 self.changelog.update_caches(transaction=tr)
2936 self.changelog.update_caches(transaction=tr)
2937
2937
2938 if repository.CACHE_MANIFESTLOG_CACHE in caches:
2938 if repository.CACHE_MANIFESTLOG_CACHE in caches:
2939 self.manifestlog.update_caches(transaction=tr)
2939 self.manifestlog.update_caches(transaction=tr)
2940 for entry in self.store.walk():
2940 for entry in self.store.walk():
2941 if not entry.is_revlog:
2941 if not entry.is_revlog:
2942 continue
2942 continue
2943 if not entry.is_manifestlog:
2943 if not entry.is_manifestlog:
2944 continue
2944 continue
2945 manifestrevlog = entry.get_revlog_instance(self).get_revlog()
2945 manifestrevlog = entry.get_revlog_instance(self).get_revlog()
2946 if manifestrevlog is not None:
2946 if manifestrevlog is not None:
2947 manifestrevlog.update_caches(transaction=tr)
2947 manifestrevlog.update_caches(transaction=tr)
2948
2948
2949 if repository.CACHE_REV_BRANCH in caches:
2949 if repository.CACHE_REV_BRANCH in caches:
2950 rbc = unfi.revbranchcache()
2950 rbc = unfi.revbranchcache()
2951 for r in unfi.changelog:
2951 for r in unfi.changelog:
2952 rbc.branchinfo(r)
2952 rbc.branchinfo(r)
2953 rbc.write()
2953 rbc.write()
2954
2954
2955 if repository.CACHE_FULL_MANIFEST in caches:
2955 if repository.CACHE_FULL_MANIFEST in caches:
2956 # ensure the working copy parents are in the manifestfulltextcache
2956 # ensure the working copy parents are in the manifestfulltextcache
2957 for ctx in self[b'.'].parents():
2957 for ctx in self[b'.'].parents():
2958 ctx.manifest() # accessing the manifest is enough
2958 ctx.manifest() # accessing the manifest is enough
2959
2959
2960 if repository.CACHE_FILE_NODE_TAGS in caches:
2960 if repository.CACHE_FILE_NODE_TAGS in caches:
2961 # accessing fnode cache warms the cache
2961 # accessing fnode cache warms the cache
2962 tagsmod.fnoderevs(self.ui, unfi, unfi.changelog.revs())
2962 tagsmod.fnoderevs(self.ui, unfi, unfi.changelog.revs())
2963
2963
2964 if repository.CACHE_TAGS_DEFAULT in caches:
2964 if repository.CACHE_TAGS_DEFAULT in caches:
2965 # accessing tags warm the cache
2965 # accessing tags warm the cache
2966 self.tags()
2966 self.tags()
2967 if repository.CACHE_TAGS_SERVED in caches:
2967 if repository.CACHE_TAGS_SERVED in caches:
2968 self.filtered(b'served').tags()
2968 self.filtered(b'served').tags()
2969
2969
2970 if repository.CACHE_BRANCHMAP_ALL in caches:
2970 if repository.CACHE_BRANCHMAP_ALL in caches:
2971 # The CACHE_BRANCHMAP_ALL updates lazily-loaded caches immediately,
2971 # The CACHE_BRANCHMAP_ALL updates lazily-loaded caches immediately,
2972 # 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
2973 # 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
2974 # 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
2975 # 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).
2976 for filt in repoview.filtertable.keys():
2976 for filt in repoview.filtertable.keys():
2977 filtered = self.filtered(filt)
2977 filtered = self.filtered(filt)
2978 filtered.branchmap().write(filtered)
2978 filtered.branchmap().write(filtered)
2979
2979
2980 def invalidatecaches(self):
2980 def invalidatecaches(self):
2981 if '_tagscache' in vars(self):
2981 if '_tagscache' in vars(self):
2982 # can't use delattr on proxy
2982 # can't use delattr on proxy
2983 del self.__dict__['_tagscache']
2983 del self.__dict__['_tagscache']
2984
2984
2985 self._branchcaches.clear()
2985 self._branchcaches.clear()
2986 self.invalidatevolatilesets()
2986 self.invalidatevolatilesets()
2987 self._sparsesignaturecache.clear()
2987 self._sparsesignaturecache.clear()
2988
2988
2989 def invalidatevolatilesets(self):
2989 def invalidatevolatilesets(self):
2990 self.filteredrevcache.clear()
2990 self.filteredrevcache.clear()
2991 obsolete.clearobscaches(self)
2991 obsolete.clearobscaches(self)
2992 self._quick_access_changeid_invalidate()
2992 self._quick_access_changeid_invalidate()
2993
2993
2994 def invalidatedirstate(self):
2994 def invalidatedirstate(self):
2995 """Invalidates the dirstate, causing the next call to dirstate
2995 """Invalidates the dirstate, causing the next call to dirstate
2996 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,
2997 rereading it if it has.
2997 rereading it if it has.
2998
2998
2999 This is different to dirstate.invalidate() that it doesn't always
2999 This is different to dirstate.invalidate() that it doesn't always
3000 rereads the dirstate. Use dirstate.invalidate() if you want to
3000 rereads the dirstate. Use dirstate.invalidate() if you want to
3001 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
3002 known good state)."""
3002 known good state)."""
3003 unfi = self.unfiltered()
3003 unfi = self.unfiltered()
3004 if 'dirstate' in unfi.__dict__:
3004 if 'dirstate' in unfi.__dict__:
3005 assert not self.dirstate.is_changing_any
3005 assert not self.dirstate.is_changing_any
3006 del unfi.__dict__['dirstate']
3006 del unfi.__dict__['dirstate']
3007
3007
3008 def invalidate(self, clearfilecache=False):
3008 def invalidate(self, clearfilecache=False):
3009 """Invalidates both store and non-store parts other than dirstate
3009 """Invalidates both store and non-store parts other than dirstate
3010
3010
3011 If a transaction is running, invalidation of store is omitted,
3011 If a transaction is running, invalidation of store is omitted,
3012 because discarding in-memory changes might cause inconsistency
3012 because discarding in-memory changes might cause inconsistency
3013 (e.g. incomplete fncache causes unintentional failure, but
3013 (e.g. incomplete fncache causes unintentional failure, but
3014 redundant one doesn't).
3014 redundant one doesn't).
3015 """
3015 """
3016 unfiltered = self.unfiltered() # all file caches are stored unfiltered
3016 unfiltered = self.unfiltered() # all file caches are stored unfiltered
3017 for k in list(self._filecache.keys()):
3017 for k in list(self._filecache.keys()):
3018 if (
3018 if (
3019 k == b'changelog'
3019 k == b'changelog'
3020 and self.currenttransaction()
3020 and self.currenttransaction()
3021 and self.changelog._delayed
3021 and self.changelog._delayed
3022 ):
3022 ):
3023 # The changelog object may store unwritten revisions. We don't
3023 # The changelog object may store unwritten revisions. We don't
3024 # want to lose them.
3024 # want to lose them.
3025 # TODO: Solve the problem instead of working around it.
3025 # TODO: Solve the problem instead of working around it.
3026 continue
3026 continue
3027
3027
3028 if clearfilecache:
3028 if clearfilecache:
3029 del self._filecache[k]
3029 del self._filecache[k]
3030 try:
3030 try:
3031 # 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
3032 # fact it refers to an attribut name. However changing this was
3032 # fact it refers to an attribut name. However changing this was
3033 # a bit a scope creep compared to the series cleaning up
3033 # a bit a scope creep compared to the series cleaning up
3034 # del/set/getattr so we kept thing simple here.
3034 # del/set/getattr so we kept thing simple here.
3035 delattr(unfiltered, pycompat.sysstr(k))
3035 delattr(unfiltered, pycompat.sysstr(k))
3036 except AttributeError:
3036 except AttributeError:
3037 pass
3037 pass
3038 self.invalidatecaches()
3038 self.invalidatecaches()
3039 if not self.currenttransaction():
3039 if not self.currenttransaction():
3040 # TODO: Changing contents of store outside transaction
3040 # TODO: Changing contents of store outside transaction
3041 # causes inconsistency. We should make in-memory store
3041 # causes inconsistency. We should make in-memory store
3042 # changes detectable, and abort if changed.
3042 # changes detectable, and abort if changed.
3043 self.store.invalidatecaches()
3043 self.store.invalidatecaches()
3044
3044
3045 def invalidateall(self):
3045 def invalidateall(self):
3046 """Fully invalidates both store and non-store parts, causing the
3046 """Fully invalidates both store and non-store parts, causing the
3047 subsequent operation to reread any outside changes."""
3047 subsequent operation to reread any outside changes."""
3048 # extension should hook this to invalidate its caches
3048 # extension should hook this to invalidate its caches
3049 self.invalidate()
3049 self.invalidate()
3050 self.invalidatedirstate()
3050 self.invalidatedirstate()
3051
3051
3052 @unfilteredmethod
3052 @unfilteredmethod
3053 def _refreshfilecachestats(self, tr):
3053 def _refreshfilecachestats(self, tr):
3054 """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"""
3055 for k, ce in self._filecache.items():
3055 for k, ce in self._filecache.items():
3056 k = pycompat.sysstr(k)
3056 k = pycompat.sysstr(k)
3057 if k == 'dirstate' or k not in self.__dict__:
3057 if k == 'dirstate' or k not in self.__dict__:
3058 continue
3058 continue
3059 ce.refresh()
3059 ce.refresh()
3060
3060
3061 def _lock(
3061 def _lock(
3062 self,
3062 self,
3063 vfs,
3063 vfs,
3064 lockname,
3064 lockname,
3065 wait,
3065 wait,
3066 releasefn,
3066 releasefn,
3067 acquirefn,
3067 acquirefn,
3068 desc,
3068 desc,
3069 ):
3069 ):
3070 timeout = 0
3070 timeout = 0
3071 warntimeout = 0
3071 warntimeout = 0
3072 if wait:
3072 if wait:
3073 timeout = self.ui.configint(b"ui", b"timeout")
3073 timeout = self.ui.configint(b"ui", b"timeout")
3074 warntimeout = self.ui.configint(b"ui", b"timeout.warn")
3074 warntimeout = self.ui.configint(b"ui", b"timeout.warn")
3075 # internal config: ui.signal-safe-lock
3075 # internal config: ui.signal-safe-lock
3076 signalsafe = self.ui.configbool(b'ui', b'signal-safe-lock')
3076 signalsafe = self.ui.configbool(b'ui', b'signal-safe-lock')
3077
3077
3078 l = lockmod.trylock(
3078 l = lockmod.trylock(
3079 self.ui,
3079 self.ui,
3080 vfs,
3080 vfs,
3081 lockname,
3081 lockname,
3082 timeout,
3082 timeout,
3083 warntimeout,
3083 warntimeout,
3084 releasefn=releasefn,
3084 releasefn=releasefn,
3085 acquirefn=acquirefn,
3085 acquirefn=acquirefn,
3086 desc=desc,
3086 desc=desc,
3087 signalsafe=signalsafe,
3087 signalsafe=signalsafe,
3088 )
3088 )
3089 return l
3089 return l
3090
3090
3091 def _afterlock(self, callback):
3091 def _afterlock(self, callback):
3092 """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
3093
3093
3094 The callback will be executed when the outermost lock is released
3094 The callback will be executed when the outermost lock is released
3095 (with wlock being higher level than 'lock')."""
3095 (with wlock being higher level than 'lock')."""
3096 for ref in (self._wlockref, self._lockref):
3096 for ref in (self._wlockref, self._lockref):
3097 l = ref and ref()
3097 l = ref and ref()
3098 if l and l.held:
3098 if l and l.held:
3099 l.postrelease.append(callback)
3099 l.postrelease.append(callback)
3100 break
3100 break
3101 else: # no lock have been found.
3101 else: # no lock have been found.
3102 callback(True)
3102 callback(True)
3103
3103
3104 def lock(self, wait=True):
3104 def lock(self, wait=True):
3105 """Lock the repository store (.hg/store) and return a weak reference
3105 """Lock the repository store (.hg/store) and return a weak reference
3106 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
3107 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.)
3108
3108
3109 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
3110 'wlock' first to avoid a dead-lock hazard."""
3110 'wlock' first to avoid a dead-lock hazard."""
3111 l = self._currentlock(self._lockref)
3111 l = self._currentlock(self._lockref)
3112 if l is not None:
3112 if l is not None:
3113 l.lock()
3113 l.lock()
3114 return l
3114 return l
3115
3115
3116 l = self._lock(
3116 l = self._lock(
3117 vfs=self.svfs,
3117 vfs=self.svfs,
3118 lockname=b"lock",
3118 lockname=b"lock",
3119 wait=wait,
3119 wait=wait,
3120 releasefn=None,
3120 releasefn=None,
3121 acquirefn=self.invalidate,
3121 acquirefn=self.invalidate,
3122 desc=_(b'repository %s') % self.origroot,
3122 desc=_(b'repository %s') % self.origroot,
3123 )
3123 )
3124 self._lockref = weakref.ref(l)
3124 self._lockref = weakref.ref(l)
3125 return l
3125 return l
3126
3126
3127 def wlock(self, wait=True):
3127 def wlock(self, wait=True):
3128 """Lock the non-store parts of the repository (everything under
3128 """Lock the non-store parts of the repository (everything under
3129 .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.
3130
3130
3131 Use this before modifying files in .hg.
3131 Use this before modifying files in .hg.
3132
3132
3133 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
3134 'wlock' first to avoid a dead-lock hazard."""
3134 'wlock' first to avoid a dead-lock hazard."""
3135 l = self._wlockref() if self._wlockref else None
3135 l = self._wlockref() if self._wlockref else None
3136 if l is not None and l.held:
3136 if l is not None and l.held:
3137 l.lock()
3137 l.lock()
3138 return l
3138 return l
3139
3139
3140 # 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
3141 # acquisition would not cause dead-lock as they would just fail.
3141 # acquisition would not cause dead-lock as they would just fail.
3142 if wait and (
3142 if wait and (
3143 self.ui.configbool(b'devel', b'all-warnings')
3143 self.ui.configbool(b'devel', b'all-warnings')
3144 or self.ui.configbool(b'devel', b'check-locks')
3144 or self.ui.configbool(b'devel', b'check-locks')
3145 ):
3145 ):
3146 if self._currentlock(self._lockref) is not None:
3146 if self._currentlock(self._lockref) is not None:
3147 self.ui.develwarn(b'"wlock" acquired after "lock"')
3147 self.ui.develwarn(b'"wlock" acquired after "lock"')
3148
3148
3149 def unlock():
3149 def unlock():
3150 if self.dirstate.is_changing_any:
3150 if self.dirstate.is_changing_any:
3151 msg = b"wlock release in the middle of a changing parents"
3151 msg = b"wlock release in the middle of a changing parents"
3152 self.ui.develwarn(msg)
3152 self.ui.develwarn(msg)
3153 self.dirstate.invalidate()
3153 self.dirstate.invalidate()
3154 else:
3154 else:
3155 if self.dirstate._dirty:
3155 if self.dirstate._dirty:
3156 msg = b"dirty dirstate on wlock release"
3156 msg = b"dirty dirstate on wlock release"
3157 self.ui.develwarn(msg)
3157 self.ui.develwarn(msg)
3158 self.dirstate.write(None)
3158 self.dirstate.write(None)
3159
3159
3160 unfi = self.unfiltered()
3160 unfi = self.unfiltered()
3161 if 'dirstate' in unfi.__dict__:
3161 if 'dirstate' in unfi.__dict__:
3162 del unfi.__dict__['dirstate']
3162 del unfi.__dict__['dirstate']
3163
3163
3164 l = self._lock(
3164 l = self._lock(
3165 self.vfs,
3165 self.vfs,
3166 b"wlock",
3166 b"wlock",
3167 wait,
3167 wait,
3168 unlock,
3168 unlock,
3169 self.invalidatedirstate,
3169 self.invalidatedirstate,
3170 _(b'working directory of %s') % self.origroot,
3170 _(b'working directory of %s') % self.origroot,
3171 )
3171 )
3172 self._wlockref = weakref.ref(l)
3172 self._wlockref = weakref.ref(l)
3173 return l
3173 return l
3174
3174
3175 def _currentlock(self, lockref):
3175 def _currentlock(self, lockref):
3176 """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."""
3177 if lockref is None:
3177 if lockref is None:
3178 return None
3178 return None
3179 l = lockref()
3179 l = lockref()
3180 if l is None or not l.held:
3180 if l is None or not l.held:
3181 return None
3181 return None
3182 return l
3182 return l
3183
3183
3184 def currentwlock(self):
3184 def currentwlock(self):
3185 """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."""
3186 return self._currentlock(self._wlockref)
3186 return self._currentlock(self._wlockref)
3187
3187
3188 def currentlock(self):
3188 def currentlock(self):
3189 """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."""
3190 return self._currentlock(self._lockref)
3190 return self._currentlock(self._lockref)
3191
3191
3192 def checkcommitpatterns(self, wctx, match, status, fail):
3192 def checkcommitpatterns(self, wctx, match, status, fail):
3193 """check for commit arguments that aren't committable"""
3193 """check for commit arguments that aren't committable"""
3194 if match.isexact() or match.prefix():
3194 if match.isexact() or match.prefix():
3195 matched = set(status.modified + status.added + status.removed)
3195 matched = set(status.modified + status.added + status.removed)
3196
3196
3197 for f in match.files():
3197 for f in match.files():
3198 f = self.dirstate.normalize(f)
3198 f = self.dirstate.normalize(f)
3199 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:
3200 continue
3200 continue
3201 if f in status.deleted:
3201 if f in status.deleted:
3202 fail(f, _(b'file not found!'))
3202 fail(f, _(b'file not found!'))
3203 # Is it a directory that exists or used to exist?
3203 # Is it a directory that exists or used to exist?
3204 if self.wvfs.isdir(f) or wctx.p1().hasdir(f):
3204 if self.wvfs.isdir(f) or wctx.p1().hasdir(f):
3205 d = f + b'/'
3205 d = f + b'/'
3206 for mf in matched:
3206 for mf in matched:
3207 if mf.startswith(d):
3207 if mf.startswith(d):
3208 break
3208 break
3209 else:
3209 else:
3210 fail(f, _(b"no match under directory!"))
3210 fail(f, _(b"no match under directory!"))
3211 elif f not in self.dirstate:
3211 elif f not in self.dirstate:
3212 fail(f, _(b"file not tracked!"))
3212 fail(f, _(b"file not tracked!"))
3213
3213
3214 @unfilteredmethod
3214 @unfilteredmethod
3215 def commit(
3215 def commit(
3216 self,
3216 self,
3217 text=b"",
3217 text=b"",
3218 user=None,
3218 user=None,
3219 date=None,
3219 date=None,
3220 match=None,
3220 match=None,
3221 force=False,
3221 force=False,
3222 editor=None,
3222 editor=None,
3223 extra=None,
3223 extra=None,
3224 ):
3224 ):
3225 """Add a new revision to current repository.
3225 """Add a new revision to current repository.
3226
3226
3227 Revision information is gathered from the working directory,
3227 Revision information is gathered from the working directory,
3228 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
3229 supplied, it is called to get a commit message.
3229 supplied, it is called to get a commit message.
3230 """
3230 """
3231 if extra is None:
3231 if extra is None:
3232 extra = {}
3232 extra = {}
3233
3233
3234 def fail(f, msg):
3234 def fail(f, msg):
3235 raise error.InputError(b'%s: %s' % (f, msg))
3235 raise error.InputError(b'%s: %s' % (f, msg))
3236
3236
3237 if not match:
3237 if not match:
3238 match = matchmod.always()
3238 match = matchmod.always()
3239
3239
3240 if not force:
3240 if not force:
3241 match.bad = fail
3241 match.bad = fail
3242
3242
3243 # lock() for recent changelog (see issue4368)
3243 # lock() for recent changelog (see issue4368)
3244 with self.wlock(), self.lock():
3244 with self.wlock(), self.lock():
3245 wctx = self[None]
3245 wctx = self[None]
3246 merge = len(wctx.parents()) > 1
3246 merge = len(wctx.parents()) > 1
3247
3247
3248 if not force and merge and not match.always():
3248 if not force and merge and not match.always():
3249 raise error.Abort(
3249 raise error.Abort(
3250 _(
3250 _(
3251 b'cannot partially commit a merge '
3251 b'cannot partially commit a merge '
3252 b'(do not specify files or patterns)'
3252 b'(do not specify files or patterns)'
3253 )
3253 )
3254 )
3254 )
3255
3255
3256 status = self.status(match=match, clean=force)
3256 status = self.status(match=match, clean=force)
3257 if force:
3257 if force:
3258 status.modified.extend(
3258 status.modified.extend(
3259 status.clean
3259 status.clean
3260 ) # mq may commit clean files
3260 ) # mq may commit clean files
3261
3261
3262 # check subrepos
3262 # check subrepos
3263 subs, commitsubs, newstate = subrepoutil.precommit(
3263 subs, commitsubs, newstate = subrepoutil.precommit(
3264 self.ui, wctx, status, match, force=force
3264 self.ui, wctx, status, match, force=force
3265 )
3265 )
3266
3266
3267 # make sure all explicit patterns are matched
3267 # make sure all explicit patterns are matched
3268 if not force:
3268 if not force:
3269 self.checkcommitpatterns(wctx, match, status, fail)
3269 self.checkcommitpatterns(wctx, match, status, fail)
3270
3270
3271 cctx = context.workingcommitctx(
3271 cctx = context.workingcommitctx(
3272 self, status, text, user, date, extra
3272 self, status, text, user, date, extra
3273 )
3273 )
3274
3274
3275 ms = mergestatemod.mergestate.read(self)
3275 ms = mergestatemod.mergestate.read(self)
3276 mergeutil.checkunresolved(ms)
3276 mergeutil.checkunresolved(ms)
3277
3277
3278 # internal config: ui.allowemptycommit
3278 # internal config: ui.allowemptycommit
3279 if cctx.isempty() and not self.ui.configbool(
3279 if cctx.isempty() and not self.ui.configbool(
3280 b'ui', b'allowemptycommit'
3280 b'ui', b'allowemptycommit'
3281 ):
3281 ):
3282 self.ui.debug(b'nothing to commit, clearing merge state\n')
3282 self.ui.debug(b'nothing to commit, clearing merge state\n')
3283 ms.reset()
3283 ms.reset()
3284 return None
3284 return None
3285
3285
3286 if merge and cctx.deleted():
3286 if merge and cctx.deleted():
3287 raise error.Abort(_(b"cannot commit merge with missing files"))
3287 raise error.Abort(_(b"cannot commit merge with missing files"))
3288
3288
3289 if editor:
3289 if editor:
3290 cctx._text = editor(self, cctx, subs)
3290 cctx._text = editor(self, cctx, subs)
3291 edited = text != cctx._text
3291 edited = text != cctx._text
3292
3292
3293 # Save commit message in case this transaction gets rolled back
3293 # Save commit message in case this transaction gets rolled back
3294 # (e.g. by a pretxncommit hook). Leave the content alone on
3294 # (e.g. by a pretxncommit hook). Leave the content alone on
3295 # the assumption that the user will use the same editor again.
3295 # the assumption that the user will use the same editor again.
3296 msg_path = self.savecommitmessage(cctx._text)
3296 msg_path = self.savecommitmessage(cctx._text)
3297
3297
3298 # commit subs and write new state
3298 # commit subs and write new state
3299 if subs:
3299 if subs:
3300 uipathfn = scmutil.getuipathfn(self)
3300 uipathfn = scmutil.getuipathfn(self)
3301 for s in sorted(commitsubs):
3301 for s in sorted(commitsubs):
3302 sub = wctx.sub(s)
3302 sub = wctx.sub(s)
3303 self.ui.status(
3303 self.ui.status(
3304 _(b'committing subrepository %s\n')
3304 _(b'committing subrepository %s\n')
3305 % uipathfn(subrepoutil.subrelpath(sub))
3305 % uipathfn(subrepoutil.subrelpath(sub))
3306 )
3306 )
3307 sr = sub.commit(cctx._text, user, date)
3307 sr = sub.commit(cctx._text, user, date)
3308 newstate[s] = (newstate[s][0], sr)
3308 newstate[s] = (newstate[s][0], sr)
3309 subrepoutil.writestate(self, newstate)
3309 subrepoutil.writestate(self, newstate)
3310
3310
3311 p1, p2 = self.dirstate.parents()
3311 p1, p2 = self.dirstate.parents()
3312 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'')
3313 try:
3313 try:
3314 self.hook(
3314 self.hook(
3315 b"precommit", throw=True, parent1=hookp1, parent2=hookp2
3315 b"precommit", throw=True, parent1=hookp1, parent2=hookp2
3316 )
3316 )
3317 with self.transaction(b'commit'):
3317 with self.transaction(b'commit'):
3318 ret = self.commitctx(cctx, True)
3318 ret = self.commitctx(cctx, True)
3319 # update bookmarks, dirstate and mergestate
3319 # update bookmarks, dirstate and mergestate
3320 bookmarks.update(self, [p1, p2], ret)
3320 bookmarks.update(self, [p1, p2], ret)
3321 cctx.markcommitted(ret)
3321 cctx.markcommitted(ret)
3322 ms.reset()
3322 ms.reset()
3323 except: # re-raises
3323 except: # re-raises
3324 if edited:
3324 if edited:
3325 self.ui.write(
3325 self.ui.write(
3326 _(b'note: commit message saved in %s\n') % msg_path
3326 _(b'note: commit message saved in %s\n') % msg_path
3327 )
3327 )
3328 self.ui.write(
3328 self.ui.write(
3329 _(
3329 _(
3330 b"note: use 'hg commit --logfile "
3330 b"note: use 'hg commit --logfile "
3331 b"%s --edit' to reuse it\n"
3331 b"%s --edit' to reuse it\n"
3332 )
3332 )
3333 % msg_path
3333 % msg_path
3334 )
3334 )
3335 raise
3335 raise
3336
3336
3337 def commithook(unused_success):
3337 def commithook(unused_success):
3338 # hack for command that use a temporary commit (eg: histedit)
3338 # hack for command that use a temporary commit (eg: histedit)
3339 # temporary commit got stripped before hook release
3339 # temporary commit got stripped before hook release
3340 if self.changelog.hasnode(ret):
3340 if self.changelog.hasnode(ret):
3341 self.hook(
3341 self.hook(
3342 b"commit", node=hex(ret), parent1=hookp1, parent2=hookp2
3342 b"commit", node=hex(ret), parent1=hookp1, parent2=hookp2
3343 )
3343 )
3344
3344
3345 self._afterlock(commithook)
3345 self._afterlock(commithook)
3346 return ret
3346 return ret
3347
3347
3348 @unfilteredmethod
3348 @unfilteredmethod
3349 def commitctx(self, ctx, error=False, origctx=None):
3349 def commitctx(self, ctx, error=False, origctx=None):
3350 return commit.commitctx(self, ctx, error=error, origctx=origctx)
3350 return commit.commitctx(self, ctx, error=error, origctx=origctx)
3351
3351
3352 @unfilteredmethod
3352 @unfilteredmethod
3353 def destroying(self):
3353 def destroying(self):
3354 """Inform the repository that nodes are about to be destroyed.
3354 """Inform the repository that nodes are about to be destroyed.
3355 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
3356 place for anything that has to be done before destroying history.
3356 place for anything that has to be done before destroying history.
3357
3357
3358 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
3359 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
3360 destroyed is imminent, the repo will be invalidated causing those
3360 destroyed is imminent, the repo will be invalidated causing those
3361 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
3362 completely.
3362 completely.
3363 """
3363 """
3364 # 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
3365 # dirty after committing. Then when we strip, the repo is invalidated,
3365 # dirty after committing. Then when we strip, the repo is invalidated,
3366 # causing those changes to disappear.
3366 # causing those changes to disappear.
3367 if '_phasecache' in vars(self):
3367 if '_phasecache' in vars(self):
3368 self._phasecache.write()
3368 self._phasecache.write()
3369
3369
3370 @unfilteredmethod
3370 @unfilteredmethod
3371 def destroyed(self):
3371 def destroyed(self):
3372 """Inform the repository that nodes have been destroyed.
3372 """Inform the repository that nodes have been destroyed.
3373 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
3374 place for anything that has to be done after destroying history.
3374 place for anything that has to be done after destroying history.
3375 """
3375 """
3376 # When one tries to:
3376 # When one tries to:
3377 # 1) destroy nodes thus calling this method (e.g. strip)
3377 # 1) destroy nodes thus calling this method (e.g. strip)
3378 # 2) use phasecache somewhere (e.g. commit)
3378 # 2) use phasecache somewhere (e.g. commit)
3379 #
3379 #
3380 # then 2) will fail because the phasecache contains nodes that were
3380 # then 2) will fail because the phasecache contains nodes that were
3381 # removed. We can either remove phasecache from the filecache,
3381 # removed. We can either remove phasecache from the filecache,
3382 # 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
3383 # the removed nodes now and write the updated cache.
3383 # the removed nodes now and write the updated cache.
3384 self._phasecache.filterunknown(self)
3384 self._phasecache.filterunknown(self)
3385 self._phasecache.write()
3385 self._phasecache.write()
3386
3386
3387 # refresh all repository caches
3387 # refresh all repository caches
3388 self.updatecaches()
3388 self.updatecaches()
3389
3389
3390 # Ensure the persistent tag cache is updated. Doing it now
3390 # Ensure the persistent tag cache is updated. Doing it now
3391 # means that the tag cache only has to worry about destroyed
3391 # means that the tag cache only has to worry about destroyed
3392 # heads immediately after a strip/rollback. That in turn
3392 # heads immediately after a strip/rollback. That in turn
3393 # guarantees that "cachetip == currenttip" (comparing both rev
3393 # guarantees that "cachetip == currenttip" (comparing both rev
3394 # and node) always means no nodes have been added or destroyed.
3394 # and node) always means no nodes have been added or destroyed.
3395
3395
3396 # XXX this is suboptimal when qrefresh'ing: we strip the current
3396 # XXX this is suboptimal when qrefresh'ing: we strip the current
3397 # head, refresh the tag cache, then immediately add a new head.
3397 # head, refresh the tag cache, then immediately add a new head.
3398 # 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
3399 # tag cache retrieval" case to work.
3399 # tag cache retrieval" case to work.
3400 self.invalidate()
3400 self.invalidate()
3401
3401
3402 def status(
3402 def status(
3403 self,
3403 self,
3404 node1=b'.',
3404 node1=b'.',
3405 node2=None,
3405 node2=None,
3406 match=None,
3406 match=None,
3407 ignored=False,
3407 ignored=False,
3408 clean=False,
3408 clean=False,
3409 unknown=False,
3409 unknown=False,
3410 listsubrepos=False,
3410 listsubrepos=False,
3411 ):
3411 ):
3412 '''a convenience method that calls node1.status(node2)'''
3412 '''a convenience method that calls node1.status(node2)'''
3413 return self[node1].status(
3413 return self[node1].status(
3414 node2, match, ignored, clean, unknown, listsubrepos
3414 node2, match, ignored, clean, unknown, listsubrepos
3415 )
3415 )
3416
3416
3417 def addpostdsstatus(self, ps):
3417 def addpostdsstatus(self, ps):
3418 """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
3419 fixups happen.
3419 fixups happen.
3420
3420
3421 On status completion, callback(wctx, status) will be called with the
3421 On status completion, callback(wctx, status) will be called with the
3422 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
3423 couldn't be grabbed.
3423 couldn't be grabbed.
3424
3424
3425 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 --
3426 it might change in the meanwhile. Instead, they should access the
3426 it might change in the meanwhile. Instead, they should access the
3427 dirstate via wctx.repo().dirstate.
3427 dirstate via wctx.repo().dirstate.
3428
3428
3429 This list is emptied out after each status run -- extensions should
3429 This list is emptied out after each status run -- extensions should
3430 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.
3431 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
3432 that don't involve the dirstate.
3432 that don't involve the dirstate.
3433 """
3433 """
3434
3434
3435 # The list is located here for uniqueness reasons -- it is actually
3435 # The list is located here for uniqueness reasons -- it is actually
3436 # managed by the workingctx, but that isn't unique per-repo.
3436 # managed by the workingctx, but that isn't unique per-repo.
3437 self._postdsstatus.append(ps)
3437 self._postdsstatus.append(ps)
3438
3438
3439 def postdsstatus(self):
3439 def postdsstatus(self):
3440 """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."""
3441 return self._postdsstatus
3441 return self._postdsstatus
3442
3442
3443 def clearpostdsstatus(self):
3443 def clearpostdsstatus(self):
3444 """Used by workingctx to clear post-dirstate-status hooks."""
3444 """Used by workingctx to clear post-dirstate-status hooks."""
3445 del self._postdsstatus[:]
3445 del self._postdsstatus[:]
3446
3446
3447 def heads(self, start=None):
3447 def heads(self, start=None):
3448 if start is None:
3448 if start is None:
3449 cl = self.changelog
3449 cl = self.changelog
3450 headrevs = reversed(cl.headrevs())
3450 headrevs = reversed(cl.headrevs())
3451 return [cl.node(rev) for rev in headrevs]
3451 return [cl.node(rev) for rev in headrevs]
3452
3452
3453 heads = self.changelog.heads(start)
3453 heads = self.changelog.heads(start)
3454 # sort the output in rev descending order
3454 # sort the output in rev descending order
3455 return sorted(heads, key=self.changelog.rev, reverse=True)
3455 return sorted(heads, key=self.changelog.rev, reverse=True)
3456
3456
3457 def branchheads(self, branch=None, start=None, closed=False):
3457 def branchheads(self, branch=None, start=None, closed=False):
3458 """return a (possibly filtered) list of heads for the given branch
3458 """return a (possibly filtered) list of heads for the given branch
3459
3459
3460 Heads are returned in topological order, from newest to oldest.
3460 Heads are returned in topological order, from newest to oldest.
3461 If branch is None, use the dirstate branch.
3461 If branch is None, use the dirstate branch.
3462 If start is not None, return only heads reachable from start.
3462 If start is not None, return only heads reachable from start.
3463 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.
3464 """
3464 """
3465 if branch is None:
3465 if branch is None:
3466 branch = self[None].branch()
3466 branch = self[None].branch()
3467 branches = self.branchmap()
3467 branches = self.branchmap()
3468 if not branches.hasbranch(branch):
3468 if not branches.hasbranch(branch):
3469 return []
3469 return []
3470 # the cache returns heads ordered lowest to highest
3470 # the cache returns heads ordered lowest to highest
3471 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
3471 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
3472 if start is not None:
3472 if start is not None:
3473 # filter out the heads that cannot be reached from startrev
3473 # filter out the heads that cannot be reached from startrev
3474 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
3474 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
3475 bheads = [h for h in bheads if h in fbheads]
3475 bheads = [h for h in bheads if h in fbheads]
3476 return bheads
3476 return bheads
3477
3477
3478 def branches(self, nodes):
3478 def branches(self, nodes):
3479 if not nodes:
3479 if not nodes:
3480 nodes = [self.changelog.tip()]
3480 nodes = [self.changelog.tip()]
3481 b = []
3481 b = []
3482 for n in nodes:
3482 for n in nodes:
3483 t = n
3483 t = n
3484 while True:
3484 while True:
3485 p = self.changelog.parents(n)
3485 p = self.changelog.parents(n)
3486 if p[1] != self.nullid or p[0] == self.nullid:
3486 if p[1] != self.nullid or p[0] == self.nullid:
3487 b.append((t, n, p[0], p[1]))
3487 b.append((t, n, p[0], p[1]))
3488 break
3488 break
3489 n = p[0]
3489 n = p[0]
3490 return b
3490 return b
3491
3491
3492 def between(self, pairs):
3492 def between(self, pairs):
3493 r = []
3493 r = []
3494
3494
3495 for top, bottom in pairs:
3495 for top, bottom in pairs:
3496 n, l, i = top, [], 0
3496 n, l, i = top, [], 0
3497 f = 1
3497 f = 1
3498
3498
3499 while n != bottom and n != self.nullid:
3499 while n != bottom and n != self.nullid:
3500 p = self.changelog.parents(n)[0]
3500 p = self.changelog.parents(n)[0]
3501 if i == f:
3501 if i == f:
3502 l.append(n)
3502 l.append(n)
3503 f = f * 2
3503 f = f * 2
3504 n = p
3504 n = p
3505 i += 1
3505 i += 1
3506
3506
3507 r.append(l)
3507 r.append(l)
3508
3508
3509 return r
3509 return r
3510
3510
3511 def checkpush(self, pushop):
3511 def checkpush(self, pushop):
3512 """Extensions can override this function if additional checks have
3512 """Extensions can override this function if additional checks have
3513 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
3514 command.
3514 command.
3515 """
3515 """
3516
3516
3517 @unfilteredpropertycache
3517 @unfilteredpropertycache
3518 def prepushoutgoinghooks(self):
3518 def prepushoutgoinghooks(self):
3519 """Return util.hooks consists of a pushop with repo, remote, outgoing
3519 """Return util.hooks consists of a pushop with repo, remote, outgoing
3520 methods, which are called before pushing changesets.
3520 methods, which are called before pushing changesets.
3521 """
3521 """
3522 return util.hooks()
3522 return util.hooks()
3523
3523
3524 def pushkey(self, namespace, key, old, new):
3524 def pushkey(self, namespace, key, old, new):
3525 try:
3525 try:
3526 tr = self.currenttransaction()
3526 tr = self.currenttransaction()
3527 hookargs = {}
3527 hookargs = {}
3528 if tr is not None:
3528 if tr is not None:
3529 hookargs.update(tr.hookargs)
3529 hookargs.update(tr.hookargs)
3530 hookargs = pycompat.strkwargs(hookargs)
3530 hookargs = pycompat.strkwargs(hookargs)
3531 hookargs['namespace'] = namespace
3531 hookargs['namespace'] = namespace
3532 hookargs['key'] = key
3532 hookargs['key'] = key
3533 hookargs['old'] = old
3533 hookargs['old'] = old
3534 hookargs['new'] = new
3534 hookargs['new'] = new
3535 self.hook(b'prepushkey', throw=True, **hookargs)
3535 self.hook(b'prepushkey', throw=True, **hookargs)
3536 except error.HookAbort as exc:
3536 except error.HookAbort as exc:
3537 self.ui.write_err(_(b"pushkey-abort: %s\n") % exc)
3537 self.ui.write_err(_(b"pushkey-abort: %s\n") % exc)
3538 if exc.hint:
3538 if exc.hint:
3539 self.ui.write_err(_(b"(%s)\n") % exc.hint)
3539 self.ui.write_err(_(b"(%s)\n") % exc.hint)
3540 return False
3540 return False
3541 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))
3542 ret = pushkey.push(self, namespace, key, old, new)
3542 ret = pushkey.push(self, namespace, key, old, new)
3543
3543
3544 def runhook(unused_success):
3544 def runhook(unused_success):
3545 self.hook(
3545 self.hook(
3546 b'pushkey',
3546 b'pushkey',
3547 namespace=namespace,
3547 namespace=namespace,
3548 key=key,
3548 key=key,
3549 old=old,
3549 old=old,
3550 new=new,
3550 new=new,
3551 ret=ret,
3551 ret=ret,
3552 )
3552 )
3553
3553
3554 self._afterlock(runhook)
3554 self._afterlock(runhook)
3555 return ret
3555 return ret
3556
3556
3557 def listkeys(self, namespace):
3557 def listkeys(self, namespace):
3558 self.hook(b'prelistkeys', throw=True, namespace=namespace)
3558 self.hook(b'prelistkeys', throw=True, namespace=namespace)
3559 self.ui.debug(b'listing keys for "%s"\n' % namespace)
3559 self.ui.debug(b'listing keys for "%s"\n' % namespace)
3560 values = pushkey.list(self, namespace)
3560 values = pushkey.list(self, namespace)
3561 self.hook(b'listkeys', namespace=namespace, values=values)
3561 self.hook(b'listkeys', namespace=namespace, values=values)
3562 return values
3562 return values
3563
3563
3564 def debugwireargs(self, one, two, three=None, four=None, five=None):
3564 def debugwireargs(self, one, two, three=None, four=None, five=None):
3565 '''used to test argument passing over the wire'''
3565 '''used to test argument passing over the wire'''
3566 return b"%s %s %s %s %s" % (
3566 return b"%s %s %s %s %s" % (
3567 one,
3567 one,
3568 two,
3568 two,
3569 pycompat.bytestr(three),
3569 pycompat.bytestr(three),
3570 pycompat.bytestr(four),
3570 pycompat.bytestr(four),
3571 pycompat.bytestr(five),
3571 pycompat.bytestr(five),
3572 )
3572 )
3573
3573
3574 def savecommitmessage(self, text):
3574 def savecommitmessage(self, text):
3575 fp = self.vfs(b'last-message.txt', b'wb')
3575 fp = self.vfs(b'last-message.txt', b'wb')
3576 try:
3576 try:
3577 fp.write(text)
3577 fp.write(text)
3578 finally:
3578 finally:
3579 fp.close()
3579 fp.close()
3580 return self.pathto(fp.name[len(self.root) + 1 :])
3580 return self.pathto(fp.name[len(self.root) + 1 :])
3581
3581
3582 def register_wanted_sidedata(self, category):
3582 def register_wanted_sidedata(self, category):
3583 if repository.REPO_FEATURE_SIDE_DATA not in self.features:
3583 if repository.REPO_FEATURE_SIDE_DATA not in self.features:
3584 # Only revlogv2 repos can want sidedata.
3584 # Only revlogv2 repos can want sidedata.
3585 return
3585 return
3586 self._wanted_sidedata.add(pycompat.bytestr(category))
3586 self._wanted_sidedata.add(pycompat.bytestr(category))
3587
3587
3588 def register_sidedata_computer(
3588 def register_sidedata_computer(
3589 self, kind, category, keys, computer, flags, replace=False
3589 self, kind, category, keys, computer, flags, replace=False
3590 ):
3590 ):
3591 if kind not in revlogconst.ALL_KINDS:
3591 if kind not in revlogconst.ALL_KINDS:
3592 msg = _(b"unexpected revlog kind '%s'.")
3592 msg = _(b"unexpected revlog kind '%s'.")
3593 raise error.ProgrammingError(msg % kind)
3593 raise error.ProgrammingError(msg % kind)
3594 category = pycompat.bytestr(category)
3594 category = pycompat.bytestr(category)
3595 already_registered = category in self._sidedata_computers.get(kind, [])
3595 already_registered = category in self._sidedata_computers.get(kind, [])
3596 if already_registered and not replace:
3596 if already_registered and not replace:
3597 msg = _(
3597 msg = _(
3598 b"cannot register a sidedata computer twice for category '%s'."
3598 b"cannot register a sidedata computer twice for category '%s'."
3599 )
3599 )
3600 raise error.ProgrammingError(msg % category)
3600 raise error.ProgrammingError(msg % category)
3601 if replace and not already_registered:
3601 if replace and not already_registered:
3602 msg = _(
3602 msg = _(
3603 b"cannot replace a sidedata computer that isn't registered "
3603 b"cannot replace a sidedata computer that isn't registered "
3604 b"for category '%s'."
3604 b"for category '%s'."
3605 )
3605 )
3606 raise error.ProgrammingError(msg % category)
3606 raise error.ProgrammingError(msg % category)
3607 self._sidedata_computers.setdefault(kind, {})
3607 self._sidedata_computers.setdefault(kind, {})
3608 self._sidedata_computers[kind][category] = (keys, computer, flags)
3608 self._sidedata_computers[kind][category] = (keys, computer, flags)
3609
3609
3610
3610
3611 def undoname(fn: bytes) -> bytes:
3611 def undoname(fn: bytes) -> bytes:
3612 base, name = os.path.split(fn)
3612 base, name = os.path.split(fn)
3613 assert name.startswith(b'journal')
3613 assert name.startswith(b'journal')
3614 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))
3615
3615
3616
3616
3617 def instance(ui, path: bytes, create, intents=None, createopts=None):
3617 def instance(ui, path: bytes, create, intents=None, createopts=None):
3618 # prevent cyclic import localrepo -> upgrade -> localrepo
3618 # prevent cyclic import localrepo -> upgrade -> localrepo
3619 from . import upgrade
3619 from . import upgrade
3620
3620
3621 localpath = urlutil.urllocalpath(path)
3621 localpath = urlutil.urllocalpath(path)
3622 if create:
3622 if create:
3623 createrepository(ui, localpath, createopts=createopts)
3623 createrepository(ui, localpath, createopts=createopts)
3624
3624
3625 def repo_maker():
3625 def repo_maker():
3626 return makelocalrepository(ui, localpath, intents=intents)
3626 return makelocalrepository(ui, localpath, intents=intents)
3627
3627
3628 repo = repo_maker()
3628 repo = repo_maker()
3629 repo = upgrade.may_auto_upgrade(repo, repo_maker)
3629 repo = upgrade.may_auto_upgrade(repo, repo_maker)
3630 return repo
3630 return repo
3631
3631
3632
3632
3633 def islocal(path: bytes) -> bool:
3633 def islocal(path: bytes) -> bool:
3634 return True
3634 return True
3635
3635
3636
3636
3637 def defaultcreateopts(ui, createopts=None):
3637 def defaultcreateopts(ui, createopts=None):
3638 """Populate the default creation options for a repository.
3638 """Populate the default creation options for a repository.
3639
3639
3640 A dictionary of explicitly requested creation options can be passed
3640 A dictionary of explicitly requested creation options can be passed
3641 in. Missing keys will be populated.
3641 in. Missing keys will be populated.
3642 """
3642 """
3643 createopts = dict(createopts or {})
3643 createopts = dict(createopts or {})
3644
3644
3645 if b'backend' not in createopts:
3645 if b'backend' not in createopts:
3646 # experimental config: storage.new-repo-backend
3646 # experimental config: storage.new-repo-backend
3647 createopts[b'backend'] = ui.config(b'storage', b'new-repo-backend')
3647 createopts[b'backend'] = ui.config(b'storage', b'new-repo-backend')
3648
3648
3649 return createopts
3649 return createopts
3650
3650
3651
3651
3652 def clone_requirements(ui, createopts, srcrepo):
3652 def clone_requirements(ui, createopts, srcrepo):
3653 """clone the requirements of a local repo for a local clone
3653 """clone the requirements of a local repo for a local clone
3654
3654
3655 The store requirements are unchanged while the working copy requirements
3655 The store requirements are unchanged while the working copy requirements
3656 depends on the configuration
3656 depends on the configuration
3657 """
3657 """
3658 target_requirements = set()
3658 target_requirements = set()
3659 if not srcrepo.requirements:
3659 if not srcrepo.requirements:
3660 # 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
3661 # with it.
3661 # with it.
3662 return target_requirements
3662 return target_requirements
3663 createopts = defaultcreateopts(ui, createopts=createopts)
3663 createopts = defaultcreateopts(ui, createopts=createopts)
3664 for r in newreporequirements(ui, createopts):
3664 for r in newreporequirements(ui, createopts):
3665 if r in requirementsmod.WORKING_DIR_REQUIREMENTS:
3665 if r in requirementsmod.WORKING_DIR_REQUIREMENTS:
3666 target_requirements.add(r)
3666 target_requirements.add(r)
3667
3667
3668 for r in srcrepo.requirements:
3668 for r in srcrepo.requirements:
3669 if r not in requirementsmod.WORKING_DIR_REQUIREMENTS:
3669 if r not in requirementsmod.WORKING_DIR_REQUIREMENTS:
3670 target_requirements.add(r)
3670 target_requirements.add(r)
3671 return target_requirements
3671 return target_requirements
3672
3672
3673
3673
3674 def newreporequirements(ui, createopts):
3674 def newreporequirements(ui, createopts):
3675 """Determine the set of requirements for a new local repository.
3675 """Determine the set of requirements for a new local repository.
3676
3676
3677 Extensions can wrap this function to specify custom requirements for
3677 Extensions can wrap this function to specify custom requirements for
3678 new repositories.
3678 new repositories.
3679 """
3679 """
3680
3680
3681 if b'backend' not in createopts:
3681 if b'backend' not in createopts:
3682 raise error.ProgrammingError(
3682 raise error.ProgrammingError(
3683 b'backend key not present in createopts; '
3683 b'backend key not present in createopts; '
3684 b'was defaultcreateopts() called?'
3684 b'was defaultcreateopts() called?'
3685 )
3685 )
3686
3686
3687 if createopts[b'backend'] != b'revlogv1':
3687 if createopts[b'backend'] != b'revlogv1':
3688 raise error.Abort(
3688 raise error.Abort(
3689 _(
3689 _(
3690 b'unable to determine repository requirements for '
3690 b'unable to determine repository requirements for '
3691 b'storage backend: %s'
3691 b'storage backend: %s'
3692 )
3692 )
3693 % createopts[b'backend']
3693 % createopts[b'backend']
3694 )
3694 )
3695
3695
3696 requirements = {requirementsmod.REVLOGV1_REQUIREMENT}
3696 requirements = {requirementsmod.REVLOGV1_REQUIREMENT}
3697 if ui.configbool(b'format', b'usestore'):
3697 if ui.configbool(b'format', b'usestore'):
3698 requirements.add(requirementsmod.STORE_REQUIREMENT)
3698 requirements.add(requirementsmod.STORE_REQUIREMENT)
3699 if ui.configbool(b'format', b'usefncache'):
3699 if ui.configbool(b'format', b'usefncache'):
3700 requirements.add(requirementsmod.FNCACHE_REQUIREMENT)
3700 requirements.add(requirementsmod.FNCACHE_REQUIREMENT)
3701 if ui.configbool(b'format', b'dotencode'):
3701 if ui.configbool(b'format', b'dotencode'):
3702 requirements.add(requirementsmod.DOTENCODE_REQUIREMENT)
3702 requirements.add(requirementsmod.DOTENCODE_REQUIREMENT)
3703
3703
3704 compengines = ui.configlist(b'format', b'revlog-compression')
3704 compengines = ui.configlist(b'format', b'revlog-compression')
3705 for compengine in compengines:
3705 for compengine in compengines:
3706 if compengine in util.compengines:
3706 if compengine in util.compengines:
3707 engine = util.compengines[compengine]
3707 engine = util.compengines[compengine]
3708 if engine.available() and engine.revlogheader():
3708 if engine.available() and engine.revlogheader():
3709 break
3709 break
3710 else:
3710 else:
3711 raise error.Abort(
3711 raise error.Abort(
3712 _(
3712 _(
3713 b'compression engines %s defined by '
3713 b'compression engines %s defined by '
3714 b'format.revlog-compression not available'
3714 b'format.revlog-compression not available'
3715 )
3715 )
3716 % b', '.join(b'"%s"' % e for e in compengines),
3716 % b', '.join(b'"%s"' % e for e in compengines),
3717 hint=_(
3717 hint=_(
3718 b'run "hg debuginstall" to list available '
3718 b'run "hg debuginstall" to list available '
3719 b'compression engines'
3719 b'compression engines'
3720 ),
3720 ),
3721 )
3721 )
3722
3722
3723 # 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.
3724 if compengine == b'zstd':
3724 if compengine == b'zstd':
3725 requirements.add(b'revlog-compression-zstd')
3725 requirements.add(b'revlog-compression-zstd')
3726 elif compengine != b'zlib':
3726 elif compengine != b'zlib':
3727 requirements.add(b'exp-compression-%s' % compengine)
3727 requirements.add(b'exp-compression-%s' % compengine)
3728
3728
3729 if scmutil.gdinitconfig(ui):
3729 if scmutil.gdinitconfig(ui):
3730 requirements.add(requirementsmod.GENERALDELTA_REQUIREMENT)
3730 requirements.add(requirementsmod.GENERALDELTA_REQUIREMENT)
3731 if ui.configbool(b'format', b'sparse-revlog'):
3731 if ui.configbool(b'format', b'sparse-revlog'):
3732 requirements.add(requirementsmod.SPARSEREVLOG_REQUIREMENT)
3732 requirements.add(requirementsmod.SPARSEREVLOG_REQUIREMENT)
3733
3733
3734 # experimental config: format.use-dirstate-v2
3734 # experimental config: format.use-dirstate-v2
3735 # 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`
3736 if ui.configbool(b'format', b'use-dirstate-v2'):
3736 if ui.configbool(b'format', b'use-dirstate-v2'):
3737 requirements.add(requirementsmod.DIRSTATE_V2_REQUIREMENT)
3737 requirements.add(requirementsmod.DIRSTATE_V2_REQUIREMENT)
3738
3738
3739 # experimental config: format.exp-use-copies-side-data-changeset
3739 # experimental config: format.exp-use-copies-side-data-changeset
3740 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'):
3741 requirements.add(requirementsmod.CHANGELOGV2_REQUIREMENT)
3741 requirements.add(requirementsmod.CHANGELOGV2_REQUIREMENT)
3742 requirements.add(requirementsmod.COPIESSDC_REQUIREMENT)
3742 requirements.add(requirementsmod.COPIESSDC_REQUIREMENT)
3743 if ui.configbool(b'experimental', b'treemanifest'):
3743 if ui.configbool(b'experimental', b'treemanifest'):
3744 requirements.add(requirementsmod.TREEMANIFEST_REQUIREMENT)
3744 requirements.add(requirementsmod.TREEMANIFEST_REQUIREMENT)
3745
3745
3746 changelogv2 = ui.config(b'format', b'exp-use-changelog-v2')
3746 changelogv2 = ui.config(b'format', b'exp-use-changelog-v2')
3747 if changelogv2 == b'enable-unstable-format-and-corrupt-my-data':
3747 if changelogv2 == b'enable-unstable-format-and-corrupt-my-data':
3748 requirements.add(requirementsmod.CHANGELOGV2_REQUIREMENT)
3748 requirements.add(requirementsmod.CHANGELOGV2_REQUIREMENT)
3749
3749
3750 revlogv2 = ui.config(b'experimental', b'revlogv2')
3750 revlogv2 = ui.config(b'experimental', b'revlogv2')
3751 if revlogv2 == b'enable-unstable-format-and-corrupt-my-data':
3751 if revlogv2 == b'enable-unstable-format-and-corrupt-my-data':
3752 requirements.discard(requirementsmod.REVLOGV1_REQUIREMENT)
3752 requirements.discard(requirementsmod.REVLOGV1_REQUIREMENT)
3753 requirements.add(requirementsmod.REVLOGV2_REQUIREMENT)
3753 requirements.add(requirementsmod.REVLOGV2_REQUIREMENT)
3754 # experimental config: format.internal-phase
3754 # experimental config: format.internal-phase
3755 if ui.configbool(b'format', b'use-internal-phase'):
3755 if ui.configbool(b'format', b'use-internal-phase'):
3756 requirements.add(requirementsmod.INTERNAL_PHASE_REQUIREMENT)
3756 requirements.add(requirementsmod.INTERNAL_PHASE_REQUIREMENT)
3757
3757
3758 # experimental config: format.exp-archived-phase
3758 # experimental config: format.exp-archived-phase
3759 if ui.configbool(b'format', b'exp-archived-phase'):
3759 if ui.configbool(b'format', b'exp-archived-phase'):
3760 requirements.add(requirementsmod.ARCHIVED_PHASE_REQUIREMENT)
3760 requirements.add(requirementsmod.ARCHIVED_PHASE_REQUIREMENT)
3761
3761
3762 if createopts.get(b'narrowfiles'):
3762 if createopts.get(b'narrowfiles'):
3763 requirements.add(requirementsmod.NARROW_REQUIREMENT)
3763 requirements.add(requirementsmod.NARROW_REQUIREMENT)
3764
3764
3765 if createopts.get(b'lfs'):
3765 if createopts.get(b'lfs'):
3766 requirements.add(b'lfs')
3766 requirements.add(b'lfs')
3767
3767
3768 if ui.configbool(b'format', b'bookmarks-in-store'):
3768 if ui.configbool(b'format', b'bookmarks-in-store'):
3769 requirements.add(requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT)
3769 requirements.add(requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT)
3770
3770
3771 # The feature is disabled unless a fast implementation is available.
3771 # The feature is disabled unless a fast implementation is available.
3772 persistent_nodemap_default = policy.importrust('revlog') is not None
3772 persistent_nodemap_default = policy.importrust('revlog') is not None
3773 if ui.configbool(
3773 if ui.configbool(
3774 b'format', b'use-persistent-nodemap', persistent_nodemap_default
3774 b'format', b'use-persistent-nodemap', persistent_nodemap_default
3775 ):
3775 ):
3776 requirements.add(requirementsmod.NODEMAP_REQUIREMENT)
3776 requirements.add(requirementsmod.NODEMAP_REQUIREMENT)
3777
3777
3778 # 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
3779 # requirement
3779 # requirement
3780 if ui.configbool(b'format', b'use-share-safe'):
3780 if ui.configbool(b'format', b'use-share-safe'):
3781 requirements.add(requirementsmod.SHARESAFE_REQUIREMENT)
3781 requirements.add(requirementsmod.SHARESAFE_REQUIREMENT)
3782
3782
3783 # 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
3784 # differently.
3784 # differently.
3785 #
3785 #
3786 # [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
3787 # working copy)
3787 # working copy)
3788 if b'sharedrepo' in createopts:
3788 if b'sharedrepo' in createopts:
3789 source_requirements = set(createopts[b'sharedrepo'].requirements)
3789 source_requirements = set(createopts[b'sharedrepo'].requirements)
3790
3790
3791 if requirementsmod.SHARESAFE_REQUIREMENT not in source_requirements:
3791 if requirementsmod.SHARESAFE_REQUIREMENT not in source_requirements:
3792 # share to an old school repository, we have to copy the
3792 # share to an old school repository, we have to copy the
3793 # requirements and hope for the best.
3793 # requirements and hope for the best.
3794 requirements = source_requirements
3794 requirements = source_requirements
3795 else:
3795 else:
3796 # 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
3797 # working copy part over, ignoring previous logic.
3797 # working copy part over, ignoring previous logic.
3798 to_drop = set()
3798 to_drop = set()
3799 for req in requirements:
3799 for req in requirements:
3800 if req in requirementsmod.WORKING_DIR_REQUIREMENTS:
3800 if req in requirementsmod.WORKING_DIR_REQUIREMENTS:
3801 continue
3801 continue
3802 if req in source_requirements:
3802 if req in source_requirements:
3803 continue
3803 continue
3804 to_drop.add(req)
3804 to_drop.add(req)
3805 requirements -= to_drop
3805 requirements -= to_drop
3806 requirements |= source_requirements
3806 requirements |= source_requirements
3807
3807
3808 if createopts.get(b'sharedrelative'):
3808 if createopts.get(b'sharedrelative'):
3809 requirements.add(requirementsmod.RELATIVE_SHARED_REQUIREMENT)
3809 requirements.add(requirementsmod.RELATIVE_SHARED_REQUIREMENT)
3810 else:
3810 else:
3811 requirements.add(requirementsmod.SHARED_REQUIREMENT)
3811 requirements.add(requirementsmod.SHARED_REQUIREMENT)
3812
3812
3813 if ui.configbool(b'format', b'use-dirstate-tracked-hint'):
3813 if ui.configbool(b'format', b'use-dirstate-tracked-hint'):
3814 version = ui.configint(b'format', b'use-dirstate-tracked-hint.version')
3814 version = ui.configint(b'format', b'use-dirstate-tracked-hint.version')
3815 msg = _(b"ignoring unknown tracked key version: %d\n")
3815 msg = _(b"ignoring unknown tracked key version: %d\n")
3816 hint = _(
3816 hint = _(
3817 b"see `hg help config.format.use-dirstate-tracked-hint-version"
3817 b"see `hg help config.format.use-dirstate-tracked-hint-version"
3818 )
3818 )
3819 if version != 1:
3819 if version != 1:
3820 ui.warn(msg % version, hint=hint)
3820 ui.warn(msg % version, hint=hint)
3821 else:
3821 else:
3822 requirements.add(requirementsmod.DIRSTATE_TRACKED_HINT_V1)
3822 requirements.add(requirementsmod.DIRSTATE_TRACKED_HINT_V1)
3823
3823
3824 return requirements
3824 return requirements
3825
3825
3826
3826
3827 def checkrequirementscompat(ui, requirements):
3827 def checkrequirementscompat(ui, requirements):
3828 """Checks compatibility of repository requirements enabled and disabled.
3828 """Checks compatibility of repository requirements enabled and disabled.
3829
3829
3830 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
3831 requirements are not enabled. Also warns users about it"""
3831 requirements are not enabled. Also warns users about it"""
3832
3832
3833 dropped = set()
3833 dropped = set()
3834
3834
3835 if requirementsmod.STORE_REQUIREMENT not in requirements:
3835 if requirementsmod.STORE_REQUIREMENT not in requirements:
3836 if requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT in requirements:
3836 if requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT in requirements:
3837 ui.warn(
3837 ui.warn(
3838 _(
3838 _(
3839 b'ignoring enabled \'format.bookmarks-in-store\' config '
3839 b'ignoring enabled \'format.bookmarks-in-store\' config '
3840 b'beacuse it is incompatible with disabled '
3840 b'beacuse it is incompatible with disabled '
3841 b'\'format.usestore\' config\n'
3841 b'\'format.usestore\' config\n'
3842 )
3842 )
3843 )
3843 )
3844 dropped.add(requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT)
3844 dropped.add(requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT)
3845
3845
3846 if (
3846 if (
3847 requirementsmod.SHARED_REQUIREMENT in requirements
3847 requirementsmod.SHARED_REQUIREMENT in requirements
3848 or requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements
3848 or requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements
3849 ):
3849 ):
3850 raise error.Abort(
3850 raise error.Abort(
3851 _(
3851 _(
3852 b"cannot create shared repository as source was created"
3852 b"cannot create shared repository as source was created"
3853 b" with 'format.usestore' config disabled"
3853 b" with 'format.usestore' config disabled"
3854 )
3854 )
3855 )
3855 )
3856
3856
3857 if requirementsmod.SHARESAFE_REQUIREMENT in requirements:
3857 if requirementsmod.SHARESAFE_REQUIREMENT in requirements:
3858 if ui.hasconfig(b'format', b'use-share-safe'):
3858 if ui.hasconfig(b'format', b'use-share-safe'):
3859 msg = _(
3859 msg = _(
3860 b"ignoring enabled 'format.use-share-safe' config because "
3860 b"ignoring enabled 'format.use-share-safe' config because "
3861 b"it is incompatible with disabled 'format.usestore'"
3861 b"it is incompatible with disabled 'format.usestore'"
3862 b" config\n"
3862 b" config\n"
3863 )
3863 )
3864 ui.warn(msg)
3864 ui.warn(msg)
3865 dropped.add(requirementsmod.SHARESAFE_REQUIREMENT)
3865 dropped.add(requirementsmod.SHARESAFE_REQUIREMENT)
3866
3866
3867 return dropped
3867 return dropped
3868
3868
3869
3869
3870 def filterknowncreateopts(ui, createopts):
3870 def filterknowncreateopts(ui, createopts):
3871 """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.
3872
3872
3873 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
3874 options that we don't know how to handle.
3874 options that we don't know how to handle.
3875
3875
3876 This function is called as part of repository creation. If the
3876 This function is called as part of repository creation. If the
3877 returned dict contains any items, repository creation will not
3877 returned dict contains any items, repository creation will not
3878 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
3879 with options not recognized by loaded code.
3879 with options not recognized by loaded code.
3880
3880
3881 Extensions can wrap this function to filter out creation options
3881 Extensions can wrap this function to filter out creation options
3882 they know how to handle.
3882 they know how to handle.
3883 """
3883 """
3884 known = {
3884 known = {
3885 b'backend',
3885 b'backend',
3886 b'lfs',
3886 b'lfs',
3887 b'narrowfiles',
3887 b'narrowfiles',
3888 b'sharedrepo',
3888 b'sharedrepo',
3889 b'sharedrelative',
3889 b'sharedrelative',
3890 b'shareditems',
3890 b'shareditems',
3891 b'shallowfilestore',
3891 b'shallowfilestore',
3892 }
3892 }
3893
3893
3894 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}
3895
3895
3896
3896
3897 def createrepository(ui, path: bytes, createopts=None, requirements=None):
3897 def createrepository(ui, path: bytes, createopts=None, requirements=None):
3898 """Create a new repository in a vfs.
3898 """Create a new repository in a vfs.
3899
3899
3900 ``path`` path to the new repo's working directory.
3900 ``path`` path to the new repo's working directory.
3901 ``createopts`` options for the new repository.
3901 ``createopts`` options for the new repository.
3902 ``requirement`` predefined set of requirements.
3902 ``requirement`` predefined set of requirements.
3903 (incompatible with ``createopts``)
3903 (incompatible with ``createopts``)
3904
3904
3905 The following keys for ``createopts`` are recognized:
3905 The following keys for ``createopts`` are recognized:
3906
3906
3907 backend
3907 backend
3908 The storage backend to use.
3908 The storage backend to use.
3909 lfs
3909 lfs
3910 Repository will be created with ``lfs`` requirement. The lfs extension
3910 Repository will be created with ``lfs`` requirement. The lfs extension
3911 will automatically be loaded when the repository is accessed.
3911 will automatically be loaded when the repository is accessed.
3912 narrowfiles
3912 narrowfiles
3913 Set up repository to support narrow file storage.
3913 Set up repository to support narrow file storage.
3914 sharedrepo
3914 sharedrepo
3915 Repository object from which storage should be shared.
3915 Repository object from which storage should be shared.
3916 sharedrelative
3916 sharedrelative
3917 Boolean indicating if the path to the shared repo should be
3917 Boolean indicating if the path to the shared repo should be
3918 stored as relative. By default, the pointer to the "parent" repo
3918 stored as relative. By default, the pointer to the "parent" repo
3919 is stored as an absolute path.
3919 is stored as an absolute path.
3920 shareditems
3920 shareditems
3921 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).
3922 shallowfilestore
3922 shallowfilestore
3923 Indicates that storage for files should be shallow (not all ancestor
3923 Indicates that storage for files should be shallow (not all ancestor
3924 revisions are known).
3924 revisions are known).
3925 """
3925 """
3926
3926
3927 if requirements is not None:
3927 if requirements is not None:
3928 if createopts is not None:
3928 if createopts is not None:
3929 msg = b'cannot specify both createopts and requirements'
3929 msg = b'cannot specify both createopts and requirements'
3930 raise error.ProgrammingError(msg)
3930 raise error.ProgrammingError(msg)
3931 createopts = {}
3931 createopts = {}
3932 else:
3932 else:
3933 createopts = defaultcreateopts(ui, createopts=createopts)
3933 createopts = defaultcreateopts(ui, createopts=createopts)
3934
3934
3935 unknownopts = filterknowncreateopts(ui, createopts)
3935 unknownopts = filterknowncreateopts(ui, createopts)
3936
3936
3937 if not isinstance(unknownopts, dict):
3937 if not isinstance(unknownopts, dict):
3938 raise error.ProgrammingError(
3938 raise error.ProgrammingError(
3939 b'filterknowncreateopts() did not return a dict'
3939 b'filterknowncreateopts() did not return a dict'
3940 )
3940 )
3941
3941
3942 if unknownopts:
3942 if unknownopts:
3943 raise error.Abort(
3943 raise error.Abort(
3944 _(
3944 _(
3945 b'unable to create repository because of unknown '
3945 b'unable to create repository because of unknown '
3946 b'creation option: %s'
3946 b'creation option: %s'
3947 )
3947 )
3948 % b', '.join(sorted(unknownopts)),
3948 % b', '.join(sorted(unknownopts)),
3949 hint=_(b'is a required extension not loaded?'),
3949 hint=_(b'is a required extension not loaded?'),
3950 )
3950 )
3951
3951
3952 requirements = newreporequirements(ui, createopts=createopts)
3952 requirements = newreporequirements(ui, createopts=createopts)
3953 requirements -= checkrequirementscompat(ui, requirements)
3953 requirements -= checkrequirementscompat(ui, requirements)
3954
3954
3955 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
3955 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
3956
3956
3957 hgvfs = vfsmod.vfs(wdirvfs.join(b'.hg'))
3957 hgvfs = vfsmod.vfs(wdirvfs.join(b'.hg'))
3958 if hgvfs.exists():
3958 if hgvfs.exists():
3959 raise error.RepoError(_(b'repository %s already exists') % path)
3959 raise error.RepoError(_(b'repository %s already exists') % path)
3960
3960
3961 if b'sharedrepo' in createopts:
3961 if b'sharedrepo' in createopts:
3962 sharedpath = createopts[b'sharedrepo'].sharedpath
3962 sharedpath = createopts[b'sharedrepo'].sharedpath
3963
3963
3964 if createopts.get(b'sharedrelative'):
3964 if createopts.get(b'sharedrelative'):
3965 try:
3965 try:
3966 sharedpath = os.path.relpath(sharedpath, hgvfs.base)
3966 sharedpath = os.path.relpath(sharedpath, hgvfs.base)
3967 sharedpath = util.pconvert(sharedpath)
3967 sharedpath = util.pconvert(sharedpath)
3968 except (IOError, ValueError) as e:
3968 except (IOError, ValueError) as e:
3969 # ValueError is raised on Windows if the drive letters differ
3969 # ValueError is raised on Windows if the drive letters differ
3970 # on each path.
3970 # on each path.
3971 raise error.Abort(
3971 raise error.Abort(
3972 _(b'cannot calculate relative path'),
3972 _(b'cannot calculate relative path'),
3973 hint=stringutil.forcebytestr(e),
3973 hint=stringutil.forcebytestr(e),
3974 )
3974 )
3975
3975
3976 if not wdirvfs.exists():
3976 if not wdirvfs.exists():
3977 wdirvfs.makedirs()
3977 wdirvfs.makedirs()
3978
3978
3979 hgvfs.makedir(notindexed=True)
3979 hgvfs.makedir(notindexed=True)
3980 if b'sharedrepo' not in createopts:
3980 if b'sharedrepo' not in createopts:
3981 hgvfs.mkdir(b'cache')
3981 hgvfs.mkdir(b'cache')
3982 hgvfs.mkdir(b'wcache')
3982 hgvfs.mkdir(b'wcache')
3983
3983
3984 has_store = requirementsmod.STORE_REQUIREMENT in requirements
3984 has_store = requirementsmod.STORE_REQUIREMENT in requirements
3985 if has_store and b'sharedrepo' not in createopts:
3985 if has_store and b'sharedrepo' not in createopts:
3986 hgvfs.mkdir(b'store')
3986 hgvfs.mkdir(b'store')
3987
3987
3988 # We create an invalid changelog outside the store so very old
3988 # We create an invalid changelog outside the store so very old
3989 # Mercurial versions (which didn't know about the requirements
3989 # Mercurial versions (which didn't know about the requirements
3990 # file) encounter an error on reading the changelog. This
3990 # file) encounter an error on reading the changelog. This
3991 # effectively locks out old clients and prevents them from
3991 # effectively locks out old clients and prevents them from
3992 # mucking with a repo in an unknown format.
3992 # mucking with a repo in an unknown format.
3993 #
3993 #
3994 # 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
3995 # such old clients.
3995 # such old clients.
3996 hgvfs.append(
3996 hgvfs.append(
3997 b'00changelog.i',
3997 b'00changelog.i',
3998 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 '
3999 b'layout',
3999 b'layout',
4000 )
4000 )
4001
4001
4002 # Filter the requirements into working copy and store ones
4002 # Filter the requirements into working copy and store ones
4003 wcreq, storereq = scmutil.filterrequirements(requirements)
4003 wcreq, storereq = scmutil.filterrequirements(requirements)
4004 # write working copy ones
4004 # write working copy ones
4005 scmutil.writerequires(hgvfs, wcreq)
4005 scmutil.writerequires(hgvfs, wcreq)
4006 # If there are store requirements and the current repository
4006 # If there are store requirements and the current repository
4007 # is not a shared one, write stored requirements
4007 # is not a shared one, write stored requirements
4008 # 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
4009 # requirements as they are already present in store requires
4009 # requirements as they are already present in store requires
4010 if storereq and b'sharedrepo' not in createopts:
4010 if storereq and b'sharedrepo' not in createopts:
4011 storevfs = vfsmod.vfs(hgvfs.join(b'store'), cacheaudited=True)
4011 storevfs = vfsmod.vfs(hgvfs.join(b'store'), cacheaudited=True)
4012 scmutil.writerequires(storevfs, storereq)
4012 scmutil.writerequires(storevfs, storereq)
4013
4013
4014 # Write out file telling readers where to find the shared store.
4014 # Write out file telling readers where to find the shared store.
4015 if b'sharedrepo' in createopts:
4015 if b'sharedrepo' in createopts:
4016 hgvfs.write(b'sharedpath', sharedpath)
4016 hgvfs.write(b'sharedpath', sharedpath)
4017
4017
4018 if createopts.get(b'shareditems'):
4018 if createopts.get(b'shareditems'):
4019 shared = b'\n'.join(sorted(createopts[b'shareditems'])) + b'\n'
4019 shared = b'\n'.join(sorted(createopts[b'shareditems'])) + b'\n'
4020 hgvfs.write(b'shared', shared)
4020 hgvfs.write(b'shared', shared)
4021
4021
4022
4022
4023 def poisonrepository(repo):
4023 def poisonrepository(repo):
4024 """Poison a repository instance so it can no longer be used."""
4024 """Poison a repository instance so it can no longer be used."""
4025 # Perform any cleanup on the instance.
4025 # Perform any cleanup on the instance.
4026 repo.close()
4026 repo.close()
4027
4027
4028 # 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
4029 # has all attribute lookups result in error.
4029 # has all attribute lookups result in error.
4030 #
4030 #
4031 # But we have to allow the close() method because some constructors
4031 # But we have to allow the close() method because some constructors
4032 # of repos call close() on repo references.
4032 # of repos call close() on repo references.
4033 class poisonedrepository:
4033 class poisonedrepository:
4034 def __getattribute__(self, item):
4034 def __getattribute__(self, item):
4035 if item == 'close':
4035 if item == 'close':
4036 return object.__getattribute__(self, item)
4036 return object.__getattribute__(self, item)
4037
4037
4038 raise error.ProgrammingError(
4038 raise error.ProgrammingError(
4039 b'repo instances should not be used after unshare'
4039 b'repo instances should not be used after unshare'
4040 )
4040 )
4041
4041
4042 def close(self):
4042 def close(self):
4043 pass
4043 pass
4044
4044
4045 # We may have a repoview, which intercepts __setattr__. So be sure
4045 # We may have a repoview, which intercepts __setattr__. So be sure
4046 # we operate at the lowest level possible.
4046 # we operate at the lowest level possible.
4047 object.__setattr__(repo, '__class__', poisonedrepository)
4047 object.__setattr__(repo, '__class__', poisonedrepository)
@@ -1,3747 +1,3745 b''
1 # revlog.py - storage back-end for mercurial
1 # revlog.py - storage back-end for mercurial
2 # coding: utf8
2 # coding: utf8
3 #
3 #
4 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
4 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 """Storage back-end for Mercurial.
9 """Storage back-end for Mercurial.
10
10
11 This provides efficient delta storage with O(1) retrieve and append
11 This provides efficient delta storage with O(1) retrieve and append
12 and O(changes) merge between branches.
12 and O(changes) merge between branches.
13 """
13 """
14
14
15
15
16 import binascii
16 import binascii
17 import collections
17 import collections
18 import contextlib
18 import contextlib
19 import io
19 import io
20 import os
20 import os
21 import struct
21 import struct
22 import weakref
22 import weakref
23 import zlib
23 import zlib
24
24
25 # import stuff from node for others to import from revlog
25 # import stuff from node for others to import from revlog
26 from .node import (
26 from .node import (
27 bin,
27 bin,
28 hex,
28 hex,
29 nullrev,
29 nullrev,
30 sha1nodeconstants,
30 sha1nodeconstants,
31 short,
31 short,
32 wdirrev,
32 wdirrev,
33 )
33 )
34 from .i18n import _
34 from .i18n import _
35 from .revlogutils.constants import (
35 from .revlogutils.constants import (
36 ALL_KINDS,
36 ALL_KINDS,
37 CHANGELOGV2,
37 CHANGELOGV2,
38 COMP_MODE_DEFAULT,
38 COMP_MODE_DEFAULT,
39 COMP_MODE_INLINE,
39 COMP_MODE_INLINE,
40 COMP_MODE_PLAIN,
40 COMP_MODE_PLAIN,
41 DELTA_BASE_REUSE_NO,
41 DELTA_BASE_REUSE_NO,
42 DELTA_BASE_REUSE_TRY,
42 DELTA_BASE_REUSE_TRY,
43 ENTRY_RANK,
43 ENTRY_RANK,
44 FEATURES_BY_VERSION,
44 FEATURES_BY_VERSION,
45 FLAG_GENERALDELTA,
45 FLAG_GENERALDELTA,
46 FLAG_INLINE_DATA,
46 FLAG_INLINE_DATA,
47 INDEX_HEADER,
47 INDEX_HEADER,
48 KIND_CHANGELOG,
48 KIND_CHANGELOG,
49 KIND_FILELOG,
49 KIND_FILELOG,
50 RANK_UNKNOWN,
50 RANK_UNKNOWN,
51 REVLOGV0,
51 REVLOGV0,
52 REVLOGV1,
52 REVLOGV1,
53 REVLOGV1_FLAGS,
53 REVLOGV1_FLAGS,
54 REVLOGV2,
54 REVLOGV2,
55 REVLOGV2_FLAGS,
55 REVLOGV2_FLAGS,
56 REVLOG_DEFAULT_FLAGS,
56 REVLOG_DEFAULT_FLAGS,
57 REVLOG_DEFAULT_FORMAT,
57 REVLOG_DEFAULT_FORMAT,
58 REVLOG_DEFAULT_VERSION,
58 REVLOG_DEFAULT_VERSION,
59 SUPPORTED_FLAGS,
59 SUPPORTED_FLAGS,
60 )
60 )
61 from .revlogutils.flagutil import (
61 from .revlogutils.flagutil import (
62 REVIDX_DEFAULT_FLAGS,
62 REVIDX_DEFAULT_FLAGS,
63 REVIDX_ELLIPSIS,
63 REVIDX_ELLIPSIS,
64 REVIDX_EXTSTORED,
64 REVIDX_EXTSTORED,
65 REVIDX_FLAGS_ORDER,
65 REVIDX_FLAGS_ORDER,
66 REVIDX_HASCOPIESINFO,
66 REVIDX_HASCOPIESINFO,
67 REVIDX_ISCENSORED,
67 REVIDX_ISCENSORED,
68 REVIDX_RAWTEXT_CHANGING_FLAGS,
68 REVIDX_RAWTEXT_CHANGING_FLAGS,
69 )
69 )
70 from .thirdparty import attr
70 from .thirdparty import attr
71 from . import (
71 from . import (
72 ancestor,
72 ancestor,
73 dagop,
73 dagop,
74 error,
74 error,
75 mdiff,
75 mdiff,
76 policy,
76 policy,
77 pycompat,
77 pycompat,
78 revlogutils,
78 revlogutils,
79 templatefilters,
79 templatefilters,
80 util,
80 util,
81 )
81 )
82 from .interfaces import (
82 from .interfaces import (
83 repository,
83 repository,
84 util as interfaceutil,
84 util as interfaceutil,
85 )
85 )
86 from .revlogutils import (
86 from .revlogutils import (
87 deltas as deltautil,
87 deltas as deltautil,
88 docket as docketutil,
88 docket as docketutil,
89 flagutil,
89 flagutil,
90 nodemap as nodemaputil,
90 nodemap as nodemaputil,
91 randomaccessfile,
91 randomaccessfile,
92 revlogv0,
92 revlogv0,
93 rewrite,
93 rewrite,
94 sidedata as sidedatautil,
94 sidedata as sidedatautil,
95 )
95 )
96 from .utils import (
96 from .utils import (
97 storageutil,
97 storageutil,
98 stringutil,
98 stringutil,
99 )
99 )
100
100
101 # blanked usage of all the name to prevent pyflakes constraints
101 # blanked usage of all the name to prevent pyflakes constraints
102 # We need these name available in the module for extensions.
102 # We need these name available in the module for extensions.
103
103
104 REVLOGV0
104 REVLOGV0
105 REVLOGV1
105 REVLOGV1
106 REVLOGV2
106 REVLOGV2
107 CHANGELOGV2
107 CHANGELOGV2
108 FLAG_INLINE_DATA
108 FLAG_INLINE_DATA
109 FLAG_GENERALDELTA
109 FLAG_GENERALDELTA
110 REVLOG_DEFAULT_FLAGS
110 REVLOG_DEFAULT_FLAGS
111 REVLOG_DEFAULT_FORMAT
111 REVLOG_DEFAULT_FORMAT
112 REVLOG_DEFAULT_VERSION
112 REVLOG_DEFAULT_VERSION
113 REVLOGV1_FLAGS
113 REVLOGV1_FLAGS
114 REVLOGV2_FLAGS
114 REVLOGV2_FLAGS
115 REVIDX_ISCENSORED
115 REVIDX_ISCENSORED
116 REVIDX_ELLIPSIS
116 REVIDX_ELLIPSIS
117 REVIDX_HASCOPIESINFO
117 REVIDX_HASCOPIESINFO
118 REVIDX_EXTSTORED
118 REVIDX_EXTSTORED
119 REVIDX_DEFAULT_FLAGS
119 REVIDX_DEFAULT_FLAGS
120 REVIDX_FLAGS_ORDER
120 REVIDX_FLAGS_ORDER
121 REVIDX_RAWTEXT_CHANGING_FLAGS
121 REVIDX_RAWTEXT_CHANGING_FLAGS
122
122
123 parsers = policy.importmod('parsers')
123 parsers = policy.importmod('parsers')
124 rustancestor = policy.importrust('ancestor')
124 rustancestor = policy.importrust('ancestor')
125 rustdagop = policy.importrust('dagop')
125 rustdagop = policy.importrust('dagop')
126 rustrevlog = policy.importrust('revlog')
126 rustrevlog = policy.importrust('revlog')
127
127
128 # Aliased for performance.
128 # Aliased for performance.
129 _zlibdecompress = zlib.decompress
129 _zlibdecompress = zlib.decompress
130
130
131 # max size of inline data embedded into a revlog
131 # max size of inline data embedded into a revlog
132 _maxinline = 131072
132 _maxinline = 131072
133
133
134 # Flag processors for REVIDX_ELLIPSIS.
134 # Flag processors for REVIDX_ELLIPSIS.
135 def ellipsisreadprocessor(rl, text):
135 def ellipsisreadprocessor(rl, text):
136 return text, False
136 return text, False
137
137
138
138
139 def ellipsiswriteprocessor(rl, text):
139 def ellipsiswriteprocessor(rl, text):
140 return text, False
140 return text, False
141
141
142
142
143 def ellipsisrawprocessor(rl, text):
143 def ellipsisrawprocessor(rl, text):
144 return False
144 return False
145
145
146
146
147 ellipsisprocessor = (
147 ellipsisprocessor = (
148 ellipsisreadprocessor,
148 ellipsisreadprocessor,
149 ellipsiswriteprocessor,
149 ellipsiswriteprocessor,
150 ellipsisrawprocessor,
150 ellipsisrawprocessor,
151 )
151 )
152
152
153
153
154 def _verify_revision(rl, skipflags, state, node):
154 def _verify_revision(rl, skipflags, state, node):
155 """Verify the integrity of the given revlog ``node`` while providing a hook
155 """Verify the integrity of the given revlog ``node`` while providing a hook
156 point for extensions to influence the operation."""
156 point for extensions to influence the operation."""
157 if skipflags:
157 if skipflags:
158 state[b'skipread'].add(node)
158 state[b'skipread'].add(node)
159 else:
159 else:
160 # Side-effect: read content and verify hash.
160 # Side-effect: read content and verify hash.
161 rl.revision(node)
161 rl.revision(node)
162
162
163
163
164 # True if a fast implementation for persistent-nodemap is available
164 # True if a fast implementation for persistent-nodemap is available
165 #
165 #
166 # We also consider we have a "fast" implementation in "pure" python because
166 # We also consider we have a "fast" implementation in "pure" python because
167 # people using pure don't really have performance consideration (and a
167 # people using pure don't really have performance consideration (and a
168 # wheelbarrow of other slowness source)
168 # wheelbarrow of other slowness source)
169 HAS_FAST_PERSISTENT_NODEMAP = rustrevlog is not None or hasattr(
169 HAS_FAST_PERSISTENT_NODEMAP = rustrevlog is not None or hasattr(
170 parsers, 'BaseIndexObject'
170 parsers, 'BaseIndexObject'
171 )
171 )
172
172
173
173
174 @interfaceutil.implementer(repository.irevisiondelta)
174 @interfaceutil.implementer(repository.irevisiondelta)
175 @attr.s(slots=True)
175 @attr.s(slots=True)
176 class revlogrevisiondelta:
176 class revlogrevisiondelta:
177 node = attr.ib()
177 node = attr.ib()
178 p1node = attr.ib()
178 p1node = attr.ib()
179 p2node = attr.ib()
179 p2node = attr.ib()
180 basenode = attr.ib()
180 basenode = attr.ib()
181 flags = attr.ib()
181 flags = attr.ib()
182 baserevisionsize = attr.ib()
182 baserevisionsize = attr.ib()
183 revision = attr.ib()
183 revision = attr.ib()
184 delta = attr.ib()
184 delta = attr.ib()
185 sidedata = attr.ib()
185 sidedata = attr.ib()
186 protocol_flags = attr.ib()
186 protocol_flags = attr.ib()
187 linknode = attr.ib(default=None)
187 linknode = attr.ib(default=None)
188
188
189
189
190 @interfaceutil.implementer(repository.iverifyproblem)
190 @interfaceutil.implementer(repository.iverifyproblem)
191 @attr.s(frozen=True)
191 @attr.s(frozen=True)
192 class revlogproblem:
192 class revlogproblem:
193 warning = attr.ib(default=None)
193 warning = attr.ib(default=None)
194 error = attr.ib(default=None)
194 error = attr.ib(default=None)
195 node = attr.ib(default=None)
195 node = attr.ib(default=None)
196
196
197
197
198 def parse_index_v1(data, inline):
198 def parse_index_v1(data, inline):
199 # call the C implementation to parse the index data
199 # call the C implementation to parse the index data
200 index, cache = parsers.parse_index2(data, inline)
200 index, cache = parsers.parse_index2(data, inline)
201 return index, cache
201 return index, cache
202
202
203
203
204 def parse_index_v2(data, inline):
204 def parse_index_v2(data, inline):
205 # call the C implementation to parse the index data
205 # call the C implementation to parse the index data
206 index, cache = parsers.parse_index2(data, inline, format=REVLOGV2)
206 index, cache = parsers.parse_index2(data, inline, format=REVLOGV2)
207 return index, cache
207 return index, cache
208
208
209
209
210 def parse_index_cl_v2(data, inline):
210 def parse_index_cl_v2(data, inline):
211 # call the C implementation to parse the index data
211 # call the C implementation to parse the index data
212 index, cache = parsers.parse_index2(data, inline, format=CHANGELOGV2)
212 index, cache = parsers.parse_index2(data, inline, format=CHANGELOGV2)
213 return index, cache
213 return index, cache
214
214
215
215
216 if hasattr(parsers, 'parse_index_devel_nodemap'):
216 if hasattr(parsers, 'parse_index_devel_nodemap'):
217
217
218 def parse_index_v1_nodemap(data, inline):
218 def parse_index_v1_nodemap(data, inline):
219 index, cache = parsers.parse_index_devel_nodemap(data, inline)
219 index, cache = parsers.parse_index_devel_nodemap(data, inline)
220 return index, cache
220 return index, cache
221
221
222
222
223 else:
223 else:
224 parse_index_v1_nodemap = None
224 parse_index_v1_nodemap = None
225
225
226
226
227 def parse_index_v1_mixed(data, inline):
227 def parse_index_v1_mixed(data, inline):
228 index, cache = parse_index_v1(data, inline)
228 index, cache = parse_index_v1(data, inline)
229 return rustrevlog.MixedIndex(index), cache
229 return rustrevlog.MixedIndex(index), cache
230
230
231
231
232 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
232 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
233 # signed integer)
233 # signed integer)
234 _maxentrysize = 0x7FFFFFFF
234 _maxentrysize = 0x7FFFFFFF
235
235
236 FILE_TOO_SHORT_MSG = _(
236 FILE_TOO_SHORT_MSG = _(
237 b'cannot read from revlog %s;'
237 b'cannot read from revlog %s;'
238 b' expected %d bytes from offset %d, data size is %d'
238 b' expected %d bytes from offset %d, data size is %d'
239 )
239 )
240
240
241 hexdigits = b'0123456789abcdefABCDEF'
241 hexdigits = b'0123456789abcdefABCDEF'
242
242
243
243
244 class _Config:
244 class _Config:
245 def copy(self):
245 def copy(self):
246 return self.__class__(**self.__dict__)
246 return self.__class__(**self.__dict__)
247
247
248
248
249 @attr.s()
249 @attr.s()
250 class FeatureConfig(_Config):
250 class FeatureConfig(_Config):
251 """Hold configuration values about the available revlog features"""
251 """Hold configuration values about the available revlog features"""
252
252
253 # the default compression engine
253 # the default compression engine
254 compression_engine = attr.ib(default=b'zlib')
254 compression_engine = attr.ib(default=b'zlib')
255 # compression engines options
255 # compression engines options
256 compression_engine_options = attr.ib(default=attr.Factory(dict))
256 compression_engine_options = attr.ib(default=attr.Factory(dict))
257
257
258 # can we use censor on this revlog
258 # can we use censor on this revlog
259 censorable = attr.ib(default=False)
259 censorable = attr.ib(default=False)
260 # does this revlog use the "side data" feature
260 # does this revlog use the "side data" feature
261 has_side_data = attr.ib(default=False)
261 has_side_data = attr.ib(default=False)
262 # might remove rank configuration once the computation has no impact
262 # might remove rank configuration once the computation has no impact
263 compute_rank = attr.ib(default=False)
263 compute_rank = attr.ib(default=False)
264 # parent order is supposed to be semantically irrelevant, so we
264 # parent order is supposed to be semantically irrelevant, so we
265 # normally resort parents to ensure that the first parent is non-null,
265 # normally resort parents to ensure that the first parent is non-null,
266 # if there is a non-null parent at all.
266 # if there is a non-null parent at all.
267 # filelog abuses the parent order as flag to mark some instances of
267 # filelog abuses the parent order as flag to mark some instances of
268 # meta-encoded files, so allow it to disable this behavior.
268 # meta-encoded files, so allow it to disable this behavior.
269 canonical_parent_order = attr.ib(default=False)
269 canonical_parent_order = attr.ib(default=False)
270 # can ellipsis commit be used
270 # can ellipsis commit be used
271 enable_ellipsis = attr.ib(default=False)
271 enable_ellipsis = attr.ib(default=False)
272
272
273 def copy(self):
273 def copy(self):
274 new = super().copy()
274 new = super().copy()
275 new.compression_engine_options = self.compression_engine_options.copy()
275 new.compression_engine_options = self.compression_engine_options.copy()
276 return new
276 return new
277
277
278
278
279 @attr.s()
279 @attr.s()
280 class DataConfig(_Config):
280 class DataConfig(_Config):
281 """Hold configuration value about how the revlog data are read"""
281 """Hold configuration value about how the revlog data are read"""
282
282
283 # should we try to open the "pending" version of the revlog
283 # should we try to open the "pending" version of the revlog
284 try_pending = attr.ib(default=False)
284 try_pending = attr.ib(default=False)
285 # should we try to open the "splitted" version of the revlog
285 # should we try to open the "splitted" version of the revlog
286 try_split = attr.ib(default=False)
286 try_split = attr.ib(default=False)
287 # When True, indexfile should be opened with checkambig=True at writing,
287 # When True, indexfile should be opened with checkambig=True at writing,
288 # to avoid file stat ambiguity.
288 # to avoid file stat ambiguity.
289 check_ambig = attr.ib(default=False)
289 check_ambig = attr.ib(default=False)
290
290
291 # If true, use mmap instead of reading to deal with large index
291 # If true, use mmap instead of reading to deal with large index
292 mmap_large_index = attr.ib(default=False)
292 mmap_large_index = attr.ib(default=False)
293 # how much data is large
293 # how much data is large
294 mmap_index_threshold = attr.ib(default=None)
294 mmap_index_threshold = attr.ib(default=None)
295 # How much data to read and cache into the raw revlog data cache.
295 # How much data to read and cache into the raw revlog data cache.
296 chunk_cache_size = attr.ib(default=65536)
296 chunk_cache_size = attr.ib(default=65536)
297
297
298 # Allow sparse reading of the revlog data
298 # Allow sparse reading of the revlog data
299 with_sparse_read = attr.ib(default=False)
299 with_sparse_read = attr.ib(default=False)
300 # minimal density of a sparse read chunk
300 # minimal density of a sparse read chunk
301 sr_density_threshold = attr.ib(default=0.50)
301 sr_density_threshold = attr.ib(default=0.50)
302 # minimal size of data we skip when performing sparse read
302 # minimal size of data we skip when performing sparse read
303 sr_min_gap_size = attr.ib(default=262144)
303 sr_min_gap_size = attr.ib(default=262144)
304
304
305 # are delta encoded against arbitrary bases.
305 # are delta encoded against arbitrary bases.
306 generaldelta = attr.ib(default=False)
306 generaldelta = attr.ib(default=False)
307
307
308
308
309 @attr.s()
309 @attr.s()
310 class DeltaConfig(_Config):
310 class DeltaConfig(_Config):
311 """Hold configuration value about how new delta are computed
311 """Hold configuration value about how new delta are computed
312
312
313 Some attributes are duplicated from DataConfig to help havign each object
313 Some attributes are duplicated from DataConfig to help havign each object
314 self contained.
314 self contained.
315 """
315 """
316
316
317 # can delta be encoded against arbitrary bases.
317 # can delta be encoded against arbitrary bases.
318 general_delta = attr.ib(default=False)
318 general_delta = attr.ib(default=False)
319 # Allow sparse writing of the revlog data
319 # Allow sparse writing of the revlog data
320 sparse_revlog = attr.ib(default=False)
320 sparse_revlog = attr.ib(default=False)
321 # maximum length of a delta chain
321 # maximum length of a delta chain
322 max_chain_len = attr.ib(default=None)
322 max_chain_len = attr.ib(default=None)
323 # Maximum distance between delta chain base start and end
323 # Maximum distance between delta chain base start and end
324 max_deltachain_span = attr.ib(default=-1)
324 max_deltachain_span = attr.ib(default=-1)
325 # If `upper_bound_comp` is not None, this is the expected maximal gain from
325 # If `upper_bound_comp` is not None, this is the expected maximal gain from
326 # compression for the data content.
326 # compression for the data content.
327 upper_bound_comp = attr.ib(default=None)
327 upper_bound_comp = attr.ib(default=None)
328 # Should we try a delta against both parent
328 # Should we try a delta against both parent
329 delta_both_parents = attr.ib(default=True)
329 delta_both_parents = attr.ib(default=True)
330 # Test delta base candidate group by chunk of this maximal size.
330 # Test delta base candidate group by chunk of this maximal size.
331 candidate_group_chunk_size = attr.ib(default=0)
331 candidate_group_chunk_size = attr.ib(default=0)
332 # Should we display debug information about delta computation
332 # Should we display debug information about delta computation
333 debug_delta = attr.ib(default=False)
333 debug_delta = attr.ib(default=False)
334 # trust incoming delta by default
334 # trust incoming delta by default
335 lazy_delta = attr.ib(default=True)
335 lazy_delta = attr.ib(default=True)
336 # trust the base of incoming delta by default
336 # trust the base of incoming delta by default
337 lazy_delta_base = attr.ib(default=False)
337 lazy_delta_base = attr.ib(default=False)
338
338
339
339
340 class revlog:
340 class revlog:
341 """
341 """
342 the underlying revision storage object
342 the underlying revision storage object
343
343
344 A revlog consists of two parts, an index and the revision data.
344 A revlog consists of two parts, an index and the revision data.
345
345
346 The index is a file with a fixed record size containing
346 The index is a file with a fixed record size containing
347 information on each revision, including its nodeid (hash), the
347 information on each revision, including its nodeid (hash), the
348 nodeids of its parents, the position and offset of its data within
348 nodeids of its parents, the position and offset of its data within
349 the data file, and the revision it's based on. Finally, each entry
349 the data file, and the revision it's based on. Finally, each entry
350 contains a linkrev entry that can serve as a pointer to external
350 contains a linkrev entry that can serve as a pointer to external
351 data.
351 data.
352
352
353 The revision data itself is a linear collection of data chunks.
353 The revision data itself is a linear collection of data chunks.
354 Each chunk represents a revision and is usually represented as a
354 Each chunk represents a revision and is usually represented as a
355 delta against the previous chunk. To bound lookup time, runs of
355 delta against the previous chunk. To bound lookup time, runs of
356 deltas are limited to about 2 times the length of the original
356 deltas are limited to about 2 times the length of the original
357 version data. This makes retrieval of a version proportional to
357 version data. This makes retrieval of a version proportional to
358 its size, or O(1) relative to the number of revisions.
358 its size, or O(1) relative to the number of revisions.
359
359
360 Both pieces of the revlog are written to in an append-only
360 Both pieces of the revlog are written to in an append-only
361 fashion, which means we never need to rewrite a file to insert or
361 fashion, which means we never need to rewrite a file to insert or
362 remove data, and can use some simple techniques to avoid the need
362 remove data, and can use some simple techniques to avoid the need
363 for locking while reading.
363 for locking while reading.
364
364
365 If checkambig, indexfile is opened with checkambig=True at
365 If checkambig, indexfile is opened with checkambig=True at
366 writing, to avoid file stat ambiguity.
366 writing, to avoid file stat ambiguity.
367
367
368 If mmaplargeindex is True, and an mmapindexthreshold is set, the
368 If mmaplargeindex is True, and an mmapindexthreshold is set, the
369 index will be mmapped rather than read if it is larger than the
369 index will be mmapped rather than read if it is larger than the
370 configured threshold.
370 configured threshold.
371
371
372 If censorable is True, the revlog can have censored revisions.
372 If censorable is True, the revlog can have censored revisions.
373
373
374 If `upperboundcomp` is not None, this is the expected maximal gain from
374 If `upperboundcomp` is not None, this is the expected maximal gain from
375 compression for the data content.
375 compression for the data content.
376
376
377 `concurrencychecker` is an optional function that receives 3 arguments: a
377 `concurrencychecker` is an optional function that receives 3 arguments: a
378 file handle, a filename, and an expected position. It should check whether
378 file handle, a filename, and an expected position. It should check whether
379 the current position in the file handle is valid, and log/warn/fail (by
379 the current position in the file handle is valid, and log/warn/fail (by
380 raising).
380 raising).
381
381
382 See mercurial/revlogutils/contants.py for details about the content of an
382 See mercurial/revlogutils/contants.py for details about the content of an
383 index entry.
383 index entry.
384 """
384 """
385
385
386 _flagserrorclass = error.RevlogError
386 _flagserrorclass = error.RevlogError
387
387
388 @staticmethod
388 @staticmethod
389 def is_inline_index(header_bytes):
389 def is_inline_index(header_bytes):
390 """Determine if a revlog is inline from the initial bytes of the index"""
390 """Determine if a revlog is inline from the initial bytes of the index"""
391 header = INDEX_HEADER.unpack(header_bytes)[0]
391 header = INDEX_HEADER.unpack(header_bytes)[0]
392
392
393 _format_flags = header & ~0xFFFF
393 _format_flags = header & ~0xFFFF
394 _format_version = header & 0xFFFF
394 _format_version = header & 0xFFFF
395
395
396 features = FEATURES_BY_VERSION[_format_version]
396 features = FEATURES_BY_VERSION[_format_version]
397 return features[b'inline'](_format_flags)
397 return features[b'inline'](_format_flags)
398
398
399 def __init__(
399 def __init__(
400 self,
400 self,
401 opener,
401 opener,
402 target,
402 target,
403 radix,
403 radix,
404 postfix=None, # only exist for `tmpcensored` now
404 postfix=None, # only exist for `tmpcensored` now
405 checkambig=False,
405 checkambig=False,
406 mmaplargeindex=False,
406 mmaplargeindex=False,
407 censorable=False,
407 censorable=False,
408 upperboundcomp=None,
408 upperboundcomp=None,
409 persistentnodemap=False,
409 persistentnodemap=False,
410 concurrencychecker=None,
410 concurrencychecker=None,
411 trypending=False,
411 trypending=False,
412 try_split=False,
412 try_split=False,
413 canonical_parent_order=True,
413 canonical_parent_order=True,
414 ):
414 ):
415 """
415 """
416 create a revlog object
416 create a revlog object
417
417
418 opener is a function that abstracts the file opening operation
418 opener is a function that abstracts the file opening operation
419 and can be used to implement COW semantics or the like.
419 and can be used to implement COW semantics or the like.
420
420
421 `target`: a (KIND, ID) tuple that identify the content stored in
421 `target`: a (KIND, ID) tuple that identify the content stored in
422 this revlog. It help the rest of the code to understand what the revlog
422 this revlog. It help the rest of the code to understand what the revlog
423 is about without having to resort to heuristic and index filename
423 is about without having to resort to heuristic and index filename
424 analysis. Note: that this must be reliably be set by normal code, but
424 analysis. Note: that this must be reliably be set by normal code, but
425 that test, debug, or performance measurement code might not set this to
425 that test, debug, or performance measurement code might not set this to
426 accurate value.
426 accurate value.
427 """
427 """
428 self.upperboundcomp = upperboundcomp
428 self.upperboundcomp = upperboundcomp
429
429
430 self.radix = radix
430 self.radix = radix
431
431
432 self._docket_file = None
432 self._docket_file = None
433 self._indexfile = None
433 self._indexfile = None
434 self._datafile = None
434 self._datafile = None
435 self._sidedatafile = None
435 self._sidedatafile = None
436 self._nodemap_file = None
436 self._nodemap_file = None
437 self.postfix = postfix
437 self.postfix = postfix
438 self._trypending = trypending
438 self._trypending = trypending
439 self._try_split = try_split
439 self._try_split = try_split
440 self.opener = opener
440 self.opener = opener
441 if persistentnodemap:
441 if persistentnodemap:
442 self._nodemap_file = nodemaputil.get_nodemap_file(self)
442 self._nodemap_file = nodemaputil.get_nodemap_file(self)
443
443
444 assert target[0] in ALL_KINDS
444 assert target[0] in ALL_KINDS
445 assert len(target) == 2
445 assert len(target) == 2
446 self.target = target
446 self.target = target
447 if b'feature-config' in self.opener.options:
447 if b'feature-config' in self.opener.options:
448 self.feature_config = self.opener.options[b'feature-config'].copy()
448 self.feature_config = self.opener.options[b'feature-config'].copy()
449 else:
449 else:
450 self.feature_config = FeatureConfig()
450 self.feature_config = FeatureConfig()
451 self.feature_config.censorable = censorable
451 self.feature_config.censorable = censorable
452 self.feature_config.canonical_parent_order = canonical_parent_order
452 self.feature_config.canonical_parent_order = canonical_parent_order
453 if b'data-config' in self.opener.options:
453 if b'data-config' in self.opener.options:
454 self.data_config = self.opener.options[b'data-config'].copy()
454 self.data_config = self.opener.options[b'data-config'].copy()
455 else:
455 else:
456 self.data_config = DataConfig()
456 self.data_config = DataConfig()
457 self.data_config.check_ambig = checkambig
457 self.data_config.check_ambig = checkambig
458 self.data_config.mmap_large_index = mmaplargeindex
458 self.data_config.mmap_large_index = mmaplargeindex
459 if b'delta-config' in self.opener.options:
459 if b'delta-config' in self.opener.options:
460 self.delta_config = self.opener.options[b'delta-config'].copy()
460 self.delta_config = self.opener.options[b'delta-config'].copy()
461 else:
461 else:
462 self.delta_config = DeltaConfig()
462 self.delta_config = DeltaConfig()
463
463
464 # 3-tuple of (node, rev, text) for a raw revision.
464 # 3-tuple of (node, rev, text) for a raw revision.
465 self._revisioncache = None
465 self._revisioncache = None
466 # Maps rev to chain base rev.
466 # Maps rev to chain base rev.
467 self._chainbasecache = util.lrucachedict(100)
467 self._chainbasecache = util.lrucachedict(100)
468 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
468 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
469 self._chunkcache = (0, b'')
469 self._chunkcache = (0, b'')
470
470
471 self.index = None
471 self.index = None
472 self._docket = None
472 self._docket = None
473 self._nodemap_docket = None
473 self._nodemap_docket = None
474 # Mapping of partial identifiers to full nodes.
474 # Mapping of partial identifiers to full nodes.
475 self._pcache = {}
475 self._pcache = {}
476
476
477 # other optionnals features
477 # other optionnals features
478
478
479 # Make copy of flag processors so each revlog instance can support
479 # Make copy of flag processors so each revlog instance can support
480 # custom flags.
480 # custom flags.
481 self._flagprocessors = dict(flagutil.flagprocessors)
481 self._flagprocessors = dict(flagutil.flagprocessors)
482
482
483 # 3-tuple of file handles being used for active writing.
483 # 3-tuple of file handles being used for active writing.
484 self._writinghandles = None
484 self._writinghandles = None
485 # prevent nesting of addgroup
485 # prevent nesting of addgroup
486 self._adding_group = None
486 self._adding_group = None
487
487
488 self._loadindex()
488 self._loadindex()
489
489
490 self._concurrencychecker = concurrencychecker
490 self._concurrencychecker = concurrencychecker
491
491
492 @property
492 @property
493 def _generaldelta(self):
493 def _generaldelta(self):
494 """temporary compatibility proxy"""
494 """temporary compatibility proxy"""
495 return self.delta_config.general_delta
495 return self.delta_config.general_delta
496
496
497 @property
497 @property
498 def _checkambig(self):
498 def _checkambig(self):
499 """temporary compatibility proxy"""
499 """temporary compatibility proxy"""
500 return self.data_config.check_ambig
500 return self.data_config.check_ambig
501
501
502 @property
502 @property
503 def _mmaplargeindex(self):
503 def _mmaplargeindex(self):
504 """temporary compatibility proxy"""
504 """temporary compatibility proxy"""
505 return self.data_config.mmap_large_index
505 return self.data_config.mmap_large_index
506
506
507 @property
507 @property
508 def _censorable(self):
508 def _censorable(self):
509 """temporary compatibility proxy"""
509 """temporary compatibility proxy"""
510 return self.feature_config.censorable
510 return self.feature_config.censorable
511
511
512 @property
512 @property
513 def _chunkcachesize(self):
513 def _chunkcachesize(self):
514 """temporary compatibility proxy"""
514 """temporary compatibility proxy"""
515 return self.data_config.chunk_cache_size
515 return self.data_config.chunk_cache_size
516
516
517 @property
517 @property
518 def _maxchainlen(self):
518 def _maxchainlen(self):
519 """temporary compatibility proxy"""
519 """temporary compatibility proxy"""
520 return self.delta_config.max_chain_len
520 return self.delta_config.max_chain_len
521
521
522 @property
522 @property
523 def _deltabothparents(self):
523 def _deltabothparents(self):
524 """temporary compatibility proxy"""
524 """temporary compatibility proxy"""
525 return self.delta_config.delta_both_parents
525 return self.delta_config.delta_both_parents
526
526
527 @property
527 @property
528 def _candidate_group_chunk_size(self):
528 def _candidate_group_chunk_size(self):
529 """temporary compatibility proxy"""
529 """temporary compatibility proxy"""
530 return self.delta_config.candidate_group_chunk_size
530 return self.delta_config.candidate_group_chunk_size
531
531
532 @property
532 @property
533 def _debug_delta(self):
533 def _debug_delta(self):
534 """temporary compatibility proxy"""
534 """temporary compatibility proxy"""
535 return self.delta_config.debug_delta
535 return self.delta_config.debug_delta
536
536
537 @property
537 @property
538 def _compengine(self):
538 def _compengine(self):
539 """temporary compatibility proxy"""
539 """temporary compatibility proxy"""
540 return self.feature_config.compression_engine
540 return self.feature_config.compression_engine
541
541
542 @property
542 @property
543 def _compengineopts(self):
543 def _compengineopts(self):
544 """temporary compatibility proxy"""
544 """temporary compatibility proxy"""
545 return self.feature_config.compression_engine_options
545 return self.feature_config.compression_engine_options
546
546
547 @property
547 @property
548 def _maxdeltachainspan(self):
548 def _maxdeltachainspan(self):
549 """temporary compatibility proxy"""
549 """temporary compatibility proxy"""
550 return self.delta_config.max_deltachain_span
550 return self.delta_config.max_deltachain_span
551
551
552 @property
552 @property
553 def _withsparseread(self):
553 def _withsparseread(self):
554 """temporary compatibility proxy"""
554 """temporary compatibility proxy"""
555 return self.data_config.with_sparse_read
555 return self.data_config.with_sparse_read
556
556
557 @property
557 @property
558 def _sparserevlog(self):
558 def _sparserevlog(self):
559 """temporary compatibility proxy"""
559 """temporary compatibility proxy"""
560 return self.delta_config.sparse_revlog
560 return self.delta_config.sparse_revlog
561
561
562 @property
562 @property
563 def hassidedata(self):
563 def hassidedata(self):
564 """temporary compatibility proxy"""
564 """temporary compatibility proxy"""
565 return self.feature_config.has_side_data
565 return self.feature_config.has_side_data
566
566
567 @property
567 @property
568 def _srdensitythreshold(self):
568 def _srdensitythreshold(self):
569 """temporary compatibility proxy"""
569 """temporary compatibility proxy"""
570 return self.data_config.sr_density_threshold
570 return self.data_config.sr_density_threshold
571
571
572 @property
572 @property
573 def _srmingapsize(self):
573 def _srmingapsize(self):
574 """temporary compatibility proxy"""
574 """temporary compatibility proxy"""
575 return self.data_config.sr_min_gap_size
575 return self.data_config.sr_min_gap_size
576
576
577 @property
577 @property
578 def _compute_rank(self):
578 def _compute_rank(self):
579 """temporary compatibility proxy"""
579 """temporary compatibility proxy"""
580 return self.feature_config.compute_rank
580 return self.feature_config.compute_rank
581
581
582 @property
582 @property
583 def canonical_parent_order(self):
583 def canonical_parent_order(self):
584 """temporary compatibility proxy"""
584 """temporary compatibility proxy"""
585 return self.feature_config.canonical_parent_order
585 return self.feature_config.canonical_parent_order
586
586
587 @property
587 @property
588 def _lazydelta(self):
588 def _lazydelta(self):
589 """temporary compatibility proxy"""
589 """temporary compatibility proxy"""
590 return self.delta_config.lazy_delta
590 return self.delta_config.lazy_delta
591
591
592 @property
592 @property
593 def _lazydeltabase(self):
593 def _lazydeltabase(self):
594 """temporary compatibility proxy"""
594 """temporary compatibility proxy"""
595 return self.delta_config.lazy_delta_base
595 return self.delta_config.lazy_delta_base
596
596
597 def _init_opts(self):
597 def _init_opts(self):
598 """process options (from above/config) to setup associated default revlog mode
598 """process options (from above/config) to setup associated default revlog mode
599
599
600 These values might be affected when actually reading on disk information.
600 These values might be affected when actually reading on disk information.
601
601
602 The relevant values are returned for use in _loadindex().
602 The relevant values are returned for use in _loadindex().
603
603
604 * newversionflags:
604 * newversionflags:
605 version header to use if we need to create a new revlog
605 version header to use if we need to create a new revlog
606
606
607 * mmapindexthreshold:
607 * mmapindexthreshold:
608 minimal index size for start to use mmap
608 minimal index size for start to use mmap
609
609
610 * force_nodemap:
610 * force_nodemap:
611 force the usage of a "development" version of the nodemap code
611 force the usage of a "development" version of the nodemap code
612 """
612 """
613 mmapindexthreshold = None
613 mmapindexthreshold = None
614 opts = self.opener.options
614 opts = self.opener.options
615
615
616 if b'changelogv2' in opts and self.revlog_kind == KIND_CHANGELOG:
616 if b'changelogv2' in opts and self.revlog_kind == KIND_CHANGELOG:
617 new_header = CHANGELOGV2
617 new_header = CHANGELOGV2
618 compute_rank = opts.get(b'changelogv2.compute-rank', True)
618 compute_rank = opts.get(b'changelogv2.compute-rank', True)
619 self.feature_config.compute_rank = compute_rank
619 self.feature_config.compute_rank = compute_rank
620 elif b'revlogv2' in opts:
620 elif b'revlogv2' in opts:
621 new_header = REVLOGV2
621 new_header = REVLOGV2
622 elif b'revlogv1' in opts:
622 elif b'revlogv1' in opts:
623 new_header = REVLOGV1 | FLAG_INLINE_DATA
623 new_header = REVLOGV1 | FLAG_INLINE_DATA
624 if b'generaldelta' in opts:
624 if b'generaldelta' in opts:
625 new_header |= FLAG_GENERALDELTA
625 new_header |= FLAG_GENERALDELTA
626 elif b'revlogv0' in self.opener.options:
626 elif b'revlogv0' in self.opener.options:
627 new_header = REVLOGV0
627 new_header = REVLOGV0
628 else:
628 else:
629 new_header = REVLOG_DEFAULT_VERSION
629 new_header = REVLOG_DEFAULT_VERSION
630
630
631 if b'chunkcachesize' in opts:
632 self.data_config.chunk_cache_size = opts[b'chunkcachesize']
633 if b'maxchainlen' in opts:
631 if b'maxchainlen' in opts:
634 self.delta_config.max_chain_len = opts[b'maxchainlen']
632 self.delta_config.max_chain_len = opts[b'maxchainlen']
635 if b'deltabothparents' in opts:
633 if b'deltabothparents' in opts:
636 self.delta_config.delta_both_parents = opts[b'deltabothparents']
634 self.delta_config.delta_both_parents = opts[b'deltabothparents']
637 dps_cgds = opts.get(b'delta-parent-search.candidate-group-chunk-size')
635 dps_cgds = opts.get(b'delta-parent-search.candidate-group-chunk-size')
638 if dps_cgds:
636 if dps_cgds:
639 self.delta_config.candidate_group_chunk_size = dps_cgds
637 self.delta_config.candidate_group_chunk_size = dps_cgds
640 if b'lazydelta' in opts:
638 if b'lazydelta' in opts:
641 self.delta_config.lazy_delta = bool(opts[b'lazydelta'])
639 self.delta_config.lazy_delta = bool(opts[b'lazydelta'])
642 if self._lazydelta and b'lazydeltabase' in opts:
640 if self._lazydelta and b'lazydeltabase' in opts:
643 self.delta_config.lazy_delta_base = opts[b'lazydeltabase']
641 self.delta_config.lazy_delta_base = opts[b'lazydeltabase']
644 if b'debug-delta' in opts:
642 if b'debug-delta' in opts:
645 self.delta_config.debug_delta = opts[b'debug-delta']
643 self.delta_config.debug_delta = opts[b'debug-delta']
646 if b'compengine' in opts:
644 if b'compengine' in opts:
647 self.feature_config.compression_engine = opts[b'compengine']
645 self.feature_config.compression_engine = opts[b'compengine']
648 comp_engine_opts = self.feature_config.compression_engine_options
646 comp_engine_opts = self.feature_config.compression_engine_options
649 if b'zlib.level' in opts:
647 if b'zlib.level' in opts:
650 comp_engine_opts[b'zlib.level'] = opts[b'zlib.level']
648 comp_engine_opts[b'zlib.level'] = opts[b'zlib.level']
651 if b'zstd.level' in opts:
649 if b'zstd.level' in opts:
652 comp_engine_opts[b'zstd.level'] = opts[b'zstd.level']
650 comp_engine_opts[b'zstd.level'] = opts[b'zstd.level']
653 if b'maxdeltachainspan' in opts:
651 if b'maxdeltachainspan' in opts:
654 self.delta_config.max_deltachain_span = opts[b'maxdeltachainspan']
652 self.delta_config.max_deltachain_span = opts[b'maxdeltachainspan']
655 if self._mmaplargeindex and b'mmapindexthreshold' in opts:
653 if self._mmaplargeindex and b'mmapindexthreshold' in opts:
656 mmapindexthreshold = opts[b'mmapindexthreshold']
654 mmapindexthreshold = opts[b'mmapindexthreshold']
657 self.data_config.mmap_index_threshold = mmapindexthreshold
655 self.data_config.mmap_index_threshold = mmapindexthreshold
658 if b'sparse-revlog' in opts:
656 if b'sparse-revlog' in opts:
659 self.delta_config.sparse_revlog = bool(opts[b'sparse-revlog'])
657 self.delta_config.sparse_revlog = bool(opts[b'sparse-revlog'])
660 if self.delta_config.sparse_revlog:
658 if self.delta_config.sparse_revlog:
661 # sparse-revlog forces sparse-read
659 # sparse-revlog forces sparse-read
662 self.data_config.with_sparse_read = True
660 self.data_config.with_sparse_read = True
663 elif b'with-sparse-read' in opts:
661 elif b'with-sparse-read' in opts:
664 self.data_config.with_sparse_read = bool(opts[b'with-sparse-read'])
662 self.data_config.with_sparse_read = bool(opts[b'with-sparse-read'])
665 if b'sparse-read-density-threshold' in opts:
663 if b'sparse-read-density-threshold' in opts:
666 self.data_config.sr_density_threshold = opts[
664 self.data_config.sr_density_threshold = opts[
667 b'sparse-read-density-threshold'
665 b'sparse-read-density-threshold'
668 ]
666 ]
669 if b'sparse-read-min-gap-size' in opts:
667 if b'sparse-read-min-gap-size' in opts:
670 self.data_config.sr_min_gap_size = opts[b'sparse-read-min-gap-size']
668 self.data_config.sr_min_gap_size = opts[b'sparse-read-min-gap-size']
671 if opts.get(b'enableellipsis'):
669 if opts.get(b'enableellipsis'):
672 self.feature_config.enable_ellipsis = True
670 self.feature_config.enable_ellipsis = True
673 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
671 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
674
672
675 # revlog v0 doesn't have flag processors
673 # revlog v0 doesn't have flag processors
676 for flag, processor in opts.get(b'flagprocessors', {}).items():
674 for flag, processor in opts.get(b'flagprocessors', {}).items():
677 flagutil.insertflagprocessor(flag, processor, self._flagprocessors)
675 flagutil.insertflagprocessor(flag, processor, self._flagprocessors)
678
676
679 chunk_cache_size = self.data_config.chunk_cache_size
677 chunk_cache_size = self.data_config.chunk_cache_size
680 if chunk_cache_size <= 0:
678 if chunk_cache_size <= 0:
681 raise error.RevlogError(
679 raise error.RevlogError(
682 _(b'revlog chunk cache size %r is not greater than 0')
680 _(b'revlog chunk cache size %r is not greater than 0')
683 % chunk_cache_size
681 % chunk_cache_size
684 )
682 )
685 elif chunk_cache_size & (chunk_cache_size - 1):
683 elif chunk_cache_size & (chunk_cache_size - 1):
686 raise error.RevlogError(
684 raise error.RevlogError(
687 _(b'revlog chunk cache size %r is not a power of 2')
685 _(b'revlog chunk cache size %r is not a power of 2')
688 % chunk_cache_size
686 % chunk_cache_size
689 )
687 )
690 force_nodemap = opts.get(b'devel-force-nodemap', False)
688 force_nodemap = opts.get(b'devel-force-nodemap', False)
691 return new_header, mmapindexthreshold, force_nodemap
689 return new_header, mmapindexthreshold, force_nodemap
692
690
693 def _get_data(self, filepath, mmap_threshold, size=None):
691 def _get_data(self, filepath, mmap_threshold, size=None):
694 """return a file content with or without mmap
692 """return a file content with or without mmap
695
693
696 If the file is missing return the empty string"""
694 If the file is missing return the empty string"""
697 try:
695 try:
698 with self.opener(filepath) as fp:
696 with self.opener(filepath) as fp:
699 if mmap_threshold is not None:
697 if mmap_threshold is not None:
700 file_size = self.opener.fstat(fp).st_size
698 file_size = self.opener.fstat(fp).st_size
701 if file_size >= mmap_threshold:
699 if file_size >= mmap_threshold:
702 if size is not None:
700 if size is not None:
703 # avoid potentiel mmap crash
701 # avoid potentiel mmap crash
704 size = min(file_size, size)
702 size = min(file_size, size)
705 # TODO: should .close() to release resources without
703 # TODO: should .close() to release resources without
706 # relying on Python GC
704 # relying on Python GC
707 if size is None:
705 if size is None:
708 return util.buffer(util.mmapread(fp))
706 return util.buffer(util.mmapread(fp))
709 else:
707 else:
710 return util.buffer(util.mmapread(fp, size))
708 return util.buffer(util.mmapread(fp, size))
711 if size is None:
709 if size is None:
712 return fp.read()
710 return fp.read()
713 else:
711 else:
714 return fp.read(size)
712 return fp.read(size)
715 except FileNotFoundError:
713 except FileNotFoundError:
716 return b''
714 return b''
717
715
718 def get_streams(self, max_linkrev, force_inline=False):
716 def get_streams(self, max_linkrev, force_inline=False):
719 """return a list of streams that represent this revlog
717 """return a list of streams that represent this revlog
720
718
721 This is used by stream-clone to do bytes to bytes copies of a repository.
719 This is used by stream-clone to do bytes to bytes copies of a repository.
722
720
723 This streams data for all revisions that refer to a changelog revision up
721 This streams data for all revisions that refer to a changelog revision up
724 to `max_linkrev`.
722 to `max_linkrev`.
725
723
726 If `force_inline` is set, it enforces that the stream will represent an inline revlog.
724 If `force_inline` is set, it enforces that the stream will represent an inline revlog.
727
725
728 It returns is a list of three-tuple:
726 It returns is a list of three-tuple:
729
727
730 [
728 [
731 (filename, bytes_stream, stream_size),
729 (filename, bytes_stream, stream_size),
732 …
730 …
733 ]
731 ]
734 """
732 """
735 n = len(self)
733 n = len(self)
736 index = self.index
734 index = self.index
737 while n > 0:
735 while n > 0:
738 linkrev = index[n - 1][4]
736 linkrev = index[n - 1][4]
739 if linkrev < max_linkrev:
737 if linkrev < max_linkrev:
740 break
738 break
741 # note: this loop will rarely go through multiple iterations, since
739 # note: this loop will rarely go through multiple iterations, since
742 # it only traverses commits created during the current streaming
740 # it only traverses commits created during the current streaming
743 # pull operation.
741 # pull operation.
744 #
742 #
745 # If this become a problem, using a binary search should cap the
743 # If this become a problem, using a binary search should cap the
746 # runtime of this.
744 # runtime of this.
747 n = n - 1
745 n = n - 1
748 if n == 0:
746 if n == 0:
749 # no data to send
747 # no data to send
750 return []
748 return []
751 index_size = n * index.entry_size
749 index_size = n * index.entry_size
752 data_size = self.end(n - 1)
750 data_size = self.end(n - 1)
753
751
754 # XXX we might have been split (or stripped) since the object
752 # XXX we might have been split (or stripped) since the object
755 # initialization, We need to close this race too, but having a way to
753 # initialization, We need to close this race too, but having a way to
756 # pre-open the file we feed to the revlog and never closing them before
754 # pre-open the file we feed to the revlog and never closing them before
757 # we are done streaming.
755 # we are done streaming.
758
756
759 if self._inline:
757 if self._inline:
760
758
761 def get_stream():
759 def get_stream():
762 with self._indexfp() as fp:
760 with self._indexfp() as fp:
763 yield None
761 yield None
764 size = index_size + data_size
762 size = index_size + data_size
765 if size <= 65536:
763 if size <= 65536:
766 yield fp.read(size)
764 yield fp.read(size)
767 else:
765 else:
768 yield from util.filechunkiter(fp, limit=size)
766 yield from util.filechunkiter(fp, limit=size)
769
767
770 inline_stream = get_stream()
768 inline_stream = get_stream()
771 next(inline_stream)
769 next(inline_stream)
772 return [
770 return [
773 (self._indexfile, inline_stream, index_size + data_size),
771 (self._indexfile, inline_stream, index_size + data_size),
774 ]
772 ]
775 elif force_inline:
773 elif force_inline:
776
774
777 def get_stream():
775 def get_stream():
778 with self.reading():
776 with self.reading():
779 yield None
777 yield None
780
778
781 for rev in range(n):
779 for rev in range(n):
782 idx = self.index.entry_binary(rev)
780 idx = self.index.entry_binary(rev)
783 if rev == 0 and self._docket is None:
781 if rev == 0 and self._docket is None:
784 # re-inject the inline flag
782 # re-inject the inline flag
785 header = self._format_flags
783 header = self._format_flags
786 header |= self._format_version
784 header |= self._format_version
787 header |= FLAG_INLINE_DATA
785 header |= FLAG_INLINE_DATA
788 header = self.index.pack_header(header)
786 header = self.index.pack_header(header)
789 idx = header + idx
787 idx = header + idx
790 yield idx
788 yield idx
791 yield self._getsegmentforrevs(rev, rev)[1]
789 yield self._getsegmentforrevs(rev, rev)[1]
792
790
793 inline_stream = get_stream()
791 inline_stream = get_stream()
794 next(inline_stream)
792 next(inline_stream)
795 return [
793 return [
796 (self._indexfile, inline_stream, index_size + data_size),
794 (self._indexfile, inline_stream, index_size + data_size),
797 ]
795 ]
798 else:
796 else:
799
797
800 def get_index_stream():
798 def get_index_stream():
801 with self._indexfp() as fp:
799 with self._indexfp() as fp:
802 yield None
800 yield None
803 if index_size <= 65536:
801 if index_size <= 65536:
804 yield fp.read(index_size)
802 yield fp.read(index_size)
805 else:
803 else:
806 yield from util.filechunkiter(fp, limit=index_size)
804 yield from util.filechunkiter(fp, limit=index_size)
807
805
808 def get_data_stream():
806 def get_data_stream():
809 with self._datafp() as fp:
807 with self._datafp() as fp:
810 yield None
808 yield None
811 if data_size <= 65536:
809 if data_size <= 65536:
812 yield fp.read(data_size)
810 yield fp.read(data_size)
813 else:
811 else:
814 yield from util.filechunkiter(fp, limit=data_size)
812 yield from util.filechunkiter(fp, limit=data_size)
815
813
816 index_stream = get_index_stream()
814 index_stream = get_index_stream()
817 next(index_stream)
815 next(index_stream)
818 data_stream = get_data_stream()
816 data_stream = get_data_stream()
819 next(data_stream)
817 next(data_stream)
820 return [
818 return [
821 (self._datafile, data_stream, data_size),
819 (self._datafile, data_stream, data_size),
822 (self._indexfile, index_stream, index_size),
820 (self._indexfile, index_stream, index_size),
823 ]
821 ]
824
822
825 def _loadindex(self, docket=None):
823 def _loadindex(self, docket=None):
826
824
827 new_header, mmapindexthreshold, force_nodemap = self._init_opts()
825 new_header, mmapindexthreshold, force_nodemap = self._init_opts()
828
826
829 if self.postfix is not None:
827 if self.postfix is not None:
830 entry_point = b'%s.i.%s' % (self.radix, self.postfix)
828 entry_point = b'%s.i.%s' % (self.radix, self.postfix)
831 elif self._trypending and self.opener.exists(b'%s.i.a' % self.radix):
829 elif self._trypending and self.opener.exists(b'%s.i.a' % self.radix):
832 entry_point = b'%s.i.a' % self.radix
830 entry_point = b'%s.i.a' % self.radix
833 elif self._try_split and self.opener.exists(self._split_index_file):
831 elif self._try_split and self.opener.exists(self._split_index_file):
834 entry_point = self._split_index_file
832 entry_point = self._split_index_file
835 else:
833 else:
836 entry_point = b'%s.i' % self.radix
834 entry_point = b'%s.i' % self.radix
837
835
838 if docket is not None:
836 if docket is not None:
839 self._docket = docket
837 self._docket = docket
840 self._docket_file = entry_point
838 self._docket_file = entry_point
841 else:
839 else:
842 self._initempty = True
840 self._initempty = True
843 entry_data = self._get_data(entry_point, mmapindexthreshold)
841 entry_data = self._get_data(entry_point, mmapindexthreshold)
844 if len(entry_data) > 0:
842 if len(entry_data) > 0:
845 header = INDEX_HEADER.unpack(entry_data[:4])[0]
843 header = INDEX_HEADER.unpack(entry_data[:4])[0]
846 self._initempty = False
844 self._initempty = False
847 else:
845 else:
848 header = new_header
846 header = new_header
849
847
850 self._format_flags = header & ~0xFFFF
848 self._format_flags = header & ~0xFFFF
851 self._format_version = header & 0xFFFF
849 self._format_version = header & 0xFFFF
852
850
853 supported_flags = SUPPORTED_FLAGS.get(self._format_version)
851 supported_flags = SUPPORTED_FLAGS.get(self._format_version)
854 if supported_flags is None:
852 if supported_flags is None:
855 msg = _(b'unknown version (%d) in revlog %s')
853 msg = _(b'unknown version (%d) in revlog %s')
856 msg %= (self._format_version, self.display_id)
854 msg %= (self._format_version, self.display_id)
857 raise error.RevlogError(msg)
855 raise error.RevlogError(msg)
858 elif self._format_flags & ~supported_flags:
856 elif self._format_flags & ~supported_flags:
859 msg = _(b'unknown flags (%#04x) in version %d revlog %s')
857 msg = _(b'unknown flags (%#04x) in version %d revlog %s')
860 display_flag = self._format_flags >> 16
858 display_flag = self._format_flags >> 16
861 msg %= (display_flag, self._format_version, self.display_id)
859 msg %= (display_flag, self._format_version, self.display_id)
862 raise error.RevlogError(msg)
860 raise error.RevlogError(msg)
863
861
864 features = FEATURES_BY_VERSION[self._format_version]
862 features = FEATURES_BY_VERSION[self._format_version]
865 self._inline = features[b'inline'](self._format_flags)
863 self._inline = features[b'inline'](self._format_flags)
866 self.delta_config.general_delta = features[b'generaldelta'](
864 self.delta_config.general_delta = features[b'generaldelta'](
867 self._format_flags
865 self._format_flags
868 )
866 )
869 self.feature_config.has_side_data = features[b'sidedata']
867 self.feature_config.has_side_data = features[b'sidedata']
870
868
871 if not features[b'docket']:
869 if not features[b'docket']:
872 self._indexfile = entry_point
870 self._indexfile = entry_point
873 index_data = entry_data
871 index_data = entry_data
874 else:
872 else:
875 self._docket_file = entry_point
873 self._docket_file = entry_point
876 if self._initempty:
874 if self._initempty:
877 self._docket = docketutil.default_docket(self, header)
875 self._docket = docketutil.default_docket(self, header)
878 else:
876 else:
879 self._docket = docketutil.parse_docket(
877 self._docket = docketutil.parse_docket(
880 self, entry_data, use_pending=self._trypending
878 self, entry_data, use_pending=self._trypending
881 )
879 )
882
880
883 if self._docket is not None:
881 if self._docket is not None:
884 self._indexfile = self._docket.index_filepath()
882 self._indexfile = self._docket.index_filepath()
885 index_data = b''
883 index_data = b''
886 index_size = self._docket.index_end
884 index_size = self._docket.index_end
887 if index_size > 0:
885 if index_size > 0:
888 index_data = self._get_data(
886 index_data = self._get_data(
889 self._indexfile, mmapindexthreshold, size=index_size
887 self._indexfile, mmapindexthreshold, size=index_size
890 )
888 )
891 if len(index_data) < index_size:
889 if len(index_data) < index_size:
892 msg = _(b'too few index data for %s: got %d, expected %d')
890 msg = _(b'too few index data for %s: got %d, expected %d')
893 msg %= (self.display_id, len(index_data), index_size)
891 msg %= (self.display_id, len(index_data), index_size)
894 raise error.RevlogError(msg)
892 raise error.RevlogError(msg)
895
893
896 self._inline = False
894 self._inline = False
897 # generaldelta implied by version 2 revlogs.
895 # generaldelta implied by version 2 revlogs.
898 self.delta_config.general_delta = True
896 self.delta_config.general_delta = True
899 # the logic for persistent nodemap will be dealt with within the
897 # the logic for persistent nodemap will be dealt with within the
900 # main docket, so disable it for now.
898 # main docket, so disable it for now.
901 self._nodemap_file = None
899 self._nodemap_file = None
902
900
903 if self._docket is not None:
901 if self._docket is not None:
904 self._datafile = self._docket.data_filepath()
902 self._datafile = self._docket.data_filepath()
905 self._sidedatafile = self._docket.sidedata_filepath()
903 self._sidedatafile = self._docket.sidedata_filepath()
906 elif self.postfix is None:
904 elif self.postfix is None:
907 self._datafile = b'%s.d' % self.radix
905 self._datafile = b'%s.d' % self.radix
908 else:
906 else:
909 self._datafile = b'%s.d.%s' % (self.radix, self.postfix)
907 self._datafile = b'%s.d.%s' % (self.radix, self.postfix)
910
908
911 self.nodeconstants = sha1nodeconstants
909 self.nodeconstants = sha1nodeconstants
912 self.nullid = self.nodeconstants.nullid
910 self.nullid = self.nodeconstants.nullid
913
911
914 # sparse-revlog can't be on without general-delta (issue6056)
912 # sparse-revlog can't be on without general-delta (issue6056)
915 if not self._generaldelta:
913 if not self._generaldelta:
916 self.delta_config.sparse_revlog = False
914 self.delta_config.sparse_revlog = False
917
915
918 self._storedeltachains = True
916 self._storedeltachains = True
919
917
920 devel_nodemap = (
918 devel_nodemap = (
921 self._nodemap_file
919 self._nodemap_file
922 and force_nodemap
920 and force_nodemap
923 and parse_index_v1_nodemap is not None
921 and parse_index_v1_nodemap is not None
924 )
922 )
925
923
926 use_rust_index = False
924 use_rust_index = False
927 if rustrevlog is not None:
925 if rustrevlog is not None:
928 if self._nodemap_file is not None:
926 if self._nodemap_file is not None:
929 use_rust_index = True
927 use_rust_index = True
930 else:
928 else:
931 use_rust_index = self.opener.options.get(b'rust.index')
929 use_rust_index = self.opener.options.get(b'rust.index')
932
930
933 self._parse_index = parse_index_v1
931 self._parse_index = parse_index_v1
934 if self._format_version == REVLOGV0:
932 if self._format_version == REVLOGV0:
935 self._parse_index = revlogv0.parse_index_v0
933 self._parse_index = revlogv0.parse_index_v0
936 elif self._format_version == REVLOGV2:
934 elif self._format_version == REVLOGV2:
937 self._parse_index = parse_index_v2
935 self._parse_index = parse_index_v2
938 elif self._format_version == CHANGELOGV2:
936 elif self._format_version == CHANGELOGV2:
939 self._parse_index = parse_index_cl_v2
937 self._parse_index = parse_index_cl_v2
940 elif devel_nodemap:
938 elif devel_nodemap:
941 self._parse_index = parse_index_v1_nodemap
939 self._parse_index = parse_index_v1_nodemap
942 elif use_rust_index:
940 elif use_rust_index:
943 self._parse_index = parse_index_v1_mixed
941 self._parse_index = parse_index_v1_mixed
944 try:
942 try:
945 d = self._parse_index(index_data, self._inline)
943 d = self._parse_index(index_data, self._inline)
946 index, chunkcache = d
944 index, chunkcache = d
947 use_nodemap = (
945 use_nodemap = (
948 not self._inline
946 not self._inline
949 and self._nodemap_file is not None
947 and self._nodemap_file is not None
950 and hasattr(index, 'update_nodemap_data')
948 and hasattr(index, 'update_nodemap_data')
951 )
949 )
952 if use_nodemap:
950 if use_nodemap:
953 nodemap_data = nodemaputil.persisted_data(self)
951 nodemap_data = nodemaputil.persisted_data(self)
954 if nodemap_data is not None:
952 if nodemap_data is not None:
955 docket = nodemap_data[0]
953 docket = nodemap_data[0]
956 if (
954 if (
957 len(d[0]) > docket.tip_rev
955 len(d[0]) > docket.tip_rev
958 and d[0][docket.tip_rev][7] == docket.tip_node
956 and d[0][docket.tip_rev][7] == docket.tip_node
959 ):
957 ):
960 # no changelog tampering
958 # no changelog tampering
961 self._nodemap_docket = docket
959 self._nodemap_docket = docket
962 index.update_nodemap_data(*nodemap_data)
960 index.update_nodemap_data(*nodemap_data)
963 except (ValueError, IndexError):
961 except (ValueError, IndexError):
964 raise error.RevlogError(
962 raise error.RevlogError(
965 _(b"index %s is corrupted") % self.display_id
963 _(b"index %s is corrupted") % self.display_id
966 )
964 )
967 self.index = index
965 self.index = index
968 self._segmentfile = randomaccessfile.randomaccessfile(
966 self._segmentfile = randomaccessfile.randomaccessfile(
969 self.opener,
967 self.opener,
970 (self._indexfile if self._inline else self._datafile),
968 (self._indexfile if self._inline else self._datafile),
971 self._chunkcachesize,
969 self._chunkcachesize,
972 chunkcache,
970 chunkcache,
973 )
971 )
974 self._segmentfile_sidedata = randomaccessfile.randomaccessfile(
972 self._segmentfile_sidedata = randomaccessfile.randomaccessfile(
975 self.opener,
973 self.opener,
976 self._sidedatafile,
974 self._sidedatafile,
977 self._chunkcachesize,
975 self._chunkcachesize,
978 )
976 )
979 # revnum -> (chain-length, sum-delta-length)
977 # revnum -> (chain-length, sum-delta-length)
980 self._chaininfocache = util.lrucachedict(500)
978 self._chaininfocache = util.lrucachedict(500)
981 # revlog header -> revlog compressor
979 # revlog header -> revlog compressor
982 self._decompressors = {}
980 self._decompressors = {}
983
981
984 def get_revlog(self):
982 def get_revlog(self):
985 """simple function to mirror API of other not-really-revlog API"""
983 """simple function to mirror API of other not-really-revlog API"""
986 return self
984 return self
987
985
988 @util.propertycache
986 @util.propertycache
989 def revlog_kind(self):
987 def revlog_kind(self):
990 return self.target[0]
988 return self.target[0]
991
989
992 @util.propertycache
990 @util.propertycache
993 def display_id(self):
991 def display_id(self):
994 """The public facing "ID" of the revlog that we use in message"""
992 """The public facing "ID" of the revlog that we use in message"""
995 if self.revlog_kind == KIND_FILELOG:
993 if self.revlog_kind == KIND_FILELOG:
996 # Reference the file without the "data/" prefix, so it is familiar
994 # Reference the file without the "data/" prefix, so it is familiar
997 # to the user.
995 # to the user.
998 return self.target[1]
996 return self.target[1]
999 else:
997 else:
1000 return self.radix
998 return self.radix
1001
999
1002 def _get_decompressor(self, t):
1000 def _get_decompressor(self, t):
1003 try:
1001 try:
1004 compressor = self._decompressors[t]
1002 compressor = self._decompressors[t]
1005 except KeyError:
1003 except KeyError:
1006 try:
1004 try:
1007 engine = util.compengines.forrevlogheader(t)
1005 engine = util.compengines.forrevlogheader(t)
1008 compressor = engine.revlogcompressor(self._compengineopts)
1006 compressor = engine.revlogcompressor(self._compengineopts)
1009 self._decompressors[t] = compressor
1007 self._decompressors[t] = compressor
1010 except KeyError:
1008 except KeyError:
1011 raise error.RevlogError(
1009 raise error.RevlogError(
1012 _(b'unknown compression type %s') % binascii.hexlify(t)
1010 _(b'unknown compression type %s') % binascii.hexlify(t)
1013 )
1011 )
1014 return compressor
1012 return compressor
1015
1013
1016 @util.propertycache
1014 @util.propertycache
1017 def _compressor(self):
1015 def _compressor(self):
1018 engine = util.compengines[self._compengine]
1016 engine = util.compengines[self._compengine]
1019 return engine.revlogcompressor(self._compengineopts)
1017 return engine.revlogcompressor(self._compengineopts)
1020
1018
1021 @util.propertycache
1019 @util.propertycache
1022 def _decompressor(self):
1020 def _decompressor(self):
1023 """the default decompressor"""
1021 """the default decompressor"""
1024 if self._docket is None:
1022 if self._docket is None:
1025 return None
1023 return None
1026 t = self._docket.default_compression_header
1024 t = self._docket.default_compression_header
1027 c = self._get_decompressor(t)
1025 c = self._get_decompressor(t)
1028 return c.decompress
1026 return c.decompress
1029
1027
1030 def _indexfp(self):
1028 def _indexfp(self):
1031 """file object for the revlog's index file"""
1029 """file object for the revlog's index file"""
1032 return self.opener(self._indexfile, mode=b"r")
1030 return self.opener(self._indexfile, mode=b"r")
1033
1031
1034 def __index_write_fp(self):
1032 def __index_write_fp(self):
1035 # You should not use this directly and use `_writing` instead
1033 # You should not use this directly and use `_writing` instead
1036 try:
1034 try:
1037 f = self.opener(
1035 f = self.opener(
1038 self._indexfile, mode=b"r+", checkambig=self._checkambig
1036 self._indexfile, mode=b"r+", checkambig=self._checkambig
1039 )
1037 )
1040 if self._docket is None:
1038 if self._docket is None:
1041 f.seek(0, os.SEEK_END)
1039 f.seek(0, os.SEEK_END)
1042 else:
1040 else:
1043 f.seek(self._docket.index_end, os.SEEK_SET)
1041 f.seek(self._docket.index_end, os.SEEK_SET)
1044 return f
1042 return f
1045 except FileNotFoundError:
1043 except FileNotFoundError:
1046 return self.opener(
1044 return self.opener(
1047 self._indexfile, mode=b"w+", checkambig=self._checkambig
1045 self._indexfile, mode=b"w+", checkambig=self._checkambig
1048 )
1046 )
1049
1047
1050 def __index_new_fp(self):
1048 def __index_new_fp(self):
1051 # You should not use this unless you are upgrading from inline revlog
1049 # You should not use this unless you are upgrading from inline revlog
1052 return self.opener(
1050 return self.opener(
1053 self._indexfile,
1051 self._indexfile,
1054 mode=b"w",
1052 mode=b"w",
1055 checkambig=self._checkambig,
1053 checkambig=self._checkambig,
1056 atomictemp=True,
1054 atomictemp=True,
1057 )
1055 )
1058
1056
1059 def _datafp(self, mode=b'r'):
1057 def _datafp(self, mode=b'r'):
1060 """file object for the revlog's data file"""
1058 """file object for the revlog's data file"""
1061 return self.opener(self._datafile, mode=mode)
1059 return self.opener(self._datafile, mode=mode)
1062
1060
1063 @contextlib.contextmanager
1061 @contextlib.contextmanager
1064 def _sidedatareadfp(self):
1062 def _sidedatareadfp(self):
1065 """file object suitable to read sidedata"""
1063 """file object suitable to read sidedata"""
1066 if self._writinghandles:
1064 if self._writinghandles:
1067 yield self._writinghandles[2]
1065 yield self._writinghandles[2]
1068 else:
1066 else:
1069 with self.opener(self._sidedatafile) as fp:
1067 with self.opener(self._sidedatafile) as fp:
1070 yield fp
1068 yield fp
1071
1069
1072 def tiprev(self):
1070 def tiprev(self):
1073 return len(self.index) - 1
1071 return len(self.index) - 1
1074
1072
1075 def tip(self):
1073 def tip(self):
1076 return self.node(self.tiprev())
1074 return self.node(self.tiprev())
1077
1075
1078 def __contains__(self, rev):
1076 def __contains__(self, rev):
1079 return 0 <= rev < len(self)
1077 return 0 <= rev < len(self)
1080
1078
1081 def __len__(self):
1079 def __len__(self):
1082 return len(self.index)
1080 return len(self.index)
1083
1081
1084 def __iter__(self):
1082 def __iter__(self):
1085 return iter(range(len(self)))
1083 return iter(range(len(self)))
1086
1084
1087 def revs(self, start=0, stop=None):
1085 def revs(self, start=0, stop=None):
1088 """iterate over all rev in this revlog (from start to stop)"""
1086 """iterate over all rev in this revlog (from start to stop)"""
1089 return storageutil.iterrevs(len(self), start=start, stop=stop)
1087 return storageutil.iterrevs(len(self), start=start, stop=stop)
1090
1088
1091 def hasnode(self, node):
1089 def hasnode(self, node):
1092 try:
1090 try:
1093 self.rev(node)
1091 self.rev(node)
1094 return True
1092 return True
1095 except KeyError:
1093 except KeyError:
1096 return False
1094 return False
1097
1095
1098 def _candelta(self, baserev, rev):
1096 def _candelta(self, baserev, rev):
1099 """whether two revisions (baserev, rev) can be delta-ed or not"""
1097 """whether two revisions (baserev, rev) can be delta-ed or not"""
1100 # Disable delta if either rev requires a content-changing flag
1098 # Disable delta if either rev requires a content-changing flag
1101 # processor (ex. LFS). This is because such flag processor can alter
1099 # processor (ex. LFS). This is because such flag processor can alter
1102 # the rawtext content that the delta will be based on, and two clients
1100 # the rawtext content that the delta will be based on, and two clients
1103 # could have a same revlog node with different flags (i.e. different
1101 # could have a same revlog node with different flags (i.e. different
1104 # rawtext contents) and the delta could be incompatible.
1102 # rawtext contents) and the delta could be incompatible.
1105 if (self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS) or (
1103 if (self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS) or (
1106 self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS
1104 self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS
1107 ):
1105 ):
1108 return False
1106 return False
1109 return True
1107 return True
1110
1108
1111 def update_caches(self, transaction):
1109 def update_caches(self, transaction):
1112 """update on disk cache
1110 """update on disk cache
1113
1111
1114 If a transaction is passed, the update may be delayed to transaction
1112 If a transaction is passed, the update may be delayed to transaction
1115 commit."""
1113 commit."""
1116 if self._nodemap_file is not None:
1114 if self._nodemap_file is not None:
1117 if transaction is None:
1115 if transaction is None:
1118 nodemaputil.update_persistent_nodemap(self)
1116 nodemaputil.update_persistent_nodemap(self)
1119 else:
1117 else:
1120 nodemaputil.setup_persistent_nodemap(transaction, self)
1118 nodemaputil.setup_persistent_nodemap(transaction, self)
1121
1119
1122 def clearcaches(self):
1120 def clearcaches(self):
1123 """Clear in-memory caches"""
1121 """Clear in-memory caches"""
1124 self._revisioncache = None
1122 self._revisioncache = None
1125 self._chainbasecache.clear()
1123 self._chainbasecache.clear()
1126 self._segmentfile.clear_cache()
1124 self._segmentfile.clear_cache()
1127 self._segmentfile_sidedata.clear_cache()
1125 self._segmentfile_sidedata.clear_cache()
1128 self._pcache = {}
1126 self._pcache = {}
1129 self._nodemap_docket = None
1127 self._nodemap_docket = None
1130 self.index.clearcaches()
1128 self.index.clearcaches()
1131 # The python code is the one responsible for validating the docket, we
1129 # The python code is the one responsible for validating the docket, we
1132 # end up having to refresh it here.
1130 # end up having to refresh it here.
1133 use_nodemap = (
1131 use_nodemap = (
1134 not self._inline
1132 not self._inline
1135 and self._nodemap_file is not None
1133 and self._nodemap_file is not None
1136 and hasattr(self.index, 'update_nodemap_data')
1134 and hasattr(self.index, 'update_nodemap_data')
1137 )
1135 )
1138 if use_nodemap:
1136 if use_nodemap:
1139 nodemap_data = nodemaputil.persisted_data(self)
1137 nodemap_data = nodemaputil.persisted_data(self)
1140 if nodemap_data is not None:
1138 if nodemap_data is not None:
1141 self._nodemap_docket = nodemap_data[0]
1139 self._nodemap_docket = nodemap_data[0]
1142 self.index.update_nodemap_data(*nodemap_data)
1140 self.index.update_nodemap_data(*nodemap_data)
1143
1141
1144 def rev(self, node):
1142 def rev(self, node):
1145 """return the revision number associated with a <nodeid>"""
1143 """return the revision number associated with a <nodeid>"""
1146 try:
1144 try:
1147 return self.index.rev(node)
1145 return self.index.rev(node)
1148 except TypeError:
1146 except TypeError:
1149 raise
1147 raise
1150 except error.RevlogError:
1148 except error.RevlogError:
1151 # parsers.c radix tree lookup failed
1149 # parsers.c radix tree lookup failed
1152 if (
1150 if (
1153 node == self.nodeconstants.wdirid
1151 node == self.nodeconstants.wdirid
1154 or node in self.nodeconstants.wdirfilenodeids
1152 or node in self.nodeconstants.wdirfilenodeids
1155 ):
1153 ):
1156 raise error.WdirUnsupported
1154 raise error.WdirUnsupported
1157 raise error.LookupError(node, self.display_id, _(b'no node'))
1155 raise error.LookupError(node, self.display_id, _(b'no node'))
1158
1156
1159 # Accessors for index entries.
1157 # Accessors for index entries.
1160
1158
1161 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
1159 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
1162 # are flags.
1160 # are flags.
1163 def start(self, rev):
1161 def start(self, rev):
1164 return int(self.index[rev][0] >> 16)
1162 return int(self.index[rev][0] >> 16)
1165
1163
1166 def sidedata_cut_off(self, rev):
1164 def sidedata_cut_off(self, rev):
1167 sd_cut_off = self.index[rev][8]
1165 sd_cut_off = self.index[rev][8]
1168 if sd_cut_off != 0:
1166 if sd_cut_off != 0:
1169 return sd_cut_off
1167 return sd_cut_off
1170 # This is some annoying dance, because entries without sidedata
1168 # This is some annoying dance, because entries without sidedata
1171 # currently use 0 as their ofsset. (instead of previous-offset +
1169 # currently use 0 as their ofsset. (instead of previous-offset +
1172 # previous-size)
1170 # previous-size)
1173 #
1171 #
1174 # We should reconsider this sidedata β†’ 0 sidata_offset policy.
1172 # We should reconsider this sidedata β†’ 0 sidata_offset policy.
1175 # In the meantime, we need this.
1173 # In the meantime, we need this.
1176 while 0 <= rev:
1174 while 0 <= rev:
1177 e = self.index[rev]
1175 e = self.index[rev]
1178 if e[9] != 0:
1176 if e[9] != 0:
1179 return e[8] + e[9]
1177 return e[8] + e[9]
1180 rev -= 1
1178 rev -= 1
1181 return 0
1179 return 0
1182
1180
1183 def flags(self, rev):
1181 def flags(self, rev):
1184 return self.index[rev][0] & 0xFFFF
1182 return self.index[rev][0] & 0xFFFF
1185
1183
1186 def length(self, rev):
1184 def length(self, rev):
1187 return self.index[rev][1]
1185 return self.index[rev][1]
1188
1186
1189 def sidedata_length(self, rev):
1187 def sidedata_length(self, rev):
1190 if not self.hassidedata:
1188 if not self.hassidedata:
1191 return 0
1189 return 0
1192 return self.index[rev][9]
1190 return self.index[rev][9]
1193
1191
1194 def rawsize(self, rev):
1192 def rawsize(self, rev):
1195 """return the length of the uncompressed text for a given revision"""
1193 """return the length of the uncompressed text for a given revision"""
1196 l = self.index[rev][2]
1194 l = self.index[rev][2]
1197 if l >= 0:
1195 if l >= 0:
1198 return l
1196 return l
1199
1197
1200 t = self.rawdata(rev)
1198 t = self.rawdata(rev)
1201 return len(t)
1199 return len(t)
1202
1200
1203 def size(self, rev):
1201 def size(self, rev):
1204 """length of non-raw text (processed by a "read" flag processor)"""
1202 """length of non-raw text (processed by a "read" flag processor)"""
1205 # fast path: if no "read" flag processor could change the content,
1203 # fast path: if no "read" flag processor could change the content,
1206 # size is rawsize. note: ELLIPSIS is known to not change the content.
1204 # size is rawsize. note: ELLIPSIS is known to not change the content.
1207 flags = self.flags(rev)
1205 flags = self.flags(rev)
1208 if flags & (flagutil.REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
1206 if flags & (flagutil.REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
1209 return self.rawsize(rev)
1207 return self.rawsize(rev)
1210
1208
1211 return len(self.revision(rev))
1209 return len(self.revision(rev))
1212
1210
1213 def fast_rank(self, rev):
1211 def fast_rank(self, rev):
1214 """Return the rank of a revision if already known, or None otherwise.
1212 """Return the rank of a revision if already known, or None otherwise.
1215
1213
1216 The rank of a revision is the size of the sub-graph it defines as a
1214 The rank of a revision is the size of the sub-graph it defines as a
1217 head. Equivalently, the rank of a revision `r` is the size of the set
1215 head. Equivalently, the rank of a revision `r` is the size of the set
1218 `ancestors(r)`, `r` included.
1216 `ancestors(r)`, `r` included.
1219
1217
1220 This method returns the rank retrieved from the revlog in constant
1218 This method returns the rank retrieved from the revlog in constant
1221 time. It makes no attempt at computing unknown values for versions of
1219 time. It makes no attempt at computing unknown values for versions of
1222 the revlog which do not persist the rank.
1220 the revlog which do not persist the rank.
1223 """
1221 """
1224 rank = self.index[rev][ENTRY_RANK]
1222 rank = self.index[rev][ENTRY_RANK]
1225 if self._format_version != CHANGELOGV2 or rank == RANK_UNKNOWN:
1223 if self._format_version != CHANGELOGV2 or rank == RANK_UNKNOWN:
1226 return None
1224 return None
1227 if rev == nullrev:
1225 if rev == nullrev:
1228 return 0 # convention
1226 return 0 # convention
1229 return rank
1227 return rank
1230
1228
1231 def chainbase(self, rev):
1229 def chainbase(self, rev):
1232 base = self._chainbasecache.get(rev)
1230 base = self._chainbasecache.get(rev)
1233 if base is not None:
1231 if base is not None:
1234 return base
1232 return base
1235
1233
1236 index = self.index
1234 index = self.index
1237 iterrev = rev
1235 iterrev = rev
1238 base = index[iterrev][3]
1236 base = index[iterrev][3]
1239 while base != iterrev:
1237 while base != iterrev:
1240 iterrev = base
1238 iterrev = base
1241 base = index[iterrev][3]
1239 base = index[iterrev][3]
1242
1240
1243 self._chainbasecache[rev] = base
1241 self._chainbasecache[rev] = base
1244 return base
1242 return base
1245
1243
1246 def linkrev(self, rev):
1244 def linkrev(self, rev):
1247 return self.index[rev][4]
1245 return self.index[rev][4]
1248
1246
1249 def parentrevs(self, rev):
1247 def parentrevs(self, rev):
1250 try:
1248 try:
1251 entry = self.index[rev]
1249 entry = self.index[rev]
1252 except IndexError:
1250 except IndexError:
1253 if rev == wdirrev:
1251 if rev == wdirrev:
1254 raise error.WdirUnsupported
1252 raise error.WdirUnsupported
1255 raise
1253 raise
1256
1254
1257 if self.canonical_parent_order and entry[5] == nullrev:
1255 if self.canonical_parent_order and entry[5] == nullrev:
1258 return entry[6], entry[5]
1256 return entry[6], entry[5]
1259 else:
1257 else:
1260 return entry[5], entry[6]
1258 return entry[5], entry[6]
1261
1259
1262 # fast parentrevs(rev) where rev isn't filtered
1260 # fast parentrevs(rev) where rev isn't filtered
1263 _uncheckedparentrevs = parentrevs
1261 _uncheckedparentrevs = parentrevs
1264
1262
1265 def node(self, rev):
1263 def node(self, rev):
1266 try:
1264 try:
1267 return self.index[rev][7]
1265 return self.index[rev][7]
1268 except IndexError:
1266 except IndexError:
1269 if rev == wdirrev:
1267 if rev == wdirrev:
1270 raise error.WdirUnsupported
1268 raise error.WdirUnsupported
1271 raise
1269 raise
1272
1270
1273 # Derived from index values.
1271 # Derived from index values.
1274
1272
1275 def end(self, rev):
1273 def end(self, rev):
1276 return self.start(rev) + self.length(rev)
1274 return self.start(rev) + self.length(rev)
1277
1275
1278 def parents(self, node):
1276 def parents(self, node):
1279 i = self.index
1277 i = self.index
1280 d = i[self.rev(node)]
1278 d = i[self.rev(node)]
1281 # inline node() to avoid function call overhead
1279 # inline node() to avoid function call overhead
1282 if self.canonical_parent_order and d[5] == self.nullid:
1280 if self.canonical_parent_order and d[5] == self.nullid:
1283 return i[d[6]][7], i[d[5]][7]
1281 return i[d[6]][7], i[d[5]][7]
1284 else:
1282 else:
1285 return i[d[5]][7], i[d[6]][7]
1283 return i[d[5]][7], i[d[6]][7]
1286
1284
1287 def chainlen(self, rev):
1285 def chainlen(self, rev):
1288 return self._chaininfo(rev)[0]
1286 return self._chaininfo(rev)[0]
1289
1287
1290 def _chaininfo(self, rev):
1288 def _chaininfo(self, rev):
1291 chaininfocache = self._chaininfocache
1289 chaininfocache = self._chaininfocache
1292 if rev in chaininfocache:
1290 if rev in chaininfocache:
1293 return chaininfocache[rev]
1291 return chaininfocache[rev]
1294 index = self.index
1292 index = self.index
1295 generaldelta = self._generaldelta
1293 generaldelta = self._generaldelta
1296 iterrev = rev
1294 iterrev = rev
1297 e = index[iterrev]
1295 e = index[iterrev]
1298 clen = 0
1296 clen = 0
1299 compresseddeltalen = 0
1297 compresseddeltalen = 0
1300 while iterrev != e[3]:
1298 while iterrev != e[3]:
1301 clen += 1
1299 clen += 1
1302 compresseddeltalen += e[1]
1300 compresseddeltalen += e[1]
1303 if generaldelta:
1301 if generaldelta:
1304 iterrev = e[3]
1302 iterrev = e[3]
1305 else:
1303 else:
1306 iterrev -= 1
1304 iterrev -= 1
1307 if iterrev in chaininfocache:
1305 if iterrev in chaininfocache:
1308 t = chaininfocache[iterrev]
1306 t = chaininfocache[iterrev]
1309 clen += t[0]
1307 clen += t[0]
1310 compresseddeltalen += t[1]
1308 compresseddeltalen += t[1]
1311 break
1309 break
1312 e = index[iterrev]
1310 e = index[iterrev]
1313 else:
1311 else:
1314 # Add text length of base since decompressing that also takes
1312 # Add text length of base since decompressing that also takes
1315 # work. For cache hits the length is already included.
1313 # work. For cache hits the length is already included.
1316 compresseddeltalen += e[1]
1314 compresseddeltalen += e[1]
1317 r = (clen, compresseddeltalen)
1315 r = (clen, compresseddeltalen)
1318 chaininfocache[rev] = r
1316 chaininfocache[rev] = r
1319 return r
1317 return r
1320
1318
1321 def _deltachain(self, rev, stoprev=None):
1319 def _deltachain(self, rev, stoprev=None):
1322 """Obtain the delta chain for a revision.
1320 """Obtain the delta chain for a revision.
1323
1321
1324 ``stoprev`` specifies a revision to stop at. If not specified, we
1322 ``stoprev`` specifies a revision to stop at. If not specified, we
1325 stop at the base of the chain.
1323 stop at the base of the chain.
1326
1324
1327 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
1325 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
1328 revs in ascending order and ``stopped`` is a bool indicating whether
1326 revs in ascending order and ``stopped`` is a bool indicating whether
1329 ``stoprev`` was hit.
1327 ``stoprev`` was hit.
1330 """
1328 """
1331 # Try C implementation.
1329 # Try C implementation.
1332 try:
1330 try:
1333 return self.index.deltachain(rev, stoprev, self._generaldelta)
1331 return self.index.deltachain(rev, stoprev, self._generaldelta)
1334 except AttributeError:
1332 except AttributeError:
1335 pass
1333 pass
1336
1334
1337 chain = []
1335 chain = []
1338
1336
1339 # Alias to prevent attribute lookup in tight loop.
1337 # Alias to prevent attribute lookup in tight loop.
1340 index = self.index
1338 index = self.index
1341 generaldelta = self._generaldelta
1339 generaldelta = self._generaldelta
1342
1340
1343 iterrev = rev
1341 iterrev = rev
1344 e = index[iterrev]
1342 e = index[iterrev]
1345 while iterrev != e[3] and iterrev != stoprev:
1343 while iterrev != e[3] and iterrev != stoprev:
1346 chain.append(iterrev)
1344 chain.append(iterrev)
1347 if generaldelta:
1345 if generaldelta:
1348 iterrev = e[3]
1346 iterrev = e[3]
1349 else:
1347 else:
1350 iterrev -= 1
1348 iterrev -= 1
1351 e = index[iterrev]
1349 e = index[iterrev]
1352
1350
1353 if iterrev == stoprev:
1351 if iterrev == stoprev:
1354 stopped = True
1352 stopped = True
1355 else:
1353 else:
1356 chain.append(iterrev)
1354 chain.append(iterrev)
1357 stopped = False
1355 stopped = False
1358
1356
1359 chain.reverse()
1357 chain.reverse()
1360 return chain, stopped
1358 return chain, stopped
1361
1359
1362 def ancestors(self, revs, stoprev=0, inclusive=False):
1360 def ancestors(self, revs, stoprev=0, inclusive=False):
1363 """Generate the ancestors of 'revs' in reverse revision order.
1361 """Generate the ancestors of 'revs' in reverse revision order.
1364 Does not generate revs lower than stoprev.
1362 Does not generate revs lower than stoprev.
1365
1363
1366 See the documentation for ancestor.lazyancestors for more details."""
1364 See the documentation for ancestor.lazyancestors for more details."""
1367
1365
1368 # first, make sure start revisions aren't filtered
1366 # first, make sure start revisions aren't filtered
1369 revs = list(revs)
1367 revs = list(revs)
1370 checkrev = self.node
1368 checkrev = self.node
1371 for r in revs:
1369 for r in revs:
1372 checkrev(r)
1370 checkrev(r)
1373 # and we're sure ancestors aren't filtered as well
1371 # and we're sure ancestors aren't filtered as well
1374
1372
1375 if rustancestor is not None and self.index.rust_ext_compat:
1373 if rustancestor is not None and self.index.rust_ext_compat:
1376 lazyancestors = rustancestor.LazyAncestors
1374 lazyancestors = rustancestor.LazyAncestors
1377 arg = self.index
1375 arg = self.index
1378 else:
1376 else:
1379 lazyancestors = ancestor.lazyancestors
1377 lazyancestors = ancestor.lazyancestors
1380 arg = self._uncheckedparentrevs
1378 arg = self._uncheckedparentrevs
1381 return lazyancestors(arg, revs, stoprev=stoprev, inclusive=inclusive)
1379 return lazyancestors(arg, revs, stoprev=stoprev, inclusive=inclusive)
1382
1380
1383 def descendants(self, revs):
1381 def descendants(self, revs):
1384 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
1382 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
1385
1383
1386 def findcommonmissing(self, common=None, heads=None):
1384 def findcommonmissing(self, common=None, heads=None):
1387 """Return a tuple of the ancestors of common and the ancestors of heads
1385 """Return a tuple of the ancestors of common and the ancestors of heads
1388 that are not ancestors of common. In revset terminology, we return the
1386 that are not ancestors of common. In revset terminology, we return the
1389 tuple:
1387 tuple:
1390
1388
1391 ::common, (::heads) - (::common)
1389 ::common, (::heads) - (::common)
1392
1390
1393 The list is sorted by revision number, meaning it is
1391 The list is sorted by revision number, meaning it is
1394 topologically sorted.
1392 topologically sorted.
1395
1393
1396 'heads' and 'common' are both lists of node IDs. If heads is
1394 'heads' and 'common' are both lists of node IDs. If heads is
1397 not supplied, uses all of the revlog's heads. If common is not
1395 not supplied, uses all of the revlog's heads. If common is not
1398 supplied, uses nullid."""
1396 supplied, uses nullid."""
1399 if common is None:
1397 if common is None:
1400 common = [self.nullid]
1398 common = [self.nullid]
1401 if heads is None:
1399 if heads is None:
1402 heads = self.heads()
1400 heads = self.heads()
1403
1401
1404 common = [self.rev(n) for n in common]
1402 common = [self.rev(n) for n in common]
1405 heads = [self.rev(n) for n in heads]
1403 heads = [self.rev(n) for n in heads]
1406
1404
1407 # we want the ancestors, but inclusive
1405 # we want the ancestors, but inclusive
1408 class lazyset:
1406 class lazyset:
1409 def __init__(self, lazyvalues):
1407 def __init__(self, lazyvalues):
1410 self.addedvalues = set()
1408 self.addedvalues = set()
1411 self.lazyvalues = lazyvalues
1409 self.lazyvalues = lazyvalues
1412
1410
1413 def __contains__(self, value):
1411 def __contains__(self, value):
1414 return value in self.addedvalues or value in self.lazyvalues
1412 return value in self.addedvalues or value in self.lazyvalues
1415
1413
1416 def __iter__(self):
1414 def __iter__(self):
1417 added = self.addedvalues
1415 added = self.addedvalues
1418 for r in added:
1416 for r in added:
1419 yield r
1417 yield r
1420 for r in self.lazyvalues:
1418 for r in self.lazyvalues:
1421 if not r in added:
1419 if not r in added:
1422 yield r
1420 yield r
1423
1421
1424 def add(self, value):
1422 def add(self, value):
1425 self.addedvalues.add(value)
1423 self.addedvalues.add(value)
1426
1424
1427 def update(self, values):
1425 def update(self, values):
1428 self.addedvalues.update(values)
1426 self.addedvalues.update(values)
1429
1427
1430 has = lazyset(self.ancestors(common))
1428 has = lazyset(self.ancestors(common))
1431 has.add(nullrev)
1429 has.add(nullrev)
1432 has.update(common)
1430 has.update(common)
1433
1431
1434 # take all ancestors from heads that aren't in has
1432 # take all ancestors from heads that aren't in has
1435 missing = set()
1433 missing = set()
1436 visit = collections.deque(r for r in heads if r not in has)
1434 visit = collections.deque(r for r in heads if r not in has)
1437 while visit:
1435 while visit:
1438 r = visit.popleft()
1436 r = visit.popleft()
1439 if r in missing:
1437 if r in missing:
1440 continue
1438 continue
1441 else:
1439 else:
1442 missing.add(r)
1440 missing.add(r)
1443 for p in self.parentrevs(r):
1441 for p in self.parentrevs(r):
1444 if p not in has:
1442 if p not in has:
1445 visit.append(p)
1443 visit.append(p)
1446 missing = list(missing)
1444 missing = list(missing)
1447 missing.sort()
1445 missing.sort()
1448 return has, [self.node(miss) for miss in missing]
1446 return has, [self.node(miss) for miss in missing]
1449
1447
1450 def incrementalmissingrevs(self, common=None):
1448 def incrementalmissingrevs(self, common=None):
1451 """Return an object that can be used to incrementally compute the
1449 """Return an object that can be used to incrementally compute the
1452 revision numbers of the ancestors of arbitrary sets that are not
1450 revision numbers of the ancestors of arbitrary sets that are not
1453 ancestors of common. This is an ancestor.incrementalmissingancestors
1451 ancestors of common. This is an ancestor.incrementalmissingancestors
1454 object.
1452 object.
1455
1453
1456 'common' is a list of revision numbers. If common is not supplied, uses
1454 'common' is a list of revision numbers. If common is not supplied, uses
1457 nullrev.
1455 nullrev.
1458 """
1456 """
1459 if common is None:
1457 if common is None:
1460 common = [nullrev]
1458 common = [nullrev]
1461
1459
1462 if rustancestor is not None and self.index.rust_ext_compat:
1460 if rustancestor is not None and self.index.rust_ext_compat:
1463 return rustancestor.MissingAncestors(self.index, common)
1461 return rustancestor.MissingAncestors(self.index, common)
1464 return ancestor.incrementalmissingancestors(self.parentrevs, common)
1462 return ancestor.incrementalmissingancestors(self.parentrevs, common)
1465
1463
1466 def findmissingrevs(self, common=None, heads=None):
1464 def findmissingrevs(self, common=None, heads=None):
1467 """Return the revision numbers of the ancestors of heads that
1465 """Return the revision numbers of the ancestors of heads that
1468 are not ancestors of common.
1466 are not ancestors of common.
1469
1467
1470 More specifically, return a list of revision numbers corresponding to
1468 More specifically, return a list of revision numbers corresponding to
1471 nodes N such that every N satisfies the following constraints:
1469 nodes N such that every N satisfies the following constraints:
1472
1470
1473 1. N is an ancestor of some node in 'heads'
1471 1. N is an ancestor of some node in 'heads'
1474 2. N is not an ancestor of any node in 'common'
1472 2. N is not an ancestor of any node in 'common'
1475
1473
1476 The list is sorted by revision number, meaning it is
1474 The list is sorted by revision number, meaning it is
1477 topologically sorted.
1475 topologically sorted.
1478
1476
1479 'heads' and 'common' are both lists of revision numbers. If heads is
1477 'heads' and 'common' are both lists of revision numbers. If heads is
1480 not supplied, uses all of the revlog's heads. If common is not
1478 not supplied, uses all of the revlog's heads. If common is not
1481 supplied, uses nullid."""
1479 supplied, uses nullid."""
1482 if common is None:
1480 if common is None:
1483 common = [nullrev]
1481 common = [nullrev]
1484 if heads is None:
1482 if heads is None:
1485 heads = self.headrevs()
1483 heads = self.headrevs()
1486
1484
1487 inc = self.incrementalmissingrevs(common=common)
1485 inc = self.incrementalmissingrevs(common=common)
1488 return inc.missingancestors(heads)
1486 return inc.missingancestors(heads)
1489
1487
1490 def findmissing(self, common=None, heads=None):
1488 def findmissing(self, common=None, heads=None):
1491 """Return the ancestors of heads that are not ancestors of common.
1489 """Return the ancestors of heads that are not ancestors of common.
1492
1490
1493 More specifically, return a list of nodes N such that every N
1491 More specifically, return a list of nodes N such that every N
1494 satisfies the following constraints:
1492 satisfies the following constraints:
1495
1493
1496 1. N is an ancestor of some node in 'heads'
1494 1. N is an ancestor of some node in 'heads'
1497 2. N is not an ancestor of any node in 'common'
1495 2. N is not an ancestor of any node in 'common'
1498
1496
1499 The list is sorted by revision number, meaning it is
1497 The list is sorted by revision number, meaning it is
1500 topologically sorted.
1498 topologically sorted.
1501
1499
1502 'heads' and 'common' are both lists of node IDs. If heads is
1500 'heads' and 'common' are both lists of node IDs. If heads is
1503 not supplied, uses all of the revlog's heads. If common is not
1501 not supplied, uses all of the revlog's heads. If common is not
1504 supplied, uses nullid."""
1502 supplied, uses nullid."""
1505 if common is None:
1503 if common is None:
1506 common = [self.nullid]
1504 common = [self.nullid]
1507 if heads is None:
1505 if heads is None:
1508 heads = self.heads()
1506 heads = self.heads()
1509
1507
1510 common = [self.rev(n) for n in common]
1508 common = [self.rev(n) for n in common]
1511 heads = [self.rev(n) for n in heads]
1509 heads = [self.rev(n) for n in heads]
1512
1510
1513 inc = self.incrementalmissingrevs(common=common)
1511 inc = self.incrementalmissingrevs(common=common)
1514 return [self.node(r) for r in inc.missingancestors(heads)]
1512 return [self.node(r) for r in inc.missingancestors(heads)]
1515
1513
1516 def nodesbetween(self, roots=None, heads=None):
1514 def nodesbetween(self, roots=None, heads=None):
1517 """Return a topological path from 'roots' to 'heads'.
1515 """Return a topological path from 'roots' to 'heads'.
1518
1516
1519 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
1517 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
1520 topologically sorted list of all nodes N that satisfy both of
1518 topologically sorted list of all nodes N that satisfy both of
1521 these constraints:
1519 these constraints:
1522
1520
1523 1. N is a descendant of some node in 'roots'
1521 1. N is a descendant of some node in 'roots'
1524 2. N is an ancestor of some node in 'heads'
1522 2. N is an ancestor of some node in 'heads'
1525
1523
1526 Every node is considered to be both a descendant and an ancestor
1524 Every node is considered to be both a descendant and an ancestor
1527 of itself, so every reachable node in 'roots' and 'heads' will be
1525 of itself, so every reachable node in 'roots' and 'heads' will be
1528 included in 'nodes'.
1526 included in 'nodes'.
1529
1527
1530 'outroots' is the list of reachable nodes in 'roots', i.e., the
1528 'outroots' is the list of reachable nodes in 'roots', i.e., the
1531 subset of 'roots' that is returned in 'nodes'. Likewise,
1529 subset of 'roots' that is returned in 'nodes'. Likewise,
1532 'outheads' is the subset of 'heads' that is also in 'nodes'.
1530 'outheads' is the subset of 'heads' that is also in 'nodes'.
1533
1531
1534 'roots' and 'heads' are both lists of node IDs. If 'roots' is
1532 'roots' and 'heads' are both lists of node IDs. If 'roots' is
1535 unspecified, uses nullid as the only root. If 'heads' is
1533 unspecified, uses nullid as the only root. If 'heads' is
1536 unspecified, uses list of all of the revlog's heads."""
1534 unspecified, uses list of all of the revlog's heads."""
1537 nonodes = ([], [], [])
1535 nonodes = ([], [], [])
1538 if roots is not None:
1536 if roots is not None:
1539 roots = list(roots)
1537 roots = list(roots)
1540 if not roots:
1538 if not roots:
1541 return nonodes
1539 return nonodes
1542 lowestrev = min([self.rev(n) for n in roots])
1540 lowestrev = min([self.rev(n) for n in roots])
1543 else:
1541 else:
1544 roots = [self.nullid] # Everybody's a descendant of nullid
1542 roots = [self.nullid] # Everybody's a descendant of nullid
1545 lowestrev = nullrev
1543 lowestrev = nullrev
1546 if (lowestrev == nullrev) and (heads is None):
1544 if (lowestrev == nullrev) and (heads is None):
1547 # We want _all_ the nodes!
1545 # We want _all_ the nodes!
1548 return (
1546 return (
1549 [self.node(r) for r in self],
1547 [self.node(r) for r in self],
1550 [self.nullid],
1548 [self.nullid],
1551 list(self.heads()),
1549 list(self.heads()),
1552 )
1550 )
1553 if heads is None:
1551 if heads is None:
1554 # All nodes are ancestors, so the latest ancestor is the last
1552 # All nodes are ancestors, so the latest ancestor is the last
1555 # node.
1553 # node.
1556 highestrev = len(self) - 1
1554 highestrev = len(self) - 1
1557 # Set ancestors to None to signal that every node is an ancestor.
1555 # Set ancestors to None to signal that every node is an ancestor.
1558 ancestors = None
1556 ancestors = None
1559 # Set heads to an empty dictionary for later discovery of heads
1557 # Set heads to an empty dictionary for later discovery of heads
1560 heads = {}
1558 heads = {}
1561 else:
1559 else:
1562 heads = list(heads)
1560 heads = list(heads)
1563 if not heads:
1561 if not heads:
1564 return nonodes
1562 return nonodes
1565 ancestors = set()
1563 ancestors = set()
1566 # Turn heads into a dictionary so we can remove 'fake' heads.
1564 # Turn heads into a dictionary so we can remove 'fake' heads.
1567 # Also, later we will be using it to filter out the heads we can't
1565 # Also, later we will be using it to filter out the heads we can't
1568 # find from roots.
1566 # find from roots.
1569 heads = dict.fromkeys(heads, False)
1567 heads = dict.fromkeys(heads, False)
1570 # Start at the top and keep marking parents until we're done.
1568 # Start at the top and keep marking parents until we're done.
1571 nodestotag = set(heads)
1569 nodestotag = set(heads)
1572 # Remember where the top was so we can use it as a limit later.
1570 # Remember where the top was so we can use it as a limit later.
1573 highestrev = max([self.rev(n) for n in nodestotag])
1571 highestrev = max([self.rev(n) for n in nodestotag])
1574 while nodestotag:
1572 while nodestotag:
1575 # grab a node to tag
1573 # grab a node to tag
1576 n = nodestotag.pop()
1574 n = nodestotag.pop()
1577 # Never tag nullid
1575 # Never tag nullid
1578 if n == self.nullid:
1576 if n == self.nullid:
1579 continue
1577 continue
1580 # A node's revision number represents its place in a
1578 # A node's revision number represents its place in a
1581 # topologically sorted list of nodes.
1579 # topologically sorted list of nodes.
1582 r = self.rev(n)
1580 r = self.rev(n)
1583 if r >= lowestrev:
1581 if r >= lowestrev:
1584 if n not in ancestors:
1582 if n not in ancestors:
1585 # If we are possibly a descendant of one of the roots
1583 # If we are possibly a descendant of one of the roots
1586 # and we haven't already been marked as an ancestor
1584 # and we haven't already been marked as an ancestor
1587 ancestors.add(n) # Mark as ancestor
1585 ancestors.add(n) # Mark as ancestor
1588 # Add non-nullid parents to list of nodes to tag.
1586 # Add non-nullid parents to list of nodes to tag.
1589 nodestotag.update(
1587 nodestotag.update(
1590 [p for p in self.parents(n) if p != self.nullid]
1588 [p for p in self.parents(n) if p != self.nullid]
1591 )
1589 )
1592 elif n in heads: # We've seen it before, is it a fake head?
1590 elif n in heads: # We've seen it before, is it a fake head?
1593 # So it is, real heads should not be the ancestors of
1591 # So it is, real heads should not be the ancestors of
1594 # any other heads.
1592 # any other heads.
1595 heads.pop(n)
1593 heads.pop(n)
1596 if not ancestors:
1594 if not ancestors:
1597 return nonodes
1595 return nonodes
1598 # Now that we have our set of ancestors, we want to remove any
1596 # Now that we have our set of ancestors, we want to remove any
1599 # roots that are not ancestors.
1597 # roots that are not ancestors.
1600
1598
1601 # If one of the roots was nullid, everything is included anyway.
1599 # If one of the roots was nullid, everything is included anyway.
1602 if lowestrev > nullrev:
1600 if lowestrev > nullrev:
1603 # But, since we weren't, let's recompute the lowest rev to not
1601 # But, since we weren't, let's recompute the lowest rev to not
1604 # include roots that aren't ancestors.
1602 # include roots that aren't ancestors.
1605
1603
1606 # Filter out roots that aren't ancestors of heads
1604 # Filter out roots that aren't ancestors of heads
1607 roots = [root for root in roots if root in ancestors]
1605 roots = [root for root in roots if root in ancestors]
1608 # Recompute the lowest revision
1606 # Recompute the lowest revision
1609 if roots:
1607 if roots:
1610 lowestrev = min([self.rev(root) for root in roots])
1608 lowestrev = min([self.rev(root) for root in roots])
1611 else:
1609 else:
1612 # No more roots? Return empty list
1610 # No more roots? Return empty list
1613 return nonodes
1611 return nonodes
1614 else:
1612 else:
1615 # We are descending from nullid, and don't need to care about
1613 # We are descending from nullid, and don't need to care about
1616 # any other roots.
1614 # any other roots.
1617 lowestrev = nullrev
1615 lowestrev = nullrev
1618 roots = [self.nullid]
1616 roots = [self.nullid]
1619 # Transform our roots list into a set.
1617 # Transform our roots list into a set.
1620 descendants = set(roots)
1618 descendants = set(roots)
1621 # Also, keep the original roots so we can filter out roots that aren't
1619 # Also, keep the original roots so we can filter out roots that aren't
1622 # 'real' roots (i.e. are descended from other roots).
1620 # 'real' roots (i.e. are descended from other roots).
1623 roots = descendants.copy()
1621 roots = descendants.copy()
1624 # Our topologically sorted list of output nodes.
1622 # Our topologically sorted list of output nodes.
1625 orderedout = []
1623 orderedout = []
1626 # Don't start at nullid since we don't want nullid in our output list,
1624 # Don't start at nullid since we don't want nullid in our output list,
1627 # and if nullid shows up in descendants, empty parents will look like
1625 # and if nullid shows up in descendants, empty parents will look like
1628 # they're descendants.
1626 # they're descendants.
1629 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1627 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1630 n = self.node(r)
1628 n = self.node(r)
1631 isdescendant = False
1629 isdescendant = False
1632 if lowestrev == nullrev: # Everybody is a descendant of nullid
1630 if lowestrev == nullrev: # Everybody is a descendant of nullid
1633 isdescendant = True
1631 isdescendant = True
1634 elif n in descendants:
1632 elif n in descendants:
1635 # n is already a descendant
1633 # n is already a descendant
1636 isdescendant = True
1634 isdescendant = True
1637 # This check only needs to be done here because all the roots
1635 # This check only needs to be done here because all the roots
1638 # will start being marked is descendants before the loop.
1636 # will start being marked is descendants before the loop.
1639 if n in roots:
1637 if n in roots:
1640 # If n was a root, check if it's a 'real' root.
1638 # If n was a root, check if it's a 'real' root.
1641 p = tuple(self.parents(n))
1639 p = tuple(self.parents(n))
1642 # If any of its parents are descendants, it's not a root.
1640 # If any of its parents are descendants, it's not a root.
1643 if (p[0] in descendants) or (p[1] in descendants):
1641 if (p[0] in descendants) or (p[1] in descendants):
1644 roots.remove(n)
1642 roots.remove(n)
1645 else:
1643 else:
1646 p = tuple(self.parents(n))
1644 p = tuple(self.parents(n))
1647 # A node is a descendant if either of its parents are
1645 # A node is a descendant if either of its parents are
1648 # descendants. (We seeded the dependents list with the roots
1646 # descendants. (We seeded the dependents list with the roots
1649 # up there, remember?)
1647 # up there, remember?)
1650 if (p[0] in descendants) or (p[1] in descendants):
1648 if (p[0] in descendants) or (p[1] in descendants):
1651 descendants.add(n)
1649 descendants.add(n)
1652 isdescendant = True
1650 isdescendant = True
1653 if isdescendant and ((ancestors is None) or (n in ancestors)):
1651 if isdescendant and ((ancestors is None) or (n in ancestors)):
1654 # Only include nodes that are both descendants and ancestors.
1652 # Only include nodes that are both descendants and ancestors.
1655 orderedout.append(n)
1653 orderedout.append(n)
1656 if (ancestors is not None) and (n in heads):
1654 if (ancestors is not None) and (n in heads):
1657 # We're trying to figure out which heads are reachable
1655 # We're trying to figure out which heads are reachable
1658 # from roots.
1656 # from roots.
1659 # Mark this head as having been reached
1657 # Mark this head as having been reached
1660 heads[n] = True
1658 heads[n] = True
1661 elif ancestors is None:
1659 elif ancestors is None:
1662 # Otherwise, we're trying to discover the heads.
1660 # Otherwise, we're trying to discover the heads.
1663 # Assume this is a head because if it isn't, the next step
1661 # Assume this is a head because if it isn't, the next step
1664 # will eventually remove it.
1662 # will eventually remove it.
1665 heads[n] = True
1663 heads[n] = True
1666 # But, obviously its parents aren't.
1664 # But, obviously its parents aren't.
1667 for p in self.parents(n):
1665 for p in self.parents(n):
1668 heads.pop(p, None)
1666 heads.pop(p, None)
1669 heads = [head for head, flag in heads.items() if flag]
1667 heads = [head for head, flag in heads.items() if flag]
1670 roots = list(roots)
1668 roots = list(roots)
1671 assert orderedout
1669 assert orderedout
1672 assert roots
1670 assert roots
1673 assert heads
1671 assert heads
1674 return (orderedout, roots, heads)
1672 return (orderedout, roots, heads)
1675
1673
1676 def headrevs(self, revs=None):
1674 def headrevs(self, revs=None):
1677 if revs is None:
1675 if revs is None:
1678 try:
1676 try:
1679 return self.index.headrevs()
1677 return self.index.headrevs()
1680 except AttributeError:
1678 except AttributeError:
1681 return self._headrevs()
1679 return self._headrevs()
1682 if rustdagop is not None and self.index.rust_ext_compat:
1680 if rustdagop is not None and self.index.rust_ext_compat:
1683 return rustdagop.headrevs(self.index, revs)
1681 return rustdagop.headrevs(self.index, revs)
1684 return dagop.headrevs(revs, self._uncheckedparentrevs)
1682 return dagop.headrevs(revs, self._uncheckedparentrevs)
1685
1683
1686 def computephases(self, roots):
1684 def computephases(self, roots):
1687 return self.index.computephasesmapsets(roots)
1685 return self.index.computephasesmapsets(roots)
1688
1686
1689 def _headrevs(self):
1687 def _headrevs(self):
1690 count = len(self)
1688 count = len(self)
1691 if not count:
1689 if not count:
1692 return [nullrev]
1690 return [nullrev]
1693 # we won't iter over filtered rev so nobody is a head at start
1691 # we won't iter over filtered rev so nobody is a head at start
1694 ishead = [0] * (count + 1)
1692 ishead = [0] * (count + 1)
1695 index = self.index
1693 index = self.index
1696 for r in self:
1694 for r in self:
1697 ishead[r] = 1 # I may be an head
1695 ishead[r] = 1 # I may be an head
1698 e = index[r]
1696 e = index[r]
1699 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1697 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1700 return [r for r, val in enumerate(ishead) if val]
1698 return [r for r, val in enumerate(ishead) if val]
1701
1699
1702 def heads(self, start=None, stop=None):
1700 def heads(self, start=None, stop=None):
1703 """return the list of all nodes that have no children
1701 """return the list of all nodes that have no children
1704
1702
1705 if start is specified, only heads that are descendants of
1703 if start is specified, only heads that are descendants of
1706 start will be returned
1704 start will be returned
1707 if stop is specified, it will consider all the revs from stop
1705 if stop is specified, it will consider all the revs from stop
1708 as if they had no children
1706 as if they had no children
1709 """
1707 """
1710 if start is None and stop is None:
1708 if start is None and stop is None:
1711 if not len(self):
1709 if not len(self):
1712 return [self.nullid]
1710 return [self.nullid]
1713 return [self.node(r) for r in self.headrevs()]
1711 return [self.node(r) for r in self.headrevs()]
1714
1712
1715 if start is None:
1713 if start is None:
1716 start = nullrev
1714 start = nullrev
1717 else:
1715 else:
1718 start = self.rev(start)
1716 start = self.rev(start)
1719
1717
1720 stoprevs = {self.rev(n) for n in stop or []}
1718 stoprevs = {self.rev(n) for n in stop or []}
1721
1719
1722 revs = dagop.headrevssubset(
1720 revs = dagop.headrevssubset(
1723 self.revs, self.parentrevs, startrev=start, stoprevs=stoprevs
1721 self.revs, self.parentrevs, startrev=start, stoprevs=stoprevs
1724 )
1722 )
1725
1723
1726 return [self.node(rev) for rev in revs]
1724 return [self.node(rev) for rev in revs]
1727
1725
1728 def children(self, node):
1726 def children(self, node):
1729 """find the children of a given node"""
1727 """find the children of a given node"""
1730 c = []
1728 c = []
1731 p = self.rev(node)
1729 p = self.rev(node)
1732 for r in self.revs(start=p + 1):
1730 for r in self.revs(start=p + 1):
1733 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1731 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1734 if prevs:
1732 if prevs:
1735 for pr in prevs:
1733 for pr in prevs:
1736 if pr == p:
1734 if pr == p:
1737 c.append(self.node(r))
1735 c.append(self.node(r))
1738 elif p == nullrev:
1736 elif p == nullrev:
1739 c.append(self.node(r))
1737 c.append(self.node(r))
1740 return c
1738 return c
1741
1739
1742 def commonancestorsheads(self, a, b):
1740 def commonancestorsheads(self, a, b):
1743 """calculate all the heads of the common ancestors of nodes a and b"""
1741 """calculate all the heads of the common ancestors of nodes a and b"""
1744 a, b = self.rev(a), self.rev(b)
1742 a, b = self.rev(a), self.rev(b)
1745 ancs = self._commonancestorsheads(a, b)
1743 ancs = self._commonancestorsheads(a, b)
1746 return pycompat.maplist(self.node, ancs)
1744 return pycompat.maplist(self.node, ancs)
1747
1745
1748 def _commonancestorsheads(self, *revs):
1746 def _commonancestorsheads(self, *revs):
1749 """calculate all the heads of the common ancestors of revs"""
1747 """calculate all the heads of the common ancestors of revs"""
1750 try:
1748 try:
1751 ancs = self.index.commonancestorsheads(*revs)
1749 ancs = self.index.commonancestorsheads(*revs)
1752 except (AttributeError, OverflowError): # C implementation failed
1750 except (AttributeError, OverflowError): # C implementation failed
1753 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1751 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1754 return ancs
1752 return ancs
1755
1753
1756 def isancestor(self, a, b):
1754 def isancestor(self, a, b):
1757 """return True if node a is an ancestor of node b
1755 """return True if node a is an ancestor of node b
1758
1756
1759 A revision is considered an ancestor of itself."""
1757 A revision is considered an ancestor of itself."""
1760 a, b = self.rev(a), self.rev(b)
1758 a, b = self.rev(a), self.rev(b)
1761 return self.isancestorrev(a, b)
1759 return self.isancestorrev(a, b)
1762
1760
1763 def isancestorrev(self, a, b):
1761 def isancestorrev(self, a, b):
1764 """return True if revision a is an ancestor of revision b
1762 """return True if revision a is an ancestor of revision b
1765
1763
1766 A revision is considered an ancestor of itself.
1764 A revision is considered an ancestor of itself.
1767
1765
1768 The implementation of this is trivial but the use of
1766 The implementation of this is trivial but the use of
1769 reachableroots is not."""
1767 reachableroots is not."""
1770 if a == nullrev:
1768 if a == nullrev:
1771 return True
1769 return True
1772 elif a == b:
1770 elif a == b:
1773 return True
1771 return True
1774 elif a > b:
1772 elif a > b:
1775 return False
1773 return False
1776 return bool(self.reachableroots(a, [b], [a], includepath=False))
1774 return bool(self.reachableroots(a, [b], [a], includepath=False))
1777
1775
1778 def reachableroots(self, minroot, heads, roots, includepath=False):
1776 def reachableroots(self, minroot, heads, roots, includepath=False):
1779 """return (heads(::(<roots> and <roots>::<heads>)))
1777 """return (heads(::(<roots> and <roots>::<heads>)))
1780
1778
1781 If includepath is True, return (<roots>::<heads>)."""
1779 If includepath is True, return (<roots>::<heads>)."""
1782 try:
1780 try:
1783 return self.index.reachableroots2(
1781 return self.index.reachableroots2(
1784 minroot, heads, roots, includepath
1782 minroot, heads, roots, includepath
1785 )
1783 )
1786 except AttributeError:
1784 except AttributeError:
1787 return dagop._reachablerootspure(
1785 return dagop._reachablerootspure(
1788 self.parentrevs, minroot, roots, heads, includepath
1786 self.parentrevs, minroot, roots, heads, includepath
1789 )
1787 )
1790
1788
1791 def ancestor(self, a, b):
1789 def ancestor(self, a, b):
1792 """calculate the "best" common ancestor of nodes a and b"""
1790 """calculate the "best" common ancestor of nodes a and b"""
1793
1791
1794 a, b = self.rev(a), self.rev(b)
1792 a, b = self.rev(a), self.rev(b)
1795 try:
1793 try:
1796 ancs = self.index.ancestors(a, b)
1794 ancs = self.index.ancestors(a, b)
1797 except (AttributeError, OverflowError):
1795 except (AttributeError, OverflowError):
1798 ancs = ancestor.ancestors(self.parentrevs, a, b)
1796 ancs = ancestor.ancestors(self.parentrevs, a, b)
1799 if ancs:
1797 if ancs:
1800 # choose a consistent winner when there's a tie
1798 # choose a consistent winner when there's a tie
1801 return min(map(self.node, ancs))
1799 return min(map(self.node, ancs))
1802 return self.nullid
1800 return self.nullid
1803
1801
1804 def _match(self, id):
1802 def _match(self, id):
1805 if isinstance(id, int):
1803 if isinstance(id, int):
1806 # rev
1804 # rev
1807 return self.node(id)
1805 return self.node(id)
1808 if len(id) == self.nodeconstants.nodelen:
1806 if len(id) == self.nodeconstants.nodelen:
1809 # possibly a binary node
1807 # possibly a binary node
1810 # odds of a binary node being all hex in ASCII are 1 in 10**25
1808 # odds of a binary node being all hex in ASCII are 1 in 10**25
1811 try:
1809 try:
1812 node = id
1810 node = id
1813 self.rev(node) # quick search the index
1811 self.rev(node) # quick search the index
1814 return node
1812 return node
1815 except error.LookupError:
1813 except error.LookupError:
1816 pass # may be partial hex id
1814 pass # may be partial hex id
1817 try:
1815 try:
1818 # str(rev)
1816 # str(rev)
1819 rev = int(id)
1817 rev = int(id)
1820 if b"%d" % rev != id:
1818 if b"%d" % rev != id:
1821 raise ValueError
1819 raise ValueError
1822 if rev < 0:
1820 if rev < 0:
1823 rev = len(self) + rev
1821 rev = len(self) + rev
1824 if rev < 0 or rev >= len(self):
1822 if rev < 0 or rev >= len(self):
1825 raise ValueError
1823 raise ValueError
1826 return self.node(rev)
1824 return self.node(rev)
1827 except (ValueError, OverflowError):
1825 except (ValueError, OverflowError):
1828 pass
1826 pass
1829 if len(id) == 2 * self.nodeconstants.nodelen:
1827 if len(id) == 2 * self.nodeconstants.nodelen:
1830 try:
1828 try:
1831 # a full hex nodeid?
1829 # a full hex nodeid?
1832 node = bin(id)
1830 node = bin(id)
1833 self.rev(node)
1831 self.rev(node)
1834 return node
1832 return node
1835 except (binascii.Error, error.LookupError):
1833 except (binascii.Error, error.LookupError):
1836 pass
1834 pass
1837
1835
1838 def _partialmatch(self, id):
1836 def _partialmatch(self, id):
1839 # we don't care wdirfilenodeids as they should be always full hash
1837 # we don't care wdirfilenodeids as they should be always full hash
1840 maybewdir = self.nodeconstants.wdirhex.startswith(id)
1838 maybewdir = self.nodeconstants.wdirhex.startswith(id)
1841 ambiguous = False
1839 ambiguous = False
1842 try:
1840 try:
1843 partial = self.index.partialmatch(id)
1841 partial = self.index.partialmatch(id)
1844 if partial and self.hasnode(partial):
1842 if partial and self.hasnode(partial):
1845 if maybewdir:
1843 if maybewdir:
1846 # single 'ff...' match in radix tree, ambiguous with wdir
1844 # single 'ff...' match in radix tree, ambiguous with wdir
1847 ambiguous = True
1845 ambiguous = True
1848 else:
1846 else:
1849 return partial
1847 return partial
1850 elif maybewdir:
1848 elif maybewdir:
1851 # no 'ff...' match in radix tree, wdir identified
1849 # no 'ff...' match in radix tree, wdir identified
1852 raise error.WdirUnsupported
1850 raise error.WdirUnsupported
1853 else:
1851 else:
1854 return None
1852 return None
1855 except error.RevlogError:
1853 except error.RevlogError:
1856 # parsers.c radix tree lookup gave multiple matches
1854 # parsers.c radix tree lookup gave multiple matches
1857 # fast path: for unfiltered changelog, radix tree is accurate
1855 # fast path: for unfiltered changelog, radix tree is accurate
1858 if not getattr(self, 'filteredrevs', None):
1856 if not getattr(self, 'filteredrevs', None):
1859 ambiguous = True
1857 ambiguous = True
1860 # fall through to slow path that filters hidden revisions
1858 # fall through to slow path that filters hidden revisions
1861 except (AttributeError, ValueError):
1859 except (AttributeError, ValueError):
1862 # we are pure python, or key is not hex
1860 # we are pure python, or key is not hex
1863 pass
1861 pass
1864 if ambiguous:
1862 if ambiguous:
1865 raise error.AmbiguousPrefixLookupError(
1863 raise error.AmbiguousPrefixLookupError(
1866 id, self.display_id, _(b'ambiguous identifier')
1864 id, self.display_id, _(b'ambiguous identifier')
1867 )
1865 )
1868
1866
1869 if id in self._pcache:
1867 if id in self._pcache:
1870 return self._pcache[id]
1868 return self._pcache[id]
1871
1869
1872 if len(id) <= 40:
1870 if len(id) <= 40:
1873 # hex(node)[:...]
1871 # hex(node)[:...]
1874 l = len(id) // 2 * 2 # grab an even number of digits
1872 l = len(id) // 2 * 2 # grab an even number of digits
1875 try:
1873 try:
1876 # we're dropping the last digit, so let's check that it's hex,
1874 # we're dropping the last digit, so let's check that it's hex,
1877 # to avoid the expensive computation below if it's not
1875 # to avoid the expensive computation below if it's not
1878 if len(id) % 2 > 0:
1876 if len(id) % 2 > 0:
1879 if not (id[-1] in hexdigits):
1877 if not (id[-1] in hexdigits):
1880 return None
1878 return None
1881 prefix = bin(id[:l])
1879 prefix = bin(id[:l])
1882 except binascii.Error:
1880 except binascii.Error:
1883 pass
1881 pass
1884 else:
1882 else:
1885 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1883 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1886 nl = [
1884 nl = [
1887 n for n in nl if hex(n).startswith(id) and self.hasnode(n)
1885 n for n in nl if hex(n).startswith(id) and self.hasnode(n)
1888 ]
1886 ]
1889 if self.nodeconstants.nullhex.startswith(id):
1887 if self.nodeconstants.nullhex.startswith(id):
1890 nl.append(self.nullid)
1888 nl.append(self.nullid)
1891 if len(nl) > 0:
1889 if len(nl) > 0:
1892 if len(nl) == 1 and not maybewdir:
1890 if len(nl) == 1 and not maybewdir:
1893 self._pcache[id] = nl[0]
1891 self._pcache[id] = nl[0]
1894 return nl[0]
1892 return nl[0]
1895 raise error.AmbiguousPrefixLookupError(
1893 raise error.AmbiguousPrefixLookupError(
1896 id, self.display_id, _(b'ambiguous identifier')
1894 id, self.display_id, _(b'ambiguous identifier')
1897 )
1895 )
1898 if maybewdir:
1896 if maybewdir:
1899 raise error.WdirUnsupported
1897 raise error.WdirUnsupported
1900 return None
1898 return None
1901
1899
1902 def lookup(self, id):
1900 def lookup(self, id):
1903 """locate a node based on:
1901 """locate a node based on:
1904 - revision number or str(revision number)
1902 - revision number or str(revision number)
1905 - nodeid or subset of hex nodeid
1903 - nodeid or subset of hex nodeid
1906 """
1904 """
1907 n = self._match(id)
1905 n = self._match(id)
1908 if n is not None:
1906 if n is not None:
1909 return n
1907 return n
1910 n = self._partialmatch(id)
1908 n = self._partialmatch(id)
1911 if n:
1909 if n:
1912 return n
1910 return n
1913
1911
1914 raise error.LookupError(id, self.display_id, _(b'no match found'))
1912 raise error.LookupError(id, self.display_id, _(b'no match found'))
1915
1913
1916 def shortest(self, node, minlength=1):
1914 def shortest(self, node, minlength=1):
1917 """Find the shortest unambiguous prefix that matches node."""
1915 """Find the shortest unambiguous prefix that matches node."""
1918
1916
1919 def isvalid(prefix):
1917 def isvalid(prefix):
1920 try:
1918 try:
1921 matchednode = self._partialmatch(prefix)
1919 matchednode = self._partialmatch(prefix)
1922 except error.AmbiguousPrefixLookupError:
1920 except error.AmbiguousPrefixLookupError:
1923 return False
1921 return False
1924 except error.WdirUnsupported:
1922 except error.WdirUnsupported:
1925 # single 'ff...' match
1923 # single 'ff...' match
1926 return True
1924 return True
1927 if matchednode is None:
1925 if matchednode is None:
1928 raise error.LookupError(node, self.display_id, _(b'no node'))
1926 raise error.LookupError(node, self.display_id, _(b'no node'))
1929 return True
1927 return True
1930
1928
1931 def maybewdir(prefix):
1929 def maybewdir(prefix):
1932 return all(c == b'f' for c in pycompat.iterbytestr(prefix))
1930 return all(c == b'f' for c in pycompat.iterbytestr(prefix))
1933
1931
1934 hexnode = hex(node)
1932 hexnode = hex(node)
1935
1933
1936 def disambiguate(hexnode, minlength):
1934 def disambiguate(hexnode, minlength):
1937 """Disambiguate against wdirid."""
1935 """Disambiguate against wdirid."""
1938 for length in range(minlength, len(hexnode) + 1):
1936 for length in range(minlength, len(hexnode) + 1):
1939 prefix = hexnode[:length]
1937 prefix = hexnode[:length]
1940 if not maybewdir(prefix):
1938 if not maybewdir(prefix):
1941 return prefix
1939 return prefix
1942
1940
1943 if not getattr(self, 'filteredrevs', None):
1941 if not getattr(self, 'filteredrevs', None):
1944 try:
1942 try:
1945 length = max(self.index.shortest(node), minlength)
1943 length = max(self.index.shortest(node), minlength)
1946 return disambiguate(hexnode, length)
1944 return disambiguate(hexnode, length)
1947 except error.RevlogError:
1945 except error.RevlogError:
1948 if node != self.nodeconstants.wdirid:
1946 if node != self.nodeconstants.wdirid:
1949 raise error.LookupError(
1947 raise error.LookupError(
1950 node, self.display_id, _(b'no node')
1948 node, self.display_id, _(b'no node')
1951 )
1949 )
1952 except AttributeError:
1950 except AttributeError:
1953 # Fall through to pure code
1951 # Fall through to pure code
1954 pass
1952 pass
1955
1953
1956 if node == self.nodeconstants.wdirid:
1954 if node == self.nodeconstants.wdirid:
1957 for length in range(minlength, len(hexnode) + 1):
1955 for length in range(minlength, len(hexnode) + 1):
1958 prefix = hexnode[:length]
1956 prefix = hexnode[:length]
1959 if isvalid(prefix):
1957 if isvalid(prefix):
1960 return prefix
1958 return prefix
1961
1959
1962 for length in range(minlength, len(hexnode) + 1):
1960 for length in range(minlength, len(hexnode) + 1):
1963 prefix = hexnode[:length]
1961 prefix = hexnode[:length]
1964 if isvalid(prefix):
1962 if isvalid(prefix):
1965 return disambiguate(hexnode, length)
1963 return disambiguate(hexnode, length)
1966
1964
1967 def cmp(self, node, text):
1965 def cmp(self, node, text):
1968 """compare text with a given file revision
1966 """compare text with a given file revision
1969
1967
1970 returns True if text is different than what is stored.
1968 returns True if text is different than what is stored.
1971 """
1969 """
1972 p1, p2 = self.parents(node)
1970 p1, p2 = self.parents(node)
1973 return storageutil.hashrevisionsha1(text, p1, p2) != node
1971 return storageutil.hashrevisionsha1(text, p1, p2) != node
1974
1972
1975 def _getsegmentforrevs(self, startrev, endrev):
1973 def _getsegmentforrevs(self, startrev, endrev):
1976 """Obtain a segment of raw data corresponding to a range of revisions.
1974 """Obtain a segment of raw data corresponding to a range of revisions.
1977
1975
1978 Accepts the start and end revisions and an optional already-open
1976 Accepts the start and end revisions and an optional already-open
1979 file handle to be used for reading. If the file handle is read, its
1977 file handle to be used for reading. If the file handle is read, its
1980 seek position will not be preserved.
1978 seek position will not be preserved.
1981
1979
1982 Requests for data may be satisfied by a cache.
1980 Requests for data may be satisfied by a cache.
1983
1981
1984 Returns a 2-tuple of (offset, data) for the requested range of
1982 Returns a 2-tuple of (offset, data) for the requested range of
1985 revisions. Offset is the integer offset from the beginning of the
1983 revisions. Offset is the integer offset from the beginning of the
1986 revlog and data is a str or buffer of the raw byte data.
1984 revlog and data is a str or buffer of the raw byte data.
1987
1985
1988 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1986 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1989 to determine where each revision's data begins and ends.
1987 to determine where each revision's data begins and ends.
1990 """
1988 """
1991 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1989 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1992 # (functions are expensive).
1990 # (functions are expensive).
1993 index = self.index
1991 index = self.index
1994 istart = index[startrev]
1992 istart = index[startrev]
1995 start = int(istart[0] >> 16)
1993 start = int(istart[0] >> 16)
1996 if startrev == endrev:
1994 if startrev == endrev:
1997 end = start + istart[1]
1995 end = start + istart[1]
1998 else:
1996 else:
1999 iend = index[endrev]
1997 iend = index[endrev]
2000 end = int(iend[0] >> 16) + iend[1]
1998 end = int(iend[0] >> 16) + iend[1]
2001
1999
2002 if self._inline:
2000 if self._inline:
2003 start += (startrev + 1) * self.index.entry_size
2001 start += (startrev + 1) * self.index.entry_size
2004 end += (endrev + 1) * self.index.entry_size
2002 end += (endrev + 1) * self.index.entry_size
2005 length = end - start
2003 length = end - start
2006
2004
2007 return start, self._segmentfile.read_chunk(start, length)
2005 return start, self._segmentfile.read_chunk(start, length)
2008
2006
2009 def _chunk(self, rev):
2007 def _chunk(self, rev):
2010 """Obtain a single decompressed chunk for a revision.
2008 """Obtain a single decompressed chunk for a revision.
2011
2009
2012 Accepts an integer revision and an optional already-open file handle
2010 Accepts an integer revision and an optional already-open file handle
2013 to be used for reading. If used, the seek position of the file will not
2011 to be used for reading. If used, the seek position of the file will not
2014 be preserved.
2012 be preserved.
2015
2013
2016 Returns a str holding uncompressed data for the requested revision.
2014 Returns a str holding uncompressed data for the requested revision.
2017 """
2015 """
2018 compression_mode = self.index[rev][10]
2016 compression_mode = self.index[rev][10]
2019 data = self._getsegmentforrevs(rev, rev)[1]
2017 data = self._getsegmentforrevs(rev, rev)[1]
2020 if compression_mode == COMP_MODE_PLAIN:
2018 if compression_mode == COMP_MODE_PLAIN:
2021 return data
2019 return data
2022 elif compression_mode == COMP_MODE_DEFAULT:
2020 elif compression_mode == COMP_MODE_DEFAULT:
2023 return self._decompressor(data)
2021 return self._decompressor(data)
2024 elif compression_mode == COMP_MODE_INLINE:
2022 elif compression_mode == COMP_MODE_INLINE:
2025 return self.decompress(data)
2023 return self.decompress(data)
2026 else:
2024 else:
2027 msg = b'unknown compression mode %d'
2025 msg = b'unknown compression mode %d'
2028 msg %= compression_mode
2026 msg %= compression_mode
2029 raise error.RevlogError(msg)
2027 raise error.RevlogError(msg)
2030
2028
2031 def _chunks(self, revs, targetsize=None):
2029 def _chunks(self, revs, targetsize=None):
2032 """Obtain decompressed chunks for the specified revisions.
2030 """Obtain decompressed chunks for the specified revisions.
2033
2031
2034 Accepts an iterable of numeric revisions that are assumed to be in
2032 Accepts an iterable of numeric revisions that are assumed to be in
2035 ascending order. Also accepts an optional already-open file handle
2033 ascending order. Also accepts an optional already-open file handle
2036 to be used for reading. If used, the seek position of the file will
2034 to be used for reading. If used, the seek position of the file will
2037 not be preserved.
2035 not be preserved.
2038
2036
2039 This function is similar to calling ``self._chunk()`` multiple times,
2037 This function is similar to calling ``self._chunk()`` multiple times,
2040 but is faster.
2038 but is faster.
2041
2039
2042 Returns a list with decompressed data for each requested revision.
2040 Returns a list with decompressed data for each requested revision.
2043 """
2041 """
2044 if not revs:
2042 if not revs:
2045 return []
2043 return []
2046 start = self.start
2044 start = self.start
2047 length = self.length
2045 length = self.length
2048 inline = self._inline
2046 inline = self._inline
2049 iosize = self.index.entry_size
2047 iosize = self.index.entry_size
2050 buffer = util.buffer
2048 buffer = util.buffer
2051
2049
2052 l = []
2050 l = []
2053 ladd = l.append
2051 ladd = l.append
2054
2052
2055 if not self._withsparseread:
2053 if not self._withsparseread:
2056 slicedchunks = (revs,)
2054 slicedchunks = (revs,)
2057 else:
2055 else:
2058 slicedchunks = deltautil.slicechunk(
2056 slicedchunks = deltautil.slicechunk(
2059 self, revs, targetsize=targetsize
2057 self, revs, targetsize=targetsize
2060 )
2058 )
2061
2059
2062 for revschunk in slicedchunks:
2060 for revschunk in slicedchunks:
2063 firstrev = revschunk[0]
2061 firstrev = revschunk[0]
2064 # Skip trailing revisions with empty diff
2062 # Skip trailing revisions with empty diff
2065 for lastrev in revschunk[::-1]:
2063 for lastrev in revschunk[::-1]:
2066 if length(lastrev) != 0:
2064 if length(lastrev) != 0:
2067 break
2065 break
2068
2066
2069 try:
2067 try:
2070 offset, data = self._getsegmentforrevs(firstrev, lastrev)
2068 offset, data = self._getsegmentforrevs(firstrev, lastrev)
2071 except OverflowError:
2069 except OverflowError:
2072 # issue4215 - we can't cache a run of chunks greater than
2070 # issue4215 - we can't cache a run of chunks greater than
2073 # 2G on Windows
2071 # 2G on Windows
2074 return [self._chunk(rev) for rev in revschunk]
2072 return [self._chunk(rev) for rev in revschunk]
2075
2073
2076 decomp = self.decompress
2074 decomp = self.decompress
2077 # self._decompressor might be None, but will not be used in that case
2075 # self._decompressor might be None, but will not be used in that case
2078 def_decomp = self._decompressor
2076 def_decomp = self._decompressor
2079 for rev in revschunk:
2077 for rev in revschunk:
2080 chunkstart = start(rev)
2078 chunkstart = start(rev)
2081 if inline:
2079 if inline:
2082 chunkstart += (rev + 1) * iosize
2080 chunkstart += (rev + 1) * iosize
2083 chunklength = length(rev)
2081 chunklength = length(rev)
2084 comp_mode = self.index[rev][10]
2082 comp_mode = self.index[rev][10]
2085 c = buffer(data, chunkstart - offset, chunklength)
2083 c = buffer(data, chunkstart - offset, chunklength)
2086 if comp_mode == COMP_MODE_PLAIN:
2084 if comp_mode == COMP_MODE_PLAIN:
2087 ladd(c)
2085 ladd(c)
2088 elif comp_mode == COMP_MODE_INLINE:
2086 elif comp_mode == COMP_MODE_INLINE:
2089 ladd(decomp(c))
2087 ladd(decomp(c))
2090 elif comp_mode == COMP_MODE_DEFAULT:
2088 elif comp_mode == COMP_MODE_DEFAULT:
2091 ladd(def_decomp(c))
2089 ladd(def_decomp(c))
2092 else:
2090 else:
2093 msg = b'unknown compression mode %d'
2091 msg = b'unknown compression mode %d'
2094 msg %= comp_mode
2092 msg %= comp_mode
2095 raise error.RevlogError(msg)
2093 raise error.RevlogError(msg)
2096
2094
2097 return l
2095 return l
2098
2096
2099 def deltaparent(self, rev):
2097 def deltaparent(self, rev):
2100 """return deltaparent of the given revision"""
2098 """return deltaparent of the given revision"""
2101 base = self.index[rev][3]
2099 base = self.index[rev][3]
2102 if base == rev:
2100 if base == rev:
2103 return nullrev
2101 return nullrev
2104 elif self._generaldelta:
2102 elif self._generaldelta:
2105 return base
2103 return base
2106 else:
2104 else:
2107 return rev - 1
2105 return rev - 1
2108
2106
2109 def issnapshot(self, rev):
2107 def issnapshot(self, rev):
2110 """tells whether rev is a snapshot"""
2108 """tells whether rev is a snapshot"""
2111 if not self._sparserevlog:
2109 if not self._sparserevlog:
2112 return self.deltaparent(rev) == nullrev
2110 return self.deltaparent(rev) == nullrev
2113 elif hasattr(self.index, 'issnapshot'):
2111 elif hasattr(self.index, 'issnapshot'):
2114 # directly assign the method to cache the testing and access
2112 # directly assign the method to cache the testing and access
2115 self.issnapshot = self.index.issnapshot
2113 self.issnapshot = self.index.issnapshot
2116 return self.issnapshot(rev)
2114 return self.issnapshot(rev)
2117 if rev == nullrev:
2115 if rev == nullrev:
2118 return True
2116 return True
2119 entry = self.index[rev]
2117 entry = self.index[rev]
2120 base = entry[3]
2118 base = entry[3]
2121 if base == rev:
2119 if base == rev:
2122 return True
2120 return True
2123 if base == nullrev:
2121 if base == nullrev:
2124 return True
2122 return True
2125 p1 = entry[5]
2123 p1 = entry[5]
2126 while self.length(p1) == 0:
2124 while self.length(p1) == 0:
2127 b = self.deltaparent(p1)
2125 b = self.deltaparent(p1)
2128 if b == p1:
2126 if b == p1:
2129 break
2127 break
2130 p1 = b
2128 p1 = b
2131 p2 = entry[6]
2129 p2 = entry[6]
2132 while self.length(p2) == 0:
2130 while self.length(p2) == 0:
2133 b = self.deltaparent(p2)
2131 b = self.deltaparent(p2)
2134 if b == p2:
2132 if b == p2:
2135 break
2133 break
2136 p2 = b
2134 p2 = b
2137 if base == p1 or base == p2:
2135 if base == p1 or base == p2:
2138 return False
2136 return False
2139 return self.issnapshot(base)
2137 return self.issnapshot(base)
2140
2138
2141 def snapshotdepth(self, rev):
2139 def snapshotdepth(self, rev):
2142 """number of snapshot in the chain before this one"""
2140 """number of snapshot in the chain before this one"""
2143 if not self.issnapshot(rev):
2141 if not self.issnapshot(rev):
2144 raise error.ProgrammingError(b'revision %d not a snapshot')
2142 raise error.ProgrammingError(b'revision %d not a snapshot')
2145 return len(self._deltachain(rev)[0]) - 1
2143 return len(self._deltachain(rev)[0]) - 1
2146
2144
2147 def revdiff(self, rev1, rev2):
2145 def revdiff(self, rev1, rev2):
2148 """return or calculate a delta between two revisions
2146 """return or calculate a delta between two revisions
2149
2147
2150 The delta calculated is in binary form and is intended to be written to
2148 The delta calculated is in binary form and is intended to be written to
2151 revlog data directly. So this function needs raw revision data.
2149 revlog data directly. So this function needs raw revision data.
2152 """
2150 """
2153 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
2151 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
2154 return bytes(self._chunk(rev2))
2152 return bytes(self._chunk(rev2))
2155
2153
2156 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
2154 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
2157
2155
2158 def revision(self, nodeorrev):
2156 def revision(self, nodeorrev):
2159 """return an uncompressed revision of a given node or revision
2157 """return an uncompressed revision of a given node or revision
2160 number.
2158 number.
2161 """
2159 """
2162 return self._revisiondata(nodeorrev)
2160 return self._revisiondata(nodeorrev)
2163
2161
2164 def sidedata(self, nodeorrev):
2162 def sidedata(self, nodeorrev):
2165 """a map of extra data related to the changeset but not part of the hash
2163 """a map of extra data related to the changeset but not part of the hash
2166
2164
2167 This function currently return a dictionary. However, more advanced
2165 This function currently return a dictionary. However, more advanced
2168 mapping object will likely be used in the future for a more
2166 mapping object will likely be used in the future for a more
2169 efficient/lazy code.
2167 efficient/lazy code.
2170 """
2168 """
2171 # deal with <nodeorrev> argument type
2169 # deal with <nodeorrev> argument type
2172 if isinstance(nodeorrev, int):
2170 if isinstance(nodeorrev, int):
2173 rev = nodeorrev
2171 rev = nodeorrev
2174 else:
2172 else:
2175 rev = self.rev(nodeorrev)
2173 rev = self.rev(nodeorrev)
2176 return self._sidedata(rev)
2174 return self._sidedata(rev)
2177
2175
2178 def _revisiondata(self, nodeorrev, raw=False):
2176 def _revisiondata(self, nodeorrev, raw=False):
2179 # deal with <nodeorrev> argument type
2177 # deal with <nodeorrev> argument type
2180 if isinstance(nodeorrev, int):
2178 if isinstance(nodeorrev, int):
2181 rev = nodeorrev
2179 rev = nodeorrev
2182 node = self.node(rev)
2180 node = self.node(rev)
2183 else:
2181 else:
2184 node = nodeorrev
2182 node = nodeorrev
2185 rev = None
2183 rev = None
2186
2184
2187 # fast path the special `nullid` rev
2185 # fast path the special `nullid` rev
2188 if node == self.nullid:
2186 if node == self.nullid:
2189 return b""
2187 return b""
2190
2188
2191 # ``rawtext`` is the text as stored inside the revlog. Might be the
2189 # ``rawtext`` is the text as stored inside the revlog. Might be the
2192 # revision or might need to be processed to retrieve the revision.
2190 # revision or might need to be processed to retrieve the revision.
2193 rev, rawtext, validated = self._rawtext(node, rev)
2191 rev, rawtext, validated = self._rawtext(node, rev)
2194
2192
2195 if raw and validated:
2193 if raw and validated:
2196 # if we don't want to process the raw text and that raw
2194 # if we don't want to process the raw text and that raw
2197 # text is cached, we can exit early.
2195 # text is cached, we can exit early.
2198 return rawtext
2196 return rawtext
2199 if rev is None:
2197 if rev is None:
2200 rev = self.rev(node)
2198 rev = self.rev(node)
2201 # the revlog's flag for this revision
2199 # the revlog's flag for this revision
2202 # (usually alter its state or content)
2200 # (usually alter its state or content)
2203 flags = self.flags(rev)
2201 flags = self.flags(rev)
2204
2202
2205 if validated and flags == REVIDX_DEFAULT_FLAGS:
2203 if validated and flags == REVIDX_DEFAULT_FLAGS:
2206 # no extra flags set, no flag processor runs, text = rawtext
2204 # no extra flags set, no flag processor runs, text = rawtext
2207 return rawtext
2205 return rawtext
2208
2206
2209 if raw:
2207 if raw:
2210 validatehash = flagutil.processflagsraw(self, rawtext, flags)
2208 validatehash = flagutil.processflagsraw(self, rawtext, flags)
2211 text = rawtext
2209 text = rawtext
2212 else:
2210 else:
2213 r = flagutil.processflagsread(self, rawtext, flags)
2211 r = flagutil.processflagsread(self, rawtext, flags)
2214 text, validatehash = r
2212 text, validatehash = r
2215 if validatehash:
2213 if validatehash:
2216 self.checkhash(text, node, rev=rev)
2214 self.checkhash(text, node, rev=rev)
2217 if not validated:
2215 if not validated:
2218 self._revisioncache = (node, rev, rawtext)
2216 self._revisioncache = (node, rev, rawtext)
2219
2217
2220 return text
2218 return text
2221
2219
2222 def _rawtext(self, node, rev):
2220 def _rawtext(self, node, rev):
2223 """return the possibly unvalidated rawtext for a revision
2221 """return the possibly unvalidated rawtext for a revision
2224
2222
2225 returns (rev, rawtext, validated)
2223 returns (rev, rawtext, validated)
2226 """
2224 """
2227
2225
2228 # revision in the cache (could be useful to apply delta)
2226 # revision in the cache (could be useful to apply delta)
2229 cachedrev = None
2227 cachedrev = None
2230 # An intermediate text to apply deltas to
2228 # An intermediate text to apply deltas to
2231 basetext = None
2229 basetext = None
2232
2230
2233 # Check if we have the entry in cache
2231 # Check if we have the entry in cache
2234 # The cache entry looks like (node, rev, rawtext)
2232 # The cache entry looks like (node, rev, rawtext)
2235 if self._revisioncache:
2233 if self._revisioncache:
2236 if self._revisioncache[0] == node:
2234 if self._revisioncache[0] == node:
2237 return (rev, self._revisioncache[2], True)
2235 return (rev, self._revisioncache[2], True)
2238 cachedrev = self._revisioncache[1]
2236 cachedrev = self._revisioncache[1]
2239
2237
2240 if rev is None:
2238 if rev is None:
2241 rev = self.rev(node)
2239 rev = self.rev(node)
2242
2240
2243 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
2241 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
2244 if stopped:
2242 if stopped:
2245 basetext = self._revisioncache[2]
2243 basetext = self._revisioncache[2]
2246
2244
2247 # drop cache to save memory, the caller is expected to
2245 # drop cache to save memory, the caller is expected to
2248 # update self._revisioncache after validating the text
2246 # update self._revisioncache after validating the text
2249 self._revisioncache = None
2247 self._revisioncache = None
2250
2248
2251 targetsize = None
2249 targetsize = None
2252 rawsize = self.index[rev][2]
2250 rawsize = self.index[rev][2]
2253 if 0 <= rawsize:
2251 if 0 <= rawsize:
2254 targetsize = 4 * rawsize
2252 targetsize = 4 * rawsize
2255
2253
2256 bins = self._chunks(chain, targetsize=targetsize)
2254 bins = self._chunks(chain, targetsize=targetsize)
2257 if basetext is None:
2255 if basetext is None:
2258 basetext = bytes(bins[0])
2256 basetext = bytes(bins[0])
2259 bins = bins[1:]
2257 bins = bins[1:]
2260
2258
2261 rawtext = mdiff.patches(basetext, bins)
2259 rawtext = mdiff.patches(basetext, bins)
2262 del basetext # let us have a chance to free memory early
2260 del basetext # let us have a chance to free memory early
2263 return (rev, rawtext, False)
2261 return (rev, rawtext, False)
2264
2262
2265 def _sidedata(self, rev):
2263 def _sidedata(self, rev):
2266 """Return the sidedata for a given revision number."""
2264 """Return the sidedata for a given revision number."""
2267 index_entry = self.index[rev]
2265 index_entry = self.index[rev]
2268 sidedata_offset = index_entry[8]
2266 sidedata_offset = index_entry[8]
2269 sidedata_size = index_entry[9]
2267 sidedata_size = index_entry[9]
2270
2268
2271 if self._inline:
2269 if self._inline:
2272 sidedata_offset += self.index.entry_size * (1 + rev)
2270 sidedata_offset += self.index.entry_size * (1 + rev)
2273 if sidedata_size == 0:
2271 if sidedata_size == 0:
2274 return {}
2272 return {}
2275
2273
2276 if self._docket.sidedata_end < sidedata_offset + sidedata_size:
2274 if self._docket.sidedata_end < sidedata_offset + sidedata_size:
2277 filename = self._sidedatafile
2275 filename = self._sidedatafile
2278 end = self._docket.sidedata_end
2276 end = self._docket.sidedata_end
2279 offset = sidedata_offset
2277 offset = sidedata_offset
2280 length = sidedata_size
2278 length = sidedata_size
2281 m = FILE_TOO_SHORT_MSG % (filename, length, offset, end)
2279 m = FILE_TOO_SHORT_MSG % (filename, length, offset, end)
2282 raise error.RevlogError(m)
2280 raise error.RevlogError(m)
2283
2281
2284 comp_segment = self._segmentfile_sidedata.read_chunk(
2282 comp_segment = self._segmentfile_sidedata.read_chunk(
2285 sidedata_offset, sidedata_size
2283 sidedata_offset, sidedata_size
2286 )
2284 )
2287
2285
2288 comp = self.index[rev][11]
2286 comp = self.index[rev][11]
2289 if comp == COMP_MODE_PLAIN:
2287 if comp == COMP_MODE_PLAIN:
2290 segment = comp_segment
2288 segment = comp_segment
2291 elif comp == COMP_MODE_DEFAULT:
2289 elif comp == COMP_MODE_DEFAULT:
2292 segment = self._decompressor(comp_segment)
2290 segment = self._decompressor(comp_segment)
2293 elif comp == COMP_MODE_INLINE:
2291 elif comp == COMP_MODE_INLINE:
2294 segment = self.decompress(comp_segment)
2292 segment = self.decompress(comp_segment)
2295 else:
2293 else:
2296 msg = b'unknown compression mode %d'
2294 msg = b'unknown compression mode %d'
2297 msg %= comp
2295 msg %= comp
2298 raise error.RevlogError(msg)
2296 raise error.RevlogError(msg)
2299
2297
2300 sidedata = sidedatautil.deserialize_sidedata(segment)
2298 sidedata = sidedatautil.deserialize_sidedata(segment)
2301 return sidedata
2299 return sidedata
2302
2300
2303 def rawdata(self, nodeorrev):
2301 def rawdata(self, nodeorrev):
2304 """return an uncompressed raw data of a given node or revision number."""
2302 """return an uncompressed raw data of a given node or revision number."""
2305 return self._revisiondata(nodeorrev, raw=True)
2303 return self._revisiondata(nodeorrev, raw=True)
2306
2304
2307 def hash(self, text, p1, p2):
2305 def hash(self, text, p1, p2):
2308 """Compute a node hash.
2306 """Compute a node hash.
2309
2307
2310 Available as a function so that subclasses can replace the hash
2308 Available as a function so that subclasses can replace the hash
2311 as needed.
2309 as needed.
2312 """
2310 """
2313 return storageutil.hashrevisionsha1(text, p1, p2)
2311 return storageutil.hashrevisionsha1(text, p1, p2)
2314
2312
2315 def checkhash(self, text, node, p1=None, p2=None, rev=None):
2313 def checkhash(self, text, node, p1=None, p2=None, rev=None):
2316 """Check node hash integrity.
2314 """Check node hash integrity.
2317
2315
2318 Available as a function so that subclasses can extend hash mismatch
2316 Available as a function so that subclasses can extend hash mismatch
2319 behaviors as needed.
2317 behaviors as needed.
2320 """
2318 """
2321 try:
2319 try:
2322 if p1 is None and p2 is None:
2320 if p1 is None and p2 is None:
2323 p1, p2 = self.parents(node)
2321 p1, p2 = self.parents(node)
2324 if node != self.hash(text, p1, p2):
2322 if node != self.hash(text, p1, p2):
2325 # Clear the revision cache on hash failure. The revision cache
2323 # Clear the revision cache on hash failure. The revision cache
2326 # only stores the raw revision and clearing the cache does have
2324 # only stores the raw revision and clearing the cache does have
2327 # the side-effect that we won't have a cache hit when the raw
2325 # the side-effect that we won't have a cache hit when the raw
2328 # revision data is accessed. But this case should be rare and
2326 # revision data is accessed. But this case should be rare and
2329 # it is extra work to teach the cache about the hash
2327 # it is extra work to teach the cache about the hash
2330 # verification state.
2328 # verification state.
2331 if self._revisioncache and self._revisioncache[0] == node:
2329 if self._revisioncache and self._revisioncache[0] == node:
2332 self._revisioncache = None
2330 self._revisioncache = None
2333
2331
2334 revornode = rev
2332 revornode = rev
2335 if revornode is None:
2333 if revornode is None:
2336 revornode = templatefilters.short(hex(node))
2334 revornode = templatefilters.short(hex(node))
2337 raise error.RevlogError(
2335 raise error.RevlogError(
2338 _(b"integrity check failed on %s:%s")
2336 _(b"integrity check failed on %s:%s")
2339 % (self.display_id, pycompat.bytestr(revornode))
2337 % (self.display_id, pycompat.bytestr(revornode))
2340 )
2338 )
2341 except error.RevlogError:
2339 except error.RevlogError:
2342 if self._censorable and storageutil.iscensoredtext(text):
2340 if self._censorable and storageutil.iscensoredtext(text):
2343 raise error.CensoredNodeError(self.display_id, node, text)
2341 raise error.CensoredNodeError(self.display_id, node, text)
2344 raise
2342 raise
2345
2343
2346 @property
2344 @property
2347 def _split_index_file(self):
2345 def _split_index_file(self):
2348 """the path where to expect the index of an ongoing splitting operation
2346 """the path where to expect the index of an ongoing splitting operation
2349
2347
2350 The file will only exist if a splitting operation is in progress, but
2348 The file will only exist if a splitting operation is in progress, but
2351 it is always expected at the same location."""
2349 it is always expected at the same location."""
2352 parts = self.radix.split(b'/')
2350 parts = self.radix.split(b'/')
2353 if len(parts) > 1:
2351 if len(parts) > 1:
2354 # adds a '-s' prefix to the ``data/` or `meta/` base
2352 # adds a '-s' prefix to the ``data/` or `meta/` base
2355 head = parts[0] + b'-s'
2353 head = parts[0] + b'-s'
2356 mids = parts[1:-1]
2354 mids = parts[1:-1]
2357 tail = parts[-1] + b'.i'
2355 tail = parts[-1] + b'.i'
2358 pieces = [head] + mids + [tail]
2356 pieces = [head] + mids + [tail]
2359 return b'/'.join(pieces)
2357 return b'/'.join(pieces)
2360 else:
2358 else:
2361 # the revlog is stored at the root of the store (changelog or
2359 # the revlog is stored at the root of the store (changelog or
2362 # manifest), no risk of collision.
2360 # manifest), no risk of collision.
2363 return self.radix + b'.i.s'
2361 return self.radix + b'.i.s'
2364
2362
2365 def _enforceinlinesize(self, tr, side_write=True):
2363 def _enforceinlinesize(self, tr, side_write=True):
2366 """Check if the revlog is too big for inline and convert if so.
2364 """Check if the revlog is too big for inline and convert if so.
2367
2365
2368 This should be called after revisions are added to the revlog. If the
2366 This should be called after revisions are added to the revlog. If the
2369 revlog has grown too large to be an inline revlog, it will convert it
2367 revlog has grown too large to be an inline revlog, it will convert it
2370 to use multiple index and data files.
2368 to use multiple index and data files.
2371 """
2369 """
2372 tiprev = len(self) - 1
2370 tiprev = len(self) - 1
2373 total_size = self.start(tiprev) + self.length(tiprev)
2371 total_size = self.start(tiprev) + self.length(tiprev)
2374 if not self._inline or total_size < _maxinline:
2372 if not self._inline or total_size < _maxinline:
2375 return
2373 return
2376
2374
2377 troffset = tr.findoffset(self._indexfile)
2375 troffset = tr.findoffset(self._indexfile)
2378 if troffset is None:
2376 if troffset is None:
2379 raise error.RevlogError(
2377 raise error.RevlogError(
2380 _(b"%s not found in the transaction") % self._indexfile
2378 _(b"%s not found in the transaction") % self._indexfile
2381 )
2379 )
2382 if troffset:
2380 if troffset:
2383 tr.addbackup(self._indexfile, for_offset=True)
2381 tr.addbackup(self._indexfile, for_offset=True)
2384 tr.add(self._datafile, 0)
2382 tr.add(self._datafile, 0)
2385
2383
2386 existing_handles = False
2384 existing_handles = False
2387 if self._writinghandles is not None:
2385 if self._writinghandles is not None:
2388 existing_handles = True
2386 existing_handles = True
2389 fp = self._writinghandles[0]
2387 fp = self._writinghandles[0]
2390 fp.flush()
2388 fp.flush()
2391 fp.close()
2389 fp.close()
2392 # We can't use the cached file handle after close(). So prevent
2390 # We can't use the cached file handle after close(). So prevent
2393 # its usage.
2391 # its usage.
2394 self._writinghandles = None
2392 self._writinghandles = None
2395 self._segmentfile.writing_handle = None
2393 self._segmentfile.writing_handle = None
2396 # No need to deal with sidedata writing handle as it is only
2394 # No need to deal with sidedata writing handle as it is only
2397 # relevant with revlog-v2 which is never inline, not reaching
2395 # relevant with revlog-v2 which is never inline, not reaching
2398 # this code
2396 # this code
2399 if side_write:
2397 if side_write:
2400 old_index_file_path = self._indexfile
2398 old_index_file_path = self._indexfile
2401 new_index_file_path = self._split_index_file
2399 new_index_file_path = self._split_index_file
2402 opener = self.opener
2400 opener = self.opener
2403 weak_self = weakref.ref(self)
2401 weak_self = weakref.ref(self)
2404
2402
2405 # the "split" index replace the real index when the transaction is finalized
2403 # the "split" index replace the real index when the transaction is finalized
2406 def finalize_callback(tr):
2404 def finalize_callback(tr):
2407 opener.rename(
2405 opener.rename(
2408 new_index_file_path,
2406 new_index_file_path,
2409 old_index_file_path,
2407 old_index_file_path,
2410 checkambig=True,
2408 checkambig=True,
2411 )
2409 )
2412 maybe_self = weak_self()
2410 maybe_self = weak_self()
2413 if maybe_self is not None:
2411 if maybe_self is not None:
2414 maybe_self._indexfile = old_index_file_path
2412 maybe_self._indexfile = old_index_file_path
2415
2413
2416 def abort_callback(tr):
2414 def abort_callback(tr):
2417 maybe_self = weak_self()
2415 maybe_self = weak_self()
2418 if maybe_self is not None:
2416 if maybe_self is not None:
2419 maybe_self._indexfile = old_index_file_path
2417 maybe_self._indexfile = old_index_file_path
2420
2418
2421 tr.registertmp(new_index_file_path)
2419 tr.registertmp(new_index_file_path)
2422 if self.target[1] is not None:
2420 if self.target[1] is not None:
2423 callback_id = b'000-revlog-split-%d-%s' % self.target
2421 callback_id = b'000-revlog-split-%d-%s' % self.target
2424 else:
2422 else:
2425 callback_id = b'000-revlog-split-%d' % self.target[0]
2423 callback_id = b'000-revlog-split-%d' % self.target[0]
2426 tr.addfinalize(callback_id, finalize_callback)
2424 tr.addfinalize(callback_id, finalize_callback)
2427 tr.addabort(callback_id, abort_callback)
2425 tr.addabort(callback_id, abort_callback)
2428
2426
2429 new_dfh = self._datafp(b'w+')
2427 new_dfh = self._datafp(b'w+')
2430 new_dfh.truncate(0) # drop any potentially existing data
2428 new_dfh.truncate(0) # drop any potentially existing data
2431 try:
2429 try:
2432 with self.reading():
2430 with self.reading():
2433 for r in self:
2431 for r in self:
2434 new_dfh.write(self._getsegmentforrevs(r, r)[1])
2432 new_dfh.write(self._getsegmentforrevs(r, r)[1])
2435 new_dfh.flush()
2433 new_dfh.flush()
2436
2434
2437 if side_write:
2435 if side_write:
2438 self._indexfile = new_index_file_path
2436 self._indexfile = new_index_file_path
2439 with self.__index_new_fp() as fp:
2437 with self.__index_new_fp() as fp:
2440 self._format_flags &= ~FLAG_INLINE_DATA
2438 self._format_flags &= ~FLAG_INLINE_DATA
2441 self._inline = False
2439 self._inline = False
2442 for i in self:
2440 for i in self:
2443 e = self.index.entry_binary(i)
2441 e = self.index.entry_binary(i)
2444 if i == 0 and self._docket is None:
2442 if i == 0 and self._docket is None:
2445 header = self._format_flags | self._format_version
2443 header = self._format_flags | self._format_version
2446 header = self.index.pack_header(header)
2444 header = self.index.pack_header(header)
2447 e = header + e
2445 e = header + e
2448 fp.write(e)
2446 fp.write(e)
2449 if self._docket is not None:
2447 if self._docket is not None:
2450 self._docket.index_end = fp.tell()
2448 self._docket.index_end = fp.tell()
2451
2449
2452 # If we don't use side-write, the temp file replace the real
2450 # If we don't use side-write, the temp file replace the real
2453 # index when we exit the context manager
2451 # index when we exit the context manager
2454
2452
2455 nodemaputil.setup_persistent_nodemap(tr, self)
2453 nodemaputil.setup_persistent_nodemap(tr, self)
2456 self._segmentfile = randomaccessfile.randomaccessfile(
2454 self._segmentfile = randomaccessfile.randomaccessfile(
2457 self.opener,
2455 self.opener,
2458 self._datafile,
2456 self._datafile,
2459 self._chunkcachesize,
2457 self._chunkcachesize,
2460 )
2458 )
2461
2459
2462 if existing_handles:
2460 if existing_handles:
2463 # switched from inline to conventional reopen the index
2461 # switched from inline to conventional reopen the index
2464 ifh = self.__index_write_fp()
2462 ifh = self.__index_write_fp()
2465 self._writinghandles = (ifh, new_dfh, None)
2463 self._writinghandles = (ifh, new_dfh, None)
2466 self._segmentfile.writing_handle = new_dfh
2464 self._segmentfile.writing_handle = new_dfh
2467 new_dfh = None
2465 new_dfh = None
2468 # No need to deal with sidedata writing handle as it is only
2466 # No need to deal with sidedata writing handle as it is only
2469 # relevant with revlog-v2 which is never inline, not reaching
2467 # relevant with revlog-v2 which is never inline, not reaching
2470 # this code
2468 # this code
2471 finally:
2469 finally:
2472 if new_dfh is not None:
2470 if new_dfh is not None:
2473 new_dfh.close()
2471 new_dfh.close()
2474
2472
2475 def _nodeduplicatecallback(self, transaction, node):
2473 def _nodeduplicatecallback(self, transaction, node):
2476 """called when trying to add a node already stored."""
2474 """called when trying to add a node already stored."""
2477
2475
2478 @contextlib.contextmanager
2476 @contextlib.contextmanager
2479 def reading(self):
2477 def reading(self):
2480 """Context manager that keeps data and sidedata files open for reading"""
2478 """Context manager that keeps data and sidedata files open for reading"""
2481 if len(self.index) == 0:
2479 if len(self.index) == 0:
2482 yield # nothing to be read
2480 yield # nothing to be read
2483 else:
2481 else:
2484 with self._segmentfile.reading():
2482 with self._segmentfile.reading():
2485 with self._segmentfile_sidedata.reading():
2483 with self._segmentfile_sidedata.reading():
2486 yield
2484 yield
2487
2485
2488 @contextlib.contextmanager
2486 @contextlib.contextmanager
2489 def _writing(self, transaction):
2487 def _writing(self, transaction):
2490 if self._trypending:
2488 if self._trypending:
2491 msg = b'try to write in a `trypending` revlog: %s'
2489 msg = b'try to write in a `trypending` revlog: %s'
2492 msg %= self.display_id
2490 msg %= self.display_id
2493 raise error.ProgrammingError(msg)
2491 raise error.ProgrammingError(msg)
2494 if self._writinghandles is not None:
2492 if self._writinghandles is not None:
2495 yield
2493 yield
2496 else:
2494 else:
2497 ifh = dfh = sdfh = None
2495 ifh = dfh = sdfh = None
2498 try:
2496 try:
2499 r = len(self)
2497 r = len(self)
2500 # opening the data file.
2498 # opening the data file.
2501 dsize = 0
2499 dsize = 0
2502 if r:
2500 if r:
2503 dsize = self.end(r - 1)
2501 dsize = self.end(r - 1)
2504 dfh = None
2502 dfh = None
2505 if not self._inline:
2503 if not self._inline:
2506 try:
2504 try:
2507 dfh = self._datafp(b"r+")
2505 dfh = self._datafp(b"r+")
2508 if self._docket is None:
2506 if self._docket is None:
2509 dfh.seek(0, os.SEEK_END)
2507 dfh.seek(0, os.SEEK_END)
2510 else:
2508 else:
2511 dfh.seek(self._docket.data_end, os.SEEK_SET)
2509 dfh.seek(self._docket.data_end, os.SEEK_SET)
2512 except FileNotFoundError:
2510 except FileNotFoundError:
2513 dfh = self._datafp(b"w+")
2511 dfh = self._datafp(b"w+")
2514 transaction.add(self._datafile, dsize)
2512 transaction.add(self._datafile, dsize)
2515 if self._sidedatafile is not None:
2513 if self._sidedatafile is not None:
2516 # revlog-v2 does not inline, help Pytype
2514 # revlog-v2 does not inline, help Pytype
2517 assert dfh is not None
2515 assert dfh is not None
2518 try:
2516 try:
2519 sdfh = self.opener(self._sidedatafile, mode=b"r+")
2517 sdfh = self.opener(self._sidedatafile, mode=b"r+")
2520 dfh.seek(self._docket.sidedata_end, os.SEEK_SET)
2518 dfh.seek(self._docket.sidedata_end, os.SEEK_SET)
2521 except FileNotFoundError:
2519 except FileNotFoundError:
2522 sdfh = self.opener(self._sidedatafile, mode=b"w+")
2520 sdfh = self.opener(self._sidedatafile, mode=b"w+")
2523 transaction.add(
2521 transaction.add(
2524 self._sidedatafile, self._docket.sidedata_end
2522 self._sidedatafile, self._docket.sidedata_end
2525 )
2523 )
2526
2524
2527 # opening the index file.
2525 # opening the index file.
2528 isize = r * self.index.entry_size
2526 isize = r * self.index.entry_size
2529 ifh = self.__index_write_fp()
2527 ifh = self.__index_write_fp()
2530 if self._inline:
2528 if self._inline:
2531 transaction.add(self._indexfile, dsize + isize)
2529 transaction.add(self._indexfile, dsize + isize)
2532 else:
2530 else:
2533 transaction.add(self._indexfile, isize)
2531 transaction.add(self._indexfile, isize)
2534 # exposing all file handle for writing.
2532 # exposing all file handle for writing.
2535 self._writinghandles = (ifh, dfh, sdfh)
2533 self._writinghandles = (ifh, dfh, sdfh)
2536 self._segmentfile.writing_handle = ifh if self._inline else dfh
2534 self._segmentfile.writing_handle = ifh if self._inline else dfh
2537 self._segmentfile_sidedata.writing_handle = sdfh
2535 self._segmentfile_sidedata.writing_handle = sdfh
2538 yield
2536 yield
2539 if self._docket is not None:
2537 if self._docket is not None:
2540 self._write_docket(transaction)
2538 self._write_docket(transaction)
2541 finally:
2539 finally:
2542 self._writinghandles = None
2540 self._writinghandles = None
2543 self._segmentfile.writing_handle = None
2541 self._segmentfile.writing_handle = None
2544 self._segmentfile_sidedata.writing_handle = None
2542 self._segmentfile_sidedata.writing_handle = None
2545 if dfh is not None:
2543 if dfh is not None:
2546 dfh.close()
2544 dfh.close()
2547 if sdfh is not None:
2545 if sdfh is not None:
2548 sdfh.close()
2546 sdfh.close()
2549 # closing the index file last to avoid exposing referent to
2547 # closing the index file last to avoid exposing referent to
2550 # potential unflushed data content.
2548 # potential unflushed data content.
2551 if ifh is not None:
2549 if ifh is not None:
2552 ifh.close()
2550 ifh.close()
2553
2551
2554 def _write_docket(self, transaction):
2552 def _write_docket(self, transaction):
2555 """write the current docket on disk
2553 """write the current docket on disk
2556
2554
2557 Exist as a method to help changelog to implement transaction logic
2555 Exist as a method to help changelog to implement transaction logic
2558
2556
2559 We could also imagine using the same transaction logic for all revlog
2557 We could also imagine using the same transaction logic for all revlog
2560 since docket are cheap."""
2558 since docket are cheap."""
2561 self._docket.write(transaction)
2559 self._docket.write(transaction)
2562
2560
2563 def addrevision(
2561 def addrevision(
2564 self,
2562 self,
2565 text,
2563 text,
2566 transaction,
2564 transaction,
2567 link,
2565 link,
2568 p1,
2566 p1,
2569 p2,
2567 p2,
2570 cachedelta=None,
2568 cachedelta=None,
2571 node=None,
2569 node=None,
2572 flags=REVIDX_DEFAULT_FLAGS,
2570 flags=REVIDX_DEFAULT_FLAGS,
2573 deltacomputer=None,
2571 deltacomputer=None,
2574 sidedata=None,
2572 sidedata=None,
2575 ):
2573 ):
2576 """add a revision to the log
2574 """add a revision to the log
2577
2575
2578 text - the revision data to add
2576 text - the revision data to add
2579 transaction - the transaction object used for rollback
2577 transaction - the transaction object used for rollback
2580 link - the linkrev data to add
2578 link - the linkrev data to add
2581 p1, p2 - the parent nodeids of the revision
2579 p1, p2 - the parent nodeids of the revision
2582 cachedelta - an optional precomputed delta
2580 cachedelta - an optional precomputed delta
2583 node - nodeid of revision; typically node is not specified, and it is
2581 node - nodeid of revision; typically node is not specified, and it is
2584 computed by default as hash(text, p1, p2), however subclasses might
2582 computed by default as hash(text, p1, p2), however subclasses might
2585 use different hashing method (and override checkhash() in such case)
2583 use different hashing method (and override checkhash() in such case)
2586 flags - the known flags to set on the revision
2584 flags - the known flags to set on the revision
2587 deltacomputer - an optional deltacomputer instance shared between
2585 deltacomputer - an optional deltacomputer instance shared between
2588 multiple calls
2586 multiple calls
2589 """
2587 """
2590 if link == nullrev:
2588 if link == nullrev:
2591 raise error.RevlogError(
2589 raise error.RevlogError(
2592 _(b"attempted to add linkrev -1 to %s") % self.display_id
2590 _(b"attempted to add linkrev -1 to %s") % self.display_id
2593 )
2591 )
2594
2592
2595 if sidedata is None:
2593 if sidedata is None:
2596 sidedata = {}
2594 sidedata = {}
2597 elif sidedata and not self.hassidedata:
2595 elif sidedata and not self.hassidedata:
2598 raise error.ProgrammingError(
2596 raise error.ProgrammingError(
2599 _(b"trying to add sidedata to a revlog who don't support them")
2597 _(b"trying to add sidedata to a revlog who don't support them")
2600 )
2598 )
2601
2599
2602 if flags:
2600 if flags:
2603 node = node or self.hash(text, p1, p2)
2601 node = node or self.hash(text, p1, p2)
2604
2602
2605 rawtext, validatehash = flagutil.processflagswrite(self, text, flags)
2603 rawtext, validatehash = flagutil.processflagswrite(self, text, flags)
2606
2604
2607 # If the flag processor modifies the revision data, ignore any provided
2605 # If the flag processor modifies the revision data, ignore any provided
2608 # cachedelta.
2606 # cachedelta.
2609 if rawtext != text:
2607 if rawtext != text:
2610 cachedelta = None
2608 cachedelta = None
2611
2609
2612 if len(rawtext) > _maxentrysize:
2610 if len(rawtext) > _maxentrysize:
2613 raise error.RevlogError(
2611 raise error.RevlogError(
2614 _(
2612 _(
2615 b"%s: size of %d bytes exceeds maximum revlog storage of 2GiB"
2613 b"%s: size of %d bytes exceeds maximum revlog storage of 2GiB"
2616 )
2614 )
2617 % (self.display_id, len(rawtext))
2615 % (self.display_id, len(rawtext))
2618 )
2616 )
2619
2617
2620 node = node or self.hash(rawtext, p1, p2)
2618 node = node or self.hash(rawtext, p1, p2)
2621 rev = self.index.get_rev(node)
2619 rev = self.index.get_rev(node)
2622 if rev is not None:
2620 if rev is not None:
2623 return rev
2621 return rev
2624
2622
2625 if validatehash:
2623 if validatehash:
2626 self.checkhash(rawtext, node, p1=p1, p2=p2)
2624 self.checkhash(rawtext, node, p1=p1, p2=p2)
2627
2625
2628 return self.addrawrevision(
2626 return self.addrawrevision(
2629 rawtext,
2627 rawtext,
2630 transaction,
2628 transaction,
2631 link,
2629 link,
2632 p1,
2630 p1,
2633 p2,
2631 p2,
2634 node,
2632 node,
2635 flags,
2633 flags,
2636 cachedelta=cachedelta,
2634 cachedelta=cachedelta,
2637 deltacomputer=deltacomputer,
2635 deltacomputer=deltacomputer,
2638 sidedata=sidedata,
2636 sidedata=sidedata,
2639 )
2637 )
2640
2638
2641 def addrawrevision(
2639 def addrawrevision(
2642 self,
2640 self,
2643 rawtext,
2641 rawtext,
2644 transaction,
2642 transaction,
2645 link,
2643 link,
2646 p1,
2644 p1,
2647 p2,
2645 p2,
2648 node,
2646 node,
2649 flags,
2647 flags,
2650 cachedelta=None,
2648 cachedelta=None,
2651 deltacomputer=None,
2649 deltacomputer=None,
2652 sidedata=None,
2650 sidedata=None,
2653 ):
2651 ):
2654 """add a raw revision with known flags, node and parents
2652 """add a raw revision with known flags, node and parents
2655 useful when reusing a revision not stored in this revlog (ex: received
2653 useful when reusing a revision not stored in this revlog (ex: received
2656 over wire, or read from an external bundle).
2654 over wire, or read from an external bundle).
2657 """
2655 """
2658 with self._writing(transaction):
2656 with self._writing(transaction):
2659 return self._addrevision(
2657 return self._addrevision(
2660 node,
2658 node,
2661 rawtext,
2659 rawtext,
2662 transaction,
2660 transaction,
2663 link,
2661 link,
2664 p1,
2662 p1,
2665 p2,
2663 p2,
2666 flags,
2664 flags,
2667 cachedelta,
2665 cachedelta,
2668 deltacomputer=deltacomputer,
2666 deltacomputer=deltacomputer,
2669 sidedata=sidedata,
2667 sidedata=sidedata,
2670 )
2668 )
2671
2669
2672 def compress(self, data):
2670 def compress(self, data):
2673 """Generate a possibly-compressed representation of data."""
2671 """Generate a possibly-compressed representation of data."""
2674 if not data:
2672 if not data:
2675 return b'', data
2673 return b'', data
2676
2674
2677 compressed = self._compressor.compress(data)
2675 compressed = self._compressor.compress(data)
2678
2676
2679 if compressed:
2677 if compressed:
2680 # The revlog compressor added the header in the returned data.
2678 # The revlog compressor added the header in the returned data.
2681 return b'', compressed
2679 return b'', compressed
2682
2680
2683 if data[0:1] == b'\0':
2681 if data[0:1] == b'\0':
2684 return b'', data
2682 return b'', data
2685 return b'u', data
2683 return b'u', data
2686
2684
2687 def decompress(self, data):
2685 def decompress(self, data):
2688 """Decompress a revlog chunk.
2686 """Decompress a revlog chunk.
2689
2687
2690 The chunk is expected to begin with a header identifying the
2688 The chunk is expected to begin with a header identifying the
2691 format type so it can be routed to an appropriate decompressor.
2689 format type so it can be routed to an appropriate decompressor.
2692 """
2690 """
2693 if not data:
2691 if not data:
2694 return data
2692 return data
2695
2693
2696 # Revlogs are read much more frequently than they are written and many
2694 # Revlogs are read much more frequently than they are written and many
2697 # chunks only take microseconds to decompress, so performance is
2695 # chunks only take microseconds to decompress, so performance is
2698 # important here.
2696 # important here.
2699 #
2697 #
2700 # We can make a few assumptions about revlogs:
2698 # We can make a few assumptions about revlogs:
2701 #
2699 #
2702 # 1) the majority of chunks will be compressed (as opposed to inline
2700 # 1) the majority of chunks will be compressed (as opposed to inline
2703 # raw data).
2701 # raw data).
2704 # 2) decompressing *any* data will likely by at least 10x slower than
2702 # 2) decompressing *any* data will likely by at least 10x slower than
2705 # returning raw inline data.
2703 # returning raw inline data.
2706 # 3) we want to prioritize common and officially supported compression
2704 # 3) we want to prioritize common and officially supported compression
2707 # engines
2705 # engines
2708 #
2706 #
2709 # It follows that we want to optimize for "decompress compressed data
2707 # It follows that we want to optimize for "decompress compressed data
2710 # when encoded with common and officially supported compression engines"
2708 # when encoded with common and officially supported compression engines"
2711 # case over "raw data" and "data encoded by less common or non-official
2709 # case over "raw data" and "data encoded by less common or non-official
2712 # compression engines." That is why we have the inline lookup first
2710 # compression engines." That is why we have the inline lookup first
2713 # followed by the compengines lookup.
2711 # followed by the compengines lookup.
2714 #
2712 #
2715 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
2713 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
2716 # compressed chunks. And this matters for changelog and manifest reads.
2714 # compressed chunks. And this matters for changelog and manifest reads.
2717 t = data[0:1]
2715 t = data[0:1]
2718
2716
2719 if t == b'x':
2717 if t == b'x':
2720 try:
2718 try:
2721 return _zlibdecompress(data)
2719 return _zlibdecompress(data)
2722 except zlib.error as e:
2720 except zlib.error as e:
2723 raise error.RevlogError(
2721 raise error.RevlogError(
2724 _(b'revlog decompress error: %s')
2722 _(b'revlog decompress error: %s')
2725 % stringutil.forcebytestr(e)
2723 % stringutil.forcebytestr(e)
2726 )
2724 )
2727 # '\0' is more common than 'u' so it goes first.
2725 # '\0' is more common than 'u' so it goes first.
2728 elif t == b'\0':
2726 elif t == b'\0':
2729 return data
2727 return data
2730 elif t == b'u':
2728 elif t == b'u':
2731 return util.buffer(data, 1)
2729 return util.buffer(data, 1)
2732
2730
2733 compressor = self._get_decompressor(t)
2731 compressor = self._get_decompressor(t)
2734
2732
2735 return compressor.decompress(data)
2733 return compressor.decompress(data)
2736
2734
2737 def _addrevision(
2735 def _addrevision(
2738 self,
2736 self,
2739 node,
2737 node,
2740 rawtext,
2738 rawtext,
2741 transaction,
2739 transaction,
2742 link,
2740 link,
2743 p1,
2741 p1,
2744 p2,
2742 p2,
2745 flags,
2743 flags,
2746 cachedelta,
2744 cachedelta,
2747 alwayscache=False,
2745 alwayscache=False,
2748 deltacomputer=None,
2746 deltacomputer=None,
2749 sidedata=None,
2747 sidedata=None,
2750 ):
2748 ):
2751 """internal function to add revisions to the log
2749 """internal function to add revisions to the log
2752
2750
2753 see addrevision for argument descriptions.
2751 see addrevision for argument descriptions.
2754
2752
2755 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2753 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2756
2754
2757 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2755 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2758 be used.
2756 be used.
2759
2757
2760 invariants:
2758 invariants:
2761 - rawtext is optional (can be None); if not set, cachedelta must be set.
2759 - rawtext is optional (can be None); if not set, cachedelta must be set.
2762 if both are set, they must correspond to each other.
2760 if both are set, they must correspond to each other.
2763 """
2761 """
2764 if node == self.nullid:
2762 if node == self.nullid:
2765 raise error.RevlogError(
2763 raise error.RevlogError(
2766 _(b"%s: attempt to add null revision") % self.display_id
2764 _(b"%s: attempt to add null revision") % self.display_id
2767 )
2765 )
2768 if (
2766 if (
2769 node == self.nodeconstants.wdirid
2767 node == self.nodeconstants.wdirid
2770 or node in self.nodeconstants.wdirfilenodeids
2768 or node in self.nodeconstants.wdirfilenodeids
2771 ):
2769 ):
2772 raise error.RevlogError(
2770 raise error.RevlogError(
2773 _(b"%s: attempt to add wdir revision") % self.display_id
2771 _(b"%s: attempt to add wdir revision") % self.display_id
2774 )
2772 )
2775 if self._writinghandles is None:
2773 if self._writinghandles is None:
2776 msg = b'adding revision outside `revlog._writing` context'
2774 msg = b'adding revision outside `revlog._writing` context'
2777 raise error.ProgrammingError(msg)
2775 raise error.ProgrammingError(msg)
2778
2776
2779 btext = [rawtext]
2777 btext = [rawtext]
2780
2778
2781 curr = len(self)
2779 curr = len(self)
2782 prev = curr - 1
2780 prev = curr - 1
2783
2781
2784 offset = self._get_data_offset(prev)
2782 offset = self._get_data_offset(prev)
2785
2783
2786 if self._concurrencychecker:
2784 if self._concurrencychecker:
2787 ifh, dfh, sdfh = self._writinghandles
2785 ifh, dfh, sdfh = self._writinghandles
2788 # XXX no checking for the sidedata file
2786 # XXX no checking for the sidedata file
2789 if self._inline:
2787 if self._inline:
2790 # offset is "as if" it were in the .d file, so we need to add on
2788 # offset is "as if" it were in the .d file, so we need to add on
2791 # the size of the entry metadata.
2789 # the size of the entry metadata.
2792 self._concurrencychecker(
2790 self._concurrencychecker(
2793 ifh, self._indexfile, offset + curr * self.index.entry_size
2791 ifh, self._indexfile, offset + curr * self.index.entry_size
2794 )
2792 )
2795 else:
2793 else:
2796 # Entries in the .i are a consistent size.
2794 # Entries in the .i are a consistent size.
2797 self._concurrencychecker(
2795 self._concurrencychecker(
2798 ifh, self._indexfile, curr * self.index.entry_size
2796 ifh, self._indexfile, curr * self.index.entry_size
2799 )
2797 )
2800 self._concurrencychecker(dfh, self._datafile, offset)
2798 self._concurrencychecker(dfh, self._datafile, offset)
2801
2799
2802 p1r, p2r = self.rev(p1), self.rev(p2)
2800 p1r, p2r = self.rev(p1), self.rev(p2)
2803
2801
2804 # full versions are inserted when the needed deltas
2802 # full versions are inserted when the needed deltas
2805 # become comparable to the uncompressed text
2803 # become comparable to the uncompressed text
2806 if rawtext is None:
2804 if rawtext is None:
2807 # need rawtext size, before changed by flag processors, which is
2805 # need rawtext size, before changed by flag processors, which is
2808 # the non-raw size. use revlog explicitly to avoid filelog's extra
2806 # the non-raw size. use revlog explicitly to avoid filelog's extra
2809 # logic that might remove metadata size.
2807 # logic that might remove metadata size.
2810 textlen = mdiff.patchedsize(
2808 textlen = mdiff.patchedsize(
2811 revlog.size(self, cachedelta[0]), cachedelta[1]
2809 revlog.size(self, cachedelta[0]), cachedelta[1]
2812 )
2810 )
2813 else:
2811 else:
2814 textlen = len(rawtext)
2812 textlen = len(rawtext)
2815
2813
2816 if deltacomputer is None:
2814 if deltacomputer is None:
2817 write_debug = None
2815 write_debug = None
2818 if self._debug_delta:
2816 if self._debug_delta:
2819 write_debug = transaction._report
2817 write_debug = transaction._report
2820 deltacomputer = deltautil.deltacomputer(
2818 deltacomputer = deltautil.deltacomputer(
2821 self, write_debug=write_debug
2819 self, write_debug=write_debug
2822 )
2820 )
2823
2821
2824 if cachedelta is not None and len(cachedelta) == 2:
2822 if cachedelta is not None and len(cachedelta) == 2:
2825 # If the cached delta has no information about how it should be
2823 # If the cached delta has no information about how it should be
2826 # reused, add the default reuse instruction according to the
2824 # reused, add the default reuse instruction according to the
2827 # revlog's configuration.
2825 # revlog's configuration.
2828 if self._generaldelta and self._lazydeltabase:
2826 if self._generaldelta and self._lazydeltabase:
2829 delta_base_reuse = DELTA_BASE_REUSE_TRY
2827 delta_base_reuse = DELTA_BASE_REUSE_TRY
2830 else:
2828 else:
2831 delta_base_reuse = DELTA_BASE_REUSE_NO
2829 delta_base_reuse = DELTA_BASE_REUSE_NO
2832 cachedelta = (cachedelta[0], cachedelta[1], delta_base_reuse)
2830 cachedelta = (cachedelta[0], cachedelta[1], delta_base_reuse)
2833
2831
2834 revinfo = revlogutils.revisioninfo(
2832 revinfo = revlogutils.revisioninfo(
2835 node,
2833 node,
2836 p1,
2834 p1,
2837 p2,
2835 p2,
2838 btext,
2836 btext,
2839 textlen,
2837 textlen,
2840 cachedelta,
2838 cachedelta,
2841 flags,
2839 flags,
2842 )
2840 )
2843
2841
2844 deltainfo = deltacomputer.finddeltainfo(revinfo)
2842 deltainfo = deltacomputer.finddeltainfo(revinfo)
2845
2843
2846 compression_mode = COMP_MODE_INLINE
2844 compression_mode = COMP_MODE_INLINE
2847 if self._docket is not None:
2845 if self._docket is not None:
2848 default_comp = self._docket.default_compression_header
2846 default_comp = self._docket.default_compression_header
2849 r = deltautil.delta_compression(default_comp, deltainfo)
2847 r = deltautil.delta_compression(default_comp, deltainfo)
2850 compression_mode, deltainfo = r
2848 compression_mode, deltainfo = r
2851
2849
2852 sidedata_compression_mode = COMP_MODE_INLINE
2850 sidedata_compression_mode = COMP_MODE_INLINE
2853 if sidedata and self.hassidedata:
2851 if sidedata and self.hassidedata:
2854 sidedata_compression_mode = COMP_MODE_PLAIN
2852 sidedata_compression_mode = COMP_MODE_PLAIN
2855 serialized_sidedata = sidedatautil.serialize_sidedata(sidedata)
2853 serialized_sidedata = sidedatautil.serialize_sidedata(sidedata)
2856 sidedata_offset = self._docket.sidedata_end
2854 sidedata_offset = self._docket.sidedata_end
2857 h, comp_sidedata = self.compress(serialized_sidedata)
2855 h, comp_sidedata = self.compress(serialized_sidedata)
2858 if (
2856 if (
2859 h != b'u'
2857 h != b'u'
2860 and comp_sidedata[0:1] != b'\0'
2858 and comp_sidedata[0:1] != b'\0'
2861 and len(comp_sidedata) < len(serialized_sidedata)
2859 and len(comp_sidedata) < len(serialized_sidedata)
2862 ):
2860 ):
2863 assert not h
2861 assert not h
2864 if (
2862 if (
2865 comp_sidedata[0:1]
2863 comp_sidedata[0:1]
2866 == self._docket.default_compression_header
2864 == self._docket.default_compression_header
2867 ):
2865 ):
2868 sidedata_compression_mode = COMP_MODE_DEFAULT
2866 sidedata_compression_mode = COMP_MODE_DEFAULT
2869 serialized_sidedata = comp_sidedata
2867 serialized_sidedata = comp_sidedata
2870 else:
2868 else:
2871 sidedata_compression_mode = COMP_MODE_INLINE
2869 sidedata_compression_mode = COMP_MODE_INLINE
2872 serialized_sidedata = comp_sidedata
2870 serialized_sidedata = comp_sidedata
2873 else:
2871 else:
2874 serialized_sidedata = b""
2872 serialized_sidedata = b""
2875 # Don't store the offset if the sidedata is empty, that way
2873 # Don't store the offset if the sidedata is empty, that way
2876 # we can easily detect empty sidedata and they will be no different
2874 # we can easily detect empty sidedata and they will be no different
2877 # than ones we manually add.
2875 # than ones we manually add.
2878 sidedata_offset = 0
2876 sidedata_offset = 0
2879
2877
2880 rank = RANK_UNKNOWN
2878 rank = RANK_UNKNOWN
2881 if self._compute_rank:
2879 if self._compute_rank:
2882 if (p1r, p2r) == (nullrev, nullrev):
2880 if (p1r, p2r) == (nullrev, nullrev):
2883 rank = 1
2881 rank = 1
2884 elif p1r != nullrev and p2r == nullrev:
2882 elif p1r != nullrev and p2r == nullrev:
2885 rank = 1 + self.fast_rank(p1r)
2883 rank = 1 + self.fast_rank(p1r)
2886 elif p1r == nullrev and p2r != nullrev:
2884 elif p1r == nullrev and p2r != nullrev:
2887 rank = 1 + self.fast_rank(p2r)
2885 rank = 1 + self.fast_rank(p2r)
2888 else: # merge node
2886 else: # merge node
2889 if rustdagop is not None and self.index.rust_ext_compat:
2887 if rustdagop is not None and self.index.rust_ext_compat:
2890 rank = rustdagop.rank(self.index, p1r, p2r)
2888 rank = rustdagop.rank(self.index, p1r, p2r)
2891 else:
2889 else:
2892 pmin, pmax = sorted((p1r, p2r))
2890 pmin, pmax = sorted((p1r, p2r))
2893 rank = 1 + self.fast_rank(pmax)
2891 rank = 1 + self.fast_rank(pmax)
2894 rank += sum(1 for _ in self.findmissingrevs([pmax], [pmin]))
2892 rank += sum(1 for _ in self.findmissingrevs([pmax], [pmin]))
2895
2893
2896 e = revlogutils.entry(
2894 e = revlogutils.entry(
2897 flags=flags,
2895 flags=flags,
2898 data_offset=offset,
2896 data_offset=offset,
2899 data_compressed_length=deltainfo.deltalen,
2897 data_compressed_length=deltainfo.deltalen,
2900 data_uncompressed_length=textlen,
2898 data_uncompressed_length=textlen,
2901 data_compression_mode=compression_mode,
2899 data_compression_mode=compression_mode,
2902 data_delta_base=deltainfo.base,
2900 data_delta_base=deltainfo.base,
2903 link_rev=link,
2901 link_rev=link,
2904 parent_rev_1=p1r,
2902 parent_rev_1=p1r,
2905 parent_rev_2=p2r,
2903 parent_rev_2=p2r,
2906 node_id=node,
2904 node_id=node,
2907 sidedata_offset=sidedata_offset,
2905 sidedata_offset=sidedata_offset,
2908 sidedata_compressed_length=len(serialized_sidedata),
2906 sidedata_compressed_length=len(serialized_sidedata),
2909 sidedata_compression_mode=sidedata_compression_mode,
2907 sidedata_compression_mode=sidedata_compression_mode,
2910 rank=rank,
2908 rank=rank,
2911 )
2909 )
2912
2910
2913 self.index.append(e)
2911 self.index.append(e)
2914 entry = self.index.entry_binary(curr)
2912 entry = self.index.entry_binary(curr)
2915 if curr == 0 and self._docket is None:
2913 if curr == 0 and self._docket is None:
2916 header = self._format_flags | self._format_version
2914 header = self._format_flags | self._format_version
2917 header = self.index.pack_header(header)
2915 header = self.index.pack_header(header)
2918 entry = header + entry
2916 entry = header + entry
2919 self._writeentry(
2917 self._writeentry(
2920 transaction,
2918 transaction,
2921 entry,
2919 entry,
2922 deltainfo.data,
2920 deltainfo.data,
2923 link,
2921 link,
2924 offset,
2922 offset,
2925 serialized_sidedata,
2923 serialized_sidedata,
2926 sidedata_offset,
2924 sidedata_offset,
2927 )
2925 )
2928
2926
2929 rawtext = btext[0]
2927 rawtext = btext[0]
2930
2928
2931 if alwayscache and rawtext is None:
2929 if alwayscache and rawtext is None:
2932 rawtext = deltacomputer.buildtext(revinfo)
2930 rawtext = deltacomputer.buildtext(revinfo)
2933
2931
2934 if type(rawtext) == bytes: # only accept immutable objects
2932 if type(rawtext) == bytes: # only accept immutable objects
2935 self._revisioncache = (node, curr, rawtext)
2933 self._revisioncache = (node, curr, rawtext)
2936 self._chainbasecache[curr] = deltainfo.chainbase
2934 self._chainbasecache[curr] = deltainfo.chainbase
2937 return curr
2935 return curr
2938
2936
2939 def _get_data_offset(self, prev):
2937 def _get_data_offset(self, prev):
2940 """Returns the current offset in the (in-transaction) data file.
2938 """Returns the current offset in the (in-transaction) data file.
2941 Versions < 2 of the revlog can get this 0(1), revlog v2 needs a docket
2939 Versions < 2 of the revlog can get this 0(1), revlog v2 needs a docket
2942 file to store that information: since sidedata can be rewritten to the
2940 file to store that information: since sidedata can be rewritten to the
2943 end of the data file within a transaction, you can have cases where, for
2941 end of the data file within a transaction, you can have cases where, for
2944 example, rev `n` does not have sidedata while rev `n - 1` does, leading
2942 example, rev `n` does not have sidedata while rev `n - 1` does, leading
2945 to `n - 1`'s sidedata being written after `n`'s data.
2943 to `n - 1`'s sidedata being written after `n`'s data.
2946
2944
2947 TODO cache this in a docket file before getting out of experimental."""
2945 TODO cache this in a docket file before getting out of experimental."""
2948 if self._docket is None:
2946 if self._docket is None:
2949 return self.end(prev)
2947 return self.end(prev)
2950 else:
2948 else:
2951 return self._docket.data_end
2949 return self._docket.data_end
2952
2950
2953 def _writeentry(
2951 def _writeentry(
2954 self, transaction, entry, data, link, offset, sidedata, sidedata_offset
2952 self, transaction, entry, data, link, offset, sidedata, sidedata_offset
2955 ):
2953 ):
2956 # Files opened in a+ mode have inconsistent behavior on various
2954 # Files opened in a+ mode have inconsistent behavior on various
2957 # platforms. Windows requires that a file positioning call be made
2955 # platforms. Windows requires that a file positioning call be made
2958 # when the file handle transitions between reads and writes. See
2956 # when the file handle transitions between reads and writes. See
2959 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2957 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2960 # platforms, Python or the platform itself can be buggy. Some versions
2958 # platforms, Python or the platform itself can be buggy. Some versions
2961 # of Solaris have been observed to not append at the end of the file
2959 # of Solaris have been observed to not append at the end of the file
2962 # if the file was seeked to before the end. See issue4943 for more.
2960 # if the file was seeked to before the end. See issue4943 for more.
2963 #
2961 #
2964 # We work around this issue by inserting a seek() before writing.
2962 # We work around this issue by inserting a seek() before writing.
2965 # Note: This is likely not necessary on Python 3. However, because
2963 # Note: This is likely not necessary on Python 3. However, because
2966 # the file handle is reused for reads and may be seeked there, we need
2964 # the file handle is reused for reads and may be seeked there, we need
2967 # to be careful before changing this.
2965 # to be careful before changing this.
2968 if self._writinghandles is None:
2966 if self._writinghandles is None:
2969 msg = b'adding revision outside `revlog._writing` context'
2967 msg = b'adding revision outside `revlog._writing` context'
2970 raise error.ProgrammingError(msg)
2968 raise error.ProgrammingError(msg)
2971 ifh, dfh, sdfh = self._writinghandles
2969 ifh, dfh, sdfh = self._writinghandles
2972 if self._docket is None:
2970 if self._docket is None:
2973 ifh.seek(0, os.SEEK_END)
2971 ifh.seek(0, os.SEEK_END)
2974 else:
2972 else:
2975 ifh.seek(self._docket.index_end, os.SEEK_SET)
2973 ifh.seek(self._docket.index_end, os.SEEK_SET)
2976 if dfh:
2974 if dfh:
2977 if self._docket is None:
2975 if self._docket is None:
2978 dfh.seek(0, os.SEEK_END)
2976 dfh.seek(0, os.SEEK_END)
2979 else:
2977 else:
2980 dfh.seek(self._docket.data_end, os.SEEK_SET)
2978 dfh.seek(self._docket.data_end, os.SEEK_SET)
2981 if sdfh:
2979 if sdfh:
2982 sdfh.seek(self._docket.sidedata_end, os.SEEK_SET)
2980 sdfh.seek(self._docket.sidedata_end, os.SEEK_SET)
2983
2981
2984 curr = len(self) - 1
2982 curr = len(self) - 1
2985 if not self._inline:
2983 if not self._inline:
2986 transaction.add(self._datafile, offset)
2984 transaction.add(self._datafile, offset)
2987 if self._sidedatafile:
2985 if self._sidedatafile:
2988 transaction.add(self._sidedatafile, sidedata_offset)
2986 transaction.add(self._sidedatafile, sidedata_offset)
2989 transaction.add(self._indexfile, curr * len(entry))
2987 transaction.add(self._indexfile, curr * len(entry))
2990 if data[0]:
2988 if data[0]:
2991 dfh.write(data[0])
2989 dfh.write(data[0])
2992 dfh.write(data[1])
2990 dfh.write(data[1])
2993 if sidedata:
2991 if sidedata:
2994 sdfh.write(sidedata)
2992 sdfh.write(sidedata)
2995 ifh.write(entry)
2993 ifh.write(entry)
2996 else:
2994 else:
2997 offset += curr * self.index.entry_size
2995 offset += curr * self.index.entry_size
2998 transaction.add(self._indexfile, offset)
2996 transaction.add(self._indexfile, offset)
2999 ifh.write(entry)
2997 ifh.write(entry)
3000 ifh.write(data[0])
2998 ifh.write(data[0])
3001 ifh.write(data[1])
2999 ifh.write(data[1])
3002 assert not sidedata
3000 assert not sidedata
3003 self._enforceinlinesize(transaction)
3001 self._enforceinlinesize(transaction)
3004 if self._docket is not None:
3002 if self._docket is not None:
3005 # revlog-v2 always has 3 writing handles, help Pytype
3003 # revlog-v2 always has 3 writing handles, help Pytype
3006 wh1 = self._writinghandles[0]
3004 wh1 = self._writinghandles[0]
3007 wh2 = self._writinghandles[1]
3005 wh2 = self._writinghandles[1]
3008 wh3 = self._writinghandles[2]
3006 wh3 = self._writinghandles[2]
3009 assert wh1 is not None
3007 assert wh1 is not None
3010 assert wh2 is not None
3008 assert wh2 is not None
3011 assert wh3 is not None
3009 assert wh3 is not None
3012 self._docket.index_end = wh1.tell()
3010 self._docket.index_end = wh1.tell()
3013 self._docket.data_end = wh2.tell()
3011 self._docket.data_end = wh2.tell()
3014 self._docket.sidedata_end = wh3.tell()
3012 self._docket.sidedata_end = wh3.tell()
3015
3013
3016 nodemaputil.setup_persistent_nodemap(transaction, self)
3014 nodemaputil.setup_persistent_nodemap(transaction, self)
3017
3015
3018 def addgroup(
3016 def addgroup(
3019 self,
3017 self,
3020 deltas,
3018 deltas,
3021 linkmapper,
3019 linkmapper,
3022 transaction,
3020 transaction,
3023 alwayscache=False,
3021 alwayscache=False,
3024 addrevisioncb=None,
3022 addrevisioncb=None,
3025 duplicaterevisioncb=None,
3023 duplicaterevisioncb=None,
3026 debug_info=None,
3024 debug_info=None,
3027 delta_base_reuse_policy=None,
3025 delta_base_reuse_policy=None,
3028 ):
3026 ):
3029 """
3027 """
3030 add a delta group
3028 add a delta group
3031
3029
3032 given a set of deltas, add them to the revision log. the
3030 given a set of deltas, add them to the revision log. the
3033 first delta is against its parent, which should be in our
3031 first delta is against its parent, which should be in our
3034 log, the rest are against the previous delta.
3032 log, the rest are against the previous delta.
3035
3033
3036 If ``addrevisioncb`` is defined, it will be called with arguments of
3034 If ``addrevisioncb`` is defined, it will be called with arguments of
3037 this revlog and the node that was added.
3035 this revlog and the node that was added.
3038 """
3036 """
3039
3037
3040 if self._adding_group:
3038 if self._adding_group:
3041 raise error.ProgrammingError(b'cannot nest addgroup() calls')
3039 raise error.ProgrammingError(b'cannot nest addgroup() calls')
3042
3040
3043 # read the default delta-base reuse policy from revlog config if the
3041 # read the default delta-base reuse policy from revlog config if the
3044 # group did not specify one.
3042 # group did not specify one.
3045 if delta_base_reuse_policy is None:
3043 if delta_base_reuse_policy is None:
3046 if self._generaldelta and self._lazydeltabase:
3044 if self._generaldelta and self._lazydeltabase:
3047 delta_base_reuse_policy = DELTA_BASE_REUSE_TRY
3045 delta_base_reuse_policy = DELTA_BASE_REUSE_TRY
3048 else:
3046 else:
3049 delta_base_reuse_policy = DELTA_BASE_REUSE_NO
3047 delta_base_reuse_policy = DELTA_BASE_REUSE_NO
3050
3048
3051 self._adding_group = True
3049 self._adding_group = True
3052 empty = True
3050 empty = True
3053 try:
3051 try:
3054 with self._writing(transaction):
3052 with self._writing(transaction):
3055 write_debug = None
3053 write_debug = None
3056 if self._debug_delta:
3054 if self._debug_delta:
3057 write_debug = transaction._report
3055 write_debug = transaction._report
3058 deltacomputer = deltautil.deltacomputer(
3056 deltacomputer = deltautil.deltacomputer(
3059 self,
3057 self,
3060 write_debug=write_debug,
3058 write_debug=write_debug,
3061 debug_info=debug_info,
3059 debug_info=debug_info,
3062 )
3060 )
3063 # loop through our set of deltas
3061 # loop through our set of deltas
3064 for data in deltas:
3062 for data in deltas:
3065 (
3063 (
3066 node,
3064 node,
3067 p1,
3065 p1,
3068 p2,
3066 p2,
3069 linknode,
3067 linknode,
3070 deltabase,
3068 deltabase,
3071 delta,
3069 delta,
3072 flags,
3070 flags,
3073 sidedata,
3071 sidedata,
3074 ) = data
3072 ) = data
3075 link = linkmapper(linknode)
3073 link = linkmapper(linknode)
3076 flags = flags or REVIDX_DEFAULT_FLAGS
3074 flags = flags or REVIDX_DEFAULT_FLAGS
3077
3075
3078 rev = self.index.get_rev(node)
3076 rev = self.index.get_rev(node)
3079 if rev is not None:
3077 if rev is not None:
3080 # this can happen if two branches make the same change
3078 # this can happen if two branches make the same change
3081 self._nodeduplicatecallback(transaction, rev)
3079 self._nodeduplicatecallback(transaction, rev)
3082 if duplicaterevisioncb:
3080 if duplicaterevisioncb:
3083 duplicaterevisioncb(self, rev)
3081 duplicaterevisioncb(self, rev)
3084 empty = False
3082 empty = False
3085 continue
3083 continue
3086
3084
3087 for p in (p1, p2):
3085 for p in (p1, p2):
3088 if not self.index.has_node(p):
3086 if not self.index.has_node(p):
3089 raise error.LookupError(
3087 raise error.LookupError(
3090 p, self.radix, _(b'unknown parent')
3088 p, self.radix, _(b'unknown parent')
3091 )
3089 )
3092
3090
3093 if not self.index.has_node(deltabase):
3091 if not self.index.has_node(deltabase):
3094 raise error.LookupError(
3092 raise error.LookupError(
3095 deltabase, self.display_id, _(b'unknown delta base')
3093 deltabase, self.display_id, _(b'unknown delta base')
3096 )
3094 )
3097
3095
3098 baserev = self.rev(deltabase)
3096 baserev = self.rev(deltabase)
3099
3097
3100 if baserev != nullrev and self.iscensored(baserev):
3098 if baserev != nullrev and self.iscensored(baserev):
3101 # if base is censored, delta must be full replacement in a
3099 # if base is censored, delta must be full replacement in a
3102 # single patch operation
3100 # single patch operation
3103 hlen = struct.calcsize(b">lll")
3101 hlen = struct.calcsize(b">lll")
3104 oldlen = self.rawsize(baserev)
3102 oldlen = self.rawsize(baserev)
3105 newlen = len(delta) - hlen
3103 newlen = len(delta) - hlen
3106 if delta[:hlen] != mdiff.replacediffheader(
3104 if delta[:hlen] != mdiff.replacediffheader(
3107 oldlen, newlen
3105 oldlen, newlen
3108 ):
3106 ):
3109 raise error.CensoredBaseError(
3107 raise error.CensoredBaseError(
3110 self.display_id, self.node(baserev)
3108 self.display_id, self.node(baserev)
3111 )
3109 )
3112
3110
3113 if not flags and self._peek_iscensored(baserev, delta):
3111 if not flags and self._peek_iscensored(baserev, delta):
3114 flags |= REVIDX_ISCENSORED
3112 flags |= REVIDX_ISCENSORED
3115
3113
3116 # We assume consumers of addrevisioncb will want to retrieve
3114 # We assume consumers of addrevisioncb will want to retrieve
3117 # the added revision, which will require a call to
3115 # the added revision, which will require a call to
3118 # revision(). revision() will fast path if there is a cache
3116 # revision(). revision() will fast path if there is a cache
3119 # hit. So, we tell _addrevision() to always cache in this case.
3117 # hit. So, we tell _addrevision() to always cache in this case.
3120 # We're only using addgroup() in the context of changegroup
3118 # We're only using addgroup() in the context of changegroup
3121 # generation so the revision data can always be handled as raw
3119 # generation so the revision data can always be handled as raw
3122 # by the flagprocessor.
3120 # by the flagprocessor.
3123 rev = self._addrevision(
3121 rev = self._addrevision(
3124 node,
3122 node,
3125 None,
3123 None,
3126 transaction,
3124 transaction,
3127 link,
3125 link,
3128 p1,
3126 p1,
3129 p2,
3127 p2,
3130 flags,
3128 flags,
3131 (baserev, delta, delta_base_reuse_policy),
3129 (baserev, delta, delta_base_reuse_policy),
3132 alwayscache=alwayscache,
3130 alwayscache=alwayscache,
3133 deltacomputer=deltacomputer,
3131 deltacomputer=deltacomputer,
3134 sidedata=sidedata,
3132 sidedata=sidedata,
3135 )
3133 )
3136
3134
3137 if addrevisioncb:
3135 if addrevisioncb:
3138 addrevisioncb(self, rev)
3136 addrevisioncb(self, rev)
3139 empty = False
3137 empty = False
3140 finally:
3138 finally:
3141 self._adding_group = False
3139 self._adding_group = False
3142 return not empty
3140 return not empty
3143
3141
3144 def iscensored(self, rev):
3142 def iscensored(self, rev):
3145 """Check if a file revision is censored."""
3143 """Check if a file revision is censored."""
3146 if not self._censorable:
3144 if not self._censorable:
3147 return False
3145 return False
3148
3146
3149 return self.flags(rev) & REVIDX_ISCENSORED
3147 return self.flags(rev) & REVIDX_ISCENSORED
3150
3148
3151 def _peek_iscensored(self, baserev, delta):
3149 def _peek_iscensored(self, baserev, delta):
3152 """Quickly check if a delta produces a censored revision."""
3150 """Quickly check if a delta produces a censored revision."""
3153 if not self._censorable:
3151 if not self._censorable:
3154 return False
3152 return False
3155
3153
3156 return storageutil.deltaiscensored(delta, baserev, self.rawsize)
3154 return storageutil.deltaiscensored(delta, baserev, self.rawsize)
3157
3155
3158 def getstrippoint(self, minlink):
3156 def getstrippoint(self, minlink):
3159 """find the minimum rev that must be stripped to strip the linkrev
3157 """find the minimum rev that must be stripped to strip the linkrev
3160
3158
3161 Returns a tuple containing the minimum rev and a set of all revs that
3159 Returns a tuple containing the minimum rev and a set of all revs that
3162 have linkrevs that will be broken by this strip.
3160 have linkrevs that will be broken by this strip.
3163 """
3161 """
3164 return storageutil.resolvestripinfo(
3162 return storageutil.resolvestripinfo(
3165 minlink,
3163 minlink,
3166 len(self) - 1,
3164 len(self) - 1,
3167 self.headrevs(),
3165 self.headrevs(),
3168 self.linkrev,
3166 self.linkrev,
3169 self.parentrevs,
3167 self.parentrevs,
3170 )
3168 )
3171
3169
3172 def strip(self, minlink, transaction):
3170 def strip(self, minlink, transaction):
3173 """truncate the revlog on the first revision with a linkrev >= minlink
3171 """truncate the revlog on the first revision with a linkrev >= minlink
3174
3172
3175 This function is called when we're stripping revision minlink and
3173 This function is called when we're stripping revision minlink and
3176 its descendants from the repository.
3174 its descendants from the repository.
3177
3175
3178 We have to remove all revisions with linkrev >= minlink, because
3176 We have to remove all revisions with linkrev >= minlink, because
3179 the equivalent changelog revisions will be renumbered after the
3177 the equivalent changelog revisions will be renumbered after the
3180 strip.
3178 strip.
3181
3179
3182 So we truncate the revlog on the first of these revisions, and
3180 So we truncate the revlog on the first of these revisions, and
3183 trust that the caller has saved the revisions that shouldn't be
3181 trust that the caller has saved the revisions that shouldn't be
3184 removed and that it'll re-add them after this truncation.
3182 removed and that it'll re-add them after this truncation.
3185 """
3183 """
3186 if len(self) == 0:
3184 if len(self) == 0:
3187 return
3185 return
3188
3186
3189 rev, _ = self.getstrippoint(minlink)
3187 rev, _ = self.getstrippoint(minlink)
3190 if rev == len(self):
3188 if rev == len(self):
3191 return
3189 return
3192
3190
3193 # first truncate the files on disk
3191 # first truncate the files on disk
3194 data_end = self.start(rev)
3192 data_end = self.start(rev)
3195 if not self._inline:
3193 if not self._inline:
3196 transaction.add(self._datafile, data_end)
3194 transaction.add(self._datafile, data_end)
3197 end = rev * self.index.entry_size
3195 end = rev * self.index.entry_size
3198 else:
3196 else:
3199 end = data_end + (rev * self.index.entry_size)
3197 end = data_end + (rev * self.index.entry_size)
3200
3198
3201 if self._sidedatafile:
3199 if self._sidedatafile:
3202 sidedata_end = self.sidedata_cut_off(rev)
3200 sidedata_end = self.sidedata_cut_off(rev)
3203 transaction.add(self._sidedatafile, sidedata_end)
3201 transaction.add(self._sidedatafile, sidedata_end)
3204
3202
3205 transaction.add(self._indexfile, end)
3203 transaction.add(self._indexfile, end)
3206 if self._docket is not None:
3204 if self._docket is not None:
3207 # XXX we could, leverage the docket while stripping. However it is
3205 # XXX we could, leverage the docket while stripping. However it is
3208 # not powerfull enough at the time of this comment
3206 # not powerfull enough at the time of this comment
3209 self._docket.index_end = end
3207 self._docket.index_end = end
3210 self._docket.data_end = data_end
3208 self._docket.data_end = data_end
3211 self._docket.sidedata_end = sidedata_end
3209 self._docket.sidedata_end = sidedata_end
3212 self._docket.write(transaction, stripping=True)
3210 self._docket.write(transaction, stripping=True)
3213
3211
3214 # then reset internal state in memory to forget those revisions
3212 # then reset internal state in memory to forget those revisions
3215 self._revisioncache = None
3213 self._revisioncache = None
3216 self._chaininfocache = util.lrucachedict(500)
3214 self._chaininfocache = util.lrucachedict(500)
3217 self._segmentfile.clear_cache()
3215 self._segmentfile.clear_cache()
3218 self._segmentfile_sidedata.clear_cache()
3216 self._segmentfile_sidedata.clear_cache()
3219
3217
3220 del self.index[rev:-1]
3218 del self.index[rev:-1]
3221
3219
3222 def checksize(self):
3220 def checksize(self):
3223 """Check size of index and data files
3221 """Check size of index and data files
3224
3222
3225 return a (dd, di) tuple.
3223 return a (dd, di) tuple.
3226 - dd: extra bytes for the "data" file
3224 - dd: extra bytes for the "data" file
3227 - di: extra bytes for the "index" file
3225 - di: extra bytes for the "index" file
3228
3226
3229 A healthy revlog will return (0, 0).
3227 A healthy revlog will return (0, 0).
3230 """
3228 """
3231 expected = 0
3229 expected = 0
3232 if len(self):
3230 if len(self):
3233 expected = max(0, self.end(len(self) - 1))
3231 expected = max(0, self.end(len(self) - 1))
3234
3232
3235 try:
3233 try:
3236 with self._datafp() as f:
3234 with self._datafp() as f:
3237 f.seek(0, io.SEEK_END)
3235 f.seek(0, io.SEEK_END)
3238 actual = f.tell()
3236 actual = f.tell()
3239 dd = actual - expected
3237 dd = actual - expected
3240 except FileNotFoundError:
3238 except FileNotFoundError:
3241 dd = 0
3239 dd = 0
3242
3240
3243 try:
3241 try:
3244 f = self.opener(self._indexfile)
3242 f = self.opener(self._indexfile)
3245 f.seek(0, io.SEEK_END)
3243 f.seek(0, io.SEEK_END)
3246 actual = f.tell()
3244 actual = f.tell()
3247 f.close()
3245 f.close()
3248 s = self.index.entry_size
3246 s = self.index.entry_size
3249 i = max(0, actual // s)
3247 i = max(0, actual // s)
3250 di = actual - (i * s)
3248 di = actual - (i * s)
3251 if self._inline:
3249 if self._inline:
3252 databytes = 0
3250 databytes = 0
3253 for r in self:
3251 for r in self:
3254 databytes += max(0, self.length(r))
3252 databytes += max(0, self.length(r))
3255 dd = 0
3253 dd = 0
3256 di = actual - len(self) * s - databytes
3254 di = actual - len(self) * s - databytes
3257 except FileNotFoundError:
3255 except FileNotFoundError:
3258 di = 0
3256 di = 0
3259
3257
3260 return (dd, di)
3258 return (dd, di)
3261
3259
3262 def files(self):
3260 def files(self):
3263 res = [self._indexfile]
3261 res = [self._indexfile]
3264 if self._docket_file is None:
3262 if self._docket_file is None:
3265 if not self._inline:
3263 if not self._inline:
3266 res.append(self._datafile)
3264 res.append(self._datafile)
3267 else:
3265 else:
3268 res.append(self._docket_file)
3266 res.append(self._docket_file)
3269 res.extend(self._docket.old_index_filepaths(include_empty=False))
3267 res.extend(self._docket.old_index_filepaths(include_empty=False))
3270 if self._docket.data_end:
3268 if self._docket.data_end:
3271 res.append(self._datafile)
3269 res.append(self._datafile)
3272 res.extend(self._docket.old_data_filepaths(include_empty=False))
3270 res.extend(self._docket.old_data_filepaths(include_empty=False))
3273 if self._docket.sidedata_end:
3271 if self._docket.sidedata_end:
3274 res.append(self._sidedatafile)
3272 res.append(self._sidedatafile)
3275 res.extend(self._docket.old_sidedata_filepaths(include_empty=False))
3273 res.extend(self._docket.old_sidedata_filepaths(include_empty=False))
3276 return res
3274 return res
3277
3275
3278 def emitrevisions(
3276 def emitrevisions(
3279 self,
3277 self,
3280 nodes,
3278 nodes,
3281 nodesorder=None,
3279 nodesorder=None,
3282 revisiondata=False,
3280 revisiondata=False,
3283 assumehaveparentrevisions=False,
3281 assumehaveparentrevisions=False,
3284 deltamode=repository.CG_DELTAMODE_STD,
3282 deltamode=repository.CG_DELTAMODE_STD,
3285 sidedata_helpers=None,
3283 sidedata_helpers=None,
3286 debug_info=None,
3284 debug_info=None,
3287 ):
3285 ):
3288 if nodesorder not in (b'nodes', b'storage', b'linear', None):
3286 if nodesorder not in (b'nodes', b'storage', b'linear', None):
3289 raise error.ProgrammingError(
3287 raise error.ProgrammingError(
3290 b'unhandled value for nodesorder: %s' % nodesorder
3288 b'unhandled value for nodesorder: %s' % nodesorder
3291 )
3289 )
3292
3290
3293 if nodesorder is None and not self._generaldelta:
3291 if nodesorder is None and not self._generaldelta:
3294 nodesorder = b'storage'
3292 nodesorder = b'storage'
3295
3293
3296 if (
3294 if (
3297 not self._storedeltachains
3295 not self._storedeltachains
3298 and deltamode != repository.CG_DELTAMODE_PREV
3296 and deltamode != repository.CG_DELTAMODE_PREV
3299 ):
3297 ):
3300 deltamode = repository.CG_DELTAMODE_FULL
3298 deltamode = repository.CG_DELTAMODE_FULL
3301
3299
3302 return storageutil.emitrevisions(
3300 return storageutil.emitrevisions(
3303 self,
3301 self,
3304 nodes,
3302 nodes,
3305 nodesorder,
3303 nodesorder,
3306 revlogrevisiondelta,
3304 revlogrevisiondelta,
3307 deltaparentfn=self.deltaparent,
3305 deltaparentfn=self.deltaparent,
3308 candeltafn=self._candelta,
3306 candeltafn=self._candelta,
3309 rawsizefn=self.rawsize,
3307 rawsizefn=self.rawsize,
3310 revdifffn=self.revdiff,
3308 revdifffn=self.revdiff,
3311 flagsfn=self.flags,
3309 flagsfn=self.flags,
3312 deltamode=deltamode,
3310 deltamode=deltamode,
3313 revisiondata=revisiondata,
3311 revisiondata=revisiondata,
3314 assumehaveparentrevisions=assumehaveparentrevisions,
3312 assumehaveparentrevisions=assumehaveparentrevisions,
3315 sidedata_helpers=sidedata_helpers,
3313 sidedata_helpers=sidedata_helpers,
3316 debug_info=debug_info,
3314 debug_info=debug_info,
3317 )
3315 )
3318
3316
3319 DELTAREUSEALWAYS = b'always'
3317 DELTAREUSEALWAYS = b'always'
3320 DELTAREUSESAMEREVS = b'samerevs'
3318 DELTAREUSESAMEREVS = b'samerevs'
3321 DELTAREUSENEVER = b'never'
3319 DELTAREUSENEVER = b'never'
3322
3320
3323 DELTAREUSEFULLADD = b'fulladd'
3321 DELTAREUSEFULLADD = b'fulladd'
3324
3322
3325 DELTAREUSEALL = {b'always', b'samerevs', b'never', b'fulladd'}
3323 DELTAREUSEALL = {b'always', b'samerevs', b'never', b'fulladd'}
3326
3324
3327 def clone(
3325 def clone(
3328 self,
3326 self,
3329 tr,
3327 tr,
3330 destrevlog,
3328 destrevlog,
3331 addrevisioncb=None,
3329 addrevisioncb=None,
3332 deltareuse=DELTAREUSESAMEREVS,
3330 deltareuse=DELTAREUSESAMEREVS,
3333 forcedeltabothparents=None,
3331 forcedeltabothparents=None,
3334 sidedata_helpers=None,
3332 sidedata_helpers=None,
3335 ):
3333 ):
3336 """Copy this revlog to another, possibly with format changes.
3334 """Copy this revlog to another, possibly with format changes.
3337
3335
3338 The destination revlog will contain the same revisions and nodes.
3336 The destination revlog will contain the same revisions and nodes.
3339 However, it may not be bit-for-bit identical due to e.g. delta encoding
3337 However, it may not be bit-for-bit identical due to e.g. delta encoding
3340 differences.
3338 differences.
3341
3339
3342 The ``deltareuse`` argument control how deltas from the existing revlog
3340 The ``deltareuse`` argument control how deltas from the existing revlog
3343 are preserved in the destination revlog. The argument can have the
3341 are preserved in the destination revlog. The argument can have the
3344 following values:
3342 following values:
3345
3343
3346 DELTAREUSEALWAYS
3344 DELTAREUSEALWAYS
3347 Deltas will always be reused (if possible), even if the destination
3345 Deltas will always be reused (if possible), even if the destination
3348 revlog would not select the same revisions for the delta. This is the
3346 revlog would not select the same revisions for the delta. This is the
3349 fastest mode of operation.
3347 fastest mode of operation.
3350 DELTAREUSESAMEREVS
3348 DELTAREUSESAMEREVS
3351 Deltas will be reused if the destination revlog would pick the same
3349 Deltas will be reused if the destination revlog would pick the same
3352 revisions for the delta. This mode strikes a balance between speed
3350 revisions for the delta. This mode strikes a balance between speed
3353 and optimization.
3351 and optimization.
3354 DELTAREUSENEVER
3352 DELTAREUSENEVER
3355 Deltas will never be reused. This is the slowest mode of execution.
3353 Deltas will never be reused. This is the slowest mode of execution.
3356 This mode can be used to recompute deltas (e.g. if the diff/delta
3354 This mode can be used to recompute deltas (e.g. if the diff/delta
3357 algorithm changes).
3355 algorithm changes).
3358 DELTAREUSEFULLADD
3356 DELTAREUSEFULLADD
3359 Revision will be re-added as if their were new content. This is
3357 Revision will be re-added as if their were new content. This is
3360 slower than DELTAREUSEALWAYS but allow more mechanism to kicks in.
3358 slower than DELTAREUSEALWAYS but allow more mechanism to kicks in.
3361 eg: large file detection and handling.
3359 eg: large file detection and handling.
3362
3360
3363 Delta computation can be slow, so the choice of delta reuse policy can
3361 Delta computation can be slow, so the choice of delta reuse policy can
3364 significantly affect run time.
3362 significantly affect run time.
3365
3363
3366 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
3364 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
3367 two extremes. Deltas will be reused if they are appropriate. But if the
3365 two extremes. Deltas will be reused if they are appropriate. But if the
3368 delta could choose a better revision, it will do so. This means if you
3366 delta could choose a better revision, it will do so. This means if you
3369 are converting a non-generaldelta revlog to a generaldelta revlog,
3367 are converting a non-generaldelta revlog to a generaldelta revlog,
3370 deltas will be recomputed if the delta's parent isn't a parent of the
3368 deltas will be recomputed if the delta's parent isn't a parent of the
3371 revision.
3369 revision.
3372
3370
3373 In addition to the delta policy, the ``forcedeltabothparents``
3371 In addition to the delta policy, the ``forcedeltabothparents``
3374 argument controls whether to force compute deltas against both parents
3372 argument controls whether to force compute deltas against both parents
3375 for merges. By default, the current default is used.
3373 for merges. By default, the current default is used.
3376
3374
3377 See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
3375 See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
3378 `sidedata_helpers`.
3376 `sidedata_helpers`.
3379 """
3377 """
3380 if deltareuse not in self.DELTAREUSEALL:
3378 if deltareuse not in self.DELTAREUSEALL:
3381 raise ValueError(
3379 raise ValueError(
3382 _(b'value for deltareuse invalid: %s') % deltareuse
3380 _(b'value for deltareuse invalid: %s') % deltareuse
3383 )
3381 )
3384
3382
3385 if len(destrevlog):
3383 if len(destrevlog):
3386 raise ValueError(_(b'destination revlog is not empty'))
3384 raise ValueError(_(b'destination revlog is not empty'))
3387
3385
3388 if getattr(self, 'filteredrevs', None):
3386 if getattr(self, 'filteredrevs', None):
3389 raise ValueError(_(b'source revlog has filtered revisions'))
3387 raise ValueError(_(b'source revlog has filtered revisions'))
3390 if getattr(destrevlog, 'filteredrevs', None):
3388 if getattr(destrevlog, 'filteredrevs', None):
3391 raise ValueError(_(b'destination revlog has filtered revisions'))
3389 raise ValueError(_(b'destination revlog has filtered revisions'))
3392
3390
3393 # lazydelta and lazydeltabase controls whether to reuse a cached delta,
3391 # lazydelta and lazydeltabase controls whether to reuse a cached delta,
3394 # if possible.
3392 # if possible.
3395 old_delta_config = destrevlog.delta_config
3393 old_delta_config = destrevlog.delta_config
3396 destrevlog.delta_config = destrevlog.delta_config.copy()
3394 destrevlog.delta_config = destrevlog.delta_config.copy()
3397
3395
3398 try:
3396 try:
3399 if deltareuse == self.DELTAREUSEALWAYS:
3397 if deltareuse == self.DELTAREUSEALWAYS:
3400 destrevlog.delta_config.lazy_delta_base = True
3398 destrevlog.delta_config.lazy_delta_base = True
3401 destrevlog.delta_config.lazy_delta = True
3399 destrevlog.delta_config.lazy_delta = True
3402 elif deltareuse == self.DELTAREUSESAMEREVS:
3400 elif deltareuse == self.DELTAREUSESAMEREVS:
3403 destrevlog.delta_config.lazy_delta_base = False
3401 destrevlog.delta_config.lazy_delta_base = False
3404 destrevlog.delta_config.lazy_delta = True
3402 destrevlog.delta_config.lazy_delta = True
3405 elif deltareuse == self.DELTAREUSENEVER:
3403 elif deltareuse == self.DELTAREUSENEVER:
3406 destrevlog.delta_config.lazy_delta_base = False
3404 destrevlog.delta_config.lazy_delta_base = False
3407 destrevlog.delta_config.lazy_delta = False
3405 destrevlog.delta_config.lazy_delta = False
3408
3406
3409 delta_both_parents = (
3407 delta_both_parents = (
3410 forcedeltabothparents or old_delta_config.delta_both_parents
3408 forcedeltabothparents or old_delta_config.delta_both_parents
3411 )
3409 )
3412 destrevlog.delta_config.delta_both_parents = delta_both_parents
3410 destrevlog.delta_config.delta_both_parents = delta_both_parents
3413
3411
3414 with self.reading():
3412 with self.reading():
3415 self._clone(
3413 self._clone(
3416 tr,
3414 tr,
3417 destrevlog,
3415 destrevlog,
3418 addrevisioncb,
3416 addrevisioncb,
3419 deltareuse,
3417 deltareuse,
3420 forcedeltabothparents,
3418 forcedeltabothparents,
3421 sidedata_helpers,
3419 sidedata_helpers,
3422 )
3420 )
3423
3421
3424 finally:
3422 finally:
3425 destrevlog.delta_config = old_delta_config
3423 destrevlog.delta_config = old_delta_config
3426
3424
3427 def _clone(
3425 def _clone(
3428 self,
3426 self,
3429 tr,
3427 tr,
3430 destrevlog,
3428 destrevlog,
3431 addrevisioncb,
3429 addrevisioncb,
3432 deltareuse,
3430 deltareuse,
3433 forcedeltabothparents,
3431 forcedeltabothparents,
3434 sidedata_helpers,
3432 sidedata_helpers,
3435 ):
3433 ):
3436 """perform the core duty of `revlog.clone` after parameter processing"""
3434 """perform the core duty of `revlog.clone` after parameter processing"""
3437 write_debug = None
3435 write_debug = None
3438 if self._debug_delta:
3436 if self._debug_delta:
3439 write_debug = tr._report
3437 write_debug = tr._report
3440 deltacomputer = deltautil.deltacomputer(
3438 deltacomputer = deltautil.deltacomputer(
3441 destrevlog,
3439 destrevlog,
3442 write_debug=write_debug,
3440 write_debug=write_debug,
3443 )
3441 )
3444 index = self.index
3442 index = self.index
3445 for rev in self:
3443 for rev in self:
3446 entry = index[rev]
3444 entry = index[rev]
3447
3445
3448 # Some classes override linkrev to take filtered revs into
3446 # Some classes override linkrev to take filtered revs into
3449 # account. Use raw entry from index.
3447 # account. Use raw entry from index.
3450 flags = entry[0] & 0xFFFF
3448 flags = entry[0] & 0xFFFF
3451 linkrev = entry[4]
3449 linkrev = entry[4]
3452 p1 = index[entry[5]][7]
3450 p1 = index[entry[5]][7]
3453 p2 = index[entry[6]][7]
3451 p2 = index[entry[6]][7]
3454 node = entry[7]
3452 node = entry[7]
3455
3453
3456 # (Possibly) reuse the delta from the revlog if allowed and
3454 # (Possibly) reuse the delta from the revlog if allowed and
3457 # the revlog chunk is a delta.
3455 # the revlog chunk is a delta.
3458 cachedelta = None
3456 cachedelta = None
3459 rawtext = None
3457 rawtext = None
3460 if deltareuse == self.DELTAREUSEFULLADD:
3458 if deltareuse == self.DELTAREUSEFULLADD:
3461 text = self._revisiondata(rev)
3459 text = self._revisiondata(rev)
3462 sidedata = self.sidedata(rev)
3460 sidedata = self.sidedata(rev)
3463
3461
3464 if sidedata_helpers is not None:
3462 if sidedata_helpers is not None:
3465 (sidedata, new_flags) = sidedatautil.run_sidedata_helpers(
3463 (sidedata, new_flags) = sidedatautil.run_sidedata_helpers(
3466 self, sidedata_helpers, sidedata, rev
3464 self, sidedata_helpers, sidedata, rev
3467 )
3465 )
3468 flags = flags | new_flags[0] & ~new_flags[1]
3466 flags = flags | new_flags[0] & ~new_flags[1]
3469
3467
3470 destrevlog.addrevision(
3468 destrevlog.addrevision(
3471 text,
3469 text,
3472 tr,
3470 tr,
3473 linkrev,
3471 linkrev,
3474 p1,
3472 p1,
3475 p2,
3473 p2,
3476 cachedelta=cachedelta,
3474 cachedelta=cachedelta,
3477 node=node,
3475 node=node,
3478 flags=flags,
3476 flags=flags,
3479 deltacomputer=deltacomputer,
3477 deltacomputer=deltacomputer,
3480 sidedata=sidedata,
3478 sidedata=sidedata,
3481 )
3479 )
3482 else:
3480 else:
3483 if destrevlog._lazydelta:
3481 if destrevlog._lazydelta:
3484 dp = self.deltaparent(rev)
3482 dp = self.deltaparent(rev)
3485 if dp != nullrev:
3483 if dp != nullrev:
3486 cachedelta = (dp, bytes(self._chunk(rev)))
3484 cachedelta = (dp, bytes(self._chunk(rev)))
3487
3485
3488 sidedata = None
3486 sidedata = None
3489 if not cachedelta:
3487 if not cachedelta:
3490 rawtext = self._revisiondata(rev)
3488 rawtext = self._revisiondata(rev)
3491 sidedata = self.sidedata(rev)
3489 sidedata = self.sidedata(rev)
3492 if sidedata is None:
3490 if sidedata is None:
3493 sidedata = self.sidedata(rev)
3491 sidedata = self.sidedata(rev)
3494
3492
3495 if sidedata_helpers is not None:
3493 if sidedata_helpers is not None:
3496 (sidedata, new_flags) = sidedatautil.run_sidedata_helpers(
3494 (sidedata, new_flags) = sidedatautil.run_sidedata_helpers(
3497 self, sidedata_helpers, sidedata, rev
3495 self, sidedata_helpers, sidedata, rev
3498 )
3496 )
3499 flags = flags | new_flags[0] & ~new_flags[1]
3497 flags = flags | new_flags[0] & ~new_flags[1]
3500
3498
3501 with destrevlog._writing(tr):
3499 with destrevlog._writing(tr):
3502 destrevlog._addrevision(
3500 destrevlog._addrevision(
3503 node,
3501 node,
3504 rawtext,
3502 rawtext,
3505 tr,
3503 tr,
3506 linkrev,
3504 linkrev,
3507 p1,
3505 p1,
3508 p2,
3506 p2,
3509 flags,
3507 flags,
3510 cachedelta,
3508 cachedelta,
3511 deltacomputer=deltacomputer,
3509 deltacomputer=deltacomputer,
3512 sidedata=sidedata,
3510 sidedata=sidedata,
3513 )
3511 )
3514
3512
3515 if addrevisioncb:
3513 if addrevisioncb:
3516 addrevisioncb(self, rev, node)
3514 addrevisioncb(self, rev, node)
3517
3515
3518 def censorrevision(self, tr, censornode, tombstone=b''):
3516 def censorrevision(self, tr, censornode, tombstone=b''):
3519 if self._format_version == REVLOGV0:
3517 if self._format_version == REVLOGV0:
3520 raise error.RevlogError(
3518 raise error.RevlogError(
3521 _(b'cannot censor with version %d revlogs')
3519 _(b'cannot censor with version %d revlogs')
3522 % self._format_version
3520 % self._format_version
3523 )
3521 )
3524 elif self._format_version == REVLOGV1:
3522 elif self._format_version == REVLOGV1:
3525 rewrite.v1_censor(self, tr, censornode, tombstone)
3523 rewrite.v1_censor(self, tr, censornode, tombstone)
3526 else:
3524 else:
3527 rewrite.v2_censor(self, tr, censornode, tombstone)
3525 rewrite.v2_censor(self, tr, censornode, tombstone)
3528
3526
3529 def verifyintegrity(self, state):
3527 def verifyintegrity(self, state):
3530 """Verifies the integrity of the revlog.
3528 """Verifies the integrity of the revlog.
3531
3529
3532 Yields ``revlogproblem`` instances describing problems that are
3530 Yields ``revlogproblem`` instances describing problems that are
3533 found.
3531 found.
3534 """
3532 """
3535 dd, di = self.checksize()
3533 dd, di = self.checksize()
3536 if dd:
3534 if dd:
3537 yield revlogproblem(error=_(b'data length off by %d bytes') % dd)
3535 yield revlogproblem(error=_(b'data length off by %d bytes') % dd)
3538 if di:
3536 if di:
3539 yield revlogproblem(error=_(b'index contains %d extra bytes') % di)
3537 yield revlogproblem(error=_(b'index contains %d extra bytes') % di)
3540
3538
3541 version = self._format_version
3539 version = self._format_version
3542
3540
3543 # The verifier tells us what version revlog we should be.
3541 # The verifier tells us what version revlog we should be.
3544 if version != state[b'expectedversion']:
3542 if version != state[b'expectedversion']:
3545 yield revlogproblem(
3543 yield revlogproblem(
3546 warning=_(b"warning: '%s' uses revlog format %d; expected %d")
3544 warning=_(b"warning: '%s' uses revlog format %d; expected %d")
3547 % (self.display_id, version, state[b'expectedversion'])
3545 % (self.display_id, version, state[b'expectedversion'])
3548 )
3546 )
3549
3547
3550 state[b'skipread'] = set()
3548 state[b'skipread'] = set()
3551 state[b'safe_renamed'] = set()
3549 state[b'safe_renamed'] = set()
3552
3550
3553 for rev in self:
3551 for rev in self:
3554 node = self.node(rev)
3552 node = self.node(rev)
3555
3553
3556 # Verify contents. 4 cases to care about:
3554 # Verify contents. 4 cases to care about:
3557 #
3555 #
3558 # common: the most common case
3556 # common: the most common case
3559 # rename: with a rename
3557 # rename: with a rename
3560 # meta: file content starts with b'\1\n', the metadata
3558 # meta: file content starts with b'\1\n', the metadata
3561 # header defined in filelog.py, but without a rename
3559 # header defined in filelog.py, but without a rename
3562 # ext: content stored externally
3560 # ext: content stored externally
3563 #
3561 #
3564 # More formally, their differences are shown below:
3562 # More formally, their differences are shown below:
3565 #
3563 #
3566 # | common | rename | meta | ext
3564 # | common | rename | meta | ext
3567 # -------------------------------------------------------
3565 # -------------------------------------------------------
3568 # flags() | 0 | 0 | 0 | not 0
3566 # flags() | 0 | 0 | 0 | not 0
3569 # renamed() | False | True | False | ?
3567 # renamed() | False | True | False | ?
3570 # rawtext[0:2]=='\1\n'| False | True | True | ?
3568 # rawtext[0:2]=='\1\n'| False | True | True | ?
3571 #
3569 #
3572 # "rawtext" means the raw text stored in revlog data, which
3570 # "rawtext" means the raw text stored in revlog data, which
3573 # could be retrieved by "rawdata(rev)". "text"
3571 # could be retrieved by "rawdata(rev)". "text"
3574 # mentioned below is "revision(rev)".
3572 # mentioned below is "revision(rev)".
3575 #
3573 #
3576 # There are 3 different lengths stored physically:
3574 # There are 3 different lengths stored physically:
3577 # 1. L1: rawsize, stored in revlog index
3575 # 1. L1: rawsize, stored in revlog index
3578 # 2. L2: len(rawtext), stored in revlog data
3576 # 2. L2: len(rawtext), stored in revlog data
3579 # 3. L3: len(text), stored in revlog data if flags==0, or
3577 # 3. L3: len(text), stored in revlog data if flags==0, or
3580 # possibly somewhere else if flags!=0
3578 # possibly somewhere else if flags!=0
3581 #
3579 #
3582 # L1 should be equal to L2. L3 could be different from them.
3580 # L1 should be equal to L2. L3 could be different from them.
3583 # "text" may or may not affect commit hash depending on flag
3581 # "text" may or may not affect commit hash depending on flag
3584 # processors (see flagutil.addflagprocessor).
3582 # processors (see flagutil.addflagprocessor).
3585 #
3583 #
3586 # | common | rename | meta | ext
3584 # | common | rename | meta | ext
3587 # -------------------------------------------------
3585 # -------------------------------------------------
3588 # rawsize() | L1 | L1 | L1 | L1
3586 # rawsize() | L1 | L1 | L1 | L1
3589 # size() | L1 | L2-LM | L1(*) | L1 (?)
3587 # size() | L1 | L2-LM | L1(*) | L1 (?)
3590 # len(rawtext) | L2 | L2 | L2 | L2
3588 # len(rawtext) | L2 | L2 | L2 | L2
3591 # len(text) | L2 | L2 | L2 | L3
3589 # len(text) | L2 | L2 | L2 | L3
3592 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
3590 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
3593 #
3591 #
3594 # LM: length of metadata, depending on rawtext
3592 # LM: length of metadata, depending on rawtext
3595 # (*): not ideal, see comment in filelog.size
3593 # (*): not ideal, see comment in filelog.size
3596 # (?): could be "- len(meta)" if the resolved content has
3594 # (?): could be "- len(meta)" if the resolved content has
3597 # rename metadata
3595 # rename metadata
3598 #
3596 #
3599 # Checks needed to be done:
3597 # Checks needed to be done:
3600 # 1. length check: L1 == L2, in all cases.
3598 # 1. length check: L1 == L2, in all cases.
3601 # 2. hash check: depending on flag processor, we may need to
3599 # 2. hash check: depending on flag processor, we may need to
3602 # use either "text" (external), or "rawtext" (in revlog).
3600 # use either "text" (external), or "rawtext" (in revlog).
3603
3601
3604 try:
3602 try:
3605 skipflags = state.get(b'skipflags', 0)
3603 skipflags = state.get(b'skipflags', 0)
3606 if skipflags:
3604 if skipflags:
3607 skipflags &= self.flags(rev)
3605 skipflags &= self.flags(rev)
3608
3606
3609 _verify_revision(self, skipflags, state, node)
3607 _verify_revision(self, skipflags, state, node)
3610
3608
3611 l1 = self.rawsize(rev)
3609 l1 = self.rawsize(rev)
3612 l2 = len(self.rawdata(node))
3610 l2 = len(self.rawdata(node))
3613
3611
3614 if l1 != l2:
3612 if l1 != l2:
3615 yield revlogproblem(
3613 yield revlogproblem(
3616 error=_(b'unpacked size is %d, %d expected') % (l2, l1),
3614 error=_(b'unpacked size is %d, %d expected') % (l2, l1),
3617 node=node,
3615 node=node,
3618 )
3616 )
3619
3617
3620 except error.CensoredNodeError:
3618 except error.CensoredNodeError:
3621 if state[b'erroroncensored']:
3619 if state[b'erroroncensored']:
3622 yield revlogproblem(
3620 yield revlogproblem(
3623 error=_(b'censored file data'), node=node
3621 error=_(b'censored file data'), node=node
3624 )
3622 )
3625 state[b'skipread'].add(node)
3623 state[b'skipread'].add(node)
3626 except Exception as e:
3624 except Exception as e:
3627 yield revlogproblem(
3625 yield revlogproblem(
3628 error=_(b'unpacking %s: %s')
3626 error=_(b'unpacking %s: %s')
3629 % (short(node), stringutil.forcebytestr(e)),
3627 % (short(node), stringutil.forcebytestr(e)),
3630 node=node,
3628 node=node,
3631 )
3629 )
3632 state[b'skipread'].add(node)
3630 state[b'skipread'].add(node)
3633
3631
3634 def storageinfo(
3632 def storageinfo(
3635 self,
3633 self,
3636 exclusivefiles=False,
3634 exclusivefiles=False,
3637 sharedfiles=False,
3635 sharedfiles=False,
3638 revisionscount=False,
3636 revisionscount=False,
3639 trackedsize=False,
3637 trackedsize=False,
3640 storedsize=False,
3638 storedsize=False,
3641 ):
3639 ):
3642 d = {}
3640 d = {}
3643
3641
3644 if exclusivefiles:
3642 if exclusivefiles:
3645 d[b'exclusivefiles'] = [(self.opener, self._indexfile)]
3643 d[b'exclusivefiles'] = [(self.opener, self._indexfile)]
3646 if not self._inline:
3644 if not self._inline:
3647 d[b'exclusivefiles'].append((self.opener, self._datafile))
3645 d[b'exclusivefiles'].append((self.opener, self._datafile))
3648
3646
3649 if sharedfiles:
3647 if sharedfiles:
3650 d[b'sharedfiles'] = []
3648 d[b'sharedfiles'] = []
3651
3649
3652 if revisionscount:
3650 if revisionscount:
3653 d[b'revisionscount'] = len(self)
3651 d[b'revisionscount'] = len(self)
3654
3652
3655 if trackedsize:
3653 if trackedsize:
3656 d[b'trackedsize'] = sum(map(self.rawsize, iter(self)))
3654 d[b'trackedsize'] = sum(map(self.rawsize, iter(self)))
3657
3655
3658 if storedsize:
3656 if storedsize:
3659 d[b'storedsize'] = sum(
3657 d[b'storedsize'] = sum(
3660 self.opener.stat(path).st_size for path in self.files()
3658 self.opener.stat(path).st_size for path in self.files()
3661 )
3659 )
3662
3660
3663 return d
3661 return d
3664
3662
3665 def rewrite_sidedata(self, transaction, helpers, startrev, endrev):
3663 def rewrite_sidedata(self, transaction, helpers, startrev, endrev):
3666 if not self.hassidedata:
3664 if not self.hassidedata:
3667 return
3665 return
3668 # revlog formats with sidedata support does not support inline
3666 # revlog formats with sidedata support does not support inline
3669 assert not self._inline
3667 assert not self._inline
3670 if not helpers[1] and not helpers[2]:
3668 if not helpers[1] and not helpers[2]:
3671 # Nothing to generate or remove
3669 # Nothing to generate or remove
3672 return
3670 return
3673
3671
3674 new_entries = []
3672 new_entries = []
3675 # append the new sidedata
3673 # append the new sidedata
3676 with self._writing(transaction):
3674 with self._writing(transaction):
3677 ifh, dfh, sdfh = self._writinghandles
3675 ifh, dfh, sdfh = self._writinghandles
3678 dfh.seek(self._docket.sidedata_end, os.SEEK_SET)
3676 dfh.seek(self._docket.sidedata_end, os.SEEK_SET)
3679
3677
3680 current_offset = sdfh.tell()
3678 current_offset = sdfh.tell()
3681 for rev in range(startrev, endrev + 1):
3679 for rev in range(startrev, endrev + 1):
3682 entry = self.index[rev]
3680 entry = self.index[rev]
3683 new_sidedata, flags = sidedatautil.run_sidedata_helpers(
3681 new_sidedata, flags = sidedatautil.run_sidedata_helpers(
3684 store=self,
3682 store=self,
3685 sidedata_helpers=helpers,
3683 sidedata_helpers=helpers,
3686 sidedata={},
3684 sidedata={},
3687 rev=rev,
3685 rev=rev,
3688 )
3686 )
3689
3687
3690 serialized_sidedata = sidedatautil.serialize_sidedata(
3688 serialized_sidedata = sidedatautil.serialize_sidedata(
3691 new_sidedata
3689 new_sidedata
3692 )
3690 )
3693
3691
3694 sidedata_compression_mode = COMP_MODE_INLINE
3692 sidedata_compression_mode = COMP_MODE_INLINE
3695 if serialized_sidedata and self.hassidedata:
3693 if serialized_sidedata and self.hassidedata:
3696 sidedata_compression_mode = COMP_MODE_PLAIN
3694 sidedata_compression_mode = COMP_MODE_PLAIN
3697 h, comp_sidedata = self.compress(serialized_sidedata)
3695 h, comp_sidedata = self.compress(serialized_sidedata)
3698 if (
3696 if (
3699 h != b'u'
3697 h != b'u'
3700 and comp_sidedata[0] != b'\0'
3698 and comp_sidedata[0] != b'\0'
3701 and len(comp_sidedata) < len(serialized_sidedata)
3699 and len(comp_sidedata) < len(serialized_sidedata)
3702 ):
3700 ):
3703 assert not h
3701 assert not h
3704 if (
3702 if (
3705 comp_sidedata[0]
3703 comp_sidedata[0]
3706 == self._docket.default_compression_header
3704 == self._docket.default_compression_header
3707 ):
3705 ):
3708 sidedata_compression_mode = COMP_MODE_DEFAULT
3706 sidedata_compression_mode = COMP_MODE_DEFAULT
3709 serialized_sidedata = comp_sidedata
3707 serialized_sidedata = comp_sidedata
3710 else:
3708 else:
3711 sidedata_compression_mode = COMP_MODE_INLINE
3709 sidedata_compression_mode = COMP_MODE_INLINE
3712 serialized_sidedata = comp_sidedata
3710 serialized_sidedata = comp_sidedata
3713 if entry[8] != 0 or entry[9] != 0:
3711 if entry[8] != 0 or entry[9] != 0:
3714 # rewriting entries that already have sidedata is not
3712 # rewriting entries that already have sidedata is not
3715 # supported yet, because it introduces garbage data in the
3713 # supported yet, because it introduces garbage data in the
3716 # revlog.
3714 # revlog.
3717 msg = b"rewriting existing sidedata is not supported yet"
3715 msg = b"rewriting existing sidedata is not supported yet"
3718 raise error.Abort(msg)
3716 raise error.Abort(msg)
3719
3717
3720 # Apply (potential) flags to add and to remove after running
3718 # Apply (potential) flags to add and to remove after running
3721 # the sidedata helpers
3719 # the sidedata helpers
3722 new_offset_flags = entry[0] | flags[0] & ~flags[1]
3720 new_offset_flags = entry[0] | flags[0] & ~flags[1]
3723 entry_update = (
3721 entry_update = (
3724 current_offset,
3722 current_offset,
3725 len(serialized_sidedata),
3723 len(serialized_sidedata),
3726 new_offset_flags,
3724 new_offset_flags,
3727 sidedata_compression_mode,
3725 sidedata_compression_mode,
3728 )
3726 )
3729
3727
3730 # the sidedata computation might have move the file cursors around
3728 # the sidedata computation might have move the file cursors around
3731 sdfh.seek(current_offset, os.SEEK_SET)
3729 sdfh.seek(current_offset, os.SEEK_SET)
3732 sdfh.write(serialized_sidedata)
3730 sdfh.write(serialized_sidedata)
3733 new_entries.append(entry_update)
3731 new_entries.append(entry_update)
3734 current_offset += len(serialized_sidedata)
3732 current_offset += len(serialized_sidedata)
3735 self._docket.sidedata_end = sdfh.tell()
3733 self._docket.sidedata_end = sdfh.tell()
3736
3734
3737 # rewrite the new index entries
3735 # rewrite the new index entries
3738 ifh.seek(startrev * self.index.entry_size)
3736 ifh.seek(startrev * self.index.entry_size)
3739 for i, e in enumerate(new_entries):
3737 for i, e in enumerate(new_entries):
3740 rev = startrev + i
3738 rev = startrev + i
3741 self.index.replace_sidedata_info(rev, *e)
3739 self.index.replace_sidedata_info(rev, *e)
3742 packed = self.index.entry_binary(rev)
3740 packed = self.index.entry_binary(rev)
3743 if rev == 0 and self._docket is None:
3741 if rev == 0 and self._docket is None:
3744 header = self._format_flags | self._format_version
3742 header = self._format_flags | self._format_version
3745 header = self.index.pack_header(header)
3743 header = self.index.pack_header(header)
3746 packed = header + packed
3744 packed = header + packed
3747 ifh.write(packed)
3745 ifh.write(packed)
General Comments 0
You need to be logged in to leave comments. Login now