##// END OF EJS Templates
revlog: explicitly set revlogv0 in vfs options...
marmoute -
r43294:188476e4 default
parent child Browse files
Show More
@@ -1,3316 +1,3318 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 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@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 hashlib
11 import hashlib
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 nullid,
22 nullid,
23 nullrev,
23 nullrev,
24 short,
24 short,
25 )
25 )
26 from . import (
26 from . import (
27 bookmarks,
27 bookmarks,
28 branchmap,
28 branchmap,
29 bundle2,
29 bundle2,
30 changegroup,
30 changegroup,
31 color,
31 color,
32 context,
32 context,
33 dirstate,
33 dirstate,
34 dirstateguard,
34 dirstateguard,
35 discovery,
35 discovery,
36 encoding,
36 encoding,
37 error,
37 error,
38 exchange,
38 exchange,
39 extensions,
39 extensions,
40 filelog,
40 filelog,
41 hook,
41 hook,
42 lock as lockmod,
42 lock as lockmod,
43 match as matchmod,
43 match as matchmod,
44 merge as mergemod,
44 merge as mergemod,
45 mergeutil,
45 mergeutil,
46 namespaces,
46 namespaces,
47 narrowspec,
47 narrowspec,
48 obsolete,
48 obsolete,
49 pathutil,
49 pathutil,
50 phases,
50 phases,
51 pushkey,
51 pushkey,
52 pycompat,
52 pycompat,
53 repoview,
53 repoview,
54 revset,
54 revset,
55 revsetlang,
55 revsetlang,
56 scmutil,
56 scmutil,
57 sparse,
57 sparse,
58 store as storemod,
58 store as storemod,
59 subrepoutil,
59 subrepoutil,
60 tags as tagsmod,
60 tags as tagsmod,
61 transaction,
61 transaction,
62 txnutil,
62 txnutil,
63 util,
63 util,
64 vfs as vfsmod,
64 vfs as vfsmod,
65 )
65 )
66
66
67 from .interfaces import (
67 from .interfaces import (
68 repository,
68 repository,
69 util as interfaceutil,
69 util as interfaceutil,
70 )
70 )
71
71
72 from .utils import (
72 from .utils import (
73 procutil,
73 procutil,
74 stringutil,
74 stringutil,
75 )
75 )
76
76
77 from .revlogutils import (
77 from .revlogutils import (
78 constants as revlogconst,
78 constants as revlogconst,
79 )
79 )
80
80
81 release = lockmod.release
81 release = lockmod.release
82 urlerr = util.urlerr
82 urlerr = util.urlerr
83 urlreq = util.urlreq
83 urlreq = util.urlreq
84
84
85 # set of (path, vfs-location) tuples. vfs-location is:
85 # set of (path, vfs-location) tuples. vfs-location is:
86 # - 'plain for vfs relative paths
86 # - 'plain for vfs relative paths
87 # - '' for svfs relative paths
87 # - '' for svfs relative paths
88 _cachedfiles = set()
88 _cachedfiles = set()
89
89
90 class _basefilecache(scmutil.filecache):
90 class _basefilecache(scmutil.filecache):
91 """All filecache usage on repo are done for logic that should be unfiltered
91 """All filecache usage on repo are done for logic that should be unfiltered
92 """
92 """
93 def __get__(self, repo, type=None):
93 def __get__(self, repo, type=None):
94 if repo is None:
94 if repo is None:
95 return self
95 return self
96 # proxy to unfiltered __dict__ since filtered repo has no entry
96 # proxy to unfiltered __dict__ since filtered repo has no entry
97 unfi = repo.unfiltered()
97 unfi = repo.unfiltered()
98 try:
98 try:
99 return unfi.__dict__[self.sname]
99 return unfi.__dict__[self.sname]
100 except KeyError:
100 except KeyError:
101 pass
101 pass
102 return super(_basefilecache, self).__get__(unfi, type)
102 return super(_basefilecache, self).__get__(unfi, type)
103
103
104 def set(self, repo, value):
104 def set(self, repo, value):
105 return super(_basefilecache, self).set(repo.unfiltered(), value)
105 return super(_basefilecache, self).set(repo.unfiltered(), value)
106
106
107 class repofilecache(_basefilecache):
107 class repofilecache(_basefilecache):
108 """filecache for files in .hg but outside of .hg/store"""
108 """filecache for files in .hg but outside of .hg/store"""
109 def __init__(self, *paths):
109 def __init__(self, *paths):
110 super(repofilecache, self).__init__(*paths)
110 super(repofilecache, self).__init__(*paths)
111 for path in paths:
111 for path in paths:
112 _cachedfiles.add((path, 'plain'))
112 _cachedfiles.add((path, 'plain'))
113
113
114 def join(self, obj, fname):
114 def join(self, obj, fname):
115 return obj.vfs.join(fname)
115 return obj.vfs.join(fname)
116
116
117 class storecache(_basefilecache):
117 class storecache(_basefilecache):
118 """filecache for files in the store"""
118 """filecache for files in the store"""
119 def __init__(self, *paths):
119 def __init__(self, *paths):
120 super(storecache, self).__init__(*paths)
120 super(storecache, self).__init__(*paths)
121 for path in paths:
121 for path in paths:
122 _cachedfiles.add((path, ''))
122 _cachedfiles.add((path, ''))
123
123
124 def join(self, obj, fname):
124 def join(self, obj, fname):
125 return obj.sjoin(fname)
125 return obj.sjoin(fname)
126
126
127 class mixedrepostorecache(_basefilecache):
127 class mixedrepostorecache(_basefilecache):
128 """filecache for a mix files in .hg/store and outside"""
128 """filecache for a mix files in .hg/store and outside"""
129 def __init__(self, *pathsandlocations):
129 def __init__(self, *pathsandlocations):
130 # scmutil.filecache only uses the path for passing back into our
130 # scmutil.filecache only uses the path for passing back into our
131 # join(), so we can safely pass a list of paths and locations
131 # join(), so we can safely pass a list of paths and locations
132 super(mixedrepostorecache, self).__init__(*pathsandlocations)
132 super(mixedrepostorecache, self).__init__(*pathsandlocations)
133 _cachedfiles.update(pathsandlocations)
133 _cachedfiles.update(pathsandlocations)
134
134
135 def join(self, obj, fnameandlocation):
135 def join(self, obj, fnameandlocation):
136 fname, location = fnameandlocation
136 fname, location = fnameandlocation
137 if location == 'plain':
137 if location == 'plain':
138 return obj.vfs.join(fname)
138 return obj.vfs.join(fname)
139 else:
139 else:
140 if location != '':
140 if location != '':
141 raise error.ProgrammingError('unexpected location: %s' %
141 raise error.ProgrammingError('unexpected location: %s' %
142 location)
142 location)
143 return obj.sjoin(fname)
143 return obj.sjoin(fname)
144
144
145 def isfilecached(repo, name):
145 def isfilecached(repo, name):
146 """check if a repo has already cached "name" filecache-ed property
146 """check if a repo has already cached "name" filecache-ed property
147
147
148 This returns (cachedobj-or-None, iscached) tuple.
148 This returns (cachedobj-or-None, iscached) tuple.
149 """
149 """
150 cacheentry = repo.unfiltered()._filecache.get(name, None)
150 cacheentry = repo.unfiltered()._filecache.get(name, None)
151 if not cacheentry:
151 if not cacheentry:
152 return None, False
152 return None, False
153 return cacheentry.obj, True
153 return cacheentry.obj, True
154
154
155 class unfilteredpropertycache(util.propertycache):
155 class unfilteredpropertycache(util.propertycache):
156 """propertycache that apply to unfiltered repo only"""
156 """propertycache that apply to unfiltered repo only"""
157
157
158 def __get__(self, repo, type=None):
158 def __get__(self, repo, type=None):
159 unfi = repo.unfiltered()
159 unfi = repo.unfiltered()
160 if unfi is repo:
160 if unfi is repo:
161 return super(unfilteredpropertycache, self).__get__(unfi)
161 return super(unfilteredpropertycache, self).__get__(unfi)
162 return getattr(unfi, self.name)
162 return getattr(unfi, self.name)
163
163
164 class filteredpropertycache(util.propertycache):
164 class filteredpropertycache(util.propertycache):
165 """propertycache that must take filtering in account"""
165 """propertycache that must take filtering in account"""
166
166
167 def cachevalue(self, obj, value):
167 def cachevalue(self, obj, value):
168 object.__setattr__(obj, self.name, value)
168 object.__setattr__(obj, self.name, value)
169
169
170
170
171 def hasunfilteredcache(repo, name):
171 def hasunfilteredcache(repo, name):
172 """check if a repo has an unfilteredpropertycache value for <name>"""
172 """check if a repo has an unfilteredpropertycache value for <name>"""
173 return name in vars(repo.unfiltered())
173 return name in vars(repo.unfiltered())
174
174
175 def unfilteredmethod(orig):
175 def unfilteredmethod(orig):
176 """decorate method that always need to be run on unfiltered version"""
176 """decorate method that always need to be run on unfiltered version"""
177 def wrapper(repo, *args, **kwargs):
177 def wrapper(repo, *args, **kwargs):
178 return orig(repo.unfiltered(), *args, **kwargs)
178 return orig(repo.unfiltered(), *args, **kwargs)
179 return wrapper
179 return wrapper
180
180
181 moderncaps = {'lookup', 'branchmap', 'pushkey', 'known', 'getbundle',
181 moderncaps = {'lookup', 'branchmap', 'pushkey', 'known', 'getbundle',
182 'unbundle'}
182 'unbundle'}
183 legacycaps = moderncaps.union({'changegroupsubset'})
183 legacycaps = moderncaps.union({'changegroupsubset'})
184
184
185 @interfaceutil.implementer(repository.ipeercommandexecutor)
185 @interfaceutil.implementer(repository.ipeercommandexecutor)
186 class localcommandexecutor(object):
186 class localcommandexecutor(object):
187 def __init__(self, peer):
187 def __init__(self, peer):
188 self._peer = peer
188 self._peer = peer
189 self._sent = False
189 self._sent = False
190 self._closed = False
190 self._closed = False
191
191
192 def __enter__(self):
192 def __enter__(self):
193 return self
193 return self
194
194
195 def __exit__(self, exctype, excvalue, exctb):
195 def __exit__(self, exctype, excvalue, exctb):
196 self.close()
196 self.close()
197
197
198 def callcommand(self, command, args):
198 def callcommand(self, command, args):
199 if self._sent:
199 if self._sent:
200 raise error.ProgrammingError('callcommand() cannot be used after '
200 raise error.ProgrammingError('callcommand() cannot be used after '
201 'sendcommands()')
201 'sendcommands()')
202
202
203 if self._closed:
203 if self._closed:
204 raise error.ProgrammingError('callcommand() cannot be used after '
204 raise error.ProgrammingError('callcommand() cannot be used after '
205 'close()')
205 'close()')
206
206
207 # We don't need to support anything fancy. Just call the named
207 # We don't need to support anything fancy. Just call the named
208 # method on the peer and return a resolved future.
208 # method on the peer and return a resolved future.
209 fn = getattr(self._peer, pycompat.sysstr(command))
209 fn = getattr(self._peer, pycompat.sysstr(command))
210
210
211 f = pycompat.futures.Future()
211 f = pycompat.futures.Future()
212
212
213 try:
213 try:
214 result = fn(**pycompat.strkwargs(args))
214 result = fn(**pycompat.strkwargs(args))
215 except Exception:
215 except Exception:
216 pycompat.future_set_exception_info(f, sys.exc_info()[1:])
216 pycompat.future_set_exception_info(f, sys.exc_info()[1:])
217 else:
217 else:
218 f.set_result(result)
218 f.set_result(result)
219
219
220 return f
220 return f
221
221
222 def sendcommands(self):
222 def sendcommands(self):
223 self._sent = True
223 self._sent = True
224
224
225 def close(self):
225 def close(self):
226 self._closed = True
226 self._closed = True
227
227
228 @interfaceutil.implementer(repository.ipeercommands)
228 @interfaceutil.implementer(repository.ipeercommands)
229 class localpeer(repository.peer):
229 class localpeer(repository.peer):
230 '''peer for a local repo; reflects only the most recent API'''
230 '''peer for a local repo; reflects only the most recent API'''
231
231
232 def __init__(self, repo, caps=None):
232 def __init__(self, repo, caps=None):
233 super(localpeer, self).__init__()
233 super(localpeer, self).__init__()
234
234
235 if caps is None:
235 if caps is None:
236 caps = moderncaps.copy()
236 caps = moderncaps.copy()
237 self._repo = repo.filtered('served')
237 self._repo = repo.filtered('served')
238 self.ui = repo.ui
238 self.ui = repo.ui
239 self._caps = repo._restrictcapabilities(caps)
239 self._caps = repo._restrictcapabilities(caps)
240
240
241 # Begin of _basepeer interface.
241 # Begin of _basepeer interface.
242
242
243 def url(self):
243 def url(self):
244 return self._repo.url()
244 return self._repo.url()
245
245
246 def local(self):
246 def local(self):
247 return self._repo
247 return self._repo
248
248
249 def peer(self):
249 def peer(self):
250 return self
250 return self
251
251
252 def canpush(self):
252 def canpush(self):
253 return True
253 return True
254
254
255 def close(self):
255 def close(self):
256 self._repo.close()
256 self._repo.close()
257
257
258 # End of _basepeer interface.
258 # End of _basepeer interface.
259
259
260 # Begin of _basewirecommands interface.
260 # Begin of _basewirecommands interface.
261
261
262 def branchmap(self):
262 def branchmap(self):
263 return self._repo.branchmap()
263 return self._repo.branchmap()
264
264
265 def capabilities(self):
265 def capabilities(self):
266 return self._caps
266 return self._caps
267
267
268 def clonebundles(self):
268 def clonebundles(self):
269 return self._repo.tryread('clonebundles.manifest')
269 return self._repo.tryread('clonebundles.manifest')
270
270
271 def debugwireargs(self, one, two, three=None, four=None, five=None):
271 def debugwireargs(self, one, two, three=None, four=None, five=None):
272 """Used to test argument passing over the wire"""
272 """Used to test argument passing over the wire"""
273 return "%s %s %s %s %s" % (one, two, pycompat.bytestr(three),
273 return "%s %s %s %s %s" % (one, two, pycompat.bytestr(three),
274 pycompat.bytestr(four),
274 pycompat.bytestr(four),
275 pycompat.bytestr(five))
275 pycompat.bytestr(five))
276
276
277 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
277 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
278 **kwargs):
278 **kwargs):
279 chunks = exchange.getbundlechunks(self._repo, source, heads=heads,
279 chunks = exchange.getbundlechunks(self._repo, source, heads=heads,
280 common=common, bundlecaps=bundlecaps,
280 common=common, bundlecaps=bundlecaps,
281 **kwargs)[1]
281 **kwargs)[1]
282 cb = util.chunkbuffer(chunks)
282 cb = util.chunkbuffer(chunks)
283
283
284 if exchange.bundle2requested(bundlecaps):
284 if exchange.bundle2requested(bundlecaps):
285 # When requesting a bundle2, getbundle returns a stream to make the
285 # When requesting a bundle2, getbundle returns a stream to make the
286 # wire level function happier. We need to build a proper object
286 # wire level function happier. We need to build a proper object
287 # from it in local peer.
287 # from it in local peer.
288 return bundle2.getunbundler(self.ui, cb)
288 return bundle2.getunbundler(self.ui, cb)
289 else:
289 else:
290 return changegroup.getunbundler('01', cb, None)
290 return changegroup.getunbundler('01', cb, None)
291
291
292 def heads(self):
292 def heads(self):
293 return self._repo.heads()
293 return self._repo.heads()
294
294
295 def known(self, nodes):
295 def known(self, nodes):
296 return self._repo.known(nodes)
296 return self._repo.known(nodes)
297
297
298 def listkeys(self, namespace):
298 def listkeys(self, namespace):
299 return self._repo.listkeys(namespace)
299 return self._repo.listkeys(namespace)
300
300
301 def lookup(self, key):
301 def lookup(self, key):
302 return self._repo.lookup(key)
302 return self._repo.lookup(key)
303
303
304 def pushkey(self, namespace, key, old, new):
304 def pushkey(self, namespace, key, old, new):
305 return self._repo.pushkey(namespace, key, old, new)
305 return self._repo.pushkey(namespace, key, old, new)
306
306
307 def stream_out(self):
307 def stream_out(self):
308 raise error.Abort(_('cannot perform stream clone against local '
308 raise error.Abort(_('cannot perform stream clone against local '
309 'peer'))
309 'peer'))
310
310
311 def unbundle(self, bundle, heads, url):
311 def unbundle(self, bundle, heads, url):
312 """apply a bundle on a repo
312 """apply a bundle on a repo
313
313
314 This function handles the repo locking itself."""
314 This function handles the repo locking itself."""
315 try:
315 try:
316 try:
316 try:
317 bundle = exchange.readbundle(self.ui, bundle, None)
317 bundle = exchange.readbundle(self.ui, bundle, None)
318 ret = exchange.unbundle(self._repo, bundle, heads, 'push', url)
318 ret = exchange.unbundle(self._repo, bundle, heads, 'push', url)
319 if util.safehasattr(ret, 'getchunks'):
319 if util.safehasattr(ret, 'getchunks'):
320 # This is a bundle20 object, turn it into an unbundler.
320 # This is a bundle20 object, turn it into an unbundler.
321 # This little dance should be dropped eventually when the
321 # This little dance should be dropped eventually when the
322 # API is finally improved.
322 # API is finally improved.
323 stream = util.chunkbuffer(ret.getchunks())
323 stream = util.chunkbuffer(ret.getchunks())
324 ret = bundle2.getunbundler(self.ui, stream)
324 ret = bundle2.getunbundler(self.ui, stream)
325 return ret
325 return ret
326 except Exception as exc:
326 except Exception as exc:
327 # If the exception contains output salvaged from a bundle2
327 # If the exception contains output salvaged from a bundle2
328 # reply, we need to make sure it is printed before continuing
328 # reply, we need to make sure it is printed before continuing
329 # to fail. So we build a bundle2 with such output and consume
329 # to fail. So we build a bundle2 with such output and consume
330 # it directly.
330 # it directly.
331 #
331 #
332 # This is not very elegant but allows a "simple" solution for
332 # This is not very elegant but allows a "simple" solution for
333 # issue4594
333 # issue4594
334 output = getattr(exc, '_bundle2salvagedoutput', ())
334 output = getattr(exc, '_bundle2salvagedoutput', ())
335 if output:
335 if output:
336 bundler = bundle2.bundle20(self._repo.ui)
336 bundler = bundle2.bundle20(self._repo.ui)
337 for out in output:
337 for out in output:
338 bundler.addpart(out)
338 bundler.addpart(out)
339 stream = util.chunkbuffer(bundler.getchunks())
339 stream = util.chunkbuffer(bundler.getchunks())
340 b = bundle2.getunbundler(self.ui, stream)
340 b = bundle2.getunbundler(self.ui, stream)
341 bundle2.processbundle(self._repo, b)
341 bundle2.processbundle(self._repo, b)
342 raise
342 raise
343 except error.PushRaced as exc:
343 except error.PushRaced as exc:
344 raise error.ResponseError(_('push failed:'),
344 raise error.ResponseError(_('push failed:'),
345 stringutil.forcebytestr(exc))
345 stringutil.forcebytestr(exc))
346
346
347 # End of _basewirecommands interface.
347 # End of _basewirecommands interface.
348
348
349 # Begin of peer interface.
349 # Begin of peer interface.
350
350
351 def commandexecutor(self):
351 def commandexecutor(self):
352 return localcommandexecutor(self)
352 return localcommandexecutor(self)
353
353
354 # End of peer interface.
354 # End of peer interface.
355
355
356 @interfaceutil.implementer(repository.ipeerlegacycommands)
356 @interfaceutil.implementer(repository.ipeerlegacycommands)
357 class locallegacypeer(localpeer):
357 class locallegacypeer(localpeer):
358 '''peer extension which implements legacy methods too; used for tests with
358 '''peer extension which implements legacy methods too; used for tests with
359 restricted capabilities'''
359 restricted capabilities'''
360
360
361 def __init__(self, repo):
361 def __init__(self, repo):
362 super(locallegacypeer, self).__init__(repo, caps=legacycaps)
362 super(locallegacypeer, self).__init__(repo, caps=legacycaps)
363
363
364 # Begin of baselegacywirecommands interface.
364 # Begin of baselegacywirecommands interface.
365
365
366 def between(self, pairs):
366 def between(self, pairs):
367 return self._repo.between(pairs)
367 return self._repo.between(pairs)
368
368
369 def branches(self, nodes):
369 def branches(self, nodes):
370 return self._repo.branches(nodes)
370 return self._repo.branches(nodes)
371
371
372 def changegroup(self, nodes, source):
372 def changegroup(self, nodes, source):
373 outgoing = discovery.outgoing(self._repo, missingroots=nodes,
373 outgoing = discovery.outgoing(self._repo, missingroots=nodes,
374 missingheads=self._repo.heads())
374 missingheads=self._repo.heads())
375 return changegroup.makechangegroup(self._repo, outgoing, '01', source)
375 return changegroup.makechangegroup(self._repo, outgoing, '01', source)
376
376
377 def changegroupsubset(self, bases, heads, source):
377 def changegroupsubset(self, bases, heads, source):
378 outgoing = discovery.outgoing(self._repo, missingroots=bases,
378 outgoing = discovery.outgoing(self._repo, missingroots=bases,
379 missingheads=heads)
379 missingheads=heads)
380 return changegroup.makechangegroup(self._repo, outgoing, '01', source)
380 return changegroup.makechangegroup(self._repo, outgoing, '01', source)
381
381
382 # End of baselegacywirecommands interface.
382 # End of baselegacywirecommands interface.
383
383
384 # Increment the sub-version when the revlog v2 format changes to lock out old
384 # Increment the sub-version when the revlog v2 format changes to lock out old
385 # clients.
385 # clients.
386 REVLOGV2_REQUIREMENT = 'exp-revlogv2.1'
386 REVLOGV2_REQUIREMENT = 'exp-revlogv2.1'
387
387
388 # A repository with the sparserevlog feature will have delta chains that
388 # A repository with the sparserevlog feature will have delta chains that
389 # can spread over a larger span. Sparse reading cuts these large spans into
389 # can spread over a larger span. Sparse reading cuts these large spans into
390 # pieces, so that each piece isn't too big.
390 # pieces, so that each piece isn't too big.
391 # Without the sparserevlog capability, reading from the repository could use
391 # Without the sparserevlog capability, reading from the repository could use
392 # huge amounts of memory, because the whole span would be read at once,
392 # huge amounts of memory, because the whole span would be read at once,
393 # including all the intermediate revisions that aren't pertinent for the chain.
393 # including all the intermediate revisions that aren't pertinent for the chain.
394 # This is why once a repository has enabled sparse-read, it becomes required.
394 # This is why once a repository has enabled sparse-read, it becomes required.
395 SPARSEREVLOG_REQUIREMENT = 'sparserevlog'
395 SPARSEREVLOG_REQUIREMENT = 'sparserevlog'
396
396
397 # Functions receiving (ui, features) that extensions can register to impact
397 # Functions receiving (ui, features) that extensions can register to impact
398 # the ability to load repositories with custom requirements. Only
398 # the ability to load repositories with custom requirements. Only
399 # functions defined in loaded extensions are called.
399 # functions defined in loaded extensions are called.
400 #
400 #
401 # The function receives a set of requirement strings that the repository
401 # The function receives a set of requirement strings that the repository
402 # is capable of opening. Functions will typically add elements to the
402 # is capable of opening. Functions will typically add elements to the
403 # set to reflect that the extension knows how to handle that requirements.
403 # set to reflect that the extension knows how to handle that requirements.
404 featuresetupfuncs = set()
404 featuresetupfuncs = set()
405
405
406 def makelocalrepository(baseui, path, intents=None):
406 def makelocalrepository(baseui, path, intents=None):
407 """Create a local repository object.
407 """Create a local repository object.
408
408
409 Given arguments needed to construct a local repository, this function
409 Given arguments needed to construct a local repository, this function
410 performs various early repository loading functionality (such as
410 performs various early repository loading functionality (such as
411 reading the ``.hg/requires`` and ``.hg/hgrc`` files), validates that
411 reading the ``.hg/requires`` and ``.hg/hgrc`` files), validates that
412 the repository can be opened, derives a type suitable for representing
412 the repository can be opened, derives a type suitable for representing
413 that repository, and returns an instance of it.
413 that repository, and returns an instance of it.
414
414
415 The returned object conforms to the ``repository.completelocalrepository``
415 The returned object conforms to the ``repository.completelocalrepository``
416 interface.
416 interface.
417
417
418 The repository type is derived by calling a series of factory functions
418 The repository type is derived by calling a series of factory functions
419 for each aspect/interface of the final repository. These are defined by
419 for each aspect/interface of the final repository. These are defined by
420 ``REPO_INTERFACES``.
420 ``REPO_INTERFACES``.
421
421
422 Each factory function is called to produce a type implementing a specific
422 Each factory function is called to produce a type implementing a specific
423 interface. The cumulative list of returned types will be combined into a
423 interface. The cumulative list of returned types will be combined into a
424 new type and that type will be instantiated to represent the local
424 new type and that type will be instantiated to represent the local
425 repository.
425 repository.
426
426
427 The factory functions each receive various state that may be consulted
427 The factory functions each receive various state that may be consulted
428 as part of deriving a type.
428 as part of deriving a type.
429
429
430 Extensions should wrap these factory functions to customize repository type
430 Extensions should wrap these factory functions to customize repository type
431 creation. Note that an extension's wrapped function may be called even if
431 creation. Note that an extension's wrapped function may be called even if
432 that extension is not loaded for the repo being constructed. Extensions
432 that extension is not loaded for the repo being constructed. Extensions
433 should check if their ``__name__`` appears in the
433 should check if their ``__name__`` appears in the
434 ``extensionmodulenames`` set passed to the factory function and no-op if
434 ``extensionmodulenames`` set passed to the factory function and no-op if
435 not.
435 not.
436 """
436 """
437 ui = baseui.copy()
437 ui = baseui.copy()
438 # Prevent copying repo configuration.
438 # Prevent copying repo configuration.
439 ui.copy = baseui.copy
439 ui.copy = baseui.copy
440
440
441 # Working directory VFS rooted at repository root.
441 # Working directory VFS rooted at repository root.
442 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
442 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
443
443
444 # Main VFS for .hg/ directory.
444 # Main VFS for .hg/ directory.
445 hgpath = wdirvfs.join(b'.hg')
445 hgpath = wdirvfs.join(b'.hg')
446 hgvfs = vfsmod.vfs(hgpath, cacheaudited=True)
446 hgvfs = vfsmod.vfs(hgpath, cacheaudited=True)
447
447
448 # The .hg/ path should exist and should be a directory. All other
448 # The .hg/ path should exist and should be a directory. All other
449 # cases are errors.
449 # cases are errors.
450 if not hgvfs.isdir():
450 if not hgvfs.isdir():
451 try:
451 try:
452 hgvfs.stat()
452 hgvfs.stat()
453 except OSError as e:
453 except OSError as e:
454 if e.errno != errno.ENOENT:
454 if e.errno != errno.ENOENT:
455 raise
455 raise
456
456
457 raise error.RepoError(_(b'repository %s not found') % path)
457 raise error.RepoError(_(b'repository %s not found') % path)
458
458
459 # .hg/requires file contains a newline-delimited list of
459 # .hg/requires file contains a newline-delimited list of
460 # features/capabilities the opener (us) must have in order to use
460 # features/capabilities the opener (us) must have in order to use
461 # the repository. This file was introduced in Mercurial 0.9.2,
461 # the repository. This file was introduced in Mercurial 0.9.2,
462 # which means very old repositories may not have one. We assume
462 # which means very old repositories may not have one. We assume
463 # a missing file translates to no requirements.
463 # a missing file translates to no requirements.
464 try:
464 try:
465 requirements = set(hgvfs.read(b'requires').splitlines())
465 requirements = set(hgvfs.read(b'requires').splitlines())
466 except IOError as e:
466 except IOError as e:
467 if e.errno != errno.ENOENT:
467 if e.errno != errno.ENOENT:
468 raise
468 raise
469 requirements = set()
469 requirements = set()
470
470
471 # The .hg/hgrc file may load extensions or contain config options
471 # The .hg/hgrc file may load extensions or contain config options
472 # that influence repository construction. Attempt to load it and
472 # that influence repository construction. Attempt to load it and
473 # process any new extensions that it may have pulled in.
473 # process any new extensions that it may have pulled in.
474 if loadhgrc(ui, wdirvfs, hgvfs, requirements):
474 if loadhgrc(ui, wdirvfs, hgvfs, requirements):
475 afterhgrcload(ui, wdirvfs, hgvfs, requirements)
475 afterhgrcload(ui, wdirvfs, hgvfs, requirements)
476 extensions.loadall(ui)
476 extensions.loadall(ui)
477 extensions.populateui(ui)
477 extensions.populateui(ui)
478
478
479 # Set of module names of extensions loaded for this repository.
479 # Set of module names of extensions loaded for this repository.
480 extensionmodulenames = {m.__name__ for n, m in extensions.extensions(ui)}
480 extensionmodulenames = {m.__name__ for n, m in extensions.extensions(ui)}
481
481
482 supportedrequirements = gathersupportedrequirements(ui)
482 supportedrequirements = gathersupportedrequirements(ui)
483
483
484 # We first validate the requirements are known.
484 # We first validate the requirements are known.
485 ensurerequirementsrecognized(requirements, supportedrequirements)
485 ensurerequirementsrecognized(requirements, supportedrequirements)
486
486
487 # Then we validate that the known set is reasonable to use together.
487 # Then we validate that the known set is reasonable to use together.
488 ensurerequirementscompatible(ui, requirements)
488 ensurerequirementscompatible(ui, requirements)
489
489
490 # TODO there are unhandled edge cases related to opening repositories with
490 # TODO there are unhandled edge cases related to opening repositories with
491 # shared storage. If storage is shared, we should also test for requirements
491 # shared storage. If storage is shared, we should also test for requirements
492 # compatibility in the pointed-to repo. This entails loading the .hg/hgrc in
492 # compatibility in the pointed-to repo. This entails loading the .hg/hgrc in
493 # that repo, as that repo may load extensions needed to open it. This is a
493 # that repo, as that repo may load extensions needed to open it. This is a
494 # bit complicated because we don't want the other hgrc to overwrite settings
494 # bit complicated because we don't want the other hgrc to overwrite settings
495 # in this hgrc.
495 # in this hgrc.
496 #
496 #
497 # This bug is somewhat mitigated by the fact that we copy the .hg/requires
497 # This bug is somewhat mitigated by the fact that we copy the .hg/requires
498 # file when sharing repos. But if a requirement is added after the share is
498 # file when sharing repos. But if a requirement is added after the share is
499 # performed, thereby introducing a new requirement for the opener, we may
499 # performed, thereby introducing a new requirement for the opener, we may
500 # will not see that and could encounter a run-time error interacting with
500 # will not see that and could encounter a run-time error interacting with
501 # that shared store since it has an unknown-to-us requirement.
501 # that shared store since it has an unknown-to-us requirement.
502
502
503 # At this point, we know we should be capable of opening the repository.
503 # At this point, we know we should be capable of opening the repository.
504 # Now get on with doing that.
504 # Now get on with doing that.
505
505
506 features = set()
506 features = set()
507
507
508 # The "store" part of the repository holds versioned data. How it is
508 # The "store" part of the repository holds versioned data. How it is
509 # accessed is determined by various requirements. The ``shared`` or
509 # accessed is determined by various requirements. The ``shared`` or
510 # ``relshared`` requirements indicate the store lives in the path contained
510 # ``relshared`` requirements indicate the store lives in the path contained
511 # in the ``.hg/sharedpath`` file. This is an absolute path for
511 # in the ``.hg/sharedpath`` file. This is an absolute path for
512 # ``shared`` and relative to ``.hg/`` for ``relshared``.
512 # ``shared`` and relative to ``.hg/`` for ``relshared``.
513 if b'shared' in requirements or b'relshared' in requirements:
513 if b'shared' in requirements or b'relshared' in requirements:
514 sharedpath = hgvfs.read(b'sharedpath').rstrip(b'\n')
514 sharedpath = hgvfs.read(b'sharedpath').rstrip(b'\n')
515 if b'relshared' in requirements:
515 if b'relshared' in requirements:
516 sharedpath = hgvfs.join(sharedpath)
516 sharedpath = hgvfs.join(sharedpath)
517
517
518 sharedvfs = vfsmod.vfs(sharedpath, realpath=True)
518 sharedvfs = vfsmod.vfs(sharedpath, realpath=True)
519
519
520 if not sharedvfs.exists():
520 if not sharedvfs.exists():
521 raise error.RepoError(_(b'.hg/sharedpath points to nonexistent '
521 raise error.RepoError(_(b'.hg/sharedpath points to nonexistent '
522 b'directory %s') % sharedvfs.base)
522 b'directory %s') % sharedvfs.base)
523
523
524 features.add(repository.REPO_FEATURE_SHARED_STORAGE)
524 features.add(repository.REPO_FEATURE_SHARED_STORAGE)
525
525
526 storebasepath = sharedvfs.base
526 storebasepath = sharedvfs.base
527 cachepath = sharedvfs.join(b'cache')
527 cachepath = sharedvfs.join(b'cache')
528 else:
528 else:
529 storebasepath = hgvfs.base
529 storebasepath = hgvfs.base
530 cachepath = hgvfs.join(b'cache')
530 cachepath = hgvfs.join(b'cache')
531 wcachepath = hgvfs.join(b'wcache')
531 wcachepath = hgvfs.join(b'wcache')
532
532
533
533
534 # The store has changed over time and the exact layout is dictated by
534 # The store has changed over time and the exact layout is dictated by
535 # requirements. The store interface abstracts differences across all
535 # requirements. The store interface abstracts differences across all
536 # of them.
536 # of them.
537 store = makestore(requirements, storebasepath,
537 store = makestore(requirements, storebasepath,
538 lambda base: vfsmod.vfs(base, cacheaudited=True))
538 lambda base: vfsmod.vfs(base, cacheaudited=True))
539 hgvfs.createmode = store.createmode
539 hgvfs.createmode = store.createmode
540
540
541 storevfs = store.vfs
541 storevfs = store.vfs
542 storevfs.options = resolvestorevfsoptions(ui, requirements, features)
542 storevfs.options = resolvestorevfsoptions(ui, requirements, features)
543
543
544 # The cache vfs is used to manage cache files.
544 # The cache vfs is used to manage cache files.
545 cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
545 cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
546 cachevfs.createmode = store.createmode
546 cachevfs.createmode = store.createmode
547 # The cache vfs is used to manage cache files related to the working copy
547 # The cache vfs is used to manage cache files related to the working copy
548 wcachevfs = vfsmod.vfs(wcachepath, cacheaudited=True)
548 wcachevfs = vfsmod.vfs(wcachepath, cacheaudited=True)
549 wcachevfs.createmode = store.createmode
549 wcachevfs.createmode = store.createmode
550
550
551 # Now resolve the type for the repository object. We do this by repeatedly
551 # Now resolve the type for the repository object. We do this by repeatedly
552 # calling a factory function to produces types for specific aspects of the
552 # calling a factory function to produces types for specific aspects of the
553 # repo's operation. The aggregate returned types are used as base classes
553 # repo's operation. The aggregate returned types are used as base classes
554 # for a dynamically-derived type, which will represent our new repository.
554 # for a dynamically-derived type, which will represent our new repository.
555
555
556 bases = []
556 bases = []
557 extrastate = {}
557 extrastate = {}
558
558
559 for iface, fn in REPO_INTERFACES:
559 for iface, fn in REPO_INTERFACES:
560 # We pass all potentially useful state to give extensions tons of
560 # We pass all potentially useful state to give extensions tons of
561 # flexibility.
561 # flexibility.
562 typ = fn()(ui=ui,
562 typ = fn()(ui=ui,
563 intents=intents,
563 intents=intents,
564 requirements=requirements,
564 requirements=requirements,
565 features=features,
565 features=features,
566 wdirvfs=wdirvfs,
566 wdirvfs=wdirvfs,
567 hgvfs=hgvfs,
567 hgvfs=hgvfs,
568 store=store,
568 store=store,
569 storevfs=storevfs,
569 storevfs=storevfs,
570 storeoptions=storevfs.options,
570 storeoptions=storevfs.options,
571 cachevfs=cachevfs,
571 cachevfs=cachevfs,
572 wcachevfs=wcachevfs,
572 wcachevfs=wcachevfs,
573 extensionmodulenames=extensionmodulenames,
573 extensionmodulenames=extensionmodulenames,
574 extrastate=extrastate,
574 extrastate=extrastate,
575 baseclasses=bases)
575 baseclasses=bases)
576
576
577 if not isinstance(typ, type):
577 if not isinstance(typ, type):
578 raise error.ProgrammingError('unable to construct type for %s' %
578 raise error.ProgrammingError('unable to construct type for %s' %
579 iface)
579 iface)
580
580
581 bases.append(typ)
581 bases.append(typ)
582
582
583 # type() allows you to use characters in type names that wouldn't be
583 # type() allows you to use characters in type names that wouldn't be
584 # recognized as Python symbols in source code. We abuse that to add
584 # recognized as Python symbols in source code. We abuse that to add
585 # rich information about our constructed repo.
585 # rich information about our constructed repo.
586 name = pycompat.sysstr(b'derivedrepo:%s<%s>' % (
586 name = pycompat.sysstr(b'derivedrepo:%s<%s>' % (
587 wdirvfs.base,
587 wdirvfs.base,
588 b','.join(sorted(requirements))))
588 b','.join(sorted(requirements))))
589
589
590 cls = type(name, tuple(bases), {})
590 cls = type(name, tuple(bases), {})
591
591
592 return cls(
592 return cls(
593 baseui=baseui,
593 baseui=baseui,
594 ui=ui,
594 ui=ui,
595 origroot=path,
595 origroot=path,
596 wdirvfs=wdirvfs,
596 wdirvfs=wdirvfs,
597 hgvfs=hgvfs,
597 hgvfs=hgvfs,
598 requirements=requirements,
598 requirements=requirements,
599 supportedrequirements=supportedrequirements,
599 supportedrequirements=supportedrequirements,
600 sharedpath=storebasepath,
600 sharedpath=storebasepath,
601 store=store,
601 store=store,
602 cachevfs=cachevfs,
602 cachevfs=cachevfs,
603 wcachevfs=wcachevfs,
603 wcachevfs=wcachevfs,
604 features=features,
604 features=features,
605 intents=intents)
605 intents=intents)
606
606
607 def loadhgrc(ui, wdirvfs, hgvfs, requirements):
607 def loadhgrc(ui, wdirvfs, hgvfs, requirements):
608 """Load hgrc files/content into a ui instance.
608 """Load hgrc files/content into a ui instance.
609
609
610 This is called during repository opening to load any additional
610 This is called during repository opening to load any additional
611 config files or settings relevant to the current repository.
611 config files or settings relevant to the current repository.
612
612
613 Returns a bool indicating whether any additional configs were loaded.
613 Returns a bool indicating whether any additional configs were loaded.
614
614
615 Extensions should monkeypatch this function to modify how per-repo
615 Extensions should monkeypatch this function to modify how per-repo
616 configs are loaded. For example, an extension may wish to pull in
616 configs are loaded. For example, an extension may wish to pull in
617 configs from alternate files or sources.
617 configs from alternate files or sources.
618 """
618 """
619 try:
619 try:
620 ui.readconfig(hgvfs.join(b'hgrc'), root=wdirvfs.base)
620 ui.readconfig(hgvfs.join(b'hgrc'), root=wdirvfs.base)
621 return True
621 return True
622 except IOError:
622 except IOError:
623 return False
623 return False
624
624
625 def afterhgrcload(ui, wdirvfs, hgvfs, requirements):
625 def afterhgrcload(ui, wdirvfs, hgvfs, requirements):
626 """Perform additional actions after .hg/hgrc is loaded.
626 """Perform additional actions after .hg/hgrc is loaded.
627
627
628 This function is called during repository loading immediately after
628 This function is called during repository loading immediately after
629 the .hg/hgrc file is loaded and before per-repo extensions are loaded.
629 the .hg/hgrc file is loaded and before per-repo extensions are loaded.
630
630
631 The function can be used to validate configs, automatically add
631 The function can be used to validate configs, automatically add
632 options (including extensions) based on requirements, etc.
632 options (including extensions) based on requirements, etc.
633 """
633 """
634
634
635 # Map of requirements to list of extensions to load automatically when
635 # Map of requirements to list of extensions to load automatically when
636 # requirement is present.
636 # requirement is present.
637 autoextensions = {
637 autoextensions = {
638 b'largefiles': [b'largefiles'],
638 b'largefiles': [b'largefiles'],
639 b'lfs': [b'lfs'],
639 b'lfs': [b'lfs'],
640 }
640 }
641
641
642 for requirement, names in sorted(autoextensions.items()):
642 for requirement, names in sorted(autoextensions.items()):
643 if requirement not in requirements:
643 if requirement not in requirements:
644 continue
644 continue
645
645
646 for name in names:
646 for name in names:
647 if not ui.hasconfig(b'extensions', name):
647 if not ui.hasconfig(b'extensions', name):
648 ui.setconfig(b'extensions', name, b'', source='autoload')
648 ui.setconfig(b'extensions', name, b'', source='autoload')
649
649
650 def gathersupportedrequirements(ui):
650 def gathersupportedrequirements(ui):
651 """Determine the complete set of recognized requirements."""
651 """Determine the complete set of recognized requirements."""
652 # Start with all requirements supported by this file.
652 # Start with all requirements supported by this file.
653 supported = set(localrepository._basesupported)
653 supported = set(localrepository._basesupported)
654
654
655 # Execute ``featuresetupfuncs`` entries if they belong to an extension
655 # Execute ``featuresetupfuncs`` entries if they belong to an extension
656 # relevant to this ui instance.
656 # relevant to this ui instance.
657 modules = {m.__name__ for n, m in extensions.extensions(ui)}
657 modules = {m.__name__ for n, m in extensions.extensions(ui)}
658
658
659 for fn in featuresetupfuncs:
659 for fn in featuresetupfuncs:
660 if fn.__module__ in modules:
660 if fn.__module__ in modules:
661 fn(ui, supported)
661 fn(ui, supported)
662
662
663 # Add derived requirements from registered compression engines.
663 # Add derived requirements from registered compression engines.
664 for name in util.compengines:
664 for name in util.compengines:
665 engine = util.compengines[name]
665 engine = util.compengines[name]
666 if engine.available() and engine.revlogheader():
666 if engine.available() and engine.revlogheader():
667 supported.add(b'exp-compression-%s' % name)
667 supported.add(b'exp-compression-%s' % name)
668 if engine.name() == 'zstd':
668 if engine.name() == 'zstd':
669 supported.add(b'revlog-compression-zstd')
669 supported.add(b'revlog-compression-zstd')
670
670
671 return supported
671 return supported
672
672
673 def ensurerequirementsrecognized(requirements, supported):
673 def ensurerequirementsrecognized(requirements, supported):
674 """Validate that a set of local requirements is recognized.
674 """Validate that a set of local requirements is recognized.
675
675
676 Receives a set of requirements. Raises an ``error.RepoError`` if there
676 Receives a set of requirements. Raises an ``error.RepoError`` if there
677 exists any requirement in that set that currently loaded code doesn't
677 exists any requirement in that set that currently loaded code doesn't
678 recognize.
678 recognize.
679
679
680 Returns a set of supported requirements.
680 Returns a set of supported requirements.
681 """
681 """
682 missing = set()
682 missing = set()
683
683
684 for requirement in requirements:
684 for requirement in requirements:
685 if requirement in supported:
685 if requirement in supported:
686 continue
686 continue
687
687
688 if not requirement or not requirement[0:1].isalnum():
688 if not requirement or not requirement[0:1].isalnum():
689 raise error.RequirementError(_(b'.hg/requires file is corrupt'))
689 raise error.RequirementError(_(b'.hg/requires file is corrupt'))
690
690
691 missing.add(requirement)
691 missing.add(requirement)
692
692
693 if missing:
693 if missing:
694 raise error.RequirementError(
694 raise error.RequirementError(
695 _(b'repository requires features unknown to this Mercurial: %s') %
695 _(b'repository requires features unknown to this Mercurial: %s') %
696 b' '.join(sorted(missing)),
696 b' '.join(sorted(missing)),
697 hint=_(b'see https://mercurial-scm.org/wiki/MissingRequirement '
697 hint=_(b'see https://mercurial-scm.org/wiki/MissingRequirement '
698 b'for more information'))
698 b'for more information'))
699
699
700 def ensurerequirementscompatible(ui, requirements):
700 def ensurerequirementscompatible(ui, requirements):
701 """Validates that a set of recognized requirements is mutually compatible.
701 """Validates that a set of recognized requirements is mutually compatible.
702
702
703 Some requirements may not be compatible with others or require
703 Some requirements may not be compatible with others or require
704 config options that aren't enabled. This function is called during
704 config options that aren't enabled. This function is called during
705 repository opening to ensure that the set of requirements needed
705 repository opening to ensure that the set of requirements needed
706 to open a repository is sane and compatible with config options.
706 to open a repository is sane and compatible with config options.
707
707
708 Extensions can monkeypatch this function to perform additional
708 Extensions can monkeypatch this function to perform additional
709 checking.
709 checking.
710
710
711 ``error.RepoError`` should be raised on failure.
711 ``error.RepoError`` should be raised on failure.
712 """
712 """
713 if b'exp-sparse' in requirements and not sparse.enabled:
713 if b'exp-sparse' in requirements and not sparse.enabled:
714 raise error.RepoError(_(b'repository is using sparse feature but '
714 raise error.RepoError(_(b'repository is using sparse feature but '
715 b'sparse is not enabled; enable the '
715 b'sparse is not enabled; enable the '
716 b'"sparse" extensions to access'))
716 b'"sparse" extensions to access'))
717
717
718 def makestore(requirements, path, vfstype):
718 def makestore(requirements, path, vfstype):
719 """Construct a storage object for a repository."""
719 """Construct a storage object for a repository."""
720 if b'store' in requirements:
720 if b'store' in requirements:
721 if b'fncache' in requirements:
721 if b'fncache' in requirements:
722 return storemod.fncachestore(path, vfstype,
722 return storemod.fncachestore(path, vfstype,
723 b'dotencode' in requirements)
723 b'dotencode' in requirements)
724
724
725 return storemod.encodedstore(path, vfstype)
725 return storemod.encodedstore(path, vfstype)
726
726
727 return storemod.basicstore(path, vfstype)
727 return storemod.basicstore(path, vfstype)
728
728
729 def resolvestorevfsoptions(ui, requirements, features):
729 def resolvestorevfsoptions(ui, requirements, features):
730 """Resolve the options to pass to the store vfs opener.
730 """Resolve the options to pass to the store vfs opener.
731
731
732 The returned dict is used to influence behavior of the storage layer.
732 The returned dict is used to influence behavior of the storage layer.
733 """
733 """
734 options = {}
734 options = {}
735
735
736 if b'treemanifest' in requirements:
736 if b'treemanifest' in requirements:
737 options[b'treemanifest'] = True
737 options[b'treemanifest'] = True
738
738
739 # experimental config: format.manifestcachesize
739 # experimental config: format.manifestcachesize
740 manifestcachesize = ui.configint(b'format', b'manifestcachesize')
740 manifestcachesize = ui.configint(b'format', b'manifestcachesize')
741 if manifestcachesize is not None:
741 if manifestcachesize is not None:
742 options[b'manifestcachesize'] = manifestcachesize
742 options[b'manifestcachesize'] = manifestcachesize
743
743
744 # In the absence of another requirement superseding a revlog-related
744 # In the absence of another requirement superseding a revlog-related
745 # requirement, we have to assume the repo is using revlog version 0.
745 # requirement, we have to assume the repo is using revlog version 0.
746 # This revlog format is super old and we don't bother trying to parse
746 # This revlog format is super old and we don't bother trying to parse
747 # opener options for it because those options wouldn't do anything
747 # opener options for it because those options wouldn't do anything
748 # meaningful on such old repos.
748 # meaningful on such old repos.
749 if b'revlogv1' in requirements or REVLOGV2_REQUIREMENT in requirements:
749 if b'revlogv1' in requirements or REVLOGV2_REQUIREMENT in requirements:
750 options.update(resolverevlogstorevfsoptions(ui, requirements, features))
750 options.update(resolverevlogstorevfsoptions(ui, requirements, features))
751 else: # explicitly mark repo as using revlogv0
752 options['revlogv0'] = True
751
753
752 return options
754 return options
753
755
754 def resolverevlogstorevfsoptions(ui, requirements, features):
756 def resolverevlogstorevfsoptions(ui, requirements, features):
755 """Resolve opener options specific to revlogs."""
757 """Resolve opener options specific to revlogs."""
756
758
757 options = {}
759 options = {}
758 options[b'flagprocessors'] = {}
760 options[b'flagprocessors'] = {}
759
761
760 if b'revlogv1' in requirements:
762 if b'revlogv1' in requirements:
761 options[b'revlogv1'] = True
763 options[b'revlogv1'] = True
762 if REVLOGV2_REQUIREMENT in requirements:
764 if REVLOGV2_REQUIREMENT in requirements:
763 options[b'revlogv2'] = True
765 options[b'revlogv2'] = True
764
766
765 if b'generaldelta' in requirements:
767 if b'generaldelta' in requirements:
766 options[b'generaldelta'] = True
768 options[b'generaldelta'] = True
767
769
768 # experimental config: format.chunkcachesize
770 # experimental config: format.chunkcachesize
769 chunkcachesize = ui.configint(b'format', b'chunkcachesize')
771 chunkcachesize = ui.configint(b'format', b'chunkcachesize')
770 if chunkcachesize is not None:
772 if chunkcachesize is not None:
771 options[b'chunkcachesize'] = chunkcachesize
773 options[b'chunkcachesize'] = chunkcachesize
772
774
773 deltabothparents = ui.configbool(b'storage',
775 deltabothparents = ui.configbool(b'storage',
774 b'revlog.optimize-delta-parent-choice')
776 b'revlog.optimize-delta-parent-choice')
775 options[b'deltabothparents'] = deltabothparents
777 options[b'deltabothparents'] = deltabothparents
776
778
777 lazydelta = ui.configbool(b'storage', b'revlog.reuse-external-delta')
779 lazydelta = ui.configbool(b'storage', b'revlog.reuse-external-delta')
778 lazydeltabase = False
780 lazydeltabase = False
779 if lazydelta:
781 if lazydelta:
780 lazydeltabase = ui.configbool(b'storage',
782 lazydeltabase = ui.configbool(b'storage',
781 b'revlog.reuse-external-delta-parent')
783 b'revlog.reuse-external-delta-parent')
782 if lazydeltabase is None:
784 if lazydeltabase is None:
783 lazydeltabase = not scmutil.gddeltaconfig(ui)
785 lazydeltabase = not scmutil.gddeltaconfig(ui)
784 options[b'lazydelta'] = lazydelta
786 options[b'lazydelta'] = lazydelta
785 options[b'lazydeltabase'] = lazydeltabase
787 options[b'lazydeltabase'] = lazydeltabase
786
788
787 chainspan = ui.configbytes(b'experimental', b'maxdeltachainspan')
789 chainspan = ui.configbytes(b'experimental', b'maxdeltachainspan')
788 if 0 <= chainspan:
790 if 0 <= chainspan:
789 options[b'maxdeltachainspan'] = chainspan
791 options[b'maxdeltachainspan'] = chainspan
790
792
791 mmapindexthreshold = ui.configbytes(b'experimental',
793 mmapindexthreshold = ui.configbytes(b'experimental',
792 b'mmapindexthreshold')
794 b'mmapindexthreshold')
793 if mmapindexthreshold is not None:
795 if mmapindexthreshold is not None:
794 options[b'mmapindexthreshold'] = mmapindexthreshold
796 options[b'mmapindexthreshold'] = mmapindexthreshold
795
797
796 withsparseread = ui.configbool(b'experimental', b'sparse-read')
798 withsparseread = ui.configbool(b'experimental', b'sparse-read')
797 srdensitythres = float(ui.config(b'experimental',
799 srdensitythres = float(ui.config(b'experimental',
798 b'sparse-read.density-threshold'))
800 b'sparse-read.density-threshold'))
799 srmingapsize = ui.configbytes(b'experimental',
801 srmingapsize = ui.configbytes(b'experimental',
800 b'sparse-read.min-gap-size')
802 b'sparse-read.min-gap-size')
801 options[b'with-sparse-read'] = withsparseread
803 options[b'with-sparse-read'] = withsparseread
802 options[b'sparse-read-density-threshold'] = srdensitythres
804 options[b'sparse-read-density-threshold'] = srdensitythres
803 options[b'sparse-read-min-gap-size'] = srmingapsize
805 options[b'sparse-read-min-gap-size'] = srmingapsize
804
806
805 sparserevlog = SPARSEREVLOG_REQUIREMENT in requirements
807 sparserevlog = SPARSEREVLOG_REQUIREMENT in requirements
806 options[b'sparse-revlog'] = sparserevlog
808 options[b'sparse-revlog'] = sparserevlog
807 if sparserevlog:
809 if sparserevlog:
808 options[b'generaldelta'] = True
810 options[b'generaldelta'] = True
809
811
810 maxchainlen = None
812 maxchainlen = None
811 if sparserevlog:
813 if sparserevlog:
812 maxchainlen = revlogconst.SPARSE_REVLOG_MAX_CHAIN_LENGTH
814 maxchainlen = revlogconst.SPARSE_REVLOG_MAX_CHAIN_LENGTH
813 # experimental config: format.maxchainlen
815 # experimental config: format.maxchainlen
814 maxchainlen = ui.configint(b'format', b'maxchainlen', maxchainlen)
816 maxchainlen = ui.configint(b'format', b'maxchainlen', maxchainlen)
815 if maxchainlen is not None:
817 if maxchainlen is not None:
816 options[b'maxchainlen'] = maxchainlen
818 options[b'maxchainlen'] = maxchainlen
817
819
818 for r in requirements:
820 for r in requirements:
819 # we allow multiple compression engine requirement to co-exist because
821 # we allow multiple compression engine requirement to co-exist because
820 # strickly speaking, revlog seems to support mixed compression style.
822 # strickly speaking, revlog seems to support mixed compression style.
821 #
823 #
822 # The compression used for new entries will be "the last one"
824 # The compression used for new entries will be "the last one"
823 prefix = r.startswith
825 prefix = r.startswith
824 if prefix('revlog-compression-') or prefix('exp-compression-'):
826 if prefix('revlog-compression-') or prefix('exp-compression-'):
825 options[b'compengine'] = r.split('-', 2)[2]
827 options[b'compengine'] = r.split('-', 2)[2]
826
828
827 options[b'zlib.level'] = ui.configint(b'storage', b'revlog.zlib.level')
829 options[b'zlib.level'] = ui.configint(b'storage', b'revlog.zlib.level')
828 if options[b'zlib.level'] is not None:
830 if options[b'zlib.level'] is not None:
829 if not (0 <= options[b'zlib.level'] <= 9):
831 if not (0 <= options[b'zlib.level'] <= 9):
830 msg = _('invalid value for `storage.revlog.zlib.level` config: %d')
832 msg = _('invalid value for `storage.revlog.zlib.level` config: %d')
831 raise error.Abort(msg % options[b'zlib.level'])
833 raise error.Abort(msg % options[b'zlib.level'])
832 options[b'zstd.level'] = ui.configint(b'storage', b'revlog.zstd.level')
834 options[b'zstd.level'] = ui.configint(b'storage', b'revlog.zstd.level')
833 if options[b'zstd.level'] is not None:
835 if options[b'zstd.level'] is not None:
834 if not (0 <= options[b'zstd.level'] <= 22):
836 if not (0 <= options[b'zstd.level'] <= 22):
835 msg = _('invalid value for `storage.revlog.zstd.level` config: %d')
837 msg = _('invalid value for `storage.revlog.zstd.level` config: %d')
836 raise error.Abort(msg % options[b'zstd.level'])
838 raise error.Abort(msg % options[b'zstd.level'])
837
839
838 if repository.NARROW_REQUIREMENT in requirements:
840 if repository.NARROW_REQUIREMENT in requirements:
839 options[b'enableellipsis'] = True
841 options[b'enableellipsis'] = True
840
842
841 return options
843 return options
842
844
843 def makemain(**kwargs):
845 def makemain(**kwargs):
844 """Produce a type conforming to ``ilocalrepositorymain``."""
846 """Produce a type conforming to ``ilocalrepositorymain``."""
845 return localrepository
847 return localrepository
846
848
847 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
849 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
848 class revlogfilestorage(object):
850 class revlogfilestorage(object):
849 """File storage when using revlogs."""
851 """File storage when using revlogs."""
850
852
851 def file(self, path):
853 def file(self, path):
852 if path[0] == b'/':
854 if path[0] == b'/':
853 path = path[1:]
855 path = path[1:]
854
856
855 return filelog.filelog(self.svfs, path)
857 return filelog.filelog(self.svfs, path)
856
858
857 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
859 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
858 class revlognarrowfilestorage(object):
860 class revlognarrowfilestorage(object):
859 """File storage when using revlogs and narrow files."""
861 """File storage when using revlogs and narrow files."""
860
862
861 def file(self, path):
863 def file(self, path):
862 if path[0] == b'/':
864 if path[0] == b'/':
863 path = path[1:]
865 path = path[1:]
864
866
865 return filelog.narrowfilelog(self.svfs, path, self._storenarrowmatch)
867 return filelog.narrowfilelog(self.svfs, path, self._storenarrowmatch)
866
868
867 def makefilestorage(requirements, features, **kwargs):
869 def makefilestorage(requirements, features, **kwargs):
868 """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
870 """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
869 features.add(repository.REPO_FEATURE_REVLOG_FILE_STORAGE)
871 features.add(repository.REPO_FEATURE_REVLOG_FILE_STORAGE)
870 features.add(repository.REPO_FEATURE_STREAM_CLONE)
872 features.add(repository.REPO_FEATURE_STREAM_CLONE)
871
873
872 if repository.NARROW_REQUIREMENT in requirements:
874 if repository.NARROW_REQUIREMENT in requirements:
873 return revlognarrowfilestorage
875 return revlognarrowfilestorage
874 else:
876 else:
875 return revlogfilestorage
877 return revlogfilestorage
876
878
877 # List of repository interfaces and factory functions for them. Each
879 # List of repository interfaces and factory functions for them. Each
878 # will be called in order during ``makelocalrepository()`` to iteratively
880 # will be called in order during ``makelocalrepository()`` to iteratively
879 # derive the final type for a local repository instance. We capture the
881 # derive the final type for a local repository instance. We capture the
880 # function as a lambda so we don't hold a reference and the module-level
882 # function as a lambda so we don't hold a reference and the module-level
881 # functions can be wrapped.
883 # functions can be wrapped.
882 REPO_INTERFACES = [
884 REPO_INTERFACES = [
883 (repository.ilocalrepositorymain, lambda: makemain),
885 (repository.ilocalrepositorymain, lambda: makemain),
884 (repository.ilocalrepositoryfilestorage, lambda: makefilestorage),
886 (repository.ilocalrepositoryfilestorage, lambda: makefilestorage),
885 ]
887 ]
886
888
887 @interfaceutil.implementer(repository.ilocalrepositorymain)
889 @interfaceutil.implementer(repository.ilocalrepositorymain)
888 class localrepository(object):
890 class localrepository(object):
889 """Main class for representing local repositories.
891 """Main class for representing local repositories.
890
892
891 All local repositories are instances of this class.
893 All local repositories are instances of this class.
892
894
893 Constructed on its own, instances of this class are not usable as
895 Constructed on its own, instances of this class are not usable as
894 repository objects. To obtain a usable repository object, call
896 repository objects. To obtain a usable repository object, call
895 ``hg.repository()``, ``localrepo.instance()``, or
897 ``hg.repository()``, ``localrepo.instance()``, or
896 ``localrepo.makelocalrepository()``. The latter is the lowest-level.
898 ``localrepo.makelocalrepository()``. The latter is the lowest-level.
897 ``instance()`` adds support for creating new repositories.
899 ``instance()`` adds support for creating new repositories.
898 ``hg.repository()`` adds more extension integration, including calling
900 ``hg.repository()`` adds more extension integration, including calling
899 ``reposetup()``. Generally speaking, ``hg.repository()`` should be
901 ``reposetup()``. Generally speaking, ``hg.repository()`` should be
900 used.
902 used.
901 """
903 """
902
904
903 # obsolete experimental requirements:
905 # obsolete experimental requirements:
904 # - manifestv2: An experimental new manifest format that allowed
906 # - manifestv2: An experimental new manifest format that allowed
905 # for stem compression of long paths. Experiment ended up not
907 # for stem compression of long paths. Experiment ended up not
906 # being successful (repository sizes went up due to worse delta
908 # being successful (repository sizes went up due to worse delta
907 # chains), and the code was deleted in 4.6.
909 # chains), and the code was deleted in 4.6.
908 supportedformats = {
910 supportedformats = {
909 'revlogv1',
911 'revlogv1',
910 'generaldelta',
912 'generaldelta',
911 'treemanifest',
913 'treemanifest',
912 REVLOGV2_REQUIREMENT,
914 REVLOGV2_REQUIREMENT,
913 SPARSEREVLOG_REQUIREMENT,
915 SPARSEREVLOG_REQUIREMENT,
914 bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT,
916 bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT,
915 }
917 }
916 _basesupported = supportedformats | {
918 _basesupported = supportedformats | {
917 'store',
919 'store',
918 'fncache',
920 'fncache',
919 'shared',
921 'shared',
920 'relshared',
922 'relshared',
921 'dotencode',
923 'dotencode',
922 'exp-sparse',
924 'exp-sparse',
923 'internal-phase'
925 'internal-phase'
924 }
926 }
925
927
926 # list of prefix for file which can be written without 'wlock'
928 # list of prefix for file which can be written without 'wlock'
927 # Extensions should extend this list when needed
929 # Extensions should extend this list when needed
928 _wlockfreeprefix = {
930 _wlockfreeprefix = {
929 # We migh consider requiring 'wlock' for the next
931 # We migh consider requiring 'wlock' for the next
930 # two, but pretty much all the existing code assume
932 # two, but pretty much all the existing code assume
931 # wlock is not needed so we keep them excluded for
933 # wlock is not needed so we keep them excluded for
932 # now.
934 # now.
933 'hgrc',
935 'hgrc',
934 'requires',
936 'requires',
935 # XXX cache is a complicatged business someone
937 # XXX cache is a complicatged business someone
936 # should investigate this in depth at some point
938 # should investigate this in depth at some point
937 'cache/',
939 'cache/',
938 # XXX shouldn't be dirstate covered by the wlock?
940 # XXX shouldn't be dirstate covered by the wlock?
939 'dirstate',
941 'dirstate',
940 # XXX bisect was still a bit too messy at the time
942 # XXX bisect was still a bit too messy at the time
941 # this changeset was introduced. Someone should fix
943 # this changeset was introduced. Someone should fix
942 # the remainig bit and drop this line
944 # the remainig bit and drop this line
943 'bisect.state',
945 'bisect.state',
944 }
946 }
945
947
946 def __init__(self, baseui, ui, origroot, wdirvfs, hgvfs, requirements,
948 def __init__(self, baseui, ui, origroot, wdirvfs, hgvfs, requirements,
947 supportedrequirements, sharedpath, store, cachevfs, wcachevfs,
949 supportedrequirements, sharedpath, store, cachevfs, wcachevfs,
948 features, intents=None):
950 features, intents=None):
949 """Create a new local repository instance.
951 """Create a new local repository instance.
950
952
951 Most callers should use ``hg.repository()``, ``localrepo.instance()``,
953 Most callers should use ``hg.repository()``, ``localrepo.instance()``,
952 or ``localrepo.makelocalrepository()`` for obtaining a new repository
954 or ``localrepo.makelocalrepository()`` for obtaining a new repository
953 object.
955 object.
954
956
955 Arguments:
957 Arguments:
956
958
957 baseui
959 baseui
958 ``ui.ui`` instance that ``ui`` argument was based off of.
960 ``ui.ui`` instance that ``ui`` argument was based off of.
959
961
960 ui
962 ui
961 ``ui.ui`` instance for use by the repository.
963 ``ui.ui`` instance for use by the repository.
962
964
963 origroot
965 origroot
964 ``bytes`` path to working directory root of this repository.
966 ``bytes`` path to working directory root of this repository.
965
967
966 wdirvfs
968 wdirvfs
967 ``vfs.vfs`` rooted at the working directory.
969 ``vfs.vfs`` rooted at the working directory.
968
970
969 hgvfs
971 hgvfs
970 ``vfs.vfs`` rooted at .hg/
972 ``vfs.vfs`` rooted at .hg/
971
973
972 requirements
974 requirements
973 ``set`` of bytestrings representing repository opening requirements.
975 ``set`` of bytestrings representing repository opening requirements.
974
976
975 supportedrequirements
977 supportedrequirements
976 ``set`` of bytestrings representing repository requirements that we
978 ``set`` of bytestrings representing repository requirements that we
977 know how to open. May be a supetset of ``requirements``.
979 know how to open. May be a supetset of ``requirements``.
978
980
979 sharedpath
981 sharedpath
980 ``bytes`` Defining path to storage base directory. Points to a
982 ``bytes`` Defining path to storage base directory. Points to a
981 ``.hg/`` directory somewhere.
983 ``.hg/`` directory somewhere.
982
984
983 store
985 store
984 ``store.basicstore`` (or derived) instance providing access to
986 ``store.basicstore`` (or derived) instance providing access to
985 versioned storage.
987 versioned storage.
986
988
987 cachevfs
989 cachevfs
988 ``vfs.vfs`` used for cache files.
990 ``vfs.vfs`` used for cache files.
989
991
990 wcachevfs
992 wcachevfs
991 ``vfs.vfs`` used for cache files related to the working copy.
993 ``vfs.vfs`` used for cache files related to the working copy.
992
994
993 features
995 features
994 ``set`` of bytestrings defining features/capabilities of this
996 ``set`` of bytestrings defining features/capabilities of this
995 instance.
997 instance.
996
998
997 intents
999 intents
998 ``set`` of system strings indicating what this repo will be used
1000 ``set`` of system strings indicating what this repo will be used
999 for.
1001 for.
1000 """
1002 """
1001 self.baseui = baseui
1003 self.baseui = baseui
1002 self.ui = ui
1004 self.ui = ui
1003 self.origroot = origroot
1005 self.origroot = origroot
1004 # vfs rooted at working directory.
1006 # vfs rooted at working directory.
1005 self.wvfs = wdirvfs
1007 self.wvfs = wdirvfs
1006 self.root = wdirvfs.base
1008 self.root = wdirvfs.base
1007 # vfs rooted at .hg/. Used to access most non-store paths.
1009 # vfs rooted at .hg/. Used to access most non-store paths.
1008 self.vfs = hgvfs
1010 self.vfs = hgvfs
1009 self.path = hgvfs.base
1011 self.path = hgvfs.base
1010 self.requirements = requirements
1012 self.requirements = requirements
1011 self.supported = supportedrequirements
1013 self.supported = supportedrequirements
1012 self.sharedpath = sharedpath
1014 self.sharedpath = sharedpath
1013 self.store = store
1015 self.store = store
1014 self.cachevfs = cachevfs
1016 self.cachevfs = cachevfs
1015 self.wcachevfs = wcachevfs
1017 self.wcachevfs = wcachevfs
1016 self.features = features
1018 self.features = features
1017
1019
1018 self.filtername = None
1020 self.filtername = None
1019
1021
1020 if (self.ui.configbool('devel', 'all-warnings') or
1022 if (self.ui.configbool('devel', 'all-warnings') or
1021 self.ui.configbool('devel', 'check-locks')):
1023 self.ui.configbool('devel', 'check-locks')):
1022 self.vfs.audit = self._getvfsward(self.vfs.audit)
1024 self.vfs.audit = self._getvfsward(self.vfs.audit)
1023 # A list of callback to shape the phase if no data were found.
1025 # A list of callback to shape the phase if no data were found.
1024 # Callback are in the form: func(repo, roots) --> processed root.
1026 # Callback are in the form: func(repo, roots) --> processed root.
1025 # This list it to be filled by extension during repo setup
1027 # This list it to be filled by extension during repo setup
1026 self._phasedefaults = []
1028 self._phasedefaults = []
1027
1029
1028 color.setup(self.ui)
1030 color.setup(self.ui)
1029
1031
1030 self.spath = self.store.path
1032 self.spath = self.store.path
1031 self.svfs = self.store.vfs
1033 self.svfs = self.store.vfs
1032 self.sjoin = self.store.join
1034 self.sjoin = self.store.join
1033 if (self.ui.configbool('devel', 'all-warnings') or
1035 if (self.ui.configbool('devel', 'all-warnings') or
1034 self.ui.configbool('devel', 'check-locks')):
1036 self.ui.configbool('devel', 'check-locks')):
1035 if util.safehasattr(self.svfs, 'vfs'): # this is filtervfs
1037 if util.safehasattr(self.svfs, 'vfs'): # this is filtervfs
1036 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
1038 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
1037 else: # standard vfs
1039 else: # standard vfs
1038 self.svfs.audit = self._getsvfsward(self.svfs.audit)
1040 self.svfs.audit = self._getsvfsward(self.svfs.audit)
1039
1041
1040 self._dirstatevalidatewarned = False
1042 self._dirstatevalidatewarned = False
1041
1043
1042 self._branchcaches = branchmap.BranchMapCache()
1044 self._branchcaches = branchmap.BranchMapCache()
1043 self._revbranchcache = None
1045 self._revbranchcache = None
1044 self._filterpats = {}
1046 self._filterpats = {}
1045 self._datafilters = {}
1047 self._datafilters = {}
1046 self._transref = self._lockref = self._wlockref = None
1048 self._transref = self._lockref = self._wlockref = None
1047
1049
1048 # A cache for various files under .hg/ that tracks file changes,
1050 # A cache for various files under .hg/ that tracks file changes,
1049 # (used by the filecache decorator)
1051 # (used by the filecache decorator)
1050 #
1052 #
1051 # Maps a property name to its util.filecacheentry
1053 # Maps a property name to its util.filecacheentry
1052 self._filecache = {}
1054 self._filecache = {}
1053
1055
1054 # hold sets of revision to be filtered
1056 # hold sets of revision to be filtered
1055 # should be cleared when something might have changed the filter value:
1057 # should be cleared when something might have changed the filter value:
1056 # - new changesets,
1058 # - new changesets,
1057 # - phase change,
1059 # - phase change,
1058 # - new obsolescence marker,
1060 # - new obsolescence marker,
1059 # - working directory parent change,
1061 # - working directory parent change,
1060 # - bookmark changes
1062 # - bookmark changes
1061 self.filteredrevcache = {}
1063 self.filteredrevcache = {}
1062
1064
1063 # post-dirstate-status hooks
1065 # post-dirstate-status hooks
1064 self._postdsstatus = []
1066 self._postdsstatus = []
1065
1067
1066 # generic mapping between names and nodes
1068 # generic mapping between names and nodes
1067 self.names = namespaces.namespaces()
1069 self.names = namespaces.namespaces()
1068
1070
1069 # Key to signature value.
1071 # Key to signature value.
1070 self._sparsesignaturecache = {}
1072 self._sparsesignaturecache = {}
1071 # Signature to cached matcher instance.
1073 # Signature to cached matcher instance.
1072 self._sparsematchercache = {}
1074 self._sparsematchercache = {}
1073
1075
1074 self._extrafilterid = repoview.extrafilter(ui)
1076 self._extrafilterid = repoview.extrafilter(ui)
1075
1077
1076 def _getvfsward(self, origfunc):
1078 def _getvfsward(self, origfunc):
1077 """build a ward for self.vfs"""
1079 """build a ward for self.vfs"""
1078 rref = weakref.ref(self)
1080 rref = weakref.ref(self)
1079 def checkvfs(path, mode=None):
1081 def checkvfs(path, mode=None):
1080 ret = origfunc(path, mode=mode)
1082 ret = origfunc(path, mode=mode)
1081 repo = rref()
1083 repo = rref()
1082 if (repo is None
1084 if (repo is None
1083 or not util.safehasattr(repo, '_wlockref')
1085 or not util.safehasattr(repo, '_wlockref')
1084 or not util.safehasattr(repo, '_lockref')):
1086 or not util.safehasattr(repo, '_lockref')):
1085 return
1087 return
1086 if mode in (None, 'r', 'rb'):
1088 if mode in (None, 'r', 'rb'):
1087 return
1089 return
1088 if path.startswith(repo.path):
1090 if path.startswith(repo.path):
1089 # truncate name relative to the repository (.hg)
1091 # truncate name relative to the repository (.hg)
1090 path = path[len(repo.path) + 1:]
1092 path = path[len(repo.path) + 1:]
1091 if path.startswith('cache/'):
1093 if path.startswith('cache/'):
1092 msg = 'accessing cache with vfs instead of cachevfs: "%s"'
1094 msg = 'accessing cache with vfs instead of cachevfs: "%s"'
1093 repo.ui.develwarn(msg % path, stacklevel=3, config="cache-vfs")
1095 repo.ui.develwarn(msg % path, stacklevel=3, config="cache-vfs")
1094 if path.startswith('journal.') or path.startswith('undo.'):
1096 if path.startswith('journal.') or path.startswith('undo.'):
1095 # journal is covered by 'lock'
1097 # journal is covered by 'lock'
1096 if repo._currentlock(repo._lockref) is None:
1098 if repo._currentlock(repo._lockref) is None:
1097 repo.ui.develwarn('write with no lock: "%s"' % path,
1099 repo.ui.develwarn('write with no lock: "%s"' % path,
1098 stacklevel=3, config='check-locks')
1100 stacklevel=3, config='check-locks')
1099 elif repo._currentlock(repo._wlockref) is None:
1101 elif repo._currentlock(repo._wlockref) is None:
1100 # rest of vfs files are covered by 'wlock'
1102 # rest of vfs files are covered by 'wlock'
1101 #
1103 #
1102 # exclude special files
1104 # exclude special files
1103 for prefix in self._wlockfreeprefix:
1105 for prefix in self._wlockfreeprefix:
1104 if path.startswith(prefix):
1106 if path.startswith(prefix):
1105 return
1107 return
1106 repo.ui.develwarn('write with no wlock: "%s"' % path,
1108 repo.ui.develwarn('write with no wlock: "%s"' % path,
1107 stacklevel=3, config='check-locks')
1109 stacklevel=3, config='check-locks')
1108 return ret
1110 return ret
1109 return checkvfs
1111 return checkvfs
1110
1112
1111 def _getsvfsward(self, origfunc):
1113 def _getsvfsward(self, origfunc):
1112 """build a ward for self.svfs"""
1114 """build a ward for self.svfs"""
1113 rref = weakref.ref(self)
1115 rref = weakref.ref(self)
1114 def checksvfs(path, mode=None):
1116 def checksvfs(path, mode=None):
1115 ret = origfunc(path, mode=mode)
1117 ret = origfunc(path, mode=mode)
1116 repo = rref()
1118 repo = rref()
1117 if repo is None or not util.safehasattr(repo, '_lockref'):
1119 if repo is None or not util.safehasattr(repo, '_lockref'):
1118 return
1120 return
1119 if mode in (None, 'r', 'rb'):
1121 if mode in (None, 'r', 'rb'):
1120 return
1122 return
1121 if path.startswith(repo.sharedpath):
1123 if path.startswith(repo.sharedpath):
1122 # truncate name relative to the repository (.hg)
1124 # truncate name relative to the repository (.hg)
1123 path = path[len(repo.sharedpath) + 1:]
1125 path = path[len(repo.sharedpath) + 1:]
1124 if repo._currentlock(repo._lockref) is None:
1126 if repo._currentlock(repo._lockref) is None:
1125 repo.ui.develwarn('write with no lock: "%s"' % path,
1127 repo.ui.develwarn('write with no lock: "%s"' % path,
1126 stacklevel=4)
1128 stacklevel=4)
1127 return ret
1129 return ret
1128 return checksvfs
1130 return checksvfs
1129
1131
1130 def close(self):
1132 def close(self):
1131 self._writecaches()
1133 self._writecaches()
1132
1134
1133 def _writecaches(self):
1135 def _writecaches(self):
1134 if self._revbranchcache:
1136 if self._revbranchcache:
1135 self._revbranchcache.write()
1137 self._revbranchcache.write()
1136
1138
1137 def _restrictcapabilities(self, caps):
1139 def _restrictcapabilities(self, caps):
1138 if self.ui.configbool('experimental', 'bundle2-advertise'):
1140 if self.ui.configbool('experimental', 'bundle2-advertise'):
1139 caps = set(caps)
1141 caps = set(caps)
1140 capsblob = bundle2.encodecaps(bundle2.getrepocaps(self,
1142 capsblob = bundle2.encodecaps(bundle2.getrepocaps(self,
1141 role='client'))
1143 role='client'))
1142 caps.add('bundle2=' + urlreq.quote(capsblob))
1144 caps.add('bundle2=' + urlreq.quote(capsblob))
1143 return caps
1145 return caps
1144
1146
1145 def _writerequirements(self):
1147 def _writerequirements(self):
1146 scmutil.writerequires(self.vfs, self.requirements)
1148 scmutil.writerequires(self.vfs, self.requirements)
1147
1149
1148 # Don't cache auditor/nofsauditor, or you'll end up with reference cycle:
1150 # Don't cache auditor/nofsauditor, or you'll end up with reference cycle:
1149 # self -> auditor -> self._checknested -> self
1151 # self -> auditor -> self._checknested -> self
1150
1152
1151 @property
1153 @property
1152 def auditor(self):
1154 def auditor(self):
1153 # This is only used by context.workingctx.match in order to
1155 # This is only used by context.workingctx.match in order to
1154 # detect files in subrepos.
1156 # detect files in subrepos.
1155 return pathutil.pathauditor(self.root, callback=self._checknested)
1157 return pathutil.pathauditor(self.root, callback=self._checknested)
1156
1158
1157 @property
1159 @property
1158 def nofsauditor(self):
1160 def nofsauditor(self):
1159 # This is only used by context.basectx.match in order to detect
1161 # This is only used by context.basectx.match in order to detect
1160 # files in subrepos.
1162 # files in subrepos.
1161 return pathutil.pathauditor(self.root, callback=self._checknested,
1163 return pathutil.pathauditor(self.root, callback=self._checknested,
1162 realfs=False, cached=True)
1164 realfs=False, cached=True)
1163
1165
1164 def _checknested(self, path):
1166 def _checknested(self, path):
1165 """Determine if path is a legal nested repository."""
1167 """Determine if path is a legal nested repository."""
1166 if not path.startswith(self.root):
1168 if not path.startswith(self.root):
1167 return False
1169 return False
1168 subpath = path[len(self.root) + 1:]
1170 subpath = path[len(self.root) + 1:]
1169 normsubpath = util.pconvert(subpath)
1171 normsubpath = util.pconvert(subpath)
1170
1172
1171 # XXX: Checking against the current working copy is wrong in
1173 # XXX: Checking against the current working copy is wrong in
1172 # the sense that it can reject things like
1174 # the sense that it can reject things like
1173 #
1175 #
1174 # $ hg cat -r 10 sub/x.txt
1176 # $ hg cat -r 10 sub/x.txt
1175 #
1177 #
1176 # if sub/ is no longer a subrepository in the working copy
1178 # if sub/ is no longer a subrepository in the working copy
1177 # parent revision.
1179 # parent revision.
1178 #
1180 #
1179 # However, it can of course also allow things that would have
1181 # However, it can of course also allow things that would have
1180 # been rejected before, such as the above cat command if sub/
1182 # been rejected before, such as the above cat command if sub/
1181 # is a subrepository now, but was a normal directory before.
1183 # is a subrepository now, but was a normal directory before.
1182 # The old path auditor would have rejected by mistake since it
1184 # The old path auditor would have rejected by mistake since it
1183 # panics when it sees sub/.hg/.
1185 # panics when it sees sub/.hg/.
1184 #
1186 #
1185 # All in all, checking against the working copy seems sensible
1187 # All in all, checking against the working copy seems sensible
1186 # since we want to prevent access to nested repositories on
1188 # since we want to prevent access to nested repositories on
1187 # the filesystem *now*.
1189 # the filesystem *now*.
1188 ctx = self[None]
1190 ctx = self[None]
1189 parts = util.splitpath(subpath)
1191 parts = util.splitpath(subpath)
1190 while parts:
1192 while parts:
1191 prefix = '/'.join(parts)
1193 prefix = '/'.join(parts)
1192 if prefix in ctx.substate:
1194 if prefix in ctx.substate:
1193 if prefix == normsubpath:
1195 if prefix == normsubpath:
1194 return True
1196 return True
1195 else:
1197 else:
1196 sub = ctx.sub(prefix)
1198 sub = ctx.sub(prefix)
1197 return sub.checknested(subpath[len(prefix) + 1:])
1199 return sub.checknested(subpath[len(prefix) + 1:])
1198 else:
1200 else:
1199 parts.pop()
1201 parts.pop()
1200 return False
1202 return False
1201
1203
1202 def peer(self):
1204 def peer(self):
1203 return localpeer(self) # not cached to avoid reference cycle
1205 return localpeer(self) # not cached to avoid reference cycle
1204
1206
1205 def unfiltered(self):
1207 def unfiltered(self):
1206 """Return unfiltered version of the repository
1208 """Return unfiltered version of the repository
1207
1209
1208 Intended to be overwritten by filtered repo."""
1210 Intended to be overwritten by filtered repo."""
1209 return self
1211 return self
1210
1212
1211 def filtered(self, name, visibilityexceptions=None):
1213 def filtered(self, name, visibilityexceptions=None):
1212 """Return a filtered version of a repository
1214 """Return a filtered version of a repository
1213
1215
1214 The `name` parameter is the identifier of the requested view. This
1216 The `name` parameter is the identifier of the requested view. This
1215 will return a repoview object set "exactly" to the specified view.
1217 will return a repoview object set "exactly" to the specified view.
1216
1218
1217 This function does not apply recursive filtering to a repository. For
1219 This function does not apply recursive filtering to a repository. For
1218 example calling `repo.filtered("served")` will return a repoview using
1220 example calling `repo.filtered("served")` will return a repoview using
1219 the "served" view, regardless of the initial view used by `repo`.
1221 the "served" view, regardless of the initial view used by `repo`.
1220
1222
1221 In other word, there is always only one level of `repoview` "filtering".
1223 In other word, there is always only one level of `repoview` "filtering".
1222 """
1224 """
1223 if self._extrafilterid is not None and '%' not in name:
1225 if self._extrafilterid is not None and '%' not in name:
1224 name = name + '%' + self._extrafilterid
1226 name = name + '%' + self._extrafilterid
1225
1227
1226 cls = repoview.newtype(self.unfiltered().__class__)
1228 cls = repoview.newtype(self.unfiltered().__class__)
1227 return cls(self, name, visibilityexceptions)
1229 return cls(self, name, visibilityexceptions)
1228
1230
1229 @mixedrepostorecache(('bookmarks', 'plain'), ('bookmarks.current', 'plain'),
1231 @mixedrepostorecache(('bookmarks', 'plain'), ('bookmarks.current', 'plain'),
1230 ('bookmarks', ''), ('00changelog.i', ''))
1232 ('bookmarks', ''), ('00changelog.i', ''))
1231 def _bookmarks(self):
1233 def _bookmarks(self):
1232 # Since the multiple files involved in the transaction cannot be
1234 # Since the multiple files involved in the transaction cannot be
1233 # written atomically (with current repository format), there is a race
1235 # written atomically (with current repository format), there is a race
1234 # condition here.
1236 # condition here.
1235 #
1237 #
1236 # 1) changelog content A is read
1238 # 1) changelog content A is read
1237 # 2) outside transaction update changelog to content B
1239 # 2) outside transaction update changelog to content B
1238 # 3) outside transaction update bookmark file referring to content B
1240 # 3) outside transaction update bookmark file referring to content B
1239 # 4) bookmarks file content is read and filtered against changelog-A
1241 # 4) bookmarks file content is read and filtered against changelog-A
1240 #
1242 #
1241 # When this happens, bookmarks against nodes missing from A are dropped.
1243 # When this happens, bookmarks against nodes missing from A are dropped.
1242 #
1244 #
1243 # Having this happening during read is not great, but it become worse
1245 # Having this happening during read is not great, but it become worse
1244 # when this happen during write because the bookmarks to the "unknown"
1246 # when this happen during write because the bookmarks to the "unknown"
1245 # nodes will be dropped for good. However, writes happen within locks.
1247 # nodes will be dropped for good. However, writes happen within locks.
1246 # This locking makes it possible to have a race free consistent read.
1248 # This locking makes it possible to have a race free consistent read.
1247 # For this purpose data read from disc before locking are
1249 # For this purpose data read from disc before locking are
1248 # "invalidated" right after the locks are taken. This invalidations are
1250 # "invalidated" right after the locks are taken. This invalidations are
1249 # "light", the `filecache` mechanism keep the data in memory and will
1251 # "light", the `filecache` mechanism keep the data in memory and will
1250 # reuse them if the underlying files did not changed. Not parsing the
1252 # reuse them if the underlying files did not changed. Not parsing the
1251 # same data multiple times helps performances.
1253 # same data multiple times helps performances.
1252 #
1254 #
1253 # Unfortunately in the case describe above, the files tracked by the
1255 # Unfortunately in the case describe above, the files tracked by the
1254 # bookmarks file cache might not have changed, but the in-memory
1256 # bookmarks file cache might not have changed, but the in-memory
1255 # content is still "wrong" because we used an older changelog content
1257 # content is still "wrong" because we used an older changelog content
1256 # to process the on-disk data. So after locking, the changelog would be
1258 # to process the on-disk data. So after locking, the changelog would be
1257 # refreshed but `_bookmarks` would be preserved.
1259 # refreshed but `_bookmarks` would be preserved.
1258 # Adding `00changelog.i` to the list of tracked file is not
1260 # Adding `00changelog.i` to the list of tracked file is not
1259 # enough, because at the time we build the content for `_bookmarks` in
1261 # enough, because at the time we build the content for `_bookmarks` in
1260 # (4), the changelog file has already diverged from the content used
1262 # (4), the changelog file has already diverged from the content used
1261 # for loading `changelog` in (1)
1263 # for loading `changelog` in (1)
1262 #
1264 #
1263 # To prevent the issue, we force the changelog to be explicitly
1265 # To prevent the issue, we force the changelog to be explicitly
1264 # reloaded while computing `_bookmarks`. The data race can still happen
1266 # reloaded while computing `_bookmarks`. The data race can still happen
1265 # without the lock (with a narrower window), but it would no longer go
1267 # without the lock (with a narrower window), but it would no longer go
1266 # undetected during the lock time refresh.
1268 # undetected during the lock time refresh.
1267 #
1269 #
1268 # The new schedule is as follow
1270 # The new schedule is as follow
1269 #
1271 #
1270 # 1) filecache logic detect that `_bookmarks` needs to be computed
1272 # 1) filecache logic detect that `_bookmarks` needs to be computed
1271 # 2) cachestat for `bookmarks` and `changelog` are captured (for book)
1273 # 2) cachestat for `bookmarks` and `changelog` are captured (for book)
1272 # 3) We force `changelog` filecache to be tested
1274 # 3) We force `changelog` filecache to be tested
1273 # 4) cachestat for `changelog` are captured (for changelog)
1275 # 4) cachestat for `changelog` are captured (for changelog)
1274 # 5) `_bookmarks` is computed and cached
1276 # 5) `_bookmarks` is computed and cached
1275 #
1277 #
1276 # The step in (3) ensure we have a changelog at least as recent as the
1278 # The step in (3) ensure we have a changelog at least as recent as the
1277 # cache stat computed in (1). As a result at locking time:
1279 # cache stat computed in (1). As a result at locking time:
1278 # * if the changelog did not changed since (1) -> we can reuse the data
1280 # * if the changelog did not changed since (1) -> we can reuse the data
1279 # * otherwise -> the bookmarks get refreshed.
1281 # * otherwise -> the bookmarks get refreshed.
1280 self._refreshchangelog()
1282 self._refreshchangelog()
1281 return bookmarks.bmstore(self)
1283 return bookmarks.bmstore(self)
1282
1284
1283 def _refreshchangelog(self):
1285 def _refreshchangelog(self):
1284 """make sure the in memory changelog match the on-disk one"""
1286 """make sure the in memory changelog match the on-disk one"""
1285 if ('changelog' in vars(self) and self.currenttransaction() is None):
1287 if ('changelog' in vars(self) and self.currenttransaction() is None):
1286 del self.changelog
1288 del self.changelog
1287
1289
1288 @property
1290 @property
1289 def _activebookmark(self):
1291 def _activebookmark(self):
1290 return self._bookmarks.active
1292 return self._bookmarks.active
1291
1293
1292 # _phasesets depend on changelog. what we need is to call
1294 # _phasesets depend on changelog. what we need is to call
1293 # _phasecache.invalidate() if '00changelog.i' was changed, but it
1295 # _phasecache.invalidate() if '00changelog.i' was changed, but it
1294 # can't be easily expressed in filecache mechanism.
1296 # can't be easily expressed in filecache mechanism.
1295 @storecache('phaseroots', '00changelog.i')
1297 @storecache('phaseroots', '00changelog.i')
1296 def _phasecache(self):
1298 def _phasecache(self):
1297 return phases.phasecache(self, self._phasedefaults)
1299 return phases.phasecache(self, self._phasedefaults)
1298
1300
1299 @storecache('obsstore')
1301 @storecache('obsstore')
1300 def obsstore(self):
1302 def obsstore(self):
1301 return obsolete.makestore(self.ui, self)
1303 return obsolete.makestore(self.ui, self)
1302
1304
1303 @storecache('00changelog.i')
1305 @storecache('00changelog.i')
1304 def changelog(self):
1306 def changelog(self):
1305 return self.store.changelog(txnutil.mayhavepending(self.root))
1307 return self.store.changelog(txnutil.mayhavepending(self.root))
1306
1308
1307 @storecache('00manifest.i')
1309 @storecache('00manifest.i')
1308 def manifestlog(self):
1310 def manifestlog(self):
1309 return self.store.manifestlog(self, self._storenarrowmatch)
1311 return self.store.manifestlog(self, self._storenarrowmatch)
1310
1312
1311 @repofilecache('dirstate')
1313 @repofilecache('dirstate')
1312 def dirstate(self):
1314 def dirstate(self):
1313 return self._makedirstate()
1315 return self._makedirstate()
1314
1316
1315 def _makedirstate(self):
1317 def _makedirstate(self):
1316 """Extension point for wrapping the dirstate per-repo."""
1318 """Extension point for wrapping the dirstate per-repo."""
1317 sparsematchfn = lambda: sparse.matcher(self)
1319 sparsematchfn = lambda: sparse.matcher(self)
1318
1320
1319 return dirstate.dirstate(self.vfs, self.ui, self.root,
1321 return dirstate.dirstate(self.vfs, self.ui, self.root,
1320 self._dirstatevalidate, sparsematchfn)
1322 self._dirstatevalidate, sparsematchfn)
1321
1323
1322 def _dirstatevalidate(self, node):
1324 def _dirstatevalidate(self, node):
1323 try:
1325 try:
1324 self.changelog.rev(node)
1326 self.changelog.rev(node)
1325 return node
1327 return node
1326 except error.LookupError:
1328 except error.LookupError:
1327 if not self._dirstatevalidatewarned:
1329 if not self._dirstatevalidatewarned:
1328 self._dirstatevalidatewarned = True
1330 self._dirstatevalidatewarned = True
1329 self.ui.warn(_("warning: ignoring unknown"
1331 self.ui.warn(_("warning: ignoring unknown"
1330 " working parent %s!\n") % short(node))
1332 " working parent %s!\n") % short(node))
1331 return nullid
1333 return nullid
1332
1334
1333 @storecache(narrowspec.FILENAME)
1335 @storecache(narrowspec.FILENAME)
1334 def narrowpats(self):
1336 def narrowpats(self):
1335 """matcher patterns for this repository's narrowspec
1337 """matcher patterns for this repository's narrowspec
1336
1338
1337 A tuple of (includes, excludes).
1339 A tuple of (includes, excludes).
1338 """
1340 """
1339 return narrowspec.load(self)
1341 return narrowspec.load(self)
1340
1342
1341 @storecache(narrowspec.FILENAME)
1343 @storecache(narrowspec.FILENAME)
1342 def _storenarrowmatch(self):
1344 def _storenarrowmatch(self):
1343 if repository.NARROW_REQUIREMENT not in self.requirements:
1345 if repository.NARROW_REQUIREMENT not in self.requirements:
1344 return matchmod.always()
1346 return matchmod.always()
1345 include, exclude = self.narrowpats
1347 include, exclude = self.narrowpats
1346 return narrowspec.match(self.root, include=include, exclude=exclude)
1348 return narrowspec.match(self.root, include=include, exclude=exclude)
1347
1349
1348 @storecache(narrowspec.FILENAME)
1350 @storecache(narrowspec.FILENAME)
1349 def _narrowmatch(self):
1351 def _narrowmatch(self):
1350 if repository.NARROW_REQUIREMENT not in self.requirements:
1352 if repository.NARROW_REQUIREMENT not in self.requirements:
1351 return matchmod.always()
1353 return matchmod.always()
1352 narrowspec.checkworkingcopynarrowspec(self)
1354 narrowspec.checkworkingcopynarrowspec(self)
1353 include, exclude = self.narrowpats
1355 include, exclude = self.narrowpats
1354 return narrowspec.match(self.root, include=include, exclude=exclude)
1356 return narrowspec.match(self.root, include=include, exclude=exclude)
1355
1357
1356 def narrowmatch(self, match=None, includeexact=False):
1358 def narrowmatch(self, match=None, includeexact=False):
1357 """matcher corresponding the the repo's narrowspec
1359 """matcher corresponding the the repo's narrowspec
1358
1360
1359 If `match` is given, then that will be intersected with the narrow
1361 If `match` is given, then that will be intersected with the narrow
1360 matcher.
1362 matcher.
1361
1363
1362 If `includeexact` is True, then any exact matches from `match` will
1364 If `includeexact` is True, then any exact matches from `match` will
1363 be included even if they're outside the narrowspec.
1365 be included even if they're outside the narrowspec.
1364 """
1366 """
1365 if match:
1367 if match:
1366 if includeexact and not self._narrowmatch.always():
1368 if includeexact and not self._narrowmatch.always():
1367 # do not exclude explicitly-specified paths so that they can
1369 # do not exclude explicitly-specified paths so that they can
1368 # be warned later on
1370 # be warned later on
1369 em = matchmod.exact(match.files())
1371 em = matchmod.exact(match.files())
1370 nm = matchmod.unionmatcher([self._narrowmatch, em])
1372 nm = matchmod.unionmatcher([self._narrowmatch, em])
1371 return matchmod.intersectmatchers(match, nm)
1373 return matchmod.intersectmatchers(match, nm)
1372 return matchmod.intersectmatchers(match, self._narrowmatch)
1374 return matchmod.intersectmatchers(match, self._narrowmatch)
1373 return self._narrowmatch
1375 return self._narrowmatch
1374
1376
1375 def setnarrowpats(self, newincludes, newexcludes):
1377 def setnarrowpats(self, newincludes, newexcludes):
1376 narrowspec.save(self, newincludes, newexcludes)
1378 narrowspec.save(self, newincludes, newexcludes)
1377 self.invalidate(clearfilecache=True)
1379 self.invalidate(clearfilecache=True)
1378
1380
1379 def __getitem__(self, changeid):
1381 def __getitem__(self, changeid):
1380 if changeid is None:
1382 if changeid is None:
1381 return context.workingctx(self)
1383 return context.workingctx(self)
1382 if isinstance(changeid, context.basectx):
1384 if isinstance(changeid, context.basectx):
1383 return changeid
1385 return changeid
1384 if isinstance(changeid, slice):
1386 if isinstance(changeid, slice):
1385 # wdirrev isn't contiguous so the slice shouldn't include it
1387 # wdirrev isn't contiguous so the slice shouldn't include it
1386 return [self[i]
1388 return [self[i]
1387 for i in pycompat.xrange(*changeid.indices(len(self)))
1389 for i in pycompat.xrange(*changeid.indices(len(self)))
1388 if i not in self.changelog.filteredrevs]
1390 if i not in self.changelog.filteredrevs]
1389 try:
1391 try:
1390 if isinstance(changeid, int):
1392 if isinstance(changeid, int):
1391 node = self.changelog.node(changeid)
1393 node = self.changelog.node(changeid)
1392 rev = changeid
1394 rev = changeid
1393 elif changeid == 'null':
1395 elif changeid == 'null':
1394 node = nullid
1396 node = nullid
1395 rev = nullrev
1397 rev = nullrev
1396 elif changeid == 'tip':
1398 elif changeid == 'tip':
1397 node = self.changelog.tip()
1399 node = self.changelog.tip()
1398 rev = self.changelog.rev(node)
1400 rev = self.changelog.rev(node)
1399 elif changeid == '.':
1401 elif changeid == '.':
1400 # this is a hack to delay/avoid loading obsmarkers
1402 # this is a hack to delay/avoid loading obsmarkers
1401 # when we know that '.' won't be hidden
1403 # when we know that '.' won't be hidden
1402 node = self.dirstate.p1()
1404 node = self.dirstate.p1()
1403 rev = self.unfiltered().changelog.rev(node)
1405 rev = self.unfiltered().changelog.rev(node)
1404 elif len(changeid) == 20:
1406 elif len(changeid) == 20:
1405 try:
1407 try:
1406 node = changeid
1408 node = changeid
1407 rev = self.changelog.rev(changeid)
1409 rev = self.changelog.rev(changeid)
1408 except error.FilteredLookupError:
1410 except error.FilteredLookupError:
1409 changeid = hex(changeid) # for the error message
1411 changeid = hex(changeid) # for the error message
1410 raise
1412 raise
1411 except LookupError:
1413 except LookupError:
1412 # check if it might have come from damaged dirstate
1414 # check if it might have come from damaged dirstate
1413 #
1415 #
1414 # XXX we could avoid the unfiltered if we had a recognizable
1416 # XXX we could avoid the unfiltered if we had a recognizable
1415 # exception for filtered changeset access
1417 # exception for filtered changeset access
1416 if (self.local()
1418 if (self.local()
1417 and changeid in self.unfiltered().dirstate.parents()):
1419 and changeid in self.unfiltered().dirstate.parents()):
1418 msg = _("working directory has unknown parent '%s'!")
1420 msg = _("working directory has unknown parent '%s'!")
1419 raise error.Abort(msg % short(changeid))
1421 raise error.Abort(msg % short(changeid))
1420 changeid = hex(changeid) # for the error message
1422 changeid = hex(changeid) # for the error message
1421 raise
1423 raise
1422
1424
1423 elif len(changeid) == 40:
1425 elif len(changeid) == 40:
1424 node = bin(changeid)
1426 node = bin(changeid)
1425 rev = self.changelog.rev(node)
1427 rev = self.changelog.rev(node)
1426 else:
1428 else:
1427 raise error.ProgrammingError(
1429 raise error.ProgrammingError(
1428 "unsupported changeid '%s' of type %s" %
1430 "unsupported changeid '%s' of type %s" %
1429 (changeid, type(changeid)))
1431 (changeid, type(changeid)))
1430
1432
1431 return context.changectx(self, rev, node)
1433 return context.changectx(self, rev, node)
1432
1434
1433 except (error.FilteredIndexError, error.FilteredLookupError):
1435 except (error.FilteredIndexError, error.FilteredLookupError):
1434 raise error.FilteredRepoLookupError(_("filtered revision '%s'")
1436 raise error.FilteredRepoLookupError(_("filtered revision '%s'")
1435 % pycompat.bytestr(changeid))
1437 % pycompat.bytestr(changeid))
1436 except (IndexError, LookupError):
1438 except (IndexError, LookupError):
1437 raise error.RepoLookupError(
1439 raise error.RepoLookupError(
1438 _("unknown revision '%s'") % pycompat.bytestr(changeid))
1440 _("unknown revision '%s'") % pycompat.bytestr(changeid))
1439 except error.WdirUnsupported:
1441 except error.WdirUnsupported:
1440 return context.workingctx(self)
1442 return context.workingctx(self)
1441
1443
1442 def __contains__(self, changeid):
1444 def __contains__(self, changeid):
1443 """True if the given changeid exists
1445 """True if the given changeid exists
1444
1446
1445 error.AmbiguousPrefixLookupError is raised if an ambiguous node
1447 error.AmbiguousPrefixLookupError is raised if an ambiguous node
1446 specified.
1448 specified.
1447 """
1449 """
1448 try:
1450 try:
1449 self[changeid]
1451 self[changeid]
1450 return True
1452 return True
1451 except error.RepoLookupError:
1453 except error.RepoLookupError:
1452 return False
1454 return False
1453
1455
1454 def __nonzero__(self):
1456 def __nonzero__(self):
1455 return True
1457 return True
1456
1458
1457 __bool__ = __nonzero__
1459 __bool__ = __nonzero__
1458
1460
1459 def __len__(self):
1461 def __len__(self):
1460 # no need to pay the cost of repoview.changelog
1462 # no need to pay the cost of repoview.changelog
1461 unfi = self.unfiltered()
1463 unfi = self.unfiltered()
1462 return len(unfi.changelog)
1464 return len(unfi.changelog)
1463
1465
1464 def __iter__(self):
1466 def __iter__(self):
1465 return iter(self.changelog)
1467 return iter(self.changelog)
1466
1468
1467 def revs(self, expr, *args):
1469 def revs(self, expr, *args):
1468 '''Find revisions matching a revset.
1470 '''Find revisions matching a revset.
1469
1471
1470 The revset is specified as a string ``expr`` that may contain
1472 The revset is specified as a string ``expr`` that may contain
1471 %-formatting to escape certain types. See ``revsetlang.formatspec``.
1473 %-formatting to escape certain types. See ``revsetlang.formatspec``.
1472
1474
1473 Revset aliases from the configuration are not expanded. To expand
1475 Revset aliases from the configuration are not expanded. To expand
1474 user aliases, consider calling ``scmutil.revrange()`` or
1476 user aliases, consider calling ``scmutil.revrange()`` or
1475 ``repo.anyrevs([expr], user=True)``.
1477 ``repo.anyrevs([expr], user=True)``.
1476
1478
1477 Returns a revset.abstractsmartset, which is a list-like interface
1479 Returns a revset.abstractsmartset, which is a list-like interface
1478 that contains integer revisions.
1480 that contains integer revisions.
1479 '''
1481 '''
1480 tree = revsetlang.spectree(expr, *args)
1482 tree = revsetlang.spectree(expr, *args)
1481 return revset.makematcher(tree)(self)
1483 return revset.makematcher(tree)(self)
1482
1484
1483 def set(self, expr, *args):
1485 def set(self, expr, *args):
1484 '''Find revisions matching a revset and emit changectx instances.
1486 '''Find revisions matching a revset and emit changectx instances.
1485
1487
1486 This is a convenience wrapper around ``revs()`` that iterates the
1488 This is a convenience wrapper around ``revs()`` that iterates the
1487 result and is a generator of changectx instances.
1489 result and is a generator of changectx instances.
1488
1490
1489 Revset aliases from the configuration are not expanded. To expand
1491 Revset aliases from the configuration are not expanded. To expand
1490 user aliases, consider calling ``scmutil.revrange()``.
1492 user aliases, consider calling ``scmutil.revrange()``.
1491 '''
1493 '''
1492 for r in self.revs(expr, *args):
1494 for r in self.revs(expr, *args):
1493 yield self[r]
1495 yield self[r]
1494
1496
1495 def anyrevs(self, specs, user=False, localalias=None):
1497 def anyrevs(self, specs, user=False, localalias=None):
1496 '''Find revisions matching one of the given revsets.
1498 '''Find revisions matching one of the given revsets.
1497
1499
1498 Revset aliases from the configuration are not expanded by default. To
1500 Revset aliases from the configuration are not expanded by default. To
1499 expand user aliases, specify ``user=True``. To provide some local
1501 expand user aliases, specify ``user=True``. To provide some local
1500 definitions overriding user aliases, set ``localalias`` to
1502 definitions overriding user aliases, set ``localalias`` to
1501 ``{name: definitionstring}``.
1503 ``{name: definitionstring}``.
1502 '''
1504 '''
1503 if user:
1505 if user:
1504 m = revset.matchany(self.ui, specs,
1506 m = revset.matchany(self.ui, specs,
1505 lookup=revset.lookupfn(self),
1507 lookup=revset.lookupfn(self),
1506 localalias=localalias)
1508 localalias=localalias)
1507 else:
1509 else:
1508 m = revset.matchany(None, specs, localalias=localalias)
1510 m = revset.matchany(None, specs, localalias=localalias)
1509 return m(self)
1511 return m(self)
1510
1512
1511 def url(self):
1513 def url(self):
1512 return 'file:' + self.root
1514 return 'file:' + self.root
1513
1515
1514 def hook(self, name, throw=False, **args):
1516 def hook(self, name, throw=False, **args):
1515 """Call a hook, passing this repo instance.
1517 """Call a hook, passing this repo instance.
1516
1518
1517 This a convenience method to aid invoking hooks. Extensions likely
1519 This a convenience method to aid invoking hooks. Extensions likely
1518 won't call this unless they have registered a custom hook or are
1520 won't call this unless they have registered a custom hook or are
1519 replacing code that is expected to call a hook.
1521 replacing code that is expected to call a hook.
1520 """
1522 """
1521 return hook.hook(self.ui, self, name, throw, **args)
1523 return hook.hook(self.ui, self, name, throw, **args)
1522
1524
1523 @filteredpropertycache
1525 @filteredpropertycache
1524 def _tagscache(self):
1526 def _tagscache(self):
1525 '''Returns a tagscache object that contains various tags related
1527 '''Returns a tagscache object that contains various tags related
1526 caches.'''
1528 caches.'''
1527
1529
1528 # This simplifies its cache management by having one decorated
1530 # This simplifies its cache management by having one decorated
1529 # function (this one) and the rest simply fetch things from it.
1531 # function (this one) and the rest simply fetch things from it.
1530 class tagscache(object):
1532 class tagscache(object):
1531 def __init__(self):
1533 def __init__(self):
1532 # These two define the set of tags for this repository. tags
1534 # These two define the set of tags for this repository. tags
1533 # maps tag name to node; tagtypes maps tag name to 'global' or
1535 # maps tag name to node; tagtypes maps tag name to 'global' or
1534 # 'local'. (Global tags are defined by .hgtags across all
1536 # 'local'. (Global tags are defined by .hgtags across all
1535 # heads, and local tags are defined in .hg/localtags.)
1537 # heads, and local tags are defined in .hg/localtags.)
1536 # They constitute the in-memory cache of tags.
1538 # They constitute the in-memory cache of tags.
1537 self.tags = self.tagtypes = None
1539 self.tags = self.tagtypes = None
1538
1540
1539 self.nodetagscache = self.tagslist = None
1541 self.nodetagscache = self.tagslist = None
1540
1542
1541 cache = tagscache()
1543 cache = tagscache()
1542 cache.tags, cache.tagtypes = self._findtags()
1544 cache.tags, cache.tagtypes = self._findtags()
1543
1545
1544 return cache
1546 return cache
1545
1547
1546 def tags(self):
1548 def tags(self):
1547 '''return a mapping of tag to node'''
1549 '''return a mapping of tag to node'''
1548 t = {}
1550 t = {}
1549 if self.changelog.filteredrevs:
1551 if self.changelog.filteredrevs:
1550 tags, tt = self._findtags()
1552 tags, tt = self._findtags()
1551 else:
1553 else:
1552 tags = self._tagscache.tags
1554 tags = self._tagscache.tags
1553 rev = self.changelog.rev
1555 rev = self.changelog.rev
1554 for k, v in tags.iteritems():
1556 for k, v in tags.iteritems():
1555 try:
1557 try:
1556 # ignore tags to unknown nodes
1558 # ignore tags to unknown nodes
1557 rev(v)
1559 rev(v)
1558 t[k] = v
1560 t[k] = v
1559 except (error.LookupError, ValueError):
1561 except (error.LookupError, ValueError):
1560 pass
1562 pass
1561 return t
1563 return t
1562
1564
1563 def _findtags(self):
1565 def _findtags(self):
1564 '''Do the hard work of finding tags. Return a pair of dicts
1566 '''Do the hard work of finding tags. Return a pair of dicts
1565 (tags, tagtypes) where tags maps tag name to node, and tagtypes
1567 (tags, tagtypes) where tags maps tag name to node, and tagtypes
1566 maps tag name to a string like \'global\' or \'local\'.
1568 maps tag name to a string like \'global\' or \'local\'.
1567 Subclasses or extensions are free to add their own tags, but
1569 Subclasses or extensions are free to add their own tags, but
1568 should be aware that the returned dicts will be retained for the
1570 should be aware that the returned dicts will be retained for the
1569 duration of the localrepo object.'''
1571 duration of the localrepo object.'''
1570
1572
1571 # XXX what tagtype should subclasses/extensions use? Currently
1573 # XXX what tagtype should subclasses/extensions use? Currently
1572 # mq and bookmarks add tags, but do not set the tagtype at all.
1574 # mq and bookmarks add tags, but do not set the tagtype at all.
1573 # Should each extension invent its own tag type? Should there
1575 # Should each extension invent its own tag type? Should there
1574 # be one tagtype for all such "virtual" tags? Or is the status
1576 # be one tagtype for all such "virtual" tags? Or is the status
1575 # quo fine?
1577 # quo fine?
1576
1578
1577
1579
1578 # map tag name to (node, hist)
1580 # map tag name to (node, hist)
1579 alltags = tagsmod.findglobaltags(self.ui, self)
1581 alltags = tagsmod.findglobaltags(self.ui, self)
1580 # map tag name to tag type
1582 # map tag name to tag type
1581 tagtypes = dict((tag, 'global') for tag in alltags)
1583 tagtypes = dict((tag, 'global') for tag in alltags)
1582
1584
1583 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
1585 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
1584
1586
1585 # Build the return dicts. Have to re-encode tag names because
1587 # Build the return dicts. Have to re-encode tag names because
1586 # the tags module always uses UTF-8 (in order not to lose info
1588 # the tags module always uses UTF-8 (in order not to lose info
1587 # writing to the cache), but the rest of Mercurial wants them in
1589 # writing to the cache), but the rest of Mercurial wants them in
1588 # local encoding.
1590 # local encoding.
1589 tags = {}
1591 tags = {}
1590 for (name, (node, hist)) in alltags.iteritems():
1592 for (name, (node, hist)) in alltags.iteritems():
1591 if node != nullid:
1593 if node != nullid:
1592 tags[encoding.tolocal(name)] = node
1594 tags[encoding.tolocal(name)] = node
1593 tags['tip'] = self.changelog.tip()
1595 tags['tip'] = self.changelog.tip()
1594 tagtypes = dict([(encoding.tolocal(name), value)
1596 tagtypes = dict([(encoding.tolocal(name), value)
1595 for (name, value) in tagtypes.iteritems()])
1597 for (name, value) in tagtypes.iteritems()])
1596 return (tags, tagtypes)
1598 return (tags, tagtypes)
1597
1599
1598 def tagtype(self, tagname):
1600 def tagtype(self, tagname):
1599 '''
1601 '''
1600 return the type of the given tag. result can be:
1602 return the type of the given tag. result can be:
1601
1603
1602 'local' : a local tag
1604 'local' : a local tag
1603 'global' : a global tag
1605 'global' : a global tag
1604 None : tag does not exist
1606 None : tag does not exist
1605 '''
1607 '''
1606
1608
1607 return self._tagscache.tagtypes.get(tagname)
1609 return self._tagscache.tagtypes.get(tagname)
1608
1610
1609 def tagslist(self):
1611 def tagslist(self):
1610 '''return a list of tags ordered by revision'''
1612 '''return a list of tags ordered by revision'''
1611 if not self._tagscache.tagslist:
1613 if not self._tagscache.tagslist:
1612 l = []
1614 l = []
1613 for t, n in self.tags().iteritems():
1615 for t, n in self.tags().iteritems():
1614 l.append((self.changelog.rev(n), t, n))
1616 l.append((self.changelog.rev(n), t, n))
1615 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
1617 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
1616
1618
1617 return self._tagscache.tagslist
1619 return self._tagscache.tagslist
1618
1620
1619 def nodetags(self, node):
1621 def nodetags(self, node):
1620 '''return the tags associated with a node'''
1622 '''return the tags associated with a node'''
1621 if not self._tagscache.nodetagscache:
1623 if not self._tagscache.nodetagscache:
1622 nodetagscache = {}
1624 nodetagscache = {}
1623 for t, n in self._tagscache.tags.iteritems():
1625 for t, n in self._tagscache.tags.iteritems():
1624 nodetagscache.setdefault(n, []).append(t)
1626 nodetagscache.setdefault(n, []).append(t)
1625 for tags in nodetagscache.itervalues():
1627 for tags in nodetagscache.itervalues():
1626 tags.sort()
1628 tags.sort()
1627 self._tagscache.nodetagscache = nodetagscache
1629 self._tagscache.nodetagscache = nodetagscache
1628 return self._tagscache.nodetagscache.get(node, [])
1630 return self._tagscache.nodetagscache.get(node, [])
1629
1631
1630 def nodebookmarks(self, node):
1632 def nodebookmarks(self, node):
1631 """return the list of bookmarks pointing to the specified node"""
1633 """return the list of bookmarks pointing to the specified node"""
1632 return self._bookmarks.names(node)
1634 return self._bookmarks.names(node)
1633
1635
1634 def branchmap(self):
1636 def branchmap(self):
1635 '''returns a dictionary {branch: [branchheads]} with branchheads
1637 '''returns a dictionary {branch: [branchheads]} with branchheads
1636 ordered by increasing revision number'''
1638 ordered by increasing revision number'''
1637 return self._branchcaches[self]
1639 return self._branchcaches[self]
1638
1640
1639 @unfilteredmethod
1641 @unfilteredmethod
1640 def revbranchcache(self):
1642 def revbranchcache(self):
1641 if not self._revbranchcache:
1643 if not self._revbranchcache:
1642 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
1644 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
1643 return self._revbranchcache
1645 return self._revbranchcache
1644
1646
1645 def branchtip(self, branch, ignoremissing=False):
1647 def branchtip(self, branch, ignoremissing=False):
1646 '''return the tip node for a given branch
1648 '''return the tip node for a given branch
1647
1649
1648 If ignoremissing is True, then this method will not raise an error.
1650 If ignoremissing is True, then this method will not raise an error.
1649 This is helpful for callers that only expect None for a missing branch
1651 This is helpful for callers that only expect None for a missing branch
1650 (e.g. namespace).
1652 (e.g. namespace).
1651
1653
1652 '''
1654 '''
1653 try:
1655 try:
1654 return self.branchmap().branchtip(branch)
1656 return self.branchmap().branchtip(branch)
1655 except KeyError:
1657 except KeyError:
1656 if not ignoremissing:
1658 if not ignoremissing:
1657 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
1659 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
1658 else:
1660 else:
1659 pass
1661 pass
1660
1662
1661 def lookup(self, key):
1663 def lookup(self, key):
1662 node = scmutil.revsymbol(self, key).node()
1664 node = scmutil.revsymbol(self, key).node()
1663 if node is None:
1665 if node is None:
1664 raise error.RepoLookupError(_("unknown revision '%s'") % key)
1666 raise error.RepoLookupError(_("unknown revision '%s'") % key)
1665 return node
1667 return node
1666
1668
1667 def lookupbranch(self, key):
1669 def lookupbranch(self, key):
1668 if self.branchmap().hasbranch(key):
1670 if self.branchmap().hasbranch(key):
1669 return key
1671 return key
1670
1672
1671 return scmutil.revsymbol(self, key).branch()
1673 return scmutil.revsymbol(self, key).branch()
1672
1674
1673 def known(self, nodes):
1675 def known(self, nodes):
1674 cl = self.changelog
1676 cl = self.changelog
1675 nm = cl.nodemap
1677 nm = cl.nodemap
1676 filtered = cl.filteredrevs
1678 filtered = cl.filteredrevs
1677 result = []
1679 result = []
1678 for n in nodes:
1680 for n in nodes:
1679 r = nm.get(n)
1681 r = nm.get(n)
1680 resp = not (r is None or r in filtered)
1682 resp = not (r is None or r in filtered)
1681 result.append(resp)
1683 result.append(resp)
1682 return result
1684 return result
1683
1685
1684 def local(self):
1686 def local(self):
1685 return self
1687 return self
1686
1688
1687 def publishing(self):
1689 def publishing(self):
1688 # it's safe (and desirable) to trust the publish flag unconditionally
1690 # it's safe (and desirable) to trust the publish flag unconditionally
1689 # so that we don't finalize changes shared between users via ssh or nfs
1691 # so that we don't finalize changes shared between users via ssh or nfs
1690 return self.ui.configbool('phases', 'publish', untrusted=True)
1692 return self.ui.configbool('phases', 'publish', untrusted=True)
1691
1693
1692 def cancopy(self):
1694 def cancopy(self):
1693 # so statichttprepo's override of local() works
1695 # so statichttprepo's override of local() works
1694 if not self.local():
1696 if not self.local():
1695 return False
1697 return False
1696 if not self.publishing():
1698 if not self.publishing():
1697 return True
1699 return True
1698 # if publishing we can't copy if there is filtered content
1700 # if publishing we can't copy if there is filtered content
1699 return not self.filtered('visible').changelog.filteredrevs
1701 return not self.filtered('visible').changelog.filteredrevs
1700
1702
1701 def shared(self):
1703 def shared(self):
1702 '''the type of shared repository (None if not shared)'''
1704 '''the type of shared repository (None if not shared)'''
1703 if self.sharedpath != self.path:
1705 if self.sharedpath != self.path:
1704 return 'store'
1706 return 'store'
1705 return None
1707 return None
1706
1708
1707 def wjoin(self, f, *insidef):
1709 def wjoin(self, f, *insidef):
1708 return self.vfs.reljoin(self.root, f, *insidef)
1710 return self.vfs.reljoin(self.root, f, *insidef)
1709
1711
1710 def setparents(self, p1, p2=nullid):
1712 def setparents(self, p1, p2=nullid):
1711 with self.dirstate.parentchange():
1713 with self.dirstate.parentchange():
1712 copies = self.dirstate.setparents(p1, p2)
1714 copies = self.dirstate.setparents(p1, p2)
1713 pctx = self[p1]
1715 pctx = self[p1]
1714 if copies:
1716 if copies:
1715 # Adjust copy records, the dirstate cannot do it, it
1717 # Adjust copy records, the dirstate cannot do it, it
1716 # requires access to parents manifests. Preserve them
1718 # requires access to parents manifests. Preserve them
1717 # only for entries added to first parent.
1719 # only for entries added to first parent.
1718 for f in copies:
1720 for f in copies:
1719 if f not in pctx and copies[f] in pctx:
1721 if f not in pctx and copies[f] in pctx:
1720 self.dirstate.copy(copies[f], f)
1722 self.dirstate.copy(copies[f], f)
1721 if p2 == nullid:
1723 if p2 == nullid:
1722 for f, s in sorted(self.dirstate.copies().items()):
1724 for f, s in sorted(self.dirstate.copies().items()):
1723 if f not in pctx and s not in pctx:
1725 if f not in pctx and s not in pctx:
1724 self.dirstate.copy(None, f)
1726 self.dirstate.copy(None, f)
1725
1727
1726 def filectx(self, path, changeid=None, fileid=None, changectx=None):
1728 def filectx(self, path, changeid=None, fileid=None, changectx=None):
1727 """changeid must be a changeset revision, if specified.
1729 """changeid must be a changeset revision, if specified.
1728 fileid can be a file revision or node."""
1730 fileid can be a file revision or node."""
1729 return context.filectx(self, path, changeid, fileid,
1731 return context.filectx(self, path, changeid, fileid,
1730 changectx=changectx)
1732 changectx=changectx)
1731
1733
1732 def getcwd(self):
1734 def getcwd(self):
1733 return self.dirstate.getcwd()
1735 return self.dirstate.getcwd()
1734
1736
1735 def pathto(self, f, cwd=None):
1737 def pathto(self, f, cwd=None):
1736 return self.dirstate.pathto(f, cwd)
1738 return self.dirstate.pathto(f, cwd)
1737
1739
1738 def _loadfilter(self, filter):
1740 def _loadfilter(self, filter):
1739 if filter not in self._filterpats:
1741 if filter not in self._filterpats:
1740 l = []
1742 l = []
1741 for pat, cmd in self.ui.configitems(filter):
1743 for pat, cmd in self.ui.configitems(filter):
1742 if cmd == '!':
1744 if cmd == '!':
1743 continue
1745 continue
1744 mf = matchmod.match(self.root, '', [pat])
1746 mf = matchmod.match(self.root, '', [pat])
1745 fn = None
1747 fn = None
1746 params = cmd
1748 params = cmd
1747 for name, filterfn in self._datafilters.iteritems():
1749 for name, filterfn in self._datafilters.iteritems():
1748 if cmd.startswith(name):
1750 if cmd.startswith(name):
1749 fn = filterfn
1751 fn = filterfn
1750 params = cmd[len(name):].lstrip()
1752 params = cmd[len(name):].lstrip()
1751 break
1753 break
1752 if not fn:
1754 if not fn:
1753 fn = lambda s, c, **kwargs: procutil.filter(s, c)
1755 fn = lambda s, c, **kwargs: procutil.filter(s, c)
1754 # Wrap old filters not supporting keyword arguments
1756 # Wrap old filters not supporting keyword arguments
1755 if not pycompat.getargspec(fn)[2]:
1757 if not pycompat.getargspec(fn)[2]:
1756 oldfn = fn
1758 oldfn = fn
1757 fn = lambda s, c, **kwargs: oldfn(s, c)
1759 fn = lambda s, c, **kwargs: oldfn(s, c)
1758 l.append((mf, fn, params))
1760 l.append((mf, fn, params))
1759 self._filterpats[filter] = l
1761 self._filterpats[filter] = l
1760 return self._filterpats[filter]
1762 return self._filterpats[filter]
1761
1763
1762 def _filter(self, filterpats, filename, data):
1764 def _filter(self, filterpats, filename, data):
1763 for mf, fn, cmd in filterpats:
1765 for mf, fn, cmd in filterpats:
1764 if mf(filename):
1766 if mf(filename):
1765 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
1767 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
1766 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
1768 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
1767 break
1769 break
1768
1770
1769 return data
1771 return data
1770
1772
1771 @unfilteredpropertycache
1773 @unfilteredpropertycache
1772 def _encodefilterpats(self):
1774 def _encodefilterpats(self):
1773 return self._loadfilter('encode')
1775 return self._loadfilter('encode')
1774
1776
1775 @unfilteredpropertycache
1777 @unfilteredpropertycache
1776 def _decodefilterpats(self):
1778 def _decodefilterpats(self):
1777 return self._loadfilter('decode')
1779 return self._loadfilter('decode')
1778
1780
1779 def adddatafilter(self, name, filter):
1781 def adddatafilter(self, name, filter):
1780 self._datafilters[name] = filter
1782 self._datafilters[name] = filter
1781
1783
1782 def wread(self, filename):
1784 def wread(self, filename):
1783 if self.wvfs.islink(filename):
1785 if self.wvfs.islink(filename):
1784 data = self.wvfs.readlink(filename)
1786 data = self.wvfs.readlink(filename)
1785 else:
1787 else:
1786 data = self.wvfs.read(filename)
1788 data = self.wvfs.read(filename)
1787 return self._filter(self._encodefilterpats, filename, data)
1789 return self._filter(self._encodefilterpats, filename, data)
1788
1790
1789 def wwrite(self, filename, data, flags, backgroundclose=False, **kwargs):
1791 def wwrite(self, filename, data, flags, backgroundclose=False, **kwargs):
1790 """write ``data`` into ``filename`` in the working directory
1792 """write ``data`` into ``filename`` in the working directory
1791
1793
1792 This returns length of written (maybe decoded) data.
1794 This returns length of written (maybe decoded) data.
1793 """
1795 """
1794 data = self._filter(self._decodefilterpats, filename, data)
1796 data = self._filter(self._decodefilterpats, filename, data)
1795 if 'l' in flags:
1797 if 'l' in flags:
1796 self.wvfs.symlink(data, filename)
1798 self.wvfs.symlink(data, filename)
1797 else:
1799 else:
1798 self.wvfs.write(filename, data, backgroundclose=backgroundclose,
1800 self.wvfs.write(filename, data, backgroundclose=backgroundclose,
1799 **kwargs)
1801 **kwargs)
1800 if 'x' in flags:
1802 if 'x' in flags:
1801 self.wvfs.setflags(filename, False, True)
1803 self.wvfs.setflags(filename, False, True)
1802 else:
1804 else:
1803 self.wvfs.setflags(filename, False, False)
1805 self.wvfs.setflags(filename, False, False)
1804 return len(data)
1806 return len(data)
1805
1807
1806 def wwritedata(self, filename, data):
1808 def wwritedata(self, filename, data):
1807 return self._filter(self._decodefilterpats, filename, data)
1809 return self._filter(self._decodefilterpats, filename, data)
1808
1810
1809 def currenttransaction(self):
1811 def currenttransaction(self):
1810 """return the current transaction or None if non exists"""
1812 """return the current transaction or None if non exists"""
1811 if self._transref:
1813 if self._transref:
1812 tr = self._transref()
1814 tr = self._transref()
1813 else:
1815 else:
1814 tr = None
1816 tr = None
1815
1817
1816 if tr and tr.running():
1818 if tr and tr.running():
1817 return tr
1819 return tr
1818 return None
1820 return None
1819
1821
1820 def transaction(self, desc, report=None):
1822 def transaction(self, desc, report=None):
1821 if (self.ui.configbool('devel', 'all-warnings')
1823 if (self.ui.configbool('devel', 'all-warnings')
1822 or self.ui.configbool('devel', 'check-locks')):
1824 or self.ui.configbool('devel', 'check-locks')):
1823 if self._currentlock(self._lockref) is None:
1825 if self._currentlock(self._lockref) is None:
1824 raise error.ProgrammingError('transaction requires locking')
1826 raise error.ProgrammingError('transaction requires locking')
1825 tr = self.currenttransaction()
1827 tr = self.currenttransaction()
1826 if tr is not None:
1828 if tr is not None:
1827 return tr.nest(name=desc)
1829 return tr.nest(name=desc)
1828
1830
1829 # abort here if the journal already exists
1831 # abort here if the journal already exists
1830 if self.svfs.exists("journal"):
1832 if self.svfs.exists("journal"):
1831 raise error.RepoError(
1833 raise error.RepoError(
1832 _("abandoned transaction found"),
1834 _("abandoned transaction found"),
1833 hint=_("run 'hg recover' to clean up transaction"))
1835 hint=_("run 'hg recover' to clean up transaction"))
1834
1836
1835 idbase = "%.40f#%f" % (random.random(), time.time())
1837 idbase = "%.40f#%f" % (random.random(), time.time())
1836 ha = hex(hashlib.sha1(idbase).digest())
1838 ha = hex(hashlib.sha1(idbase).digest())
1837 txnid = 'TXN:' + ha
1839 txnid = 'TXN:' + ha
1838 self.hook('pretxnopen', throw=True, txnname=desc, txnid=txnid)
1840 self.hook('pretxnopen', throw=True, txnname=desc, txnid=txnid)
1839
1841
1840 self._writejournal(desc)
1842 self._writejournal(desc)
1841 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
1843 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
1842 if report:
1844 if report:
1843 rp = report
1845 rp = report
1844 else:
1846 else:
1845 rp = self.ui.warn
1847 rp = self.ui.warn
1846 vfsmap = {'plain': self.vfs, 'store': self.svfs} # root of .hg/
1848 vfsmap = {'plain': self.vfs, 'store': self.svfs} # root of .hg/
1847 # we must avoid cyclic reference between repo and transaction.
1849 # we must avoid cyclic reference between repo and transaction.
1848 reporef = weakref.ref(self)
1850 reporef = weakref.ref(self)
1849 # Code to track tag movement
1851 # Code to track tag movement
1850 #
1852 #
1851 # Since tags are all handled as file content, it is actually quite hard
1853 # Since tags are all handled as file content, it is actually quite hard
1852 # to track these movement from a code perspective. So we fallback to a
1854 # to track these movement from a code perspective. So we fallback to a
1853 # tracking at the repository level. One could envision to track changes
1855 # tracking at the repository level. One could envision to track changes
1854 # to the '.hgtags' file through changegroup apply but that fails to
1856 # to the '.hgtags' file through changegroup apply but that fails to
1855 # cope with case where transaction expose new heads without changegroup
1857 # cope with case where transaction expose new heads without changegroup
1856 # being involved (eg: phase movement).
1858 # being involved (eg: phase movement).
1857 #
1859 #
1858 # For now, We gate the feature behind a flag since this likely comes
1860 # For now, We gate the feature behind a flag since this likely comes
1859 # with performance impacts. The current code run more often than needed
1861 # with performance impacts. The current code run more often than needed
1860 # and do not use caches as much as it could. The current focus is on
1862 # and do not use caches as much as it could. The current focus is on
1861 # the behavior of the feature so we disable it by default. The flag
1863 # the behavior of the feature so we disable it by default. The flag
1862 # will be removed when we are happy with the performance impact.
1864 # will be removed when we are happy with the performance impact.
1863 #
1865 #
1864 # Once this feature is no longer experimental move the following
1866 # Once this feature is no longer experimental move the following
1865 # documentation to the appropriate help section:
1867 # documentation to the appropriate help section:
1866 #
1868 #
1867 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
1869 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
1868 # tags (new or changed or deleted tags). In addition the details of
1870 # tags (new or changed or deleted tags). In addition the details of
1869 # these changes are made available in a file at:
1871 # these changes are made available in a file at:
1870 # ``REPOROOT/.hg/changes/tags.changes``.
1872 # ``REPOROOT/.hg/changes/tags.changes``.
1871 # Make sure you check for HG_TAG_MOVED before reading that file as it
1873 # Make sure you check for HG_TAG_MOVED before reading that file as it
1872 # might exist from a previous transaction even if no tag were touched
1874 # might exist from a previous transaction even if no tag were touched
1873 # in this one. Changes are recorded in a line base format::
1875 # in this one. Changes are recorded in a line base format::
1874 #
1876 #
1875 # <action> <hex-node> <tag-name>\n
1877 # <action> <hex-node> <tag-name>\n
1876 #
1878 #
1877 # Actions are defined as follow:
1879 # Actions are defined as follow:
1878 # "-R": tag is removed,
1880 # "-R": tag is removed,
1879 # "+A": tag is added,
1881 # "+A": tag is added,
1880 # "-M": tag is moved (old value),
1882 # "-M": tag is moved (old value),
1881 # "+M": tag is moved (new value),
1883 # "+M": tag is moved (new value),
1882 tracktags = lambda x: None
1884 tracktags = lambda x: None
1883 # experimental config: experimental.hook-track-tags
1885 # experimental config: experimental.hook-track-tags
1884 shouldtracktags = self.ui.configbool('experimental', 'hook-track-tags')
1886 shouldtracktags = self.ui.configbool('experimental', 'hook-track-tags')
1885 if desc != 'strip' and shouldtracktags:
1887 if desc != 'strip' and shouldtracktags:
1886 oldheads = self.changelog.headrevs()
1888 oldheads = self.changelog.headrevs()
1887 def tracktags(tr2):
1889 def tracktags(tr2):
1888 repo = reporef()
1890 repo = reporef()
1889 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
1891 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
1890 newheads = repo.changelog.headrevs()
1892 newheads = repo.changelog.headrevs()
1891 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
1893 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
1892 # notes: we compare lists here.
1894 # notes: we compare lists here.
1893 # As we do it only once buiding set would not be cheaper
1895 # As we do it only once buiding set would not be cheaper
1894 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
1896 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
1895 if changes:
1897 if changes:
1896 tr2.hookargs['tag_moved'] = '1'
1898 tr2.hookargs['tag_moved'] = '1'
1897 with repo.vfs('changes/tags.changes', 'w',
1899 with repo.vfs('changes/tags.changes', 'w',
1898 atomictemp=True) as changesfile:
1900 atomictemp=True) as changesfile:
1899 # note: we do not register the file to the transaction
1901 # note: we do not register the file to the transaction
1900 # because we needs it to still exist on the transaction
1902 # because we needs it to still exist on the transaction
1901 # is close (for txnclose hooks)
1903 # is close (for txnclose hooks)
1902 tagsmod.writediff(changesfile, changes)
1904 tagsmod.writediff(changesfile, changes)
1903 def validate(tr2):
1905 def validate(tr2):
1904 """will run pre-closing hooks"""
1906 """will run pre-closing hooks"""
1905 # XXX the transaction API is a bit lacking here so we take a hacky
1907 # XXX the transaction API is a bit lacking here so we take a hacky
1906 # path for now
1908 # path for now
1907 #
1909 #
1908 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
1910 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
1909 # dict is copied before these run. In addition we needs the data
1911 # dict is copied before these run. In addition we needs the data
1910 # available to in memory hooks too.
1912 # available to in memory hooks too.
1911 #
1913 #
1912 # Moreover, we also need to make sure this runs before txnclose
1914 # Moreover, we also need to make sure this runs before txnclose
1913 # hooks and there is no "pending" mechanism that would execute
1915 # hooks and there is no "pending" mechanism that would execute
1914 # logic only if hooks are about to run.
1916 # logic only if hooks are about to run.
1915 #
1917 #
1916 # Fixing this limitation of the transaction is also needed to track
1918 # Fixing this limitation of the transaction is also needed to track
1917 # other families of changes (bookmarks, phases, obsolescence).
1919 # other families of changes (bookmarks, phases, obsolescence).
1918 #
1920 #
1919 # This will have to be fixed before we remove the experimental
1921 # This will have to be fixed before we remove the experimental
1920 # gating.
1922 # gating.
1921 tracktags(tr2)
1923 tracktags(tr2)
1922 repo = reporef()
1924 repo = reporef()
1923
1925
1924 r = repo.ui.configsuboptions('experimental',
1926 r = repo.ui.configsuboptions('experimental',
1925 'single-head-per-branch')
1927 'single-head-per-branch')
1926 singlehead, singleheadsub = r
1928 singlehead, singleheadsub = r
1927 if singlehead:
1929 if singlehead:
1928 accountclosed = singleheadsub.get("account-closed-heads", False)
1930 accountclosed = singleheadsub.get("account-closed-heads", False)
1929 scmutil.enforcesinglehead(repo, tr2, desc, accountclosed)
1931 scmutil.enforcesinglehead(repo, tr2, desc, accountclosed)
1930 if hook.hashook(repo.ui, 'pretxnclose-bookmark'):
1932 if hook.hashook(repo.ui, 'pretxnclose-bookmark'):
1931 for name, (old, new) in sorted(tr.changes['bookmarks'].items()):
1933 for name, (old, new) in sorted(tr.changes['bookmarks'].items()):
1932 args = tr.hookargs.copy()
1934 args = tr.hookargs.copy()
1933 args.update(bookmarks.preparehookargs(name, old, new))
1935 args.update(bookmarks.preparehookargs(name, old, new))
1934 repo.hook('pretxnclose-bookmark', throw=True,
1936 repo.hook('pretxnclose-bookmark', throw=True,
1935 **pycompat.strkwargs(args))
1937 **pycompat.strkwargs(args))
1936 if hook.hashook(repo.ui, 'pretxnclose-phase'):
1938 if hook.hashook(repo.ui, 'pretxnclose-phase'):
1937 cl = repo.unfiltered().changelog
1939 cl = repo.unfiltered().changelog
1938 for rev, (old, new) in tr.changes['phases'].items():
1940 for rev, (old, new) in tr.changes['phases'].items():
1939 args = tr.hookargs.copy()
1941 args = tr.hookargs.copy()
1940 node = hex(cl.node(rev))
1942 node = hex(cl.node(rev))
1941 args.update(phases.preparehookargs(node, old, new))
1943 args.update(phases.preparehookargs(node, old, new))
1942 repo.hook('pretxnclose-phase', throw=True,
1944 repo.hook('pretxnclose-phase', throw=True,
1943 **pycompat.strkwargs(args))
1945 **pycompat.strkwargs(args))
1944
1946
1945 repo.hook('pretxnclose', throw=True,
1947 repo.hook('pretxnclose', throw=True,
1946 **pycompat.strkwargs(tr.hookargs))
1948 **pycompat.strkwargs(tr.hookargs))
1947 def releasefn(tr, success):
1949 def releasefn(tr, success):
1948 repo = reporef()
1950 repo = reporef()
1949 if repo is None:
1951 if repo is None:
1950 # If the repo has been GC'd (and this release function is being
1952 # If the repo has been GC'd (and this release function is being
1951 # called from transaction.__del__), there's not much we can do,
1953 # called from transaction.__del__), there's not much we can do,
1952 # so just leave the unfinished transaction there and let the
1954 # so just leave the unfinished transaction there and let the
1953 # user run `hg recover`.
1955 # user run `hg recover`.
1954 return
1956 return
1955 if success:
1957 if success:
1956 # this should be explicitly invoked here, because
1958 # this should be explicitly invoked here, because
1957 # in-memory changes aren't written out at closing
1959 # in-memory changes aren't written out at closing
1958 # transaction, if tr.addfilegenerator (via
1960 # transaction, if tr.addfilegenerator (via
1959 # dirstate.write or so) isn't invoked while
1961 # dirstate.write or so) isn't invoked while
1960 # transaction running
1962 # transaction running
1961 repo.dirstate.write(None)
1963 repo.dirstate.write(None)
1962 else:
1964 else:
1963 # discard all changes (including ones already written
1965 # discard all changes (including ones already written
1964 # out) in this transaction
1966 # out) in this transaction
1965 narrowspec.restorebackup(self, 'journal.narrowspec')
1967 narrowspec.restorebackup(self, 'journal.narrowspec')
1966 narrowspec.restorewcbackup(self, 'journal.narrowspec.dirstate')
1968 narrowspec.restorewcbackup(self, 'journal.narrowspec.dirstate')
1967 repo.dirstate.restorebackup(None, 'journal.dirstate')
1969 repo.dirstate.restorebackup(None, 'journal.dirstate')
1968
1970
1969 repo.invalidate(clearfilecache=True)
1971 repo.invalidate(clearfilecache=True)
1970
1972
1971 tr = transaction.transaction(rp, self.svfs, vfsmap,
1973 tr = transaction.transaction(rp, self.svfs, vfsmap,
1972 "journal",
1974 "journal",
1973 "undo",
1975 "undo",
1974 aftertrans(renames),
1976 aftertrans(renames),
1975 self.store.createmode,
1977 self.store.createmode,
1976 validator=validate,
1978 validator=validate,
1977 releasefn=releasefn,
1979 releasefn=releasefn,
1978 checkambigfiles=_cachedfiles,
1980 checkambigfiles=_cachedfiles,
1979 name=desc)
1981 name=desc)
1980 tr.changes['origrepolen'] = len(self)
1982 tr.changes['origrepolen'] = len(self)
1981 tr.changes['obsmarkers'] = set()
1983 tr.changes['obsmarkers'] = set()
1982 tr.changes['phases'] = {}
1984 tr.changes['phases'] = {}
1983 tr.changes['bookmarks'] = {}
1985 tr.changes['bookmarks'] = {}
1984
1986
1985 tr.hookargs['txnid'] = txnid
1987 tr.hookargs['txnid'] = txnid
1986 tr.hookargs['txnname'] = desc
1988 tr.hookargs['txnname'] = desc
1987 # note: writing the fncache only during finalize mean that the file is
1989 # note: writing the fncache only during finalize mean that the file is
1988 # outdated when running hooks. As fncache is used for streaming clone,
1990 # outdated when running hooks. As fncache is used for streaming clone,
1989 # this is not expected to break anything that happen during the hooks.
1991 # this is not expected to break anything that happen during the hooks.
1990 tr.addfinalize('flush-fncache', self.store.write)
1992 tr.addfinalize('flush-fncache', self.store.write)
1991 def txnclosehook(tr2):
1993 def txnclosehook(tr2):
1992 """To be run if transaction is successful, will schedule a hook run
1994 """To be run if transaction is successful, will schedule a hook run
1993 """
1995 """
1994 # Don't reference tr2 in hook() so we don't hold a reference.
1996 # Don't reference tr2 in hook() so we don't hold a reference.
1995 # This reduces memory consumption when there are multiple
1997 # This reduces memory consumption when there are multiple
1996 # transactions per lock. This can likely go away if issue5045
1998 # transactions per lock. This can likely go away if issue5045
1997 # fixes the function accumulation.
1999 # fixes the function accumulation.
1998 hookargs = tr2.hookargs
2000 hookargs = tr2.hookargs
1999
2001
2000 def hookfunc():
2002 def hookfunc():
2001 repo = reporef()
2003 repo = reporef()
2002 if hook.hashook(repo.ui, 'txnclose-bookmark'):
2004 if hook.hashook(repo.ui, 'txnclose-bookmark'):
2003 bmchanges = sorted(tr.changes['bookmarks'].items())
2005 bmchanges = sorted(tr.changes['bookmarks'].items())
2004 for name, (old, new) in bmchanges:
2006 for name, (old, new) in bmchanges:
2005 args = tr.hookargs.copy()
2007 args = tr.hookargs.copy()
2006 args.update(bookmarks.preparehookargs(name, old, new))
2008 args.update(bookmarks.preparehookargs(name, old, new))
2007 repo.hook('txnclose-bookmark', throw=False,
2009 repo.hook('txnclose-bookmark', throw=False,
2008 **pycompat.strkwargs(args))
2010 **pycompat.strkwargs(args))
2009
2011
2010 if hook.hashook(repo.ui, 'txnclose-phase'):
2012 if hook.hashook(repo.ui, 'txnclose-phase'):
2011 cl = repo.unfiltered().changelog
2013 cl = repo.unfiltered().changelog
2012 phasemv = sorted(tr.changes['phases'].items())
2014 phasemv = sorted(tr.changes['phases'].items())
2013 for rev, (old, new) in phasemv:
2015 for rev, (old, new) in phasemv:
2014 args = tr.hookargs.copy()
2016 args = tr.hookargs.copy()
2015 node = hex(cl.node(rev))
2017 node = hex(cl.node(rev))
2016 args.update(phases.preparehookargs(node, old, new))
2018 args.update(phases.preparehookargs(node, old, new))
2017 repo.hook('txnclose-phase', throw=False,
2019 repo.hook('txnclose-phase', throw=False,
2018 **pycompat.strkwargs(args))
2020 **pycompat.strkwargs(args))
2019
2021
2020 repo.hook('txnclose', throw=False,
2022 repo.hook('txnclose', throw=False,
2021 **pycompat.strkwargs(hookargs))
2023 **pycompat.strkwargs(hookargs))
2022 reporef()._afterlock(hookfunc)
2024 reporef()._afterlock(hookfunc)
2023 tr.addfinalize('txnclose-hook', txnclosehook)
2025 tr.addfinalize('txnclose-hook', txnclosehook)
2024 # Include a leading "-" to make it happen before the transaction summary
2026 # Include a leading "-" to make it happen before the transaction summary
2025 # reports registered via scmutil.registersummarycallback() whose names
2027 # reports registered via scmutil.registersummarycallback() whose names
2026 # are 00-txnreport etc. That way, the caches will be warm when the
2028 # are 00-txnreport etc. That way, the caches will be warm when the
2027 # callbacks run.
2029 # callbacks run.
2028 tr.addpostclose('-warm-cache', self._buildcacheupdater(tr))
2030 tr.addpostclose('-warm-cache', self._buildcacheupdater(tr))
2029 def txnaborthook(tr2):
2031 def txnaborthook(tr2):
2030 """To be run if transaction is aborted
2032 """To be run if transaction is aborted
2031 """
2033 """
2032 reporef().hook('txnabort', throw=False,
2034 reporef().hook('txnabort', throw=False,
2033 **pycompat.strkwargs(tr2.hookargs))
2035 **pycompat.strkwargs(tr2.hookargs))
2034 tr.addabort('txnabort-hook', txnaborthook)
2036 tr.addabort('txnabort-hook', txnaborthook)
2035 # avoid eager cache invalidation. in-memory data should be identical
2037 # avoid eager cache invalidation. in-memory data should be identical
2036 # to stored data if transaction has no error.
2038 # to stored data if transaction has no error.
2037 tr.addpostclose('refresh-filecachestats', self._refreshfilecachestats)
2039 tr.addpostclose('refresh-filecachestats', self._refreshfilecachestats)
2038 self._transref = weakref.ref(tr)
2040 self._transref = weakref.ref(tr)
2039 scmutil.registersummarycallback(self, tr, desc)
2041 scmutil.registersummarycallback(self, tr, desc)
2040 return tr
2042 return tr
2041
2043
2042 def _journalfiles(self):
2044 def _journalfiles(self):
2043 return ((self.svfs, 'journal'),
2045 return ((self.svfs, 'journal'),
2044 (self.svfs, 'journal.narrowspec'),
2046 (self.svfs, 'journal.narrowspec'),
2045 (self.vfs, 'journal.narrowspec.dirstate'),
2047 (self.vfs, 'journal.narrowspec.dirstate'),
2046 (self.vfs, 'journal.dirstate'),
2048 (self.vfs, 'journal.dirstate'),
2047 (self.vfs, 'journal.branch'),
2049 (self.vfs, 'journal.branch'),
2048 (self.vfs, 'journal.desc'),
2050 (self.vfs, 'journal.desc'),
2049 (bookmarks.bookmarksvfs(self), 'journal.bookmarks'),
2051 (bookmarks.bookmarksvfs(self), 'journal.bookmarks'),
2050 (self.svfs, 'journal.phaseroots'))
2052 (self.svfs, 'journal.phaseroots'))
2051
2053
2052 def undofiles(self):
2054 def undofiles(self):
2053 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
2055 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
2054
2056
2055 @unfilteredmethod
2057 @unfilteredmethod
2056 def _writejournal(self, desc):
2058 def _writejournal(self, desc):
2057 self.dirstate.savebackup(None, 'journal.dirstate')
2059 self.dirstate.savebackup(None, 'journal.dirstate')
2058 narrowspec.savewcbackup(self, 'journal.narrowspec.dirstate')
2060 narrowspec.savewcbackup(self, 'journal.narrowspec.dirstate')
2059 narrowspec.savebackup(self, 'journal.narrowspec')
2061 narrowspec.savebackup(self, 'journal.narrowspec')
2060 self.vfs.write("journal.branch",
2062 self.vfs.write("journal.branch",
2061 encoding.fromlocal(self.dirstate.branch()))
2063 encoding.fromlocal(self.dirstate.branch()))
2062 self.vfs.write("journal.desc",
2064 self.vfs.write("journal.desc",
2063 "%d\n%s\n" % (len(self), desc))
2065 "%d\n%s\n" % (len(self), desc))
2064 bookmarksvfs = bookmarks.bookmarksvfs(self)
2066 bookmarksvfs = bookmarks.bookmarksvfs(self)
2065 bookmarksvfs.write("journal.bookmarks",
2067 bookmarksvfs.write("journal.bookmarks",
2066 bookmarksvfs.tryread("bookmarks"))
2068 bookmarksvfs.tryread("bookmarks"))
2067 self.svfs.write("journal.phaseroots",
2069 self.svfs.write("journal.phaseroots",
2068 self.svfs.tryread("phaseroots"))
2070 self.svfs.tryread("phaseroots"))
2069
2071
2070 def recover(self):
2072 def recover(self):
2071 with self.lock():
2073 with self.lock():
2072 if self.svfs.exists("journal"):
2074 if self.svfs.exists("journal"):
2073 self.ui.status(_("rolling back interrupted transaction\n"))
2075 self.ui.status(_("rolling back interrupted transaction\n"))
2074 vfsmap = {'': self.svfs,
2076 vfsmap = {'': self.svfs,
2075 'plain': self.vfs,}
2077 'plain': self.vfs,}
2076 transaction.rollback(self.svfs, vfsmap, "journal",
2078 transaction.rollback(self.svfs, vfsmap, "journal",
2077 self.ui.warn,
2079 self.ui.warn,
2078 checkambigfiles=_cachedfiles)
2080 checkambigfiles=_cachedfiles)
2079 self.invalidate()
2081 self.invalidate()
2080 return True
2082 return True
2081 else:
2083 else:
2082 self.ui.warn(_("no interrupted transaction available\n"))
2084 self.ui.warn(_("no interrupted transaction available\n"))
2083 return False
2085 return False
2084
2086
2085 def rollback(self, dryrun=False, force=False):
2087 def rollback(self, dryrun=False, force=False):
2086 wlock = lock = dsguard = None
2088 wlock = lock = dsguard = None
2087 try:
2089 try:
2088 wlock = self.wlock()
2090 wlock = self.wlock()
2089 lock = self.lock()
2091 lock = self.lock()
2090 if self.svfs.exists("undo"):
2092 if self.svfs.exists("undo"):
2091 dsguard = dirstateguard.dirstateguard(self, 'rollback')
2093 dsguard = dirstateguard.dirstateguard(self, 'rollback')
2092
2094
2093 return self._rollback(dryrun, force, dsguard)
2095 return self._rollback(dryrun, force, dsguard)
2094 else:
2096 else:
2095 self.ui.warn(_("no rollback information available\n"))
2097 self.ui.warn(_("no rollback information available\n"))
2096 return 1
2098 return 1
2097 finally:
2099 finally:
2098 release(dsguard, lock, wlock)
2100 release(dsguard, lock, wlock)
2099
2101
2100 @unfilteredmethod # Until we get smarter cache management
2102 @unfilteredmethod # Until we get smarter cache management
2101 def _rollback(self, dryrun, force, dsguard):
2103 def _rollback(self, dryrun, force, dsguard):
2102 ui = self.ui
2104 ui = self.ui
2103 try:
2105 try:
2104 args = self.vfs.read('undo.desc').splitlines()
2106 args = self.vfs.read('undo.desc').splitlines()
2105 (oldlen, desc, detail) = (int(args[0]), args[1], None)
2107 (oldlen, desc, detail) = (int(args[0]), args[1], None)
2106 if len(args) >= 3:
2108 if len(args) >= 3:
2107 detail = args[2]
2109 detail = args[2]
2108 oldtip = oldlen - 1
2110 oldtip = oldlen - 1
2109
2111
2110 if detail and ui.verbose:
2112 if detail and ui.verbose:
2111 msg = (_('repository tip rolled back to revision %d'
2113 msg = (_('repository tip rolled back to revision %d'
2112 ' (undo %s: %s)\n')
2114 ' (undo %s: %s)\n')
2113 % (oldtip, desc, detail))
2115 % (oldtip, desc, detail))
2114 else:
2116 else:
2115 msg = (_('repository tip rolled back to revision %d'
2117 msg = (_('repository tip rolled back to revision %d'
2116 ' (undo %s)\n')
2118 ' (undo %s)\n')
2117 % (oldtip, desc))
2119 % (oldtip, desc))
2118 except IOError:
2120 except IOError:
2119 msg = _('rolling back unknown transaction\n')
2121 msg = _('rolling back unknown transaction\n')
2120 desc = None
2122 desc = None
2121
2123
2122 if not force and self['.'] != self['tip'] and desc == 'commit':
2124 if not force and self['.'] != self['tip'] and desc == 'commit':
2123 raise error.Abort(
2125 raise error.Abort(
2124 _('rollback of last commit while not checked out '
2126 _('rollback of last commit while not checked out '
2125 'may lose data'), hint=_('use -f to force'))
2127 'may lose data'), hint=_('use -f to force'))
2126
2128
2127 ui.status(msg)
2129 ui.status(msg)
2128 if dryrun:
2130 if dryrun:
2129 return 0
2131 return 0
2130
2132
2131 parents = self.dirstate.parents()
2133 parents = self.dirstate.parents()
2132 self.destroying()
2134 self.destroying()
2133 vfsmap = {'plain': self.vfs, '': self.svfs}
2135 vfsmap = {'plain': self.vfs, '': self.svfs}
2134 transaction.rollback(self.svfs, vfsmap, 'undo', ui.warn,
2136 transaction.rollback(self.svfs, vfsmap, 'undo', ui.warn,
2135 checkambigfiles=_cachedfiles)
2137 checkambigfiles=_cachedfiles)
2136 bookmarksvfs = bookmarks.bookmarksvfs(self)
2138 bookmarksvfs = bookmarks.bookmarksvfs(self)
2137 if bookmarksvfs.exists('undo.bookmarks'):
2139 if bookmarksvfs.exists('undo.bookmarks'):
2138 bookmarksvfs.rename('undo.bookmarks', 'bookmarks', checkambig=True)
2140 bookmarksvfs.rename('undo.bookmarks', 'bookmarks', checkambig=True)
2139 if self.svfs.exists('undo.phaseroots'):
2141 if self.svfs.exists('undo.phaseroots'):
2140 self.svfs.rename('undo.phaseroots', 'phaseroots', checkambig=True)
2142 self.svfs.rename('undo.phaseroots', 'phaseroots', checkambig=True)
2141 self.invalidate()
2143 self.invalidate()
2142
2144
2143 parentgone = any(p not in self.changelog.nodemap for p in parents)
2145 parentgone = any(p not in self.changelog.nodemap for p in parents)
2144 if parentgone:
2146 if parentgone:
2145 # prevent dirstateguard from overwriting already restored one
2147 # prevent dirstateguard from overwriting already restored one
2146 dsguard.close()
2148 dsguard.close()
2147
2149
2148 narrowspec.restorebackup(self, 'undo.narrowspec')
2150 narrowspec.restorebackup(self, 'undo.narrowspec')
2149 narrowspec.restorewcbackup(self, 'undo.narrowspec.dirstate')
2151 narrowspec.restorewcbackup(self, 'undo.narrowspec.dirstate')
2150 self.dirstate.restorebackup(None, 'undo.dirstate')
2152 self.dirstate.restorebackup(None, 'undo.dirstate')
2151 try:
2153 try:
2152 branch = self.vfs.read('undo.branch')
2154 branch = self.vfs.read('undo.branch')
2153 self.dirstate.setbranch(encoding.tolocal(branch))
2155 self.dirstate.setbranch(encoding.tolocal(branch))
2154 except IOError:
2156 except IOError:
2155 ui.warn(_('named branch could not be reset: '
2157 ui.warn(_('named branch could not be reset: '
2156 'current branch is still \'%s\'\n')
2158 'current branch is still \'%s\'\n')
2157 % self.dirstate.branch())
2159 % self.dirstate.branch())
2158
2160
2159 parents = tuple([p.rev() for p in self[None].parents()])
2161 parents = tuple([p.rev() for p in self[None].parents()])
2160 if len(parents) > 1:
2162 if len(parents) > 1:
2161 ui.status(_('working directory now based on '
2163 ui.status(_('working directory now based on '
2162 'revisions %d and %d\n') % parents)
2164 'revisions %d and %d\n') % parents)
2163 else:
2165 else:
2164 ui.status(_('working directory now based on '
2166 ui.status(_('working directory now based on '
2165 'revision %d\n') % parents)
2167 'revision %d\n') % parents)
2166 mergemod.mergestate.clean(self, self['.'].node())
2168 mergemod.mergestate.clean(self, self['.'].node())
2167
2169
2168 # TODO: if we know which new heads may result from this rollback, pass
2170 # TODO: if we know which new heads may result from this rollback, pass
2169 # them to destroy(), which will prevent the branchhead cache from being
2171 # them to destroy(), which will prevent the branchhead cache from being
2170 # invalidated.
2172 # invalidated.
2171 self.destroyed()
2173 self.destroyed()
2172 return 0
2174 return 0
2173
2175
2174 def _buildcacheupdater(self, newtransaction):
2176 def _buildcacheupdater(self, newtransaction):
2175 """called during transaction to build the callback updating cache
2177 """called during transaction to build the callback updating cache
2176
2178
2177 Lives on the repository to help extension who might want to augment
2179 Lives on the repository to help extension who might want to augment
2178 this logic. For this purpose, the created transaction is passed to the
2180 this logic. For this purpose, the created transaction is passed to the
2179 method.
2181 method.
2180 """
2182 """
2181 # we must avoid cyclic reference between repo and transaction.
2183 # we must avoid cyclic reference between repo and transaction.
2182 reporef = weakref.ref(self)
2184 reporef = weakref.ref(self)
2183 def updater(tr):
2185 def updater(tr):
2184 repo = reporef()
2186 repo = reporef()
2185 repo.updatecaches(tr)
2187 repo.updatecaches(tr)
2186 return updater
2188 return updater
2187
2189
2188 @unfilteredmethod
2190 @unfilteredmethod
2189 def updatecaches(self, tr=None, full=False):
2191 def updatecaches(self, tr=None, full=False):
2190 """warm appropriate caches
2192 """warm appropriate caches
2191
2193
2192 If this function is called after a transaction closed. The transaction
2194 If this function is called after a transaction closed. The transaction
2193 will be available in the 'tr' argument. This can be used to selectively
2195 will be available in the 'tr' argument. This can be used to selectively
2194 update caches relevant to the changes in that transaction.
2196 update caches relevant to the changes in that transaction.
2195
2197
2196 If 'full' is set, make sure all caches the function knows about have
2198 If 'full' is set, make sure all caches the function knows about have
2197 up-to-date data. Even the ones usually loaded more lazily.
2199 up-to-date data. Even the ones usually loaded more lazily.
2198 """
2200 """
2199 if tr is not None and tr.hookargs.get('source') == 'strip':
2201 if tr is not None and tr.hookargs.get('source') == 'strip':
2200 # During strip, many caches are invalid but
2202 # During strip, many caches are invalid but
2201 # later call to `destroyed` will refresh them.
2203 # later call to `destroyed` will refresh them.
2202 return
2204 return
2203
2205
2204 if tr is None or tr.changes['origrepolen'] < len(self):
2206 if tr is None or tr.changes['origrepolen'] < len(self):
2205 # accessing the 'ser ved' branchmap should refresh all the others,
2207 # accessing the 'ser ved' branchmap should refresh all the others,
2206 self.ui.debug('updating the branch cache\n')
2208 self.ui.debug('updating the branch cache\n')
2207 self.filtered('served').branchmap()
2209 self.filtered('served').branchmap()
2208 self.filtered('served.hidden').branchmap()
2210 self.filtered('served.hidden').branchmap()
2209
2211
2210 if full:
2212 if full:
2211 unfi = self.unfiltered()
2213 unfi = self.unfiltered()
2212 rbc = unfi.revbranchcache()
2214 rbc = unfi.revbranchcache()
2213 for r in unfi.changelog:
2215 for r in unfi.changelog:
2214 rbc.branchinfo(r)
2216 rbc.branchinfo(r)
2215 rbc.write()
2217 rbc.write()
2216
2218
2217 # ensure the working copy parents are in the manifestfulltextcache
2219 # ensure the working copy parents are in the manifestfulltextcache
2218 for ctx in self['.'].parents():
2220 for ctx in self['.'].parents():
2219 ctx.manifest() # accessing the manifest is enough
2221 ctx.manifest() # accessing the manifest is enough
2220
2222
2221 # accessing fnode cache warms the cache
2223 # accessing fnode cache warms the cache
2222 tagsmod.fnoderevs(self.ui, unfi, unfi.changelog.revs())
2224 tagsmod.fnoderevs(self.ui, unfi, unfi.changelog.revs())
2223 # accessing tags warm the cache
2225 # accessing tags warm the cache
2224 self.tags()
2226 self.tags()
2225 self.filtered('served').tags()
2227 self.filtered('served').tags()
2226
2228
2227 # The `full` arg is documented as updating even the lazily-loaded
2229 # The `full` arg is documented as updating even the lazily-loaded
2228 # caches immediately, so we're forcing a write to cause these caches
2230 # caches immediately, so we're forcing a write to cause these caches
2229 # to be warmed up even if they haven't explicitly been requested
2231 # to be warmed up even if they haven't explicitly been requested
2230 # yet (if they've never been used by hg, they won't ever have been
2232 # yet (if they've never been used by hg, they won't ever have been
2231 # written, even if they're a subset of another kind of cache that
2233 # written, even if they're a subset of another kind of cache that
2232 # *has* been used).
2234 # *has* been used).
2233 for filt in repoview.filtertable.keys():
2235 for filt in repoview.filtertable.keys():
2234 filtered = self.filtered(filt)
2236 filtered = self.filtered(filt)
2235 filtered.branchmap().write(filtered)
2237 filtered.branchmap().write(filtered)
2236
2238
2237 def invalidatecaches(self):
2239 def invalidatecaches(self):
2238
2240
2239 if r'_tagscache' in vars(self):
2241 if r'_tagscache' in vars(self):
2240 # can't use delattr on proxy
2242 # can't use delattr on proxy
2241 del self.__dict__[r'_tagscache']
2243 del self.__dict__[r'_tagscache']
2242
2244
2243 self._branchcaches.clear()
2245 self._branchcaches.clear()
2244 self.invalidatevolatilesets()
2246 self.invalidatevolatilesets()
2245 self._sparsesignaturecache.clear()
2247 self._sparsesignaturecache.clear()
2246
2248
2247 def invalidatevolatilesets(self):
2249 def invalidatevolatilesets(self):
2248 self.filteredrevcache.clear()
2250 self.filteredrevcache.clear()
2249 obsolete.clearobscaches(self)
2251 obsolete.clearobscaches(self)
2250
2252
2251 def invalidatedirstate(self):
2253 def invalidatedirstate(self):
2252 '''Invalidates the dirstate, causing the next call to dirstate
2254 '''Invalidates the dirstate, causing the next call to dirstate
2253 to check if it was modified since the last time it was read,
2255 to check if it was modified since the last time it was read,
2254 rereading it if it has.
2256 rereading it if it has.
2255
2257
2256 This is different to dirstate.invalidate() that it doesn't always
2258 This is different to dirstate.invalidate() that it doesn't always
2257 rereads the dirstate. Use dirstate.invalidate() if you want to
2259 rereads the dirstate. Use dirstate.invalidate() if you want to
2258 explicitly read the dirstate again (i.e. restoring it to a previous
2260 explicitly read the dirstate again (i.e. restoring it to a previous
2259 known good state).'''
2261 known good state).'''
2260 if hasunfilteredcache(self, r'dirstate'):
2262 if hasunfilteredcache(self, r'dirstate'):
2261 for k in self.dirstate._filecache:
2263 for k in self.dirstate._filecache:
2262 try:
2264 try:
2263 delattr(self.dirstate, k)
2265 delattr(self.dirstate, k)
2264 except AttributeError:
2266 except AttributeError:
2265 pass
2267 pass
2266 delattr(self.unfiltered(), r'dirstate')
2268 delattr(self.unfiltered(), r'dirstate')
2267
2269
2268 def invalidate(self, clearfilecache=False):
2270 def invalidate(self, clearfilecache=False):
2269 '''Invalidates both store and non-store parts other than dirstate
2271 '''Invalidates both store and non-store parts other than dirstate
2270
2272
2271 If a transaction is running, invalidation of store is omitted,
2273 If a transaction is running, invalidation of store is omitted,
2272 because discarding in-memory changes might cause inconsistency
2274 because discarding in-memory changes might cause inconsistency
2273 (e.g. incomplete fncache causes unintentional failure, but
2275 (e.g. incomplete fncache causes unintentional failure, but
2274 redundant one doesn't).
2276 redundant one doesn't).
2275 '''
2277 '''
2276 unfiltered = self.unfiltered() # all file caches are stored unfiltered
2278 unfiltered = self.unfiltered() # all file caches are stored unfiltered
2277 for k in list(self._filecache.keys()):
2279 for k in list(self._filecache.keys()):
2278 # dirstate is invalidated separately in invalidatedirstate()
2280 # dirstate is invalidated separately in invalidatedirstate()
2279 if k == 'dirstate':
2281 if k == 'dirstate':
2280 continue
2282 continue
2281 if (k == 'changelog' and
2283 if (k == 'changelog' and
2282 self.currenttransaction() and
2284 self.currenttransaction() and
2283 self.changelog._delayed):
2285 self.changelog._delayed):
2284 # The changelog object may store unwritten revisions. We don't
2286 # The changelog object may store unwritten revisions. We don't
2285 # want to lose them.
2287 # want to lose them.
2286 # TODO: Solve the problem instead of working around it.
2288 # TODO: Solve the problem instead of working around it.
2287 continue
2289 continue
2288
2290
2289 if clearfilecache:
2291 if clearfilecache:
2290 del self._filecache[k]
2292 del self._filecache[k]
2291 try:
2293 try:
2292 delattr(unfiltered, k)
2294 delattr(unfiltered, k)
2293 except AttributeError:
2295 except AttributeError:
2294 pass
2296 pass
2295 self.invalidatecaches()
2297 self.invalidatecaches()
2296 if not self.currenttransaction():
2298 if not self.currenttransaction():
2297 # TODO: Changing contents of store outside transaction
2299 # TODO: Changing contents of store outside transaction
2298 # causes inconsistency. We should make in-memory store
2300 # causes inconsistency. We should make in-memory store
2299 # changes detectable, and abort if changed.
2301 # changes detectable, and abort if changed.
2300 self.store.invalidatecaches()
2302 self.store.invalidatecaches()
2301
2303
2302 def invalidateall(self):
2304 def invalidateall(self):
2303 '''Fully invalidates both store and non-store parts, causing the
2305 '''Fully invalidates both store and non-store parts, causing the
2304 subsequent operation to reread any outside changes.'''
2306 subsequent operation to reread any outside changes.'''
2305 # extension should hook this to invalidate its caches
2307 # extension should hook this to invalidate its caches
2306 self.invalidate()
2308 self.invalidate()
2307 self.invalidatedirstate()
2309 self.invalidatedirstate()
2308
2310
2309 @unfilteredmethod
2311 @unfilteredmethod
2310 def _refreshfilecachestats(self, tr):
2312 def _refreshfilecachestats(self, tr):
2311 """Reload stats of cached files so that they are flagged as valid"""
2313 """Reload stats of cached files so that they are flagged as valid"""
2312 for k, ce in self._filecache.items():
2314 for k, ce in self._filecache.items():
2313 k = pycompat.sysstr(k)
2315 k = pycompat.sysstr(k)
2314 if k == r'dirstate' or k not in self.__dict__:
2316 if k == r'dirstate' or k not in self.__dict__:
2315 continue
2317 continue
2316 ce.refresh()
2318 ce.refresh()
2317
2319
2318 def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc,
2320 def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc,
2319 inheritchecker=None, parentenvvar=None):
2321 inheritchecker=None, parentenvvar=None):
2320 parentlock = None
2322 parentlock = None
2321 # the contents of parentenvvar are used by the underlying lock to
2323 # the contents of parentenvvar are used by the underlying lock to
2322 # determine whether it can be inherited
2324 # determine whether it can be inherited
2323 if parentenvvar is not None:
2325 if parentenvvar is not None:
2324 parentlock = encoding.environ.get(parentenvvar)
2326 parentlock = encoding.environ.get(parentenvvar)
2325
2327
2326 timeout = 0
2328 timeout = 0
2327 warntimeout = 0
2329 warntimeout = 0
2328 if wait:
2330 if wait:
2329 timeout = self.ui.configint("ui", "timeout")
2331 timeout = self.ui.configint("ui", "timeout")
2330 warntimeout = self.ui.configint("ui", "timeout.warn")
2332 warntimeout = self.ui.configint("ui", "timeout.warn")
2331 # internal config: ui.signal-safe-lock
2333 # internal config: ui.signal-safe-lock
2332 signalsafe = self.ui.configbool('ui', 'signal-safe-lock')
2334 signalsafe = self.ui.configbool('ui', 'signal-safe-lock')
2333
2335
2334 l = lockmod.trylock(self.ui, vfs, lockname, timeout, warntimeout,
2336 l = lockmod.trylock(self.ui, vfs, lockname, timeout, warntimeout,
2335 releasefn=releasefn,
2337 releasefn=releasefn,
2336 acquirefn=acquirefn, desc=desc,
2338 acquirefn=acquirefn, desc=desc,
2337 inheritchecker=inheritchecker,
2339 inheritchecker=inheritchecker,
2338 parentlock=parentlock,
2340 parentlock=parentlock,
2339 signalsafe=signalsafe)
2341 signalsafe=signalsafe)
2340 return l
2342 return l
2341
2343
2342 def _afterlock(self, callback):
2344 def _afterlock(self, callback):
2343 """add a callback to be run when the repository is fully unlocked
2345 """add a callback to be run when the repository is fully unlocked
2344
2346
2345 The callback will be executed when the outermost lock is released
2347 The callback will be executed when the outermost lock is released
2346 (with wlock being higher level than 'lock')."""
2348 (with wlock being higher level than 'lock')."""
2347 for ref in (self._wlockref, self._lockref):
2349 for ref in (self._wlockref, self._lockref):
2348 l = ref and ref()
2350 l = ref and ref()
2349 if l and l.held:
2351 if l and l.held:
2350 l.postrelease.append(callback)
2352 l.postrelease.append(callback)
2351 break
2353 break
2352 else: # no lock have been found.
2354 else: # no lock have been found.
2353 callback()
2355 callback()
2354
2356
2355 def lock(self, wait=True):
2357 def lock(self, wait=True):
2356 '''Lock the repository store (.hg/store) and return a weak reference
2358 '''Lock the repository store (.hg/store) and return a weak reference
2357 to the lock. Use this before modifying the store (e.g. committing or
2359 to the lock. Use this before modifying the store (e.g. committing or
2358 stripping). If you are opening a transaction, get a lock as well.)
2360 stripping). If you are opening a transaction, get a lock as well.)
2359
2361
2360 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2362 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2361 'wlock' first to avoid a dead-lock hazard.'''
2363 'wlock' first to avoid a dead-lock hazard.'''
2362 l = self._currentlock(self._lockref)
2364 l = self._currentlock(self._lockref)
2363 if l is not None:
2365 if l is not None:
2364 l.lock()
2366 l.lock()
2365 return l
2367 return l
2366
2368
2367 l = self._lock(vfs=self.svfs,
2369 l = self._lock(vfs=self.svfs,
2368 lockname="lock",
2370 lockname="lock",
2369 wait=wait,
2371 wait=wait,
2370 releasefn=None,
2372 releasefn=None,
2371 acquirefn=self.invalidate,
2373 acquirefn=self.invalidate,
2372 desc=_('repository %s') % self.origroot)
2374 desc=_('repository %s') % self.origroot)
2373 self._lockref = weakref.ref(l)
2375 self._lockref = weakref.ref(l)
2374 return l
2376 return l
2375
2377
2376 def _wlockchecktransaction(self):
2378 def _wlockchecktransaction(self):
2377 if self.currenttransaction() is not None:
2379 if self.currenttransaction() is not None:
2378 raise error.LockInheritanceContractViolation(
2380 raise error.LockInheritanceContractViolation(
2379 'wlock cannot be inherited in the middle of a transaction')
2381 'wlock cannot be inherited in the middle of a transaction')
2380
2382
2381 def wlock(self, wait=True):
2383 def wlock(self, wait=True):
2382 '''Lock the non-store parts of the repository (everything under
2384 '''Lock the non-store parts of the repository (everything under
2383 .hg except .hg/store) and return a weak reference to the lock.
2385 .hg except .hg/store) and return a weak reference to the lock.
2384
2386
2385 Use this before modifying files in .hg.
2387 Use this before modifying files in .hg.
2386
2388
2387 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2389 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2388 'wlock' first to avoid a dead-lock hazard.'''
2390 'wlock' first to avoid a dead-lock hazard.'''
2389 l = self._wlockref and self._wlockref()
2391 l = self._wlockref and self._wlockref()
2390 if l is not None and l.held:
2392 if l is not None and l.held:
2391 l.lock()
2393 l.lock()
2392 return l
2394 return l
2393
2395
2394 # We do not need to check for non-waiting lock acquisition. Such
2396 # We do not need to check for non-waiting lock acquisition. Such
2395 # acquisition would not cause dead-lock as they would just fail.
2397 # acquisition would not cause dead-lock as they would just fail.
2396 if wait and (self.ui.configbool('devel', 'all-warnings')
2398 if wait and (self.ui.configbool('devel', 'all-warnings')
2397 or self.ui.configbool('devel', 'check-locks')):
2399 or self.ui.configbool('devel', 'check-locks')):
2398 if self._currentlock(self._lockref) is not None:
2400 if self._currentlock(self._lockref) is not None:
2399 self.ui.develwarn('"wlock" acquired after "lock"')
2401 self.ui.develwarn('"wlock" acquired after "lock"')
2400
2402
2401 def unlock():
2403 def unlock():
2402 if self.dirstate.pendingparentchange():
2404 if self.dirstate.pendingparentchange():
2403 self.dirstate.invalidate()
2405 self.dirstate.invalidate()
2404 else:
2406 else:
2405 self.dirstate.write(None)
2407 self.dirstate.write(None)
2406
2408
2407 self._filecache['dirstate'].refresh()
2409 self._filecache['dirstate'].refresh()
2408
2410
2409 l = self._lock(self.vfs, "wlock", wait, unlock,
2411 l = self._lock(self.vfs, "wlock", wait, unlock,
2410 self.invalidatedirstate, _('working directory of %s') %
2412 self.invalidatedirstate, _('working directory of %s') %
2411 self.origroot,
2413 self.origroot,
2412 inheritchecker=self._wlockchecktransaction,
2414 inheritchecker=self._wlockchecktransaction,
2413 parentenvvar='HG_WLOCK_LOCKER')
2415 parentenvvar='HG_WLOCK_LOCKER')
2414 self._wlockref = weakref.ref(l)
2416 self._wlockref = weakref.ref(l)
2415 return l
2417 return l
2416
2418
2417 def _currentlock(self, lockref):
2419 def _currentlock(self, lockref):
2418 """Returns the lock if it's held, or None if it's not."""
2420 """Returns the lock if it's held, or None if it's not."""
2419 if lockref is None:
2421 if lockref is None:
2420 return None
2422 return None
2421 l = lockref()
2423 l = lockref()
2422 if l is None or not l.held:
2424 if l is None or not l.held:
2423 return None
2425 return None
2424 return l
2426 return l
2425
2427
2426 def currentwlock(self):
2428 def currentwlock(self):
2427 """Returns the wlock if it's held, or None if it's not."""
2429 """Returns the wlock if it's held, or None if it's not."""
2428 return self._currentlock(self._wlockref)
2430 return self._currentlock(self._wlockref)
2429
2431
2430 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist,
2432 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist,
2431 includecopymeta):
2433 includecopymeta):
2432 """
2434 """
2433 commit an individual file as part of a larger transaction
2435 commit an individual file as part of a larger transaction
2434 """
2436 """
2435
2437
2436 fname = fctx.path()
2438 fname = fctx.path()
2437 fparent1 = manifest1.get(fname, nullid)
2439 fparent1 = manifest1.get(fname, nullid)
2438 fparent2 = manifest2.get(fname, nullid)
2440 fparent2 = manifest2.get(fname, nullid)
2439 if isinstance(fctx, context.filectx):
2441 if isinstance(fctx, context.filectx):
2440 node = fctx.filenode()
2442 node = fctx.filenode()
2441 if node in [fparent1, fparent2]:
2443 if node in [fparent1, fparent2]:
2442 self.ui.debug('reusing %s filelog entry\n' % fname)
2444 self.ui.debug('reusing %s filelog entry\n' % fname)
2443 if ((fparent1 != nullid and
2445 if ((fparent1 != nullid and
2444 manifest1.flags(fname) != fctx.flags()) or
2446 manifest1.flags(fname) != fctx.flags()) or
2445 (fparent2 != nullid and
2447 (fparent2 != nullid and
2446 manifest2.flags(fname) != fctx.flags())):
2448 manifest2.flags(fname) != fctx.flags())):
2447 changelist.append(fname)
2449 changelist.append(fname)
2448 return node
2450 return node
2449
2451
2450 flog = self.file(fname)
2452 flog = self.file(fname)
2451 meta = {}
2453 meta = {}
2452 cfname = fctx.copysource()
2454 cfname = fctx.copysource()
2453 if cfname and cfname != fname:
2455 if cfname and cfname != fname:
2454 # Mark the new revision of this file as a copy of another
2456 # Mark the new revision of this file as a copy of another
2455 # file. This copy data will effectively act as a parent
2457 # file. This copy data will effectively act as a parent
2456 # of this new revision. If this is a merge, the first
2458 # of this new revision. If this is a merge, the first
2457 # parent will be the nullid (meaning "look up the copy data")
2459 # parent will be the nullid (meaning "look up the copy data")
2458 # and the second one will be the other parent. For example:
2460 # and the second one will be the other parent. For example:
2459 #
2461 #
2460 # 0 --- 1 --- 3 rev1 changes file foo
2462 # 0 --- 1 --- 3 rev1 changes file foo
2461 # \ / rev2 renames foo to bar and changes it
2463 # \ / rev2 renames foo to bar and changes it
2462 # \- 2 -/ rev3 should have bar with all changes and
2464 # \- 2 -/ rev3 should have bar with all changes and
2463 # should record that bar descends from
2465 # should record that bar descends from
2464 # bar in rev2 and foo in rev1
2466 # bar in rev2 and foo in rev1
2465 #
2467 #
2466 # this allows this merge to succeed:
2468 # this allows this merge to succeed:
2467 #
2469 #
2468 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
2470 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
2469 # \ / merging rev3 and rev4 should use bar@rev2
2471 # \ / merging rev3 and rev4 should use bar@rev2
2470 # \- 2 --- 4 as the merge base
2472 # \- 2 --- 4 as the merge base
2471 #
2473 #
2472
2474
2473 cnode = manifest1.get(cfname)
2475 cnode = manifest1.get(cfname)
2474 newfparent = fparent2
2476 newfparent = fparent2
2475
2477
2476 if manifest2: # branch merge
2478 if manifest2: # branch merge
2477 if fparent2 == nullid or cnode is None: # copied on remote side
2479 if fparent2 == nullid or cnode is None: # copied on remote side
2478 if cfname in manifest2:
2480 if cfname in manifest2:
2479 cnode = manifest2[cfname]
2481 cnode = manifest2[cfname]
2480 newfparent = fparent1
2482 newfparent = fparent1
2481
2483
2482 # Here, we used to search backwards through history to try to find
2484 # Here, we used to search backwards through history to try to find
2483 # where the file copy came from if the source of a copy was not in
2485 # where the file copy came from if the source of a copy was not in
2484 # the parent directory. However, this doesn't actually make sense to
2486 # the parent directory. However, this doesn't actually make sense to
2485 # do (what does a copy from something not in your working copy even
2487 # do (what does a copy from something not in your working copy even
2486 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
2488 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
2487 # the user that copy information was dropped, so if they didn't
2489 # the user that copy information was dropped, so if they didn't
2488 # expect this outcome it can be fixed, but this is the correct
2490 # expect this outcome it can be fixed, but this is the correct
2489 # behavior in this circumstance.
2491 # behavior in this circumstance.
2490
2492
2491 if cnode:
2493 if cnode:
2492 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(cnode)))
2494 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(cnode)))
2493 if includecopymeta:
2495 if includecopymeta:
2494 meta["copy"] = cfname
2496 meta["copy"] = cfname
2495 meta["copyrev"] = hex(cnode)
2497 meta["copyrev"] = hex(cnode)
2496 fparent1, fparent2 = nullid, newfparent
2498 fparent1, fparent2 = nullid, newfparent
2497 else:
2499 else:
2498 self.ui.warn(_("warning: can't find ancestor for '%s' "
2500 self.ui.warn(_("warning: can't find ancestor for '%s' "
2499 "copied from '%s'!\n") % (fname, cfname))
2501 "copied from '%s'!\n") % (fname, cfname))
2500
2502
2501 elif fparent1 == nullid:
2503 elif fparent1 == nullid:
2502 fparent1, fparent2 = fparent2, nullid
2504 fparent1, fparent2 = fparent2, nullid
2503 elif fparent2 != nullid:
2505 elif fparent2 != nullid:
2504 # is one parent an ancestor of the other?
2506 # is one parent an ancestor of the other?
2505 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
2507 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
2506 if fparent1 in fparentancestors:
2508 if fparent1 in fparentancestors:
2507 fparent1, fparent2 = fparent2, nullid
2509 fparent1, fparent2 = fparent2, nullid
2508 elif fparent2 in fparentancestors:
2510 elif fparent2 in fparentancestors:
2509 fparent2 = nullid
2511 fparent2 = nullid
2510
2512
2511 # is the file changed?
2513 # is the file changed?
2512 text = fctx.data()
2514 text = fctx.data()
2513 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
2515 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
2514 changelist.append(fname)
2516 changelist.append(fname)
2515 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
2517 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
2516 # are just the flags changed during merge?
2518 # are just the flags changed during merge?
2517 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
2519 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
2518 changelist.append(fname)
2520 changelist.append(fname)
2519
2521
2520 return fparent1
2522 return fparent1
2521
2523
2522 def checkcommitpatterns(self, wctx, vdirs, match, status, fail):
2524 def checkcommitpatterns(self, wctx, vdirs, match, status, fail):
2523 """check for commit arguments that aren't committable"""
2525 """check for commit arguments that aren't committable"""
2524 if match.isexact() or match.prefix():
2526 if match.isexact() or match.prefix():
2525 matched = set(status.modified + status.added + status.removed)
2527 matched = set(status.modified + status.added + status.removed)
2526
2528
2527 for f in match.files():
2529 for f in match.files():
2528 f = self.dirstate.normalize(f)
2530 f = self.dirstate.normalize(f)
2529 if f == '.' or f in matched or f in wctx.substate:
2531 if f == '.' or f in matched or f in wctx.substate:
2530 continue
2532 continue
2531 if f in status.deleted:
2533 if f in status.deleted:
2532 fail(f, _('file not found!'))
2534 fail(f, _('file not found!'))
2533 if f in vdirs: # visited directory
2535 if f in vdirs: # visited directory
2534 d = f + '/'
2536 d = f + '/'
2535 for mf in matched:
2537 for mf in matched:
2536 if mf.startswith(d):
2538 if mf.startswith(d):
2537 break
2539 break
2538 else:
2540 else:
2539 fail(f, _("no match under directory!"))
2541 fail(f, _("no match under directory!"))
2540 elif f not in self.dirstate:
2542 elif f not in self.dirstate:
2541 fail(f, _("file not tracked!"))
2543 fail(f, _("file not tracked!"))
2542
2544
2543 @unfilteredmethod
2545 @unfilteredmethod
2544 def commit(self, text="", user=None, date=None, match=None, force=False,
2546 def commit(self, text="", user=None, date=None, match=None, force=False,
2545 editor=False, extra=None):
2547 editor=False, extra=None):
2546 """Add a new revision to current repository.
2548 """Add a new revision to current repository.
2547
2549
2548 Revision information is gathered from the working directory,
2550 Revision information is gathered from the working directory,
2549 match can be used to filter the committed files. If editor is
2551 match can be used to filter the committed files. If editor is
2550 supplied, it is called to get a commit message.
2552 supplied, it is called to get a commit message.
2551 """
2553 """
2552 if extra is None:
2554 if extra is None:
2553 extra = {}
2555 extra = {}
2554
2556
2555 def fail(f, msg):
2557 def fail(f, msg):
2556 raise error.Abort('%s: %s' % (f, msg))
2558 raise error.Abort('%s: %s' % (f, msg))
2557
2559
2558 if not match:
2560 if not match:
2559 match = matchmod.always()
2561 match = matchmod.always()
2560
2562
2561 if not force:
2563 if not force:
2562 vdirs = []
2564 vdirs = []
2563 match.explicitdir = vdirs.append
2565 match.explicitdir = vdirs.append
2564 match.bad = fail
2566 match.bad = fail
2565
2567
2566 # lock() for recent changelog (see issue4368)
2568 # lock() for recent changelog (see issue4368)
2567 with self.wlock(), self.lock():
2569 with self.wlock(), self.lock():
2568 wctx = self[None]
2570 wctx = self[None]
2569 merge = len(wctx.parents()) > 1
2571 merge = len(wctx.parents()) > 1
2570
2572
2571 if not force and merge and not match.always():
2573 if not force and merge and not match.always():
2572 raise error.Abort(_('cannot partially commit a merge '
2574 raise error.Abort(_('cannot partially commit a merge '
2573 '(do not specify files or patterns)'))
2575 '(do not specify files or patterns)'))
2574
2576
2575 status = self.status(match=match, clean=force)
2577 status = self.status(match=match, clean=force)
2576 if force:
2578 if force:
2577 status.modified.extend(status.clean) # mq may commit clean files
2579 status.modified.extend(status.clean) # mq may commit clean files
2578
2580
2579 # check subrepos
2581 # check subrepos
2580 subs, commitsubs, newstate = subrepoutil.precommit(
2582 subs, commitsubs, newstate = subrepoutil.precommit(
2581 self.ui, wctx, status, match, force=force)
2583 self.ui, wctx, status, match, force=force)
2582
2584
2583 # make sure all explicit patterns are matched
2585 # make sure all explicit patterns are matched
2584 if not force:
2586 if not force:
2585 self.checkcommitpatterns(wctx, vdirs, match, status, fail)
2587 self.checkcommitpatterns(wctx, vdirs, match, status, fail)
2586
2588
2587 cctx = context.workingcommitctx(self, status,
2589 cctx = context.workingcommitctx(self, status,
2588 text, user, date, extra)
2590 text, user, date, extra)
2589
2591
2590 # internal config: ui.allowemptycommit
2592 # internal config: ui.allowemptycommit
2591 allowemptycommit = (wctx.branch() != wctx.p1().branch()
2593 allowemptycommit = (wctx.branch() != wctx.p1().branch()
2592 or extra.get('close') or merge or cctx.files()
2594 or extra.get('close') or merge or cctx.files()
2593 or self.ui.configbool('ui', 'allowemptycommit'))
2595 or self.ui.configbool('ui', 'allowemptycommit'))
2594 if not allowemptycommit:
2596 if not allowemptycommit:
2595 return None
2597 return None
2596
2598
2597 if merge and cctx.deleted():
2599 if merge and cctx.deleted():
2598 raise error.Abort(_("cannot commit merge with missing files"))
2600 raise error.Abort(_("cannot commit merge with missing files"))
2599
2601
2600 ms = mergemod.mergestate.read(self)
2602 ms = mergemod.mergestate.read(self)
2601 mergeutil.checkunresolved(ms)
2603 mergeutil.checkunresolved(ms)
2602
2604
2603 if editor:
2605 if editor:
2604 cctx._text = editor(self, cctx, subs)
2606 cctx._text = editor(self, cctx, subs)
2605 edited = (text != cctx._text)
2607 edited = (text != cctx._text)
2606
2608
2607 # Save commit message in case this transaction gets rolled back
2609 # Save commit message in case this transaction gets rolled back
2608 # (e.g. by a pretxncommit hook). Leave the content alone on
2610 # (e.g. by a pretxncommit hook). Leave the content alone on
2609 # the assumption that the user will use the same editor again.
2611 # the assumption that the user will use the same editor again.
2610 msgfn = self.savecommitmessage(cctx._text)
2612 msgfn = self.savecommitmessage(cctx._text)
2611
2613
2612 # commit subs and write new state
2614 # commit subs and write new state
2613 if subs:
2615 if subs:
2614 uipathfn = scmutil.getuipathfn(self)
2616 uipathfn = scmutil.getuipathfn(self)
2615 for s in sorted(commitsubs):
2617 for s in sorted(commitsubs):
2616 sub = wctx.sub(s)
2618 sub = wctx.sub(s)
2617 self.ui.status(_('committing subrepository %s\n') %
2619 self.ui.status(_('committing subrepository %s\n') %
2618 uipathfn(subrepoutil.subrelpath(sub)))
2620 uipathfn(subrepoutil.subrelpath(sub)))
2619 sr = sub.commit(cctx._text, user, date)
2621 sr = sub.commit(cctx._text, user, date)
2620 newstate[s] = (newstate[s][0], sr)
2622 newstate[s] = (newstate[s][0], sr)
2621 subrepoutil.writestate(self, newstate)
2623 subrepoutil.writestate(self, newstate)
2622
2624
2623 p1, p2 = self.dirstate.parents()
2625 p1, p2 = self.dirstate.parents()
2624 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
2626 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
2625 try:
2627 try:
2626 self.hook("precommit", throw=True, parent1=hookp1,
2628 self.hook("precommit", throw=True, parent1=hookp1,
2627 parent2=hookp2)
2629 parent2=hookp2)
2628 with self.transaction('commit'):
2630 with self.transaction('commit'):
2629 ret = self.commitctx(cctx, True)
2631 ret = self.commitctx(cctx, True)
2630 # update bookmarks, dirstate and mergestate
2632 # update bookmarks, dirstate and mergestate
2631 bookmarks.update(self, [p1, p2], ret)
2633 bookmarks.update(self, [p1, p2], ret)
2632 cctx.markcommitted(ret)
2634 cctx.markcommitted(ret)
2633 ms.reset()
2635 ms.reset()
2634 except: # re-raises
2636 except: # re-raises
2635 if edited:
2637 if edited:
2636 self.ui.write(
2638 self.ui.write(
2637 _('note: commit message saved in %s\n') % msgfn)
2639 _('note: commit message saved in %s\n') % msgfn)
2638 raise
2640 raise
2639
2641
2640 def commithook():
2642 def commithook():
2641 # hack for command that use a temporary commit (eg: histedit)
2643 # hack for command that use a temporary commit (eg: histedit)
2642 # temporary commit got stripped before hook release
2644 # temporary commit got stripped before hook release
2643 if self.changelog.hasnode(ret):
2645 if self.changelog.hasnode(ret):
2644 self.hook("commit", node=hex(ret), parent1=hookp1,
2646 self.hook("commit", node=hex(ret), parent1=hookp1,
2645 parent2=hookp2)
2647 parent2=hookp2)
2646 self._afterlock(commithook)
2648 self._afterlock(commithook)
2647 return ret
2649 return ret
2648
2650
2649 @unfilteredmethod
2651 @unfilteredmethod
2650 def commitctx(self, ctx, error=False, origctx=None):
2652 def commitctx(self, ctx, error=False, origctx=None):
2651 """Add a new revision to current repository.
2653 """Add a new revision to current repository.
2652 Revision information is passed via the context argument.
2654 Revision information is passed via the context argument.
2653
2655
2654 ctx.files() should list all files involved in this commit, i.e.
2656 ctx.files() should list all files involved in this commit, i.e.
2655 modified/added/removed files. On merge, it may be wider than the
2657 modified/added/removed files. On merge, it may be wider than the
2656 ctx.files() to be committed, since any file nodes derived directly
2658 ctx.files() to be committed, since any file nodes derived directly
2657 from p1 or p2 are excluded from the committed ctx.files().
2659 from p1 or p2 are excluded from the committed ctx.files().
2658
2660
2659 origctx is for convert to work around the problem that bug
2661 origctx is for convert to work around the problem that bug
2660 fixes to the files list in changesets change hashes. For
2662 fixes to the files list in changesets change hashes. For
2661 convert to be the identity, it can pass an origctx and this
2663 convert to be the identity, it can pass an origctx and this
2662 function will use the same files list when it makes sense to
2664 function will use the same files list when it makes sense to
2663 do so.
2665 do so.
2664 """
2666 """
2665
2667
2666 p1, p2 = ctx.p1(), ctx.p2()
2668 p1, p2 = ctx.p1(), ctx.p2()
2667 user = ctx.user()
2669 user = ctx.user()
2668
2670
2669 writecopiesto = self.ui.config('experimental', 'copies.write-to')
2671 writecopiesto = self.ui.config('experimental', 'copies.write-to')
2670 writefilecopymeta = writecopiesto != 'changeset-only'
2672 writefilecopymeta = writecopiesto != 'changeset-only'
2671 writechangesetcopy = (writecopiesto in
2673 writechangesetcopy = (writecopiesto in
2672 ('changeset-only', 'compatibility'))
2674 ('changeset-only', 'compatibility'))
2673 p1copies, p2copies = None, None
2675 p1copies, p2copies = None, None
2674 if writechangesetcopy:
2676 if writechangesetcopy:
2675 p1copies = ctx.p1copies()
2677 p1copies = ctx.p1copies()
2676 p2copies = ctx.p2copies()
2678 p2copies = ctx.p2copies()
2677 filesadded, filesremoved = None, None
2679 filesadded, filesremoved = None, None
2678 with self.lock(), self.transaction("commit") as tr:
2680 with self.lock(), self.transaction("commit") as tr:
2679 trp = weakref.proxy(tr)
2681 trp = weakref.proxy(tr)
2680
2682
2681 if ctx.manifestnode():
2683 if ctx.manifestnode():
2682 # reuse an existing manifest revision
2684 # reuse an existing manifest revision
2683 self.ui.debug('reusing known manifest\n')
2685 self.ui.debug('reusing known manifest\n')
2684 mn = ctx.manifestnode()
2686 mn = ctx.manifestnode()
2685 files = ctx.files()
2687 files = ctx.files()
2686 if writechangesetcopy:
2688 if writechangesetcopy:
2687 filesadded = ctx.filesadded()
2689 filesadded = ctx.filesadded()
2688 filesremoved = ctx.filesremoved()
2690 filesremoved = ctx.filesremoved()
2689 elif ctx.files():
2691 elif ctx.files():
2690 m1ctx = p1.manifestctx()
2692 m1ctx = p1.manifestctx()
2691 m2ctx = p2.manifestctx()
2693 m2ctx = p2.manifestctx()
2692 mctx = m1ctx.copy()
2694 mctx = m1ctx.copy()
2693
2695
2694 m = mctx.read()
2696 m = mctx.read()
2695 m1 = m1ctx.read()
2697 m1 = m1ctx.read()
2696 m2 = m2ctx.read()
2698 m2 = m2ctx.read()
2697
2699
2698 # check in files
2700 # check in files
2699 added = []
2701 added = []
2700 changed = []
2702 changed = []
2701 removed = list(ctx.removed())
2703 removed = list(ctx.removed())
2702 linkrev = len(self)
2704 linkrev = len(self)
2703 self.ui.note(_("committing files:\n"))
2705 self.ui.note(_("committing files:\n"))
2704 uipathfn = scmutil.getuipathfn(self)
2706 uipathfn = scmutil.getuipathfn(self)
2705 for f in sorted(ctx.modified() + ctx.added()):
2707 for f in sorted(ctx.modified() + ctx.added()):
2706 self.ui.note(uipathfn(f) + "\n")
2708 self.ui.note(uipathfn(f) + "\n")
2707 try:
2709 try:
2708 fctx = ctx[f]
2710 fctx = ctx[f]
2709 if fctx is None:
2711 if fctx is None:
2710 removed.append(f)
2712 removed.append(f)
2711 else:
2713 else:
2712 added.append(f)
2714 added.append(f)
2713 m[f] = self._filecommit(fctx, m1, m2, linkrev,
2715 m[f] = self._filecommit(fctx, m1, m2, linkrev,
2714 trp, changed,
2716 trp, changed,
2715 writefilecopymeta)
2717 writefilecopymeta)
2716 m.setflag(f, fctx.flags())
2718 m.setflag(f, fctx.flags())
2717 except OSError:
2719 except OSError:
2718 self.ui.warn(_("trouble committing %s!\n") %
2720 self.ui.warn(_("trouble committing %s!\n") %
2719 uipathfn(f))
2721 uipathfn(f))
2720 raise
2722 raise
2721 except IOError as inst:
2723 except IOError as inst:
2722 errcode = getattr(inst, 'errno', errno.ENOENT)
2724 errcode = getattr(inst, 'errno', errno.ENOENT)
2723 if error or errcode and errcode != errno.ENOENT:
2725 if error or errcode and errcode != errno.ENOENT:
2724 self.ui.warn(_("trouble committing %s!\n") %
2726 self.ui.warn(_("trouble committing %s!\n") %
2725 uipathfn(f))
2727 uipathfn(f))
2726 raise
2728 raise
2727
2729
2728 # update manifest
2730 # update manifest
2729 removed = [f for f in removed if f in m1 or f in m2]
2731 removed = [f for f in removed if f in m1 or f in m2]
2730 drop = sorted([f for f in removed if f in m])
2732 drop = sorted([f for f in removed if f in m])
2731 for f in drop:
2733 for f in drop:
2732 del m[f]
2734 del m[f]
2733 if p2.rev() != nullrev:
2735 if p2.rev() != nullrev:
2734 @util.cachefunc
2736 @util.cachefunc
2735 def mas():
2737 def mas():
2736 p1n = p1.node()
2738 p1n = p1.node()
2737 p2n = p2.node()
2739 p2n = p2.node()
2738 cahs = self.changelog.commonancestorsheads(p1n, p2n)
2740 cahs = self.changelog.commonancestorsheads(p1n, p2n)
2739 if not cahs:
2741 if not cahs:
2740 cahs = [nullrev]
2742 cahs = [nullrev]
2741 return [self[r].manifest() for r in cahs]
2743 return [self[r].manifest() for r in cahs]
2742 def deletionfromparent(f):
2744 def deletionfromparent(f):
2743 # When a file is removed relative to p1 in a merge, this
2745 # When a file is removed relative to p1 in a merge, this
2744 # function determines whether the absence is due to a
2746 # function determines whether the absence is due to a
2745 # deletion from a parent, or whether the merge commit
2747 # deletion from a parent, or whether the merge commit
2746 # itself deletes the file. We decide this by doing a
2748 # itself deletes the file. We decide this by doing a
2747 # simplified three way merge of the manifest entry for
2749 # simplified three way merge of the manifest entry for
2748 # the file. There are two ways we decide the merge
2750 # the file. There are two ways we decide the merge
2749 # itself didn't delete a file:
2751 # itself didn't delete a file:
2750 # - neither parent (nor the merge) contain the file
2752 # - neither parent (nor the merge) contain the file
2751 # - exactly one parent contains the file, and that
2753 # - exactly one parent contains the file, and that
2752 # parent has the same filelog entry as the merge
2754 # parent has the same filelog entry as the merge
2753 # ancestor (or all of them if there two). In other
2755 # ancestor (or all of them if there two). In other
2754 # words, that parent left the file unchanged while the
2756 # words, that parent left the file unchanged while the
2755 # other one deleted it.
2757 # other one deleted it.
2756 # One way to think about this is that deleting a file is
2758 # One way to think about this is that deleting a file is
2757 # similar to emptying it, so the list of changed files
2759 # similar to emptying it, so the list of changed files
2758 # should be similar either way. The computation
2760 # should be similar either way. The computation
2759 # described above is not done directly in _filecommit
2761 # described above is not done directly in _filecommit
2760 # when creating the list of changed files, however
2762 # when creating the list of changed files, however
2761 # it does something very similar by comparing filelog
2763 # it does something very similar by comparing filelog
2762 # nodes.
2764 # nodes.
2763 if f in m1:
2765 if f in m1:
2764 return (f not in m2
2766 return (f not in m2
2765 and all(f in ma and ma.find(f) == m1.find(f)
2767 and all(f in ma and ma.find(f) == m1.find(f)
2766 for ma in mas()))
2768 for ma in mas()))
2767 elif f in m2:
2769 elif f in m2:
2768 return all(f in ma and ma.find(f) == m2.find(f)
2770 return all(f in ma and ma.find(f) == m2.find(f)
2769 for ma in mas())
2771 for ma in mas())
2770 else:
2772 else:
2771 return True
2773 return True
2772 removed = [f for f in removed if not deletionfromparent(f)]
2774 removed = [f for f in removed if not deletionfromparent(f)]
2773
2775
2774 files = changed + removed
2776 files = changed + removed
2775 md = None
2777 md = None
2776 if not files:
2778 if not files:
2777 # if no "files" actually changed in terms of the changelog,
2779 # if no "files" actually changed in terms of the changelog,
2778 # try hard to detect unmodified manifest entry so that the
2780 # try hard to detect unmodified manifest entry so that the
2779 # exact same commit can be reproduced later on convert.
2781 # exact same commit can be reproduced later on convert.
2780 md = m1.diff(m, scmutil.matchfiles(self, ctx.files()))
2782 md = m1.diff(m, scmutil.matchfiles(self, ctx.files()))
2781 if not files and md:
2783 if not files and md:
2782 self.ui.debug('not reusing manifest (no file change in '
2784 self.ui.debug('not reusing manifest (no file change in '
2783 'changelog, but manifest differs)\n')
2785 'changelog, but manifest differs)\n')
2784 if files or md:
2786 if files or md:
2785 self.ui.note(_("committing manifest\n"))
2787 self.ui.note(_("committing manifest\n"))
2786 # we're using narrowmatch here since it's already applied at
2788 # we're using narrowmatch here since it's already applied at
2787 # other stages (such as dirstate.walk), so we're already
2789 # other stages (such as dirstate.walk), so we're already
2788 # ignoring things outside of narrowspec in most cases. The
2790 # ignoring things outside of narrowspec in most cases. The
2789 # one case where we might have files outside the narrowspec
2791 # one case where we might have files outside the narrowspec
2790 # at this point is merges, and we already error out in the
2792 # at this point is merges, and we already error out in the
2791 # case where the merge has files outside of the narrowspec,
2793 # case where the merge has files outside of the narrowspec,
2792 # so this is safe.
2794 # so this is safe.
2793 mn = mctx.write(trp, linkrev,
2795 mn = mctx.write(trp, linkrev,
2794 p1.manifestnode(), p2.manifestnode(),
2796 p1.manifestnode(), p2.manifestnode(),
2795 added, drop, match=self.narrowmatch())
2797 added, drop, match=self.narrowmatch())
2796
2798
2797 if writechangesetcopy:
2799 if writechangesetcopy:
2798 filesadded = [f for f in changed
2800 filesadded = [f for f in changed
2799 if not (f in m1 or f in m2)]
2801 if not (f in m1 or f in m2)]
2800 filesremoved = removed
2802 filesremoved = removed
2801 else:
2803 else:
2802 self.ui.debug('reusing manifest from p1 (listed files '
2804 self.ui.debug('reusing manifest from p1 (listed files '
2803 'actually unchanged)\n')
2805 'actually unchanged)\n')
2804 mn = p1.manifestnode()
2806 mn = p1.manifestnode()
2805 else:
2807 else:
2806 self.ui.debug('reusing manifest from p1 (no file change)\n')
2808 self.ui.debug('reusing manifest from p1 (no file change)\n')
2807 mn = p1.manifestnode()
2809 mn = p1.manifestnode()
2808 files = []
2810 files = []
2809
2811
2810 if writecopiesto == 'changeset-only':
2812 if writecopiesto == 'changeset-only':
2811 # If writing only to changeset extras, use None to indicate that
2813 # If writing only to changeset extras, use None to indicate that
2812 # no entry should be written. If writing to both, write an empty
2814 # no entry should be written. If writing to both, write an empty
2813 # entry to prevent the reader from falling back to reading
2815 # entry to prevent the reader from falling back to reading
2814 # filelogs.
2816 # filelogs.
2815 p1copies = p1copies or None
2817 p1copies = p1copies or None
2816 p2copies = p2copies or None
2818 p2copies = p2copies or None
2817 filesadded = filesadded or None
2819 filesadded = filesadded or None
2818 filesremoved = filesremoved or None
2820 filesremoved = filesremoved or None
2819
2821
2820 if origctx and origctx.manifestnode() == mn:
2822 if origctx and origctx.manifestnode() == mn:
2821 files = origctx.files()
2823 files = origctx.files()
2822
2824
2823 # update changelog
2825 # update changelog
2824 self.ui.note(_("committing changelog\n"))
2826 self.ui.note(_("committing changelog\n"))
2825 self.changelog.delayupdate(tr)
2827 self.changelog.delayupdate(tr)
2826 n = self.changelog.add(mn, files, ctx.description(),
2828 n = self.changelog.add(mn, files, ctx.description(),
2827 trp, p1.node(), p2.node(),
2829 trp, p1.node(), p2.node(),
2828 user, ctx.date(), ctx.extra().copy(),
2830 user, ctx.date(), ctx.extra().copy(),
2829 p1copies, p2copies, filesadded, filesremoved)
2831 p1copies, p2copies, filesadded, filesremoved)
2830 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
2832 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
2831 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
2833 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
2832 parent2=xp2)
2834 parent2=xp2)
2833 # set the new commit is proper phase
2835 # set the new commit is proper phase
2834 targetphase = subrepoutil.newcommitphase(self.ui, ctx)
2836 targetphase = subrepoutil.newcommitphase(self.ui, ctx)
2835 if targetphase:
2837 if targetphase:
2836 # retract boundary do not alter parent changeset.
2838 # retract boundary do not alter parent changeset.
2837 # if a parent have higher the resulting phase will
2839 # if a parent have higher the resulting phase will
2838 # be compliant anyway
2840 # be compliant anyway
2839 #
2841 #
2840 # if minimal phase was 0 we don't need to retract anything
2842 # if minimal phase was 0 we don't need to retract anything
2841 phases.registernew(self, tr, targetphase, [n])
2843 phases.registernew(self, tr, targetphase, [n])
2842 return n
2844 return n
2843
2845
2844 @unfilteredmethod
2846 @unfilteredmethod
2845 def destroying(self):
2847 def destroying(self):
2846 '''Inform the repository that nodes are about to be destroyed.
2848 '''Inform the repository that nodes are about to be destroyed.
2847 Intended for use by strip and rollback, so there's a common
2849 Intended for use by strip and rollback, so there's a common
2848 place for anything that has to be done before destroying history.
2850 place for anything that has to be done before destroying history.
2849
2851
2850 This is mostly useful for saving state that is in memory and waiting
2852 This is mostly useful for saving state that is in memory and waiting
2851 to be flushed when the current lock is released. Because a call to
2853 to be flushed when the current lock is released. Because a call to
2852 destroyed is imminent, the repo will be invalidated causing those
2854 destroyed is imminent, the repo will be invalidated causing those
2853 changes to stay in memory (waiting for the next unlock), or vanish
2855 changes to stay in memory (waiting for the next unlock), or vanish
2854 completely.
2856 completely.
2855 '''
2857 '''
2856 # When using the same lock to commit and strip, the phasecache is left
2858 # When using the same lock to commit and strip, the phasecache is left
2857 # dirty after committing. Then when we strip, the repo is invalidated,
2859 # dirty after committing. Then when we strip, the repo is invalidated,
2858 # causing those changes to disappear.
2860 # causing those changes to disappear.
2859 if '_phasecache' in vars(self):
2861 if '_phasecache' in vars(self):
2860 self._phasecache.write()
2862 self._phasecache.write()
2861
2863
2862 @unfilteredmethod
2864 @unfilteredmethod
2863 def destroyed(self):
2865 def destroyed(self):
2864 '''Inform the repository that nodes have been destroyed.
2866 '''Inform the repository that nodes have been destroyed.
2865 Intended for use by strip and rollback, so there's a common
2867 Intended for use by strip and rollback, so there's a common
2866 place for anything that has to be done after destroying history.
2868 place for anything that has to be done after destroying history.
2867 '''
2869 '''
2868 # When one tries to:
2870 # When one tries to:
2869 # 1) destroy nodes thus calling this method (e.g. strip)
2871 # 1) destroy nodes thus calling this method (e.g. strip)
2870 # 2) use phasecache somewhere (e.g. commit)
2872 # 2) use phasecache somewhere (e.g. commit)
2871 #
2873 #
2872 # then 2) will fail because the phasecache contains nodes that were
2874 # then 2) will fail because the phasecache contains nodes that were
2873 # removed. We can either remove phasecache from the filecache,
2875 # removed. We can either remove phasecache from the filecache,
2874 # causing it to reload next time it is accessed, or simply filter
2876 # causing it to reload next time it is accessed, or simply filter
2875 # the removed nodes now and write the updated cache.
2877 # the removed nodes now and write the updated cache.
2876 self._phasecache.filterunknown(self)
2878 self._phasecache.filterunknown(self)
2877 self._phasecache.write()
2879 self._phasecache.write()
2878
2880
2879 # refresh all repository caches
2881 # refresh all repository caches
2880 self.updatecaches()
2882 self.updatecaches()
2881
2883
2882 # Ensure the persistent tag cache is updated. Doing it now
2884 # Ensure the persistent tag cache is updated. Doing it now
2883 # means that the tag cache only has to worry about destroyed
2885 # means that the tag cache only has to worry about destroyed
2884 # heads immediately after a strip/rollback. That in turn
2886 # heads immediately after a strip/rollback. That in turn
2885 # guarantees that "cachetip == currenttip" (comparing both rev
2887 # guarantees that "cachetip == currenttip" (comparing both rev
2886 # and node) always means no nodes have been added or destroyed.
2888 # and node) always means no nodes have been added or destroyed.
2887
2889
2888 # XXX this is suboptimal when qrefresh'ing: we strip the current
2890 # XXX this is suboptimal when qrefresh'ing: we strip the current
2889 # head, refresh the tag cache, then immediately add a new head.
2891 # head, refresh the tag cache, then immediately add a new head.
2890 # But I think doing it this way is necessary for the "instant
2892 # But I think doing it this way is necessary for the "instant
2891 # tag cache retrieval" case to work.
2893 # tag cache retrieval" case to work.
2892 self.invalidate()
2894 self.invalidate()
2893
2895
2894 def status(self, node1='.', node2=None, match=None,
2896 def status(self, node1='.', node2=None, match=None,
2895 ignored=False, clean=False, unknown=False,
2897 ignored=False, clean=False, unknown=False,
2896 listsubrepos=False):
2898 listsubrepos=False):
2897 '''a convenience method that calls node1.status(node2)'''
2899 '''a convenience method that calls node1.status(node2)'''
2898 return self[node1].status(node2, match, ignored, clean, unknown,
2900 return self[node1].status(node2, match, ignored, clean, unknown,
2899 listsubrepos)
2901 listsubrepos)
2900
2902
2901 def addpostdsstatus(self, ps):
2903 def addpostdsstatus(self, ps):
2902 """Add a callback to run within the wlock, at the point at which status
2904 """Add a callback to run within the wlock, at the point at which status
2903 fixups happen.
2905 fixups happen.
2904
2906
2905 On status completion, callback(wctx, status) will be called with the
2907 On status completion, callback(wctx, status) will be called with the
2906 wlock held, unless the dirstate has changed from underneath or the wlock
2908 wlock held, unless the dirstate has changed from underneath or the wlock
2907 couldn't be grabbed.
2909 couldn't be grabbed.
2908
2910
2909 Callbacks should not capture and use a cached copy of the dirstate --
2911 Callbacks should not capture and use a cached copy of the dirstate --
2910 it might change in the meanwhile. Instead, they should access the
2912 it might change in the meanwhile. Instead, they should access the
2911 dirstate via wctx.repo().dirstate.
2913 dirstate via wctx.repo().dirstate.
2912
2914
2913 This list is emptied out after each status run -- extensions should
2915 This list is emptied out after each status run -- extensions should
2914 make sure it adds to this list each time dirstate.status is called.
2916 make sure it adds to this list each time dirstate.status is called.
2915 Extensions should also make sure they don't call this for statuses
2917 Extensions should also make sure they don't call this for statuses
2916 that don't involve the dirstate.
2918 that don't involve the dirstate.
2917 """
2919 """
2918
2920
2919 # The list is located here for uniqueness reasons -- it is actually
2921 # The list is located here for uniqueness reasons -- it is actually
2920 # managed by the workingctx, but that isn't unique per-repo.
2922 # managed by the workingctx, but that isn't unique per-repo.
2921 self._postdsstatus.append(ps)
2923 self._postdsstatus.append(ps)
2922
2924
2923 def postdsstatus(self):
2925 def postdsstatus(self):
2924 """Used by workingctx to get the list of post-dirstate-status hooks."""
2926 """Used by workingctx to get the list of post-dirstate-status hooks."""
2925 return self._postdsstatus
2927 return self._postdsstatus
2926
2928
2927 def clearpostdsstatus(self):
2929 def clearpostdsstatus(self):
2928 """Used by workingctx to clear post-dirstate-status hooks."""
2930 """Used by workingctx to clear post-dirstate-status hooks."""
2929 del self._postdsstatus[:]
2931 del self._postdsstatus[:]
2930
2932
2931 def heads(self, start=None):
2933 def heads(self, start=None):
2932 if start is None:
2934 if start is None:
2933 cl = self.changelog
2935 cl = self.changelog
2934 headrevs = reversed(cl.headrevs())
2936 headrevs = reversed(cl.headrevs())
2935 return [cl.node(rev) for rev in headrevs]
2937 return [cl.node(rev) for rev in headrevs]
2936
2938
2937 heads = self.changelog.heads(start)
2939 heads = self.changelog.heads(start)
2938 # sort the output in rev descending order
2940 # sort the output in rev descending order
2939 return sorted(heads, key=self.changelog.rev, reverse=True)
2941 return sorted(heads, key=self.changelog.rev, reverse=True)
2940
2942
2941 def branchheads(self, branch=None, start=None, closed=False):
2943 def branchheads(self, branch=None, start=None, closed=False):
2942 '''return a (possibly filtered) list of heads for the given branch
2944 '''return a (possibly filtered) list of heads for the given branch
2943
2945
2944 Heads are returned in topological order, from newest to oldest.
2946 Heads are returned in topological order, from newest to oldest.
2945 If branch is None, use the dirstate branch.
2947 If branch is None, use the dirstate branch.
2946 If start is not None, return only heads reachable from start.
2948 If start is not None, return only heads reachable from start.
2947 If closed is True, return heads that are marked as closed as well.
2949 If closed is True, return heads that are marked as closed as well.
2948 '''
2950 '''
2949 if branch is None:
2951 if branch is None:
2950 branch = self[None].branch()
2952 branch = self[None].branch()
2951 branches = self.branchmap()
2953 branches = self.branchmap()
2952 if not branches.hasbranch(branch):
2954 if not branches.hasbranch(branch):
2953 return []
2955 return []
2954 # the cache returns heads ordered lowest to highest
2956 # the cache returns heads ordered lowest to highest
2955 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
2957 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
2956 if start is not None:
2958 if start is not None:
2957 # filter out the heads that cannot be reached from startrev
2959 # filter out the heads that cannot be reached from startrev
2958 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
2960 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
2959 bheads = [h for h in bheads if h in fbheads]
2961 bheads = [h for h in bheads if h in fbheads]
2960 return bheads
2962 return bheads
2961
2963
2962 def branches(self, nodes):
2964 def branches(self, nodes):
2963 if not nodes:
2965 if not nodes:
2964 nodes = [self.changelog.tip()]
2966 nodes = [self.changelog.tip()]
2965 b = []
2967 b = []
2966 for n in nodes:
2968 for n in nodes:
2967 t = n
2969 t = n
2968 while True:
2970 while True:
2969 p = self.changelog.parents(n)
2971 p = self.changelog.parents(n)
2970 if p[1] != nullid or p[0] == nullid:
2972 if p[1] != nullid or p[0] == nullid:
2971 b.append((t, n, p[0], p[1]))
2973 b.append((t, n, p[0], p[1]))
2972 break
2974 break
2973 n = p[0]
2975 n = p[0]
2974 return b
2976 return b
2975
2977
2976 def between(self, pairs):
2978 def between(self, pairs):
2977 r = []
2979 r = []
2978
2980
2979 for top, bottom in pairs:
2981 for top, bottom in pairs:
2980 n, l, i = top, [], 0
2982 n, l, i = top, [], 0
2981 f = 1
2983 f = 1
2982
2984
2983 while n != bottom and n != nullid:
2985 while n != bottom and n != nullid:
2984 p = self.changelog.parents(n)[0]
2986 p = self.changelog.parents(n)[0]
2985 if i == f:
2987 if i == f:
2986 l.append(n)
2988 l.append(n)
2987 f = f * 2
2989 f = f * 2
2988 n = p
2990 n = p
2989 i += 1
2991 i += 1
2990
2992
2991 r.append(l)
2993 r.append(l)
2992
2994
2993 return r
2995 return r
2994
2996
2995 def checkpush(self, pushop):
2997 def checkpush(self, pushop):
2996 """Extensions can override this function if additional checks have
2998 """Extensions can override this function if additional checks have
2997 to be performed before pushing, or call it if they override push
2999 to be performed before pushing, or call it if they override push
2998 command.
3000 command.
2999 """
3001 """
3000
3002
3001 @unfilteredpropertycache
3003 @unfilteredpropertycache
3002 def prepushoutgoinghooks(self):
3004 def prepushoutgoinghooks(self):
3003 """Return util.hooks consists of a pushop with repo, remote, outgoing
3005 """Return util.hooks consists of a pushop with repo, remote, outgoing
3004 methods, which are called before pushing changesets.
3006 methods, which are called before pushing changesets.
3005 """
3007 """
3006 return util.hooks()
3008 return util.hooks()
3007
3009
3008 def pushkey(self, namespace, key, old, new):
3010 def pushkey(self, namespace, key, old, new):
3009 try:
3011 try:
3010 tr = self.currenttransaction()
3012 tr = self.currenttransaction()
3011 hookargs = {}
3013 hookargs = {}
3012 if tr is not None:
3014 if tr is not None:
3013 hookargs.update(tr.hookargs)
3015 hookargs.update(tr.hookargs)
3014 hookargs = pycompat.strkwargs(hookargs)
3016 hookargs = pycompat.strkwargs(hookargs)
3015 hookargs[r'namespace'] = namespace
3017 hookargs[r'namespace'] = namespace
3016 hookargs[r'key'] = key
3018 hookargs[r'key'] = key
3017 hookargs[r'old'] = old
3019 hookargs[r'old'] = old
3018 hookargs[r'new'] = new
3020 hookargs[r'new'] = new
3019 self.hook('prepushkey', throw=True, **hookargs)
3021 self.hook('prepushkey', throw=True, **hookargs)
3020 except error.HookAbort as exc:
3022 except error.HookAbort as exc:
3021 self.ui.write_err(_("pushkey-abort: %s\n") % exc)
3023 self.ui.write_err(_("pushkey-abort: %s\n") % exc)
3022 if exc.hint:
3024 if exc.hint:
3023 self.ui.write_err(_("(%s)\n") % exc.hint)
3025 self.ui.write_err(_("(%s)\n") % exc.hint)
3024 return False
3026 return False
3025 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
3027 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
3026 ret = pushkey.push(self, namespace, key, old, new)
3028 ret = pushkey.push(self, namespace, key, old, new)
3027 def runhook():
3029 def runhook():
3028 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
3030 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
3029 ret=ret)
3031 ret=ret)
3030 self._afterlock(runhook)
3032 self._afterlock(runhook)
3031 return ret
3033 return ret
3032
3034
3033 def listkeys(self, namespace):
3035 def listkeys(self, namespace):
3034 self.hook('prelistkeys', throw=True, namespace=namespace)
3036 self.hook('prelistkeys', throw=True, namespace=namespace)
3035 self.ui.debug('listing keys for "%s"\n' % namespace)
3037 self.ui.debug('listing keys for "%s"\n' % namespace)
3036 values = pushkey.list(self, namespace)
3038 values = pushkey.list(self, namespace)
3037 self.hook('listkeys', namespace=namespace, values=values)
3039 self.hook('listkeys', namespace=namespace, values=values)
3038 return values
3040 return values
3039
3041
3040 def debugwireargs(self, one, two, three=None, four=None, five=None):
3042 def debugwireargs(self, one, two, three=None, four=None, five=None):
3041 '''used to test argument passing over the wire'''
3043 '''used to test argument passing over the wire'''
3042 return "%s %s %s %s %s" % (one, two, pycompat.bytestr(three),
3044 return "%s %s %s %s %s" % (one, two, pycompat.bytestr(three),
3043 pycompat.bytestr(four),
3045 pycompat.bytestr(four),
3044 pycompat.bytestr(five))
3046 pycompat.bytestr(five))
3045
3047
3046 def savecommitmessage(self, text):
3048 def savecommitmessage(self, text):
3047 fp = self.vfs('last-message.txt', 'wb')
3049 fp = self.vfs('last-message.txt', 'wb')
3048 try:
3050 try:
3049 fp.write(text)
3051 fp.write(text)
3050 finally:
3052 finally:
3051 fp.close()
3053 fp.close()
3052 return self.pathto(fp.name[len(self.root) + 1:])
3054 return self.pathto(fp.name[len(self.root) + 1:])
3053
3055
3054 # used to avoid circular references so destructors work
3056 # used to avoid circular references so destructors work
3055 def aftertrans(files):
3057 def aftertrans(files):
3056 renamefiles = [tuple(t) for t in files]
3058 renamefiles = [tuple(t) for t in files]
3057 def a():
3059 def a():
3058 for vfs, src, dest in renamefiles:
3060 for vfs, src, dest in renamefiles:
3059 # if src and dest refer to a same file, vfs.rename is a no-op,
3061 # if src and dest refer to a same file, vfs.rename is a no-op,
3060 # leaving both src and dest on disk. delete dest to make sure
3062 # leaving both src and dest on disk. delete dest to make sure
3061 # the rename couldn't be such a no-op.
3063 # the rename couldn't be such a no-op.
3062 vfs.tryunlink(dest)
3064 vfs.tryunlink(dest)
3063 try:
3065 try:
3064 vfs.rename(src, dest)
3066 vfs.rename(src, dest)
3065 except OSError: # journal file does not yet exist
3067 except OSError: # journal file does not yet exist
3066 pass
3068 pass
3067 return a
3069 return a
3068
3070
3069 def undoname(fn):
3071 def undoname(fn):
3070 base, name = os.path.split(fn)
3072 base, name = os.path.split(fn)
3071 assert name.startswith('journal')
3073 assert name.startswith('journal')
3072 return os.path.join(base, name.replace('journal', 'undo', 1))
3074 return os.path.join(base, name.replace('journal', 'undo', 1))
3073
3075
3074 def instance(ui, path, create, intents=None, createopts=None):
3076 def instance(ui, path, create, intents=None, createopts=None):
3075 localpath = util.urllocalpath(path)
3077 localpath = util.urllocalpath(path)
3076 if create:
3078 if create:
3077 createrepository(ui, localpath, createopts=createopts)
3079 createrepository(ui, localpath, createopts=createopts)
3078
3080
3079 return makelocalrepository(ui, localpath, intents=intents)
3081 return makelocalrepository(ui, localpath, intents=intents)
3080
3082
3081 def islocal(path):
3083 def islocal(path):
3082 return True
3084 return True
3083
3085
3084 def defaultcreateopts(ui, createopts=None):
3086 def defaultcreateopts(ui, createopts=None):
3085 """Populate the default creation options for a repository.
3087 """Populate the default creation options for a repository.
3086
3088
3087 A dictionary of explicitly requested creation options can be passed
3089 A dictionary of explicitly requested creation options can be passed
3088 in. Missing keys will be populated.
3090 in. Missing keys will be populated.
3089 """
3091 """
3090 createopts = dict(createopts or {})
3092 createopts = dict(createopts or {})
3091
3093
3092 if 'backend' not in createopts:
3094 if 'backend' not in createopts:
3093 # experimental config: storage.new-repo-backend
3095 # experimental config: storage.new-repo-backend
3094 createopts['backend'] = ui.config('storage', 'new-repo-backend')
3096 createopts['backend'] = ui.config('storage', 'new-repo-backend')
3095
3097
3096 return createopts
3098 return createopts
3097
3099
3098 def newreporequirements(ui, createopts):
3100 def newreporequirements(ui, createopts):
3099 """Determine the set of requirements for a new local repository.
3101 """Determine the set of requirements for a new local repository.
3100
3102
3101 Extensions can wrap this function to specify custom requirements for
3103 Extensions can wrap this function to specify custom requirements for
3102 new repositories.
3104 new repositories.
3103 """
3105 """
3104 # If the repo is being created from a shared repository, we copy
3106 # If the repo is being created from a shared repository, we copy
3105 # its requirements.
3107 # its requirements.
3106 if 'sharedrepo' in createopts:
3108 if 'sharedrepo' in createopts:
3107 requirements = set(createopts['sharedrepo'].requirements)
3109 requirements = set(createopts['sharedrepo'].requirements)
3108 if createopts.get('sharedrelative'):
3110 if createopts.get('sharedrelative'):
3109 requirements.add('relshared')
3111 requirements.add('relshared')
3110 else:
3112 else:
3111 requirements.add('shared')
3113 requirements.add('shared')
3112
3114
3113 return requirements
3115 return requirements
3114
3116
3115 if 'backend' not in createopts:
3117 if 'backend' not in createopts:
3116 raise error.ProgrammingError('backend key not present in createopts; '
3118 raise error.ProgrammingError('backend key not present in createopts; '
3117 'was defaultcreateopts() called?')
3119 'was defaultcreateopts() called?')
3118
3120
3119 if createopts['backend'] != 'revlogv1':
3121 if createopts['backend'] != 'revlogv1':
3120 raise error.Abort(_('unable to determine repository requirements for '
3122 raise error.Abort(_('unable to determine repository requirements for '
3121 'storage backend: %s') % createopts['backend'])
3123 'storage backend: %s') % createopts['backend'])
3122
3124
3123 requirements = {'revlogv1'}
3125 requirements = {'revlogv1'}
3124 if ui.configbool('format', 'usestore'):
3126 if ui.configbool('format', 'usestore'):
3125 requirements.add('store')
3127 requirements.add('store')
3126 if ui.configbool('format', 'usefncache'):
3128 if ui.configbool('format', 'usefncache'):
3127 requirements.add('fncache')
3129 requirements.add('fncache')
3128 if ui.configbool('format', 'dotencode'):
3130 if ui.configbool('format', 'dotencode'):
3129 requirements.add('dotencode')
3131 requirements.add('dotencode')
3130
3132
3131 compengine = ui.config('format', 'revlog-compression')
3133 compengine = ui.config('format', 'revlog-compression')
3132 if compengine not in util.compengines:
3134 if compengine not in util.compengines:
3133 raise error.Abort(_('compression engine %s defined by '
3135 raise error.Abort(_('compression engine %s defined by '
3134 'format.revlog-compression not available') %
3136 'format.revlog-compression not available') %
3135 compengine,
3137 compengine,
3136 hint=_('run "hg debuginstall" to list available '
3138 hint=_('run "hg debuginstall" to list available '
3137 'compression engines'))
3139 'compression engines'))
3138
3140
3139 # zlib is the historical default and doesn't need an explicit requirement.
3141 # zlib is the historical default and doesn't need an explicit requirement.
3140 elif compengine == 'zstd':
3142 elif compengine == 'zstd':
3141 requirements.add('revlog-compression-zstd')
3143 requirements.add('revlog-compression-zstd')
3142 elif compengine != 'zlib':
3144 elif compengine != 'zlib':
3143 requirements.add('exp-compression-%s' % compengine)
3145 requirements.add('exp-compression-%s' % compengine)
3144
3146
3145 if scmutil.gdinitconfig(ui):
3147 if scmutil.gdinitconfig(ui):
3146 requirements.add('generaldelta')
3148 requirements.add('generaldelta')
3147 if ui.configbool('format', 'sparse-revlog'):
3149 if ui.configbool('format', 'sparse-revlog'):
3148 requirements.add(SPARSEREVLOG_REQUIREMENT)
3150 requirements.add(SPARSEREVLOG_REQUIREMENT)
3149 if ui.configbool('experimental', 'treemanifest'):
3151 if ui.configbool('experimental', 'treemanifest'):
3150 requirements.add('treemanifest')
3152 requirements.add('treemanifest')
3151
3153
3152 revlogv2 = ui.config('experimental', 'revlogv2')
3154 revlogv2 = ui.config('experimental', 'revlogv2')
3153 if revlogv2 == 'enable-unstable-format-and-corrupt-my-data':
3155 if revlogv2 == 'enable-unstable-format-and-corrupt-my-data':
3154 requirements.remove('revlogv1')
3156 requirements.remove('revlogv1')
3155 # generaldelta is implied by revlogv2.
3157 # generaldelta is implied by revlogv2.
3156 requirements.discard('generaldelta')
3158 requirements.discard('generaldelta')
3157 requirements.add(REVLOGV2_REQUIREMENT)
3159 requirements.add(REVLOGV2_REQUIREMENT)
3158 # experimental config: format.internal-phase
3160 # experimental config: format.internal-phase
3159 if ui.configbool('format', 'internal-phase'):
3161 if ui.configbool('format', 'internal-phase'):
3160 requirements.add('internal-phase')
3162 requirements.add('internal-phase')
3161
3163
3162 if createopts.get('narrowfiles'):
3164 if createopts.get('narrowfiles'):
3163 requirements.add(repository.NARROW_REQUIREMENT)
3165 requirements.add(repository.NARROW_REQUIREMENT)
3164
3166
3165 if createopts.get('lfs'):
3167 if createopts.get('lfs'):
3166 requirements.add('lfs')
3168 requirements.add('lfs')
3167
3169
3168 if ui.configbool('format', 'bookmarks-in-store'):
3170 if ui.configbool('format', 'bookmarks-in-store'):
3169 requirements.add(bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT)
3171 requirements.add(bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT)
3170
3172
3171 return requirements
3173 return requirements
3172
3174
3173 def filterknowncreateopts(ui, createopts):
3175 def filterknowncreateopts(ui, createopts):
3174 """Filters a dict of repo creation options against options that are known.
3176 """Filters a dict of repo creation options against options that are known.
3175
3177
3176 Receives a dict of repo creation options and returns a dict of those
3178 Receives a dict of repo creation options and returns a dict of those
3177 options that we don't know how to handle.
3179 options that we don't know how to handle.
3178
3180
3179 This function is called as part of repository creation. If the
3181 This function is called as part of repository creation. If the
3180 returned dict contains any items, repository creation will not
3182 returned dict contains any items, repository creation will not
3181 be allowed, as it means there was a request to create a repository
3183 be allowed, as it means there was a request to create a repository
3182 with options not recognized by loaded code.
3184 with options not recognized by loaded code.
3183
3185
3184 Extensions can wrap this function to filter out creation options
3186 Extensions can wrap this function to filter out creation options
3185 they know how to handle.
3187 they know how to handle.
3186 """
3188 """
3187 known = {
3189 known = {
3188 'backend',
3190 'backend',
3189 'lfs',
3191 'lfs',
3190 'narrowfiles',
3192 'narrowfiles',
3191 'sharedrepo',
3193 'sharedrepo',
3192 'sharedrelative',
3194 'sharedrelative',
3193 'shareditems',
3195 'shareditems',
3194 'shallowfilestore',
3196 'shallowfilestore',
3195 }
3197 }
3196
3198
3197 return {k: v for k, v in createopts.items() if k not in known}
3199 return {k: v for k, v in createopts.items() if k not in known}
3198
3200
3199 def createrepository(ui, path, createopts=None):
3201 def createrepository(ui, path, createopts=None):
3200 """Create a new repository in a vfs.
3202 """Create a new repository in a vfs.
3201
3203
3202 ``path`` path to the new repo's working directory.
3204 ``path`` path to the new repo's working directory.
3203 ``createopts`` options for the new repository.
3205 ``createopts`` options for the new repository.
3204
3206
3205 The following keys for ``createopts`` are recognized:
3207 The following keys for ``createopts`` are recognized:
3206
3208
3207 backend
3209 backend
3208 The storage backend to use.
3210 The storage backend to use.
3209 lfs
3211 lfs
3210 Repository will be created with ``lfs`` requirement. The lfs extension
3212 Repository will be created with ``lfs`` requirement. The lfs extension
3211 will automatically be loaded when the repository is accessed.
3213 will automatically be loaded when the repository is accessed.
3212 narrowfiles
3214 narrowfiles
3213 Set up repository to support narrow file storage.
3215 Set up repository to support narrow file storage.
3214 sharedrepo
3216 sharedrepo
3215 Repository object from which storage should be shared.
3217 Repository object from which storage should be shared.
3216 sharedrelative
3218 sharedrelative
3217 Boolean indicating if the path to the shared repo should be
3219 Boolean indicating if the path to the shared repo should be
3218 stored as relative. By default, the pointer to the "parent" repo
3220 stored as relative. By default, the pointer to the "parent" repo
3219 is stored as an absolute path.
3221 is stored as an absolute path.
3220 shareditems
3222 shareditems
3221 Set of items to share to the new repository (in addition to storage).
3223 Set of items to share to the new repository (in addition to storage).
3222 shallowfilestore
3224 shallowfilestore
3223 Indicates that storage for files should be shallow (not all ancestor
3225 Indicates that storage for files should be shallow (not all ancestor
3224 revisions are known).
3226 revisions are known).
3225 """
3227 """
3226 createopts = defaultcreateopts(ui, createopts=createopts)
3228 createopts = defaultcreateopts(ui, createopts=createopts)
3227
3229
3228 unknownopts = filterknowncreateopts(ui, createopts)
3230 unknownopts = filterknowncreateopts(ui, createopts)
3229
3231
3230 if not isinstance(unknownopts, dict):
3232 if not isinstance(unknownopts, dict):
3231 raise error.ProgrammingError('filterknowncreateopts() did not return '
3233 raise error.ProgrammingError('filterknowncreateopts() did not return '
3232 'a dict')
3234 'a dict')
3233
3235
3234 if unknownopts:
3236 if unknownopts:
3235 raise error.Abort(_('unable to create repository because of unknown '
3237 raise error.Abort(_('unable to create repository because of unknown '
3236 'creation option: %s') %
3238 'creation option: %s') %
3237 ', '.join(sorted(unknownopts)),
3239 ', '.join(sorted(unknownopts)),
3238 hint=_('is a required extension not loaded?'))
3240 hint=_('is a required extension not loaded?'))
3239
3241
3240 requirements = newreporequirements(ui, createopts=createopts)
3242 requirements = newreporequirements(ui, createopts=createopts)
3241
3243
3242 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
3244 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
3243
3245
3244 hgvfs = vfsmod.vfs(wdirvfs.join(b'.hg'))
3246 hgvfs = vfsmod.vfs(wdirvfs.join(b'.hg'))
3245 if hgvfs.exists():
3247 if hgvfs.exists():
3246 raise error.RepoError(_('repository %s already exists') % path)
3248 raise error.RepoError(_('repository %s already exists') % path)
3247
3249
3248 if 'sharedrepo' in createopts:
3250 if 'sharedrepo' in createopts:
3249 sharedpath = createopts['sharedrepo'].sharedpath
3251 sharedpath = createopts['sharedrepo'].sharedpath
3250
3252
3251 if createopts.get('sharedrelative'):
3253 if createopts.get('sharedrelative'):
3252 try:
3254 try:
3253 sharedpath = os.path.relpath(sharedpath, hgvfs.base)
3255 sharedpath = os.path.relpath(sharedpath, hgvfs.base)
3254 except (IOError, ValueError) as e:
3256 except (IOError, ValueError) as e:
3255 # ValueError is raised on Windows if the drive letters differ
3257 # ValueError is raised on Windows if the drive letters differ
3256 # on each path.
3258 # on each path.
3257 raise error.Abort(_('cannot calculate relative path'),
3259 raise error.Abort(_('cannot calculate relative path'),
3258 hint=stringutil.forcebytestr(e))
3260 hint=stringutil.forcebytestr(e))
3259
3261
3260 if not wdirvfs.exists():
3262 if not wdirvfs.exists():
3261 wdirvfs.makedirs()
3263 wdirvfs.makedirs()
3262
3264
3263 hgvfs.makedir(notindexed=True)
3265 hgvfs.makedir(notindexed=True)
3264 if 'sharedrepo' not in createopts:
3266 if 'sharedrepo' not in createopts:
3265 hgvfs.mkdir(b'cache')
3267 hgvfs.mkdir(b'cache')
3266 hgvfs.mkdir(b'wcache')
3268 hgvfs.mkdir(b'wcache')
3267
3269
3268 if b'store' in requirements and 'sharedrepo' not in createopts:
3270 if b'store' in requirements and 'sharedrepo' not in createopts:
3269 hgvfs.mkdir(b'store')
3271 hgvfs.mkdir(b'store')
3270
3272
3271 # We create an invalid changelog outside the store so very old
3273 # We create an invalid changelog outside the store so very old
3272 # Mercurial versions (which didn't know about the requirements
3274 # Mercurial versions (which didn't know about the requirements
3273 # file) encounter an error on reading the changelog. This
3275 # file) encounter an error on reading the changelog. This
3274 # effectively locks out old clients and prevents them from
3276 # effectively locks out old clients and prevents them from
3275 # mucking with a repo in an unknown format.
3277 # mucking with a repo in an unknown format.
3276 #
3278 #
3277 # The revlog header has version 2, which won't be recognized by
3279 # The revlog header has version 2, which won't be recognized by
3278 # such old clients.
3280 # such old clients.
3279 hgvfs.append(b'00changelog.i',
3281 hgvfs.append(b'00changelog.i',
3280 b'\0\0\0\2 dummy changelog to prevent using the old repo '
3282 b'\0\0\0\2 dummy changelog to prevent using the old repo '
3281 b'layout')
3283 b'layout')
3282
3284
3283 scmutil.writerequires(hgvfs, requirements)
3285 scmutil.writerequires(hgvfs, requirements)
3284
3286
3285 # Write out file telling readers where to find the shared store.
3287 # Write out file telling readers where to find the shared store.
3286 if 'sharedrepo' in createopts:
3288 if 'sharedrepo' in createopts:
3287 hgvfs.write(b'sharedpath', sharedpath)
3289 hgvfs.write(b'sharedpath', sharedpath)
3288
3290
3289 if createopts.get('shareditems'):
3291 if createopts.get('shareditems'):
3290 shared = b'\n'.join(sorted(createopts['shareditems'])) + b'\n'
3292 shared = b'\n'.join(sorted(createopts['shareditems'])) + b'\n'
3291 hgvfs.write(b'shared', shared)
3293 hgvfs.write(b'shared', shared)
3292
3294
3293 def poisonrepository(repo):
3295 def poisonrepository(repo):
3294 """Poison a repository instance so it can no longer be used."""
3296 """Poison a repository instance so it can no longer be used."""
3295 # Perform any cleanup on the instance.
3297 # Perform any cleanup on the instance.
3296 repo.close()
3298 repo.close()
3297
3299
3298 # Our strategy is to replace the type of the object with one that
3300 # Our strategy is to replace the type of the object with one that
3299 # has all attribute lookups result in error.
3301 # has all attribute lookups result in error.
3300 #
3302 #
3301 # But we have to allow the close() method because some constructors
3303 # But we have to allow the close() method because some constructors
3302 # of repos call close() on repo references.
3304 # of repos call close() on repo references.
3303 class poisonedrepository(object):
3305 class poisonedrepository(object):
3304 def __getattribute__(self, item):
3306 def __getattribute__(self, item):
3305 if item == r'close':
3307 if item == r'close':
3306 return object.__getattribute__(self, item)
3308 return object.__getattribute__(self, item)
3307
3309
3308 raise error.ProgrammingError('repo instances should not be used '
3310 raise error.ProgrammingError('repo instances should not be used '
3309 'after unshare')
3311 'after unshare')
3310
3312
3311 def close(self):
3313 def close(self):
3312 pass
3314 pass
3313
3315
3314 # We may have a repoview, which intercepts __setattr__. So be sure
3316 # We may have a repoview, which intercepts __setattr__. So be sure
3315 # we operate at the lowest level possible.
3317 # we operate at the lowest level possible.
3316 object.__setattr__(repo, r'__class__', poisonedrepository)
3318 object.__setattr__(repo, r'__class__', poisonedrepository)
@@ -1,2663 +1,2660 b''
1 # revlog.py - storage back-end for mercurial
1 # revlog.py - storage back-end for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@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 collections
16 import collections
17 import contextlib
17 import contextlib
18 import errno
18 import errno
19 import io
19 import io
20 import os
20 import os
21 import struct
21 import struct
22 import zlib
22 import zlib
23
23
24 # import stuff from node for others to import from revlog
24 # import stuff from node for others to import from revlog
25 from .node import (
25 from .node import (
26 bin,
26 bin,
27 hex,
27 hex,
28 nullhex,
28 nullhex,
29 nullid,
29 nullid,
30 nullrev,
30 nullrev,
31 short,
31 short,
32 wdirfilenodeids,
32 wdirfilenodeids,
33 wdirhex,
33 wdirhex,
34 wdirid,
34 wdirid,
35 wdirrev,
35 wdirrev,
36 )
36 )
37 from .i18n import _
37 from .i18n import _
38 from .revlogutils.constants import (
38 from .revlogutils.constants import (
39 FLAG_GENERALDELTA,
39 FLAG_GENERALDELTA,
40 FLAG_INLINE_DATA,
40 FLAG_INLINE_DATA,
41 REVLOGV0,
41 REVLOGV0,
42 REVLOGV1,
42 REVLOGV1,
43 REVLOGV1_FLAGS,
43 REVLOGV1_FLAGS,
44 REVLOGV2,
44 REVLOGV2,
45 REVLOGV2_FLAGS,
45 REVLOGV2_FLAGS,
46 REVLOG_DEFAULT_FLAGS,
46 REVLOG_DEFAULT_FLAGS,
47 REVLOG_DEFAULT_FORMAT,
47 REVLOG_DEFAULT_FORMAT,
48 REVLOG_DEFAULT_VERSION,
48 REVLOG_DEFAULT_VERSION,
49 )
49 )
50 from .revlogutils.flagutil import (
50 from .revlogutils.flagutil import (
51 REVIDX_DEFAULT_FLAGS,
51 REVIDX_DEFAULT_FLAGS,
52 REVIDX_ELLIPSIS,
52 REVIDX_ELLIPSIS,
53 REVIDX_EXTSTORED,
53 REVIDX_EXTSTORED,
54 REVIDX_FLAGS_ORDER,
54 REVIDX_FLAGS_ORDER,
55 REVIDX_ISCENSORED,
55 REVIDX_ISCENSORED,
56 REVIDX_RAWTEXT_CHANGING_FLAGS,
56 REVIDX_RAWTEXT_CHANGING_FLAGS,
57 )
57 )
58 from .thirdparty import (
58 from .thirdparty import (
59 attr,
59 attr,
60 )
60 )
61 from . import (
61 from . import (
62 ancestor,
62 ancestor,
63 dagop,
63 dagop,
64 error,
64 error,
65 mdiff,
65 mdiff,
66 policy,
66 policy,
67 pycompat,
67 pycompat,
68 templatefilters,
68 templatefilters,
69 util,
69 util,
70 )
70 )
71 from .interfaces import (
71 from .interfaces import (
72 repository,
72 repository,
73 util as interfaceutil,
73 util as interfaceutil,
74 )
74 )
75 from .revlogutils import (
75 from .revlogutils import (
76 deltas as deltautil,
76 deltas as deltautil,
77 flagutil,
77 flagutil,
78 )
78 )
79 from .utils import (
79 from .utils import (
80 storageutil,
80 storageutil,
81 stringutil,
81 stringutil,
82 )
82 )
83
83
84 # blanked usage of all the name to prevent pyflakes constraints
84 # blanked usage of all the name to prevent pyflakes constraints
85 # We need these name available in the module for extensions.
85 # We need these name available in the module for extensions.
86 REVLOGV0
86 REVLOGV0
87 REVLOGV1
87 REVLOGV1
88 REVLOGV2
88 REVLOGV2
89 FLAG_INLINE_DATA
89 FLAG_INLINE_DATA
90 FLAG_GENERALDELTA
90 FLAG_GENERALDELTA
91 REVLOG_DEFAULT_FLAGS
91 REVLOG_DEFAULT_FLAGS
92 REVLOG_DEFAULT_FORMAT
92 REVLOG_DEFAULT_FORMAT
93 REVLOG_DEFAULT_VERSION
93 REVLOG_DEFAULT_VERSION
94 REVLOGV1_FLAGS
94 REVLOGV1_FLAGS
95 REVLOGV2_FLAGS
95 REVLOGV2_FLAGS
96 REVIDX_ISCENSORED
96 REVIDX_ISCENSORED
97 REVIDX_ELLIPSIS
97 REVIDX_ELLIPSIS
98 REVIDX_EXTSTORED
98 REVIDX_EXTSTORED
99 REVIDX_DEFAULT_FLAGS
99 REVIDX_DEFAULT_FLAGS
100 REVIDX_FLAGS_ORDER
100 REVIDX_FLAGS_ORDER
101 REVIDX_RAWTEXT_CHANGING_FLAGS
101 REVIDX_RAWTEXT_CHANGING_FLAGS
102
102
103 parsers = policy.importmod(r'parsers')
103 parsers = policy.importmod(r'parsers')
104 rustancestor = policy.importrust(r'ancestor')
104 rustancestor = policy.importrust(r'ancestor')
105 rustdagop = policy.importrust(r'dagop')
105 rustdagop = policy.importrust(r'dagop')
106
106
107 # Aliased for performance.
107 # Aliased for performance.
108 _zlibdecompress = zlib.decompress
108 _zlibdecompress = zlib.decompress
109
109
110 # max size of revlog with inline data
110 # max size of revlog with inline data
111 _maxinline = 131072
111 _maxinline = 131072
112 _chunksize = 1048576
112 _chunksize = 1048576
113
113
114 # Flag processors for REVIDX_ELLIPSIS.
114 # Flag processors for REVIDX_ELLIPSIS.
115 def ellipsisreadprocessor(rl, text):
115 def ellipsisreadprocessor(rl, text):
116 return text, False, {}
116 return text, False, {}
117
117
118 def ellipsiswriteprocessor(rl, text, sidedata):
118 def ellipsiswriteprocessor(rl, text, sidedata):
119 return text, False
119 return text, False
120
120
121 def ellipsisrawprocessor(rl, text):
121 def ellipsisrawprocessor(rl, text):
122 return False
122 return False
123
123
124 ellipsisprocessor = (
124 ellipsisprocessor = (
125 ellipsisreadprocessor,
125 ellipsisreadprocessor,
126 ellipsiswriteprocessor,
126 ellipsiswriteprocessor,
127 ellipsisrawprocessor,
127 ellipsisrawprocessor,
128 )
128 )
129
129
130 def getoffset(q):
130 def getoffset(q):
131 return int(q >> 16)
131 return int(q >> 16)
132
132
133 def gettype(q):
133 def gettype(q):
134 return int(q & 0xFFFF)
134 return int(q & 0xFFFF)
135
135
136 def offset_type(offset, type):
136 def offset_type(offset, type):
137 if (type & ~flagutil.REVIDX_KNOWN_FLAGS) != 0:
137 if (type & ~flagutil.REVIDX_KNOWN_FLAGS) != 0:
138 raise ValueError('unknown revlog index flags')
138 raise ValueError('unknown revlog index flags')
139 return int(int(offset) << 16 | type)
139 return int(int(offset) << 16 | type)
140
140
141 @attr.s(slots=True, frozen=True)
141 @attr.s(slots=True, frozen=True)
142 class _revisioninfo(object):
142 class _revisioninfo(object):
143 """Information about a revision that allows building its fulltext
143 """Information about a revision that allows building its fulltext
144 node: expected hash of the revision
144 node: expected hash of the revision
145 p1, p2: parent revs of the revision
145 p1, p2: parent revs of the revision
146 btext: built text cache consisting of a one-element list
146 btext: built text cache consisting of a one-element list
147 cachedelta: (baserev, uncompressed_delta) or None
147 cachedelta: (baserev, uncompressed_delta) or None
148 flags: flags associated to the revision storage
148 flags: flags associated to the revision storage
149
149
150 One of btext[0] or cachedelta must be set.
150 One of btext[0] or cachedelta must be set.
151 """
151 """
152 node = attr.ib()
152 node = attr.ib()
153 p1 = attr.ib()
153 p1 = attr.ib()
154 p2 = attr.ib()
154 p2 = attr.ib()
155 btext = attr.ib()
155 btext = attr.ib()
156 textlen = attr.ib()
156 textlen = attr.ib()
157 cachedelta = attr.ib()
157 cachedelta = attr.ib()
158 flags = attr.ib()
158 flags = attr.ib()
159
159
160 @interfaceutil.implementer(repository.irevisiondelta)
160 @interfaceutil.implementer(repository.irevisiondelta)
161 @attr.s(slots=True)
161 @attr.s(slots=True)
162 class revlogrevisiondelta(object):
162 class revlogrevisiondelta(object):
163 node = attr.ib()
163 node = attr.ib()
164 p1node = attr.ib()
164 p1node = attr.ib()
165 p2node = attr.ib()
165 p2node = attr.ib()
166 basenode = attr.ib()
166 basenode = attr.ib()
167 flags = attr.ib()
167 flags = attr.ib()
168 baserevisionsize = attr.ib()
168 baserevisionsize = attr.ib()
169 revision = attr.ib()
169 revision = attr.ib()
170 delta = attr.ib()
170 delta = attr.ib()
171 linknode = attr.ib(default=None)
171 linknode = attr.ib(default=None)
172
172
173 @interfaceutil.implementer(repository.iverifyproblem)
173 @interfaceutil.implementer(repository.iverifyproblem)
174 @attr.s(frozen=True)
174 @attr.s(frozen=True)
175 class revlogproblem(object):
175 class revlogproblem(object):
176 warning = attr.ib(default=None)
176 warning = attr.ib(default=None)
177 error = attr.ib(default=None)
177 error = attr.ib(default=None)
178 node = attr.ib(default=None)
178 node = attr.ib(default=None)
179
179
180 # index v0:
180 # index v0:
181 # 4 bytes: offset
181 # 4 bytes: offset
182 # 4 bytes: compressed length
182 # 4 bytes: compressed length
183 # 4 bytes: base rev
183 # 4 bytes: base rev
184 # 4 bytes: link rev
184 # 4 bytes: link rev
185 # 20 bytes: parent 1 nodeid
185 # 20 bytes: parent 1 nodeid
186 # 20 bytes: parent 2 nodeid
186 # 20 bytes: parent 2 nodeid
187 # 20 bytes: nodeid
187 # 20 bytes: nodeid
188 indexformatv0 = struct.Struct(">4l20s20s20s")
188 indexformatv0 = struct.Struct(">4l20s20s20s")
189 indexformatv0_pack = indexformatv0.pack
189 indexformatv0_pack = indexformatv0.pack
190 indexformatv0_unpack = indexformatv0.unpack
190 indexformatv0_unpack = indexformatv0.unpack
191
191
192 class revlogoldindex(list):
192 class revlogoldindex(list):
193 def __getitem__(self, i):
193 def __getitem__(self, i):
194 if i == -1:
194 if i == -1:
195 return (0, 0, 0, -1, -1, -1, -1, nullid)
195 return (0, 0, 0, -1, -1, -1, -1, nullid)
196 return list.__getitem__(self, i)
196 return list.__getitem__(self, i)
197
197
198 class revlogoldio(object):
198 class revlogoldio(object):
199 def __init__(self):
199 def __init__(self):
200 self.size = indexformatv0.size
200 self.size = indexformatv0.size
201
201
202 def parseindex(self, data, inline):
202 def parseindex(self, data, inline):
203 s = self.size
203 s = self.size
204 index = []
204 index = []
205 nodemap = {nullid: nullrev}
205 nodemap = {nullid: nullrev}
206 n = off = 0
206 n = off = 0
207 l = len(data)
207 l = len(data)
208 while off + s <= l:
208 while off + s <= l:
209 cur = data[off:off + s]
209 cur = data[off:off + s]
210 off += s
210 off += s
211 e = indexformatv0_unpack(cur)
211 e = indexformatv0_unpack(cur)
212 # transform to revlogv1 format
212 # transform to revlogv1 format
213 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
213 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
214 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
214 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
215 index.append(e2)
215 index.append(e2)
216 nodemap[e[6]] = n
216 nodemap[e[6]] = n
217 n += 1
217 n += 1
218
218
219 return revlogoldindex(index), nodemap, None
219 return revlogoldindex(index), nodemap, None
220
220
221 def packentry(self, entry, node, version, rev):
221 def packentry(self, entry, node, version, rev):
222 if gettype(entry[0]):
222 if gettype(entry[0]):
223 raise error.RevlogError(_('index entry flags need revlog '
223 raise error.RevlogError(_('index entry flags need revlog '
224 'version 1'))
224 'version 1'))
225 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
225 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
226 node(entry[5]), node(entry[6]), entry[7])
226 node(entry[5]), node(entry[6]), entry[7])
227 return indexformatv0_pack(*e2)
227 return indexformatv0_pack(*e2)
228
228
229 # index ng:
229 # index ng:
230 # 6 bytes: offset
230 # 6 bytes: offset
231 # 2 bytes: flags
231 # 2 bytes: flags
232 # 4 bytes: compressed length
232 # 4 bytes: compressed length
233 # 4 bytes: uncompressed length
233 # 4 bytes: uncompressed length
234 # 4 bytes: base rev
234 # 4 bytes: base rev
235 # 4 bytes: link rev
235 # 4 bytes: link rev
236 # 4 bytes: parent 1 rev
236 # 4 bytes: parent 1 rev
237 # 4 bytes: parent 2 rev
237 # 4 bytes: parent 2 rev
238 # 32 bytes: nodeid
238 # 32 bytes: nodeid
239 indexformatng = struct.Struct(">Qiiiiii20s12x")
239 indexformatng = struct.Struct(">Qiiiiii20s12x")
240 indexformatng_pack = indexformatng.pack
240 indexformatng_pack = indexformatng.pack
241 versionformat = struct.Struct(">I")
241 versionformat = struct.Struct(">I")
242 versionformat_pack = versionformat.pack
242 versionformat_pack = versionformat.pack
243 versionformat_unpack = versionformat.unpack
243 versionformat_unpack = versionformat.unpack
244
244
245 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
245 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
246 # signed integer)
246 # signed integer)
247 _maxentrysize = 0x7fffffff
247 _maxentrysize = 0x7fffffff
248
248
249 class revlogio(object):
249 class revlogio(object):
250 def __init__(self):
250 def __init__(self):
251 self.size = indexformatng.size
251 self.size = indexformatng.size
252
252
253 def parseindex(self, data, inline):
253 def parseindex(self, data, inline):
254 # call the C implementation to parse the index data
254 # call the C implementation to parse the index data
255 index, cache = parsers.parse_index2(data, inline)
255 index, cache = parsers.parse_index2(data, inline)
256 return index, getattr(index, 'nodemap', None), cache
256 return index, getattr(index, 'nodemap', None), cache
257
257
258 def packentry(self, entry, node, version, rev):
258 def packentry(self, entry, node, version, rev):
259 p = indexformatng_pack(*entry)
259 p = indexformatng_pack(*entry)
260 if rev == 0:
260 if rev == 0:
261 p = versionformat_pack(version) + p[4:]
261 p = versionformat_pack(version) + p[4:]
262 return p
262 return p
263
263
264 class revlog(object):
264 class revlog(object):
265 """
265 """
266 the underlying revision storage object
266 the underlying revision storage object
267
267
268 A revlog consists of two parts, an index and the revision data.
268 A revlog consists of two parts, an index and the revision data.
269
269
270 The index is a file with a fixed record size containing
270 The index is a file with a fixed record size containing
271 information on each revision, including its nodeid (hash), the
271 information on each revision, including its nodeid (hash), the
272 nodeids of its parents, the position and offset of its data within
272 nodeids of its parents, the position and offset of its data within
273 the data file, and the revision it's based on. Finally, each entry
273 the data file, and the revision it's based on. Finally, each entry
274 contains a linkrev entry that can serve as a pointer to external
274 contains a linkrev entry that can serve as a pointer to external
275 data.
275 data.
276
276
277 The revision data itself is a linear collection of data chunks.
277 The revision data itself is a linear collection of data chunks.
278 Each chunk represents a revision and is usually represented as a
278 Each chunk represents a revision and is usually represented as a
279 delta against the previous chunk. To bound lookup time, runs of
279 delta against the previous chunk. To bound lookup time, runs of
280 deltas are limited to about 2 times the length of the original
280 deltas are limited to about 2 times the length of the original
281 version data. This makes retrieval of a version proportional to
281 version data. This makes retrieval of a version proportional to
282 its size, or O(1) relative to the number of revisions.
282 its size, or O(1) relative to the number of revisions.
283
283
284 Both pieces of the revlog are written to in an append-only
284 Both pieces of the revlog are written to in an append-only
285 fashion, which means we never need to rewrite a file to insert or
285 fashion, which means we never need to rewrite a file to insert or
286 remove data, and can use some simple techniques to avoid the need
286 remove data, and can use some simple techniques to avoid the need
287 for locking while reading.
287 for locking while reading.
288
288
289 If checkambig, indexfile is opened with checkambig=True at
289 If checkambig, indexfile is opened with checkambig=True at
290 writing, to avoid file stat ambiguity.
290 writing, to avoid file stat ambiguity.
291
291
292 If mmaplargeindex is True, and an mmapindexthreshold is set, the
292 If mmaplargeindex is True, and an mmapindexthreshold is set, the
293 index will be mmapped rather than read if it is larger than the
293 index will be mmapped rather than read if it is larger than the
294 configured threshold.
294 configured threshold.
295
295
296 If censorable is True, the revlog can have censored revisions.
296 If censorable is True, the revlog can have censored revisions.
297
297
298 If `upperboundcomp` is not None, this is the expected maximal gain from
298 If `upperboundcomp` is not None, this is the expected maximal gain from
299 compression for the data content.
299 compression for the data content.
300 """
300 """
301
301
302 _flagserrorclass = error.RevlogError
302 _flagserrorclass = error.RevlogError
303
303
304 def __init__(self, opener, indexfile, datafile=None, checkambig=False,
304 def __init__(self, opener, indexfile, datafile=None, checkambig=False,
305 mmaplargeindex=False, censorable=False,
305 mmaplargeindex=False, censorable=False,
306 upperboundcomp=None):
306 upperboundcomp=None):
307 """
307 """
308 create a revlog object
308 create a revlog object
309
309
310 opener is a function that abstracts the file opening operation
310 opener is a function that abstracts the file opening operation
311 and can be used to implement COW semantics or the like.
311 and can be used to implement COW semantics or the like.
312
312
313 """
313 """
314 self.upperboundcomp = upperboundcomp
314 self.upperboundcomp = upperboundcomp
315 self.indexfile = indexfile
315 self.indexfile = indexfile
316 self.datafile = datafile or (indexfile[:-2] + ".d")
316 self.datafile = datafile or (indexfile[:-2] + ".d")
317 self.opener = opener
317 self.opener = opener
318 # When True, indexfile is opened with checkambig=True at writing, to
318 # When True, indexfile is opened with checkambig=True at writing, to
319 # avoid file stat ambiguity.
319 # avoid file stat ambiguity.
320 self._checkambig = checkambig
320 self._checkambig = checkambig
321 self._mmaplargeindex = mmaplargeindex
321 self._mmaplargeindex = mmaplargeindex
322 self._censorable = censorable
322 self._censorable = censorable
323 # 3-tuple of (node, rev, text) for a raw revision.
323 # 3-tuple of (node, rev, text) for a raw revision.
324 self._revisioncache = None
324 self._revisioncache = None
325 # Maps rev to chain base rev.
325 # Maps rev to chain base rev.
326 self._chainbasecache = util.lrucachedict(100)
326 self._chainbasecache = util.lrucachedict(100)
327 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
327 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
328 self._chunkcache = (0, '')
328 self._chunkcache = (0, '')
329 # How much data to read and cache into the raw revlog data cache.
329 # How much data to read and cache into the raw revlog data cache.
330 self._chunkcachesize = 65536
330 self._chunkcachesize = 65536
331 self._maxchainlen = None
331 self._maxchainlen = None
332 self._deltabothparents = True
332 self._deltabothparents = True
333 self.index = []
333 self.index = []
334 # Mapping of partial identifiers to full nodes.
334 # Mapping of partial identifiers to full nodes.
335 self._pcache = {}
335 self._pcache = {}
336 # Mapping of revision integer to full node.
336 # Mapping of revision integer to full node.
337 self._nodecache = {nullid: nullrev}
337 self._nodecache = {nullid: nullrev}
338 self._nodepos = None
338 self._nodepos = None
339 self._compengine = 'zlib'
339 self._compengine = 'zlib'
340 self._compengineopts = {}
340 self._compengineopts = {}
341 self._maxdeltachainspan = -1
341 self._maxdeltachainspan = -1
342 self._withsparseread = False
342 self._withsparseread = False
343 self._sparserevlog = False
343 self._sparserevlog = False
344 self._srdensitythreshold = 0.50
344 self._srdensitythreshold = 0.50
345 self._srmingapsize = 262144
345 self._srmingapsize = 262144
346
346
347 # Make copy of flag processors so each revlog instance can support
347 # Make copy of flag processors so each revlog instance can support
348 # custom flags.
348 # custom flags.
349 self._flagprocessors = dict(flagutil.flagprocessors)
349 self._flagprocessors = dict(flagutil.flagprocessors)
350
350
351 # 2-tuple of file handles being used for active writing.
351 # 2-tuple of file handles being used for active writing.
352 self._writinghandles = None
352 self._writinghandles = None
353
353
354 self._loadindex()
354 self._loadindex()
355
355
356 def _loadindex(self):
356 def _loadindex(self):
357 mmapindexthreshold = None
357 mmapindexthreshold = None
358 opts = getattr(self.opener, 'options', {}) or {}
358 opts = getattr(self.opener, 'options', {}) or {}
359
359
360 if 'revlogv2' in opts:
360 if 'revlogv2' in opts:
361 newversionflags = REVLOGV2 | FLAG_INLINE_DATA
361 newversionflags = REVLOGV2 | FLAG_INLINE_DATA
362 elif 'revlogv1' in opts:
362 elif 'revlogv1' in opts:
363 newversionflags = REVLOGV1 | FLAG_INLINE_DATA
363 newversionflags = REVLOGV1 | FLAG_INLINE_DATA
364 if 'generaldelta' in opts:
364 if 'generaldelta' in opts:
365 newversionflags |= FLAG_GENERALDELTA
365 newversionflags |= FLAG_GENERALDELTA
366 elif getattr(self.opener, 'options', None) is not None:
366 elif 'revlogv0' in getattr(self.opener, 'options', {}):
367 # If options provided but no 'revlog*' found, the repository
368 # would have no 'requires' file in it, which means we have to
369 # stick to the old format.
370 newversionflags = REVLOGV0
367 newversionflags = REVLOGV0
371 else:
368 else:
372 newversionflags = REVLOG_DEFAULT_VERSION
369 newversionflags = REVLOG_DEFAULT_VERSION
373
370
374 if 'chunkcachesize' in opts:
371 if 'chunkcachesize' in opts:
375 self._chunkcachesize = opts['chunkcachesize']
372 self._chunkcachesize = opts['chunkcachesize']
376 if 'maxchainlen' in opts:
373 if 'maxchainlen' in opts:
377 self._maxchainlen = opts['maxchainlen']
374 self._maxchainlen = opts['maxchainlen']
378 if 'deltabothparents' in opts:
375 if 'deltabothparents' in opts:
379 self._deltabothparents = opts['deltabothparents']
376 self._deltabothparents = opts['deltabothparents']
380 self._lazydelta = bool(opts.get('lazydelta', True))
377 self._lazydelta = bool(opts.get('lazydelta', True))
381 self._lazydeltabase = False
378 self._lazydeltabase = False
382 if self._lazydelta:
379 if self._lazydelta:
383 self._lazydeltabase = bool(opts.get('lazydeltabase', False))
380 self._lazydeltabase = bool(opts.get('lazydeltabase', False))
384 if 'compengine' in opts:
381 if 'compengine' in opts:
385 self._compengine = opts['compengine']
382 self._compengine = opts['compengine']
386 if 'zlib.level' in opts:
383 if 'zlib.level' in opts:
387 self._compengineopts['zlib.level'] = opts['zlib.level']
384 self._compengineopts['zlib.level'] = opts['zlib.level']
388 if 'zstd.level' in opts:
385 if 'zstd.level' in opts:
389 self._compengineopts['zstd.level'] = opts['zstd.level']
386 self._compengineopts['zstd.level'] = opts['zstd.level']
390 if 'maxdeltachainspan' in opts:
387 if 'maxdeltachainspan' in opts:
391 self._maxdeltachainspan = opts['maxdeltachainspan']
388 self._maxdeltachainspan = opts['maxdeltachainspan']
392 if self._mmaplargeindex and 'mmapindexthreshold' in opts:
389 if self._mmaplargeindex and 'mmapindexthreshold' in opts:
393 mmapindexthreshold = opts['mmapindexthreshold']
390 mmapindexthreshold = opts['mmapindexthreshold']
394 self._sparserevlog = bool(opts.get('sparse-revlog', False))
391 self._sparserevlog = bool(opts.get('sparse-revlog', False))
395 withsparseread = bool(opts.get('with-sparse-read', False))
392 withsparseread = bool(opts.get('with-sparse-read', False))
396 # sparse-revlog forces sparse-read
393 # sparse-revlog forces sparse-read
397 self._withsparseread = self._sparserevlog or withsparseread
394 self._withsparseread = self._sparserevlog or withsparseread
398 if 'sparse-read-density-threshold' in opts:
395 if 'sparse-read-density-threshold' in opts:
399 self._srdensitythreshold = opts['sparse-read-density-threshold']
396 self._srdensitythreshold = opts['sparse-read-density-threshold']
400 if 'sparse-read-min-gap-size' in opts:
397 if 'sparse-read-min-gap-size' in opts:
401 self._srmingapsize = opts['sparse-read-min-gap-size']
398 self._srmingapsize = opts['sparse-read-min-gap-size']
402 if opts.get('enableellipsis'):
399 if opts.get('enableellipsis'):
403 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
400 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
404
401
405 # revlog v0 doesn't have flag processors
402 # revlog v0 doesn't have flag processors
406 for flag, processor in opts.get(b'flagprocessors', {}).iteritems():
403 for flag, processor in opts.get(b'flagprocessors', {}).iteritems():
407 flagutil.insertflagprocessor(flag, processor, self._flagprocessors)
404 flagutil.insertflagprocessor(flag, processor, self._flagprocessors)
408
405
409 if self._chunkcachesize <= 0:
406 if self._chunkcachesize <= 0:
410 raise error.RevlogError(_('revlog chunk cache size %r is not '
407 raise error.RevlogError(_('revlog chunk cache size %r is not '
411 'greater than 0') % self._chunkcachesize)
408 'greater than 0') % self._chunkcachesize)
412 elif self._chunkcachesize & (self._chunkcachesize - 1):
409 elif self._chunkcachesize & (self._chunkcachesize - 1):
413 raise error.RevlogError(_('revlog chunk cache size %r is not a '
410 raise error.RevlogError(_('revlog chunk cache size %r is not a '
414 'power of 2') % self._chunkcachesize)
411 'power of 2') % self._chunkcachesize)
415
412
416 indexdata = ''
413 indexdata = ''
417 self._initempty = True
414 self._initempty = True
418 try:
415 try:
419 with self._indexfp() as f:
416 with self._indexfp() as f:
420 if (mmapindexthreshold is not None and
417 if (mmapindexthreshold is not None and
421 self.opener.fstat(f).st_size >= mmapindexthreshold):
418 self.opener.fstat(f).st_size >= mmapindexthreshold):
422 # TODO: should .close() to release resources without
419 # TODO: should .close() to release resources without
423 # relying on Python GC
420 # relying on Python GC
424 indexdata = util.buffer(util.mmapread(f))
421 indexdata = util.buffer(util.mmapread(f))
425 else:
422 else:
426 indexdata = f.read()
423 indexdata = f.read()
427 if len(indexdata) > 0:
424 if len(indexdata) > 0:
428 versionflags = versionformat_unpack(indexdata[:4])[0]
425 versionflags = versionformat_unpack(indexdata[:4])[0]
429 self._initempty = False
426 self._initempty = False
430 else:
427 else:
431 versionflags = newversionflags
428 versionflags = newversionflags
432 except IOError as inst:
429 except IOError as inst:
433 if inst.errno != errno.ENOENT:
430 if inst.errno != errno.ENOENT:
434 raise
431 raise
435
432
436 versionflags = newversionflags
433 versionflags = newversionflags
437
434
438 self.version = versionflags
435 self.version = versionflags
439
436
440 flags = versionflags & ~0xFFFF
437 flags = versionflags & ~0xFFFF
441 fmt = versionflags & 0xFFFF
438 fmt = versionflags & 0xFFFF
442
439
443 if fmt == REVLOGV0:
440 if fmt == REVLOGV0:
444 if flags:
441 if flags:
445 raise error.RevlogError(_('unknown flags (%#04x) in version %d '
442 raise error.RevlogError(_('unknown flags (%#04x) in version %d '
446 'revlog %s') %
443 'revlog %s') %
447 (flags >> 16, fmt, self.indexfile))
444 (flags >> 16, fmt, self.indexfile))
448
445
449 self._inline = False
446 self._inline = False
450 self._generaldelta = False
447 self._generaldelta = False
451
448
452 elif fmt == REVLOGV1:
449 elif fmt == REVLOGV1:
453 if flags & ~REVLOGV1_FLAGS:
450 if flags & ~REVLOGV1_FLAGS:
454 raise error.RevlogError(_('unknown flags (%#04x) in version %d '
451 raise error.RevlogError(_('unknown flags (%#04x) in version %d '
455 'revlog %s') %
452 'revlog %s') %
456 (flags >> 16, fmt, self.indexfile))
453 (flags >> 16, fmt, self.indexfile))
457
454
458 self._inline = versionflags & FLAG_INLINE_DATA
455 self._inline = versionflags & FLAG_INLINE_DATA
459 self._generaldelta = versionflags & FLAG_GENERALDELTA
456 self._generaldelta = versionflags & FLAG_GENERALDELTA
460
457
461 elif fmt == REVLOGV2:
458 elif fmt == REVLOGV2:
462 if flags & ~REVLOGV2_FLAGS:
459 if flags & ~REVLOGV2_FLAGS:
463 raise error.RevlogError(_('unknown flags (%#04x) in version %d '
460 raise error.RevlogError(_('unknown flags (%#04x) in version %d '
464 'revlog %s') %
461 'revlog %s') %
465 (flags >> 16, fmt, self.indexfile))
462 (flags >> 16, fmt, self.indexfile))
466
463
467 self._inline = versionflags & FLAG_INLINE_DATA
464 self._inline = versionflags & FLAG_INLINE_DATA
468 # generaldelta implied by version 2 revlogs.
465 # generaldelta implied by version 2 revlogs.
469 self._generaldelta = True
466 self._generaldelta = True
470
467
471 else:
468 else:
472 raise error.RevlogError(_('unknown version (%d) in revlog %s') %
469 raise error.RevlogError(_('unknown version (%d) in revlog %s') %
473 (fmt, self.indexfile))
470 (fmt, self.indexfile))
474 # sparse-revlog can't be on without general-delta (issue6056)
471 # sparse-revlog can't be on without general-delta (issue6056)
475 if not self._generaldelta:
472 if not self._generaldelta:
476 self._sparserevlog = False
473 self._sparserevlog = False
477
474
478 self._storedeltachains = True
475 self._storedeltachains = True
479
476
480 self._io = revlogio()
477 self._io = revlogio()
481 if self.version == REVLOGV0:
478 if self.version == REVLOGV0:
482 self._io = revlogoldio()
479 self._io = revlogoldio()
483 try:
480 try:
484 d = self._io.parseindex(indexdata, self._inline)
481 d = self._io.parseindex(indexdata, self._inline)
485 except (ValueError, IndexError):
482 except (ValueError, IndexError):
486 raise error.RevlogError(_("index %s is corrupted") %
483 raise error.RevlogError(_("index %s is corrupted") %
487 self.indexfile)
484 self.indexfile)
488 self.index, nodemap, self._chunkcache = d
485 self.index, nodemap, self._chunkcache = d
489 if nodemap is not None:
486 if nodemap is not None:
490 self.nodemap = self._nodecache = nodemap
487 self.nodemap = self._nodecache = nodemap
491 if not self._chunkcache:
488 if not self._chunkcache:
492 self._chunkclear()
489 self._chunkclear()
493 # revnum -> (chain-length, sum-delta-length)
490 # revnum -> (chain-length, sum-delta-length)
494 self._chaininfocache = {}
491 self._chaininfocache = {}
495 # revlog header -> revlog compressor
492 # revlog header -> revlog compressor
496 self._decompressors = {}
493 self._decompressors = {}
497
494
498 @util.propertycache
495 @util.propertycache
499 def _compressor(self):
496 def _compressor(self):
500 engine = util.compengines[self._compengine]
497 engine = util.compengines[self._compengine]
501 return engine.revlogcompressor(self._compengineopts)
498 return engine.revlogcompressor(self._compengineopts)
502
499
503 def _indexfp(self, mode='r'):
500 def _indexfp(self, mode='r'):
504 """file object for the revlog's index file"""
501 """file object for the revlog's index file"""
505 args = {r'mode': mode}
502 args = {r'mode': mode}
506 if mode != 'r':
503 if mode != 'r':
507 args[r'checkambig'] = self._checkambig
504 args[r'checkambig'] = self._checkambig
508 if mode == 'w':
505 if mode == 'w':
509 args[r'atomictemp'] = True
506 args[r'atomictemp'] = True
510 return self.opener(self.indexfile, **args)
507 return self.opener(self.indexfile, **args)
511
508
512 def _datafp(self, mode='r'):
509 def _datafp(self, mode='r'):
513 """file object for the revlog's data file"""
510 """file object for the revlog's data file"""
514 return self.opener(self.datafile, mode=mode)
511 return self.opener(self.datafile, mode=mode)
515
512
516 @contextlib.contextmanager
513 @contextlib.contextmanager
517 def _datareadfp(self, existingfp=None):
514 def _datareadfp(self, existingfp=None):
518 """file object suitable to read data"""
515 """file object suitable to read data"""
519 # Use explicit file handle, if given.
516 # Use explicit file handle, if given.
520 if existingfp is not None:
517 if existingfp is not None:
521 yield existingfp
518 yield existingfp
522
519
523 # Use a file handle being actively used for writes, if available.
520 # Use a file handle being actively used for writes, if available.
524 # There is some danger to doing this because reads will seek the
521 # There is some danger to doing this because reads will seek the
525 # file. However, _writeentry() performs a SEEK_END before all writes,
522 # file. However, _writeentry() performs a SEEK_END before all writes,
526 # so we should be safe.
523 # so we should be safe.
527 elif self._writinghandles:
524 elif self._writinghandles:
528 if self._inline:
525 if self._inline:
529 yield self._writinghandles[0]
526 yield self._writinghandles[0]
530 else:
527 else:
531 yield self._writinghandles[1]
528 yield self._writinghandles[1]
532
529
533 # Otherwise open a new file handle.
530 # Otherwise open a new file handle.
534 else:
531 else:
535 if self._inline:
532 if self._inline:
536 func = self._indexfp
533 func = self._indexfp
537 else:
534 else:
538 func = self._datafp
535 func = self._datafp
539 with func() as fp:
536 with func() as fp:
540 yield fp
537 yield fp
541
538
542 def tip(self):
539 def tip(self):
543 return self.node(len(self.index) - 1)
540 return self.node(len(self.index) - 1)
544 def __contains__(self, rev):
541 def __contains__(self, rev):
545 return 0 <= rev < len(self)
542 return 0 <= rev < len(self)
546 def __len__(self):
543 def __len__(self):
547 return len(self.index)
544 return len(self.index)
548 def __iter__(self):
545 def __iter__(self):
549 return iter(pycompat.xrange(len(self)))
546 return iter(pycompat.xrange(len(self)))
550 def revs(self, start=0, stop=None):
547 def revs(self, start=0, stop=None):
551 """iterate over all rev in this revlog (from start to stop)"""
548 """iterate over all rev in this revlog (from start to stop)"""
552 return storageutil.iterrevs(len(self), start=start, stop=stop)
549 return storageutil.iterrevs(len(self), start=start, stop=stop)
553
550
554 @util.propertycache
551 @util.propertycache
555 def nodemap(self):
552 def nodemap(self):
556 if self.index:
553 if self.index:
557 # populate mapping down to the initial node
554 # populate mapping down to the initial node
558 node0 = self.index[0][7] # get around changelog filtering
555 node0 = self.index[0][7] # get around changelog filtering
559 self.rev(node0)
556 self.rev(node0)
560 return self._nodecache
557 return self._nodecache
561
558
562 def hasnode(self, node):
559 def hasnode(self, node):
563 try:
560 try:
564 self.rev(node)
561 self.rev(node)
565 return True
562 return True
566 except KeyError:
563 except KeyError:
567 return False
564 return False
568
565
569 def candelta(self, baserev, rev):
566 def candelta(self, baserev, rev):
570 """whether two revisions (baserev, rev) can be delta-ed or not"""
567 """whether two revisions (baserev, rev) can be delta-ed or not"""
571 # Disable delta if either rev requires a content-changing flag
568 # Disable delta if either rev requires a content-changing flag
572 # processor (ex. LFS). This is because such flag processor can alter
569 # processor (ex. LFS). This is because such flag processor can alter
573 # the rawtext content that the delta will be based on, and two clients
570 # the rawtext content that the delta will be based on, and two clients
574 # could have a same revlog node with different flags (i.e. different
571 # could have a same revlog node with different flags (i.e. different
575 # rawtext contents) and the delta could be incompatible.
572 # rawtext contents) and the delta could be incompatible.
576 if ((self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS)
573 if ((self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS)
577 or (self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS)):
574 or (self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS)):
578 return False
575 return False
579 return True
576 return True
580
577
581 def clearcaches(self):
578 def clearcaches(self):
582 self._revisioncache = None
579 self._revisioncache = None
583 self._chainbasecache.clear()
580 self._chainbasecache.clear()
584 self._chunkcache = (0, '')
581 self._chunkcache = (0, '')
585 self._pcache = {}
582 self._pcache = {}
586
583
587 try:
584 try:
588 # If we are using the native C version, you are in a fun case
585 # If we are using the native C version, you are in a fun case
589 # where self.index, self.nodemap and self._nodecaches is the same
586 # where self.index, self.nodemap and self._nodecaches is the same
590 # object.
587 # object.
591 self._nodecache.clearcaches()
588 self._nodecache.clearcaches()
592 except AttributeError:
589 except AttributeError:
593 self._nodecache = {nullid: nullrev}
590 self._nodecache = {nullid: nullrev}
594 self._nodepos = None
591 self._nodepos = None
595
592
596 def rev(self, node):
593 def rev(self, node):
597 try:
594 try:
598 return self._nodecache[node]
595 return self._nodecache[node]
599 except TypeError:
596 except TypeError:
600 raise
597 raise
601 except error.RevlogError:
598 except error.RevlogError:
602 # parsers.c radix tree lookup failed
599 # parsers.c radix tree lookup failed
603 if node == wdirid or node in wdirfilenodeids:
600 if node == wdirid or node in wdirfilenodeids:
604 raise error.WdirUnsupported
601 raise error.WdirUnsupported
605 raise error.LookupError(node, self.indexfile, _('no node'))
602 raise error.LookupError(node, self.indexfile, _('no node'))
606 except KeyError:
603 except KeyError:
607 # pure python cache lookup failed
604 # pure python cache lookup failed
608 n = self._nodecache
605 n = self._nodecache
609 i = self.index
606 i = self.index
610 p = self._nodepos
607 p = self._nodepos
611 if p is None:
608 if p is None:
612 p = len(i) - 1
609 p = len(i) - 1
613 else:
610 else:
614 assert p < len(i)
611 assert p < len(i)
615 for r in pycompat.xrange(p, -1, -1):
612 for r in pycompat.xrange(p, -1, -1):
616 v = i[r][7]
613 v = i[r][7]
617 n[v] = r
614 n[v] = r
618 if v == node:
615 if v == node:
619 self._nodepos = r - 1
616 self._nodepos = r - 1
620 return r
617 return r
621 if node == wdirid or node in wdirfilenodeids:
618 if node == wdirid or node in wdirfilenodeids:
622 raise error.WdirUnsupported
619 raise error.WdirUnsupported
623 raise error.LookupError(node, self.indexfile, _('no node'))
620 raise error.LookupError(node, self.indexfile, _('no node'))
624
621
625 # Accessors for index entries.
622 # Accessors for index entries.
626
623
627 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
624 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
628 # are flags.
625 # are flags.
629 def start(self, rev):
626 def start(self, rev):
630 return int(self.index[rev][0] >> 16)
627 return int(self.index[rev][0] >> 16)
631
628
632 def flags(self, rev):
629 def flags(self, rev):
633 return self.index[rev][0] & 0xFFFF
630 return self.index[rev][0] & 0xFFFF
634
631
635 def length(self, rev):
632 def length(self, rev):
636 return self.index[rev][1]
633 return self.index[rev][1]
637
634
638 def rawsize(self, rev):
635 def rawsize(self, rev):
639 """return the length of the uncompressed text for a given revision"""
636 """return the length of the uncompressed text for a given revision"""
640 l = self.index[rev][2]
637 l = self.index[rev][2]
641 if l >= 0:
638 if l >= 0:
642 return l
639 return l
643
640
644 t = self.rawdata(rev)
641 t = self.rawdata(rev)
645 return len(t)
642 return len(t)
646
643
647 def size(self, rev):
644 def size(self, rev):
648 """length of non-raw text (processed by a "read" flag processor)"""
645 """length of non-raw text (processed by a "read" flag processor)"""
649 # fast path: if no "read" flag processor could change the content,
646 # fast path: if no "read" flag processor could change the content,
650 # size is rawsize. note: ELLIPSIS is known to not change the content.
647 # size is rawsize. note: ELLIPSIS is known to not change the content.
651 flags = self.flags(rev)
648 flags = self.flags(rev)
652 if flags & (flagutil.REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
649 if flags & (flagutil.REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
653 return self.rawsize(rev)
650 return self.rawsize(rev)
654
651
655 return len(self.revision(rev, raw=False))
652 return len(self.revision(rev, raw=False))
656
653
657 def chainbase(self, rev):
654 def chainbase(self, rev):
658 base = self._chainbasecache.get(rev)
655 base = self._chainbasecache.get(rev)
659 if base is not None:
656 if base is not None:
660 return base
657 return base
661
658
662 index = self.index
659 index = self.index
663 iterrev = rev
660 iterrev = rev
664 base = index[iterrev][3]
661 base = index[iterrev][3]
665 while base != iterrev:
662 while base != iterrev:
666 iterrev = base
663 iterrev = base
667 base = index[iterrev][3]
664 base = index[iterrev][3]
668
665
669 self._chainbasecache[rev] = base
666 self._chainbasecache[rev] = base
670 return base
667 return base
671
668
672 def linkrev(self, rev):
669 def linkrev(self, rev):
673 return self.index[rev][4]
670 return self.index[rev][4]
674
671
675 def parentrevs(self, rev):
672 def parentrevs(self, rev):
676 try:
673 try:
677 entry = self.index[rev]
674 entry = self.index[rev]
678 except IndexError:
675 except IndexError:
679 if rev == wdirrev:
676 if rev == wdirrev:
680 raise error.WdirUnsupported
677 raise error.WdirUnsupported
681 raise
678 raise
682
679
683 return entry[5], entry[6]
680 return entry[5], entry[6]
684
681
685 # fast parentrevs(rev) where rev isn't filtered
682 # fast parentrevs(rev) where rev isn't filtered
686 _uncheckedparentrevs = parentrevs
683 _uncheckedparentrevs = parentrevs
687
684
688 def node(self, rev):
685 def node(self, rev):
689 try:
686 try:
690 return self.index[rev][7]
687 return self.index[rev][7]
691 except IndexError:
688 except IndexError:
692 if rev == wdirrev:
689 if rev == wdirrev:
693 raise error.WdirUnsupported
690 raise error.WdirUnsupported
694 raise
691 raise
695
692
696 # Derived from index values.
693 # Derived from index values.
697
694
698 def end(self, rev):
695 def end(self, rev):
699 return self.start(rev) + self.length(rev)
696 return self.start(rev) + self.length(rev)
700
697
701 def parents(self, node):
698 def parents(self, node):
702 i = self.index
699 i = self.index
703 d = i[self.rev(node)]
700 d = i[self.rev(node)]
704 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
701 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
705
702
706 def chainlen(self, rev):
703 def chainlen(self, rev):
707 return self._chaininfo(rev)[0]
704 return self._chaininfo(rev)[0]
708
705
709 def _chaininfo(self, rev):
706 def _chaininfo(self, rev):
710 chaininfocache = self._chaininfocache
707 chaininfocache = self._chaininfocache
711 if rev in chaininfocache:
708 if rev in chaininfocache:
712 return chaininfocache[rev]
709 return chaininfocache[rev]
713 index = self.index
710 index = self.index
714 generaldelta = self._generaldelta
711 generaldelta = self._generaldelta
715 iterrev = rev
712 iterrev = rev
716 e = index[iterrev]
713 e = index[iterrev]
717 clen = 0
714 clen = 0
718 compresseddeltalen = 0
715 compresseddeltalen = 0
719 while iterrev != e[3]:
716 while iterrev != e[3]:
720 clen += 1
717 clen += 1
721 compresseddeltalen += e[1]
718 compresseddeltalen += e[1]
722 if generaldelta:
719 if generaldelta:
723 iterrev = e[3]
720 iterrev = e[3]
724 else:
721 else:
725 iterrev -= 1
722 iterrev -= 1
726 if iterrev in chaininfocache:
723 if iterrev in chaininfocache:
727 t = chaininfocache[iterrev]
724 t = chaininfocache[iterrev]
728 clen += t[0]
725 clen += t[0]
729 compresseddeltalen += t[1]
726 compresseddeltalen += t[1]
730 break
727 break
731 e = index[iterrev]
728 e = index[iterrev]
732 else:
729 else:
733 # Add text length of base since decompressing that also takes
730 # Add text length of base since decompressing that also takes
734 # work. For cache hits the length is already included.
731 # work. For cache hits the length is already included.
735 compresseddeltalen += e[1]
732 compresseddeltalen += e[1]
736 r = (clen, compresseddeltalen)
733 r = (clen, compresseddeltalen)
737 chaininfocache[rev] = r
734 chaininfocache[rev] = r
738 return r
735 return r
739
736
740 def _deltachain(self, rev, stoprev=None):
737 def _deltachain(self, rev, stoprev=None):
741 """Obtain the delta chain for a revision.
738 """Obtain the delta chain for a revision.
742
739
743 ``stoprev`` specifies a revision to stop at. If not specified, we
740 ``stoprev`` specifies a revision to stop at. If not specified, we
744 stop at the base of the chain.
741 stop at the base of the chain.
745
742
746 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
743 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
747 revs in ascending order and ``stopped`` is a bool indicating whether
744 revs in ascending order and ``stopped`` is a bool indicating whether
748 ``stoprev`` was hit.
745 ``stoprev`` was hit.
749 """
746 """
750 # Try C implementation.
747 # Try C implementation.
751 try:
748 try:
752 return self.index.deltachain(rev, stoprev, self._generaldelta)
749 return self.index.deltachain(rev, stoprev, self._generaldelta)
753 except AttributeError:
750 except AttributeError:
754 pass
751 pass
755
752
756 chain = []
753 chain = []
757
754
758 # Alias to prevent attribute lookup in tight loop.
755 # Alias to prevent attribute lookup in tight loop.
759 index = self.index
756 index = self.index
760 generaldelta = self._generaldelta
757 generaldelta = self._generaldelta
761
758
762 iterrev = rev
759 iterrev = rev
763 e = index[iterrev]
760 e = index[iterrev]
764 while iterrev != e[3] and iterrev != stoprev:
761 while iterrev != e[3] and iterrev != stoprev:
765 chain.append(iterrev)
762 chain.append(iterrev)
766 if generaldelta:
763 if generaldelta:
767 iterrev = e[3]
764 iterrev = e[3]
768 else:
765 else:
769 iterrev -= 1
766 iterrev -= 1
770 e = index[iterrev]
767 e = index[iterrev]
771
768
772 if iterrev == stoprev:
769 if iterrev == stoprev:
773 stopped = True
770 stopped = True
774 else:
771 else:
775 chain.append(iterrev)
772 chain.append(iterrev)
776 stopped = False
773 stopped = False
777
774
778 chain.reverse()
775 chain.reverse()
779 return chain, stopped
776 return chain, stopped
780
777
781 def ancestors(self, revs, stoprev=0, inclusive=False):
778 def ancestors(self, revs, stoprev=0, inclusive=False):
782 """Generate the ancestors of 'revs' in reverse revision order.
779 """Generate the ancestors of 'revs' in reverse revision order.
783 Does not generate revs lower than stoprev.
780 Does not generate revs lower than stoprev.
784
781
785 See the documentation for ancestor.lazyancestors for more details."""
782 See the documentation for ancestor.lazyancestors for more details."""
786
783
787 # first, make sure start revisions aren't filtered
784 # first, make sure start revisions aren't filtered
788 revs = list(revs)
785 revs = list(revs)
789 checkrev = self.node
786 checkrev = self.node
790 for r in revs:
787 for r in revs:
791 checkrev(r)
788 checkrev(r)
792 # and we're sure ancestors aren't filtered as well
789 # and we're sure ancestors aren't filtered as well
793
790
794 if rustancestor is not None:
791 if rustancestor is not None:
795 lazyancestors = rustancestor.LazyAncestors
792 lazyancestors = rustancestor.LazyAncestors
796 arg = self.index
793 arg = self.index
797 elif util.safehasattr(parsers, 'rustlazyancestors'):
794 elif util.safehasattr(parsers, 'rustlazyancestors'):
798 lazyancestors = ancestor.rustlazyancestors
795 lazyancestors = ancestor.rustlazyancestors
799 arg = self.index
796 arg = self.index
800 else:
797 else:
801 lazyancestors = ancestor.lazyancestors
798 lazyancestors = ancestor.lazyancestors
802 arg = self._uncheckedparentrevs
799 arg = self._uncheckedparentrevs
803 return lazyancestors(arg, revs, stoprev=stoprev, inclusive=inclusive)
800 return lazyancestors(arg, revs, stoprev=stoprev, inclusive=inclusive)
804
801
805 def descendants(self, revs):
802 def descendants(self, revs):
806 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
803 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
807
804
808 def findcommonmissing(self, common=None, heads=None):
805 def findcommonmissing(self, common=None, heads=None):
809 """Return a tuple of the ancestors of common and the ancestors of heads
806 """Return a tuple of the ancestors of common and the ancestors of heads
810 that are not ancestors of common. In revset terminology, we return the
807 that are not ancestors of common. In revset terminology, we return the
811 tuple:
808 tuple:
812
809
813 ::common, (::heads) - (::common)
810 ::common, (::heads) - (::common)
814
811
815 The list is sorted by revision number, meaning it is
812 The list is sorted by revision number, meaning it is
816 topologically sorted.
813 topologically sorted.
817
814
818 'heads' and 'common' are both lists of node IDs. If heads is
815 'heads' and 'common' are both lists of node IDs. If heads is
819 not supplied, uses all of the revlog's heads. If common is not
816 not supplied, uses all of the revlog's heads. If common is not
820 supplied, uses nullid."""
817 supplied, uses nullid."""
821 if common is None:
818 if common is None:
822 common = [nullid]
819 common = [nullid]
823 if heads is None:
820 if heads is None:
824 heads = self.heads()
821 heads = self.heads()
825
822
826 common = [self.rev(n) for n in common]
823 common = [self.rev(n) for n in common]
827 heads = [self.rev(n) for n in heads]
824 heads = [self.rev(n) for n in heads]
828
825
829 # we want the ancestors, but inclusive
826 # we want the ancestors, but inclusive
830 class lazyset(object):
827 class lazyset(object):
831 def __init__(self, lazyvalues):
828 def __init__(self, lazyvalues):
832 self.addedvalues = set()
829 self.addedvalues = set()
833 self.lazyvalues = lazyvalues
830 self.lazyvalues = lazyvalues
834
831
835 def __contains__(self, value):
832 def __contains__(self, value):
836 return value in self.addedvalues or value in self.lazyvalues
833 return value in self.addedvalues or value in self.lazyvalues
837
834
838 def __iter__(self):
835 def __iter__(self):
839 added = self.addedvalues
836 added = self.addedvalues
840 for r in added:
837 for r in added:
841 yield r
838 yield r
842 for r in self.lazyvalues:
839 for r in self.lazyvalues:
843 if not r in added:
840 if not r in added:
844 yield r
841 yield r
845
842
846 def add(self, value):
843 def add(self, value):
847 self.addedvalues.add(value)
844 self.addedvalues.add(value)
848
845
849 def update(self, values):
846 def update(self, values):
850 self.addedvalues.update(values)
847 self.addedvalues.update(values)
851
848
852 has = lazyset(self.ancestors(common))
849 has = lazyset(self.ancestors(common))
853 has.add(nullrev)
850 has.add(nullrev)
854 has.update(common)
851 has.update(common)
855
852
856 # take all ancestors from heads that aren't in has
853 # take all ancestors from heads that aren't in has
857 missing = set()
854 missing = set()
858 visit = collections.deque(r for r in heads if r not in has)
855 visit = collections.deque(r for r in heads if r not in has)
859 while visit:
856 while visit:
860 r = visit.popleft()
857 r = visit.popleft()
861 if r in missing:
858 if r in missing:
862 continue
859 continue
863 else:
860 else:
864 missing.add(r)
861 missing.add(r)
865 for p in self.parentrevs(r):
862 for p in self.parentrevs(r):
866 if p not in has:
863 if p not in has:
867 visit.append(p)
864 visit.append(p)
868 missing = list(missing)
865 missing = list(missing)
869 missing.sort()
866 missing.sort()
870 return has, [self.node(miss) for miss in missing]
867 return has, [self.node(miss) for miss in missing]
871
868
872 def incrementalmissingrevs(self, common=None):
869 def incrementalmissingrevs(self, common=None):
873 """Return an object that can be used to incrementally compute the
870 """Return an object that can be used to incrementally compute the
874 revision numbers of the ancestors of arbitrary sets that are not
871 revision numbers of the ancestors of arbitrary sets that are not
875 ancestors of common. This is an ancestor.incrementalmissingancestors
872 ancestors of common. This is an ancestor.incrementalmissingancestors
876 object.
873 object.
877
874
878 'common' is a list of revision numbers. If common is not supplied, uses
875 'common' is a list of revision numbers. If common is not supplied, uses
879 nullrev.
876 nullrev.
880 """
877 """
881 if common is None:
878 if common is None:
882 common = [nullrev]
879 common = [nullrev]
883
880
884 if rustancestor is not None:
881 if rustancestor is not None:
885 return rustancestor.MissingAncestors(self.index, common)
882 return rustancestor.MissingAncestors(self.index, common)
886 return ancestor.incrementalmissingancestors(self.parentrevs, common)
883 return ancestor.incrementalmissingancestors(self.parentrevs, common)
887
884
888 def findmissingrevs(self, common=None, heads=None):
885 def findmissingrevs(self, common=None, heads=None):
889 """Return the revision numbers of the ancestors of heads that
886 """Return the revision numbers of the ancestors of heads that
890 are not ancestors of common.
887 are not ancestors of common.
891
888
892 More specifically, return a list of revision numbers corresponding to
889 More specifically, return a list of revision numbers corresponding to
893 nodes N such that every N satisfies the following constraints:
890 nodes N such that every N satisfies the following constraints:
894
891
895 1. N is an ancestor of some node in 'heads'
892 1. N is an ancestor of some node in 'heads'
896 2. N is not an ancestor of any node in 'common'
893 2. N is not an ancestor of any node in 'common'
897
894
898 The list is sorted by revision number, meaning it is
895 The list is sorted by revision number, meaning it is
899 topologically sorted.
896 topologically sorted.
900
897
901 'heads' and 'common' are both lists of revision numbers. If heads is
898 'heads' and 'common' are both lists of revision numbers. If heads is
902 not supplied, uses all of the revlog's heads. If common is not
899 not supplied, uses all of the revlog's heads. If common is not
903 supplied, uses nullid."""
900 supplied, uses nullid."""
904 if common is None:
901 if common is None:
905 common = [nullrev]
902 common = [nullrev]
906 if heads is None:
903 if heads is None:
907 heads = self.headrevs()
904 heads = self.headrevs()
908
905
909 inc = self.incrementalmissingrevs(common=common)
906 inc = self.incrementalmissingrevs(common=common)
910 return inc.missingancestors(heads)
907 return inc.missingancestors(heads)
911
908
912 def findmissing(self, common=None, heads=None):
909 def findmissing(self, common=None, heads=None):
913 """Return the ancestors of heads that are not ancestors of common.
910 """Return the ancestors of heads that are not ancestors of common.
914
911
915 More specifically, return a list of nodes N such that every N
912 More specifically, return a list of nodes N such that every N
916 satisfies the following constraints:
913 satisfies the following constraints:
917
914
918 1. N is an ancestor of some node in 'heads'
915 1. N is an ancestor of some node in 'heads'
919 2. N is not an ancestor of any node in 'common'
916 2. N is not an ancestor of any node in 'common'
920
917
921 The list is sorted by revision number, meaning it is
918 The list is sorted by revision number, meaning it is
922 topologically sorted.
919 topologically sorted.
923
920
924 'heads' and 'common' are both lists of node IDs. If heads is
921 'heads' and 'common' are both lists of node IDs. If heads is
925 not supplied, uses all of the revlog's heads. If common is not
922 not supplied, uses all of the revlog's heads. If common is not
926 supplied, uses nullid."""
923 supplied, uses nullid."""
927 if common is None:
924 if common is None:
928 common = [nullid]
925 common = [nullid]
929 if heads is None:
926 if heads is None:
930 heads = self.heads()
927 heads = self.heads()
931
928
932 common = [self.rev(n) for n in common]
929 common = [self.rev(n) for n in common]
933 heads = [self.rev(n) for n in heads]
930 heads = [self.rev(n) for n in heads]
934
931
935 inc = self.incrementalmissingrevs(common=common)
932 inc = self.incrementalmissingrevs(common=common)
936 return [self.node(r) for r in inc.missingancestors(heads)]
933 return [self.node(r) for r in inc.missingancestors(heads)]
937
934
938 def nodesbetween(self, roots=None, heads=None):
935 def nodesbetween(self, roots=None, heads=None):
939 """Return a topological path from 'roots' to 'heads'.
936 """Return a topological path from 'roots' to 'heads'.
940
937
941 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
938 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
942 topologically sorted list of all nodes N that satisfy both of
939 topologically sorted list of all nodes N that satisfy both of
943 these constraints:
940 these constraints:
944
941
945 1. N is a descendant of some node in 'roots'
942 1. N is a descendant of some node in 'roots'
946 2. N is an ancestor of some node in 'heads'
943 2. N is an ancestor of some node in 'heads'
947
944
948 Every node is considered to be both a descendant and an ancestor
945 Every node is considered to be both a descendant and an ancestor
949 of itself, so every reachable node in 'roots' and 'heads' will be
946 of itself, so every reachable node in 'roots' and 'heads' will be
950 included in 'nodes'.
947 included in 'nodes'.
951
948
952 'outroots' is the list of reachable nodes in 'roots', i.e., the
949 'outroots' is the list of reachable nodes in 'roots', i.e., the
953 subset of 'roots' that is returned in 'nodes'. Likewise,
950 subset of 'roots' that is returned in 'nodes'. Likewise,
954 'outheads' is the subset of 'heads' that is also in 'nodes'.
951 'outheads' is the subset of 'heads' that is also in 'nodes'.
955
952
956 'roots' and 'heads' are both lists of node IDs. If 'roots' is
953 'roots' and 'heads' are both lists of node IDs. If 'roots' is
957 unspecified, uses nullid as the only root. If 'heads' is
954 unspecified, uses nullid as the only root. If 'heads' is
958 unspecified, uses list of all of the revlog's heads."""
955 unspecified, uses list of all of the revlog's heads."""
959 nonodes = ([], [], [])
956 nonodes = ([], [], [])
960 if roots is not None:
957 if roots is not None:
961 roots = list(roots)
958 roots = list(roots)
962 if not roots:
959 if not roots:
963 return nonodes
960 return nonodes
964 lowestrev = min([self.rev(n) for n in roots])
961 lowestrev = min([self.rev(n) for n in roots])
965 else:
962 else:
966 roots = [nullid] # Everybody's a descendant of nullid
963 roots = [nullid] # Everybody's a descendant of nullid
967 lowestrev = nullrev
964 lowestrev = nullrev
968 if (lowestrev == nullrev) and (heads is None):
965 if (lowestrev == nullrev) and (heads is None):
969 # We want _all_ the nodes!
966 # We want _all_ the nodes!
970 return ([self.node(r) for r in self], [nullid], list(self.heads()))
967 return ([self.node(r) for r in self], [nullid], list(self.heads()))
971 if heads is None:
968 if heads is None:
972 # All nodes are ancestors, so the latest ancestor is the last
969 # All nodes are ancestors, so the latest ancestor is the last
973 # node.
970 # node.
974 highestrev = len(self) - 1
971 highestrev = len(self) - 1
975 # Set ancestors to None to signal that every node is an ancestor.
972 # Set ancestors to None to signal that every node is an ancestor.
976 ancestors = None
973 ancestors = None
977 # Set heads to an empty dictionary for later discovery of heads
974 # Set heads to an empty dictionary for later discovery of heads
978 heads = {}
975 heads = {}
979 else:
976 else:
980 heads = list(heads)
977 heads = list(heads)
981 if not heads:
978 if not heads:
982 return nonodes
979 return nonodes
983 ancestors = set()
980 ancestors = set()
984 # Turn heads into a dictionary so we can remove 'fake' heads.
981 # Turn heads into a dictionary so we can remove 'fake' heads.
985 # Also, later we will be using it to filter out the heads we can't
982 # Also, later we will be using it to filter out the heads we can't
986 # find from roots.
983 # find from roots.
987 heads = dict.fromkeys(heads, False)
984 heads = dict.fromkeys(heads, False)
988 # Start at the top and keep marking parents until we're done.
985 # Start at the top and keep marking parents until we're done.
989 nodestotag = set(heads)
986 nodestotag = set(heads)
990 # Remember where the top was so we can use it as a limit later.
987 # Remember where the top was so we can use it as a limit later.
991 highestrev = max([self.rev(n) for n in nodestotag])
988 highestrev = max([self.rev(n) for n in nodestotag])
992 while nodestotag:
989 while nodestotag:
993 # grab a node to tag
990 # grab a node to tag
994 n = nodestotag.pop()
991 n = nodestotag.pop()
995 # Never tag nullid
992 # Never tag nullid
996 if n == nullid:
993 if n == nullid:
997 continue
994 continue
998 # A node's revision number represents its place in a
995 # A node's revision number represents its place in a
999 # topologically sorted list of nodes.
996 # topologically sorted list of nodes.
1000 r = self.rev(n)
997 r = self.rev(n)
1001 if r >= lowestrev:
998 if r >= lowestrev:
1002 if n not in ancestors:
999 if n not in ancestors:
1003 # If we are possibly a descendant of one of the roots
1000 # If we are possibly a descendant of one of the roots
1004 # and we haven't already been marked as an ancestor
1001 # and we haven't already been marked as an ancestor
1005 ancestors.add(n) # Mark as ancestor
1002 ancestors.add(n) # Mark as ancestor
1006 # Add non-nullid parents to list of nodes to tag.
1003 # Add non-nullid parents to list of nodes to tag.
1007 nodestotag.update([p for p in self.parents(n) if
1004 nodestotag.update([p for p in self.parents(n) if
1008 p != nullid])
1005 p != nullid])
1009 elif n in heads: # We've seen it before, is it a fake head?
1006 elif n in heads: # We've seen it before, is it a fake head?
1010 # So it is, real heads should not be the ancestors of
1007 # So it is, real heads should not be the ancestors of
1011 # any other heads.
1008 # any other heads.
1012 heads.pop(n)
1009 heads.pop(n)
1013 if not ancestors:
1010 if not ancestors:
1014 return nonodes
1011 return nonodes
1015 # Now that we have our set of ancestors, we want to remove any
1012 # Now that we have our set of ancestors, we want to remove any
1016 # roots that are not ancestors.
1013 # roots that are not ancestors.
1017
1014
1018 # If one of the roots was nullid, everything is included anyway.
1015 # If one of the roots was nullid, everything is included anyway.
1019 if lowestrev > nullrev:
1016 if lowestrev > nullrev:
1020 # But, since we weren't, let's recompute the lowest rev to not
1017 # But, since we weren't, let's recompute the lowest rev to not
1021 # include roots that aren't ancestors.
1018 # include roots that aren't ancestors.
1022
1019
1023 # Filter out roots that aren't ancestors of heads
1020 # Filter out roots that aren't ancestors of heads
1024 roots = [root for root in roots if root in ancestors]
1021 roots = [root for root in roots if root in ancestors]
1025 # Recompute the lowest revision
1022 # Recompute the lowest revision
1026 if roots:
1023 if roots:
1027 lowestrev = min([self.rev(root) for root in roots])
1024 lowestrev = min([self.rev(root) for root in roots])
1028 else:
1025 else:
1029 # No more roots? Return empty list
1026 # No more roots? Return empty list
1030 return nonodes
1027 return nonodes
1031 else:
1028 else:
1032 # We are descending from nullid, and don't need to care about
1029 # We are descending from nullid, and don't need to care about
1033 # any other roots.
1030 # any other roots.
1034 lowestrev = nullrev
1031 lowestrev = nullrev
1035 roots = [nullid]
1032 roots = [nullid]
1036 # Transform our roots list into a set.
1033 # Transform our roots list into a set.
1037 descendants = set(roots)
1034 descendants = set(roots)
1038 # Also, keep the original roots so we can filter out roots that aren't
1035 # Also, keep the original roots so we can filter out roots that aren't
1039 # 'real' roots (i.e. are descended from other roots).
1036 # 'real' roots (i.e. are descended from other roots).
1040 roots = descendants.copy()
1037 roots = descendants.copy()
1041 # Our topologically sorted list of output nodes.
1038 # Our topologically sorted list of output nodes.
1042 orderedout = []
1039 orderedout = []
1043 # Don't start at nullid since we don't want nullid in our output list,
1040 # Don't start at nullid since we don't want nullid in our output list,
1044 # and if nullid shows up in descendants, empty parents will look like
1041 # and if nullid shows up in descendants, empty parents will look like
1045 # they're descendants.
1042 # they're descendants.
1046 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1043 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1047 n = self.node(r)
1044 n = self.node(r)
1048 isdescendant = False
1045 isdescendant = False
1049 if lowestrev == nullrev: # Everybody is a descendant of nullid
1046 if lowestrev == nullrev: # Everybody is a descendant of nullid
1050 isdescendant = True
1047 isdescendant = True
1051 elif n in descendants:
1048 elif n in descendants:
1052 # n is already a descendant
1049 # n is already a descendant
1053 isdescendant = True
1050 isdescendant = True
1054 # This check only needs to be done here because all the roots
1051 # This check only needs to be done here because all the roots
1055 # will start being marked is descendants before the loop.
1052 # will start being marked is descendants before the loop.
1056 if n in roots:
1053 if n in roots:
1057 # If n was a root, check if it's a 'real' root.
1054 # If n was a root, check if it's a 'real' root.
1058 p = tuple(self.parents(n))
1055 p = tuple(self.parents(n))
1059 # If any of its parents are descendants, it's not a root.
1056 # If any of its parents are descendants, it's not a root.
1060 if (p[0] in descendants) or (p[1] in descendants):
1057 if (p[0] in descendants) or (p[1] in descendants):
1061 roots.remove(n)
1058 roots.remove(n)
1062 else:
1059 else:
1063 p = tuple(self.parents(n))
1060 p = tuple(self.parents(n))
1064 # A node is a descendant if either of its parents are
1061 # A node is a descendant if either of its parents are
1065 # descendants. (We seeded the dependents list with the roots
1062 # descendants. (We seeded the dependents list with the roots
1066 # up there, remember?)
1063 # up there, remember?)
1067 if (p[0] in descendants) or (p[1] in descendants):
1064 if (p[0] in descendants) or (p[1] in descendants):
1068 descendants.add(n)
1065 descendants.add(n)
1069 isdescendant = True
1066 isdescendant = True
1070 if isdescendant and ((ancestors is None) or (n in ancestors)):
1067 if isdescendant and ((ancestors is None) or (n in ancestors)):
1071 # Only include nodes that are both descendants and ancestors.
1068 # Only include nodes that are both descendants and ancestors.
1072 orderedout.append(n)
1069 orderedout.append(n)
1073 if (ancestors is not None) and (n in heads):
1070 if (ancestors is not None) and (n in heads):
1074 # We're trying to figure out which heads are reachable
1071 # We're trying to figure out which heads are reachable
1075 # from roots.
1072 # from roots.
1076 # Mark this head as having been reached
1073 # Mark this head as having been reached
1077 heads[n] = True
1074 heads[n] = True
1078 elif ancestors is None:
1075 elif ancestors is None:
1079 # Otherwise, we're trying to discover the heads.
1076 # Otherwise, we're trying to discover the heads.
1080 # Assume this is a head because if it isn't, the next step
1077 # Assume this is a head because if it isn't, the next step
1081 # will eventually remove it.
1078 # will eventually remove it.
1082 heads[n] = True
1079 heads[n] = True
1083 # But, obviously its parents aren't.
1080 # But, obviously its parents aren't.
1084 for p in self.parents(n):
1081 for p in self.parents(n):
1085 heads.pop(p, None)
1082 heads.pop(p, None)
1086 heads = [head for head, flag in heads.iteritems() if flag]
1083 heads = [head for head, flag in heads.iteritems() if flag]
1087 roots = list(roots)
1084 roots = list(roots)
1088 assert orderedout
1085 assert orderedout
1089 assert roots
1086 assert roots
1090 assert heads
1087 assert heads
1091 return (orderedout, roots, heads)
1088 return (orderedout, roots, heads)
1092
1089
1093 def headrevs(self, revs=None):
1090 def headrevs(self, revs=None):
1094 if revs is None:
1091 if revs is None:
1095 try:
1092 try:
1096 return self.index.headrevs()
1093 return self.index.headrevs()
1097 except AttributeError:
1094 except AttributeError:
1098 return self._headrevs()
1095 return self._headrevs()
1099 if rustdagop is not None:
1096 if rustdagop is not None:
1100 return rustdagop.headrevs(self.index, revs)
1097 return rustdagop.headrevs(self.index, revs)
1101 return dagop.headrevs(revs, self._uncheckedparentrevs)
1098 return dagop.headrevs(revs, self._uncheckedparentrevs)
1102
1099
1103 def computephases(self, roots):
1100 def computephases(self, roots):
1104 return self.index.computephasesmapsets(roots)
1101 return self.index.computephasesmapsets(roots)
1105
1102
1106 def _headrevs(self):
1103 def _headrevs(self):
1107 count = len(self)
1104 count = len(self)
1108 if not count:
1105 if not count:
1109 return [nullrev]
1106 return [nullrev]
1110 # we won't iter over filtered rev so nobody is a head at start
1107 # we won't iter over filtered rev so nobody is a head at start
1111 ishead = [0] * (count + 1)
1108 ishead = [0] * (count + 1)
1112 index = self.index
1109 index = self.index
1113 for r in self:
1110 for r in self:
1114 ishead[r] = 1 # I may be an head
1111 ishead[r] = 1 # I may be an head
1115 e = index[r]
1112 e = index[r]
1116 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1113 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1117 return [r for r, val in enumerate(ishead) if val]
1114 return [r for r, val in enumerate(ishead) if val]
1118
1115
1119 def heads(self, start=None, stop=None):
1116 def heads(self, start=None, stop=None):
1120 """return the list of all nodes that have no children
1117 """return the list of all nodes that have no children
1121
1118
1122 if start is specified, only heads that are descendants of
1119 if start is specified, only heads that are descendants of
1123 start will be returned
1120 start will be returned
1124 if stop is specified, it will consider all the revs from stop
1121 if stop is specified, it will consider all the revs from stop
1125 as if they had no children
1122 as if they had no children
1126 """
1123 """
1127 if start is None and stop is None:
1124 if start is None and stop is None:
1128 if not len(self):
1125 if not len(self):
1129 return [nullid]
1126 return [nullid]
1130 return [self.node(r) for r in self.headrevs()]
1127 return [self.node(r) for r in self.headrevs()]
1131
1128
1132 if start is None:
1129 if start is None:
1133 start = nullrev
1130 start = nullrev
1134 else:
1131 else:
1135 start = self.rev(start)
1132 start = self.rev(start)
1136
1133
1137 stoprevs = set(self.rev(n) for n in stop or [])
1134 stoprevs = set(self.rev(n) for n in stop or [])
1138
1135
1139 revs = dagop.headrevssubset(self.revs, self.parentrevs, startrev=start,
1136 revs = dagop.headrevssubset(self.revs, self.parentrevs, startrev=start,
1140 stoprevs=stoprevs)
1137 stoprevs=stoprevs)
1141
1138
1142 return [self.node(rev) for rev in revs]
1139 return [self.node(rev) for rev in revs]
1143
1140
1144 def children(self, node):
1141 def children(self, node):
1145 """find the children of a given node"""
1142 """find the children of a given node"""
1146 c = []
1143 c = []
1147 p = self.rev(node)
1144 p = self.rev(node)
1148 for r in self.revs(start=p + 1):
1145 for r in self.revs(start=p + 1):
1149 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1146 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1150 if prevs:
1147 if prevs:
1151 for pr in prevs:
1148 for pr in prevs:
1152 if pr == p:
1149 if pr == p:
1153 c.append(self.node(r))
1150 c.append(self.node(r))
1154 elif p == nullrev:
1151 elif p == nullrev:
1155 c.append(self.node(r))
1152 c.append(self.node(r))
1156 return c
1153 return c
1157
1154
1158 def commonancestorsheads(self, a, b):
1155 def commonancestorsheads(self, a, b):
1159 """calculate all the heads of the common ancestors of nodes a and b"""
1156 """calculate all the heads of the common ancestors of nodes a and b"""
1160 a, b = self.rev(a), self.rev(b)
1157 a, b = self.rev(a), self.rev(b)
1161 ancs = self._commonancestorsheads(a, b)
1158 ancs = self._commonancestorsheads(a, b)
1162 return pycompat.maplist(self.node, ancs)
1159 return pycompat.maplist(self.node, ancs)
1163
1160
1164 def _commonancestorsheads(self, *revs):
1161 def _commonancestorsheads(self, *revs):
1165 """calculate all the heads of the common ancestors of revs"""
1162 """calculate all the heads of the common ancestors of revs"""
1166 try:
1163 try:
1167 ancs = self.index.commonancestorsheads(*revs)
1164 ancs = self.index.commonancestorsheads(*revs)
1168 except (AttributeError, OverflowError): # C implementation failed
1165 except (AttributeError, OverflowError): # C implementation failed
1169 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1166 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1170 return ancs
1167 return ancs
1171
1168
1172 def isancestor(self, a, b):
1169 def isancestor(self, a, b):
1173 """return True if node a is an ancestor of node b
1170 """return True if node a is an ancestor of node b
1174
1171
1175 A revision is considered an ancestor of itself."""
1172 A revision is considered an ancestor of itself."""
1176 a, b = self.rev(a), self.rev(b)
1173 a, b = self.rev(a), self.rev(b)
1177 return self.isancestorrev(a, b)
1174 return self.isancestorrev(a, b)
1178
1175
1179 def isancestorrev(self, a, b):
1176 def isancestorrev(self, a, b):
1180 """return True if revision a is an ancestor of revision b
1177 """return True if revision a is an ancestor of revision b
1181
1178
1182 A revision is considered an ancestor of itself.
1179 A revision is considered an ancestor of itself.
1183
1180
1184 The implementation of this is trivial but the use of
1181 The implementation of this is trivial but the use of
1185 reachableroots is not."""
1182 reachableroots is not."""
1186 if a == nullrev:
1183 if a == nullrev:
1187 return True
1184 return True
1188 elif a == b:
1185 elif a == b:
1189 return True
1186 return True
1190 elif a > b:
1187 elif a > b:
1191 return False
1188 return False
1192 return bool(self.reachableroots(a, [b], [a], includepath=False))
1189 return bool(self.reachableroots(a, [b], [a], includepath=False))
1193
1190
1194 def reachableroots(self, minroot, heads, roots, includepath=False):
1191 def reachableroots(self, minroot, heads, roots, includepath=False):
1195 """return (heads(::<roots> and <roots>::<heads>))
1192 """return (heads(::<roots> and <roots>::<heads>))
1196
1193
1197 If includepath is True, return (<roots>::<heads>)."""
1194 If includepath is True, return (<roots>::<heads>)."""
1198 try:
1195 try:
1199 return self.index.reachableroots2(minroot, heads, roots,
1196 return self.index.reachableroots2(minroot, heads, roots,
1200 includepath)
1197 includepath)
1201 except AttributeError:
1198 except AttributeError:
1202 return dagop._reachablerootspure(self.parentrevs,
1199 return dagop._reachablerootspure(self.parentrevs,
1203 minroot, roots, heads, includepath)
1200 minroot, roots, heads, includepath)
1204
1201
1205 def ancestor(self, a, b):
1202 def ancestor(self, a, b):
1206 """calculate the "best" common ancestor of nodes a and b"""
1203 """calculate the "best" common ancestor of nodes a and b"""
1207
1204
1208 a, b = self.rev(a), self.rev(b)
1205 a, b = self.rev(a), self.rev(b)
1209 try:
1206 try:
1210 ancs = self.index.ancestors(a, b)
1207 ancs = self.index.ancestors(a, b)
1211 except (AttributeError, OverflowError):
1208 except (AttributeError, OverflowError):
1212 ancs = ancestor.ancestors(self.parentrevs, a, b)
1209 ancs = ancestor.ancestors(self.parentrevs, a, b)
1213 if ancs:
1210 if ancs:
1214 # choose a consistent winner when there's a tie
1211 # choose a consistent winner when there's a tie
1215 return min(map(self.node, ancs))
1212 return min(map(self.node, ancs))
1216 return nullid
1213 return nullid
1217
1214
1218 def _match(self, id):
1215 def _match(self, id):
1219 if isinstance(id, int):
1216 if isinstance(id, int):
1220 # rev
1217 # rev
1221 return self.node(id)
1218 return self.node(id)
1222 if len(id) == 20:
1219 if len(id) == 20:
1223 # possibly a binary node
1220 # possibly a binary node
1224 # odds of a binary node being all hex in ASCII are 1 in 10**25
1221 # odds of a binary node being all hex in ASCII are 1 in 10**25
1225 try:
1222 try:
1226 node = id
1223 node = id
1227 self.rev(node) # quick search the index
1224 self.rev(node) # quick search the index
1228 return node
1225 return node
1229 except error.LookupError:
1226 except error.LookupError:
1230 pass # may be partial hex id
1227 pass # may be partial hex id
1231 try:
1228 try:
1232 # str(rev)
1229 # str(rev)
1233 rev = int(id)
1230 rev = int(id)
1234 if "%d" % rev != id:
1231 if "%d" % rev != id:
1235 raise ValueError
1232 raise ValueError
1236 if rev < 0:
1233 if rev < 0:
1237 rev = len(self) + rev
1234 rev = len(self) + rev
1238 if rev < 0 or rev >= len(self):
1235 if rev < 0 or rev >= len(self):
1239 raise ValueError
1236 raise ValueError
1240 return self.node(rev)
1237 return self.node(rev)
1241 except (ValueError, OverflowError):
1238 except (ValueError, OverflowError):
1242 pass
1239 pass
1243 if len(id) == 40:
1240 if len(id) == 40:
1244 try:
1241 try:
1245 # a full hex nodeid?
1242 # a full hex nodeid?
1246 node = bin(id)
1243 node = bin(id)
1247 self.rev(node)
1244 self.rev(node)
1248 return node
1245 return node
1249 except (TypeError, error.LookupError):
1246 except (TypeError, error.LookupError):
1250 pass
1247 pass
1251
1248
1252 def _partialmatch(self, id):
1249 def _partialmatch(self, id):
1253 # we don't care wdirfilenodeids as they should be always full hash
1250 # we don't care wdirfilenodeids as they should be always full hash
1254 maybewdir = wdirhex.startswith(id)
1251 maybewdir = wdirhex.startswith(id)
1255 try:
1252 try:
1256 partial = self.index.partialmatch(id)
1253 partial = self.index.partialmatch(id)
1257 if partial and self.hasnode(partial):
1254 if partial and self.hasnode(partial):
1258 if maybewdir:
1255 if maybewdir:
1259 # single 'ff...' match in radix tree, ambiguous with wdir
1256 # single 'ff...' match in radix tree, ambiguous with wdir
1260 raise error.RevlogError
1257 raise error.RevlogError
1261 return partial
1258 return partial
1262 if maybewdir:
1259 if maybewdir:
1263 # no 'ff...' match in radix tree, wdir identified
1260 # no 'ff...' match in radix tree, wdir identified
1264 raise error.WdirUnsupported
1261 raise error.WdirUnsupported
1265 return None
1262 return None
1266 except error.RevlogError:
1263 except error.RevlogError:
1267 # parsers.c radix tree lookup gave multiple matches
1264 # parsers.c radix tree lookup gave multiple matches
1268 # fast path: for unfiltered changelog, radix tree is accurate
1265 # fast path: for unfiltered changelog, radix tree is accurate
1269 if not getattr(self, 'filteredrevs', None):
1266 if not getattr(self, 'filteredrevs', None):
1270 raise error.AmbiguousPrefixLookupError(
1267 raise error.AmbiguousPrefixLookupError(
1271 id, self.indexfile, _('ambiguous identifier'))
1268 id, self.indexfile, _('ambiguous identifier'))
1272 # fall through to slow path that filters hidden revisions
1269 # fall through to slow path that filters hidden revisions
1273 except (AttributeError, ValueError):
1270 except (AttributeError, ValueError):
1274 # we are pure python, or key was too short to search radix tree
1271 # we are pure python, or key was too short to search radix tree
1275 pass
1272 pass
1276
1273
1277 if id in self._pcache:
1274 if id in self._pcache:
1278 return self._pcache[id]
1275 return self._pcache[id]
1279
1276
1280 if len(id) <= 40:
1277 if len(id) <= 40:
1281 try:
1278 try:
1282 # hex(node)[:...]
1279 # hex(node)[:...]
1283 l = len(id) // 2 # grab an even number of digits
1280 l = len(id) // 2 # grab an even number of digits
1284 prefix = bin(id[:l * 2])
1281 prefix = bin(id[:l * 2])
1285 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1282 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1286 nl = [n for n in nl if hex(n).startswith(id) and
1283 nl = [n for n in nl if hex(n).startswith(id) and
1287 self.hasnode(n)]
1284 self.hasnode(n)]
1288 if nullhex.startswith(id):
1285 if nullhex.startswith(id):
1289 nl.append(nullid)
1286 nl.append(nullid)
1290 if len(nl) > 0:
1287 if len(nl) > 0:
1291 if len(nl) == 1 and not maybewdir:
1288 if len(nl) == 1 and not maybewdir:
1292 self._pcache[id] = nl[0]
1289 self._pcache[id] = nl[0]
1293 return nl[0]
1290 return nl[0]
1294 raise error.AmbiguousPrefixLookupError(
1291 raise error.AmbiguousPrefixLookupError(
1295 id, self.indexfile, _('ambiguous identifier'))
1292 id, self.indexfile, _('ambiguous identifier'))
1296 if maybewdir:
1293 if maybewdir:
1297 raise error.WdirUnsupported
1294 raise error.WdirUnsupported
1298 return None
1295 return None
1299 except TypeError:
1296 except TypeError:
1300 pass
1297 pass
1301
1298
1302 def lookup(self, id):
1299 def lookup(self, id):
1303 """locate a node based on:
1300 """locate a node based on:
1304 - revision number or str(revision number)
1301 - revision number or str(revision number)
1305 - nodeid or subset of hex nodeid
1302 - nodeid or subset of hex nodeid
1306 """
1303 """
1307 n = self._match(id)
1304 n = self._match(id)
1308 if n is not None:
1305 if n is not None:
1309 return n
1306 return n
1310 n = self._partialmatch(id)
1307 n = self._partialmatch(id)
1311 if n:
1308 if n:
1312 return n
1309 return n
1313
1310
1314 raise error.LookupError(id, self.indexfile, _('no match found'))
1311 raise error.LookupError(id, self.indexfile, _('no match found'))
1315
1312
1316 def shortest(self, node, minlength=1):
1313 def shortest(self, node, minlength=1):
1317 """Find the shortest unambiguous prefix that matches node."""
1314 """Find the shortest unambiguous prefix that matches node."""
1318 def isvalid(prefix):
1315 def isvalid(prefix):
1319 try:
1316 try:
1320 matchednode = self._partialmatch(prefix)
1317 matchednode = self._partialmatch(prefix)
1321 except error.AmbiguousPrefixLookupError:
1318 except error.AmbiguousPrefixLookupError:
1322 return False
1319 return False
1323 except error.WdirUnsupported:
1320 except error.WdirUnsupported:
1324 # single 'ff...' match
1321 # single 'ff...' match
1325 return True
1322 return True
1326 if matchednode is None:
1323 if matchednode is None:
1327 raise error.LookupError(node, self.indexfile, _('no node'))
1324 raise error.LookupError(node, self.indexfile, _('no node'))
1328 return True
1325 return True
1329
1326
1330 def maybewdir(prefix):
1327 def maybewdir(prefix):
1331 return all(c == 'f' for c in pycompat.iterbytestr(prefix))
1328 return all(c == 'f' for c in pycompat.iterbytestr(prefix))
1332
1329
1333 hexnode = hex(node)
1330 hexnode = hex(node)
1334
1331
1335 def disambiguate(hexnode, minlength):
1332 def disambiguate(hexnode, minlength):
1336 """Disambiguate against wdirid."""
1333 """Disambiguate against wdirid."""
1337 for length in range(minlength, 41):
1334 for length in range(minlength, 41):
1338 prefix = hexnode[:length]
1335 prefix = hexnode[:length]
1339 if not maybewdir(prefix):
1336 if not maybewdir(prefix):
1340 return prefix
1337 return prefix
1341
1338
1342 if not getattr(self, 'filteredrevs', None):
1339 if not getattr(self, 'filteredrevs', None):
1343 try:
1340 try:
1344 length = max(self.index.shortest(node), minlength)
1341 length = max(self.index.shortest(node), minlength)
1345 return disambiguate(hexnode, length)
1342 return disambiguate(hexnode, length)
1346 except error.RevlogError:
1343 except error.RevlogError:
1347 if node != wdirid:
1344 if node != wdirid:
1348 raise error.LookupError(node, self.indexfile, _('no node'))
1345 raise error.LookupError(node, self.indexfile, _('no node'))
1349 except AttributeError:
1346 except AttributeError:
1350 # Fall through to pure code
1347 # Fall through to pure code
1351 pass
1348 pass
1352
1349
1353 if node == wdirid:
1350 if node == wdirid:
1354 for length in range(minlength, 41):
1351 for length in range(minlength, 41):
1355 prefix = hexnode[:length]
1352 prefix = hexnode[:length]
1356 if isvalid(prefix):
1353 if isvalid(prefix):
1357 return prefix
1354 return prefix
1358
1355
1359 for length in range(minlength, 41):
1356 for length in range(minlength, 41):
1360 prefix = hexnode[:length]
1357 prefix = hexnode[:length]
1361 if isvalid(prefix):
1358 if isvalid(prefix):
1362 return disambiguate(hexnode, length)
1359 return disambiguate(hexnode, length)
1363
1360
1364 def cmp(self, node, text):
1361 def cmp(self, node, text):
1365 """compare text with a given file revision
1362 """compare text with a given file revision
1366
1363
1367 returns True if text is different than what is stored.
1364 returns True if text is different than what is stored.
1368 """
1365 """
1369 p1, p2 = self.parents(node)
1366 p1, p2 = self.parents(node)
1370 return storageutil.hashrevisionsha1(text, p1, p2) != node
1367 return storageutil.hashrevisionsha1(text, p1, p2) != node
1371
1368
1372 def _cachesegment(self, offset, data):
1369 def _cachesegment(self, offset, data):
1373 """Add a segment to the revlog cache.
1370 """Add a segment to the revlog cache.
1374
1371
1375 Accepts an absolute offset and the data that is at that location.
1372 Accepts an absolute offset and the data that is at that location.
1376 """
1373 """
1377 o, d = self._chunkcache
1374 o, d = self._chunkcache
1378 # try to add to existing cache
1375 # try to add to existing cache
1379 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1376 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1380 self._chunkcache = o, d + data
1377 self._chunkcache = o, d + data
1381 else:
1378 else:
1382 self._chunkcache = offset, data
1379 self._chunkcache = offset, data
1383
1380
1384 def _readsegment(self, offset, length, df=None):
1381 def _readsegment(self, offset, length, df=None):
1385 """Load a segment of raw data from the revlog.
1382 """Load a segment of raw data from the revlog.
1386
1383
1387 Accepts an absolute offset, length to read, and an optional existing
1384 Accepts an absolute offset, length to read, and an optional existing
1388 file handle to read from.
1385 file handle to read from.
1389
1386
1390 If an existing file handle is passed, it will be seeked and the
1387 If an existing file handle is passed, it will be seeked and the
1391 original seek position will NOT be restored.
1388 original seek position will NOT be restored.
1392
1389
1393 Returns a str or buffer of raw byte data.
1390 Returns a str or buffer of raw byte data.
1394
1391
1395 Raises if the requested number of bytes could not be read.
1392 Raises if the requested number of bytes could not be read.
1396 """
1393 """
1397 # Cache data both forward and backward around the requested
1394 # Cache data both forward and backward around the requested
1398 # data, in a fixed size window. This helps speed up operations
1395 # data, in a fixed size window. This helps speed up operations
1399 # involving reading the revlog backwards.
1396 # involving reading the revlog backwards.
1400 cachesize = self._chunkcachesize
1397 cachesize = self._chunkcachesize
1401 realoffset = offset & ~(cachesize - 1)
1398 realoffset = offset & ~(cachesize - 1)
1402 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
1399 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
1403 - realoffset)
1400 - realoffset)
1404 with self._datareadfp(df) as df:
1401 with self._datareadfp(df) as df:
1405 df.seek(realoffset)
1402 df.seek(realoffset)
1406 d = df.read(reallength)
1403 d = df.read(reallength)
1407
1404
1408 self._cachesegment(realoffset, d)
1405 self._cachesegment(realoffset, d)
1409 if offset != realoffset or reallength != length:
1406 if offset != realoffset or reallength != length:
1410 startoffset = offset - realoffset
1407 startoffset = offset - realoffset
1411 if len(d) - startoffset < length:
1408 if len(d) - startoffset < length:
1412 raise error.RevlogError(
1409 raise error.RevlogError(
1413 _('partial read of revlog %s; expected %d bytes from '
1410 _('partial read of revlog %s; expected %d bytes from '
1414 'offset %d, got %d') %
1411 'offset %d, got %d') %
1415 (self.indexfile if self._inline else self.datafile,
1412 (self.indexfile if self._inline else self.datafile,
1416 length, realoffset, len(d) - startoffset))
1413 length, realoffset, len(d) - startoffset))
1417
1414
1418 return util.buffer(d, startoffset, length)
1415 return util.buffer(d, startoffset, length)
1419
1416
1420 if len(d) < length:
1417 if len(d) < length:
1421 raise error.RevlogError(
1418 raise error.RevlogError(
1422 _('partial read of revlog %s; expected %d bytes from offset '
1419 _('partial read of revlog %s; expected %d bytes from offset '
1423 '%d, got %d') %
1420 '%d, got %d') %
1424 (self.indexfile if self._inline else self.datafile,
1421 (self.indexfile if self._inline else self.datafile,
1425 length, offset, len(d)))
1422 length, offset, len(d)))
1426
1423
1427 return d
1424 return d
1428
1425
1429 def _getsegment(self, offset, length, df=None):
1426 def _getsegment(self, offset, length, df=None):
1430 """Obtain a segment of raw data from the revlog.
1427 """Obtain a segment of raw data from the revlog.
1431
1428
1432 Accepts an absolute offset, length of bytes to obtain, and an
1429 Accepts an absolute offset, length of bytes to obtain, and an
1433 optional file handle to the already-opened revlog. If the file
1430 optional file handle to the already-opened revlog. If the file
1434 handle is used, it's original seek position will not be preserved.
1431 handle is used, it's original seek position will not be preserved.
1435
1432
1436 Requests for data may be returned from a cache.
1433 Requests for data may be returned from a cache.
1437
1434
1438 Returns a str or a buffer instance of raw byte data.
1435 Returns a str or a buffer instance of raw byte data.
1439 """
1436 """
1440 o, d = self._chunkcache
1437 o, d = self._chunkcache
1441 l = len(d)
1438 l = len(d)
1442
1439
1443 # is it in the cache?
1440 # is it in the cache?
1444 cachestart = offset - o
1441 cachestart = offset - o
1445 cacheend = cachestart + length
1442 cacheend = cachestart + length
1446 if cachestart >= 0 and cacheend <= l:
1443 if cachestart >= 0 and cacheend <= l:
1447 if cachestart == 0 and cacheend == l:
1444 if cachestart == 0 and cacheend == l:
1448 return d # avoid a copy
1445 return d # avoid a copy
1449 return util.buffer(d, cachestart, cacheend - cachestart)
1446 return util.buffer(d, cachestart, cacheend - cachestart)
1450
1447
1451 return self._readsegment(offset, length, df=df)
1448 return self._readsegment(offset, length, df=df)
1452
1449
1453 def _getsegmentforrevs(self, startrev, endrev, df=None):
1450 def _getsegmentforrevs(self, startrev, endrev, df=None):
1454 """Obtain a segment of raw data corresponding to a range of revisions.
1451 """Obtain a segment of raw data corresponding to a range of revisions.
1455
1452
1456 Accepts the start and end revisions and an optional already-open
1453 Accepts the start and end revisions and an optional already-open
1457 file handle to be used for reading. If the file handle is read, its
1454 file handle to be used for reading. If the file handle is read, its
1458 seek position will not be preserved.
1455 seek position will not be preserved.
1459
1456
1460 Requests for data may be satisfied by a cache.
1457 Requests for data may be satisfied by a cache.
1461
1458
1462 Returns a 2-tuple of (offset, data) for the requested range of
1459 Returns a 2-tuple of (offset, data) for the requested range of
1463 revisions. Offset is the integer offset from the beginning of the
1460 revisions. Offset is the integer offset from the beginning of the
1464 revlog and data is a str or buffer of the raw byte data.
1461 revlog and data is a str or buffer of the raw byte data.
1465
1462
1466 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1463 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1467 to determine where each revision's data begins and ends.
1464 to determine where each revision's data begins and ends.
1468 """
1465 """
1469 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1466 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1470 # (functions are expensive).
1467 # (functions are expensive).
1471 index = self.index
1468 index = self.index
1472 istart = index[startrev]
1469 istart = index[startrev]
1473 start = int(istart[0] >> 16)
1470 start = int(istart[0] >> 16)
1474 if startrev == endrev:
1471 if startrev == endrev:
1475 end = start + istart[1]
1472 end = start + istart[1]
1476 else:
1473 else:
1477 iend = index[endrev]
1474 iend = index[endrev]
1478 end = int(iend[0] >> 16) + iend[1]
1475 end = int(iend[0] >> 16) + iend[1]
1479
1476
1480 if self._inline:
1477 if self._inline:
1481 start += (startrev + 1) * self._io.size
1478 start += (startrev + 1) * self._io.size
1482 end += (endrev + 1) * self._io.size
1479 end += (endrev + 1) * self._io.size
1483 length = end - start
1480 length = end - start
1484
1481
1485 return start, self._getsegment(start, length, df=df)
1482 return start, self._getsegment(start, length, df=df)
1486
1483
1487 def _chunk(self, rev, df=None):
1484 def _chunk(self, rev, df=None):
1488 """Obtain a single decompressed chunk for a revision.
1485 """Obtain a single decompressed chunk for a revision.
1489
1486
1490 Accepts an integer revision and an optional already-open file handle
1487 Accepts an integer revision and an optional already-open file handle
1491 to be used for reading. If used, the seek position of the file will not
1488 to be used for reading. If used, the seek position of the file will not
1492 be preserved.
1489 be preserved.
1493
1490
1494 Returns a str holding uncompressed data for the requested revision.
1491 Returns a str holding uncompressed data for the requested revision.
1495 """
1492 """
1496 return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
1493 return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
1497
1494
1498 def _chunks(self, revs, df=None, targetsize=None):
1495 def _chunks(self, revs, df=None, targetsize=None):
1499 """Obtain decompressed chunks for the specified revisions.
1496 """Obtain decompressed chunks for the specified revisions.
1500
1497
1501 Accepts an iterable of numeric revisions that are assumed to be in
1498 Accepts an iterable of numeric revisions that are assumed to be in
1502 ascending order. Also accepts an optional already-open file handle
1499 ascending order. Also accepts an optional already-open file handle
1503 to be used for reading. If used, the seek position of the file will
1500 to be used for reading. If used, the seek position of the file will
1504 not be preserved.
1501 not be preserved.
1505
1502
1506 This function is similar to calling ``self._chunk()`` multiple times,
1503 This function is similar to calling ``self._chunk()`` multiple times,
1507 but is faster.
1504 but is faster.
1508
1505
1509 Returns a list with decompressed data for each requested revision.
1506 Returns a list with decompressed data for each requested revision.
1510 """
1507 """
1511 if not revs:
1508 if not revs:
1512 return []
1509 return []
1513 start = self.start
1510 start = self.start
1514 length = self.length
1511 length = self.length
1515 inline = self._inline
1512 inline = self._inline
1516 iosize = self._io.size
1513 iosize = self._io.size
1517 buffer = util.buffer
1514 buffer = util.buffer
1518
1515
1519 l = []
1516 l = []
1520 ladd = l.append
1517 ladd = l.append
1521
1518
1522 if not self._withsparseread:
1519 if not self._withsparseread:
1523 slicedchunks = (revs,)
1520 slicedchunks = (revs,)
1524 else:
1521 else:
1525 slicedchunks = deltautil.slicechunk(self, revs,
1522 slicedchunks = deltautil.slicechunk(self, revs,
1526 targetsize=targetsize)
1523 targetsize=targetsize)
1527
1524
1528 for revschunk in slicedchunks:
1525 for revschunk in slicedchunks:
1529 firstrev = revschunk[0]
1526 firstrev = revschunk[0]
1530 # Skip trailing revisions with empty diff
1527 # Skip trailing revisions with empty diff
1531 for lastrev in revschunk[::-1]:
1528 for lastrev in revschunk[::-1]:
1532 if length(lastrev) != 0:
1529 if length(lastrev) != 0:
1533 break
1530 break
1534
1531
1535 try:
1532 try:
1536 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1533 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1537 except OverflowError:
1534 except OverflowError:
1538 # issue4215 - we can't cache a run of chunks greater than
1535 # issue4215 - we can't cache a run of chunks greater than
1539 # 2G on Windows
1536 # 2G on Windows
1540 return [self._chunk(rev, df=df) for rev in revschunk]
1537 return [self._chunk(rev, df=df) for rev in revschunk]
1541
1538
1542 decomp = self.decompress
1539 decomp = self.decompress
1543 for rev in revschunk:
1540 for rev in revschunk:
1544 chunkstart = start(rev)
1541 chunkstart = start(rev)
1545 if inline:
1542 if inline:
1546 chunkstart += (rev + 1) * iosize
1543 chunkstart += (rev + 1) * iosize
1547 chunklength = length(rev)
1544 chunklength = length(rev)
1548 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
1545 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
1549
1546
1550 return l
1547 return l
1551
1548
1552 def _chunkclear(self):
1549 def _chunkclear(self):
1553 """Clear the raw chunk cache."""
1550 """Clear the raw chunk cache."""
1554 self._chunkcache = (0, '')
1551 self._chunkcache = (0, '')
1555
1552
1556 def deltaparent(self, rev):
1553 def deltaparent(self, rev):
1557 """return deltaparent of the given revision"""
1554 """return deltaparent of the given revision"""
1558 base = self.index[rev][3]
1555 base = self.index[rev][3]
1559 if base == rev:
1556 if base == rev:
1560 return nullrev
1557 return nullrev
1561 elif self._generaldelta:
1558 elif self._generaldelta:
1562 return base
1559 return base
1563 else:
1560 else:
1564 return rev - 1
1561 return rev - 1
1565
1562
1566 def issnapshot(self, rev):
1563 def issnapshot(self, rev):
1567 """tells whether rev is a snapshot
1564 """tells whether rev is a snapshot
1568 """
1565 """
1569 if not self._sparserevlog:
1566 if not self._sparserevlog:
1570 return self.deltaparent(rev) == nullrev
1567 return self.deltaparent(rev) == nullrev
1571 elif util.safehasattr(self.index, 'issnapshot'):
1568 elif util.safehasattr(self.index, 'issnapshot'):
1572 # directly assign the method to cache the testing and access
1569 # directly assign the method to cache the testing and access
1573 self.issnapshot = self.index.issnapshot
1570 self.issnapshot = self.index.issnapshot
1574 return self.issnapshot(rev)
1571 return self.issnapshot(rev)
1575 if rev == nullrev:
1572 if rev == nullrev:
1576 return True
1573 return True
1577 entry = self.index[rev]
1574 entry = self.index[rev]
1578 base = entry[3]
1575 base = entry[3]
1579 if base == rev:
1576 if base == rev:
1580 return True
1577 return True
1581 if base == nullrev:
1578 if base == nullrev:
1582 return True
1579 return True
1583 p1 = entry[5]
1580 p1 = entry[5]
1584 p2 = entry[6]
1581 p2 = entry[6]
1585 if base == p1 or base == p2:
1582 if base == p1 or base == p2:
1586 return False
1583 return False
1587 return self.issnapshot(base)
1584 return self.issnapshot(base)
1588
1585
1589 def snapshotdepth(self, rev):
1586 def snapshotdepth(self, rev):
1590 """number of snapshot in the chain before this one"""
1587 """number of snapshot in the chain before this one"""
1591 if not self.issnapshot(rev):
1588 if not self.issnapshot(rev):
1592 raise error.ProgrammingError('revision %d not a snapshot')
1589 raise error.ProgrammingError('revision %d not a snapshot')
1593 return len(self._deltachain(rev)[0]) - 1
1590 return len(self._deltachain(rev)[0]) - 1
1594
1591
1595 def revdiff(self, rev1, rev2):
1592 def revdiff(self, rev1, rev2):
1596 """return or calculate a delta between two revisions
1593 """return or calculate a delta between two revisions
1597
1594
1598 The delta calculated is in binary form and is intended to be written to
1595 The delta calculated is in binary form and is intended to be written to
1599 revlog data directly. So this function needs raw revision data.
1596 revlog data directly. So this function needs raw revision data.
1600 """
1597 """
1601 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1598 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1602 return bytes(self._chunk(rev2))
1599 return bytes(self._chunk(rev2))
1603
1600
1604 return mdiff.textdiff(self.rawdata(rev1),
1601 return mdiff.textdiff(self.rawdata(rev1),
1605 self.rawdata(rev2))
1602 self.rawdata(rev2))
1606
1603
1607 def _processflags(self, text, flags, operation, raw=False):
1604 def _processflags(self, text, flags, operation, raw=False):
1608 """deprecated entry point to access flag processors"""
1605 """deprecated entry point to access flag processors"""
1609 msg = ('_processflag(...) use the specialized variant')
1606 msg = ('_processflag(...) use the specialized variant')
1610 util.nouideprecwarn(msg, '5.2', stacklevel=2)
1607 util.nouideprecwarn(msg, '5.2', stacklevel=2)
1611 if raw:
1608 if raw:
1612 return text, flagutil.processflagsraw(self, text, flags)
1609 return text, flagutil.processflagsraw(self, text, flags)
1613 elif operation == 'read':
1610 elif operation == 'read':
1614 return flagutil.processflagsread(self, text, flags)
1611 return flagutil.processflagsread(self, text, flags)
1615 else: # write operation
1612 else: # write operation
1616 return flagutil.processflagswrite(self, text, flags)
1613 return flagutil.processflagswrite(self, text, flags)
1617
1614
1618 def revision(self, nodeorrev, _df=None, raw=False):
1615 def revision(self, nodeorrev, _df=None, raw=False):
1619 """return an uncompressed revision of a given node or revision
1616 """return an uncompressed revision of a given node or revision
1620 number.
1617 number.
1621
1618
1622 _df - an existing file handle to read from. (internal-only)
1619 _df - an existing file handle to read from. (internal-only)
1623 raw - an optional argument specifying if the revision data is to be
1620 raw - an optional argument specifying if the revision data is to be
1624 treated as raw data when applying flag transforms. 'raw' should be set
1621 treated as raw data when applying flag transforms. 'raw' should be set
1625 to True when generating changegroups or in debug commands.
1622 to True when generating changegroups or in debug commands.
1626 """
1623 """
1627 if raw:
1624 if raw:
1628 msg = ('revlog.revision(..., raw=True) is deprecated, '
1625 msg = ('revlog.revision(..., raw=True) is deprecated, '
1629 'use revlog.rawdata(...)')
1626 'use revlog.rawdata(...)')
1630 util.nouideprecwarn(msg, '5.2', stacklevel=2)
1627 util.nouideprecwarn(msg, '5.2', stacklevel=2)
1631 return self._revisiondata(nodeorrev, _df, raw=raw)[0]
1628 return self._revisiondata(nodeorrev, _df, raw=raw)[0]
1632
1629
1633 def sidedata(self, nodeorrev, _df=None):
1630 def sidedata(self, nodeorrev, _df=None):
1634 """a map of extra data related to the changeset but not part of the hash
1631 """a map of extra data related to the changeset but not part of the hash
1635
1632
1636 This function currently return a dictionary. However, more advanced
1633 This function currently return a dictionary. However, more advanced
1637 mapping object will likely be used in the future for a more
1634 mapping object will likely be used in the future for a more
1638 efficient/lazy code.
1635 efficient/lazy code.
1639 """
1636 """
1640 return self._revisiondata(nodeorrev, _df)[1]
1637 return self._revisiondata(nodeorrev, _df)[1]
1641
1638
1642 def _revisiondata(self, nodeorrev, _df=None, raw=False):
1639 def _revisiondata(self, nodeorrev, _df=None, raw=False):
1643 # deal with <nodeorrev> argument type
1640 # deal with <nodeorrev> argument type
1644 if isinstance(nodeorrev, int):
1641 if isinstance(nodeorrev, int):
1645 rev = nodeorrev
1642 rev = nodeorrev
1646 node = self.node(rev)
1643 node = self.node(rev)
1647 else:
1644 else:
1648 node = nodeorrev
1645 node = nodeorrev
1649 rev = None
1646 rev = None
1650
1647
1651 # fast path the special `nullid` rev
1648 # fast path the special `nullid` rev
1652 if node == nullid:
1649 if node == nullid:
1653 return "", {}
1650 return "", {}
1654
1651
1655 # The text as stored inside the revlog. Might be the revision or might
1652 # The text as stored inside the revlog. Might be the revision or might
1656 # need to be processed to retrieve the revision.
1653 # need to be processed to retrieve the revision.
1657 rawtext = None
1654 rawtext = None
1658
1655
1659 rev, rawtext, validated = self._rawtext(node, rev, _df=_df)
1656 rev, rawtext, validated = self._rawtext(node, rev, _df=_df)
1660
1657
1661 if raw and validated:
1658 if raw and validated:
1662 # if we don't want to process the raw text and that raw
1659 # if we don't want to process the raw text and that raw
1663 # text is cached, we can exit early.
1660 # text is cached, we can exit early.
1664 return rawtext, {}
1661 return rawtext, {}
1665 if rev is None:
1662 if rev is None:
1666 rev = self.rev(node)
1663 rev = self.rev(node)
1667 # the revlog's flag for this revision
1664 # the revlog's flag for this revision
1668 # (usually alter its state or content)
1665 # (usually alter its state or content)
1669 flags = self.flags(rev)
1666 flags = self.flags(rev)
1670
1667
1671 if validated and flags == REVIDX_DEFAULT_FLAGS:
1668 if validated and flags == REVIDX_DEFAULT_FLAGS:
1672 # no extra flags set, no flag processor runs, text = rawtext
1669 # no extra flags set, no flag processor runs, text = rawtext
1673 return rawtext, {}
1670 return rawtext, {}
1674
1671
1675 sidedata = {}
1672 sidedata = {}
1676 if raw:
1673 if raw:
1677 validatehash = flagutil.processflagsraw(self, rawtext, flags)
1674 validatehash = flagutil.processflagsraw(self, rawtext, flags)
1678 text = rawtext
1675 text = rawtext
1679 else:
1676 else:
1680 r = flagutil.processflagsread(self, rawtext, flags)
1677 r = flagutil.processflagsread(self, rawtext, flags)
1681 text, validatehash, sidedata = r
1678 text, validatehash, sidedata = r
1682 if validatehash:
1679 if validatehash:
1683 self.checkhash(text, node, rev=rev)
1680 self.checkhash(text, node, rev=rev)
1684 if not validated:
1681 if not validated:
1685 self._revisioncache = (node, rev, rawtext)
1682 self._revisioncache = (node, rev, rawtext)
1686
1683
1687 return text, sidedata
1684 return text, sidedata
1688
1685
1689 def _rawtext(self, node, rev, _df=None):
1686 def _rawtext(self, node, rev, _df=None):
1690 """return the possibly unvalidated rawtext for a revision
1687 """return the possibly unvalidated rawtext for a revision
1691
1688
1692 returns (rev, rawtext, validated)
1689 returns (rev, rawtext, validated)
1693 """
1690 """
1694
1691
1695 # revision in the cache (could be useful to apply delta)
1692 # revision in the cache (could be useful to apply delta)
1696 cachedrev = None
1693 cachedrev = None
1697 # An intermediate text to apply deltas to
1694 # An intermediate text to apply deltas to
1698 basetext = None
1695 basetext = None
1699
1696
1700 # Check if we have the entry in cache
1697 # Check if we have the entry in cache
1701 # The cache entry looks like (node, rev, rawtext)
1698 # The cache entry looks like (node, rev, rawtext)
1702 if self._revisioncache:
1699 if self._revisioncache:
1703 if self._revisioncache[0] == node:
1700 if self._revisioncache[0] == node:
1704 return (rev, self._revisioncache[2], True)
1701 return (rev, self._revisioncache[2], True)
1705 cachedrev = self._revisioncache[1]
1702 cachedrev = self._revisioncache[1]
1706
1703
1707 if rev is None:
1704 if rev is None:
1708 rev = self.rev(node)
1705 rev = self.rev(node)
1709
1706
1710 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1707 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1711 if stopped:
1708 if stopped:
1712 basetext = self._revisioncache[2]
1709 basetext = self._revisioncache[2]
1713
1710
1714 # drop cache to save memory, the caller is expected to
1711 # drop cache to save memory, the caller is expected to
1715 # update self._revisioncache after validating the text
1712 # update self._revisioncache after validating the text
1716 self._revisioncache = None
1713 self._revisioncache = None
1717
1714
1718 targetsize = None
1715 targetsize = None
1719 rawsize = self.index[rev][2]
1716 rawsize = self.index[rev][2]
1720 if 0 <= rawsize:
1717 if 0 <= rawsize:
1721 targetsize = 4 * rawsize
1718 targetsize = 4 * rawsize
1722
1719
1723 bins = self._chunks(chain, df=_df, targetsize=targetsize)
1720 bins = self._chunks(chain, df=_df, targetsize=targetsize)
1724 if basetext is None:
1721 if basetext is None:
1725 basetext = bytes(bins[0])
1722 basetext = bytes(bins[0])
1726 bins = bins[1:]
1723 bins = bins[1:]
1727
1724
1728 rawtext = mdiff.patches(basetext, bins)
1725 rawtext = mdiff.patches(basetext, bins)
1729 del basetext # let us have a chance to free memory early
1726 del basetext # let us have a chance to free memory early
1730 return (rev, rawtext, False)
1727 return (rev, rawtext, False)
1731
1728
1732 def rawdata(self, nodeorrev, _df=None):
1729 def rawdata(self, nodeorrev, _df=None):
1733 """return an uncompressed raw data of a given node or revision number.
1730 """return an uncompressed raw data of a given node or revision number.
1734
1731
1735 _df - an existing file handle to read from. (internal-only)
1732 _df - an existing file handle to read from. (internal-only)
1736 """
1733 """
1737 return self._revisiondata(nodeorrev, _df, raw=True)[0]
1734 return self._revisiondata(nodeorrev, _df, raw=True)[0]
1738
1735
1739 def hash(self, text, p1, p2):
1736 def hash(self, text, p1, p2):
1740 """Compute a node hash.
1737 """Compute a node hash.
1741
1738
1742 Available as a function so that subclasses can replace the hash
1739 Available as a function so that subclasses can replace the hash
1743 as needed.
1740 as needed.
1744 """
1741 """
1745 return storageutil.hashrevisionsha1(text, p1, p2)
1742 return storageutil.hashrevisionsha1(text, p1, p2)
1746
1743
1747 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1744 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1748 """Check node hash integrity.
1745 """Check node hash integrity.
1749
1746
1750 Available as a function so that subclasses can extend hash mismatch
1747 Available as a function so that subclasses can extend hash mismatch
1751 behaviors as needed.
1748 behaviors as needed.
1752 """
1749 """
1753 try:
1750 try:
1754 if p1 is None and p2 is None:
1751 if p1 is None and p2 is None:
1755 p1, p2 = self.parents(node)
1752 p1, p2 = self.parents(node)
1756 if node != self.hash(text, p1, p2):
1753 if node != self.hash(text, p1, p2):
1757 # Clear the revision cache on hash failure. The revision cache
1754 # Clear the revision cache on hash failure. The revision cache
1758 # only stores the raw revision and clearing the cache does have
1755 # only stores the raw revision and clearing the cache does have
1759 # the side-effect that we won't have a cache hit when the raw
1756 # the side-effect that we won't have a cache hit when the raw
1760 # revision data is accessed. But this case should be rare and
1757 # revision data is accessed. But this case should be rare and
1761 # it is extra work to teach the cache about the hash
1758 # it is extra work to teach the cache about the hash
1762 # verification state.
1759 # verification state.
1763 if self._revisioncache and self._revisioncache[0] == node:
1760 if self._revisioncache and self._revisioncache[0] == node:
1764 self._revisioncache = None
1761 self._revisioncache = None
1765
1762
1766 revornode = rev
1763 revornode = rev
1767 if revornode is None:
1764 if revornode is None:
1768 revornode = templatefilters.short(hex(node))
1765 revornode = templatefilters.short(hex(node))
1769 raise error.RevlogError(_("integrity check failed on %s:%s")
1766 raise error.RevlogError(_("integrity check failed on %s:%s")
1770 % (self.indexfile, pycompat.bytestr(revornode)))
1767 % (self.indexfile, pycompat.bytestr(revornode)))
1771 except error.RevlogError:
1768 except error.RevlogError:
1772 if self._censorable and storageutil.iscensoredtext(text):
1769 if self._censorable and storageutil.iscensoredtext(text):
1773 raise error.CensoredNodeError(self.indexfile, node, text)
1770 raise error.CensoredNodeError(self.indexfile, node, text)
1774 raise
1771 raise
1775
1772
1776 def _enforceinlinesize(self, tr, fp=None):
1773 def _enforceinlinesize(self, tr, fp=None):
1777 """Check if the revlog is too big for inline and convert if so.
1774 """Check if the revlog is too big for inline and convert if so.
1778
1775
1779 This should be called after revisions are added to the revlog. If the
1776 This should be called after revisions are added to the revlog. If the
1780 revlog has grown too large to be an inline revlog, it will convert it
1777 revlog has grown too large to be an inline revlog, it will convert it
1781 to use multiple index and data files.
1778 to use multiple index and data files.
1782 """
1779 """
1783 tiprev = len(self) - 1
1780 tiprev = len(self) - 1
1784 if (not self._inline or
1781 if (not self._inline or
1785 (self.start(tiprev) + self.length(tiprev)) < _maxinline):
1782 (self.start(tiprev) + self.length(tiprev)) < _maxinline):
1786 return
1783 return
1787
1784
1788 trinfo = tr.find(self.indexfile)
1785 trinfo = tr.find(self.indexfile)
1789 if trinfo is None:
1786 if trinfo is None:
1790 raise error.RevlogError(_("%s not found in the transaction")
1787 raise error.RevlogError(_("%s not found in the transaction")
1791 % self.indexfile)
1788 % self.indexfile)
1792
1789
1793 trindex = trinfo[2]
1790 trindex = trinfo[2]
1794 if trindex is not None:
1791 if trindex is not None:
1795 dataoff = self.start(trindex)
1792 dataoff = self.start(trindex)
1796 else:
1793 else:
1797 # revlog was stripped at start of transaction, use all leftover data
1794 # revlog was stripped at start of transaction, use all leftover data
1798 trindex = len(self) - 1
1795 trindex = len(self) - 1
1799 dataoff = self.end(tiprev)
1796 dataoff = self.end(tiprev)
1800
1797
1801 tr.add(self.datafile, dataoff)
1798 tr.add(self.datafile, dataoff)
1802
1799
1803 if fp:
1800 if fp:
1804 fp.flush()
1801 fp.flush()
1805 fp.close()
1802 fp.close()
1806 # We can't use the cached file handle after close(). So prevent
1803 # We can't use the cached file handle after close(). So prevent
1807 # its usage.
1804 # its usage.
1808 self._writinghandles = None
1805 self._writinghandles = None
1809
1806
1810 with self._indexfp('r') as ifh, self._datafp('w') as dfh:
1807 with self._indexfp('r') as ifh, self._datafp('w') as dfh:
1811 for r in self:
1808 for r in self:
1812 dfh.write(self._getsegmentforrevs(r, r, df=ifh)[1])
1809 dfh.write(self._getsegmentforrevs(r, r, df=ifh)[1])
1813
1810
1814 with self._indexfp('w') as fp:
1811 with self._indexfp('w') as fp:
1815 self.version &= ~FLAG_INLINE_DATA
1812 self.version &= ~FLAG_INLINE_DATA
1816 self._inline = False
1813 self._inline = False
1817 io = self._io
1814 io = self._io
1818 for i in self:
1815 for i in self:
1819 e = io.packentry(self.index[i], self.node, self.version, i)
1816 e = io.packentry(self.index[i], self.node, self.version, i)
1820 fp.write(e)
1817 fp.write(e)
1821
1818
1822 # the temp file replace the real index when we exit the context
1819 # the temp file replace the real index when we exit the context
1823 # manager
1820 # manager
1824
1821
1825 tr.replace(self.indexfile, trindex * self._io.size)
1822 tr.replace(self.indexfile, trindex * self._io.size)
1826 self._chunkclear()
1823 self._chunkclear()
1827
1824
1828 def _nodeduplicatecallback(self, transaction, node):
1825 def _nodeduplicatecallback(self, transaction, node):
1829 """called when trying to add a node already stored.
1826 """called when trying to add a node already stored.
1830 """
1827 """
1831
1828
1832 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
1829 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
1833 node=None, flags=REVIDX_DEFAULT_FLAGS, deltacomputer=None,
1830 node=None, flags=REVIDX_DEFAULT_FLAGS, deltacomputer=None,
1834 sidedata=None):
1831 sidedata=None):
1835 """add a revision to the log
1832 """add a revision to the log
1836
1833
1837 text - the revision data to add
1834 text - the revision data to add
1838 transaction - the transaction object used for rollback
1835 transaction - the transaction object used for rollback
1839 link - the linkrev data to add
1836 link - the linkrev data to add
1840 p1, p2 - the parent nodeids of the revision
1837 p1, p2 - the parent nodeids of the revision
1841 cachedelta - an optional precomputed delta
1838 cachedelta - an optional precomputed delta
1842 node - nodeid of revision; typically node is not specified, and it is
1839 node - nodeid of revision; typically node is not specified, and it is
1843 computed by default as hash(text, p1, p2), however subclasses might
1840 computed by default as hash(text, p1, p2), however subclasses might
1844 use different hashing method (and override checkhash() in such case)
1841 use different hashing method (and override checkhash() in such case)
1845 flags - the known flags to set on the revision
1842 flags - the known flags to set on the revision
1846 deltacomputer - an optional deltacomputer instance shared between
1843 deltacomputer - an optional deltacomputer instance shared between
1847 multiple calls
1844 multiple calls
1848 """
1845 """
1849 if link == nullrev:
1846 if link == nullrev:
1850 raise error.RevlogError(_("attempted to add linkrev -1 to %s")
1847 raise error.RevlogError(_("attempted to add linkrev -1 to %s")
1851 % self.indexfile)
1848 % self.indexfile)
1852
1849
1853 if sidedata is None:
1850 if sidedata is None:
1854 sidedata = {}
1851 sidedata = {}
1855
1852
1856 if flags:
1853 if flags:
1857 node = node or self.hash(text, p1, p2)
1854 node = node or self.hash(text, p1, p2)
1858
1855
1859 rawtext, validatehash = flagutil.processflagswrite(self, text, flags,
1856 rawtext, validatehash = flagutil.processflagswrite(self, text, flags,
1860 sidedata=sidedata)
1857 sidedata=sidedata)
1861
1858
1862 # If the flag processor modifies the revision data, ignore any provided
1859 # If the flag processor modifies the revision data, ignore any provided
1863 # cachedelta.
1860 # cachedelta.
1864 if rawtext != text:
1861 if rawtext != text:
1865 cachedelta = None
1862 cachedelta = None
1866
1863
1867 if len(rawtext) > _maxentrysize:
1864 if len(rawtext) > _maxentrysize:
1868 raise error.RevlogError(
1865 raise error.RevlogError(
1869 _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
1866 _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
1870 % (self.indexfile, len(rawtext)))
1867 % (self.indexfile, len(rawtext)))
1871
1868
1872 node = node or self.hash(rawtext, p1, p2)
1869 node = node or self.hash(rawtext, p1, p2)
1873 if node in self.nodemap:
1870 if node in self.nodemap:
1874 return node
1871 return node
1875
1872
1876 if validatehash:
1873 if validatehash:
1877 self.checkhash(rawtext, node, p1=p1, p2=p2)
1874 self.checkhash(rawtext, node, p1=p1, p2=p2)
1878
1875
1879 return self.addrawrevision(rawtext, transaction, link, p1, p2, node,
1876 return self.addrawrevision(rawtext, transaction, link, p1, p2, node,
1880 flags, cachedelta=cachedelta,
1877 flags, cachedelta=cachedelta,
1881 deltacomputer=deltacomputer)
1878 deltacomputer=deltacomputer)
1882
1879
1883 def addrawrevision(self, rawtext, transaction, link, p1, p2, node, flags,
1880 def addrawrevision(self, rawtext, transaction, link, p1, p2, node, flags,
1884 cachedelta=None, deltacomputer=None):
1881 cachedelta=None, deltacomputer=None):
1885 """add a raw revision with known flags, node and parents
1882 """add a raw revision with known flags, node and parents
1886 useful when reusing a revision not stored in this revlog (ex: received
1883 useful when reusing a revision not stored in this revlog (ex: received
1887 over wire, or read from an external bundle).
1884 over wire, or read from an external bundle).
1888 """
1885 """
1889 dfh = None
1886 dfh = None
1890 if not self._inline:
1887 if not self._inline:
1891 dfh = self._datafp("a+")
1888 dfh = self._datafp("a+")
1892 ifh = self._indexfp("a+")
1889 ifh = self._indexfp("a+")
1893 try:
1890 try:
1894 return self._addrevision(node, rawtext, transaction, link, p1, p2,
1891 return self._addrevision(node, rawtext, transaction, link, p1, p2,
1895 flags, cachedelta, ifh, dfh,
1892 flags, cachedelta, ifh, dfh,
1896 deltacomputer=deltacomputer)
1893 deltacomputer=deltacomputer)
1897 finally:
1894 finally:
1898 if dfh:
1895 if dfh:
1899 dfh.close()
1896 dfh.close()
1900 ifh.close()
1897 ifh.close()
1901
1898
1902 def compress(self, data):
1899 def compress(self, data):
1903 """Generate a possibly-compressed representation of data."""
1900 """Generate a possibly-compressed representation of data."""
1904 if not data:
1901 if not data:
1905 return '', data
1902 return '', data
1906
1903
1907 compressed = self._compressor.compress(data)
1904 compressed = self._compressor.compress(data)
1908
1905
1909 if compressed:
1906 if compressed:
1910 # The revlog compressor added the header in the returned data.
1907 # The revlog compressor added the header in the returned data.
1911 return '', compressed
1908 return '', compressed
1912
1909
1913 if data[0:1] == '\0':
1910 if data[0:1] == '\0':
1914 return '', data
1911 return '', data
1915 return 'u', data
1912 return 'u', data
1916
1913
1917 def decompress(self, data):
1914 def decompress(self, data):
1918 """Decompress a revlog chunk.
1915 """Decompress a revlog chunk.
1919
1916
1920 The chunk is expected to begin with a header identifying the
1917 The chunk is expected to begin with a header identifying the
1921 format type so it can be routed to an appropriate decompressor.
1918 format type so it can be routed to an appropriate decompressor.
1922 """
1919 """
1923 if not data:
1920 if not data:
1924 return data
1921 return data
1925
1922
1926 # Revlogs are read much more frequently than they are written and many
1923 # Revlogs are read much more frequently than they are written and many
1927 # chunks only take microseconds to decompress, so performance is
1924 # chunks only take microseconds to decompress, so performance is
1928 # important here.
1925 # important here.
1929 #
1926 #
1930 # We can make a few assumptions about revlogs:
1927 # We can make a few assumptions about revlogs:
1931 #
1928 #
1932 # 1) the majority of chunks will be compressed (as opposed to inline
1929 # 1) the majority of chunks will be compressed (as opposed to inline
1933 # raw data).
1930 # raw data).
1934 # 2) decompressing *any* data will likely by at least 10x slower than
1931 # 2) decompressing *any* data will likely by at least 10x slower than
1935 # returning raw inline data.
1932 # returning raw inline data.
1936 # 3) we want to prioritize common and officially supported compression
1933 # 3) we want to prioritize common and officially supported compression
1937 # engines
1934 # engines
1938 #
1935 #
1939 # It follows that we want to optimize for "decompress compressed data
1936 # It follows that we want to optimize for "decompress compressed data
1940 # when encoded with common and officially supported compression engines"
1937 # when encoded with common and officially supported compression engines"
1941 # case over "raw data" and "data encoded by less common or non-official
1938 # case over "raw data" and "data encoded by less common or non-official
1942 # compression engines." That is why we have the inline lookup first
1939 # compression engines." That is why we have the inline lookup first
1943 # followed by the compengines lookup.
1940 # followed by the compengines lookup.
1944 #
1941 #
1945 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
1942 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
1946 # compressed chunks. And this matters for changelog and manifest reads.
1943 # compressed chunks. And this matters for changelog and manifest reads.
1947 t = data[0:1]
1944 t = data[0:1]
1948
1945
1949 if t == 'x':
1946 if t == 'x':
1950 try:
1947 try:
1951 return _zlibdecompress(data)
1948 return _zlibdecompress(data)
1952 except zlib.error as e:
1949 except zlib.error as e:
1953 raise error.RevlogError(_('revlog decompress error: %s') %
1950 raise error.RevlogError(_('revlog decompress error: %s') %
1954 stringutil.forcebytestr(e))
1951 stringutil.forcebytestr(e))
1955 # '\0' is more common than 'u' so it goes first.
1952 # '\0' is more common than 'u' so it goes first.
1956 elif t == '\0':
1953 elif t == '\0':
1957 return data
1954 return data
1958 elif t == 'u':
1955 elif t == 'u':
1959 return util.buffer(data, 1)
1956 return util.buffer(data, 1)
1960
1957
1961 try:
1958 try:
1962 compressor = self._decompressors[t]
1959 compressor = self._decompressors[t]
1963 except KeyError:
1960 except KeyError:
1964 try:
1961 try:
1965 engine = util.compengines.forrevlogheader(t)
1962 engine = util.compengines.forrevlogheader(t)
1966 compressor = engine.revlogcompressor(self._compengineopts)
1963 compressor = engine.revlogcompressor(self._compengineopts)
1967 self._decompressors[t] = compressor
1964 self._decompressors[t] = compressor
1968 except KeyError:
1965 except KeyError:
1969 raise error.RevlogError(_('unknown compression type %r') % t)
1966 raise error.RevlogError(_('unknown compression type %r') % t)
1970
1967
1971 return compressor.decompress(data)
1968 return compressor.decompress(data)
1972
1969
1973 def _addrevision(self, node, rawtext, transaction, link, p1, p2, flags,
1970 def _addrevision(self, node, rawtext, transaction, link, p1, p2, flags,
1974 cachedelta, ifh, dfh, alwayscache=False,
1971 cachedelta, ifh, dfh, alwayscache=False,
1975 deltacomputer=None):
1972 deltacomputer=None):
1976 """internal function to add revisions to the log
1973 """internal function to add revisions to the log
1977
1974
1978 see addrevision for argument descriptions.
1975 see addrevision for argument descriptions.
1979
1976
1980 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
1977 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
1981
1978
1982 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
1979 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
1983 be used.
1980 be used.
1984
1981
1985 invariants:
1982 invariants:
1986 - rawtext is optional (can be None); if not set, cachedelta must be set.
1983 - rawtext is optional (can be None); if not set, cachedelta must be set.
1987 if both are set, they must correspond to each other.
1984 if both are set, they must correspond to each other.
1988 """
1985 """
1989 if node == nullid:
1986 if node == nullid:
1990 raise error.RevlogError(_("%s: attempt to add null revision") %
1987 raise error.RevlogError(_("%s: attempt to add null revision") %
1991 self.indexfile)
1988 self.indexfile)
1992 if node == wdirid or node in wdirfilenodeids:
1989 if node == wdirid or node in wdirfilenodeids:
1993 raise error.RevlogError(_("%s: attempt to add wdir revision") %
1990 raise error.RevlogError(_("%s: attempt to add wdir revision") %
1994 self.indexfile)
1991 self.indexfile)
1995
1992
1996 if self._inline:
1993 if self._inline:
1997 fh = ifh
1994 fh = ifh
1998 else:
1995 else:
1999 fh = dfh
1996 fh = dfh
2000
1997
2001 btext = [rawtext]
1998 btext = [rawtext]
2002
1999
2003 curr = len(self)
2000 curr = len(self)
2004 prev = curr - 1
2001 prev = curr - 1
2005 offset = self.end(prev)
2002 offset = self.end(prev)
2006 p1r, p2r = self.rev(p1), self.rev(p2)
2003 p1r, p2r = self.rev(p1), self.rev(p2)
2007
2004
2008 # full versions are inserted when the needed deltas
2005 # full versions are inserted when the needed deltas
2009 # become comparable to the uncompressed text
2006 # become comparable to the uncompressed text
2010 if rawtext is None:
2007 if rawtext is None:
2011 # need rawtext size, before changed by flag processors, which is
2008 # need rawtext size, before changed by flag processors, which is
2012 # the non-raw size. use revlog explicitly to avoid filelog's extra
2009 # the non-raw size. use revlog explicitly to avoid filelog's extra
2013 # logic that might remove metadata size.
2010 # logic that might remove metadata size.
2014 textlen = mdiff.patchedsize(revlog.size(self, cachedelta[0]),
2011 textlen = mdiff.patchedsize(revlog.size(self, cachedelta[0]),
2015 cachedelta[1])
2012 cachedelta[1])
2016 else:
2013 else:
2017 textlen = len(rawtext)
2014 textlen = len(rawtext)
2018
2015
2019 if deltacomputer is None:
2016 if deltacomputer is None:
2020 deltacomputer = deltautil.deltacomputer(self)
2017 deltacomputer = deltautil.deltacomputer(self)
2021
2018
2022 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
2019 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
2023
2020
2024 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2021 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2025
2022
2026 e = (offset_type(offset, flags), deltainfo.deltalen, textlen,
2023 e = (offset_type(offset, flags), deltainfo.deltalen, textlen,
2027 deltainfo.base, link, p1r, p2r, node)
2024 deltainfo.base, link, p1r, p2r, node)
2028 self.index.append(e)
2025 self.index.append(e)
2029 self.nodemap[node] = curr
2026 self.nodemap[node] = curr
2030
2027
2031 # Reset the pure node cache start lookup offset to account for new
2028 # Reset the pure node cache start lookup offset to account for new
2032 # revision.
2029 # revision.
2033 if self._nodepos is not None:
2030 if self._nodepos is not None:
2034 self._nodepos = curr
2031 self._nodepos = curr
2035
2032
2036 entry = self._io.packentry(e, self.node, self.version, curr)
2033 entry = self._io.packentry(e, self.node, self.version, curr)
2037 self._writeentry(transaction, ifh, dfh, entry, deltainfo.data,
2034 self._writeentry(transaction, ifh, dfh, entry, deltainfo.data,
2038 link, offset)
2035 link, offset)
2039
2036
2040 rawtext = btext[0]
2037 rawtext = btext[0]
2041
2038
2042 if alwayscache and rawtext is None:
2039 if alwayscache and rawtext is None:
2043 rawtext = deltacomputer.buildtext(revinfo, fh)
2040 rawtext = deltacomputer.buildtext(revinfo, fh)
2044
2041
2045 if type(rawtext) == bytes: # only accept immutable objects
2042 if type(rawtext) == bytes: # only accept immutable objects
2046 self._revisioncache = (node, curr, rawtext)
2043 self._revisioncache = (node, curr, rawtext)
2047 self._chainbasecache[curr] = deltainfo.chainbase
2044 self._chainbasecache[curr] = deltainfo.chainbase
2048 return node
2045 return node
2049
2046
2050 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
2047 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
2051 # Files opened in a+ mode have inconsistent behavior on various
2048 # Files opened in a+ mode have inconsistent behavior on various
2052 # platforms. Windows requires that a file positioning call be made
2049 # platforms. Windows requires that a file positioning call be made
2053 # when the file handle transitions between reads and writes. See
2050 # when the file handle transitions between reads and writes. See
2054 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2051 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2055 # platforms, Python or the platform itself can be buggy. Some versions
2052 # platforms, Python or the platform itself can be buggy. Some versions
2056 # of Solaris have been observed to not append at the end of the file
2053 # of Solaris have been observed to not append at the end of the file
2057 # if the file was seeked to before the end. See issue4943 for more.
2054 # if the file was seeked to before the end. See issue4943 for more.
2058 #
2055 #
2059 # We work around this issue by inserting a seek() before writing.
2056 # We work around this issue by inserting a seek() before writing.
2060 # Note: This is likely not necessary on Python 3. However, because
2057 # Note: This is likely not necessary on Python 3. However, because
2061 # the file handle is reused for reads and may be seeked there, we need
2058 # the file handle is reused for reads and may be seeked there, we need
2062 # to be careful before changing this.
2059 # to be careful before changing this.
2063 ifh.seek(0, os.SEEK_END)
2060 ifh.seek(0, os.SEEK_END)
2064 if dfh:
2061 if dfh:
2065 dfh.seek(0, os.SEEK_END)
2062 dfh.seek(0, os.SEEK_END)
2066
2063
2067 curr = len(self) - 1
2064 curr = len(self) - 1
2068 if not self._inline:
2065 if not self._inline:
2069 transaction.add(self.datafile, offset)
2066 transaction.add(self.datafile, offset)
2070 transaction.add(self.indexfile, curr * len(entry))
2067 transaction.add(self.indexfile, curr * len(entry))
2071 if data[0]:
2068 if data[0]:
2072 dfh.write(data[0])
2069 dfh.write(data[0])
2073 dfh.write(data[1])
2070 dfh.write(data[1])
2074 ifh.write(entry)
2071 ifh.write(entry)
2075 else:
2072 else:
2076 offset += curr * self._io.size
2073 offset += curr * self._io.size
2077 transaction.add(self.indexfile, offset, curr)
2074 transaction.add(self.indexfile, offset, curr)
2078 ifh.write(entry)
2075 ifh.write(entry)
2079 ifh.write(data[0])
2076 ifh.write(data[0])
2080 ifh.write(data[1])
2077 ifh.write(data[1])
2081 self._enforceinlinesize(transaction, ifh)
2078 self._enforceinlinesize(transaction, ifh)
2082
2079
2083 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
2080 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
2084 """
2081 """
2085 add a delta group
2082 add a delta group
2086
2083
2087 given a set of deltas, add them to the revision log. the
2084 given a set of deltas, add them to the revision log. the
2088 first delta is against its parent, which should be in our
2085 first delta is against its parent, which should be in our
2089 log, the rest are against the previous delta.
2086 log, the rest are against the previous delta.
2090
2087
2091 If ``addrevisioncb`` is defined, it will be called with arguments of
2088 If ``addrevisioncb`` is defined, it will be called with arguments of
2092 this revlog and the node that was added.
2089 this revlog and the node that was added.
2093 """
2090 """
2094
2091
2095 if self._writinghandles:
2092 if self._writinghandles:
2096 raise error.ProgrammingError('cannot nest addgroup() calls')
2093 raise error.ProgrammingError('cannot nest addgroup() calls')
2097
2094
2098 nodes = []
2095 nodes = []
2099
2096
2100 r = len(self)
2097 r = len(self)
2101 end = 0
2098 end = 0
2102 if r:
2099 if r:
2103 end = self.end(r - 1)
2100 end = self.end(r - 1)
2104 ifh = self._indexfp("a+")
2101 ifh = self._indexfp("a+")
2105 isize = r * self._io.size
2102 isize = r * self._io.size
2106 if self._inline:
2103 if self._inline:
2107 transaction.add(self.indexfile, end + isize, r)
2104 transaction.add(self.indexfile, end + isize, r)
2108 dfh = None
2105 dfh = None
2109 else:
2106 else:
2110 transaction.add(self.indexfile, isize, r)
2107 transaction.add(self.indexfile, isize, r)
2111 transaction.add(self.datafile, end)
2108 transaction.add(self.datafile, end)
2112 dfh = self._datafp("a+")
2109 dfh = self._datafp("a+")
2113 def flush():
2110 def flush():
2114 if dfh:
2111 if dfh:
2115 dfh.flush()
2112 dfh.flush()
2116 ifh.flush()
2113 ifh.flush()
2117
2114
2118 self._writinghandles = (ifh, dfh)
2115 self._writinghandles = (ifh, dfh)
2119
2116
2120 try:
2117 try:
2121 deltacomputer = deltautil.deltacomputer(self)
2118 deltacomputer = deltautil.deltacomputer(self)
2122 # loop through our set of deltas
2119 # loop through our set of deltas
2123 for data in deltas:
2120 for data in deltas:
2124 node, p1, p2, linknode, deltabase, delta, flags = data
2121 node, p1, p2, linknode, deltabase, delta, flags = data
2125 link = linkmapper(linknode)
2122 link = linkmapper(linknode)
2126 flags = flags or REVIDX_DEFAULT_FLAGS
2123 flags = flags or REVIDX_DEFAULT_FLAGS
2127
2124
2128 nodes.append(node)
2125 nodes.append(node)
2129
2126
2130 if node in self.nodemap:
2127 if node in self.nodemap:
2131 self._nodeduplicatecallback(transaction, node)
2128 self._nodeduplicatecallback(transaction, node)
2132 # this can happen if two branches make the same change
2129 # this can happen if two branches make the same change
2133 continue
2130 continue
2134
2131
2135 for p in (p1, p2):
2132 for p in (p1, p2):
2136 if p not in self.nodemap:
2133 if p not in self.nodemap:
2137 raise error.LookupError(p, self.indexfile,
2134 raise error.LookupError(p, self.indexfile,
2138 _('unknown parent'))
2135 _('unknown parent'))
2139
2136
2140 if deltabase not in self.nodemap:
2137 if deltabase not in self.nodemap:
2141 raise error.LookupError(deltabase, self.indexfile,
2138 raise error.LookupError(deltabase, self.indexfile,
2142 _('unknown delta base'))
2139 _('unknown delta base'))
2143
2140
2144 baserev = self.rev(deltabase)
2141 baserev = self.rev(deltabase)
2145
2142
2146 if baserev != nullrev and self.iscensored(baserev):
2143 if baserev != nullrev and self.iscensored(baserev):
2147 # if base is censored, delta must be full replacement in a
2144 # if base is censored, delta must be full replacement in a
2148 # single patch operation
2145 # single patch operation
2149 hlen = struct.calcsize(">lll")
2146 hlen = struct.calcsize(">lll")
2150 oldlen = self.rawsize(baserev)
2147 oldlen = self.rawsize(baserev)
2151 newlen = len(delta) - hlen
2148 newlen = len(delta) - hlen
2152 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2149 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2153 raise error.CensoredBaseError(self.indexfile,
2150 raise error.CensoredBaseError(self.indexfile,
2154 self.node(baserev))
2151 self.node(baserev))
2155
2152
2156 if not flags and self._peek_iscensored(baserev, delta, flush):
2153 if not flags and self._peek_iscensored(baserev, delta, flush):
2157 flags |= REVIDX_ISCENSORED
2154 flags |= REVIDX_ISCENSORED
2158
2155
2159 # We assume consumers of addrevisioncb will want to retrieve
2156 # We assume consumers of addrevisioncb will want to retrieve
2160 # the added revision, which will require a call to
2157 # the added revision, which will require a call to
2161 # revision(). revision() will fast path if there is a cache
2158 # revision(). revision() will fast path if there is a cache
2162 # hit. So, we tell _addrevision() to always cache in this case.
2159 # hit. So, we tell _addrevision() to always cache in this case.
2163 # We're only using addgroup() in the context of changegroup
2160 # We're only using addgroup() in the context of changegroup
2164 # generation so the revision data can always be handled as raw
2161 # generation so the revision data can always be handled as raw
2165 # by the flagprocessor.
2162 # by the flagprocessor.
2166 self._addrevision(node, None, transaction, link,
2163 self._addrevision(node, None, transaction, link,
2167 p1, p2, flags, (baserev, delta),
2164 p1, p2, flags, (baserev, delta),
2168 ifh, dfh,
2165 ifh, dfh,
2169 alwayscache=bool(addrevisioncb),
2166 alwayscache=bool(addrevisioncb),
2170 deltacomputer=deltacomputer)
2167 deltacomputer=deltacomputer)
2171
2168
2172 if addrevisioncb:
2169 if addrevisioncb:
2173 addrevisioncb(self, node)
2170 addrevisioncb(self, node)
2174
2171
2175 if not dfh and not self._inline:
2172 if not dfh and not self._inline:
2176 # addrevision switched from inline to conventional
2173 # addrevision switched from inline to conventional
2177 # reopen the index
2174 # reopen the index
2178 ifh.close()
2175 ifh.close()
2179 dfh = self._datafp("a+")
2176 dfh = self._datafp("a+")
2180 ifh = self._indexfp("a+")
2177 ifh = self._indexfp("a+")
2181 self._writinghandles = (ifh, dfh)
2178 self._writinghandles = (ifh, dfh)
2182 finally:
2179 finally:
2183 self._writinghandles = None
2180 self._writinghandles = None
2184
2181
2185 if dfh:
2182 if dfh:
2186 dfh.close()
2183 dfh.close()
2187 ifh.close()
2184 ifh.close()
2188
2185
2189 return nodes
2186 return nodes
2190
2187
2191 def iscensored(self, rev):
2188 def iscensored(self, rev):
2192 """Check if a file revision is censored."""
2189 """Check if a file revision is censored."""
2193 if not self._censorable:
2190 if not self._censorable:
2194 return False
2191 return False
2195
2192
2196 return self.flags(rev) & REVIDX_ISCENSORED
2193 return self.flags(rev) & REVIDX_ISCENSORED
2197
2194
2198 def _peek_iscensored(self, baserev, delta, flush):
2195 def _peek_iscensored(self, baserev, delta, flush):
2199 """Quickly check if a delta produces a censored revision."""
2196 """Quickly check if a delta produces a censored revision."""
2200 if not self._censorable:
2197 if not self._censorable:
2201 return False
2198 return False
2202
2199
2203 return storageutil.deltaiscensored(delta, baserev, self.rawsize)
2200 return storageutil.deltaiscensored(delta, baserev, self.rawsize)
2204
2201
2205 def getstrippoint(self, minlink):
2202 def getstrippoint(self, minlink):
2206 """find the minimum rev that must be stripped to strip the linkrev
2203 """find the minimum rev that must be stripped to strip the linkrev
2207
2204
2208 Returns a tuple containing the minimum rev and a set of all revs that
2205 Returns a tuple containing the minimum rev and a set of all revs that
2209 have linkrevs that will be broken by this strip.
2206 have linkrevs that will be broken by this strip.
2210 """
2207 """
2211 return storageutil.resolvestripinfo(minlink, len(self) - 1,
2208 return storageutil.resolvestripinfo(minlink, len(self) - 1,
2212 self.headrevs(),
2209 self.headrevs(),
2213 self.linkrev, self.parentrevs)
2210 self.linkrev, self.parentrevs)
2214
2211
2215 def strip(self, minlink, transaction):
2212 def strip(self, minlink, transaction):
2216 """truncate the revlog on the first revision with a linkrev >= minlink
2213 """truncate the revlog on the first revision with a linkrev >= minlink
2217
2214
2218 This function is called when we're stripping revision minlink and
2215 This function is called when we're stripping revision minlink and
2219 its descendants from the repository.
2216 its descendants from the repository.
2220
2217
2221 We have to remove all revisions with linkrev >= minlink, because
2218 We have to remove all revisions with linkrev >= minlink, because
2222 the equivalent changelog revisions will be renumbered after the
2219 the equivalent changelog revisions will be renumbered after the
2223 strip.
2220 strip.
2224
2221
2225 So we truncate the revlog on the first of these revisions, and
2222 So we truncate the revlog on the first of these revisions, and
2226 trust that the caller has saved the revisions that shouldn't be
2223 trust that the caller has saved the revisions that shouldn't be
2227 removed and that it'll re-add them after this truncation.
2224 removed and that it'll re-add them after this truncation.
2228 """
2225 """
2229 if len(self) == 0:
2226 if len(self) == 0:
2230 return
2227 return
2231
2228
2232 rev, _ = self.getstrippoint(minlink)
2229 rev, _ = self.getstrippoint(minlink)
2233 if rev == len(self):
2230 if rev == len(self):
2234 return
2231 return
2235
2232
2236 # first truncate the files on disk
2233 # first truncate the files on disk
2237 end = self.start(rev)
2234 end = self.start(rev)
2238 if not self._inline:
2235 if not self._inline:
2239 transaction.add(self.datafile, end)
2236 transaction.add(self.datafile, end)
2240 end = rev * self._io.size
2237 end = rev * self._io.size
2241 else:
2238 else:
2242 end += rev * self._io.size
2239 end += rev * self._io.size
2243
2240
2244 transaction.add(self.indexfile, end)
2241 transaction.add(self.indexfile, end)
2245
2242
2246 # then reset internal state in memory to forget those revisions
2243 # then reset internal state in memory to forget those revisions
2247 self._revisioncache = None
2244 self._revisioncache = None
2248 self._chaininfocache = {}
2245 self._chaininfocache = {}
2249 self._chunkclear()
2246 self._chunkclear()
2250 for x in pycompat.xrange(rev, len(self)):
2247 for x in pycompat.xrange(rev, len(self)):
2251 del self.nodemap[self.node(x)]
2248 del self.nodemap[self.node(x)]
2252
2249
2253 del self.index[rev:-1]
2250 del self.index[rev:-1]
2254 self._nodepos = None
2251 self._nodepos = None
2255
2252
2256 def checksize(self):
2253 def checksize(self):
2257 """Check size of index and data files
2254 """Check size of index and data files
2258
2255
2259 return a (dd, di) tuple.
2256 return a (dd, di) tuple.
2260 - dd: extra bytes for the "data" file
2257 - dd: extra bytes for the "data" file
2261 - di: extra bytes for the "index" file
2258 - di: extra bytes for the "index" file
2262
2259
2263 A healthy revlog will return (0, 0).
2260 A healthy revlog will return (0, 0).
2264 """
2261 """
2265 expected = 0
2262 expected = 0
2266 if len(self):
2263 if len(self):
2267 expected = max(0, self.end(len(self) - 1))
2264 expected = max(0, self.end(len(self) - 1))
2268
2265
2269 try:
2266 try:
2270 with self._datafp() as f:
2267 with self._datafp() as f:
2271 f.seek(0, io.SEEK_END)
2268 f.seek(0, io.SEEK_END)
2272 actual = f.tell()
2269 actual = f.tell()
2273 dd = actual - expected
2270 dd = actual - expected
2274 except IOError as inst:
2271 except IOError as inst:
2275 if inst.errno != errno.ENOENT:
2272 if inst.errno != errno.ENOENT:
2276 raise
2273 raise
2277 dd = 0
2274 dd = 0
2278
2275
2279 try:
2276 try:
2280 f = self.opener(self.indexfile)
2277 f = self.opener(self.indexfile)
2281 f.seek(0, io.SEEK_END)
2278 f.seek(0, io.SEEK_END)
2282 actual = f.tell()
2279 actual = f.tell()
2283 f.close()
2280 f.close()
2284 s = self._io.size
2281 s = self._io.size
2285 i = max(0, actual // s)
2282 i = max(0, actual // s)
2286 di = actual - (i * s)
2283 di = actual - (i * s)
2287 if self._inline:
2284 if self._inline:
2288 databytes = 0
2285 databytes = 0
2289 for r in self:
2286 for r in self:
2290 databytes += max(0, self.length(r))
2287 databytes += max(0, self.length(r))
2291 dd = 0
2288 dd = 0
2292 di = actual - len(self) * s - databytes
2289 di = actual - len(self) * s - databytes
2293 except IOError as inst:
2290 except IOError as inst:
2294 if inst.errno != errno.ENOENT:
2291 if inst.errno != errno.ENOENT:
2295 raise
2292 raise
2296 di = 0
2293 di = 0
2297
2294
2298 return (dd, di)
2295 return (dd, di)
2299
2296
2300 def files(self):
2297 def files(self):
2301 res = [self.indexfile]
2298 res = [self.indexfile]
2302 if not self._inline:
2299 if not self._inline:
2303 res.append(self.datafile)
2300 res.append(self.datafile)
2304 return res
2301 return res
2305
2302
2306 def emitrevisions(self, nodes, nodesorder=None, revisiondata=False,
2303 def emitrevisions(self, nodes, nodesorder=None, revisiondata=False,
2307 assumehaveparentrevisions=False,
2304 assumehaveparentrevisions=False,
2308 deltamode=repository.CG_DELTAMODE_STD):
2305 deltamode=repository.CG_DELTAMODE_STD):
2309 if nodesorder not in ('nodes', 'storage', 'linear', None):
2306 if nodesorder not in ('nodes', 'storage', 'linear', None):
2310 raise error.ProgrammingError('unhandled value for nodesorder: %s' %
2307 raise error.ProgrammingError('unhandled value for nodesorder: %s' %
2311 nodesorder)
2308 nodesorder)
2312
2309
2313 if nodesorder is None and not self._generaldelta:
2310 if nodesorder is None and not self._generaldelta:
2314 nodesorder = 'storage'
2311 nodesorder = 'storage'
2315
2312
2316 if (not self._storedeltachains and
2313 if (not self._storedeltachains and
2317 deltamode != repository.CG_DELTAMODE_PREV):
2314 deltamode != repository.CG_DELTAMODE_PREV):
2318 deltamode = repository.CG_DELTAMODE_FULL
2315 deltamode = repository.CG_DELTAMODE_FULL
2319
2316
2320 return storageutil.emitrevisions(
2317 return storageutil.emitrevisions(
2321 self, nodes, nodesorder, revlogrevisiondelta,
2318 self, nodes, nodesorder, revlogrevisiondelta,
2322 deltaparentfn=self.deltaparent,
2319 deltaparentfn=self.deltaparent,
2323 candeltafn=self.candelta,
2320 candeltafn=self.candelta,
2324 rawsizefn=self.rawsize,
2321 rawsizefn=self.rawsize,
2325 revdifffn=self.revdiff,
2322 revdifffn=self.revdiff,
2326 flagsfn=self.flags,
2323 flagsfn=self.flags,
2327 deltamode=deltamode,
2324 deltamode=deltamode,
2328 revisiondata=revisiondata,
2325 revisiondata=revisiondata,
2329 assumehaveparentrevisions=assumehaveparentrevisions)
2326 assumehaveparentrevisions=assumehaveparentrevisions)
2330
2327
2331 DELTAREUSEALWAYS = 'always'
2328 DELTAREUSEALWAYS = 'always'
2332 DELTAREUSESAMEREVS = 'samerevs'
2329 DELTAREUSESAMEREVS = 'samerevs'
2333 DELTAREUSENEVER = 'never'
2330 DELTAREUSENEVER = 'never'
2334
2331
2335 DELTAREUSEFULLADD = 'fulladd'
2332 DELTAREUSEFULLADD = 'fulladd'
2336
2333
2337 DELTAREUSEALL = {'always', 'samerevs', 'never', 'fulladd'}
2334 DELTAREUSEALL = {'always', 'samerevs', 'never', 'fulladd'}
2338
2335
2339 def clone(self, tr, destrevlog, addrevisioncb=None,
2336 def clone(self, tr, destrevlog, addrevisioncb=None,
2340 deltareuse=DELTAREUSESAMEREVS, forcedeltabothparents=None):
2337 deltareuse=DELTAREUSESAMEREVS, forcedeltabothparents=None):
2341 """Copy this revlog to another, possibly with format changes.
2338 """Copy this revlog to another, possibly with format changes.
2342
2339
2343 The destination revlog will contain the same revisions and nodes.
2340 The destination revlog will contain the same revisions and nodes.
2344 However, it may not be bit-for-bit identical due to e.g. delta encoding
2341 However, it may not be bit-for-bit identical due to e.g. delta encoding
2345 differences.
2342 differences.
2346
2343
2347 The ``deltareuse`` argument control how deltas from the existing revlog
2344 The ``deltareuse`` argument control how deltas from the existing revlog
2348 are preserved in the destination revlog. The argument can have the
2345 are preserved in the destination revlog. The argument can have the
2349 following values:
2346 following values:
2350
2347
2351 DELTAREUSEALWAYS
2348 DELTAREUSEALWAYS
2352 Deltas will always be reused (if possible), even if the destination
2349 Deltas will always be reused (if possible), even if the destination
2353 revlog would not select the same revisions for the delta. This is the
2350 revlog would not select the same revisions for the delta. This is the
2354 fastest mode of operation.
2351 fastest mode of operation.
2355 DELTAREUSESAMEREVS
2352 DELTAREUSESAMEREVS
2356 Deltas will be reused if the destination revlog would pick the same
2353 Deltas will be reused if the destination revlog would pick the same
2357 revisions for the delta. This mode strikes a balance between speed
2354 revisions for the delta. This mode strikes a balance between speed
2358 and optimization.
2355 and optimization.
2359 DELTAREUSENEVER
2356 DELTAREUSENEVER
2360 Deltas will never be reused. This is the slowest mode of execution.
2357 Deltas will never be reused. This is the slowest mode of execution.
2361 This mode can be used to recompute deltas (e.g. if the diff/delta
2358 This mode can be used to recompute deltas (e.g. if the diff/delta
2362 algorithm changes).
2359 algorithm changes).
2363 DELTAREUSEFULLADD
2360 DELTAREUSEFULLADD
2364 Revision will be re-added as if their were new content. This is
2361 Revision will be re-added as if their were new content. This is
2365 slower than DELTAREUSEALWAYS but allow more mechanism to kicks in.
2362 slower than DELTAREUSEALWAYS but allow more mechanism to kicks in.
2366 eg: large file detection and handling.
2363 eg: large file detection and handling.
2367
2364
2368 Delta computation can be slow, so the choice of delta reuse policy can
2365 Delta computation can be slow, so the choice of delta reuse policy can
2369 significantly affect run time.
2366 significantly affect run time.
2370
2367
2371 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2368 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2372 two extremes. Deltas will be reused if they are appropriate. But if the
2369 two extremes. Deltas will be reused if they are appropriate. But if the
2373 delta could choose a better revision, it will do so. This means if you
2370 delta could choose a better revision, it will do so. This means if you
2374 are converting a non-generaldelta revlog to a generaldelta revlog,
2371 are converting a non-generaldelta revlog to a generaldelta revlog,
2375 deltas will be recomputed if the delta's parent isn't a parent of the
2372 deltas will be recomputed if the delta's parent isn't a parent of the
2376 revision.
2373 revision.
2377
2374
2378 In addition to the delta policy, the ``forcedeltabothparents``
2375 In addition to the delta policy, the ``forcedeltabothparents``
2379 argument controls whether to force compute deltas against both parents
2376 argument controls whether to force compute deltas against both parents
2380 for merges. By default, the current default is used.
2377 for merges. By default, the current default is used.
2381 """
2378 """
2382 if deltareuse not in self.DELTAREUSEALL:
2379 if deltareuse not in self.DELTAREUSEALL:
2383 raise ValueError(_('value for deltareuse invalid: %s') % deltareuse)
2380 raise ValueError(_('value for deltareuse invalid: %s') % deltareuse)
2384
2381
2385 if len(destrevlog):
2382 if len(destrevlog):
2386 raise ValueError(_('destination revlog is not empty'))
2383 raise ValueError(_('destination revlog is not empty'))
2387
2384
2388 if getattr(self, 'filteredrevs', None):
2385 if getattr(self, 'filteredrevs', None):
2389 raise ValueError(_('source revlog has filtered revisions'))
2386 raise ValueError(_('source revlog has filtered revisions'))
2390 if getattr(destrevlog, 'filteredrevs', None):
2387 if getattr(destrevlog, 'filteredrevs', None):
2391 raise ValueError(_('destination revlog has filtered revisions'))
2388 raise ValueError(_('destination revlog has filtered revisions'))
2392
2389
2393 # lazydelta and lazydeltabase controls whether to reuse a cached delta,
2390 # lazydelta and lazydeltabase controls whether to reuse a cached delta,
2394 # if possible.
2391 # if possible.
2395 oldlazydelta = destrevlog._lazydelta
2392 oldlazydelta = destrevlog._lazydelta
2396 oldlazydeltabase = destrevlog._lazydeltabase
2393 oldlazydeltabase = destrevlog._lazydeltabase
2397 oldamd = destrevlog._deltabothparents
2394 oldamd = destrevlog._deltabothparents
2398
2395
2399 try:
2396 try:
2400 if deltareuse == self.DELTAREUSEALWAYS:
2397 if deltareuse == self.DELTAREUSEALWAYS:
2401 destrevlog._lazydeltabase = True
2398 destrevlog._lazydeltabase = True
2402 destrevlog._lazydelta = True
2399 destrevlog._lazydelta = True
2403 elif deltareuse == self.DELTAREUSESAMEREVS:
2400 elif deltareuse == self.DELTAREUSESAMEREVS:
2404 destrevlog._lazydeltabase = False
2401 destrevlog._lazydeltabase = False
2405 destrevlog._lazydelta = True
2402 destrevlog._lazydelta = True
2406 elif deltareuse == self.DELTAREUSENEVER:
2403 elif deltareuse == self.DELTAREUSENEVER:
2407 destrevlog._lazydeltabase = False
2404 destrevlog._lazydeltabase = False
2408 destrevlog._lazydelta = False
2405 destrevlog._lazydelta = False
2409
2406
2410 destrevlog._deltabothparents = forcedeltabothparents or oldamd
2407 destrevlog._deltabothparents = forcedeltabothparents or oldamd
2411
2408
2412 self._clone(tr, destrevlog, addrevisioncb, deltareuse,
2409 self._clone(tr, destrevlog, addrevisioncb, deltareuse,
2413 forcedeltabothparents)
2410 forcedeltabothparents)
2414
2411
2415 finally:
2412 finally:
2416 destrevlog._lazydelta = oldlazydelta
2413 destrevlog._lazydelta = oldlazydelta
2417 destrevlog._lazydeltabase = oldlazydeltabase
2414 destrevlog._lazydeltabase = oldlazydeltabase
2418 destrevlog._deltabothparents = oldamd
2415 destrevlog._deltabothparents = oldamd
2419
2416
2420 def _clone(self, tr, destrevlog, addrevisioncb, deltareuse,
2417 def _clone(self, tr, destrevlog, addrevisioncb, deltareuse,
2421 forcedeltabothparents):
2418 forcedeltabothparents):
2422 """perform the core duty of `revlog.clone` after parameter processing"""
2419 """perform the core duty of `revlog.clone` after parameter processing"""
2423 deltacomputer = deltautil.deltacomputer(destrevlog)
2420 deltacomputer = deltautil.deltacomputer(destrevlog)
2424 index = self.index
2421 index = self.index
2425 for rev in self:
2422 for rev in self:
2426 entry = index[rev]
2423 entry = index[rev]
2427
2424
2428 # Some classes override linkrev to take filtered revs into
2425 # Some classes override linkrev to take filtered revs into
2429 # account. Use raw entry from index.
2426 # account. Use raw entry from index.
2430 flags = entry[0] & 0xffff
2427 flags = entry[0] & 0xffff
2431 linkrev = entry[4]
2428 linkrev = entry[4]
2432 p1 = index[entry[5]][7]
2429 p1 = index[entry[5]][7]
2433 p2 = index[entry[6]][7]
2430 p2 = index[entry[6]][7]
2434 node = entry[7]
2431 node = entry[7]
2435
2432
2436 # (Possibly) reuse the delta from the revlog if allowed and
2433 # (Possibly) reuse the delta from the revlog if allowed and
2437 # the revlog chunk is a delta.
2434 # the revlog chunk is a delta.
2438 cachedelta = None
2435 cachedelta = None
2439 rawtext = None
2436 rawtext = None
2440 if deltareuse == self.DELTAREUSEFULLADD:
2437 if deltareuse == self.DELTAREUSEFULLADD:
2441 text = self.revision(rev)
2438 text = self.revision(rev)
2442 destrevlog.addrevision(text, tr, linkrev, p1, p2,
2439 destrevlog.addrevision(text, tr, linkrev, p1, p2,
2443 cachedelta=cachedelta,
2440 cachedelta=cachedelta,
2444 node=node, flags=flags,
2441 node=node, flags=flags,
2445 deltacomputer=deltacomputer)
2442 deltacomputer=deltacomputer)
2446 else:
2443 else:
2447 if destrevlog._lazydelta:
2444 if destrevlog._lazydelta:
2448 dp = self.deltaparent(rev)
2445 dp = self.deltaparent(rev)
2449 if dp != nullrev:
2446 if dp != nullrev:
2450 cachedelta = (dp, bytes(self._chunk(rev)))
2447 cachedelta = (dp, bytes(self._chunk(rev)))
2451
2448
2452 if not cachedelta:
2449 if not cachedelta:
2453 rawtext = self.rawdata(rev)
2450 rawtext = self.rawdata(rev)
2454
2451
2455 ifh = destrevlog.opener(destrevlog.indexfile, 'a+',
2452 ifh = destrevlog.opener(destrevlog.indexfile, 'a+',
2456 checkambig=False)
2453 checkambig=False)
2457 dfh = None
2454 dfh = None
2458 if not destrevlog._inline:
2455 if not destrevlog._inline:
2459 dfh = destrevlog.opener(destrevlog.datafile, 'a+')
2456 dfh = destrevlog.opener(destrevlog.datafile, 'a+')
2460 try:
2457 try:
2461 destrevlog._addrevision(node, rawtext, tr, linkrev, p1,
2458 destrevlog._addrevision(node, rawtext, tr, linkrev, p1,
2462 p2, flags, cachedelta, ifh, dfh,
2459 p2, flags, cachedelta, ifh, dfh,
2463 deltacomputer=deltacomputer)
2460 deltacomputer=deltacomputer)
2464 finally:
2461 finally:
2465 if dfh:
2462 if dfh:
2466 dfh.close()
2463 dfh.close()
2467 ifh.close()
2464 ifh.close()
2468
2465
2469 if addrevisioncb:
2466 if addrevisioncb:
2470 addrevisioncb(self, rev, node)
2467 addrevisioncb(self, rev, node)
2471
2468
2472 def censorrevision(self, tr, censornode, tombstone=b''):
2469 def censorrevision(self, tr, censornode, tombstone=b''):
2473 if (self.version & 0xFFFF) == REVLOGV0:
2470 if (self.version & 0xFFFF) == REVLOGV0:
2474 raise error.RevlogError(_('cannot censor with version %d revlogs') %
2471 raise error.RevlogError(_('cannot censor with version %d revlogs') %
2475 self.version)
2472 self.version)
2476
2473
2477 censorrev = self.rev(censornode)
2474 censorrev = self.rev(censornode)
2478 tombstone = storageutil.packmeta({b'censored': tombstone}, b'')
2475 tombstone = storageutil.packmeta({b'censored': tombstone}, b'')
2479
2476
2480 if len(tombstone) > self.rawsize(censorrev):
2477 if len(tombstone) > self.rawsize(censorrev):
2481 raise error.Abort(_('censor tombstone must be no longer than '
2478 raise error.Abort(_('censor tombstone must be no longer than '
2482 'censored data'))
2479 'censored data'))
2483
2480
2484 # Rewriting the revlog in place is hard. Our strategy for censoring is
2481 # Rewriting the revlog in place is hard. Our strategy for censoring is
2485 # to create a new revlog, copy all revisions to it, then replace the
2482 # to create a new revlog, copy all revisions to it, then replace the
2486 # revlogs on transaction close.
2483 # revlogs on transaction close.
2487
2484
2488 newindexfile = self.indexfile + b'.tmpcensored'
2485 newindexfile = self.indexfile + b'.tmpcensored'
2489 newdatafile = self.datafile + b'.tmpcensored'
2486 newdatafile = self.datafile + b'.tmpcensored'
2490
2487
2491 # This is a bit dangerous. We could easily have a mismatch of state.
2488 # This is a bit dangerous. We could easily have a mismatch of state.
2492 newrl = revlog(self.opener, newindexfile, newdatafile,
2489 newrl = revlog(self.opener, newindexfile, newdatafile,
2493 censorable=True)
2490 censorable=True)
2494 newrl.version = self.version
2491 newrl.version = self.version
2495 newrl._generaldelta = self._generaldelta
2492 newrl._generaldelta = self._generaldelta
2496 newrl._io = self._io
2493 newrl._io = self._io
2497
2494
2498 for rev in self.revs():
2495 for rev in self.revs():
2499 node = self.node(rev)
2496 node = self.node(rev)
2500 p1, p2 = self.parents(node)
2497 p1, p2 = self.parents(node)
2501
2498
2502 if rev == censorrev:
2499 if rev == censorrev:
2503 newrl.addrawrevision(tombstone, tr, self.linkrev(censorrev),
2500 newrl.addrawrevision(tombstone, tr, self.linkrev(censorrev),
2504 p1, p2, censornode, REVIDX_ISCENSORED)
2501 p1, p2, censornode, REVIDX_ISCENSORED)
2505
2502
2506 if newrl.deltaparent(rev) != nullrev:
2503 if newrl.deltaparent(rev) != nullrev:
2507 raise error.Abort(_('censored revision stored as delta; '
2504 raise error.Abort(_('censored revision stored as delta; '
2508 'cannot censor'),
2505 'cannot censor'),
2509 hint=_('censoring of revlogs is not '
2506 hint=_('censoring of revlogs is not '
2510 'fully implemented; please report '
2507 'fully implemented; please report '
2511 'this bug'))
2508 'this bug'))
2512 continue
2509 continue
2513
2510
2514 if self.iscensored(rev):
2511 if self.iscensored(rev):
2515 if self.deltaparent(rev) != nullrev:
2512 if self.deltaparent(rev) != nullrev:
2516 raise error.Abort(_('cannot censor due to censored '
2513 raise error.Abort(_('cannot censor due to censored '
2517 'revision having delta stored'))
2514 'revision having delta stored'))
2518 rawtext = self._chunk(rev)
2515 rawtext = self._chunk(rev)
2519 else:
2516 else:
2520 rawtext = self.rawdata(rev)
2517 rawtext = self.rawdata(rev)
2521
2518
2522 newrl.addrawrevision(rawtext, tr, self.linkrev(rev), p1, p2, node,
2519 newrl.addrawrevision(rawtext, tr, self.linkrev(rev), p1, p2, node,
2523 self.flags(rev))
2520 self.flags(rev))
2524
2521
2525 tr.addbackup(self.indexfile, location='store')
2522 tr.addbackup(self.indexfile, location='store')
2526 if not self._inline:
2523 if not self._inline:
2527 tr.addbackup(self.datafile, location='store')
2524 tr.addbackup(self.datafile, location='store')
2528
2525
2529 self.opener.rename(newrl.indexfile, self.indexfile)
2526 self.opener.rename(newrl.indexfile, self.indexfile)
2530 if not self._inline:
2527 if not self._inline:
2531 self.opener.rename(newrl.datafile, self.datafile)
2528 self.opener.rename(newrl.datafile, self.datafile)
2532
2529
2533 self.clearcaches()
2530 self.clearcaches()
2534 self._loadindex()
2531 self._loadindex()
2535
2532
2536 def verifyintegrity(self, state):
2533 def verifyintegrity(self, state):
2537 """Verifies the integrity of the revlog.
2534 """Verifies the integrity of the revlog.
2538
2535
2539 Yields ``revlogproblem`` instances describing problems that are
2536 Yields ``revlogproblem`` instances describing problems that are
2540 found.
2537 found.
2541 """
2538 """
2542 dd, di = self.checksize()
2539 dd, di = self.checksize()
2543 if dd:
2540 if dd:
2544 yield revlogproblem(error=_('data length off by %d bytes') % dd)
2541 yield revlogproblem(error=_('data length off by %d bytes') % dd)
2545 if di:
2542 if di:
2546 yield revlogproblem(error=_('index contains %d extra bytes') % di)
2543 yield revlogproblem(error=_('index contains %d extra bytes') % di)
2547
2544
2548 version = self.version & 0xFFFF
2545 version = self.version & 0xFFFF
2549
2546
2550 # The verifier tells us what version revlog we should be.
2547 # The verifier tells us what version revlog we should be.
2551 if version != state['expectedversion']:
2548 if version != state['expectedversion']:
2552 yield revlogproblem(
2549 yield revlogproblem(
2553 warning=_("warning: '%s' uses revlog format %d; expected %d") %
2550 warning=_("warning: '%s' uses revlog format %d; expected %d") %
2554 (self.indexfile, version, state['expectedversion']))
2551 (self.indexfile, version, state['expectedversion']))
2555
2552
2556 state['skipread'] = set()
2553 state['skipread'] = set()
2557
2554
2558 for rev in self:
2555 for rev in self:
2559 node = self.node(rev)
2556 node = self.node(rev)
2560
2557
2561 # Verify contents. 4 cases to care about:
2558 # Verify contents. 4 cases to care about:
2562 #
2559 #
2563 # common: the most common case
2560 # common: the most common case
2564 # rename: with a rename
2561 # rename: with a rename
2565 # meta: file content starts with b'\1\n', the metadata
2562 # meta: file content starts with b'\1\n', the metadata
2566 # header defined in filelog.py, but without a rename
2563 # header defined in filelog.py, but without a rename
2567 # ext: content stored externally
2564 # ext: content stored externally
2568 #
2565 #
2569 # More formally, their differences are shown below:
2566 # More formally, their differences are shown below:
2570 #
2567 #
2571 # | common | rename | meta | ext
2568 # | common | rename | meta | ext
2572 # -------------------------------------------------------
2569 # -------------------------------------------------------
2573 # flags() | 0 | 0 | 0 | not 0
2570 # flags() | 0 | 0 | 0 | not 0
2574 # renamed() | False | True | False | ?
2571 # renamed() | False | True | False | ?
2575 # rawtext[0:2]=='\1\n'| False | True | True | ?
2572 # rawtext[0:2]=='\1\n'| False | True | True | ?
2576 #
2573 #
2577 # "rawtext" means the raw text stored in revlog data, which
2574 # "rawtext" means the raw text stored in revlog data, which
2578 # could be retrieved by "rawdata(rev)". "text"
2575 # could be retrieved by "rawdata(rev)". "text"
2579 # mentioned below is "revision(rev)".
2576 # mentioned below is "revision(rev)".
2580 #
2577 #
2581 # There are 3 different lengths stored physically:
2578 # There are 3 different lengths stored physically:
2582 # 1. L1: rawsize, stored in revlog index
2579 # 1. L1: rawsize, stored in revlog index
2583 # 2. L2: len(rawtext), stored in revlog data
2580 # 2. L2: len(rawtext), stored in revlog data
2584 # 3. L3: len(text), stored in revlog data if flags==0, or
2581 # 3. L3: len(text), stored in revlog data if flags==0, or
2585 # possibly somewhere else if flags!=0
2582 # possibly somewhere else if flags!=0
2586 #
2583 #
2587 # L1 should be equal to L2. L3 could be different from them.
2584 # L1 should be equal to L2. L3 could be different from them.
2588 # "text" may or may not affect commit hash depending on flag
2585 # "text" may or may not affect commit hash depending on flag
2589 # processors (see flagutil.addflagprocessor).
2586 # processors (see flagutil.addflagprocessor).
2590 #
2587 #
2591 # | common | rename | meta | ext
2588 # | common | rename | meta | ext
2592 # -------------------------------------------------
2589 # -------------------------------------------------
2593 # rawsize() | L1 | L1 | L1 | L1
2590 # rawsize() | L1 | L1 | L1 | L1
2594 # size() | L1 | L2-LM | L1(*) | L1 (?)
2591 # size() | L1 | L2-LM | L1(*) | L1 (?)
2595 # len(rawtext) | L2 | L2 | L2 | L2
2592 # len(rawtext) | L2 | L2 | L2 | L2
2596 # len(text) | L2 | L2 | L2 | L3
2593 # len(text) | L2 | L2 | L2 | L3
2597 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
2594 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
2598 #
2595 #
2599 # LM: length of metadata, depending on rawtext
2596 # LM: length of metadata, depending on rawtext
2600 # (*): not ideal, see comment in filelog.size
2597 # (*): not ideal, see comment in filelog.size
2601 # (?): could be "- len(meta)" if the resolved content has
2598 # (?): could be "- len(meta)" if the resolved content has
2602 # rename metadata
2599 # rename metadata
2603 #
2600 #
2604 # Checks needed to be done:
2601 # Checks needed to be done:
2605 # 1. length check: L1 == L2, in all cases.
2602 # 1. length check: L1 == L2, in all cases.
2606 # 2. hash check: depending on flag processor, we may need to
2603 # 2. hash check: depending on flag processor, we may need to
2607 # use either "text" (external), or "rawtext" (in revlog).
2604 # use either "text" (external), or "rawtext" (in revlog).
2608
2605
2609 try:
2606 try:
2610 skipflags = state.get('skipflags', 0)
2607 skipflags = state.get('skipflags', 0)
2611 if skipflags:
2608 if skipflags:
2612 skipflags &= self.flags(rev)
2609 skipflags &= self.flags(rev)
2613
2610
2614 if skipflags:
2611 if skipflags:
2615 state['skipread'].add(node)
2612 state['skipread'].add(node)
2616 else:
2613 else:
2617 # Side-effect: read content and verify hash.
2614 # Side-effect: read content and verify hash.
2618 self.revision(node)
2615 self.revision(node)
2619
2616
2620 l1 = self.rawsize(rev)
2617 l1 = self.rawsize(rev)
2621 l2 = len(self.rawdata(node))
2618 l2 = len(self.rawdata(node))
2622
2619
2623 if l1 != l2:
2620 if l1 != l2:
2624 yield revlogproblem(
2621 yield revlogproblem(
2625 error=_('unpacked size is %d, %d expected') % (l2, l1),
2622 error=_('unpacked size is %d, %d expected') % (l2, l1),
2626 node=node)
2623 node=node)
2627
2624
2628 except error.CensoredNodeError:
2625 except error.CensoredNodeError:
2629 if state['erroroncensored']:
2626 if state['erroroncensored']:
2630 yield revlogproblem(error=_('censored file data'),
2627 yield revlogproblem(error=_('censored file data'),
2631 node=node)
2628 node=node)
2632 state['skipread'].add(node)
2629 state['skipread'].add(node)
2633 except Exception as e:
2630 except Exception as e:
2634 yield revlogproblem(
2631 yield revlogproblem(
2635 error=_('unpacking %s: %s') % (short(node),
2632 error=_('unpacking %s: %s') % (short(node),
2636 stringutil.forcebytestr(e)),
2633 stringutil.forcebytestr(e)),
2637 node=node)
2634 node=node)
2638 state['skipread'].add(node)
2635 state['skipread'].add(node)
2639
2636
2640 def storageinfo(self, exclusivefiles=False, sharedfiles=False,
2637 def storageinfo(self, exclusivefiles=False, sharedfiles=False,
2641 revisionscount=False, trackedsize=False,
2638 revisionscount=False, trackedsize=False,
2642 storedsize=False):
2639 storedsize=False):
2643 d = {}
2640 d = {}
2644
2641
2645 if exclusivefiles:
2642 if exclusivefiles:
2646 d['exclusivefiles'] = [(self.opener, self.indexfile)]
2643 d['exclusivefiles'] = [(self.opener, self.indexfile)]
2647 if not self._inline:
2644 if not self._inline:
2648 d['exclusivefiles'].append((self.opener, self.datafile))
2645 d['exclusivefiles'].append((self.opener, self.datafile))
2649
2646
2650 if sharedfiles:
2647 if sharedfiles:
2651 d['sharedfiles'] = []
2648 d['sharedfiles'] = []
2652
2649
2653 if revisionscount:
2650 if revisionscount:
2654 d['revisionscount'] = len(self)
2651 d['revisionscount'] = len(self)
2655
2652
2656 if trackedsize:
2653 if trackedsize:
2657 d['trackedsize'] = sum(map(self.rawsize, iter(self)))
2654 d['trackedsize'] = sum(map(self.rawsize, iter(self)))
2658
2655
2659 if storedsize:
2656 if storedsize:
2660 d['storedsize'] = sum(self.opener.stat(path).st_size
2657 d['storedsize'] = sum(self.opener.stat(path).st_size
2661 for path in self.files())
2658 for path in self.files())
2662
2659
2663 return d
2660 return d
General Comments 0
You need to be logged in to leave comments. Login now