##// END OF EJS Templates
core: don't hard-code node length...
Joerg Sonnenberger -
r47816:c5e1cc0b default
parent child Browse files
Show More
@@ -1,3760 +1,3760 b''
1 # localrepo.py - read/write repository class for mercurial
1 # localrepo.py - read/write repository class for mercurial
2 #
2 #
3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import functools
11 import functools
12 import os
12 import os
13 import random
13 import random
14 import sys
14 import sys
15 import time
15 import time
16 import weakref
16 import weakref
17
17
18 from .i18n import _
18 from .i18n import _
19 from .node import (
19 from .node import (
20 bin,
20 bin,
21 hex,
21 hex,
22 nullrev,
22 nullrev,
23 sha1nodeconstants,
23 sha1nodeconstants,
24 short,
24 short,
25 )
25 )
26 from .pycompat import (
26 from .pycompat import (
27 delattr,
27 delattr,
28 getattr,
28 getattr,
29 )
29 )
30 from . import (
30 from . import (
31 bookmarks,
31 bookmarks,
32 branchmap,
32 branchmap,
33 bundle2,
33 bundle2,
34 bundlecaches,
34 bundlecaches,
35 changegroup,
35 changegroup,
36 color,
36 color,
37 commit,
37 commit,
38 context,
38 context,
39 dirstate,
39 dirstate,
40 dirstateguard,
40 dirstateguard,
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 metadata as metadatamod,
52 metadata as metadatamod,
53 namespaces,
53 namespaces,
54 narrowspec,
54 narrowspec,
55 obsolete,
55 obsolete,
56 pathutil,
56 pathutil,
57 phases,
57 phases,
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 )
93 )
94
94
95 release = lockmod.release
95 release = lockmod.release
96 urlerr = util.urlerr
96 urlerr = util.urlerr
97 urlreq = util.urlreq
97 urlreq = util.urlreq
98
98
99 # set of (path, vfs-location) tuples. vfs-location is:
99 # set of (path, vfs-location) tuples. vfs-location is:
100 # - 'plain for vfs relative paths
100 # - 'plain for vfs relative paths
101 # - '' for svfs relative paths
101 # - '' for svfs relative paths
102 _cachedfiles = set()
102 _cachedfiles = set()
103
103
104
104
105 class _basefilecache(scmutil.filecache):
105 class _basefilecache(scmutil.filecache):
106 """All filecache usage on repo are done for logic that should be unfiltered"""
106 """All filecache usage on repo are done for logic that should be unfiltered"""
107
107
108 def __get__(self, repo, type=None):
108 def __get__(self, repo, type=None):
109 if repo is None:
109 if repo is None:
110 return self
110 return self
111 # proxy to unfiltered __dict__ since filtered repo has no entry
111 # proxy to unfiltered __dict__ since filtered repo has no entry
112 unfi = repo.unfiltered()
112 unfi = repo.unfiltered()
113 try:
113 try:
114 return unfi.__dict__[self.sname]
114 return unfi.__dict__[self.sname]
115 except KeyError:
115 except KeyError:
116 pass
116 pass
117 return super(_basefilecache, self).__get__(unfi, type)
117 return super(_basefilecache, self).__get__(unfi, type)
118
118
119 def set(self, repo, value):
119 def set(self, repo, value):
120 return super(_basefilecache, self).set(repo.unfiltered(), value)
120 return super(_basefilecache, self).set(repo.unfiltered(), value)
121
121
122
122
123 class repofilecache(_basefilecache):
123 class repofilecache(_basefilecache):
124 """filecache for files in .hg but outside of .hg/store"""
124 """filecache for files in .hg but outside of .hg/store"""
125
125
126 def __init__(self, *paths):
126 def __init__(self, *paths):
127 super(repofilecache, self).__init__(*paths)
127 super(repofilecache, self).__init__(*paths)
128 for path in paths:
128 for path in paths:
129 _cachedfiles.add((path, b'plain'))
129 _cachedfiles.add((path, b'plain'))
130
130
131 def join(self, obj, fname):
131 def join(self, obj, fname):
132 return obj.vfs.join(fname)
132 return obj.vfs.join(fname)
133
133
134
134
135 class storecache(_basefilecache):
135 class storecache(_basefilecache):
136 """filecache for files in the store"""
136 """filecache for files in the store"""
137
137
138 def __init__(self, *paths):
138 def __init__(self, *paths):
139 super(storecache, self).__init__(*paths)
139 super(storecache, self).__init__(*paths)
140 for path in paths:
140 for path in paths:
141 _cachedfiles.add((path, b''))
141 _cachedfiles.add((path, b''))
142
142
143 def join(self, obj, fname):
143 def join(self, obj, fname):
144 return obj.sjoin(fname)
144 return obj.sjoin(fname)
145
145
146
146
147 class mixedrepostorecache(_basefilecache):
147 class mixedrepostorecache(_basefilecache):
148 """filecache for a mix files in .hg/store and outside"""
148 """filecache for a mix files in .hg/store and outside"""
149
149
150 def __init__(self, *pathsandlocations):
150 def __init__(self, *pathsandlocations):
151 # scmutil.filecache only uses the path for passing back into our
151 # scmutil.filecache only uses the path for passing back into our
152 # join(), so we can safely pass a list of paths and locations
152 # join(), so we can safely pass a list of paths and locations
153 super(mixedrepostorecache, self).__init__(*pathsandlocations)
153 super(mixedrepostorecache, self).__init__(*pathsandlocations)
154 _cachedfiles.update(pathsandlocations)
154 _cachedfiles.update(pathsandlocations)
155
155
156 def join(self, obj, fnameandlocation):
156 def join(self, obj, fnameandlocation):
157 fname, location = fnameandlocation
157 fname, location = fnameandlocation
158 if location == b'plain':
158 if location == b'plain':
159 return obj.vfs.join(fname)
159 return obj.vfs.join(fname)
160 else:
160 else:
161 if location != b'':
161 if location != b'':
162 raise error.ProgrammingError(
162 raise error.ProgrammingError(
163 b'unexpected location: %s' % location
163 b'unexpected location: %s' % location
164 )
164 )
165 return obj.sjoin(fname)
165 return obj.sjoin(fname)
166
166
167
167
168 def isfilecached(repo, name):
168 def isfilecached(repo, name):
169 """check if a repo has already cached "name" filecache-ed property
169 """check if a repo has already cached "name" filecache-ed property
170
170
171 This returns (cachedobj-or-None, iscached) tuple.
171 This returns (cachedobj-or-None, iscached) tuple.
172 """
172 """
173 cacheentry = repo.unfiltered()._filecache.get(name, None)
173 cacheentry = repo.unfiltered()._filecache.get(name, None)
174 if not cacheentry:
174 if not cacheentry:
175 return None, False
175 return None, False
176 return cacheentry.obj, True
176 return cacheentry.obj, True
177
177
178
178
179 class unfilteredpropertycache(util.propertycache):
179 class unfilteredpropertycache(util.propertycache):
180 """propertycache that apply to unfiltered repo only"""
180 """propertycache that apply to unfiltered repo only"""
181
181
182 def __get__(self, repo, type=None):
182 def __get__(self, repo, type=None):
183 unfi = repo.unfiltered()
183 unfi = repo.unfiltered()
184 if unfi is repo:
184 if unfi is repo:
185 return super(unfilteredpropertycache, self).__get__(unfi)
185 return super(unfilteredpropertycache, self).__get__(unfi)
186 return getattr(unfi, self.name)
186 return getattr(unfi, self.name)
187
187
188
188
189 class filteredpropertycache(util.propertycache):
189 class filteredpropertycache(util.propertycache):
190 """propertycache that must take filtering in account"""
190 """propertycache that must take filtering in account"""
191
191
192 def cachevalue(self, obj, value):
192 def cachevalue(self, obj, value):
193 object.__setattr__(obj, self.name, value)
193 object.__setattr__(obj, self.name, value)
194
194
195
195
196 def hasunfilteredcache(repo, name):
196 def hasunfilteredcache(repo, name):
197 """check if a repo has an unfilteredpropertycache value for <name>"""
197 """check if a repo has an unfilteredpropertycache value for <name>"""
198 return name in vars(repo.unfiltered())
198 return name in vars(repo.unfiltered())
199
199
200
200
201 def unfilteredmethod(orig):
201 def unfilteredmethod(orig):
202 """decorate method that always need to be run on unfiltered version"""
202 """decorate method that always need to be run on unfiltered version"""
203
203
204 @functools.wraps(orig)
204 @functools.wraps(orig)
205 def wrapper(repo, *args, **kwargs):
205 def wrapper(repo, *args, **kwargs):
206 return orig(repo.unfiltered(), *args, **kwargs)
206 return orig(repo.unfiltered(), *args, **kwargs)
207
207
208 return wrapper
208 return wrapper
209
209
210
210
211 moderncaps = {
211 moderncaps = {
212 b'lookup',
212 b'lookup',
213 b'branchmap',
213 b'branchmap',
214 b'pushkey',
214 b'pushkey',
215 b'known',
215 b'known',
216 b'getbundle',
216 b'getbundle',
217 b'unbundle',
217 b'unbundle',
218 }
218 }
219 legacycaps = moderncaps.union({b'changegroupsubset'})
219 legacycaps = moderncaps.union({b'changegroupsubset'})
220
220
221
221
222 @interfaceutil.implementer(repository.ipeercommandexecutor)
222 @interfaceutil.implementer(repository.ipeercommandexecutor)
223 class localcommandexecutor(object):
223 class localcommandexecutor(object):
224 def __init__(self, peer):
224 def __init__(self, peer):
225 self._peer = peer
225 self._peer = peer
226 self._sent = False
226 self._sent = False
227 self._closed = False
227 self._closed = False
228
228
229 def __enter__(self):
229 def __enter__(self):
230 return self
230 return self
231
231
232 def __exit__(self, exctype, excvalue, exctb):
232 def __exit__(self, exctype, excvalue, exctb):
233 self.close()
233 self.close()
234
234
235 def callcommand(self, command, args):
235 def callcommand(self, command, args):
236 if self._sent:
236 if self._sent:
237 raise error.ProgrammingError(
237 raise error.ProgrammingError(
238 b'callcommand() cannot be used after sendcommands()'
238 b'callcommand() cannot be used after sendcommands()'
239 )
239 )
240
240
241 if self._closed:
241 if self._closed:
242 raise error.ProgrammingError(
242 raise error.ProgrammingError(
243 b'callcommand() cannot be used after close()'
243 b'callcommand() cannot be used after close()'
244 )
244 )
245
245
246 # We don't need to support anything fancy. Just call the named
246 # We don't need to support anything fancy. Just call the named
247 # method on the peer and return a resolved future.
247 # method on the peer and return a resolved future.
248 fn = getattr(self._peer, pycompat.sysstr(command))
248 fn = getattr(self._peer, pycompat.sysstr(command))
249
249
250 f = pycompat.futures.Future()
250 f = pycompat.futures.Future()
251
251
252 try:
252 try:
253 result = fn(**pycompat.strkwargs(args))
253 result = fn(**pycompat.strkwargs(args))
254 except Exception:
254 except Exception:
255 pycompat.future_set_exception_info(f, sys.exc_info()[1:])
255 pycompat.future_set_exception_info(f, sys.exc_info()[1:])
256 else:
256 else:
257 f.set_result(result)
257 f.set_result(result)
258
258
259 return f
259 return f
260
260
261 def sendcommands(self):
261 def sendcommands(self):
262 self._sent = True
262 self._sent = True
263
263
264 def close(self):
264 def close(self):
265 self._closed = True
265 self._closed = True
266
266
267
267
268 @interfaceutil.implementer(repository.ipeercommands)
268 @interfaceutil.implementer(repository.ipeercommands)
269 class localpeer(repository.peer):
269 class localpeer(repository.peer):
270 '''peer for a local repo; reflects only the most recent API'''
270 '''peer for a local repo; reflects only the most recent API'''
271
271
272 def __init__(self, repo, caps=None):
272 def __init__(self, repo, caps=None):
273 super(localpeer, self).__init__()
273 super(localpeer, self).__init__()
274
274
275 if caps is None:
275 if caps is None:
276 caps = moderncaps.copy()
276 caps = moderncaps.copy()
277 self._repo = repo.filtered(b'served')
277 self._repo = repo.filtered(b'served')
278 self.ui = repo.ui
278 self.ui = repo.ui
279
279
280 if repo._wanted_sidedata:
280 if repo._wanted_sidedata:
281 formatted = bundle2.format_remote_wanted_sidedata(repo)
281 formatted = bundle2.format_remote_wanted_sidedata(repo)
282 caps.add(b'exp-wanted-sidedata=' + formatted)
282 caps.add(b'exp-wanted-sidedata=' + formatted)
283
283
284 self._caps = repo._restrictcapabilities(caps)
284 self._caps = repo._restrictcapabilities(caps)
285
285
286 # Begin of _basepeer interface.
286 # Begin of _basepeer interface.
287
287
288 def url(self):
288 def url(self):
289 return self._repo.url()
289 return self._repo.url()
290
290
291 def local(self):
291 def local(self):
292 return self._repo
292 return self._repo
293
293
294 def peer(self):
294 def peer(self):
295 return self
295 return self
296
296
297 def canpush(self):
297 def canpush(self):
298 return True
298 return True
299
299
300 def close(self):
300 def close(self):
301 self._repo.close()
301 self._repo.close()
302
302
303 # End of _basepeer interface.
303 # End of _basepeer interface.
304
304
305 # Begin of _basewirecommands interface.
305 # Begin of _basewirecommands interface.
306
306
307 def branchmap(self):
307 def branchmap(self):
308 return self._repo.branchmap()
308 return self._repo.branchmap()
309
309
310 def capabilities(self):
310 def capabilities(self):
311 return self._caps
311 return self._caps
312
312
313 def clonebundles(self):
313 def clonebundles(self):
314 return self._repo.tryread(bundlecaches.CB_MANIFEST_FILE)
314 return self._repo.tryread(bundlecaches.CB_MANIFEST_FILE)
315
315
316 def debugwireargs(self, one, two, three=None, four=None, five=None):
316 def debugwireargs(self, one, two, three=None, four=None, five=None):
317 """Used to test argument passing over the wire"""
317 """Used to test argument passing over the wire"""
318 return b"%s %s %s %s %s" % (
318 return b"%s %s %s %s %s" % (
319 one,
319 one,
320 two,
320 two,
321 pycompat.bytestr(three),
321 pycompat.bytestr(three),
322 pycompat.bytestr(four),
322 pycompat.bytestr(four),
323 pycompat.bytestr(five),
323 pycompat.bytestr(five),
324 )
324 )
325
325
326 def getbundle(
326 def getbundle(
327 self,
327 self,
328 source,
328 source,
329 heads=None,
329 heads=None,
330 common=None,
330 common=None,
331 bundlecaps=None,
331 bundlecaps=None,
332 remote_sidedata=None,
332 remote_sidedata=None,
333 **kwargs
333 **kwargs
334 ):
334 ):
335 chunks = exchange.getbundlechunks(
335 chunks = exchange.getbundlechunks(
336 self._repo,
336 self._repo,
337 source,
337 source,
338 heads=heads,
338 heads=heads,
339 common=common,
339 common=common,
340 bundlecaps=bundlecaps,
340 bundlecaps=bundlecaps,
341 remote_sidedata=remote_sidedata,
341 remote_sidedata=remote_sidedata,
342 **kwargs
342 **kwargs
343 )[1]
343 )[1]
344 cb = util.chunkbuffer(chunks)
344 cb = util.chunkbuffer(chunks)
345
345
346 if exchange.bundle2requested(bundlecaps):
346 if exchange.bundle2requested(bundlecaps):
347 # When requesting a bundle2, getbundle returns a stream to make the
347 # When requesting a bundle2, getbundle returns a stream to make the
348 # wire level function happier. We need to build a proper object
348 # wire level function happier. We need to build a proper object
349 # from it in local peer.
349 # from it in local peer.
350 return bundle2.getunbundler(self.ui, cb)
350 return bundle2.getunbundler(self.ui, cb)
351 else:
351 else:
352 return changegroup.getunbundler(b'01', cb, None)
352 return changegroup.getunbundler(b'01', cb, None)
353
353
354 def heads(self):
354 def heads(self):
355 return self._repo.heads()
355 return self._repo.heads()
356
356
357 def known(self, nodes):
357 def known(self, nodes):
358 return self._repo.known(nodes)
358 return self._repo.known(nodes)
359
359
360 def listkeys(self, namespace):
360 def listkeys(self, namespace):
361 return self._repo.listkeys(namespace)
361 return self._repo.listkeys(namespace)
362
362
363 def lookup(self, key):
363 def lookup(self, key):
364 return self._repo.lookup(key)
364 return self._repo.lookup(key)
365
365
366 def pushkey(self, namespace, key, old, new):
366 def pushkey(self, namespace, key, old, new):
367 return self._repo.pushkey(namespace, key, old, new)
367 return self._repo.pushkey(namespace, key, old, new)
368
368
369 def stream_out(self):
369 def stream_out(self):
370 raise error.Abort(_(b'cannot perform stream clone against local peer'))
370 raise error.Abort(_(b'cannot perform stream clone against local peer'))
371
371
372 def unbundle(self, bundle, heads, url):
372 def unbundle(self, bundle, heads, url):
373 """apply a bundle on a repo
373 """apply a bundle on a repo
374
374
375 This function handles the repo locking itself."""
375 This function handles the repo locking itself."""
376 try:
376 try:
377 try:
377 try:
378 bundle = exchange.readbundle(self.ui, bundle, None)
378 bundle = exchange.readbundle(self.ui, bundle, None)
379 ret = exchange.unbundle(self._repo, bundle, heads, b'push', url)
379 ret = exchange.unbundle(self._repo, bundle, heads, b'push', url)
380 if util.safehasattr(ret, b'getchunks'):
380 if util.safehasattr(ret, b'getchunks'):
381 # This is a bundle20 object, turn it into an unbundler.
381 # This is a bundle20 object, turn it into an unbundler.
382 # This little dance should be dropped eventually when the
382 # This little dance should be dropped eventually when the
383 # API is finally improved.
383 # API is finally improved.
384 stream = util.chunkbuffer(ret.getchunks())
384 stream = util.chunkbuffer(ret.getchunks())
385 ret = bundle2.getunbundler(self.ui, stream)
385 ret = bundle2.getunbundler(self.ui, stream)
386 return ret
386 return ret
387 except Exception as exc:
387 except Exception as exc:
388 # If the exception contains output salvaged from a bundle2
388 # If the exception contains output salvaged from a bundle2
389 # reply, we need to make sure it is printed before continuing
389 # reply, we need to make sure it is printed before continuing
390 # to fail. So we build a bundle2 with such output and consume
390 # to fail. So we build a bundle2 with such output and consume
391 # it directly.
391 # it directly.
392 #
392 #
393 # This is not very elegant but allows a "simple" solution for
393 # This is not very elegant but allows a "simple" solution for
394 # issue4594
394 # issue4594
395 output = getattr(exc, '_bundle2salvagedoutput', ())
395 output = getattr(exc, '_bundle2salvagedoutput', ())
396 if output:
396 if output:
397 bundler = bundle2.bundle20(self._repo.ui)
397 bundler = bundle2.bundle20(self._repo.ui)
398 for out in output:
398 for out in output:
399 bundler.addpart(out)
399 bundler.addpart(out)
400 stream = util.chunkbuffer(bundler.getchunks())
400 stream = util.chunkbuffer(bundler.getchunks())
401 b = bundle2.getunbundler(self.ui, stream)
401 b = bundle2.getunbundler(self.ui, stream)
402 bundle2.processbundle(self._repo, b)
402 bundle2.processbundle(self._repo, b)
403 raise
403 raise
404 except error.PushRaced as exc:
404 except error.PushRaced as exc:
405 raise error.ResponseError(
405 raise error.ResponseError(
406 _(b'push failed:'), stringutil.forcebytestr(exc)
406 _(b'push failed:'), stringutil.forcebytestr(exc)
407 )
407 )
408
408
409 # End of _basewirecommands interface.
409 # End of _basewirecommands interface.
410
410
411 # Begin of peer interface.
411 # Begin of peer interface.
412
412
413 def commandexecutor(self):
413 def commandexecutor(self):
414 return localcommandexecutor(self)
414 return localcommandexecutor(self)
415
415
416 # End of peer interface.
416 # End of peer interface.
417
417
418
418
419 @interfaceutil.implementer(repository.ipeerlegacycommands)
419 @interfaceutil.implementer(repository.ipeerlegacycommands)
420 class locallegacypeer(localpeer):
420 class locallegacypeer(localpeer):
421 """peer extension which implements legacy methods too; used for tests with
421 """peer extension which implements legacy methods too; used for tests with
422 restricted capabilities"""
422 restricted capabilities"""
423
423
424 def __init__(self, repo):
424 def __init__(self, repo):
425 super(locallegacypeer, self).__init__(repo, caps=legacycaps)
425 super(locallegacypeer, self).__init__(repo, caps=legacycaps)
426
426
427 # Begin of baselegacywirecommands interface.
427 # Begin of baselegacywirecommands interface.
428
428
429 def between(self, pairs):
429 def between(self, pairs):
430 return self._repo.between(pairs)
430 return self._repo.between(pairs)
431
431
432 def branches(self, nodes):
432 def branches(self, nodes):
433 return self._repo.branches(nodes)
433 return self._repo.branches(nodes)
434
434
435 def changegroup(self, nodes, source):
435 def changegroup(self, nodes, source):
436 outgoing = discovery.outgoing(
436 outgoing = discovery.outgoing(
437 self._repo, missingroots=nodes, ancestorsof=self._repo.heads()
437 self._repo, missingroots=nodes, ancestorsof=self._repo.heads()
438 )
438 )
439 return changegroup.makechangegroup(self._repo, outgoing, b'01', source)
439 return changegroup.makechangegroup(self._repo, outgoing, b'01', source)
440
440
441 def changegroupsubset(self, bases, heads, source):
441 def changegroupsubset(self, bases, heads, source):
442 outgoing = discovery.outgoing(
442 outgoing = discovery.outgoing(
443 self._repo, missingroots=bases, ancestorsof=heads
443 self._repo, missingroots=bases, ancestorsof=heads
444 )
444 )
445 return changegroup.makechangegroup(self._repo, outgoing, b'01', source)
445 return changegroup.makechangegroup(self._repo, outgoing, b'01', source)
446
446
447 # End of baselegacywirecommands interface.
447 # End of baselegacywirecommands interface.
448
448
449
449
450 # Functions receiving (ui, features) that extensions can register to impact
450 # Functions receiving (ui, features) that extensions can register to impact
451 # the ability to load repositories with custom requirements. Only
451 # the ability to load repositories with custom requirements. Only
452 # functions defined in loaded extensions are called.
452 # functions defined in loaded extensions are called.
453 #
453 #
454 # The function receives a set of requirement strings that the repository
454 # The function receives a set of requirement strings that the repository
455 # is capable of opening. Functions will typically add elements to the
455 # is capable of opening. Functions will typically add elements to the
456 # set to reflect that the extension knows how to handle that requirements.
456 # set to reflect that the extension knows how to handle that requirements.
457 featuresetupfuncs = set()
457 featuresetupfuncs = set()
458
458
459
459
460 def _getsharedvfs(hgvfs, requirements):
460 def _getsharedvfs(hgvfs, requirements):
461 """returns the vfs object pointing to root of shared source
461 """returns the vfs object pointing to root of shared source
462 repo for a shared repository
462 repo for a shared repository
463
463
464 hgvfs is vfs pointing at .hg/ of current repo (shared one)
464 hgvfs is vfs pointing at .hg/ of current repo (shared one)
465 requirements is a set of requirements of current repo (shared one)
465 requirements is a set of requirements of current repo (shared one)
466 """
466 """
467 # The ``shared`` or ``relshared`` requirements indicate the
467 # The ``shared`` or ``relshared`` requirements indicate the
468 # store lives in the path contained in the ``.hg/sharedpath`` file.
468 # store lives in the path contained in the ``.hg/sharedpath`` file.
469 # This is an absolute path for ``shared`` and relative to
469 # This is an absolute path for ``shared`` and relative to
470 # ``.hg/`` for ``relshared``.
470 # ``.hg/`` for ``relshared``.
471 sharedpath = hgvfs.read(b'sharedpath').rstrip(b'\n')
471 sharedpath = hgvfs.read(b'sharedpath').rstrip(b'\n')
472 if requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements:
472 if requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements:
473 sharedpath = util.normpath(hgvfs.join(sharedpath))
473 sharedpath = util.normpath(hgvfs.join(sharedpath))
474
474
475 sharedvfs = vfsmod.vfs(sharedpath, realpath=True)
475 sharedvfs = vfsmod.vfs(sharedpath, realpath=True)
476
476
477 if not sharedvfs.exists():
477 if not sharedvfs.exists():
478 raise error.RepoError(
478 raise error.RepoError(
479 _(b'.hg/sharedpath points to nonexistent directory %s')
479 _(b'.hg/sharedpath points to nonexistent directory %s')
480 % sharedvfs.base
480 % sharedvfs.base
481 )
481 )
482 return sharedvfs
482 return sharedvfs
483
483
484
484
485 def _readrequires(vfs, allowmissing):
485 def _readrequires(vfs, allowmissing):
486 """reads the require file present at root of this vfs
486 """reads the require file present at root of this vfs
487 and return a set of requirements
487 and return a set of requirements
488
488
489 If allowmissing is True, we suppress ENOENT if raised"""
489 If allowmissing is True, we suppress ENOENT if raised"""
490 # requires file contains a newline-delimited list of
490 # requires file contains a newline-delimited list of
491 # features/capabilities the opener (us) must have in order to use
491 # features/capabilities the opener (us) must have in order to use
492 # the repository. This file was introduced in Mercurial 0.9.2,
492 # the repository. This file was introduced in Mercurial 0.9.2,
493 # which means very old repositories may not have one. We assume
493 # which means very old repositories may not have one. We assume
494 # a missing file translates to no requirements.
494 # a missing file translates to no requirements.
495 try:
495 try:
496 requirements = set(vfs.read(b'requires').splitlines())
496 requirements = set(vfs.read(b'requires').splitlines())
497 except IOError as e:
497 except IOError as e:
498 if not (allowmissing and e.errno == errno.ENOENT):
498 if not (allowmissing and e.errno == errno.ENOENT):
499 raise
499 raise
500 requirements = set()
500 requirements = set()
501 return requirements
501 return requirements
502
502
503
503
504 def makelocalrepository(baseui, path, intents=None):
504 def makelocalrepository(baseui, path, intents=None):
505 """Create a local repository object.
505 """Create a local repository object.
506
506
507 Given arguments needed to construct a local repository, this function
507 Given arguments needed to construct a local repository, this function
508 performs various early repository loading functionality (such as
508 performs various early repository loading functionality (such as
509 reading the ``.hg/requires`` and ``.hg/hgrc`` files), validates that
509 reading the ``.hg/requires`` and ``.hg/hgrc`` files), validates that
510 the repository can be opened, derives a type suitable for representing
510 the repository can be opened, derives a type suitable for representing
511 that repository, and returns an instance of it.
511 that repository, and returns an instance of it.
512
512
513 The returned object conforms to the ``repository.completelocalrepository``
513 The returned object conforms to the ``repository.completelocalrepository``
514 interface.
514 interface.
515
515
516 The repository type is derived by calling a series of factory functions
516 The repository type is derived by calling a series of factory functions
517 for each aspect/interface of the final repository. These are defined by
517 for each aspect/interface of the final repository. These are defined by
518 ``REPO_INTERFACES``.
518 ``REPO_INTERFACES``.
519
519
520 Each factory function is called to produce a type implementing a specific
520 Each factory function is called to produce a type implementing a specific
521 interface. The cumulative list of returned types will be combined into a
521 interface. The cumulative list of returned types will be combined into a
522 new type and that type will be instantiated to represent the local
522 new type and that type will be instantiated to represent the local
523 repository.
523 repository.
524
524
525 The factory functions each receive various state that may be consulted
525 The factory functions each receive various state that may be consulted
526 as part of deriving a type.
526 as part of deriving a type.
527
527
528 Extensions should wrap these factory functions to customize repository type
528 Extensions should wrap these factory functions to customize repository type
529 creation. Note that an extension's wrapped function may be called even if
529 creation. Note that an extension's wrapped function may be called even if
530 that extension is not loaded for the repo being constructed. Extensions
530 that extension is not loaded for the repo being constructed. Extensions
531 should check if their ``__name__`` appears in the
531 should check if their ``__name__`` appears in the
532 ``extensionmodulenames`` set passed to the factory function and no-op if
532 ``extensionmodulenames`` set passed to the factory function and no-op if
533 not.
533 not.
534 """
534 """
535 ui = baseui.copy()
535 ui = baseui.copy()
536 # Prevent copying repo configuration.
536 # Prevent copying repo configuration.
537 ui.copy = baseui.copy
537 ui.copy = baseui.copy
538
538
539 # Working directory VFS rooted at repository root.
539 # Working directory VFS rooted at repository root.
540 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
540 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
541
541
542 # Main VFS for .hg/ directory.
542 # Main VFS for .hg/ directory.
543 hgpath = wdirvfs.join(b'.hg')
543 hgpath = wdirvfs.join(b'.hg')
544 hgvfs = vfsmod.vfs(hgpath, cacheaudited=True)
544 hgvfs = vfsmod.vfs(hgpath, cacheaudited=True)
545 # Whether this repository is shared one or not
545 # Whether this repository is shared one or not
546 shared = False
546 shared = False
547 # If this repository is shared, vfs pointing to shared repo
547 # If this repository is shared, vfs pointing to shared repo
548 sharedvfs = None
548 sharedvfs = None
549
549
550 # The .hg/ path should exist and should be a directory. All other
550 # The .hg/ path should exist and should be a directory. All other
551 # cases are errors.
551 # cases are errors.
552 if not hgvfs.isdir():
552 if not hgvfs.isdir():
553 try:
553 try:
554 hgvfs.stat()
554 hgvfs.stat()
555 except OSError as e:
555 except OSError as e:
556 if e.errno != errno.ENOENT:
556 if e.errno != errno.ENOENT:
557 raise
557 raise
558 except ValueError as e:
558 except ValueError as e:
559 # Can be raised on Python 3.8 when path is invalid.
559 # Can be raised on Python 3.8 when path is invalid.
560 raise error.Abort(
560 raise error.Abort(
561 _(b'invalid path %s: %s') % (path, stringutil.forcebytestr(e))
561 _(b'invalid path %s: %s') % (path, stringutil.forcebytestr(e))
562 )
562 )
563
563
564 raise error.RepoError(_(b'repository %s not found') % path)
564 raise error.RepoError(_(b'repository %s not found') % path)
565
565
566 requirements = _readrequires(hgvfs, True)
566 requirements = _readrequires(hgvfs, True)
567 shared = (
567 shared = (
568 requirementsmod.SHARED_REQUIREMENT in requirements
568 requirementsmod.SHARED_REQUIREMENT in requirements
569 or requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements
569 or requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements
570 )
570 )
571 storevfs = None
571 storevfs = None
572 if shared:
572 if shared:
573 # This is a shared repo
573 # This is a shared repo
574 sharedvfs = _getsharedvfs(hgvfs, requirements)
574 sharedvfs = _getsharedvfs(hgvfs, requirements)
575 storevfs = vfsmod.vfs(sharedvfs.join(b'store'))
575 storevfs = vfsmod.vfs(sharedvfs.join(b'store'))
576 else:
576 else:
577 storevfs = vfsmod.vfs(hgvfs.join(b'store'))
577 storevfs = vfsmod.vfs(hgvfs.join(b'store'))
578
578
579 # if .hg/requires contains the sharesafe requirement, it means
579 # if .hg/requires contains the sharesafe requirement, it means
580 # there exists a `.hg/store/requires` too and we should read it
580 # there exists a `.hg/store/requires` too and we should read it
581 # NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
581 # NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
582 # is present. We never write SHARESAFE_REQUIREMENT for a repo if store
582 # is present. We never write SHARESAFE_REQUIREMENT for a repo if store
583 # is not present, refer checkrequirementscompat() for that
583 # is not present, refer checkrequirementscompat() for that
584 #
584 #
585 # However, if SHARESAFE_REQUIREMENT is not present, it means that the
585 # However, if SHARESAFE_REQUIREMENT is not present, it means that the
586 # repository was shared the old way. We check the share source .hg/requires
586 # repository was shared the old way. We check the share source .hg/requires
587 # for SHARESAFE_REQUIREMENT to detect whether the current repository needs
587 # for SHARESAFE_REQUIREMENT to detect whether the current repository needs
588 # to be reshared
588 # to be reshared
589 hint = _(b"see `hg help config.format.use-share-safe` for more information")
589 hint = _(b"see `hg help config.format.use-share-safe` for more information")
590 if requirementsmod.SHARESAFE_REQUIREMENT in requirements:
590 if requirementsmod.SHARESAFE_REQUIREMENT in requirements:
591
591
592 if (
592 if (
593 shared
593 shared
594 and requirementsmod.SHARESAFE_REQUIREMENT
594 and requirementsmod.SHARESAFE_REQUIREMENT
595 not in _readrequires(sharedvfs, True)
595 not in _readrequires(sharedvfs, True)
596 ):
596 ):
597 mismatch_warn = ui.configbool(
597 mismatch_warn = ui.configbool(
598 b'share', b'safe-mismatch.source-not-safe.warn'
598 b'share', b'safe-mismatch.source-not-safe.warn'
599 )
599 )
600 mismatch_config = ui.config(
600 mismatch_config = ui.config(
601 b'share', b'safe-mismatch.source-not-safe'
601 b'share', b'safe-mismatch.source-not-safe'
602 )
602 )
603 if mismatch_config in (
603 if mismatch_config in (
604 b'downgrade-allow',
604 b'downgrade-allow',
605 b'allow',
605 b'allow',
606 b'downgrade-abort',
606 b'downgrade-abort',
607 ):
607 ):
608 # prevent cyclic import localrepo -> upgrade -> localrepo
608 # prevent cyclic import localrepo -> upgrade -> localrepo
609 from . import upgrade
609 from . import upgrade
610
610
611 upgrade.downgrade_share_to_non_safe(
611 upgrade.downgrade_share_to_non_safe(
612 ui,
612 ui,
613 hgvfs,
613 hgvfs,
614 sharedvfs,
614 sharedvfs,
615 requirements,
615 requirements,
616 mismatch_config,
616 mismatch_config,
617 mismatch_warn,
617 mismatch_warn,
618 )
618 )
619 elif mismatch_config == b'abort':
619 elif mismatch_config == b'abort':
620 raise error.Abort(
620 raise error.Abort(
621 _(b"share source does not support share-safe requirement"),
621 _(b"share source does not support share-safe requirement"),
622 hint=hint,
622 hint=hint,
623 )
623 )
624 else:
624 else:
625 raise error.Abort(
625 raise error.Abort(
626 _(
626 _(
627 b"share-safe mismatch with source.\nUnrecognized"
627 b"share-safe mismatch with source.\nUnrecognized"
628 b" value '%s' of `share.safe-mismatch.source-not-safe`"
628 b" value '%s' of `share.safe-mismatch.source-not-safe`"
629 b" set."
629 b" set."
630 )
630 )
631 % mismatch_config,
631 % mismatch_config,
632 hint=hint,
632 hint=hint,
633 )
633 )
634 else:
634 else:
635 requirements |= _readrequires(storevfs, False)
635 requirements |= _readrequires(storevfs, False)
636 elif shared:
636 elif shared:
637 sourcerequires = _readrequires(sharedvfs, False)
637 sourcerequires = _readrequires(sharedvfs, False)
638 if requirementsmod.SHARESAFE_REQUIREMENT in sourcerequires:
638 if requirementsmod.SHARESAFE_REQUIREMENT in sourcerequires:
639 mismatch_config = ui.config(b'share', b'safe-mismatch.source-safe')
639 mismatch_config = ui.config(b'share', b'safe-mismatch.source-safe')
640 mismatch_warn = ui.configbool(
640 mismatch_warn = ui.configbool(
641 b'share', b'safe-mismatch.source-safe.warn'
641 b'share', b'safe-mismatch.source-safe.warn'
642 )
642 )
643 if mismatch_config in (
643 if mismatch_config in (
644 b'upgrade-allow',
644 b'upgrade-allow',
645 b'allow',
645 b'allow',
646 b'upgrade-abort',
646 b'upgrade-abort',
647 ):
647 ):
648 # prevent cyclic import localrepo -> upgrade -> localrepo
648 # prevent cyclic import localrepo -> upgrade -> localrepo
649 from . import upgrade
649 from . import upgrade
650
650
651 upgrade.upgrade_share_to_safe(
651 upgrade.upgrade_share_to_safe(
652 ui,
652 ui,
653 hgvfs,
653 hgvfs,
654 storevfs,
654 storevfs,
655 requirements,
655 requirements,
656 mismatch_config,
656 mismatch_config,
657 mismatch_warn,
657 mismatch_warn,
658 )
658 )
659 elif mismatch_config == b'abort':
659 elif mismatch_config == b'abort':
660 raise error.Abort(
660 raise error.Abort(
661 _(
661 _(
662 b'version mismatch: source uses share-safe'
662 b'version mismatch: source uses share-safe'
663 b' functionality while the current share does not'
663 b' functionality while the current share does not'
664 ),
664 ),
665 hint=hint,
665 hint=hint,
666 )
666 )
667 else:
667 else:
668 raise error.Abort(
668 raise error.Abort(
669 _(
669 _(
670 b"share-safe mismatch with source.\nUnrecognized"
670 b"share-safe mismatch with source.\nUnrecognized"
671 b" value '%s' of `share.safe-mismatch.source-safe` set."
671 b" value '%s' of `share.safe-mismatch.source-safe` set."
672 )
672 )
673 % mismatch_config,
673 % mismatch_config,
674 hint=hint,
674 hint=hint,
675 )
675 )
676
676
677 # The .hg/hgrc file may load extensions or contain config options
677 # The .hg/hgrc file may load extensions or contain config options
678 # that influence repository construction. Attempt to load it and
678 # that influence repository construction. Attempt to load it and
679 # process any new extensions that it may have pulled in.
679 # process any new extensions that it may have pulled in.
680 if loadhgrc(ui, wdirvfs, hgvfs, requirements, sharedvfs):
680 if loadhgrc(ui, wdirvfs, hgvfs, requirements, sharedvfs):
681 afterhgrcload(ui, wdirvfs, hgvfs, requirements)
681 afterhgrcload(ui, wdirvfs, hgvfs, requirements)
682 extensions.loadall(ui)
682 extensions.loadall(ui)
683 extensions.populateui(ui)
683 extensions.populateui(ui)
684
684
685 # Set of module names of extensions loaded for this repository.
685 # Set of module names of extensions loaded for this repository.
686 extensionmodulenames = {m.__name__ for n, m in extensions.extensions(ui)}
686 extensionmodulenames = {m.__name__ for n, m in extensions.extensions(ui)}
687
687
688 supportedrequirements = gathersupportedrequirements(ui)
688 supportedrequirements = gathersupportedrequirements(ui)
689
689
690 # We first validate the requirements are known.
690 # We first validate the requirements are known.
691 ensurerequirementsrecognized(requirements, supportedrequirements)
691 ensurerequirementsrecognized(requirements, supportedrequirements)
692
692
693 # Then we validate that the known set is reasonable to use together.
693 # Then we validate that the known set is reasonable to use together.
694 ensurerequirementscompatible(ui, requirements)
694 ensurerequirementscompatible(ui, requirements)
695
695
696 # TODO there are unhandled edge cases related to opening repositories with
696 # TODO there are unhandled edge cases related to opening repositories with
697 # shared storage. If storage is shared, we should also test for requirements
697 # shared storage. If storage is shared, we should also test for requirements
698 # compatibility in the pointed-to repo. This entails loading the .hg/hgrc in
698 # compatibility in the pointed-to repo. This entails loading the .hg/hgrc in
699 # that repo, as that repo may load extensions needed to open it. This is a
699 # that repo, as that repo may load extensions needed to open it. This is a
700 # bit complicated because we don't want the other hgrc to overwrite settings
700 # bit complicated because we don't want the other hgrc to overwrite settings
701 # in this hgrc.
701 # in this hgrc.
702 #
702 #
703 # This bug is somewhat mitigated by the fact that we copy the .hg/requires
703 # This bug is somewhat mitigated by the fact that we copy the .hg/requires
704 # file when sharing repos. But if a requirement is added after the share is
704 # file when sharing repos. But if a requirement is added after the share is
705 # performed, thereby introducing a new requirement for the opener, we may
705 # performed, thereby introducing a new requirement for the opener, we may
706 # will not see that and could encounter a run-time error interacting with
706 # will not see that and could encounter a run-time error interacting with
707 # that shared store since it has an unknown-to-us requirement.
707 # that shared store since it has an unknown-to-us requirement.
708
708
709 # At this point, we know we should be capable of opening the repository.
709 # At this point, we know we should be capable of opening the repository.
710 # Now get on with doing that.
710 # Now get on with doing that.
711
711
712 features = set()
712 features = set()
713
713
714 # The "store" part of the repository holds versioned data. How it is
714 # The "store" part of the repository holds versioned data. How it is
715 # accessed is determined by various requirements. If `shared` or
715 # accessed is determined by various requirements. If `shared` or
716 # `relshared` requirements are present, this indicates current repository
716 # `relshared` requirements are present, this indicates current repository
717 # is a share and store exists in path mentioned in `.hg/sharedpath`
717 # is a share and store exists in path mentioned in `.hg/sharedpath`
718 if shared:
718 if shared:
719 storebasepath = sharedvfs.base
719 storebasepath = sharedvfs.base
720 cachepath = sharedvfs.join(b'cache')
720 cachepath = sharedvfs.join(b'cache')
721 features.add(repository.REPO_FEATURE_SHARED_STORAGE)
721 features.add(repository.REPO_FEATURE_SHARED_STORAGE)
722 else:
722 else:
723 storebasepath = hgvfs.base
723 storebasepath = hgvfs.base
724 cachepath = hgvfs.join(b'cache')
724 cachepath = hgvfs.join(b'cache')
725 wcachepath = hgvfs.join(b'wcache')
725 wcachepath = hgvfs.join(b'wcache')
726
726
727 # The store has changed over time and the exact layout is dictated by
727 # The store has changed over time and the exact layout is dictated by
728 # requirements. The store interface abstracts differences across all
728 # requirements. The store interface abstracts differences across all
729 # of them.
729 # of them.
730 store = makestore(
730 store = makestore(
731 requirements,
731 requirements,
732 storebasepath,
732 storebasepath,
733 lambda base: vfsmod.vfs(base, cacheaudited=True),
733 lambda base: vfsmod.vfs(base, cacheaudited=True),
734 )
734 )
735 hgvfs.createmode = store.createmode
735 hgvfs.createmode = store.createmode
736
736
737 storevfs = store.vfs
737 storevfs = store.vfs
738 storevfs.options = resolvestorevfsoptions(ui, requirements, features)
738 storevfs.options = resolvestorevfsoptions(ui, requirements, features)
739
739
740 # The cache vfs is used to manage cache files.
740 # The cache vfs is used to manage cache files.
741 cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
741 cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
742 cachevfs.createmode = store.createmode
742 cachevfs.createmode = store.createmode
743 # The cache vfs is used to manage cache files related to the working copy
743 # The cache vfs is used to manage cache files related to the working copy
744 wcachevfs = vfsmod.vfs(wcachepath, cacheaudited=True)
744 wcachevfs = vfsmod.vfs(wcachepath, cacheaudited=True)
745 wcachevfs.createmode = store.createmode
745 wcachevfs.createmode = store.createmode
746
746
747 # Now resolve the type for the repository object. We do this by repeatedly
747 # Now resolve the type for the repository object. We do this by repeatedly
748 # calling a factory function to produces types for specific aspects of the
748 # calling a factory function to produces types for specific aspects of the
749 # repo's operation. The aggregate returned types are used as base classes
749 # repo's operation. The aggregate returned types are used as base classes
750 # for a dynamically-derived type, which will represent our new repository.
750 # for a dynamically-derived type, which will represent our new repository.
751
751
752 bases = []
752 bases = []
753 extrastate = {}
753 extrastate = {}
754
754
755 for iface, fn in REPO_INTERFACES:
755 for iface, fn in REPO_INTERFACES:
756 # We pass all potentially useful state to give extensions tons of
756 # We pass all potentially useful state to give extensions tons of
757 # flexibility.
757 # flexibility.
758 typ = fn()(
758 typ = fn()(
759 ui=ui,
759 ui=ui,
760 intents=intents,
760 intents=intents,
761 requirements=requirements,
761 requirements=requirements,
762 features=features,
762 features=features,
763 wdirvfs=wdirvfs,
763 wdirvfs=wdirvfs,
764 hgvfs=hgvfs,
764 hgvfs=hgvfs,
765 store=store,
765 store=store,
766 storevfs=storevfs,
766 storevfs=storevfs,
767 storeoptions=storevfs.options,
767 storeoptions=storevfs.options,
768 cachevfs=cachevfs,
768 cachevfs=cachevfs,
769 wcachevfs=wcachevfs,
769 wcachevfs=wcachevfs,
770 extensionmodulenames=extensionmodulenames,
770 extensionmodulenames=extensionmodulenames,
771 extrastate=extrastate,
771 extrastate=extrastate,
772 baseclasses=bases,
772 baseclasses=bases,
773 )
773 )
774
774
775 if not isinstance(typ, type):
775 if not isinstance(typ, type):
776 raise error.ProgrammingError(
776 raise error.ProgrammingError(
777 b'unable to construct type for %s' % iface
777 b'unable to construct type for %s' % iface
778 )
778 )
779
779
780 bases.append(typ)
780 bases.append(typ)
781
781
782 # type() allows you to use characters in type names that wouldn't be
782 # type() allows you to use characters in type names that wouldn't be
783 # recognized as Python symbols in source code. We abuse that to add
783 # recognized as Python symbols in source code. We abuse that to add
784 # rich information about our constructed repo.
784 # rich information about our constructed repo.
785 name = pycompat.sysstr(
785 name = pycompat.sysstr(
786 b'derivedrepo:%s<%s>' % (wdirvfs.base, b','.join(sorted(requirements)))
786 b'derivedrepo:%s<%s>' % (wdirvfs.base, b','.join(sorted(requirements)))
787 )
787 )
788
788
789 cls = type(name, tuple(bases), {})
789 cls = type(name, tuple(bases), {})
790
790
791 return cls(
791 return cls(
792 baseui=baseui,
792 baseui=baseui,
793 ui=ui,
793 ui=ui,
794 origroot=path,
794 origroot=path,
795 wdirvfs=wdirvfs,
795 wdirvfs=wdirvfs,
796 hgvfs=hgvfs,
796 hgvfs=hgvfs,
797 requirements=requirements,
797 requirements=requirements,
798 supportedrequirements=supportedrequirements,
798 supportedrequirements=supportedrequirements,
799 sharedpath=storebasepath,
799 sharedpath=storebasepath,
800 store=store,
800 store=store,
801 cachevfs=cachevfs,
801 cachevfs=cachevfs,
802 wcachevfs=wcachevfs,
802 wcachevfs=wcachevfs,
803 features=features,
803 features=features,
804 intents=intents,
804 intents=intents,
805 )
805 )
806
806
807
807
808 def loadhgrc(ui, wdirvfs, hgvfs, requirements, sharedvfs=None):
808 def loadhgrc(ui, wdirvfs, hgvfs, requirements, sharedvfs=None):
809 """Load hgrc files/content into a ui instance.
809 """Load hgrc files/content into a ui instance.
810
810
811 This is called during repository opening to load any additional
811 This is called during repository opening to load any additional
812 config files or settings relevant to the current repository.
812 config files or settings relevant to the current repository.
813
813
814 Returns a bool indicating whether any additional configs were loaded.
814 Returns a bool indicating whether any additional configs were loaded.
815
815
816 Extensions should monkeypatch this function to modify how per-repo
816 Extensions should monkeypatch this function to modify how per-repo
817 configs are loaded. For example, an extension may wish to pull in
817 configs are loaded. For example, an extension may wish to pull in
818 configs from alternate files or sources.
818 configs from alternate files or sources.
819
819
820 sharedvfs is vfs object pointing to source repo if the current one is a
820 sharedvfs is vfs object pointing to source repo if the current one is a
821 shared one
821 shared one
822 """
822 """
823 if not rcutil.use_repo_hgrc():
823 if not rcutil.use_repo_hgrc():
824 return False
824 return False
825
825
826 ret = False
826 ret = False
827 # first load config from shared source if we has to
827 # first load config from shared source if we has to
828 if requirementsmod.SHARESAFE_REQUIREMENT in requirements and sharedvfs:
828 if requirementsmod.SHARESAFE_REQUIREMENT in requirements and sharedvfs:
829 try:
829 try:
830 ui.readconfig(sharedvfs.join(b'hgrc'), root=sharedvfs.base)
830 ui.readconfig(sharedvfs.join(b'hgrc'), root=sharedvfs.base)
831 ret = True
831 ret = True
832 except IOError:
832 except IOError:
833 pass
833 pass
834
834
835 try:
835 try:
836 ui.readconfig(hgvfs.join(b'hgrc'), root=wdirvfs.base)
836 ui.readconfig(hgvfs.join(b'hgrc'), root=wdirvfs.base)
837 ret = True
837 ret = True
838 except IOError:
838 except IOError:
839 pass
839 pass
840
840
841 try:
841 try:
842 ui.readconfig(hgvfs.join(b'hgrc-not-shared'), root=wdirvfs.base)
842 ui.readconfig(hgvfs.join(b'hgrc-not-shared'), root=wdirvfs.base)
843 ret = True
843 ret = True
844 except IOError:
844 except IOError:
845 pass
845 pass
846
846
847 return ret
847 return ret
848
848
849
849
850 def afterhgrcload(ui, wdirvfs, hgvfs, requirements):
850 def afterhgrcload(ui, wdirvfs, hgvfs, requirements):
851 """Perform additional actions after .hg/hgrc is loaded.
851 """Perform additional actions after .hg/hgrc is loaded.
852
852
853 This function is called during repository loading immediately after
853 This function is called during repository loading immediately after
854 the .hg/hgrc file is loaded and before per-repo extensions are loaded.
854 the .hg/hgrc file is loaded and before per-repo extensions are loaded.
855
855
856 The function can be used to validate configs, automatically add
856 The function can be used to validate configs, automatically add
857 options (including extensions) based on requirements, etc.
857 options (including extensions) based on requirements, etc.
858 """
858 """
859
859
860 # Map of requirements to list of extensions to load automatically when
860 # Map of requirements to list of extensions to load automatically when
861 # requirement is present.
861 # requirement is present.
862 autoextensions = {
862 autoextensions = {
863 b'git': [b'git'],
863 b'git': [b'git'],
864 b'largefiles': [b'largefiles'],
864 b'largefiles': [b'largefiles'],
865 b'lfs': [b'lfs'],
865 b'lfs': [b'lfs'],
866 }
866 }
867
867
868 for requirement, names in sorted(autoextensions.items()):
868 for requirement, names in sorted(autoextensions.items()):
869 if requirement not in requirements:
869 if requirement not in requirements:
870 continue
870 continue
871
871
872 for name in names:
872 for name in names:
873 if not ui.hasconfig(b'extensions', name):
873 if not ui.hasconfig(b'extensions', name):
874 ui.setconfig(b'extensions', name, b'', source=b'autoload')
874 ui.setconfig(b'extensions', name, b'', source=b'autoload')
875
875
876
876
877 def gathersupportedrequirements(ui):
877 def gathersupportedrequirements(ui):
878 """Determine the complete set of recognized requirements."""
878 """Determine the complete set of recognized requirements."""
879 # Start with all requirements supported by this file.
879 # Start with all requirements supported by this file.
880 supported = set(localrepository._basesupported)
880 supported = set(localrepository._basesupported)
881
881
882 # Execute ``featuresetupfuncs`` entries if they belong to an extension
882 # Execute ``featuresetupfuncs`` entries if they belong to an extension
883 # relevant to this ui instance.
883 # relevant to this ui instance.
884 modules = {m.__name__ for n, m in extensions.extensions(ui)}
884 modules = {m.__name__ for n, m in extensions.extensions(ui)}
885
885
886 for fn in featuresetupfuncs:
886 for fn in featuresetupfuncs:
887 if fn.__module__ in modules:
887 if fn.__module__ in modules:
888 fn(ui, supported)
888 fn(ui, supported)
889
889
890 # Add derived requirements from registered compression engines.
890 # Add derived requirements from registered compression engines.
891 for name in util.compengines:
891 for name in util.compengines:
892 engine = util.compengines[name]
892 engine = util.compengines[name]
893 if engine.available() and engine.revlogheader():
893 if engine.available() and engine.revlogheader():
894 supported.add(b'exp-compression-%s' % name)
894 supported.add(b'exp-compression-%s' % name)
895 if engine.name() == b'zstd':
895 if engine.name() == b'zstd':
896 supported.add(b'revlog-compression-zstd')
896 supported.add(b'revlog-compression-zstd')
897
897
898 return supported
898 return supported
899
899
900
900
901 def ensurerequirementsrecognized(requirements, supported):
901 def ensurerequirementsrecognized(requirements, supported):
902 """Validate that a set of local requirements is recognized.
902 """Validate that a set of local requirements is recognized.
903
903
904 Receives a set of requirements. Raises an ``error.RepoError`` if there
904 Receives a set of requirements. Raises an ``error.RepoError`` if there
905 exists any requirement in that set that currently loaded code doesn't
905 exists any requirement in that set that currently loaded code doesn't
906 recognize.
906 recognize.
907
907
908 Returns a set of supported requirements.
908 Returns a set of supported requirements.
909 """
909 """
910 missing = set()
910 missing = set()
911
911
912 for requirement in requirements:
912 for requirement in requirements:
913 if requirement in supported:
913 if requirement in supported:
914 continue
914 continue
915
915
916 if not requirement or not requirement[0:1].isalnum():
916 if not requirement or not requirement[0:1].isalnum():
917 raise error.RequirementError(_(b'.hg/requires file is corrupt'))
917 raise error.RequirementError(_(b'.hg/requires file is corrupt'))
918
918
919 missing.add(requirement)
919 missing.add(requirement)
920
920
921 if missing:
921 if missing:
922 raise error.RequirementError(
922 raise error.RequirementError(
923 _(b'repository requires features unknown to this Mercurial: %s')
923 _(b'repository requires features unknown to this Mercurial: %s')
924 % b' '.join(sorted(missing)),
924 % b' '.join(sorted(missing)),
925 hint=_(
925 hint=_(
926 b'see https://mercurial-scm.org/wiki/MissingRequirement '
926 b'see https://mercurial-scm.org/wiki/MissingRequirement '
927 b'for more information'
927 b'for more information'
928 ),
928 ),
929 )
929 )
930
930
931
931
932 def ensurerequirementscompatible(ui, requirements):
932 def ensurerequirementscompatible(ui, requirements):
933 """Validates that a set of recognized requirements is mutually compatible.
933 """Validates that a set of recognized requirements is mutually compatible.
934
934
935 Some requirements may not be compatible with others or require
935 Some requirements may not be compatible with others or require
936 config options that aren't enabled. This function is called during
936 config options that aren't enabled. This function is called during
937 repository opening to ensure that the set of requirements needed
937 repository opening to ensure that the set of requirements needed
938 to open a repository is sane and compatible with config options.
938 to open a repository is sane and compatible with config options.
939
939
940 Extensions can monkeypatch this function to perform additional
940 Extensions can monkeypatch this function to perform additional
941 checking.
941 checking.
942
942
943 ``error.RepoError`` should be raised on failure.
943 ``error.RepoError`` should be raised on failure.
944 """
944 """
945 if (
945 if (
946 requirementsmod.SPARSE_REQUIREMENT in requirements
946 requirementsmod.SPARSE_REQUIREMENT in requirements
947 and not sparse.enabled
947 and not sparse.enabled
948 ):
948 ):
949 raise error.RepoError(
949 raise error.RepoError(
950 _(
950 _(
951 b'repository is using sparse feature but '
951 b'repository is using sparse feature but '
952 b'sparse is not enabled; enable the '
952 b'sparse is not enabled; enable the '
953 b'"sparse" extensions to access'
953 b'"sparse" extensions to access'
954 )
954 )
955 )
955 )
956
956
957
957
958 def makestore(requirements, path, vfstype):
958 def makestore(requirements, path, vfstype):
959 """Construct a storage object for a repository."""
959 """Construct a storage object for a repository."""
960 if requirementsmod.STORE_REQUIREMENT in requirements:
960 if requirementsmod.STORE_REQUIREMENT in requirements:
961 if requirementsmod.FNCACHE_REQUIREMENT in requirements:
961 if requirementsmod.FNCACHE_REQUIREMENT in requirements:
962 dotencode = requirementsmod.DOTENCODE_REQUIREMENT in requirements
962 dotencode = requirementsmod.DOTENCODE_REQUIREMENT in requirements
963 return storemod.fncachestore(path, vfstype, dotencode)
963 return storemod.fncachestore(path, vfstype, dotencode)
964
964
965 return storemod.encodedstore(path, vfstype)
965 return storemod.encodedstore(path, vfstype)
966
966
967 return storemod.basicstore(path, vfstype)
967 return storemod.basicstore(path, vfstype)
968
968
969
969
970 def resolvestorevfsoptions(ui, requirements, features):
970 def resolvestorevfsoptions(ui, requirements, features):
971 """Resolve the options to pass to the store vfs opener.
971 """Resolve the options to pass to the store vfs opener.
972
972
973 The returned dict is used to influence behavior of the storage layer.
973 The returned dict is used to influence behavior of the storage layer.
974 """
974 """
975 options = {}
975 options = {}
976
976
977 if requirementsmod.TREEMANIFEST_REQUIREMENT in requirements:
977 if requirementsmod.TREEMANIFEST_REQUIREMENT in requirements:
978 options[b'treemanifest'] = True
978 options[b'treemanifest'] = True
979
979
980 # experimental config: format.manifestcachesize
980 # experimental config: format.manifestcachesize
981 manifestcachesize = ui.configint(b'format', b'manifestcachesize')
981 manifestcachesize = ui.configint(b'format', b'manifestcachesize')
982 if manifestcachesize is not None:
982 if manifestcachesize is not None:
983 options[b'manifestcachesize'] = manifestcachesize
983 options[b'manifestcachesize'] = manifestcachesize
984
984
985 # In the absence of another requirement superseding a revlog-related
985 # In the absence of another requirement superseding a revlog-related
986 # requirement, we have to assume the repo is using revlog version 0.
986 # requirement, we have to assume the repo is using revlog version 0.
987 # This revlog format is super old and we don't bother trying to parse
987 # This revlog format is super old and we don't bother trying to parse
988 # opener options for it because those options wouldn't do anything
988 # opener options for it because those options wouldn't do anything
989 # meaningful on such old repos.
989 # meaningful on such old repos.
990 if (
990 if (
991 requirementsmod.REVLOGV1_REQUIREMENT in requirements
991 requirementsmod.REVLOGV1_REQUIREMENT in requirements
992 or requirementsmod.REVLOGV2_REQUIREMENT in requirements
992 or requirementsmod.REVLOGV2_REQUIREMENT in requirements
993 ):
993 ):
994 options.update(resolverevlogstorevfsoptions(ui, requirements, features))
994 options.update(resolverevlogstorevfsoptions(ui, requirements, features))
995 else: # explicitly mark repo as using revlogv0
995 else: # explicitly mark repo as using revlogv0
996 options[b'revlogv0'] = True
996 options[b'revlogv0'] = True
997
997
998 if requirementsmod.COPIESSDC_REQUIREMENT in requirements:
998 if requirementsmod.COPIESSDC_REQUIREMENT in requirements:
999 options[b'copies-storage'] = b'changeset-sidedata'
999 options[b'copies-storage'] = b'changeset-sidedata'
1000 else:
1000 else:
1001 writecopiesto = ui.config(b'experimental', b'copies.write-to')
1001 writecopiesto = ui.config(b'experimental', b'copies.write-to')
1002 copiesextramode = (b'changeset-only', b'compatibility')
1002 copiesextramode = (b'changeset-only', b'compatibility')
1003 if writecopiesto in copiesextramode:
1003 if writecopiesto in copiesextramode:
1004 options[b'copies-storage'] = b'extra'
1004 options[b'copies-storage'] = b'extra'
1005
1005
1006 return options
1006 return options
1007
1007
1008
1008
1009 def resolverevlogstorevfsoptions(ui, requirements, features):
1009 def resolverevlogstorevfsoptions(ui, requirements, features):
1010 """Resolve opener options specific to revlogs."""
1010 """Resolve opener options specific to revlogs."""
1011
1011
1012 options = {}
1012 options = {}
1013 options[b'flagprocessors'] = {}
1013 options[b'flagprocessors'] = {}
1014
1014
1015 if requirementsmod.REVLOGV1_REQUIREMENT in requirements:
1015 if requirementsmod.REVLOGV1_REQUIREMENT in requirements:
1016 options[b'revlogv1'] = True
1016 options[b'revlogv1'] = True
1017 if requirementsmod.REVLOGV2_REQUIREMENT in requirements:
1017 if requirementsmod.REVLOGV2_REQUIREMENT in requirements:
1018 options[b'revlogv2'] = True
1018 options[b'revlogv2'] = True
1019
1019
1020 if requirementsmod.GENERALDELTA_REQUIREMENT in requirements:
1020 if requirementsmod.GENERALDELTA_REQUIREMENT in requirements:
1021 options[b'generaldelta'] = True
1021 options[b'generaldelta'] = True
1022
1022
1023 # experimental config: format.chunkcachesize
1023 # experimental config: format.chunkcachesize
1024 chunkcachesize = ui.configint(b'format', b'chunkcachesize')
1024 chunkcachesize = ui.configint(b'format', b'chunkcachesize')
1025 if chunkcachesize is not None:
1025 if chunkcachesize is not None:
1026 options[b'chunkcachesize'] = chunkcachesize
1026 options[b'chunkcachesize'] = chunkcachesize
1027
1027
1028 deltabothparents = ui.configbool(
1028 deltabothparents = ui.configbool(
1029 b'storage', b'revlog.optimize-delta-parent-choice'
1029 b'storage', b'revlog.optimize-delta-parent-choice'
1030 )
1030 )
1031 options[b'deltabothparents'] = deltabothparents
1031 options[b'deltabothparents'] = deltabothparents
1032
1032
1033 lazydelta = ui.configbool(b'storage', b'revlog.reuse-external-delta')
1033 lazydelta = ui.configbool(b'storage', b'revlog.reuse-external-delta')
1034 lazydeltabase = False
1034 lazydeltabase = False
1035 if lazydelta:
1035 if lazydelta:
1036 lazydeltabase = ui.configbool(
1036 lazydeltabase = ui.configbool(
1037 b'storage', b'revlog.reuse-external-delta-parent'
1037 b'storage', b'revlog.reuse-external-delta-parent'
1038 )
1038 )
1039 if lazydeltabase is None:
1039 if lazydeltabase is None:
1040 lazydeltabase = not scmutil.gddeltaconfig(ui)
1040 lazydeltabase = not scmutil.gddeltaconfig(ui)
1041 options[b'lazydelta'] = lazydelta
1041 options[b'lazydelta'] = lazydelta
1042 options[b'lazydeltabase'] = lazydeltabase
1042 options[b'lazydeltabase'] = lazydeltabase
1043
1043
1044 chainspan = ui.configbytes(b'experimental', b'maxdeltachainspan')
1044 chainspan = ui.configbytes(b'experimental', b'maxdeltachainspan')
1045 if 0 <= chainspan:
1045 if 0 <= chainspan:
1046 options[b'maxdeltachainspan'] = chainspan
1046 options[b'maxdeltachainspan'] = chainspan
1047
1047
1048 mmapindexthreshold = ui.configbytes(b'experimental', b'mmapindexthreshold')
1048 mmapindexthreshold = ui.configbytes(b'experimental', b'mmapindexthreshold')
1049 if mmapindexthreshold is not None:
1049 if mmapindexthreshold is not None:
1050 options[b'mmapindexthreshold'] = mmapindexthreshold
1050 options[b'mmapindexthreshold'] = mmapindexthreshold
1051
1051
1052 withsparseread = ui.configbool(b'experimental', b'sparse-read')
1052 withsparseread = ui.configbool(b'experimental', b'sparse-read')
1053 srdensitythres = float(
1053 srdensitythres = float(
1054 ui.config(b'experimental', b'sparse-read.density-threshold')
1054 ui.config(b'experimental', b'sparse-read.density-threshold')
1055 )
1055 )
1056 srmingapsize = ui.configbytes(b'experimental', b'sparse-read.min-gap-size')
1056 srmingapsize = ui.configbytes(b'experimental', b'sparse-read.min-gap-size')
1057 options[b'with-sparse-read'] = withsparseread
1057 options[b'with-sparse-read'] = withsparseread
1058 options[b'sparse-read-density-threshold'] = srdensitythres
1058 options[b'sparse-read-density-threshold'] = srdensitythres
1059 options[b'sparse-read-min-gap-size'] = srmingapsize
1059 options[b'sparse-read-min-gap-size'] = srmingapsize
1060
1060
1061 sparserevlog = requirementsmod.SPARSEREVLOG_REQUIREMENT in requirements
1061 sparserevlog = requirementsmod.SPARSEREVLOG_REQUIREMENT in requirements
1062 options[b'sparse-revlog'] = sparserevlog
1062 options[b'sparse-revlog'] = sparserevlog
1063 if sparserevlog:
1063 if sparserevlog:
1064 options[b'generaldelta'] = True
1064 options[b'generaldelta'] = True
1065
1065
1066 sidedata = requirementsmod.SIDEDATA_REQUIREMENT in requirements
1066 sidedata = requirementsmod.SIDEDATA_REQUIREMENT in requirements
1067 options[b'side-data'] = sidedata
1067 options[b'side-data'] = sidedata
1068
1068
1069 maxchainlen = None
1069 maxchainlen = None
1070 if sparserevlog:
1070 if sparserevlog:
1071 maxchainlen = revlogconst.SPARSE_REVLOG_MAX_CHAIN_LENGTH
1071 maxchainlen = revlogconst.SPARSE_REVLOG_MAX_CHAIN_LENGTH
1072 # experimental config: format.maxchainlen
1072 # experimental config: format.maxchainlen
1073 maxchainlen = ui.configint(b'format', b'maxchainlen', maxchainlen)
1073 maxchainlen = ui.configint(b'format', b'maxchainlen', maxchainlen)
1074 if maxchainlen is not None:
1074 if maxchainlen is not None:
1075 options[b'maxchainlen'] = maxchainlen
1075 options[b'maxchainlen'] = maxchainlen
1076
1076
1077 for r in requirements:
1077 for r in requirements:
1078 # we allow multiple compression engine requirement to co-exist because
1078 # we allow multiple compression engine requirement to co-exist because
1079 # strickly speaking, revlog seems to support mixed compression style.
1079 # strickly speaking, revlog seems to support mixed compression style.
1080 #
1080 #
1081 # The compression used for new entries will be "the last one"
1081 # The compression used for new entries will be "the last one"
1082 prefix = r.startswith
1082 prefix = r.startswith
1083 if prefix(b'revlog-compression-') or prefix(b'exp-compression-'):
1083 if prefix(b'revlog-compression-') or prefix(b'exp-compression-'):
1084 options[b'compengine'] = r.split(b'-', 2)[2]
1084 options[b'compengine'] = r.split(b'-', 2)[2]
1085
1085
1086 options[b'zlib.level'] = ui.configint(b'storage', b'revlog.zlib.level')
1086 options[b'zlib.level'] = ui.configint(b'storage', b'revlog.zlib.level')
1087 if options[b'zlib.level'] is not None:
1087 if options[b'zlib.level'] is not None:
1088 if not (0 <= options[b'zlib.level'] <= 9):
1088 if not (0 <= options[b'zlib.level'] <= 9):
1089 msg = _(b'invalid value for `storage.revlog.zlib.level` config: %d')
1089 msg = _(b'invalid value for `storage.revlog.zlib.level` config: %d')
1090 raise error.Abort(msg % options[b'zlib.level'])
1090 raise error.Abort(msg % options[b'zlib.level'])
1091 options[b'zstd.level'] = ui.configint(b'storage', b'revlog.zstd.level')
1091 options[b'zstd.level'] = ui.configint(b'storage', b'revlog.zstd.level')
1092 if options[b'zstd.level'] is not None:
1092 if options[b'zstd.level'] is not None:
1093 if not (0 <= options[b'zstd.level'] <= 22):
1093 if not (0 <= options[b'zstd.level'] <= 22):
1094 msg = _(b'invalid value for `storage.revlog.zstd.level` config: %d')
1094 msg = _(b'invalid value for `storage.revlog.zstd.level` config: %d')
1095 raise error.Abort(msg % options[b'zstd.level'])
1095 raise error.Abort(msg % options[b'zstd.level'])
1096
1096
1097 if requirementsmod.NARROW_REQUIREMENT in requirements:
1097 if requirementsmod.NARROW_REQUIREMENT in requirements:
1098 options[b'enableellipsis'] = True
1098 options[b'enableellipsis'] = True
1099
1099
1100 if ui.configbool(b'experimental', b'rust.index'):
1100 if ui.configbool(b'experimental', b'rust.index'):
1101 options[b'rust.index'] = True
1101 options[b'rust.index'] = True
1102 if requirementsmod.NODEMAP_REQUIREMENT in requirements:
1102 if requirementsmod.NODEMAP_REQUIREMENT in requirements:
1103 slow_path = ui.config(
1103 slow_path = ui.config(
1104 b'storage', b'revlog.persistent-nodemap.slow-path'
1104 b'storage', b'revlog.persistent-nodemap.slow-path'
1105 )
1105 )
1106 if slow_path not in (b'allow', b'warn', b'abort'):
1106 if slow_path not in (b'allow', b'warn', b'abort'):
1107 default = ui.config_default(
1107 default = ui.config_default(
1108 b'storage', b'revlog.persistent-nodemap.slow-path'
1108 b'storage', b'revlog.persistent-nodemap.slow-path'
1109 )
1109 )
1110 msg = _(
1110 msg = _(
1111 b'unknown value for config '
1111 b'unknown value for config '
1112 b'"storage.revlog.persistent-nodemap.slow-path": "%s"\n'
1112 b'"storage.revlog.persistent-nodemap.slow-path": "%s"\n'
1113 )
1113 )
1114 ui.warn(msg % slow_path)
1114 ui.warn(msg % slow_path)
1115 if not ui.quiet:
1115 if not ui.quiet:
1116 ui.warn(_(b'falling back to default value: %s\n') % default)
1116 ui.warn(_(b'falling back to default value: %s\n') % default)
1117 slow_path = default
1117 slow_path = default
1118
1118
1119 msg = _(
1119 msg = _(
1120 b"accessing `persistent-nodemap` repository without associated "
1120 b"accessing `persistent-nodemap` repository without associated "
1121 b"fast implementation."
1121 b"fast implementation."
1122 )
1122 )
1123 hint = _(
1123 hint = _(
1124 b"check `hg help config.format.use-persistent-nodemap` "
1124 b"check `hg help config.format.use-persistent-nodemap` "
1125 b"for details"
1125 b"for details"
1126 )
1126 )
1127 if not revlog.HAS_FAST_PERSISTENT_NODEMAP:
1127 if not revlog.HAS_FAST_PERSISTENT_NODEMAP:
1128 if slow_path == b'warn':
1128 if slow_path == b'warn':
1129 msg = b"warning: " + msg + b'\n'
1129 msg = b"warning: " + msg + b'\n'
1130 ui.warn(msg)
1130 ui.warn(msg)
1131 if not ui.quiet:
1131 if not ui.quiet:
1132 hint = b'(' + hint + b')\n'
1132 hint = b'(' + hint + b')\n'
1133 ui.warn(hint)
1133 ui.warn(hint)
1134 if slow_path == b'abort':
1134 if slow_path == b'abort':
1135 raise error.Abort(msg, hint=hint)
1135 raise error.Abort(msg, hint=hint)
1136 options[b'persistent-nodemap'] = True
1136 options[b'persistent-nodemap'] = True
1137 if ui.configbool(b'storage', b'revlog.persistent-nodemap.mmap'):
1137 if ui.configbool(b'storage', b'revlog.persistent-nodemap.mmap'):
1138 options[b'persistent-nodemap.mmap'] = True
1138 options[b'persistent-nodemap.mmap'] = True
1139 if ui.configbool(b'devel', b'persistent-nodemap'):
1139 if ui.configbool(b'devel', b'persistent-nodemap'):
1140 options[b'devel-force-nodemap'] = True
1140 options[b'devel-force-nodemap'] = True
1141
1141
1142 return options
1142 return options
1143
1143
1144
1144
1145 def makemain(**kwargs):
1145 def makemain(**kwargs):
1146 """Produce a type conforming to ``ilocalrepositorymain``."""
1146 """Produce a type conforming to ``ilocalrepositorymain``."""
1147 return localrepository
1147 return localrepository
1148
1148
1149
1149
1150 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
1150 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
1151 class revlogfilestorage(object):
1151 class revlogfilestorage(object):
1152 """File storage when using revlogs."""
1152 """File storage when using revlogs."""
1153
1153
1154 def file(self, path):
1154 def file(self, path):
1155 if path.startswith(b'/'):
1155 if path.startswith(b'/'):
1156 path = path[1:]
1156 path = path[1:]
1157
1157
1158 return filelog.filelog(self.svfs, path)
1158 return filelog.filelog(self.svfs, path)
1159
1159
1160
1160
1161 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
1161 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
1162 class revlognarrowfilestorage(object):
1162 class revlognarrowfilestorage(object):
1163 """File storage when using revlogs and narrow files."""
1163 """File storage when using revlogs and narrow files."""
1164
1164
1165 def file(self, path):
1165 def file(self, path):
1166 if path.startswith(b'/'):
1166 if path.startswith(b'/'):
1167 path = path[1:]
1167 path = path[1:]
1168
1168
1169 return filelog.narrowfilelog(self.svfs, path, self._storenarrowmatch)
1169 return filelog.narrowfilelog(self.svfs, path, self._storenarrowmatch)
1170
1170
1171
1171
1172 def makefilestorage(requirements, features, **kwargs):
1172 def makefilestorage(requirements, features, **kwargs):
1173 """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
1173 """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
1174 features.add(repository.REPO_FEATURE_REVLOG_FILE_STORAGE)
1174 features.add(repository.REPO_FEATURE_REVLOG_FILE_STORAGE)
1175 features.add(repository.REPO_FEATURE_STREAM_CLONE)
1175 features.add(repository.REPO_FEATURE_STREAM_CLONE)
1176
1176
1177 if requirementsmod.NARROW_REQUIREMENT in requirements:
1177 if requirementsmod.NARROW_REQUIREMENT in requirements:
1178 return revlognarrowfilestorage
1178 return revlognarrowfilestorage
1179 else:
1179 else:
1180 return revlogfilestorage
1180 return revlogfilestorage
1181
1181
1182
1182
1183 # List of repository interfaces and factory functions for them. Each
1183 # List of repository interfaces and factory functions for them. Each
1184 # will be called in order during ``makelocalrepository()`` to iteratively
1184 # will be called in order during ``makelocalrepository()`` to iteratively
1185 # derive the final type for a local repository instance. We capture the
1185 # derive the final type for a local repository instance. We capture the
1186 # function as a lambda so we don't hold a reference and the module-level
1186 # function as a lambda so we don't hold a reference and the module-level
1187 # functions can be wrapped.
1187 # functions can be wrapped.
1188 REPO_INTERFACES = [
1188 REPO_INTERFACES = [
1189 (repository.ilocalrepositorymain, lambda: makemain),
1189 (repository.ilocalrepositorymain, lambda: makemain),
1190 (repository.ilocalrepositoryfilestorage, lambda: makefilestorage),
1190 (repository.ilocalrepositoryfilestorage, lambda: makefilestorage),
1191 ]
1191 ]
1192
1192
1193
1193
1194 @interfaceutil.implementer(repository.ilocalrepositorymain)
1194 @interfaceutil.implementer(repository.ilocalrepositorymain)
1195 class localrepository(object):
1195 class localrepository(object):
1196 """Main class for representing local repositories.
1196 """Main class for representing local repositories.
1197
1197
1198 All local repositories are instances of this class.
1198 All local repositories are instances of this class.
1199
1199
1200 Constructed on its own, instances of this class are not usable as
1200 Constructed on its own, instances of this class are not usable as
1201 repository objects. To obtain a usable repository object, call
1201 repository objects. To obtain a usable repository object, call
1202 ``hg.repository()``, ``localrepo.instance()``, or
1202 ``hg.repository()``, ``localrepo.instance()``, or
1203 ``localrepo.makelocalrepository()``. The latter is the lowest-level.
1203 ``localrepo.makelocalrepository()``. The latter is the lowest-level.
1204 ``instance()`` adds support for creating new repositories.
1204 ``instance()`` adds support for creating new repositories.
1205 ``hg.repository()`` adds more extension integration, including calling
1205 ``hg.repository()`` adds more extension integration, including calling
1206 ``reposetup()``. Generally speaking, ``hg.repository()`` should be
1206 ``reposetup()``. Generally speaking, ``hg.repository()`` should be
1207 used.
1207 used.
1208 """
1208 """
1209
1209
1210 # obsolete experimental requirements:
1210 # obsolete experimental requirements:
1211 # - manifestv2: An experimental new manifest format that allowed
1211 # - manifestv2: An experimental new manifest format that allowed
1212 # for stem compression of long paths. Experiment ended up not
1212 # for stem compression of long paths. Experiment ended up not
1213 # being successful (repository sizes went up due to worse delta
1213 # being successful (repository sizes went up due to worse delta
1214 # chains), and the code was deleted in 4.6.
1214 # chains), and the code was deleted in 4.6.
1215 supportedformats = {
1215 supportedformats = {
1216 requirementsmod.REVLOGV1_REQUIREMENT,
1216 requirementsmod.REVLOGV1_REQUIREMENT,
1217 requirementsmod.GENERALDELTA_REQUIREMENT,
1217 requirementsmod.GENERALDELTA_REQUIREMENT,
1218 requirementsmod.TREEMANIFEST_REQUIREMENT,
1218 requirementsmod.TREEMANIFEST_REQUIREMENT,
1219 requirementsmod.COPIESSDC_REQUIREMENT,
1219 requirementsmod.COPIESSDC_REQUIREMENT,
1220 requirementsmod.REVLOGV2_REQUIREMENT,
1220 requirementsmod.REVLOGV2_REQUIREMENT,
1221 requirementsmod.SIDEDATA_REQUIREMENT,
1221 requirementsmod.SIDEDATA_REQUIREMENT,
1222 requirementsmod.SPARSEREVLOG_REQUIREMENT,
1222 requirementsmod.SPARSEREVLOG_REQUIREMENT,
1223 requirementsmod.NODEMAP_REQUIREMENT,
1223 requirementsmod.NODEMAP_REQUIREMENT,
1224 bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT,
1224 bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT,
1225 requirementsmod.SHARESAFE_REQUIREMENT,
1225 requirementsmod.SHARESAFE_REQUIREMENT,
1226 }
1226 }
1227 _basesupported = supportedformats | {
1227 _basesupported = supportedformats | {
1228 requirementsmod.STORE_REQUIREMENT,
1228 requirementsmod.STORE_REQUIREMENT,
1229 requirementsmod.FNCACHE_REQUIREMENT,
1229 requirementsmod.FNCACHE_REQUIREMENT,
1230 requirementsmod.SHARED_REQUIREMENT,
1230 requirementsmod.SHARED_REQUIREMENT,
1231 requirementsmod.RELATIVE_SHARED_REQUIREMENT,
1231 requirementsmod.RELATIVE_SHARED_REQUIREMENT,
1232 requirementsmod.DOTENCODE_REQUIREMENT,
1232 requirementsmod.DOTENCODE_REQUIREMENT,
1233 requirementsmod.SPARSE_REQUIREMENT,
1233 requirementsmod.SPARSE_REQUIREMENT,
1234 requirementsmod.INTERNAL_PHASE_REQUIREMENT,
1234 requirementsmod.INTERNAL_PHASE_REQUIREMENT,
1235 }
1235 }
1236
1236
1237 # list of prefix for file which can be written without 'wlock'
1237 # list of prefix for file which can be written without 'wlock'
1238 # Extensions should extend this list when needed
1238 # Extensions should extend this list when needed
1239 _wlockfreeprefix = {
1239 _wlockfreeprefix = {
1240 # We migh consider requiring 'wlock' for the next
1240 # We migh consider requiring 'wlock' for the next
1241 # two, but pretty much all the existing code assume
1241 # two, but pretty much all the existing code assume
1242 # wlock is not needed so we keep them excluded for
1242 # wlock is not needed so we keep them excluded for
1243 # now.
1243 # now.
1244 b'hgrc',
1244 b'hgrc',
1245 b'requires',
1245 b'requires',
1246 # XXX cache is a complicatged business someone
1246 # XXX cache is a complicatged business someone
1247 # should investigate this in depth at some point
1247 # should investigate this in depth at some point
1248 b'cache/',
1248 b'cache/',
1249 # XXX shouldn't be dirstate covered by the wlock?
1249 # XXX shouldn't be dirstate covered by the wlock?
1250 b'dirstate',
1250 b'dirstate',
1251 # XXX bisect was still a bit too messy at the time
1251 # XXX bisect was still a bit too messy at the time
1252 # this changeset was introduced. Someone should fix
1252 # this changeset was introduced. Someone should fix
1253 # the remainig bit and drop this line
1253 # the remainig bit and drop this line
1254 b'bisect.state',
1254 b'bisect.state',
1255 }
1255 }
1256
1256
1257 def __init__(
1257 def __init__(
1258 self,
1258 self,
1259 baseui,
1259 baseui,
1260 ui,
1260 ui,
1261 origroot,
1261 origroot,
1262 wdirvfs,
1262 wdirvfs,
1263 hgvfs,
1263 hgvfs,
1264 requirements,
1264 requirements,
1265 supportedrequirements,
1265 supportedrequirements,
1266 sharedpath,
1266 sharedpath,
1267 store,
1267 store,
1268 cachevfs,
1268 cachevfs,
1269 wcachevfs,
1269 wcachevfs,
1270 features,
1270 features,
1271 intents=None,
1271 intents=None,
1272 ):
1272 ):
1273 """Create a new local repository instance.
1273 """Create a new local repository instance.
1274
1274
1275 Most callers should use ``hg.repository()``, ``localrepo.instance()``,
1275 Most callers should use ``hg.repository()``, ``localrepo.instance()``,
1276 or ``localrepo.makelocalrepository()`` for obtaining a new repository
1276 or ``localrepo.makelocalrepository()`` for obtaining a new repository
1277 object.
1277 object.
1278
1278
1279 Arguments:
1279 Arguments:
1280
1280
1281 baseui
1281 baseui
1282 ``ui.ui`` instance that ``ui`` argument was based off of.
1282 ``ui.ui`` instance that ``ui`` argument was based off of.
1283
1283
1284 ui
1284 ui
1285 ``ui.ui`` instance for use by the repository.
1285 ``ui.ui`` instance for use by the repository.
1286
1286
1287 origroot
1287 origroot
1288 ``bytes`` path to working directory root of this repository.
1288 ``bytes`` path to working directory root of this repository.
1289
1289
1290 wdirvfs
1290 wdirvfs
1291 ``vfs.vfs`` rooted at the working directory.
1291 ``vfs.vfs`` rooted at the working directory.
1292
1292
1293 hgvfs
1293 hgvfs
1294 ``vfs.vfs`` rooted at .hg/
1294 ``vfs.vfs`` rooted at .hg/
1295
1295
1296 requirements
1296 requirements
1297 ``set`` of bytestrings representing repository opening requirements.
1297 ``set`` of bytestrings representing repository opening requirements.
1298
1298
1299 supportedrequirements
1299 supportedrequirements
1300 ``set`` of bytestrings representing repository requirements that we
1300 ``set`` of bytestrings representing repository requirements that we
1301 know how to open. May be a supetset of ``requirements``.
1301 know how to open. May be a supetset of ``requirements``.
1302
1302
1303 sharedpath
1303 sharedpath
1304 ``bytes`` Defining path to storage base directory. Points to a
1304 ``bytes`` Defining path to storage base directory. Points to a
1305 ``.hg/`` directory somewhere.
1305 ``.hg/`` directory somewhere.
1306
1306
1307 store
1307 store
1308 ``store.basicstore`` (or derived) instance providing access to
1308 ``store.basicstore`` (or derived) instance providing access to
1309 versioned storage.
1309 versioned storage.
1310
1310
1311 cachevfs
1311 cachevfs
1312 ``vfs.vfs`` used for cache files.
1312 ``vfs.vfs`` used for cache files.
1313
1313
1314 wcachevfs
1314 wcachevfs
1315 ``vfs.vfs`` used for cache files related to the working copy.
1315 ``vfs.vfs`` used for cache files related to the working copy.
1316
1316
1317 features
1317 features
1318 ``set`` of bytestrings defining features/capabilities of this
1318 ``set`` of bytestrings defining features/capabilities of this
1319 instance.
1319 instance.
1320
1320
1321 intents
1321 intents
1322 ``set`` of system strings indicating what this repo will be used
1322 ``set`` of system strings indicating what this repo will be used
1323 for.
1323 for.
1324 """
1324 """
1325 self.baseui = baseui
1325 self.baseui = baseui
1326 self.ui = ui
1326 self.ui = ui
1327 self.origroot = origroot
1327 self.origroot = origroot
1328 # vfs rooted at working directory.
1328 # vfs rooted at working directory.
1329 self.wvfs = wdirvfs
1329 self.wvfs = wdirvfs
1330 self.root = wdirvfs.base
1330 self.root = wdirvfs.base
1331 # vfs rooted at .hg/. Used to access most non-store paths.
1331 # vfs rooted at .hg/. Used to access most non-store paths.
1332 self.vfs = hgvfs
1332 self.vfs = hgvfs
1333 self.path = hgvfs.base
1333 self.path = hgvfs.base
1334 self.requirements = requirements
1334 self.requirements = requirements
1335 self.nodeconstants = sha1nodeconstants
1335 self.nodeconstants = sha1nodeconstants
1336 self.nullid = self.nodeconstants.nullid
1336 self.nullid = self.nodeconstants.nullid
1337 self.supported = supportedrequirements
1337 self.supported = supportedrequirements
1338 self.sharedpath = sharedpath
1338 self.sharedpath = sharedpath
1339 self.store = store
1339 self.store = store
1340 self.cachevfs = cachevfs
1340 self.cachevfs = cachevfs
1341 self.wcachevfs = wcachevfs
1341 self.wcachevfs = wcachevfs
1342 self.features = features
1342 self.features = features
1343
1343
1344 self.filtername = None
1344 self.filtername = None
1345
1345
1346 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1346 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1347 b'devel', b'check-locks'
1347 b'devel', b'check-locks'
1348 ):
1348 ):
1349 self.vfs.audit = self._getvfsward(self.vfs.audit)
1349 self.vfs.audit = self._getvfsward(self.vfs.audit)
1350 # A list of callback to shape the phase if no data were found.
1350 # A list of callback to shape the phase if no data were found.
1351 # Callback are in the form: func(repo, roots) --> processed root.
1351 # Callback are in the form: func(repo, roots) --> processed root.
1352 # This list it to be filled by extension during repo setup
1352 # This list it to be filled by extension during repo setup
1353 self._phasedefaults = []
1353 self._phasedefaults = []
1354
1354
1355 color.setup(self.ui)
1355 color.setup(self.ui)
1356
1356
1357 self.spath = self.store.path
1357 self.spath = self.store.path
1358 self.svfs = self.store.vfs
1358 self.svfs = self.store.vfs
1359 self.sjoin = self.store.join
1359 self.sjoin = self.store.join
1360 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1360 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
1361 b'devel', b'check-locks'
1361 b'devel', b'check-locks'
1362 ):
1362 ):
1363 if util.safehasattr(self.svfs, b'vfs'): # this is filtervfs
1363 if util.safehasattr(self.svfs, b'vfs'): # this is filtervfs
1364 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
1364 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
1365 else: # standard vfs
1365 else: # standard vfs
1366 self.svfs.audit = self._getsvfsward(self.svfs.audit)
1366 self.svfs.audit = self._getsvfsward(self.svfs.audit)
1367
1367
1368 self._dirstatevalidatewarned = False
1368 self._dirstatevalidatewarned = False
1369
1369
1370 self._branchcaches = branchmap.BranchMapCache()
1370 self._branchcaches = branchmap.BranchMapCache()
1371 self._revbranchcache = None
1371 self._revbranchcache = None
1372 self._filterpats = {}
1372 self._filterpats = {}
1373 self._datafilters = {}
1373 self._datafilters = {}
1374 self._transref = self._lockref = self._wlockref = None
1374 self._transref = self._lockref = self._wlockref = None
1375
1375
1376 # A cache for various files under .hg/ that tracks file changes,
1376 # A cache for various files under .hg/ that tracks file changes,
1377 # (used by the filecache decorator)
1377 # (used by the filecache decorator)
1378 #
1378 #
1379 # Maps a property name to its util.filecacheentry
1379 # Maps a property name to its util.filecacheentry
1380 self._filecache = {}
1380 self._filecache = {}
1381
1381
1382 # hold sets of revision to be filtered
1382 # hold sets of revision to be filtered
1383 # should be cleared when something might have changed the filter value:
1383 # should be cleared when something might have changed the filter value:
1384 # - new changesets,
1384 # - new changesets,
1385 # - phase change,
1385 # - phase change,
1386 # - new obsolescence marker,
1386 # - new obsolescence marker,
1387 # - working directory parent change,
1387 # - working directory parent change,
1388 # - bookmark changes
1388 # - bookmark changes
1389 self.filteredrevcache = {}
1389 self.filteredrevcache = {}
1390
1390
1391 # post-dirstate-status hooks
1391 # post-dirstate-status hooks
1392 self._postdsstatus = []
1392 self._postdsstatus = []
1393
1393
1394 # generic mapping between names and nodes
1394 # generic mapping between names and nodes
1395 self.names = namespaces.namespaces()
1395 self.names = namespaces.namespaces()
1396
1396
1397 # Key to signature value.
1397 # Key to signature value.
1398 self._sparsesignaturecache = {}
1398 self._sparsesignaturecache = {}
1399 # Signature to cached matcher instance.
1399 # Signature to cached matcher instance.
1400 self._sparsematchercache = {}
1400 self._sparsematchercache = {}
1401
1401
1402 self._extrafilterid = repoview.extrafilter(ui)
1402 self._extrafilterid = repoview.extrafilter(ui)
1403
1403
1404 self.filecopiesmode = None
1404 self.filecopiesmode = None
1405 if requirementsmod.COPIESSDC_REQUIREMENT in self.requirements:
1405 if requirementsmod.COPIESSDC_REQUIREMENT in self.requirements:
1406 self.filecopiesmode = b'changeset-sidedata'
1406 self.filecopiesmode = b'changeset-sidedata'
1407
1407
1408 self._wanted_sidedata = set()
1408 self._wanted_sidedata = set()
1409 self._sidedata_computers = {}
1409 self._sidedata_computers = {}
1410 metadatamod.set_sidedata_spec_for_repo(self)
1410 metadatamod.set_sidedata_spec_for_repo(self)
1411
1411
1412 def _getvfsward(self, origfunc):
1412 def _getvfsward(self, origfunc):
1413 """build a ward for self.vfs"""
1413 """build a ward for self.vfs"""
1414 rref = weakref.ref(self)
1414 rref = weakref.ref(self)
1415
1415
1416 def checkvfs(path, mode=None):
1416 def checkvfs(path, mode=None):
1417 ret = origfunc(path, mode=mode)
1417 ret = origfunc(path, mode=mode)
1418 repo = rref()
1418 repo = rref()
1419 if (
1419 if (
1420 repo is None
1420 repo is None
1421 or not util.safehasattr(repo, b'_wlockref')
1421 or not util.safehasattr(repo, b'_wlockref')
1422 or not util.safehasattr(repo, b'_lockref')
1422 or not util.safehasattr(repo, b'_lockref')
1423 ):
1423 ):
1424 return
1424 return
1425 if mode in (None, b'r', b'rb'):
1425 if mode in (None, b'r', b'rb'):
1426 return
1426 return
1427 if path.startswith(repo.path):
1427 if path.startswith(repo.path):
1428 # truncate name relative to the repository (.hg)
1428 # truncate name relative to the repository (.hg)
1429 path = path[len(repo.path) + 1 :]
1429 path = path[len(repo.path) + 1 :]
1430 if path.startswith(b'cache/'):
1430 if path.startswith(b'cache/'):
1431 msg = b'accessing cache with vfs instead of cachevfs: "%s"'
1431 msg = b'accessing cache with vfs instead of cachevfs: "%s"'
1432 repo.ui.develwarn(msg % path, stacklevel=3, config=b"cache-vfs")
1432 repo.ui.develwarn(msg % path, stacklevel=3, config=b"cache-vfs")
1433 # path prefixes covered by 'lock'
1433 # path prefixes covered by 'lock'
1434 vfs_path_prefixes = (
1434 vfs_path_prefixes = (
1435 b'journal.',
1435 b'journal.',
1436 b'undo.',
1436 b'undo.',
1437 b'strip-backup/',
1437 b'strip-backup/',
1438 b'cache/',
1438 b'cache/',
1439 )
1439 )
1440 if any(path.startswith(prefix) for prefix in vfs_path_prefixes):
1440 if any(path.startswith(prefix) for prefix in vfs_path_prefixes):
1441 if repo._currentlock(repo._lockref) is None:
1441 if repo._currentlock(repo._lockref) is None:
1442 repo.ui.develwarn(
1442 repo.ui.develwarn(
1443 b'write with no lock: "%s"' % path,
1443 b'write with no lock: "%s"' % path,
1444 stacklevel=3,
1444 stacklevel=3,
1445 config=b'check-locks',
1445 config=b'check-locks',
1446 )
1446 )
1447 elif repo._currentlock(repo._wlockref) is None:
1447 elif repo._currentlock(repo._wlockref) is None:
1448 # rest of vfs files are covered by 'wlock'
1448 # rest of vfs files are covered by 'wlock'
1449 #
1449 #
1450 # exclude special files
1450 # exclude special files
1451 for prefix in self._wlockfreeprefix:
1451 for prefix in self._wlockfreeprefix:
1452 if path.startswith(prefix):
1452 if path.startswith(prefix):
1453 return
1453 return
1454 repo.ui.develwarn(
1454 repo.ui.develwarn(
1455 b'write with no wlock: "%s"' % path,
1455 b'write with no wlock: "%s"' % path,
1456 stacklevel=3,
1456 stacklevel=3,
1457 config=b'check-locks',
1457 config=b'check-locks',
1458 )
1458 )
1459 return ret
1459 return ret
1460
1460
1461 return checkvfs
1461 return checkvfs
1462
1462
1463 def _getsvfsward(self, origfunc):
1463 def _getsvfsward(self, origfunc):
1464 """build a ward for self.svfs"""
1464 """build a ward for self.svfs"""
1465 rref = weakref.ref(self)
1465 rref = weakref.ref(self)
1466
1466
1467 def checksvfs(path, mode=None):
1467 def checksvfs(path, mode=None):
1468 ret = origfunc(path, mode=mode)
1468 ret = origfunc(path, mode=mode)
1469 repo = rref()
1469 repo = rref()
1470 if repo is None or not util.safehasattr(repo, b'_lockref'):
1470 if repo is None or not util.safehasattr(repo, b'_lockref'):
1471 return
1471 return
1472 if mode in (None, b'r', b'rb'):
1472 if mode in (None, b'r', b'rb'):
1473 return
1473 return
1474 if path.startswith(repo.sharedpath):
1474 if path.startswith(repo.sharedpath):
1475 # truncate name relative to the repository (.hg)
1475 # truncate name relative to the repository (.hg)
1476 path = path[len(repo.sharedpath) + 1 :]
1476 path = path[len(repo.sharedpath) + 1 :]
1477 if repo._currentlock(repo._lockref) is None:
1477 if repo._currentlock(repo._lockref) is None:
1478 repo.ui.develwarn(
1478 repo.ui.develwarn(
1479 b'write with no lock: "%s"' % path, stacklevel=4
1479 b'write with no lock: "%s"' % path, stacklevel=4
1480 )
1480 )
1481 return ret
1481 return ret
1482
1482
1483 return checksvfs
1483 return checksvfs
1484
1484
1485 def close(self):
1485 def close(self):
1486 self._writecaches()
1486 self._writecaches()
1487
1487
1488 def _writecaches(self):
1488 def _writecaches(self):
1489 if self._revbranchcache:
1489 if self._revbranchcache:
1490 self._revbranchcache.write()
1490 self._revbranchcache.write()
1491
1491
1492 def _restrictcapabilities(self, caps):
1492 def _restrictcapabilities(self, caps):
1493 if self.ui.configbool(b'experimental', b'bundle2-advertise'):
1493 if self.ui.configbool(b'experimental', b'bundle2-advertise'):
1494 caps = set(caps)
1494 caps = set(caps)
1495 capsblob = bundle2.encodecaps(
1495 capsblob = bundle2.encodecaps(
1496 bundle2.getrepocaps(self, role=b'client')
1496 bundle2.getrepocaps(self, role=b'client')
1497 )
1497 )
1498 caps.add(b'bundle2=' + urlreq.quote(capsblob))
1498 caps.add(b'bundle2=' + urlreq.quote(capsblob))
1499 if self.ui.configbool(b'experimental', b'narrow'):
1499 if self.ui.configbool(b'experimental', b'narrow'):
1500 caps.add(wireprototypes.NARROWCAP)
1500 caps.add(wireprototypes.NARROWCAP)
1501 return caps
1501 return caps
1502
1502
1503 # Don't cache auditor/nofsauditor, or you'll end up with reference cycle:
1503 # Don't cache auditor/nofsauditor, or you'll end up with reference cycle:
1504 # self -> auditor -> self._checknested -> self
1504 # self -> auditor -> self._checknested -> self
1505
1505
1506 @property
1506 @property
1507 def auditor(self):
1507 def auditor(self):
1508 # This is only used by context.workingctx.match in order to
1508 # This is only used by context.workingctx.match in order to
1509 # detect files in subrepos.
1509 # detect files in subrepos.
1510 return pathutil.pathauditor(self.root, callback=self._checknested)
1510 return pathutil.pathauditor(self.root, callback=self._checknested)
1511
1511
1512 @property
1512 @property
1513 def nofsauditor(self):
1513 def nofsauditor(self):
1514 # This is only used by context.basectx.match in order to detect
1514 # This is only used by context.basectx.match in order to detect
1515 # files in subrepos.
1515 # files in subrepos.
1516 return pathutil.pathauditor(
1516 return pathutil.pathauditor(
1517 self.root, callback=self._checknested, realfs=False, cached=True
1517 self.root, callback=self._checknested, realfs=False, cached=True
1518 )
1518 )
1519
1519
1520 def _checknested(self, path):
1520 def _checknested(self, path):
1521 """Determine if path is a legal nested repository."""
1521 """Determine if path is a legal nested repository."""
1522 if not path.startswith(self.root):
1522 if not path.startswith(self.root):
1523 return False
1523 return False
1524 subpath = path[len(self.root) + 1 :]
1524 subpath = path[len(self.root) + 1 :]
1525 normsubpath = util.pconvert(subpath)
1525 normsubpath = util.pconvert(subpath)
1526
1526
1527 # XXX: Checking against the current working copy is wrong in
1527 # XXX: Checking against the current working copy is wrong in
1528 # the sense that it can reject things like
1528 # the sense that it can reject things like
1529 #
1529 #
1530 # $ hg cat -r 10 sub/x.txt
1530 # $ hg cat -r 10 sub/x.txt
1531 #
1531 #
1532 # if sub/ is no longer a subrepository in the working copy
1532 # if sub/ is no longer a subrepository in the working copy
1533 # parent revision.
1533 # parent revision.
1534 #
1534 #
1535 # However, it can of course also allow things that would have
1535 # However, it can of course also allow things that would have
1536 # been rejected before, such as the above cat command if sub/
1536 # been rejected before, such as the above cat command if sub/
1537 # is a subrepository now, but was a normal directory before.
1537 # is a subrepository now, but was a normal directory before.
1538 # The old path auditor would have rejected by mistake since it
1538 # The old path auditor would have rejected by mistake since it
1539 # panics when it sees sub/.hg/.
1539 # panics when it sees sub/.hg/.
1540 #
1540 #
1541 # All in all, checking against the working copy seems sensible
1541 # All in all, checking against the working copy seems sensible
1542 # since we want to prevent access to nested repositories on
1542 # since we want to prevent access to nested repositories on
1543 # the filesystem *now*.
1543 # the filesystem *now*.
1544 ctx = self[None]
1544 ctx = self[None]
1545 parts = util.splitpath(subpath)
1545 parts = util.splitpath(subpath)
1546 while parts:
1546 while parts:
1547 prefix = b'/'.join(parts)
1547 prefix = b'/'.join(parts)
1548 if prefix in ctx.substate:
1548 if prefix in ctx.substate:
1549 if prefix == normsubpath:
1549 if prefix == normsubpath:
1550 return True
1550 return True
1551 else:
1551 else:
1552 sub = ctx.sub(prefix)
1552 sub = ctx.sub(prefix)
1553 return sub.checknested(subpath[len(prefix) + 1 :])
1553 return sub.checknested(subpath[len(prefix) + 1 :])
1554 else:
1554 else:
1555 parts.pop()
1555 parts.pop()
1556 return False
1556 return False
1557
1557
1558 def peer(self):
1558 def peer(self):
1559 return localpeer(self) # not cached to avoid reference cycle
1559 return localpeer(self) # not cached to avoid reference cycle
1560
1560
1561 def unfiltered(self):
1561 def unfiltered(self):
1562 """Return unfiltered version of the repository
1562 """Return unfiltered version of the repository
1563
1563
1564 Intended to be overwritten by filtered repo."""
1564 Intended to be overwritten by filtered repo."""
1565 return self
1565 return self
1566
1566
1567 def filtered(self, name, visibilityexceptions=None):
1567 def filtered(self, name, visibilityexceptions=None):
1568 """Return a filtered version of a repository
1568 """Return a filtered version of a repository
1569
1569
1570 The `name` parameter is the identifier of the requested view. This
1570 The `name` parameter is the identifier of the requested view. This
1571 will return a repoview object set "exactly" to the specified view.
1571 will return a repoview object set "exactly" to the specified view.
1572
1572
1573 This function does not apply recursive filtering to a repository. For
1573 This function does not apply recursive filtering to a repository. For
1574 example calling `repo.filtered("served")` will return a repoview using
1574 example calling `repo.filtered("served")` will return a repoview using
1575 the "served" view, regardless of the initial view used by `repo`.
1575 the "served" view, regardless of the initial view used by `repo`.
1576
1576
1577 In other word, there is always only one level of `repoview` "filtering".
1577 In other word, there is always only one level of `repoview` "filtering".
1578 """
1578 """
1579 if self._extrafilterid is not None and b'%' not in name:
1579 if self._extrafilterid is not None and b'%' not in name:
1580 name = name + b'%' + self._extrafilterid
1580 name = name + b'%' + self._extrafilterid
1581
1581
1582 cls = repoview.newtype(self.unfiltered().__class__)
1582 cls = repoview.newtype(self.unfiltered().__class__)
1583 return cls(self, name, visibilityexceptions)
1583 return cls(self, name, visibilityexceptions)
1584
1584
1585 @mixedrepostorecache(
1585 @mixedrepostorecache(
1586 (b'bookmarks', b'plain'),
1586 (b'bookmarks', b'plain'),
1587 (b'bookmarks.current', b'plain'),
1587 (b'bookmarks.current', b'plain'),
1588 (b'bookmarks', b''),
1588 (b'bookmarks', b''),
1589 (b'00changelog.i', b''),
1589 (b'00changelog.i', b''),
1590 )
1590 )
1591 def _bookmarks(self):
1591 def _bookmarks(self):
1592 # Since the multiple files involved in the transaction cannot be
1592 # Since the multiple files involved in the transaction cannot be
1593 # written atomically (with current repository format), there is a race
1593 # written atomically (with current repository format), there is a race
1594 # condition here.
1594 # condition here.
1595 #
1595 #
1596 # 1) changelog content A is read
1596 # 1) changelog content A is read
1597 # 2) outside transaction update changelog to content B
1597 # 2) outside transaction update changelog to content B
1598 # 3) outside transaction update bookmark file referring to content B
1598 # 3) outside transaction update bookmark file referring to content B
1599 # 4) bookmarks file content is read and filtered against changelog-A
1599 # 4) bookmarks file content is read and filtered against changelog-A
1600 #
1600 #
1601 # When this happens, bookmarks against nodes missing from A are dropped.
1601 # When this happens, bookmarks against nodes missing from A are dropped.
1602 #
1602 #
1603 # Having this happening during read is not great, but it become worse
1603 # Having this happening during read is not great, but it become worse
1604 # when this happen during write because the bookmarks to the "unknown"
1604 # when this happen during write because the bookmarks to the "unknown"
1605 # nodes will be dropped for good. However, writes happen within locks.
1605 # nodes will be dropped for good. However, writes happen within locks.
1606 # This locking makes it possible to have a race free consistent read.
1606 # This locking makes it possible to have a race free consistent read.
1607 # For this purpose data read from disc before locking are
1607 # For this purpose data read from disc before locking are
1608 # "invalidated" right after the locks are taken. This invalidations are
1608 # "invalidated" right after the locks are taken. This invalidations are
1609 # "light", the `filecache` mechanism keep the data in memory and will
1609 # "light", the `filecache` mechanism keep the data in memory and will
1610 # reuse them if the underlying files did not changed. Not parsing the
1610 # reuse them if the underlying files did not changed. Not parsing the
1611 # same data multiple times helps performances.
1611 # same data multiple times helps performances.
1612 #
1612 #
1613 # Unfortunately in the case describe above, the files tracked by the
1613 # Unfortunately in the case describe above, the files tracked by the
1614 # bookmarks file cache might not have changed, but the in-memory
1614 # bookmarks file cache might not have changed, but the in-memory
1615 # content is still "wrong" because we used an older changelog content
1615 # content is still "wrong" because we used an older changelog content
1616 # to process the on-disk data. So after locking, the changelog would be
1616 # to process the on-disk data. So after locking, the changelog would be
1617 # refreshed but `_bookmarks` would be preserved.
1617 # refreshed but `_bookmarks` would be preserved.
1618 # Adding `00changelog.i` to the list of tracked file is not
1618 # Adding `00changelog.i` to the list of tracked file is not
1619 # enough, because at the time we build the content for `_bookmarks` in
1619 # enough, because at the time we build the content for `_bookmarks` in
1620 # (4), the changelog file has already diverged from the content used
1620 # (4), the changelog file has already diverged from the content used
1621 # for loading `changelog` in (1)
1621 # for loading `changelog` in (1)
1622 #
1622 #
1623 # To prevent the issue, we force the changelog to be explicitly
1623 # To prevent the issue, we force the changelog to be explicitly
1624 # reloaded while computing `_bookmarks`. The data race can still happen
1624 # reloaded while computing `_bookmarks`. The data race can still happen
1625 # without the lock (with a narrower window), but it would no longer go
1625 # without the lock (with a narrower window), but it would no longer go
1626 # undetected during the lock time refresh.
1626 # undetected during the lock time refresh.
1627 #
1627 #
1628 # The new schedule is as follow
1628 # The new schedule is as follow
1629 #
1629 #
1630 # 1) filecache logic detect that `_bookmarks` needs to be computed
1630 # 1) filecache logic detect that `_bookmarks` needs to be computed
1631 # 2) cachestat for `bookmarks` and `changelog` are captured (for book)
1631 # 2) cachestat for `bookmarks` and `changelog` are captured (for book)
1632 # 3) We force `changelog` filecache to be tested
1632 # 3) We force `changelog` filecache to be tested
1633 # 4) cachestat for `changelog` are captured (for changelog)
1633 # 4) cachestat for `changelog` are captured (for changelog)
1634 # 5) `_bookmarks` is computed and cached
1634 # 5) `_bookmarks` is computed and cached
1635 #
1635 #
1636 # The step in (3) ensure we have a changelog at least as recent as the
1636 # The step in (3) ensure we have a changelog at least as recent as the
1637 # cache stat computed in (1). As a result at locking time:
1637 # cache stat computed in (1). As a result at locking time:
1638 # * if the changelog did not changed since (1) -> we can reuse the data
1638 # * if the changelog did not changed since (1) -> we can reuse the data
1639 # * otherwise -> the bookmarks get refreshed.
1639 # * otherwise -> the bookmarks get refreshed.
1640 self._refreshchangelog()
1640 self._refreshchangelog()
1641 return bookmarks.bmstore(self)
1641 return bookmarks.bmstore(self)
1642
1642
1643 def _refreshchangelog(self):
1643 def _refreshchangelog(self):
1644 """make sure the in memory changelog match the on-disk one"""
1644 """make sure the in memory changelog match the on-disk one"""
1645 if 'changelog' in vars(self) and self.currenttransaction() is None:
1645 if 'changelog' in vars(self) and self.currenttransaction() is None:
1646 del self.changelog
1646 del self.changelog
1647
1647
1648 @property
1648 @property
1649 def _activebookmark(self):
1649 def _activebookmark(self):
1650 return self._bookmarks.active
1650 return self._bookmarks.active
1651
1651
1652 # _phasesets depend on changelog. what we need is to call
1652 # _phasesets depend on changelog. what we need is to call
1653 # _phasecache.invalidate() if '00changelog.i' was changed, but it
1653 # _phasecache.invalidate() if '00changelog.i' was changed, but it
1654 # can't be easily expressed in filecache mechanism.
1654 # can't be easily expressed in filecache mechanism.
1655 @storecache(b'phaseroots', b'00changelog.i')
1655 @storecache(b'phaseroots', b'00changelog.i')
1656 def _phasecache(self):
1656 def _phasecache(self):
1657 return phases.phasecache(self, self._phasedefaults)
1657 return phases.phasecache(self, self._phasedefaults)
1658
1658
1659 @storecache(b'obsstore')
1659 @storecache(b'obsstore')
1660 def obsstore(self):
1660 def obsstore(self):
1661 return obsolete.makestore(self.ui, self)
1661 return obsolete.makestore(self.ui, self)
1662
1662
1663 @storecache(b'00changelog.i')
1663 @storecache(b'00changelog.i')
1664 def changelog(self):
1664 def changelog(self):
1665 # load dirstate before changelog to avoid race see issue6303
1665 # load dirstate before changelog to avoid race see issue6303
1666 self.dirstate.prefetch_parents()
1666 self.dirstate.prefetch_parents()
1667 return self.store.changelog(
1667 return self.store.changelog(
1668 txnutil.mayhavepending(self.root),
1668 txnutil.mayhavepending(self.root),
1669 concurrencychecker=revlogchecker.get_checker(self.ui, b'changelog'),
1669 concurrencychecker=revlogchecker.get_checker(self.ui, b'changelog'),
1670 )
1670 )
1671
1671
1672 @storecache(b'00manifest.i')
1672 @storecache(b'00manifest.i')
1673 def manifestlog(self):
1673 def manifestlog(self):
1674 return self.store.manifestlog(self, self._storenarrowmatch)
1674 return self.store.manifestlog(self, self._storenarrowmatch)
1675
1675
1676 @repofilecache(b'dirstate')
1676 @repofilecache(b'dirstate')
1677 def dirstate(self):
1677 def dirstate(self):
1678 return self._makedirstate()
1678 return self._makedirstate()
1679
1679
1680 def _makedirstate(self):
1680 def _makedirstate(self):
1681 """Extension point for wrapping the dirstate per-repo."""
1681 """Extension point for wrapping the dirstate per-repo."""
1682 sparsematchfn = lambda: sparse.matcher(self)
1682 sparsematchfn = lambda: sparse.matcher(self)
1683
1683
1684 return dirstate.dirstate(
1684 return dirstate.dirstate(
1685 self.vfs,
1685 self.vfs,
1686 self.ui,
1686 self.ui,
1687 self.root,
1687 self.root,
1688 self._dirstatevalidate,
1688 self._dirstatevalidate,
1689 sparsematchfn,
1689 sparsematchfn,
1690 self.nodeconstants,
1690 self.nodeconstants,
1691 )
1691 )
1692
1692
1693 def _dirstatevalidate(self, node):
1693 def _dirstatevalidate(self, node):
1694 try:
1694 try:
1695 self.changelog.rev(node)
1695 self.changelog.rev(node)
1696 return node
1696 return node
1697 except error.LookupError:
1697 except error.LookupError:
1698 if not self._dirstatevalidatewarned:
1698 if not self._dirstatevalidatewarned:
1699 self._dirstatevalidatewarned = True
1699 self._dirstatevalidatewarned = True
1700 self.ui.warn(
1700 self.ui.warn(
1701 _(b"warning: ignoring unknown working parent %s!\n")
1701 _(b"warning: ignoring unknown working parent %s!\n")
1702 % short(node)
1702 % short(node)
1703 )
1703 )
1704 return self.nullid
1704 return self.nullid
1705
1705
1706 @storecache(narrowspec.FILENAME)
1706 @storecache(narrowspec.FILENAME)
1707 def narrowpats(self):
1707 def narrowpats(self):
1708 """matcher patterns for this repository's narrowspec
1708 """matcher patterns for this repository's narrowspec
1709
1709
1710 A tuple of (includes, excludes).
1710 A tuple of (includes, excludes).
1711 """
1711 """
1712 return narrowspec.load(self)
1712 return narrowspec.load(self)
1713
1713
1714 @storecache(narrowspec.FILENAME)
1714 @storecache(narrowspec.FILENAME)
1715 def _storenarrowmatch(self):
1715 def _storenarrowmatch(self):
1716 if requirementsmod.NARROW_REQUIREMENT not in self.requirements:
1716 if requirementsmod.NARROW_REQUIREMENT not in self.requirements:
1717 return matchmod.always()
1717 return matchmod.always()
1718 include, exclude = self.narrowpats
1718 include, exclude = self.narrowpats
1719 return narrowspec.match(self.root, include=include, exclude=exclude)
1719 return narrowspec.match(self.root, include=include, exclude=exclude)
1720
1720
1721 @storecache(narrowspec.FILENAME)
1721 @storecache(narrowspec.FILENAME)
1722 def _narrowmatch(self):
1722 def _narrowmatch(self):
1723 if requirementsmod.NARROW_REQUIREMENT not in self.requirements:
1723 if requirementsmod.NARROW_REQUIREMENT not in self.requirements:
1724 return matchmod.always()
1724 return matchmod.always()
1725 narrowspec.checkworkingcopynarrowspec(self)
1725 narrowspec.checkworkingcopynarrowspec(self)
1726 include, exclude = self.narrowpats
1726 include, exclude = self.narrowpats
1727 return narrowspec.match(self.root, include=include, exclude=exclude)
1727 return narrowspec.match(self.root, include=include, exclude=exclude)
1728
1728
1729 def narrowmatch(self, match=None, includeexact=False):
1729 def narrowmatch(self, match=None, includeexact=False):
1730 """matcher corresponding the the repo's narrowspec
1730 """matcher corresponding the the repo's narrowspec
1731
1731
1732 If `match` is given, then that will be intersected with the narrow
1732 If `match` is given, then that will be intersected with the narrow
1733 matcher.
1733 matcher.
1734
1734
1735 If `includeexact` is True, then any exact matches from `match` will
1735 If `includeexact` is True, then any exact matches from `match` will
1736 be included even if they're outside the narrowspec.
1736 be included even if they're outside the narrowspec.
1737 """
1737 """
1738 if match:
1738 if match:
1739 if includeexact and not self._narrowmatch.always():
1739 if includeexact and not self._narrowmatch.always():
1740 # do not exclude explicitly-specified paths so that they can
1740 # do not exclude explicitly-specified paths so that they can
1741 # be warned later on
1741 # be warned later on
1742 em = matchmod.exact(match.files())
1742 em = matchmod.exact(match.files())
1743 nm = matchmod.unionmatcher([self._narrowmatch, em])
1743 nm = matchmod.unionmatcher([self._narrowmatch, em])
1744 return matchmod.intersectmatchers(match, nm)
1744 return matchmod.intersectmatchers(match, nm)
1745 return matchmod.intersectmatchers(match, self._narrowmatch)
1745 return matchmod.intersectmatchers(match, self._narrowmatch)
1746 return self._narrowmatch
1746 return self._narrowmatch
1747
1747
1748 def setnarrowpats(self, newincludes, newexcludes):
1748 def setnarrowpats(self, newincludes, newexcludes):
1749 narrowspec.save(self, newincludes, newexcludes)
1749 narrowspec.save(self, newincludes, newexcludes)
1750 self.invalidate(clearfilecache=True)
1750 self.invalidate(clearfilecache=True)
1751
1751
1752 @unfilteredpropertycache
1752 @unfilteredpropertycache
1753 def _quick_access_changeid_null(self):
1753 def _quick_access_changeid_null(self):
1754 return {
1754 return {
1755 b'null': (nullrev, self.nodeconstants.nullid),
1755 b'null': (nullrev, self.nodeconstants.nullid),
1756 nullrev: (nullrev, self.nodeconstants.nullid),
1756 nullrev: (nullrev, self.nodeconstants.nullid),
1757 self.nullid: (nullrev, self.nullid),
1757 self.nullid: (nullrev, self.nullid),
1758 }
1758 }
1759
1759
1760 @unfilteredpropertycache
1760 @unfilteredpropertycache
1761 def _quick_access_changeid_wc(self):
1761 def _quick_access_changeid_wc(self):
1762 # also fast path access to the working copy parents
1762 # also fast path access to the working copy parents
1763 # however, only do it for filter that ensure wc is visible.
1763 # however, only do it for filter that ensure wc is visible.
1764 quick = self._quick_access_changeid_null.copy()
1764 quick = self._quick_access_changeid_null.copy()
1765 cl = self.unfiltered().changelog
1765 cl = self.unfiltered().changelog
1766 for node in self.dirstate.parents():
1766 for node in self.dirstate.parents():
1767 if node == self.nullid:
1767 if node == self.nullid:
1768 continue
1768 continue
1769 rev = cl.index.get_rev(node)
1769 rev = cl.index.get_rev(node)
1770 if rev is None:
1770 if rev is None:
1771 # unknown working copy parent case:
1771 # unknown working copy parent case:
1772 #
1772 #
1773 # skip the fast path and let higher code deal with it
1773 # skip the fast path and let higher code deal with it
1774 continue
1774 continue
1775 pair = (rev, node)
1775 pair = (rev, node)
1776 quick[rev] = pair
1776 quick[rev] = pair
1777 quick[node] = pair
1777 quick[node] = pair
1778 # also add the parents of the parents
1778 # also add the parents of the parents
1779 for r in cl.parentrevs(rev):
1779 for r in cl.parentrevs(rev):
1780 if r == nullrev:
1780 if r == nullrev:
1781 continue
1781 continue
1782 n = cl.node(r)
1782 n = cl.node(r)
1783 pair = (r, n)
1783 pair = (r, n)
1784 quick[r] = pair
1784 quick[r] = pair
1785 quick[n] = pair
1785 quick[n] = pair
1786 p1node = self.dirstate.p1()
1786 p1node = self.dirstate.p1()
1787 if p1node != self.nullid:
1787 if p1node != self.nullid:
1788 quick[b'.'] = quick[p1node]
1788 quick[b'.'] = quick[p1node]
1789 return quick
1789 return quick
1790
1790
1791 @unfilteredmethod
1791 @unfilteredmethod
1792 def _quick_access_changeid_invalidate(self):
1792 def _quick_access_changeid_invalidate(self):
1793 if '_quick_access_changeid_wc' in vars(self):
1793 if '_quick_access_changeid_wc' in vars(self):
1794 del self.__dict__['_quick_access_changeid_wc']
1794 del self.__dict__['_quick_access_changeid_wc']
1795
1795
1796 @property
1796 @property
1797 def _quick_access_changeid(self):
1797 def _quick_access_changeid(self):
1798 """an helper dictionnary for __getitem__ calls
1798 """an helper dictionnary for __getitem__ calls
1799
1799
1800 This contains a list of symbol we can recognise right away without
1800 This contains a list of symbol we can recognise right away without
1801 further processing.
1801 further processing.
1802 """
1802 """
1803 if self.filtername in repoview.filter_has_wc:
1803 if self.filtername in repoview.filter_has_wc:
1804 return self._quick_access_changeid_wc
1804 return self._quick_access_changeid_wc
1805 return self._quick_access_changeid_null
1805 return self._quick_access_changeid_null
1806
1806
1807 def __getitem__(self, changeid):
1807 def __getitem__(self, changeid):
1808 # dealing with special cases
1808 # dealing with special cases
1809 if changeid is None:
1809 if changeid is None:
1810 return context.workingctx(self)
1810 return context.workingctx(self)
1811 if isinstance(changeid, context.basectx):
1811 if isinstance(changeid, context.basectx):
1812 return changeid
1812 return changeid
1813
1813
1814 # dealing with multiple revisions
1814 # dealing with multiple revisions
1815 if isinstance(changeid, slice):
1815 if isinstance(changeid, slice):
1816 # wdirrev isn't contiguous so the slice shouldn't include it
1816 # wdirrev isn't contiguous so the slice shouldn't include it
1817 return [
1817 return [
1818 self[i]
1818 self[i]
1819 for i in pycompat.xrange(*changeid.indices(len(self)))
1819 for i in pycompat.xrange(*changeid.indices(len(self)))
1820 if i not in self.changelog.filteredrevs
1820 if i not in self.changelog.filteredrevs
1821 ]
1821 ]
1822
1822
1823 # dealing with some special values
1823 # dealing with some special values
1824 quick_access = self._quick_access_changeid.get(changeid)
1824 quick_access = self._quick_access_changeid.get(changeid)
1825 if quick_access is not None:
1825 if quick_access is not None:
1826 rev, node = quick_access
1826 rev, node = quick_access
1827 return context.changectx(self, rev, node, maybe_filtered=False)
1827 return context.changectx(self, rev, node, maybe_filtered=False)
1828 if changeid == b'tip':
1828 if changeid == b'tip':
1829 node = self.changelog.tip()
1829 node = self.changelog.tip()
1830 rev = self.changelog.rev(node)
1830 rev = self.changelog.rev(node)
1831 return context.changectx(self, rev, node)
1831 return context.changectx(self, rev, node)
1832
1832
1833 # dealing with arbitrary values
1833 # dealing with arbitrary values
1834 try:
1834 try:
1835 if isinstance(changeid, int):
1835 if isinstance(changeid, int):
1836 node = self.changelog.node(changeid)
1836 node = self.changelog.node(changeid)
1837 rev = changeid
1837 rev = changeid
1838 elif changeid == b'.':
1838 elif changeid == b'.':
1839 # this is a hack to delay/avoid loading obsmarkers
1839 # this is a hack to delay/avoid loading obsmarkers
1840 # when we know that '.' won't be hidden
1840 # when we know that '.' won't be hidden
1841 node = self.dirstate.p1()
1841 node = self.dirstate.p1()
1842 rev = self.unfiltered().changelog.rev(node)
1842 rev = self.unfiltered().changelog.rev(node)
1843 elif len(changeid) == 20:
1843 elif len(changeid) == self.nodeconstants.nodelen:
1844 try:
1844 try:
1845 node = changeid
1845 node = changeid
1846 rev = self.changelog.rev(changeid)
1846 rev = self.changelog.rev(changeid)
1847 except error.FilteredLookupError:
1847 except error.FilteredLookupError:
1848 changeid = hex(changeid) # for the error message
1848 changeid = hex(changeid) # for the error message
1849 raise
1849 raise
1850 except LookupError:
1850 except LookupError:
1851 # check if it might have come from damaged dirstate
1851 # check if it might have come from damaged dirstate
1852 #
1852 #
1853 # XXX we could avoid the unfiltered if we had a recognizable
1853 # XXX we could avoid the unfiltered if we had a recognizable
1854 # exception for filtered changeset access
1854 # exception for filtered changeset access
1855 if (
1855 if (
1856 self.local()
1856 self.local()
1857 and changeid in self.unfiltered().dirstate.parents()
1857 and changeid in self.unfiltered().dirstate.parents()
1858 ):
1858 ):
1859 msg = _(b"working directory has unknown parent '%s'!")
1859 msg = _(b"working directory has unknown parent '%s'!")
1860 raise error.Abort(msg % short(changeid))
1860 raise error.Abort(msg % short(changeid))
1861 changeid = hex(changeid) # for the error message
1861 changeid = hex(changeid) # for the error message
1862 raise
1862 raise
1863
1863
1864 elif len(changeid) == 2 * self.nodeconstants.nodelen:
1864 elif len(changeid) == 2 * self.nodeconstants.nodelen:
1865 node = bin(changeid)
1865 node = bin(changeid)
1866 rev = self.changelog.rev(node)
1866 rev = self.changelog.rev(node)
1867 else:
1867 else:
1868 raise error.ProgrammingError(
1868 raise error.ProgrammingError(
1869 b"unsupported changeid '%s' of type %s"
1869 b"unsupported changeid '%s' of type %s"
1870 % (changeid, pycompat.bytestr(type(changeid)))
1870 % (changeid, pycompat.bytestr(type(changeid)))
1871 )
1871 )
1872
1872
1873 return context.changectx(self, rev, node)
1873 return context.changectx(self, rev, node)
1874
1874
1875 except (error.FilteredIndexError, error.FilteredLookupError):
1875 except (error.FilteredIndexError, error.FilteredLookupError):
1876 raise error.FilteredRepoLookupError(
1876 raise error.FilteredRepoLookupError(
1877 _(b"filtered revision '%s'") % pycompat.bytestr(changeid)
1877 _(b"filtered revision '%s'") % pycompat.bytestr(changeid)
1878 )
1878 )
1879 except (IndexError, LookupError):
1879 except (IndexError, LookupError):
1880 raise error.RepoLookupError(
1880 raise error.RepoLookupError(
1881 _(b"unknown revision '%s'") % pycompat.bytestr(changeid)
1881 _(b"unknown revision '%s'") % pycompat.bytestr(changeid)
1882 )
1882 )
1883 except error.WdirUnsupported:
1883 except error.WdirUnsupported:
1884 return context.workingctx(self)
1884 return context.workingctx(self)
1885
1885
1886 def __contains__(self, changeid):
1886 def __contains__(self, changeid):
1887 """True if the given changeid exists"""
1887 """True if the given changeid exists"""
1888 try:
1888 try:
1889 self[changeid]
1889 self[changeid]
1890 return True
1890 return True
1891 except error.RepoLookupError:
1891 except error.RepoLookupError:
1892 return False
1892 return False
1893
1893
1894 def __nonzero__(self):
1894 def __nonzero__(self):
1895 return True
1895 return True
1896
1896
1897 __bool__ = __nonzero__
1897 __bool__ = __nonzero__
1898
1898
1899 def __len__(self):
1899 def __len__(self):
1900 # no need to pay the cost of repoview.changelog
1900 # no need to pay the cost of repoview.changelog
1901 unfi = self.unfiltered()
1901 unfi = self.unfiltered()
1902 return len(unfi.changelog)
1902 return len(unfi.changelog)
1903
1903
1904 def __iter__(self):
1904 def __iter__(self):
1905 return iter(self.changelog)
1905 return iter(self.changelog)
1906
1906
1907 def revs(self, expr, *args):
1907 def revs(self, expr, *args):
1908 """Find revisions matching a revset.
1908 """Find revisions matching a revset.
1909
1909
1910 The revset is specified as a string ``expr`` that may contain
1910 The revset is specified as a string ``expr`` that may contain
1911 %-formatting to escape certain types. See ``revsetlang.formatspec``.
1911 %-formatting to escape certain types. See ``revsetlang.formatspec``.
1912
1912
1913 Revset aliases from the configuration are not expanded. To expand
1913 Revset aliases from the configuration are not expanded. To expand
1914 user aliases, consider calling ``scmutil.revrange()`` or
1914 user aliases, consider calling ``scmutil.revrange()`` or
1915 ``repo.anyrevs([expr], user=True)``.
1915 ``repo.anyrevs([expr], user=True)``.
1916
1916
1917 Returns a smartset.abstractsmartset, which is a list-like interface
1917 Returns a smartset.abstractsmartset, which is a list-like interface
1918 that contains integer revisions.
1918 that contains integer revisions.
1919 """
1919 """
1920 tree = revsetlang.spectree(expr, *args)
1920 tree = revsetlang.spectree(expr, *args)
1921 return revset.makematcher(tree)(self)
1921 return revset.makematcher(tree)(self)
1922
1922
1923 def set(self, expr, *args):
1923 def set(self, expr, *args):
1924 """Find revisions matching a revset and emit changectx instances.
1924 """Find revisions matching a revset and emit changectx instances.
1925
1925
1926 This is a convenience wrapper around ``revs()`` that iterates the
1926 This is a convenience wrapper around ``revs()`` that iterates the
1927 result and is a generator of changectx instances.
1927 result and is a generator of changectx instances.
1928
1928
1929 Revset aliases from the configuration are not expanded. To expand
1929 Revset aliases from the configuration are not expanded. To expand
1930 user aliases, consider calling ``scmutil.revrange()``.
1930 user aliases, consider calling ``scmutil.revrange()``.
1931 """
1931 """
1932 for r in self.revs(expr, *args):
1932 for r in self.revs(expr, *args):
1933 yield self[r]
1933 yield self[r]
1934
1934
1935 def anyrevs(self, specs, user=False, localalias=None):
1935 def anyrevs(self, specs, user=False, localalias=None):
1936 """Find revisions matching one of the given revsets.
1936 """Find revisions matching one of the given revsets.
1937
1937
1938 Revset aliases from the configuration are not expanded by default. To
1938 Revset aliases from the configuration are not expanded by default. To
1939 expand user aliases, specify ``user=True``. To provide some local
1939 expand user aliases, specify ``user=True``. To provide some local
1940 definitions overriding user aliases, set ``localalias`` to
1940 definitions overriding user aliases, set ``localalias`` to
1941 ``{name: definitionstring}``.
1941 ``{name: definitionstring}``.
1942 """
1942 """
1943 if specs == [b'null']:
1943 if specs == [b'null']:
1944 return revset.baseset([nullrev])
1944 return revset.baseset([nullrev])
1945 if specs == [b'.']:
1945 if specs == [b'.']:
1946 quick_data = self._quick_access_changeid.get(b'.')
1946 quick_data = self._quick_access_changeid.get(b'.')
1947 if quick_data is not None:
1947 if quick_data is not None:
1948 return revset.baseset([quick_data[0]])
1948 return revset.baseset([quick_data[0]])
1949 if user:
1949 if user:
1950 m = revset.matchany(
1950 m = revset.matchany(
1951 self.ui,
1951 self.ui,
1952 specs,
1952 specs,
1953 lookup=revset.lookupfn(self),
1953 lookup=revset.lookupfn(self),
1954 localalias=localalias,
1954 localalias=localalias,
1955 )
1955 )
1956 else:
1956 else:
1957 m = revset.matchany(None, specs, localalias=localalias)
1957 m = revset.matchany(None, specs, localalias=localalias)
1958 return m(self)
1958 return m(self)
1959
1959
1960 def url(self):
1960 def url(self):
1961 return b'file:' + self.root
1961 return b'file:' + self.root
1962
1962
1963 def hook(self, name, throw=False, **args):
1963 def hook(self, name, throw=False, **args):
1964 """Call a hook, passing this repo instance.
1964 """Call a hook, passing this repo instance.
1965
1965
1966 This a convenience method to aid invoking hooks. Extensions likely
1966 This a convenience method to aid invoking hooks. Extensions likely
1967 won't call this unless they have registered a custom hook or are
1967 won't call this unless they have registered a custom hook or are
1968 replacing code that is expected to call a hook.
1968 replacing code that is expected to call a hook.
1969 """
1969 """
1970 return hook.hook(self.ui, self, name, throw, **args)
1970 return hook.hook(self.ui, self, name, throw, **args)
1971
1971
1972 @filteredpropertycache
1972 @filteredpropertycache
1973 def _tagscache(self):
1973 def _tagscache(self):
1974 """Returns a tagscache object that contains various tags related
1974 """Returns a tagscache object that contains various tags related
1975 caches."""
1975 caches."""
1976
1976
1977 # This simplifies its cache management by having one decorated
1977 # This simplifies its cache management by having one decorated
1978 # function (this one) and the rest simply fetch things from it.
1978 # function (this one) and the rest simply fetch things from it.
1979 class tagscache(object):
1979 class tagscache(object):
1980 def __init__(self):
1980 def __init__(self):
1981 # These two define the set of tags for this repository. tags
1981 # These two define the set of tags for this repository. tags
1982 # maps tag name to node; tagtypes maps tag name to 'global' or
1982 # maps tag name to node; tagtypes maps tag name to 'global' or
1983 # 'local'. (Global tags are defined by .hgtags across all
1983 # 'local'. (Global tags are defined by .hgtags across all
1984 # heads, and local tags are defined in .hg/localtags.)
1984 # heads, and local tags are defined in .hg/localtags.)
1985 # They constitute the in-memory cache of tags.
1985 # They constitute the in-memory cache of tags.
1986 self.tags = self.tagtypes = None
1986 self.tags = self.tagtypes = None
1987
1987
1988 self.nodetagscache = self.tagslist = None
1988 self.nodetagscache = self.tagslist = None
1989
1989
1990 cache = tagscache()
1990 cache = tagscache()
1991 cache.tags, cache.tagtypes = self._findtags()
1991 cache.tags, cache.tagtypes = self._findtags()
1992
1992
1993 return cache
1993 return cache
1994
1994
1995 def tags(self):
1995 def tags(self):
1996 '''return a mapping of tag to node'''
1996 '''return a mapping of tag to node'''
1997 t = {}
1997 t = {}
1998 if self.changelog.filteredrevs:
1998 if self.changelog.filteredrevs:
1999 tags, tt = self._findtags()
1999 tags, tt = self._findtags()
2000 else:
2000 else:
2001 tags = self._tagscache.tags
2001 tags = self._tagscache.tags
2002 rev = self.changelog.rev
2002 rev = self.changelog.rev
2003 for k, v in pycompat.iteritems(tags):
2003 for k, v in pycompat.iteritems(tags):
2004 try:
2004 try:
2005 # ignore tags to unknown nodes
2005 # ignore tags to unknown nodes
2006 rev(v)
2006 rev(v)
2007 t[k] = v
2007 t[k] = v
2008 except (error.LookupError, ValueError):
2008 except (error.LookupError, ValueError):
2009 pass
2009 pass
2010 return t
2010 return t
2011
2011
2012 def _findtags(self):
2012 def _findtags(self):
2013 """Do the hard work of finding tags. Return a pair of dicts
2013 """Do the hard work of finding tags. Return a pair of dicts
2014 (tags, tagtypes) where tags maps tag name to node, and tagtypes
2014 (tags, tagtypes) where tags maps tag name to node, and tagtypes
2015 maps tag name to a string like \'global\' or \'local\'.
2015 maps tag name to a string like \'global\' or \'local\'.
2016 Subclasses or extensions are free to add their own tags, but
2016 Subclasses or extensions are free to add their own tags, but
2017 should be aware that the returned dicts will be retained for the
2017 should be aware that the returned dicts will be retained for the
2018 duration of the localrepo object."""
2018 duration of the localrepo object."""
2019
2019
2020 # XXX what tagtype should subclasses/extensions use? Currently
2020 # XXX what tagtype should subclasses/extensions use? Currently
2021 # mq and bookmarks add tags, but do not set the tagtype at all.
2021 # mq and bookmarks add tags, but do not set the tagtype at all.
2022 # Should each extension invent its own tag type? Should there
2022 # Should each extension invent its own tag type? Should there
2023 # be one tagtype for all such "virtual" tags? Or is the status
2023 # be one tagtype for all such "virtual" tags? Or is the status
2024 # quo fine?
2024 # quo fine?
2025
2025
2026 # map tag name to (node, hist)
2026 # map tag name to (node, hist)
2027 alltags = tagsmod.findglobaltags(self.ui, self)
2027 alltags = tagsmod.findglobaltags(self.ui, self)
2028 # map tag name to tag type
2028 # map tag name to tag type
2029 tagtypes = {tag: b'global' for tag in alltags}
2029 tagtypes = {tag: b'global' for tag in alltags}
2030
2030
2031 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
2031 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
2032
2032
2033 # Build the return dicts. Have to re-encode tag names because
2033 # Build the return dicts. Have to re-encode tag names because
2034 # the tags module always uses UTF-8 (in order not to lose info
2034 # the tags module always uses UTF-8 (in order not to lose info
2035 # writing to the cache), but the rest of Mercurial wants them in
2035 # writing to the cache), but the rest of Mercurial wants them in
2036 # local encoding.
2036 # local encoding.
2037 tags = {}
2037 tags = {}
2038 for (name, (node, hist)) in pycompat.iteritems(alltags):
2038 for (name, (node, hist)) in pycompat.iteritems(alltags):
2039 if node != self.nullid:
2039 if node != self.nullid:
2040 tags[encoding.tolocal(name)] = node
2040 tags[encoding.tolocal(name)] = node
2041 tags[b'tip'] = self.changelog.tip()
2041 tags[b'tip'] = self.changelog.tip()
2042 tagtypes = {
2042 tagtypes = {
2043 encoding.tolocal(name): value
2043 encoding.tolocal(name): value
2044 for (name, value) in pycompat.iteritems(tagtypes)
2044 for (name, value) in pycompat.iteritems(tagtypes)
2045 }
2045 }
2046 return (tags, tagtypes)
2046 return (tags, tagtypes)
2047
2047
2048 def tagtype(self, tagname):
2048 def tagtype(self, tagname):
2049 """
2049 """
2050 return the type of the given tag. result can be:
2050 return the type of the given tag. result can be:
2051
2051
2052 'local' : a local tag
2052 'local' : a local tag
2053 'global' : a global tag
2053 'global' : a global tag
2054 None : tag does not exist
2054 None : tag does not exist
2055 """
2055 """
2056
2056
2057 return self._tagscache.tagtypes.get(tagname)
2057 return self._tagscache.tagtypes.get(tagname)
2058
2058
2059 def tagslist(self):
2059 def tagslist(self):
2060 '''return a list of tags ordered by revision'''
2060 '''return a list of tags ordered by revision'''
2061 if not self._tagscache.tagslist:
2061 if not self._tagscache.tagslist:
2062 l = []
2062 l = []
2063 for t, n in pycompat.iteritems(self.tags()):
2063 for t, n in pycompat.iteritems(self.tags()):
2064 l.append((self.changelog.rev(n), t, n))
2064 l.append((self.changelog.rev(n), t, n))
2065 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
2065 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
2066
2066
2067 return self._tagscache.tagslist
2067 return self._tagscache.tagslist
2068
2068
2069 def nodetags(self, node):
2069 def nodetags(self, node):
2070 '''return the tags associated with a node'''
2070 '''return the tags associated with a node'''
2071 if not self._tagscache.nodetagscache:
2071 if not self._tagscache.nodetagscache:
2072 nodetagscache = {}
2072 nodetagscache = {}
2073 for t, n in pycompat.iteritems(self._tagscache.tags):
2073 for t, n in pycompat.iteritems(self._tagscache.tags):
2074 nodetagscache.setdefault(n, []).append(t)
2074 nodetagscache.setdefault(n, []).append(t)
2075 for tags in pycompat.itervalues(nodetagscache):
2075 for tags in pycompat.itervalues(nodetagscache):
2076 tags.sort()
2076 tags.sort()
2077 self._tagscache.nodetagscache = nodetagscache
2077 self._tagscache.nodetagscache = nodetagscache
2078 return self._tagscache.nodetagscache.get(node, [])
2078 return self._tagscache.nodetagscache.get(node, [])
2079
2079
2080 def nodebookmarks(self, node):
2080 def nodebookmarks(self, node):
2081 """return the list of bookmarks pointing to the specified node"""
2081 """return the list of bookmarks pointing to the specified node"""
2082 return self._bookmarks.names(node)
2082 return self._bookmarks.names(node)
2083
2083
2084 def branchmap(self):
2084 def branchmap(self):
2085 """returns a dictionary {branch: [branchheads]} with branchheads
2085 """returns a dictionary {branch: [branchheads]} with branchheads
2086 ordered by increasing revision number"""
2086 ordered by increasing revision number"""
2087 return self._branchcaches[self]
2087 return self._branchcaches[self]
2088
2088
2089 @unfilteredmethod
2089 @unfilteredmethod
2090 def revbranchcache(self):
2090 def revbranchcache(self):
2091 if not self._revbranchcache:
2091 if not self._revbranchcache:
2092 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
2092 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
2093 return self._revbranchcache
2093 return self._revbranchcache
2094
2094
2095 def register_changeset(self, rev, changelogrevision):
2095 def register_changeset(self, rev, changelogrevision):
2096 self.revbranchcache().setdata(rev, changelogrevision)
2096 self.revbranchcache().setdata(rev, changelogrevision)
2097
2097
2098 def branchtip(self, branch, ignoremissing=False):
2098 def branchtip(self, branch, ignoremissing=False):
2099 """return the tip node for a given branch
2099 """return the tip node for a given branch
2100
2100
2101 If ignoremissing is True, then this method will not raise an error.
2101 If ignoremissing is True, then this method will not raise an error.
2102 This is helpful for callers that only expect None for a missing branch
2102 This is helpful for callers that only expect None for a missing branch
2103 (e.g. namespace).
2103 (e.g. namespace).
2104
2104
2105 """
2105 """
2106 try:
2106 try:
2107 return self.branchmap().branchtip(branch)
2107 return self.branchmap().branchtip(branch)
2108 except KeyError:
2108 except KeyError:
2109 if not ignoremissing:
2109 if not ignoremissing:
2110 raise error.RepoLookupError(_(b"unknown branch '%s'") % branch)
2110 raise error.RepoLookupError(_(b"unknown branch '%s'") % branch)
2111 else:
2111 else:
2112 pass
2112 pass
2113
2113
2114 def lookup(self, key):
2114 def lookup(self, key):
2115 node = scmutil.revsymbol(self, key).node()
2115 node = scmutil.revsymbol(self, key).node()
2116 if node is None:
2116 if node is None:
2117 raise error.RepoLookupError(_(b"unknown revision '%s'") % key)
2117 raise error.RepoLookupError(_(b"unknown revision '%s'") % key)
2118 return node
2118 return node
2119
2119
2120 def lookupbranch(self, key):
2120 def lookupbranch(self, key):
2121 if self.branchmap().hasbranch(key):
2121 if self.branchmap().hasbranch(key):
2122 return key
2122 return key
2123
2123
2124 return scmutil.revsymbol(self, key).branch()
2124 return scmutil.revsymbol(self, key).branch()
2125
2125
2126 def known(self, nodes):
2126 def known(self, nodes):
2127 cl = self.changelog
2127 cl = self.changelog
2128 get_rev = cl.index.get_rev
2128 get_rev = cl.index.get_rev
2129 filtered = cl.filteredrevs
2129 filtered = cl.filteredrevs
2130 result = []
2130 result = []
2131 for n in nodes:
2131 for n in nodes:
2132 r = get_rev(n)
2132 r = get_rev(n)
2133 resp = not (r is None or r in filtered)
2133 resp = not (r is None or r in filtered)
2134 result.append(resp)
2134 result.append(resp)
2135 return result
2135 return result
2136
2136
2137 def local(self):
2137 def local(self):
2138 return self
2138 return self
2139
2139
2140 def publishing(self):
2140 def publishing(self):
2141 # it's safe (and desirable) to trust the publish flag unconditionally
2141 # it's safe (and desirable) to trust the publish flag unconditionally
2142 # so that we don't finalize changes shared between users via ssh or nfs
2142 # so that we don't finalize changes shared between users via ssh or nfs
2143 return self.ui.configbool(b'phases', b'publish', untrusted=True)
2143 return self.ui.configbool(b'phases', b'publish', untrusted=True)
2144
2144
2145 def cancopy(self):
2145 def cancopy(self):
2146 # so statichttprepo's override of local() works
2146 # so statichttprepo's override of local() works
2147 if not self.local():
2147 if not self.local():
2148 return False
2148 return False
2149 if not self.publishing():
2149 if not self.publishing():
2150 return True
2150 return True
2151 # if publishing we can't copy if there is filtered content
2151 # if publishing we can't copy if there is filtered content
2152 return not self.filtered(b'visible').changelog.filteredrevs
2152 return not self.filtered(b'visible').changelog.filteredrevs
2153
2153
2154 def shared(self):
2154 def shared(self):
2155 '''the type of shared repository (None if not shared)'''
2155 '''the type of shared repository (None if not shared)'''
2156 if self.sharedpath != self.path:
2156 if self.sharedpath != self.path:
2157 return b'store'
2157 return b'store'
2158 return None
2158 return None
2159
2159
2160 def wjoin(self, f, *insidef):
2160 def wjoin(self, f, *insidef):
2161 return self.vfs.reljoin(self.root, f, *insidef)
2161 return self.vfs.reljoin(self.root, f, *insidef)
2162
2162
2163 def setparents(self, p1, p2=None):
2163 def setparents(self, p1, p2=None):
2164 if p2 is None:
2164 if p2 is None:
2165 p2 = self.nullid
2165 p2 = self.nullid
2166 self[None].setparents(p1, p2)
2166 self[None].setparents(p1, p2)
2167 self._quick_access_changeid_invalidate()
2167 self._quick_access_changeid_invalidate()
2168
2168
2169 def filectx(self, path, changeid=None, fileid=None, changectx=None):
2169 def filectx(self, path, changeid=None, fileid=None, changectx=None):
2170 """changeid must be a changeset revision, if specified.
2170 """changeid must be a changeset revision, if specified.
2171 fileid can be a file revision or node."""
2171 fileid can be a file revision or node."""
2172 return context.filectx(
2172 return context.filectx(
2173 self, path, changeid, fileid, changectx=changectx
2173 self, path, changeid, fileid, changectx=changectx
2174 )
2174 )
2175
2175
2176 def getcwd(self):
2176 def getcwd(self):
2177 return self.dirstate.getcwd()
2177 return self.dirstate.getcwd()
2178
2178
2179 def pathto(self, f, cwd=None):
2179 def pathto(self, f, cwd=None):
2180 return self.dirstate.pathto(f, cwd)
2180 return self.dirstate.pathto(f, cwd)
2181
2181
2182 def _loadfilter(self, filter):
2182 def _loadfilter(self, filter):
2183 if filter not in self._filterpats:
2183 if filter not in self._filterpats:
2184 l = []
2184 l = []
2185 for pat, cmd in self.ui.configitems(filter):
2185 for pat, cmd in self.ui.configitems(filter):
2186 if cmd == b'!':
2186 if cmd == b'!':
2187 continue
2187 continue
2188 mf = matchmod.match(self.root, b'', [pat])
2188 mf = matchmod.match(self.root, b'', [pat])
2189 fn = None
2189 fn = None
2190 params = cmd
2190 params = cmd
2191 for name, filterfn in pycompat.iteritems(self._datafilters):
2191 for name, filterfn in pycompat.iteritems(self._datafilters):
2192 if cmd.startswith(name):
2192 if cmd.startswith(name):
2193 fn = filterfn
2193 fn = filterfn
2194 params = cmd[len(name) :].lstrip()
2194 params = cmd[len(name) :].lstrip()
2195 break
2195 break
2196 if not fn:
2196 if not fn:
2197 fn = lambda s, c, **kwargs: procutil.filter(s, c)
2197 fn = lambda s, c, **kwargs: procutil.filter(s, c)
2198 fn.__name__ = 'commandfilter'
2198 fn.__name__ = 'commandfilter'
2199 # Wrap old filters not supporting keyword arguments
2199 # Wrap old filters not supporting keyword arguments
2200 if not pycompat.getargspec(fn)[2]:
2200 if not pycompat.getargspec(fn)[2]:
2201 oldfn = fn
2201 oldfn = fn
2202 fn = lambda s, c, oldfn=oldfn, **kwargs: oldfn(s, c)
2202 fn = lambda s, c, oldfn=oldfn, **kwargs: oldfn(s, c)
2203 fn.__name__ = 'compat-' + oldfn.__name__
2203 fn.__name__ = 'compat-' + oldfn.__name__
2204 l.append((mf, fn, params))
2204 l.append((mf, fn, params))
2205 self._filterpats[filter] = l
2205 self._filterpats[filter] = l
2206 return self._filterpats[filter]
2206 return self._filterpats[filter]
2207
2207
2208 def _filter(self, filterpats, filename, data):
2208 def _filter(self, filterpats, filename, data):
2209 for mf, fn, cmd in filterpats:
2209 for mf, fn, cmd in filterpats:
2210 if mf(filename):
2210 if mf(filename):
2211 self.ui.debug(
2211 self.ui.debug(
2212 b"filtering %s through %s\n"
2212 b"filtering %s through %s\n"
2213 % (filename, cmd or pycompat.sysbytes(fn.__name__))
2213 % (filename, cmd or pycompat.sysbytes(fn.__name__))
2214 )
2214 )
2215 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
2215 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
2216 break
2216 break
2217
2217
2218 return data
2218 return data
2219
2219
2220 @unfilteredpropertycache
2220 @unfilteredpropertycache
2221 def _encodefilterpats(self):
2221 def _encodefilterpats(self):
2222 return self._loadfilter(b'encode')
2222 return self._loadfilter(b'encode')
2223
2223
2224 @unfilteredpropertycache
2224 @unfilteredpropertycache
2225 def _decodefilterpats(self):
2225 def _decodefilterpats(self):
2226 return self._loadfilter(b'decode')
2226 return self._loadfilter(b'decode')
2227
2227
2228 def adddatafilter(self, name, filter):
2228 def adddatafilter(self, name, filter):
2229 self._datafilters[name] = filter
2229 self._datafilters[name] = filter
2230
2230
2231 def wread(self, filename):
2231 def wread(self, filename):
2232 if self.wvfs.islink(filename):
2232 if self.wvfs.islink(filename):
2233 data = self.wvfs.readlink(filename)
2233 data = self.wvfs.readlink(filename)
2234 else:
2234 else:
2235 data = self.wvfs.read(filename)
2235 data = self.wvfs.read(filename)
2236 return self._filter(self._encodefilterpats, filename, data)
2236 return self._filter(self._encodefilterpats, filename, data)
2237
2237
2238 def wwrite(self, filename, data, flags, backgroundclose=False, **kwargs):
2238 def wwrite(self, filename, data, flags, backgroundclose=False, **kwargs):
2239 """write ``data`` into ``filename`` in the working directory
2239 """write ``data`` into ``filename`` in the working directory
2240
2240
2241 This returns length of written (maybe decoded) data.
2241 This returns length of written (maybe decoded) data.
2242 """
2242 """
2243 data = self._filter(self._decodefilterpats, filename, data)
2243 data = self._filter(self._decodefilterpats, filename, data)
2244 if b'l' in flags:
2244 if b'l' in flags:
2245 self.wvfs.symlink(data, filename)
2245 self.wvfs.symlink(data, filename)
2246 else:
2246 else:
2247 self.wvfs.write(
2247 self.wvfs.write(
2248 filename, data, backgroundclose=backgroundclose, **kwargs
2248 filename, data, backgroundclose=backgroundclose, **kwargs
2249 )
2249 )
2250 if b'x' in flags:
2250 if b'x' in flags:
2251 self.wvfs.setflags(filename, False, True)
2251 self.wvfs.setflags(filename, False, True)
2252 else:
2252 else:
2253 self.wvfs.setflags(filename, False, False)
2253 self.wvfs.setflags(filename, False, False)
2254 return len(data)
2254 return len(data)
2255
2255
2256 def wwritedata(self, filename, data):
2256 def wwritedata(self, filename, data):
2257 return self._filter(self._decodefilterpats, filename, data)
2257 return self._filter(self._decodefilterpats, filename, data)
2258
2258
2259 def currenttransaction(self):
2259 def currenttransaction(self):
2260 """return the current transaction or None if non exists"""
2260 """return the current transaction or None if non exists"""
2261 if self._transref:
2261 if self._transref:
2262 tr = self._transref()
2262 tr = self._transref()
2263 else:
2263 else:
2264 tr = None
2264 tr = None
2265
2265
2266 if tr and tr.running():
2266 if tr and tr.running():
2267 return tr
2267 return tr
2268 return None
2268 return None
2269
2269
2270 def transaction(self, desc, report=None):
2270 def transaction(self, desc, report=None):
2271 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
2271 if self.ui.configbool(b'devel', b'all-warnings') or self.ui.configbool(
2272 b'devel', b'check-locks'
2272 b'devel', b'check-locks'
2273 ):
2273 ):
2274 if self._currentlock(self._lockref) is None:
2274 if self._currentlock(self._lockref) is None:
2275 raise error.ProgrammingError(b'transaction requires locking')
2275 raise error.ProgrammingError(b'transaction requires locking')
2276 tr = self.currenttransaction()
2276 tr = self.currenttransaction()
2277 if tr is not None:
2277 if tr is not None:
2278 return tr.nest(name=desc)
2278 return tr.nest(name=desc)
2279
2279
2280 # abort here if the journal already exists
2280 # abort here if the journal already exists
2281 if self.svfs.exists(b"journal"):
2281 if self.svfs.exists(b"journal"):
2282 raise error.RepoError(
2282 raise error.RepoError(
2283 _(b"abandoned transaction found"),
2283 _(b"abandoned transaction found"),
2284 hint=_(b"run 'hg recover' to clean up transaction"),
2284 hint=_(b"run 'hg recover' to clean up transaction"),
2285 )
2285 )
2286
2286
2287 idbase = b"%.40f#%f" % (random.random(), time.time())
2287 idbase = b"%.40f#%f" % (random.random(), time.time())
2288 ha = hex(hashutil.sha1(idbase).digest())
2288 ha = hex(hashutil.sha1(idbase).digest())
2289 txnid = b'TXN:' + ha
2289 txnid = b'TXN:' + ha
2290 self.hook(b'pretxnopen', throw=True, txnname=desc, txnid=txnid)
2290 self.hook(b'pretxnopen', throw=True, txnname=desc, txnid=txnid)
2291
2291
2292 self._writejournal(desc)
2292 self._writejournal(desc)
2293 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
2293 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
2294 if report:
2294 if report:
2295 rp = report
2295 rp = report
2296 else:
2296 else:
2297 rp = self.ui.warn
2297 rp = self.ui.warn
2298 vfsmap = {b'plain': self.vfs, b'store': self.svfs} # root of .hg/
2298 vfsmap = {b'plain': self.vfs, b'store': self.svfs} # root of .hg/
2299 # we must avoid cyclic reference between repo and transaction.
2299 # we must avoid cyclic reference between repo and transaction.
2300 reporef = weakref.ref(self)
2300 reporef = weakref.ref(self)
2301 # Code to track tag movement
2301 # Code to track tag movement
2302 #
2302 #
2303 # Since tags are all handled as file content, it is actually quite hard
2303 # Since tags are all handled as file content, it is actually quite hard
2304 # to track these movement from a code perspective. So we fallback to a
2304 # to track these movement from a code perspective. So we fallback to a
2305 # tracking at the repository level. One could envision to track changes
2305 # tracking at the repository level. One could envision to track changes
2306 # to the '.hgtags' file through changegroup apply but that fails to
2306 # to the '.hgtags' file through changegroup apply but that fails to
2307 # cope with case where transaction expose new heads without changegroup
2307 # cope with case where transaction expose new heads without changegroup
2308 # being involved (eg: phase movement).
2308 # being involved (eg: phase movement).
2309 #
2309 #
2310 # For now, We gate the feature behind a flag since this likely comes
2310 # For now, We gate the feature behind a flag since this likely comes
2311 # with performance impacts. The current code run more often than needed
2311 # with performance impacts. The current code run more often than needed
2312 # and do not use caches as much as it could. The current focus is on
2312 # and do not use caches as much as it could. The current focus is on
2313 # the behavior of the feature so we disable it by default. The flag
2313 # the behavior of the feature so we disable it by default. The flag
2314 # will be removed when we are happy with the performance impact.
2314 # will be removed when we are happy with the performance impact.
2315 #
2315 #
2316 # Once this feature is no longer experimental move the following
2316 # Once this feature is no longer experimental move the following
2317 # documentation to the appropriate help section:
2317 # documentation to the appropriate help section:
2318 #
2318 #
2319 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
2319 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
2320 # tags (new or changed or deleted tags). In addition the details of
2320 # tags (new or changed or deleted tags). In addition the details of
2321 # these changes are made available in a file at:
2321 # these changes are made available in a file at:
2322 # ``REPOROOT/.hg/changes/tags.changes``.
2322 # ``REPOROOT/.hg/changes/tags.changes``.
2323 # Make sure you check for HG_TAG_MOVED before reading that file as it
2323 # Make sure you check for HG_TAG_MOVED before reading that file as it
2324 # might exist from a previous transaction even if no tag were touched
2324 # might exist from a previous transaction even if no tag were touched
2325 # in this one. Changes are recorded in a line base format::
2325 # in this one. Changes are recorded in a line base format::
2326 #
2326 #
2327 # <action> <hex-node> <tag-name>\n
2327 # <action> <hex-node> <tag-name>\n
2328 #
2328 #
2329 # Actions are defined as follow:
2329 # Actions are defined as follow:
2330 # "-R": tag is removed,
2330 # "-R": tag is removed,
2331 # "+A": tag is added,
2331 # "+A": tag is added,
2332 # "-M": tag is moved (old value),
2332 # "-M": tag is moved (old value),
2333 # "+M": tag is moved (new value),
2333 # "+M": tag is moved (new value),
2334 tracktags = lambda x: None
2334 tracktags = lambda x: None
2335 # experimental config: experimental.hook-track-tags
2335 # experimental config: experimental.hook-track-tags
2336 shouldtracktags = self.ui.configbool(
2336 shouldtracktags = self.ui.configbool(
2337 b'experimental', b'hook-track-tags'
2337 b'experimental', b'hook-track-tags'
2338 )
2338 )
2339 if desc != b'strip' and shouldtracktags:
2339 if desc != b'strip' and shouldtracktags:
2340 oldheads = self.changelog.headrevs()
2340 oldheads = self.changelog.headrevs()
2341
2341
2342 def tracktags(tr2):
2342 def tracktags(tr2):
2343 repo = reporef()
2343 repo = reporef()
2344 assert repo is not None # help pytype
2344 assert repo is not None # help pytype
2345 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
2345 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
2346 newheads = repo.changelog.headrevs()
2346 newheads = repo.changelog.headrevs()
2347 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
2347 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
2348 # notes: we compare lists here.
2348 # notes: we compare lists here.
2349 # As we do it only once buiding set would not be cheaper
2349 # As we do it only once buiding set would not be cheaper
2350 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
2350 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
2351 if changes:
2351 if changes:
2352 tr2.hookargs[b'tag_moved'] = b'1'
2352 tr2.hookargs[b'tag_moved'] = b'1'
2353 with repo.vfs(
2353 with repo.vfs(
2354 b'changes/tags.changes', b'w', atomictemp=True
2354 b'changes/tags.changes', b'w', atomictemp=True
2355 ) as changesfile:
2355 ) as changesfile:
2356 # note: we do not register the file to the transaction
2356 # note: we do not register the file to the transaction
2357 # because we needs it to still exist on the transaction
2357 # because we needs it to still exist on the transaction
2358 # is close (for txnclose hooks)
2358 # is close (for txnclose hooks)
2359 tagsmod.writediff(changesfile, changes)
2359 tagsmod.writediff(changesfile, changes)
2360
2360
2361 def validate(tr2):
2361 def validate(tr2):
2362 """will run pre-closing hooks"""
2362 """will run pre-closing hooks"""
2363 # XXX the transaction API is a bit lacking here so we take a hacky
2363 # XXX the transaction API is a bit lacking here so we take a hacky
2364 # path for now
2364 # path for now
2365 #
2365 #
2366 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
2366 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
2367 # dict is copied before these run. In addition we needs the data
2367 # dict is copied before these run. In addition we needs the data
2368 # available to in memory hooks too.
2368 # available to in memory hooks too.
2369 #
2369 #
2370 # Moreover, we also need to make sure this runs before txnclose
2370 # Moreover, we also need to make sure this runs before txnclose
2371 # hooks and there is no "pending" mechanism that would execute
2371 # hooks and there is no "pending" mechanism that would execute
2372 # logic only if hooks are about to run.
2372 # logic only if hooks are about to run.
2373 #
2373 #
2374 # Fixing this limitation of the transaction is also needed to track
2374 # Fixing this limitation of the transaction is also needed to track
2375 # other families of changes (bookmarks, phases, obsolescence).
2375 # other families of changes (bookmarks, phases, obsolescence).
2376 #
2376 #
2377 # This will have to be fixed before we remove the experimental
2377 # This will have to be fixed before we remove the experimental
2378 # gating.
2378 # gating.
2379 tracktags(tr2)
2379 tracktags(tr2)
2380 repo = reporef()
2380 repo = reporef()
2381 assert repo is not None # help pytype
2381 assert repo is not None # help pytype
2382
2382
2383 singleheadopt = (b'experimental', b'single-head-per-branch')
2383 singleheadopt = (b'experimental', b'single-head-per-branch')
2384 singlehead = repo.ui.configbool(*singleheadopt)
2384 singlehead = repo.ui.configbool(*singleheadopt)
2385 if singlehead:
2385 if singlehead:
2386 singleheadsub = repo.ui.configsuboptions(*singleheadopt)[1]
2386 singleheadsub = repo.ui.configsuboptions(*singleheadopt)[1]
2387 accountclosed = singleheadsub.get(
2387 accountclosed = singleheadsub.get(
2388 b"account-closed-heads", False
2388 b"account-closed-heads", False
2389 )
2389 )
2390 if singleheadsub.get(b"public-changes-only", False):
2390 if singleheadsub.get(b"public-changes-only", False):
2391 filtername = b"immutable"
2391 filtername = b"immutable"
2392 else:
2392 else:
2393 filtername = b"visible"
2393 filtername = b"visible"
2394 scmutil.enforcesinglehead(
2394 scmutil.enforcesinglehead(
2395 repo, tr2, desc, accountclosed, filtername
2395 repo, tr2, desc, accountclosed, filtername
2396 )
2396 )
2397 if hook.hashook(repo.ui, b'pretxnclose-bookmark'):
2397 if hook.hashook(repo.ui, b'pretxnclose-bookmark'):
2398 for name, (old, new) in sorted(
2398 for name, (old, new) in sorted(
2399 tr.changes[b'bookmarks'].items()
2399 tr.changes[b'bookmarks'].items()
2400 ):
2400 ):
2401 args = tr.hookargs.copy()
2401 args = tr.hookargs.copy()
2402 args.update(bookmarks.preparehookargs(name, old, new))
2402 args.update(bookmarks.preparehookargs(name, old, new))
2403 repo.hook(
2403 repo.hook(
2404 b'pretxnclose-bookmark',
2404 b'pretxnclose-bookmark',
2405 throw=True,
2405 throw=True,
2406 **pycompat.strkwargs(args)
2406 **pycompat.strkwargs(args)
2407 )
2407 )
2408 if hook.hashook(repo.ui, b'pretxnclose-phase'):
2408 if hook.hashook(repo.ui, b'pretxnclose-phase'):
2409 cl = repo.unfiltered().changelog
2409 cl = repo.unfiltered().changelog
2410 for revs, (old, new) in tr.changes[b'phases']:
2410 for revs, (old, new) in tr.changes[b'phases']:
2411 for rev in revs:
2411 for rev in revs:
2412 args = tr.hookargs.copy()
2412 args = tr.hookargs.copy()
2413 node = hex(cl.node(rev))
2413 node = hex(cl.node(rev))
2414 args.update(phases.preparehookargs(node, old, new))
2414 args.update(phases.preparehookargs(node, old, new))
2415 repo.hook(
2415 repo.hook(
2416 b'pretxnclose-phase',
2416 b'pretxnclose-phase',
2417 throw=True,
2417 throw=True,
2418 **pycompat.strkwargs(args)
2418 **pycompat.strkwargs(args)
2419 )
2419 )
2420
2420
2421 repo.hook(
2421 repo.hook(
2422 b'pretxnclose', throw=True, **pycompat.strkwargs(tr.hookargs)
2422 b'pretxnclose', throw=True, **pycompat.strkwargs(tr.hookargs)
2423 )
2423 )
2424
2424
2425 def releasefn(tr, success):
2425 def releasefn(tr, success):
2426 repo = reporef()
2426 repo = reporef()
2427 if repo is None:
2427 if repo is None:
2428 # If the repo has been GC'd (and this release function is being
2428 # If the repo has been GC'd (and this release function is being
2429 # called from transaction.__del__), there's not much we can do,
2429 # called from transaction.__del__), there's not much we can do,
2430 # so just leave the unfinished transaction there and let the
2430 # so just leave the unfinished transaction there and let the
2431 # user run `hg recover`.
2431 # user run `hg recover`.
2432 return
2432 return
2433 if success:
2433 if success:
2434 # this should be explicitly invoked here, because
2434 # this should be explicitly invoked here, because
2435 # in-memory changes aren't written out at closing
2435 # in-memory changes aren't written out at closing
2436 # transaction, if tr.addfilegenerator (via
2436 # transaction, if tr.addfilegenerator (via
2437 # dirstate.write or so) isn't invoked while
2437 # dirstate.write or so) isn't invoked while
2438 # transaction running
2438 # transaction running
2439 repo.dirstate.write(None)
2439 repo.dirstate.write(None)
2440 else:
2440 else:
2441 # discard all changes (including ones already written
2441 # discard all changes (including ones already written
2442 # out) in this transaction
2442 # out) in this transaction
2443 narrowspec.restorebackup(self, b'journal.narrowspec')
2443 narrowspec.restorebackup(self, b'journal.narrowspec')
2444 narrowspec.restorewcbackup(self, b'journal.narrowspec.dirstate')
2444 narrowspec.restorewcbackup(self, b'journal.narrowspec.dirstate')
2445 repo.dirstate.restorebackup(None, b'journal.dirstate')
2445 repo.dirstate.restorebackup(None, b'journal.dirstate')
2446
2446
2447 repo.invalidate(clearfilecache=True)
2447 repo.invalidate(clearfilecache=True)
2448
2448
2449 tr = transaction.transaction(
2449 tr = transaction.transaction(
2450 rp,
2450 rp,
2451 self.svfs,
2451 self.svfs,
2452 vfsmap,
2452 vfsmap,
2453 b"journal",
2453 b"journal",
2454 b"undo",
2454 b"undo",
2455 aftertrans(renames),
2455 aftertrans(renames),
2456 self.store.createmode,
2456 self.store.createmode,
2457 validator=validate,
2457 validator=validate,
2458 releasefn=releasefn,
2458 releasefn=releasefn,
2459 checkambigfiles=_cachedfiles,
2459 checkambigfiles=_cachedfiles,
2460 name=desc,
2460 name=desc,
2461 )
2461 )
2462 tr.changes[b'origrepolen'] = len(self)
2462 tr.changes[b'origrepolen'] = len(self)
2463 tr.changes[b'obsmarkers'] = set()
2463 tr.changes[b'obsmarkers'] = set()
2464 tr.changes[b'phases'] = []
2464 tr.changes[b'phases'] = []
2465 tr.changes[b'bookmarks'] = {}
2465 tr.changes[b'bookmarks'] = {}
2466
2466
2467 tr.hookargs[b'txnid'] = txnid
2467 tr.hookargs[b'txnid'] = txnid
2468 tr.hookargs[b'txnname'] = desc
2468 tr.hookargs[b'txnname'] = desc
2469 tr.hookargs[b'changes'] = tr.changes
2469 tr.hookargs[b'changes'] = tr.changes
2470 # note: writing the fncache only during finalize mean that the file is
2470 # note: writing the fncache only during finalize mean that the file is
2471 # outdated when running hooks. As fncache is used for streaming clone,
2471 # outdated when running hooks. As fncache is used for streaming clone,
2472 # this is not expected to break anything that happen during the hooks.
2472 # this is not expected to break anything that happen during the hooks.
2473 tr.addfinalize(b'flush-fncache', self.store.write)
2473 tr.addfinalize(b'flush-fncache', self.store.write)
2474
2474
2475 def txnclosehook(tr2):
2475 def txnclosehook(tr2):
2476 """To be run if transaction is successful, will schedule a hook run"""
2476 """To be run if transaction is successful, will schedule a hook run"""
2477 # Don't reference tr2 in hook() so we don't hold a reference.
2477 # Don't reference tr2 in hook() so we don't hold a reference.
2478 # This reduces memory consumption when there are multiple
2478 # This reduces memory consumption when there are multiple
2479 # transactions per lock. This can likely go away if issue5045
2479 # transactions per lock. This can likely go away if issue5045
2480 # fixes the function accumulation.
2480 # fixes the function accumulation.
2481 hookargs = tr2.hookargs
2481 hookargs = tr2.hookargs
2482
2482
2483 def hookfunc(unused_success):
2483 def hookfunc(unused_success):
2484 repo = reporef()
2484 repo = reporef()
2485 assert repo is not None # help pytype
2485 assert repo is not None # help pytype
2486
2486
2487 if hook.hashook(repo.ui, b'txnclose-bookmark'):
2487 if hook.hashook(repo.ui, b'txnclose-bookmark'):
2488 bmchanges = sorted(tr.changes[b'bookmarks'].items())
2488 bmchanges = sorted(tr.changes[b'bookmarks'].items())
2489 for name, (old, new) in bmchanges:
2489 for name, (old, new) in bmchanges:
2490 args = tr.hookargs.copy()
2490 args = tr.hookargs.copy()
2491 args.update(bookmarks.preparehookargs(name, old, new))
2491 args.update(bookmarks.preparehookargs(name, old, new))
2492 repo.hook(
2492 repo.hook(
2493 b'txnclose-bookmark',
2493 b'txnclose-bookmark',
2494 throw=False,
2494 throw=False,
2495 **pycompat.strkwargs(args)
2495 **pycompat.strkwargs(args)
2496 )
2496 )
2497
2497
2498 if hook.hashook(repo.ui, b'txnclose-phase'):
2498 if hook.hashook(repo.ui, b'txnclose-phase'):
2499 cl = repo.unfiltered().changelog
2499 cl = repo.unfiltered().changelog
2500 phasemv = sorted(
2500 phasemv = sorted(
2501 tr.changes[b'phases'], key=lambda r: r[0][0]
2501 tr.changes[b'phases'], key=lambda r: r[0][0]
2502 )
2502 )
2503 for revs, (old, new) in phasemv:
2503 for revs, (old, new) in phasemv:
2504 for rev in revs:
2504 for rev in revs:
2505 args = tr.hookargs.copy()
2505 args = tr.hookargs.copy()
2506 node = hex(cl.node(rev))
2506 node = hex(cl.node(rev))
2507 args.update(phases.preparehookargs(node, old, new))
2507 args.update(phases.preparehookargs(node, old, new))
2508 repo.hook(
2508 repo.hook(
2509 b'txnclose-phase',
2509 b'txnclose-phase',
2510 throw=False,
2510 throw=False,
2511 **pycompat.strkwargs(args)
2511 **pycompat.strkwargs(args)
2512 )
2512 )
2513
2513
2514 repo.hook(
2514 repo.hook(
2515 b'txnclose', throw=False, **pycompat.strkwargs(hookargs)
2515 b'txnclose', throw=False, **pycompat.strkwargs(hookargs)
2516 )
2516 )
2517
2517
2518 repo = reporef()
2518 repo = reporef()
2519 assert repo is not None # help pytype
2519 assert repo is not None # help pytype
2520 repo._afterlock(hookfunc)
2520 repo._afterlock(hookfunc)
2521
2521
2522 tr.addfinalize(b'txnclose-hook', txnclosehook)
2522 tr.addfinalize(b'txnclose-hook', txnclosehook)
2523 # Include a leading "-" to make it happen before the transaction summary
2523 # Include a leading "-" to make it happen before the transaction summary
2524 # reports registered via scmutil.registersummarycallback() whose names
2524 # reports registered via scmutil.registersummarycallback() whose names
2525 # are 00-txnreport etc. That way, the caches will be warm when the
2525 # are 00-txnreport etc. That way, the caches will be warm when the
2526 # callbacks run.
2526 # callbacks run.
2527 tr.addpostclose(b'-warm-cache', self._buildcacheupdater(tr))
2527 tr.addpostclose(b'-warm-cache', self._buildcacheupdater(tr))
2528
2528
2529 def txnaborthook(tr2):
2529 def txnaborthook(tr2):
2530 """To be run if transaction is aborted"""
2530 """To be run if transaction is aborted"""
2531 repo = reporef()
2531 repo = reporef()
2532 assert repo is not None # help pytype
2532 assert repo is not None # help pytype
2533 repo.hook(
2533 repo.hook(
2534 b'txnabort', throw=False, **pycompat.strkwargs(tr2.hookargs)
2534 b'txnabort', throw=False, **pycompat.strkwargs(tr2.hookargs)
2535 )
2535 )
2536
2536
2537 tr.addabort(b'txnabort-hook', txnaborthook)
2537 tr.addabort(b'txnabort-hook', txnaborthook)
2538 # avoid eager cache invalidation. in-memory data should be identical
2538 # avoid eager cache invalidation. in-memory data should be identical
2539 # to stored data if transaction has no error.
2539 # to stored data if transaction has no error.
2540 tr.addpostclose(b'refresh-filecachestats', self._refreshfilecachestats)
2540 tr.addpostclose(b'refresh-filecachestats', self._refreshfilecachestats)
2541 self._transref = weakref.ref(tr)
2541 self._transref = weakref.ref(tr)
2542 scmutil.registersummarycallback(self, tr, desc)
2542 scmutil.registersummarycallback(self, tr, desc)
2543 return tr
2543 return tr
2544
2544
2545 def _journalfiles(self):
2545 def _journalfiles(self):
2546 return (
2546 return (
2547 (self.svfs, b'journal'),
2547 (self.svfs, b'journal'),
2548 (self.svfs, b'journal.narrowspec'),
2548 (self.svfs, b'journal.narrowspec'),
2549 (self.vfs, b'journal.narrowspec.dirstate'),
2549 (self.vfs, b'journal.narrowspec.dirstate'),
2550 (self.vfs, b'journal.dirstate'),
2550 (self.vfs, b'journal.dirstate'),
2551 (self.vfs, b'journal.branch'),
2551 (self.vfs, b'journal.branch'),
2552 (self.vfs, b'journal.desc'),
2552 (self.vfs, b'journal.desc'),
2553 (bookmarks.bookmarksvfs(self), b'journal.bookmarks'),
2553 (bookmarks.bookmarksvfs(self), b'journal.bookmarks'),
2554 (self.svfs, b'journal.phaseroots'),
2554 (self.svfs, b'journal.phaseroots'),
2555 )
2555 )
2556
2556
2557 def undofiles(self):
2557 def undofiles(self):
2558 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
2558 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
2559
2559
2560 @unfilteredmethod
2560 @unfilteredmethod
2561 def _writejournal(self, desc):
2561 def _writejournal(self, desc):
2562 self.dirstate.savebackup(None, b'journal.dirstate')
2562 self.dirstate.savebackup(None, b'journal.dirstate')
2563 narrowspec.savewcbackup(self, b'journal.narrowspec.dirstate')
2563 narrowspec.savewcbackup(self, b'journal.narrowspec.dirstate')
2564 narrowspec.savebackup(self, b'journal.narrowspec')
2564 narrowspec.savebackup(self, b'journal.narrowspec')
2565 self.vfs.write(
2565 self.vfs.write(
2566 b"journal.branch", encoding.fromlocal(self.dirstate.branch())
2566 b"journal.branch", encoding.fromlocal(self.dirstate.branch())
2567 )
2567 )
2568 self.vfs.write(b"journal.desc", b"%d\n%s\n" % (len(self), desc))
2568 self.vfs.write(b"journal.desc", b"%d\n%s\n" % (len(self), desc))
2569 bookmarksvfs = bookmarks.bookmarksvfs(self)
2569 bookmarksvfs = bookmarks.bookmarksvfs(self)
2570 bookmarksvfs.write(
2570 bookmarksvfs.write(
2571 b"journal.bookmarks", bookmarksvfs.tryread(b"bookmarks")
2571 b"journal.bookmarks", bookmarksvfs.tryread(b"bookmarks")
2572 )
2572 )
2573 self.svfs.write(b"journal.phaseroots", self.svfs.tryread(b"phaseroots"))
2573 self.svfs.write(b"journal.phaseroots", self.svfs.tryread(b"phaseroots"))
2574
2574
2575 def recover(self):
2575 def recover(self):
2576 with self.lock():
2576 with self.lock():
2577 if self.svfs.exists(b"journal"):
2577 if self.svfs.exists(b"journal"):
2578 self.ui.status(_(b"rolling back interrupted transaction\n"))
2578 self.ui.status(_(b"rolling back interrupted transaction\n"))
2579 vfsmap = {
2579 vfsmap = {
2580 b'': self.svfs,
2580 b'': self.svfs,
2581 b'plain': self.vfs,
2581 b'plain': self.vfs,
2582 }
2582 }
2583 transaction.rollback(
2583 transaction.rollback(
2584 self.svfs,
2584 self.svfs,
2585 vfsmap,
2585 vfsmap,
2586 b"journal",
2586 b"journal",
2587 self.ui.warn,
2587 self.ui.warn,
2588 checkambigfiles=_cachedfiles,
2588 checkambigfiles=_cachedfiles,
2589 )
2589 )
2590 self.invalidate()
2590 self.invalidate()
2591 return True
2591 return True
2592 else:
2592 else:
2593 self.ui.warn(_(b"no interrupted transaction available\n"))
2593 self.ui.warn(_(b"no interrupted transaction available\n"))
2594 return False
2594 return False
2595
2595
2596 def rollback(self, dryrun=False, force=False):
2596 def rollback(self, dryrun=False, force=False):
2597 wlock = lock = dsguard = None
2597 wlock = lock = dsguard = None
2598 try:
2598 try:
2599 wlock = self.wlock()
2599 wlock = self.wlock()
2600 lock = self.lock()
2600 lock = self.lock()
2601 if self.svfs.exists(b"undo"):
2601 if self.svfs.exists(b"undo"):
2602 dsguard = dirstateguard.dirstateguard(self, b'rollback')
2602 dsguard = dirstateguard.dirstateguard(self, b'rollback')
2603
2603
2604 return self._rollback(dryrun, force, dsguard)
2604 return self._rollback(dryrun, force, dsguard)
2605 else:
2605 else:
2606 self.ui.warn(_(b"no rollback information available\n"))
2606 self.ui.warn(_(b"no rollback information available\n"))
2607 return 1
2607 return 1
2608 finally:
2608 finally:
2609 release(dsguard, lock, wlock)
2609 release(dsguard, lock, wlock)
2610
2610
2611 @unfilteredmethod # Until we get smarter cache management
2611 @unfilteredmethod # Until we get smarter cache management
2612 def _rollback(self, dryrun, force, dsguard):
2612 def _rollback(self, dryrun, force, dsguard):
2613 ui = self.ui
2613 ui = self.ui
2614 try:
2614 try:
2615 args = self.vfs.read(b'undo.desc').splitlines()
2615 args = self.vfs.read(b'undo.desc').splitlines()
2616 (oldlen, desc, detail) = (int(args[0]), args[1], None)
2616 (oldlen, desc, detail) = (int(args[0]), args[1], None)
2617 if len(args) >= 3:
2617 if len(args) >= 3:
2618 detail = args[2]
2618 detail = args[2]
2619 oldtip = oldlen - 1
2619 oldtip = oldlen - 1
2620
2620
2621 if detail and ui.verbose:
2621 if detail and ui.verbose:
2622 msg = _(
2622 msg = _(
2623 b'repository tip rolled back to revision %d'
2623 b'repository tip rolled back to revision %d'
2624 b' (undo %s: %s)\n'
2624 b' (undo %s: %s)\n'
2625 ) % (oldtip, desc, detail)
2625 ) % (oldtip, desc, detail)
2626 else:
2626 else:
2627 msg = _(
2627 msg = _(
2628 b'repository tip rolled back to revision %d (undo %s)\n'
2628 b'repository tip rolled back to revision %d (undo %s)\n'
2629 ) % (oldtip, desc)
2629 ) % (oldtip, desc)
2630 except IOError:
2630 except IOError:
2631 msg = _(b'rolling back unknown transaction\n')
2631 msg = _(b'rolling back unknown transaction\n')
2632 desc = None
2632 desc = None
2633
2633
2634 if not force and self[b'.'] != self[b'tip'] and desc == b'commit':
2634 if not force and self[b'.'] != self[b'tip'] and desc == b'commit':
2635 raise error.Abort(
2635 raise error.Abort(
2636 _(
2636 _(
2637 b'rollback of last commit while not checked out '
2637 b'rollback of last commit while not checked out '
2638 b'may lose data'
2638 b'may lose data'
2639 ),
2639 ),
2640 hint=_(b'use -f to force'),
2640 hint=_(b'use -f to force'),
2641 )
2641 )
2642
2642
2643 ui.status(msg)
2643 ui.status(msg)
2644 if dryrun:
2644 if dryrun:
2645 return 0
2645 return 0
2646
2646
2647 parents = self.dirstate.parents()
2647 parents = self.dirstate.parents()
2648 self.destroying()
2648 self.destroying()
2649 vfsmap = {b'plain': self.vfs, b'': self.svfs}
2649 vfsmap = {b'plain': self.vfs, b'': self.svfs}
2650 transaction.rollback(
2650 transaction.rollback(
2651 self.svfs, vfsmap, b'undo', ui.warn, checkambigfiles=_cachedfiles
2651 self.svfs, vfsmap, b'undo', ui.warn, checkambigfiles=_cachedfiles
2652 )
2652 )
2653 bookmarksvfs = bookmarks.bookmarksvfs(self)
2653 bookmarksvfs = bookmarks.bookmarksvfs(self)
2654 if bookmarksvfs.exists(b'undo.bookmarks'):
2654 if bookmarksvfs.exists(b'undo.bookmarks'):
2655 bookmarksvfs.rename(
2655 bookmarksvfs.rename(
2656 b'undo.bookmarks', b'bookmarks', checkambig=True
2656 b'undo.bookmarks', b'bookmarks', checkambig=True
2657 )
2657 )
2658 if self.svfs.exists(b'undo.phaseroots'):
2658 if self.svfs.exists(b'undo.phaseroots'):
2659 self.svfs.rename(b'undo.phaseroots', b'phaseroots', checkambig=True)
2659 self.svfs.rename(b'undo.phaseroots', b'phaseroots', checkambig=True)
2660 self.invalidate()
2660 self.invalidate()
2661
2661
2662 has_node = self.changelog.index.has_node
2662 has_node = self.changelog.index.has_node
2663 parentgone = any(not has_node(p) for p in parents)
2663 parentgone = any(not has_node(p) for p in parents)
2664 if parentgone:
2664 if parentgone:
2665 # prevent dirstateguard from overwriting already restored one
2665 # prevent dirstateguard from overwriting already restored one
2666 dsguard.close()
2666 dsguard.close()
2667
2667
2668 narrowspec.restorebackup(self, b'undo.narrowspec')
2668 narrowspec.restorebackup(self, b'undo.narrowspec')
2669 narrowspec.restorewcbackup(self, b'undo.narrowspec.dirstate')
2669 narrowspec.restorewcbackup(self, b'undo.narrowspec.dirstate')
2670 self.dirstate.restorebackup(None, b'undo.dirstate')
2670 self.dirstate.restorebackup(None, b'undo.dirstate')
2671 try:
2671 try:
2672 branch = self.vfs.read(b'undo.branch')
2672 branch = self.vfs.read(b'undo.branch')
2673 self.dirstate.setbranch(encoding.tolocal(branch))
2673 self.dirstate.setbranch(encoding.tolocal(branch))
2674 except IOError:
2674 except IOError:
2675 ui.warn(
2675 ui.warn(
2676 _(
2676 _(
2677 b'named branch could not be reset: '
2677 b'named branch could not be reset: '
2678 b'current branch is still \'%s\'\n'
2678 b'current branch is still \'%s\'\n'
2679 )
2679 )
2680 % self.dirstate.branch()
2680 % self.dirstate.branch()
2681 )
2681 )
2682
2682
2683 parents = tuple([p.rev() for p in self[None].parents()])
2683 parents = tuple([p.rev() for p in self[None].parents()])
2684 if len(parents) > 1:
2684 if len(parents) > 1:
2685 ui.status(
2685 ui.status(
2686 _(
2686 _(
2687 b'working directory now based on '
2687 b'working directory now based on '
2688 b'revisions %d and %d\n'
2688 b'revisions %d and %d\n'
2689 )
2689 )
2690 % parents
2690 % parents
2691 )
2691 )
2692 else:
2692 else:
2693 ui.status(
2693 ui.status(
2694 _(b'working directory now based on revision %d\n') % parents
2694 _(b'working directory now based on revision %d\n') % parents
2695 )
2695 )
2696 mergestatemod.mergestate.clean(self)
2696 mergestatemod.mergestate.clean(self)
2697
2697
2698 # TODO: if we know which new heads may result from this rollback, pass
2698 # TODO: if we know which new heads may result from this rollback, pass
2699 # them to destroy(), which will prevent the branchhead cache from being
2699 # them to destroy(), which will prevent the branchhead cache from being
2700 # invalidated.
2700 # invalidated.
2701 self.destroyed()
2701 self.destroyed()
2702 return 0
2702 return 0
2703
2703
2704 def _buildcacheupdater(self, newtransaction):
2704 def _buildcacheupdater(self, newtransaction):
2705 """called during transaction to build the callback updating cache
2705 """called during transaction to build the callback updating cache
2706
2706
2707 Lives on the repository to help extension who might want to augment
2707 Lives on the repository to help extension who might want to augment
2708 this logic. For this purpose, the created transaction is passed to the
2708 this logic. For this purpose, the created transaction is passed to the
2709 method.
2709 method.
2710 """
2710 """
2711 # we must avoid cyclic reference between repo and transaction.
2711 # we must avoid cyclic reference between repo and transaction.
2712 reporef = weakref.ref(self)
2712 reporef = weakref.ref(self)
2713
2713
2714 def updater(tr):
2714 def updater(tr):
2715 repo = reporef()
2715 repo = reporef()
2716 assert repo is not None # help pytype
2716 assert repo is not None # help pytype
2717 repo.updatecaches(tr)
2717 repo.updatecaches(tr)
2718
2718
2719 return updater
2719 return updater
2720
2720
2721 @unfilteredmethod
2721 @unfilteredmethod
2722 def updatecaches(self, tr=None, full=False):
2722 def updatecaches(self, tr=None, full=False):
2723 """warm appropriate caches
2723 """warm appropriate caches
2724
2724
2725 If this function is called after a transaction closed. The transaction
2725 If this function is called after a transaction closed. The transaction
2726 will be available in the 'tr' argument. This can be used to selectively
2726 will be available in the 'tr' argument. This can be used to selectively
2727 update caches relevant to the changes in that transaction.
2727 update caches relevant to the changes in that transaction.
2728
2728
2729 If 'full' is set, make sure all caches the function knows about have
2729 If 'full' is set, make sure all caches the function knows about have
2730 up-to-date data. Even the ones usually loaded more lazily.
2730 up-to-date data. Even the ones usually loaded more lazily.
2731 """
2731 """
2732 if tr is not None and tr.hookargs.get(b'source') == b'strip':
2732 if tr is not None and tr.hookargs.get(b'source') == b'strip':
2733 # During strip, many caches are invalid but
2733 # During strip, many caches are invalid but
2734 # later call to `destroyed` will refresh them.
2734 # later call to `destroyed` will refresh them.
2735 return
2735 return
2736
2736
2737 if tr is None or tr.changes[b'origrepolen'] < len(self):
2737 if tr is None or tr.changes[b'origrepolen'] < len(self):
2738 # accessing the 'served' branchmap should refresh all the others,
2738 # accessing the 'served' branchmap should refresh all the others,
2739 self.ui.debug(b'updating the branch cache\n')
2739 self.ui.debug(b'updating the branch cache\n')
2740 self.filtered(b'served').branchmap()
2740 self.filtered(b'served').branchmap()
2741 self.filtered(b'served.hidden').branchmap()
2741 self.filtered(b'served.hidden').branchmap()
2742
2742
2743 if full:
2743 if full:
2744 unfi = self.unfiltered()
2744 unfi = self.unfiltered()
2745
2745
2746 self.changelog.update_caches(transaction=tr)
2746 self.changelog.update_caches(transaction=tr)
2747 self.manifestlog.update_caches(transaction=tr)
2747 self.manifestlog.update_caches(transaction=tr)
2748
2748
2749 rbc = unfi.revbranchcache()
2749 rbc = unfi.revbranchcache()
2750 for r in unfi.changelog:
2750 for r in unfi.changelog:
2751 rbc.branchinfo(r)
2751 rbc.branchinfo(r)
2752 rbc.write()
2752 rbc.write()
2753
2753
2754 # ensure the working copy parents are in the manifestfulltextcache
2754 # ensure the working copy parents are in the manifestfulltextcache
2755 for ctx in self[b'.'].parents():
2755 for ctx in self[b'.'].parents():
2756 ctx.manifest() # accessing the manifest is enough
2756 ctx.manifest() # accessing the manifest is enough
2757
2757
2758 # accessing fnode cache warms the cache
2758 # accessing fnode cache warms the cache
2759 tagsmod.fnoderevs(self.ui, unfi, unfi.changelog.revs())
2759 tagsmod.fnoderevs(self.ui, unfi, unfi.changelog.revs())
2760 # accessing tags warm the cache
2760 # accessing tags warm the cache
2761 self.tags()
2761 self.tags()
2762 self.filtered(b'served').tags()
2762 self.filtered(b'served').tags()
2763
2763
2764 # The `full` arg is documented as updating even the lazily-loaded
2764 # The `full` arg is documented as updating even the lazily-loaded
2765 # caches immediately, so we're forcing a write to cause these caches
2765 # caches immediately, so we're forcing a write to cause these caches
2766 # to be warmed up even if they haven't explicitly been requested
2766 # to be warmed up even if they haven't explicitly been requested
2767 # yet (if they've never been used by hg, they won't ever have been
2767 # yet (if they've never been used by hg, they won't ever have been
2768 # written, even if they're a subset of another kind of cache that
2768 # written, even if they're a subset of another kind of cache that
2769 # *has* been used).
2769 # *has* been used).
2770 for filt in repoview.filtertable.keys():
2770 for filt in repoview.filtertable.keys():
2771 filtered = self.filtered(filt)
2771 filtered = self.filtered(filt)
2772 filtered.branchmap().write(filtered)
2772 filtered.branchmap().write(filtered)
2773
2773
2774 def invalidatecaches(self):
2774 def invalidatecaches(self):
2775
2775
2776 if '_tagscache' in vars(self):
2776 if '_tagscache' in vars(self):
2777 # can't use delattr on proxy
2777 # can't use delattr on proxy
2778 del self.__dict__['_tagscache']
2778 del self.__dict__['_tagscache']
2779
2779
2780 self._branchcaches.clear()
2780 self._branchcaches.clear()
2781 self.invalidatevolatilesets()
2781 self.invalidatevolatilesets()
2782 self._sparsesignaturecache.clear()
2782 self._sparsesignaturecache.clear()
2783
2783
2784 def invalidatevolatilesets(self):
2784 def invalidatevolatilesets(self):
2785 self.filteredrevcache.clear()
2785 self.filteredrevcache.clear()
2786 obsolete.clearobscaches(self)
2786 obsolete.clearobscaches(self)
2787 self._quick_access_changeid_invalidate()
2787 self._quick_access_changeid_invalidate()
2788
2788
2789 def invalidatedirstate(self):
2789 def invalidatedirstate(self):
2790 """Invalidates the dirstate, causing the next call to dirstate
2790 """Invalidates the dirstate, causing the next call to dirstate
2791 to check if it was modified since the last time it was read,
2791 to check if it was modified since the last time it was read,
2792 rereading it if it has.
2792 rereading it if it has.
2793
2793
2794 This is different to dirstate.invalidate() that it doesn't always
2794 This is different to dirstate.invalidate() that it doesn't always
2795 rereads the dirstate. Use dirstate.invalidate() if you want to
2795 rereads the dirstate. Use dirstate.invalidate() if you want to
2796 explicitly read the dirstate again (i.e. restoring it to a previous
2796 explicitly read the dirstate again (i.e. restoring it to a previous
2797 known good state)."""
2797 known good state)."""
2798 if hasunfilteredcache(self, 'dirstate'):
2798 if hasunfilteredcache(self, 'dirstate'):
2799 for k in self.dirstate._filecache:
2799 for k in self.dirstate._filecache:
2800 try:
2800 try:
2801 delattr(self.dirstate, k)
2801 delattr(self.dirstate, k)
2802 except AttributeError:
2802 except AttributeError:
2803 pass
2803 pass
2804 delattr(self.unfiltered(), 'dirstate')
2804 delattr(self.unfiltered(), 'dirstate')
2805
2805
2806 def invalidate(self, clearfilecache=False):
2806 def invalidate(self, clearfilecache=False):
2807 """Invalidates both store and non-store parts other than dirstate
2807 """Invalidates both store and non-store parts other than dirstate
2808
2808
2809 If a transaction is running, invalidation of store is omitted,
2809 If a transaction is running, invalidation of store is omitted,
2810 because discarding in-memory changes might cause inconsistency
2810 because discarding in-memory changes might cause inconsistency
2811 (e.g. incomplete fncache causes unintentional failure, but
2811 (e.g. incomplete fncache causes unintentional failure, but
2812 redundant one doesn't).
2812 redundant one doesn't).
2813 """
2813 """
2814 unfiltered = self.unfiltered() # all file caches are stored unfiltered
2814 unfiltered = self.unfiltered() # all file caches are stored unfiltered
2815 for k in list(self._filecache.keys()):
2815 for k in list(self._filecache.keys()):
2816 # dirstate is invalidated separately in invalidatedirstate()
2816 # dirstate is invalidated separately in invalidatedirstate()
2817 if k == b'dirstate':
2817 if k == b'dirstate':
2818 continue
2818 continue
2819 if (
2819 if (
2820 k == b'changelog'
2820 k == b'changelog'
2821 and self.currenttransaction()
2821 and self.currenttransaction()
2822 and self.changelog._delayed
2822 and self.changelog._delayed
2823 ):
2823 ):
2824 # The changelog object may store unwritten revisions. We don't
2824 # The changelog object may store unwritten revisions. We don't
2825 # want to lose them.
2825 # want to lose them.
2826 # TODO: Solve the problem instead of working around it.
2826 # TODO: Solve the problem instead of working around it.
2827 continue
2827 continue
2828
2828
2829 if clearfilecache:
2829 if clearfilecache:
2830 del self._filecache[k]
2830 del self._filecache[k]
2831 try:
2831 try:
2832 delattr(unfiltered, k)
2832 delattr(unfiltered, k)
2833 except AttributeError:
2833 except AttributeError:
2834 pass
2834 pass
2835 self.invalidatecaches()
2835 self.invalidatecaches()
2836 if not self.currenttransaction():
2836 if not self.currenttransaction():
2837 # TODO: Changing contents of store outside transaction
2837 # TODO: Changing contents of store outside transaction
2838 # causes inconsistency. We should make in-memory store
2838 # causes inconsistency. We should make in-memory store
2839 # changes detectable, and abort if changed.
2839 # changes detectable, and abort if changed.
2840 self.store.invalidatecaches()
2840 self.store.invalidatecaches()
2841
2841
2842 def invalidateall(self):
2842 def invalidateall(self):
2843 """Fully invalidates both store and non-store parts, causing the
2843 """Fully invalidates both store and non-store parts, causing the
2844 subsequent operation to reread any outside changes."""
2844 subsequent operation to reread any outside changes."""
2845 # extension should hook this to invalidate its caches
2845 # extension should hook this to invalidate its caches
2846 self.invalidate()
2846 self.invalidate()
2847 self.invalidatedirstate()
2847 self.invalidatedirstate()
2848
2848
2849 @unfilteredmethod
2849 @unfilteredmethod
2850 def _refreshfilecachestats(self, tr):
2850 def _refreshfilecachestats(self, tr):
2851 """Reload stats of cached files so that they are flagged as valid"""
2851 """Reload stats of cached files so that they are flagged as valid"""
2852 for k, ce in self._filecache.items():
2852 for k, ce in self._filecache.items():
2853 k = pycompat.sysstr(k)
2853 k = pycompat.sysstr(k)
2854 if k == 'dirstate' or k not in self.__dict__:
2854 if k == 'dirstate' or k not in self.__dict__:
2855 continue
2855 continue
2856 ce.refresh()
2856 ce.refresh()
2857
2857
2858 def _lock(
2858 def _lock(
2859 self,
2859 self,
2860 vfs,
2860 vfs,
2861 lockname,
2861 lockname,
2862 wait,
2862 wait,
2863 releasefn,
2863 releasefn,
2864 acquirefn,
2864 acquirefn,
2865 desc,
2865 desc,
2866 ):
2866 ):
2867 timeout = 0
2867 timeout = 0
2868 warntimeout = 0
2868 warntimeout = 0
2869 if wait:
2869 if wait:
2870 timeout = self.ui.configint(b"ui", b"timeout")
2870 timeout = self.ui.configint(b"ui", b"timeout")
2871 warntimeout = self.ui.configint(b"ui", b"timeout.warn")
2871 warntimeout = self.ui.configint(b"ui", b"timeout.warn")
2872 # internal config: ui.signal-safe-lock
2872 # internal config: ui.signal-safe-lock
2873 signalsafe = self.ui.configbool(b'ui', b'signal-safe-lock')
2873 signalsafe = self.ui.configbool(b'ui', b'signal-safe-lock')
2874
2874
2875 l = lockmod.trylock(
2875 l = lockmod.trylock(
2876 self.ui,
2876 self.ui,
2877 vfs,
2877 vfs,
2878 lockname,
2878 lockname,
2879 timeout,
2879 timeout,
2880 warntimeout,
2880 warntimeout,
2881 releasefn=releasefn,
2881 releasefn=releasefn,
2882 acquirefn=acquirefn,
2882 acquirefn=acquirefn,
2883 desc=desc,
2883 desc=desc,
2884 signalsafe=signalsafe,
2884 signalsafe=signalsafe,
2885 )
2885 )
2886 return l
2886 return l
2887
2887
2888 def _afterlock(self, callback):
2888 def _afterlock(self, callback):
2889 """add a callback to be run when the repository is fully unlocked
2889 """add a callback to be run when the repository is fully unlocked
2890
2890
2891 The callback will be executed when the outermost lock is released
2891 The callback will be executed when the outermost lock is released
2892 (with wlock being higher level than 'lock')."""
2892 (with wlock being higher level than 'lock')."""
2893 for ref in (self._wlockref, self._lockref):
2893 for ref in (self._wlockref, self._lockref):
2894 l = ref and ref()
2894 l = ref and ref()
2895 if l and l.held:
2895 if l and l.held:
2896 l.postrelease.append(callback)
2896 l.postrelease.append(callback)
2897 break
2897 break
2898 else: # no lock have been found.
2898 else: # no lock have been found.
2899 callback(True)
2899 callback(True)
2900
2900
2901 def lock(self, wait=True):
2901 def lock(self, wait=True):
2902 """Lock the repository store (.hg/store) and return a weak reference
2902 """Lock the repository store (.hg/store) and return a weak reference
2903 to the lock. Use this before modifying the store (e.g. committing or
2903 to the lock. Use this before modifying the store (e.g. committing or
2904 stripping). If you are opening a transaction, get a lock as well.)
2904 stripping). If you are opening a transaction, get a lock as well.)
2905
2905
2906 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2906 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2907 'wlock' first to avoid a dead-lock hazard."""
2907 'wlock' first to avoid a dead-lock hazard."""
2908 l = self._currentlock(self._lockref)
2908 l = self._currentlock(self._lockref)
2909 if l is not None:
2909 if l is not None:
2910 l.lock()
2910 l.lock()
2911 return l
2911 return l
2912
2912
2913 l = self._lock(
2913 l = self._lock(
2914 vfs=self.svfs,
2914 vfs=self.svfs,
2915 lockname=b"lock",
2915 lockname=b"lock",
2916 wait=wait,
2916 wait=wait,
2917 releasefn=None,
2917 releasefn=None,
2918 acquirefn=self.invalidate,
2918 acquirefn=self.invalidate,
2919 desc=_(b'repository %s') % self.origroot,
2919 desc=_(b'repository %s') % self.origroot,
2920 )
2920 )
2921 self._lockref = weakref.ref(l)
2921 self._lockref = weakref.ref(l)
2922 return l
2922 return l
2923
2923
2924 def wlock(self, wait=True):
2924 def wlock(self, wait=True):
2925 """Lock the non-store parts of the repository (everything under
2925 """Lock the non-store parts of the repository (everything under
2926 .hg except .hg/store) and return a weak reference to the lock.
2926 .hg except .hg/store) and return a weak reference to the lock.
2927
2927
2928 Use this before modifying files in .hg.
2928 Use this before modifying files in .hg.
2929
2929
2930 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2930 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2931 'wlock' first to avoid a dead-lock hazard."""
2931 'wlock' first to avoid a dead-lock hazard."""
2932 l = self._wlockref() if self._wlockref else None
2932 l = self._wlockref() if self._wlockref else None
2933 if l is not None and l.held:
2933 if l is not None and l.held:
2934 l.lock()
2934 l.lock()
2935 return l
2935 return l
2936
2936
2937 # We do not need to check for non-waiting lock acquisition. Such
2937 # We do not need to check for non-waiting lock acquisition. Such
2938 # acquisition would not cause dead-lock as they would just fail.
2938 # acquisition would not cause dead-lock as they would just fail.
2939 if wait and (
2939 if wait and (
2940 self.ui.configbool(b'devel', b'all-warnings')
2940 self.ui.configbool(b'devel', b'all-warnings')
2941 or self.ui.configbool(b'devel', b'check-locks')
2941 or self.ui.configbool(b'devel', b'check-locks')
2942 ):
2942 ):
2943 if self._currentlock(self._lockref) is not None:
2943 if self._currentlock(self._lockref) is not None:
2944 self.ui.develwarn(b'"wlock" acquired after "lock"')
2944 self.ui.develwarn(b'"wlock" acquired after "lock"')
2945
2945
2946 def unlock():
2946 def unlock():
2947 if self.dirstate.pendingparentchange():
2947 if self.dirstate.pendingparentchange():
2948 self.dirstate.invalidate()
2948 self.dirstate.invalidate()
2949 else:
2949 else:
2950 self.dirstate.write(None)
2950 self.dirstate.write(None)
2951
2951
2952 self._filecache[b'dirstate'].refresh()
2952 self._filecache[b'dirstate'].refresh()
2953
2953
2954 l = self._lock(
2954 l = self._lock(
2955 self.vfs,
2955 self.vfs,
2956 b"wlock",
2956 b"wlock",
2957 wait,
2957 wait,
2958 unlock,
2958 unlock,
2959 self.invalidatedirstate,
2959 self.invalidatedirstate,
2960 _(b'working directory of %s') % self.origroot,
2960 _(b'working directory of %s') % self.origroot,
2961 )
2961 )
2962 self._wlockref = weakref.ref(l)
2962 self._wlockref = weakref.ref(l)
2963 return l
2963 return l
2964
2964
2965 def _currentlock(self, lockref):
2965 def _currentlock(self, lockref):
2966 """Returns the lock if it's held, or None if it's not."""
2966 """Returns the lock if it's held, or None if it's not."""
2967 if lockref is None:
2967 if lockref is None:
2968 return None
2968 return None
2969 l = lockref()
2969 l = lockref()
2970 if l is None or not l.held:
2970 if l is None or not l.held:
2971 return None
2971 return None
2972 return l
2972 return l
2973
2973
2974 def currentwlock(self):
2974 def currentwlock(self):
2975 """Returns the wlock if it's held, or None if it's not."""
2975 """Returns the wlock if it's held, or None if it's not."""
2976 return self._currentlock(self._wlockref)
2976 return self._currentlock(self._wlockref)
2977
2977
2978 def checkcommitpatterns(self, wctx, match, status, fail):
2978 def checkcommitpatterns(self, wctx, match, status, fail):
2979 """check for commit arguments that aren't committable"""
2979 """check for commit arguments that aren't committable"""
2980 if match.isexact() or match.prefix():
2980 if match.isexact() or match.prefix():
2981 matched = set(status.modified + status.added + status.removed)
2981 matched = set(status.modified + status.added + status.removed)
2982
2982
2983 for f in match.files():
2983 for f in match.files():
2984 f = self.dirstate.normalize(f)
2984 f = self.dirstate.normalize(f)
2985 if f == b'.' or f in matched or f in wctx.substate:
2985 if f == b'.' or f in matched or f in wctx.substate:
2986 continue
2986 continue
2987 if f in status.deleted:
2987 if f in status.deleted:
2988 fail(f, _(b'file not found!'))
2988 fail(f, _(b'file not found!'))
2989 # Is it a directory that exists or used to exist?
2989 # Is it a directory that exists or used to exist?
2990 if self.wvfs.isdir(f) or wctx.p1().hasdir(f):
2990 if self.wvfs.isdir(f) or wctx.p1().hasdir(f):
2991 d = f + b'/'
2991 d = f + b'/'
2992 for mf in matched:
2992 for mf in matched:
2993 if mf.startswith(d):
2993 if mf.startswith(d):
2994 break
2994 break
2995 else:
2995 else:
2996 fail(f, _(b"no match under directory!"))
2996 fail(f, _(b"no match under directory!"))
2997 elif f not in self.dirstate:
2997 elif f not in self.dirstate:
2998 fail(f, _(b"file not tracked!"))
2998 fail(f, _(b"file not tracked!"))
2999
2999
3000 @unfilteredmethod
3000 @unfilteredmethod
3001 def commit(
3001 def commit(
3002 self,
3002 self,
3003 text=b"",
3003 text=b"",
3004 user=None,
3004 user=None,
3005 date=None,
3005 date=None,
3006 match=None,
3006 match=None,
3007 force=False,
3007 force=False,
3008 editor=None,
3008 editor=None,
3009 extra=None,
3009 extra=None,
3010 ):
3010 ):
3011 """Add a new revision to current repository.
3011 """Add a new revision to current repository.
3012
3012
3013 Revision information is gathered from the working directory,
3013 Revision information is gathered from the working directory,
3014 match can be used to filter the committed files. If editor is
3014 match can be used to filter the committed files. If editor is
3015 supplied, it is called to get a commit message.
3015 supplied, it is called to get a commit message.
3016 """
3016 """
3017 if extra is None:
3017 if extra is None:
3018 extra = {}
3018 extra = {}
3019
3019
3020 def fail(f, msg):
3020 def fail(f, msg):
3021 raise error.InputError(b'%s: %s' % (f, msg))
3021 raise error.InputError(b'%s: %s' % (f, msg))
3022
3022
3023 if not match:
3023 if not match:
3024 match = matchmod.always()
3024 match = matchmod.always()
3025
3025
3026 if not force:
3026 if not force:
3027 match.bad = fail
3027 match.bad = fail
3028
3028
3029 # lock() for recent changelog (see issue4368)
3029 # lock() for recent changelog (see issue4368)
3030 with self.wlock(), self.lock():
3030 with self.wlock(), self.lock():
3031 wctx = self[None]
3031 wctx = self[None]
3032 merge = len(wctx.parents()) > 1
3032 merge = len(wctx.parents()) > 1
3033
3033
3034 if not force and merge and not match.always():
3034 if not force and merge and not match.always():
3035 raise error.Abort(
3035 raise error.Abort(
3036 _(
3036 _(
3037 b'cannot partially commit a merge '
3037 b'cannot partially commit a merge '
3038 b'(do not specify files or patterns)'
3038 b'(do not specify files or patterns)'
3039 )
3039 )
3040 )
3040 )
3041
3041
3042 status = self.status(match=match, clean=force)
3042 status = self.status(match=match, clean=force)
3043 if force:
3043 if force:
3044 status.modified.extend(
3044 status.modified.extend(
3045 status.clean
3045 status.clean
3046 ) # mq may commit clean files
3046 ) # mq may commit clean files
3047
3047
3048 # check subrepos
3048 # check subrepos
3049 subs, commitsubs, newstate = subrepoutil.precommit(
3049 subs, commitsubs, newstate = subrepoutil.precommit(
3050 self.ui, wctx, status, match, force=force
3050 self.ui, wctx, status, match, force=force
3051 )
3051 )
3052
3052
3053 # make sure all explicit patterns are matched
3053 # make sure all explicit patterns are matched
3054 if not force:
3054 if not force:
3055 self.checkcommitpatterns(wctx, match, status, fail)
3055 self.checkcommitpatterns(wctx, match, status, fail)
3056
3056
3057 cctx = context.workingcommitctx(
3057 cctx = context.workingcommitctx(
3058 self, status, text, user, date, extra
3058 self, status, text, user, date, extra
3059 )
3059 )
3060
3060
3061 ms = mergestatemod.mergestate.read(self)
3061 ms = mergestatemod.mergestate.read(self)
3062 mergeutil.checkunresolved(ms)
3062 mergeutil.checkunresolved(ms)
3063
3063
3064 # internal config: ui.allowemptycommit
3064 # internal config: ui.allowemptycommit
3065 if cctx.isempty() and not self.ui.configbool(
3065 if cctx.isempty() and not self.ui.configbool(
3066 b'ui', b'allowemptycommit'
3066 b'ui', b'allowemptycommit'
3067 ):
3067 ):
3068 self.ui.debug(b'nothing to commit, clearing merge state\n')
3068 self.ui.debug(b'nothing to commit, clearing merge state\n')
3069 ms.reset()
3069 ms.reset()
3070 return None
3070 return None
3071
3071
3072 if merge and cctx.deleted():
3072 if merge and cctx.deleted():
3073 raise error.Abort(_(b"cannot commit merge with missing files"))
3073 raise error.Abort(_(b"cannot commit merge with missing files"))
3074
3074
3075 if editor:
3075 if editor:
3076 cctx._text = editor(self, cctx, subs)
3076 cctx._text = editor(self, cctx, subs)
3077 edited = text != cctx._text
3077 edited = text != cctx._text
3078
3078
3079 # Save commit message in case this transaction gets rolled back
3079 # Save commit message in case this transaction gets rolled back
3080 # (e.g. by a pretxncommit hook). Leave the content alone on
3080 # (e.g. by a pretxncommit hook). Leave the content alone on
3081 # the assumption that the user will use the same editor again.
3081 # the assumption that the user will use the same editor again.
3082 msgfn = self.savecommitmessage(cctx._text)
3082 msgfn = self.savecommitmessage(cctx._text)
3083
3083
3084 # commit subs and write new state
3084 # commit subs and write new state
3085 if subs:
3085 if subs:
3086 uipathfn = scmutil.getuipathfn(self)
3086 uipathfn = scmutil.getuipathfn(self)
3087 for s in sorted(commitsubs):
3087 for s in sorted(commitsubs):
3088 sub = wctx.sub(s)
3088 sub = wctx.sub(s)
3089 self.ui.status(
3089 self.ui.status(
3090 _(b'committing subrepository %s\n')
3090 _(b'committing subrepository %s\n')
3091 % uipathfn(subrepoutil.subrelpath(sub))
3091 % uipathfn(subrepoutil.subrelpath(sub))
3092 )
3092 )
3093 sr = sub.commit(cctx._text, user, date)
3093 sr = sub.commit(cctx._text, user, date)
3094 newstate[s] = (newstate[s][0], sr)
3094 newstate[s] = (newstate[s][0], sr)
3095 subrepoutil.writestate(self, newstate)
3095 subrepoutil.writestate(self, newstate)
3096
3096
3097 p1, p2 = self.dirstate.parents()
3097 p1, p2 = self.dirstate.parents()
3098 hookp1, hookp2 = hex(p1), (p2 != self.nullid and hex(p2) or b'')
3098 hookp1, hookp2 = hex(p1), (p2 != self.nullid and hex(p2) or b'')
3099 try:
3099 try:
3100 self.hook(
3100 self.hook(
3101 b"precommit", throw=True, parent1=hookp1, parent2=hookp2
3101 b"precommit", throw=True, parent1=hookp1, parent2=hookp2
3102 )
3102 )
3103 with self.transaction(b'commit'):
3103 with self.transaction(b'commit'):
3104 ret = self.commitctx(cctx, True)
3104 ret = self.commitctx(cctx, True)
3105 # update bookmarks, dirstate and mergestate
3105 # update bookmarks, dirstate and mergestate
3106 bookmarks.update(self, [p1, p2], ret)
3106 bookmarks.update(self, [p1, p2], ret)
3107 cctx.markcommitted(ret)
3107 cctx.markcommitted(ret)
3108 ms.reset()
3108 ms.reset()
3109 except: # re-raises
3109 except: # re-raises
3110 if edited:
3110 if edited:
3111 self.ui.write(
3111 self.ui.write(
3112 _(b'note: commit message saved in %s\n') % msgfn
3112 _(b'note: commit message saved in %s\n') % msgfn
3113 )
3113 )
3114 self.ui.write(
3114 self.ui.write(
3115 _(
3115 _(
3116 b"note: use 'hg commit --logfile "
3116 b"note: use 'hg commit --logfile "
3117 b".hg/last-message.txt --edit' to reuse it\n"
3117 b".hg/last-message.txt --edit' to reuse it\n"
3118 )
3118 )
3119 )
3119 )
3120 raise
3120 raise
3121
3121
3122 def commithook(unused_success):
3122 def commithook(unused_success):
3123 # hack for command that use a temporary commit (eg: histedit)
3123 # hack for command that use a temporary commit (eg: histedit)
3124 # temporary commit got stripped before hook release
3124 # temporary commit got stripped before hook release
3125 if self.changelog.hasnode(ret):
3125 if self.changelog.hasnode(ret):
3126 self.hook(
3126 self.hook(
3127 b"commit", node=hex(ret), parent1=hookp1, parent2=hookp2
3127 b"commit", node=hex(ret), parent1=hookp1, parent2=hookp2
3128 )
3128 )
3129
3129
3130 self._afterlock(commithook)
3130 self._afterlock(commithook)
3131 return ret
3131 return ret
3132
3132
3133 @unfilteredmethod
3133 @unfilteredmethod
3134 def commitctx(self, ctx, error=False, origctx=None):
3134 def commitctx(self, ctx, error=False, origctx=None):
3135 return commit.commitctx(self, ctx, error=error, origctx=origctx)
3135 return commit.commitctx(self, ctx, error=error, origctx=origctx)
3136
3136
3137 @unfilteredmethod
3137 @unfilteredmethod
3138 def destroying(self):
3138 def destroying(self):
3139 """Inform the repository that nodes are about to be destroyed.
3139 """Inform the repository that nodes are about to be destroyed.
3140 Intended for use by strip and rollback, so there's a common
3140 Intended for use by strip and rollback, so there's a common
3141 place for anything that has to be done before destroying history.
3141 place for anything that has to be done before destroying history.
3142
3142
3143 This is mostly useful for saving state that is in memory and waiting
3143 This is mostly useful for saving state that is in memory and waiting
3144 to be flushed when the current lock is released. Because a call to
3144 to be flushed when the current lock is released. Because a call to
3145 destroyed is imminent, the repo will be invalidated causing those
3145 destroyed is imminent, the repo will be invalidated causing those
3146 changes to stay in memory (waiting for the next unlock), or vanish
3146 changes to stay in memory (waiting for the next unlock), or vanish
3147 completely.
3147 completely.
3148 """
3148 """
3149 # When using the same lock to commit and strip, the phasecache is left
3149 # When using the same lock to commit and strip, the phasecache is left
3150 # dirty after committing. Then when we strip, the repo is invalidated,
3150 # dirty after committing. Then when we strip, the repo is invalidated,
3151 # causing those changes to disappear.
3151 # causing those changes to disappear.
3152 if '_phasecache' in vars(self):
3152 if '_phasecache' in vars(self):
3153 self._phasecache.write()
3153 self._phasecache.write()
3154
3154
3155 @unfilteredmethod
3155 @unfilteredmethod
3156 def destroyed(self):
3156 def destroyed(self):
3157 """Inform the repository that nodes have been destroyed.
3157 """Inform the repository that nodes have been destroyed.
3158 Intended for use by strip and rollback, so there's a common
3158 Intended for use by strip and rollback, so there's a common
3159 place for anything that has to be done after destroying history.
3159 place for anything that has to be done after destroying history.
3160 """
3160 """
3161 # When one tries to:
3161 # When one tries to:
3162 # 1) destroy nodes thus calling this method (e.g. strip)
3162 # 1) destroy nodes thus calling this method (e.g. strip)
3163 # 2) use phasecache somewhere (e.g. commit)
3163 # 2) use phasecache somewhere (e.g. commit)
3164 #
3164 #
3165 # then 2) will fail because the phasecache contains nodes that were
3165 # then 2) will fail because the phasecache contains nodes that were
3166 # removed. We can either remove phasecache from the filecache,
3166 # removed. We can either remove phasecache from the filecache,
3167 # causing it to reload next time it is accessed, or simply filter
3167 # causing it to reload next time it is accessed, or simply filter
3168 # the removed nodes now and write the updated cache.
3168 # the removed nodes now and write the updated cache.
3169 self._phasecache.filterunknown(self)
3169 self._phasecache.filterunknown(self)
3170 self._phasecache.write()
3170 self._phasecache.write()
3171
3171
3172 # refresh all repository caches
3172 # refresh all repository caches
3173 self.updatecaches()
3173 self.updatecaches()
3174
3174
3175 # Ensure the persistent tag cache is updated. Doing it now
3175 # Ensure the persistent tag cache is updated. Doing it now
3176 # means that the tag cache only has to worry about destroyed
3176 # means that the tag cache only has to worry about destroyed
3177 # heads immediately after a strip/rollback. That in turn
3177 # heads immediately after a strip/rollback. That in turn
3178 # guarantees that "cachetip == currenttip" (comparing both rev
3178 # guarantees that "cachetip == currenttip" (comparing both rev
3179 # and node) always means no nodes have been added or destroyed.
3179 # and node) always means no nodes have been added or destroyed.
3180
3180
3181 # XXX this is suboptimal when qrefresh'ing: we strip the current
3181 # XXX this is suboptimal when qrefresh'ing: we strip the current
3182 # head, refresh the tag cache, then immediately add a new head.
3182 # head, refresh the tag cache, then immediately add a new head.
3183 # But I think doing it this way is necessary for the "instant
3183 # But I think doing it this way is necessary for the "instant
3184 # tag cache retrieval" case to work.
3184 # tag cache retrieval" case to work.
3185 self.invalidate()
3185 self.invalidate()
3186
3186
3187 def status(
3187 def status(
3188 self,
3188 self,
3189 node1=b'.',
3189 node1=b'.',
3190 node2=None,
3190 node2=None,
3191 match=None,
3191 match=None,
3192 ignored=False,
3192 ignored=False,
3193 clean=False,
3193 clean=False,
3194 unknown=False,
3194 unknown=False,
3195 listsubrepos=False,
3195 listsubrepos=False,
3196 ):
3196 ):
3197 '''a convenience method that calls node1.status(node2)'''
3197 '''a convenience method that calls node1.status(node2)'''
3198 return self[node1].status(
3198 return self[node1].status(
3199 node2, match, ignored, clean, unknown, listsubrepos
3199 node2, match, ignored, clean, unknown, listsubrepos
3200 )
3200 )
3201
3201
3202 def addpostdsstatus(self, ps):
3202 def addpostdsstatus(self, ps):
3203 """Add a callback to run within the wlock, at the point at which status
3203 """Add a callback to run within the wlock, at the point at which status
3204 fixups happen.
3204 fixups happen.
3205
3205
3206 On status completion, callback(wctx, status) will be called with the
3206 On status completion, callback(wctx, status) will be called with the
3207 wlock held, unless the dirstate has changed from underneath or the wlock
3207 wlock held, unless the dirstate has changed from underneath or the wlock
3208 couldn't be grabbed.
3208 couldn't be grabbed.
3209
3209
3210 Callbacks should not capture and use a cached copy of the dirstate --
3210 Callbacks should not capture and use a cached copy of the dirstate --
3211 it might change in the meanwhile. Instead, they should access the
3211 it might change in the meanwhile. Instead, they should access the
3212 dirstate via wctx.repo().dirstate.
3212 dirstate via wctx.repo().dirstate.
3213
3213
3214 This list is emptied out after each status run -- extensions should
3214 This list is emptied out after each status run -- extensions should
3215 make sure it adds to this list each time dirstate.status is called.
3215 make sure it adds to this list each time dirstate.status is called.
3216 Extensions should also make sure they don't call this for statuses
3216 Extensions should also make sure they don't call this for statuses
3217 that don't involve the dirstate.
3217 that don't involve the dirstate.
3218 """
3218 """
3219
3219
3220 # The list is located here for uniqueness reasons -- it is actually
3220 # The list is located here for uniqueness reasons -- it is actually
3221 # managed by the workingctx, but that isn't unique per-repo.
3221 # managed by the workingctx, but that isn't unique per-repo.
3222 self._postdsstatus.append(ps)
3222 self._postdsstatus.append(ps)
3223
3223
3224 def postdsstatus(self):
3224 def postdsstatus(self):
3225 """Used by workingctx to get the list of post-dirstate-status hooks."""
3225 """Used by workingctx to get the list of post-dirstate-status hooks."""
3226 return self._postdsstatus
3226 return self._postdsstatus
3227
3227
3228 def clearpostdsstatus(self):
3228 def clearpostdsstatus(self):
3229 """Used by workingctx to clear post-dirstate-status hooks."""
3229 """Used by workingctx to clear post-dirstate-status hooks."""
3230 del self._postdsstatus[:]
3230 del self._postdsstatus[:]
3231
3231
3232 def heads(self, start=None):
3232 def heads(self, start=None):
3233 if start is None:
3233 if start is None:
3234 cl = self.changelog
3234 cl = self.changelog
3235 headrevs = reversed(cl.headrevs())
3235 headrevs = reversed(cl.headrevs())
3236 return [cl.node(rev) for rev in headrevs]
3236 return [cl.node(rev) for rev in headrevs]
3237
3237
3238 heads = self.changelog.heads(start)
3238 heads = self.changelog.heads(start)
3239 # sort the output in rev descending order
3239 # sort the output in rev descending order
3240 return sorted(heads, key=self.changelog.rev, reverse=True)
3240 return sorted(heads, key=self.changelog.rev, reverse=True)
3241
3241
3242 def branchheads(self, branch=None, start=None, closed=False):
3242 def branchheads(self, branch=None, start=None, closed=False):
3243 """return a (possibly filtered) list of heads for the given branch
3243 """return a (possibly filtered) list of heads for the given branch
3244
3244
3245 Heads are returned in topological order, from newest to oldest.
3245 Heads are returned in topological order, from newest to oldest.
3246 If branch is None, use the dirstate branch.
3246 If branch is None, use the dirstate branch.
3247 If start is not None, return only heads reachable from start.
3247 If start is not None, return only heads reachable from start.
3248 If closed is True, return heads that are marked as closed as well.
3248 If closed is True, return heads that are marked as closed as well.
3249 """
3249 """
3250 if branch is None:
3250 if branch is None:
3251 branch = self[None].branch()
3251 branch = self[None].branch()
3252 branches = self.branchmap()
3252 branches = self.branchmap()
3253 if not branches.hasbranch(branch):
3253 if not branches.hasbranch(branch):
3254 return []
3254 return []
3255 # the cache returns heads ordered lowest to highest
3255 # the cache returns heads ordered lowest to highest
3256 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
3256 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
3257 if start is not None:
3257 if start is not None:
3258 # filter out the heads that cannot be reached from startrev
3258 # filter out the heads that cannot be reached from startrev
3259 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
3259 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
3260 bheads = [h for h in bheads if h in fbheads]
3260 bheads = [h for h in bheads if h in fbheads]
3261 return bheads
3261 return bheads
3262
3262
3263 def branches(self, nodes):
3263 def branches(self, nodes):
3264 if not nodes:
3264 if not nodes:
3265 nodes = [self.changelog.tip()]
3265 nodes = [self.changelog.tip()]
3266 b = []
3266 b = []
3267 for n in nodes:
3267 for n in nodes:
3268 t = n
3268 t = n
3269 while True:
3269 while True:
3270 p = self.changelog.parents(n)
3270 p = self.changelog.parents(n)
3271 if p[1] != self.nullid or p[0] == self.nullid:
3271 if p[1] != self.nullid or p[0] == self.nullid:
3272 b.append((t, n, p[0], p[1]))
3272 b.append((t, n, p[0], p[1]))
3273 break
3273 break
3274 n = p[0]
3274 n = p[0]
3275 return b
3275 return b
3276
3276
3277 def between(self, pairs):
3277 def between(self, pairs):
3278 r = []
3278 r = []
3279
3279
3280 for top, bottom in pairs:
3280 for top, bottom in pairs:
3281 n, l, i = top, [], 0
3281 n, l, i = top, [], 0
3282 f = 1
3282 f = 1
3283
3283
3284 while n != bottom and n != self.nullid:
3284 while n != bottom and n != self.nullid:
3285 p = self.changelog.parents(n)[0]
3285 p = self.changelog.parents(n)[0]
3286 if i == f:
3286 if i == f:
3287 l.append(n)
3287 l.append(n)
3288 f = f * 2
3288 f = f * 2
3289 n = p
3289 n = p
3290 i += 1
3290 i += 1
3291
3291
3292 r.append(l)
3292 r.append(l)
3293
3293
3294 return r
3294 return r
3295
3295
3296 def checkpush(self, pushop):
3296 def checkpush(self, pushop):
3297 """Extensions can override this function if additional checks have
3297 """Extensions can override this function if additional checks have
3298 to be performed before pushing, or call it if they override push
3298 to be performed before pushing, or call it if they override push
3299 command.
3299 command.
3300 """
3300 """
3301
3301
3302 @unfilteredpropertycache
3302 @unfilteredpropertycache
3303 def prepushoutgoinghooks(self):
3303 def prepushoutgoinghooks(self):
3304 """Return util.hooks consists of a pushop with repo, remote, outgoing
3304 """Return util.hooks consists of a pushop with repo, remote, outgoing
3305 methods, which are called before pushing changesets.
3305 methods, which are called before pushing changesets.
3306 """
3306 """
3307 return util.hooks()
3307 return util.hooks()
3308
3308
3309 def pushkey(self, namespace, key, old, new):
3309 def pushkey(self, namespace, key, old, new):
3310 try:
3310 try:
3311 tr = self.currenttransaction()
3311 tr = self.currenttransaction()
3312 hookargs = {}
3312 hookargs = {}
3313 if tr is not None:
3313 if tr is not None:
3314 hookargs.update(tr.hookargs)
3314 hookargs.update(tr.hookargs)
3315 hookargs = pycompat.strkwargs(hookargs)
3315 hookargs = pycompat.strkwargs(hookargs)
3316 hookargs['namespace'] = namespace
3316 hookargs['namespace'] = namespace
3317 hookargs['key'] = key
3317 hookargs['key'] = key
3318 hookargs['old'] = old
3318 hookargs['old'] = old
3319 hookargs['new'] = new
3319 hookargs['new'] = new
3320 self.hook(b'prepushkey', throw=True, **hookargs)
3320 self.hook(b'prepushkey', throw=True, **hookargs)
3321 except error.HookAbort as exc:
3321 except error.HookAbort as exc:
3322 self.ui.write_err(_(b"pushkey-abort: %s\n") % exc)
3322 self.ui.write_err(_(b"pushkey-abort: %s\n") % exc)
3323 if exc.hint:
3323 if exc.hint:
3324 self.ui.write_err(_(b"(%s)\n") % exc.hint)
3324 self.ui.write_err(_(b"(%s)\n") % exc.hint)
3325 return False
3325 return False
3326 self.ui.debug(b'pushing key for "%s:%s"\n' % (namespace, key))
3326 self.ui.debug(b'pushing key for "%s:%s"\n' % (namespace, key))
3327 ret = pushkey.push(self, namespace, key, old, new)
3327 ret = pushkey.push(self, namespace, key, old, new)
3328
3328
3329 def runhook(unused_success):
3329 def runhook(unused_success):
3330 self.hook(
3330 self.hook(
3331 b'pushkey',
3331 b'pushkey',
3332 namespace=namespace,
3332 namespace=namespace,
3333 key=key,
3333 key=key,
3334 old=old,
3334 old=old,
3335 new=new,
3335 new=new,
3336 ret=ret,
3336 ret=ret,
3337 )
3337 )
3338
3338
3339 self._afterlock(runhook)
3339 self._afterlock(runhook)
3340 return ret
3340 return ret
3341
3341
3342 def listkeys(self, namespace):
3342 def listkeys(self, namespace):
3343 self.hook(b'prelistkeys', throw=True, namespace=namespace)
3343 self.hook(b'prelistkeys', throw=True, namespace=namespace)
3344 self.ui.debug(b'listing keys for "%s"\n' % namespace)
3344 self.ui.debug(b'listing keys for "%s"\n' % namespace)
3345 values = pushkey.list(self, namespace)
3345 values = pushkey.list(self, namespace)
3346 self.hook(b'listkeys', namespace=namespace, values=values)
3346 self.hook(b'listkeys', namespace=namespace, values=values)
3347 return values
3347 return values
3348
3348
3349 def debugwireargs(self, one, two, three=None, four=None, five=None):
3349 def debugwireargs(self, one, two, three=None, four=None, five=None):
3350 '''used to test argument passing over the wire'''
3350 '''used to test argument passing over the wire'''
3351 return b"%s %s %s %s %s" % (
3351 return b"%s %s %s %s %s" % (
3352 one,
3352 one,
3353 two,
3353 two,
3354 pycompat.bytestr(three),
3354 pycompat.bytestr(three),
3355 pycompat.bytestr(four),
3355 pycompat.bytestr(four),
3356 pycompat.bytestr(five),
3356 pycompat.bytestr(five),
3357 )
3357 )
3358
3358
3359 def savecommitmessage(self, text):
3359 def savecommitmessage(self, text):
3360 fp = self.vfs(b'last-message.txt', b'wb')
3360 fp = self.vfs(b'last-message.txt', b'wb')
3361 try:
3361 try:
3362 fp.write(text)
3362 fp.write(text)
3363 finally:
3363 finally:
3364 fp.close()
3364 fp.close()
3365 return self.pathto(fp.name[len(self.root) + 1 :])
3365 return self.pathto(fp.name[len(self.root) + 1 :])
3366
3366
3367 def register_wanted_sidedata(self, category):
3367 def register_wanted_sidedata(self, category):
3368 self._wanted_sidedata.add(pycompat.bytestr(category))
3368 self._wanted_sidedata.add(pycompat.bytestr(category))
3369
3369
3370 def register_sidedata_computer(self, kind, category, keys, computer):
3370 def register_sidedata_computer(self, kind, category, keys, computer):
3371 if kind not in (b"changelog", b"manifest", b"filelog"):
3371 if kind not in (b"changelog", b"manifest", b"filelog"):
3372 msg = _(b"unexpected revlog kind '%s'.")
3372 msg = _(b"unexpected revlog kind '%s'.")
3373 raise error.ProgrammingError(msg % kind)
3373 raise error.ProgrammingError(msg % kind)
3374 category = pycompat.bytestr(category)
3374 category = pycompat.bytestr(category)
3375 if category in self._sidedata_computers.get(kind, []):
3375 if category in self._sidedata_computers.get(kind, []):
3376 msg = _(
3376 msg = _(
3377 b"cannot register a sidedata computer twice for category '%s'."
3377 b"cannot register a sidedata computer twice for category '%s'."
3378 )
3378 )
3379 raise error.ProgrammingError(msg % category)
3379 raise error.ProgrammingError(msg % category)
3380 self._sidedata_computers.setdefault(kind, {})
3380 self._sidedata_computers.setdefault(kind, {})
3381 self._sidedata_computers[kind][category] = (keys, computer)
3381 self._sidedata_computers[kind][category] = (keys, computer)
3382
3382
3383
3383
3384 # used to avoid circular references so destructors work
3384 # used to avoid circular references so destructors work
3385 def aftertrans(files):
3385 def aftertrans(files):
3386 renamefiles = [tuple(t) for t in files]
3386 renamefiles = [tuple(t) for t in files]
3387
3387
3388 def a():
3388 def a():
3389 for vfs, src, dest in renamefiles:
3389 for vfs, src, dest in renamefiles:
3390 # if src and dest refer to a same file, vfs.rename is a no-op,
3390 # if src and dest refer to a same file, vfs.rename is a no-op,
3391 # leaving both src and dest on disk. delete dest to make sure
3391 # leaving both src and dest on disk. delete dest to make sure
3392 # the rename couldn't be such a no-op.
3392 # the rename couldn't be such a no-op.
3393 vfs.tryunlink(dest)
3393 vfs.tryunlink(dest)
3394 try:
3394 try:
3395 vfs.rename(src, dest)
3395 vfs.rename(src, dest)
3396 except OSError: # journal file does not yet exist
3396 except OSError: # journal file does not yet exist
3397 pass
3397 pass
3398
3398
3399 return a
3399 return a
3400
3400
3401
3401
3402 def undoname(fn):
3402 def undoname(fn):
3403 base, name = os.path.split(fn)
3403 base, name = os.path.split(fn)
3404 assert name.startswith(b'journal')
3404 assert name.startswith(b'journal')
3405 return os.path.join(base, name.replace(b'journal', b'undo', 1))
3405 return os.path.join(base, name.replace(b'journal', b'undo', 1))
3406
3406
3407
3407
3408 def instance(ui, path, create, intents=None, createopts=None):
3408 def instance(ui, path, create, intents=None, createopts=None):
3409 localpath = urlutil.urllocalpath(path)
3409 localpath = urlutil.urllocalpath(path)
3410 if create:
3410 if create:
3411 createrepository(ui, localpath, createopts=createopts)
3411 createrepository(ui, localpath, createopts=createopts)
3412
3412
3413 return makelocalrepository(ui, localpath, intents=intents)
3413 return makelocalrepository(ui, localpath, intents=intents)
3414
3414
3415
3415
3416 def islocal(path):
3416 def islocal(path):
3417 return True
3417 return True
3418
3418
3419
3419
3420 def defaultcreateopts(ui, createopts=None):
3420 def defaultcreateopts(ui, createopts=None):
3421 """Populate the default creation options for a repository.
3421 """Populate the default creation options for a repository.
3422
3422
3423 A dictionary of explicitly requested creation options can be passed
3423 A dictionary of explicitly requested creation options can be passed
3424 in. Missing keys will be populated.
3424 in. Missing keys will be populated.
3425 """
3425 """
3426 createopts = dict(createopts or {})
3426 createopts = dict(createopts or {})
3427
3427
3428 if b'backend' not in createopts:
3428 if b'backend' not in createopts:
3429 # experimental config: storage.new-repo-backend
3429 # experimental config: storage.new-repo-backend
3430 createopts[b'backend'] = ui.config(b'storage', b'new-repo-backend')
3430 createopts[b'backend'] = ui.config(b'storage', b'new-repo-backend')
3431
3431
3432 return createopts
3432 return createopts
3433
3433
3434
3434
3435 def newreporequirements(ui, createopts):
3435 def newreporequirements(ui, createopts):
3436 """Determine the set of requirements for a new local repository.
3436 """Determine the set of requirements for a new local repository.
3437
3437
3438 Extensions can wrap this function to specify custom requirements for
3438 Extensions can wrap this function to specify custom requirements for
3439 new repositories.
3439 new repositories.
3440 """
3440 """
3441 # If the repo is being created from a shared repository, we copy
3441 # If the repo is being created from a shared repository, we copy
3442 # its requirements.
3442 # its requirements.
3443 if b'sharedrepo' in createopts:
3443 if b'sharedrepo' in createopts:
3444 requirements = set(createopts[b'sharedrepo'].requirements)
3444 requirements = set(createopts[b'sharedrepo'].requirements)
3445 if createopts.get(b'sharedrelative'):
3445 if createopts.get(b'sharedrelative'):
3446 requirements.add(requirementsmod.RELATIVE_SHARED_REQUIREMENT)
3446 requirements.add(requirementsmod.RELATIVE_SHARED_REQUIREMENT)
3447 else:
3447 else:
3448 requirements.add(requirementsmod.SHARED_REQUIREMENT)
3448 requirements.add(requirementsmod.SHARED_REQUIREMENT)
3449
3449
3450 return requirements
3450 return requirements
3451
3451
3452 if b'backend' not in createopts:
3452 if b'backend' not in createopts:
3453 raise error.ProgrammingError(
3453 raise error.ProgrammingError(
3454 b'backend key not present in createopts; '
3454 b'backend key not present in createopts; '
3455 b'was defaultcreateopts() called?'
3455 b'was defaultcreateopts() called?'
3456 )
3456 )
3457
3457
3458 if createopts[b'backend'] != b'revlogv1':
3458 if createopts[b'backend'] != b'revlogv1':
3459 raise error.Abort(
3459 raise error.Abort(
3460 _(
3460 _(
3461 b'unable to determine repository requirements for '
3461 b'unable to determine repository requirements for '
3462 b'storage backend: %s'
3462 b'storage backend: %s'
3463 )
3463 )
3464 % createopts[b'backend']
3464 % createopts[b'backend']
3465 )
3465 )
3466
3466
3467 requirements = {requirementsmod.REVLOGV1_REQUIREMENT}
3467 requirements = {requirementsmod.REVLOGV1_REQUIREMENT}
3468 if ui.configbool(b'format', b'usestore'):
3468 if ui.configbool(b'format', b'usestore'):
3469 requirements.add(requirementsmod.STORE_REQUIREMENT)
3469 requirements.add(requirementsmod.STORE_REQUIREMENT)
3470 if ui.configbool(b'format', b'usefncache'):
3470 if ui.configbool(b'format', b'usefncache'):
3471 requirements.add(requirementsmod.FNCACHE_REQUIREMENT)
3471 requirements.add(requirementsmod.FNCACHE_REQUIREMENT)
3472 if ui.configbool(b'format', b'dotencode'):
3472 if ui.configbool(b'format', b'dotencode'):
3473 requirements.add(requirementsmod.DOTENCODE_REQUIREMENT)
3473 requirements.add(requirementsmod.DOTENCODE_REQUIREMENT)
3474
3474
3475 compengines = ui.configlist(b'format', b'revlog-compression')
3475 compengines = ui.configlist(b'format', b'revlog-compression')
3476 for compengine in compengines:
3476 for compengine in compengines:
3477 if compengine in util.compengines:
3477 if compengine in util.compengines:
3478 engine = util.compengines[compengine]
3478 engine = util.compengines[compengine]
3479 if engine.available() and engine.revlogheader():
3479 if engine.available() and engine.revlogheader():
3480 break
3480 break
3481 else:
3481 else:
3482 raise error.Abort(
3482 raise error.Abort(
3483 _(
3483 _(
3484 b'compression engines %s defined by '
3484 b'compression engines %s defined by '
3485 b'format.revlog-compression not available'
3485 b'format.revlog-compression not available'
3486 )
3486 )
3487 % b', '.join(b'"%s"' % e for e in compengines),
3487 % b', '.join(b'"%s"' % e for e in compengines),
3488 hint=_(
3488 hint=_(
3489 b'run "hg debuginstall" to list available '
3489 b'run "hg debuginstall" to list available '
3490 b'compression engines'
3490 b'compression engines'
3491 ),
3491 ),
3492 )
3492 )
3493
3493
3494 # zlib is the historical default and doesn't need an explicit requirement.
3494 # zlib is the historical default and doesn't need an explicit requirement.
3495 if compengine == b'zstd':
3495 if compengine == b'zstd':
3496 requirements.add(b'revlog-compression-zstd')
3496 requirements.add(b'revlog-compression-zstd')
3497 elif compengine != b'zlib':
3497 elif compengine != b'zlib':
3498 requirements.add(b'exp-compression-%s' % compengine)
3498 requirements.add(b'exp-compression-%s' % compengine)
3499
3499
3500 if scmutil.gdinitconfig(ui):
3500 if scmutil.gdinitconfig(ui):
3501 requirements.add(requirementsmod.GENERALDELTA_REQUIREMENT)
3501 requirements.add(requirementsmod.GENERALDELTA_REQUIREMENT)
3502 if ui.configbool(b'format', b'sparse-revlog'):
3502 if ui.configbool(b'format', b'sparse-revlog'):
3503 requirements.add(requirementsmod.SPARSEREVLOG_REQUIREMENT)
3503 requirements.add(requirementsmod.SPARSEREVLOG_REQUIREMENT)
3504
3504
3505 # experimental config: format.exp-use-side-data
3505 # experimental config: format.exp-use-side-data
3506 if ui.configbool(b'format', b'exp-use-side-data'):
3506 if ui.configbool(b'format', b'exp-use-side-data'):
3507 requirements.discard(requirementsmod.REVLOGV1_REQUIREMENT)
3507 requirements.discard(requirementsmod.REVLOGV1_REQUIREMENT)
3508 requirements.add(requirementsmod.REVLOGV2_REQUIREMENT)
3508 requirements.add(requirementsmod.REVLOGV2_REQUIREMENT)
3509 requirements.add(requirementsmod.SIDEDATA_REQUIREMENT)
3509 requirements.add(requirementsmod.SIDEDATA_REQUIREMENT)
3510 # experimental config: format.exp-use-copies-side-data-changeset
3510 # experimental config: format.exp-use-copies-side-data-changeset
3511 if ui.configbool(b'format', b'exp-use-copies-side-data-changeset'):
3511 if ui.configbool(b'format', b'exp-use-copies-side-data-changeset'):
3512 requirements.discard(requirementsmod.REVLOGV1_REQUIREMENT)
3512 requirements.discard(requirementsmod.REVLOGV1_REQUIREMENT)
3513 requirements.add(requirementsmod.REVLOGV2_REQUIREMENT)
3513 requirements.add(requirementsmod.REVLOGV2_REQUIREMENT)
3514 requirements.add(requirementsmod.SIDEDATA_REQUIREMENT)
3514 requirements.add(requirementsmod.SIDEDATA_REQUIREMENT)
3515 requirements.add(requirementsmod.COPIESSDC_REQUIREMENT)
3515 requirements.add(requirementsmod.COPIESSDC_REQUIREMENT)
3516 if ui.configbool(b'experimental', b'treemanifest'):
3516 if ui.configbool(b'experimental', b'treemanifest'):
3517 requirements.add(requirementsmod.TREEMANIFEST_REQUIREMENT)
3517 requirements.add(requirementsmod.TREEMANIFEST_REQUIREMENT)
3518
3518
3519 revlogv2 = ui.config(b'experimental', b'revlogv2')
3519 revlogv2 = ui.config(b'experimental', b'revlogv2')
3520 if revlogv2 == b'enable-unstable-format-and-corrupt-my-data':
3520 if revlogv2 == b'enable-unstable-format-and-corrupt-my-data':
3521 requirements.discard(requirementsmod.REVLOGV1_REQUIREMENT)
3521 requirements.discard(requirementsmod.REVLOGV1_REQUIREMENT)
3522 # generaldelta is implied by revlogv2.
3522 # generaldelta is implied by revlogv2.
3523 requirements.discard(requirementsmod.GENERALDELTA_REQUIREMENT)
3523 requirements.discard(requirementsmod.GENERALDELTA_REQUIREMENT)
3524 requirements.add(requirementsmod.REVLOGV2_REQUIREMENT)
3524 requirements.add(requirementsmod.REVLOGV2_REQUIREMENT)
3525 # experimental config: format.internal-phase
3525 # experimental config: format.internal-phase
3526 if ui.configbool(b'format', b'internal-phase'):
3526 if ui.configbool(b'format', b'internal-phase'):
3527 requirements.add(requirementsmod.INTERNAL_PHASE_REQUIREMENT)
3527 requirements.add(requirementsmod.INTERNAL_PHASE_REQUIREMENT)
3528
3528
3529 if createopts.get(b'narrowfiles'):
3529 if createopts.get(b'narrowfiles'):
3530 requirements.add(requirementsmod.NARROW_REQUIREMENT)
3530 requirements.add(requirementsmod.NARROW_REQUIREMENT)
3531
3531
3532 if createopts.get(b'lfs'):
3532 if createopts.get(b'lfs'):
3533 requirements.add(b'lfs')
3533 requirements.add(b'lfs')
3534
3534
3535 if ui.configbool(b'format', b'bookmarks-in-store'):
3535 if ui.configbool(b'format', b'bookmarks-in-store'):
3536 requirements.add(bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT)
3536 requirements.add(bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT)
3537
3537
3538 if ui.configbool(b'format', b'use-persistent-nodemap'):
3538 if ui.configbool(b'format', b'use-persistent-nodemap'):
3539 requirements.add(requirementsmod.NODEMAP_REQUIREMENT)
3539 requirements.add(requirementsmod.NODEMAP_REQUIREMENT)
3540
3540
3541 # if share-safe is enabled, let's create the new repository with the new
3541 # if share-safe is enabled, let's create the new repository with the new
3542 # requirement
3542 # requirement
3543 if ui.configbool(b'format', b'use-share-safe'):
3543 if ui.configbool(b'format', b'use-share-safe'):
3544 requirements.add(requirementsmod.SHARESAFE_REQUIREMENT)
3544 requirements.add(requirementsmod.SHARESAFE_REQUIREMENT)
3545
3545
3546 return requirements
3546 return requirements
3547
3547
3548
3548
3549 def checkrequirementscompat(ui, requirements):
3549 def checkrequirementscompat(ui, requirements):
3550 """Checks compatibility of repository requirements enabled and disabled.
3550 """Checks compatibility of repository requirements enabled and disabled.
3551
3551
3552 Returns a set of requirements which needs to be dropped because dependend
3552 Returns a set of requirements which needs to be dropped because dependend
3553 requirements are not enabled. Also warns users about it"""
3553 requirements are not enabled. Also warns users about it"""
3554
3554
3555 dropped = set()
3555 dropped = set()
3556
3556
3557 if requirementsmod.STORE_REQUIREMENT not in requirements:
3557 if requirementsmod.STORE_REQUIREMENT not in requirements:
3558 if bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT in requirements:
3558 if bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT in requirements:
3559 ui.warn(
3559 ui.warn(
3560 _(
3560 _(
3561 b'ignoring enabled \'format.bookmarks-in-store\' config '
3561 b'ignoring enabled \'format.bookmarks-in-store\' config '
3562 b'beacuse it is incompatible with disabled '
3562 b'beacuse it is incompatible with disabled '
3563 b'\'format.usestore\' config\n'
3563 b'\'format.usestore\' config\n'
3564 )
3564 )
3565 )
3565 )
3566 dropped.add(bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT)
3566 dropped.add(bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT)
3567
3567
3568 if (
3568 if (
3569 requirementsmod.SHARED_REQUIREMENT in requirements
3569 requirementsmod.SHARED_REQUIREMENT in requirements
3570 or requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements
3570 or requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements
3571 ):
3571 ):
3572 raise error.Abort(
3572 raise error.Abort(
3573 _(
3573 _(
3574 b"cannot create shared repository as source was created"
3574 b"cannot create shared repository as source was created"
3575 b" with 'format.usestore' config disabled"
3575 b" with 'format.usestore' config disabled"
3576 )
3576 )
3577 )
3577 )
3578
3578
3579 if requirementsmod.SHARESAFE_REQUIREMENT in requirements:
3579 if requirementsmod.SHARESAFE_REQUIREMENT in requirements:
3580 ui.warn(
3580 ui.warn(
3581 _(
3581 _(
3582 b"ignoring enabled 'format.use-share-safe' config because "
3582 b"ignoring enabled 'format.use-share-safe' config because "
3583 b"it is incompatible with disabled 'format.usestore'"
3583 b"it is incompatible with disabled 'format.usestore'"
3584 b" config\n"
3584 b" config\n"
3585 )
3585 )
3586 )
3586 )
3587 dropped.add(requirementsmod.SHARESAFE_REQUIREMENT)
3587 dropped.add(requirementsmod.SHARESAFE_REQUIREMENT)
3588
3588
3589 return dropped
3589 return dropped
3590
3590
3591
3591
3592 def filterknowncreateopts(ui, createopts):
3592 def filterknowncreateopts(ui, createopts):
3593 """Filters a dict of repo creation options against options that are known.
3593 """Filters a dict of repo creation options against options that are known.
3594
3594
3595 Receives a dict of repo creation options and returns a dict of those
3595 Receives a dict of repo creation options and returns a dict of those
3596 options that we don't know how to handle.
3596 options that we don't know how to handle.
3597
3597
3598 This function is called as part of repository creation. If the
3598 This function is called as part of repository creation. If the
3599 returned dict contains any items, repository creation will not
3599 returned dict contains any items, repository creation will not
3600 be allowed, as it means there was a request to create a repository
3600 be allowed, as it means there was a request to create a repository
3601 with options not recognized by loaded code.
3601 with options not recognized by loaded code.
3602
3602
3603 Extensions can wrap this function to filter out creation options
3603 Extensions can wrap this function to filter out creation options
3604 they know how to handle.
3604 they know how to handle.
3605 """
3605 """
3606 known = {
3606 known = {
3607 b'backend',
3607 b'backend',
3608 b'lfs',
3608 b'lfs',
3609 b'narrowfiles',
3609 b'narrowfiles',
3610 b'sharedrepo',
3610 b'sharedrepo',
3611 b'sharedrelative',
3611 b'sharedrelative',
3612 b'shareditems',
3612 b'shareditems',
3613 b'shallowfilestore',
3613 b'shallowfilestore',
3614 }
3614 }
3615
3615
3616 return {k: v for k, v in createopts.items() if k not in known}
3616 return {k: v for k, v in createopts.items() if k not in known}
3617
3617
3618
3618
3619 def createrepository(ui, path, createopts=None):
3619 def createrepository(ui, path, createopts=None):
3620 """Create a new repository in a vfs.
3620 """Create a new repository in a vfs.
3621
3621
3622 ``path`` path to the new repo's working directory.
3622 ``path`` path to the new repo's working directory.
3623 ``createopts`` options for the new repository.
3623 ``createopts`` options for the new repository.
3624
3624
3625 The following keys for ``createopts`` are recognized:
3625 The following keys for ``createopts`` are recognized:
3626
3626
3627 backend
3627 backend
3628 The storage backend to use.
3628 The storage backend to use.
3629 lfs
3629 lfs
3630 Repository will be created with ``lfs`` requirement. The lfs extension
3630 Repository will be created with ``lfs`` requirement. The lfs extension
3631 will automatically be loaded when the repository is accessed.
3631 will automatically be loaded when the repository is accessed.
3632 narrowfiles
3632 narrowfiles
3633 Set up repository to support narrow file storage.
3633 Set up repository to support narrow file storage.
3634 sharedrepo
3634 sharedrepo
3635 Repository object from which storage should be shared.
3635 Repository object from which storage should be shared.
3636 sharedrelative
3636 sharedrelative
3637 Boolean indicating if the path to the shared repo should be
3637 Boolean indicating if the path to the shared repo should be
3638 stored as relative. By default, the pointer to the "parent" repo
3638 stored as relative. By default, the pointer to the "parent" repo
3639 is stored as an absolute path.
3639 is stored as an absolute path.
3640 shareditems
3640 shareditems
3641 Set of items to share to the new repository (in addition to storage).
3641 Set of items to share to the new repository (in addition to storage).
3642 shallowfilestore
3642 shallowfilestore
3643 Indicates that storage for files should be shallow (not all ancestor
3643 Indicates that storage for files should be shallow (not all ancestor
3644 revisions are known).
3644 revisions are known).
3645 """
3645 """
3646 createopts = defaultcreateopts(ui, createopts=createopts)
3646 createopts = defaultcreateopts(ui, createopts=createopts)
3647
3647
3648 unknownopts = filterknowncreateopts(ui, createopts)
3648 unknownopts = filterknowncreateopts(ui, createopts)
3649
3649
3650 if not isinstance(unknownopts, dict):
3650 if not isinstance(unknownopts, dict):
3651 raise error.ProgrammingError(
3651 raise error.ProgrammingError(
3652 b'filterknowncreateopts() did not return a dict'
3652 b'filterknowncreateopts() did not return a dict'
3653 )
3653 )
3654
3654
3655 if unknownopts:
3655 if unknownopts:
3656 raise error.Abort(
3656 raise error.Abort(
3657 _(
3657 _(
3658 b'unable to create repository because of unknown '
3658 b'unable to create repository because of unknown '
3659 b'creation option: %s'
3659 b'creation option: %s'
3660 )
3660 )
3661 % b', '.join(sorted(unknownopts)),
3661 % b', '.join(sorted(unknownopts)),
3662 hint=_(b'is a required extension not loaded?'),
3662 hint=_(b'is a required extension not loaded?'),
3663 )
3663 )
3664
3664
3665 requirements = newreporequirements(ui, createopts=createopts)
3665 requirements = newreporequirements(ui, createopts=createopts)
3666 requirements -= checkrequirementscompat(ui, requirements)
3666 requirements -= checkrequirementscompat(ui, requirements)
3667
3667
3668 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
3668 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
3669
3669
3670 hgvfs = vfsmod.vfs(wdirvfs.join(b'.hg'))
3670 hgvfs = vfsmod.vfs(wdirvfs.join(b'.hg'))
3671 if hgvfs.exists():
3671 if hgvfs.exists():
3672 raise error.RepoError(_(b'repository %s already exists') % path)
3672 raise error.RepoError(_(b'repository %s already exists') % path)
3673
3673
3674 if b'sharedrepo' in createopts:
3674 if b'sharedrepo' in createopts:
3675 sharedpath = createopts[b'sharedrepo'].sharedpath
3675 sharedpath = createopts[b'sharedrepo'].sharedpath
3676
3676
3677 if createopts.get(b'sharedrelative'):
3677 if createopts.get(b'sharedrelative'):
3678 try:
3678 try:
3679 sharedpath = os.path.relpath(sharedpath, hgvfs.base)
3679 sharedpath = os.path.relpath(sharedpath, hgvfs.base)
3680 sharedpath = util.pconvert(sharedpath)
3680 sharedpath = util.pconvert(sharedpath)
3681 except (IOError, ValueError) as e:
3681 except (IOError, ValueError) as e:
3682 # ValueError is raised on Windows if the drive letters differ
3682 # ValueError is raised on Windows if the drive letters differ
3683 # on each path.
3683 # on each path.
3684 raise error.Abort(
3684 raise error.Abort(
3685 _(b'cannot calculate relative path'),
3685 _(b'cannot calculate relative path'),
3686 hint=stringutil.forcebytestr(e),
3686 hint=stringutil.forcebytestr(e),
3687 )
3687 )
3688
3688
3689 if not wdirvfs.exists():
3689 if not wdirvfs.exists():
3690 wdirvfs.makedirs()
3690 wdirvfs.makedirs()
3691
3691
3692 hgvfs.makedir(notindexed=True)
3692 hgvfs.makedir(notindexed=True)
3693 if b'sharedrepo' not in createopts:
3693 if b'sharedrepo' not in createopts:
3694 hgvfs.mkdir(b'cache')
3694 hgvfs.mkdir(b'cache')
3695 hgvfs.mkdir(b'wcache')
3695 hgvfs.mkdir(b'wcache')
3696
3696
3697 has_store = requirementsmod.STORE_REQUIREMENT in requirements
3697 has_store = requirementsmod.STORE_REQUIREMENT in requirements
3698 if has_store and b'sharedrepo' not in createopts:
3698 if has_store and b'sharedrepo' not in createopts:
3699 hgvfs.mkdir(b'store')
3699 hgvfs.mkdir(b'store')
3700
3700
3701 # We create an invalid changelog outside the store so very old
3701 # We create an invalid changelog outside the store so very old
3702 # Mercurial versions (which didn't know about the requirements
3702 # Mercurial versions (which didn't know about the requirements
3703 # file) encounter an error on reading the changelog. This
3703 # file) encounter an error on reading the changelog. This
3704 # effectively locks out old clients and prevents them from
3704 # effectively locks out old clients and prevents them from
3705 # mucking with a repo in an unknown format.
3705 # mucking with a repo in an unknown format.
3706 #
3706 #
3707 # The revlog header has version 65535, which won't be recognized by
3707 # The revlog header has version 65535, which won't be recognized by
3708 # such old clients.
3708 # such old clients.
3709 hgvfs.append(
3709 hgvfs.append(
3710 b'00changelog.i',
3710 b'00changelog.i',
3711 b'\0\0\xFF\xFF dummy changelog to prevent using the old repo '
3711 b'\0\0\xFF\xFF dummy changelog to prevent using the old repo '
3712 b'layout',
3712 b'layout',
3713 )
3713 )
3714
3714
3715 # Filter the requirements into working copy and store ones
3715 # Filter the requirements into working copy and store ones
3716 wcreq, storereq = scmutil.filterrequirements(requirements)
3716 wcreq, storereq = scmutil.filterrequirements(requirements)
3717 # write working copy ones
3717 # write working copy ones
3718 scmutil.writerequires(hgvfs, wcreq)
3718 scmutil.writerequires(hgvfs, wcreq)
3719 # If there are store requirements and the current repository
3719 # If there are store requirements and the current repository
3720 # is not a shared one, write stored requirements
3720 # is not a shared one, write stored requirements
3721 # For new shared repository, we don't need to write the store
3721 # For new shared repository, we don't need to write the store
3722 # requirements as they are already present in store requires
3722 # requirements as they are already present in store requires
3723 if storereq and b'sharedrepo' not in createopts:
3723 if storereq and b'sharedrepo' not in createopts:
3724 storevfs = vfsmod.vfs(hgvfs.join(b'store'), cacheaudited=True)
3724 storevfs = vfsmod.vfs(hgvfs.join(b'store'), cacheaudited=True)
3725 scmutil.writerequires(storevfs, storereq)
3725 scmutil.writerequires(storevfs, storereq)
3726
3726
3727 # Write out file telling readers where to find the shared store.
3727 # Write out file telling readers where to find the shared store.
3728 if b'sharedrepo' in createopts:
3728 if b'sharedrepo' in createopts:
3729 hgvfs.write(b'sharedpath', sharedpath)
3729 hgvfs.write(b'sharedpath', sharedpath)
3730
3730
3731 if createopts.get(b'shareditems'):
3731 if createopts.get(b'shareditems'):
3732 shared = b'\n'.join(sorted(createopts[b'shareditems'])) + b'\n'
3732 shared = b'\n'.join(sorted(createopts[b'shareditems'])) + b'\n'
3733 hgvfs.write(b'shared', shared)
3733 hgvfs.write(b'shared', shared)
3734
3734
3735
3735
3736 def poisonrepository(repo):
3736 def poisonrepository(repo):
3737 """Poison a repository instance so it can no longer be used."""
3737 """Poison a repository instance so it can no longer be used."""
3738 # Perform any cleanup on the instance.
3738 # Perform any cleanup on the instance.
3739 repo.close()
3739 repo.close()
3740
3740
3741 # Our strategy is to replace the type of the object with one that
3741 # Our strategy is to replace the type of the object with one that
3742 # has all attribute lookups result in error.
3742 # has all attribute lookups result in error.
3743 #
3743 #
3744 # But we have to allow the close() method because some constructors
3744 # But we have to allow the close() method because some constructors
3745 # of repos call close() on repo references.
3745 # of repos call close() on repo references.
3746 class poisonedrepository(object):
3746 class poisonedrepository(object):
3747 def __getattribute__(self, item):
3747 def __getattribute__(self, item):
3748 if item == 'close':
3748 if item == 'close':
3749 return object.__getattribute__(self, item)
3749 return object.__getattribute__(self, item)
3750
3750
3751 raise error.ProgrammingError(
3751 raise error.ProgrammingError(
3752 b'repo instances should not be used after unshare'
3752 b'repo instances should not be used after unshare'
3753 )
3753 )
3754
3754
3755 def close(self):
3755 def close(self):
3756 pass
3756 pass
3757
3757
3758 # We may have a repoview, which intercepts __setattr__. So be sure
3758 # We may have a repoview, which intercepts __setattr__. So be sure
3759 # we operate at the lowest level possible.
3759 # we operate at the lowest level possible.
3760 object.__setattr__(repo, '__class__', poisonedrepository)
3760 object.__setattr__(repo, '__class__', poisonedrepository)
@@ -1,3121 +1,3121 b''
1 # revlog.py - storage back-end for mercurial
1 # revlog.py - storage back-end for mercurial
2 #
2 #
3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """Storage back-end for Mercurial.
8 """Storage back-end for Mercurial.
9
9
10 This provides efficient delta storage with O(1) retrieve and append
10 This provides efficient delta storage with O(1) retrieve and append
11 and O(changes) merge between branches.
11 and O(changes) merge between branches.
12 """
12 """
13
13
14 from __future__ import absolute_import
14 from __future__ import absolute_import
15
15
16 import binascii
16 import binascii
17 import collections
17 import collections
18 import contextlib
18 import contextlib
19 import errno
19 import errno
20 import io
20 import io
21 import os
21 import os
22 import struct
22 import struct
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 .pycompat import getattr
35 from .pycompat import getattr
36 from .revlogutils.constants import (
36 from .revlogutils.constants import (
37 FLAG_GENERALDELTA,
37 FLAG_GENERALDELTA,
38 FLAG_INLINE_DATA,
38 FLAG_INLINE_DATA,
39 INDEX_HEADER,
39 INDEX_HEADER,
40 REVLOGV0,
40 REVLOGV0,
41 REVLOGV1,
41 REVLOGV1,
42 REVLOGV1_FLAGS,
42 REVLOGV1_FLAGS,
43 REVLOGV2,
43 REVLOGV2,
44 REVLOGV2_FLAGS,
44 REVLOGV2_FLAGS,
45 REVLOG_DEFAULT_FLAGS,
45 REVLOG_DEFAULT_FLAGS,
46 REVLOG_DEFAULT_FORMAT,
46 REVLOG_DEFAULT_FORMAT,
47 REVLOG_DEFAULT_VERSION,
47 REVLOG_DEFAULT_VERSION,
48 )
48 )
49 from .revlogutils.flagutil import (
49 from .revlogutils.flagutil import (
50 REVIDX_DEFAULT_FLAGS,
50 REVIDX_DEFAULT_FLAGS,
51 REVIDX_ELLIPSIS,
51 REVIDX_ELLIPSIS,
52 REVIDX_EXTSTORED,
52 REVIDX_EXTSTORED,
53 REVIDX_FLAGS_ORDER,
53 REVIDX_FLAGS_ORDER,
54 REVIDX_HASCOPIESINFO,
54 REVIDX_HASCOPIESINFO,
55 REVIDX_ISCENSORED,
55 REVIDX_ISCENSORED,
56 REVIDX_RAWTEXT_CHANGING_FLAGS,
56 REVIDX_RAWTEXT_CHANGING_FLAGS,
57 REVIDX_SIDEDATA,
57 REVIDX_SIDEDATA,
58 )
58 )
59 from .thirdparty import attr
59 from .thirdparty import attr
60 from . import (
60 from . import (
61 ancestor,
61 ancestor,
62 dagop,
62 dagop,
63 error,
63 error,
64 mdiff,
64 mdiff,
65 policy,
65 policy,
66 pycompat,
66 pycompat,
67 templatefilters,
67 templatefilters,
68 util,
68 util,
69 )
69 )
70 from .interfaces import (
70 from .interfaces import (
71 repository,
71 repository,
72 util as interfaceutil,
72 util as interfaceutil,
73 )
73 )
74 from .revlogutils import (
74 from .revlogutils import (
75 deltas as deltautil,
75 deltas as deltautil,
76 flagutil,
76 flagutil,
77 nodemap as nodemaputil,
77 nodemap as nodemaputil,
78 revlogv0,
78 revlogv0,
79 sidedata as sidedatautil,
79 sidedata as sidedatautil,
80 )
80 )
81 from .utils import (
81 from .utils import (
82 storageutil,
82 storageutil,
83 stringutil,
83 stringutil,
84 )
84 )
85
85
86 # blanked usage of all the name to prevent pyflakes constraints
86 # blanked usage of all the name to prevent pyflakes constraints
87 # We need these name available in the module for extensions.
87 # We need these name available in the module for extensions.
88 REVLOGV0
88 REVLOGV0
89 REVLOGV1
89 REVLOGV1
90 REVLOGV2
90 REVLOGV2
91 FLAG_INLINE_DATA
91 FLAG_INLINE_DATA
92 FLAG_GENERALDELTA
92 FLAG_GENERALDELTA
93 REVLOG_DEFAULT_FLAGS
93 REVLOG_DEFAULT_FLAGS
94 REVLOG_DEFAULT_FORMAT
94 REVLOG_DEFAULT_FORMAT
95 REVLOG_DEFAULT_VERSION
95 REVLOG_DEFAULT_VERSION
96 REVLOGV1_FLAGS
96 REVLOGV1_FLAGS
97 REVLOGV2_FLAGS
97 REVLOGV2_FLAGS
98 REVIDX_ISCENSORED
98 REVIDX_ISCENSORED
99 REVIDX_ELLIPSIS
99 REVIDX_ELLIPSIS
100 REVIDX_SIDEDATA
100 REVIDX_SIDEDATA
101 REVIDX_HASCOPIESINFO
101 REVIDX_HASCOPIESINFO
102 REVIDX_EXTSTORED
102 REVIDX_EXTSTORED
103 REVIDX_DEFAULT_FLAGS
103 REVIDX_DEFAULT_FLAGS
104 REVIDX_FLAGS_ORDER
104 REVIDX_FLAGS_ORDER
105 REVIDX_RAWTEXT_CHANGING_FLAGS
105 REVIDX_RAWTEXT_CHANGING_FLAGS
106
106
107 parsers = policy.importmod('parsers')
107 parsers = policy.importmod('parsers')
108 rustancestor = policy.importrust('ancestor')
108 rustancestor = policy.importrust('ancestor')
109 rustdagop = policy.importrust('dagop')
109 rustdagop = policy.importrust('dagop')
110 rustrevlog = policy.importrust('revlog')
110 rustrevlog = policy.importrust('revlog')
111
111
112 # Aliased for performance.
112 # Aliased for performance.
113 _zlibdecompress = zlib.decompress
113 _zlibdecompress = zlib.decompress
114
114
115 # max size of revlog with inline data
115 # max size of revlog with inline data
116 _maxinline = 131072
116 _maxinline = 131072
117 _chunksize = 1048576
117 _chunksize = 1048576
118
118
119 # Flag processors for REVIDX_ELLIPSIS.
119 # Flag processors for REVIDX_ELLIPSIS.
120 def ellipsisreadprocessor(rl, text):
120 def ellipsisreadprocessor(rl, text):
121 return text, False
121 return text, False
122
122
123
123
124 def ellipsiswriteprocessor(rl, text):
124 def ellipsiswriteprocessor(rl, text):
125 return text, False
125 return text, False
126
126
127
127
128 def ellipsisrawprocessor(rl, text):
128 def ellipsisrawprocessor(rl, text):
129 return False
129 return False
130
130
131
131
132 ellipsisprocessor = (
132 ellipsisprocessor = (
133 ellipsisreadprocessor,
133 ellipsisreadprocessor,
134 ellipsiswriteprocessor,
134 ellipsiswriteprocessor,
135 ellipsisrawprocessor,
135 ellipsisrawprocessor,
136 )
136 )
137
137
138
138
139 def offset_type(offset, type):
139 def offset_type(offset, type):
140 if (type & ~flagutil.REVIDX_KNOWN_FLAGS) != 0:
140 if (type & ~flagutil.REVIDX_KNOWN_FLAGS) != 0:
141 raise ValueError(b'unknown revlog index flags')
141 raise ValueError(b'unknown revlog index flags')
142 return int(int(offset) << 16 | type)
142 return int(int(offset) << 16 | type)
143
143
144
144
145 def _verify_revision(rl, skipflags, state, node):
145 def _verify_revision(rl, skipflags, state, node):
146 """Verify the integrity of the given revlog ``node`` while providing a hook
146 """Verify the integrity of the given revlog ``node`` while providing a hook
147 point for extensions to influence the operation."""
147 point for extensions to influence the operation."""
148 if skipflags:
148 if skipflags:
149 state[b'skipread'].add(node)
149 state[b'skipread'].add(node)
150 else:
150 else:
151 # Side-effect: read content and verify hash.
151 # Side-effect: read content and verify hash.
152 rl.revision(node)
152 rl.revision(node)
153
153
154
154
155 # True if a fast implementation for persistent-nodemap is available
155 # True if a fast implementation for persistent-nodemap is available
156 #
156 #
157 # We also consider we have a "fast" implementation in "pure" python because
157 # We also consider we have a "fast" implementation in "pure" python because
158 # people using pure don't really have performance consideration (and a
158 # people using pure don't really have performance consideration (and a
159 # wheelbarrow of other slowness source)
159 # wheelbarrow of other slowness source)
160 HAS_FAST_PERSISTENT_NODEMAP = rustrevlog is not None or util.safehasattr(
160 HAS_FAST_PERSISTENT_NODEMAP = rustrevlog is not None or util.safehasattr(
161 parsers, 'BaseIndexObject'
161 parsers, 'BaseIndexObject'
162 )
162 )
163
163
164
164
165 @attr.s(slots=True, frozen=True)
165 @attr.s(slots=True, frozen=True)
166 class _revisioninfo(object):
166 class _revisioninfo(object):
167 """Information about a revision that allows building its fulltext
167 """Information about a revision that allows building its fulltext
168 node: expected hash of the revision
168 node: expected hash of the revision
169 p1, p2: parent revs of the revision
169 p1, p2: parent revs of the revision
170 btext: built text cache consisting of a one-element list
170 btext: built text cache consisting of a one-element list
171 cachedelta: (baserev, uncompressed_delta) or None
171 cachedelta: (baserev, uncompressed_delta) or None
172 flags: flags associated to the revision storage
172 flags: flags associated to the revision storage
173
173
174 One of btext[0] or cachedelta must be set.
174 One of btext[0] or cachedelta must be set.
175 """
175 """
176
176
177 node = attr.ib()
177 node = attr.ib()
178 p1 = attr.ib()
178 p1 = attr.ib()
179 p2 = attr.ib()
179 p2 = attr.ib()
180 btext = attr.ib()
180 btext = attr.ib()
181 textlen = attr.ib()
181 textlen = attr.ib()
182 cachedelta = attr.ib()
182 cachedelta = attr.ib()
183 flags = attr.ib()
183 flags = attr.ib()
184
184
185
185
186 @interfaceutil.implementer(repository.irevisiondelta)
186 @interfaceutil.implementer(repository.irevisiondelta)
187 @attr.s(slots=True)
187 @attr.s(slots=True)
188 class revlogrevisiondelta(object):
188 class revlogrevisiondelta(object):
189 node = attr.ib()
189 node = attr.ib()
190 p1node = attr.ib()
190 p1node = attr.ib()
191 p2node = attr.ib()
191 p2node = attr.ib()
192 basenode = attr.ib()
192 basenode = attr.ib()
193 flags = attr.ib()
193 flags = attr.ib()
194 baserevisionsize = attr.ib()
194 baserevisionsize = attr.ib()
195 revision = attr.ib()
195 revision = attr.ib()
196 delta = attr.ib()
196 delta = attr.ib()
197 sidedata = attr.ib()
197 sidedata = attr.ib()
198 linknode = attr.ib(default=None)
198 linknode = attr.ib(default=None)
199
199
200
200
201 @interfaceutil.implementer(repository.iverifyproblem)
201 @interfaceutil.implementer(repository.iverifyproblem)
202 @attr.s(frozen=True)
202 @attr.s(frozen=True)
203 class revlogproblem(object):
203 class revlogproblem(object):
204 warning = attr.ib(default=None)
204 warning = attr.ib(default=None)
205 error = attr.ib(default=None)
205 error = attr.ib(default=None)
206 node = attr.ib(default=None)
206 node = attr.ib(default=None)
207
207
208
208
209 def parse_index_v1(data, inline):
209 def parse_index_v1(data, inline):
210 # call the C implementation to parse the index data
210 # call the C implementation to parse the index data
211 index, cache = parsers.parse_index2(data, inline)
211 index, cache = parsers.parse_index2(data, inline)
212 return index, cache
212 return index, cache
213
213
214
214
215 def parse_index_v2(data, inline):
215 def parse_index_v2(data, inline):
216 # call the C implementation to parse the index data
216 # call the C implementation to parse the index data
217 index, cache = parsers.parse_index2(data, inline, revlogv2=True)
217 index, cache = parsers.parse_index2(data, inline, revlogv2=True)
218 return index, cache
218 return index, cache
219
219
220
220
221 if util.safehasattr(parsers, 'parse_index_devel_nodemap'):
221 if util.safehasattr(parsers, 'parse_index_devel_nodemap'):
222
222
223 def parse_index_v1_nodemap(data, inline):
223 def parse_index_v1_nodemap(data, inline):
224 index, cache = parsers.parse_index_devel_nodemap(data, inline)
224 index, cache = parsers.parse_index_devel_nodemap(data, inline)
225 return index, cache
225 return index, cache
226
226
227
227
228 else:
228 else:
229 parse_index_v1_nodemap = None
229 parse_index_v1_nodemap = None
230
230
231
231
232 def parse_index_v1_mixed(data, inline):
232 def parse_index_v1_mixed(data, inline):
233 index, cache = parse_index_v1(data, inline)
233 index, cache = parse_index_v1(data, inline)
234 return rustrevlog.MixedIndex(index), cache
234 return rustrevlog.MixedIndex(index), cache
235
235
236
236
237 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
237 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
238 # signed integer)
238 # signed integer)
239 _maxentrysize = 0x7FFFFFFF
239 _maxentrysize = 0x7FFFFFFF
240
240
241
241
242 class revlog(object):
242 class revlog(object):
243 """
243 """
244 the underlying revision storage object
244 the underlying revision storage object
245
245
246 A revlog consists of two parts, an index and the revision data.
246 A revlog consists of two parts, an index and the revision data.
247
247
248 The index is a file with a fixed record size containing
248 The index is a file with a fixed record size containing
249 information on each revision, including its nodeid (hash), the
249 information on each revision, including its nodeid (hash), the
250 nodeids of its parents, the position and offset of its data within
250 nodeids of its parents, the position and offset of its data within
251 the data file, and the revision it's based on. Finally, each entry
251 the data file, and the revision it's based on. Finally, each entry
252 contains a linkrev entry that can serve as a pointer to external
252 contains a linkrev entry that can serve as a pointer to external
253 data.
253 data.
254
254
255 The revision data itself is a linear collection of data chunks.
255 The revision data itself is a linear collection of data chunks.
256 Each chunk represents a revision and is usually represented as a
256 Each chunk represents a revision and is usually represented as a
257 delta against the previous chunk. To bound lookup time, runs of
257 delta against the previous chunk. To bound lookup time, runs of
258 deltas are limited to about 2 times the length of the original
258 deltas are limited to about 2 times the length of the original
259 version data. This makes retrieval of a version proportional to
259 version data. This makes retrieval of a version proportional to
260 its size, or O(1) relative to the number of revisions.
260 its size, or O(1) relative to the number of revisions.
261
261
262 Both pieces of the revlog are written to in an append-only
262 Both pieces of the revlog are written to in an append-only
263 fashion, which means we never need to rewrite a file to insert or
263 fashion, which means we never need to rewrite a file to insert or
264 remove data, and can use some simple techniques to avoid the need
264 remove data, and can use some simple techniques to avoid the need
265 for locking while reading.
265 for locking while reading.
266
266
267 If checkambig, indexfile is opened with checkambig=True at
267 If checkambig, indexfile is opened with checkambig=True at
268 writing, to avoid file stat ambiguity.
268 writing, to avoid file stat ambiguity.
269
269
270 If mmaplargeindex is True, and an mmapindexthreshold is set, the
270 If mmaplargeindex is True, and an mmapindexthreshold is set, the
271 index will be mmapped rather than read if it is larger than the
271 index will be mmapped rather than read if it is larger than the
272 configured threshold.
272 configured threshold.
273
273
274 If censorable is True, the revlog can have censored revisions.
274 If censorable is True, the revlog can have censored revisions.
275
275
276 If `upperboundcomp` is not None, this is the expected maximal gain from
276 If `upperboundcomp` is not None, this is the expected maximal gain from
277 compression for the data content.
277 compression for the data content.
278
278
279 `concurrencychecker` is an optional function that receives 3 arguments: a
279 `concurrencychecker` is an optional function that receives 3 arguments: a
280 file handle, a filename, and an expected position. It should check whether
280 file handle, a filename, and an expected position. It should check whether
281 the current position in the file handle is valid, and log/warn/fail (by
281 the current position in the file handle is valid, and log/warn/fail (by
282 raising).
282 raising).
283 """
283 """
284
284
285 _flagserrorclass = error.RevlogError
285 _flagserrorclass = error.RevlogError
286
286
287 def __init__(
287 def __init__(
288 self,
288 self,
289 opener,
289 opener,
290 indexfile,
290 indexfile,
291 datafile=None,
291 datafile=None,
292 checkambig=False,
292 checkambig=False,
293 mmaplargeindex=False,
293 mmaplargeindex=False,
294 censorable=False,
294 censorable=False,
295 upperboundcomp=None,
295 upperboundcomp=None,
296 persistentnodemap=False,
296 persistentnodemap=False,
297 concurrencychecker=None,
297 concurrencychecker=None,
298 ):
298 ):
299 """
299 """
300 create a revlog object
300 create a revlog object
301
301
302 opener is a function that abstracts the file opening operation
302 opener is a function that abstracts the file opening operation
303 and can be used to implement COW semantics or the like.
303 and can be used to implement COW semantics or the like.
304
304
305 """
305 """
306 self.upperboundcomp = upperboundcomp
306 self.upperboundcomp = upperboundcomp
307 self.indexfile = indexfile
307 self.indexfile = indexfile
308 self.datafile = datafile or (indexfile[:-2] + b".d")
308 self.datafile = datafile or (indexfile[:-2] + b".d")
309 self.nodemap_file = None
309 self.nodemap_file = None
310 if persistentnodemap:
310 if persistentnodemap:
311 self.nodemap_file = nodemaputil.get_nodemap_file(
311 self.nodemap_file = nodemaputil.get_nodemap_file(
312 opener, self.indexfile
312 opener, self.indexfile
313 )
313 )
314
314
315 self.opener = opener
315 self.opener = opener
316 # When True, indexfile is opened with checkambig=True at writing, to
316 # When True, indexfile is opened with checkambig=True at writing, to
317 # avoid file stat ambiguity.
317 # avoid file stat ambiguity.
318 self._checkambig = checkambig
318 self._checkambig = checkambig
319 self._mmaplargeindex = mmaplargeindex
319 self._mmaplargeindex = mmaplargeindex
320 self._censorable = censorable
320 self._censorable = censorable
321 # 3-tuple of (node, rev, text) for a raw revision.
321 # 3-tuple of (node, rev, text) for a raw revision.
322 self._revisioncache = None
322 self._revisioncache = None
323 # Maps rev to chain base rev.
323 # Maps rev to chain base rev.
324 self._chainbasecache = util.lrucachedict(100)
324 self._chainbasecache = util.lrucachedict(100)
325 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
325 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
326 self._chunkcache = (0, b'')
326 self._chunkcache = (0, b'')
327 # How much data to read and cache into the raw revlog data cache.
327 # How much data to read and cache into the raw revlog data cache.
328 self._chunkcachesize = 65536
328 self._chunkcachesize = 65536
329 self._maxchainlen = None
329 self._maxchainlen = None
330 self._deltabothparents = True
330 self._deltabothparents = True
331 self.index = None
331 self.index = None
332 self._nodemap_docket = None
332 self._nodemap_docket = None
333 # Mapping of partial identifiers to full nodes.
333 # Mapping of partial identifiers to full nodes.
334 self._pcache = {}
334 self._pcache = {}
335 # Mapping of revision integer to full node.
335 # Mapping of revision integer to full node.
336 self._compengine = b'zlib'
336 self._compengine = b'zlib'
337 self._compengineopts = {}
337 self._compengineopts = {}
338 self._maxdeltachainspan = -1
338 self._maxdeltachainspan = -1
339 self._withsparseread = False
339 self._withsparseread = False
340 self._sparserevlog = False
340 self._sparserevlog = False
341 self._srdensitythreshold = 0.50
341 self._srdensitythreshold = 0.50
342 self._srmingapsize = 262144
342 self._srmingapsize = 262144
343
343
344 # Make copy of flag processors so each revlog instance can support
344 # Make copy of flag processors so each revlog instance can support
345 # custom flags.
345 # custom flags.
346 self._flagprocessors = dict(flagutil.flagprocessors)
346 self._flagprocessors = dict(flagutil.flagprocessors)
347
347
348 # 2-tuple of file handles being used for active writing.
348 # 2-tuple of file handles being used for active writing.
349 self._writinghandles = None
349 self._writinghandles = None
350
350
351 self._loadindex()
351 self._loadindex()
352
352
353 self._concurrencychecker = concurrencychecker
353 self._concurrencychecker = concurrencychecker
354
354
355 def _loadindex(self):
355 def _loadindex(self):
356 mmapindexthreshold = None
356 mmapindexthreshold = None
357 opts = self.opener.options
357 opts = self.opener.options
358
358
359 if b'revlogv2' in opts:
359 if b'revlogv2' in opts:
360 newversionflags = REVLOGV2 | FLAG_INLINE_DATA
360 newversionflags = REVLOGV2 | FLAG_INLINE_DATA
361 elif b'revlogv1' in opts:
361 elif b'revlogv1' in opts:
362 newversionflags = REVLOGV1 | FLAG_INLINE_DATA
362 newversionflags = REVLOGV1 | FLAG_INLINE_DATA
363 if b'generaldelta' in opts:
363 if b'generaldelta' in opts:
364 newversionflags |= FLAG_GENERALDELTA
364 newversionflags |= FLAG_GENERALDELTA
365 elif b'revlogv0' in self.opener.options:
365 elif b'revlogv0' in self.opener.options:
366 newversionflags = REVLOGV0
366 newversionflags = REVLOGV0
367 else:
367 else:
368 newversionflags = REVLOG_DEFAULT_VERSION
368 newversionflags = REVLOG_DEFAULT_VERSION
369
369
370 if b'chunkcachesize' in opts:
370 if b'chunkcachesize' in opts:
371 self._chunkcachesize = opts[b'chunkcachesize']
371 self._chunkcachesize = opts[b'chunkcachesize']
372 if b'maxchainlen' in opts:
372 if b'maxchainlen' in opts:
373 self._maxchainlen = opts[b'maxchainlen']
373 self._maxchainlen = opts[b'maxchainlen']
374 if b'deltabothparents' in opts:
374 if b'deltabothparents' in opts:
375 self._deltabothparents = opts[b'deltabothparents']
375 self._deltabothparents = opts[b'deltabothparents']
376 self._lazydelta = bool(opts.get(b'lazydelta', True))
376 self._lazydelta = bool(opts.get(b'lazydelta', True))
377 self._lazydeltabase = False
377 self._lazydeltabase = False
378 if self._lazydelta:
378 if self._lazydelta:
379 self._lazydeltabase = bool(opts.get(b'lazydeltabase', False))
379 self._lazydeltabase = bool(opts.get(b'lazydeltabase', False))
380 if b'compengine' in opts:
380 if b'compengine' in opts:
381 self._compengine = opts[b'compengine']
381 self._compengine = opts[b'compengine']
382 if b'zlib.level' in opts:
382 if b'zlib.level' in opts:
383 self._compengineopts[b'zlib.level'] = opts[b'zlib.level']
383 self._compengineopts[b'zlib.level'] = opts[b'zlib.level']
384 if b'zstd.level' in opts:
384 if b'zstd.level' in opts:
385 self._compengineopts[b'zstd.level'] = opts[b'zstd.level']
385 self._compengineopts[b'zstd.level'] = opts[b'zstd.level']
386 if b'maxdeltachainspan' in opts:
386 if b'maxdeltachainspan' in opts:
387 self._maxdeltachainspan = opts[b'maxdeltachainspan']
387 self._maxdeltachainspan = opts[b'maxdeltachainspan']
388 if self._mmaplargeindex and b'mmapindexthreshold' in opts:
388 if self._mmaplargeindex and b'mmapindexthreshold' in opts:
389 mmapindexthreshold = opts[b'mmapindexthreshold']
389 mmapindexthreshold = opts[b'mmapindexthreshold']
390 self.hassidedata = bool(opts.get(b'side-data', False))
390 self.hassidedata = bool(opts.get(b'side-data', False))
391 self._sparserevlog = bool(opts.get(b'sparse-revlog', False))
391 self._sparserevlog = bool(opts.get(b'sparse-revlog', False))
392 withsparseread = bool(opts.get(b'with-sparse-read', False))
392 withsparseread = bool(opts.get(b'with-sparse-read', False))
393 # sparse-revlog forces sparse-read
393 # sparse-revlog forces sparse-read
394 self._withsparseread = self._sparserevlog or withsparseread
394 self._withsparseread = self._sparserevlog or withsparseread
395 if b'sparse-read-density-threshold' in opts:
395 if b'sparse-read-density-threshold' in opts:
396 self._srdensitythreshold = opts[b'sparse-read-density-threshold']
396 self._srdensitythreshold = opts[b'sparse-read-density-threshold']
397 if b'sparse-read-min-gap-size' in opts:
397 if b'sparse-read-min-gap-size' in opts:
398 self._srmingapsize = opts[b'sparse-read-min-gap-size']
398 self._srmingapsize = opts[b'sparse-read-min-gap-size']
399 if opts.get(b'enableellipsis'):
399 if opts.get(b'enableellipsis'):
400 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
400 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
401
401
402 # revlog v0 doesn't have flag processors
402 # revlog v0 doesn't have flag processors
403 for flag, processor in pycompat.iteritems(
403 for flag, processor in pycompat.iteritems(
404 opts.get(b'flagprocessors', {})
404 opts.get(b'flagprocessors', {})
405 ):
405 ):
406 flagutil.insertflagprocessor(flag, processor, self._flagprocessors)
406 flagutil.insertflagprocessor(flag, processor, self._flagprocessors)
407
407
408 if self._chunkcachesize <= 0:
408 if self._chunkcachesize <= 0:
409 raise error.RevlogError(
409 raise error.RevlogError(
410 _(b'revlog chunk cache size %r is not greater than 0')
410 _(b'revlog chunk cache size %r is not greater than 0')
411 % self._chunkcachesize
411 % self._chunkcachesize
412 )
412 )
413 elif self._chunkcachesize & (self._chunkcachesize - 1):
413 elif self._chunkcachesize & (self._chunkcachesize - 1):
414 raise error.RevlogError(
414 raise error.RevlogError(
415 _(b'revlog chunk cache size %r is not a power of 2')
415 _(b'revlog chunk cache size %r is not a power of 2')
416 % self._chunkcachesize
416 % self._chunkcachesize
417 )
417 )
418
418
419 indexdata = b''
419 indexdata = b''
420 self._initempty = True
420 self._initempty = True
421 try:
421 try:
422 with self._indexfp() as f:
422 with self._indexfp() as f:
423 if (
423 if (
424 mmapindexthreshold is not None
424 mmapindexthreshold is not None
425 and self.opener.fstat(f).st_size >= mmapindexthreshold
425 and self.opener.fstat(f).st_size >= mmapindexthreshold
426 ):
426 ):
427 # TODO: should .close() to release resources without
427 # TODO: should .close() to release resources without
428 # relying on Python GC
428 # relying on Python GC
429 indexdata = util.buffer(util.mmapread(f))
429 indexdata = util.buffer(util.mmapread(f))
430 else:
430 else:
431 indexdata = f.read()
431 indexdata = f.read()
432 if len(indexdata) > 0:
432 if len(indexdata) > 0:
433 versionflags = INDEX_HEADER.unpack(indexdata[:4])[0]
433 versionflags = INDEX_HEADER.unpack(indexdata[:4])[0]
434 self._initempty = False
434 self._initempty = False
435 else:
435 else:
436 versionflags = newversionflags
436 versionflags = newversionflags
437 except IOError as inst:
437 except IOError as inst:
438 if inst.errno != errno.ENOENT:
438 if inst.errno != errno.ENOENT:
439 raise
439 raise
440
440
441 versionflags = newversionflags
441 versionflags = newversionflags
442
442
443 self.version = versionflags
443 self.version = versionflags
444
444
445 flags = versionflags & ~0xFFFF
445 flags = versionflags & ~0xFFFF
446 fmt = versionflags & 0xFFFF
446 fmt = versionflags & 0xFFFF
447
447
448 if fmt == REVLOGV0:
448 if fmt == REVLOGV0:
449 if flags:
449 if flags:
450 raise error.RevlogError(
450 raise error.RevlogError(
451 _(b'unknown flags (%#04x) in version %d revlog %s')
451 _(b'unknown flags (%#04x) in version %d revlog %s')
452 % (flags >> 16, fmt, self.indexfile)
452 % (flags >> 16, fmt, self.indexfile)
453 )
453 )
454
454
455 self._inline = False
455 self._inline = False
456 self._generaldelta = False
456 self._generaldelta = False
457
457
458 elif fmt == REVLOGV1:
458 elif fmt == REVLOGV1:
459 if flags & ~REVLOGV1_FLAGS:
459 if flags & ~REVLOGV1_FLAGS:
460 raise error.RevlogError(
460 raise error.RevlogError(
461 _(b'unknown flags (%#04x) in version %d revlog %s')
461 _(b'unknown flags (%#04x) in version %d revlog %s')
462 % (flags >> 16, fmt, self.indexfile)
462 % (flags >> 16, fmt, self.indexfile)
463 )
463 )
464
464
465 self._inline = versionflags & FLAG_INLINE_DATA
465 self._inline = versionflags & FLAG_INLINE_DATA
466 self._generaldelta = versionflags & FLAG_GENERALDELTA
466 self._generaldelta = versionflags & FLAG_GENERALDELTA
467
467
468 elif fmt == REVLOGV2:
468 elif fmt == REVLOGV2:
469 if flags & ~REVLOGV2_FLAGS:
469 if flags & ~REVLOGV2_FLAGS:
470 raise error.RevlogError(
470 raise error.RevlogError(
471 _(b'unknown flags (%#04x) in version %d revlog %s')
471 _(b'unknown flags (%#04x) in version %d revlog %s')
472 % (flags >> 16, fmt, self.indexfile)
472 % (flags >> 16, fmt, self.indexfile)
473 )
473 )
474
474
475 # There is a bug in the transaction handling when going from an
475 # There is a bug in the transaction handling when going from an
476 # inline revlog to a separate index and data file. Turn it off until
476 # inline revlog to a separate index and data file. Turn it off until
477 # it's fixed, since v2 revlogs sometimes get rewritten on exchange.
477 # it's fixed, since v2 revlogs sometimes get rewritten on exchange.
478 # See issue6485
478 # See issue6485
479 self._inline = False
479 self._inline = False
480 # generaldelta implied by version 2 revlogs.
480 # generaldelta implied by version 2 revlogs.
481 self._generaldelta = True
481 self._generaldelta = True
482
482
483 else:
483 else:
484 raise error.RevlogError(
484 raise error.RevlogError(
485 _(b'unknown version (%d) in revlog %s') % (fmt, self.indexfile)
485 _(b'unknown version (%d) in revlog %s') % (fmt, self.indexfile)
486 )
486 )
487
487
488 self.nodeconstants = sha1nodeconstants
488 self.nodeconstants = sha1nodeconstants
489 self.nullid = self.nodeconstants.nullid
489 self.nullid = self.nodeconstants.nullid
490
490
491 # sparse-revlog can't be on without general-delta (issue6056)
491 # sparse-revlog can't be on without general-delta (issue6056)
492 if not self._generaldelta:
492 if not self._generaldelta:
493 self._sparserevlog = False
493 self._sparserevlog = False
494
494
495 self._storedeltachains = True
495 self._storedeltachains = True
496
496
497 devel_nodemap = (
497 devel_nodemap = (
498 self.nodemap_file
498 self.nodemap_file
499 and opts.get(b'devel-force-nodemap', False)
499 and opts.get(b'devel-force-nodemap', False)
500 and parse_index_v1_nodemap is not None
500 and parse_index_v1_nodemap is not None
501 )
501 )
502
502
503 use_rust_index = False
503 use_rust_index = False
504 if rustrevlog is not None:
504 if rustrevlog is not None:
505 if self.nodemap_file is not None:
505 if self.nodemap_file is not None:
506 use_rust_index = True
506 use_rust_index = True
507 else:
507 else:
508 use_rust_index = self.opener.options.get(b'rust.index')
508 use_rust_index = self.opener.options.get(b'rust.index')
509
509
510 self._parse_index = parse_index_v1
510 self._parse_index = parse_index_v1
511 if self.version == REVLOGV0:
511 if self.version == REVLOGV0:
512 self._parse_index = revlogv0.parse_index_v0
512 self._parse_index = revlogv0.parse_index_v0
513 elif fmt == REVLOGV2:
513 elif fmt == REVLOGV2:
514 self._parse_index = parse_index_v2
514 self._parse_index = parse_index_v2
515 elif devel_nodemap:
515 elif devel_nodemap:
516 self._parse_index = parse_index_v1_nodemap
516 self._parse_index = parse_index_v1_nodemap
517 elif use_rust_index:
517 elif use_rust_index:
518 self._parse_index = parse_index_v1_mixed
518 self._parse_index = parse_index_v1_mixed
519 try:
519 try:
520 d = self._parse_index(indexdata, self._inline)
520 d = self._parse_index(indexdata, self._inline)
521 index, _chunkcache = d
521 index, _chunkcache = d
522 use_nodemap = (
522 use_nodemap = (
523 not self._inline
523 not self._inline
524 and self.nodemap_file is not None
524 and self.nodemap_file is not None
525 and util.safehasattr(index, 'update_nodemap_data')
525 and util.safehasattr(index, 'update_nodemap_data')
526 )
526 )
527 if use_nodemap:
527 if use_nodemap:
528 nodemap_data = nodemaputil.persisted_data(self)
528 nodemap_data = nodemaputil.persisted_data(self)
529 if nodemap_data is not None:
529 if nodemap_data is not None:
530 docket = nodemap_data[0]
530 docket = nodemap_data[0]
531 if (
531 if (
532 len(d[0]) > docket.tip_rev
532 len(d[0]) > docket.tip_rev
533 and d[0][docket.tip_rev][7] == docket.tip_node
533 and d[0][docket.tip_rev][7] == docket.tip_node
534 ):
534 ):
535 # no changelog tampering
535 # no changelog tampering
536 self._nodemap_docket = docket
536 self._nodemap_docket = docket
537 index.update_nodemap_data(*nodemap_data)
537 index.update_nodemap_data(*nodemap_data)
538 except (ValueError, IndexError):
538 except (ValueError, IndexError):
539 raise error.RevlogError(
539 raise error.RevlogError(
540 _(b"index %s is corrupted") % self.indexfile
540 _(b"index %s is corrupted") % self.indexfile
541 )
541 )
542 self.index, self._chunkcache = d
542 self.index, self._chunkcache = d
543 if not self._chunkcache:
543 if not self._chunkcache:
544 self._chunkclear()
544 self._chunkclear()
545 # revnum -> (chain-length, sum-delta-length)
545 # revnum -> (chain-length, sum-delta-length)
546 self._chaininfocache = util.lrucachedict(500)
546 self._chaininfocache = util.lrucachedict(500)
547 # revlog header -> revlog compressor
547 # revlog header -> revlog compressor
548 self._decompressors = {}
548 self._decompressors = {}
549
549
550 @util.propertycache
550 @util.propertycache
551 def _compressor(self):
551 def _compressor(self):
552 engine = util.compengines[self._compengine]
552 engine = util.compengines[self._compengine]
553 return engine.revlogcompressor(self._compengineopts)
553 return engine.revlogcompressor(self._compengineopts)
554
554
555 def _indexfp(self, mode=b'r'):
555 def _indexfp(self, mode=b'r'):
556 """file object for the revlog's index file"""
556 """file object for the revlog's index file"""
557 args = {'mode': mode}
557 args = {'mode': mode}
558 if mode != b'r':
558 if mode != b'r':
559 args['checkambig'] = self._checkambig
559 args['checkambig'] = self._checkambig
560 if mode == b'w':
560 if mode == b'w':
561 args['atomictemp'] = True
561 args['atomictemp'] = True
562 return self.opener(self.indexfile, **args)
562 return self.opener(self.indexfile, **args)
563
563
564 def _datafp(self, mode=b'r'):
564 def _datafp(self, mode=b'r'):
565 """file object for the revlog's data file"""
565 """file object for the revlog's data file"""
566 return self.opener(self.datafile, mode=mode)
566 return self.opener(self.datafile, mode=mode)
567
567
568 @contextlib.contextmanager
568 @contextlib.contextmanager
569 def _datareadfp(self, existingfp=None):
569 def _datareadfp(self, existingfp=None):
570 """file object suitable to read data"""
570 """file object suitable to read data"""
571 # Use explicit file handle, if given.
571 # Use explicit file handle, if given.
572 if existingfp is not None:
572 if existingfp is not None:
573 yield existingfp
573 yield existingfp
574
574
575 # Use a file handle being actively used for writes, if available.
575 # Use a file handle being actively used for writes, if available.
576 # There is some danger to doing this because reads will seek the
576 # There is some danger to doing this because reads will seek the
577 # file. However, _writeentry() performs a SEEK_END before all writes,
577 # file. However, _writeentry() performs a SEEK_END before all writes,
578 # so we should be safe.
578 # so we should be safe.
579 elif self._writinghandles:
579 elif self._writinghandles:
580 if self._inline:
580 if self._inline:
581 yield self._writinghandles[0]
581 yield self._writinghandles[0]
582 else:
582 else:
583 yield self._writinghandles[1]
583 yield self._writinghandles[1]
584
584
585 # Otherwise open a new file handle.
585 # Otherwise open a new file handle.
586 else:
586 else:
587 if self._inline:
587 if self._inline:
588 func = self._indexfp
588 func = self._indexfp
589 else:
589 else:
590 func = self._datafp
590 func = self._datafp
591 with func() as fp:
591 with func() as fp:
592 yield fp
592 yield fp
593
593
594 def tiprev(self):
594 def tiprev(self):
595 return len(self.index) - 1
595 return len(self.index) - 1
596
596
597 def tip(self):
597 def tip(self):
598 return self.node(self.tiprev())
598 return self.node(self.tiprev())
599
599
600 def __contains__(self, rev):
600 def __contains__(self, rev):
601 return 0 <= rev < len(self)
601 return 0 <= rev < len(self)
602
602
603 def __len__(self):
603 def __len__(self):
604 return len(self.index)
604 return len(self.index)
605
605
606 def __iter__(self):
606 def __iter__(self):
607 return iter(pycompat.xrange(len(self)))
607 return iter(pycompat.xrange(len(self)))
608
608
609 def revs(self, start=0, stop=None):
609 def revs(self, start=0, stop=None):
610 """iterate over all rev in this revlog (from start to stop)"""
610 """iterate over all rev in this revlog (from start to stop)"""
611 return storageutil.iterrevs(len(self), start=start, stop=stop)
611 return storageutil.iterrevs(len(self), start=start, stop=stop)
612
612
613 @property
613 @property
614 def nodemap(self):
614 def nodemap(self):
615 msg = (
615 msg = (
616 b"revlog.nodemap is deprecated, "
616 b"revlog.nodemap is deprecated, "
617 b"use revlog.index.[has_node|rev|get_rev]"
617 b"use revlog.index.[has_node|rev|get_rev]"
618 )
618 )
619 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
619 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
620 return self.index.nodemap
620 return self.index.nodemap
621
621
622 @property
622 @property
623 def _nodecache(self):
623 def _nodecache(self):
624 msg = b"revlog._nodecache is deprecated, use revlog.index.nodemap"
624 msg = b"revlog._nodecache is deprecated, use revlog.index.nodemap"
625 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
625 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
626 return self.index.nodemap
626 return self.index.nodemap
627
627
628 def hasnode(self, node):
628 def hasnode(self, node):
629 try:
629 try:
630 self.rev(node)
630 self.rev(node)
631 return True
631 return True
632 except KeyError:
632 except KeyError:
633 return False
633 return False
634
634
635 def candelta(self, baserev, rev):
635 def candelta(self, baserev, rev):
636 """whether two revisions (baserev, rev) can be delta-ed or not"""
636 """whether two revisions (baserev, rev) can be delta-ed or not"""
637 # Disable delta if either rev requires a content-changing flag
637 # Disable delta if either rev requires a content-changing flag
638 # processor (ex. LFS). This is because such flag processor can alter
638 # processor (ex. LFS). This is because such flag processor can alter
639 # the rawtext content that the delta will be based on, and two clients
639 # the rawtext content that the delta will be based on, and two clients
640 # could have a same revlog node with different flags (i.e. different
640 # could have a same revlog node with different flags (i.e. different
641 # rawtext contents) and the delta could be incompatible.
641 # rawtext contents) and the delta could be incompatible.
642 if (self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS) or (
642 if (self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS) or (
643 self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS
643 self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS
644 ):
644 ):
645 return False
645 return False
646 return True
646 return True
647
647
648 def update_caches(self, transaction):
648 def update_caches(self, transaction):
649 if self.nodemap_file is not None:
649 if self.nodemap_file is not None:
650 if transaction is None:
650 if transaction is None:
651 nodemaputil.update_persistent_nodemap(self)
651 nodemaputil.update_persistent_nodemap(self)
652 else:
652 else:
653 nodemaputil.setup_persistent_nodemap(transaction, self)
653 nodemaputil.setup_persistent_nodemap(transaction, self)
654
654
655 def clearcaches(self):
655 def clearcaches(self):
656 self._revisioncache = None
656 self._revisioncache = None
657 self._chainbasecache.clear()
657 self._chainbasecache.clear()
658 self._chunkcache = (0, b'')
658 self._chunkcache = (0, b'')
659 self._pcache = {}
659 self._pcache = {}
660 self._nodemap_docket = None
660 self._nodemap_docket = None
661 self.index.clearcaches()
661 self.index.clearcaches()
662 # The python code is the one responsible for validating the docket, we
662 # The python code is the one responsible for validating the docket, we
663 # end up having to refresh it here.
663 # end up having to refresh it here.
664 use_nodemap = (
664 use_nodemap = (
665 not self._inline
665 not self._inline
666 and self.nodemap_file is not None
666 and self.nodemap_file is not None
667 and util.safehasattr(self.index, 'update_nodemap_data')
667 and util.safehasattr(self.index, 'update_nodemap_data')
668 )
668 )
669 if use_nodemap:
669 if use_nodemap:
670 nodemap_data = nodemaputil.persisted_data(self)
670 nodemap_data = nodemaputil.persisted_data(self)
671 if nodemap_data is not None:
671 if nodemap_data is not None:
672 self._nodemap_docket = nodemap_data[0]
672 self._nodemap_docket = nodemap_data[0]
673 self.index.update_nodemap_data(*nodemap_data)
673 self.index.update_nodemap_data(*nodemap_data)
674
674
675 def rev(self, node):
675 def rev(self, node):
676 try:
676 try:
677 return self.index.rev(node)
677 return self.index.rev(node)
678 except TypeError:
678 except TypeError:
679 raise
679 raise
680 except error.RevlogError:
680 except error.RevlogError:
681 # parsers.c radix tree lookup failed
681 # parsers.c radix tree lookup failed
682 if (
682 if (
683 node == self.nodeconstants.wdirid
683 node == self.nodeconstants.wdirid
684 or node in self.nodeconstants.wdirfilenodeids
684 or node in self.nodeconstants.wdirfilenodeids
685 ):
685 ):
686 raise error.WdirUnsupported
686 raise error.WdirUnsupported
687 raise error.LookupError(node, self.indexfile, _(b'no node'))
687 raise error.LookupError(node, self.indexfile, _(b'no node'))
688
688
689 # Accessors for index entries.
689 # Accessors for index entries.
690
690
691 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
691 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
692 # are flags.
692 # are flags.
693 def start(self, rev):
693 def start(self, rev):
694 return int(self.index[rev][0] >> 16)
694 return int(self.index[rev][0] >> 16)
695
695
696 def flags(self, rev):
696 def flags(self, rev):
697 return self.index[rev][0] & 0xFFFF
697 return self.index[rev][0] & 0xFFFF
698
698
699 def length(self, rev):
699 def length(self, rev):
700 return self.index[rev][1]
700 return self.index[rev][1]
701
701
702 def sidedata_length(self, rev):
702 def sidedata_length(self, rev):
703 if self.version & 0xFFFF != REVLOGV2:
703 if self.version & 0xFFFF != REVLOGV2:
704 return 0
704 return 0
705 return self.index[rev][9]
705 return self.index[rev][9]
706
706
707 def rawsize(self, rev):
707 def rawsize(self, rev):
708 """return the length of the uncompressed text for a given revision"""
708 """return the length of the uncompressed text for a given revision"""
709 l = self.index[rev][2]
709 l = self.index[rev][2]
710 if l >= 0:
710 if l >= 0:
711 return l
711 return l
712
712
713 t = self.rawdata(rev)
713 t = self.rawdata(rev)
714 return len(t)
714 return len(t)
715
715
716 def size(self, rev):
716 def size(self, rev):
717 """length of non-raw text (processed by a "read" flag processor)"""
717 """length of non-raw text (processed by a "read" flag processor)"""
718 # fast path: if no "read" flag processor could change the content,
718 # fast path: if no "read" flag processor could change the content,
719 # size is rawsize. note: ELLIPSIS is known to not change the content.
719 # size is rawsize. note: ELLIPSIS is known to not change the content.
720 flags = self.flags(rev)
720 flags = self.flags(rev)
721 if flags & (flagutil.REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
721 if flags & (flagutil.REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
722 return self.rawsize(rev)
722 return self.rawsize(rev)
723
723
724 return len(self.revision(rev, raw=False))
724 return len(self.revision(rev, raw=False))
725
725
726 def chainbase(self, rev):
726 def chainbase(self, rev):
727 base = self._chainbasecache.get(rev)
727 base = self._chainbasecache.get(rev)
728 if base is not None:
728 if base is not None:
729 return base
729 return base
730
730
731 index = self.index
731 index = self.index
732 iterrev = rev
732 iterrev = rev
733 base = index[iterrev][3]
733 base = index[iterrev][3]
734 while base != iterrev:
734 while base != iterrev:
735 iterrev = base
735 iterrev = base
736 base = index[iterrev][3]
736 base = index[iterrev][3]
737
737
738 self._chainbasecache[rev] = base
738 self._chainbasecache[rev] = base
739 return base
739 return base
740
740
741 def linkrev(self, rev):
741 def linkrev(self, rev):
742 return self.index[rev][4]
742 return self.index[rev][4]
743
743
744 def parentrevs(self, rev):
744 def parentrevs(self, rev):
745 try:
745 try:
746 entry = self.index[rev]
746 entry = self.index[rev]
747 except IndexError:
747 except IndexError:
748 if rev == wdirrev:
748 if rev == wdirrev:
749 raise error.WdirUnsupported
749 raise error.WdirUnsupported
750 raise
750 raise
751 if entry[5] == nullrev:
751 if entry[5] == nullrev:
752 return entry[6], entry[5]
752 return entry[6], entry[5]
753 else:
753 else:
754 return entry[5], entry[6]
754 return entry[5], entry[6]
755
755
756 # fast parentrevs(rev) where rev isn't filtered
756 # fast parentrevs(rev) where rev isn't filtered
757 _uncheckedparentrevs = parentrevs
757 _uncheckedparentrevs = parentrevs
758
758
759 def node(self, rev):
759 def node(self, rev):
760 try:
760 try:
761 return self.index[rev][7]
761 return self.index[rev][7]
762 except IndexError:
762 except IndexError:
763 if rev == wdirrev:
763 if rev == wdirrev:
764 raise error.WdirUnsupported
764 raise error.WdirUnsupported
765 raise
765 raise
766
766
767 # Derived from index values.
767 # Derived from index values.
768
768
769 def end(self, rev):
769 def end(self, rev):
770 return self.start(rev) + self.length(rev)
770 return self.start(rev) + self.length(rev)
771
771
772 def parents(self, node):
772 def parents(self, node):
773 i = self.index
773 i = self.index
774 d = i[self.rev(node)]
774 d = i[self.rev(node)]
775 # inline node() to avoid function call overhead
775 # inline node() to avoid function call overhead
776 if d[5] == self.nullid:
776 if d[5] == self.nullid:
777 return i[d[6]][7], i[d[5]][7]
777 return i[d[6]][7], i[d[5]][7]
778 else:
778 else:
779 return i[d[5]][7], i[d[6]][7]
779 return i[d[5]][7], i[d[6]][7]
780
780
781 def chainlen(self, rev):
781 def chainlen(self, rev):
782 return self._chaininfo(rev)[0]
782 return self._chaininfo(rev)[0]
783
783
784 def _chaininfo(self, rev):
784 def _chaininfo(self, rev):
785 chaininfocache = self._chaininfocache
785 chaininfocache = self._chaininfocache
786 if rev in chaininfocache:
786 if rev in chaininfocache:
787 return chaininfocache[rev]
787 return chaininfocache[rev]
788 index = self.index
788 index = self.index
789 generaldelta = self._generaldelta
789 generaldelta = self._generaldelta
790 iterrev = rev
790 iterrev = rev
791 e = index[iterrev]
791 e = index[iterrev]
792 clen = 0
792 clen = 0
793 compresseddeltalen = 0
793 compresseddeltalen = 0
794 while iterrev != e[3]:
794 while iterrev != e[3]:
795 clen += 1
795 clen += 1
796 compresseddeltalen += e[1]
796 compresseddeltalen += e[1]
797 if generaldelta:
797 if generaldelta:
798 iterrev = e[3]
798 iterrev = e[3]
799 else:
799 else:
800 iterrev -= 1
800 iterrev -= 1
801 if iterrev in chaininfocache:
801 if iterrev in chaininfocache:
802 t = chaininfocache[iterrev]
802 t = chaininfocache[iterrev]
803 clen += t[0]
803 clen += t[0]
804 compresseddeltalen += t[1]
804 compresseddeltalen += t[1]
805 break
805 break
806 e = index[iterrev]
806 e = index[iterrev]
807 else:
807 else:
808 # Add text length of base since decompressing that also takes
808 # Add text length of base since decompressing that also takes
809 # work. For cache hits the length is already included.
809 # work. For cache hits the length is already included.
810 compresseddeltalen += e[1]
810 compresseddeltalen += e[1]
811 r = (clen, compresseddeltalen)
811 r = (clen, compresseddeltalen)
812 chaininfocache[rev] = r
812 chaininfocache[rev] = r
813 return r
813 return r
814
814
815 def _deltachain(self, rev, stoprev=None):
815 def _deltachain(self, rev, stoprev=None):
816 """Obtain the delta chain for a revision.
816 """Obtain the delta chain for a revision.
817
817
818 ``stoprev`` specifies a revision to stop at. If not specified, we
818 ``stoprev`` specifies a revision to stop at. If not specified, we
819 stop at the base of the chain.
819 stop at the base of the chain.
820
820
821 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
821 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
822 revs in ascending order and ``stopped`` is a bool indicating whether
822 revs in ascending order and ``stopped`` is a bool indicating whether
823 ``stoprev`` was hit.
823 ``stoprev`` was hit.
824 """
824 """
825 # Try C implementation.
825 # Try C implementation.
826 try:
826 try:
827 return self.index.deltachain(rev, stoprev, self._generaldelta)
827 return self.index.deltachain(rev, stoprev, self._generaldelta)
828 except AttributeError:
828 except AttributeError:
829 pass
829 pass
830
830
831 chain = []
831 chain = []
832
832
833 # Alias to prevent attribute lookup in tight loop.
833 # Alias to prevent attribute lookup in tight loop.
834 index = self.index
834 index = self.index
835 generaldelta = self._generaldelta
835 generaldelta = self._generaldelta
836
836
837 iterrev = rev
837 iterrev = rev
838 e = index[iterrev]
838 e = index[iterrev]
839 while iterrev != e[3] and iterrev != stoprev:
839 while iterrev != e[3] and iterrev != stoprev:
840 chain.append(iterrev)
840 chain.append(iterrev)
841 if generaldelta:
841 if generaldelta:
842 iterrev = e[3]
842 iterrev = e[3]
843 else:
843 else:
844 iterrev -= 1
844 iterrev -= 1
845 e = index[iterrev]
845 e = index[iterrev]
846
846
847 if iterrev == stoprev:
847 if iterrev == stoprev:
848 stopped = True
848 stopped = True
849 else:
849 else:
850 chain.append(iterrev)
850 chain.append(iterrev)
851 stopped = False
851 stopped = False
852
852
853 chain.reverse()
853 chain.reverse()
854 return chain, stopped
854 return chain, stopped
855
855
856 def ancestors(self, revs, stoprev=0, inclusive=False):
856 def ancestors(self, revs, stoprev=0, inclusive=False):
857 """Generate the ancestors of 'revs' in reverse revision order.
857 """Generate the ancestors of 'revs' in reverse revision order.
858 Does not generate revs lower than stoprev.
858 Does not generate revs lower than stoprev.
859
859
860 See the documentation for ancestor.lazyancestors for more details."""
860 See the documentation for ancestor.lazyancestors for more details."""
861
861
862 # first, make sure start revisions aren't filtered
862 # first, make sure start revisions aren't filtered
863 revs = list(revs)
863 revs = list(revs)
864 checkrev = self.node
864 checkrev = self.node
865 for r in revs:
865 for r in revs:
866 checkrev(r)
866 checkrev(r)
867 # and we're sure ancestors aren't filtered as well
867 # and we're sure ancestors aren't filtered as well
868
868
869 if rustancestor is not None:
869 if rustancestor is not None:
870 lazyancestors = rustancestor.LazyAncestors
870 lazyancestors = rustancestor.LazyAncestors
871 arg = self.index
871 arg = self.index
872 else:
872 else:
873 lazyancestors = ancestor.lazyancestors
873 lazyancestors = ancestor.lazyancestors
874 arg = self._uncheckedparentrevs
874 arg = self._uncheckedparentrevs
875 return lazyancestors(arg, revs, stoprev=stoprev, inclusive=inclusive)
875 return lazyancestors(arg, revs, stoprev=stoprev, inclusive=inclusive)
876
876
877 def descendants(self, revs):
877 def descendants(self, revs):
878 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
878 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
879
879
880 def findcommonmissing(self, common=None, heads=None):
880 def findcommonmissing(self, common=None, heads=None):
881 """Return a tuple of the ancestors of common and the ancestors of heads
881 """Return a tuple of the ancestors of common and the ancestors of heads
882 that are not ancestors of common. In revset terminology, we return the
882 that are not ancestors of common. In revset terminology, we return the
883 tuple:
883 tuple:
884
884
885 ::common, (::heads) - (::common)
885 ::common, (::heads) - (::common)
886
886
887 The list is sorted by revision number, meaning it is
887 The list is sorted by revision number, meaning it is
888 topologically sorted.
888 topologically sorted.
889
889
890 'heads' and 'common' are both lists of node IDs. If heads is
890 'heads' and 'common' are both lists of node IDs. If heads is
891 not supplied, uses all of the revlog's heads. If common is not
891 not supplied, uses all of the revlog's heads. If common is not
892 supplied, uses nullid."""
892 supplied, uses nullid."""
893 if common is None:
893 if common is None:
894 common = [self.nullid]
894 common = [self.nullid]
895 if heads is None:
895 if heads is None:
896 heads = self.heads()
896 heads = self.heads()
897
897
898 common = [self.rev(n) for n in common]
898 common = [self.rev(n) for n in common]
899 heads = [self.rev(n) for n in heads]
899 heads = [self.rev(n) for n in heads]
900
900
901 # we want the ancestors, but inclusive
901 # we want the ancestors, but inclusive
902 class lazyset(object):
902 class lazyset(object):
903 def __init__(self, lazyvalues):
903 def __init__(self, lazyvalues):
904 self.addedvalues = set()
904 self.addedvalues = set()
905 self.lazyvalues = lazyvalues
905 self.lazyvalues = lazyvalues
906
906
907 def __contains__(self, value):
907 def __contains__(self, value):
908 return value in self.addedvalues or value in self.lazyvalues
908 return value in self.addedvalues or value in self.lazyvalues
909
909
910 def __iter__(self):
910 def __iter__(self):
911 added = self.addedvalues
911 added = self.addedvalues
912 for r in added:
912 for r in added:
913 yield r
913 yield r
914 for r in self.lazyvalues:
914 for r in self.lazyvalues:
915 if not r in added:
915 if not r in added:
916 yield r
916 yield r
917
917
918 def add(self, value):
918 def add(self, value):
919 self.addedvalues.add(value)
919 self.addedvalues.add(value)
920
920
921 def update(self, values):
921 def update(self, values):
922 self.addedvalues.update(values)
922 self.addedvalues.update(values)
923
923
924 has = lazyset(self.ancestors(common))
924 has = lazyset(self.ancestors(common))
925 has.add(nullrev)
925 has.add(nullrev)
926 has.update(common)
926 has.update(common)
927
927
928 # take all ancestors from heads that aren't in has
928 # take all ancestors from heads that aren't in has
929 missing = set()
929 missing = set()
930 visit = collections.deque(r for r in heads if r not in has)
930 visit = collections.deque(r for r in heads if r not in has)
931 while visit:
931 while visit:
932 r = visit.popleft()
932 r = visit.popleft()
933 if r in missing:
933 if r in missing:
934 continue
934 continue
935 else:
935 else:
936 missing.add(r)
936 missing.add(r)
937 for p in self.parentrevs(r):
937 for p in self.parentrevs(r):
938 if p not in has:
938 if p not in has:
939 visit.append(p)
939 visit.append(p)
940 missing = list(missing)
940 missing = list(missing)
941 missing.sort()
941 missing.sort()
942 return has, [self.node(miss) for miss in missing]
942 return has, [self.node(miss) for miss in missing]
943
943
944 def incrementalmissingrevs(self, common=None):
944 def incrementalmissingrevs(self, common=None):
945 """Return an object that can be used to incrementally compute the
945 """Return an object that can be used to incrementally compute the
946 revision numbers of the ancestors of arbitrary sets that are not
946 revision numbers of the ancestors of arbitrary sets that are not
947 ancestors of common. This is an ancestor.incrementalmissingancestors
947 ancestors of common. This is an ancestor.incrementalmissingancestors
948 object.
948 object.
949
949
950 'common' is a list of revision numbers. If common is not supplied, uses
950 'common' is a list of revision numbers. If common is not supplied, uses
951 nullrev.
951 nullrev.
952 """
952 """
953 if common is None:
953 if common is None:
954 common = [nullrev]
954 common = [nullrev]
955
955
956 if rustancestor is not None:
956 if rustancestor is not None:
957 return rustancestor.MissingAncestors(self.index, common)
957 return rustancestor.MissingAncestors(self.index, common)
958 return ancestor.incrementalmissingancestors(self.parentrevs, common)
958 return ancestor.incrementalmissingancestors(self.parentrevs, common)
959
959
960 def findmissingrevs(self, common=None, heads=None):
960 def findmissingrevs(self, common=None, heads=None):
961 """Return the revision numbers of the ancestors of heads that
961 """Return the revision numbers of the ancestors of heads that
962 are not ancestors of common.
962 are not ancestors of common.
963
963
964 More specifically, return a list of revision numbers corresponding to
964 More specifically, return a list of revision numbers corresponding to
965 nodes N such that every N satisfies the following constraints:
965 nodes N such that every N satisfies the following constraints:
966
966
967 1. N is an ancestor of some node in 'heads'
967 1. N is an ancestor of some node in 'heads'
968 2. N is not an ancestor of any node in 'common'
968 2. N is not an ancestor of any node in 'common'
969
969
970 The list is sorted by revision number, meaning it is
970 The list is sorted by revision number, meaning it is
971 topologically sorted.
971 topologically sorted.
972
972
973 'heads' and 'common' are both lists of revision numbers. If heads is
973 'heads' and 'common' are both lists of revision numbers. If heads is
974 not supplied, uses all of the revlog's heads. If common is not
974 not supplied, uses all of the revlog's heads. If common is not
975 supplied, uses nullid."""
975 supplied, uses nullid."""
976 if common is None:
976 if common is None:
977 common = [nullrev]
977 common = [nullrev]
978 if heads is None:
978 if heads is None:
979 heads = self.headrevs()
979 heads = self.headrevs()
980
980
981 inc = self.incrementalmissingrevs(common=common)
981 inc = self.incrementalmissingrevs(common=common)
982 return inc.missingancestors(heads)
982 return inc.missingancestors(heads)
983
983
984 def findmissing(self, common=None, heads=None):
984 def findmissing(self, common=None, heads=None):
985 """Return the ancestors of heads that are not ancestors of common.
985 """Return the ancestors of heads that are not ancestors of common.
986
986
987 More specifically, return a list of nodes N such that every N
987 More specifically, return a list of nodes N such that every N
988 satisfies the following constraints:
988 satisfies the following constraints:
989
989
990 1. N is an ancestor of some node in 'heads'
990 1. N is an ancestor of some node in 'heads'
991 2. N is not an ancestor of any node in 'common'
991 2. N is not an ancestor of any node in 'common'
992
992
993 The list is sorted by revision number, meaning it is
993 The list is sorted by revision number, meaning it is
994 topologically sorted.
994 topologically sorted.
995
995
996 'heads' and 'common' are both lists of node IDs. If heads is
996 'heads' and 'common' are both lists of node IDs. If heads is
997 not supplied, uses all of the revlog's heads. If common is not
997 not supplied, uses all of the revlog's heads. If common is not
998 supplied, uses nullid."""
998 supplied, uses nullid."""
999 if common is None:
999 if common is None:
1000 common = [self.nullid]
1000 common = [self.nullid]
1001 if heads is None:
1001 if heads is None:
1002 heads = self.heads()
1002 heads = self.heads()
1003
1003
1004 common = [self.rev(n) for n in common]
1004 common = [self.rev(n) for n in common]
1005 heads = [self.rev(n) for n in heads]
1005 heads = [self.rev(n) for n in heads]
1006
1006
1007 inc = self.incrementalmissingrevs(common=common)
1007 inc = self.incrementalmissingrevs(common=common)
1008 return [self.node(r) for r in inc.missingancestors(heads)]
1008 return [self.node(r) for r in inc.missingancestors(heads)]
1009
1009
1010 def nodesbetween(self, roots=None, heads=None):
1010 def nodesbetween(self, roots=None, heads=None):
1011 """Return a topological path from 'roots' to 'heads'.
1011 """Return a topological path from 'roots' to 'heads'.
1012
1012
1013 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
1013 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
1014 topologically sorted list of all nodes N that satisfy both of
1014 topologically sorted list of all nodes N that satisfy both of
1015 these constraints:
1015 these constraints:
1016
1016
1017 1. N is a descendant of some node in 'roots'
1017 1. N is a descendant of some node in 'roots'
1018 2. N is an ancestor of some node in 'heads'
1018 2. N is an ancestor of some node in 'heads'
1019
1019
1020 Every node is considered to be both a descendant and an ancestor
1020 Every node is considered to be both a descendant and an ancestor
1021 of itself, so every reachable node in 'roots' and 'heads' will be
1021 of itself, so every reachable node in 'roots' and 'heads' will be
1022 included in 'nodes'.
1022 included in 'nodes'.
1023
1023
1024 'outroots' is the list of reachable nodes in 'roots', i.e., the
1024 'outroots' is the list of reachable nodes in 'roots', i.e., the
1025 subset of 'roots' that is returned in 'nodes'. Likewise,
1025 subset of 'roots' that is returned in 'nodes'. Likewise,
1026 'outheads' is the subset of 'heads' that is also in 'nodes'.
1026 'outheads' is the subset of 'heads' that is also in 'nodes'.
1027
1027
1028 'roots' and 'heads' are both lists of node IDs. If 'roots' is
1028 'roots' and 'heads' are both lists of node IDs. If 'roots' is
1029 unspecified, uses nullid as the only root. If 'heads' is
1029 unspecified, uses nullid as the only root. If 'heads' is
1030 unspecified, uses list of all of the revlog's heads."""
1030 unspecified, uses list of all of the revlog's heads."""
1031 nonodes = ([], [], [])
1031 nonodes = ([], [], [])
1032 if roots is not None:
1032 if roots is not None:
1033 roots = list(roots)
1033 roots = list(roots)
1034 if not roots:
1034 if not roots:
1035 return nonodes
1035 return nonodes
1036 lowestrev = min([self.rev(n) for n in roots])
1036 lowestrev = min([self.rev(n) for n in roots])
1037 else:
1037 else:
1038 roots = [self.nullid] # Everybody's a descendant of nullid
1038 roots = [self.nullid] # Everybody's a descendant of nullid
1039 lowestrev = nullrev
1039 lowestrev = nullrev
1040 if (lowestrev == nullrev) and (heads is None):
1040 if (lowestrev == nullrev) and (heads is None):
1041 # We want _all_ the nodes!
1041 # We want _all_ the nodes!
1042 return (
1042 return (
1043 [self.node(r) for r in self],
1043 [self.node(r) for r in self],
1044 [self.nullid],
1044 [self.nullid],
1045 list(self.heads()),
1045 list(self.heads()),
1046 )
1046 )
1047 if heads is None:
1047 if heads is None:
1048 # All nodes are ancestors, so the latest ancestor is the last
1048 # All nodes are ancestors, so the latest ancestor is the last
1049 # node.
1049 # node.
1050 highestrev = len(self) - 1
1050 highestrev = len(self) - 1
1051 # Set ancestors to None to signal that every node is an ancestor.
1051 # Set ancestors to None to signal that every node is an ancestor.
1052 ancestors = None
1052 ancestors = None
1053 # Set heads to an empty dictionary for later discovery of heads
1053 # Set heads to an empty dictionary for later discovery of heads
1054 heads = {}
1054 heads = {}
1055 else:
1055 else:
1056 heads = list(heads)
1056 heads = list(heads)
1057 if not heads:
1057 if not heads:
1058 return nonodes
1058 return nonodes
1059 ancestors = set()
1059 ancestors = set()
1060 # Turn heads into a dictionary so we can remove 'fake' heads.
1060 # Turn heads into a dictionary so we can remove 'fake' heads.
1061 # Also, later we will be using it to filter out the heads we can't
1061 # Also, later we will be using it to filter out the heads we can't
1062 # find from roots.
1062 # find from roots.
1063 heads = dict.fromkeys(heads, False)
1063 heads = dict.fromkeys(heads, False)
1064 # Start at the top and keep marking parents until we're done.
1064 # Start at the top and keep marking parents until we're done.
1065 nodestotag = set(heads)
1065 nodestotag = set(heads)
1066 # Remember where the top was so we can use it as a limit later.
1066 # Remember where the top was so we can use it as a limit later.
1067 highestrev = max([self.rev(n) for n in nodestotag])
1067 highestrev = max([self.rev(n) for n in nodestotag])
1068 while nodestotag:
1068 while nodestotag:
1069 # grab a node to tag
1069 # grab a node to tag
1070 n = nodestotag.pop()
1070 n = nodestotag.pop()
1071 # Never tag nullid
1071 # Never tag nullid
1072 if n == self.nullid:
1072 if n == self.nullid:
1073 continue
1073 continue
1074 # A node's revision number represents its place in a
1074 # A node's revision number represents its place in a
1075 # topologically sorted list of nodes.
1075 # topologically sorted list of nodes.
1076 r = self.rev(n)
1076 r = self.rev(n)
1077 if r >= lowestrev:
1077 if r >= lowestrev:
1078 if n not in ancestors:
1078 if n not in ancestors:
1079 # If we are possibly a descendant of one of the roots
1079 # If we are possibly a descendant of one of the roots
1080 # and we haven't already been marked as an ancestor
1080 # and we haven't already been marked as an ancestor
1081 ancestors.add(n) # Mark as ancestor
1081 ancestors.add(n) # Mark as ancestor
1082 # Add non-nullid parents to list of nodes to tag.
1082 # Add non-nullid parents to list of nodes to tag.
1083 nodestotag.update(
1083 nodestotag.update(
1084 [p for p in self.parents(n) if p != self.nullid]
1084 [p for p in self.parents(n) if p != self.nullid]
1085 )
1085 )
1086 elif n in heads: # We've seen it before, is it a fake head?
1086 elif n in heads: # We've seen it before, is it a fake head?
1087 # So it is, real heads should not be the ancestors of
1087 # So it is, real heads should not be the ancestors of
1088 # any other heads.
1088 # any other heads.
1089 heads.pop(n)
1089 heads.pop(n)
1090 if not ancestors:
1090 if not ancestors:
1091 return nonodes
1091 return nonodes
1092 # Now that we have our set of ancestors, we want to remove any
1092 # Now that we have our set of ancestors, we want to remove any
1093 # roots that are not ancestors.
1093 # roots that are not ancestors.
1094
1094
1095 # If one of the roots was nullid, everything is included anyway.
1095 # If one of the roots was nullid, everything is included anyway.
1096 if lowestrev > nullrev:
1096 if lowestrev > nullrev:
1097 # But, since we weren't, let's recompute the lowest rev to not
1097 # But, since we weren't, let's recompute the lowest rev to not
1098 # include roots that aren't ancestors.
1098 # include roots that aren't ancestors.
1099
1099
1100 # Filter out roots that aren't ancestors of heads
1100 # Filter out roots that aren't ancestors of heads
1101 roots = [root for root in roots if root in ancestors]
1101 roots = [root for root in roots if root in ancestors]
1102 # Recompute the lowest revision
1102 # Recompute the lowest revision
1103 if roots:
1103 if roots:
1104 lowestrev = min([self.rev(root) for root in roots])
1104 lowestrev = min([self.rev(root) for root in roots])
1105 else:
1105 else:
1106 # No more roots? Return empty list
1106 # No more roots? Return empty list
1107 return nonodes
1107 return nonodes
1108 else:
1108 else:
1109 # We are descending from nullid, and don't need to care about
1109 # We are descending from nullid, and don't need to care about
1110 # any other roots.
1110 # any other roots.
1111 lowestrev = nullrev
1111 lowestrev = nullrev
1112 roots = [self.nullid]
1112 roots = [self.nullid]
1113 # Transform our roots list into a set.
1113 # Transform our roots list into a set.
1114 descendants = set(roots)
1114 descendants = set(roots)
1115 # Also, keep the original roots so we can filter out roots that aren't
1115 # Also, keep the original roots so we can filter out roots that aren't
1116 # 'real' roots (i.e. are descended from other roots).
1116 # 'real' roots (i.e. are descended from other roots).
1117 roots = descendants.copy()
1117 roots = descendants.copy()
1118 # Our topologically sorted list of output nodes.
1118 # Our topologically sorted list of output nodes.
1119 orderedout = []
1119 orderedout = []
1120 # Don't start at nullid since we don't want nullid in our output list,
1120 # Don't start at nullid since we don't want nullid in our output list,
1121 # and if nullid shows up in descendants, empty parents will look like
1121 # and if nullid shows up in descendants, empty parents will look like
1122 # they're descendants.
1122 # they're descendants.
1123 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1123 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1124 n = self.node(r)
1124 n = self.node(r)
1125 isdescendant = False
1125 isdescendant = False
1126 if lowestrev == nullrev: # Everybody is a descendant of nullid
1126 if lowestrev == nullrev: # Everybody is a descendant of nullid
1127 isdescendant = True
1127 isdescendant = True
1128 elif n in descendants:
1128 elif n in descendants:
1129 # n is already a descendant
1129 # n is already a descendant
1130 isdescendant = True
1130 isdescendant = True
1131 # This check only needs to be done here because all the roots
1131 # This check only needs to be done here because all the roots
1132 # will start being marked is descendants before the loop.
1132 # will start being marked is descendants before the loop.
1133 if n in roots:
1133 if n in roots:
1134 # If n was a root, check if it's a 'real' root.
1134 # If n was a root, check if it's a 'real' root.
1135 p = tuple(self.parents(n))
1135 p = tuple(self.parents(n))
1136 # If any of its parents are descendants, it's not a root.
1136 # If any of its parents are descendants, it's not a root.
1137 if (p[0] in descendants) or (p[1] in descendants):
1137 if (p[0] in descendants) or (p[1] in descendants):
1138 roots.remove(n)
1138 roots.remove(n)
1139 else:
1139 else:
1140 p = tuple(self.parents(n))
1140 p = tuple(self.parents(n))
1141 # A node is a descendant if either of its parents are
1141 # A node is a descendant if either of its parents are
1142 # descendants. (We seeded the dependents list with the roots
1142 # descendants. (We seeded the dependents list with the roots
1143 # up there, remember?)
1143 # up there, remember?)
1144 if (p[0] in descendants) or (p[1] in descendants):
1144 if (p[0] in descendants) or (p[1] in descendants):
1145 descendants.add(n)
1145 descendants.add(n)
1146 isdescendant = True
1146 isdescendant = True
1147 if isdescendant and ((ancestors is None) or (n in ancestors)):
1147 if isdescendant and ((ancestors is None) or (n in ancestors)):
1148 # Only include nodes that are both descendants and ancestors.
1148 # Only include nodes that are both descendants and ancestors.
1149 orderedout.append(n)
1149 orderedout.append(n)
1150 if (ancestors is not None) and (n in heads):
1150 if (ancestors is not None) and (n in heads):
1151 # We're trying to figure out which heads are reachable
1151 # We're trying to figure out which heads are reachable
1152 # from roots.
1152 # from roots.
1153 # Mark this head as having been reached
1153 # Mark this head as having been reached
1154 heads[n] = True
1154 heads[n] = True
1155 elif ancestors is None:
1155 elif ancestors is None:
1156 # Otherwise, we're trying to discover the heads.
1156 # Otherwise, we're trying to discover the heads.
1157 # Assume this is a head because if it isn't, the next step
1157 # Assume this is a head because if it isn't, the next step
1158 # will eventually remove it.
1158 # will eventually remove it.
1159 heads[n] = True
1159 heads[n] = True
1160 # But, obviously its parents aren't.
1160 # But, obviously its parents aren't.
1161 for p in self.parents(n):
1161 for p in self.parents(n):
1162 heads.pop(p, None)
1162 heads.pop(p, None)
1163 heads = [head for head, flag in pycompat.iteritems(heads) if flag]
1163 heads = [head for head, flag in pycompat.iteritems(heads) if flag]
1164 roots = list(roots)
1164 roots = list(roots)
1165 assert orderedout
1165 assert orderedout
1166 assert roots
1166 assert roots
1167 assert heads
1167 assert heads
1168 return (orderedout, roots, heads)
1168 return (orderedout, roots, heads)
1169
1169
1170 def headrevs(self, revs=None):
1170 def headrevs(self, revs=None):
1171 if revs is None:
1171 if revs is None:
1172 try:
1172 try:
1173 return self.index.headrevs()
1173 return self.index.headrevs()
1174 except AttributeError:
1174 except AttributeError:
1175 return self._headrevs()
1175 return self._headrevs()
1176 if rustdagop is not None:
1176 if rustdagop is not None:
1177 return rustdagop.headrevs(self.index, revs)
1177 return rustdagop.headrevs(self.index, revs)
1178 return dagop.headrevs(revs, self._uncheckedparentrevs)
1178 return dagop.headrevs(revs, self._uncheckedparentrevs)
1179
1179
1180 def computephases(self, roots):
1180 def computephases(self, roots):
1181 return self.index.computephasesmapsets(roots)
1181 return self.index.computephasesmapsets(roots)
1182
1182
1183 def _headrevs(self):
1183 def _headrevs(self):
1184 count = len(self)
1184 count = len(self)
1185 if not count:
1185 if not count:
1186 return [nullrev]
1186 return [nullrev]
1187 # we won't iter over filtered rev so nobody is a head at start
1187 # we won't iter over filtered rev so nobody is a head at start
1188 ishead = [0] * (count + 1)
1188 ishead = [0] * (count + 1)
1189 index = self.index
1189 index = self.index
1190 for r in self:
1190 for r in self:
1191 ishead[r] = 1 # I may be an head
1191 ishead[r] = 1 # I may be an head
1192 e = index[r]
1192 e = index[r]
1193 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1193 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1194 return [r for r, val in enumerate(ishead) if val]
1194 return [r for r, val in enumerate(ishead) if val]
1195
1195
1196 def heads(self, start=None, stop=None):
1196 def heads(self, start=None, stop=None):
1197 """return the list of all nodes that have no children
1197 """return the list of all nodes that have no children
1198
1198
1199 if start is specified, only heads that are descendants of
1199 if start is specified, only heads that are descendants of
1200 start will be returned
1200 start will be returned
1201 if stop is specified, it will consider all the revs from stop
1201 if stop is specified, it will consider all the revs from stop
1202 as if they had no children
1202 as if they had no children
1203 """
1203 """
1204 if start is None and stop is None:
1204 if start is None and stop is None:
1205 if not len(self):
1205 if not len(self):
1206 return [self.nullid]
1206 return [self.nullid]
1207 return [self.node(r) for r in self.headrevs()]
1207 return [self.node(r) for r in self.headrevs()]
1208
1208
1209 if start is None:
1209 if start is None:
1210 start = nullrev
1210 start = nullrev
1211 else:
1211 else:
1212 start = self.rev(start)
1212 start = self.rev(start)
1213
1213
1214 stoprevs = {self.rev(n) for n in stop or []}
1214 stoprevs = {self.rev(n) for n in stop or []}
1215
1215
1216 revs = dagop.headrevssubset(
1216 revs = dagop.headrevssubset(
1217 self.revs, self.parentrevs, startrev=start, stoprevs=stoprevs
1217 self.revs, self.parentrevs, startrev=start, stoprevs=stoprevs
1218 )
1218 )
1219
1219
1220 return [self.node(rev) for rev in revs]
1220 return [self.node(rev) for rev in revs]
1221
1221
1222 def children(self, node):
1222 def children(self, node):
1223 """find the children of a given node"""
1223 """find the children of a given node"""
1224 c = []
1224 c = []
1225 p = self.rev(node)
1225 p = self.rev(node)
1226 for r in self.revs(start=p + 1):
1226 for r in self.revs(start=p + 1):
1227 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1227 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1228 if prevs:
1228 if prevs:
1229 for pr in prevs:
1229 for pr in prevs:
1230 if pr == p:
1230 if pr == p:
1231 c.append(self.node(r))
1231 c.append(self.node(r))
1232 elif p == nullrev:
1232 elif p == nullrev:
1233 c.append(self.node(r))
1233 c.append(self.node(r))
1234 return c
1234 return c
1235
1235
1236 def commonancestorsheads(self, a, b):
1236 def commonancestorsheads(self, a, b):
1237 """calculate all the heads of the common ancestors of nodes a and b"""
1237 """calculate all the heads of the common ancestors of nodes a and b"""
1238 a, b = self.rev(a), self.rev(b)
1238 a, b = self.rev(a), self.rev(b)
1239 ancs = self._commonancestorsheads(a, b)
1239 ancs = self._commonancestorsheads(a, b)
1240 return pycompat.maplist(self.node, ancs)
1240 return pycompat.maplist(self.node, ancs)
1241
1241
1242 def _commonancestorsheads(self, *revs):
1242 def _commonancestorsheads(self, *revs):
1243 """calculate all the heads of the common ancestors of revs"""
1243 """calculate all the heads of the common ancestors of revs"""
1244 try:
1244 try:
1245 ancs = self.index.commonancestorsheads(*revs)
1245 ancs = self.index.commonancestorsheads(*revs)
1246 except (AttributeError, OverflowError): # C implementation failed
1246 except (AttributeError, OverflowError): # C implementation failed
1247 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1247 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1248 return ancs
1248 return ancs
1249
1249
1250 def isancestor(self, a, b):
1250 def isancestor(self, a, b):
1251 """return True if node a is an ancestor of node b
1251 """return True if node a is an ancestor of node b
1252
1252
1253 A revision is considered an ancestor of itself."""
1253 A revision is considered an ancestor of itself."""
1254 a, b = self.rev(a), self.rev(b)
1254 a, b = self.rev(a), self.rev(b)
1255 return self.isancestorrev(a, b)
1255 return self.isancestorrev(a, b)
1256
1256
1257 def isancestorrev(self, a, b):
1257 def isancestorrev(self, a, b):
1258 """return True if revision a is an ancestor of revision b
1258 """return True if revision a is an ancestor of revision b
1259
1259
1260 A revision is considered an ancestor of itself.
1260 A revision is considered an ancestor of itself.
1261
1261
1262 The implementation of this is trivial but the use of
1262 The implementation of this is trivial but the use of
1263 reachableroots is not."""
1263 reachableroots is not."""
1264 if a == nullrev:
1264 if a == nullrev:
1265 return True
1265 return True
1266 elif a == b:
1266 elif a == b:
1267 return True
1267 return True
1268 elif a > b:
1268 elif a > b:
1269 return False
1269 return False
1270 return bool(self.reachableroots(a, [b], [a], includepath=False))
1270 return bool(self.reachableroots(a, [b], [a], includepath=False))
1271
1271
1272 def reachableroots(self, minroot, heads, roots, includepath=False):
1272 def reachableroots(self, minroot, heads, roots, includepath=False):
1273 """return (heads(::(<roots> and <roots>::<heads>)))
1273 """return (heads(::(<roots> and <roots>::<heads>)))
1274
1274
1275 If includepath is True, return (<roots>::<heads>)."""
1275 If includepath is True, return (<roots>::<heads>)."""
1276 try:
1276 try:
1277 return self.index.reachableroots2(
1277 return self.index.reachableroots2(
1278 minroot, heads, roots, includepath
1278 minroot, heads, roots, includepath
1279 )
1279 )
1280 except AttributeError:
1280 except AttributeError:
1281 return dagop._reachablerootspure(
1281 return dagop._reachablerootspure(
1282 self.parentrevs, minroot, roots, heads, includepath
1282 self.parentrevs, minroot, roots, heads, includepath
1283 )
1283 )
1284
1284
1285 def ancestor(self, a, b):
1285 def ancestor(self, a, b):
1286 """calculate the "best" common ancestor of nodes a and b"""
1286 """calculate the "best" common ancestor of nodes a and b"""
1287
1287
1288 a, b = self.rev(a), self.rev(b)
1288 a, b = self.rev(a), self.rev(b)
1289 try:
1289 try:
1290 ancs = self.index.ancestors(a, b)
1290 ancs = self.index.ancestors(a, b)
1291 except (AttributeError, OverflowError):
1291 except (AttributeError, OverflowError):
1292 ancs = ancestor.ancestors(self.parentrevs, a, b)
1292 ancs = ancestor.ancestors(self.parentrevs, a, b)
1293 if ancs:
1293 if ancs:
1294 # choose a consistent winner when there's a tie
1294 # choose a consistent winner when there's a tie
1295 return min(map(self.node, ancs))
1295 return min(map(self.node, ancs))
1296 return self.nullid
1296 return self.nullid
1297
1297
1298 def _match(self, id):
1298 def _match(self, id):
1299 if isinstance(id, int):
1299 if isinstance(id, int):
1300 # rev
1300 # rev
1301 return self.node(id)
1301 return self.node(id)
1302 if len(id) == 20:
1302 if len(id) == self.nodeconstants.nodelen:
1303 # possibly a binary node
1303 # possibly a binary node
1304 # odds of a binary node being all hex in ASCII are 1 in 10**25
1304 # odds of a binary node being all hex in ASCII are 1 in 10**25
1305 try:
1305 try:
1306 node = id
1306 node = id
1307 self.rev(node) # quick search the index
1307 self.rev(node) # quick search the index
1308 return node
1308 return node
1309 except error.LookupError:
1309 except error.LookupError:
1310 pass # may be partial hex id
1310 pass # may be partial hex id
1311 try:
1311 try:
1312 # str(rev)
1312 # str(rev)
1313 rev = int(id)
1313 rev = int(id)
1314 if b"%d" % rev != id:
1314 if b"%d" % rev != id:
1315 raise ValueError
1315 raise ValueError
1316 if rev < 0:
1316 if rev < 0:
1317 rev = len(self) + rev
1317 rev = len(self) + rev
1318 if rev < 0 or rev >= len(self):
1318 if rev < 0 or rev >= len(self):
1319 raise ValueError
1319 raise ValueError
1320 return self.node(rev)
1320 return self.node(rev)
1321 except (ValueError, OverflowError):
1321 except (ValueError, OverflowError):
1322 pass
1322 pass
1323 if len(id) == 2 * self.nodeconstants.nodelen:
1323 if len(id) == 2 * self.nodeconstants.nodelen:
1324 try:
1324 try:
1325 # a full hex nodeid?
1325 # a full hex nodeid?
1326 node = bin(id)
1326 node = bin(id)
1327 self.rev(node)
1327 self.rev(node)
1328 return node
1328 return node
1329 except (TypeError, error.LookupError):
1329 except (TypeError, error.LookupError):
1330 pass
1330 pass
1331
1331
1332 def _partialmatch(self, id):
1332 def _partialmatch(self, id):
1333 # we don't care wdirfilenodeids as they should be always full hash
1333 # we don't care wdirfilenodeids as they should be always full hash
1334 maybewdir = self.nodeconstants.wdirhex.startswith(id)
1334 maybewdir = self.nodeconstants.wdirhex.startswith(id)
1335 try:
1335 try:
1336 partial = self.index.partialmatch(id)
1336 partial = self.index.partialmatch(id)
1337 if partial and self.hasnode(partial):
1337 if partial and self.hasnode(partial):
1338 if maybewdir:
1338 if maybewdir:
1339 # single 'ff...' match in radix tree, ambiguous with wdir
1339 # single 'ff...' match in radix tree, ambiguous with wdir
1340 raise error.RevlogError
1340 raise error.RevlogError
1341 return partial
1341 return partial
1342 if maybewdir:
1342 if maybewdir:
1343 # no 'ff...' match in radix tree, wdir identified
1343 # no 'ff...' match in radix tree, wdir identified
1344 raise error.WdirUnsupported
1344 raise error.WdirUnsupported
1345 return None
1345 return None
1346 except error.RevlogError:
1346 except error.RevlogError:
1347 # parsers.c radix tree lookup gave multiple matches
1347 # parsers.c radix tree lookup gave multiple matches
1348 # fast path: for unfiltered changelog, radix tree is accurate
1348 # fast path: for unfiltered changelog, radix tree is accurate
1349 if not getattr(self, 'filteredrevs', None):
1349 if not getattr(self, 'filteredrevs', None):
1350 raise error.AmbiguousPrefixLookupError(
1350 raise error.AmbiguousPrefixLookupError(
1351 id, self.indexfile, _(b'ambiguous identifier')
1351 id, self.indexfile, _(b'ambiguous identifier')
1352 )
1352 )
1353 # fall through to slow path that filters hidden revisions
1353 # fall through to slow path that filters hidden revisions
1354 except (AttributeError, ValueError):
1354 except (AttributeError, ValueError):
1355 # we are pure python, or key was too short to search radix tree
1355 # we are pure python, or key was too short to search radix tree
1356 pass
1356 pass
1357
1357
1358 if id in self._pcache:
1358 if id in self._pcache:
1359 return self._pcache[id]
1359 return self._pcache[id]
1360
1360
1361 if len(id) <= 40:
1361 if len(id) <= 40:
1362 try:
1362 try:
1363 # hex(node)[:...]
1363 # hex(node)[:...]
1364 l = len(id) // 2 # grab an even number of digits
1364 l = len(id) // 2 # grab an even number of digits
1365 prefix = bin(id[: l * 2])
1365 prefix = bin(id[: l * 2])
1366 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1366 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1367 nl = [
1367 nl = [
1368 n for n in nl if hex(n).startswith(id) and self.hasnode(n)
1368 n for n in nl if hex(n).startswith(id) and self.hasnode(n)
1369 ]
1369 ]
1370 if self.nodeconstants.nullhex.startswith(id):
1370 if self.nodeconstants.nullhex.startswith(id):
1371 nl.append(self.nullid)
1371 nl.append(self.nullid)
1372 if len(nl) > 0:
1372 if len(nl) > 0:
1373 if len(nl) == 1 and not maybewdir:
1373 if len(nl) == 1 and not maybewdir:
1374 self._pcache[id] = nl[0]
1374 self._pcache[id] = nl[0]
1375 return nl[0]
1375 return nl[0]
1376 raise error.AmbiguousPrefixLookupError(
1376 raise error.AmbiguousPrefixLookupError(
1377 id, self.indexfile, _(b'ambiguous identifier')
1377 id, self.indexfile, _(b'ambiguous identifier')
1378 )
1378 )
1379 if maybewdir:
1379 if maybewdir:
1380 raise error.WdirUnsupported
1380 raise error.WdirUnsupported
1381 return None
1381 return None
1382 except TypeError:
1382 except TypeError:
1383 pass
1383 pass
1384
1384
1385 def lookup(self, id):
1385 def lookup(self, id):
1386 """locate a node based on:
1386 """locate a node based on:
1387 - revision number or str(revision number)
1387 - revision number or str(revision number)
1388 - nodeid or subset of hex nodeid
1388 - nodeid or subset of hex nodeid
1389 """
1389 """
1390 n = self._match(id)
1390 n = self._match(id)
1391 if n is not None:
1391 if n is not None:
1392 return n
1392 return n
1393 n = self._partialmatch(id)
1393 n = self._partialmatch(id)
1394 if n:
1394 if n:
1395 return n
1395 return n
1396
1396
1397 raise error.LookupError(id, self.indexfile, _(b'no match found'))
1397 raise error.LookupError(id, self.indexfile, _(b'no match found'))
1398
1398
1399 def shortest(self, node, minlength=1):
1399 def shortest(self, node, minlength=1):
1400 """Find the shortest unambiguous prefix that matches node."""
1400 """Find the shortest unambiguous prefix that matches node."""
1401
1401
1402 def isvalid(prefix):
1402 def isvalid(prefix):
1403 try:
1403 try:
1404 matchednode = self._partialmatch(prefix)
1404 matchednode = self._partialmatch(prefix)
1405 except error.AmbiguousPrefixLookupError:
1405 except error.AmbiguousPrefixLookupError:
1406 return False
1406 return False
1407 except error.WdirUnsupported:
1407 except error.WdirUnsupported:
1408 # single 'ff...' match
1408 # single 'ff...' match
1409 return True
1409 return True
1410 if matchednode is None:
1410 if matchednode is None:
1411 raise error.LookupError(node, self.indexfile, _(b'no node'))
1411 raise error.LookupError(node, self.indexfile, _(b'no node'))
1412 return True
1412 return True
1413
1413
1414 def maybewdir(prefix):
1414 def maybewdir(prefix):
1415 return all(c == b'f' for c in pycompat.iterbytestr(prefix))
1415 return all(c == b'f' for c in pycompat.iterbytestr(prefix))
1416
1416
1417 hexnode = hex(node)
1417 hexnode = hex(node)
1418
1418
1419 def disambiguate(hexnode, minlength):
1419 def disambiguate(hexnode, minlength):
1420 """Disambiguate against wdirid."""
1420 """Disambiguate against wdirid."""
1421 for length in range(minlength, len(hexnode) + 1):
1421 for length in range(minlength, len(hexnode) + 1):
1422 prefix = hexnode[:length]
1422 prefix = hexnode[:length]
1423 if not maybewdir(prefix):
1423 if not maybewdir(prefix):
1424 return prefix
1424 return prefix
1425
1425
1426 if not getattr(self, 'filteredrevs', None):
1426 if not getattr(self, 'filteredrevs', None):
1427 try:
1427 try:
1428 length = max(self.index.shortest(node), minlength)
1428 length = max(self.index.shortest(node), minlength)
1429 return disambiguate(hexnode, length)
1429 return disambiguate(hexnode, length)
1430 except error.RevlogError:
1430 except error.RevlogError:
1431 if node != self.nodeconstants.wdirid:
1431 if node != self.nodeconstants.wdirid:
1432 raise error.LookupError(node, self.indexfile, _(b'no node'))
1432 raise error.LookupError(node, self.indexfile, _(b'no node'))
1433 except AttributeError:
1433 except AttributeError:
1434 # Fall through to pure code
1434 # Fall through to pure code
1435 pass
1435 pass
1436
1436
1437 if node == self.nodeconstants.wdirid:
1437 if node == self.nodeconstants.wdirid:
1438 for length in range(minlength, len(hexnode) + 1):
1438 for length in range(minlength, len(hexnode) + 1):
1439 prefix = hexnode[:length]
1439 prefix = hexnode[:length]
1440 if isvalid(prefix):
1440 if isvalid(prefix):
1441 return prefix
1441 return prefix
1442
1442
1443 for length in range(minlength, len(hexnode) + 1):
1443 for length in range(minlength, len(hexnode) + 1):
1444 prefix = hexnode[:length]
1444 prefix = hexnode[:length]
1445 if isvalid(prefix):
1445 if isvalid(prefix):
1446 return disambiguate(hexnode, length)
1446 return disambiguate(hexnode, length)
1447
1447
1448 def cmp(self, node, text):
1448 def cmp(self, node, text):
1449 """compare text with a given file revision
1449 """compare text with a given file revision
1450
1450
1451 returns True if text is different than what is stored.
1451 returns True if text is different than what is stored.
1452 """
1452 """
1453 p1, p2 = self.parents(node)
1453 p1, p2 = self.parents(node)
1454 return storageutil.hashrevisionsha1(text, p1, p2) != node
1454 return storageutil.hashrevisionsha1(text, p1, p2) != node
1455
1455
1456 def _cachesegment(self, offset, data):
1456 def _cachesegment(self, offset, data):
1457 """Add a segment to the revlog cache.
1457 """Add a segment to the revlog cache.
1458
1458
1459 Accepts an absolute offset and the data that is at that location.
1459 Accepts an absolute offset and the data that is at that location.
1460 """
1460 """
1461 o, d = self._chunkcache
1461 o, d = self._chunkcache
1462 # try to add to existing cache
1462 # try to add to existing cache
1463 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1463 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1464 self._chunkcache = o, d + data
1464 self._chunkcache = o, d + data
1465 else:
1465 else:
1466 self._chunkcache = offset, data
1466 self._chunkcache = offset, data
1467
1467
1468 def _readsegment(self, offset, length, df=None):
1468 def _readsegment(self, offset, length, df=None):
1469 """Load a segment of raw data from the revlog.
1469 """Load a segment of raw data from the revlog.
1470
1470
1471 Accepts an absolute offset, length to read, and an optional existing
1471 Accepts an absolute offset, length to read, and an optional existing
1472 file handle to read from.
1472 file handle to read from.
1473
1473
1474 If an existing file handle is passed, it will be seeked and the
1474 If an existing file handle is passed, it will be seeked and the
1475 original seek position will NOT be restored.
1475 original seek position will NOT be restored.
1476
1476
1477 Returns a str or buffer of raw byte data.
1477 Returns a str or buffer of raw byte data.
1478
1478
1479 Raises if the requested number of bytes could not be read.
1479 Raises if the requested number of bytes could not be read.
1480 """
1480 """
1481 # Cache data both forward and backward around the requested
1481 # Cache data both forward and backward around the requested
1482 # data, in a fixed size window. This helps speed up operations
1482 # data, in a fixed size window. This helps speed up operations
1483 # involving reading the revlog backwards.
1483 # involving reading the revlog backwards.
1484 cachesize = self._chunkcachesize
1484 cachesize = self._chunkcachesize
1485 realoffset = offset & ~(cachesize - 1)
1485 realoffset = offset & ~(cachesize - 1)
1486 reallength = (
1486 reallength = (
1487 (offset + length + cachesize) & ~(cachesize - 1)
1487 (offset + length + cachesize) & ~(cachesize - 1)
1488 ) - realoffset
1488 ) - realoffset
1489 with self._datareadfp(df) as df:
1489 with self._datareadfp(df) as df:
1490 df.seek(realoffset)
1490 df.seek(realoffset)
1491 d = df.read(reallength)
1491 d = df.read(reallength)
1492
1492
1493 self._cachesegment(realoffset, d)
1493 self._cachesegment(realoffset, d)
1494 if offset != realoffset or reallength != length:
1494 if offset != realoffset or reallength != length:
1495 startoffset = offset - realoffset
1495 startoffset = offset - realoffset
1496 if len(d) - startoffset < length:
1496 if len(d) - startoffset < length:
1497 raise error.RevlogError(
1497 raise error.RevlogError(
1498 _(
1498 _(
1499 b'partial read of revlog %s; expected %d bytes from '
1499 b'partial read of revlog %s; expected %d bytes from '
1500 b'offset %d, got %d'
1500 b'offset %d, got %d'
1501 )
1501 )
1502 % (
1502 % (
1503 self.indexfile if self._inline else self.datafile,
1503 self.indexfile if self._inline else self.datafile,
1504 length,
1504 length,
1505 realoffset,
1505 realoffset,
1506 len(d) - startoffset,
1506 len(d) - startoffset,
1507 )
1507 )
1508 )
1508 )
1509
1509
1510 return util.buffer(d, startoffset, length)
1510 return util.buffer(d, startoffset, length)
1511
1511
1512 if len(d) < length:
1512 if len(d) < length:
1513 raise error.RevlogError(
1513 raise error.RevlogError(
1514 _(
1514 _(
1515 b'partial read of revlog %s; expected %d bytes from offset '
1515 b'partial read of revlog %s; expected %d bytes from offset '
1516 b'%d, got %d'
1516 b'%d, got %d'
1517 )
1517 )
1518 % (
1518 % (
1519 self.indexfile if self._inline else self.datafile,
1519 self.indexfile if self._inline else self.datafile,
1520 length,
1520 length,
1521 offset,
1521 offset,
1522 len(d),
1522 len(d),
1523 )
1523 )
1524 )
1524 )
1525
1525
1526 return d
1526 return d
1527
1527
1528 def _getsegment(self, offset, length, df=None):
1528 def _getsegment(self, offset, length, df=None):
1529 """Obtain a segment of raw data from the revlog.
1529 """Obtain a segment of raw data from the revlog.
1530
1530
1531 Accepts an absolute offset, length of bytes to obtain, and an
1531 Accepts an absolute offset, length of bytes to obtain, and an
1532 optional file handle to the already-opened revlog. If the file
1532 optional file handle to the already-opened revlog. If the file
1533 handle is used, it's original seek position will not be preserved.
1533 handle is used, it's original seek position will not be preserved.
1534
1534
1535 Requests for data may be returned from a cache.
1535 Requests for data may be returned from a cache.
1536
1536
1537 Returns a str or a buffer instance of raw byte data.
1537 Returns a str or a buffer instance of raw byte data.
1538 """
1538 """
1539 o, d = self._chunkcache
1539 o, d = self._chunkcache
1540 l = len(d)
1540 l = len(d)
1541
1541
1542 # is it in the cache?
1542 # is it in the cache?
1543 cachestart = offset - o
1543 cachestart = offset - o
1544 cacheend = cachestart + length
1544 cacheend = cachestart + length
1545 if cachestart >= 0 and cacheend <= l:
1545 if cachestart >= 0 and cacheend <= l:
1546 if cachestart == 0 and cacheend == l:
1546 if cachestart == 0 and cacheend == l:
1547 return d # avoid a copy
1547 return d # avoid a copy
1548 return util.buffer(d, cachestart, cacheend - cachestart)
1548 return util.buffer(d, cachestart, cacheend - cachestart)
1549
1549
1550 return self._readsegment(offset, length, df=df)
1550 return self._readsegment(offset, length, df=df)
1551
1551
1552 def _getsegmentforrevs(self, startrev, endrev, df=None):
1552 def _getsegmentforrevs(self, startrev, endrev, df=None):
1553 """Obtain a segment of raw data corresponding to a range of revisions.
1553 """Obtain a segment of raw data corresponding to a range of revisions.
1554
1554
1555 Accepts the start and end revisions and an optional already-open
1555 Accepts the start and end revisions and an optional already-open
1556 file handle to be used for reading. If the file handle is read, its
1556 file handle to be used for reading. If the file handle is read, its
1557 seek position will not be preserved.
1557 seek position will not be preserved.
1558
1558
1559 Requests for data may be satisfied by a cache.
1559 Requests for data may be satisfied by a cache.
1560
1560
1561 Returns a 2-tuple of (offset, data) for the requested range of
1561 Returns a 2-tuple of (offset, data) for the requested range of
1562 revisions. Offset is the integer offset from the beginning of the
1562 revisions. Offset is the integer offset from the beginning of the
1563 revlog and data is a str or buffer of the raw byte data.
1563 revlog and data is a str or buffer of the raw byte data.
1564
1564
1565 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1565 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1566 to determine where each revision's data begins and ends.
1566 to determine where each revision's data begins and ends.
1567 """
1567 """
1568 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1568 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1569 # (functions are expensive).
1569 # (functions are expensive).
1570 index = self.index
1570 index = self.index
1571 istart = index[startrev]
1571 istart = index[startrev]
1572 start = int(istart[0] >> 16)
1572 start = int(istart[0] >> 16)
1573 if startrev == endrev:
1573 if startrev == endrev:
1574 end = start + istart[1]
1574 end = start + istart[1]
1575 else:
1575 else:
1576 iend = index[endrev]
1576 iend = index[endrev]
1577 end = int(iend[0] >> 16) + iend[1]
1577 end = int(iend[0] >> 16) + iend[1]
1578
1578
1579 if self._inline:
1579 if self._inline:
1580 start += (startrev + 1) * self.index.entry_size
1580 start += (startrev + 1) * self.index.entry_size
1581 end += (endrev + 1) * self.index.entry_size
1581 end += (endrev + 1) * self.index.entry_size
1582 length = end - start
1582 length = end - start
1583
1583
1584 return start, self._getsegment(start, length, df=df)
1584 return start, self._getsegment(start, length, df=df)
1585
1585
1586 def _chunk(self, rev, df=None):
1586 def _chunk(self, rev, df=None):
1587 """Obtain a single decompressed chunk for a revision.
1587 """Obtain a single decompressed chunk for a revision.
1588
1588
1589 Accepts an integer revision and an optional already-open file handle
1589 Accepts an integer revision and an optional already-open file handle
1590 to be used for reading. If used, the seek position of the file will not
1590 to be used for reading. If used, the seek position of the file will not
1591 be preserved.
1591 be preserved.
1592
1592
1593 Returns a str holding uncompressed data for the requested revision.
1593 Returns a str holding uncompressed data for the requested revision.
1594 """
1594 """
1595 return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
1595 return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
1596
1596
1597 def _chunks(self, revs, df=None, targetsize=None):
1597 def _chunks(self, revs, df=None, targetsize=None):
1598 """Obtain decompressed chunks for the specified revisions.
1598 """Obtain decompressed chunks for the specified revisions.
1599
1599
1600 Accepts an iterable of numeric revisions that are assumed to be in
1600 Accepts an iterable of numeric revisions that are assumed to be in
1601 ascending order. Also accepts an optional already-open file handle
1601 ascending order. Also accepts an optional already-open file handle
1602 to be used for reading. If used, the seek position of the file will
1602 to be used for reading. If used, the seek position of the file will
1603 not be preserved.
1603 not be preserved.
1604
1604
1605 This function is similar to calling ``self._chunk()`` multiple times,
1605 This function is similar to calling ``self._chunk()`` multiple times,
1606 but is faster.
1606 but is faster.
1607
1607
1608 Returns a list with decompressed data for each requested revision.
1608 Returns a list with decompressed data for each requested revision.
1609 """
1609 """
1610 if not revs:
1610 if not revs:
1611 return []
1611 return []
1612 start = self.start
1612 start = self.start
1613 length = self.length
1613 length = self.length
1614 inline = self._inline
1614 inline = self._inline
1615 iosize = self.index.entry_size
1615 iosize = self.index.entry_size
1616 buffer = util.buffer
1616 buffer = util.buffer
1617
1617
1618 l = []
1618 l = []
1619 ladd = l.append
1619 ladd = l.append
1620
1620
1621 if not self._withsparseread:
1621 if not self._withsparseread:
1622 slicedchunks = (revs,)
1622 slicedchunks = (revs,)
1623 else:
1623 else:
1624 slicedchunks = deltautil.slicechunk(
1624 slicedchunks = deltautil.slicechunk(
1625 self, revs, targetsize=targetsize
1625 self, revs, targetsize=targetsize
1626 )
1626 )
1627
1627
1628 for revschunk in slicedchunks:
1628 for revschunk in slicedchunks:
1629 firstrev = revschunk[0]
1629 firstrev = revschunk[0]
1630 # Skip trailing revisions with empty diff
1630 # Skip trailing revisions with empty diff
1631 for lastrev in revschunk[::-1]:
1631 for lastrev in revschunk[::-1]:
1632 if length(lastrev) != 0:
1632 if length(lastrev) != 0:
1633 break
1633 break
1634
1634
1635 try:
1635 try:
1636 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1636 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1637 except OverflowError:
1637 except OverflowError:
1638 # issue4215 - we can't cache a run of chunks greater than
1638 # issue4215 - we can't cache a run of chunks greater than
1639 # 2G on Windows
1639 # 2G on Windows
1640 return [self._chunk(rev, df=df) for rev in revschunk]
1640 return [self._chunk(rev, df=df) for rev in revschunk]
1641
1641
1642 decomp = self.decompress
1642 decomp = self.decompress
1643 for rev in revschunk:
1643 for rev in revschunk:
1644 chunkstart = start(rev)
1644 chunkstart = start(rev)
1645 if inline:
1645 if inline:
1646 chunkstart += (rev + 1) * iosize
1646 chunkstart += (rev + 1) * iosize
1647 chunklength = length(rev)
1647 chunklength = length(rev)
1648 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
1648 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
1649
1649
1650 return l
1650 return l
1651
1651
1652 def _chunkclear(self):
1652 def _chunkclear(self):
1653 """Clear the raw chunk cache."""
1653 """Clear the raw chunk cache."""
1654 self._chunkcache = (0, b'')
1654 self._chunkcache = (0, b'')
1655
1655
1656 def deltaparent(self, rev):
1656 def deltaparent(self, rev):
1657 """return deltaparent of the given revision"""
1657 """return deltaparent of the given revision"""
1658 base = self.index[rev][3]
1658 base = self.index[rev][3]
1659 if base == rev:
1659 if base == rev:
1660 return nullrev
1660 return nullrev
1661 elif self._generaldelta:
1661 elif self._generaldelta:
1662 return base
1662 return base
1663 else:
1663 else:
1664 return rev - 1
1664 return rev - 1
1665
1665
1666 def issnapshot(self, rev):
1666 def issnapshot(self, rev):
1667 """tells whether rev is a snapshot"""
1667 """tells whether rev is a snapshot"""
1668 if not self._sparserevlog:
1668 if not self._sparserevlog:
1669 return self.deltaparent(rev) == nullrev
1669 return self.deltaparent(rev) == nullrev
1670 elif util.safehasattr(self.index, b'issnapshot'):
1670 elif util.safehasattr(self.index, b'issnapshot'):
1671 # directly assign the method to cache the testing and access
1671 # directly assign the method to cache the testing and access
1672 self.issnapshot = self.index.issnapshot
1672 self.issnapshot = self.index.issnapshot
1673 return self.issnapshot(rev)
1673 return self.issnapshot(rev)
1674 if rev == nullrev:
1674 if rev == nullrev:
1675 return True
1675 return True
1676 entry = self.index[rev]
1676 entry = self.index[rev]
1677 base = entry[3]
1677 base = entry[3]
1678 if base == rev:
1678 if base == rev:
1679 return True
1679 return True
1680 if base == nullrev:
1680 if base == nullrev:
1681 return True
1681 return True
1682 p1 = entry[5]
1682 p1 = entry[5]
1683 p2 = entry[6]
1683 p2 = entry[6]
1684 if base == p1 or base == p2:
1684 if base == p1 or base == p2:
1685 return False
1685 return False
1686 return self.issnapshot(base)
1686 return self.issnapshot(base)
1687
1687
1688 def snapshotdepth(self, rev):
1688 def snapshotdepth(self, rev):
1689 """number of snapshot in the chain before this one"""
1689 """number of snapshot in the chain before this one"""
1690 if not self.issnapshot(rev):
1690 if not self.issnapshot(rev):
1691 raise error.ProgrammingError(b'revision %d not a snapshot')
1691 raise error.ProgrammingError(b'revision %d not a snapshot')
1692 return len(self._deltachain(rev)[0]) - 1
1692 return len(self._deltachain(rev)[0]) - 1
1693
1693
1694 def revdiff(self, rev1, rev2):
1694 def revdiff(self, rev1, rev2):
1695 """return or calculate a delta between two revisions
1695 """return or calculate a delta between two revisions
1696
1696
1697 The delta calculated is in binary form and is intended to be written to
1697 The delta calculated is in binary form and is intended to be written to
1698 revlog data directly. So this function needs raw revision data.
1698 revlog data directly. So this function needs raw revision data.
1699 """
1699 """
1700 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1700 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1701 return bytes(self._chunk(rev2))
1701 return bytes(self._chunk(rev2))
1702
1702
1703 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
1703 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
1704
1704
1705 def _processflags(self, text, flags, operation, raw=False):
1705 def _processflags(self, text, flags, operation, raw=False):
1706 """deprecated entry point to access flag processors"""
1706 """deprecated entry point to access flag processors"""
1707 msg = b'_processflag(...) use the specialized variant'
1707 msg = b'_processflag(...) use the specialized variant'
1708 util.nouideprecwarn(msg, b'5.2', stacklevel=2)
1708 util.nouideprecwarn(msg, b'5.2', stacklevel=2)
1709 if raw:
1709 if raw:
1710 return text, flagutil.processflagsraw(self, text, flags)
1710 return text, flagutil.processflagsraw(self, text, flags)
1711 elif operation == b'read':
1711 elif operation == b'read':
1712 return flagutil.processflagsread(self, text, flags)
1712 return flagutil.processflagsread(self, text, flags)
1713 else: # write operation
1713 else: # write operation
1714 return flagutil.processflagswrite(self, text, flags)
1714 return flagutil.processflagswrite(self, text, flags)
1715
1715
1716 def revision(self, nodeorrev, _df=None, raw=False):
1716 def revision(self, nodeorrev, _df=None, raw=False):
1717 """return an uncompressed revision of a given node or revision
1717 """return an uncompressed revision of a given node or revision
1718 number.
1718 number.
1719
1719
1720 _df - an existing file handle to read from. (internal-only)
1720 _df - an existing file handle to read from. (internal-only)
1721 raw - an optional argument specifying if the revision data is to be
1721 raw - an optional argument specifying if the revision data is to be
1722 treated as raw data when applying flag transforms. 'raw' should be set
1722 treated as raw data when applying flag transforms. 'raw' should be set
1723 to True when generating changegroups or in debug commands.
1723 to True when generating changegroups or in debug commands.
1724 """
1724 """
1725 if raw:
1725 if raw:
1726 msg = (
1726 msg = (
1727 b'revlog.revision(..., raw=True) is deprecated, '
1727 b'revlog.revision(..., raw=True) is deprecated, '
1728 b'use revlog.rawdata(...)'
1728 b'use revlog.rawdata(...)'
1729 )
1729 )
1730 util.nouideprecwarn(msg, b'5.2', stacklevel=2)
1730 util.nouideprecwarn(msg, b'5.2', stacklevel=2)
1731 return self._revisiondata(nodeorrev, _df, raw=raw)[0]
1731 return self._revisiondata(nodeorrev, _df, raw=raw)[0]
1732
1732
1733 def sidedata(self, nodeorrev, _df=None):
1733 def sidedata(self, nodeorrev, _df=None):
1734 """a map of extra data related to the changeset but not part of the hash
1734 """a map of extra data related to the changeset but not part of the hash
1735
1735
1736 This function currently return a dictionary. However, more advanced
1736 This function currently return a dictionary. However, more advanced
1737 mapping object will likely be used in the future for a more
1737 mapping object will likely be used in the future for a more
1738 efficient/lazy code.
1738 efficient/lazy code.
1739 """
1739 """
1740 return self._revisiondata(nodeorrev, _df)[1]
1740 return self._revisiondata(nodeorrev, _df)[1]
1741
1741
1742 def _revisiondata(self, nodeorrev, _df=None, raw=False):
1742 def _revisiondata(self, nodeorrev, _df=None, raw=False):
1743 # deal with <nodeorrev> argument type
1743 # deal with <nodeorrev> argument type
1744 if isinstance(nodeorrev, int):
1744 if isinstance(nodeorrev, int):
1745 rev = nodeorrev
1745 rev = nodeorrev
1746 node = self.node(rev)
1746 node = self.node(rev)
1747 else:
1747 else:
1748 node = nodeorrev
1748 node = nodeorrev
1749 rev = None
1749 rev = None
1750
1750
1751 # fast path the special `nullid` rev
1751 # fast path the special `nullid` rev
1752 if node == self.nullid:
1752 if node == self.nullid:
1753 return b"", {}
1753 return b"", {}
1754
1754
1755 # ``rawtext`` is the text as stored inside the revlog. Might be the
1755 # ``rawtext`` is the text as stored inside the revlog. Might be the
1756 # revision or might need to be processed to retrieve the revision.
1756 # revision or might need to be processed to retrieve the revision.
1757 rev, rawtext, validated = self._rawtext(node, rev, _df=_df)
1757 rev, rawtext, validated = self._rawtext(node, rev, _df=_df)
1758
1758
1759 if self.version & 0xFFFF == REVLOGV2:
1759 if self.version & 0xFFFF == REVLOGV2:
1760 if rev is None:
1760 if rev is None:
1761 rev = self.rev(node)
1761 rev = self.rev(node)
1762 sidedata = self._sidedata(rev)
1762 sidedata = self._sidedata(rev)
1763 else:
1763 else:
1764 sidedata = {}
1764 sidedata = {}
1765
1765
1766 if raw and validated:
1766 if raw and validated:
1767 # if we don't want to process the raw text and that raw
1767 # if we don't want to process the raw text and that raw
1768 # text is cached, we can exit early.
1768 # text is cached, we can exit early.
1769 return rawtext, sidedata
1769 return rawtext, sidedata
1770 if rev is None:
1770 if rev is None:
1771 rev = self.rev(node)
1771 rev = self.rev(node)
1772 # the revlog's flag for this revision
1772 # the revlog's flag for this revision
1773 # (usually alter its state or content)
1773 # (usually alter its state or content)
1774 flags = self.flags(rev)
1774 flags = self.flags(rev)
1775
1775
1776 if validated and flags == REVIDX_DEFAULT_FLAGS:
1776 if validated and flags == REVIDX_DEFAULT_FLAGS:
1777 # no extra flags set, no flag processor runs, text = rawtext
1777 # no extra flags set, no flag processor runs, text = rawtext
1778 return rawtext, sidedata
1778 return rawtext, sidedata
1779
1779
1780 if raw:
1780 if raw:
1781 validatehash = flagutil.processflagsraw(self, rawtext, flags)
1781 validatehash = flagutil.processflagsraw(self, rawtext, flags)
1782 text = rawtext
1782 text = rawtext
1783 else:
1783 else:
1784 r = flagutil.processflagsread(self, rawtext, flags)
1784 r = flagutil.processflagsread(self, rawtext, flags)
1785 text, validatehash = r
1785 text, validatehash = r
1786 if validatehash:
1786 if validatehash:
1787 self.checkhash(text, node, rev=rev)
1787 self.checkhash(text, node, rev=rev)
1788 if not validated:
1788 if not validated:
1789 self._revisioncache = (node, rev, rawtext)
1789 self._revisioncache = (node, rev, rawtext)
1790
1790
1791 return text, sidedata
1791 return text, sidedata
1792
1792
1793 def _rawtext(self, node, rev, _df=None):
1793 def _rawtext(self, node, rev, _df=None):
1794 """return the possibly unvalidated rawtext for a revision
1794 """return the possibly unvalidated rawtext for a revision
1795
1795
1796 returns (rev, rawtext, validated)
1796 returns (rev, rawtext, validated)
1797 """
1797 """
1798
1798
1799 # revision in the cache (could be useful to apply delta)
1799 # revision in the cache (could be useful to apply delta)
1800 cachedrev = None
1800 cachedrev = None
1801 # An intermediate text to apply deltas to
1801 # An intermediate text to apply deltas to
1802 basetext = None
1802 basetext = None
1803
1803
1804 # Check if we have the entry in cache
1804 # Check if we have the entry in cache
1805 # The cache entry looks like (node, rev, rawtext)
1805 # The cache entry looks like (node, rev, rawtext)
1806 if self._revisioncache:
1806 if self._revisioncache:
1807 if self._revisioncache[0] == node:
1807 if self._revisioncache[0] == node:
1808 return (rev, self._revisioncache[2], True)
1808 return (rev, self._revisioncache[2], True)
1809 cachedrev = self._revisioncache[1]
1809 cachedrev = self._revisioncache[1]
1810
1810
1811 if rev is None:
1811 if rev is None:
1812 rev = self.rev(node)
1812 rev = self.rev(node)
1813
1813
1814 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1814 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1815 if stopped:
1815 if stopped:
1816 basetext = self._revisioncache[2]
1816 basetext = self._revisioncache[2]
1817
1817
1818 # drop cache to save memory, the caller is expected to
1818 # drop cache to save memory, the caller is expected to
1819 # update self._revisioncache after validating the text
1819 # update self._revisioncache after validating the text
1820 self._revisioncache = None
1820 self._revisioncache = None
1821
1821
1822 targetsize = None
1822 targetsize = None
1823 rawsize = self.index[rev][2]
1823 rawsize = self.index[rev][2]
1824 if 0 <= rawsize:
1824 if 0 <= rawsize:
1825 targetsize = 4 * rawsize
1825 targetsize = 4 * rawsize
1826
1826
1827 bins = self._chunks(chain, df=_df, targetsize=targetsize)
1827 bins = self._chunks(chain, df=_df, targetsize=targetsize)
1828 if basetext is None:
1828 if basetext is None:
1829 basetext = bytes(bins[0])
1829 basetext = bytes(bins[0])
1830 bins = bins[1:]
1830 bins = bins[1:]
1831
1831
1832 rawtext = mdiff.patches(basetext, bins)
1832 rawtext = mdiff.patches(basetext, bins)
1833 del basetext # let us have a chance to free memory early
1833 del basetext # let us have a chance to free memory early
1834 return (rev, rawtext, False)
1834 return (rev, rawtext, False)
1835
1835
1836 def _sidedata(self, rev):
1836 def _sidedata(self, rev):
1837 """Return the sidedata for a given revision number."""
1837 """Return the sidedata for a given revision number."""
1838 index_entry = self.index[rev]
1838 index_entry = self.index[rev]
1839 sidedata_offset = index_entry[8]
1839 sidedata_offset = index_entry[8]
1840 sidedata_size = index_entry[9]
1840 sidedata_size = index_entry[9]
1841
1841
1842 if self._inline:
1842 if self._inline:
1843 sidedata_offset += self.index.entry_size * (1 + rev)
1843 sidedata_offset += self.index.entry_size * (1 + rev)
1844 if sidedata_size == 0:
1844 if sidedata_size == 0:
1845 return {}
1845 return {}
1846
1846
1847 segment = self._getsegment(sidedata_offset, sidedata_size)
1847 segment = self._getsegment(sidedata_offset, sidedata_size)
1848 sidedata = sidedatautil.deserialize_sidedata(segment)
1848 sidedata = sidedatautil.deserialize_sidedata(segment)
1849 return sidedata
1849 return sidedata
1850
1850
1851 def rawdata(self, nodeorrev, _df=None):
1851 def rawdata(self, nodeorrev, _df=None):
1852 """return an uncompressed raw data of a given node or revision number.
1852 """return an uncompressed raw data of a given node or revision number.
1853
1853
1854 _df - an existing file handle to read from. (internal-only)
1854 _df - an existing file handle to read from. (internal-only)
1855 """
1855 """
1856 return self._revisiondata(nodeorrev, _df, raw=True)[0]
1856 return self._revisiondata(nodeorrev, _df, raw=True)[0]
1857
1857
1858 def hash(self, text, p1, p2):
1858 def hash(self, text, p1, p2):
1859 """Compute a node hash.
1859 """Compute a node hash.
1860
1860
1861 Available as a function so that subclasses can replace the hash
1861 Available as a function so that subclasses can replace the hash
1862 as needed.
1862 as needed.
1863 """
1863 """
1864 return storageutil.hashrevisionsha1(text, p1, p2)
1864 return storageutil.hashrevisionsha1(text, p1, p2)
1865
1865
1866 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1866 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1867 """Check node hash integrity.
1867 """Check node hash integrity.
1868
1868
1869 Available as a function so that subclasses can extend hash mismatch
1869 Available as a function so that subclasses can extend hash mismatch
1870 behaviors as needed.
1870 behaviors as needed.
1871 """
1871 """
1872 try:
1872 try:
1873 if p1 is None and p2 is None:
1873 if p1 is None and p2 is None:
1874 p1, p2 = self.parents(node)
1874 p1, p2 = self.parents(node)
1875 if node != self.hash(text, p1, p2):
1875 if node != self.hash(text, p1, p2):
1876 # Clear the revision cache on hash failure. The revision cache
1876 # Clear the revision cache on hash failure. The revision cache
1877 # only stores the raw revision and clearing the cache does have
1877 # only stores the raw revision and clearing the cache does have
1878 # the side-effect that we won't have a cache hit when the raw
1878 # the side-effect that we won't have a cache hit when the raw
1879 # revision data is accessed. But this case should be rare and
1879 # revision data is accessed. But this case should be rare and
1880 # it is extra work to teach the cache about the hash
1880 # it is extra work to teach the cache about the hash
1881 # verification state.
1881 # verification state.
1882 if self._revisioncache and self._revisioncache[0] == node:
1882 if self._revisioncache and self._revisioncache[0] == node:
1883 self._revisioncache = None
1883 self._revisioncache = None
1884
1884
1885 revornode = rev
1885 revornode = rev
1886 if revornode is None:
1886 if revornode is None:
1887 revornode = templatefilters.short(hex(node))
1887 revornode = templatefilters.short(hex(node))
1888 raise error.RevlogError(
1888 raise error.RevlogError(
1889 _(b"integrity check failed on %s:%s")
1889 _(b"integrity check failed on %s:%s")
1890 % (self.indexfile, pycompat.bytestr(revornode))
1890 % (self.indexfile, pycompat.bytestr(revornode))
1891 )
1891 )
1892 except error.RevlogError:
1892 except error.RevlogError:
1893 if self._censorable and storageutil.iscensoredtext(text):
1893 if self._censorable and storageutil.iscensoredtext(text):
1894 raise error.CensoredNodeError(self.indexfile, node, text)
1894 raise error.CensoredNodeError(self.indexfile, node, text)
1895 raise
1895 raise
1896
1896
1897 def _enforceinlinesize(self, tr, fp=None):
1897 def _enforceinlinesize(self, tr, fp=None):
1898 """Check if the revlog is too big for inline and convert if so.
1898 """Check if the revlog is too big for inline and convert if so.
1899
1899
1900 This should be called after revisions are added to the revlog. If the
1900 This should be called after revisions are added to the revlog. If the
1901 revlog has grown too large to be an inline revlog, it will convert it
1901 revlog has grown too large to be an inline revlog, it will convert it
1902 to use multiple index and data files.
1902 to use multiple index and data files.
1903 """
1903 """
1904 tiprev = len(self) - 1
1904 tiprev = len(self) - 1
1905 if (
1905 if (
1906 not self._inline
1906 not self._inline
1907 or (self.start(tiprev) + self.length(tiprev)) < _maxinline
1907 or (self.start(tiprev) + self.length(tiprev)) < _maxinline
1908 ):
1908 ):
1909 return
1909 return
1910
1910
1911 troffset = tr.findoffset(self.indexfile)
1911 troffset = tr.findoffset(self.indexfile)
1912 if troffset is None:
1912 if troffset is None:
1913 raise error.RevlogError(
1913 raise error.RevlogError(
1914 _(b"%s not found in the transaction") % self.indexfile
1914 _(b"%s not found in the transaction") % self.indexfile
1915 )
1915 )
1916 trindex = 0
1916 trindex = 0
1917 tr.add(self.datafile, 0)
1917 tr.add(self.datafile, 0)
1918
1918
1919 if fp:
1919 if fp:
1920 fp.flush()
1920 fp.flush()
1921 fp.close()
1921 fp.close()
1922 # We can't use the cached file handle after close(). So prevent
1922 # We can't use the cached file handle after close(). So prevent
1923 # its usage.
1923 # its usage.
1924 self._writinghandles = None
1924 self._writinghandles = None
1925
1925
1926 with self._indexfp(b'r') as ifh, self._datafp(b'w') as dfh:
1926 with self._indexfp(b'r') as ifh, self._datafp(b'w') as dfh:
1927 for r in self:
1927 for r in self:
1928 dfh.write(self._getsegmentforrevs(r, r, df=ifh)[1])
1928 dfh.write(self._getsegmentforrevs(r, r, df=ifh)[1])
1929 if troffset <= self.start(r):
1929 if troffset <= self.start(r):
1930 trindex = r
1930 trindex = r
1931
1931
1932 with self._indexfp(b'w') as fp:
1932 with self._indexfp(b'w') as fp:
1933 self.version &= ~FLAG_INLINE_DATA
1933 self.version &= ~FLAG_INLINE_DATA
1934 self._inline = False
1934 self._inline = False
1935 for i in self:
1935 for i in self:
1936 e = self.index.entry_binary(i)
1936 e = self.index.entry_binary(i)
1937 if i == 0:
1937 if i == 0:
1938 header = self.index.pack_header(self.version)
1938 header = self.index.pack_header(self.version)
1939 e = header + e
1939 e = header + e
1940 fp.write(e)
1940 fp.write(e)
1941
1941
1942 # the temp file replace the real index when we exit the context
1942 # the temp file replace the real index when we exit the context
1943 # manager
1943 # manager
1944
1944
1945 tr.replace(self.indexfile, trindex * self.index.entry_size)
1945 tr.replace(self.indexfile, trindex * self.index.entry_size)
1946 nodemaputil.setup_persistent_nodemap(tr, self)
1946 nodemaputil.setup_persistent_nodemap(tr, self)
1947 self._chunkclear()
1947 self._chunkclear()
1948
1948
1949 def _nodeduplicatecallback(self, transaction, node):
1949 def _nodeduplicatecallback(self, transaction, node):
1950 """called when trying to add a node already stored."""
1950 """called when trying to add a node already stored."""
1951
1951
1952 def addrevision(
1952 def addrevision(
1953 self,
1953 self,
1954 text,
1954 text,
1955 transaction,
1955 transaction,
1956 link,
1956 link,
1957 p1,
1957 p1,
1958 p2,
1958 p2,
1959 cachedelta=None,
1959 cachedelta=None,
1960 node=None,
1960 node=None,
1961 flags=REVIDX_DEFAULT_FLAGS,
1961 flags=REVIDX_DEFAULT_FLAGS,
1962 deltacomputer=None,
1962 deltacomputer=None,
1963 sidedata=None,
1963 sidedata=None,
1964 ):
1964 ):
1965 """add a revision to the log
1965 """add a revision to the log
1966
1966
1967 text - the revision data to add
1967 text - the revision data to add
1968 transaction - the transaction object used for rollback
1968 transaction - the transaction object used for rollback
1969 link - the linkrev data to add
1969 link - the linkrev data to add
1970 p1, p2 - the parent nodeids of the revision
1970 p1, p2 - the parent nodeids of the revision
1971 cachedelta - an optional precomputed delta
1971 cachedelta - an optional precomputed delta
1972 node - nodeid of revision; typically node is not specified, and it is
1972 node - nodeid of revision; typically node is not specified, and it is
1973 computed by default as hash(text, p1, p2), however subclasses might
1973 computed by default as hash(text, p1, p2), however subclasses might
1974 use different hashing method (and override checkhash() in such case)
1974 use different hashing method (and override checkhash() in such case)
1975 flags - the known flags to set on the revision
1975 flags - the known flags to set on the revision
1976 deltacomputer - an optional deltacomputer instance shared between
1976 deltacomputer - an optional deltacomputer instance shared between
1977 multiple calls
1977 multiple calls
1978 """
1978 """
1979 if link == nullrev:
1979 if link == nullrev:
1980 raise error.RevlogError(
1980 raise error.RevlogError(
1981 _(b"attempted to add linkrev -1 to %s") % self.indexfile
1981 _(b"attempted to add linkrev -1 to %s") % self.indexfile
1982 )
1982 )
1983
1983
1984 if sidedata is None:
1984 if sidedata is None:
1985 sidedata = {}
1985 sidedata = {}
1986 elif not self.hassidedata:
1986 elif not self.hassidedata:
1987 raise error.ProgrammingError(
1987 raise error.ProgrammingError(
1988 _(b"trying to add sidedata to a revlog who don't support them")
1988 _(b"trying to add sidedata to a revlog who don't support them")
1989 )
1989 )
1990
1990
1991 if flags:
1991 if flags:
1992 node = node or self.hash(text, p1, p2)
1992 node = node or self.hash(text, p1, p2)
1993
1993
1994 rawtext, validatehash = flagutil.processflagswrite(self, text, flags)
1994 rawtext, validatehash = flagutil.processflagswrite(self, text, flags)
1995
1995
1996 # If the flag processor modifies the revision data, ignore any provided
1996 # If the flag processor modifies the revision data, ignore any provided
1997 # cachedelta.
1997 # cachedelta.
1998 if rawtext != text:
1998 if rawtext != text:
1999 cachedelta = None
1999 cachedelta = None
2000
2000
2001 if len(rawtext) > _maxentrysize:
2001 if len(rawtext) > _maxentrysize:
2002 raise error.RevlogError(
2002 raise error.RevlogError(
2003 _(
2003 _(
2004 b"%s: size of %d bytes exceeds maximum revlog storage of 2GiB"
2004 b"%s: size of %d bytes exceeds maximum revlog storage of 2GiB"
2005 )
2005 )
2006 % (self.indexfile, len(rawtext))
2006 % (self.indexfile, len(rawtext))
2007 )
2007 )
2008
2008
2009 node = node or self.hash(rawtext, p1, p2)
2009 node = node or self.hash(rawtext, p1, p2)
2010 rev = self.index.get_rev(node)
2010 rev = self.index.get_rev(node)
2011 if rev is not None:
2011 if rev is not None:
2012 return rev
2012 return rev
2013
2013
2014 if validatehash:
2014 if validatehash:
2015 self.checkhash(rawtext, node, p1=p1, p2=p2)
2015 self.checkhash(rawtext, node, p1=p1, p2=p2)
2016
2016
2017 return self.addrawrevision(
2017 return self.addrawrevision(
2018 rawtext,
2018 rawtext,
2019 transaction,
2019 transaction,
2020 link,
2020 link,
2021 p1,
2021 p1,
2022 p2,
2022 p2,
2023 node,
2023 node,
2024 flags,
2024 flags,
2025 cachedelta=cachedelta,
2025 cachedelta=cachedelta,
2026 deltacomputer=deltacomputer,
2026 deltacomputer=deltacomputer,
2027 sidedata=sidedata,
2027 sidedata=sidedata,
2028 )
2028 )
2029
2029
2030 def addrawrevision(
2030 def addrawrevision(
2031 self,
2031 self,
2032 rawtext,
2032 rawtext,
2033 transaction,
2033 transaction,
2034 link,
2034 link,
2035 p1,
2035 p1,
2036 p2,
2036 p2,
2037 node,
2037 node,
2038 flags,
2038 flags,
2039 cachedelta=None,
2039 cachedelta=None,
2040 deltacomputer=None,
2040 deltacomputer=None,
2041 sidedata=None,
2041 sidedata=None,
2042 ):
2042 ):
2043 """add a raw revision with known flags, node and parents
2043 """add a raw revision with known flags, node and parents
2044 useful when reusing a revision not stored in this revlog (ex: received
2044 useful when reusing a revision not stored in this revlog (ex: received
2045 over wire, or read from an external bundle).
2045 over wire, or read from an external bundle).
2046 """
2046 """
2047 dfh = None
2047 dfh = None
2048 if not self._inline:
2048 if not self._inline:
2049 dfh = self._datafp(b"a+")
2049 dfh = self._datafp(b"a+")
2050 ifh = self._indexfp(b"a+")
2050 ifh = self._indexfp(b"a+")
2051 try:
2051 try:
2052 return self._addrevision(
2052 return self._addrevision(
2053 node,
2053 node,
2054 rawtext,
2054 rawtext,
2055 transaction,
2055 transaction,
2056 link,
2056 link,
2057 p1,
2057 p1,
2058 p2,
2058 p2,
2059 flags,
2059 flags,
2060 cachedelta,
2060 cachedelta,
2061 ifh,
2061 ifh,
2062 dfh,
2062 dfh,
2063 deltacomputer=deltacomputer,
2063 deltacomputer=deltacomputer,
2064 sidedata=sidedata,
2064 sidedata=sidedata,
2065 )
2065 )
2066 finally:
2066 finally:
2067 if dfh:
2067 if dfh:
2068 dfh.close()
2068 dfh.close()
2069 ifh.close()
2069 ifh.close()
2070
2070
2071 def compress(self, data):
2071 def compress(self, data):
2072 """Generate a possibly-compressed representation of data."""
2072 """Generate a possibly-compressed representation of data."""
2073 if not data:
2073 if not data:
2074 return b'', data
2074 return b'', data
2075
2075
2076 compressed = self._compressor.compress(data)
2076 compressed = self._compressor.compress(data)
2077
2077
2078 if compressed:
2078 if compressed:
2079 # The revlog compressor added the header in the returned data.
2079 # The revlog compressor added the header in the returned data.
2080 return b'', compressed
2080 return b'', compressed
2081
2081
2082 if data[0:1] == b'\0':
2082 if data[0:1] == b'\0':
2083 return b'', data
2083 return b'', data
2084 return b'u', data
2084 return b'u', data
2085
2085
2086 def decompress(self, data):
2086 def decompress(self, data):
2087 """Decompress a revlog chunk.
2087 """Decompress a revlog chunk.
2088
2088
2089 The chunk is expected to begin with a header identifying the
2089 The chunk is expected to begin with a header identifying the
2090 format type so it can be routed to an appropriate decompressor.
2090 format type so it can be routed to an appropriate decompressor.
2091 """
2091 """
2092 if not data:
2092 if not data:
2093 return data
2093 return data
2094
2094
2095 # Revlogs are read much more frequently than they are written and many
2095 # Revlogs are read much more frequently than they are written and many
2096 # chunks only take microseconds to decompress, so performance is
2096 # chunks only take microseconds to decompress, so performance is
2097 # important here.
2097 # important here.
2098 #
2098 #
2099 # We can make a few assumptions about revlogs:
2099 # We can make a few assumptions about revlogs:
2100 #
2100 #
2101 # 1) the majority of chunks will be compressed (as opposed to inline
2101 # 1) the majority of chunks will be compressed (as opposed to inline
2102 # raw data).
2102 # raw data).
2103 # 2) decompressing *any* data will likely by at least 10x slower than
2103 # 2) decompressing *any* data will likely by at least 10x slower than
2104 # returning raw inline data.
2104 # returning raw inline data.
2105 # 3) we want to prioritize common and officially supported compression
2105 # 3) we want to prioritize common and officially supported compression
2106 # engines
2106 # engines
2107 #
2107 #
2108 # It follows that we want to optimize for "decompress compressed data
2108 # It follows that we want to optimize for "decompress compressed data
2109 # when encoded with common and officially supported compression engines"
2109 # when encoded with common and officially supported compression engines"
2110 # case over "raw data" and "data encoded by less common or non-official
2110 # case over "raw data" and "data encoded by less common or non-official
2111 # compression engines." That is why we have the inline lookup first
2111 # compression engines." That is why we have the inline lookup first
2112 # followed by the compengines lookup.
2112 # followed by the compengines lookup.
2113 #
2113 #
2114 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
2114 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
2115 # compressed chunks. And this matters for changelog and manifest reads.
2115 # compressed chunks. And this matters for changelog and manifest reads.
2116 t = data[0:1]
2116 t = data[0:1]
2117
2117
2118 if t == b'x':
2118 if t == b'x':
2119 try:
2119 try:
2120 return _zlibdecompress(data)
2120 return _zlibdecompress(data)
2121 except zlib.error as e:
2121 except zlib.error as e:
2122 raise error.RevlogError(
2122 raise error.RevlogError(
2123 _(b'revlog decompress error: %s')
2123 _(b'revlog decompress error: %s')
2124 % stringutil.forcebytestr(e)
2124 % stringutil.forcebytestr(e)
2125 )
2125 )
2126 # '\0' is more common than 'u' so it goes first.
2126 # '\0' is more common than 'u' so it goes first.
2127 elif t == b'\0':
2127 elif t == b'\0':
2128 return data
2128 return data
2129 elif t == b'u':
2129 elif t == b'u':
2130 return util.buffer(data, 1)
2130 return util.buffer(data, 1)
2131
2131
2132 try:
2132 try:
2133 compressor = self._decompressors[t]
2133 compressor = self._decompressors[t]
2134 except KeyError:
2134 except KeyError:
2135 try:
2135 try:
2136 engine = util.compengines.forrevlogheader(t)
2136 engine = util.compengines.forrevlogheader(t)
2137 compressor = engine.revlogcompressor(self._compengineopts)
2137 compressor = engine.revlogcompressor(self._compengineopts)
2138 self._decompressors[t] = compressor
2138 self._decompressors[t] = compressor
2139 except KeyError:
2139 except KeyError:
2140 raise error.RevlogError(
2140 raise error.RevlogError(
2141 _(b'unknown compression type %s') % binascii.hexlify(t)
2141 _(b'unknown compression type %s') % binascii.hexlify(t)
2142 )
2142 )
2143
2143
2144 return compressor.decompress(data)
2144 return compressor.decompress(data)
2145
2145
2146 def _addrevision(
2146 def _addrevision(
2147 self,
2147 self,
2148 node,
2148 node,
2149 rawtext,
2149 rawtext,
2150 transaction,
2150 transaction,
2151 link,
2151 link,
2152 p1,
2152 p1,
2153 p2,
2153 p2,
2154 flags,
2154 flags,
2155 cachedelta,
2155 cachedelta,
2156 ifh,
2156 ifh,
2157 dfh,
2157 dfh,
2158 alwayscache=False,
2158 alwayscache=False,
2159 deltacomputer=None,
2159 deltacomputer=None,
2160 sidedata=None,
2160 sidedata=None,
2161 ):
2161 ):
2162 """internal function to add revisions to the log
2162 """internal function to add revisions to the log
2163
2163
2164 see addrevision for argument descriptions.
2164 see addrevision for argument descriptions.
2165
2165
2166 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2166 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2167
2167
2168 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2168 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2169 be used.
2169 be used.
2170
2170
2171 invariants:
2171 invariants:
2172 - rawtext is optional (can be None); if not set, cachedelta must be set.
2172 - rawtext is optional (can be None); if not set, cachedelta must be set.
2173 if both are set, they must correspond to each other.
2173 if both are set, they must correspond to each other.
2174 """
2174 """
2175 if node == self.nullid:
2175 if node == self.nullid:
2176 raise error.RevlogError(
2176 raise error.RevlogError(
2177 _(b"%s: attempt to add null revision") % self.indexfile
2177 _(b"%s: attempt to add null revision") % self.indexfile
2178 )
2178 )
2179 if (
2179 if (
2180 node == self.nodeconstants.wdirid
2180 node == self.nodeconstants.wdirid
2181 or node in self.nodeconstants.wdirfilenodeids
2181 or node in self.nodeconstants.wdirfilenodeids
2182 ):
2182 ):
2183 raise error.RevlogError(
2183 raise error.RevlogError(
2184 _(b"%s: attempt to add wdir revision") % self.indexfile
2184 _(b"%s: attempt to add wdir revision") % self.indexfile
2185 )
2185 )
2186
2186
2187 if self._inline:
2187 if self._inline:
2188 fh = ifh
2188 fh = ifh
2189 else:
2189 else:
2190 fh = dfh
2190 fh = dfh
2191
2191
2192 btext = [rawtext]
2192 btext = [rawtext]
2193
2193
2194 curr = len(self)
2194 curr = len(self)
2195 prev = curr - 1
2195 prev = curr - 1
2196
2196
2197 offset = self._get_data_offset(prev)
2197 offset = self._get_data_offset(prev)
2198
2198
2199 if self._concurrencychecker:
2199 if self._concurrencychecker:
2200 if self._inline:
2200 if self._inline:
2201 # offset is "as if" it were in the .d file, so we need to add on
2201 # offset is "as if" it were in the .d file, so we need to add on
2202 # the size of the entry metadata.
2202 # the size of the entry metadata.
2203 self._concurrencychecker(
2203 self._concurrencychecker(
2204 ifh, self.indexfile, offset + curr * self.index.entry_size
2204 ifh, self.indexfile, offset + curr * self.index.entry_size
2205 )
2205 )
2206 else:
2206 else:
2207 # Entries in the .i are a consistent size.
2207 # Entries in the .i are a consistent size.
2208 self._concurrencychecker(
2208 self._concurrencychecker(
2209 ifh, self.indexfile, curr * self.index.entry_size
2209 ifh, self.indexfile, curr * self.index.entry_size
2210 )
2210 )
2211 self._concurrencychecker(dfh, self.datafile, offset)
2211 self._concurrencychecker(dfh, self.datafile, offset)
2212
2212
2213 p1r, p2r = self.rev(p1), self.rev(p2)
2213 p1r, p2r = self.rev(p1), self.rev(p2)
2214
2214
2215 # full versions are inserted when the needed deltas
2215 # full versions are inserted when the needed deltas
2216 # become comparable to the uncompressed text
2216 # become comparable to the uncompressed text
2217 if rawtext is None:
2217 if rawtext is None:
2218 # need rawtext size, before changed by flag processors, which is
2218 # need rawtext size, before changed by flag processors, which is
2219 # the non-raw size. use revlog explicitly to avoid filelog's extra
2219 # the non-raw size. use revlog explicitly to avoid filelog's extra
2220 # logic that might remove metadata size.
2220 # logic that might remove metadata size.
2221 textlen = mdiff.patchedsize(
2221 textlen = mdiff.patchedsize(
2222 revlog.size(self, cachedelta[0]), cachedelta[1]
2222 revlog.size(self, cachedelta[0]), cachedelta[1]
2223 )
2223 )
2224 else:
2224 else:
2225 textlen = len(rawtext)
2225 textlen = len(rawtext)
2226
2226
2227 if deltacomputer is None:
2227 if deltacomputer is None:
2228 deltacomputer = deltautil.deltacomputer(self)
2228 deltacomputer = deltautil.deltacomputer(self)
2229
2229
2230 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
2230 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
2231
2231
2232 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2232 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2233
2233
2234 if sidedata:
2234 if sidedata:
2235 serialized_sidedata = sidedatautil.serialize_sidedata(sidedata)
2235 serialized_sidedata = sidedatautil.serialize_sidedata(sidedata)
2236 sidedata_offset = offset + deltainfo.deltalen
2236 sidedata_offset = offset + deltainfo.deltalen
2237 else:
2237 else:
2238 serialized_sidedata = b""
2238 serialized_sidedata = b""
2239 # Don't store the offset if the sidedata is empty, that way
2239 # Don't store the offset if the sidedata is empty, that way
2240 # we can easily detect empty sidedata and they will be no different
2240 # we can easily detect empty sidedata and they will be no different
2241 # than ones we manually add.
2241 # than ones we manually add.
2242 sidedata_offset = 0
2242 sidedata_offset = 0
2243
2243
2244 e = (
2244 e = (
2245 offset_type(offset, flags),
2245 offset_type(offset, flags),
2246 deltainfo.deltalen,
2246 deltainfo.deltalen,
2247 textlen,
2247 textlen,
2248 deltainfo.base,
2248 deltainfo.base,
2249 link,
2249 link,
2250 p1r,
2250 p1r,
2251 p2r,
2251 p2r,
2252 node,
2252 node,
2253 sidedata_offset,
2253 sidedata_offset,
2254 len(serialized_sidedata),
2254 len(serialized_sidedata),
2255 )
2255 )
2256
2256
2257 if self.version & 0xFFFF != REVLOGV2:
2257 if self.version & 0xFFFF != REVLOGV2:
2258 e = e[:8]
2258 e = e[:8]
2259
2259
2260 self.index.append(e)
2260 self.index.append(e)
2261 entry = self.index.entry_binary(curr)
2261 entry = self.index.entry_binary(curr)
2262 if curr == 0:
2262 if curr == 0:
2263 header = self.index.pack_header(self.version)
2263 header = self.index.pack_header(self.version)
2264 entry = header + entry
2264 entry = header + entry
2265 self._writeentry(
2265 self._writeentry(
2266 transaction,
2266 transaction,
2267 ifh,
2267 ifh,
2268 dfh,
2268 dfh,
2269 entry,
2269 entry,
2270 deltainfo.data,
2270 deltainfo.data,
2271 link,
2271 link,
2272 offset,
2272 offset,
2273 serialized_sidedata,
2273 serialized_sidedata,
2274 )
2274 )
2275
2275
2276 rawtext = btext[0]
2276 rawtext = btext[0]
2277
2277
2278 if alwayscache and rawtext is None:
2278 if alwayscache and rawtext is None:
2279 rawtext = deltacomputer.buildtext(revinfo, fh)
2279 rawtext = deltacomputer.buildtext(revinfo, fh)
2280
2280
2281 if type(rawtext) == bytes: # only accept immutable objects
2281 if type(rawtext) == bytes: # only accept immutable objects
2282 self._revisioncache = (node, curr, rawtext)
2282 self._revisioncache = (node, curr, rawtext)
2283 self._chainbasecache[curr] = deltainfo.chainbase
2283 self._chainbasecache[curr] = deltainfo.chainbase
2284 return curr
2284 return curr
2285
2285
2286 def _get_data_offset(self, prev):
2286 def _get_data_offset(self, prev):
2287 """Returns the current offset in the (in-transaction) data file.
2287 """Returns the current offset in the (in-transaction) data file.
2288 Versions < 2 of the revlog can get this 0(1), revlog v2 needs a docket
2288 Versions < 2 of the revlog can get this 0(1), revlog v2 needs a docket
2289 file to store that information: since sidedata can be rewritten to the
2289 file to store that information: since sidedata can be rewritten to the
2290 end of the data file within a transaction, you can have cases where, for
2290 end of the data file within a transaction, you can have cases where, for
2291 example, rev `n` does not have sidedata while rev `n - 1` does, leading
2291 example, rev `n` does not have sidedata while rev `n - 1` does, leading
2292 to `n - 1`'s sidedata being written after `n`'s data.
2292 to `n - 1`'s sidedata being written after `n`'s data.
2293
2293
2294 TODO cache this in a docket file before getting out of experimental."""
2294 TODO cache this in a docket file before getting out of experimental."""
2295 if self.version & 0xFFFF != REVLOGV2:
2295 if self.version & 0xFFFF != REVLOGV2:
2296 return self.end(prev)
2296 return self.end(prev)
2297
2297
2298 offset = 0
2298 offset = 0
2299 for rev, entry in enumerate(self.index):
2299 for rev, entry in enumerate(self.index):
2300 sidedata_end = entry[8] + entry[9]
2300 sidedata_end = entry[8] + entry[9]
2301 # Sidedata for a previous rev has potentially been written after
2301 # Sidedata for a previous rev has potentially been written after
2302 # this rev's end, so take the max.
2302 # this rev's end, so take the max.
2303 offset = max(self.end(rev), offset, sidedata_end)
2303 offset = max(self.end(rev), offset, sidedata_end)
2304 return offset
2304 return offset
2305
2305
2306 def _writeentry(
2306 def _writeentry(
2307 self, transaction, ifh, dfh, entry, data, link, offset, sidedata
2307 self, transaction, ifh, dfh, entry, data, link, offset, sidedata
2308 ):
2308 ):
2309 # Files opened in a+ mode have inconsistent behavior on various
2309 # Files opened in a+ mode have inconsistent behavior on various
2310 # platforms. Windows requires that a file positioning call be made
2310 # platforms. Windows requires that a file positioning call be made
2311 # when the file handle transitions between reads and writes. See
2311 # when the file handle transitions between reads and writes. See
2312 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2312 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2313 # platforms, Python or the platform itself can be buggy. Some versions
2313 # platforms, Python or the platform itself can be buggy. Some versions
2314 # of Solaris have been observed to not append at the end of the file
2314 # of Solaris have been observed to not append at the end of the file
2315 # if the file was seeked to before the end. See issue4943 for more.
2315 # if the file was seeked to before the end. See issue4943 for more.
2316 #
2316 #
2317 # We work around this issue by inserting a seek() before writing.
2317 # We work around this issue by inserting a seek() before writing.
2318 # Note: This is likely not necessary on Python 3. However, because
2318 # Note: This is likely not necessary on Python 3. However, because
2319 # the file handle is reused for reads and may be seeked there, we need
2319 # the file handle is reused for reads and may be seeked there, we need
2320 # to be careful before changing this.
2320 # to be careful before changing this.
2321 ifh.seek(0, os.SEEK_END)
2321 ifh.seek(0, os.SEEK_END)
2322 if dfh:
2322 if dfh:
2323 dfh.seek(0, os.SEEK_END)
2323 dfh.seek(0, os.SEEK_END)
2324
2324
2325 curr = len(self) - 1
2325 curr = len(self) - 1
2326 if not self._inline:
2326 if not self._inline:
2327 transaction.add(self.datafile, offset)
2327 transaction.add(self.datafile, offset)
2328 transaction.add(self.indexfile, curr * len(entry))
2328 transaction.add(self.indexfile, curr * len(entry))
2329 if data[0]:
2329 if data[0]:
2330 dfh.write(data[0])
2330 dfh.write(data[0])
2331 dfh.write(data[1])
2331 dfh.write(data[1])
2332 if sidedata:
2332 if sidedata:
2333 dfh.write(sidedata)
2333 dfh.write(sidedata)
2334 ifh.write(entry)
2334 ifh.write(entry)
2335 else:
2335 else:
2336 offset += curr * self.index.entry_size
2336 offset += curr * self.index.entry_size
2337 transaction.add(self.indexfile, offset)
2337 transaction.add(self.indexfile, offset)
2338 ifh.write(entry)
2338 ifh.write(entry)
2339 ifh.write(data[0])
2339 ifh.write(data[0])
2340 ifh.write(data[1])
2340 ifh.write(data[1])
2341 if sidedata:
2341 if sidedata:
2342 ifh.write(sidedata)
2342 ifh.write(sidedata)
2343 self._enforceinlinesize(transaction, ifh)
2343 self._enforceinlinesize(transaction, ifh)
2344 nodemaputil.setup_persistent_nodemap(transaction, self)
2344 nodemaputil.setup_persistent_nodemap(transaction, self)
2345
2345
2346 def addgroup(
2346 def addgroup(
2347 self,
2347 self,
2348 deltas,
2348 deltas,
2349 linkmapper,
2349 linkmapper,
2350 transaction,
2350 transaction,
2351 alwayscache=False,
2351 alwayscache=False,
2352 addrevisioncb=None,
2352 addrevisioncb=None,
2353 duplicaterevisioncb=None,
2353 duplicaterevisioncb=None,
2354 ):
2354 ):
2355 """
2355 """
2356 add a delta group
2356 add a delta group
2357
2357
2358 given a set of deltas, add them to the revision log. the
2358 given a set of deltas, add them to the revision log. the
2359 first delta is against its parent, which should be in our
2359 first delta is against its parent, which should be in our
2360 log, the rest are against the previous delta.
2360 log, the rest are against the previous delta.
2361
2361
2362 If ``addrevisioncb`` is defined, it will be called with arguments of
2362 If ``addrevisioncb`` is defined, it will be called with arguments of
2363 this revlog and the node that was added.
2363 this revlog and the node that was added.
2364 """
2364 """
2365
2365
2366 if self._writinghandles:
2366 if self._writinghandles:
2367 raise error.ProgrammingError(b'cannot nest addgroup() calls')
2367 raise error.ProgrammingError(b'cannot nest addgroup() calls')
2368
2368
2369 r = len(self)
2369 r = len(self)
2370 end = 0
2370 end = 0
2371 if r:
2371 if r:
2372 end = self.end(r - 1)
2372 end = self.end(r - 1)
2373 ifh = self._indexfp(b"a+")
2373 ifh = self._indexfp(b"a+")
2374 isize = r * self.index.entry_size
2374 isize = r * self.index.entry_size
2375 if self._inline:
2375 if self._inline:
2376 transaction.add(self.indexfile, end + isize)
2376 transaction.add(self.indexfile, end + isize)
2377 dfh = None
2377 dfh = None
2378 else:
2378 else:
2379 transaction.add(self.indexfile, isize)
2379 transaction.add(self.indexfile, isize)
2380 transaction.add(self.datafile, end)
2380 transaction.add(self.datafile, end)
2381 dfh = self._datafp(b"a+")
2381 dfh = self._datafp(b"a+")
2382
2382
2383 def flush():
2383 def flush():
2384 if dfh:
2384 if dfh:
2385 dfh.flush()
2385 dfh.flush()
2386 ifh.flush()
2386 ifh.flush()
2387
2387
2388 self._writinghandles = (ifh, dfh)
2388 self._writinghandles = (ifh, dfh)
2389 empty = True
2389 empty = True
2390
2390
2391 try:
2391 try:
2392 deltacomputer = deltautil.deltacomputer(self)
2392 deltacomputer = deltautil.deltacomputer(self)
2393 # loop through our set of deltas
2393 # loop through our set of deltas
2394 for data in deltas:
2394 for data in deltas:
2395 node, p1, p2, linknode, deltabase, delta, flags, sidedata = data
2395 node, p1, p2, linknode, deltabase, delta, flags, sidedata = data
2396 link = linkmapper(linknode)
2396 link = linkmapper(linknode)
2397 flags = flags or REVIDX_DEFAULT_FLAGS
2397 flags = flags or REVIDX_DEFAULT_FLAGS
2398
2398
2399 rev = self.index.get_rev(node)
2399 rev = self.index.get_rev(node)
2400 if rev is not None:
2400 if rev is not None:
2401 # this can happen if two branches make the same change
2401 # this can happen if two branches make the same change
2402 self._nodeduplicatecallback(transaction, rev)
2402 self._nodeduplicatecallback(transaction, rev)
2403 if duplicaterevisioncb:
2403 if duplicaterevisioncb:
2404 duplicaterevisioncb(self, rev)
2404 duplicaterevisioncb(self, rev)
2405 empty = False
2405 empty = False
2406 continue
2406 continue
2407
2407
2408 for p in (p1, p2):
2408 for p in (p1, p2):
2409 if not self.index.has_node(p):
2409 if not self.index.has_node(p):
2410 raise error.LookupError(
2410 raise error.LookupError(
2411 p, self.indexfile, _(b'unknown parent')
2411 p, self.indexfile, _(b'unknown parent')
2412 )
2412 )
2413
2413
2414 if not self.index.has_node(deltabase):
2414 if not self.index.has_node(deltabase):
2415 raise error.LookupError(
2415 raise error.LookupError(
2416 deltabase, self.indexfile, _(b'unknown delta base')
2416 deltabase, self.indexfile, _(b'unknown delta base')
2417 )
2417 )
2418
2418
2419 baserev = self.rev(deltabase)
2419 baserev = self.rev(deltabase)
2420
2420
2421 if baserev != nullrev and self.iscensored(baserev):
2421 if baserev != nullrev and self.iscensored(baserev):
2422 # if base is censored, delta must be full replacement in a
2422 # if base is censored, delta must be full replacement in a
2423 # single patch operation
2423 # single patch operation
2424 hlen = struct.calcsize(b">lll")
2424 hlen = struct.calcsize(b">lll")
2425 oldlen = self.rawsize(baserev)
2425 oldlen = self.rawsize(baserev)
2426 newlen = len(delta) - hlen
2426 newlen = len(delta) - hlen
2427 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2427 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2428 raise error.CensoredBaseError(
2428 raise error.CensoredBaseError(
2429 self.indexfile, self.node(baserev)
2429 self.indexfile, self.node(baserev)
2430 )
2430 )
2431
2431
2432 if not flags and self._peek_iscensored(baserev, delta, flush):
2432 if not flags and self._peek_iscensored(baserev, delta, flush):
2433 flags |= REVIDX_ISCENSORED
2433 flags |= REVIDX_ISCENSORED
2434
2434
2435 # We assume consumers of addrevisioncb will want to retrieve
2435 # We assume consumers of addrevisioncb will want to retrieve
2436 # the added revision, which will require a call to
2436 # the added revision, which will require a call to
2437 # revision(). revision() will fast path if there is a cache
2437 # revision(). revision() will fast path if there is a cache
2438 # hit. So, we tell _addrevision() to always cache in this case.
2438 # hit. So, we tell _addrevision() to always cache in this case.
2439 # We're only using addgroup() in the context of changegroup
2439 # We're only using addgroup() in the context of changegroup
2440 # generation so the revision data can always be handled as raw
2440 # generation so the revision data can always be handled as raw
2441 # by the flagprocessor.
2441 # by the flagprocessor.
2442 rev = self._addrevision(
2442 rev = self._addrevision(
2443 node,
2443 node,
2444 None,
2444 None,
2445 transaction,
2445 transaction,
2446 link,
2446 link,
2447 p1,
2447 p1,
2448 p2,
2448 p2,
2449 flags,
2449 flags,
2450 (baserev, delta),
2450 (baserev, delta),
2451 ifh,
2451 ifh,
2452 dfh,
2452 dfh,
2453 alwayscache=alwayscache,
2453 alwayscache=alwayscache,
2454 deltacomputer=deltacomputer,
2454 deltacomputer=deltacomputer,
2455 sidedata=sidedata,
2455 sidedata=sidedata,
2456 )
2456 )
2457
2457
2458 if addrevisioncb:
2458 if addrevisioncb:
2459 addrevisioncb(self, rev)
2459 addrevisioncb(self, rev)
2460 empty = False
2460 empty = False
2461
2461
2462 if not dfh and not self._inline:
2462 if not dfh and not self._inline:
2463 # addrevision switched from inline to conventional
2463 # addrevision switched from inline to conventional
2464 # reopen the index
2464 # reopen the index
2465 ifh.close()
2465 ifh.close()
2466 dfh = self._datafp(b"a+")
2466 dfh = self._datafp(b"a+")
2467 ifh = self._indexfp(b"a+")
2467 ifh = self._indexfp(b"a+")
2468 self._writinghandles = (ifh, dfh)
2468 self._writinghandles = (ifh, dfh)
2469 finally:
2469 finally:
2470 self._writinghandles = None
2470 self._writinghandles = None
2471
2471
2472 if dfh:
2472 if dfh:
2473 dfh.close()
2473 dfh.close()
2474 ifh.close()
2474 ifh.close()
2475 return not empty
2475 return not empty
2476
2476
2477 def iscensored(self, rev):
2477 def iscensored(self, rev):
2478 """Check if a file revision is censored."""
2478 """Check if a file revision is censored."""
2479 if not self._censorable:
2479 if not self._censorable:
2480 return False
2480 return False
2481
2481
2482 return self.flags(rev) & REVIDX_ISCENSORED
2482 return self.flags(rev) & REVIDX_ISCENSORED
2483
2483
2484 def _peek_iscensored(self, baserev, delta, flush):
2484 def _peek_iscensored(self, baserev, delta, flush):
2485 """Quickly check if a delta produces a censored revision."""
2485 """Quickly check if a delta produces a censored revision."""
2486 if not self._censorable:
2486 if not self._censorable:
2487 return False
2487 return False
2488
2488
2489 return storageutil.deltaiscensored(delta, baserev, self.rawsize)
2489 return storageutil.deltaiscensored(delta, baserev, self.rawsize)
2490
2490
2491 def getstrippoint(self, minlink):
2491 def getstrippoint(self, minlink):
2492 """find the minimum rev that must be stripped to strip the linkrev
2492 """find the minimum rev that must be stripped to strip the linkrev
2493
2493
2494 Returns a tuple containing the minimum rev and a set of all revs that
2494 Returns a tuple containing the minimum rev and a set of all revs that
2495 have linkrevs that will be broken by this strip.
2495 have linkrevs that will be broken by this strip.
2496 """
2496 """
2497 return storageutil.resolvestripinfo(
2497 return storageutil.resolvestripinfo(
2498 minlink,
2498 minlink,
2499 len(self) - 1,
2499 len(self) - 1,
2500 self.headrevs(),
2500 self.headrevs(),
2501 self.linkrev,
2501 self.linkrev,
2502 self.parentrevs,
2502 self.parentrevs,
2503 )
2503 )
2504
2504
2505 def strip(self, minlink, transaction):
2505 def strip(self, minlink, transaction):
2506 """truncate the revlog on the first revision with a linkrev >= minlink
2506 """truncate the revlog on the first revision with a linkrev >= minlink
2507
2507
2508 This function is called when we're stripping revision minlink and
2508 This function is called when we're stripping revision minlink and
2509 its descendants from the repository.
2509 its descendants from the repository.
2510
2510
2511 We have to remove all revisions with linkrev >= minlink, because
2511 We have to remove all revisions with linkrev >= minlink, because
2512 the equivalent changelog revisions will be renumbered after the
2512 the equivalent changelog revisions will be renumbered after the
2513 strip.
2513 strip.
2514
2514
2515 So we truncate the revlog on the first of these revisions, and
2515 So we truncate the revlog on the first of these revisions, and
2516 trust that the caller has saved the revisions that shouldn't be
2516 trust that the caller has saved the revisions that shouldn't be
2517 removed and that it'll re-add them after this truncation.
2517 removed and that it'll re-add them after this truncation.
2518 """
2518 """
2519 if len(self) == 0:
2519 if len(self) == 0:
2520 return
2520 return
2521
2521
2522 rev, _ = self.getstrippoint(minlink)
2522 rev, _ = self.getstrippoint(minlink)
2523 if rev == len(self):
2523 if rev == len(self):
2524 return
2524 return
2525
2525
2526 # first truncate the files on disk
2526 # first truncate the files on disk
2527 end = self.start(rev)
2527 end = self.start(rev)
2528 if not self._inline:
2528 if not self._inline:
2529 transaction.add(self.datafile, end)
2529 transaction.add(self.datafile, end)
2530 end = rev * self.index.entry_size
2530 end = rev * self.index.entry_size
2531 else:
2531 else:
2532 end += rev * self.index.entry_size
2532 end += rev * self.index.entry_size
2533
2533
2534 transaction.add(self.indexfile, end)
2534 transaction.add(self.indexfile, end)
2535
2535
2536 # then reset internal state in memory to forget those revisions
2536 # then reset internal state in memory to forget those revisions
2537 self._revisioncache = None
2537 self._revisioncache = None
2538 self._chaininfocache = util.lrucachedict(500)
2538 self._chaininfocache = util.lrucachedict(500)
2539 self._chunkclear()
2539 self._chunkclear()
2540
2540
2541 del self.index[rev:-1]
2541 del self.index[rev:-1]
2542
2542
2543 def checksize(self):
2543 def checksize(self):
2544 """Check size of index and data files
2544 """Check size of index and data files
2545
2545
2546 return a (dd, di) tuple.
2546 return a (dd, di) tuple.
2547 - dd: extra bytes for the "data" file
2547 - dd: extra bytes for the "data" file
2548 - di: extra bytes for the "index" file
2548 - di: extra bytes for the "index" file
2549
2549
2550 A healthy revlog will return (0, 0).
2550 A healthy revlog will return (0, 0).
2551 """
2551 """
2552 expected = 0
2552 expected = 0
2553 if len(self):
2553 if len(self):
2554 expected = max(0, self.end(len(self) - 1))
2554 expected = max(0, self.end(len(self) - 1))
2555
2555
2556 try:
2556 try:
2557 with self._datafp() as f:
2557 with self._datafp() as f:
2558 f.seek(0, io.SEEK_END)
2558 f.seek(0, io.SEEK_END)
2559 actual = f.tell()
2559 actual = f.tell()
2560 dd = actual - expected
2560 dd = actual - expected
2561 except IOError as inst:
2561 except IOError as inst:
2562 if inst.errno != errno.ENOENT:
2562 if inst.errno != errno.ENOENT:
2563 raise
2563 raise
2564 dd = 0
2564 dd = 0
2565
2565
2566 try:
2566 try:
2567 f = self.opener(self.indexfile)
2567 f = self.opener(self.indexfile)
2568 f.seek(0, io.SEEK_END)
2568 f.seek(0, io.SEEK_END)
2569 actual = f.tell()
2569 actual = f.tell()
2570 f.close()
2570 f.close()
2571 s = self.index.entry_size
2571 s = self.index.entry_size
2572 i = max(0, actual // s)
2572 i = max(0, actual // s)
2573 di = actual - (i * s)
2573 di = actual - (i * s)
2574 if self._inline:
2574 if self._inline:
2575 databytes = 0
2575 databytes = 0
2576 for r in self:
2576 for r in self:
2577 databytes += max(0, self.length(r))
2577 databytes += max(0, self.length(r))
2578 dd = 0
2578 dd = 0
2579 di = actual - len(self) * s - databytes
2579 di = actual - len(self) * s - databytes
2580 except IOError as inst:
2580 except IOError as inst:
2581 if inst.errno != errno.ENOENT:
2581 if inst.errno != errno.ENOENT:
2582 raise
2582 raise
2583 di = 0
2583 di = 0
2584
2584
2585 return (dd, di)
2585 return (dd, di)
2586
2586
2587 def files(self):
2587 def files(self):
2588 res = [self.indexfile]
2588 res = [self.indexfile]
2589 if not self._inline:
2589 if not self._inline:
2590 res.append(self.datafile)
2590 res.append(self.datafile)
2591 return res
2591 return res
2592
2592
2593 def emitrevisions(
2593 def emitrevisions(
2594 self,
2594 self,
2595 nodes,
2595 nodes,
2596 nodesorder=None,
2596 nodesorder=None,
2597 revisiondata=False,
2597 revisiondata=False,
2598 assumehaveparentrevisions=False,
2598 assumehaveparentrevisions=False,
2599 deltamode=repository.CG_DELTAMODE_STD,
2599 deltamode=repository.CG_DELTAMODE_STD,
2600 sidedata_helpers=None,
2600 sidedata_helpers=None,
2601 ):
2601 ):
2602 if nodesorder not in (b'nodes', b'storage', b'linear', None):
2602 if nodesorder not in (b'nodes', b'storage', b'linear', None):
2603 raise error.ProgrammingError(
2603 raise error.ProgrammingError(
2604 b'unhandled value for nodesorder: %s' % nodesorder
2604 b'unhandled value for nodesorder: %s' % nodesorder
2605 )
2605 )
2606
2606
2607 if nodesorder is None and not self._generaldelta:
2607 if nodesorder is None and not self._generaldelta:
2608 nodesorder = b'storage'
2608 nodesorder = b'storage'
2609
2609
2610 if (
2610 if (
2611 not self._storedeltachains
2611 not self._storedeltachains
2612 and deltamode != repository.CG_DELTAMODE_PREV
2612 and deltamode != repository.CG_DELTAMODE_PREV
2613 ):
2613 ):
2614 deltamode = repository.CG_DELTAMODE_FULL
2614 deltamode = repository.CG_DELTAMODE_FULL
2615
2615
2616 return storageutil.emitrevisions(
2616 return storageutil.emitrevisions(
2617 self,
2617 self,
2618 nodes,
2618 nodes,
2619 nodesorder,
2619 nodesorder,
2620 revlogrevisiondelta,
2620 revlogrevisiondelta,
2621 deltaparentfn=self.deltaparent,
2621 deltaparentfn=self.deltaparent,
2622 candeltafn=self.candelta,
2622 candeltafn=self.candelta,
2623 rawsizefn=self.rawsize,
2623 rawsizefn=self.rawsize,
2624 revdifffn=self.revdiff,
2624 revdifffn=self.revdiff,
2625 flagsfn=self.flags,
2625 flagsfn=self.flags,
2626 deltamode=deltamode,
2626 deltamode=deltamode,
2627 revisiondata=revisiondata,
2627 revisiondata=revisiondata,
2628 assumehaveparentrevisions=assumehaveparentrevisions,
2628 assumehaveparentrevisions=assumehaveparentrevisions,
2629 sidedata_helpers=sidedata_helpers,
2629 sidedata_helpers=sidedata_helpers,
2630 )
2630 )
2631
2631
2632 DELTAREUSEALWAYS = b'always'
2632 DELTAREUSEALWAYS = b'always'
2633 DELTAREUSESAMEREVS = b'samerevs'
2633 DELTAREUSESAMEREVS = b'samerevs'
2634 DELTAREUSENEVER = b'never'
2634 DELTAREUSENEVER = b'never'
2635
2635
2636 DELTAREUSEFULLADD = b'fulladd'
2636 DELTAREUSEFULLADD = b'fulladd'
2637
2637
2638 DELTAREUSEALL = {b'always', b'samerevs', b'never', b'fulladd'}
2638 DELTAREUSEALL = {b'always', b'samerevs', b'never', b'fulladd'}
2639
2639
2640 def clone(
2640 def clone(
2641 self,
2641 self,
2642 tr,
2642 tr,
2643 destrevlog,
2643 destrevlog,
2644 addrevisioncb=None,
2644 addrevisioncb=None,
2645 deltareuse=DELTAREUSESAMEREVS,
2645 deltareuse=DELTAREUSESAMEREVS,
2646 forcedeltabothparents=None,
2646 forcedeltabothparents=None,
2647 sidedatacompanion=None,
2647 sidedatacompanion=None,
2648 ):
2648 ):
2649 """Copy this revlog to another, possibly with format changes.
2649 """Copy this revlog to another, possibly with format changes.
2650
2650
2651 The destination revlog will contain the same revisions and nodes.
2651 The destination revlog will contain the same revisions and nodes.
2652 However, it may not be bit-for-bit identical due to e.g. delta encoding
2652 However, it may not be bit-for-bit identical due to e.g. delta encoding
2653 differences.
2653 differences.
2654
2654
2655 The ``deltareuse`` argument control how deltas from the existing revlog
2655 The ``deltareuse`` argument control how deltas from the existing revlog
2656 are preserved in the destination revlog. The argument can have the
2656 are preserved in the destination revlog. The argument can have the
2657 following values:
2657 following values:
2658
2658
2659 DELTAREUSEALWAYS
2659 DELTAREUSEALWAYS
2660 Deltas will always be reused (if possible), even if the destination
2660 Deltas will always be reused (if possible), even if the destination
2661 revlog would not select the same revisions for the delta. This is the
2661 revlog would not select the same revisions for the delta. This is the
2662 fastest mode of operation.
2662 fastest mode of operation.
2663 DELTAREUSESAMEREVS
2663 DELTAREUSESAMEREVS
2664 Deltas will be reused if the destination revlog would pick the same
2664 Deltas will be reused if the destination revlog would pick the same
2665 revisions for the delta. This mode strikes a balance between speed
2665 revisions for the delta. This mode strikes a balance between speed
2666 and optimization.
2666 and optimization.
2667 DELTAREUSENEVER
2667 DELTAREUSENEVER
2668 Deltas will never be reused. This is the slowest mode of execution.
2668 Deltas will never be reused. This is the slowest mode of execution.
2669 This mode can be used to recompute deltas (e.g. if the diff/delta
2669 This mode can be used to recompute deltas (e.g. if the diff/delta
2670 algorithm changes).
2670 algorithm changes).
2671 DELTAREUSEFULLADD
2671 DELTAREUSEFULLADD
2672 Revision will be re-added as if their were new content. This is
2672 Revision will be re-added as if their were new content. This is
2673 slower than DELTAREUSEALWAYS but allow more mechanism to kicks in.
2673 slower than DELTAREUSEALWAYS but allow more mechanism to kicks in.
2674 eg: large file detection and handling.
2674 eg: large file detection and handling.
2675
2675
2676 Delta computation can be slow, so the choice of delta reuse policy can
2676 Delta computation can be slow, so the choice of delta reuse policy can
2677 significantly affect run time.
2677 significantly affect run time.
2678
2678
2679 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2679 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2680 two extremes. Deltas will be reused if they are appropriate. But if the
2680 two extremes. Deltas will be reused if they are appropriate. But if the
2681 delta could choose a better revision, it will do so. This means if you
2681 delta could choose a better revision, it will do so. This means if you
2682 are converting a non-generaldelta revlog to a generaldelta revlog,
2682 are converting a non-generaldelta revlog to a generaldelta revlog,
2683 deltas will be recomputed if the delta's parent isn't a parent of the
2683 deltas will be recomputed if the delta's parent isn't a parent of the
2684 revision.
2684 revision.
2685
2685
2686 In addition to the delta policy, the ``forcedeltabothparents``
2686 In addition to the delta policy, the ``forcedeltabothparents``
2687 argument controls whether to force compute deltas against both parents
2687 argument controls whether to force compute deltas against both parents
2688 for merges. By default, the current default is used.
2688 for merges. By default, the current default is used.
2689
2689
2690 If not None, the `sidedatacompanion` is callable that accept two
2690 If not None, the `sidedatacompanion` is callable that accept two
2691 arguments:
2691 arguments:
2692
2692
2693 (srcrevlog, rev)
2693 (srcrevlog, rev)
2694
2694
2695 and return a quintet that control changes to sidedata content from the
2695 and return a quintet that control changes to sidedata content from the
2696 old revision to the new clone result:
2696 old revision to the new clone result:
2697
2697
2698 (dropall, filterout, update, new_flags, dropped_flags)
2698 (dropall, filterout, update, new_flags, dropped_flags)
2699
2699
2700 * if `dropall` is True, all sidedata should be dropped
2700 * if `dropall` is True, all sidedata should be dropped
2701 * `filterout` is a set of sidedata keys that should be dropped
2701 * `filterout` is a set of sidedata keys that should be dropped
2702 * `update` is a mapping of additionnal/new key -> value
2702 * `update` is a mapping of additionnal/new key -> value
2703 * new_flags is a bitfields of new flags that the revision should get
2703 * new_flags is a bitfields of new flags that the revision should get
2704 * dropped_flags is a bitfields of new flags that the revision shoudl not longer have
2704 * dropped_flags is a bitfields of new flags that the revision shoudl not longer have
2705 """
2705 """
2706 if deltareuse not in self.DELTAREUSEALL:
2706 if deltareuse not in self.DELTAREUSEALL:
2707 raise ValueError(
2707 raise ValueError(
2708 _(b'value for deltareuse invalid: %s') % deltareuse
2708 _(b'value for deltareuse invalid: %s') % deltareuse
2709 )
2709 )
2710
2710
2711 if len(destrevlog):
2711 if len(destrevlog):
2712 raise ValueError(_(b'destination revlog is not empty'))
2712 raise ValueError(_(b'destination revlog is not empty'))
2713
2713
2714 if getattr(self, 'filteredrevs', None):
2714 if getattr(self, 'filteredrevs', None):
2715 raise ValueError(_(b'source revlog has filtered revisions'))
2715 raise ValueError(_(b'source revlog has filtered revisions'))
2716 if getattr(destrevlog, 'filteredrevs', None):
2716 if getattr(destrevlog, 'filteredrevs', None):
2717 raise ValueError(_(b'destination revlog has filtered revisions'))
2717 raise ValueError(_(b'destination revlog has filtered revisions'))
2718
2718
2719 # lazydelta and lazydeltabase controls whether to reuse a cached delta,
2719 # lazydelta and lazydeltabase controls whether to reuse a cached delta,
2720 # if possible.
2720 # if possible.
2721 oldlazydelta = destrevlog._lazydelta
2721 oldlazydelta = destrevlog._lazydelta
2722 oldlazydeltabase = destrevlog._lazydeltabase
2722 oldlazydeltabase = destrevlog._lazydeltabase
2723 oldamd = destrevlog._deltabothparents
2723 oldamd = destrevlog._deltabothparents
2724
2724
2725 try:
2725 try:
2726 if deltareuse == self.DELTAREUSEALWAYS:
2726 if deltareuse == self.DELTAREUSEALWAYS:
2727 destrevlog._lazydeltabase = True
2727 destrevlog._lazydeltabase = True
2728 destrevlog._lazydelta = True
2728 destrevlog._lazydelta = True
2729 elif deltareuse == self.DELTAREUSESAMEREVS:
2729 elif deltareuse == self.DELTAREUSESAMEREVS:
2730 destrevlog._lazydeltabase = False
2730 destrevlog._lazydeltabase = False
2731 destrevlog._lazydelta = True
2731 destrevlog._lazydelta = True
2732 elif deltareuse == self.DELTAREUSENEVER:
2732 elif deltareuse == self.DELTAREUSENEVER:
2733 destrevlog._lazydeltabase = False
2733 destrevlog._lazydeltabase = False
2734 destrevlog._lazydelta = False
2734 destrevlog._lazydelta = False
2735
2735
2736 destrevlog._deltabothparents = forcedeltabothparents or oldamd
2736 destrevlog._deltabothparents = forcedeltabothparents or oldamd
2737
2737
2738 self._clone(
2738 self._clone(
2739 tr,
2739 tr,
2740 destrevlog,
2740 destrevlog,
2741 addrevisioncb,
2741 addrevisioncb,
2742 deltareuse,
2742 deltareuse,
2743 forcedeltabothparents,
2743 forcedeltabothparents,
2744 sidedatacompanion,
2744 sidedatacompanion,
2745 )
2745 )
2746
2746
2747 finally:
2747 finally:
2748 destrevlog._lazydelta = oldlazydelta
2748 destrevlog._lazydelta = oldlazydelta
2749 destrevlog._lazydeltabase = oldlazydeltabase
2749 destrevlog._lazydeltabase = oldlazydeltabase
2750 destrevlog._deltabothparents = oldamd
2750 destrevlog._deltabothparents = oldamd
2751
2751
2752 def _clone(
2752 def _clone(
2753 self,
2753 self,
2754 tr,
2754 tr,
2755 destrevlog,
2755 destrevlog,
2756 addrevisioncb,
2756 addrevisioncb,
2757 deltareuse,
2757 deltareuse,
2758 forcedeltabothparents,
2758 forcedeltabothparents,
2759 sidedatacompanion,
2759 sidedatacompanion,
2760 ):
2760 ):
2761 """perform the core duty of `revlog.clone` after parameter processing"""
2761 """perform the core duty of `revlog.clone` after parameter processing"""
2762 deltacomputer = deltautil.deltacomputer(destrevlog)
2762 deltacomputer = deltautil.deltacomputer(destrevlog)
2763 index = self.index
2763 index = self.index
2764 for rev in self:
2764 for rev in self:
2765 entry = index[rev]
2765 entry = index[rev]
2766
2766
2767 # Some classes override linkrev to take filtered revs into
2767 # Some classes override linkrev to take filtered revs into
2768 # account. Use raw entry from index.
2768 # account. Use raw entry from index.
2769 flags = entry[0] & 0xFFFF
2769 flags = entry[0] & 0xFFFF
2770 linkrev = entry[4]
2770 linkrev = entry[4]
2771 p1 = index[entry[5]][7]
2771 p1 = index[entry[5]][7]
2772 p2 = index[entry[6]][7]
2772 p2 = index[entry[6]][7]
2773 node = entry[7]
2773 node = entry[7]
2774
2774
2775 sidedataactions = (False, [], {}, 0, 0)
2775 sidedataactions = (False, [], {}, 0, 0)
2776 if sidedatacompanion is not None:
2776 if sidedatacompanion is not None:
2777 sidedataactions = sidedatacompanion(self, rev)
2777 sidedataactions = sidedatacompanion(self, rev)
2778
2778
2779 # (Possibly) reuse the delta from the revlog if allowed and
2779 # (Possibly) reuse the delta from the revlog if allowed and
2780 # the revlog chunk is a delta.
2780 # the revlog chunk is a delta.
2781 cachedelta = None
2781 cachedelta = None
2782 rawtext = None
2782 rawtext = None
2783 if any(sidedataactions) or deltareuse == self.DELTAREUSEFULLADD:
2783 if any(sidedataactions) or deltareuse == self.DELTAREUSEFULLADD:
2784 dropall = sidedataactions[0]
2784 dropall = sidedataactions[0]
2785 filterout = sidedataactions[1]
2785 filterout = sidedataactions[1]
2786 update = sidedataactions[2]
2786 update = sidedataactions[2]
2787 new_flags = sidedataactions[3]
2787 new_flags = sidedataactions[3]
2788 dropped_flags = sidedataactions[4]
2788 dropped_flags = sidedataactions[4]
2789 text, sidedata = self._revisiondata(rev)
2789 text, sidedata = self._revisiondata(rev)
2790 if dropall:
2790 if dropall:
2791 sidedata = {}
2791 sidedata = {}
2792 for key in filterout:
2792 for key in filterout:
2793 sidedata.pop(key, None)
2793 sidedata.pop(key, None)
2794 sidedata.update(update)
2794 sidedata.update(update)
2795 if not sidedata:
2795 if not sidedata:
2796 sidedata = None
2796 sidedata = None
2797
2797
2798 flags |= new_flags
2798 flags |= new_flags
2799 flags &= ~dropped_flags
2799 flags &= ~dropped_flags
2800
2800
2801 destrevlog.addrevision(
2801 destrevlog.addrevision(
2802 text,
2802 text,
2803 tr,
2803 tr,
2804 linkrev,
2804 linkrev,
2805 p1,
2805 p1,
2806 p2,
2806 p2,
2807 cachedelta=cachedelta,
2807 cachedelta=cachedelta,
2808 node=node,
2808 node=node,
2809 flags=flags,
2809 flags=flags,
2810 deltacomputer=deltacomputer,
2810 deltacomputer=deltacomputer,
2811 sidedata=sidedata,
2811 sidedata=sidedata,
2812 )
2812 )
2813 else:
2813 else:
2814 if destrevlog._lazydelta:
2814 if destrevlog._lazydelta:
2815 dp = self.deltaparent(rev)
2815 dp = self.deltaparent(rev)
2816 if dp != nullrev:
2816 if dp != nullrev:
2817 cachedelta = (dp, bytes(self._chunk(rev)))
2817 cachedelta = (dp, bytes(self._chunk(rev)))
2818
2818
2819 if not cachedelta:
2819 if not cachedelta:
2820 rawtext = self.rawdata(rev)
2820 rawtext = self.rawdata(rev)
2821
2821
2822 ifh = destrevlog.opener(
2822 ifh = destrevlog.opener(
2823 destrevlog.indexfile, b'a+', checkambig=False
2823 destrevlog.indexfile, b'a+', checkambig=False
2824 )
2824 )
2825 dfh = None
2825 dfh = None
2826 if not destrevlog._inline:
2826 if not destrevlog._inline:
2827 dfh = destrevlog.opener(destrevlog.datafile, b'a+')
2827 dfh = destrevlog.opener(destrevlog.datafile, b'a+')
2828 try:
2828 try:
2829 destrevlog._addrevision(
2829 destrevlog._addrevision(
2830 node,
2830 node,
2831 rawtext,
2831 rawtext,
2832 tr,
2832 tr,
2833 linkrev,
2833 linkrev,
2834 p1,
2834 p1,
2835 p2,
2835 p2,
2836 flags,
2836 flags,
2837 cachedelta,
2837 cachedelta,
2838 ifh,
2838 ifh,
2839 dfh,
2839 dfh,
2840 deltacomputer=deltacomputer,
2840 deltacomputer=deltacomputer,
2841 )
2841 )
2842 finally:
2842 finally:
2843 if dfh:
2843 if dfh:
2844 dfh.close()
2844 dfh.close()
2845 ifh.close()
2845 ifh.close()
2846
2846
2847 if addrevisioncb:
2847 if addrevisioncb:
2848 addrevisioncb(self, rev, node)
2848 addrevisioncb(self, rev, node)
2849
2849
2850 def censorrevision(self, tr, censornode, tombstone=b''):
2850 def censorrevision(self, tr, censornode, tombstone=b''):
2851 if (self.version & 0xFFFF) == REVLOGV0:
2851 if (self.version & 0xFFFF) == REVLOGV0:
2852 raise error.RevlogError(
2852 raise error.RevlogError(
2853 _(b'cannot censor with version %d revlogs') % self.version
2853 _(b'cannot censor with version %d revlogs') % self.version
2854 )
2854 )
2855
2855
2856 censorrev = self.rev(censornode)
2856 censorrev = self.rev(censornode)
2857 tombstone = storageutil.packmeta({b'censored': tombstone}, b'')
2857 tombstone = storageutil.packmeta({b'censored': tombstone}, b'')
2858
2858
2859 if len(tombstone) > self.rawsize(censorrev):
2859 if len(tombstone) > self.rawsize(censorrev):
2860 raise error.Abort(
2860 raise error.Abort(
2861 _(b'censor tombstone must be no longer than censored data')
2861 _(b'censor tombstone must be no longer than censored data')
2862 )
2862 )
2863
2863
2864 # Rewriting the revlog in place is hard. Our strategy for censoring is
2864 # Rewriting the revlog in place is hard. Our strategy for censoring is
2865 # to create a new revlog, copy all revisions to it, then replace the
2865 # to create a new revlog, copy all revisions to it, then replace the
2866 # revlogs on transaction close.
2866 # revlogs on transaction close.
2867
2867
2868 newindexfile = self.indexfile + b'.tmpcensored'
2868 newindexfile = self.indexfile + b'.tmpcensored'
2869 newdatafile = self.datafile + b'.tmpcensored'
2869 newdatafile = self.datafile + b'.tmpcensored'
2870
2870
2871 # This is a bit dangerous. We could easily have a mismatch of state.
2871 # This is a bit dangerous. We could easily have a mismatch of state.
2872 newrl = revlog(self.opener, newindexfile, newdatafile, censorable=True)
2872 newrl = revlog(self.opener, newindexfile, newdatafile, censorable=True)
2873 newrl.version = self.version
2873 newrl.version = self.version
2874 newrl._generaldelta = self._generaldelta
2874 newrl._generaldelta = self._generaldelta
2875 newrl._parse_index = self._parse_index
2875 newrl._parse_index = self._parse_index
2876
2876
2877 for rev in self.revs():
2877 for rev in self.revs():
2878 node = self.node(rev)
2878 node = self.node(rev)
2879 p1, p2 = self.parents(node)
2879 p1, p2 = self.parents(node)
2880
2880
2881 if rev == censorrev:
2881 if rev == censorrev:
2882 newrl.addrawrevision(
2882 newrl.addrawrevision(
2883 tombstone,
2883 tombstone,
2884 tr,
2884 tr,
2885 self.linkrev(censorrev),
2885 self.linkrev(censorrev),
2886 p1,
2886 p1,
2887 p2,
2887 p2,
2888 censornode,
2888 censornode,
2889 REVIDX_ISCENSORED,
2889 REVIDX_ISCENSORED,
2890 )
2890 )
2891
2891
2892 if newrl.deltaparent(rev) != nullrev:
2892 if newrl.deltaparent(rev) != nullrev:
2893 raise error.Abort(
2893 raise error.Abort(
2894 _(
2894 _(
2895 b'censored revision stored as delta; '
2895 b'censored revision stored as delta; '
2896 b'cannot censor'
2896 b'cannot censor'
2897 ),
2897 ),
2898 hint=_(
2898 hint=_(
2899 b'censoring of revlogs is not '
2899 b'censoring of revlogs is not '
2900 b'fully implemented; please report '
2900 b'fully implemented; please report '
2901 b'this bug'
2901 b'this bug'
2902 ),
2902 ),
2903 )
2903 )
2904 continue
2904 continue
2905
2905
2906 if self.iscensored(rev):
2906 if self.iscensored(rev):
2907 if self.deltaparent(rev) != nullrev:
2907 if self.deltaparent(rev) != nullrev:
2908 raise error.Abort(
2908 raise error.Abort(
2909 _(
2909 _(
2910 b'cannot censor due to censored '
2910 b'cannot censor due to censored '
2911 b'revision having delta stored'
2911 b'revision having delta stored'
2912 )
2912 )
2913 )
2913 )
2914 rawtext = self._chunk(rev)
2914 rawtext = self._chunk(rev)
2915 else:
2915 else:
2916 rawtext = self.rawdata(rev)
2916 rawtext = self.rawdata(rev)
2917
2917
2918 newrl.addrawrevision(
2918 newrl.addrawrevision(
2919 rawtext, tr, self.linkrev(rev), p1, p2, node, self.flags(rev)
2919 rawtext, tr, self.linkrev(rev), p1, p2, node, self.flags(rev)
2920 )
2920 )
2921
2921
2922 tr.addbackup(self.indexfile, location=b'store')
2922 tr.addbackup(self.indexfile, location=b'store')
2923 if not self._inline:
2923 if not self._inline:
2924 tr.addbackup(self.datafile, location=b'store')
2924 tr.addbackup(self.datafile, location=b'store')
2925
2925
2926 self.opener.rename(newrl.indexfile, self.indexfile)
2926 self.opener.rename(newrl.indexfile, self.indexfile)
2927 if not self._inline:
2927 if not self._inline:
2928 self.opener.rename(newrl.datafile, self.datafile)
2928 self.opener.rename(newrl.datafile, self.datafile)
2929
2929
2930 self.clearcaches()
2930 self.clearcaches()
2931 self._loadindex()
2931 self._loadindex()
2932
2932
2933 def verifyintegrity(self, state):
2933 def verifyintegrity(self, state):
2934 """Verifies the integrity of the revlog.
2934 """Verifies the integrity of the revlog.
2935
2935
2936 Yields ``revlogproblem`` instances describing problems that are
2936 Yields ``revlogproblem`` instances describing problems that are
2937 found.
2937 found.
2938 """
2938 """
2939 dd, di = self.checksize()
2939 dd, di = self.checksize()
2940 if dd:
2940 if dd:
2941 yield revlogproblem(error=_(b'data length off by %d bytes') % dd)
2941 yield revlogproblem(error=_(b'data length off by %d bytes') % dd)
2942 if di:
2942 if di:
2943 yield revlogproblem(error=_(b'index contains %d extra bytes') % di)
2943 yield revlogproblem(error=_(b'index contains %d extra bytes') % di)
2944
2944
2945 version = self.version & 0xFFFF
2945 version = self.version & 0xFFFF
2946
2946
2947 # The verifier tells us what version revlog we should be.
2947 # The verifier tells us what version revlog we should be.
2948 if version != state[b'expectedversion']:
2948 if version != state[b'expectedversion']:
2949 yield revlogproblem(
2949 yield revlogproblem(
2950 warning=_(b"warning: '%s' uses revlog format %d; expected %d")
2950 warning=_(b"warning: '%s' uses revlog format %d; expected %d")
2951 % (self.indexfile, version, state[b'expectedversion'])
2951 % (self.indexfile, version, state[b'expectedversion'])
2952 )
2952 )
2953
2953
2954 state[b'skipread'] = set()
2954 state[b'skipread'] = set()
2955 state[b'safe_renamed'] = set()
2955 state[b'safe_renamed'] = set()
2956
2956
2957 for rev in self:
2957 for rev in self:
2958 node = self.node(rev)
2958 node = self.node(rev)
2959
2959
2960 # Verify contents. 4 cases to care about:
2960 # Verify contents. 4 cases to care about:
2961 #
2961 #
2962 # common: the most common case
2962 # common: the most common case
2963 # rename: with a rename
2963 # rename: with a rename
2964 # meta: file content starts with b'\1\n', the metadata
2964 # meta: file content starts with b'\1\n', the metadata
2965 # header defined in filelog.py, but without a rename
2965 # header defined in filelog.py, but without a rename
2966 # ext: content stored externally
2966 # ext: content stored externally
2967 #
2967 #
2968 # More formally, their differences are shown below:
2968 # More formally, their differences are shown below:
2969 #
2969 #
2970 # | common | rename | meta | ext
2970 # | common | rename | meta | ext
2971 # -------------------------------------------------------
2971 # -------------------------------------------------------
2972 # flags() | 0 | 0 | 0 | not 0
2972 # flags() | 0 | 0 | 0 | not 0
2973 # renamed() | False | True | False | ?
2973 # renamed() | False | True | False | ?
2974 # rawtext[0:2]=='\1\n'| False | True | True | ?
2974 # rawtext[0:2]=='\1\n'| False | True | True | ?
2975 #
2975 #
2976 # "rawtext" means the raw text stored in revlog data, which
2976 # "rawtext" means the raw text stored in revlog data, which
2977 # could be retrieved by "rawdata(rev)". "text"
2977 # could be retrieved by "rawdata(rev)". "text"
2978 # mentioned below is "revision(rev)".
2978 # mentioned below is "revision(rev)".
2979 #
2979 #
2980 # There are 3 different lengths stored physically:
2980 # There are 3 different lengths stored physically:
2981 # 1. L1: rawsize, stored in revlog index
2981 # 1. L1: rawsize, stored in revlog index
2982 # 2. L2: len(rawtext), stored in revlog data
2982 # 2. L2: len(rawtext), stored in revlog data
2983 # 3. L3: len(text), stored in revlog data if flags==0, or
2983 # 3. L3: len(text), stored in revlog data if flags==0, or
2984 # possibly somewhere else if flags!=0
2984 # possibly somewhere else if flags!=0
2985 #
2985 #
2986 # L1 should be equal to L2. L3 could be different from them.
2986 # L1 should be equal to L2. L3 could be different from them.
2987 # "text" may or may not affect commit hash depending on flag
2987 # "text" may or may not affect commit hash depending on flag
2988 # processors (see flagutil.addflagprocessor).
2988 # processors (see flagutil.addflagprocessor).
2989 #
2989 #
2990 # | common | rename | meta | ext
2990 # | common | rename | meta | ext
2991 # -------------------------------------------------
2991 # -------------------------------------------------
2992 # rawsize() | L1 | L1 | L1 | L1
2992 # rawsize() | L1 | L1 | L1 | L1
2993 # size() | L1 | L2-LM | L1(*) | L1 (?)
2993 # size() | L1 | L2-LM | L1(*) | L1 (?)
2994 # len(rawtext) | L2 | L2 | L2 | L2
2994 # len(rawtext) | L2 | L2 | L2 | L2
2995 # len(text) | L2 | L2 | L2 | L3
2995 # len(text) | L2 | L2 | L2 | L3
2996 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
2996 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
2997 #
2997 #
2998 # LM: length of metadata, depending on rawtext
2998 # LM: length of metadata, depending on rawtext
2999 # (*): not ideal, see comment in filelog.size
2999 # (*): not ideal, see comment in filelog.size
3000 # (?): could be "- len(meta)" if the resolved content has
3000 # (?): could be "- len(meta)" if the resolved content has
3001 # rename metadata
3001 # rename metadata
3002 #
3002 #
3003 # Checks needed to be done:
3003 # Checks needed to be done:
3004 # 1. length check: L1 == L2, in all cases.
3004 # 1. length check: L1 == L2, in all cases.
3005 # 2. hash check: depending on flag processor, we may need to
3005 # 2. hash check: depending on flag processor, we may need to
3006 # use either "text" (external), or "rawtext" (in revlog).
3006 # use either "text" (external), or "rawtext" (in revlog).
3007
3007
3008 try:
3008 try:
3009 skipflags = state.get(b'skipflags', 0)
3009 skipflags = state.get(b'skipflags', 0)
3010 if skipflags:
3010 if skipflags:
3011 skipflags &= self.flags(rev)
3011 skipflags &= self.flags(rev)
3012
3012
3013 _verify_revision(self, skipflags, state, node)
3013 _verify_revision(self, skipflags, state, node)
3014
3014
3015 l1 = self.rawsize(rev)
3015 l1 = self.rawsize(rev)
3016 l2 = len(self.rawdata(node))
3016 l2 = len(self.rawdata(node))
3017
3017
3018 if l1 != l2:
3018 if l1 != l2:
3019 yield revlogproblem(
3019 yield revlogproblem(
3020 error=_(b'unpacked size is %d, %d expected') % (l2, l1),
3020 error=_(b'unpacked size is %d, %d expected') % (l2, l1),
3021 node=node,
3021 node=node,
3022 )
3022 )
3023
3023
3024 except error.CensoredNodeError:
3024 except error.CensoredNodeError:
3025 if state[b'erroroncensored']:
3025 if state[b'erroroncensored']:
3026 yield revlogproblem(
3026 yield revlogproblem(
3027 error=_(b'censored file data'), node=node
3027 error=_(b'censored file data'), node=node
3028 )
3028 )
3029 state[b'skipread'].add(node)
3029 state[b'skipread'].add(node)
3030 except Exception as e:
3030 except Exception as e:
3031 yield revlogproblem(
3031 yield revlogproblem(
3032 error=_(b'unpacking %s: %s')
3032 error=_(b'unpacking %s: %s')
3033 % (short(node), stringutil.forcebytestr(e)),
3033 % (short(node), stringutil.forcebytestr(e)),
3034 node=node,
3034 node=node,
3035 )
3035 )
3036 state[b'skipread'].add(node)
3036 state[b'skipread'].add(node)
3037
3037
3038 def storageinfo(
3038 def storageinfo(
3039 self,
3039 self,
3040 exclusivefiles=False,
3040 exclusivefiles=False,
3041 sharedfiles=False,
3041 sharedfiles=False,
3042 revisionscount=False,
3042 revisionscount=False,
3043 trackedsize=False,
3043 trackedsize=False,
3044 storedsize=False,
3044 storedsize=False,
3045 ):
3045 ):
3046 d = {}
3046 d = {}
3047
3047
3048 if exclusivefiles:
3048 if exclusivefiles:
3049 d[b'exclusivefiles'] = [(self.opener, self.indexfile)]
3049 d[b'exclusivefiles'] = [(self.opener, self.indexfile)]
3050 if not self._inline:
3050 if not self._inline:
3051 d[b'exclusivefiles'].append((self.opener, self.datafile))
3051 d[b'exclusivefiles'].append((self.opener, self.datafile))
3052
3052
3053 if sharedfiles:
3053 if sharedfiles:
3054 d[b'sharedfiles'] = []
3054 d[b'sharedfiles'] = []
3055
3055
3056 if revisionscount:
3056 if revisionscount:
3057 d[b'revisionscount'] = len(self)
3057 d[b'revisionscount'] = len(self)
3058
3058
3059 if trackedsize:
3059 if trackedsize:
3060 d[b'trackedsize'] = sum(map(self.rawsize, iter(self)))
3060 d[b'trackedsize'] = sum(map(self.rawsize, iter(self)))
3061
3061
3062 if storedsize:
3062 if storedsize:
3063 d[b'storedsize'] = sum(
3063 d[b'storedsize'] = sum(
3064 self.opener.stat(path).st_size for path in self.files()
3064 self.opener.stat(path).st_size for path in self.files()
3065 )
3065 )
3066
3066
3067 return d
3067 return d
3068
3068
3069 def rewrite_sidedata(self, helpers, startrev, endrev):
3069 def rewrite_sidedata(self, helpers, startrev, endrev):
3070 if self.version & 0xFFFF != REVLOGV2:
3070 if self.version & 0xFFFF != REVLOGV2:
3071 return
3071 return
3072 # inline are not yet supported because they suffer from an issue when
3072 # inline are not yet supported because they suffer from an issue when
3073 # rewriting them (since it's not an append-only operation).
3073 # rewriting them (since it's not an append-only operation).
3074 # See issue6485.
3074 # See issue6485.
3075 assert not self._inline
3075 assert not self._inline
3076 if not helpers[1] and not helpers[2]:
3076 if not helpers[1] and not helpers[2]:
3077 # Nothing to generate or remove
3077 # Nothing to generate or remove
3078 return
3078 return
3079
3079
3080 new_entries = []
3080 new_entries = []
3081 # append the new sidedata
3081 # append the new sidedata
3082 with self._datafp(b'a+') as fp:
3082 with self._datafp(b'a+') as fp:
3083 # Maybe this bug still exists, see revlog._writeentry
3083 # Maybe this bug still exists, see revlog._writeentry
3084 fp.seek(0, os.SEEK_END)
3084 fp.seek(0, os.SEEK_END)
3085 current_offset = fp.tell()
3085 current_offset = fp.tell()
3086 for rev in range(startrev, endrev + 1):
3086 for rev in range(startrev, endrev + 1):
3087 entry = self.index[rev]
3087 entry = self.index[rev]
3088 new_sidedata = storageutil.run_sidedata_helpers(
3088 new_sidedata = storageutil.run_sidedata_helpers(
3089 store=self,
3089 store=self,
3090 sidedata_helpers=helpers,
3090 sidedata_helpers=helpers,
3091 sidedata={},
3091 sidedata={},
3092 rev=rev,
3092 rev=rev,
3093 )
3093 )
3094
3094
3095 serialized_sidedata = sidedatautil.serialize_sidedata(
3095 serialized_sidedata = sidedatautil.serialize_sidedata(
3096 new_sidedata
3096 new_sidedata
3097 )
3097 )
3098 if entry[8] != 0 or entry[9] != 0:
3098 if entry[8] != 0 or entry[9] != 0:
3099 # rewriting entries that already have sidedata is not
3099 # rewriting entries that already have sidedata is not
3100 # supported yet, because it introduces garbage data in the
3100 # supported yet, because it introduces garbage data in the
3101 # revlog.
3101 # revlog.
3102 msg = b"Rewriting existing sidedata is not supported yet"
3102 msg = b"Rewriting existing sidedata is not supported yet"
3103 raise error.Abort(msg)
3103 raise error.Abort(msg)
3104 entry = entry[:8]
3104 entry = entry[:8]
3105 entry += (current_offset, len(serialized_sidedata))
3105 entry += (current_offset, len(serialized_sidedata))
3106
3106
3107 fp.write(serialized_sidedata)
3107 fp.write(serialized_sidedata)
3108 new_entries.append(entry)
3108 new_entries.append(entry)
3109 current_offset += len(serialized_sidedata)
3109 current_offset += len(serialized_sidedata)
3110
3110
3111 # rewrite the new index entries
3111 # rewrite the new index entries
3112 with self._indexfp(b'w+') as fp:
3112 with self._indexfp(b'w+') as fp:
3113 fp.seek(startrev * self.index.entry_size)
3113 fp.seek(startrev * self.index.entry_size)
3114 for i, entry in enumerate(new_entries):
3114 for i, entry in enumerate(new_entries):
3115 rev = startrev + i
3115 rev = startrev + i
3116 self.index.replace_sidedata_info(rev, entry[8], entry[9])
3116 self.index.replace_sidedata_info(rev, entry[8], entry[9])
3117 packed = self.index.entry_binary(rev)
3117 packed = self.index.entry_binary(rev)
3118 if rev == 0:
3118 if rev == 0:
3119 header = self.index.pack_header(self.version)
3119 header = self.index.pack_header(self.version)
3120 packed = header + packed
3120 packed = header + packed
3121 fp.write(packed)
3121 fp.write(packed)
General Comments 0
You need to be logged in to leave comments. Login now