##// END OF EJS Templates
revlog: allow flag processors to be applied via store options...
Matt Harbison -
r40303:9d5ddf55 default
parent child Browse files
Show More
@@ -1,3031 +1,3032 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 changelog,
31 changelog,
32 color,
32 color,
33 context,
33 context,
34 dirstate,
34 dirstate,
35 dirstateguard,
35 dirstateguard,
36 discovery,
36 discovery,
37 encoding,
37 encoding,
38 error,
38 error,
39 exchange,
39 exchange,
40 extensions,
40 extensions,
41 filelog,
41 filelog,
42 hook,
42 hook,
43 lock as lockmod,
43 lock as lockmod,
44 manifest,
44 manifest,
45 match as matchmod,
45 match as matchmod,
46 merge as mergemod,
46 merge as mergemod,
47 mergeutil,
47 mergeutil,
48 namespaces,
48 namespaces,
49 narrowspec,
49 narrowspec,
50 obsolete,
50 obsolete,
51 pathutil,
51 pathutil,
52 phases,
52 phases,
53 pushkey,
53 pushkey,
54 pycompat,
54 pycompat,
55 repository,
55 repository,
56 repoview,
56 repoview,
57 revset,
57 revset,
58 revsetlang,
58 revsetlang,
59 scmutil,
59 scmutil,
60 sparse,
60 sparse,
61 store as storemod,
61 store as storemod,
62 subrepoutil,
62 subrepoutil,
63 tags as tagsmod,
63 tags as tagsmod,
64 transaction,
64 transaction,
65 txnutil,
65 txnutil,
66 util,
66 util,
67 vfs as vfsmod,
67 vfs as vfsmod,
68 )
68 )
69 from .utils import (
69 from .utils import (
70 interfaceutil,
70 interfaceutil,
71 procutil,
71 procutil,
72 stringutil,
72 stringutil,
73 )
73 )
74
74
75 from .revlogutils import (
75 from .revlogutils import (
76 constants as revlogconst,
76 constants as revlogconst,
77 )
77 )
78
78
79 release = lockmod.release
79 release = lockmod.release
80 urlerr = util.urlerr
80 urlerr = util.urlerr
81 urlreq = util.urlreq
81 urlreq = util.urlreq
82
82
83 # set of (path, vfs-location) tuples. vfs-location is:
83 # set of (path, vfs-location) tuples. vfs-location is:
84 # - 'plain for vfs relative paths
84 # - 'plain for vfs relative paths
85 # - '' for svfs relative paths
85 # - '' for svfs relative paths
86 _cachedfiles = set()
86 _cachedfiles = set()
87
87
88 class _basefilecache(scmutil.filecache):
88 class _basefilecache(scmutil.filecache):
89 """All filecache usage on repo are done for logic that should be unfiltered
89 """All filecache usage on repo are done for logic that should be unfiltered
90 """
90 """
91 def __get__(self, repo, type=None):
91 def __get__(self, repo, type=None):
92 if repo is None:
92 if repo is None:
93 return self
93 return self
94 return super(_basefilecache, self).__get__(repo.unfiltered(), type)
94 return super(_basefilecache, self).__get__(repo.unfiltered(), type)
95 def __set__(self, repo, value):
95 def __set__(self, repo, value):
96 return super(_basefilecache, self).__set__(repo.unfiltered(), value)
96 return super(_basefilecache, self).__set__(repo.unfiltered(), value)
97 def __delete__(self, repo):
97 def __delete__(self, repo):
98 return super(_basefilecache, self).__delete__(repo.unfiltered())
98 return super(_basefilecache, self).__delete__(repo.unfiltered())
99
99
100 class repofilecache(_basefilecache):
100 class repofilecache(_basefilecache):
101 """filecache for files in .hg but outside of .hg/store"""
101 """filecache for files in .hg but outside of .hg/store"""
102 def __init__(self, *paths):
102 def __init__(self, *paths):
103 super(repofilecache, self).__init__(*paths)
103 super(repofilecache, self).__init__(*paths)
104 for path in paths:
104 for path in paths:
105 _cachedfiles.add((path, 'plain'))
105 _cachedfiles.add((path, 'plain'))
106
106
107 def join(self, obj, fname):
107 def join(self, obj, fname):
108 return obj.vfs.join(fname)
108 return obj.vfs.join(fname)
109
109
110 class storecache(_basefilecache):
110 class storecache(_basefilecache):
111 """filecache for files in the store"""
111 """filecache for files in the store"""
112 def __init__(self, *paths):
112 def __init__(self, *paths):
113 super(storecache, self).__init__(*paths)
113 super(storecache, self).__init__(*paths)
114 for path in paths:
114 for path in paths:
115 _cachedfiles.add((path, ''))
115 _cachedfiles.add((path, ''))
116
116
117 def join(self, obj, fname):
117 def join(self, obj, fname):
118 return obj.sjoin(fname)
118 return obj.sjoin(fname)
119
119
120 def isfilecached(repo, name):
120 def isfilecached(repo, name):
121 """check if a repo has already cached "name" filecache-ed property
121 """check if a repo has already cached "name" filecache-ed property
122
122
123 This returns (cachedobj-or-None, iscached) tuple.
123 This returns (cachedobj-or-None, iscached) tuple.
124 """
124 """
125 cacheentry = repo.unfiltered()._filecache.get(name, None)
125 cacheentry = repo.unfiltered()._filecache.get(name, None)
126 if not cacheentry:
126 if not cacheentry:
127 return None, False
127 return None, False
128 return cacheentry.obj, True
128 return cacheentry.obj, True
129
129
130 class unfilteredpropertycache(util.propertycache):
130 class unfilteredpropertycache(util.propertycache):
131 """propertycache that apply to unfiltered repo only"""
131 """propertycache that apply to unfiltered repo only"""
132
132
133 def __get__(self, repo, type=None):
133 def __get__(self, repo, type=None):
134 unfi = repo.unfiltered()
134 unfi = repo.unfiltered()
135 if unfi is repo:
135 if unfi is repo:
136 return super(unfilteredpropertycache, self).__get__(unfi)
136 return super(unfilteredpropertycache, self).__get__(unfi)
137 return getattr(unfi, self.name)
137 return getattr(unfi, self.name)
138
138
139 class filteredpropertycache(util.propertycache):
139 class filteredpropertycache(util.propertycache):
140 """propertycache that must take filtering in account"""
140 """propertycache that must take filtering in account"""
141
141
142 def cachevalue(self, obj, value):
142 def cachevalue(self, obj, value):
143 object.__setattr__(obj, self.name, value)
143 object.__setattr__(obj, self.name, value)
144
144
145
145
146 def hasunfilteredcache(repo, name):
146 def hasunfilteredcache(repo, name):
147 """check if a repo has an unfilteredpropertycache value for <name>"""
147 """check if a repo has an unfilteredpropertycache value for <name>"""
148 return name in vars(repo.unfiltered())
148 return name in vars(repo.unfiltered())
149
149
150 def unfilteredmethod(orig):
150 def unfilteredmethod(orig):
151 """decorate method that always need to be run on unfiltered version"""
151 """decorate method that always need to be run on unfiltered version"""
152 def wrapper(repo, *args, **kwargs):
152 def wrapper(repo, *args, **kwargs):
153 return orig(repo.unfiltered(), *args, **kwargs)
153 return orig(repo.unfiltered(), *args, **kwargs)
154 return wrapper
154 return wrapper
155
155
156 moderncaps = {'lookup', 'branchmap', 'pushkey', 'known', 'getbundle',
156 moderncaps = {'lookup', 'branchmap', 'pushkey', 'known', 'getbundle',
157 'unbundle'}
157 'unbundle'}
158 legacycaps = moderncaps.union({'changegroupsubset'})
158 legacycaps = moderncaps.union({'changegroupsubset'})
159
159
160 @interfaceutil.implementer(repository.ipeercommandexecutor)
160 @interfaceutil.implementer(repository.ipeercommandexecutor)
161 class localcommandexecutor(object):
161 class localcommandexecutor(object):
162 def __init__(self, peer):
162 def __init__(self, peer):
163 self._peer = peer
163 self._peer = peer
164 self._sent = False
164 self._sent = False
165 self._closed = False
165 self._closed = False
166
166
167 def __enter__(self):
167 def __enter__(self):
168 return self
168 return self
169
169
170 def __exit__(self, exctype, excvalue, exctb):
170 def __exit__(self, exctype, excvalue, exctb):
171 self.close()
171 self.close()
172
172
173 def callcommand(self, command, args):
173 def callcommand(self, command, args):
174 if self._sent:
174 if self._sent:
175 raise error.ProgrammingError('callcommand() cannot be used after '
175 raise error.ProgrammingError('callcommand() cannot be used after '
176 'sendcommands()')
176 'sendcommands()')
177
177
178 if self._closed:
178 if self._closed:
179 raise error.ProgrammingError('callcommand() cannot be used after '
179 raise error.ProgrammingError('callcommand() cannot be used after '
180 'close()')
180 'close()')
181
181
182 # We don't need to support anything fancy. Just call the named
182 # We don't need to support anything fancy. Just call the named
183 # method on the peer and return a resolved future.
183 # method on the peer and return a resolved future.
184 fn = getattr(self._peer, pycompat.sysstr(command))
184 fn = getattr(self._peer, pycompat.sysstr(command))
185
185
186 f = pycompat.futures.Future()
186 f = pycompat.futures.Future()
187
187
188 try:
188 try:
189 result = fn(**pycompat.strkwargs(args))
189 result = fn(**pycompat.strkwargs(args))
190 except Exception:
190 except Exception:
191 pycompat.future_set_exception_info(f, sys.exc_info()[1:])
191 pycompat.future_set_exception_info(f, sys.exc_info()[1:])
192 else:
192 else:
193 f.set_result(result)
193 f.set_result(result)
194
194
195 return f
195 return f
196
196
197 def sendcommands(self):
197 def sendcommands(self):
198 self._sent = True
198 self._sent = True
199
199
200 def close(self):
200 def close(self):
201 self._closed = True
201 self._closed = True
202
202
203 @interfaceutil.implementer(repository.ipeercommands)
203 @interfaceutil.implementer(repository.ipeercommands)
204 class localpeer(repository.peer):
204 class localpeer(repository.peer):
205 '''peer for a local repo; reflects only the most recent API'''
205 '''peer for a local repo; reflects only the most recent API'''
206
206
207 def __init__(self, repo, caps=None):
207 def __init__(self, repo, caps=None):
208 super(localpeer, self).__init__()
208 super(localpeer, self).__init__()
209
209
210 if caps is None:
210 if caps is None:
211 caps = moderncaps.copy()
211 caps = moderncaps.copy()
212 self._repo = repo.filtered('served')
212 self._repo = repo.filtered('served')
213 self.ui = repo.ui
213 self.ui = repo.ui
214 self._caps = repo._restrictcapabilities(caps)
214 self._caps = repo._restrictcapabilities(caps)
215
215
216 # Begin of _basepeer interface.
216 # Begin of _basepeer interface.
217
217
218 def url(self):
218 def url(self):
219 return self._repo.url()
219 return self._repo.url()
220
220
221 def local(self):
221 def local(self):
222 return self._repo
222 return self._repo
223
223
224 def peer(self):
224 def peer(self):
225 return self
225 return self
226
226
227 def canpush(self):
227 def canpush(self):
228 return True
228 return True
229
229
230 def close(self):
230 def close(self):
231 self._repo.close()
231 self._repo.close()
232
232
233 # End of _basepeer interface.
233 # End of _basepeer interface.
234
234
235 # Begin of _basewirecommands interface.
235 # Begin of _basewirecommands interface.
236
236
237 def branchmap(self):
237 def branchmap(self):
238 return self._repo.branchmap()
238 return self._repo.branchmap()
239
239
240 def capabilities(self):
240 def capabilities(self):
241 return self._caps
241 return self._caps
242
242
243 def clonebundles(self):
243 def clonebundles(self):
244 return self._repo.tryread('clonebundles.manifest')
244 return self._repo.tryread('clonebundles.manifest')
245
245
246 def debugwireargs(self, one, two, three=None, four=None, five=None):
246 def debugwireargs(self, one, two, three=None, four=None, five=None):
247 """Used to test argument passing over the wire"""
247 """Used to test argument passing over the wire"""
248 return "%s %s %s %s %s" % (one, two, pycompat.bytestr(three),
248 return "%s %s %s %s %s" % (one, two, pycompat.bytestr(three),
249 pycompat.bytestr(four),
249 pycompat.bytestr(four),
250 pycompat.bytestr(five))
250 pycompat.bytestr(five))
251
251
252 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
252 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
253 **kwargs):
253 **kwargs):
254 chunks = exchange.getbundlechunks(self._repo, source, heads=heads,
254 chunks = exchange.getbundlechunks(self._repo, source, heads=heads,
255 common=common, bundlecaps=bundlecaps,
255 common=common, bundlecaps=bundlecaps,
256 **kwargs)[1]
256 **kwargs)[1]
257 cb = util.chunkbuffer(chunks)
257 cb = util.chunkbuffer(chunks)
258
258
259 if exchange.bundle2requested(bundlecaps):
259 if exchange.bundle2requested(bundlecaps):
260 # When requesting a bundle2, getbundle returns a stream to make the
260 # When requesting a bundle2, getbundle returns a stream to make the
261 # wire level function happier. We need to build a proper object
261 # wire level function happier. We need to build a proper object
262 # from it in local peer.
262 # from it in local peer.
263 return bundle2.getunbundler(self.ui, cb)
263 return bundle2.getunbundler(self.ui, cb)
264 else:
264 else:
265 return changegroup.getunbundler('01', cb, None)
265 return changegroup.getunbundler('01', cb, None)
266
266
267 def heads(self):
267 def heads(self):
268 return self._repo.heads()
268 return self._repo.heads()
269
269
270 def known(self, nodes):
270 def known(self, nodes):
271 return self._repo.known(nodes)
271 return self._repo.known(nodes)
272
272
273 def listkeys(self, namespace):
273 def listkeys(self, namespace):
274 return self._repo.listkeys(namespace)
274 return self._repo.listkeys(namespace)
275
275
276 def lookup(self, key):
276 def lookup(self, key):
277 return self._repo.lookup(key)
277 return self._repo.lookup(key)
278
278
279 def pushkey(self, namespace, key, old, new):
279 def pushkey(self, namespace, key, old, new):
280 return self._repo.pushkey(namespace, key, old, new)
280 return self._repo.pushkey(namespace, key, old, new)
281
281
282 def stream_out(self):
282 def stream_out(self):
283 raise error.Abort(_('cannot perform stream clone against local '
283 raise error.Abort(_('cannot perform stream clone against local '
284 'peer'))
284 'peer'))
285
285
286 def unbundle(self, bundle, heads, url):
286 def unbundle(self, bundle, heads, url):
287 """apply a bundle on a repo
287 """apply a bundle on a repo
288
288
289 This function handles the repo locking itself."""
289 This function handles the repo locking itself."""
290 try:
290 try:
291 try:
291 try:
292 bundle = exchange.readbundle(self.ui, bundle, None)
292 bundle = exchange.readbundle(self.ui, bundle, None)
293 ret = exchange.unbundle(self._repo, bundle, heads, 'push', url)
293 ret = exchange.unbundle(self._repo, bundle, heads, 'push', url)
294 if util.safehasattr(ret, 'getchunks'):
294 if util.safehasattr(ret, 'getchunks'):
295 # This is a bundle20 object, turn it into an unbundler.
295 # This is a bundle20 object, turn it into an unbundler.
296 # This little dance should be dropped eventually when the
296 # This little dance should be dropped eventually when the
297 # API is finally improved.
297 # API is finally improved.
298 stream = util.chunkbuffer(ret.getchunks())
298 stream = util.chunkbuffer(ret.getchunks())
299 ret = bundle2.getunbundler(self.ui, stream)
299 ret = bundle2.getunbundler(self.ui, stream)
300 return ret
300 return ret
301 except Exception as exc:
301 except Exception as exc:
302 # If the exception contains output salvaged from a bundle2
302 # If the exception contains output salvaged from a bundle2
303 # reply, we need to make sure it is printed before continuing
303 # reply, we need to make sure it is printed before continuing
304 # to fail. So we build a bundle2 with such output and consume
304 # to fail. So we build a bundle2 with such output and consume
305 # it directly.
305 # it directly.
306 #
306 #
307 # This is not very elegant but allows a "simple" solution for
307 # This is not very elegant but allows a "simple" solution for
308 # issue4594
308 # issue4594
309 output = getattr(exc, '_bundle2salvagedoutput', ())
309 output = getattr(exc, '_bundle2salvagedoutput', ())
310 if output:
310 if output:
311 bundler = bundle2.bundle20(self._repo.ui)
311 bundler = bundle2.bundle20(self._repo.ui)
312 for out in output:
312 for out in output:
313 bundler.addpart(out)
313 bundler.addpart(out)
314 stream = util.chunkbuffer(bundler.getchunks())
314 stream = util.chunkbuffer(bundler.getchunks())
315 b = bundle2.getunbundler(self.ui, stream)
315 b = bundle2.getunbundler(self.ui, stream)
316 bundle2.processbundle(self._repo, b)
316 bundle2.processbundle(self._repo, b)
317 raise
317 raise
318 except error.PushRaced as exc:
318 except error.PushRaced as exc:
319 raise error.ResponseError(_('push failed:'),
319 raise error.ResponseError(_('push failed:'),
320 stringutil.forcebytestr(exc))
320 stringutil.forcebytestr(exc))
321
321
322 # End of _basewirecommands interface.
322 # End of _basewirecommands interface.
323
323
324 # Begin of peer interface.
324 # Begin of peer interface.
325
325
326 def commandexecutor(self):
326 def commandexecutor(self):
327 return localcommandexecutor(self)
327 return localcommandexecutor(self)
328
328
329 # End of peer interface.
329 # End of peer interface.
330
330
331 @interfaceutil.implementer(repository.ipeerlegacycommands)
331 @interfaceutil.implementer(repository.ipeerlegacycommands)
332 class locallegacypeer(localpeer):
332 class locallegacypeer(localpeer):
333 '''peer extension which implements legacy methods too; used for tests with
333 '''peer extension which implements legacy methods too; used for tests with
334 restricted capabilities'''
334 restricted capabilities'''
335
335
336 def __init__(self, repo):
336 def __init__(self, repo):
337 super(locallegacypeer, self).__init__(repo, caps=legacycaps)
337 super(locallegacypeer, self).__init__(repo, caps=legacycaps)
338
338
339 # Begin of baselegacywirecommands interface.
339 # Begin of baselegacywirecommands interface.
340
340
341 def between(self, pairs):
341 def between(self, pairs):
342 return self._repo.between(pairs)
342 return self._repo.between(pairs)
343
343
344 def branches(self, nodes):
344 def branches(self, nodes):
345 return self._repo.branches(nodes)
345 return self._repo.branches(nodes)
346
346
347 def changegroup(self, nodes, source):
347 def changegroup(self, nodes, source):
348 outgoing = discovery.outgoing(self._repo, missingroots=nodes,
348 outgoing = discovery.outgoing(self._repo, missingroots=nodes,
349 missingheads=self._repo.heads())
349 missingheads=self._repo.heads())
350 return changegroup.makechangegroup(self._repo, outgoing, '01', source)
350 return changegroup.makechangegroup(self._repo, outgoing, '01', source)
351
351
352 def changegroupsubset(self, bases, heads, source):
352 def changegroupsubset(self, bases, heads, source):
353 outgoing = discovery.outgoing(self._repo, missingroots=bases,
353 outgoing = discovery.outgoing(self._repo, missingroots=bases,
354 missingheads=heads)
354 missingheads=heads)
355 return changegroup.makechangegroup(self._repo, outgoing, '01', source)
355 return changegroup.makechangegroup(self._repo, outgoing, '01', source)
356
356
357 # End of baselegacywirecommands interface.
357 # End of baselegacywirecommands interface.
358
358
359 # Increment the sub-version when the revlog v2 format changes to lock out old
359 # Increment the sub-version when the revlog v2 format changes to lock out old
360 # clients.
360 # clients.
361 REVLOGV2_REQUIREMENT = 'exp-revlogv2.0'
361 REVLOGV2_REQUIREMENT = 'exp-revlogv2.0'
362
362
363 # A repository with the sparserevlog feature will have delta chains that
363 # A repository with the sparserevlog feature will have delta chains that
364 # can spread over a larger span. Sparse reading cuts these large spans into
364 # can spread over a larger span. Sparse reading cuts these large spans into
365 # pieces, so that each piece isn't too big.
365 # pieces, so that each piece isn't too big.
366 # Without the sparserevlog capability, reading from the repository could use
366 # Without the sparserevlog capability, reading from the repository could use
367 # huge amounts of memory, because the whole span would be read at once,
367 # huge amounts of memory, because the whole span would be read at once,
368 # including all the intermediate revisions that aren't pertinent for the chain.
368 # including all the intermediate revisions that aren't pertinent for the chain.
369 # This is why once a repository has enabled sparse-read, it becomes required.
369 # This is why once a repository has enabled sparse-read, it becomes required.
370 SPARSEREVLOG_REQUIREMENT = 'sparserevlog'
370 SPARSEREVLOG_REQUIREMENT = 'sparserevlog'
371
371
372 # Functions receiving (ui, features) that extensions can register to impact
372 # Functions receiving (ui, features) that extensions can register to impact
373 # the ability to load repositories with custom requirements. Only
373 # the ability to load repositories with custom requirements. Only
374 # functions defined in loaded extensions are called.
374 # functions defined in loaded extensions are called.
375 #
375 #
376 # The function receives a set of requirement strings that the repository
376 # The function receives a set of requirement strings that the repository
377 # is capable of opening. Functions will typically add elements to the
377 # is capable of opening. Functions will typically add elements to the
378 # set to reflect that the extension knows how to handle that requirements.
378 # set to reflect that the extension knows how to handle that requirements.
379 featuresetupfuncs = set()
379 featuresetupfuncs = set()
380
380
381 def makelocalrepository(baseui, path, intents=None):
381 def makelocalrepository(baseui, path, intents=None):
382 """Create a local repository object.
382 """Create a local repository object.
383
383
384 Given arguments needed to construct a local repository, this function
384 Given arguments needed to construct a local repository, this function
385 performs various early repository loading functionality (such as
385 performs various early repository loading functionality (such as
386 reading the ``.hg/requires`` and ``.hg/hgrc`` files), validates that
386 reading the ``.hg/requires`` and ``.hg/hgrc`` files), validates that
387 the repository can be opened, derives a type suitable for representing
387 the repository can be opened, derives a type suitable for representing
388 that repository, and returns an instance of it.
388 that repository, and returns an instance of it.
389
389
390 The returned object conforms to the ``repository.completelocalrepository``
390 The returned object conforms to the ``repository.completelocalrepository``
391 interface.
391 interface.
392
392
393 The repository type is derived by calling a series of factory functions
393 The repository type is derived by calling a series of factory functions
394 for each aspect/interface of the final repository. These are defined by
394 for each aspect/interface of the final repository. These are defined by
395 ``REPO_INTERFACES``.
395 ``REPO_INTERFACES``.
396
396
397 Each factory function is called to produce a type implementing a specific
397 Each factory function is called to produce a type implementing a specific
398 interface. The cumulative list of returned types will be combined into a
398 interface. The cumulative list of returned types will be combined into a
399 new type and that type will be instantiated to represent the local
399 new type and that type will be instantiated to represent the local
400 repository.
400 repository.
401
401
402 The factory functions each receive various state that may be consulted
402 The factory functions each receive various state that may be consulted
403 as part of deriving a type.
403 as part of deriving a type.
404
404
405 Extensions should wrap these factory functions to customize repository type
405 Extensions should wrap these factory functions to customize repository type
406 creation. Note that an extension's wrapped function may be called even if
406 creation. Note that an extension's wrapped function may be called even if
407 that extension is not loaded for the repo being constructed. Extensions
407 that extension is not loaded for the repo being constructed. Extensions
408 should check if their ``__name__`` appears in the
408 should check if their ``__name__`` appears in the
409 ``extensionmodulenames`` set passed to the factory function and no-op if
409 ``extensionmodulenames`` set passed to the factory function and no-op if
410 not.
410 not.
411 """
411 """
412 ui = baseui.copy()
412 ui = baseui.copy()
413 # Prevent copying repo configuration.
413 # Prevent copying repo configuration.
414 ui.copy = baseui.copy
414 ui.copy = baseui.copy
415
415
416 # Working directory VFS rooted at repository root.
416 # Working directory VFS rooted at repository root.
417 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
417 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
418
418
419 # Main VFS for .hg/ directory.
419 # Main VFS for .hg/ directory.
420 hgpath = wdirvfs.join(b'.hg')
420 hgpath = wdirvfs.join(b'.hg')
421 hgvfs = vfsmod.vfs(hgpath, cacheaudited=True)
421 hgvfs = vfsmod.vfs(hgpath, cacheaudited=True)
422
422
423 # The .hg/ path should exist and should be a directory. All other
423 # The .hg/ path should exist and should be a directory. All other
424 # cases are errors.
424 # cases are errors.
425 if not hgvfs.isdir():
425 if not hgvfs.isdir():
426 try:
426 try:
427 hgvfs.stat()
427 hgvfs.stat()
428 except OSError as e:
428 except OSError as e:
429 if e.errno != errno.ENOENT:
429 if e.errno != errno.ENOENT:
430 raise
430 raise
431
431
432 raise error.RepoError(_(b'repository %s not found') % path)
432 raise error.RepoError(_(b'repository %s not found') % path)
433
433
434 # .hg/requires file contains a newline-delimited list of
434 # .hg/requires file contains a newline-delimited list of
435 # features/capabilities the opener (us) must have in order to use
435 # features/capabilities the opener (us) must have in order to use
436 # the repository. This file was introduced in Mercurial 0.9.2,
436 # the repository. This file was introduced in Mercurial 0.9.2,
437 # which means very old repositories may not have one. We assume
437 # which means very old repositories may not have one. We assume
438 # a missing file translates to no requirements.
438 # a missing file translates to no requirements.
439 try:
439 try:
440 requirements = set(hgvfs.read(b'requires').splitlines())
440 requirements = set(hgvfs.read(b'requires').splitlines())
441 except IOError as e:
441 except IOError as e:
442 if e.errno != errno.ENOENT:
442 if e.errno != errno.ENOENT:
443 raise
443 raise
444 requirements = set()
444 requirements = set()
445
445
446 # The .hg/hgrc file may load extensions or contain config options
446 # The .hg/hgrc file may load extensions or contain config options
447 # that influence repository construction. Attempt to load it and
447 # that influence repository construction. Attempt to load it and
448 # process any new extensions that it may have pulled in.
448 # process any new extensions that it may have pulled in.
449 try:
449 try:
450 ui.readconfig(hgvfs.join(b'hgrc'), root=wdirvfs.base)
450 ui.readconfig(hgvfs.join(b'hgrc'), root=wdirvfs.base)
451 # Run this before extensions.loadall() so extensions can be
451 # Run this before extensions.loadall() so extensions can be
452 # automatically enabled.
452 # automatically enabled.
453 afterhgrcload(ui, wdirvfs, hgvfs, requirements)
453 afterhgrcload(ui, wdirvfs, hgvfs, requirements)
454 except IOError:
454 except IOError:
455 pass
455 pass
456 else:
456 else:
457 extensions.loadall(ui)
457 extensions.loadall(ui)
458
458
459 # Set of module names of extensions loaded for this repository.
459 # Set of module names of extensions loaded for this repository.
460 extensionmodulenames = {m.__name__ for n, m in extensions.extensions(ui)}
460 extensionmodulenames = {m.__name__ for n, m in extensions.extensions(ui)}
461
461
462 supportedrequirements = gathersupportedrequirements(ui)
462 supportedrequirements = gathersupportedrequirements(ui)
463
463
464 # We first validate the requirements are known.
464 # We first validate the requirements are known.
465 ensurerequirementsrecognized(requirements, supportedrequirements)
465 ensurerequirementsrecognized(requirements, supportedrequirements)
466
466
467 # Then we validate that the known set is reasonable to use together.
467 # Then we validate that the known set is reasonable to use together.
468 ensurerequirementscompatible(ui, requirements)
468 ensurerequirementscompatible(ui, requirements)
469
469
470 # TODO there are unhandled edge cases related to opening repositories with
470 # TODO there are unhandled edge cases related to opening repositories with
471 # shared storage. If storage is shared, we should also test for requirements
471 # shared storage. If storage is shared, we should also test for requirements
472 # compatibility in the pointed-to repo. This entails loading the .hg/hgrc in
472 # compatibility in the pointed-to repo. This entails loading the .hg/hgrc in
473 # that repo, as that repo may load extensions needed to open it. This is a
473 # that repo, as that repo may load extensions needed to open it. This is a
474 # bit complicated because we don't want the other hgrc to overwrite settings
474 # bit complicated because we don't want the other hgrc to overwrite settings
475 # in this hgrc.
475 # in this hgrc.
476 #
476 #
477 # This bug is somewhat mitigated by the fact that we copy the .hg/requires
477 # This bug is somewhat mitigated by the fact that we copy the .hg/requires
478 # file when sharing repos. But if a requirement is added after the share is
478 # file when sharing repos. But if a requirement is added after the share is
479 # performed, thereby introducing a new requirement for the opener, we may
479 # performed, thereby introducing a new requirement for the opener, we may
480 # will not see that and could encounter a run-time error interacting with
480 # will not see that and could encounter a run-time error interacting with
481 # that shared store since it has an unknown-to-us requirement.
481 # that shared store since it has an unknown-to-us requirement.
482
482
483 # At this point, we know we should be capable of opening the repository.
483 # At this point, we know we should be capable of opening the repository.
484 # Now get on with doing that.
484 # Now get on with doing that.
485
485
486 features = set()
486 features = set()
487
487
488 # The "store" part of the repository holds versioned data. How it is
488 # The "store" part of the repository holds versioned data. How it is
489 # accessed is determined by various requirements. The ``shared`` or
489 # accessed is determined by various requirements. The ``shared`` or
490 # ``relshared`` requirements indicate the store lives in the path contained
490 # ``relshared`` requirements indicate the store lives in the path contained
491 # in the ``.hg/sharedpath`` file. This is an absolute path for
491 # in the ``.hg/sharedpath`` file. This is an absolute path for
492 # ``shared`` and relative to ``.hg/`` for ``relshared``.
492 # ``shared`` and relative to ``.hg/`` for ``relshared``.
493 if b'shared' in requirements or b'relshared' in requirements:
493 if b'shared' in requirements or b'relshared' in requirements:
494 sharedpath = hgvfs.read(b'sharedpath').rstrip(b'\n')
494 sharedpath = hgvfs.read(b'sharedpath').rstrip(b'\n')
495 if b'relshared' in requirements:
495 if b'relshared' in requirements:
496 sharedpath = hgvfs.join(sharedpath)
496 sharedpath = hgvfs.join(sharedpath)
497
497
498 sharedvfs = vfsmod.vfs(sharedpath, realpath=True)
498 sharedvfs = vfsmod.vfs(sharedpath, realpath=True)
499
499
500 if not sharedvfs.exists():
500 if not sharedvfs.exists():
501 raise error.RepoError(_(b'.hg/sharedpath points to nonexistent '
501 raise error.RepoError(_(b'.hg/sharedpath points to nonexistent '
502 b'directory %s') % sharedvfs.base)
502 b'directory %s') % sharedvfs.base)
503
503
504 features.add(repository.REPO_FEATURE_SHARED_STORAGE)
504 features.add(repository.REPO_FEATURE_SHARED_STORAGE)
505
505
506 storebasepath = sharedvfs.base
506 storebasepath = sharedvfs.base
507 cachepath = sharedvfs.join(b'cache')
507 cachepath = sharedvfs.join(b'cache')
508 else:
508 else:
509 storebasepath = hgvfs.base
509 storebasepath = hgvfs.base
510 cachepath = hgvfs.join(b'cache')
510 cachepath = hgvfs.join(b'cache')
511
511
512 # The store has changed over time and the exact layout is dictated by
512 # The store has changed over time and the exact layout is dictated by
513 # requirements. The store interface abstracts differences across all
513 # requirements. The store interface abstracts differences across all
514 # of them.
514 # of them.
515 store = makestore(requirements, storebasepath,
515 store = makestore(requirements, storebasepath,
516 lambda base: vfsmod.vfs(base, cacheaudited=True))
516 lambda base: vfsmod.vfs(base, cacheaudited=True))
517 hgvfs.createmode = store.createmode
517 hgvfs.createmode = store.createmode
518
518
519 storevfs = store.vfs
519 storevfs = store.vfs
520 storevfs.options = resolvestorevfsoptions(ui, requirements, features)
520 storevfs.options = resolvestorevfsoptions(ui, requirements, features)
521
521
522 # The cache vfs is used to manage cache files.
522 # The cache vfs is used to manage cache files.
523 cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
523 cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
524 cachevfs.createmode = store.createmode
524 cachevfs.createmode = store.createmode
525
525
526 # Now resolve the type for the repository object. We do this by repeatedly
526 # Now resolve the type for the repository object. We do this by repeatedly
527 # calling a factory function to produces types for specific aspects of the
527 # calling a factory function to produces types for specific aspects of the
528 # repo's operation. The aggregate returned types are used as base classes
528 # repo's operation. The aggregate returned types are used as base classes
529 # for a dynamically-derived type, which will represent our new repository.
529 # for a dynamically-derived type, which will represent our new repository.
530
530
531 bases = []
531 bases = []
532 extrastate = {}
532 extrastate = {}
533
533
534 for iface, fn in REPO_INTERFACES:
534 for iface, fn in REPO_INTERFACES:
535 # We pass all potentially useful state to give extensions tons of
535 # We pass all potentially useful state to give extensions tons of
536 # flexibility.
536 # flexibility.
537 typ = fn()(ui=ui,
537 typ = fn()(ui=ui,
538 intents=intents,
538 intents=intents,
539 requirements=requirements,
539 requirements=requirements,
540 features=features,
540 features=features,
541 wdirvfs=wdirvfs,
541 wdirvfs=wdirvfs,
542 hgvfs=hgvfs,
542 hgvfs=hgvfs,
543 store=store,
543 store=store,
544 storevfs=storevfs,
544 storevfs=storevfs,
545 storeoptions=storevfs.options,
545 storeoptions=storevfs.options,
546 cachevfs=cachevfs,
546 cachevfs=cachevfs,
547 extensionmodulenames=extensionmodulenames,
547 extensionmodulenames=extensionmodulenames,
548 extrastate=extrastate,
548 extrastate=extrastate,
549 baseclasses=bases)
549 baseclasses=bases)
550
550
551 if not isinstance(typ, type):
551 if not isinstance(typ, type):
552 raise error.ProgrammingError('unable to construct type for %s' %
552 raise error.ProgrammingError('unable to construct type for %s' %
553 iface)
553 iface)
554
554
555 bases.append(typ)
555 bases.append(typ)
556
556
557 # type() allows you to use characters in type names that wouldn't be
557 # type() allows you to use characters in type names that wouldn't be
558 # recognized as Python symbols in source code. We abuse that to add
558 # recognized as Python symbols in source code. We abuse that to add
559 # rich information about our constructed repo.
559 # rich information about our constructed repo.
560 name = pycompat.sysstr(b'derivedrepo:%s<%s>' % (
560 name = pycompat.sysstr(b'derivedrepo:%s<%s>' % (
561 wdirvfs.base,
561 wdirvfs.base,
562 b','.join(sorted(requirements))))
562 b','.join(sorted(requirements))))
563
563
564 cls = type(name, tuple(bases), {})
564 cls = type(name, tuple(bases), {})
565
565
566 return cls(
566 return cls(
567 baseui=baseui,
567 baseui=baseui,
568 ui=ui,
568 ui=ui,
569 origroot=path,
569 origroot=path,
570 wdirvfs=wdirvfs,
570 wdirvfs=wdirvfs,
571 hgvfs=hgvfs,
571 hgvfs=hgvfs,
572 requirements=requirements,
572 requirements=requirements,
573 supportedrequirements=supportedrequirements,
573 supportedrequirements=supportedrequirements,
574 sharedpath=storebasepath,
574 sharedpath=storebasepath,
575 store=store,
575 store=store,
576 cachevfs=cachevfs,
576 cachevfs=cachevfs,
577 features=features,
577 features=features,
578 intents=intents)
578 intents=intents)
579
579
580 def afterhgrcload(ui, wdirvfs, hgvfs, requirements):
580 def afterhgrcload(ui, wdirvfs, hgvfs, requirements):
581 """Perform additional actions after .hg/hgrc is loaded.
581 """Perform additional actions after .hg/hgrc is loaded.
582
582
583 This function is called during repository loading immediately after
583 This function is called during repository loading immediately after
584 the .hg/hgrc file is loaded and before per-repo extensions are loaded.
584 the .hg/hgrc file is loaded and before per-repo extensions are loaded.
585
585
586 The function can be used to validate configs, automatically add
586 The function can be used to validate configs, automatically add
587 options (including extensions) based on requirements, etc.
587 options (including extensions) based on requirements, etc.
588 """
588 """
589
589
590 # Map of requirements to list of extensions to load automatically when
590 # Map of requirements to list of extensions to load automatically when
591 # requirement is present.
591 # requirement is present.
592 autoextensions = {
592 autoextensions = {
593 b'largefiles': [b'largefiles'],
593 b'largefiles': [b'largefiles'],
594 b'lfs': [b'lfs'],
594 b'lfs': [b'lfs'],
595 }
595 }
596
596
597 for requirement, names in sorted(autoextensions.items()):
597 for requirement, names in sorted(autoextensions.items()):
598 if requirement not in requirements:
598 if requirement not in requirements:
599 continue
599 continue
600
600
601 for name in names:
601 for name in names:
602 if not ui.hasconfig(b'extensions', name):
602 if not ui.hasconfig(b'extensions', name):
603 ui.setconfig(b'extensions', name, b'', source='autoload')
603 ui.setconfig(b'extensions', name, b'', source='autoload')
604
604
605 def gathersupportedrequirements(ui):
605 def gathersupportedrequirements(ui):
606 """Determine the complete set of recognized requirements."""
606 """Determine the complete set of recognized requirements."""
607 # Start with all requirements supported by this file.
607 # Start with all requirements supported by this file.
608 supported = set(localrepository._basesupported)
608 supported = set(localrepository._basesupported)
609
609
610 # Execute ``featuresetupfuncs`` entries if they belong to an extension
610 # Execute ``featuresetupfuncs`` entries if they belong to an extension
611 # relevant to this ui instance.
611 # relevant to this ui instance.
612 modules = {m.__name__ for n, m in extensions.extensions(ui)}
612 modules = {m.__name__ for n, m in extensions.extensions(ui)}
613
613
614 for fn in featuresetupfuncs:
614 for fn in featuresetupfuncs:
615 if fn.__module__ in modules:
615 if fn.__module__ in modules:
616 fn(ui, supported)
616 fn(ui, supported)
617
617
618 # Add derived requirements from registered compression engines.
618 # Add derived requirements from registered compression engines.
619 for name in util.compengines:
619 for name in util.compengines:
620 engine = util.compengines[name]
620 engine = util.compengines[name]
621 if engine.revlogheader():
621 if engine.revlogheader():
622 supported.add(b'exp-compression-%s' % name)
622 supported.add(b'exp-compression-%s' % name)
623
623
624 return supported
624 return supported
625
625
626 def ensurerequirementsrecognized(requirements, supported):
626 def ensurerequirementsrecognized(requirements, supported):
627 """Validate that a set of local requirements is recognized.
627 """Validate that a set of local requirements is recognized.
628
628
629 Receives a set of requirements. Raises an ``error.RepoError`` if there
629 Receives a set of requirements. Raises an ``error.RepoError`` if there
630 exists any requirement in that set that currently loaded code doesn't
630 exists any requirement in that set that currently loaded code doesn't
631 recognize.
631 recognize.
632
632
633 Returns a set of supported requirements.
633 Returns a set of supported requirements.
634 """
634 """
635 missing = set()
635 missing = set()
636
636
637 for requirement in requirements:
637 for requirement in requirements:
638 if requirement in supported:
638 if requirement in supported:
639 continue
639 continue
640
640
641 if not requirement or not requirement[0:1].isalnum():
641 if not requirement or not requirement[0:1].isalnum():
642 raise error.RequirementError(_(b'.hg/requires file is corrupt'))
642 raise error.RequirementError(_(b'.hg/requires file is corrupt'))
643
643
644 missing.add(requirement)
644 missing.add(requirement)
645
645
646 if missing:
646 if missing:
647 raise error.RequirementError(
647 raise error.RequirementError(
648 _(b'repository requires features unknown to this Mercurial: %s') %
648 _(b'repository requires features unknown to this Mercurial: %s') %
649 b' '.join(sorted(missing)),
649 b' '.join(sorted(missing)),
650 hint=_(b'see https://mercurial-scm.org/wiki/MissingRequirement '
650 hint=_(b'see https://mercurial-scm.org/wiki/MissingRequirement '
651 b'for more information'))
651 b'for more information'))
652
652
653 def ensurerequirementscompatible(ui, requirements):
653 def ensurerequirementscompatible(ui, requirements):
654 """Validates that a set of recognized requirements is mutually compatible.
654 """Validates that a set of recognized requirements is mutually compatible.
655
655
656 Some requirements may not be compatible with others or require
656 Some requirements may not be compatible with others or require
657 config options that aren't enabled. This function is called during
657 config options that aren't enabled. This function is called during
658 repository opening to ensure that the set of requirements needed
658 repository opening to ensure that the set of requirements needed
659 to open a repository is sane and compatible with config options.
659 to open a repository is sane and compatible with config options.
660
660
661 Extensions can monkeypatch this function to perform additional
661 Extensions can monkeypatch this function to perform additional
662 checking.
662 checking.
663
663
664 ``error.RepoError`` should be raised on failure.
664 ``error.RepoError`` should be raised on failure.
665 """
665 """
666 if b'exp-sparse' in requirements and not sparse.enabled:
666 if b'exp-sparse' in requirements and not sparse.enabled:
667 raise error.RepoError(_(b'repository is using sparse feature but '
667 raise error.RepoError(_(b'repository is using sparse feature but '
668 b'sparse is not enabled; enable the '
668 b'sparse is not enabled; enable the '
669 b'"sparse" extensions to access'))
669 b'"sparse" extensions to access'))
670
670
671 def makestore(requirements, path, vfstype):
671 def makestore(requirements, path, vfstype):
672 """Construct a storage object for a repository."""
672 """Construct a storage object for a repository."""
673 if b'store' in requirements:
673 if b'store' in requirements:
674 if b'fncache' in requirements:
674 if b'fncache' in requirements:
675 return storemod.fncachestore(path, vfstype,
675 return storemod.fncachestore(path, vfstype,
676 b'dotencode' in requirements)
676 b'dotencode' in requirements)
677
677
678 return storemod.encodedstore(path, vfstype)
678 return storemod.encodedstore(path, vfstype)
679
679
680 return storemod.basicstore(path, vfstype)
680 return storemod.basicstore(path, vfstype)
681
681
682 def resolvestorevfsoptions(ui, requirements, features):
682 def resolvestorevfsoptions(ui, requirements, features):
683 """Resolve the options to pass to the store vfs opener.
683 """Resolve the options to pass to the store vfs opener.
684
684
685 The returned dict is used to influence behavior of the storage layer.
685 The returned dict is used to influence behavior of the storage layer.
686 """
686 """
687 options = {}
687 options = {}
688
688
689 if b'treemanifest' in requirements:
689 if b'treemanifest' in requirements:
690 options[b'treemanifest'] = True
690 options[b'treemanifest'] = True
691
691
692 # experimental config: format.manifestcachesize
692 # experimental config: format.manifestcachesize
693 manifestcachesize = ui.configint(b'format', b'manifestcachesize')
693 manifestcachesize = ui.configint(b'format', b'manifestcachesize')
694 if manifestcachesize is not None:
694 if manifestcachesize is not None:
695 options[b'manifestcachesize'] = manifestcachesize
695 options[b'manifestcachesize'] = manifestcachesize
696
696
697 # In the absence of another requirement superseding a revlog-related
697 # In the absence of another requirement superseding a revlog-related
698 # requirement, we have to assume the repo is using revlog version 0.
698 # requirement, we have to assume the repo is using revlog version 0.
699 # This revlog format is super old and we don't bother trying to parse
699 # This revlog format is super old and we don't bother trying to parse
700 # opener options for it because those options wouldn't do anything
700 # opener options for it because those options wouldn't do anything
701 # meaningful on such old repos.
701 # meaningful on such old repos.
702 if b'revlogv1' in requirements or REVLOGV2_REQUIREMENT in requirements:
702 if b'revlogv1' in requirements or REVLOGV2_REQUIREMENT in requirements:
703 options.update(resolverevlogstorevfsoptions(ui, requirements, features))
703 options.update(resolverevlogstorevfsoptions(ui, requirements, features))
704
704
705 return options
705 return options
706
706
707 def resolverevlogstorevfsoptions(ui, requirements, features):
707 def resolverevlogstorevfsoptions(ui, requirements, features):
708 """Resolve opener options specific to revlogs."""
708 """Resolve opener options specific to revlogs."""
709
709
710 options = {}
710 options = {}
711 options[b'flagprocessors'] = {}
711
712
712 if b'revlogv1' in requirements:
713 if b'revlogv1' in requirements:
713 options[b'revlogv1'] = True
714 options[b'revlogv1'] = True
714 if REVLOGV2_REQUIREMENT in requirements:
715 if REVLOGV2_REQUIREMENT in requirements:
715 options[b'revlogv2'] = True
716 options[b'revlogv2'] = True
716
717
717 if b'generaldelta' in requirements:
718 if b'generaldelta' in requirements:
718 options[b'generaldelta'] = True
719 options[b'generaldelta'] = True
719
720
720 # experimental config: format.chunkcachesize
721 # experimental config: format.chunkcachesize
721 chunkcachesize = ui.configint(b'format', b'chunkcachesize')
722 chunkcachesize = ui.configint(b'format', b'chunkcachesize')
722 if chunkcachesize is not None:
723 if chunkcachesize is not None:
723 options[b'chunkcachesize'] = chunkcachesize
724 options[b'chunkcachesize'] = chunkcachesize
724
725
725 deltabothparents = ui.configbool(b'storage',
726 deltabothparents = ui.configbool(b'storage',
726 b'revlog.optimize-delta-parent-choice')
727 b'revlog.optimize-delta-parent-choice')
727 options[b'deltabothparents'] = deltabothparents
728 options[b'deltabothparents'] = deltabothparents
728
729
729 options[b'lazydeltabase'] = not scmutil.gddeltaconfig(ui)
730 options[b'lazydeltabase'] = not scmutil.gddeltaconfig(ui)
730
731
731 chainspan = ui.configbytes(b'experimental', b'maxdeltachainspan')
732 chainspan = ui.configbytes(b'experimental', b'maxdeltachainspan')
732 if 0 <= chainspan:
733 if 0 <= chainspan:
733 options[b'maxdeltachainspan'] = chainspan
734 options[b'maxdeltachainspan'] = chainspan
734
735
735 mmapindexthreshold = ui.configbytes(b'experimental',
736 mmapindexthreshold = ui.configbytes(b'experimental',
736 b'mmapindexthreshold')
737 b'mmapindexthreshold')
737 if mmapindexthreshold is not None:
738 if mmapindexthreshold is not None:
738 options[b'mmapindexthreshold'] = mmapindexthreshold
739 options[b'mmapindexthreshold'] = mmapindexthreshold
739
740
740 withsparseread = ui.configbool(b'experimental', b'sparse-read')
741 withsparseread = ui.configbool(b'experimental', b'sparse-read')
741 srdensitythres = float(ui.config(b'experimental',
742 srdensitythres = float(ui.config(b'experimental',
742 b'sparse-read.density-threshold'))
743 b'sparse-read.density-threshold'))
743 srmingapsize = ui.configbytes(b'experimental',
744 srmingapsize = ui.configbytes(b'experimental',
744 b'sparse-read.min-gap-size')
745 b'sparse-read.min-gap-size')
745 options[b'with-sparse-read'] = withsparseread
746 options[b'with-sparse-read'] = withsparseread
746 options[b'sparse-read-density-threshold'] = srdensitythres
747 options[b'sparse-read-density-threshold'] = srdensitythres
747 options[b'sparse-read-min-gap-size'] = srmingapsize
748 options[b'sparse-read-min-gap-size'] = srmingapsize
748
749
749 sparserevlog = SPARSEREVLOG_REQUIREMENT in requirements
750 sparserevlog = SPARSEREVLOG_REQUIREMENT in requirements
750 options[b'sparse-revlog'] = sparserevlog
751 options[b'sparse-revlog'] = sparserevlog
751 if sparserevlog:
752 if sparserevlog:
752 options[b'generaldelta'] = True
753 options[b'generaldelta'] = True
753
754
754 maxchainlen = None
755 maxchainlen = None
755 if sparserevlog:
756 if sparserevlog:
756 maxchainlen = revlogconst.SPARSE_REVLOG_MAX_CHAIN_LENGTH
757 maxchainlen = revlogconst.SPARSE_REVLOG_MAX_CHAIN_LENGTH
757 # experimental config: format.maxchainlen
758 # experimental config: format.maxchainlen
758 maxchainlen = ui.configint(b'format', b'maxchainlen', maxchainlen)
759 maxchainlen = ui.configint(b'format', b'maxchainlen', maxchainlen)
759 if maxchainlen is not None:
760 if maxchainlen is not None:
760 options[b'maxchainlen'] = maxchainlen
761 options[b'maxchainlen'] = maxchainlen
761
762
762 for r in requirements:
763 for r in requirements:
763 if r.startswith(b'exp-compression-'):
764 if r.startswith(b'exp-compression-'):
764 options[b'compengine'] = r[len(b'exp-compression-'):]
765 options[b'compengine'] = r[len(b'exp-compression-'):]
765
766
766 if repository.NARROW_REQUIREMENT in requirements:
767 if repository.NARROW_REQUIREMENT in requirements:
767 options[b'enableellipsis'] = True
768 options[b'enableellipsis'] = True
768
769
769 return options
770 return options
770
771
771 def makemain(**kwargs):
772 def makemain(**kwargs):
772 """Produce a type conforming to ``ilocalrepositorymain``."""
773 """Produce a type conforming to ``ilocalrepositorymain``."""
773 return localrepository
774 return localrepository
774
775
775 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
776 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
776 class revlogfilestorage(object):
777 class revlogfilestorage(object):
777 """File storage when using revlogs."""
778 """File storage when using revlogs."""
778
779
779 def file(self, path):
780 def file(self, path):
780 if path[0] == b'/':
781 if path[0] == b'/':
781 path = path[1:]
782 path = path[1:]
782
783
783 return filelog.filelog(self.svfs, path)
784 return filelog.filelog(self.svfs, path)
784
785
785 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
786 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
786 class revlognarrowfilestorage(object):
787 class revlognarrowfilestorage(object):
787 """File storage when using revlogs and narrow files."""
788 """File storage when using revlogs and narrow files."""
788
789
789 def file(self, path):
790 def file(self, path):
790 if path[0] == b'/':
791 if path[0] == b'/':
791 path = path[1:]
792 path = path[1:]
792
793
793 return filelog.narrowfilelog(self.svfs, path, self.narrowmatch())
794 return filelog.narrowfilelog(self.svfs, path, self.narrowmatch())
794
795
795 def makefilestorage(requirements, features, **kwargs):
796 def makefilestorage(requirements, features, **kwargs):
796 """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
797 """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
797 features.add(repository.REPO_FEATURE_REVLOG_FILE_STORAGE)
798 features.add(repository.REPO_FEATURE_REVLOG_FILE_STORAGE)
798 features.add(repository.REPO_FEATURE_STREAM_CLONE)
799 features.add(repository.REPO_FEATURE_STREAM_CLONE)
799
800
800 if repository.NARROW_REQUIREMENT in requirements:
801 if repository.NARROW_REQUIREMENT in requirements:
801 return revlognarrowfilestorage
802 return revlognarrowfilestorage
802 else:
803 else:
803 return revlogfilestorage
804 return revlogfilestorage
804
805
805 # List of repository interfaces and factory functions for them. Each
806 # List of repository interfaces and factory functions for them. Each
806 # will be called in order during ``makelocalrepository()`` to iteratively
807 # will be called in order during ``makelocalrepository()`` to iteratively
807 # derive the final type for a local repository instance. We capture the
808 # derive the final type for a local repository instance. We capture the
808 # function as a lambda so we don't hold a reference and the module-level
809 # function as a lambda so we don't hold a reference and the module-level
809 # functions can be wrapped.
810 # functions can be wrapped.
810 REPO_INTERFACES = [
811 REPO_INTERFACES = [
811 (repository.ilocalrepositorymain, lambda: makemain),
812 (repository.ilocalrepositorymain, lambda: makemain),
812 (repository.ilocalrepositoryfilestorage, lambda: makefilestorage),
813 (repository.ilocalrepositoryfilestorage, lambda: makefilestorage),
813 ]
814 ]
814
815
815 @interfaceutil.implementer(repository.ilocalrepositorymain)
816 @interfaceutil.implementer(repository.ilocalrepositorymain)
816 class localrepository(object):
817 class localrepository(object):
817 """Main class for representing local repositories.
818 """Main class for representing local repositories.
818
819
819 All local repositories are instances of this class.
820 All local repositories are instances of this class.
820
821
821 Constructed on its own, instances of this class are not usable as
822 Constructed on its own, instances of this class are not usable as
822 repository objects. To obtain a usable repository object, call
823 repository objects. To obtain a usable repository object, call
823 ``hg.repository()``, ``localrepo.instance()``, or
824 ``hg.repository()``, ``localrepo.instance()``, or
824 ``localrepo.makelocalrepository()``. The latter is the lowest-level.
825 ``localrepo.makelocalrepository()``. The latter is the lowest-level.
825 ``instance()`` adds support for creating new repositories.
826 ``instance()`` adds support for creating new repositories.
826 ``hg.repository()`` adds more extension integration, including calling
827 ``hg.repository()`` adds more extension integration, including calling
827 ``reposetup()``. Generally speaking, ``hg.repository()`` should be
828 ``reposetup()``. Generally speaking, ``hg.repository()`` should be
828 used.
829 used.
829 """
830 """
830
831
831 # obsolete experimental requirements:
832 # obsolete experimental requirements:
832 # - manifestv2: An experimental new manifest format that allowed
833 # - manifestv2: An experimental new manifest format that allowed
833 # for stem compression of long paths. Experiment ended up not
834 # for stem compression of long paths. Experiment ended up not
834 # being successful (repository sizes went up due to worse delta
835 # being successful (repository sizes went up due to worse delta
835 # chains), and the code was deleted in 4.6.
836 # chains), and the code was deleted in 4.6.
836 supportedformats = {
837 supportedformats = {
837 'revlogv1',
838 'revlogv1',
838 'generaldelta',
839 'generaldelta',
839 'treemanifest',
840 'treemanifest',
840 REVLOGV2_REQUIREMENT,
841 REVLOGV2_REQUIREMENT,
841 SPARSEREVLOG_REQUIREMENT,
842 SPARSEREVLOG_REQUIREMENT,
842 }
843 }
843 _basesupported = supportedformats | {
844 _basesupported = supportedformats | {
844 'store',
845 'store',
845 'fncache',
846 'fncache',
846 'shared',
847 'shared',
847 'relshared',
848 'relshared',
848 'dotencode',
849 'dotencode',
849 'exp-sparse',
850 'exp-sparse',
850 'internal-phase'
851 'internal-phase'
851 }
852 }
852
853
853 # list of prefix for file which can be written without 'wlock'
854 # list of prefix for file which can be written without 'wlock'
854 # Extensions should extend this list when needed
855 # Extensions should extend this list when needed
855 _wlockfreeprefix = {
856 _wlockfreeprefix = {
856 # We migh consider requiring 'wlock' for the next
857 # We migh consider requiring 'wlock' for the next
857 # two, but pretty much all the existing code assume
858 # two, but pretty much all the existing code assume
858 # wlock is not needed so we keep them excluded for
859 # wlock is not needed so we keep them excluded for
859 # now.
860 # now.
860 'hgrc',
861 'hgrc',
861 'requires',
862 'requires',
862 # XXX cache is a complicatged business someone
863 # XXX cache is a complicatged business someone
863 # should investigate this in depth at some point
864 # should investigate this in depth at some point
864 'cache/',
865 'cache/',
865 # XXX shouldn't be dirstate covered by the wlock?
866 # XXX shouldn't be dirstate covered by the wlock?
866 'dirstate',
867 'dirstate',
867 # XXX bisect was still a bit too messy at the time
868 # XXX bisect was still a bit too messy at the time
868 # this changeset was introduced. Someone should fix
869 # this changeset was introduced. Someone should fix
869 # the remainig bit and drop this line
870 # the remainig bit and drop this line
870 'bisect.state',
871 'bisect.state',
871 }
872 }
872
873
873 def __init__(self, baseui, ui, origroot, wdirvfs, hgvfs, requirements,
874 def __init__(self, baseui, ui, origroot, wdirvfs, hgvfs, requirements,
874 supportedrequirements, sharedpath, store, cachevfs,
875 supportedrequirements, sharedpath, store, cachevfs,
875 features, intents=None):
876 features, intents=None):
876 """Create a new local repository instance.
877 """Create a new local repository instance.
877
878
878 Most callers should use ``hg.repository()``, ``localrepo.instance()``,
879 Most callers should use ``hg.repository()``, ``localrepo.instance()``,
879 or ``localrepo.makelocalrepository()`` for obtaining a new repository
880 or ``localrepo.makelocalrepository()`` for obtaining a new repository
880 object.
881 object.
881
882
882 Arguments:
883 Arguments:
883
884
884 baseui
885 baseui
885 ``ui.ui`` instance that ``ui`` argument was based off of.
886 ``ui.ui`` instance that ``ui`` argument was based off of.
886
887
887 ui
888 ui
888 ``ui.ui`` instance for use by the repository.
889 ``ui.ui`` instance for use by the repository.
889
890
890 origroot
891 origroot
891 ``bytes`` path to working directory root of this repository.
892 ``bytes`` path to working directory root of this repository.
892
893
893 wdirvfs
894 wdirvfs
894 ``vfs.vfs`` rooted at the working directory.
895 ``vfs.vfs`` rooted at the working directory.
895
896
896 hgvfs
897 hgvfs
897 ``vfs.vfs`` rooted at .hg/
898 ``vfs.vfs`` rooted at .hg/
898
899
899 requirements
900 requirements
900 ``set`` of bytestrings representing repository opening requirements.
901 ``set`` of bytestrings representing repository opening requirements.
901
902
902 supportedrequirements
903 supportedrequirements
903 ``set`` of bytestrings representing repository requirements that we
904 ``set`` of bytestrings representing repository requirements that we
904 know how to open. May be a supetset of ``requirements``.
905 know how to open. May be a supetset of ``requirements``.
905
906
906 sharedpath
907 sharedpath
907 ``bytes`` Defining path to storage base directory. Points to a
908 ``bytes`` Defining path to storage base directory. Points to a
908 ``.hg/`` directory somewhere.
909 ``.hg/`` directory somewhere.
909
910
910 store
911 store
911 ``store.basicstore`` (or derived) instance providing access to
912 ``store.basicstore`` (or derived) instance providing access to
912 versioned storage.
913 versioned storage.
913
914
914 cachevfs
915 cachevfs
915 ``vfs.vfs`` used for cache files.
916 ``vfs.vfs`` used for cache files.
916
917
917 features
918 features
918 ``set`` of bytestrings defining features/capabilities of this
919 ``set`` of bytestrings defining features/capabilities of this
919 instance.
920 instance.
920
921
921 intents
922 intents
922 ``set`` of system strings indicating what this repo will be used
923 ``set`` of system strings indicating what this repo will be used
923 for.
924 for.
924 """
925 """
925 self.baseui = baseui
926 self.baseui = baseui
926 self.ui = ui
927 self.ui = ui
927 self.origroot = origroot
928 self.origroot = origroot
928 # vfs rooted at working directory.
929 # vfs rooted at working directory.
929 self.wvfs = wdirvfs
930 self.wvfs = wdirvfs
930 self.root = wdirvfs.base
931 self.root = wdirvfs.base
931 # vfs rooted at .hg/. Used to access most non-store paths.
932 # vfs rooted at .hg/. Used to access most non-store paths.
932 self.vfs = hgvfs
933 self.vfs = hgvfs
933 self.path = hgvfs.base
934 self.path = hgvfs.base
934 self.requirements = requirements
935 self.requirements = requirements
935 self.supported = supportedrequirements
936 self.supported = supportedrequirements
936 self.sharedpath = sharedpath
937 self.sharedpath = sharedpath
937 self.store = store
938 self.store = store
938 self.cachevfs = cachevfs
939 self.cachevfs = cachevfs
939 self.features = features
940 self.features = features
940
941
941 self.filtername = None
942 self.filtername = None
942
943
943 if (self.ui.configbool('devel', 'all-warnings') or
944 if (self.ui.configbool('devel', 'all-warnings') or
944 self.ui.configbool('devel', 'check-locks')):
945 self.ui.configbool('devel', 'check-locks')):
945 self.vfs.audit = self._getvfsward(self.vfs.audit)
946 self.vfs.audit = self._getvfsward(self.vfs.audit)
946 # A list of callback to shape the phase if no data were found.
947 # A list of callback to shape the phase if no data were found.
947 # Callback are in the form: func(repo, roots) --> processed root.
948 # Callback are in the form: func(repo, roots) --> processed root.
948 # This list it to be filled by extension during repo setup
949 # This list it to be filled by extension during repo setup
949 self._phasedefaults = []
950 self._phasedefaults = []
950
951
951 color.setup(self.ui)
952 color.setup(self.ui)
952
953
953 self.spath = self.store.path
954 self.spath = self.store.path
954 self.svfs = self.store.vfs
955 self.svfs = self.store.vfs
955 self.sjoin = self.store.join
956 self.sjoin = self.store.join
956 if (self.ui.configbool('devel', 'all-warnings') or
957 if (self.ui.configbool('devel', 'all-warnings') or
957 self.ui.configbool('devel', 'check-locks')):
958 self.ui.configbool('devel', 'check-locks')):
958 if util.safehasattr(self.svfs, 'vfs'): # this is filtervfs
959 if util.safehasattr(self.svfs, 'vfs'): # this is filtervfs
959 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
960 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
960 else: # standard vfs
961 else: # standard vfs
961 self.svfs.audit = self._getsvfsward(self.svfs.audit)
962 self.svfs.audit = self._getsvfsward(self.svfs.audit)
962
963
963 self._dirstatevalidatewarned = False
964 self._dirstatevalidatewarned = False
964
965
965 self._branchcaches = {}
966 self._branchcaches = {}
966 self._revbranchcache = None
967 self._revbranchcache = None
967 self._filterpats = {}
968 self._filterpats = {}
968 self._datafilters = {}
969 self._datafilters = {}
969 self._transref = self._lockref = self._wlockref = None
970 self._transref = self._lockref = self._wlockref = None
970
971
971 # A cache for various files under .hg/ that tracks file changes,
972 # A cache for various files under .hg/ that tracks file changes,
972 # (used by the filecache decorator)
973 # (used by the filecache decorator)
973 #
974 #
974 # Maps a property name to its util.filecacheentry
975 # Maps a property name to its util.filecacheentry
975 self._filecache = {}
976 self._filecache = {}
976
977
977 # hold sets of revision to be filtered
978 # hold sets of revision to be filtered
978 # should be cleared when something might have changed the filter value:
979 # should be cleared when something might have changed the filter value:
979 # - new changesets,
980 # - new changesets,
980 # - phase change,
981 # - phase change,
981 # - new obsolescence marker,
982 # - new obsolescence marker,
982 # - working directory parent change,
983 # - working directory parent change,
983 # - bookmark changes
984 # - bookmark changes
984 self.filteredrevcache = {}
985 self.filteredrevcache = {}
985
986
986 # post-dirstate-status hooks
987 # post-dirstate-status hooks
987 self._postdsstatus = []
988 self._postdsstatus = []
988
989
989 # generic mapping between names and nodes
990 # generic mapping between names and nodes
990 self.names = namespaces.namespaces()
991 self.names = namespaces.namespaces()
991
992
992 # Key to signature value.
993 # Key to signature value.
993 self._sparsesignaturecache = {}
994 self._sparsesignaturecache = {}
994 # Signature to cached matcher instance.
995 # Signature to cached matcher instance.
995 self._sparsematchercache = {}
996 self._sparsematchercache = {}
996
997
997 def _getvfsward(self, origfunc):
998 def _getvfsward(self, origfunc):
998 """build a ward for self.vfs"""
999 """build a ward for self.vfs"""
999 rref = weakref.ref(self)
1000 rref = weakref.ref(self)
1000 def checkvfs(path, mode=None):
1001 def checkvfs(path, mode=None):
1001 ret = origfunc(path, mode=mode)
1002 ret = origfunc(path, mode=mode)
1002 repo = rref()
1003 repo = rref()
1003 if (repo is None
1004 if (repo is None
1004 or not util.safehasattr(repo, '_wlockref')
1005 or not util.safehasattr(repo, '_wlockref')
1005 or not util.safehasattr(repo, '_lockref')):
1006 or not util.safehasattr(repo, '_lockref')):
1006 return
1007 return
1007 if mode in (None, 'r', 'rb'):
1008 if mode in (None, 'r', 'rb'):
1008 return
1009 return
1009 if path.startswith(repo.path):
1010 if path.startswith(repo.path):
1010 # truncate name relative to the repository (.hg)
1011 # truncate name relative to the repository (.hg)
1011 path = path[len(repo.path) + 1:]
1012 path = path[len(repo.path) + 1:]
1012 if path.startswith('cache/'):
1013 if path.startswith('cache/'):
1013 msg = 'accessing cache with vfs instead of cachevfs: "%s"'
1014 msg = 'accessing cache with vfs instead of cachevfs: "%s"'
1014 repo.ui.develwarn(msg % path, stacklevel=2, config="cache-vfs")
1015 repo.ui.develwarn(msg % path, stacklevel=2, config="cache-vfs")
1015 if path.startswith('journal.'):
1016 if path.startswith('journal.'):
1016 # journal is covered by 'lock'
1017 # journal is covered by 'lock'
1017 if repo._currentlock(repo._lockref) is None:
1018 if repo._currentlock(repo._lockref) is None:
1018 repo.ui.develwarn('write with no lock: "%s"' % path,
1019 repo.ui.develwarn('write with no lock: "%s"' % path,
1019 stacklevel=2, config='check-locks')
1020 stacklevel=2, config='check-locks')
1020 elif repo._currentlock(repo._wlockref) is None:
1021 elif repo._currentlock(repo._wlockref) is None:
1021 # rest of vfs files are covered by 'wlock'
1022 # rest of vfs files are covered by 'wlock'
1022 #
1023 #
1023 # exclude special files
1024 # exclude special files
1024 for prefix in self._wlockfreeprefix:
1025 for prefix in self._wlockfreeprefix:
1025 if path.startswith(prefix):
1026 if path.startswith(prefix):
1026 return
1027 return
1027 repo.ui.develwarn('write with no wlock: "%s"' % path,
1028 repo.ui.develwarn('write with no wlock: "%s"' % path,
1028 stacklevel=2, config='check-locks')
1029 stacklevel=2, config='check-locks')
1029 return ret
1030 return ret
1030 return checkvfs
1031 return checkvfs
1031
1032
1032 def _getsvfsward(self, origfunc):
1033 def _getsvfsward(self, origfunc):
1033 """build a ward for self.svfs"""
1034 """build a ward for self.svfs"""
1034 rref = weakref.ref(self)
1035 rref = weakref.ref(self)
1035 def checksvfs(path, mode=None):
1036 def checksvfs(path, mode=None):
1036 ret = origfunc(path, mode=mode)
1037 ret = origfunc(path, mode=mode)
1037 repo = rref()
1038 repo = rref()
1038 if repo is None or not util.safehasattr(repo, '_lockref'):
1039 if repo is None or not util.safehasattr(repo, '_lockref'):
1039 return
1040 return
1040 if mode in (None, 'r', 'rb'):
1041 if mode in (None, 'r', 'rb'):
1041 return
1042 return
1042 if path.startswith(repo.sharedpath):
1043 if path.startswith(repo.sharedpath):
1043 # truncate name relative to the repository (.hg)
1044 # truncate name relative to the repository (.hg)
1044 path = path[len(repo.sharedpath) + 1:]
1045 path = path[len(repo.sharedpath) + 1:]
1045 if repo._currentlock(repo._lockref) is None:
1046 if repo._currentlock(repo._lockref) is None:
1046 repo.ui.develwarn('write with no lock: "%s"' % path,
1047 repo.ui.develwarn('write with no lock: "%s"' % path,
1047 stacklevel=3)
1048 stacklevel=3)
1048 return ret
1049 return ret
1049 return checksvfs
1050 return checksvfs
1050
1051
1051 def close(self):
1052 def close(self):
1052 self._writecaches()
1053 self._writecaches()
1053
1054
1054 def _writecaches(self):
1055 def _writecaches(self):
1055 if self._revbranchcache:
1056 if self._revbranchcache:
1056 self._revbranchcache.write()
1057 self._revbranchcache.write()
1057
1058
1058 def _restrictcapabilities(self, caps):
1059 def _restrictcapabilities(self, caps):
1059 if self.ui.configbool('experimental', 'bundle2-advertise'):
1060 if self.ui.configbool('experimental', 'bundle2-advertise'):
1060 caps = set(caps)
1061 caps = set(caps)
1061 capsblob = bundle2.encodecaps(bundle2.getrepocaps(self,
1062 capsblob = bundle2.encodecaps(bundle2.getrepocaps(self,
1062 role='client'))
1063 role='client'))
1063 caps.add('bundle2=' + urlreq.quote(capsblob))
1064 caps.add('bundle2=' + urlreq.quote(capsblob))
1064 return caps
1065 return caps
1065
1066
1066 def _writerequirements(self):
1067 def _writerequirements(self):
1067 scmutil.writerequires(self.vfs, self.requirements)
1068 scmutil.writerequires(self.vfs, self.requirements)
1068
1069
1069 # Don't cache auditor/nofsauditor, or you'll end up with reference cycle:
1070 # Don't cache auditor/nofsauditor, or you'll end up with reference cycle:
1070 # self -> auditor -> self._checknested -> self
1071 # self -> auditor -> self._checknested -> self
1071
1072
1072 @property
1073 @property
1073 def auditor(self):
1074 def auditor(self):
1074 # This is only used by context.workingctx.match in order to
1075 # This is only used by context.workingctx.match in order to
1075 # detect files in subrepos.
1076 # detect files in subrepos.
1076 return pathutil.pathauditor(self.root, callback=self._checknested)
1077 return pathutil.pathauditor(self.root, callback=self._checknested)
1077
1078
1078 @property
1079 @property
1079 def nofsauditor(self):
1080 def nofsauditor(self):
1080 # This is only used by context.basectx.match in order to detect
1081 # This is only used by context.basectx.match in order to detect
1081 # files in subrepos.
1082 # files in subrepos.
1082 return pathutil.pathauditor(self.root, callback=self._checknested,
1083 return pathutil.pathauditor(self.root, callback=self._checknested,
1083 realfs=False, cached=True)
1084 realfs=False, cached=True)
1084
1085
1085 def _checknested(self, path):
1086 def _checknested(self, path):
1086 """Determine if path is a legal nested repository."""
1087 """Determine if path is a legal nested repository."""
1087 if not path.startswith(self.root):
1088 if not path.startswith(self.root):
1088 return False
1089 return False
1089 subpath = path[len(self.root) + 1:]
1090 subpath = path[len(self.root) + 1:]
1090 normsubpath = util.pconvert(subpath)
1091 normsubpath = util.pconvert(subpath)
1091
1092
1092 # XXX: Checking against the current working copy is wrong in
1093 # XXX: Checking against the current working copy is wrong in
1093 # the sense that it can reject things like
1094 # the sense that it can reject things like
1094 #
1095 #
1095 # $ hg cat -r 10 sub/x.txt
1096 # $ hg cat -r 10 sub/x.txt
1096 #
1097 #
1097 # if sub/ is no longer a subrepository in the working copy
1098 # if sub/ is no longer a subrepository in the working copy
1098 # parent revision.
1099 # parent revision.
1099 #
1100 #
1100 # However, it can of course also allow things that would have
1101 # However, it can of course also allow things that would have
1101 # been rejected before, such as the above cat command if sub/
1102 # been rejected before, such as the above cat command if sub/
1102 # is a subrepository now, but was a normal directory before.
1103 # is a subrepository now, but was a normal directory before.
1103 # The old path auditor would have rejected by mistake since it
1104 # The old path auditor would have rejected by mistake since it
1104 # panics when it sees sub/.hg/.
1105 # panics when it sees sub/.hg/.
1105 #
1106 #
1106 # All in all, checking against the working copy seems sensible
1107 # All in all, checking against the working copy seems sensible
1107 # since we want to prevent access to nested repositories on
1108 # since we want to prevent access to nested repositories on
1108 # the filesystem *now*.
1109 # the filesystem *now*.
1109 ctx = self[None]
1110 ctx = self[None]
1110 parts = util.splitpath(subpath)
1111 parts = util.splitpath(subpath)
1111 while parts:
1112 while parts:
1112 prefix = '/'.join(parts)
1113 prefix = '/'.join(parts)
1113 if prefix in ctx.substate:
1114 if prefix in ctx.substate:
1114 if prefix == normsubpath:
1115 if prefix == normsubpath:
1115 return True
1116 return True
1116 else:
1117 else:
1117 sub = ctx.sub(prefix)
1118 sub = ctx.sub(prefix)
1118 return sub.checknested(subpath[len(prefix) + 1:])
1119 return sub.checknested(subpath[len(prefix) + 1:])
1119 else:
1120 else:
1120 parts.pop()
1121 parts.pop()
1121 return False
1122 return False
1122
1123
1123 def peer(self):
1124 def peer(self):
1124 return localpeer(self) # not cached to avoid reference cycle
1125 return localpeer(self) # not cached to avoid reference cycle
1125
1126
1126 def unfiltered(self):
1127 def unfiltered(self):
1127 """Return unfiltered version of the repository
1128 """Return unfiltered version of the repository
1128
1129
1129 Intended to be overwritten by filtered repo."""
1130 Intended to be overwritten by filtered repo."""
1130 return self
1131 return self
1131
1132
1132 def filtered(self, name, visibilityexceptions=None):
1133 def filtered(self, name, visibilityexceptions=None):
1133 """Return a filtered version of a repository"""
1134 """Return a filtered version of a repository"""
1134 cls = repoview.newtype(self.unfiltered().__class__)
1135 cls = repoview.newtype(self.unfiltered().__class__)
1135 return cls(self, name, visibilityexceptions)
1136 return cls(self, name, visibilityexceptions)
1136
1137
1137 @repofilecache('bookmarks', 'bookmarks.current')
1138 @repofilecache('bookmarks', 'bookmarks.current')
1138 def _bookmarks(self):
1139 def _bookmarks(self):
1139 return bookmarks.bmstore(self)
1140 return bookmarks.bmstore(self)
1140
1141
1141 @property
1142 @property
1142 def _activebookmark(self):
1143 def _activebookmark(self):
1143 return self._bookmarks.active
1144 return self._bookmarks.active
1144
1145
1145 # _phasesets depend on changelog. what we need is to call
1146 # _phasesets depend on changelog. what we need is to call
1146 # _phasecache.invalidate() if '00changelog.i' was changed, but it
1147 # _phasecache.invalidate() if '00changelog.i' was changed, but it
1147 # can't be easily expressed in filecache mechanism.
1148 # can't be easily expressed in filecache mechanism.
1148 @storecache('phaseroots', '00changelog.i')
1149 @storecache('phaseroots', '00changelog.i')
1149 def _phasecache(self):
1150 def _phasecache(self):
1150 return phases.phasecache(self, self._phasedefaults)
1151 return phases.phasecache(self, self._phasedefaults)
1151
1152
1152 @storecache('obsstore')
1153 @storecache('obsstore')
1153 def obsstore(self):
1154 def obsstore(self):
1154 return obsolete.makestore(self.ui, self)
1155 return obsolete.makestore(self.ui, self)
1155
1156
1156 @storecache('00changelog.i')
1157 @storecache('00changelog.i')
1157 def changelog(self):
1158 def changelog(self):
1158 return changelog.changelog(self.svfs,
1159 return changelog.changelog(self.svfs,
1159 trypending=txnutil.mayhavepending(self.root))
1160 trypending=txnutil.mayhavepending(self.root))
1160
1161
1161 @storecache('00manifest.i')
1162 @storecache('00manifest.i')
1162 def manifestlog(self):
1163 def manifestlog(self):
1163 rootstore = manifest.manifestrevlog(self.svfs)
1164 rootstore = manifest.manifestrevlog(self.svfs)
1164 return manifest.manifestlog(self.svfs, self, rootstore)
1165 return manifest.manifestlog(self.svfs, self, rootstore)
1165
1166
1166 @repofilecache('dirstate')
1167 @repofilecache('dirstate')
1167 def dirstate(self):
1168 def dirstate(self):
1168 return self._makedirstate()
1169 return self._makedirstate()
1169
1170
1170 def _makedirstate(self):
1171 def _makedirstate(self):
1171 """Extension point for wrapping the dirstate per-repo."""
1172 """Extension point for wrapping the dirstate per-repo."""
1172 sparsematchfn = lambda: sparse.matcher(self)
1173 sparsematchfn = lambda: sparse.matcher(self)
1173
1174
1174 return dirstate.dirstate(self.vfs, self.ui, self.root,
1175 return dirstate.dirstate(self.vfs, self.ui, self.root,
1175 self._dirstatevalidate, sparsematchfn)
1176 self._dirstatevalidate, sparsematchfn)
1176
1177
1177 def _dirstatevalidate(self, node):
1178 def _dirstatevalidate(self, node):
1178 try:
1179 try:
1179 self.changelog.rev(node)
1180 self.changelog.rev(node)
1180 return node
1181 return node
1181 except error.LookupError:
1182 except error.LookupError:
1182 if not self._dirstatevalidatewarned:
1183 if not self._dirstatevalidatewarned:
1183 self._dirstatevalidatewarned = True
1184 self._dirstatevalidatewarned = True
1184 self.ui.warn(_("warning: ignoring unknown"
1185 self.ui.warn(_("warning: ignoring unknown"
1185 " working parent %s!\n") % short(node))
1186 " working parent %s!\n") % short(node))
1186 return nullid
1187 return nullid
1187
1188
1188 @storecache(narrowspec.FILENAME)
1189 @storecache(narrowspec.FILENAME)
1189 def narrowpats(self):
1190 def narrowpats(self):
1190 """matcher patterns for this repository's narrowspec
1191 """matcher patterns for this repository's narrowspec
1191
1192
1192 A tuple of (includes, excludes).
1193 A tuple of (includes, excludes).
1193 """
1194 """
1194 return narrowspec.load(self)
1195 return narrowspec.load(self)
1195
1196
1196 @storecache(narrowspec.FILENAME)
1197 @storecache(narrowspec.FILENAME)
1197 def _narrowmatch(self):
1198 def _narrowmatch(self):
1198 if repository.NARROW_REQUIREMENT not in self.requirements:
1199 if repository.NARROW_REQUIREMENT not in self.requirements:
1199 return matchmod.always(self.root, '')
1200 return matchmod.always(self.root, '')
1200 include, exclude = self.narrowpats
1201 include, exclude = self.narrowpats
1201 return narrowspec.match(self.root, include=include, exclude=exclude)
1202 return narrowspec.match(self.root, include=include, exclude=exclude)
1202
1203
1203 def narrowmatch(self, match=None, includeexact=False):
1204 def narrowmatch(self, match=None, includeexact=False):
1204 """matcher corresponding the the repo's narrowspec
1205 """matcher corresponding the the repo's narrowspec
1205
1206
1206 If `match` is given, then that will be intersected with the narrow
1207 If `match` is given, then that will be intersected with the narrow
1207 matcher.
1208 matcher.
1208
1209
1209 If `includeexact` is True, then any exact matches from `match` will
1210 If `includeexact` is True, then any exact matches from `match` will
1210 be included even if they're outside the narrowspec.
1211 be included even if they're outside the narrowspec.
1211 """
1212 """
1212 if match:
1213 if match:
1213 if includeexact and not self._narrowmatch.always():
1214 if includeexact and not self._narrowmatch.always():
1214 # do not exclude explicitly-specified paths so that they can
1215 # do not exclude explicitly-specified paths so that they can
1215 # be warned later on
1216 # be warned later on
1216 em = matchmod.exact(match._root, match._cwd, match.files())
1217 em = matchmod.exact(match._root, match._cwd, match.files())
1217 nm = matchmod.unionmatcher([self._narrowmatch, em])
1218 nm = matchmod.unionmatcher([self._narrowmatch, em])
1218 return matchmod.intersectmatchers(match, nm)
1219 return matchmod.intersectmatchers(match, nm)
1219 return matchmod.intersectmatchers(match, self._narrowmatch)
1220 return matchmod.intersectmatchers(match, self._narrowmatch)
1220 return self._narrowmatch
1221 return self._narrowmatch
1221
1222
1222 def setnarrowpats(self, newincludes, newexcludes):
1223 def setnarrowpats(self, newincludes, newexcludes):
1223 narrowspec.save(self, newincludes, newexcludes)
1224 narrowspec.save(self, newincludes, newexcludes)
1224 self.invalidate(clearfilecache=True)
1225 self.invalidate(clearfilecache=True)
1225
1226
1226 def __getitem__(self, changeid):
1227 def __getitem__(self, changeid):
1227 if changeid is None:
1228 if changeid is None:
1228 return context.workingctx(self)
1229 return context.workingctx(self)
1229 if isinstance(changeid, context.basectx):
1230 if isinstance(changeid, context.basectx):
1230 return changeid
1231 return changeid
1231 if isinstance(changeid, slice):
1232 if isinstance(changeid, slice):
1232 # wdirrev isn't contiguous so the slice shouldn't include it
1233 # wdirrev isn't contiguous so the slice shouldn't include it
1233 return [self[i]
1234 return [self[i]
1234 for i in pycompat.xrange(*changeid.indices(len(self)))
1235 for i in pycompat.xrange(*changeid.indices(len(self)))
1235 if i not in self.changelog.filteredrevs]
1236 if i not in self.changelog.filteredrevs]
1236 try:
1237 try:
1237 if isinstance(changeid, int):
1238 if isinstance(changeid, int):
1238 node = self.changelog.node(changeid)
1239 node = self.changelog.node(changeid)
1239 rev = changeid
1240 rev = changeid
1240 elif changeid == 'null':
1241 elif changeid == 'null':
1241 node = nullid
1242 node = nullid
1242 rev = nullrev
1243 rev = nullrev
1243 elif changeid == 'tip':
1244 elif changeid == 'tip':
1244 node = self.changelog.tip()
1245 node = self.changelog.tip()
1245 rev = self.changelog.rev(node)
1246 rev = self.changelog.rev(node)
1246 elif changeid == '.':
1247 elif changeid == '.':
1247 # this is a hack to delay/avoid loading obsmarkers
1248 # this is a hack to delay/avoid loading obsmarkers
1248 # when we know that '.' won't be hidden
1249 # when we know that '.' won't be hidden
1249 node = self.dirstate.p1()
1250 node = self.dirstate.p1()
1250 rev = self.unfiltered().changelog.rev(node)
1251 rev = self.unfiltered().changelog.rev(node)
1251 elif len(changeid) == 20:
1252 elif len(changeid) == 20:
1252 try:
1253 try:
1253 node = changeid
1254 node = changeid
1254 rev = self.changelog.rev(changeid)
1255 rev = self.changelog.rev(changeid)
1255 except error.FilteredLookupError:
1256 except error.FilteredLookupError:
1256 changeid = hex(changeid) # for the error message
1257 changeid = hex(changeid) # for the error message
1257 raise
1258 raise
1258 except LookupError:
1259 except LookupError:
1259 # check if it might have come from damaged dirstate
1260 # check if it might have come from damaged dirstate
1260 #
1261 #
1261 # XXX we could avoid the unfiltered if we had a recognizable
1262 # XXX we could avoid the unfiltered if we had a recognizable
1262 # exception for filtered changeset access
1263 # exception for filtered changeset access
1263 if (self.local()
1264 if (self.local()
1264 and changeid in self.unfiltered().dirstate.parents()):
1265 and changeid in self.unfiltered().dirstate.parents()):
1265 msg = _("working directory has unknown parent '%s'!")
1266 msg = _("working directory has unknown parent '%s'!")
1266 raise error.Abort(msg % short(changeid))
1267 raise error.Abort(msg % short(changeid))
1267 changeid = hex(changeid) # for the error message
1268 changeid = hex(changeid) # for the error message
1268 raise
1269 raise
1269
1270
1270 elif len(changeid) == 40:
1271 elif len(changeid) == 40:
1271 node = bin(changeid)
1272 node = bin(changeid)
1272 rev = self.changelog.rev(node)
1273 rev = self.changelog.rev(node)
1273 else:
1274 else:
1274 raise error.ProgrammingError(
1275 raise error.ProgrammingError(
1275 "unsupported changeid '%s' of type %s" %
1276 "unsupported changeid '%s' of type %s" %
1276 (changeid, type(changeid)))
1277 (changeid, type(changeid)))
1277
1278
1278 return context.changectx(self, rev, node)
1279 return context.changectx(self, rev, node)
1279
1280
1280 except (error.FilteredIndexError, error.FilteredLookupError):
1281 except (error.FilteredIndexError, error.FilteredLookupError):
1281 raise error.FilteredRepoLookupError(_("filtered revision '%s'")
1282 raise error.FilteredRepoLookupError(_("filtered revision '%s'")
1282 % pycompat.bytestr(changeid))
1283 % pycompat.bytestr(changeid))
1283 except (IndexError, LookupError):
1284 except (IndexError, LookupError):
1284 raise error.RepoLookupError(_("unknown revision '%s'") % changeid)
1285 raise error.RepoLookupError(_("unknown revision '%s'") % changeid)
1285 except error.WdirUnsupported:
1286 except error.WdirUnsupported:
1286 return context.workingctx(self)
1287 return context.workingctx(self)
1287
1288
1288 def __contains__(self, changeid):
1289 def __contains__(self, changeid):
1289 """True if the given changeid exists
1290 """True if the given changeid exists
1290
1291
1291 error.AmbiguousPrefixLookupError is raised if an ambiguous node
1292 error.AmbiguousPrefixLookupError is raised if an ambiguous node
1292 specified.
1293 specified.
1293 """
1294 """
1294 try:
1295 try:
1295 self[changeid]
1296 self[changeid]
1296 return True
1297 return True
1297 except error.RepoLookupError:
1298 except error.RepoLookupError:
1298 return False
1299 return False
1299
1300
1300 def __nonzero__(self):
1301 def __nonzero__(self):
1301 return True
1302 return True
1302
1303
1303 __bool__ = __nonzero__
1304 __bool__ = __nonzero__
1304
1305
1305 def __len__(self):
1306 def __len__(self):
1306 # no need to pay the cost of repoview.changelog
1307 # no need to pay the cost of repoview.changelog
1307 unfi = self.unfiltered()
1308 unfi = self.unfiltered()
1308 return len(unfi.changelog)
1309 return len(unfi.changelog)
1309
1310
1310 def __iter__(self):
1311 def __iter__(self):
1311 return iter(self.changelog)
1312 return iter(self.changelog)
1312
1313
1313 def revs(self, expr, *args):
1314 def revs(self, expr, *args):
1314 '''Find revisions matching a revset.
1315 '''Find revisions matching a revset.
1315
1316
1316 The revset is specified as a string ``expr`` that may contain
1317 The revset is specified as a string ``expr`` that may contain
1317 %-formatting to escape certain types. See ``revsetlang.formatspec``.
1318 %-formatting to escape certain types. See ``revsetlang.formatspec``.
1318
1319
1319 Revset aliases from the configuration are not expanded. To expand
1320 Revset aliases from the configuration are not expanded. To expand
1320 user aliases, consider calling ``scmutil.revrange()`` or
1321 user aliases, consider calling ``scmutil.revrange()`` or
1321 ``repo.anyrevs([expr], user=True)``.
1322 ``repo.anyrevs([expr], user=True)``.
1322
1323
1323 Returns a revset.abstractsmartset, which is a list-like interface
1324 Returns a revset.abstractsmartset, which is a list-like interface
1324 that contains integer revisions.
1325 that contains integer revisions.
1325 '''
1326 '''
1326 expr = revsetlang.formatspec(expr, *args)
1327 expr = revsetlang.formatspec(expr, *args)
1327 m = revset.match(None, expr)
1328 m = revset.match(None, expr)
1328 return m(self)
1329 return m(self)
1329
1330
1330 def set(self, expr, *args):
1331 def set(self, expr, *args):
1331 '''Find revisions matching a revset and emit changectx instances.
1332 '''Find revisions matching a revset and emit changectx instances.
1332
1333
1333 This is a convenience wrapper around ``revs()`` that iterates the
1334 This is a convenience wrapper around ``revs()`` that iterates the
1334 result and is a generator of changectx instances.
1335 result and is a generator of changectx instances.
1335
1336
1336 Revset aliases from the configuration are not expanded. To expand
1337 Revset aliases from the configuration are not expanded. To expand
1337 user aliases, consider calling ``scmutil.revrange()``.
1338 user aliases, consider calling ``scmutil.revrange()``.
1338 '''
1339 '''
1339 for r in self.revs(expr, *args):
1340 for r in self.revs(expr, *args):
1340 yield self[r]
1341 yield self[r]
1341
1342
1342 def anyrevs(self, specs, user=False, localalias=None):
1343 def anyrevs(self, specs, user=False, localalias=None):
1343 '''Find revisions matching one of the given revsets.
1344 '''Find revisions matching one of the given revsets.
1344
1345
1345 Revset aliases from the configuration are not expanded by default. To
1346 Revset aliases from the configuration are not expanded by default. To
1346 expand user aliases, specify ``user=True``. To provide some local
1347 expand user aliases, specify ``user=True``. To provide some local
1347 definitions overriding user aliases, set ``localalias`` to
1348 definitions overriding user aliases, set ``localalias`` to
1348 ``{name: definitionstring}``.
1349 ``{name: definitionstring}``.
1349 '''
1350 '''
1350 if user:
1351 if user:
1351 m = revset.matchany(self.ui, specs,
1352 m = revset.matchany(self.ui, specs,
1352 lookup=revset.lookupfn(self),
1353 lookup=revset.lookupfn(self),
1353 localalias=localalias)
1354 localalias=localalias)
1354 else:
1355 else:
1355 m = revset.matchany(None, specs, localalias=localalias)
1356 m = revset.matchany(None, specs, localalias=localalias)
1356 return m(self)
1357 return m(self)
1357
1358
1358 def url(self):
1359 def url(self):
1359 return 'file:' + self.root
1360 return 'file:' + self.root
1360
1361
1361 def hook(self, name, throw=False, **args):
1362 def hook(self, name, throw=False, **args):
1362 """Call a hook, passing this repo instance.
1363 """Call a hook, passing this repo instance.
1363
1364
1364 This a convenience method to aid invoking hooks. Extensions likely
1365 This a convenience method to aid invoking hooks. Extensions likely
1365 won't call this unless they have registered a custom hook or are
1366 won't call this unless they have registered a custom hook or are
1366 replacing code that is expected to call a hook.
1367 replacing code that is expected to call a hook.
1367 """
1368 """
1368 return hook.hook(self.ui, self, name, throw, **args)
1369 return hook.hook(self.ui, self, name, throw, **args)
1369
1370
1370 @filteredpropertycache
1371 @filteredpropertycache
1371 def _tagscache(self):
1372 def _tagscache(self):
1372 '''Returns a tagscache object that contains various tags related
1373 '''Returns a tagscache object that contains various tags related
1373 caches.'''
1374 caches.'''
1374
1375
1375 # This simplifies its cache management by having one decorated
1376 # This simplifies its cache management by having one decorated
1376 # function (this one) and the rest simply fetch things from it.
1377 # function (this one) and the rest simply fetch things from it.
1377 class tagscache(object):
1378 class tagscache(object):
1378 def __init__(self):
1379 def __init__(self):
1379 # These two define the set of tags for this repository. tags
1380 # These two define the set of tags for this repository. tags
1380 # maps tag name to node; tagtypes maps tag name to 'global' or
1381 # maps tag name to node; tagtypes maps tag name to 'global' or
1381 # 'local'. (Global tags are defined by .hgtags across all
1382 # 'local'. (Global tags are defined by .hgtags across all
1382 # heads, and local tags are defined in .hg/localtags.)
1383 # heads, and local tags are defined in .hg/localtags.)
1383 # They constitute the in-memory cache of tags.
1384 # They constitute the in-memory cache of tags.
1384 self.tags = self.tagtypes = None
1385 self.tags = self.tagtypes = None
1385
1386
1386 self.nodetagscache = self.tagslist = None
1387 self.nodetagscache = self.tagslist = None
1387
1388
1388 cache = tagscache()
1389 cache = tagscache()
1389 cache.tags, cache.tagtypes = self._findtags()
1390 cache.tags, cache.tagtypes = self._findtags()
1390
1391
1391 return cache
1392 return cache
1392
1393
1393 def tags(self):
1394 def tags(self):
1394 '''return a mapping of tag to node'''
1395 '''return a mapping of tag to node'''
1395 t = {}
1396 t = {}
1396 if self.changelog.filteredrevs:
1397 if self.changelog.filteredrevs:
1397 tags, tt = self._findtags()
1398 tags, tt = self._findtags()
1398 else:
1399 else:
1399 tags = self._tagscache.tags
1400 tags = self._tagscache.tags
1400 for k, v in tags.iteritems():
1401 for k, v in tags.iteritems():
1401 try:
1402 try:
1402 # ignore tags to unknown nodes
1403 # ignore tags to unknown nodes
1403 self.changelog.rev(v)
1404 self.changelog.rev(v)
1404 t[k] = v
1405 t[k] = v
1405 except (error.LookupError, ValueError):
1406 except (error.LookupError, ValueError):
1406 pass
1407 pass
1407 return t
1408 return t
1408
1409
1409 def _findtags(self):
1410 def _findtags(self):
1410 '''Do the hard work of finding tags. Return a pair of dicts
1411 '''Do the hard work of finding tags. Return a pair of dicts
1411 (tags, tagtypes) where tags maps tag name to node, and tagtypes
1412 (tags, tagtypes) where tags maps tag name to node, and tagtypes
1412 maps tag name to a string like \'global\' or \'local\'.
1413 maps tag name to a string like \'global\' or \'local\'.
1413 Subclasses or extensions are free to add their own tags, but
1414 Subclasses or extensions are free to add their own tags, but
1414 should be aware that the returned dicts will be retained for the
1415 should be aware that the returned dicts will be retained for the
1415 duration of the localrepo object.'''
1416 duration of the localrepo object.'''
1416
1417
1417 # XXX what tagtype should subclasses/extensions use? Currently
1418 # XXX what tagtype should subclasses/extensions use? Currently
1418 # mq and bookmarks add tags, but do not set the tagtype at all.
1419 # mq and bookmarks add tags, but do not set the tagtype at all.
1419 # Should each extension invent its own tag type? Should there
1420 # Should each extension invent its own tag type? Should there
1420 # be one tagtype for all such "virtual" tags? Or is the status
1421 # be one tagtype for all such "virtual" tags? Or is the status
1421 # quo fine?
1422 # quo fine?
1422
1423
1423
1424
1424 # map tag name to (node, hist)
1425 # map tag name to (node, hist)
1425 alltags = tagsmod.findglobaltags(self.ui, self)
1426 alltags = tagsmod.findglobaltags(self.ui, self)
1426 # map tag name to tag type
1427 # map tag name to tag type
1427 tagtypes = dict((tag, 'global') for tag in alltags)
1428 tagtypes = dict((tag, 'global') for tag in alltags)
1428
1429
1429 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
1430 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
1430
1431
1431 # Build the return dicts. Have to re-encode tag names because
1432 # Build the return dicts. Have to re-encode tag names because
1432 # the tags module always uses UTF-8 (in order not to lose info
1433 # the tags module always uses UTF-8 (in order not to lose info
1433 # writing to the cache), but the rest of Mercurial wants them in
1434 # writing to the cache), but the rest of Mercurial wants them in
1434 # local encoding.
1435 # local encoding.
1435 tags = {}
1436 tags = {}
1436 for (name, (node, hist)) in alltags.iteritems():
1437 for (name, (node, hist)) in alltags.iteritems():
1437 if node != nullid:
1438 if node != nullid:
1438 tags[encoding.tolocal(name)] = node
1439 tags[encoding.tolocal(name)] = node
1439 tags['tip'] = self.changelog.tip()
1440 tags['tip'] = self.changelog.tip()
1440 tagtypes = dict([(encoding.tolocal(name), value)
1441 tagtypes = dict([(encoding.tolocal(name), value)
1441 for (name, value) in tagtypes.iteritems()])
1442 for (name, value) in tagtypes.iteritems()])
1442 return (tags, tagtypes)
1443 return (tags, tagtypes)
1443
1444
1444 def tagtype(self, tagname):
1445 def tagtype(self, tagname):
1445 '''
1446 '''
1446 return the type of the given tag. result can be:
1447 return the type of the given tag. result can be:
1447
1448
1448 'local' : a local tag
1449 'local' : a local tag
1449 'global' : a global tag
1450 'global' : a global tag
1450 None : tag does not exist
1451 None : tag does not exist
1451 '''
1452 '''
1452
1453
1453 return self._tagscache.tagtypes.get(tagname)
1454 return self._tagscache.tagtypes.get(tagname)
1454
1455
1455 def tagslist(self):
1456 def tagslist(self):
1456 '''return a list of tags ordered by revision'''
1457 '''return a list of tags ordered by revision'''
1457 if not self._tagscache.tagslist:
1458 if not self._tagscache.tagslist:
1458 l = []
1459 l = []
1459 for t, n in self.tags().iteritems():
1460 for t, n in self.tags().iteritems():
1460 l.append((self.changelog.rev(n), t, n))
1461 l.append((self.changelog.rev(n), t, n))
1461 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
1462 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
1462
1463
1463 return self._tagscache.tagslist
1464 return self._tagscache.tagslist
1464
1465
1465 def nodetags(self, node):
1466 def nodetags(self, node):
1466 '''return the tags associated with a node'''
1467 '''return the tags associated with a node'''
1467 if not self._tagscache.nodetagscache:
1468 if not self._tagscache.nodetagscache:
1468 nodetagscache = {}
1469 nodetagscache = {}
1469 for t, n in self._tagscache.tags.iteritems():
1470 for t, n in self._tagscache.tags.iteritems():
1470 nodetagscache.setdefault(n, []).append(t)
1471 nodetagscache.setdefault(n, []).append(t)
1471 for tags in nodetagscache.itervalues():
1472 for tags in nodetagscache.itervalues():
1472 tags.sort()
1473 tags.sort()
1473 self._tagscache.nodetagscache = nodetagscache
1474 self._tagscache.nodetagscache = nodetagscache
1474 return self._tagscache.nodetagscache.get(node, [])
1475 return self._tagscache.nodetagscache.get(node, [])
1475
1476
1476 def nodebookmarks(self, node):
1477 def nodebookmarks(self, node):
1477 """return the list of bookmarks pointing to the specified node"""
1478 """return the list of bookmarks pointing to the specified node"""
1478 return self._bookmarks.names(node)
1479 return self._bookmarks.names(node)
1479
1480
1480 def branchmap(self):
1481 def branchmap(self):
1481 '''returns a dictionary {branch: [branchheads]} with branchheads
1482 '''returns a dictionary {branch: [branchheads]} with branchheads
1482 ordered by increasing revision number'''
1483 ordered by increasing revision number'''
1483 branchmap.updatecache(self)
1484 branchmap.updatecache(self)
1484 return self._branchcaches[self.filtername]
1485 return self._branchcaches[self.filtername]
1485
1486
1486 @unfilteredmethod
1487 @unfilteredmethod
1487 def revbranchcache(self):
1488 def revbranchcache(self):
1488 if not self._revbranchcache:
1489 if not self._revbranchcache:
1489 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
1490 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
1490 return self._revbranchcache
1491 return self._revbranchcache
1491
1492
1492 def branchtip(self, branch, ignoremissing=False):
1493 def branchtip(self, branch, ignoremissing=False):
1493 '''return the tip node for a given branch
1494 '''return the tip node for a given branch
1494
1495
1495 If ignoremissing is True, then this method will not raise an error.
1496 If ignoremissing is True, then this method will not raise an error.
1496 This is helpful for callers that only expect None for a missing branch
1497 This is helpful for callers that only expect None for a missing branch
1497 (e.g. namespace).
1498 (e.g. namespace).
1498
1499
1499 '''
1500 '''
1500 try:
1501 try:
1501 return self.branchmap().branchtip(branch)
1502 return self.branchmap().branchtip(branch)
1502 except KeyError:
1503 except KeyError:
1503 if not ignoremissing:
1504 if not ignoremissing:
1504 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
1505 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
1505 else:
1506 else:
1506 pass
1507 pass
1507
1508
1508 def lookup(self, key):
1509 def lookup(self, key):
1509 return scmutil.revsymbol(self, key).node()
1510 return scmutil.revsymbol(self, key).node()
1510
1511
1511 def lookupbranch(self, key):
1512 def lookupbranch(self, key):
1512 if key in self.branchmap():
1513 if key in self.branchmap():
1513 return key
1514 return key
1514
1515
1515 return scmutil.revsymbol(self, key).branch()
1516 return scmutil.revsymbol(self, key).branch()
1516
1517
1517 def known(self, nodes):
1518 def known(self, nodes):
1518 cl = self.changelog
1519 cl = self.changelog
1519 nm = cl.nodemap
1520 nm = cl.nodemap
1520 filtered = cl.filteredrevs
1521 filtered = cl.filteredrevs
1521 result = []
1522 result = []
1522 for n in nodes:
1523 for n in nodes:
1523 r = nm.get(n)
1524 r = nm.get(n)
1524 resp = not (r is None or r in filtered)
1525 resp = not (r is None or r in filtered)
1525 result.append(resp)
1526 result.append(resp)
1526 return result
1527 return result
1527
1528
1528 def local(self):
1529 def local(self):
1529 return self
1530 return self
1530
1531
1531 def publishing(self):
1532 def publishing(self):
1532 # it's safe (and desirable) to trust the publish flag unconditionally
1533 # it's safe (and desirable) to trust the publish flag unconditionally
1533 # so that we don't finalize changes shared between users via ssh or nfs
1534 # so that we don't finalize changes shared between users via ssh or nfs
1534 return self.ui.configbool('phases', 'publish', untrusted=True)
1535 return self.ui.configbool('phases', 'publish', untrusted=True)
1535
1536
1536 def cancopy(self):
1537 def cancopy(self):
1537 # so statichttprepo's override of local() works
1538 # so statichttprepo's override of local() works
1538 if not self.local():
1539 if not self.local():
1539 return False
1540 return False
1540 if not self.publishing():
1541 if not self.publishing():
1541 return True
1542 return True
1542 # if publishing we can't copy if there is filtered content
1543 # if publishing we can't copy if there is filtered content
1543 return not self.filtered('visible').changelog.filteredrevs
1544 return not self.filtered('visible').changelog.filteredrevs
1544
1545
1545 def shared(self):
1546 def shared(self):
1546 '''the type of shared repository (None if not shared)'''
1547 '''the type of shared repository (None if not shared)'''
1547 if self.sharedpath != self.path:
1548 if self.sharedpath != self.path:
1548 return 'store'
1549 return 'store'
1549 return None
1550 return None
1550
1551
1551 def wjoin(self, f, *insidef):
1552 def wjoin(self, f, *insidef):
1552 return self.vfs.reljoin(self.root, f, *insidef)
1553 return self.vfs.reljoin(self.root, f, *insidef)
1553
1554
1554 def setparents(self, p1, p2=nullid):
1555 def setparents(self, p1, p2=nullid):
1555 with self.dirstate.parentchange():
1556 with self.dirstate.parentchange():
1556 copies = self.dirstate.setparents(p1, p2)
1557 copies = self.dirstate.setparents(p1, p2)
1557 pctx = self[p1]
1558 pctx = self[p1]
1558 if copies:
1559 if copies:
1559 # Adjust copy records, the dirstate cannot do it, it
1560 # Adjust copy records, the dirstate cannot do it, it
1560 # requires access to parents manifests. Preserve them
1561 # requires access to parents manifests. Preserve them
1561 # only for entries added to first parent.
1562 # only for entries added to first parent.
1562 for f in copies:
1563 for f in copies:
1563 if f not in pctx and copies[f] in pctx:
1564 if f not in pctx and copies[f] in pctx:
1564 self.dirstate.copy(copies[f], f)
1565 self.dirstate.copy(copies[f], f)
1565 if p2 == nullid:
1566 if p2 == nullid:
1566 for f, s in sorted(self.dirstate.copies().items()):
1567 for f, s in sorted(self.dirstate.copies().items()):
1567 if f not in pctx and s not in pctx:
1568 if f not in pctx and s not in pctx:
1568 self.dirstate.copy(None, f)
1569 self.dirstate.copy(None, f)
1569
1570
1570 def filectx(self, path, changeid=None, fileid=None, changectx=None):
1571 def filectx(self, path, changeid=None, fileid=None, changectx=None):
1571 """changeid can be a changeset revision, node, or tag.
1572 """changeid can be a changeset revision, node, or tag.
1572 fileid can be a file revision or node."""
1573 fileid can be a file revision or node."""
1573 return context.filectx(self, path, changeid, fileid,
1574 return context.filectx(self, path, changeid, fileid,
1574 changectx=changectx)
1575 changectx=changectx)
1575
1576
1576 def getcwd(self):
1577 def getcwd(self):
1577 return self.dirstate.getcwd()
1578 return self.dirstate.getcwd()
1578
1579
1579 def pathto(self, f, cwd=None):
1580 def pathto(self, f, cwd=None):
1580 return self.dirstate.pathto(f, cwd)
1581 return self.dirstate.pathto(f, cwd)
1581
1582
1582 def _loadfilter(self, filter):
1583 def _loadfilter(self, filter):
1583 if filter not in self._filterpats:
1584 if filter not in self._filterpats:
1584 l = []
1585 l = []
1585 for pat, cmd in self.ui.configitems(filter):
1586 for pat, cmd in self.ui.configitems(filter):
1586 if cmd == '!':
1587 if cmd == '!':
1587 continue
1588 continue
1588 mf = matchmod.match(self.root, '', [pat])
1589 mf = matchmod.match(self.root, '', [pat])
1589 fn = None
1590 fn = None
1590 params = cmd
1591 params = cmd
1591 for name, filterfn in self._datafilters.iteritems():
1592 for name, filterfn in self._datafilters.iteritems():
1592 if cmd.startswith(name):
1593 if cmd.startswith(name):
1593 fn = filterfn
1594 fn = filterfn
1594 params = cmd[len(name):].lstrip()
1595 params = cmd[len(name):].lstrip()
1595 break
1596 break
1596 if not fn:
1597 if not fn:
1597 fn = lambda s, c, **kwargs: procutil.filter(s, c)
1598 fn = lambda s, c, **kwargs: procutil.filter(s, c)
1598 # Wrap old filters not supporting keyword arguments
1599 # Wrap old filters not supporting keyword arguments
1599 if not pycompat.getargspec(fn)[2]:
1600 if not pycompat.getargspec(fn)[2]:
1600 oldfn = fn
1601 oldfn = fn
1601 fn = lambda s, c, **kwargs: oldfn(s, c)
1602 fn = lambda s, c, **kwargs: oldfn(s, c)
1602 l.append((mf, fn, params))
1603 l.append((mf, fn, params))
1603 self._filterpats[filter] = l
1604 self._filterpats[filter] = l
1604 return self._filterpats[filter]
1605 return self._filterpats[filter]
1605
1606
1606 def _filter(self, filterpats, filename, data):
1607 def _filter(self, filterpats, filename, data):
1607 for mf, fn, cmd in filterpats:
1608 for mf, fn, cmd in filterpats:
1608 if mf(filename):
1609 if mf(filename):
1609 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
1610 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
1610 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
1611 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
1611 break
1612 break
1612
1613
1613 return data
1614 return data
1614
1615
1615 @unfilteredpropertycache
1616 @unfilteredpropertycache
1616 def _encodefilterpats(self):
1617 def _encodefilterpats(self):
1617 return self._loadfilter('encode')
1618 return self._loadfilter('encode')
1618
1619
1619 @unfilteredpropertycache
1620 @unfilteredpropertycache
1620 def _decodefilterpats(self):
1621 def _decodefilterpats(self):
1621 return self._loadfilter('decode')
1622 return self._loadfilter('decode')
1622
1623
1623 def adddatafilter(self, name, filter):
1624 def adddatafilter(self, name, filter):
1624 self._datafilters[name] = filter
1625 self._datafilters[name] = filter
1625
1626
1626 def wread(self, filename):
1627 def wread(self, filename):
1627 if self.wvfs.islink(filename):
1628 if self.wvfs.islink(filename):
1628 data = self.wvfs.readlink(filename)
1629 data = self.wvfs.readlink(filename)
1629 else:
1630 else:
1630 data = self.wvfs.read(filename)
1631 data = self.wvfs.read(filename)
1631 return self._filter(self._encodefilterpats, filename, data)
1632 return self._filter(self._encodefilterpats, filename, data)
1632
1633
1633 def wwrite(self, filename, data, flags, backgroundclose=False, **kwargs):
1634 def wwrite(self, filename, data, flags, backgroundclose=False, **kwargs):
1634 """write ``data`` into ``filename`` in the working directory
1635 """write ``data`` into ``filename`` in the working directory
1635
1636
1636 This returns length of written (maybe decoded) data.
1637 This returns length of written (maybe decoded) data.
1637 """
1638 """
1638 data = self._filter(self._decodefilterpats, filename, data)
1639 data = self._filter(self._decodefilterpats, filename, data)
1639 if 'l' in flags:
1640 if 'l' in flags:
1640 self.wvfs.symlink(data, filename)
1641 self.wvfs.symlink(data, filename)
1641 else:
1642 else:
1642 self.wvfs.write(filename, data, backgroundclose=backgroundclose,
1643 self.wvfs.write(filename, data, backgroundclose=backgroundclose,
1643 **kwargs)
1644 **kwargs)
1644 if 'x' in flags:
1645 if 'x' in flags:
1645 self.wvfs.setflags(filename, False, True)
1646 self.wvfs.setflags(filename, False, True)
1646 else:
1647 else:
1647 self.wvfs.setflags(filename, False, False)
1648 self.wvfs.setflags(filename, False, False)
1648 return len(data)
1649 return len(data)
1649
1650
1650 def wwritedata(self, filename, data):
1651 def wwritedata(self, filename, data):
1651 return self._filter(self._decodefilterpats, filename, data)
1652 return self._filter(self._decodefilterpats, filename, data)
1652
1653
1653 def currenttransaction(self):
1654 def currenttransaction(self):
1654 """return the current transaction or None if non exists"""
1655 """return the current transaction or None if non exists"""
1655 if self._transref:
1656 if self._transref:
1656 tr = self._transref()
1657 tr = self._transref()
1657 else:
1658 else:
1658 tr = None
1659 tr = None
1659
1660
1660 if tr and tr.running():
1661 if tr and tr.running():
1661 return tr
1662 return tr
1662 return None
1663 return None
1663
1664
1664 def transaction(self, desc, report=None):
1665 def transaction(self, desc, report=None):
1665 if (self.ui.configbool('devel', 'all-warnings')
1666 if (self.ui.configbool('devel', 'all-warnings')
1666 or self.ui.configbool('devel', 'check-locks')):
1667 or self.ui.configbool('devel', 'check-locks')):
1667 if self._currentlock(self._lockref) is None:
1668 if self._currentlock(self._lockref) is None:
1668 raise error.ProgrammingError('transaction requires locking')
1669 raise error.ProgrammingError('transaction requires locking')
1669 tr = self.currenttransaction()
1670 tr = self.currenttransaction()
1670 if tr is not None:
1671 if tr is not None:
1671 return tr.nest(name=desc)
1672 return tr.nest(name=desc)
1672
1673
1673 # abort here if the journal already exists
1674 # abort here if the journal already exists
1674 if self.svfs.exists("journal"):
1675 if self.svfs.exists("journal"):
1675 raise error.RepoError(
1676 raise error.RepoError(
1676 _("abandoned transaction found"),
1677 _("abandoned transaction found"),
1677 hint=_("run 'hg recover' to clean up transaction"))
1678 hint=_("run 'hg recover' to clean up transaction"))
1678
1679
1679 idbase = "%.40f#%f" % (random.random(), time.time())
1680 idbase = "%.40f#%f" % (random.random(), time.time())
1680 ha = hex(hashlib.sha1(idbase).digest())
1681 ha = hex(hashlib.sha1(idbase).digest())
1681 txnid = 'TXN:' + ha
1682 txnid = 'TXN:' + ha
1682 self.hook('pretxnopen', throw=True, txnname=desc, txnid=txnid)
1683 self.hook('pretxnopen', throw=True, txnname=desc, txnid=txnid)
1683
1684
1684 self._writejournal(desc)
1685 self._writejournal(desc)
1685 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
1686 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
1686 if report:
1687 if report:
1687 rp = report
1688 rp = report
1688 else:
1689 else:
1689 rp = self.ui.warn
1690 rp = self.ui.warn
1690 vfsmap = {'plain': self.vfs, 'store': self.svfs} # root of .hg/
1691 vfsmap = {'plain': self.vfs, 'store': self.svfs} # root of .hg/
1691 # we must avoid cyclic reference between repo and transaction.
1692 # we must avoid cyclic reference between repo and transaction.
1692 reporef = weakref.ref(self)
1693 reporef = weakref.ref(self)
1693 # Code to track tag movement
1694 # Code to track tag movement
1694 #
1695 #
1695 # Since tags are all handled as file content, it is actually quite hard
1696 # Since tags are all handled as file content, it is actually quite hard
1696 # to track these movement from a code perspective. So we fallback to a
1697 # to track these movement from a code perspective. So we fallback to a
1697 # tracking at the repository level. One could envision to track changes
1698 # tracking at the repository level. One could envision to track changes
1698 # to the '.hgtags' file through changegroup apply but that fails to
1699 # to the '.hgtags' file through changegroup apply but that fails to
1699 # cope with case where transaction expose new heads without changegroup
1700 # cope with case where transaction expose new heads without changegroup
1700 # being involved (eg: phase movement).
1701 # being involved (eg: phase movement).
1701 #
1702 #
1702 # For now, We gate the feature behind a flag since this likely comes
1703 # For now, We gate the feature behind a flag since this likely comes
1703 # with performance impacts. The current code run more often than needed
1704 # with performance impacts. The current code run more often than needed
1704 # and do not use caches as much as it could. The current focus is on
1705 # and do not use caches as much as it could. The current focus is on
1705 # the behavior of the feature so we disable it by default. The flag
1706 # the behavior of the feature so we disable it by default. The flag
1706 # will be removed when we are happy with the performance impact.
1707 # will be removed when we are happy with the performance impact.
1707 #
1708 #
1708 # Once this feature is no longer experimental move the following
1709 # Once this feature is no longer experimental move the following
1709 # documentation to the appropriate help section:
1710 # documentation to the appropriate help section:
1710 #
1711 #
1711 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
1712 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
1712 # tags (new or changed or deleted tags). In addition the details of
1713 # tags (new or changed or deleted tags). In addition the details of
1713 # these changes are made available in a file at:
1714 # these changes are made available in a file at:
1714 # ``REPOROOT/.hg/changes/tags.changes``.
1715 # ``REPOROOT/.hg/changes/tags.changes``.
1715 # Make sure you check for HG_TAG_MOVED before reading that file as it
1716 # Make sure you check for HG_TAG_MOVED before reading that file as it
1716 # might exist from a previous transaction even if no tag were touched
1717 # might exist from a previous transaction even if no tag were touched
1717 # in this one. Changes are recorded in a line base format::
1718 # in this one. Changes are recorded in a line base format::
1718 #
1719 #
1719 # <action> <hex-node> <tag-name>\n
1720 # <action> <hex-node> <tag-name>\n
1720 #
1721 #
1721 # Actions are defined as follow:
1722 # Actions are defined as follow:
1722 # "-R": tag is removed,
1723 # "-R": tag is removed,
1723 # "+A": tag is added,
1724 # "+A": tag is added,
1724 # "-M": tag is moved (old value),
1725 # "-M": tag is moved (old value),
1725 # "+M": tag is moved (new value),
1726 # "+M": tag is moved (new value),
1726 tracktags = lambda x: None
1727 tracktags = lambda x: None
1727 # experimental config: experimental.hook-track-tags
1728 # experimental config: experimental.hook-track-tags
1728 shouldtracktags = self.ui.configbool('experimental', 'hook-track-tags')
1729 shouldtracktags = self.ui.configbool('experimental', 'hook-track-tags')
1729 if desc != 'strip' and shouldtracktags:
1730 if desc != 'strip' and shouldtracktags:
1730 oldheads = self.changelog.headrevs()
1731 oldheads = self.changelog.headrevs()
1731 def tracktags(tr2):
1732 def tracktags(tr2):
1732 repo = reporef()
1733 repo = reporef()
1733 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
1734 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
1734 newheads = repo.changelog.headrevs()
1735 newheads = repo.changelog.headrevs()
1735 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
1736 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
1736 # notes: we compare lists here.
1737 # notes: we compare lists here.
1737 # As we do it only once buiding set would not be cheaper
1738 # As we do it only once buiding set would not be cheaper
1738 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
1739 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
1739 if changes:
1740 if changes:
1740 tr2.hookargs['tag_moved'] = '1'
1741 tr2.hookargs['tag_moved'] = '1'
1741 with repo.vfs('changes/tags.changes', 'w',
1742 with repo.vfs('changes/tags.changes', 'w',
1742 atomictemp=True) as changesfile:
1743 atomictemp=True) as changesfile:
1743 # note: we do not register the file to the transaction
1744 # note: we do not register the file to the transaction
1744 # because we needs it to still exist on the transaction
1745 # because we needs it to still exist on the transaction
1745 # is close (for txnclose hooks)
1746 # is close (for txnclose hooks)
1746 tagsmod.writediff(changesfile, changes)
1747 tagsmod.writediff(changesfile, changes)
1747 def validate(tr2):
1748 def validate(tr2):
1748 """will run pre-closing hooks"""
1749 """will run pre-closing hooks"""
1749 # XXX the transaction API is a bit lacking here so we take a hacky
1750 # XXX the transaction API is a bit lacking here so we take a hacky
1750 # path for now
1751 # path for now
1751 #
1752 #
1752 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
1753 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
1753 # dict is copied before these run. In addition we needs the data
1754 # dict is copied before these run. In addition we needs the data
1754 # available to in memory hooks too.
1755 # available to in memory hooks too.
1755 #
1756 #
1756 # Moreover, we also need to make sure this runs before txnclose
1757 # Moreover, we also need to make sure this runs before txnclose
1757 # hooks and there is no "pending" mechanism that would execute
1758 # hooks and there is no "pending" mechanism that would execute
1758 # logic only if hooks are about to run.
1759 # logic only if hooks are about to run.
1759 #
1760 #
1760 # Fixing this limitation of the transaction is also needed to track
1761 # Fixing this limitation of the transaction is also needed to track
1761 # other families of changes (bookmarks, phases, obsolescence).
1762 # other families of changes (bookmarks, phases, obsolescence).
1762 #
1763 #
1763 # This will have to be fixed before we remove the experimental
1764 # This will have to be fixed before we remove the experimental
1764 # gating.
1765 # gating.
1765 tracktags(tr2)
1766 tracktags(tr2)
1766 repo = reporef()
1767 repo = reporef()
1767 if repo.ui.configbool('experimental', 'single-head-per-branch'):
1768 if repo.ui.configbool('experimental', 'single-head-per-branch'):
1768 scmutil.enforcesinglehead(repo, tr2, desc)
1769 scmutil.enforcesinglehead(repo, tr2, desc)
1769 if hook.hashook(repo.ui, 'pretxnclose-bookmark'):
1770 if hook.hashook(repo.ui, 'pretxnclose-bookmark'):
1770 for name, (old, new) in sorted(tr.changes['bookmarks'].items()):
1771 for name, (old, new) in sorted(tr.changes['bookmarks'].items()):
1771 args = tr.hookargs.copy()
1772 args = tr.hookargs.copy()
1772 args.update(bookmarks.preparehookargs(name, old, new))
1773 args.update(bookmarks.preparehookargs(name, old, new))
1773 repo.hook('pretxnclose-bookmark', throw=True,
1774 repo.hook('pretxnclose-bookmark', throw=True,
1774 txnname=desc,
1775 txnname=desc,
1775 **pycompat.strkwargs(args))
1776 **pycompat.strkwargs(args))
1776 if hook.hashook(repo.ui, 'pretxnclose-phase'):
1777 if hook.hashook(repo.ui, 'pretxnclose-phase'):
1777 cl = repo.unfiltered().changelog
1778 cl = repo.unfiltered().changelog
1778 for rev, (old, new) in tr.changes['phases'].items():
1779 for rev, (old, new) in tr.changes['phases'].items():
1779 args = tr.hookargs.copy()
1780 args = tr.hookargs.copy()
1780 node = hex(cl.node(rev))
1781 node = hex(cl.node(rev))
1781 args.update(phases.preparehookargs(node, old, new))
1782 args.update(phases.preparehookargs(node, old, new))
1782 repo.hook('pretxnclose-phase', throw=True, txnname=desc,
1783 repo.hook('pretxnclose-phase', throw=True, txnname=desc,
1783 **pycompat.strkwargs(args))
1784 **pycompat.strkwargs(args))
1784
1785
1785 repo.hook('pretxnclose', throw=True,
1786 repo.hook('pretxnclose', throw=True,
1786 txnname=desc, **pycompat.strkwargs(tr.hookargs))
1787 txnname=desc, **pycompat.strkwargs(tr.hookargs))
1787 def releasefn(tr, success):
1788 def releasefn(tr, success):
1788 repo = reporef()
1789 repo = reporef()
1789 if success:
1790 if success:
1790 # this should be explicitly invoked here, because
1791 # this should be explicitly invoked here, because
1791 # in-memory changes aren't written out at closing
1792 # in-memory changes aren't written out at closing
1792 # transaction, if tr.addfilegenerator (via
1793 # transaction, if tr.addfilegenerator (via
1793 # dirstate.write or so) isn't invoked while
1794 # dirstate.write or so) isn't invoked while
1794 # transaction running
1795 # transaction running
1795 repo.dirstate.write(None)
1796 repo.dirstate.write(None)
1796 else:
1797 else:
1797 # discard all changes (including ones already written
1798 # discard all changes (including ones already written
1798 # out) in this transaction
1799 # out) in this transaction
1799 narrowspec.restorebackup(self, 'journal.narrowspec')
1800 narrowspec.restorebackup(self, 'journal.narrowspec')
1800 repo.dirstate.restorebackup(None, 'journal.dirstate')
1801 repo.dirstate.restorebackup(None, 'journal.dirstate')
1801
1802
1802 repo.invalidate(clearfilecache=True)
1803 repo.invalidate(clearfilecache=True)
1803
1804
1804 tr = transaction.transaction(rp, self.svfs, vfsmap,
1805 tr = transaction.transaction(rp, self.svfs, vfsmap,
1805 "journal",
1806 "journal",
1806 "undo",
1807 "undo",
1807 aftertrans(renames),
1808 aftertrans(renames),
1808 self.store.createmode,
1809 self.store.createmode,
1809 validator=validate,
1810 validator=validate,
1810 releasefn=releasefn,
1811 releasefn=releasefn,
1811 checkambigfiles=_cachedfiles,
1812 checkambigfiles=_cachedfiles,
1812 name=desc)
1813 name=desc)
1813 tr.changes['origrepolen'] = len(self)
1814 tr.changes['origrepolen'] = len(self)
1814 tr.changes['obsmarkers'] = set()
1815 tr.changes['obsmarkers'] = set()
1815 tr.changes['phases'] = {}
1816 tr.changes['phases'] = {}
1816 tr.changes['bookmarks'] = {}
1817 tr.changes['bookmarks'] = {}
1817
1818
1818 tr.hookargs['txnid'] = txnid
1819 tr.hookargs['txnid'] = txnid
1819 # note: writing the fncache only during finalize mean that the file is
1820 # note: writing the fncache only during finalize mean that the file is
1820 # outdated when running hooks. As fncache is used for streaming clone,
1821 # outdated when running hooks. As fncache is used for streaming clone,
1821 # this is not expected to break anything that happen during the hooks.
1822 # this is not expected to break anything that happen during the hooks.
1822 tr.addfinalize('flush-fncache', self.store.write)
1823 tr.addfinalize('flush-fncache', self.store.write)
1823 def txnclosehook(tr2):
1824 def txnclosehook(tr2):
1824 """To be run if transaction is successful, will schedule a hook run
1825 """To be run if transaction is successful, will schedule a hook run
1825 """
1826 """
1826 # Don't reference tr2 in hook() so we don't hold a reference.
1827 # Don't reference tr2 in hook() so we don't hold a reference.
1827 # This reduces memory consumption when there are multiple
1828 # This reduces memory consumption when there are multiple
1828 # transactions per lock. This can likely go away if issue5045
1829 # transactions per lock. This can likely go away if issue5045
1829 # fixes the function accumulation.
1830 # fixes the function accumulation.
1830 hookargs = tr2.hookargs
1831 hookargs = tr2.hookargs
1831
1832
1832 def hookfunc():
1833 def hookfunc():
1833 repo = reporef()
1834 repo = reporef()
1834 if hook.hashook(repo.ui, 'txnclose-bookmark'):
1835 if hook.hashook(repo.ui, 'txnclose-bookmark'):
1835 bmchanges = sorted(tr.changes['bookmarks'].items())
1836 bmchanges = sorted(tr.changes['bookmarks'].items())
1836 for name, (old, new) in bmchanges:
1837 for name, (old, new) in bmchanges:
1837 args = tr.hookargs.copy()
1838 args = tr.hookargs.copy()
1838 args.update(bookmarks.preparehookargs(name, old, new))
1839 args.update(bookmarks.preparehookargs(name, old, new))
1839 repo.hook('txnclose-bookmark', throw=False,
1840 repo.hook('txnclose-bookmark', throw=False,
1840 txnname=desc, **pycompat.strkwargs(args))
1841 txnname=desc, **pycompat.strkwargs(args))
1841
1842
1842 if hook.hashook(repo.ui, 'txnclose-phase'):
1843 if hook.hashook(repo.ui, 'txnclose-phase'):
1843 cl = repo.unfiltered().changelog
1844 cl = repo.unfiltered().changelog
1844 phasemv = sorted(tr.changes['phases'].items())
1845 phasemv = sorted(tr.changes['phases'].items())
1845 for rev, (old, new) in phasemv:
1846 for rev, (old, new) in phasemv:
1846 args = tr.hookargs.copy()
1847 args = tr.hookargs.copy()
1847 node = hex(cl.node(rev))
1848 node = hex(cl.node(rev))
1848 args.update(phases.preparehookargs(node, old, new))
1849 args.update(phases.preparehookargs(node, old, new))
1849 repo.hook('txnclose-phase', throw=False, txnname=desc,
1850 repo.hook('txnclose-phase', throw=False, txnname=desc,
1850 **pycompat.strkwargs(args))
1851 **pycompat.strkwargs(args))
1851
1852
1852 repo.hook('txnclose', throw=False, txnname=desc,
1853 repo.hook('txnclose', throw=False, txnname=desc,
1853 **pycompat.strkwargs(hookargs))
1854 **pycompat.strkwargs(hookargs))
1854 reporef()._afterlock(hookfunc)
1855 reporef()._afterlock(hookfunc)
1855 tr.addfinalize('txnclose-hook', txnclosehook)
1856 tr.addfinalize('txnclose-hook', txnclosehook)
1856 # Include a leading "-" to make it happen before the transaction summary
1857 # Include a leading "-" to make it happen before the transaction summary
1857 # reports registered via scmutil.registersummarycallback() whose names
1858 # reports registered via scmutil.registersummarycallback() whose names
1858 # are 00-txnreport etc. That way, the caches will be warm when the
1859 # are 00-txnreport etc. That way, the caches will be warm when the
1859 # callbacks run.
1860 # callbacks run.
1860 tr.addpostclose('-warm-cache', self._buildcacheupdater(tr))
1861 tr.addpostclose('-warm-cache', self._buildcacheupdater(tr))
1861 def txnaborthook(tr2):
1862 def txnaborthook(tr2):
1862 """To be run if transaction is aborted
1863 """To be run if transaction is aborted
1863 """
1864 """
1864 reporef().hook('txnabort', throw=False, txnname=desc,
1865 reporef().hook('txnabort', throw=False, txnname=desc,
1865 **pycompat.strkwargs(tr2.hookargs))
1866 **pycompat.strkwargs(tr2.hookargs))
1866 tr.addabort('txnabort-hook', txnaborthook)
1867 tr.addabort('txnabort-hook', txnaborthook)
1867 # avoid eager cache invalidation. in-memory data should be identical
1868 # avoid eager cache invalidation. in-memory data should be identical
1868 # to stored data if transaction has no error.
1869 # to stored data if transaction has no error.
1869 tr.addpostclose('refresh-filecachestats', self._refreshfilecachestats)
1870 tr.addpostclose('refresh-filecachestats', self._refreshfilecachestats)
1870 self._transref = weakref.ref(tr)
1871 self._transref = weakref.ref(tr)
1871 scmutil.registersummarycallback(self, tr, desc)
1872 scmutil.registersummarycallback(self, tr, desc)
1872 return tr
1873 return tr
1873
1874
1874 def _journalfiles(self):
1875 def _journalfiles(self):
1875 return ((self.svfs, 'journal'),
1876 return ((self.svfs, 'journal'),
1876 (self.vfs, 'journal.dirstate'),
1877 (self.vfs, 'journal.dirstate'),
1877 (self.vfs, 'journal.branch'),
1878 (self.vfs, 'journal.branch'),
1878 (self.vfs, 'journal.desc'),
1879 (self.vfs, 'journal.desc'),
1879 (self.vfs, 'journal.bookmarks'),
1880 (self.vfs, 'journal.bookmarks'),
1880 (self.svfs, 'journal.phaseroots'))
1881 (self.svfs, 'journal.phaseroots'))
1881
1882
1882 def undofiles(self):
1883 def undofiles(self):
1883 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
1884 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
1884
1885
1885 @unfilteredmethod
1886 @unfilteredmethod
1886 def _writejournal(self, desc):
1887 def _writejournal(self, desc):
1887 self.dirstate.savebackup(None, 'journal.dirstate')
1888 self.dirstate.savebackup(None, 'journal.dirstate')
1888 narrowspec.savebackup(self, 'journal.narrowspec')
1889 narrowspec.savebackup(self, 'journal.narrowspec')
1889 self.vfs.write("journal.branch",
1890 self.vfs.write("journal.branch",
1890 encoding.fromlocal(self.dirstate.branch()))
1891 encoding.fromlocal(self.dirstate.branch()))
1891 self.vfs.write("journal.desc",
1892 self.vfs.write("journal.desc",
1892 "%d\n%s\n" % (len(self), desc))
1893 "%d\n%s\n" % (len(self), desc))
1893 self.vfs.write("journal.bookmarks",
1894 self.vfs.write("journal.bookmarks",
1894 self.vfs.tryread("bookmarks"))
1895 self.vfs.tryread("bookmarks"))
1895 self.svfs.write("journal.phaseroots",
1896 self.svfs.write("journal.phaseroots",
1896 self.svfs.tryread("phaseroots"))
1897 self.svfs.tryread("phaseroots"))
1897
1898
1898 def recover(self):
1899 def recover(self):
1899 with self.lock():
1900 with self.lock():
1900 if self.svfs.exists("journal"):
1901 if self.svfs.exists("journal"):
1901 self.ui.status(_("rolling back interrupted transaction\n"))
1902 self.ui.status(_("rolling back interrupted transaction\n"))
1902 vfsmap = {'': self.svfs,
1903 vfsmap = {'': self.svfs,
1903 'plain': self.vfs,}
1904 'plain': self.vfs,}
1904 transaction.rollback(self.svfs, vfsmap, "journal",
1905 transaction.rollback(self.svfs, vfsmap, "journal",
1905 self.ui.warn,
1906 self.ui.warn,
1906 checkambigfiles=_cachedfiles)
1907 checkambigfiles=_cachedfiles)
1907 self.invalidate()
1908 self.invalidate()
1908 return True
1909 return True
1909 else:
1910 else:
1910 self.ui.warn(_("no interrupted transaction available\n"))
1911 self.ui.warn(_("no interrupted transaction available\n"))
1911 return False
1912 return False
1912
1913
1913 def rollback(self, dryrun=False, force=False):
1914 def rollback(self, dryrun=False, force=False):
1914 wlock = lock = dsguard = None
1915 wlock = lock = dsguard = None
1915 try:
1916 try:
1916 wlock = self.wlock()
1917 wlock = self.wlock()
1917 lock = self.lock()
1918 lock = self.lock()
1918 if self.svfs.exists("undo"):
1919 if self.svfs.exists("undo"):
1919 dsguard = dirstateguard.dirstateguard(self, 'rollback')
1920 dsguard = dirstateguard.dirstateguard(self, 'rollback')
1920
1921
1921 return self._rollback(dryrun, force, dsguard)
1922 return self._rollback(dryrun, force, dsguard)
1922 else:
1923 else:
1923 self.ui.warn(_("no rollback information available\n"))
1924 self.ui.warn(_("no rollback information available\n"))
1924 return 1
1925 return 1
1925 finally:
1926 finally:
1926 release(dsguard, lock, wlock)
1927 release(dsguard, lock, wlock)
1927
1928
1928 @unfilteredmethod # Until we get smarter cache management
1929 @unfilteredmethod # Until we get smarter cache management
1929 def _rollback(self, dryrun, force, dsguard):
1930 def _rollback(self, dryrun, force, dsguard):
1930 ui = self.ui
1931 ui = self.ui
1931 try:
1932 try:
1932 args = self.vfs.read('undo.desc').splitlines()
1933 args = self.vfs.read('undo.desc').splitlines()
1933 (oldlen, desc, detail) = (int(args[0]), args[1], None)
1934 (oldlen, desc, detail) = (int(args[0]), args[1], None)
1934 if len(args) >= 3:
1935 if len(args) >= 3:
1935 detail = args[2]
1936 detail = args[2]
1936 oldtip = oldlen - 1
1937 oldtip = oldlen - 1
1937
1938
1938 if detail and ui.verbose:
1939 if detail and ui.verbose:
1939 msg = (_('repository tip rolled back to revision %d'
1940 msg = (_('repository tip rolled back to revision %d'
1940 ' (undo %s: %s)\n')
1941 ' (undo %s: %s)\n')
1941 % (oldtip, desc, detail))
1942 % (oldtip, desc, detail))
1942 else:
1943 else:
1943 msg = (_('repository tip rolled back to revision %d'
1944 msg = (_('repository tip rolled back to revision %d'
1944 ' (undo %s)\n')
1945 ' (undo %s)\n')
1945 % (oldtip, desc))
1946 % (oldtip, desc))
1946 except IOError:
1947 except IOError:
1947 msg = _('rolling back unknown transaction\n')
1948 msg = _('rolling back unknown transaction\n')
1948 desc = None
1949 desc = None
1949
1950
1950 if not force and self['.'] != self['tip'] and desc == 'commit':
1951 if not force and self['.'] != self['tip'] and desc == 'commit':
1951 raise error.Abort(
1952 raise error.Abort(
1952 _('rollback of last commit while not checked out '
1953 _('rollback of last commit while not checked out '
1953 'may lose data'), hint=_('use -f to force'))
1954 'may lose data'), hint=_('use -f to force'))
1954
1955
1955 ui.status(msg)
1956 ui.status(msg)
1956 if dryrun:
1957 if dryrun:
1957 return 0
1958 return 0
1958
1959
1959 parents = self.dirstate.parents()
1960 parents = self.dirstate.parents()
1960 self.destroying()
1961 self.destroying()
1961 vfsmap = {'plain': self.vfs, '': self.svfs}
1962 vfsmap = {'plain': self.vfs, '': self.svfs}
1962 transaction.rollback(self.svfs, vfsmap, 'undo', ui.warn,
1963 transaction.rollback(self.svfs, vfsmap, 'undo', ui.warn,
1963 checkambigfiles=_cachedfiles)
1964 checkambigfiles=_cachedfiles)
1964 if self.vfs.exists('undo.bookmarks'):
1965 if self.vfs.exists('undo.bookmarks'):
1965 self.vfs.rename('undo.bookmarks', 'bookmarks', checkambig=True)
1966 self.vfs.rename('undo.bookmarks', 'bookmarks', checkambig=True)
1966 if self.svfs.exists('undo.phaseroots'):
1967 if self.svfs.exists('undo.phaseroots'):
1967 self.svfs.rename('undo.phaseroots', 'phaseroots', checkambig=True)
1968 self.svfs.rename('undo.phaseroots', 'phaseroots', checkambig=True)
1968 self.invalidate()
1969 self.invalidate()
1969
1970
1970 parentgone = (parents[0] not in self.changelog.nodemap or
1971 parentgone = (parents[0] not in self.changelog.nodemap or
1971 parents[1] not in self.changelog.nodemap)
1972 parents[1] not in self.changelog.nodemap)
1972 if parentgone:
1973 if parentgone:
1973 # prevent dirstateguard from overwriting already restored one
1974 # prevent dirstateguard from overwriting already restored one
1974 dsguard.close()
1975 dsguard.close()
1975
1976
1976 narrowspec.restorebackup(self, 'undo.narrowspec')
1977 narrowspec.restorebackup(self, 'undo.narrowspec')
1977 self.dirstate.restorebackup(None, 'undo.dirstate')
1978 self.dirstate.restorebackup(None, 'undo.dirstate')
1978 try:
1979 try:
1979 branch = self.vfs.read('undo.branch')
1980 branch = self.vfs.read('undo.branch')
1980 self.dirstate.setbranch(encoding.tolocal(branch))
1981 self.dirstate.setbranch(encoding.tolocal(branch))
1981 except IOError:
1982 except IOError:
1982 ui.warn(_('named branch could not be reset: '
1983 ui.warn(_('named branch could not be reset: '
1983 'current branch is still \'%s\'\n')
1984 'current branch is still \'%s\'\n')
1984 % self.dirstate.branch())
1985 % self.dirstate.branch())
1985
1986
1986 parents = tuple([p.rev() for p in self[None].parents()])
1987 parents = tuple([p.rev() for p in self[None].parents()])
1987 if len(parents) > 1:
1988 if len(parents) > 1:
1988 ui.status(_('working directory now based on '
1989 ui.status(_('working directory now based on '
1989 'revisions %d and %d\n') % parents)
1990 'revisions %d and %d\n') % parents)
1990 else:
1991 else:
1991 ui.status(_('working directory now based on '
1992 ui.status(_('working directory now based on '
1992 'revision %d\n') % parents)
1993 'revision %d\n') % parents)
1993 mergemod.mergestate.clean(self, self['.'].node())
1994 mergemod.mergestate.clean(self, self['.'].node())
1994
1995
1995 # TODO: if we know which new heads may result from this rollback, pass
1996 # TODO: if we know which new heads may result from this rollback, pass
1996 # them to destroy(), which will prevent the branchhead cache from being
1997 # them to destroy(), which will prevent the branchhead cache from being
1997 # invalidated.
1998 # invalidated.
1998 self.destroyed()
1999 self.destroyed()
1999 return 0
2000 return 0
2000
2001
2001 def _buildcacheupdater(self, newtransaction):
2002 def _buildcacheupdater(self, newtransaction):
2002 """called during transaction to build the callback updating cache
2003 """called during transaction to build the callback updating cache
2003
2004
2004 Lives on the repository to help extension who might want to augment
2005 Lives on the repository to help extension who might want to augment
2005 this logic. For this purpose, the created transaction is passed to the
2006 this logic. For this purpose, the created transaction is passed to the
2006 method.
2007 method.
2007 """
2008 """
2008 # we must avoid cyclic reference between repo and transaction.
2009 # we must avoid cyclic reference between repo and transaction.
2009 reporef = weakref.ref(self)
2010 reporef = weakref.ref(self)
2010 def updater(tr):
2011 def updater(tr):
2011 repo = reporef()
2012 repo = reporef()
2012 repo.updatecaches(tr)
2013 repo.updatecaches(tr)
2013 return updater
2014 return updater
2014
2015
2015 @unfilteredmethod
2016 @unfilteredmethod
2016 def updatecaches(self, tr=None, full=False):
2017 def updatecaches(self, tr=None, full=False):
2017 """warm appropriate caches
2018 """warm appropriate caches
2018
2019
2019 If this function is called after a transaction closed. The transaction
2020 If this function is called after a transaction closed. The transaction
2020 will be available in the 'tr' argument. This can be used to selectively
2021 will be available in the 'tr' argument. This can be used to selectively
2021 update caches relevant to the changes in that transaction.
2022 update caches relevant to the changes in that transaction.
2022
2023
2023 If 'full' is set, make sure all caches the function knows about have
2024 If 'full' is set, make sure all caches the function knows about have
2024 up-to-date data. Even the ones usually loaded more lazily.
2025 up-to-date data. Even the ones usually loaded more lazily.
2025 """
2026 """
2026 if tr is not None and tr.hookargs.get('source') == 'strip':
2027 if tr is not None and tr.hookargs.get('source') == 'strip':
2027 # During strip, many caches are invalid but
2028 # During strip, many caches are invalid but
2028 # later call to `destroyed` will refresh them.
2029 # later call to `destroyed` will refresh them.
2029 return
2030 return
2030
2031
2031 if tr is None or tr.changes['origrepolen'] < len(self):
2032 if tr is None or tr.changes['origrepolen'] < len(self):
2032 # updating the unfiltered branchmap should refresh all the others,
2033 # updating the unfiltered branchmap should refresh all the others,
2033 self.ui.debug('updating the branch cache\n')
2034 self.ui.debug('updating the branch cache\n')
2034 branchmap.updatecache(self.filtered('served'))
2035 branchmap.updatecache(self.filtered('served'))
2035
2036
2036 if full:
2037 if full:
2037 rbc = self.revbranchcache()
2038 rbc = self.revbranchcache()
2038 for r in self.changelog:
2039 for r in self.changelog:
2039 rbc.branchinfo(r)
2040 rbc.branchinfo(r)
2040 rbc.write()
2041 rbc.write()
2041
2042
2042 # ensure the working copy parents are in the manifestfulltextcache
2043 # ensure the working copy parents are in the manifestfulltextcache
2043 for ctx in self['.'].parents():
2044 for ctx in self['.'].parents():
2044 ctx.manifest() # accessing the manifest is enough
2045 ctx.manifest() # accessing the manifest is enough
2045
2046
2046 def invalidatecaches(self):
2047 def invalidatecaches(self):
2047
2048
2048 if '_tagscache' in vars(self):
2049 if '_tagscache' in vars(self):
2049 # can't use delattr on proxy
2050 # can't use delattr on proxy
2050 del self.__dict__['_tagscache']
2051 del self.__dict__['_tagscache']
2051
2052
2052 self.unfiltered()._branchcaches.clear()
2053 self.unfiltered()._branchcaches.clear()
2053 self.invalidatevolatilesets()
2054 self.invalidatevolatilesets()
2054 self._sparsesignaturecache.clear()
2055 self._sparsesignaturecache.clear()
2055
2056
2056 def invalidatevolatilesets(self):
2057 def invalidatevolatilesets(self):
2057 self.filteredrevcache.clear()
2058 self.filteredrevcache.clear()
2058 obsolete.clearobscaches(self)
2059 obsolete.clearobscaches(self)
2059
2060
2060 def invalidatedirstate(self):
2061 def invalidatedirstate(self):
2061 '''Invalidates the dirstate, causing the next call to dirstate
2062 '''Invalidates the dirstate, causing the next call to dirstate
2062 to check if it was modified since the last time it was read,
2063 to check if it was modified since the last time it was read,
2063 rereading it if it has.
2064 rereading it if it has.
2064
2065
2065 This is different to dirstate.invalidate() that it doesn't always
2066 This is different to dirstate.invalidate() that it doesn't always
2066 rereads the dirstate. Use dirstate.invalidate() if you want to
2067 rereads the dirstate. Use dirstate.invalidate() if you want to
2067 explicitly read the dirstate again (i.e. restoring it to a previous
2068 explicitly read the dirstate again (i.e. restoring it to a previous
2068 known good state).'''
2069 known good state).'''
2069 if hasunfilteredcache(self, 'dirstate'):
2070 if hasunfilteredcache(self, 'dirstate'):
2070 for k in self.dirstate._filecache:
2071 for k in self.dirstate._filecache:
2071 try:
2072 try:
2072 delattr(self.dirstate, k)
2073 delattr(self.dirstate, k)
2073 except AttributeError:
2074 except AttributeError:
2074 pass
2075 pass
2075 delattr(self.unfiltered(), 'dirstate')
2076 delattr(self.unfiltered(), 'dirstate')
2076
2077
2077 def invalidate(self, clearfilecache=False):
2078 def invalidate(self, clearfilecache=False):
2078 '''Invalidates both store and non-store parts other than dirstate
2079 '''Invalidates both store and non-store parts other than dirstate
2079
2080
2080 If a transaction is running, invalidation of store is omitted,
2081 If a transaction is running, invalidation of store is omitted,
2081 because discarding in-memory changes might cause inconsistency
2082 because discarding in-memory changes might cause inconsistency
2082 (e.g. incomplete fncache causes unintentional failure, but
2083 (e.g. incomplete fncache causes unintentional failure, but
2083 redundant one doesn't).
2084 redundant one doesn't).
2084 '''
2085 '''
2085 unfiltered = self.unfiltered() # all file caches are stored unfiltered
2086 unfiltered = self.unfiltered() # all file caches are stored unfiltered
2086 for k in list(self._filecache.keys()):
2087 for k in list(self._filecache.keys()):
2087 # dirstate is invalidated separately in invalidatedirstate()
2088 # dirstate is invalidated separately in invalidatedirstate()
2088 if k == 'dirstate':
2089 if k == 'dirstate':
2089 continue
2090 continue
2090 if (k == 'changelog' and
2091 if (k == 'changelog' and
2091 self.currenttransaction() and
2092 self.currenttransaction() and
2092 self.changelog._delayed):
2093 self.changelog._delayed):
2093 # The changelog object may store unwritten revisions. We don't
2094 # The changelog object may store unwritten revisions. We don't
2094 # want to lose them.
2095 # want to lose them.
2095 # TODO: Solve the problem instead of working around it.
2096 # TODO: Solve the problem instead of working around it.
2096 continue
2097 continue
2097
2098
2098 if clearfilecache:
2099 if clearfilecache:
2099 del self._filecache[k]
2100 del self._filecache[k]
2100 try:
2101 try:
2101 delattr(unfiltered, k)
2102 delattr(unfiltered, k)
2102 except AttributeError:
2103 except AttributeError:
2103 pass
2104 pass
2104 self.invalidatecaches()
2105 self.invalidatecaches()
2105 if not self.currenttransaction():
2106 if not self.currenttransaction():
2106 # TODO: Changing contents of store outside transaction
2107 # TODO: Changing contents of store outside transaction
2107 # causes inconsistency. We should make in-memory store
2108 # causes inconsistency. We should make in-memory store
2108 # changes detectable, and abort if changed.
2109 # changes detectable, and abort if changed.
2109 self.store.invalidatecaches()
2110 self.store.invalidatecaches()
2110
2111
2111 def invalidateall(self):
2112 def invalidateall(self):
2112 '''Fully invalidates both store and non-store parts, causing the
2113 '''Fully invalidates both store and non-store parts, causing the
2113 subsequent operation to reread any outside changes.'''
2114 subsequent operation to reread any outside changes.'''
2114 # extension should hook this to invalidate its caches
2115 # extension should hook this to invalidate its caches
2115 self.invalidate()
2116 self.invalidate()
2116 self.invalidatedirstate()
2117 self.invalidatedirstate()
2117
2118
2118 @unfilteredmethod
2119 @unfilteredmethod
2119 def _refreshfilecachestats(self, tr):
2120 def _refreshfilecachestats(self, tr):
2120 """Reload stats of cached files so that they are flagged as valid"""
2121 """Reload stats of cached files so that they are flagged as valid"""
2121 for k, ce in self._filecache.items():
2122 for k, ce in self._filecache.items():
2122 k = pycompat.sysstr(k)
2123 k = pycompat.sysstr(k)
2123 if k == r'dirstate' or k not in self.__dict__:
2124 if k == r'dirstate' or k not in self.__dict__:
2124 continue
2125 continue
2125 ce.refresh()
2126 ce.refresh()
2126
2127
2127 def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc,
2128 def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc,
2128 inheritchecker=None, parentenvvar=None):
2129 inheritchecker=None, parentenvvar=None):
2129 parentlock = None
2130 parentlock = None
2130 # the contents of parentenvvar are used by the underlying lock to
2131 # the contents of parentenvvar are used by the underlying lock to
2131 # determine whether it can be inherited
2132 # determine whether it can be inherited
2132 if parentenvvar is not None:
2133 if parentenvvar is not None:
2133 parentlock = encoding.environ.get(parentenvvar)
2134 parentlock = encoding.environ.get(parentenvvar)
2134
2135
2135 timeout = 0
2136 timeout = 0
2136 warntimeout = 0
2137 warntimeout = 0
2137 if wait:
2138 if wait:
2138 timeout = self.ui.configint("ui", "timeout")
2139 timeout = self.ui.configint("ui", "timeout")
2139 warntimeout = self.ui.configint("ui", "timeout.warn")
2140 warntimeout = self.ui.configint("ui", "timeout.warn")
2140 # internal config: ui.signal-safe-lock
2141 # internal config: ui.signal-safe-lock
2141 signalsafe = self.ui.configbool('ui', 'signal-safe-lock')
2142 signalsafe = self.ui.configbool('ui', 'signal-safe-lock')
2142
2143
2143 l = lockmod.trylock(self.ui, vfs, lockname, timeout, warntimeout,
2144 l = lockmod.trylock(self.ui, vfs, lockname, timeout, warntimeout,
2144 releasefn=releasefn,
2145 releasefn=releasefn,
2145 acquirefn=acquirefn, desc=desc,
2146 acquirefn=acquirefn, desc=desc,
2146 inheritchecker=inheritchecker,
2147 inheritchecker=inheritchecker,
2147 parentlock=parentlock,
2148 parentlock=parentlock,
2148 signalsafe=signalsafe)
2149 signalsafe=signalsafe)
2149 return l
2150 return l
2150
2151
2151 def _afterlock(self, callback):
2152 def _afterlock(self, callback):
2152 """add a callback to be run when the repository is fully unlocked
2153 """add a callback to be run when the repository is fully unlocked
2153
2154
2154 The callback will be executed when the outermost lock is released
2155 The callback will be executed when the outermost lock is released
2155 (with wlock being higher level than 'lock')."""
2156 (with wlock being higher level than 'lock')."""
2156 for ref in (self._wlockref, self._lockref):
2157 for ref in (self._wlockref, self._lockref):
2157 l = ref and ref()
2158 l = ref and ref()
2158 if l and l.held:
2159 if l and l.held:
2159 l.postrelease.append(callback)
2160 l.postrelease.append(callback)
2160 break
2161 break
2161 else: # no lock have been found.
2162 else: # no lock have been found.
2162 callback()
2163 callback()
2163
2164
2164 def lock(self, wait=True):
2165 def lock(self, wait=True):
2165 '''Lock the repository store (.hg/store) and return a weak reference
2166 '''Lock the repository store (.hg/store) and return a weak reference
2166 to the lock. Use this before modifying the store (e.g. committing or
2167 to the lock. Use this before modifying the store (e.g. committing or
2167 stripping). If you are opening a transaction, get a lock as well.)
2168 stripping). If you are opening a transaction, get a lock as well.)
2168
2169
2169 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2170 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2170 'wlock' first to avoid a dead-lock hazard.'''
2171 'wlock' first to avoid a dead-lock hazard.'''
2171 l = self._currentlock(self._lockref)
2172 l = self._currentlock(self._lockref)
2172 if l is not None:
2173 if l is not None:
2173 l.lock()
2174 l.lock()
2174 return l
2175 return l
2175
2176
2176 l = self._lock(self.svfs, "lock", wait, None,
2177 l = self._lock(self.svfs, "lock", wait, None,
2177 self.invalidate, _('repository %s') % self.origroot)
2178 self.invalidate, _('repository %s') % self.origroot)
2178 self._lockref = weakref.ref(l)
2179 self._lockref = weakref.ref(l)
2179 return l
2180 return l
2180
2181
2181 def _wlockchecktransaction(self):
2182 def _wlockchecktransaction(self):
2182 if self.currenttransaction() is not None:
2183 if self.currenttransaction() is not None:
2183 raise error.LockInheritanceContractViolation(
2184 raise error.LockInheritanceContractViolation(
2184 'wlock cannot be inherited in the middle of a transaction')
2185 'wlock cannot be inherited in the middle of a transaction')
2185
2186
2186 def wlock(self, wait=True):
2187 def wlock(self, wait=True):
2187 '''Lock the non-store parts of the repository (everything under
2188 '''Lock the non-store parts of the repository (everything under
2188 .hg except .hg/store) and return a weak reference to the lock.
2189 .hg except .hg/store) and return a weak reference to the lock.
2189
2190
2190 Use this before modifying files in .hg.
2191 Use this before modifying files in .hg.
2191
2192
2192 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2193 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2193 'wlock' first to avoid a dead-lock hazard.'''
2194 'wlock' first to avoid a dead-lock hazard.'''
2194 l = self._wlockref and self._wlockref()
2195 l = self._wlockref and self._wlockref()
2195 if l is not None and l.held:
2196 if l is not None and l.held:
2196 l.lock()
2197 l.lock()
2197 return l
2198 return l
2198
2199
2199 # We do not need to check for non-waiting lock acquisition. Such
2200 # We do not need to check for non-waiting lock acquisition. Such
2200 # acquisition would not cause dead-lock as they would just fail.
2201 # acquisition would not cause dead-lock as they would just fail.
2201 if wait and (self.ui.configbool('devel', 'all-warnings')
2202 if wait and (self.ui.configbool('devel', 'all-warnings')
2202 or self.ui.configbool('devel', 'check-locks')):
2203 or self.ui.configbool('devel', 'check-locks')):
2203 if self._currentlock(self._lockref) is not None:
2204 if self._currentlock(self._lockref) is not None:
2204 self.ui.develwarn('"wlock" acquired after "lock"')
2205 self.ui.develwarn('"wlock" acquired after "lock"')
2205
2206
2206 def unlock():
2207 def unlock():
2207 if self.dirstate.pendingparentchange():
2208 if self.dirstate.pendingparentchange():
2208 self.dirstate.invalidate()
2209 self.dirstate.invalidate()
2209 else:
2210 else:
2210 self.dirstate.write(None)
2211 self.dirstate.write(None)
2211
2212
2212 self._filecache['dirstate'].refresh()
2213 self._filecache['dirstate'].refresh()
2213
2214
2214 l = self._lock(self.vfs, "wlock", wait, unlock,
2215 l = self._lock(self.vfs, "wlock", wait, unlock,
2215 self.invalidatedirstate, _('working directory of %s') %
2216 self.invalidatedirstate, _('working directory of %s') %
2216 self.origroot,
2217 self.origroot,
2217 inheritchecker=self._wlockchecktransaction,
2218 inheritchecker=self._wlockchecktransaction,
2218 parentenvvar='HG_WLOCK_LOCKER')
2219 parentenvvar='HG_WLOCK_LOCKER')
2219 self._wlockref = weakref.ref(l)
2220 self._wlockref = weakref.ref(l)
2220 return l
2221 return l
2221
2222
2222 def _currentlock(self, lockref):
2223 def _currentlock(self, lockref):
2223 """Returns the lock if it's held, or None if it's not."""
2224 """Returns the lock if it's held, or None if it's not."""
2224 if lockref is None:
2225 if lockref is None:
2225 return None
2226 return None
2226 l = lockref()
2227 l = lockref()
2227 if l is None or not l.held:
2228 if l is None or not l.held:
2228 return None
2229 return None
2229 return l
2230 return l
2230
2231
2231 def currentwlock(self):
2232 def currentwlock(self):
2232 """Returns the wlock if it's held, or None if it's not."""
2233 """Returns the wlock if it's held, or None if it's not."""
2233 return self._currentlock(self._wlockref)
2234 return self._currentlock(self._wlockref)
2234
2235
2235 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
2236 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
2236 """
2237 """
2237 commit an individual file as part of a larger transaction
2238 commit an individual file as part of a larger transaction
2238 """
2239 """
2239
2240
2240 fname = fctx.path()
2241 fname = fctx.path()
2241 fparent1 = manifest1.get(fname, nullid)
2242 fparent1 = manifest1.get(fname, nullid)
2242 fparent2 = manifest2.get(fname, nullid)
2243 fparent2 = manifest2.get(fname, nullid)
2243 if isinstance(fctx, context.filectx):
2244 if isinstance(fctx, context.filectx):
2244 node = fctx.filenode()
2245 node = fctx.filenode()
2245 if node in [fparent1, fparent2]:
2246 if node in [fparent1, fparent2]:
2246 self.ui.debug('reusing %s filelog entry\n' % fname)
2247 self.ui.debug('reusing %s filelog entry\n' % fname)
2247 if manifest1.flags(fname) != fctx.flags():
2248 if manifest1.flags(fname) != fctx.flags():
2248 changelist.append(fname)
2249 changelist.append(fname)
2249 return node
2250 return node
2250
2251
2251 flog = self.file(fname)
2252 flog = self.file(fname)
2252 meta = {}
2253 meta = {}
2253 copy = fctx.renamed()
2254 copy = fctx.renamed()
2254 if copy and copy[0] != fname:
2255 if copy and copy[0] != fname:
2255 # Mark the new revision of this file as a copy of another
2256 # Mark the new revision of this file as a copy of another
2256 # file. This copy data will effectively act as a parent
2257 # file. This copy data will effectively act as a parent
2257 # of this new revision. If this is a merge, the first
2258 # of this new revision. If this is a merge, the first
2258 # parent will be the nullid (meaning "look up the copy data")
2259 # parent will be the nullid (meaning "look up the copy data")
2259 # and the second one will be the other parent. For example:
2260 # and the second one will be the other parent. For example:
2260 #
2261 #
2261 # 0 --- 1 --- 3 rev1 changes file foo
2262 # 0 --- 1 --- 3 rev1 changes file foo
2262 # \ / rev2 renames foo to bar and changes it
2263 # \ / rev2 renames foo to bar and changes it
2263 # \- 2 -/ rev3 should have bar with all changes and
2264 # \- 2 -/ rev3 should have bar with all changes and
2264 # should record that bar descends from
2265 # should record that bar descends from
2265 # bar in rev2 and foo in rev1
2266 # bar in rev2 and foo in rev1
2266 #
2267 #
2267 # this allows this merge to succeed:
2268 # this allows this merge to succeed:
2268 #
2269 #
2269 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
2270 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
2270 # \ / merging rev3 and rev4 should use bar@rev2
2271 # \ / merging rev3 and rev4 should use bar@rev2
2271 # \- 2 --- 4 as the merge base
2272 # \- 2 --- 4 as the merge base
2272 #
2273 #
2273
2274
2274 cfname = copy[0]
2275 cfname = copy[0]
2275 crev = manifest1.get(cfname)
2276 crev = manifest1.get(cfname)
2276 newfparent = fparent2
2277 newfparent = fparent2
2277
2278
2278 if manifest2: # branch merge
2279 if manifest2: # branch merge
2279 if fparent2 == nullid or crev is None: # copied on remote side
2280 if fparent2 == nullid or crev is None: # copied on remote side
2280 if cfname in manifest2:
2281 if cfname in manifest2:
2281 crev = manifest2[cfname]
2282 crev = manifest2[cfname]
2282 newfparent = fparent1
2283 newfparent = fparent1
2283
2284
2284 # Here, we used to search backwards through history to try to find
2285 # Here, we used to search backwards through history to try to find
2285 # where the file copy came from if the source of a copy was not in
2286 # where the file copy came from if the source of a copy was not in
2286 # the parent directory. However, this doesn't actually make sense to
2287 # the parent directory. However, this doesn't actually make sense to
2287 # do (what does a copy from something not in your working copy even
2288 # do (what does a copy from something not in your working copy even
2288 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
2289 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
2289 # the user that copy information was dropped, so if they didn't
2290 # the user that copy information was dropped, so if they didn't
2290 # expect this outcome it can be fixed, but this is the correct
2291 # expect this outcome it can be fixed, but this is the correct
2291 # behavior in this circumstance.
2292 # behavior in this circumstance.
2292
2293
2293 if crev:
2294 if crev:
2294 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
2295 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
2295 meta["copy"] = cfname
2296 meta["copy"] = cfname
2296 meta["copyrev"] = hex(crev)
2297 meta["copyrev"] = hex(crev)
2297 fparent1, fparent2 = nullid, newfparent
2298 fparent1, fparent2 = nullid, newfparent
2298 else:
2299 else:
2299 self.ui.warn(_("warning: can't find ancestor for '%s' "
2300 self.ui.warn(_("warning: can't find ancestor for '%s' "
2300 "copied from '%s'!\n") % (fname, cfname))
2301 "copied from '%s'!\n") % (fname, cfname))
2301
2302
2302 elif fparent1 == nullid:
2303 elif fparent1 == nullid:
2303 fparent1, fparent2 = fparent2, nullid
2304 fparent1, fparent2 = fparent2, nullid
2304 elif fparent2 != nullid:
2305 elif fparent2 != nullid:
2305 # is one parent an ancestor of the other?
2306 # is one parent an ancestor of the other?
2306 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
2307 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
2307 if fparent1 in fparentancestors:
2308 if fparent1 in fparentancestors:
2308 fparent1, fparent2 = fparent2, nullid
2309 fparent1, fparent2 = fparent2, nullid
2309 elif fparent2 in fparentancestors:
2310 elif fparent2 in fparentancestors:
2310 fparent2 = nullid
2311 fparent2 = nullid
2311
2312
2312 # is the file changed?
2313 # is the file changed?
2313 text = fctx.data()
2314 text = fctx.data()
2314 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
2315 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
2315 changelist.append(fname)
2316 changelist.append(fname)
2316 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
2317 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
2317 # are just the flags changed during merge?
2318 # are just the flags changed during merge?
2318 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
2319 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
2319 changelist.append(fname)
2320 changelist.append(fname)
2320
2321
2321 return fparent1
2322 return fparent1
2322
2323
2323 def checkcommitpatterns(self, wctx, vdirs, match, status, fail):
2324 def checkcommitpatterns(self, wctx, vdirs, match, status, fail):
2324 """check for commit arguments that aren't committable"""
2325 """check for commit arguments that aren't committable"""
2325 if match.isexact() or match.prefix():
2326 if match.isexact() or match.prefix():
2326 matched = set(status.modified + status.added + status.removed)
2327 matched = set(status.modified + status.added + status.removed)
2327
2328
2328 for f in match.files():
2329 for f in match.files():
2329 f = self.dirstate.normalize(f)
2330 f = self.dirstate.normalize(f)
2330 if f == '.' or f in matched or f in wctx.substate:
2331 if f == '.' or f in matched or f in wctx.substate:
2331 continue
2332 continue
2332 if f in status.deleted:
2333 if f in status.deleted:
2333 fail(f, _('file not found!'))
2334 fail(f, _('file not found!'))
2334 if f in vdirs: # visited directory
2335 if f in vdirs: # visited directory
2335 d = f + '/'
2336 d = f + '/'
2336 for mf in matched:
2337 for mf in matched:
2337 if mf.startswith(d):
2338 if mf.startswith(d):
2338 break
2339 break
2339 else:
2340 else:
2340 fail(f, _("no match under directory!"))
2341 fail(f, _("no match under directory!"))
2341 elif f not in self.dirstate:
2342 elif f not in self.dirstate:
2342 fail(f, _("file not tracked!"))
2343 fail(f, _("file not tracked!"))
2343
2344
2344 @unfilteredmethod
2345 @unfilteredmethod
2345 def commit(self, text="", user=None, date=None, match=None, force=False,
2346 def commit(self, text="", user=None, date=None, match=None, force=False,
2346 editor=False, extra=None):
2347 editor=False, extra=None):
2347 """Add a new revision to current repository.
2348 """Add a new revision to current repository.
2348
2349
2349 Revision information is gathered from the working directory,
2350 Revision information is gathered from the working directory,
2350 match can be used to filter the committed files. If editor is
2351 match can be used to filter the committed files. If editor is
2351 supplied, it is called to get a commit message.
2352 supplied, it is called to get a commit message.
2352 """
2353 """
2353 if extra is None:
2354 if extra is None:
2354 extra = {}
2355 extra = {}
2355
2356
2356 def fail(f, msg):
2357 def fail(f, msg):
2357 raise error.Abort('%s: %s' % (f, msg))
2358 raise error.Abort('%s: %s' % (f, msg))
2358
2359
2359 if not match:
2360 if not match:
2360 match = matchmod.always(self.root, '')
2361 match = matchmod.always(self.root, '')
2361
2362
2362 if not force:
2363 if not force:
2363 vdirs = []
2364 vdirs = []
2364 match.explicitdir = vdirs.append
2365 match.explicitdir = vdirs.append
2365 match.bad = fail
2366 match.bad = fail
2366
2367
2367 wlock = lock = tr = None
2368 wlock = lock = tr = None
2368 try:
2369 try:
2369 wlock = self.wlock()
2370 wlock = self.wlock()
2370 lock = self.lock() # for recent changelog (see issue4368)
2371 lock = self.lock() # for recent changelog (see issue4368)
2371
2372
2372 wctx = self[None]
2373 wctx = self[None]
2373 merge = len(wctx.parents()) > 1
2374 merge = len(wctx.parents()) > 1
2374
2375
2375 if not force and merge and not match.always():
2376 if not force and merge and not match.always():
2376 raise error.Abort(_('cannot partially commit a merge '
2377 raise error.Abort(_('cannot partially commit a merge '
2377 '(do not specify files or patterns)'))
2378 '(do not specify files or patterns)'))
2378
2379
2379 status = self.status(match=match, clean=force)
2380 status = self.status(match=match, clean=force)
2380 if force:
2381 if force:
2381 status.modified.extend(status.clean) # mq may commit clean files
2382 status.modified.extend(status.clean) # mq may commit clean files
2382
2383
2383 # check subrepos
2384 # check subrepos
2384 subs, commitsubs, newstate = subrepoutil.precommit(
2385 subs, commitsubs, newstate = subrepoutil.precommit(
2385 self.ui, wctx, status, match, force=force)
2386 self.ui, wctx, status, match, force=force)
2386
2387
2387 # make sure all explicit patterns are matched
2388 # make sure all explicit patterns are matched
2388 if not force:
2389 if not force:
2389 self.checkcommitpatterns(wctx, vdirs, match, status, fail)
2390 self.checkcommitpatterns(wctx, vdirs, match, status, fail)
2390
2391
2391 cctx = context.workingcommitctx(self, status,
2392 cctx = context.workingcommitctx(self, status,
2392 text, user, date, extra)
2393 text, user, date, extra)
2393
2394
2394 # internal config: ui.allowemptycommit
2395 # internal config: ui.allowemptycommit
2395 allowemptycommit = (wctx.branch() != wctx.p1().branch()
2396 allowemptycommit = (wctx.branch() != wctx.p1().branch()
2396 or extra.get('close') or merge or cctx.files()
2397 or extra.get('close') or merge or cctx.files()
2397 or self.ui.configbool('ui', 'allowemptycommit'))
2398 or self.ui.configbool('ui', 'allowemptycommit'))
2398 if not allowemptycommit:
2399 if not allowemptycommit:
2399 return None
2400 return None
2400
2401
2401 if merge and cctx.deleted():
2402 if merge and cctx.deleted():
2402 raise error.Abort(_("cannot commit merge with missing files"))
2403 raise error.Abort(_("cannot commit merge with missing files"))
2403
2404
2404 ms = mergemod.mergestate.read(self)
2405 ms = mergemod.mergestate.read(self)
2405 mergeutil.checkunresolved(ms)
2406 mergeutil.checkunresolved(ms)
2406
2407
2407 if editor:
2408 if editor:
2408 cctx._text = editor(self, cctx, subs)
2409 cctx._text = editor(self, cctx, subs)
2409 edited = (text != cctx._text)
2410 edited = (text != cctx._text)
2410
2411
2411 # Save commit message in case this transaction gets rolled back
2412 # Save commit message in case this transaction gets rolled back
2412 # (e.g. by a pretxncommit hook). Leave the content alone on
2413 # (e.g. by a pretxncommit hook). Leave the content alone on
2413 # the assumption that the user will use the same editor again.
2414 # the assumption that the user will use the same editor again.
2414 msgfn = self.savecommitmessage(cctx._text)
2415 msgfn = self.savecommitmessage(cctx._text)
2415
2416
2416 # commit subs and write new state
2417 # commit subs and write new state
2417 if subs:
2418 if subs:
2418 for s in sorted(commitsubs):
2419 for s in sorted(commitsubs):
2419 sub = wctx.sub(s)
2420 sub = wctx.sub(s)
2420 self.ui.status(_('committing subrepository %s\n') %
2421 self.ui.status(_('committing subrepository %s\n') %
2421 subrepoutil.subrelpath(sub))
2422 subrepoutil.subrelpath(sub))
2422 sr = sub.commit(cctx._text, user, date)
2423 sr = sub.commit(cctx._text, user, date)
2423 newstate[s] = (newstate[s][0], sr)
2424 newstate[s] = (newstate[s][0], sr)
2424 subrepoutil.writestate(self, newstate)
2425 subrepoutil.writestate(self, newstate)
2425
2426
2426 p1, p2 = self.dirstate.parents()
2427 p1, p2 = self.dirstate.parents()
2427 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
2428 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
2428 try:
2429 try:
2429 self.hook("precommit", throw=True, parent1=hookp1,
2430 self.hook("precommit", throw=True, parent1=hookp1,
2430 parent2=hookp2)
2431 parent2=hookp2)
2431 tr = self.transaction('commit')
2432 tr = self.transaction('commit')
2432 ret = self.commitctx(cctx, True)
2433 ret = self.commitctx(cctx, True)
2433 except: # re-raises
2434 except: # re-raises
2434 if edited:
2435 if edited:
2435 self.ui.write(
2436 self.ui.write(
2436 _('note: commit message saved in %s\n') % msgfn)
2437 _('note: commit message saved in %s\n') % msgfn)
2437 raise
2438 raise
2438 # update bookmarks, dirstate and mergestate
2439 # update bookmarks, dirstate and mergestate
2439 bookmarks.update(self, [p1, p2], ret)
2440 bookmarks.update(self, [p1, p2], ret)
2440 cctx.markcommitted(ret)
2441 cctx.markcommitted(ret)
2441 ms.reset()
2442 ms.reset()
2442 tr.close()
2443 tr.close()
2443
2444
2444 finally:
2445 finally:
2445 lockmod.release(tr, lock, wlock)
2446 lockmod.release(tr, lock, wlock)
2446
2447
2447 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
2448 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
2448 # hack for command that use a temporary commit (eg: histedit)
2449 # hack for command that use a temporary commit (eg: histedit)
2449 # temporary commit got stripped before hook release
2450 # temporary commit got stripped before hook release
2450 if self.changelog.hasnode(ret):
2451 if self.changelog.hasnode(ret):
2451 self.hook("commit", node=node, parent1=parent1,
2452 self.hook("commit", node=node, parent1=parent1,
2452 parent2=parent2)
2453 parent2=parent2)
2453 self._afterlock(commithook)
2454 self._afterlock(commithook)
2454 return ret
2455 return ret
2455
2456
2456 @unfilteredmethod
2457 @unfilteredmethod
2457 def commitctx(self, ctx, error=False):
2458 def commitctx(self, ctx, error=False):
2458 """Add a new revision to current repository.
2459 """Add a new revision to current repository.
2459 Revision information is passed via the context argument.
2460 Revision information is passed via the context argument.
2460
2461
2461 ctx.files() should list all files involved in this commit, i.e.
2462 ctx.files() should list all files involved in this commit, i.e.
2462 modified/added/removed files. On merge, it may be wider than the
2463 modified/added/removed files. On merge, it may be wider than the
2463 ctx.files() to be committed, since any file nodes derived directly
2464 ctx.files() to be committed, since any file nodes derived directly
2464 from p1 or p2 are excluded from the committed ctx.files().
2465 from p1 or p2 are excluded from the committed ctx.files().
2465 """
2466 """
2466
2467
2467 tr = None
2468 tr = None
2468 p1, p2 = ctx.p1(), ctx.p2()
2469 p1, p2 = ctx.p1(), ctx.p2()
2469 user = ctx.user()
2470 user = ctx.user()
2470
2471
2471 lock = self.lock()
2472 lock = self.lock()
2472 try:
2473 try:
2473 tr = self.transaction("commit")
2474 tr = self.transaction("commit")
2474 trp = weakref.proxy(tr)
2475 trp = weakref.proxy(tr)
2475
2476
2476 if ctx.manifestnode():
2477 if ctx.manifestnode():
2477 # reuse an existing manifest revision
2478 # reuse an existing manifest revision
2478 self.ui.debug('reusing known manifest\n')
2479 self.ui.debug('reusing known manifest\n')
2479 mn = ctx.manifestnode()
2480 mn = ctx.manifestnode()
2480 files = ctx.files()
2481 files = ctx.files()
2481 elif ctx.files():
2482 elif ctx.files():
2482 m1ctx = p1.manifestctx()
2483 m1ctx = p1.manifestctx()
2483 m2ctx = p2.manifestctx()
2484 m2ctx = p2.manifestctx()
2484 mctx = m1ctx.copy()
2485 mctx = m1ctx.copy()
2485
2486
2486 m = mctx.read()
2487 m = mctx.read()
2487 m1 = m1ctx.read()
2488 m1 = m1ctx.read()
2488 m2 = m2ctx.read()
2489 m2 = m2ctx.read()
2489
2490
2490 # check in files
2491 # check in files
2491 added = []
2492 added = []
2492 changed = []
2493 changed = []
2493 removed = list(ctx.removed())
2494 removed = list(ctx.removed())
2494 linkrev = len(self)
2495 linkrev = len(self)
2495 self.ui.note(_("committing files:\n"))
2496 self.ui.note(_("committing files:\n"))
2496 for f in sorted(ctx.modified() + ctx.added()):
2497 for f in sorted(ctx.modified() + ctx.added()):
2497 self.ui.note(f + "\n")
2498 self.ui.note(f + "\n")
2498 try:
2499 try:
2499 fctx = ctx[f]
2500 fctx = ctx[f]
2500 if fctx is None:
2501 if fctx is None:
2501 removed.append(f)
2502 removed.append(f)
2502 else:
2503 else:
2503 added.append(f)
2504 added.append(f)
2504 m[f] = self._filecommit(fctx, m1, m2, linkrev,
2505 m[f] = self._filecommit(fctx, m1, m2, linkrev,
2505 trp, changed)
2506 trp, changed)
2506 m.setflag(f, fctx.flags())
2507 m.setflag(f, fctx.flags())
2507 except OSError as inst:
2508 except OSError as inst:
2508 self.ui.warn(_("trouble committing %s!\n") % f)
2509 self.ui.warn(_("trouble committing %s!\n") % f)
2509 raise
2510 raise
2510 except IOError as inst:
2511 except IOError as inst:
2511 errcode = getattr(inst, 'errno', errno.ENOENT)
2512 errcode = getattr(inst, 'errno', errno.ENOENT)
2512 if error or errcode and errcode != errno.ENOENT:
2513 if error or errcode and errcode != errno.ENOENT:
2513 self.ui.warn(_("trouble committing %s!\n") % f)
2514 self.ui.warn(_("trouble committing %s!\n") % f)
2514 raise
2515 raise
2515
2516
2516 # update manifest
2517 # update manifest
2517 removed = [f for f in sorted(removed) if f in m1 or f in m2]
2518 removed = [f for f in sorted(removed) if f in m1 or f in m2]
2518 drop = [f for f in removed if f in m]
2519 drop = [f for f in removed if f in m]
2519 for f in drop:
2520 for f in drop:
2520 del m[f]
2521 del m[f]
2521 files = changed + removed
2522 files = changed + removed
2522 md = None
2523 md = None
2523 if not files:
2524 if not files:
2524 # if no "files" actually changed in terms of the changelog,
2525 # if no "files" actually changed in terms of the changelog,
2525 # try hard to detect unmodified manifest entry so that the
2526 # try hard to detect unmodified manifest entry so that the
2526 # exact same commit can be reproduced later on convert.
2527 # exact same commit can be reproduced later on convert.
2527 md = m1.diff(m, scmutil.matchfiles(self, ctx.files()))
2528 md = m1.diff(m, scmutil.matchfiles(self, ctx.files()))
2528 if not files and md:
2529 if not files and md:
2529 self.ui.debug('not reusing manifest (no file change in '
2530 self.ui.debug('not reusing manifest (no file change in '
2530 'changelog, but manifest differs)\n')
2531 'changelog, but manifest differs)\n')
2531 if files or md:
2532 if files or md:
2532 self.ui.note(_("committing manifest\n"))
2533 self.ui.note(_("committing manifest\n"))
2533 # we're using narrowmatch here since it's already applied at
2534 # we're using narrowmatch here since it's already applied at
2534 # other stages (such as dirstate.walk), so we're already
2535 # other stages (such as dirstate.walk), so we're already
2535 # ignoring things outside of narrowspec in most cases. The
2536 # ignoring things outside of narrowspec in most cases. The
2536 # one case where we might have files outside the narrowspec
2537 # one case where we might have files outside the narrowspec
2537 # at this point is merges, and we already error out in the
2538 # at this point is merges, and we already error out in the
2538 # case where the merge has files outside of the narrowspec,
2539 # case where the merge has files outside of the narrowspec,
2539 # so this is safe.
2540 # so this is safe.
2540 mn = mctx.write(trp, linkrev,
2541 mn = mctx.write(trp, linkrev,
2541 p1.manifestnode(), p2.manifestnode(),
2542 p1.manifestnode(), p2.manifestnode(),
2542 added, drop, match=self.narrowmatch())
2543 added, drop, match=self.narrowmatch())
2543 else:
2544 else:
2544 self.ui.debug('reusing manifest form p1 (listed files '
2545 self.ui.debug('reusing manifest form p1 (listed files '
2545 'actually unchanged)\n')
2546 'actually unchanged)\n')
2546 mn = p1.manifestnode()
2547 mn = p1.manifestnode()
2547 else:
2548 else:
2548 self.ui.debug('reusing manifest from p1 (no file change)\n')
2549 self.ui.debug('reusing manifest from p1 (no file change)\n')
2549 mn = p1.manifestnode()
2550 mn = p1.manifestnode()
2550 files = []
2551 files = []
2551
2552
2552 # update changelog
2553 # update changelog
2553 self.ui.note(_("committing changelog\n"))
2554 self.ui.note(_("committing changelog\n"))
2554 self.changelog.delayupdate(tr)
2555 self.changelog.delayupdate(tr)
2555 n = self.changelog.add(mn, files, ctx.description(),
2556 n = self.changelog.add(mn, files, ctx.description(),
2556 trp, p1.node(), p2.node(),
2557 trp, p1.node(), p2.node(),
2557 user, ctx.date(), ctx.extra().copy())
2558 user, ctx.date(), ctx.extra().copy())
2558 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
2559 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
2559 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
2560 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
2560 parent2=xp2)
2561 parent2=xp2)
2561 # set the new commit is proper phase
2562 # set the new commit is proper phase
2562 targetphase = subrepoutil.newcommitphase(self.ui, ctx)
2563 targetphase = subrepoutil.newcommitphase(self.ui, ctx)
2563 if targetphase:
2564 if targetphase:
2564 # retract boundary do not alter parent changeset.
2565 # retract boundary do not alter parent changeset.
2565 # if a parent have higher the resulting phase will
2566 # if a parent have higher the resulting phase will
2566 # be compliant anyway
2567 # be compliant anyway
2567 #
2568 #
2568 # if minimal phase was 0 we don't need to retract anything
2569 # if minimal phase was 0 we don't need to retract anything
2569 phases.registernew(self, tr, targetphase, [n])
2570 phases.registernew(self, tr, targetphase, [n])
2570 tr.close()
2571 tr.close()
2571 return n
2572 return n
2572 finally:
2573 finally:
2573 if tr:
2574 if tr:
2574 tr.release()
2575 tr.release()
2575 lock.release()
2576 lock.release()
2576
2577
2577 @unfilteredmethod
2578 @unfilteredmethod
2578 def destroying(self):
2579 def destroying(self):
2579 '''Inform the repository that nodes are about to be destroyed.
2580 '''Inform the repository that nodes are about to be destroyed.
2580 Intended for use by strip and rollback, so there's a common
2581 Intended for use by strip and rollback, so there's a common
2581 place for anything that has to be done before destroying history.
2582 place for anything that has to be done before destroying history.
2582
2583
2583 This is mostly useful for saving state that is in memory and waiting
2584 This is mostly useful for saving state that is in memory and waiting
2584 to be flushed when the current lock is released. Because a call to
2585 to be flushed when the current lock is released. Because a call to
2585 destroyed is imminent, the repo will be invalidated causing those
2586 destroyed is imminent, the repo will be invalidated causing those
2586 changes to stay in memory (waiting for the next unlock), or vanish
2587 changes to stay in memory (waiting for the next unlock), or vanish
2587 completely.
2588 completely.
2588 '''
2589 '''
2589 # When using the same lock to commit and strip, the phasecache is left
2590 # When using the same lock to commit and strip, the phasecache is left
2590 # dirty after committing. Then when we strip, the repo is invalidated,
2591 # dirty after committing. Then when we strip, the repo is invalidated,
2591 # causing those changes to disappear.
2592 # causing those changes to disappear.
2592 if '_phasecache' in vars(self):
2593 if '_phasecache' in vars(self):
2593 self._phasecache.write()
2594 self._phasecache.write()
2594
2595
2595 @unfilteredmethod
2596 @unfilteredmethod
2596 def destroyed(self):
2597 def destroyed(self):
2597 '''Inform the repository that nodes have been destroyed.
2598 '''Inform the repository that nodes have been destroyed.
2598 Intended for use by strip and rollback, so there's a common
2599 Intended for use by strip and rollback, so there's a common
2599 place for anything that has to be done after destroying history.
2600 place for anything that has to be done after destroying history.
2600 '''
2601 '''
2601 # When one tries to:
2602 # When one tries to:
2602 # 1) destroy nodes thus calling this method (e.g. strip)
2603 # 1) destroy nodes thus calling this method (e.g. strip)
2603 # 2) use phasecache somewhere (e.g. commit)
2604 # 2) use phasecache somewhere (e.g. commit)
2604 #
2605 #
2605 # then 2) will fail because the phasecache contains nodes that were
2606 # then 2) will fail because the phasecache contains nodes that were
2606 # removed. We can either remove phasecache from the filecache,
2607 # removed. We can either remove phasecache from the filecache,
2607 # causing it to reload next time it is accessed, or simply filter
2608 # causing it to reload next time it is accessed, or simply filter
2608 # the removed nodes now and write the updated cache.
2609 # the removed nodes now and write the updated cache.
2609 self._phasecache.filterunknown(self)
2610 self._phasecache.filterunknown(self)
2610 self._phasecache.write()
2611 self._phasecache.write()
2611
2612
2612 # refresh all repository caches
2613 # refresh all repository caches
2613 self.updatecaches()
2614 self.updatecaches()
2614
2615
2615 # Ensure the persistent tag cache is updated. Doing it now
2616 # Ensure the persistent tag cache is updated. Doing it now
2616 # means that the tag cache only has to worry about destroyed
2617 # means that the tag cache only has to worry about destroyed
2617 # heads immediately after a strip/rollback. That in turn
2618 # heads immediately after a strip/rollback. That in turn
2618 # guarantees that "cachetip == currenttip" (comparing both rev
2619 # guarantees that "cachetip == currenttip" (comparing both rev
2619 # and node) always means no nodes have been added or destroyed.
2620 # and node) always means no nodes have been added or destroyed.
2620
2621
2621 # XXX this is suboptimal when qrefresh'ing: we strip the current
2622 # XXX this is suboptimal when qrefresh'ing: we strip the current
2622 # head, refresh the tag cache, then immediately add a new head.
2623 # head, refresh the tag cache, then immediately add a new head.
2623 # But I think doing it this way is necessary for the "instant
2624 # But I think doing it this way is necessary for the "instant
2624 # tag cache retrieval" case to work.
2625 # tag cache retrieval" case to work.
2625 self.invalidate()
2626 self.invalidate()
2626
2627
2627 def status(self, node1='.', node2=None, match=None,
2628 def status(self, node1='.', node2=None, match=None,
2628 ignored=False, clean=False, unknown=False,
2629 ignored=False, clean=False, unknown=False,
2629 listsubrepos=False):
2630 listsubrepos=False):
2630 '''a convenience method that calls node1.status(node2)'''
2631 '''a convenience method that calls node1.status(node2)'''
2631 return self[node1].status(node2, match, ignored, clean, unknown,
2632 return self[node1].status(node2, match, ignored, clean, unknown,
2632 listsubrepos)
2633 listsubrepos)
2633
2634
2634 def addpostdsstatus(self, ps):
2635 def addpostdsstatus(self, ps):
2635 """Add a callback to run within the wlock, at the point at which status
2636 """Add a callback to run within the wlock, at the point at which status
2636 fixups happen.
2637 fixups happen.
2637
2638
2638 On status completion, callback(wctx, status) will be called with the
2639 On status completion, callback(wctx, status) will be called with the
2639 wlock held, unless the dirstate has changed from underneath or the wlock
2640 wlock held, unless the dirstate has changed from underneath or the wlock
2640 couldn't be grabbed.
2641 couldn't be grabbed.
2641
2642
2642 Callbacks should not capture and use a cached copy of the dirstate --
2643 Callbacks should not capture and use a cached copy of the dirstate --
2643 it might change in the meanwhile. Instead, they should access the
2644 it might change in the meanwhile. Instead, they should access the
2644 dirstate via wctx.repo().dirstate.
2645 dirstate via wctx.repo().dirstate.
2645
2646
2646 This list is emptied out after each status run -- extensions should
2647 This list is emptied out after each status run -- extensions should
2647 make sure it adds to this list each time dirstate.status is called.
2648 make sure it adds to this list each time dirstate.status is called.
2648 Extensions should also make sure they don't call this for statuses
2649 Extensions should also make sure they don't call this for statuses
2649 that don't involve the dirstate.
2650 that don't involve the dirstate.
2650 """
2651 """
2651
2652
2652 # The list is located here for uniqueness reasons -- it is actually
2653 # The list is located here for uniqueness reasons -- it is actually
2653 # managed by the workingctx, but that isn't unique per-repo.
2654 # managed by the workingctx, but that isn't unique per-repo.
2654 self._postdsstatus.append(ps)
2655 self._postdsstatus.append(ps)
2655
2656
2656 def postdsstatus(self):
2657 def postdsstatus(self):
2657 """Used by workingctx to get the list of post-dirstate-status hooks."""
2658 """Used by workingctx to get the list of post-dirstate-status hooks."""
2658 return self._postdsstatus
2659 return self._postdsstatus
2659
2660
2660 def clearpostdsstatus(self):
2661 def clearpostdsstatus(self):
2661 """Used by workingctx to clear post-dirstate-status hooks."""
2662 """Used by workingctx to clear post-dirstate-status hooks."""
2662 del self._postdsstatus[:]
2663 del self._postdsstatus[:]
2663
2664
2664 def heads(self, start=None):
2665 def heads(self, start=None):
2665 if start is None:
2666 if start is None:
2666 cl = self.changelog
2667 cl = self.changelog
2667 headrevs = reversed(cl.headrevs())
2668 headrevs = reversed(cl.headrevs())
2668 return [cl.node(rev) for rev in headrevs]
2669 return [cl.node(rev) for rev in headrevs]
2669
2670
2670 heads = self.changelog.heads(start)
2671 heads = self.changelog.heads(start)
2671 # sort the output in rev descending order
2672 # sort the output in rev descending order
2672 return sorted(heads, key=self.changelog.rev, reverse=True)
2673 return sorted(heads, key=self.changelog.rev, reverse=True)
2673
2674
2674 def branchheads(self, branch=None, start=None, closed=False):
2675 def branchheads(self, branch=None, start=None, closed=False):
2675 '''return a (possibly filtered) list of heads for the given branch
2676 '''return a (possibly filtered) list of heads for the given branch
2676
2677
2677 Heads are returned in topological order, from newest to oldest.
2678 Heads are returned in topological order, from newest to oldest.
2678 If branch is None, use the dirstate branch.
2679 If branch is None, use the dirstate branch.
2679 If start is not None, return only heads reachable from start.
2680 If start is not None, return only heads reachable from start.
2680 If closed is True, return heads that are marked as closed as well.
2681 If closed is True, return heads that are marked as closed as well.
2681 '''
2682 '''
2682 if branch is None:
2683 if branch is None:
2683 branch = self[None].branch()
2684 branch = self[None].branch()
2684 branches = self.branchmap()
2685 branches = self.branchmap()
2685 if branch not in branches:
2686 if branch not in branches:
2686 return []
2687 return []
2687 # the cache returns heads ordered lowest to highest
2688 # the cache returns heads ordered lowest to highest
2688 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
2689 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
2689 if start is not None:
2690 if start is not None:
2690 # filter out the heads that cannot be reached from startrev
2691 # filter out the heads that cannot be reached from startrev
2691 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
2692 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
2692 bheads = [h for h in bheads if h in fbheads]
2693 bheads = [h for h in bheads if h in fbheads]
2693 return bheads
2694 return bheads
2694
2695
2695 def branches(self, nodes):
2696 def branches(self, nodes):
2696 if not nodes:
2697 if not nodes:
2697 nodes = [self.changelog.tip()]
2698 nodes = [self.changelog.tip()]
2698 b = []
2699 b = []
2699 for n in nodes:
2700 for n in nodes:
2700 t = n
2701 t = n
2701 while True:
2702 while True:
2702 p = self.changelog.parents(n)
2703 p = self.changelog.parents(n)
2703 if p[1] != nullid or p[0] == nullid:
2704 if p[1] != nullid or p[0] == nullid:
2704 b.append((t, n, p[0], p[1]))
2705 b.append((t, n, p[0], p[1]))
2705 break
2706 break
2706 n = p[0]
2707 n = p[0]
2707 return b
2708 return b
2708
2709
2709 def between(self, pairs):
2710 def between(self, pairs):
2710 r = []
2711 r = []
2711
2712
2712 for top, bottom in pairs:
2713 for top, bottom in pairs:
2713 n, l, i = top, [], 0
2714 n, l, i = top, [], 0
2714 f = 1
2715 f = 1
2715
2716
2716 while n != bottom and n != nullid:
2717 while n != bottom and n != nullid:
2717 p = self.changelog.parents(n)[0]
2718 p = self.changelog.parents(n)[0]
2718 if i == f:
2719 if i == f:
2719 l.append(n)
2720 l.append(n)
2720 f = f * 2
2721 f = f * 2
2721 n = p
2722 n = p
2722 i += 1
2723 i += 1
2723
2724
2724 r.append(l)
2725 r.append(l)
2725
2726
2726 return r
2727 return r
2727
2728
2728 def checkpush(self, pushop):
2729 def checkpush(self, pushop):
2729 """Extensions can override this function if additional checks have
2730 """Extensions can override this function if additional checks have
2730 to be performed before pushing, or call it if they override push
2731 to be performed before pushing, or call it if they override push
2731 command.
2732 command.
2732 """
2733 """
2733
2734
2734 @unfilteredpropertycache
2735 @unfilteredpropertycache
2735 def prepushoutgoinghooks(self):
2736 def prepushoutgoinghooks(self):
2736 """Return util.hooks consists of a pushop with repo, remote, outgoing
2737 """Return util.hooks consists of a pushop with repo, remote, outgoing
2737 methods, which are called before pushing changesets.
2738 methods, which are called before pushing changesets.
2738 """
2739 """
2739 return util.hooks()
2740 return util.hooks()
2740
2741
2741 def pushkey(self, namespace, key, old, new):
2742 def pushkey(self, namespace, key, old, new):
2742 try:
2743 try:
2743 tr = self.currenttransaction()
2744 tr = self.currenttransaction()
2744 hookargs = {}
2745 hookargs = {}
2745 if tr is not None:
2746 if tr is not None:
2746 hookargs.update(tr.hookargs)
2747 hookargs.update(tr.hookargs)
2747 hookargs = pycompat.strkwargs(hookargs)
2748 hookargs = pycompat.strkwargs(hookargs)
2748 hookargs[r'namespace'] = namespace
2749 hookargs[r'namespace'] = namespace
2749 hookargs[r'key'] = key
2750 hookargs[r'key'] = key
2750 hookargs[r'old'] = old
2751 hookargs[r'old'] = old
2751 hookargs[r'new'] = new
2752 hookargs[r'new'] = new
2752 self.hook('prepushkey', throw=True, **hookargs)
2753 self.hook('prepushkey', throw=True, **hookargs)
2753 except error.HookAbort as exc:
2754 except error.HookAbort as exc:
2754 self.ui.write_err(_("pushkey-abort: %s\n") % exc)
2755 self.ui.write_err(_("pushkey-abort: %s\n") % exc)
2755 if exc.hint:
2756 if exc.hint:
2756 self.ui.write_err(_("(%s)\n") % exc.hint)
2757 self.ui.write_err(_("(%s)\n") % exc.hint)
2757 return False
2758 return False
2758 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
2759 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
2759 ret = pushkey.push(self, namespace, key, old, new)
2760 ret = pushkey.push(self, namespace, key, old, new)
2760 def runhook():
2761 def runhook():
2761 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
2762 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
2762 ret=ret)
2763 ret=ret)
2763 self._afterlock(runhook)
2764 self._afterlock(runhook)
2764 return ret
2765 return ret
2765
2766
2766 def listkeys(self, namespace):
2767 def listkeys(self, namespace):
2767 self.hook('prelistkeys', throw=True, namespace=namespace)
2768 self.hook('prelistkeys', throw=True, namespace=namespace)
2768 self.ui.debug('listing keys for "%s"\n' % namespace)
2769 self.ui.debug('listing keys for "%s"\n' % namespace)
2769 values = pushkey.list(self, namespace)
2770 values = pushkey.list(self, namespace)
2770 self.hook('listkeys', namespace=namespace, values=values)
2771 self.hook('listkeys', namespace=namespace, values=values)
2771 return values
2772 return values
2772
2773
2773 def debugwireargs(self, one, two, three=None, four=None, five=None):
2774 def debugwireargs(self, one, two, three=None, four=None, five=None):
2774 '''used to test argument passing over the wire'''
2775 '''used to test argument passing over the wire'''
2775 return "%s %s %s %s %s" % (one, two, pycompat.bytestr(three),
2776 return "%s %s %s %s %s" % (one, two, pycompat.bytestr(three),
2776 pycompat.bytestr(four),
2777 pycompat.bytestr(four),
2777 pycompat.bytestr(five))
2778 pycompat.bytestr(five))
2778
2779
2779 def savecommitmessage(self, text):
2780 def savecommitmessage(self, text):
2780 fp = self.vfs('last-message.txt', 'wb')
2781 fp = self.vfs('last-message.txt', 'wb')
2781 try:
2782 try:
2782 fp.write(text)
2783 fp.write(text)
2783 finally:
2784 finally:
2784 fp.close()
2785 fp.close()
2785 return self.pathto(fp.name[len(self.root) + 1:])
2786 return self.pathto(fp.name[len(self.root) + 1:])
2786
2787
2787 # used to avoid circular references so destructors work
2788 # used to avoid circular references so destructors work
2788 def aftertrans(files):
2789 def aftertrans(files):
2789 renamefiles = [tuple(t) for t in files]
2790 renamefiles = [tuple(t) for t in files]
2790 def a():
2791 def a():
2791 for vfs, src, dest in renamefiles:
2792 for vfs, src, dest in renamefiles:
2792 # if src and dest refer to a same file, vfs.rename is a no-op,
2793 # if src and dest refer to a same file, vfs.rename is a no-op,
2793 # leaving both src and dest on disk. delete dest to make sure
2794 # leaving both src and dest on disk. delete dest to make sure
2794 # the rename couldn't be such a no-op.
2795 # the rename couldn't be such a no-op.
2795 vfs.tryunlink(dest)
2796 vfs.tryunlink(dest)
2796 try:
2797 try:
2797 vfs.rename(src, dest)
2798 vfs.rename(src, dest)
2798 except OSError: # journal file does not yet exist
2799 except OSError: # journal file does not yet exist
2799 pass
2800 pass
2800 return a
2801 return a
2801
2802
2802 def undoname(fn):
2803 def undoname(fn):
2803 base, name = os.path.split(fn)
2804 base, name = os.path.split(fn)
2804 assert name.startswith('journal')
2805 assert name.startswith('journal')
2805 return os.path.join(base, name.replace('journal', 'undo', 1))
2806 return os.path.join(base, name.replace('journal', 'undo', 1))
2806
2807
2807 def instance(ui, path, create, intents=None, createopts=None):
2808 def instance(ui, path, create, intents=None, createopts=None):
2808 localpath = util.urllocalpath(path)
2809 localpath = util.urllocalpath(path)
2809 if create:
2810 if create:
2810 createrepository(ui, localpath, createopts=createopts)
2811 createrepository(ui, localpath, createopts=createopts)
2811
2812
2812 return makelocalrepository(ui, localpath, intents=intents)
2813 return makelocalrepository(ui, localpath, intents=intents)
2813
2814
2814 def islocal(path):
2815 def islocal(path):
2815 return True
2816 return True
2816
2817
2817 def defaultcreateopts(ui, createopts=None):
2818 def defaultcreateopts(ui, createopts=None):
2818 """Populate the default creation options for a repository.
2819 """Populate the default creation options for a repository.
2819
2820
2820 A dictionary of explicitly requested creation options can be passed
2821 A dictionary of explicitly requested creation options can be passed
2821 in. Missing keys will be populated.
2822 in. Missing keys will be populated.
2822 """
2823 """
2823 createopts = dict(createopts or {})
2824 createopts = dict(createopts or {})
2824
2825
2825 if 'backend' not in createopts:
2826 if 'backend' not in createopts:
2826 # experimental config: storage.new-repo-backend
2827 # experimental config: storage.new-repo-backend
2827 createopts['backend'] = ui.config('storage', 'new-repo-backend')
2828 createopts['backend'] = ui.config('storage', 'new-repo-backend')
2828
2829
2829 return createopts
2830 return createopts
2830
2831
2831 def newreporequirements(ui, createopts):
2832 def newreporequirements(ui, createopts):
2832 """Determine the set of requirements for a new local repository.
2833 """Determine the set of requirements for a new local repository.
2833
2834
2834 Extensions can wrap this function to specify custom requirements for
2835 Extensions can wrap this function to specify custom requirements for
2835 new repositories.
2836 new repositories.
2836 """
2837 """
2837 # If the repo is being created from a shared repository, we copy
2838 # If the repo is being created from a shared repository, we copy
2838 # its requirements.
2839 # its requirements.
2839 if 'sharedrepo' in createopts:
2840 if 'sharedrepo' in createopts:
2840 requirements = set(createopts['sharedrepo'].requirements)
2841 requirements = set(createopts['sharedrepo'].requirements)
2841 if createopts.get('sharedrelative'):
2842 if createopts.get('sharedrelative'):
2842 requirements.add('relshared')
2843 requirements.add('relshared')
2843 else:
2844 else:
2844 requirements.add('shared')
2845 requirements.add('shared')
2845
2846
2846 return requirements
2847 return requirements
2847
2848
2848 if 'backend' not in createopts:
2849 if 'backend' not in createopts:
2849 raise error.ProgrammingError('backend key not present in createopts; '
2850 raise error.ProgrammingError('backend key not present in createopts; '
2850 'was defaultcreateopts() called?')
2851 'was defaultcreateopts() called?')
2851
2852
2852 if createopts['backend'] != 'revlogv1':
2853 if createopts['backend'] != 'revlogv1':
2853 raise error.Abort(_('unable to determine repository requirements for '
2854 raise error.Abort(_('unable to determine repository requirements for '
2854 'storage backend: %s') % createopts['backend'])
2855 'storage backend: %s') % createopts['backend'])
2855
2856
2856 requirements = {'revlogv1'}
2857 requirements = {'revlogv1'}
2857 if ui.configbool('format', 'usestore'):
2858 if ui.configbool('format', 'usestore'):
2858 requirements.add('store')
2859 requirements.add('store')
2859 if ui.configbool('format', 'usefncache'):
2860 if ui.configbool('format', 'usefncache'):
2860 requirements.add('fncache')
2861 requirements.add('fncache')
2861 if ui.configbool('format', 'dotencode'):
2862 if ui.configbool('format', 'dotencode'):
2862 requirements.add('dotencode')
2863 requirements.add('dotencode')
2863
2864
2864 compengine = ui.config('experimental', 'format.compression')
2865 compengine = ui.config('experimental', 'format.compression')
2865 if compengine not in util.compengines:
2866 if compengine not in util.compengines:
2866 raise error.Abort(_('compression engine %s defined by '
2867 raise error.Abort(_('compression engine %s defined by '
2867 'experimental.format.compression not available') %
2868 'experimental.format.compression not available') %
2868 compengine,
2869 compengine,
2869 hint=_('run "hg debuginstall" to list available '
2870 hint=_('run "hg debuginstall" to list available '
2870 'compression engines'))
2871 'compression engines'))
2871
2872
2872 # zlib is the historical default and doesn't need an explicit requirement.
2873 # zlib is the historical default and doesn't need an explicit requirement.
2873 if compengine != 'zlib':
2874 if compengine != 'zlib':
2874 requirements.add('exp-compression-%s' % compengine)
2875 requirements.add('exp-compression-%s' % compengine)
2875
2876
2876 if scmutil.gdinitconfig(ui):
2877 if scmutil.gdinitconfig(ui):
2877 requirements.add('generaldelta')
2878 requirements.add('generaldelta')
2878 if ui.configbool('experimental', 'treemanifest'):
2879 if ui.configbool('experimental', 'treemanifest'):
2879 requirements.add('treemanifest')
2880 requirements.add('treemanifest')
2880 # experimental config: format.sparse-revlog
2881 # experimental config: format.sparse-revlog
2881 if ui.configbool('format', 'sparse-revlog'):
2882 if ui.configbool('format', 'sparse-revlog'):
2882 requirements.add(SPARSEREVLOG_REQUIREMENT)
2883 requirements.add(SPARSEREVLOG_REQUIREMENT)
2883
2884
2884 revlogv2 = ui.config('experimental', 'revlogv2')
2885 revlogv2 = ui.config('experimental', 'revlogv2')
2885 if revlogv2 == 'enable-unstable-format-and-corrupt-my-data':
2886 if revlogv2 == 'enable-unstable-format-and-corrupt-my-data':
2886 requirements.remove('revlogv1')
2887 requirements.remove('revlogv1')
2887 # generaldelta is implied by revlogv2.
2888 # generaldelta is implied by revlogv2.
2888 requirements.discard('generaldelta')
2889 requirements.discard('generaldelta')
2889 requirements.add(REVLOGV2_REQUIREMENT)
2890 requirements.add(REVLOGV2_REQUIREMENT)
2890 # experimental config: format.internal-phase
2891 # experimental config: format.internal-phase
2891 if ui.configbool('format', 'internal-phase'):
2892 if ui.configbool('format', 'internal-phase'):
2892 requirements.add('internal-phase')
2893 requirements.add('internal-phase')
2893
2894
2894 if createopts.get('narrowfiles'):
2895 if createopts.get('narrowfiles'):
2895 requirements.add(repository.NARROW_REQUIREMENT)
2896 requirements.add(repository.NARROW_REQUIREMENT)
2896
2897
2897 return requirements
2898 return requirements
2898
2899
2899 def filterknowncreateopts(ui, createopts):
2900 def filterknowncreateopts(ui, createopts):
2900 """Filters a dict of repo creation options against options that are known.
2901 """Filters a dict of repo creation options against options that are known.
2901
2902
2902 Receives a dict of repo creation options and returns a dict of those
2903 Receives a dict of repo creation options and returns a dict of those
2903 options that we don't know how to handle.
2904 options that we don't know how to handle.
2904
2905
2905 This function is called as part of repository creation. If the
2906 This function is called as part of repository creation. If the
2906 returned dict contains any items, repository creation will not
2907 returned dict contains any items, repository creation will not
2907 be allowed, as it means there was a request to create a repository
2908 be allowed, as it means there was a request to create a repository
2908 with options not recognized by loaded code.
2909 with options not recognized by loaded code.
2909
2910
2910 Extensions can wrap this function to filter out creation options
2911 Extensions can wrap this function to filter out creation options
2911 they know how to handle.
2912 they know how to handle.
2912 """
2913 """
2913 known = {
2914 known = {
2914 'backend',
2915 'backend',
2915 'narrowfiles',
2916 'narrowfiles',
2916 'sharedrepo',
2917 'sharedrepo',
2917 'sharedrelative',
2918 'sharedrelative',
2918 'shareditems',
2919 'shareditems',
2919 }
2920 }
2920
2921
2921 return {k: v for k, v in createopts.items() if k not in known}
2922 return {k: v for k, v in createopts.items() if k not in known}
2922
2923
2923 def createrepository(ui, path, createopts=None):
2924 def createrepository(ui, path, createopts=None):
2924 """Create a new repository in a vfs.
2925 """Create a new repository in a vfs.
2925
2926
2926 ``path`` path to the new repo's working directory.
2927 ``path`` path to the new repo's working directory.
2927 ``createopts`` options for the new repository.
2928 ``createopts`` options for the new repository.
2928
2929
2929 The following keys for ``createopts`` are recognized:
2930 The following keys for ``createopts`` are recognized:
2930
2931
2931 backend
2932 backend
2932 The storage backend to use.
2933 The storage backend to use.
2933 narrowfiles
2934 narrowfiles
2934 Set up repository to support narrow file storage.
2935 Set up repository to support narrow file storage.
2935 sharedrepo
2936 sharedrepo
2936 Repository object from which storage should be shared.
2937 Repository object from which storage should be shared.
2937 sharedrelative
2938 sharedrelative
2938 Boolean indicating if the path to the shared repo should be
2939 Boolean indicating if the path to the shared repo should be
2939 stored as relative. By default, the pointer to the "parent" repo
2940 stored as relative. By default, the pointer to the "parent" repo
2940 is stored as an absolute path.
2941 is stored as an absolute path.
2941 shareditems
2942 shareditems
2942 Set of items to share to the new repository (in addition to storage).
2943 Set of items to share to the new repository (in addition to storage).
2943 """
2944 """
2944 createopts = defaultcreateopts(ui, createopts=createopts)
2945 createopts = defaultcreateopts(ui, createopts=createopts)
2945
2946
2946 unknownopts = filterknowncreateopts(ui, createopts)
2947 unknownopts = filterknowncreateopts(ui, createopts)
2947
2948
2948 if not isinstance(unknownopts, dict):
2949 if not isinstance(unknownopts, dict):
2949 raise error.ProgrammingError('filterknowncreateopts() did not return '
2950 raise error.ProgrammingError('filterknowncreateopts() did not return '
2950 'a dict')
2951 'a dict')
2951
2952
2952 if unknownopts:
2953 if unknownopts:
2953 raise error.Abort(_('unable to create repository because of unknown '
2954 raise error.Abort(_('unable to create repository because of unknown '
2954 'creation option: %s') %
2955 'creation option: %s') %
2955 ', '.join(sorted(unknownopts)),
2956 ', '.join(sorted(unknownopts)),
2956 hint=_('is a required extension not loaded?'))
2957 hint=_('is a required extension not loaded?'))
2957
2958
2958 requirements = newreporequirements(ui, createopts=createopts)
2959 requirements = newreporequirements(ui, createopts=createopts)
2959
2960
2960 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
2961 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
2961
2962
2962 hgvfs = vfsmod.vfs(wdirvfs.join(b'.hg'))
2963 hgvfs = vfsmod.vfs(wdirvfs.join(b'.hg'))
2963 if hgvfs.exists():
2964 if hgvfs.exists():
2964 raise error.RepoError(_('repository %s already exists') % path)
2965 raise error.RepoError(_('repository %s already exists') % path)
2965
2966
2966 if 'sharedrepo' in createopts:
2967 if 'sharedrepo' in createopts:
2967 sharedpath = createopts['sharedrepo'].sharedpath
2968 sharedpath = createopts['sharedrepo'].sharedpath
2968
2969
2969 if createopts.get('sharedrelative'):
2970 if createopts.get('sharedrelative'):
2970 try:
2971 try:
2971 sharedpath = os.path.relpath(sharedpath, hgvfs.base)
2972 sharedpath = os.path.relpath(sharedpath, hgvfs.base)
2972 except (IOError, ValueError) as e:
2973 except (IOError, ValueError) as e:
2973 # ValueError is raised on Windows if the drive letters differ
2974 # ValueError is raised on Windows if the drive letters differ
2974 # on each path.
2975 # on each path.
2975 raise error.Abort(_('cannot calculate relative path'),
2976 raise error.Abort(_('cannot calculate relative path'),
2976 hint=stringutil.forcebytestr(e))
2977 hint=stringutil.forcebytestr(e))
2977
2978
2978 if not wdirvfs.exists():
2979 if not wdirvfs.exists():
2979 wdirvfs.makedirs()
2980 wdirvfs.makedirs()
2980
2981
2981 hgvfs.makedir(notindexed=True)
2982 hgvfs.makedir(notindexed=True)
2982
2983
2983 if b'store' in requirements and 'sharedrepo' not in createopts:
2984 if b'store' in requirements and 'sharedrepo' not in createopts:
2984 hgvfs.mkdir(b'store')
2985 hgvfs.mkdir(b'store')
2985
2986
2986 # We create an invalid changelog outside the store so very old
2987 # We create an invalid changelog outside the store so very old
2987 # Mercurial versions (which didn't know about the requirements
2988 # Mercurial versions (which didn't know about the requirements
2988 # file) encounter an error on reading the changelog. This
2989 # file) encounter an error on reading the changelog. This
2989 # effectively locks out old clients and prevents them from
2990 # effectively locks out old clients and prevents them from
2990 # mucking with a repo in an unknown format.
2991 # mucking with a repo in an unknown format.
2991 #
2992 #
2992 # The revlog header has version 2, which won't be recognized by
2993 # The revlog header has version 2, which won't be recognized by
2993 # such old clients.
2994 # such old clients.
2994 hgvfs.append(b'00changelog.i',
2995 hgvfs.append(b'00changelog.i',
2995 b'\0\0\0\2 dummy changelog to prevent using the old repo '
2996 b'\0\0\0\2 dummy changelog to prevent using the old repo '
2996 b'layout')
2997 b'layout')
2997
2998
2998 scmutil.writerequires(hgvfs, requirements)
2999 scmutil.writerequires(hgvfs, requirements)
2999
3000
3000 # Write out file telling readers where to find the shared store.
3001 # Write out file telling readers where to find the shared store.
3001 if 'sharedrepo' in createopts:
3002 if 'sharedrepo' in createopts:
3002 hgvfs.write(b'sharedpath', sharedpath)
3003 hgvfs.write(b'sharedpath', sharedpath)
3003
3004
3004 if createopts.get('shareditems'):
3005 if createopts.get('shareditems'):
3005 shared = b'\n'.join(sorted(createopts['shareditems'])) + b'\n'
3006 shared = b'\n'.join(sorted(createopts['shareditems'])) + b'\n'
3006 hgvfs.write(b'shared', shared)
3007 hgvfs.write(b'shared', shared)
3007
3008
3008 def poisonrepository(repo):
3009 def poisonrepository(repo):
3009 """Poison a repository instance so it can no longer be used."""
3010 """Poison a repository instance so it can no longer be used."""
3010 # Perform any cleanup on the instance.
3011 # Perform any cleanup on the instance.
3011 repo.close()
3012 repo.close()
3012
3013
3013 # Our strategy is to replace the type of the object with one that
3014 # Our strategy is to replace the type of the object with one that
3014 # has all attribute lookups result in error.
3015 # has all attribute lookups result in error.
3015 #
3016 #
3016 # But we have to allow the close() method because some constructors
3017 # But we have to allow the close() method because some constructors
3017 # of repos call close() on repo references.
3018 # of repos call close() on repo references.
3018 class poisonedrepository(object):
3019 class poisonedrepository(object):
3019 def __getattribute__(self, item):
3020 def __getattribute__(self, item):
3020 if item == r'close':
3021 if item == r'close':
3021 return object.__getattribute__(self, item)
3022 return object.__getattribute__(self, item)
3022
3023
3023 raise error.ProgrammingError('repo instances should not be used '
3024 raise error.ProgrammingError('repo instances should not be used '
3024 'after unshare')
3025 'after unshare')
3025
3026
3026 def close(self):
3027 def close(self):
3027 pass
3028 pass
3028
3029
3029 # We may have a repoview, which intercepts __setattr__. So be sure
3030 # We may have a repoview, which intercepts __setattr__. So be sure
3030 # we operate at the lowest level possible.
3031 # we operate at the lowest level possible.
3031 object.__setattr__(repo, r'__class__', poisonedrepository)
3032 object.__setattr__(repo, r'__class__', poisonedrepository)
@@ -1,2549 +1,2556 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 os
19 import os
20 import struct
20 import struct
21 import zlib
21 import zlib
22
22
23 # import stuff from node for others to import from revlog
23 # import stuff from node for others to import from revlog
24 from .node import (
24 from .node import (
25 bin,
25 bin,
26 hex,
26 hex,
27 nullhex,
27 nullhex,
28 nullid,
28 nullid,
29 nullrev,
29 nullrev,
30 short,
30 short,
31 wdirfilenodeids,
31 wdirfilenodeids,
32 wdirhex,
32 wdirhex,
33 wdirid,
33 wdirid,
34 wdirrev,
34 wdirrev,
35 )
35 )
36 from .i18n import _
36 from .i18n import _
37 from .revlogutils.constants import (
37 from .revlogutils.constants import (
38 FLAG_GENERALDELTA,
38 FLAG_GENERALDELTA,
39 FLAG_INLINE_DATA,
39 FLAG_INLINE_DATA,
40 REVIDX_DEFAULT_FLAGS,
40 REVIDX_DEFAULT_FLAGS,
41 REVIDX_ELLIPSIS,
41 REVIDX_ELLIPSIS,
42 REVIDX_EXTSTORED,
42 REVIDX_EXTSTORED,
43 REVIDX_FLAGS_ORDER,
43 REVIDX_FLAGS_ORDER,
44 REVIDX_ISCENSORED,
44 REVIDX_ISCENSORED,
45 REVIDX_KNOWN_FLAGS,
45 REVIDX_KNOWN_FLAGS,
46 REVIDX_RAWTEXT_CHANGING_FLAGS,
46 REVIDX_RAWTEXT_CHANGING_FLAGS,
47 REVLOGV0,
47 REVLOGV0,
48 REVLOGV1,
48 REVLOGV1,
49 REVLOGV1_FLAGS,
49 REVLOGV1_FLAGS,
50 REVLOGV2,
50 REVLOGV2,
51 REVLOGV2_FLAGS,
51 REVLOGV2_FLAGS,
52 REVLOG_DEFAULT_FLAGS,
52 REVLOG_DEFAULT_FLAGS,
53 REVLOG_DEFAULT_FORMAT,
53 REVLOG_DEFAULT_FORMAT,
54 REVLOG_DEFAULT_VERSION,
54 REVLOG_DEFAULT_VERSION,
55 )
55 )
56 from .thirdparty import (
56 from .thirdparty import (
57 attr,
57 attr,
58 )
58 )
59 from . import (
59 from . import (
60 ancestor,
60 ancestor,
61 dagop,
61 dagop,
62 error,
62 error,
63 mdiff,
63 mdiff,
64 policy,
64 policy,
65 pycompat,
65 pycompat,
66 repository,
66 repository,
67 templatefilters,
67 templatefilters,
68 util,
68 util,
69 )
69 )
70 from .revlogutils import (
70 from .revlogutils import (
71 deltas as deltautil,
71 deltas as deltautil,
72 )
72 )
73 from .utils import (
73 from .utils import (
74 interfaceutil,
74 interfaceutil,
75 storageutil,
75 storageutil,
76 stringutil,
76 stringutil,
77 )
77 )
78
78
79 # blanked usage of all the name to prevent pyflakes constraints
79 # blanked usage of all the name to prevent pyflakes constraints
80 # We need these name available in the module for extensions.
80 # We need these name available in the module for extensions.
81 REVLOGV0
81 REVLOGV0
82 REVLOGV1
82 REVLOGV1
83 REVLOGV2
83 REVLOGV2
84 FLAG_INLINE_DATA
84 FLAG_INLINE_DATA
85 FLAG_GENERALDELTA
85 FLAG_GENERALDELTA
86 REVLOG_DEFAULT_FLAGS
86 REVLOG_DEFAULT_FLAGS
87 REVLOG_DEFAULT_FORMAT
87 REVLOG_DEFAULT_FORMAT
88 REVLOG_DEFAULT_VERSION
88 REVLOG_DEFAULT_VERSION
89 REVLOGV1_FLAGS
89 REVLOGV1_FLAGS
90 REVLOGV2_FLAGS
90 REVLOGV2_FLAGS
91 REVIDX_ISCENSORED
91 REVIDX_ISCENSORED
92 REVIDX_ELLIPSIS
92 REVIDX_ELLIPSIS
93 REVIDX_EXTSTORED
93 REVIDX_EXTSTORED
94 REVIDX_DEFAULT_FLAGS
94 REVIDX_DEFAULT_FLAGS
95 REVIDX_FLAGS_ORDER
95 REVIDX_FLAGS_ORDER
96 REVIDX_KNOWN_FLAGS
96 REVIDX_KNOWN_FLAGS
97 REVIDX_RAWTEXT_CHANGING_FLAGS
97 REVIDX_RAWTEXT_CHANGING_FLAGS
98
98
99 parsers = policy.importmod(r'parsers')
99 parsers = policy.importmod(r'parsers')
100
100
101 # Aliased for performance.
101 # Aliased for performance.
102 _zlibdecompress = zlib.decompress
102 _zlibdecompress = zlib.decompress
103
103
104 # max size of revlog with inline data
104 # max size of revlog with inline data
105 _maxinline = 131072
105 _maxinline = 131072
106 _chunksize = 1048576
106 _chunksize = 1048576
107
107
108 # Store flag processors (cf. 'addflagprocessor()' to register)
108 # Store flag processors (cf. 'addflagprocessor()' to register)
109 _flagprocessors = {
109 _flagprocessors = {
110 REVIDX_ISCENSORED: None,
110 REVIDX_ISCENSORED: None,
111 }
111 }
112
112
113 # Flag processors for REVIDX_ELLIPSIS.
113 # Flag processors for REVIDX_ELLIPSIS.
114 def ellipsisreadprocessor(rl, text):
114 def ellipsisreadprocessor(rl, text):
115 return text, False
115 return text, False
116
116
117 def ellipsiswriteprocessor(rl, text):
117 def ellipsiswriteprocessor(rl, text):
118 return text, False
118 return text, False
119
119
120 def ellipsisrawprocessor(rl, text):
120 def ellipsisrawprocessor(rl, text):
121 return False
121 return False
122
122
123 ellipsisprocessor = (
123 ellipsisprocessor = (
124 ellipsisreadprocessor,
124 ellipsisreadprocessor,
125 ellipsiswriteprocessor,
125 ellipsiswriteprocessor,
126 ellipsisrawprocessor,
126 ellipsisrawprocessor,
127 )
127 )
128
128
129 def addflagprocessor(flag, processor):
129 def addflagprocessor(flag, processor):
130 """Register a flag processor on a revision data flag.
130 """Register a flag processor on a revision data flag.
131
131
132 Invariant:
132 Invariant:
133 - Flags need to be defined in REVIDX_KNOWN_FLAGS and REVIDX_FLAGS_ORDER,
133 - Flags need to be defined in REVIDX_KNOWN_FLAGS and REVIDX_FLAGS_ORDER,
134 and REVIDX_RAWTEXT_CHANGING_FLAGS if they can alter rawtext.
134 and REVIDX_RAWTEXT_CHANGING_FLAGS if they can alter rawtext.
135 - Only one flag processor can be registered on a specific flag.
135 - Only one flag processor can be registered on a specific flag.
136 - flagprocessors must be 3-tuples of functions (read, write, raw) with the
136 - flagprocessors must be 3-tuples of functions (read, write, raw) with the
137 following signatures:
137 following signatures:
138 - (read) f(self, rawtext) -> text, bool
138 - (read) f(self, rawtext) -> text, bool
139 - (write) f(self, text) -> rawtext, bool
139 - (write) f(self, text) -> rawtext, bool
140 - (raw) f(self, rawtext) -> bool
140 - (raw) f(self, rawtext) -> bool
141 "text" is presented to the user. "rawtext" is stored in revlog data, not
141 "text" is presented to the user. "rawtext" is stored in revlog data, not
142 directly visible to the user.
142 directly visible to the user.
143 The boolean returned by these transforms is used to determine whether
143 The boolean returned by these transforms is used to determine whether
144 the returned text can be used for hash integrity checking. For example,
144 the returned text can be used for hash integrity checking. For example,
145 if "write" returns False, then "text" is used to generate hash. If
145 if "write" returns False, then "text" is used to generate hash. If
146 "write" returns True, that basically means "rawtext" returned by "write"
146 "write" returns True, that basically means "rawtext" returned by "write"
147 should be used to generate hash. Usually, "write" and "read" return
147 should be used to generate hash. Usually, "write" and "read" return
148 different booleans. And "raw" returns a same boolean as "write".
148 different booleans. And "raw" returns a same boolean as "write".
149
149
150 Note: The 'raw' transform is used for changegroup generation and in some
150 Note: The 'raw' transform is used for changegroup generation and in some
151 debug commands. In this case the transform only indicates whether the
151 debug commands. In this case the transform only indicates whether the
152 contents can be used for hash integrity checks.
152 contents can be used for hash integrity checks.
153 """
153 """
154 _insertflagprocessor(flag, processor, _flagprocessors)
155
156 def _insertflagprocessor(flag, processor, flagprocessors):
154 if not flag & REVIDX_KNOWN_FLAGS:
157 if not flag & REVIDX_KNOWN_FLAGS:
155 msg = _("cannot register processor on unknown flag '%#x'.") % (flag)
158 msg = _("cannot register processor on unknown flag '%#x'.") % (flag)
156 raise error.ProgrammingError(msg)
159 raise error.ProgrammingError(msg)
157 if flag not in REVIDX_FLAGS_ORDER:
160 if flag not in REVIDX_FLAGS_ORDER:
158 msg = _("flag '%#x' undefined in REVIDX_FLAGS_ORDER.") % (flag)
161 msg = _("flag '%#x' undefined in REVIDX_FLAGS_ORDER.") % (flag)
159 raise error.ProgrammingError(msg)
162 raise error.ProgrammingError(msg)
160 if flag in _flagprocessors:
163 if flag in flagprocessors:
161 msg = _("cannot register multiple processors on flag '%#x'.") % (flag)
164 msg = _("cannot register multiple processors on flag '%#x'.") % (flag)
162 raise error.Abort(msg)
165 raise error.Abort(msg)
163 _flagprocessors[flag] = processor
166 flagprocessors[flag] = processor
164
167
165 def getoffset(q):
168 def getoffset(q):
166 return int(q >> 16)
169 return int(q >> 16)
167
170
168 def gettype(q):
171 def gettype(q):
169 return int(q & 0xFFFF)
172 return int(q & 0xFFFF)
170
173
171 def offset_type(offset, type):
174 def offset_type(offset, type):
172 if (type & ~REVIDX_KNOWN_FLAGS) != 0:
175 if (type & ~REVIDX_KNOWN_FLAGS) != 0:
173 raise ValueError('unknown revlog index flags')
176 raise ValueError('unknown revlog index flags')
174 return int(int(offset) << 16 | type)
177 return int(int(offset) << 16 | type)
175
178
176 @attr.s(slots=True, frozen=True)
179 @attr.s(slots=True, frozen=True)
177 class _revisioninfo(object):
180 class _revisioninfo(object):
178 """Information about a revision that allows building its fulltext
181 """Information about a revision that allows building its fulltext
179 node: expected hash of the revision
182 node: expected hash of the revision
180 p1, p2: parent revs of the revision
183 p1, p2: parent revs of the revision
181 btext: built text cache consisting of a one-element list
184 btext: built text cache consisting of a one-element list
182 cachedelta: (baserev, uncompressed_delta) or None
185 cachedelta: (baserev, uncompressed_delta) or None
183 flags: flags associated to the revision storage
186 flags: flags associated to the revision storage
184
187
185 One of btext[0] or cachedelta must be set.
188 One of btext[0] or cachedelta must be set.
186 """
189 """
187 node = attr.ib()
190 node = attr.ib()
188 p1 = attr.ib()
191 p1 = attr.ib()
189 p2 = attr.ib()
192 p2 = attr.ib()
190 btext = attr.ib()
193 btext = attr.ib()
191 textlen = attr.ib()
194 textlen = attr.ib()
192 cachedelta = attr.ib()
195 cachedelta = attr.ib()
193 flags = attr.ib()
196 flags = attr.ib()
194
197
195 @interfaceutil.implementer(repository.irevisiondelta)
198 @interfaceutil.implementer(repository.irevisiondelta)
196 @attr.s(slots=True)
199 @attr.s(slots=True)
197 class revlogrevisiondelta(object):
200 class revlogrevisiondelta(object):
198 node = attr.ib()
201 node = attr.ib()
199 p1node = attr.ib()
202 p1node = attr.ib()
200 p2node = attr.ib()
203 p2node = attr.ib()
201 basenode = attr.ib()
204 basenode = attr.ib()
202 flags = attr.ib()
205 flags = attr.ib()
203 baserevisionsize = attr.ib()
206 baserevisionsize = attr.ib()
204 revision = attr.ib()
207 revision = attr.ib()
205 delta = attr.ib()
208 delta = attr.ib()
206 linknode = attr.ib(default=None)
209 linknode = attr.ib(default=None)
207
210
208 @interfaceutil.implementer(repository.iverifyproblem)
211 @interfaceutil.implementer(repository.iverifyproblem)
209 @attr.s(frozen=True)
212 @attr.s(frozen=True)
210 class revlogproblem(object):
213 class revlogproblem(object):
211 warning = attr.ib(default=None)
214 warning = attr.ib(default=None)
212 error = attr.ib(default=None)
215 error = attr.ib(default=None)
213 node = attr.ib(default=None)
216 node = attr.ib(default=None)
214
217
215 # index v0:
218 # index v0:
216 # 4 bytes: offset
219 # 4 bytes: offset
217 # 4 bytes: compressed length
220 # 4 bytes: compressed length
218 # 4 bytes: base rev
221 # 4 bytes: base rev
219 # 4 bytes: link rev
222 # 4 bytes: link rev
220 # 20 bytes: parent 1 nodeid
223 # 20 bytes: parent 1 nodeid
221 # 20 bytes: parent 2 nodeid
224 # 20 bytes: parent 2 nodeid
222 # 20 bytes: nodeid
225 # 20 bytes: nodeid
223 indexformatv0 = struct.Struct(">4l20s20s20s")
226 indexformatv0 = struct.Struct(">4l20s20s20s")
224 indexformatv0_pack = indexformatv0.pack
227 indexformatv0_pack = indexformatv0.pack
225 indexformatv0_unpack = indexformatv0.unpack
228 indexformatv0_unpack = indexformatv0.unpack
226
229
227 class revlogoldindex(list):
230 class revlogoldindex(list):
228 def __getitem__(self, i):
231 def __getitem__(self, i):
229 if i == -1:
232 if i == -1:
230 return (0, 0, 0, -1, -1, -1, -1, nullid)
233 return (0, 0, 0, -1, -1, -1, -1, nullid)
231 return list.__getitem__(self, i)
234 return list.__getitem__(self, i)
232
235
233 class revlogoldio(object):
236 class revlogoldio(object):
234 def __init__(self):
237 def __init__(self):
235 self.size = indexformatv0.size
238 self.size = indexformatv0.size
236
239
237 def parseindex(self, data, inline):
240 def parseindex(self, data, inline):
238 s = self.size
241 s = self.size
239 index = []
242 index = []
240 nodemap = {nullid: nullrev}
243 nodemap = {nullid: nullrev}
241 n = off = 0
244 n = off = 0
242 l = len(data)
245 l = len(data)
243 while off + s <= l:
246 while off + s <= l:
244 cur = data[off:off + s]
247 cur = data[off:off + s]
245 off += s
248 off += s
246 e = indexformatv0_unpack(cur)
249 e = indexformatv0_unpack(cur)
247 # transform to revlogv1 format
250 # transform to revlogv1 format
248 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
251 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
249 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
252 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
250 index.append(e2)
253 index.append(e2)
251 nodemap[e[6]] = n
254 nodemap[e[6]] = n
252 n += 1
255 n += 1
253
256
254 return revlogoldindex(index), nodemap, None
257 return revlogoldindex(index), nodemap, None
255
258
256 def packentry(self, entry, node, version, rev):
259 def packentry(self, entry, node, version, rev):
257 if gettype(entry[0]):
260 if gettype(entry[0]):
258 raise error.RevlogError(_('index entry flags need revlog '
261 raise error.RevlogError(_('index entry flags need revlog '
259 'version 1'))
262 'version 1'))
260 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
263 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
261 node(entry[5]), node(entry[6]), entry[7])
264 node(entry[5]), node(entry[6]), entry[7])
262 return indexformatv0_pack(*e2)
265 return indexformatv0_pack(*e2)
263
266
264 # index ng:
267 # index ng:
265 # 6 bytes: offset
268 # 6 bytes: offset
266 # 2 bytes: flags
269 # 2 bytes: flags
267 # 4 bytes: compressed length
270 # 4 bytes: compressed length
268 # 4 bytes: uncompressed length
271 # 4 bytes: uncompressed length
269 # 4 bytes: base rev
272 # 4 bytes: base rev
270 # 4 bytes: link rev
273 # 4 bytes: link rev
271 # 4 bytes: parent 1 rev
274 # 4 bytes: parent 1 rev
272 # 4 bytes: parent 2 rev
275 # 4 bytes: parent 2 rev
273 # 32 bytes: nodeid
276 # 32 bytes: nodeid
274 indexformatng = struct.Struct(">Qiiiiii20s12x")
277 indexformatng = struct.Struct(">Qiiiiii20s12x")
275 indexformatng_pack = indexformatng.pack
278 indexformatng_pack = indexformatng.pack
276 versionformat = struct.Struct(">I")
279 versionformat = struct.Struct(">I")
277 versionformat_pack = versionformat.pack
280 versionformat_pack = versionformat.pack
278 versionformat_unpack = versionformat.unpack
281 versionformat_unpack = versionformat.unpack
279
282
280 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
283 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
281 # signed integer)
284 # signed integer)
282 _maxentrysize = 0x7fffffff
285 _maxentrysize = 0x7fffffff
283
286
284 class revlogio(object):
287 class revlogio(object):
285 def __init__(self):
288 def __init__(self):
286 self.size = indexformatng.size
289 self.size = indexformatng.size
287
290
288 def parseindex(self, data, inline):
291 def parseindex(self, data, inline):
289 # call the C implementation to parse the index data
292 # call the C implementation to parse the index data
290 index, cache = parsers.parse_index2(data, inline)
293 index, cache = parsers.parse_index2(data, inline)
291 return index, getattr(index, 'nodemap', None), cache
294 return index, getattr(index, 'nodemap', None), cache
292
295
293 def packentry(self, entry, node, version, rev):
296 def packentry(self, entry, node, version, rev):
294 p = indexformatng_pack(*entry)
297 p = indexformatng_pack(*entry)
295 if rev == 0:
298 if rev == 0:
296 p = versionformat_pack(version) + p[4:]
299 p = versionformat_pack(version) + p[4:]
297 return p
300 return p
298
301
299 class revlog(object):
302 class revlog(object):
300 """
303 """
301 the underlying revision storage object
304 the underlying revision storage object
302
305
303 A revlog consists of two parts, an index and the revision data.
306 A revlog consists of two parts, an index and the revision data.
304
307
305 The index is a file with a fixed record size containing
308 The index is a file with a fixed record size containing
306 information on each revision, including its nodeid (hash), the
309 information on each revision, including its nodeid (hash), the
307 nodeids of its parents, the position and offset of its data within
310 nodeids of its parents, the position and offset of its data within
308 the data file, and the revision it's based on. Finally, each entry
311 the data file, and the revision it's based on. Finally, each entry
309 contains a linkrev entry that can serve as a pointer to external
312 contains a linkrev entry that can serve as a pointer to external
310 data.
313 data.
311
314
312 The revision data itself is a linear collection of data chunks.
315 The revision data itself is a linear collection of data chunks.
313 Each chunk represents a revision and is usually represented as a
316 Each chunk represents a revision and is usually represented as a
314 delta against the previous chunk. To bound lookup time, runs of
317 delta against the previous chunk. To bound lookup time, runs of
315 deltas are limited to about 2 times the length of the original
318 deltas are limited to about 2 times the length of the original
316 version data. This makes retrieval of a version proportional to
319 version data. This makes retrieval of a version proportional to
317 its size, or O(1) relative to the number of revisions.
320 its size, or O(1) relative to the number of revisions.
318
321
319 Both pieces of the revlog are written to in an append-only
322 Both pieces of the revlog are written to in an append-only
320 fashion, which means we never need to rewrite a file to insert or
323 fashion, which means we never need to rewrite a file to insert or
321 remove data, and can use some simple techniques to avoid the need
324 remove data, and can use some simple techniques to avoid the need
322 for locking while reading.
325 for locking while reading.
323
326
324 If checkambig, indexfile is opened with checkambig=True at
327 If checkambig, indexfile is opened with checkambig=True at
325 writing, to avoid file stat ambiguity.
328 writing, to avoid file stat ambiguity.
326
329
327 If mmaplargeindex is True, and an mmapindexthreshold is set, the
330 If mmaplargeindex is True, and an mmapindexthreshold is set, the
328 index will be mmapped rather than read if it is larger than the
331 index will be mmapped rather than read if it is larger than the
329 configured threshold.
332 configured threshold.
330
333
331 If censorable is True, the revlog can have censored revisions.
334 If censorable is True, the revlog can have censored revisions.
332 """
335 """
333 def __init__(self, opener, indexfile, datafile=None, checkambig=False,
336 def __init__(self, opener, indexfile, datafile=None, checkambig=False,
334 mmaplargeindex=False, censorable=False):
337 mmaplargeindex=False, censorable=False):
335 """
338 """
336 create a revlog object
339 create a revlog object
337
340
338 opener is a function that abstracts the file opening operation
341 opener is a function that abstracts the file opening operation
339 and can be used to implement COW semantics or the like.
342 and can be used to implement COW semantics or the like.
340 """
343 """
341 self.indexfile = indexfile
344 self.indexfile = indexfile
342 self.datafile = datafile or (indexfile[:-2] + ".d")
345 self.datafile = datafile or (indexfile[:-2] + ".d")
343 self.opener = opener
346 self.opener = opener
344 # When True, indexfile is opened with checkambig=True at writing, to
347 # When True, indexfile is opened with checkambig=True at writing, to
345 # avoid file stat ambiguity.
348 # avoid file stat ambiguity.
346 self._checkambig = checkambig
349 self._checkambig = checkambig
347 self._censorable = censorable
350 self._censorable = censorable
348 # 3-tuple of (node, rev, text) for a raw revision.
351 # 3-tuple of (node, rev, text) for a raw revision.
349 self._revisioncache = None
352 self._revisioncache = None
350 # Maps rev to chain base rev.
353 # Maps rev to chain base rev.
351 self._chainbasecache = util.lrucachedict(100)
354 self._chainbasecache = util.lrucachedict(100)
352 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
355 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
353 self._chunkcache = (0, '')
356 self._chunkcache = (0, '')
354 # How much data to read and cache into the raw revlog data cache.
357 # How much data to read and cache into the raw revlog data cache.
355 self._chunkcachesize = 65536
358 self._chunkcachesize = 65536
356 self._maxchainlen = None
359 self._maxchainlen = None
357 self._deltabothparents = True
360 self._deltabothparents = True
358 self.index = []
361 self.index = []
359 # Mapping of partial identifiers to full nodes.
362 # Mapping of partial identifiers to full nodes.
360 self._pcache = {}
363 self._pcache = {}
361 # Mapping of revision integer to full node.
364 # Mapping of revision integer to full node.
362 self._nodecache = {nullid: nullrev}
365 self._nodecache = {nullid: nullrev}
363 self._nodepos = None
366 self._nodepos = None
364 self._compengine = 'zlib'
367 self._compengine = 'zlib'
365 self._maxdeltachainspan = -1
368 self._maxdeltachainspan = -1
366 self._withsparseread = False
369 self._withsparseread = False
367 self._sparserevlog = False
370 self._sparserevlog = False
368 self._srdensitythreshold = 0.50
371 self._srdensitythreshold = 0.50
369 self._srmingapsize = 262144
372 self._srmingapsize = 262144
370
373
371 # Make copy of flag processors so each revlog instance can support
374 # Make copy of flag processors so each revlog instance can support
372 # custom flags.
375 # custom flags.
373 self._flagprocessors = dict(_flagprocessors)
376 self._flagprocessors = dict(_flagprocessors)
374
377
375 mmapindexthreshold = None
378 mmapindexthreshold = None
376 v = REVLOG_DEFAULT_VERSION
379 v = REVLOG_DEFAULT_VERSION
377 opts = getattr(opener, 'options', None)
380 opts = getattr(opener, 'options', None)
378 if opts is not None:
381 if opts is not None:
379 if 'revlogv2' in opts:
382 if 'revlogv2' in opts:
380 # version 2 revlogs always use generaldelta.
383 # version 2 revlogs always use generaldelta.
381 v = REVLOGV2 | FLAG_GENERALDELTA | FLAG_INLINE_DATA
384 v = REVLOGV2 | FLAG_GENERALDELTA | FLAG_INLINE_DATA
382 elif 'revlogv1' in opts:
385 elif 'revlogv1' in opts:
383 if 'generaldelta' in opts:
386 if 'generaldelta' in opts:
384 v |= FLAG_GENERALDELTA
387 v |= FLAG_GENERALDELTA
385 else:
388 else:
386 v = 0
389 v = 0
387 if 'chunkcachesize' in opts:
390 if 'chunkcachesize' in opts:
388 self._chunkcachesize = opts['chunkcachesize']
391 self._chunkcachesize = opts['chunkcachesize']
389 if 'maxchainlen' in opts:
392 if 'maxchainlen' in opts:
390 self._maxchainlen = opts['maxchainlen']
393 self._maxchainlen = opts['maxchainlen']
391 if 'deltabothparents' in opts:
394 if 'deltabothparents' in opts:
392 self._deltabothparents = opts['deltabothparents']
395 self._deltabothparents = opts['deltabothparents']
393 self._lazydeltabase = bool(opts.get('lazydeltabase', False))
396 self._lazydeltabase = bool(opts.get('lazydeltabase', False))
394 if 'compengine' in opts:
397 if 'compengine' in opts:
395 self._compengine = opts['compengine']
398 self._compengine = opts['compengine']
396 if 'maxdeltachainspan' in opts:
399 if 'maxdeltachainspan' in opts:
397 self._maxdeltachainspan = opts['maxdeltachainspan']
400 self._maxdeltachainspan = opts['maxdeltachainspan']
398 if mmaplargeindex and 'mmapindexthreshold' in opts:
401 if mmaplargeindex and 'mmapindexthreshold' in opts:
399 mmapindexthreshold = opts['mmapindexthreshold']
402 mmapindexthreshold = opts['mmapindexthreshold']
400 self._sparserevlog = bool(opts.get('sparse-revlog', False))
403 self._sparserevlog = bool(opts.get('sparse-revlog', False))
401 withsparseread = bool(opts.get('with-sparse-read', False))
404 withsparseread = bool(opts.get('with-sparse-read', False))
402 # sparse-revlog forces sparse-read
405 # sparse-revlog forces sparse-read
403 self._withsparseread = self._sparserevlog or withsparseread
406 self._withsparseread = self._sparserevlog or withsparseread
404 if 'sparse-read-density-threshold' in opts:
407 if 'sparse-read-density-threshold' in opts:
405 self._srdensitythreshold = opts['sparse-read-density-threshold']
408 self._srdensitythreshold = opts['sparse-read-density-threshold']
406 if 'sparse-read-min-gap-size' in opts:
409 if 'sparse-read-min-gap-size' in opts:
407 self._srmingapsize = opts['sparse-read-min-gap-size']
410 self._srmingapsize = opts['sparse-read-min-gap-size']
408 if opts.get('enableellipsis'):
411 if opts.get('enableellipsis'):
409 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
412 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
410
413
414 # revlog v0 doesn't have flag processors
415 for flag, processor in opts.get(b'flagprocessors', {}).iteritems():
416 _insertflagprocessor(flag, processor, self._flagprocessors)
417
411 if self._chunkcachesize <= 0:
418 if self._chunkcachesize <= 0:
412 raise error.RevlogError(_('revlog chunk cache size %r is not '
419 raise error.RevlogError(_('revlog chunk cache size %r is not '
413 'greater than 0') % self._chunkcachesize)
420 'greater than 0') % self._chunkcachesize)
414 elif self._chunkcachesize & (self._chunkcachesize - 1):
421 elif self._chunkcachesize & (self._chunkcachesize - 1):
415 raise error.RevlogError(_('revlog chunk cache size %r is not a '
422 raise error.RevlogError(_('revlog chunk cache size %r is not a '
416 'power of 2') % self._chunkcachesize)
423 'power of 2') % self._chunkcachesize)
417
424
418 self._loadindex(v, mmapindexthreshold)
425 self._loadindex(v, mmapindexthreshold)
419
426
420 def _loadindex(self, v, mmapindexthreshold):
427 def _loadindex(self, v, mmapindexthreshold):
421 indexdata = ''
428 indexdata = ''
422 self._initempty = True
429 self._initempty = True
423 try:
430 try:
424 with self._indexfp() as f:
431 with self._indexfp() as f:
425 if (mmapindexthreshold is not None and
432 if (mmapindexthreshold is not None and
426 self.opener.fstat(f).st_size >= mmapindexthreshold):
433 self.opener.fstat(f).st_size >= mmapindexthreshold):
427 indexdata = util.buffer(util.mmapread(f))
434 indexdata = util.buffer(util.mmapread(f))
428 else:
435 else:
429 indexdata = f.read()
436 indexdata = f.read()
430 if len(indexdata) > 0:
437 if len(indexdata) > 0:
431 v = versionformat_unpack(indexdata[:4])[0]
438 v = versionformat_unpack(indexdata[:4])[0]
432 self._initempty = False
439 self._initempty = False
433 except IOError as inst:
440 except IOError as inst:
434 if inst.errno != errno.ENOENT:
441 if inst.errno != errno.ENOENT:
435 raise
442 raise
436
443
437 self.version = v
444 self.version = v
438 self._inline = v & FLAG_INLINE_DATA
445 self._inline = v & FLAG_INLINE_DATA
439 self._generaldelta = v & FLAG_GENERALDELTA
446 self._generaldelta = v & FLAG_GENERALDELTA
440 flags = v & ~0xFFFF
447 flags = v & ~0xFFFF
441 fmt = v & 0xFFFF
448 fmt = v & 0xFFFF
442 if fmt == REVLOGV0:
449 if fmt == REVLOGV0:
443 if flags:
450 if flags:
444 raise error.RevlogError(_('unknown flags (%#04x) in version %d '
451 raise error.RevlogError(_('unknown flags (%#04x) in version %d '
445 'revlog %s') %
452 'revlog %s') %
446 (flags >> 16, fmt, self.indexfile))
453 (flags >> 16, fmt, self.indexfile))
447 elif fmt == REVLOGV1:
454 elif fmt == REVLOGV1:
448 if flags & ~REVLOGV1_FLAGS:
455 if flags & ~REVLOGV1_FLAGS:
449 raise error.RevlogError(_('unknown flags (%#04x) in version %d '
456 raise error.RevlogError(_('unknown flags (%#04x) in version %d '
450 'revlog %s') %
457 'revlog %s') %
451 (flags >> 16, fmt, self.indexfile))
458 (flags >> 16, fmt, self.indexfile))
452 elif fmt == REVLOGV2:
459 elif fmt == REVLOGV2:
453 if flags & ~REVLOGV2_FLAGS:
460 if flags & ~REVLOGV2_FLAGS:
454 raise error.RevlogError(_('unknown flags (%#04x) in version %d '
461 raise error.RevlogError(_('unknown flags (%#04x) in version %d '
455 'revlog %s') %
462 'revlog %s') %
456 (flags >> 16, fmt, self.indexfile))
463 (flags >> 16, fmt, self.indexfile))
457 else:
464 else:
458 raise error.RevlogError(_('unknown version (%d) in revlog %s') %
465 raise error.RevlogError(_('unknown version (%d) in revlog %s') %
459 (fmt, self.indexfile))
466 (fmt, self.indexfile))
460
467
461 self._storedeltachains = True
468 self._storedeltachains = True
462
469
463 self._io = revlogio()
470 self._io = revlogio()
464 if self.version == REVLOGV0:
471 if self.version == REVLOGV0:
465 self._io = revlogoldio()
472 self._io = revlogoldio()
466 try:
473 try:
467 d = self._io.parseindex(indexdata, self._inline)
474 d = self._io.parseindex(indexdata, self._inline)
468 except (ValueError, IndexError):
475 except (ValueError, IndexError):
469 raise error.RevlogError(_("index %s is corrupted") %
476 raise error.RevlogError(_("index %s is corrupted") %
470 self.indexfile)
477 self.indexfile)
471 self.index, nodemap, self._chunkcache = d
478 self.index, nodemap, self._chunkcache = d
472 if nodemap is not None:
479 if nodemap is not None:
473 self.nodemap = self._nodecache = nodemap
480 self.nodemap = self._nodecache = nodemap
474 if not self._chunkcache:
481 if not self._chunkcache:
475 self._chunkclear()
482 self._chunkclear()
476 # revnum -> (chain-length, sum-delta-length)
483 # revnum -> (chain-length, sum-delta-length)
477 self._chaininfocache = {}
484 self._chaininfocache = {}
478 # revlog header -> revlog compressor
485 # revlog header -> revlog compressor
479 self._decompressors = {}
486 self._decompressors = {}
480
487
481 @util.propertycache
488 @util.propertycache
482 def _compressor(self):
489 def _compressor(self):
483 return util.compengines[self._compengine].revlogcompressor()
490 return util.compengines[self._compengine].revlogcompressor()
484
491
485 def _indexfp(self, mode='r'):
492 def _indexfp(self, mode='r'):
486 """file object for the revlog's index file"""
493 """file object for the revlog's index file"""
487 args = {r'mode': mode}
494 args = {r'mode': mode}
488 if mode != 'r':
495 if mode != 'r':
489 args[r'checkambig'] = self._checkambig
496 args[r'checkambig'] = self._checkambig
490 if mode == 'w':
497 if mode == 'w':
491 args[r'atomictemp'] = True
498 args[r'atomictemp'] = True
492 return self.opener(self.indexfile, **args)
499 return self.opener(self.indexfile, **args)
493
500
494 def _datafp(self, mode='r'):
501 def _datafp(self, mode='r'):
495 """file object for the revlog's data file"""
502 """file object for the revlog's data file"""
496 return self.opener(self.datafile, mode=mode)
503 return self.opener(self.datafile, mode=mode)
497
504
498 @contextlib.contextmanager
505 @contextlib.contextmanager
499 def _datareadfp(self, existingfp=None):
506 def _datareadfp(self, existingfp=None):
500 """file object suitable to read data"""
507 """file object suitable to read data"""
501 if existingfp is not None:
508 if existingfp is not None:
502 yield existingfp
509 yield existingfp
503 else:
510 else:
504 if self._inline:
511 if self._inline:
505 func = self._indexfp
512 func = self._indexfp
506 else:
513 else:
507 func = self._datafp
514 func = self._datafp
508 with func() as fp:
515 with func() as fp:
509 yield fp
516 yield fp
510
517
511 def tip(self):
518 def tip(self):
512 return self.node(len(self.index) - 1)
519 return self.node(len(self.index) - 1)
513 def __contains__(self, rev):
520 def __contains__(self, rev):
514 return 0 <= rev < len(self)
521 return 0 <= rev < len(self)
515 def __len__(self):
522 def __len__(self):
516 return len(self.index)
523 return len(self.index)
517 def __iter__(self):
524 def __iter__(self):
518 return iter(pycompat.xrange(len(self)))
525 return iter(pycompat.xrange(len(self)))
519 def revs(self, start=0, stop=None):
526 def revs(self, start=0, stop=None):
520 """iterate over all rev in this revlog (from start to stop)"""
527 """iterate over all rev in this revlog (from start to stop)"""
521 return storageutil.iterrevs(len(self), start=start, stop=stop)
528 return storageutil.iterrevs(len(self), start=start, stop=stop)
522
529
523 @util.propertycache
530 @util.propertycache
524 def nodemap(self):
531 def nodemap(self):
525 if self.index:
532 if self.index:
526 # populate mapping down to the initial node
533 # populate mapping down to the initial node
527 node0 = self.index[0][7] # get around changelog filtering
534 node0 = self.index[0][7] # get around changelog filtering
528 self.rev(node0)
535 self.rev(node0)
529 return self._nodecache
536 return self._nodecache
530
537
531 def hasnode(self, node):
538 def hasnode(self, node):
532 try:
539 try:
533 self.rev(node)
540 self.rev(node)
534 return True
541 return True
535 except KeyError:
542 except KeyError:
536 return False
543 return False
537
544
538 def candelta(self, baserev, rev):
545 def candelta(self, baserev, rev):
539 """whether two revisions (baserev, rev) can be delta-ed or not"""
546 """whether two revisions (baserev, rev) can be delta-ed or not"""
540 # Disable delta if either rev requires a content-changing flag
547 # Disable delta if either rev requires a content-changing flag
541 # processor (ex. LFS). This is because such flag processor can alter
548 # processor (ex. LFS). This is because such flag processor can alter
542 # the rawtext content that the delta will be based on, and two clients
549 # the rawtext content that the delta will be based on, and two clients
543 # could have a same revlog node with different flags (i.e. different
550 # could have a same revlog node with different flags (i.e. different
544 # rawtext contents) and the delta could be incompatible.
551 # rawtext contents) and the delta could be incompatible.
545 if ((self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS)
552 if ((self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS)
546 or (self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS)):
553 or (self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS)):
547 return False
554 return False
548 return True
555 return True
549
556
550 def clearcaches(self):
557 def clearcaches(self):
551 self._revisioncache = None
558 self._revisioncache = None
552 self._chainbasecache.clear()
559 self._chainbasecache.clear()
553 self._chunkcache = (0, '')
560 self._chunkcache = (0, '')
554 self._pcache = {}
561 self._pcache = {}
555
562
556 try:
563 try:
557 self._nodecache.clearcaches()
564 self._nodecache.clearcaches()
558 except AttributeError:
565 except AttributeError:
559 self._nodecache = {nullid: nullrev}
566 self._nodecache = {nullid: nullrev}
560 self._nodepos = None
567 self._nodepos = None
561
568
562 def rev(self, node):
569 def rev(self, node):
563 try:
570 try:
564 return self._nodecache[node]
571 return self._nodecache[node]
565 except TypeError:
572 except TypeError:
566 raise
573 raise
567 except error.RevlogError:
574 except error.RevlogError:
568 # parsers.c radix tree lookup failed
575 # parsers.c radix tree lookup failed
569 if node == wdirid or node in wdirfilenodeids:
576 if node == wdirid or node in wdirfilenodeids:
570 raise error.WdirUnsupported
577 raise error.WdirUnsupported
571 raise error.LookupError(node, self.indexfile, _('no node'))
578 raise error.LookupError(node, self.indexfile, _('no node'))
572 except KeyError:
579 except KeyError:
573 # pure python cache lookup failed
580 # pure python cache lookup failed
574 n = self._nodecache
581 n = self._nodecache
575 i = self.index
582 i = self.index
576 p = self._nodepos
583 p = self._nodepos
577 if p is None:
584 if p is None:
578 p = len(i) - 1
585 p = len(i) - 1
579 else:
586 else:
580 assert p < len(i)
587 assert p < len(i)
581 for r in pycompat.xrange(p, -1, -1):
588 for r in pycompat.xrange(p, -1, -1):
582 v = i[r][7]
589 v = i[r][7]
583 n[v] = r
590 n[v] = r
584 if v == node:
591 if v == node:
585 self._nodepos = r - 1
592 self._nodepos = r - 1
586 return r
593 return r
587 if node == wdirid or node in wdirfilenodeids:
594 if node == wdirid or node in wdirfilenodeids:
588 raise error.WdirUnsupported
595 raise error.WdirUnsupported
589 raise error.LookupError(node, self.indexfile, _('no node'))
596 raise error.LookupError(node, self.indexfile, _('no node'))
590
597
591 # Accessors for index entries.
598 # Accessors for index entries.
592
599
593 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
600 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
594 # are flags.
601 # are flags.
595 def start(self, rev):
602 def start(self, rev):
596 return int(self.index[rev][0] >> 16)
603 return int(self.index[rev][0] >> 16)
597
604
598 def flags(self, rev):
605 def flags(self, rev):
599 return self.index[rev][0] & 0xFFFF
606 return self.index[rev][0] & 0xFFFF
600
607
601 def length(self, rev):
608 def length(self, rev):
602 return self.index[rev][1]
609 return self.index[rev][1]
603
610
604 def rawsize(self, rev):
611 def rawsize(self, rev):
605 """return the length of the uncompressed text for a given revision"""
612 """return the length of the uncompressed text for a given revision"""
606 l = self.index[rev][2]
613 l = self.index[rev][2]
607 if l >= 0:
614 if l >= 0:
608 return l
615 return l
609
616
610 t = self.revision(rev, raw=True)
617 t = self.revision(rev, raw=True)
611 return len(t)
618 return len(t)
612
619
613 def size(self, rev):
620 def size(self, rev):
614 """length of non-raw text (processed by a "read" flag processor)"""
621 """length of non-raw text (processed by a "read" flag processor)"""
615 # fast path: if no "read" flag processor could change the content,
622 # fast path: if no "read" flag processor could change the content,
616 # size is rawsize. note: ELLIPSIS is known to not change the content.
623 # size is rawsize. note: ELLIPSIS is known to not change the content.
617 flags = self.flags(rev)
624 flags = self.flags(rev)
618 if flags & (REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
625 if flags & (REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
619 return self.rawsize(rev)
626 return self.rawsize(rev)
620
627
621 return len(self.revision(rev, raw=False))
628 return len(self.revision(rev, raw=False))
622
629
623 def chainbase(self, rev):
630 def chainbase(self, rev):
624 base = self._chainbasecache.get(rev)
631 base = self._chainbasecache.get(rev)
625 if base is not None:
632 if base is not None:
626 return base
633 return base
627
634
628 index = self.index
635 index = self.index
629 iterrev = rev
636 iterrev = rev
630 base = index[iterrev][3]
637 base = index[iterrev][3]
631 while base != iterrev:
638 while base != iterrev:
632 iterrev = base
639 iterrev = base
633 base = index[iterrev][3]
640 base = index[iterrev][3]
634
641
635 self._chainbasecache[rev] = base
642 self._chainbasecache[rev] = base
636 return base
643 return base
637
644
638 def linkrev(self, rev):
645 def linkrev(self, rev):
639 return self.index[rev][4]
646 return self.index[rev][4]
640
647
641 def parentrevs(self, rev):
648 def parentrevs(self, rev):
642 try:
649 try:
643 entry = self.index[rev]
650 entry = self.index[rev]
644 except IndexError:
651 except IndexError:
645 if rev == wdirrev:
652 if rev == wdirrev:
646 raise error.WdirUnsupported
653 raise error.WdirUnsupported
647 raise
654 raise
648
655
649 return entry[5], entry[6]
656 return entry[5], entry[6]
650
657
651 # fast parentrevs(rev) where rev isn't filtered
658 # fast parentrevs(rev) where rev isn't filtered
652 _uncheckedparentrevs = parentrevs
659 _uncheckedparentrevs = parentrevs
653
660
654 def node(self, rev):
661 def node(self, rev):
655 try:
662 try:
656 return self.index[rev][7]
663 return self.index[rev][7]
657 except IndexError:
664 except IndexError:
658 if rev == wdirrev:
665 if rev == wdirrev:
659 raise error.WdirUnsupported
666 raise error.WdirUnsupported
660 raise
667 raise
661
668
662 # Derived from index values.
669 # Derived from index values.
663
670
664 def end(self, rev):
671 def end(self, rev):
665 return self.start(rev) + self.length(rev)
672 return self.start(rev) + self.length(rev)
666
673
667 def parents(self, node):
674 def parents(self, node):
668 i = self.index
675 i = self.index
669 d = i[self.rev(node)]
676 d = i[self.rev(node)]
670 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
677 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
671
678
672 def chainlen(self, rev):
679 def chainlen(self, rev):
673 return self._chaininfo(rev)[0]
680 return self._chaininfo(rev)[0]
674
681
675 def _chaininfo(self, rev):
682 def _chaininfo(self, rev):
676 chaininfocache = self._chaininfocache
683 chaininfocache = self._chaininfocache
677 if rev in chaininfocache:
684 if rev in chaininfocache:
678 return chaininfocache[rev]
685 return chaininfocache[rev]
679 index = self.index
686 index = self.index
680 generaldelta = self._generaldelta
687 generaldelta = self._generaldelta
681 iterrev = rev
688 iterrev = rev
682 e = index[iterrev]
689 e = index[iterrev]
683 clen = 0
690 clen = 0
684 compresseddeltalen = 0
691 compresseddeltalen = 0
685 while iterrev != e[3]:
692 while iterrev != e[3]:
686 clen += 1
693 clen += 1
687 compresseddeltalen += e[1]
694 compresseddeltalen += e[1]
688 if generaldelta:
695 if generaldelta:
689 iterrev = e[3]
696 iterrev = e[3]
690 else:
697 else:
691 iterrev -= 1
698 iterrev -= 1
692 if iterrev in chaininfocache:
699 if iterrev in chaininfocache:
693 t = chaininfocache[iterrev]
700 t = chaininfocache[iterrev]
694 clen += t[0]
701 clen += t[0]
695 compresseddeltalen += t[1]
702 compresseddeltalen += t[1]
696 break
703 break
697 e = index[iterrev]
704 e = index[iterrev]
698 else:
705 else:
699 # Add text length of base since decompressing that also takes
706 # Add text length of base since decompressing that also takes
700 # work. For cache hits the length is already included.
707 # work. For cache hits the length is already included.
701 compresseddeltalen += e[1]
708 compresseddeltalen += e[1]
702 r = (clen, compresseddeltalen)
709 r = (clen, compresseddeltalen)
703 chaininfocache[rev] = r
710 chaininfocache[rev] = r
704 return r
711 return r
705
712
706 def _deltachain(self, rev, stoprev=None):
713 def _deltachain(self, rev, stoprev=None):
707 """Obtain the delta chain for a revision.
714 """Obtain the delta chain for a revision.
708
715
709 ``stoprev`` specifies a revision to stop at. If not specified, we
716 ``stoprev`` specifies a revision to stop at. If not specified, we
710 stop at the base of the chain.
717 stop at the base of the chain.
711
718
712 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
719 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
713 revs in ascending order and ``stopped`` is a bool indicating whether
720 revs in ascending order and ``stopped`` is a bool indicating whether
714 ``stoprev`` was hit.
721 ``stoprev`` was hit.
715 """
722 """
716 # Try C implementation.
723 # Try C implementation.
717 try:
724 try:
718 return self.index.deltachain(rev, stoprev, self._generaldelta)
725 return self.index.deltachain(rev, stoprev, self._generaldelta)
719 except AttributeError:
726 except AttributeError:
720 pass
727 pass
721
728
722 chain = []
729 chain = []
723
730
724 # Alias to prevent attribute lookup in tight loop.
731 # Alias to prevent attribute lookup in tight loop.
725 index = self.index
732 index = self.index
726 generaldelta = self._generaldelta
733 generaldelta = self._generaldelta
727
734
728 iterrev = rev
735 iterrev = rev
729 e = index[iterrev]
736 e = index[iterrev]
730 while iterrev != e[3] and iterrev != stoprev:
737 while iterrev != e[3] and iterrev != stoprev:
731 chain.append(iterrev)
738 chain.append(iterrev)
732 if generaldelta:
739 if generaldelta:
733 iterrev = e[3]
740 iterrev = e[3]
734 else:
741 else:
735 iterrev -= 1
742 iterrev -= 1
736 e = index[iterrev]
743 e = index[iterrev]
737
744
738 if iterrev == stoprev:
745 if iterrev == stoprev:
739 stopped = True
746 stopped = True
740 else:
747 else:
741 chain.append(iterrev)
748 chain.append(iterrev)
742 stopped = False
749 stopped = False
743
750
744 chain.reverse()
751 chain.reverse()
745 return chain, stopped
752 return chain, stopped
746
753
747 def ancestors(self, revs, stoprev=0, inclusive=False):
754 def ancestors(self, revs, stoprev=0, inclusive=False):
748 """Generate the ancestors of 'revs' in reverse topological order.
755 """Generate the ancestors of 'revs' in reverse topological order.
749 Does not generate revs lower than stoprev.
756 Does not generate revs lower than stoprev.
750
757
751 See the documentation for ancestor.lazyancestors for more details."""
758 See the documentation for ancestor.lazyancestors for more details."""
752
759
753 # first, make sure start revisions aren't filtered
760 # first, make sure start revisions aren't filtered
754 revs = list(revs)
761 revs = list(revs)
755 checkrev = self.node
762 checkrev = self.node
756 for r in revs:
763 for r in revs:
757 checkrev(r)
764 checkrev(r)
758 # and we're sure ancestors aren't filtered as well
765 # and we're sure ancestors aren't filtered as well
759 return ancestor.lazyancestors(self._uncheckedparentrevs, revs,
766 return ancestor.lazyancestors(self._uncheckedparentrevs, revs,
760 stoprev=stoprev, inclusive=inclusive)
767 stoprev=stoprev, inclusive=inclusive)
761
768
762 def descendants(self, revs):
769 def descendants(self, revs):
763 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
770 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
764
771
765 def findcommonmissing(self, common=None, heads=None):
772 def findcommonmissing(self, common=None, heads=None):
766 """Return a tuple of the ancestors of common and the ancestors of heads
773 """Return a tuple of the ancestors of common and the ancestors of heads
767 that are not ancestors of common. In revset terminology, we return the
774 that are not ancestors of common. In revset terminology, we return the
768 tuple:
775 tuple:
769
776
770 ::common, (::heads) - (::common)
777 ::common, (::heads) - (::common)
771
778
772 The list is sorted by revision number, meaning it is
779 The list is sorted by revision number, meaning it is
773 topologically sorted.
780 topologically sorted.
774
781
775 'heads' and 'common' are both lists of node IDs. If heads is
782 'heads' and 'common' are both lists of node IDs. If heads is
776 not supplied, uses all of the revlog's heads. If common is not
783 not supplied, uses all of the revlog's heads. If common is not
777 supplied, uses nullid."""
784 supplied, uses nullid."""
778 if common is None:
785 if common is None:
779 common = [nullid]
786 common = [nullid]
780 if heads is None:
787 if heads is None:
781 heads = self.heads()
788 heads = self.heads()
782
789
783 common = [self.rev(n) for n in common]
790 common = [self.rev(n) for n in common]
784 heads = [self.rev(n) for n in heads]
791 heads = [self.rev(n) for n in heads]
785
792
786 # we want the ancestors, but inclusive
793 # we want the ancestors, but inclusive
787 class lazyset(object):
794 class lazyset(object):
788 def __init__(self, lazyvalues):
795 def __init__(self, lazyvalues):
789 self.addedvalues = set()
796 self.addedvalues = set()
790 self.lazyvalues = lazyvalues
797 self.lazyvalues = lazyvalues
791
798
792 def __contains__(self, value):
799 def __contains__(self, value):
793 return value in self.addedvalues or value in self.lazyvalues
800 return value in self.addedvalues or value in self.lazyvalues
794
801
795 def __iter__(self):
802 def __iter__(self):
796 added = self.addedvalues
803 added = self.addedvalues
797 for r in added:
804 for r in added:
798 yield r
805 yield r
799 for r in self.lazyvalues:
806 for r in self.lazyvalues:
800 if not r in added:
807 if not r in added:
801 yield r
808 yield r
802
809
803 def add(self, value):
810 def add(self, value):
804 self.addedvalues.add(value)
811 self.addedvalues.add(value)
805
812
806 def update(self, values):
813 def update(self, values):
807 self.addedvalues.update(values)
814 self.addedvalues.update(values)
808
815
809 has = lazyset(self.ancestors(common))
816 has = lazyset(self.ancestors(common))
810 has.add(nullrev)
817 has.add(nullrev)
811 has.update(common)
818 has.update(common)
812
819
813 # take all ancestors from heads that aren't in has
820 # take all ancestors from heads that aren't in has
814 missing = set()
821 missing = set()
815 visit = collections.deque(r for r in heads if r not in has)
822 visit = collections.deque(r for r in heads if r not in has)
816 while visit:
823 while visit:
817 r = visit.popleft()
824 r = visit.popleft()
818 if r in missing:
825 if r in missing:
819 continue
826 continue
820 else:
827 else:
821 missing.add(r)
828 missing.add(r)
822 for p in self.parentrevs(r):
829 for p in self.parentrevs(r):
823 if p not in has:
830 if p not in has:
824 visit.append(p)
831 visit.append(p)
825 missing = list(missing)
832 missing = list(missing)
826 missing.sort()
833 missing.sort()
827 return has, [self.node(miss) for miss in missing]
834 return has, [self.node(miss) for miss in missing]
828
835
829 def incrementalmissingrevs(self, common=None):
836 def incrementalmissingrevs(self, common=None):
830 """Return an object that can be used to incrementally compute the
837 """Return an object that can be used to incrementally compute the
831 revision numbers of the ancestors of arbitrary sets that are not
838 revision numbers of the ancestors of arbitrary sets that are not
832 ancestors of common. This is an ancestor.incrementalmissingancestors
839 ancestors of common. This is an ancestor.incrementalmissingancestors
833 object.
840 object.
834
841
835 'common' is a list of revision numbers. If common is not supplied, uses
842 'common' is a list of revision numbers. If common is not supplied, uses
836 nullrev.
843 nullrev.
837 """
844 """
838 if common is None:
845 if common is None:
839 common = [nullrev]
846 common = [nullrev]
840
847
841 return ancestor.incrementalmissingancestors(self.parentrevs, common)
848 return ancestor.incrementalmissingancestors(self.parentrevs, common)
842
849
843 def findmissingrevs(self, common=None, heads=None):
850 def findmissingrevs(self, common=None, heads=None):
844 """Return the revision numbers of the ancestors of heads that
851 """Return the revision numbers of the ancestors of heads that
845 are not ancestors of common.
852 are not ancestors of common.
846
853
847 More specifically, return a list of revision numbers corresponding to
854 More specifically, return a list of revision numbers corresponding to
848 nodes N such that every N satisfies the following constraints:
855 nodes N such that every N satisfies the following constraints:
849
856
850 1. N is an ancestor of some node in 'heads'
857 1. N is an ancestor of some node in 'heads'
851 2. N is not an ancestor of any node in 'common'
858 2. N is not an ancestor of any node in 'common'
852
859
853 The list is sorted by revision number, meaning it is
860 The list is sorted by revision number, meaning it is
854 topologically sorted.
861 topologically sorted.
855
862
856 'heads' and 'common' are both lists of revision numbers. If heads is
863 'heads' and 'common' are both lists of revision numbers. If heads is
857 not supplied, uses all of the revlog's heads. If common is not
864 not supplied, uses all of the revlog's heads. If common is not
858 supplied, uses nullid."""
865 supplied, uses nullid."""
859 if common is None:
866 if common is None:
860 common = [nullrev]
867 common = [nullrev]
861 if heads is None:
868 if heads is None:
862 heads = self.headrevs()
869 heads = self.headrevs()
863
870
864 inc = self.incrementalmissingrevs(common=common)
871 inc = self.incrementalmissingrevs(common=common)
865 return inc.missingancestors(heads)
872 return inc.missingancestors(heads)
866
873
867 def findmissing(self, common=None, heads=None):
874 def findmissing(self, common=None, heads=None):
868 """Return the ancestors of heads that are not ancestors of common.
875 """Return the ancestors of heads that are not ancestors of common.
869
876
870 More specifically, return a list of nodes N such that every N
877 More specifically, return a list of nodes N such that every N
871 satisfies the following constraints:
878 satisfies the following constraints:
872
879
873 1. N is an ancestor of some node in 'heads'
880 1. N is an ancestor of some node in 'heads'
874 2. N is not an ancestor of any node in 'common'
881 2. N is not an ancestor of any node in 'common'
875
882
876 The list is sorted by revision number, meaning it is
883 The list is sorted by revision number, meaning it is
877 topologically sorted.
884 topologically sorted.
878
885
879 'heads' and 'common' are both lists of node IDs. If heads is
886 'heads' and 'common' are both lists of node IDs. If heads is
880 not supplied, uses all of the revlog's heads. If common is not
887 not supplied, uses all of the revlog's heads. If common is not
881 supplied, uses nullid."""
888 supplied, uses nullid."""
882 if common is None:
889 if common is None:
883 common = [nullid]
890 common = [nullid]
884 if heads is None:
891 if heads is None:
885 heads = self.heads()
892 heads = self.heads()
886
893
887 common = [self.rev(n) for n in common]
894 common = [self.rev(n) for n in common]
888 heads = [self.rev(n) for n in heads]
895 heads = [self.rev(n) for n in heads]
889
896
890 inc = self.incrementalmissingrevs(common=common)
897 inc = self.incrementalmissingrevs(common=common)
891 return [self.node(r) for r in inc.missingancestors(heads)]
898 return [self.node(r) for r in inc.missingancestors(heads)]
892
899
893 def nodesbetween(self, roots=None, heads=None):
900 def nodesbetween(self, roots=None, heads=None):
894 """Return a topological path from 'roots' to 'heads'.
901 """Return a topological path from 'roots' to 'heads'.
895
902
896 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
903 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
897 topologically sorted list of all nodes N that satisfy both of
904 topologically sorted list of all nodes N that satisfy both of
898 these constraints:
905 these constraints:
899
906
900 1. N is a descendant of some node in 'roots'
907 1. N is a descendant of some node in 'roots'
901 2. N is an ancestor of some node in 'heads'
908 2. N is an ancestor of some node in 'heads'
902
909
903 Every node is considered to be both a descendant and an ancestor
910 Every node is considered to be both a descendant and an ancestor
904 of itself, so every reachable node in 'roots' and 'heads' will be
911 of itself, so every reachable node in 'roots' and 'heads' will be
905 included in 'nodes'.
912 included in 'nodes'.
906
913
907 'outroots' is the list of reachable nodes in 'roots', i.e., the
914 'outroots' is the list of reachable nodes in 'roots', i.e., the
908 subset of 'roots' that is returned in 'nodes'. Likewise,
915 subset of 'roots' that is returned in 'nodes'. Likewise,
909 'outheads' is the subset of 'heads' that is also in 'nodes'.
916 'outheads' is the subset of 'heads' that is also in 'nodes'.
910
917
911 'roots' and 'heads' are both lists of node IDs. If 'roots' is
918 'roots' and 'heads' are both lists of node IDs. If 'roots' is
912 unspecified, uses nullid as the only root. If 'heads' is
919 unspecified, uses nullid as the only root. If 'heads' is
913 unspecified, uses list of all of the revlog's heads."""
920 unspecified, uses list of all of the revlog's heads."""
914 nonodes = ([], [], [])
921 nonodes = ([], [], [])
915 if roots is not None:
922 if roots is not None:
916 roots = list(roots)
923 roots = list(roots)
917 if not roots:
924 if not roots:
918 return nonodes
925 return nonodes
919 lowestrev = min([self.rev(n) for n in roots])
926 lowestrev = min([self.rev(n) for n in roots])
920 else:
927 else:
921 roots = [nullid] # Everybody's a descendant of nullid
928 roots = [nullid] # Everybody's a descendant of nullid
922 lowestrev = nullrev
929 lowestrev = nullrev
923 if (lowestrev == nullrev) and (heads is None):
930 if (lowestrev == nullrev) and (heads is None):
924 # We want _all_ the nodes!
931 # We want _all_ the nodes!
925 return ([self.node(r) for r in self], [nullid], list(self.heads()))
932 return ([self.node(r) for r in self], [nullid], list(self.heads()))
926 if heads is None:
933 if heads is None:
927 # All nodes are ancestors, so the latest ancestor is the last
934 # All nodes are ancestors, so the latest ancestor is the last
928 # node.
935 # node.
929 highestrev = len(self) - 1
936 highestrev = len(self) - 1
930 # Set ancestors to None to signal that every node is an ancestor.
937 # Set ancestors to None to signal that every node is an ancestor.
931 ancestors = None
938 ancestors = None
932 # Set heads to an empty dictionary for later discovery of heads
939 # Set heads to an empty dictionary for later discovery of heads
933 heads = {}
940 heads = {}
934 else:
941 else:
935 heads = list(heads)
942 heads = list(heads)
936 if not heads:
943 if not heads:
937 return nonodes
944 return nonodes
938 ancestors = set()
945 ancestors = set()
939 # Turn heads into a dictionary so we can remove 'fake' heads.
946 # Turn heads into a dictionary so we can remove 'fake' heads.
940 # Also, later we will be using it to filter out the heads we can't
947 # Also, later we will be using it to filter out the heads we can't
941 # find from roots.
948 # find from roots.
942 heads = dict.fromkeys(heads, False)
949 heads = dict.fromkeys(heads, False)
943 # Start at the top and keep marking parents until we're done.
950 # Start at the top and keep marking parents until we're done.
944 nodestotag = set(heads)
951 nodestotag = set(heads)
945 # Remember where the top was so we can use it as a limit later.
952 # Remember where the top was so we can use it as a limit later.
946 highestrev = max([self.rev(n) for n in nodestotag])
953 highestrev = max([self.rev(n) for n in nodestotag])
947 while nodestotag:
954 while nodestotag:
948 # grab a node to tag
955 # grab a node to tag
949 n = nodestotag.pop()
956 n = nodestotag.pop()
950 # Never tag nullid
957 # Never tag nullid
951 if n == nullid:
958 if n == nullid:
952 continue
959 continue
953 # A node's revision number represents its place in a
960 # A node's revision number represents its place in a
954 # topologically sorted list of nodes.
961 # topologically sorted list of nodes.
955 r = self.rev(n)
962 r = self.rev(n)
956 if r >= lowestrev:
963 if r >= lowestrev:
957 if n not in ancestors:
964 if n not in ancestors:
958 # If we are possibly a descendant of one of the roots
965 # If we are possibly a descendant of one of the roots
959 # and we haven't already been marked as an ancestor
966 # and we haven't already been marked as an ancestor
960 ancestors.add(n) # Mark as ancestor
967 ancestors.add(n) # Mark as ancestor
961 # Add non-nullid parents to list of nodes to tag.
968 # Add non-nullid parents to list of nodes to tag.
962 nodestotag.update([p for p in self.parents(n) if
969 nodestotag.update([p for p in self.parents(n) if
963 p != nullid])
970 p != nullid])
964 elif n in heads: # We've seen it before, is it a fake head?
971 elif n in heads: # We've seen it before, is it a fake head?
965 # So it is, real heads should not be the ancestors of
972 # So it is, real heads should not be the ancestors of
966 # any other heads.
973 # any other heads.
967 heads.pop(n)
974 heads.pop(n)
968 if not ancestors:
975 if not ancestors:
969 return nonodes
976 return nonodes
970 # Now that we have our set of ancestors, we want to remove any
977 # Now that we have our set of ancestors, we want to remove any
971 # roots that are not ancestors.
978 # roots that are not ancestors.
972
979
973 # If one of the roots was nullid, everything is included anyway.
980 # If one of the roots was nullid, everything is included anyway.
974 if lowestrev > nullrev:
981 if lowestrev > nullrev:
975 # But, since we weren't, let's recompute the lowest rev to not
982 # But, since we weren't, let's recompute the lowest rev to not
976 # include roots that aren't ancestors.
983 # include roots that aren't ancestors.
977
984
978 # Filter out roots that aren't ancestors of heads
985 # Filter out roots that aren't ancestors of heads
979 roots = [root for root in roots if root in ancestors]
986 roots = [root for root in roots if root in ancestors]
980 # Recompute the lowest revision
987 # Recompute the lowest revision
981 if roots:
988 if roots:
982 lowestrev = min([self.rev(root) for root in roots])
989 lowestrev = min([self.rev(root) for root in roots])
983 else:
990 else:
984 # No more roots? Return empty list
991 # No more roots? Return empty list
985 return nonodes
992 return nonodes
986 else:
993 else:
987 # We are descending from nullid, and don't need to care about
994 # We are descending from nullid, and don't need to care about
988 # any other roots.
995 # any other roots.
989 lowestrev = nullrev
996 lowestrev = nullrev
990 roots = [nullid]
997 roots = [nullid]
991 # Transform our roots list into a set.
998 # Transform our roots list into a set.
992 descendants = set(roots)
999 descendants = set(roots)
993 # Also, keep the original roots so we can filter out roots that aren't
1000 # Also, keep the original roots so we can filter out roots that aren't
994 # 'real' roots (i.e. are descended from other roots).
1001 # 'real' roots (i.e. are descended from other roots).
995 roots = descendants.copy()
1002 roots = descendants.copy()
996 # Our topologically sorted list of output nodes.
1003 # Our topologically sorted list of output nodes.
997 orderedout = []
1004 orderedout = []
998 # Don't start at nullid since we don't want nullid in our output list,
1005 # Don't start at nullid since we don't want nullid in our output list,
999 # and if nullid shows up in descendants, empty parents will look like
1006 # and if nullid shows up in descendants, empty parents will look like
1000 # they're descendants.
1007 # they're descendants.
1001 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1008 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1002 n = self.node(r)
1009 n = self.node(r)
1003 isdescendant = False
1010 isdescendant = False
1004 if lowestrev == nullrev: # Everybody is a descendant of nullid
1011 if lowestrev == nullrev: # Everybody is a descendant of nullid
1005 isdescendant = True
1012 isdescendant = True
1006 elif n in descendants:
1013 elif n in descendants:
1007 # n is already a descendant
1014 # n is already a descendant
1008 isdescendant = True
1015 isdescendant = True
1009 # This check only needs to be done here because all the roots
1016 # This check only needs to be done here because all the roots
1010 # will start being marked is descendants before the loop.
1017 # will start being marked is descendants before the loop.
1011 if n in roots:
1018 if n in roots:
1012 # If n was a root, check if it's a 'real' root.
1019 # If n was a root, check if it's a 'real' root.
1013 p = tuple(self.parents(n))
1020 p = tuple(self.parents(n))
1014 # If any of its parents are descendants, it's not a root.
1021 # If any of its parents are descendants, it's not a root.
1015 if (p[0] in descendants) or (p[1] in descendants):
1022 if (p[0] in descendants) or (p[1] in descendants):
1016 roots.remove(n)
1023 roots.remove(n)
1017 else:
1024 else:
1018 p = tuple(self.parents(n))
1025 p = tuple(self.parents(n))
1019 # A node is a descendant if either of its parents are
1026 # A node is a descendant if either of its parents are
1020 # descendants. (We seeded the dependents list with the roots
1027 # descendants. (We seeded the dependents list with the roots
1021 # up there, remember?)
1028 # up there, remember?)
1022 if (p[0] in descendants) or (p[1] in descendants):
1029 if (p[0] in descendants) or (p[1] in descendants):
1023 descendants.add(n)
1030 descendants.add(n)
1024 isdescendant = True
1031 isdescendant = True
1025 if isdescendant and ((ancestors is None) or (n in ancestors)):
1032 if isdescendant and ((ancestors is None) or (n in ancestors)):
1026 # Only include nodes that are both descendants and ancestors.
1033 # Only include nodes that are both descendants and ancestors.
1027 orderedout.append(n)
1034 orderedout.append(n)
1028 if (ancestors is not None) and (n in heads):
1035 if (ancestors is not None) and (n in heads):
1029 # We're trying to figure out which heads are reachable
1036 # We're trying to figure out which heads are reachable
1030 # from roots.
1037 # from roots.
1031 # Mark this head as having been reached
1038 # Mark this head as having been reached
1032 heads[n] = True
1039 heads[n] = True
1033 elif ancestors is None:
1040 elif ancestors is None:
1034 # Otherwise, we're trying to discover the heads.
1041 # Otherwise, we're trying to discover the heads.
1035 # Assume this is a head because if it isn't, the next step
1042 # Assume this is a head because if it isn't, the next step
1036 # will eventually remove it.
1043 # will eventually remove it.
1037 heads[n] = True
1044 heads[n] = True
1038 # But, obviously its parents aren't.
1045 # But, obviously its parents aren't.
1039 for p in self.parents(n):
1046 for p in self.parents(n):
1040 heads.pop(p, None)
1047 heads.pop(p, None)
1041 heads = [head for head, flag in heads.iteritems() if flag]
1048 heads = [head for head, flag in heads.iteritems() if flag]
1042 roots = list(roots)
1049 roots = list(roots)
1043 assert orderedout
1050 assert orderedout
1044 assert roots
1051 assert roots
1045 assert heads
1052 assert heads
1046 return (orderedout, roots, heads)
1053 return (orderedout, roots, heads)
1047
1054
1048 def headrevs(self):
1055 def headrevs(self):
1049 try:
1056 try:
1050 return self.index.headrevs()
1057 return self.index.headrevs()
1051 except AttributeError:
1058 except AttributeError:
1052 return self._headrevs()
1059 return self._headrevs()
1053
1060
1054 def computephases(self, roots):
1061 def computephases(self, roots):
1055 return self.index.computephasesmapsets(roots)
1062 return self.index.computephasesmapsets(roots)
1056
1063
1057 def _headrevs(self):
1064 def _headrevs(self):
1058 count = len(self)
1065 count = len(self)
1059 if not count:
1066 if not count:
1060 return [nullrev]
1067 return [nullrev]
1061 # we won't iter over filtered rev so nobody is a head at start
1068 # we won't iter over filtered rev so nobody is a head at start
1062 ishead = [0] * (count + 1)
1069 ishead = [0] * (count + 1)
1063 index = self.index
1070 index = self.index
1064 for r in self:
1071 for r in self:
1065 ishead[r] = 1 # I may be an head
1072 ishead[r] = 1 # I may be an head
1066 e = index[r]
1073 e = index[r]
1067 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1074 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1068 return [r for r, val in enumerate(ishead) if val]
1075 return [r for r, val in enumerate(ishead) if val]
1069
1076
1070 def heads(self, start=None, stop=None):
1077 def heads(self, start=None, stop=None):
1071 """return the list of all nodes that have no children
1078 """return the list of all nodes that have no children
1072
1079
1073 if start is specified, only heads that are descendants of
1080 if start is specified, only heads that are descendants of
1074 start will be returned
1081 start will be returned
1075 if stop is specified, it will consider all the revs from stop
1082 if stop is specified, it will consider all the revs from stop
1076 as if they had no children
1083 as if they had no children
1077 """
1084 """
1078 if start is None and stop is None:
1085 if start is None and stop is None:
1079 if not len(self):
1086 if not len(self):
1080 return [nullid]
1087 return [nullid]
1081 return [self.node(r) for r in self.headrevs()]
1088 return [self.node(r) for r in self.headrevs()]
1082
1089
1083 if start is None:
1090 if start is None:
1084 start = nullrev
1091 start = nullrev
1085 else:
1092 else:
1086 start = self.rev(start)
1093 start = self.rev(start)
1087
1094
1088 stoprevs = set(self.rev(n) for n in stop or [])
1095 stoprevs = set(self.rev(n) for n in stop or [])
1089
1096
1090 revs = dagop.headrevssubset(self.revs, self.parentrevs, startrev=start,
1097 revs = dagop.headrevssubset(self.revs, self.parentrevs, startrev=start,
1091 stoprevs=stoprevs)
1098 stoprevs=stoprevs)
1092
1099
1093 return [self.node(rev) for rev in revs]
1100 return [self.node(rev) for rev in revs]
1094
1101
1095 def children(self, node):
1102 def children(self, node):
1096 """find the children of a given node"""
1103 """find the children of a given node"""
1097 c = []
1104 c = []
1098 p = self.rev(node)
1105 p = self.rev(node)
1099 for r in self.revs(start=p + 1):
1106 for r in self.revs(start=p + 1):
1100 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1107 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1101 if prevs:
1108 if prevs:
1102 for pr in prevs:
1109 for pr in prevs:
1103 if pr == p:
1110 if pr == p:
1104 c.append(self.node(r))
1111 c.append(self.node(r))
1105 elif p == nullrev:
1112 elif p == nullrev:
1106 c.append(self.node(r))
1113 c.append(self.node(r))
1107 return c
1114 return c
1108
1115
1109 def commonancestorsheads(self, a, b):
1116 def commonancestorsheads(self, a, b):
1110 """calculate all the heads of the common ancestors of nodes a and b"""
1117 """calculate all the heads of the common ancestors of nodes a and b"""
1111 a, b = self.rev(a), self.rev(b)
1118 a, b = self.rev(a), self.rev(b)
1112 ancs = self._commonancestorsheads(a, b)
1119 ancs = self._commonancestorsheads(a, b)
1113 return pycompat.maplist(self.node, ancs)
1120 return pycompat.maplist(self.node, ancs)
1114
1121
1115 def _commonancestorsheads(self, *revs):
1122 def _commonancestorsheads(self, *revs):
1116 """calculate all the heads of the common ancestors of revs"""
1123 """calculate all the heads of the common ancestors of revs"""
1117 try:
1124 try:
1118 ancs = self.index.commonancestorsheads(*revs)
1125 ancs = self.index.commonancestorsheads(*revs)
1119 except (AttributeError, OverflowError): # C implementation failed
1126 except (AttributeError, OverflowError): # C implementation failed
1120 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1127 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1121 return ancs
1128 return ancs
1122
1129
1123 def isancestor(self, a, b):
1130 def isancestor(self, a, b):
1124 """return True if node a is an ancestor of node b
1131 """return True if node a is an ancestor of node b
1125
1132
1126 A revision is considered an ancestor of itself."""
1133 A revision is considered an ancestor of itself."""
1127 a, b = self.rev(a), self.rev(b)
1134 a, b = self.rev(a), self.rev(b)
1128 return self.isancestorrev(a, b)
1135 return self.isancestorrev(a, b)
1129
1136
1130 def isancestorrev(self, a, b):
1137 def isancestorrev(self, a, b):
1131 """return True if revision a is an ancestor of revision b
1138 """return True if revision a is an ancestor of revision b
1132
1139
1133 A revision is considered an ancestor of itself.
1140 A revision is considered an ancestor of itself.
1134
1141
1135 The implementation of this is trivial but the use of
1142 The implementation of this is trivial but the use of
1136 commonancestorsheads is not."""
1143 commonancestorsheads is not."""
1137 if a == nullrev:
1144 if a == nullrev:
1138 return True
1145 return True
1139 elif a == b:
1146 elif a == b:
1140 return True
1147 return True
1141 elif a > b:
1148 elif a > b:
1142 return False
1149 return False
1143 return a in self._commonancestorsheads(a, b)
1150 return a in self._commonancestorsheads(a, b)
1144
1151
1145 def ancestor(self, a, b):
1152 def ancestor(self, a, b):
1146 """calculate the "best" common ancestor of nodes a and b"""
1153 """calculate the "best" common ancestor of nodes a and b"""
1147
1154
1148 a, b = self.rev(a), self.rev(b)
1155 a, b = self.rev(a), self.rev(b)
1149 try:
1156 try:
1150 ancs = self.index.ancestors(a, b)
1157 ancs = self.index.ancestors(a, b)
1151 except (AttributeError, OverflowError):
1158 except (AttributeError, OverflowError):
1152 ancs = ancestor.ancestors(self.parentrevs, a, b)
1159 ancs = ancestor.ancestors(self.parentrevs, a, b)
1153 if ancs:
1160 if ancs:
1154 # choose a consistent winner when there's a tie
1161 # choose a consistent winner when there's a tie
1155 return min(map(self.node, ancs))
1162 return min(map(self.node, ancs))
1156 return nullid
1163 return nullid
1157
1164
1158 def _match(self, id):
1165 def _match(self, id):
1159 if isinstance(id, int):
1166 if isinstance(id, int):
1160 # rev
1167 # rev
1161 return self.node(id)
1168 return self.node(id)
1162 if len(id) == 20:
1169 if len(id) == 20:
1163 # possibly a binary node
1170 # possibly a binary node
1164 # odds of a binary node being all hex in ASCII are 1 in 10**25
1171 # odds of a binary node being all hex in ASCII are 1 in 10**25
1165 try:
1172 try:
1166 node = id
1173 node = id
1167 self.rev(node) # quick search the index
1174 self.rev(node) # quick search the index
1168 return node
1175 return node
1169 except error.LookupError:
1176 except error.LookupError:
1170 pass # may be partial hex id
1177 pass # may be partial hex id
1171 try:
1178 try:
1172 # str(rev)
1179 # str(rev)
1173 rev = int(id)
1180 rev = int(id)
1174 if "%d" % rev != id:
1181 if "%d" % rev != id:
1175 raise ValueError
1182 raise ValueError
1176 if rev < 0:
1183 if rev < 0:
1177 rev = len(self) + rev
1184 rev = len(self) + rev
1178 if rev < 0 or rev >= len(self):
1185 if rev < 0 or rev >= len(self):
1179 raise ValueError
1186 raise ValueError
1180 return self.node(rev)
1187 return self.node(rev)
1181 except (ValueError, OverflowError):
1188 except (ValueError, OverflowError):
1182 pass
1189 pass
1183 if len(id) == 40:
1190 if len(id) == 40:
1184 try:
1191 try:
1185 # a full hex nodeid?
1192 # a full hex nodeid?
1186 node = bin(id)
1193 node = bin(id)
1187 self.rev(node)
1194 self.rev(node)
1188 return node
1195 return node
1189 except (TypeError, error.LookupError):
1196 except (TypeError, error.LookupError):
1190 pass
1197 pass
1191
1198
1192 def _partialmatch(self, id):
1199 def _partialmatch(self, id):
1193 # we don't care wdirfilenodeids as they should be always full hash
1200 # we don't care wdirfilenodeids as they should be always full hash
1194 maybewdir = wdirhex.startswith(id)
1201 maybewdir = wdirhex.startswith(id)
1195 try:
1202 try:
1196 partial = self.index.partialmatch(id)
1203 partial = self.index.partialmatch(id)
1197 if partial and self.hasnode(partial):
1204 if partial and self.hasnode(partial):
1198 if maybewdir:
1205 if maybewdir:
1199 # single 'ff...' match in radix tree, ambiguous with wdir
1206 # single 'ff...' match in radix tree, ambiguous with wdir
1200 raise error.RevlogError
1207 raise error.RevlogError
1201 return partial
1208 return partial
1202 if maybewdir:
1209 if maybewdir:
1203 # no 'ff...' match in radix tree, wdir identified
1210 # no 'ff...' match in radix tree, wdir identified
1204 raise error.WdirUnsupported
1211 raise error.WdirUnsupported
1205 return None
1212 return None
1206 except error.RevlogError:
1213 except error.RevlogError:
1207 # parsers.c radix tree lookup gave multiple matches
1214 # parsers.c radix tree lookup gave multiple matches
1208 # fast path: for unfiltered changelog, radix tree is accurate
1215 # fast path: for unfiltered changelog, radix tree is accurate
1209 if not getattr(self, 'filteredrevs', None):
1216 if not getattr(self, 'filteredrevs', None):
1210 raise error.AmbiguousPrefixLookupError(
1217 raise error.AmbiguousPrefixLookupError(
1211 id, self.indexfile, _('ambiguous identifier'))
1218 id, self.indexfile, _('ambiguous identifier'))
1212 # fall through to slow path that filters hidden revisions
1219 # fall through to slow path that filters hidden revisions
1213 except (AttributeError, ValueError):
1220 except (AttributeError, ValueError):
1214 # we are pure python, or key was too short to search radix tree
1221 # we are pure python, or key was too short to search radix tree
1215 pass
1222 pass
1216
1223
1217 if id in self._pcache:
1224 if id in self._pcache:
1218 return self._pcache[id]
1225 return self._pcache[id]
1219
1226
1220 if len(id) <= 40:
1227 if len(id) <= 40:
1221 try:
1228 try:
1222 # hex(node)[:...]
1229 # hex(node)[:...]
1223 l = len(id) // 2 # grab an even number of digits
1230 l = len(id) // 2 # grab an even number of digits
1224 prefix = bin(id[:l * 2])
1231 prefix = bin(id[:l * 2])
1225 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1232 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1226 nl = [n for n in nl if hex(n).startswith(id) and
1233 nl = [n for n in nl if hex(n).startswith(id) and
1227 self.hasnode(n)]
1234 self.hasnode(n)]
1228 if nullhex.startswith(id):
1235 if nullhex.startswith(id):
1229 nl.append(nullid)
1236 nl.append(nullid)
1230 if len(nl) > 0:
1237 if len(nl) > 0:
1231 if len(nl) == 1 and not maybewdir:
1238 if len(nl) == 1 and not maybewdir:
1232 self._pcache[id] = nl[0]
1239 self._pcache[id] = nl[0]
1233 return nl[0]
1240 return nl[0]
1234 raise error.AmbiguousPrefixLookupError(
1241 raise error.AmbiguousPrefixLookupError(
1235 id, self.indexfile, _('ambiguous identifier'))
1242 id, self.indexfile, _('ambiguous identifier'))
1236 if maybewdir:
1243 if maybewdir:
1237 raise error.WdirUnsupported
1244 raise error.WdirUnsupported
1238 return None
1245 return None
1239 except TypeError:
1246 except TypeError:
1240 pass
1247 pass
1241
1248
1242 def lookup(self, id):
1249 def lookup(self, id):
1243 """locate a node based on:
1250 """locate a node based on:
1244 - revision number or str(revision number)
1251 - revision number or str(revision number)
1245 - nodeid or subset of hex nodeid
1252 - nodeid or subset of hex nodeid
1246 """
1253 """
1247 n = self._match(id)
1254 n = self._match(id)
1248 if n is not None:
1255 if n is not None:
1249 return n
1256 return n
1250 n = self._partialmatch(id)
1257 n = self._partialmatch(id)
1251 if n:
1258 if n:
1252 return n
1259 return n
1253
1260
1254 raise error.LookupError(id, self.indexfile, _('no match found'))
1261 raise error.LookupError(id, self.indexfile, _('no match found'))
1255
1262
1256 def shortest(self, node, minlength=1):
1263 def shortest(self, node, minlength=1):
1257 """Find the shortest unambiguous prefix that matches node."""
1264 """Find the shortest unambiguous prefix that matches node."""
1258 def isvalid(prefix):
1265 def isvalid(prefix):
1259 try:
1266 try:
1260 node = self._partialmatch(prefix)
1267 node = self._partialmatch(prefix)
1261 except error.AmbiguousPrefixLookupError:
1268 except error.AmbiguousPrefixLookupError:
1262 return False
1269 return False
1263 except error.WdirUnsupported:
1270 except error.WdirUnsupported:
1264 # single 'ff...' match
1271 # single 'ff...' match
1265 return True
1272 return True
1266 if node is None:
1273 if node is None:
1267 raise error.LookupError(node, self.indexfile, _('no node'))
1274 raise error.LookupError(node, self.indexfile, _('no node'))
1268 return True
1275 return True
1269
1276
1270 def maybewdir(prefix):
1277 def maybewdir(prefix):
1271 return all(c == 'f' for c in prefix)
1278 return all(c == 'f' for c in prefix)
1272
1279
1273 hexnode = hex(node)
1280 hexnode = hex(node)
1274
1281
1275 def disambiguate(hexnode, minlength):
1282 def disambiguate(hexnode, minlength):
1276 """Disambiguate against wdirid."""
1283 """Disambiguate against wdirid."""
1277 for length in range(minlength, 41):
1284 for length in range(minlength, 41):
1278 prefix = hexnode[:length]
1285 prefix = hexnode[:length]
1279 if not maybewdir(prefix):
1286 if not maybewdir(prefix):
1280 return prefix
1287 return prefix
1281
1288
1282 if not getattr(self, 'filteredrevs', None):
1289 if not getattr(self, 'filteredrevs', None):
1283 try:
1290 try:
1284 length = max(self.index.shortest(node), minlength)
1291 length = max(self.index.shortest(node), minlength)
1285 return disambiguate(hexnode, length)
1292 return disambiguate(hexnode, length)
1286 except error.RevlogError:
1293 except error.RevlogError:
1287 if node != wdirid:
1294 if node != wdirid:
1288 raise error.LookupError(node, self.indexfile, _('no node'))
1295 raise error.LookupError(node, self.indexfile, _('no node'))
1289 except AttributeError:
1296 except AttributeError:
1290 # Fall through to pure code
1297 # Fall through to pure code
1291 pass
1298 pass
1292
1299
1293 if node == wdirid:
1300 if node == wdirid:
1294 for length in range(minlength, 41):
1301 for length in range(minlength, 41):
1295 prefix = hexnode[:length]
1302 prefix = hexnode[:length]
1296 if isvalid(prefix):
1303 if isvalid(prefix):
1297 return prefix
1304 return prefix
1298
1305
1299 for length in range(minlength, 41):
1306 for length in range(minlength, 41):
1300 prefix = hexnode[:length]
1307 prefix = hexnode[:length]
1301 if isvalid(prefix):
1308 if isvalid(prefix):
1302 return disambiguate(hexnode, length)
1309 return disambiguate(hexnode, length)
1303
1310
1304 def cmp(self, node, text):
1311 def cmp(self, node, text):
1305 """compare text with a given file revision
1312 """compare text with a given file revision
1306
1313
1307 returns True if text is different than what is stored.
1314 returns True if text is different than what is stored.
1308 """
1315 """
1309 p1, p2 = self.parents(node)
1316 p1, p2 = self.parents(node)
1310 return storageutil.hashrevisionsha1(text, p1, p2) != node
1317 return storageutil.hashrevisionsha1(text, p1, p2) != node
1311
1318
1312 def _cachesegment(self, offset, data):
1319 def _cachesegment(self, offset, data):
1313 """Add a segment to the revlog cache.
1320 """Add a segment to the revlog cache.
1314
1321
1315 Accepts an absolute offset and the data that is at that location.
1322 Accepts an absolute offset and the data that is at that location.
1316 """
1323 """
1317 o, d = self._chunkcache
1324 o, d = self._chunkcache
1318 # try to add to existing cache
1325 # try to add to existing cache
1319 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1326 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1320 self._chunkcache = o, d + data
1327 self._chunkcache = o, d + data
1321 else:
1328 else:
1322 self._chunkcache = offset, data
1329 self._chunkcache = offset, data
1323
1330
1324 def _readsegment(self, offset, length, df=None):
1331 def _readsegment(self, offset, length, df=None):
1325 """Load a segment of raw data from the revlog.
1332 """Load a segment of raw data from the revlog.
1326
1333
1327 Accepts an absolute offset, length to read, and an optional existing
1334 Accepts an absolute offset, length to read, and an optional existing
1328 file handle to read from.
1335 file handle to read from.
1329
1336
1330 If an existing file handle is passed, it will be seeked and the
1337 If an existing file handle is passed, it will be seeked and the
1331 original seek position will NOT be restored.
1338 original seek position will NOT be restored.
1332
1339
1333 Returns a str or buffer of raw byte data.
1340 Returns a str or buffer of raw byte data.
1334 """
1341 """
1335 # Cache data both forward and backward around the requested
1342 # Cache data both forward and backward around the requested
1336 # data, in a fixed size window. This helps speed up operations
1343 # data, in a fixed size window. This helps speed up operations
1337 # involving reading the revlog backwards.
1344 # involving reading the revlog backwards.
1338 cachesize = self._chunkcachesize
1345 cachesize = self._chunkcachesize
1339 realoffset = offset & ~(cachesize - 1)
1346 realoffset = offset & ~(cachesize - 1)
1340 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
1347 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
1341 - realoffset)
1348 - realoffset)
1342 with self._datareadfp(df) as df:
1349 with self._datareadfp(df) as df:
1343 df.seek(realoffset)
1350 df.seek(realoffset)
1344 d = df.read(reallength)
1351 d = df.read(reallength)
1345 self._cachesegment(realoffset, d)
1352 self._cachesegment(realoffset, d)
1346 if offset != realoffset or reallength != length:
1353 if offset != realoffset or reallength != length:
1347 return util.buffer(d, offset - realoffset, length)
1354 return util.buffer(d, offset - realoffset, length)
1348 return d
1355 return d
1349
1356
1350 def _getsegment(self, offset, length, df=None):
1357 def _getsegment(self, offset, length, df=None):
1351 """Obtain a segment of raw data from the revlog.
1358 """Obtain a segment of raw data from the revlog.
1352
1359
1353 Accepts an absolute offset, length of bytes to obtain, and an
1360 Accepts an absolute offset, length of bytes to obtain, and an
1354 optional file handle to the already-opened revlog. If the file
1361 optional file handle to the already-opened revlog. If the file
1355 handle is used, it's original seek position will not be preserved.
1362 handle is used, it's original seek position will not be preserved.
1356
1363
1357 Requests for data may be returned from a cache.
1364 Requests for data may be returned from a cache.
1358
1365
1359 Returns a str or a buffer instance of raw byte data.
1366 Returns a str or a buffer instance of raw byte data.
1360 """
1367 """
1361 o, d = self._chunkcache
1368 o, d = self._chunkcache
1362 l = len(d)
1369 l = len(d)
1363
1370
1364 # is it in the cache?
1371 # is it in the cache?
1365 cachestart = offset - o
1372 cachestart = offset - o
1366 cacheend = cachestart + length
1373 cacheend = cachestart + length
1367 if cachestart >= 0 and cacheend <= l:
1374 if cachestart >= 0 and cacheend <= l:
1368 if cachestart == 0 and cacheend == l:
1375 if cachestart == 0 and cacheend == l:
1369 return d # avoid a copy
1376 return d # avoid a copy
1370 return util.buffer(d, cachestart, cacheend - cachestart)
1377 return util.buffer(d, cachestart, cacheend - cachestart)
1371
1378
1372 return self._readsegment(offset, length, df=df)
1379 return self._readsegment(offset, length, df=df)
1373
1380
1374 def _getsegmentforrevs(self, startrev, endrev, df=None):
1381 def _getsegmentforrevs(self, startrev, endrev, df=None):
1375 """Obtain a segment of raw data corresponding to a range of revisions.
1382 """Obtain a segment of raw data corresponding to a range of revisions.
1376
1383
1377 Accepts the start and end revisions and an optional already-open
1384 Accepts the start and end revisions and an optional already-open
1378 file handle to be used for reading. If the file handle is read, its
1385 file handle to be used for reading. If the file handle is read, its
1379 seek position will not be preserved.
1386 seek position will not be preserved.
1380
1387
1381 Requests for data may be satisfied by a cache.
1388 Requests for data may be satisfied by a cache.
1382
1389
1383 Returns a 2-tuple of (offset, data) for the requested range of
1390 Returns a 2-tuple of (offset, data) for the requested range of
1384 revisions. Offset is the integer offset from the beginning of the
1391 revisions. Offset is the integer offset from the beginning of the
1385 revlog and data is a str or buffer of the raw byte data.
1392 revlog and data is a str or buffer of the raw byte data.
1386
1393
1387 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1394 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1388 to determine where each revision's data begins and ends.
1395 to determine where each revision's data begins and ends.
1389 """
1396 """
1390 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1397 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1391 # (functions are expensive).
1398 # (functions are expensive).
1392 index = self.index
1399 index = self.index
1393 istart = index[startrev]
1400 istart = index[startrev]
1394 start = int(istart[0] >> 16)
1401 start = int(istart[0] >> 16)
1395 if startrev == endrev:
1402 if startrev == endrev:
1396 end = start + istart[1]
1403 end = start + istart[1]
1397 else:
1404 else:
1398 iend = index[endrev]
1405 iend = index[endrev]
1399 end = int(iend[0] >> 16) + iend[1]
1406 end = int(iend[0] >> 16) + iend[1]
1400
1407
1401 if self._inline:
1408 if self._inline:
1402 start += (startrev + 1) * self._io.size
1409 start += (startrev + 1) * self._io.size
1403 end += (endrev + 1) * self._io.size
1410 end += (endrev + 1) * self._io.size
1404 length = end - start
1411 length = end - start
1405
1412
1406 return start, self._getsegment(start, length, df=df)
1413 return start, self._getsegment(start, length, df=df)
1407
1414
1408 def _chunk(self, rev, df=None):
1415 def _chunk(self, rev, df=None):
1409 """Obtain a single decompressed chunk for a revision.
1416 """Obtain a single decompressed chunk for a revision.
1410
1417
1411 Accepts an integer revision and an optional already-open file handle
1418 Accepts an integer revision and an optional already-open file handle
1412 to be used for reading. If used, the seek position of the file will not
1419 to be used for reading. If used, the seek position of the file will not
1413 be preserved.
1420 be preserved.
1414
1421
1415 Returns a str holding uncompressed data for the requested revision.
1422 Returns a str holding uncompressed data for the requested revision.
1416 """
1423 """
1417 return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
1424 return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
1418
1425
1419 def _chunks(self, revs, df=None, targetsize=None):
1426 def _chunks(self, revs, df=None, targetsize=None):
1420 """Obtain decompressed chunks for the specified revisions.
1427 """Obtain decompressed chunks for the specified revisions.
1421
1428
1422 Accepts an iterable of numeric revisions that are assumed to be in
1429 Accepts an iterable of numeric revisions that are assumed to be in
1423 ascending order. Also accepts an optional already-open file handle
1430 ascending order. Also accepts an optional already-open file handle
1424 to be used for reading. If used, the seek position of the file will
1431 to be used for reading. If used, the seek position of the file will
1425 not be preserved.
1432 not be preserved.
1426
1433
1427 This function is similar to calling ``self._chunk()`` multiple times,
1434 This function is similar to calling ``self._chunk()`` multiple times,
1428 but is faster.
1435 but is faster.
1429
1436
1430 Returns a list with decompressed data for each requested revision.
1437 Returns a list with decompressed data for each requested revision.
1431 """
1438 """
1432 if not revs:
1439 if not revs:
1433 return []
1440 return []
1434 start = self.start
1441 start = self.start
1435 length = self.length
1442 length = self.length
1436 inline = self._inline
1443 inline = self._inline
1437 iosize = self._io.size
1444 iosize = self._io.size
1438 buffer = util.buffer
1445 buffer = util.buffer
1439
1446
1440 l = []
1447 l = []
1441 ladd = l.append
1448 ladd = l.append
1442
1449
1443 if not self._withsparseread:
1450 if not self._withsparseread:
1444 slicedchunks = (revs,)
1451 slicedchunks = (revs,)
1445 else:
1452 else:
1446 slicedchunks = deltautil.slicechunk(self, revs,
1453 slicedchunks = deltautil.slicechunk(self, revs,
1447 targetsize=targetsize)
1454 targetsize=targetsize)
1448
1455
1449 for revschunk in slicedchunks:
1456 for revschunk in slicedchunks:
1450 firstrev = revschunk[0]
1457 firstrev = revschunk[0]
1451 # Skip trailing revisions with empty diff
1458 # Skip trailing revisions with empty diff
1452 for lastrev in revschunk[::-1]:
1459 for lastrev in revschunk[::-1]:
1453 if length(lastrev) != 0:
1460 if length(lastrev) != 0:
1454 break
1461 break
1455
1462
1456 try:
1463 try:
1457 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1464 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1458 except OverflowError:
1465 except OverflowError:
1459 # issue4215 - we can't cache a run of chunks greater than
1466 # issue4215 - we can't cache a run of chunks greater than
1460 # 2G on Windows
1467 # 2G on Windows
1461 return [self._chunk(rev, df=df) for rev in revschunk]
1468 return [self._chunk(rev, df=df) for rev in revschunk]
1462
1469
1463 decomp = self.decompress
1470 decomp = self.decompress
1464 for rev in revschunk:
1471 for rev in revschunk:
1465 chunkstart = start(rev)
1472 chunkstart = start(rev)
1466 if inline:
1473 if inline:
1467 chunkstart += (rev + 1) * iosize
1474 chunkstart += (rev + 1) * iosize
1468 chunklength = length(rev)
1475 chunklength = length(rev)
1469 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
1476 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
1470
1477
1471 return l
1478 return l
1472
1479
1473 def _chunkclear(self):
1480 def _chunkclear(self):
1474 """Clear the raw chunk cache."""
1481 """Clear the raw chunk cache."""
1475 self._chunkcache = (0, '')
1482 self._chunkcache = (0, '')
1476
1483
1477 def deltaparent(self, rev):
1484 def deltaparent(self, rev):
1478 """return deltaparent of the given revision"""
1485 """return deltaparent of the given revision"""
1479 base = self.index[rev][3]
1486 base = self.index[rev][3]
1480 if base == rev:
1487 if base == rev:
1481 return nullrev
1488 return nullrev
1482 elif self._generaldelta:
1489 elif self._generaldelta:
1483 return base
1490 return base
1484 else:
1491 else:
1485 return rev - 1
1492 return rev - 1
1486
1493
1487 def issnapshot(self, rev):
1494 def issnapshot(self, rev):
1488 """tells whether rev is a snapshot
1495 """tells whether rev is a snapshot
1489 """
1496 """
1490 if rev == nullrev:
1497 if rev == nullrev:
1491 return True
1498 return True
1492 deltap = self.deltaparent(rev)
1499 deltap = self.deltaparent(rev)
1493 if deltap == nullrev:
1500 if deltap == nullrev:
1494 return True
1501 return True
1495 p1, p2 = self.parentrevs(rev)
1502 p1, p2 = self.parentrevs(rev)
1496 if deltap in (p1, p2):
1503 if deltap in (p1, p2):
1497 return False
1504 return False
1498 return self.issnapshot(deltap)
1505 return self.issnapshot(deltap)
1499
1506
1500 def snapshotdepth(self, rev):
1507 def snapshotdepth(self, rev):
1501 """number of snapshot in the chain before this one"""
1508 """number of snapshot in the chain before this one"""
1502 if not self.issnapshot(rev):
1509 if not self.issnapshot(rev):
1503 raise error.ProgrammingError('revision %d not a snapshot')
1510 raise error.ProgrammingError('revision %d not a snapshot')
1504 return len(self._deltachain(rev)[0]) - 1
1511 return len(self._deltachain(rev)[0]) - 1
1505
1512
1506 def revdiff(self, rev1, rev2):
1513 def revdiff(self, rev1, rev2):
1507 """return or calculate a delta between two revisions
1514 """return or calculate a delta between two revisions
1508
1515
1509 The delta calculated is in binary form and is intended to be written to
1516 The delta calculated is in binary form and is intended to be written to
1510 revlog data directly. So this function needs raw revision data.
1517 revlog data directly. So this function needs raw revision data.
1511 """
1518 """
1512 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1519 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1513 return bytes(self._chunk(rev2))
1520 return bytes(self._chunk(rev2))
1514
1521
1515 return mdiff.textdiff(self.revision(rev1, raw=True),
1522 return mdiff.textdiff(self.revision(rev1, raw=True),
1516 self.revision(rev2, raw=True))
1523 self.revision(rev2, raw=True))
1517
1524
1518 def revision(self, nodeorrev, _df=None, raw=False):
1525 def revision(self, nodeorrev, _df=None, raw=False):
1519 """return an uncompressed revision of a given node or revision
1526 """return an uncompressed revision of a given node or revision
1520 number.
1527 number.
1521
1528
1522 _df - an existing file handle to read from. (internal-only)
1529 _df - an existing file handle to read from. (internal-only)
1523 raw - an optional argument specifying if the revision data is to be
1530 raw - an optional argument specifying if the revision data is to be
1524 treated as raw data when applying flag transforms. 'raw' should be set
1531 treated as raw data when applying flag transforms. 'raw' should be set
1525 to True when generating changegroups or in debug commands.
1532 to True when generating changegroups or in debug commands.
1526 """
1533 """
1527 if isinstance(nodeorrev, int):
1534 if isinstance(nodeorrev, int):
1528 rev = nodeorrev
1535 rev = nodeorrev
1529 node = self.node(rev)
1536 node = self.node(rev)
1530 else:
1537 else:
1531 node = nodeorrev
1538 node = nodeorrev
1532 rev = None
1539 rev = None
1533
1540
1534 cachedrev = None
1541 cachedrev = None
1535 flags = None
1542 flags = None
1536 rawtext = None
1543 rawtext = None
1537 if node == nullid:
1544 if node == nullid:
1538 return ""
1545 return ""
1539 if self._revisioncache:
1546 if self._revisioncache:
1540 if self._revisioncache[0] == node:
1547 if self._revisioncache[0] == node:
1541 # _cache only stores rawtext
1548 # _cache only stores rawtext
1542 if raw:
1549 if raw:
1543 return self._revisioncache[2]
1550 return self._revisioncache[2]
1544 # duplicated, but good for perf
1551 # duplicated, but good for perf
1545 if rev is None:
1552 if rev is None:
1546 rev = self.rev(node)
1553 rev = self.rev(node)
1547 if flags is None:
1554 if flags is None:
1548 flags = self.flags(rev)
1555 flags = self.flags(rev)
1549 # no extra flags set, no flag processor runs, text = rawtext
1556 # no extra flags set, no flag processor runs, text = rawtext
1550 if flags == REVIDX_DEFAULT_FLAGS:
1557 if flags == REVIDX_DEFAULT_FLAGS:
1551 return self._revisioncache[2]
1558 return self._revisioncache[2]
1552 # rawtext is reusable. need to run flag processor
1559 # rawtext is reusable. need to run flag processor
1553 rawtext = self._revisioncache[2]
1560 rawtext = self._revisioncache[2]
1554
1561
1555 cachedrev = self._revisioncache[1]
1562 cachedrev = self._revisioncache[1]
1556
1563
1557 # look up what we need to read
1564 # look up what we need to read
1558 if rawtext is None:
1565 if rawtext is None:
1559 if rev is None:
1566 if rev is None:
1560 rev = self.rev(node)
1567 rev = self.rev(node)
1561
1568
1562 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1569 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1563 if stopped:
1570 if stopped:
1564 rawtext = self._revisioncache[2]
1571 rawtext = self._revisioncache[2]
1565
1572
1566 # drop cache to save memory
1573 # drop cache to save memory
1567 self._revisioncache = None
1574 self._revisioncache = None
1568
1575
1569 targetsize = None
1576 targetsize = None
1570 rawsize = self.index[rev][2]
1577 rawsize = self.index[rev][2]
1571 if 0 <= rawsize:
1578 if 0 <= rawsize:
1572 targetsize = 4 * rawsize
1579 targetsize = 4 * rawsize
1573
1580
1574 bins = self._chunks(chain, df=_df, targetsize=targetsize)
1581 bins = self._chunks(chain, df=_df, targetsize=targetsize)
1575 if rawtext is None:
1582 if rawtext is None:
1576 rawtext = bytes(bins[0])
1583 rawtext = bytes(bins[0])
1577 bins = bins[1:]
1584 bins = bins[1:]
1578
1585
1579 rawtext = mdiff.patches(rawtext, bins)
1586 rawtext = mdiff.patches(rawtext, bins)
1580 self._revisioncache = (node, rev, rawtext)
1587 self._revisioncache = (node, rev, rawtext)
1581
1588
1582 if flags is None:
1589 if flags is None:
1583 if rev is None:
1590 if rev is None:
1584 rev = self.rev(node)
1591 rev = self.rev(node)
1585 flags = self.flags(rev)
1592 flags = self.flags(rev)
1586
1593
1587 text, validatehash = self._processflags(rawtext, flags, 'read', raw=raw)
1594 text, validatehash = self._processflags(rawtext, flags, 'read', raw=raw)
1588 if validatehash:
1595 if validatehash:
1589 self.checkhash(text, node, rev=rev)
1596 self.checkhash(text, node, rev=rev)
1590
1597
1591 return text
1598 return text
1592
1599
1593 def hash(self, text, p1, p2):
1600 def hash(self, text, p1, p2):
1594 """Compute a node hash.
1601 """Compute a node hash.
1595
1602
1596 Available as a function so that subclasses can replace the hash
1603 Available as a function so that subclasses can replace the hash
1597 as needed.
1604 as needed.
1598 """
1605 """
1599 return storageutil.hashrevisionsha1(text, p1, p2)
1606 return storageutil.hashrevisionsha1(text, p1, p2)
1600
1607
1601 def _processflags(self, text, flags, operation, raw=False):
1608 def _processflags(self, text, flags, operation, raw=False):
1602 """Inspect revision data flags and applies transforms defined by
1609 """Inspect revision data flags and applies transforms defined by
1603 registered flag processors.
1610 registered flag processors.
1604
1611
1605 ``text`` - the revision data to process
1612 ``text`` - the revision data to process
1606 ``flags`` - the revision flags
1613 ``flags`` - the revision flags
1607 ``operation`` - the operation being performed (read or write)
1614 ``operation`` - the operation being performed (read or write)
1608 ``raw`` - an optional argument describing if the raw transform should be
1615 ``raw`` - an optional argument describing if the raw transform should be
1609 applied.
1616 applied.
1610
1617
1611 This method processes the flags in the order (or reverse order if
1618 This method processes the flags in the order (or reverse order if
1612 ``operation`` is 'write') defined by REVIDX_FLAGS_ORDER, applying the
1619 ``operation`` is 'write') defined by REVIDX_FLAGS_ORDER, applying the
1613 flag processors registered for present flags. The order of flags defined
1620 flag processors registered for present flags. The order of flags defined
1614 in REVIDX_FLAGS_ORDER needs to be stable to allow non-commutativity.
1621 in REVIDX_FLAGS_ORDER needs to be stable to allow non-commutativity.
1615
1622
1616 Returns a 2-tuple of ``(text, validatehash)`` where ``text`` is the
1623 Returns a 2-tuple of ``(text, validatehash)`` where ``text`` is the
1617 processed text and ``validatehash`` is a bool indicating whether the
1624 processed text and ``validatehash`` is a bool indicating whether the
1618 returned text should be checked for hash integrity.
1625 returned text should be checked for hash integrity.
1619
1626
1620 Note: If the ``raw`` argument is set, it has precedence over the
1627 Note: If the ``raw`` argument is set, it has precedence over the
1621 operation and will only update the value of ``validatehash``.
1628 operation and will only update the value of ``validatehash``.
1622 """
1629 """
1623 # fast path: no flag processors will run
1630 # fast path: no flag processors will run
1624 if flags == 0:
1631 if flags == 0:
1625 return text, True
1632 return text, True
1626 if not operation in ('read', 'write'):
1633 if not operation in ('read', 'write'):
1627 raise error.ProgrammingError(_("invalid '%s' operation") %
1634 raise error.ProgrammingError(_("invalid '%s' operation") %
1628 operation)
1635 operation)
1629 # Check all flags are known.
1636 # Check all flags are known.
1630 if flags & ~REVIDX_KNOWN_FLAGS:
1637 if flags & ~REVIDX_KNOWN_FLAGS:
1631 raise error.RevlogError(_("incompatible revision flag '%#x'") %
1638 raise error.RevlogError(_("incompatible revision flag '%#x'") %
1632 (flags & ~REVIDX_KNOWN_FLAGS))
1639 (flags & ~REVIDX_KNOWN_FLAGS))
1633 validatehash = True
1640 validatehash = True
1634 # Depending on the operation (read or write), the order might be
1641 # Depending on the operation (read or write), the order might be
1635 # reversed due to non-commutative transforms.
1642 # reversed due to non-commutative transforms.
1636 orderedflags = REVIDX_FLAGS_ORDER
1643 orderedflags = REVIDX_FLAGS_ORDER
1637 if operation == 'write':
1644 if operation == 'write':
1638 orderedflags = reversed(orderedflags)
1645 orderedflags = reversed(orderedflags)
1639
1646
1640 for flag in orderedflags:
1647 for flag in orderedflags:
1641 # If a flagprocessor has been registered for a known flag, apply the
1648 # If a flagprocessor has been registered for a known flag, apply the
1642 # related operation transform and update result tuple.
1649 # related operation transform and update result tuple.
1643 if flag & flags:
1650 if flag & flags:
1644 vhash = True
1651 vhash = True
1645
1652
1646 if flag not in self._flagprocessors:
1653 if flag not in self._flagprocessors:
1647 message = _("missing processor for flag '%#x'") % (flag)
1654 message = _("missing processor for flag '%#x'") % (flag)
1648 raise error.RevlogError(message)
1655 raise error.RevlogError(message)
1649
1656
1650 processor = self._flagprocessors[flag]
1657 processor = self._flagprocessors[flag]
1651 if processor is not None:
1658 if processor is not None:
1652 readtransform, writetransform, rawtransform = processor
1659 readtransform, writetransform, rawtransform = processor
1653
1660
1654 if raw:
1661 if raw:
1655 vhash = rawtransform(self, text)
1662 vhash = rawtransform(self, text)
1656 elif operation == 'read':
1663 elif operation == 'read':
1657 text, vhash = readtransform(self, text)
1664 text, vhash = readtransform(self, text)
1658 else: # write operation
1665 else: # write operation
1659 text, vhash = writetransform(self, text)
1666 text, vhash = writetransform(self, text)
1660 validatehash = validatehash and vhash
1667 validatehash = validatehash and vhash
1661
1668
1662 return text, validatehash
1669 return text, validatehash
1663
1670
1664 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1671 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1665 """Check node hash integrity.
1672 """Check node hash integrity.
1666
1673
1667 Available as a function so that subclasses can extend hash mismatch
1674 Available as a function so that subclasses can extend hash mismatch
1668 behaviors as needed.
1675 behaviors as needed.
1669 """
1676 """
1670 try:
1677 try:
1671 if p1 is None and p2 is None:
1678 if p1 is None and p2 is None:
1672 p1, p2 = self.parents(node)
1679 p1, p2 = self.parents(node)
1673 if node != self.hash(text, p1, p2):
1680 if node != self.hash(text, p1, p2):
1674 # Clear the revision cache on hash failure. The revision cache
1681 # Clear the revision cache on hash failure. The revision cache
1675 # only stores the raw revision and clearing the cache does have
1682 # only stores the raw revision and clearing the cache does have
1676 # the side-effect that we won't have a cache hit when the raw
1683 # the side-effect that we won't have a cache hit when the raw
1677 # revision data is accessed. But this case should be rare and
1684 # revision data is accessed. But this case should be rare and
1678 # it is extra work to teach the cache about the hash
1685 # it is extra work to teach the cache about the hash
1679 # verification state.
1686 # verification state.
1680 if self._revisioncache and self._revisioncache[0] == node:
1687 if self._revisioncache and self._revisioncache[0] == node:
1681 self._revisioncache = None
1688 self._revisioncache = None
1682
1689
1683 revornode = rev
1690 revornode = rev
1684 if revornode is None:
1691 if revornode is None:
1685 revornode = templatefilters.short(hex(node))
1692 revornode = templatefilters.short(hex(node))
1686 raise error.RevlogError(_("integrity check failed on %s:%s")
1693 raise error.RevlogError(_("integrity check failed on %s:%s")
1687 % (self.indexfile, pycompat.bytestr(revornode)))
1694 % (self.indexfile, pycompat.bytestr(revornode)))
1688 except error.RevlogError:
1695 except error.RevlogError:
1689 if self._censorable and storageutil.iscensoredtext(text):
1696 if self._censorable and storageutil.iscensoredtext(text):
1690 raise error.CensoredNodeError(self.indexfile, node, text)
1697 raise error.CensoredNodeError(self.indexfile, node, text)
1691 raise
1698 raise
1692
1699
1693 def _enforceinlinesize(self, tr, fp=None):
1700 def _enforceinlinesize(self, tr, fp=None):
1694 """Check if the revlog is too big for inline and convert if so.
1701 """Check if the revlog is too big for inline and convert if so.
1695
1702
1696 This should be called after revisions are added to the revlog. If the
1703 This should be called after revisions are added to the revlog. If the
1697 revlog has grown too large to be an inline revlog, it will convert it
1704 revlog has grown too large to be an inline revlog, it will convert it
1698 to use multiple index and data files.
1705 to use multiple index and data files.
1699 """
1706 """
1700 tiprev = len(self) - 1
1707 tiprev = len(self) - 1
1701 if (not self._inline or
1708 if (not self._inline or
1702 (self.start(tiprev) + self.length(tiprev)) < _maxinline):
1709 (self.start(tiprev) + self.length(tiprev)) < _maxinline):
1703 return
1710 return
1704
1711
1705 trinfo = tr.find(self.indexfile)
1712 trinfo = tr.find(self.indexfile)
1706 if trinfo is None:
1713 if trinfo is None:
1707 raise error.RevlogError(_("%s not found in the transaction")
1714 raise error.RevlogError(_("%s not found in the transaction")
1708 % self.indexfile)
1715 % self.indexfile)
1709
1716
1710 trindex = trinfo[2]
1717 trindex = trinfo[2]
1711 if trindex is not None:
1718 if trindex is not None:
1712 dataoff = self.start(trindex)
1719 dataoff = self.start(trindex)
1713 else:
1720 else:
1714 # revlog was stripped at start of transaction, use all leftover data
1721 # revlog was stripped at start of transaction, use all leftover data
1715 trindex = len(self) - 1
1722 trindex = len(self) - 1
1716 dataoff = self.end(tiprev)
1723 dataoff = self.end(tiprev)
1717
1724
1718 tr.add(self.datafile, dataoff)
1725 tr.add(self.datafile, dataoff)
1719
1726
1720 if fp:
1727 if fp:
1721 fp.flush()
1728 fp.flush()
1722 fp.close()
1729 fp.close()
1723
1730
1724 with self._datafp('w') as df:
1731 with self._datafp('w') as df:
1725 for r in self:
1732 for r in self:
1726 df.write(self._getsegmentforrevs(r, r)[1])
1733 df.write(self._getsegmentforrevs(r, r)[1])
1727
1734
1728 with self._indexfp('w') as fp:
1735 with self._indexfp('w') as fp:
1729 self.version &= ~FLAG_INLINE_DATA
1736 self.version &= ~FLAG_INLINE_DATA
1730 self._inline = False
1737 self._inline = False
1731 io = self._io
1738 io = self._io
1732 for i in self:
1739 for i in self:
1733 e = io.packentry(self.index[i], self.node, self.version, i)
1740 e = io.packentry(self.index[i], self.node, self.version, i)
1734 fp.write(e)
1741 fp.write(e)
1735
1742
1736 # the temp file replace the real index when we exit the context
1743 # the temp file replace the real index when we exit the context
1737 # manager
1744 # manager
1738
1745
1739 tr.replace(self.indexfile, trindex * self._io.size)
1746 tr.replace(self.indexfile, trindex * self._io.size)
1740 self._chunkclear()
1747 self._chunkclear()
1741
1748
1742 def _nodeduplicatecallback(self, transaction, node):
1749 def _nodeduplicatecallback(self, transaction, node):
1743 """called when trying to add a node already stored.
1750 """called when trying to add a node already stored.
1744 """
1751 """
1745
1752
1746 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
1753 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
1747 node=None, flags=REVIDX_DEFAULT_FLAGS, deltacomputer=None):
1754 node=None, flags=REVIDX_DEFAULT_FLAGS, deltacomputer=None):
1748 """add a revision to the log
1755 """add a revision to the log
1749
1756
1750 text - the revision data to add
1757 text - the revision data to add
1751 transaction - the transaction object used for rollback
1758 transaction - the transaction object used for rollback
1752 link - the linkrev data to add
1759 link - the linkrev data to add
1753 p1, p2 - the parent nodeids of the revision
1760 p1, p2 - the parent nodeids of the revision
1754 cachedelta - an optional precomputed delta
1761 cachedelta - an optional precomputed delta
1755 node - nodeid of revision; typically node is not specified, and it is
1762 node - nodeid of revision; typically node is not specified, and it is
1756 computed by default as hash(text, p1, p2), however subclasses might
1763 computed by default as hash(text, p1, p2), however subclasses might
1757 use different hashing method (and override checkhash() in such case)
1764 use different hashing method (and override checkhash() in such case)
1758 flags - the known flags to set on the revision
1765 flags - the known flags to set on the revision
1759 deltacomputer - an optional deltacomputer instance shared between
1766 deltacomputer - an optional deltacomputer instance shared between
1760 multiple calls
1767 multiple calls
1761 """
1768 """
1762 if link == nullrev:
1769 if link == nullrev:
1763 raise error.RevlogError(_("attempted to add linkrev -1 to %s")
1770 raise error.RevlogError(_("attempted to add linkrev -1 to %s")
1764 % self.indexfile)
1771 % self.indexfile)
1765
1772
1766 if flags:
1773 if flags:
1767 node = node or self.hash(text, p1, p2)
1774 node = node or self.hash(text, p1, p2)
1768
1775
1769 rawtext, validatehash = self._processflags(text, flags, 'write')
1776 rawtext, validatehash = self._processflags(text, flags, 'write')
1770
1777
1771 # If the flag processor modifies the revision data, ignore any provided
1778 # If the flag processor modifies the revision data, ignore any provided
1772 # cachedelta.
1779 # cachedelta.
1773 if rawtext != text:
1780 if rawtext != text:
1774 cachedelta = None
1781 cachedelta = None
1775
1782
1776 if len(rawtext) > _maxentrysize:
1783 if len(rawtext) > _maxentrysize:
1777 raise error.RevlogError(
1784 raise error.RevlogError(
1778 _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
1785 _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
1779 % (self.indexfile, len(rawtext)))
1786 % (self.indexfile, len(rawtext)))
1780
1787
1781 node = node or self.hash(rawtext, p1, p2)
1788 node = node or self.hash(rawtext, p1, p2)
1782 if node in self.nodemap:
1789 if node in self.nodemap:
1783 return node
1790 return node
1784
1791
1785 if validatehash:
1792 if validatehash:
1786 self.checkhash(rawtext, node, p1=p1, p2=p2)
1793 self.checkhash(rawtext, node, p1=p1, p2=p2)
1787
1794
1788 return self.addrawrevision(rawtext, transaction, link, p1, p2, node,
1795 return self.addrawrevision(rawtext, transaction, link, p1, p2, node,
1789 flags, cachedelta=cachedelta,
1796 flags, cachedelta=cachedelta,
1790 deltacomputer=deltacomputer)
1797 deltacomputer=deltacomputer)
1791
1798
1792 def addrawrevision(self, rawtext, transaction, link, p1, p2, node, flags,
1799 def addrawrevision(self, rawtext, transaction, link, p1, p2, node, flags,
1793 cachedelta=None, deltacomputer=None):
1800 cachedelta=None, deltacomputer=None):
1794 """add a raw revision with known flags, node and parents
1801 """add a raw revision with known flags, node and parents
1795 useful when reusing a revision not stored in this revlog (ex: received
1802 useful when reusing a revision not stored in this revlog (ex: received
1796 over wire, or read from an external bundle).
1803 over wire, or read from an external bundle).
1797 """
1804 """
1798 dfh = None
1805 dfh = None
1799 if not self._inline:
1806 if not self._inline:
1800 dfh = self._datafp("a+")
1807 dfh = self._datafp("a+")
1801 ifh = self._indexfp("a+")
1808 ifh = self._indexfp("a+")
1802 try:
1809 try:
1803 return self._addrevision(node, rawtext, transaction, link, p1, p2,
1810 return self._addrevision(node, rawtext, transaction, link, p1, p2,
1804 flags, cachedelta, ifh, dfh,
1811 flags, cachedelta, ifh, dfh,
1805 deltacomputer=deltacomputer)
1812 deltacomputer=deltacomputer)
1806 finally:
1813 finally:
1807 if dfh:
1814 if dfh:
1808 dfh.close()
1815 dfh.close()
1809 ifh.close()
1816 ifh.close()
1810
1817
1811 def compress(self, data):
1818 def compress(self, data):
1812 """Generate a possibly-compressed representation of data."""
1819 """Generate a possibly-compressed representation of data."""
1813 if not data:
1820 if not data:
1814 return '', data
1821 return '', data
1815
1822
1816 compressed = self._compressor.compress(data)
1823 compressed = self._compressor.compress(data)
1817
1824
1818 if compressed:
1825 if compressed:
1819 # The revlog compressor added the header in the returned data.
1826 # The revlog compressor added the header in the returned data.
1820 return '', compressed
1827 return '', compressed
1821
1828
1822 if data[0:1] == '\0':
1829 if data[0:1] == '\0':
1823 return '', data
1830 return '', data
1824 return 'u', data
1831 return 'u', data
1825
1832
1826 def decompress(self, data):
1833 def decompress(self, data):
1827 """Decompress a revlog chunk.
1834 """Decompress a revlog chunk.
1828
1835
1829 The chunk is expected to begin with a header identifying the
1836 The chunk is expected to begin with a header identifying the
1830 format type so it can be routed to an appropriate decompressor.
1837 format type so it can be routed to an appropriate decompressor.
1831 """
1838 """
1832 if not data:
1839 if not data:
1833 return data
1840 return data
1834
1841
1835 # Revlogs are read much more frequently than they are written and many
1842 # Revlogs are read much more frequently than they are written and many
1836 # chunks only take microseconds to decompress, so performance is
1843 # chunks only take microseconds to decompress, so performance is
1837 # important here.
1844 # important here.
1838 #
1845 #
1839 # We can make a few assumptions about revlogs:
1846 # We can make a few assumptions about revlogs:
1840 #
1847 #
1841 # 1) the majority of chunks will be compressed (as opposed to inline
1848 # 1) the majority of chunks will be compressed (as opposed to inline
1842 # raw data).
1849 # raw data).
1843 # 2) decompressing *any* data will likely by at least 10x slower than
1850 # 2) decompressing *any* data will likely by at least 10x slower than
1844 # returning raw inline data.
1851 # returning raw inline data.
1845 # 3) we want to prioritize common and officially supported compression
1852 # 3) we want to prioritize common and officially supported compression
1846 # engines
1853 # engines
1847 #
1854 #
1848 # It follows that we want to optimize for "decompress compressed data
1855 # It follows that we want to optimize for "decompress compressed data
1849 # when encoded with common and officially supported compression engines"
1856 # when encoded with common and officially supported compression engines"
1850 # case over "raw data" and "data encoded by less common or non-official
1857 # case over "raw data" and "data encoded by less common or non-official
1851 # compression engines." That is why we have the inline lookup first
1858 # compression engines." That is why we have the inline lookup first
1852 # followed by the compengines lookup.
1859 # followed by the compengines lookup.
1853 #
1860 #
1854 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
1861 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
1855 # compressed chunks. And this matters for changelog and manifest reads.
1862 # compressed chunks. And this matters for changelog and manifest reads.
1856 t = data[0:1]
1863 t = data[0:1]
1857
1864
1858 if t == 'x':
1865 if t == 'x':
1859 try:
1866 try:
1860 return _zlibdecompress(data)
1867 return _zlibdecompress(data)
1861 except zlib.error as e:
1868 except zlib.error as e:
1862 raise error.RevlogError(_('revlog decompress error: %s') %
1869 raise error.RevlogError(_('revlog decompress error: %s') %
1863 stringutil.forcebytestr(e))
1870 stringutil.forcebytestr(e))
1864 # '\0' is more common than 'u' so it goes first.
1871 # '\0' is more common than 'u' so it goes first.
1865 elif t == '\0':
1872 elif t == '\0':
1866 return data
1873 return data
1867 elif t == 'u':
1874 elif t == 'u':
1868 return util.buffer(data, 1)
1875 return util.buffer(data, 1)
1869
1876
1870 try:
1877 try:
1871 compressor = self._decompressors[t]
1878 compressor = self._decompressors[t]
1872 except KeyError:
1879 except KeyError:
1873 try:
1880 try:
1874 engine = util.compengines.forrevlogheader(t)
1881 engine = util.compengines.forrevlogheader(t)
1875 compressor = engine.revlogcompressor()
1882 compressor = engine.revlogcompressor()
1876 self._decompressors[t] = compressor
1883 self._decompressors[t] = compressor
1877 except KeyError:
1884 except KeyError:
1878 raise error.RevlogError(_('unknown compression type %r') % t)
1885 raise error.RevlogError(_('unknown compression type %r') % t)
1879
1886
1880 return compressor.decompress(data)
1887 return compressor.decompress(data)
1881
1888
1882 def _addrevision(self, node, rawtext, transaction, link, p1, p2, flags,
1889 def _addrevision(self, node, rawtext, transaction, link, p1, p2, flags,
1883 cachedelta, ifh, dfh, alwayscache=False,
1890 cachedelta, ifh, dfh, alwayscache=False,
1884 deltacomputer=None):
1891 deltacomputer=None):
1885 """internal function to add revisions to the log
1892 """internal function to add revisions to the log
1886
1893
1887 see addrevision for argument descriptions.
1894 see addrevision for argument descriptions.
1888
1895
1889 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
1896 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
1890
1897
1891 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
1898 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
1892 be used.
1899 be used.
1893
1900
1894 invariants:
1901 invariants:
1895 - rawtext is optional (can be None); if not set, cachedelta must be set.
1902 - rawtext is optional (can be None); if not set, cachedelta must be set.
1896 if both are set, they must correspond to each other.
1903 if both are set, they must correspond to each other.
1897 """
1904 """
1898 if node == nullid:
1905 if node == nullid:
1899 raise error.RevlogError(_("%s: attempt to add null revision") %
1906 raise error.RevlogError(_("%s: attempt to add null revision") %
1900 self.indexfile)
1907 self.indexfile)
1901 if node == wdirid or node in wdirfilenodeids:
1908 if node == wdirid or node in wdirfilenodeids:
1902 raise error.RevlogError(_("%s: attempt to add wdir revision") %
1909 raise error.RevlogError(_("%s: attempt to add wdir revision") %
1903 self.indexfile)
1910 self.indexfile)
1904
1911
1905 if self._inline:
1912 if self._inline:
1906 fh = ifh
1913 fh = ifh
1907 else:
1914 else:
1908 fh = dfh
1915 fh = dfh
1909
1916
1910 btext = [rawtext]
1917 btext = [rawtext]
1911
1918
1912 curr = len(self)
1919 curr = len(self)
1913 prev = curr - 1
1920 prev = curr - 1
1914 offset = self.end(prev)
1921 offset = self.end(prev)
1915 p1r, p2r = self.rev(p1), self.rev(p2)
1922 p1r, p2r = self.rev(p1), self.rev(p2)
1916
1923
1917 # full versions are inserted when the needed deltas
1924 # full versions are inserted when the needed deltas
1918 # become comparable to the uncompressed text
1925 # become comparable to the uncompressed text
1919 if rawtext is None:
1926 if rawtext is None:
1920 # need rawtext size, before changed by flag processors, which is
1927 # need rawtext size, before changed by flag processors, which is
1921 # the non-raw size. use revlog explicitly to avoid filelog's extra
1928 # the non-raw size. use revlog explicitly to avoid filelog's extra
1922 # logic that might remove metadata size.
1929 # logic that might remove metadata size.
1923 textlen = mdiff.patchedsize(revlog.size(self, cachedelta[0]),
1930 textlen = mdiff.patchedsize(revlog.size(self, cachedelta[0]),
1924 cachedelta[1])
1931 cachedelta[1])
1925 else:
1932 else:
1926 textlen = len(rawtext)
1933 textlen = len(rawtext)
1927
1934
1928 if deltacomputer is None:
1935 if deltacomputer is None:
1929 deltacomputer = deltautil.deltacomputer(self)
1936 deltacomputer = deltautil.deltacomputer(self)
1930
1937
1931 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
1938 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
1932
1939
1933 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
1940 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
1934
1941
1935 e = (offset_type(offset, flags), deltainfo.deltalen, textlen,
1942 e = (offset_type(offset, flags), deltainfo.deltalen, textlen,
1936 deltainfo.base, link, p1r, p2r, node)
1943 deltainfo.base, link, p1r, p2r, node)
1937 self.index.append(e)
1944 self.index.append(e)
1938 self.nodemap[node] = curr
1945 self.nodemap[node] = curr
1939
1946
1940 # Reset the pure node cache start lookup offset to account for new
1947 # Reset the pure node cache start lookup offset to account for new
1941 # revision.
1948 # revision.
1942 if self._nodepos is not None:
1949 if self._nodepos is not None:
1943 self._nodepos = curr
1950 self._nodepos = curr
1944
1951
1945 entry = self._io.packentry(e, self.node, self.version, curr)
1952 entry = self._io.packentry(e, self.node, self.version, curr)
1946 self._writeentry(transaction, ifh, dfh, entry, deltainfo.data,
1953 self._writeentry(transaction, ifh, dfh, entry, deltainfo.data,
1947 link, offset)
1954 link, offset)
1948
1955
1949 rawtext = btext[0]
1956 rawtext = btext[0]
1950
1957
1951 if alwayscache and rawtext is None:
1958 if alwayscache and rawtext is None:
1952 rawtext = deltacomputer.buildtext(revinfo, fh)
1959 rawtext = deltacomputer.buildtext(revinfo, fh)
1953
1960
1954 if type(rawtext) == bytes: # only accept immutable objects
1961 if type(rawtext) == bytes: # only accept immutable objects
1955 self._revisioncache = (node, curr, rawtext)
1962 self._revisioncache = (node, curr, rawtext)
1956 self._chainbasecache[curr] = deltainfo.chainbase
1963 self._chainbasecache[curr] = deltainfo.chainbase
1957 return node
1964 return node
1958
1965
1959 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
1966 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
1960 # Files opened in a+ mode have inconsistent behavior on various
1967 # Files opened in a+ mode have inconsistent behavior on various
1961 # platforms. Windows requires that a file positioning call be made
1968 # platforms. Windows requires that a file positioning call be made
1962 # when the file handle transitions between reads and writes. See
1969 # when the file handle transitions between reads and writes. See
1963 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
1970 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
1964 # platforms, Python or the platform itself can be buggy. Some versions
1971 # platforms, Python or the platform itself can be buggy. Some versions
1965 # of Solaris have been observed to not append at the end of the file
1972 # of Solaris have been observed to not append at the end of the file
1966 # if the file was seeked to before the end. See issue4943 for more.
1973 # if the file was seeked to before the end. See issue4943 for more.
1967 #
1974 #
1968 # We work around this issue by inserting a seek() before writing.
1975 # We work around this issue by inserting a seek() before writing.
1969 # Note: This is likely not necessary on Python 3.
1976 # Note: This is likely not necessary on Python 3.
1970 ifh.seek(0, os.SEEK_END)
1977 ifh.seek(0, os.SEEK_END)
1971 if dfh:
1978 if dfh:
1972 dfh.seek(0, os.SEEK_END)
1979 dfh.seek(0, os.SEEK_END)
1973
1980
1974 curr = len(self) - 1
1981 curr = len(self) - 1
1975 if not self._inline:
1982 if not self._inline:
1976 transaction.add(self.datafile, offset)
1983 transaction.add(self.datafile, offset)
1977 transaction.add(self.indexfile, curr * len(entry))
1984 transaction.add(self.indexfile, curr * len(entry))
1978 if data[0]:
1985 if data[0]:
1979 dfh.write(data[0])
1986 dfh.write(data[0])
1980 dfh.write(data[1])
1987 dfh.write(data[1])
1981 ifh.write(entry)
1988 ifh.write(entry)
1982 else:
1989 else:
1983 offset += curr * self._io.size
1990 offset += curr * self._io.size
1984 transaction.add(self.indexfile, offset, curr)
1991 transaction.add(self.indexfile, offset, curr)
1985 ifh.write(entry)
1992 ifh.write(entry)
1986 ifh.write(data[0])
1993 ifh.write(data[0])
1987 ifh.write(data[1])
1994 ifh.write(data[1])
1988 self._enforceinlinesize(transaction, ifh)
1995 self._enforceinlinesize(transaction, ifh)
1989
1996
1990 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
1997 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
1991 """
1998 """
1992 add a delta group
1999 add a delta group
1993
2000
1994 given a set of deltas, add them to the revision log. the
2001 given a set of deltas, add them to the revision log. the
1995 first delta is against its parent, which should be in our
2002 first delta is against its parent, which should be in our
1996 log, the rest are against the previous delta.
2003 log, the rest are against the previous delta.
1997
2004
1998 If ``addrevisioncb`` is defined, it will be called with arguments of
2005 If ``addrevisioncb`` is defined, it will be called with arguments of
1999 this revlog and the node that was added.
2006 this revlog and the node that was added.
2000 """
2007 """
2001
2008
2002 nodes = []
2009 nodes = []
2003
2010
2004 r = len(self)
2011 r = len(self)
2005 end = 0
2012 end = 0
2006 if r:
2013 if r:
2007 end = self.end(r - 1)
2014 end = self.end(r - 1)
2008 ifh = self._indexfp("a+")
2015 ifh = self._indexfp("a+")
2009 isize = r * self._io.size
2016 isize = r * self._io.size
2010 if self._inline:
2017 if self._inline:
2011 transaction.add(self.indexfile, end + isize, r)
2018 transaction.add(self.indexfile, end + isize, r)
2012 dfh = None
2019 dfh = None
2013 else:
2020 else:
2014 transaction.add(self.indexfile, isize, r)
2021 transaction.add(self.indexfile, isize, r)
2015 transaction.add(self.datafile, end)
2022 transaction.add(self.datafile, end)
2016 dfh = self._datafp("a+")
2023 dfh = self._datafp("a+")
2017 def flush():
2024 def flush():
2018 if dfh:
2025 if dfh:
2019 dfh.flush()
2026 dfh.flush()
2020 ifh.flush()
2027 ifh.flush()
2021 try:
2028 try:
2022 deltacomputer = deltautil.deltacomputer(self)
2029 deltacomputer = deltautil.deltacomputer(self)
2023 # loop through our set of deltas
2030 # loop through our set of deltas
2024 for data in deltas:
2031 for data in deltas:
2025 node, p1, p2, linknode, deltabase, delta, flags = data
2032 node, p1, p2, linknode, deltabase, delta, flags = data
2026 link = linkmapper(linknode)
2033 link = linkmapper(linknode)
2027 flags = flags or REVIDX_DEFAULT_FLAGS
2034 flags = flags or REVIDX_DEFAULT_FLAGS
2028
2035
2029 nodes.append(node)
2036 nodes.append(node)
2030
2037
2031 if node in self.nodemap:
2038 if node in self.nodemap:
2032 self._nodeduplicatecallback(transaction, node)
2039 self._nodeduplicatecallback(transaction, node)
2033 # this can happen if two branches make the same change
2040 # this can happen if two branches make the same change
2034 continue
2041 continue
2035
2042
2036 for p in (p1, p2):
2043 for p in (p1, p2):
2037 if p not in self.nodemap:
2044 if p not in self.nodemap:
2038 raise error.LookupError(p, self.indexfile,
2045 raise error.LookupError(p, self.indexfile,
2039 _('unknown parent'))
2046 _('unknown parent'))
2040
2047
2041 if deltabase not in self.nodemap:
2048 if deltabase not in self.nodemap:
2042 raise error.LookupError(deltabase, self.indexfile,
2049 raise error.LookupError(deltabase, self.indexfile,
2043 _('unknown delta base'))
2050 _('unknown delta base'))
2044
2051
2045 baserev = self.rev(deltabase)
2052 baserev = self.rev(deltabase)
2046
2053
2047 if baserev != nullrev and self.iscensored(baserev):
2054 if baserev != nullrev and self.iscensored(baserev):
2048 # if base is censored, delta must be full replacement in a
2055 # if base is censored, delta must be full replacement in a
2049 # single patch operation
2056 # single patch operation
2050 hlen = struct.calcsize(">lll")
2057 hlen = struct.calcsize(">lll")
2051 oldlen = self.rawsize(baserev)
2058 oldlen = self.rawsize(baserev)
2052 newlen = len(delta) - hlen
2059 newlen = len(delta) - hlen
2053 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2060 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2054 raise error.CensoredBaseError(self.indexfile,
2061 raise error.CensoredBaseError(self.indexfile,
2055 self.node(baserev))
2062 self.node(baserev))
2056
2063
2057 if not flags and self._peek_iscensored(baserev, delta, flush):
2064 if not flags and self._peek_iscensored(baserev, delta, flush):
2058 flags |= REVIDX_ISCENSORED
2065 flags |= REVIDX_ISCENSORED
2059
2066
2060 # We assume consumers of addrevisioncb will want to retrieve
2067 # We assume consumers of addrevisioncb will want to retrieve
2061 # the added revision, which will require a call to
2068 # the added revision, which will require a call to
2062 # revision(). revision() will fast path if there is a cache
2069 # revision(). revision() will fast path if there is a cache
2063 # hit. So, we tell _addrevision() to always cache in this case.
2070 # hit. So, we tell _addrevision() to always cache in this case.
2064 # We're only using addgroup() in the context of changegroup
2071 # We're only using addgroup() in the context of changegroup
2065 # generation so the revision data can always be handled as raw
2072 # generation so the revision data can always be handled as raw
2066 # by the flagprocessor.
2073 # by the flagprocessor.
2067 self._addrevision(node, None, transaction, link,
2074 self._addrevision(node, None, transaction, link,
2068 p1, p2, flags, (baserev, delta),
2075 p1, p2, flags, (baserev, delta),
2069 ifh, dfh,
2076 ifh, dfh,
2070 alwayscache=bool(addrevisioncb),
2077 alwayscache=bool(addrevisioncb),
2071 deltacomputer=deltacomputer)
2078 deltacomputer=deltacomputer)
2072
2079
2073 if addrevisioncb:
2080 if addrevisioncb:
2074 addrevisioncb(self, node)
2081 addrevisioncb(self, node)
2075
2082
2076 if not dfh and not self._inline:
2083 if not dfh and not self._inline:
2077 # addrevision switched from inline to conventional
2084 # addrevision switched from inline to conventional
2078 # reopen the index
2085 # reopen the index
2079 ifh.close()
2086 ifh.close()
2080 dfh = self._datafp("a+")
2087 dfh = self._datafp("a+")
2081 ifh = self._indexfp("a+")
2088 ifh = self._indexfp("a+")
2082 finally:
2089 finally:
2083 if dfh:
2090 if dfh:
2084 dfh.close()
2091 dfh.close()
2085 ifh.close()
2092 ifh.close()
2086
2093
2087 return nodes
2094 return nodes
2088
2095
2089 def iscensored(self, rev):
2096 def iscensored(self, rev):
2090 """Check if a file revision is censored."""
2097 """Check if a file revision is censored."""
2091 if not self._censorable:
2098 if not self._censorable:
2092 return False
2099 return False
2093
2100
2094 return self.flags(rev) & REVIDX_ISCENSORED
2101 return self.flags(rev) & REVIDX_ISCENSORED
2095
2102
2096 def _peek_iscensored(self, baserev, delta, flush):
2103 def _peek_iscensored(self, baserev, delta, flush):
2097 """Quickly check if a delta produces a censored revision."""
2104 """Quickly check if a delta produces a censored revision."""
2098 if not self._censorable:
2105 if not self._censorable:
2099 return False
2106 return False
2100
2107
2101 # Fragile heuristic: unless new file meta keys are added alphabetically
2108 # Fragile heuristic: unless new file meta keys are added alphabetically
2102 # preceding "censored", all censored revisions are prefixed by
2109 # preceding "censored", all censored revisions are prefixed by
2103 # "\1\ncensored:". A delta producing such a censored revision must be a
2110 # "\1\ncensored:". A delta producing such a censored revision must be a
2104 # full-replacement delta, so we inspect the first and only patch in the
2111 # full-replacement delta, so we inspect the first and only patch in the
2105 # delta for this prefix.
2112 # delta for this prefix.
2106 hlen = struct.calcsize(">lll")
2113 hlen = struct.calcsize(">lll")
2107 if len(delta) <= hlen:
2114 if len(delta) <= hlen:
2108 return False
2115 return False
2109
2116
2110 oldlen = self.rawsize(baserev)
2117 oldlen = self.rawsize(baserev)
2111 newlen = len(delta) - hlen
2118 newlen = len(delta) - hlen
2112 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2119 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2113 return False
2120 return False
2114
2121
2115 add = "\1\ncensored:"
2122 add = "\1\ncensored:"
2116 addlen = len(add)
2123 addlen = len(add)
2117 return newlen >= addlen and delta[hlen:hlen + addlen] == add
2124 return newlen >= addlen and delta[hlen:hlen + addlen] == add
2118
2125
2119 def getstrippoint(self, minlink):
2126 def getstrippoint(self, minlink):
2120 """find the minimum rev that must be stripped to strip the linkrev
2127 """find the minimum rev that must be stripped to strip the linkrev
2121
2128
2122 Returns a tuple containing the minimum rev and a set of all revs that
2129 Returns a tuple containing the minimum rev and a set of all revs that
2123 have linkrevs that will be broken by this strip.
2130 have linkrevs that will be broken by this strip.
2124 """
2131 """
2125 return storageutil.resolvestripinfo(minlink, len(self) - 1,
2132 return storageutil.resolvestripinfo(minlink, len(self) - 1,
2126 self.headrevs(),
2133 self.headrevs(),
2127 self.linkrev, self.parentrevs)
2134 self.linkrev, self.parentrevs)
2128
2135
2129 def strip(self, minlink, transaction):
2136 def strip(self, minlink, transaction):
2130 """truncate the revlog on the first revision with a linkrev >= minlink
2137 """truncate the revlog on the first revision with a linkrev >= minlink
2131
2138
2132 This function is called when we're stripping revision minlink and
2139 This function is called when we're stripping revision minlink and
2133 its descendants from the repository.
2140 its descendants from the repository.
2134
2141
2135 We have to remove all revisions with linkrev >= minlink, because
2142 We have to remove all revisions with linkrev >= minlink, because
2136 the equivalent changelog revisions will be renumbered after the
2143 the equivalent changelog revisions will be renumbered after the
2137 strip.
2144 strip.
2138
2145
2139 So we truncate the revlog on the first of these revisions, and
2146 So we truncate the revlog on the first of these revisions, and
2140 trust that the caller has saved the revisions that shouldn't be
2147 trust that the caller has saved the revisions that shouldn't be
2141 removed and that it'll re-add them after this truncation.
2148 removed and that it'll re-add them after this truncation.
2142 """
2149 """
2143 if len(self) == 0:
2150 if len(self) == 0:
2144 return
2151 return
2145
2152
2146 rev, _ = self.getstrippoint(minlink)
2153 rev, _ = self.getstrippoint(minlink)
2147 if rev == len(self):
2154 if rev == len(self):
2148 return
2155 return
2149
2156
2150 # first truncate the files on disk
2157 # first truncate the files on disk
2151 end = self.start(rev)
2158 end = self.start(rev)
2152 if not self._inline:
2159 if not self._inline:
2153 transaction.add(self.datafile, end)
2160 transaction.add(self.datafile, end)
2154 end = rev * self._io.size
2161 end = rev * self._io.size
2155 else:
2162 else:
2156 end += rev * self._io.size
2163 end += rev * self._io.size
2157
2164
2158 transaction.add(self.indexfile, end)
2165 transaction.add(self.indexfile, end)
2159
2166
2160 # then reset internal state in memory to forget those revisions
2167 # then reset internal state in memory to forget those revisions
2161 self._revisioncache = None
2168 self._revisioncache = None
2162 self._chaininfocache = {}
2169 self._chaininfocache = {}
2163 self._chunkclear()
2170 self._chunkclear()
2164 for x in pycompat.xrange(rev, len(self)):
2171 for x in pycompat.xrange(rev, len(self)):
2165 del self.nodemap[self.node(x)]
2172 del self.nodemap[self.node(x)]
2166
2173
2167 del self.index[rev:-1]
2174 del self.index[rev:-1]
2168 self._nodepos = None
2175 self._nodepos = None
2169
2176
2170 def checksize(self):
2177 def checksize(self):
2171 expected = 0
2178 expected = 0
2172 if len(self):
2179 if len(self):
2173 expected = max(0, self.end(len(self) - 1))
2180 expected = max(0, self.end(len(self) - 1))
2174
2181
2175 try:
2182 try:
2176 with self._datafp() as f:
2183 with self._datafp() as f:
2177 f.seek(0, 2)
2184 f.seek(0, 2)
2178 actual = f.tell()
2185 actual = f.tell()
2179 dd = actual - expected
2186 dd = actual - expected
2180 except IOError as inst:
2187 except IOError as inst:
2181 if inst.errno != errno.ENOENT:
2188 if inst.errno != errno.ENOENT:
2182 raise
2189 raise
2183 dd = 0
2190 dd = 0
2184
2191
2185 try:
2192 try:
2186 f = self.opener(self.indexfile)
2193 f = self.opener(self.indexfile)
2187 f.seek(0, 2)
2194 f.seek(0, 2)
2188 actual = f.tell()
2195 actual = f.tell()
2189 f.close()
2196 f.close()
2190 s = self._io.size
2197 s = self._io.size
2191 i = max(0, actual // s)
2198 i = max(0, actual // s)
2192 di = actual - (i * s)
2199 di = actual - (i * s)
2193 if self._inline:
2200 if self._inline:
2194 databytes = 0
2201 databytes = 0
2195 for r in self:
2202 for r in self:
2196 databytes += max(0, self.length(r))
2203 databytes += max(0, self.length(r))
2197 dd = 0
2204 dd = 0
2198 di = actual - len(self) * s - databytes
2205 di = actual - len(self) * s - databytes
2199 except IOError as inst:
2206 except IOError as inst:
2200 if inst.errno != errno.ENOENT:
2207 if inst.errno != errno.ENOENT:
2201 raise
2208 raise
2202 di = 0
2209 di = 0
2203
2210
2204 return (dd, di)
2211 return (dd, di)
2205
2212
2206 def files(self):
2213 def files(self):
2207 res = [self.indexfile]
2214 res = [self.indexfile]
2208 if not self._inline:
2215 if not self._inline:
2209 res.append(self.datafile)
2216 res.append(self.datafile)
2210 return res
2217 return res
2211
2218
2212 def emitrevisions(self, nodes, nodesorder=None, revisiondata=False,
2219 def emitrevisions(self, nodes, nodesorder=None, revisiondata=False,
2213 assumehaveparentrevisions=False, deltaprevious=False):
2220 assumehaveparentrevisions=False, deltaprevious=False):
2214 if nodesorder not in ('nodes', 'storage', None):
2221 if nodesorder not in ('nodes', 'storage', None):
2215 raise error.ProgrammingError('unhandled value for nodesorder: %s' %
2222 raise error.ProgrammingError('unhandled value for nodesorder: %s' %
2216 nodesorder)
2223 nodesorder)
2217
2224
2218 if nodesorder is None and not self._generaldelta:
2225 if nodesorder is None and not self._generaldelta:
2219 nodesorder = 'storage'
2226 nodesorder = 'storage'
2220
2227
2221 return storageutil.emitrevisions(
2228 return storageutil.emitrevisions(
2222 self, nodes, nodesorder, revlogrevisiondelta,
2229 self, nodes, nodesorder, revlogrevisiondelta,
2223 deltaparentfn=self.deltaparent,
2230 deltaparentfn=self.deltaparent,
2224 candeltafn=self.candelta,
2231 candeltafn=self.candelta,
2225 rawsizefn=self.rawsize,
2232 rawsizefn=self.rawsize,
2226 revdifffn=self.revdiff,
2233 revdifffn=self.revdiff,
2227 flagsfn=self.flags,
2234 flagsfn=self.flags,
2228 sendfulltext=not self._storedeltachains,
2235 sendfulltext=not self._storedeltachains,
2229 revisiondata=revisiondata,
2236 revisiondata=revisiondata,
2230 assumehaveparentrevisions=assumehaveparentrevisions,
2237 assumehaveparentrevisions=assumehaveparentrevisions,
2231 deltaprevious=deltaprevious)
2238 deltaprevious=deltaprevious)
2232
2239
2233 DELTAREUSEALWAYS = 'always'
2240 DELTAREUSEALWAYS = 'always'
2234 DELTAREUSESAMEREVS = 'samerevs'
2241 DELTAREUSESAMEREVS = 'samerevs'
2235 DELTAREUSENEVER = 'never'
2242 DELTAREUSENEVER = 'never'
2236
2243
2237 DELTAREUSEFULLADD = 'fulladd'
2244 DELTAREUSEFULLADD = 'fulladd'
2238
2245
2239 DELTAREUSEALL = {'always', 'samerevs', 'never', 'fulladd'}
2246 DELTAREUSEALL = {'always', 'samerevs', 'never', 'fulladd'}
2240
2247
2241 def clone(self, tr, destrevlog, addrevisioncb=None,
2248 def clone(self, tr, destrevlog, addrevisioncb=None,
2242 deltareuse=DELTAREUSESAMEREVS, deltabothparents=None):
2249 deltareuse=DELTAREUSESAMEREVS, deltabothparents=None):
2243 """Copy this revlog to another, possibly with format changes.
2250 """Copy this revlog to another, possibly with format changes.
2244
2251
2245 The destination revlog will contain the same revisions and nodes.
2252 The destination revlog will contain the same revisions and nodes.
2246 However, it may not be bit-for-bit identical due to e.g. delta encoding
2253 However, it may not be bit-for-bit identical due to e.g. delta encoding
2247 differences.
2254 differences.
2248
2255
2249 The ``deltareuse`` argument control how deltas from the existing revlog
2256 The ``deltareuse`` argument control how deltas from the existing revlog
2250 are preserved in the destination revlog. The argument can have the
2257 are preserved in the destination revlog. The argument can have the
2251 following values:
2258 following values:
2252
2259
2253 DELTAREUSEALWAYS
2260 DELTAREUSEALWAYS
2254 Deltas will always be reused (if possible), even if the destination
2261 Deltas will always be reused (if possible), even if the destination
2255 revlog would not select the same revisions for the delta. This is the
2262 revlog would not select the same revisions for the delta. This is the
2256 fastest mode of operation.
2263 fastest mode of operation.
2257 DELTAREUSESAMEREVS
2264 DELTAREUSESAMEREVS
2258 Deltas will be reused if the destination revlog would pick the same
2265 Deltas will be reused if the destination revlog would pick the same
2259 revisions for the delta. This mode strikes a balance between speed
2266 revisions for the delta. This mode strikes a balance between speed
2260 and optimization.
2267 and optimization.
2261 DELTAREUSENEVER
2268 DELTAREUSENEVER
2262 Deltas will never be reused. This is the slowest mode of execution.
2269 Deltas will never be reused. This is the slowest mode of execution.
2263 This mode can be used to recompute deltas (e.g. if the diff/delta
2270 This mode can be used to recompute deltas (e.g. if the diff/delta
2264 algorithm changes).
2271 algorithm changes).
2265
2272
2266 Delta computation can be slow, so the choice of delta reuse policy can
2273 Delta computation can be slow, so the choice of delta reuse policy can
2267 significantly affect run time.
2274 significantly affect run time.
2268
2275
2269 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2276 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2270 two extremes. Deltas will be reused if they are appropriate. But if the
2277 two extremes. Deltas will be reused if they are appropriate. But if the
2271 delta could choose a better revision, it will do so. This means if you
2278 delta could choose a better revision, it will do so. This means if you
2272 are converting a non-generaldelta revlog to a generaldelta revlog,
2279 are converting a non-generaldelta revlog to a generaldelta revlog,
2273 deltas will be recomputed if the delta's parent isn't a parent of the
2280 deltas will be recomputed if the delta's parent isn't a parent of the
2274 revision.
2281 revision.
2275
2282
2276 In addition to the delta policy, the ``deltabothparents`` argument
2283 In addition to the delta policy, the ``deltabothparents`` argument
2277 controls whether to compute deltas against both parents for merges.
2284 controls whether to compute deltas against both parents for merges.
2278 By default, the current default is used.
2285 By default, the current default is used.
2279 """
2286 """
2280 if deltareuse not in self.DELTAREUSEALL:
2287 if deltareuse not in self.DELTAREUSEALL:
2281 raise ValueError(_('value for deltareuse invalid: %s') % deltareuse)
2288 raise ValueError(_('value for deltareuse invalid: %s') % deltareuse)
2282
2289
2283 if len(destrevlog):
2290 if len(destrevlog):
2284 raise ValueError(_('destination revlog is not empty'))
2291 raise ValueError(_('destination revlog is not empty'))
2285
2292
2286 if getattr(self, 'filteredrevs', None):
2293 if getattr(self, 'filteredrevs', None):
2287 raise ValueError(_('source revlog has filtered revisions'))
2294 raise ValueError(_('source revlog has filtered revisions'))
2288 if getattr(destrevlog, 'filteredrevs', None):
2295 if getattr(destrevlog, 'filteredrevs', None):
2289 raise ValueError(_('destination revlog has filtered revisions'))
2296 raise ValueError(_('destination revlog has filtered revisions'))
2290
2297
2291 # lazydeltabase controls whether to reuse a cached delta, if possible.
2298 # lazydeltabase controls whether to reuse a cached delta, if possible.
2292 oldlazydeltabase = destrevlog._lazydeltabase
2299 oldlazydeltabase = destrevlog._lazydeltabase
2293 oldamd = destrevlog._deltabothparents
2300 oldamd = destrevlog._deltabothparents
2294
2301
2295 try:
2302 try:
2296 if deltareuse == self.DELTAREUSEALWAYS:
2303 if deltareuse == self.DELTAREUSEALWAYS:
2297 destrevlog._lazydeltabase = True
2304 destrevlog._lazydeltabase = True
2298 elif deltareuse == self.DELTAREUSESAMEREVS:
2305 elif deltareuse == self.DELTAREUSESAMEREVS:
2299 destrevlog._lazydeltabase = False
2306 destrevlog._lazydeltabase = False
2300
2307
2301 destrevlog._deltabothparents = deltabothparents or oldamd
2308 destrevlog._deltabothparents = deltabothparents or oldamd
2302
2309
2303 populatecachedelta = deltareuse in (self.DELTAREUSEALWAYS,
2310 populatecachedelta = deltareuse in (self.DELTAREUSEALWAYS,
2304 self.DELTAREUSESAMEREVS)
2311 self.DELTAREUSESAMEREVS)
2305
2312
2306 deltacomputer = deltautil.deltacomputer(destrevlog)
2313 deltacomputer = deltautil.deltacomputer(destrevlog)
2307 index = self.index
2314 index = self.index
2308 for rev in self:
2315 for rev in self:
2309 entry = index[rev]
2316 entry = index[rev]
2310
2317
2311 # Some classes override linkrev to take filtered revs into
2318 # Some classes override linkrev to take filtered revs into
2312 # account. Use raw entry from index.
2319 # account. Use raw entry from index.
2313 flags = entry[0] & 0xffff
2320 flags = entry[0] & 0xffff
2314 linkrev = entry[4]
2321 linkrev = entry[4]
2315 p1 = index[entry[5]][7]
2322 p1 = index[entry[5]][7]
2316 p2 = index[entry[6]][7]
2323 p2 = index[entry[6]][7]
2317 node = entry[7]
2324 node = entry[7]
2318
2325
2319 # (Possibly) reuse the delta from the revlog if allowed and
2326 # (Possibly) reuse the delta from the revlog if allowed and
2320 # the revlog chunk is a delta.
2327 # the revlog chunk is a delta.
2321 cachedelta = None
2328 cachedelta = None
2322 rawtext = None
2329 rawtext = None
2323 if populatecachedelta:
2330 if populatecachedelta:
2324 dp = self.deltaparent(rev)
2331 dp = self.deltaparent(rev)
2325 if dp != nullrev:
2332 if dp != nullrev:
2326 cachedelta = (dp, bytes(self._chunk(rev)))
2333 cachedelta = (dp, bytes(self._chunk(rev)))
2327
2334
2328 if not cachedelta:
2335 if not cachedelta:
2329 rawtext = self.revision(rev, raw=True)
2336 rawtext = self.revision(rev, raw=True)
2330
2337
2331
2338
2332 if deltareuse == self.DELTAREUSEFULLADD:
2339 if deltareuse == self.DELTAREUSEFULLADD:
2333 destrevlog.addrevision(rawtext, tr, linkrev, p1, p2,
2340 destrevlog.addrevision(rawtext, tr, linkrev, p1, p2,
2334 cachedelta=cachedelta,
2341 cachedelta=cachedelta,
2335 node=node, flags=flags,
2342 node=node, flags=flags,
2336 deltacomputer=deltacomputer)
2343 deltacomputer=deltacomputer)
2337 else:
2344 else:
2338 ifh = destrevlog.opener(destrevlog.indexfile, 'a+',
2345 ifh = destrevlog.opener(destrevlog.indexfile, 'a+',
2339 checkambig=False)
2346 checkambig=False)
2340 dfh = None
2347 dfh = None
2341 if not destrevlog._inline:
2348 if not destrevlog._inline:
2342 dfh = destrevlog.opener(destrevlog.datafile, 'a+')
2349 dfh = destrevlog.opener(destrevlog.datafile, 'a+')
2343 try:
2350 try:
2344 destrevlog._addrevision(node, rawtext, tr, linkrev, p1,
2351 destrevlog._addrevision(node, rawtext, tr, linkrev, p1,
2345 p2, flags, cachedelta, ifh, dfh,
2352 p2, flags, cachedelta, ifh, dfh,
2346 deltacomputer=deltacomputer)
2353 deltacomputer=deltacomputer)
2347 finally:
2354 finally:
2348 if dfh:
2355 if dfh:
2349 dfh.close()
2356 dfh.close()
2350 ifh.close()
2357 ifh.close()
2351
2358
2352 if addrevisioncb:
2359 if addrevisioncb:
2353 addrevisioncb(self, rev, node)
2360 addrevisioncb(self, rev, node)
2354 finally:
2361 finally:
2355 destrevlog._lazydeltabase = oldlazydeltabase
2362 destrevlog._lazydeltabase = oldlazydeltabase
2356 destrevlog._deltabothparents = oldamd
2363 destrevlog._deltabothparents = oldamd
2357
2364
2358 def censorrevision(self, tr, censornode, tombstone=b''):
2365 def censorrevision(self, tr, censornode, tombstone=b''):
2359 if (self.version & 0xFFFF) == REVLOGV0:
2366 if (self.version & 0xFFFF) == REVLOGV0:
2360 raise error.RevlogError(_('cannot censor with version %d revlogs') %
2367 raise error.RevlogError(_('cannot censor with version %d revlogs') %
2361 self.version)
2368 self.version)
2362
2369
2363 censorrev = self.rev(censornode)
2370 censorrev = self.rev(censornode)
2364 tombstone = storageutil.packmeta({b'censored': tombstone}, b'')
2371 tombstone = storageutil.packmeta({b'censored': tombstone}, b'')
2365
2372
2366 if len(tombstone) > self.rawsize(censorrev):
2373 if len(tombstone) > self.rawsize(censorrev):
2367 raise error.Abort(_('censor tombstone must be no longer than '
2374 raise error.Abort(_('censor tombstone must be no longer than '
2368 'censored data'))
2375 'censored data'))
2369
2376
2370 # Rewriting the revlog in place is hard. Our strategy for censoring is
2377 # Rewriting the revlog in place is hard. Our strategy for censoring is
2371 # to create a new revlog, copy all revisions to it, then replace the
2378 # to create a new revlog, copy all revisions to it, then replace the
2372 # revlogs on transaction close.
2379 # revlogs on transaction close.
2373
2380
2374 newindexfile = self.indexfile + b'.tmpcensored'
2381 newindexfile = self.indexfile + b'.tmpcensored'
2375 newdatafile = self.datafile + b'.tmpcensored'
2382 newdatafile = self.datafile + b'.tmpcensored'
2376
2383
2377 # This is a bit dangerous. We could easily have a mismatch of state.
2384 # This is a bit dangerous. We could easily have a mismatch of state.
2378 newrl = revlog(self.opener, newindexfile, newdatafile,
2385 newrl = revlog(self.opener, newindexfile, newdatafile,
2379 censorable=True)
2386 censorable=True)
2380 newrl.version = self.version
2387 newrl.version = self.version
2381 newrl._generaldelta = self._generaldelta
2388 newrl._generaldelta = self._generaldelta
2382 newrl._io = self._io
2389 newrl._io = self._io
2383
2390
2384 for rev in self.revs():
2391 for rev in self.revs():
2385 node = self.node(rev)
2392 node = self.node(rev)
2386 p1, p2 = self.parents(node)
2393 p1, p2 = self.parents(node)
2387
2394
2388 if rev == censorrev:
2395 if rev == censorrev:
2389 newrl.addrawrevision(tombstone, tr, self.linkrev(censorrev),
2396 newrl.addrawrevision(tombstone, tr, self.linkrev(censorrev),
2390 p1, p2, censornode, REVIDX_ISCENSORED)
2397 p1, p2, censornode, REVIDX_ISCENSORED)
2391
2398
2392 if newrl.deltaparent(rev) != nullrev:
2399 if newrl.deltaparent(rev) != nullrev:
2393 raise error.Abort(_('censored revision stored as delta; '
2400 raise error.Abort(_('censored revision stored as delta; '
2394 'cannot censor'),
2401 'cannot censor'),
2395 hint=_('censoring of revlogs is not '
2402 hint=_('censoring of revlogs is not '
2396 'fully implemented; please report '
2403 'fully implemented; please report '
2397 'this bug'))
2404 'this bug'))
2398 continue
2405 continue
2399
2406
2400 if self.iscensored(rev):
2407 if self.iscensored(rev):
2401 if self.deltaparent(rev) != nullrev:
2408 if self.deltaparent(rev) != nullrev:
2402 raise error.Abort(_('cannot censor due to censored '
2409 raise error.Abort(_('cannot censor due to censored '
2403 'revision having delta stored'))
2410 'revision having delta stored'))
2404 rawtext = self._chunk(rev)
2411 rawtext = self._chunk(rev)
2405 else:
2412 else:
2406 rawtext = self.revision(rev, raw=True)
2413 rawtext = self.revision(rev, raw=True)
2407
2414
2408 newrl.addrawrevision(rawtext, tr, self.linkrev(rev), p1, p2, node,
2415 newrl.addrawrevision(rawtext, tr, self.linkrev(rev), p1, p2, node,
2409 self.flags(rev))
2416 self.flags(rev))
2410
2417
2411 tr.addbackup(self.indexfile, location='store')
2418 tr.addbackup(self.indexfile, location='store')
2412 if not self._inline:
2419 if not self._inline:
2413 tr.addbackup(self.datafile, location='store')
2420 tr.addbackup(self.datafile, location='store')
2414
2421
2415 self.opener.rename(newrl.indexfile, self.indexfile)
2422 self.opener.rename(newrl.indexfile, self.indexfile)
2416 if not self._inline:
2423 if not self._inline:
2417 self.opener.rename(newrl.datafile, self.datafile)
2424 self.opener.rename(newrl.datafile, self.datafile)
2418
2425
2419 self.clearcaches()
2426 self.clearcaches()
2420 self._loadindex(self.version, None)
2427 self._loadindex(self.version, None)
2421
2428
2422 def verifyintegrity(self, state):
2429 def verifyintegrity(self, state):
2423 """Verifies the integrity of the revlog.
2430 """Verifies the integrity of the revlog.
2424
2431
2425 Yields ``revlogproblem`` instances describing problems that are
2432 Yields ``revlogproblem`` instances describing problems that are
2426 found.
2433 found.
2427 """
2434 """
2428 dd, di = self.checksize()
2435 dd, di = self.checksize()
2429 if dd:
2436 if dd:
2430 yield revlogproblem(error=_('data length off by %d bytes') % dd)
2437 yield revlogproblem(error=_('data length off by %d bytes') % dd)
2431 if di:
2438 if di:
2432 yield revlogproblem(error=_('index contains %d extra bytes') % di)
2439 yield revlogproblem(error=_('index contains %d extra bytes') % di)
2433
2440
2434 version = self.version & 0xFFFF
2441 version = self.version & 0xFFFF
2435
2442
2436 # The verifier tells us what version revlog we should be.
2443 # The verifier tells us what version revlog we should be.
2437 if version != state['expectedversion']:
2444 if version != state['expectedversion']:
2438 yield revlogproblem(
2445 yield revlogproblem(
2439 warning=_("warning: '%s' uses revlog format %d; expected %d") %
2446 warning=_("warning: '%s' uses revlog format %d; expected %d") %
2440 (self.indexfile, version, state['expectedversion']))
2447 (self.indexfile, version, state['expectedversion']))
2441
2448
2442 state['skipread'] = set()
2449 state['skipread'] = set()
2443
2450
2444 for rev in self:
2451 for rev in self:
2445 node = self.node(rev)
2452 node = self.node(rev)
2446
2453
2447 # Verify contents. 4 cases to care about:
2454 # Verify contents. 4 cases to care about:
2448 #
2455 #
2449 # common: the most common case
2456 # common: the most common case
2450 # rename: with a rename
2457 # rename: with a rename
2451 # meta: file content starts with b'\1\n', the metadata
2458 # meta: file content starts with b'\1\n', the metadata
2452 # header defined in filelog.py, but without a rename
2459 # header defined in filelog.py, but without a rename
2453 # ext: content stored externally
2460 # ext: content stored externally
2454 #
2461 #
2455 # More formally, their differences are shown below:
2462 # More formally, their differences are shown below:
2456 #
2463 #
2457 # | common | rename | meta | ext
2464 # | common | rename | meta | ext
2458 # -------------------------------------------------------
2465 # -------------------------------------------------------
2459 # flags() | 0 | 0 | 0 | not 0
2466 # flags() | 0 | 0 | 0 | not 0
2460 # renamed() | False | True | False | ?
2467 # renamed() | False | True | False | ?
2461 # rawtext[0:2]=='\1\n'| False | True | True | ?
2468 # rawtext[0:2]=='\1\n'| False | True | True | ?
2462 #
2469 #
2463 # "rawtext" means the raw text stored in revlog data, which
2470 # "rawtext" means the raw text stored in revlog data, which
2464 # could be retrieved by "revision(rev, raw=True)". "text"
2471 # could be retrieved by "revision(rev, raw=True)". "text"
2465 # mentioned below is "revision(rev, raw=False)".
2472 # mentioned below is "revision(rev, raw=False)".
2466 #
2473 #
2467 # There are 3 different lengths stored physically:
2474 # There are 3 different lengths stored physically:
2468 # 1. L1: rawsize, stored in revlog index
2475 # 1. L1: rawsize, stored in revlog index
2469 # 2. L2: len(rawtext), stored in revlog data
2476 # 2. L2: len(rawtext), stored in revlog data
2470 # 3. L3: len(text), stored in revlog data if flags==0, or
2477 # 3. L3: len(text), stored in revlog data if flags==0, or
2471 # possibly somewhere else if flags!=0
2478 # possibly somewhere else if flags!=0
2472 #
2479 #
2473 # L1 should be equal to L2. L3 could be different from them.
2480 # L1 should be equal to L2. L3 could be different from them.
2474 # "text" may or may not affect commit hash depending on flag
2481 # "text" may or may not affect commit hash depending on flag
2475 # processors (see revlog.addflagprocessor).
2482 # processors (see revlog.addflagprocessor).
2476 #
2483 #
2477 # | common | rename | meta | ext
2484 # | common | rename | meta | ext
2478 # -------------------------------------------------
2485 # -------------------------------------------------
2479 # rawsize() | L1 | L1 | L1 | L1
2486 # rawsize() | L1 | L1 | L1 | L1
2480 # size() | L1 | L2-LM | L1(*) | L1 (?)
2487 # size() | L1 | L2-LM | L1(*) | L1 (?)
2481 # len(rawtext) | L2 | L2 | L2 | L2
2488 # len(rawtext) | L2 | L2 | L2 | L2
2482 # len(text) | L2 | L2 | L2 | L3
2489 # len(text) | L2 | L2 | L2 | L3
2483 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
2490 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
2484 #
2491 #
2485 # LM: length of metadata, depending on rawtext
2492 # LM: length of metadata, depending on rawtext
2486 # (*): not ideal, see comment in filelog.size
2493 # (*): not ideal, see comment in filelog.size
2487 # (?): could be "- len(meta)" if the resolved content has
2494 # (?): could be "- len(meta)" if the resolved content has
2488 # rename metadata
2495 # rename metadata
2489 #
2496 #
2490 # Checks needed to be done:
2497 # Checks needed to be done:
2491 # 1. length check: L1 == L2, in all cases.
2498 # 1. length check: L1 == L2, in all cases.
2492 # 2. hash check: depending on flag processor, we may need to
2499 # 2. hash check: depending on flag processor, we may need to
2493 # use either "text" (external), or "rawtext" (in revlog).
2500 # use either "text" (external), or "rawtext" (in revlog).
2494
2501
2495 try:
2502 try:
2496 skipflags = state.get('skipflags', 0)
2503 skipflags = state.get('skipflags', 0)
2497 if skipflags:
2504 if skipflags:
2498 skipflags &= self.flags(rev)
2505 skipflags &= self.flags(rev)
2499
2506
2500 if skipflags:
2507 if skipflags:
2501 state['skipread'].add(node)
2508 state['skipread'].add(node)
2502 else:
2509 else:
2503 # Side-effect: read content and verify hash.
2510 # Side-effect: read content and verify hash.
2504 self.revision(node)
2511 self.revision(node)
2505
2512
2506 l1 = self.rawsize(rev)
2513 l1 = self.rawsize(rev)
2507 l2 = len(self.revision(node, raw=True))
2514 l2 = len(self.revision(node, raw=True))
2508
2515
2509 if l1 != l2:
2516 if l1 != l2:
2510 yield revlogproblem(
2517 yield revlogproblem(
2511 error=_('unpacked size is %d, %d expected') % (l2, l1),
2518 error=_('unpacked size is %d, %d expected') % (l2, l1),
2512 node=node)
2519 node=node)
2513
2520
2514 except error.CensoredNodeError:
2521 except error.CensoredNodeError:
2515 if state['erroroncensored']:
2522 if state['erroroncensored']:
2516 yield revlogproblem(error=_('censored file data'),
2523 yield revlogproblem(error=_('censored file data'),
2517 node=node)
2524 node=node)
2518 state['skipread'].add(node)
2525 state['skipread'].add(node)
2519 except Exception as e:
2526 except Exception as e:
2520 yield revlogproblem(
2527 yield revlogproblem(
2521 error=_('unpacking %s: %s') % (short(node),
2528 error=_('unpacking %s: %s') % (short(node),
2522 stringutil.forcebytestr(e)),
2529 stringutil.forcebytestr(e)),
2523 node=node)
2530 node=node)
2524 state['skipread'].add(node)
2531 state['skipread'].add(node)
2525
2532
2526 def storageinfo(self, exclusivefiles=False, sharedfiles=False,
2533 def storageinfo(self, exclusivefiles=False, sharedfiles=False,
2527 revisionscount=False, trackedsize=False,
2534 revisionscount=False, trackedsize=False,
2528 storedsize=False):
2535 storedsize=False):
2529 d = {}
2536 d = {}
2530
2537
2531 if exclusivefiles:
2538 if exclusivefiles:
2532 d['exclusivefiles'] = [(self.opener, self.indexfile)]
2539 d['exclusivefiles'] = [(self.opener, self.indexfile)]
2533 if not self._inline:
2540 if not self._inline:
2534 d['exclusivefiles'].append((self.opener, self.datafile))
2541 d['exclusivefiles'].append((self.opener, self.datafile))
2535
2542
2536 if sharedfiles:
2543 if sharedfiles:
2537 d['sharedfiles'] = []
2544 d['sharedfiles'] = []
2538
2545
2539 if revisionscount:
2546 if revisionscount:
2540 d['revisionscount'] = len(self)
2547 d['revisionscount'] = len(self)
2541
2548
2542 if trackedsize:
2549 if trackedsize:
2543 d['trackedsize'] = sum(map(self.rawsize, iter(self)))
2550 d['trackedsize'] = sum(map(self.rawsize, iter(self)))
2544
2551
2545 if storedsize:
2552 if storedsize:
2546 d['storedsize'] = sum(self.opener.stat(path).st_size
2553 d['storedsize'] = sum(self.opener.stat(path).st_size
2547 for path in self.files())
2554 for path in self.files())
2548
2555
2549 return d
2556 return d
@@ -1,300 +1,302 b''
1 # Create server
1 # Create server
2 $ hg init server
2 $ hg init server
3 $ cd server
3 $ cd server
4 $ cat >> .hg/hgrc << EOF
4 $ cat >> .hg/hgrc << EOF
5 > [extensions]
5 > [extensions]
6 > extension=$TESTDIR/flagprocessorext.py
6 > extension=$TESTDIR/flagprocessorext.py
7 > EOF
7 > EOF
8 $ cd ../
8 $ cd ../
9
9
10 # Clone server and enable extensions
10 # Clone server and enable extensions
11 $ hg clone -q server client
11 $ hg clone -q server client
12 $ cd client
12 $ cd client
13 $ cat >> .hg/hgrc << EOF
13 $ cat >> .hg/hgrc << EOF
14 > [extensions]
14 > [extensions]
15 > extension=$TESTDIR/flagprocessorext.py
15 > extension=$TESTDIR/flagprocessorext.py
16 > EOF
16 > EOF
17
17
18 # Commit file that will trigger the noop extension
18 # Commit file that will trigger the noop extension
19 $ echo '[NOOP]' > noop
19 $ echo '[NOOP]' > noop
20 $ hg commit -Aqm "noop"
20 $ hg commit -Aqm "noop"
21
21
22 # Commit file that will trigger the base64 extension
22 # Commit file that will trigger the base64 extension
23 $ echo '[BASE64]' > base64
23 $ echo '[BASE64]' > base64
24 $ hg commit -Aqm 'base64'
24 $ hg commit -Aqm 'base64'
25
25
26 # Commit file that will trigger the gzip extension
26 # Commit file that will trigger the gzip extension
27 $ echo '[GZIP]' > gzip
27 $ echo '[GZIP]' > gzip
28 $ hg commit -Aqm 'gzip'
28 $ hg commit -Aqm 'gzip'
29
29
30 # Commit file that will trigger noop and base64
30 # Commit file that will trigger noop and base64
31 $ echo '[NOOP][BASE64]' > noop-base64
31 $ echo '[NOOP][BASE64]' > noop-base64
32 $ hg commit -Aqm 'noop+base64'
32 $ hg commit -Aqm 'noop+base64'
33
33
34 # Commit file that will trigger noop and gzip
34 # Commit file that will trigger noop and gzip
35 $ echo '[NOOP][GZIP]' > noop-gzip
35 $ echo '[NOOP][GZIP]' > noop-gzip
36 $ hg commit -Aqm 'noop+gzip'
36 $ hg commit -Aqm 'noop+gzip'
37
37
38 # Commit file that will trigger base64 and gzip
38 # Commit file that will trigger base64 and gzip
39 $ echo '[BASE64][GZIP]' > base64-gzip
39 $ echo '[BASE64][GZIP]' > base64-gzip
40 $ hg commit -Aqm 'base64+gzip'
40 $ hg commit -Aqm 'base64+gzip'
41
41
42 # Commit file that will trigger base64, gzip and noop
42 # Commit file that will trigger base64, gzip and noop
43 $ echo '[BASE64][GZIP][NOOP]' > base64-gzip-noop
43 $ echo '[BASE64][GZIP][NOOP]' > base64-gzip-noop
44 $ hg commit -Aqm 'base64+gzip+noop'
44 $ hg commit -Aqm 'base64+gzip+noop'
45
45
46 # TEST: ensure the revision data is consistent
46 # TEST: ensure the revision data is consistent
47 $ hg cat noop
47 $ hg cat noop
48 [NOOP]
48 [NOOP]
49 $ hg debugdata noop 0
49 $ hg debugdata noop 0
50 [NOOP]
50 [NOOP]
51
51
52 $ hg cat -r . base64
52 $ hg cat -r . base64
53 [BASE64]
53 [BASE64]
54 $ hg debugdata base64 0
54 $ hg debugdata base64 0
55 W0JBU0U2NF0K (no-eol)
55 W0JBU0U2NF0K (no-eol)
56
56
57 $ hg cat -r . gzip
57 $ hg cat -r . gzip
58 [GZIP]
58 [GZIP]
59 $ hg debugdata gzip 0
59 $ hg debugdata gzip 0
60 x\x9c\x8bv\x8f\xf2\x0c\x88\xe5\x02\x00\x08\xc8\x01\xfd (no-eol) (esc)
60 x\x9c\x8bv\x8f\xf2\x0c\x88\xe5\x02\x00\x08\xc8\x01\xfd (no-eol) (esc)
61
61
62 $ hg cat -r . noop-base64
62 $ hg cat -r . noop-base64
63 [NOOP][BASE64]
63 [NOOP][BASE64]
64 $ hg debugdata noop-base64 0
64 $ hg debugdata noop-base64 0
65 W05PT1BdW0JBU0U2NF0K (no-eol)
65 W05PT1BdW0JBU0U2NF0K (no-eol)
66
66
67 $ hg cat -r . noop-gzip
67 $ hg cat -r . noop-gzip
68 [NOOP][GZIP]
68 [NOOP][GZIP]
69 $ hg debugdata noop-gzip 0
69 $ hg debugdata noop-gzip 0
70 x\x9c\x8b\xf6\xf3\xf7\x0f\x88\x8dv\x8f\xf2\x0c\x88\xe5\x02\x00\x1dH\x03\xf1 (no-eol) (esc)
70 x\x9c\x8b\xf6\xf3\xf7\x0f\x88\x8dv\x8f\xf2\x0c\x88\xe5\x02\x00\x1dH\x03\xf1 (no-eol) (esc)
71
71
72 $ hg cat -r . base64-gzip
72 $ hg cat -r . base64-gzip
73 [BASE64][GZIP]
73 [BASE64][GZIP]
74 $ hg debugdata base64-gzip 0
74 $ hg debugdata base64-gzip 0
75 eJyLdnIMdjUziY12j/IMiOUCACLBBDo= (no-eol)
75 eJyLdnIMdjUziY12j/IMiOUCACLBBDo= (no-eol)
76
76
77 $ hg cat -r . base64-gzip-noop
77 $ hg cat -r . base64-gzip-noop
78 [BASE64][GZIP][NOOP]
78 [BASE64][GZIP][NOOP]
79 $ hg debugdata base64-gzip-noop 0
79 $ hg debugdata base64-gzip-noop 0
80 eJyLdnIMdjUziY12j/IMiI328/cPiOUCAESjBi4= (no-eol)
80 eJyLdnIMdjUziY12j/IMiI328/cPiOUCAESjBi4= (no-eol)
81
81
82 # Push to the server
82 # Push to the server
83 $ hg push
83 $ hg push
84 pushing to $TESTTMP/server
84 pushing to $TESTTMP/server
85 searching for changes
85 searching for changes
86 adding changesets
86 adding changesets
87 adding manifests
87 adding manifests
88 adding file changes
88 adding file changes
89 added 7 changesets with 7 changes to 7 files
89 added 7 changesets with 7 changes to 7 files
90
90
91 Ensure the data got to the server OK
91 Ensure the data got to the server OK
92
92
93 $ cd ../server
93 $ cd ../server
94 $ hg cat -r 6e48f4215d24 noop
94 $ hg cat -r 6e48f4215d24 noop
95 [NOOP]
95 [NOOP]
96 $ hg debugdata noop 0
96 $ hg debugdata noop 0
97 [NOOP]
97 [NOOP]
98
98
99 $ hg cat -r 6e48f4215d24 base64
99 $ hg cat -r 6e48f4215d24 base64
100 [BASE64]
100 [BASE64]
101 $ hg debugdata base64 0
101 $ hg debugdata base64 0
102 W0JBU0U2NF0K (no-eol)
102 W0JBU0U2NF0K (no-eol)
103
103
104 $ hg cat -r 6e48f4215d24 gzip
104 $ hg cat -r 6e48f4215d24 gzip
105 [GZIP]
105 [GZIP]
106 $ hg debugdata gzip 0
106 $ hg debugdata gzip 0
107 x\x9c\x8bv\x8f\xf2\x0c\x88\xe5\x02\x00\x08\xc8\x01\xfd (no-eol) (esc)
107 x\x9c\x8bv\x8f\xf2\x0c\x88\xe5\x02\x00\x08\xc8\x01\xfd (no-eol) (esc)
108
108
109 $ hg cat -r 6e48f4215d24 noop-base64
109 $ hg cat -r 6e48f4215d24 noop-base64
110 [NOOP][BASE64]
110 [NOOP][BASE64]
111 $ hg debugdata noop-base64 0
111 $ hg debugdata noop-base64 0
112 W05PT1BdW0JBU0U2NF0K (no-eol)
112 W05PT1BdW0JBU0U2NF0K (no-eol)
113
113
114 $ hg cat -r 6e48f4215d24 noop-gzip
114 $ hg cat -r 6e48f4215d24 noop-gzip
115 [NOOP][GZIP]
115 [NOOP][GZIP]
116 $ hg debugdata noop-gzip 0
116 $ hg debugdata noop-gzip 0
117 x\x9c\x8b\xf6\xf3\xf7\x0f\x88\x8dv\x8f\xf2\x0c\x88\xe5\x02\x00\x1dH\x03\xf1 (no-eol) (esc)
117 x\x9c\x8b\xf6\xf3\xf7\x0f\x88\x8dv\x8f\xf2\x0c\x88\xe5\x02\x00\x1dH\x03\xf1 (no-eol) (esc)
118
118
119 $ hg cat -r 6e48f4215d24 base64-gzip
119 $ hg cat -r 6e48f4215d24 base64-gzip
120 [BASE64][GZIP]
120 [BASE64][GZIP]
121 $ hg debugdata base64-gzip 0
121 $ hg debugdata base64-gzip 0
122 eJyLdnIMdjUziY12j/IMiOUCACLBBDo= (no-eol)
122 eJyLdnIMdjUziY12j/IMiOUCACLBBDo= (no-eol)
123
123
124 $ hg cat -r 6e48f4215d24 base64-gzip-noop
124 $ hg cat -r 6e48f4215d24 base64-gzip-noop
125 [BASE64][GZIP][NOOP]
125 [BASE64][GZIP][NOOP]
126 $ hg debugdata base64-gzip-noop 0
126 $ hg debugdata base64-gzip-noop 0
127 eJyLdnIMdjUziY12j/IMiI328/cPiOUCAESjBi4= (no-eol)
127 eJyLdnIMdjUziY12j/IMiI328/cPiOUCAESjBi4= (no-eol)
128
128
129 # Initialize new client (not cloning) and setup extension
129 # Initialize new client (not cloning) and setup extension
130 $ cd ..
130 $ cd ..
131 $ hg init client2
131 $ hg init client2
132 $ cd client2
132 $ cd client2
133 $ cat >> .hg/hgrc << EOF
133 $ cat >> .hg/hgrc << EOF
134 > [paths]
134 > [paths]
135 > default = $TESTTMP/server
135 > default = $TESTTMP/server
136 > [extensions]
136 > [extensions]
137 > extension=$TESTDIR/flagprocessorext.py
137 > extension=$TESTDIR/flagprocessorext.py
138 > EOF
138 > EOF
139
139
140 # Pull from server and update to latest revision
140 # Pull from server and update to latest revision
141 $ hg pull default
141 $ hg pull default
142 pulling from $TESTTMP/server
142 pulling from $TESTTMP/server
143 requesting all changes
143 requesting all changes
144 adding changesets
144 adding changesets
145 adding manifests
145 adding manifests
146 adding file changes
146 adding file changes
147 added 7 changesets with 7 changes to 7 files
147 added 7 changesets with 7 changes to 7 files
148 new changesets 07b1b9442c5b:6e48f4215d24
148 new changesets 07b1b9442c5b:6e48f4215d24
149 (run 'hg update' to get a working copy)
149 (run 'hg update' to get a working copy)
150 $ hg update
150 $ hg update
151 7 files updated, 0 files merged, 0 files removed, 0 files unresolved
151 7 files updated, 0 files merged, 0 files removed, 0 files unresolved
152
152
153 # TEST: ensure the revision data is consistent
153 # TEST: ensure the revision data is consistent
154 $ hg cat noop
154 $ hg cat noop
155 [NOOP]
155 [NOOP]
156 $ hg debugdata noop 0
156 $ hg debugdata noop 0
157 [NOOP]
157 [NOOP]
158
158
159 $ hg cat -r . base64
159 $ hg cat -r . base64
160 [BASE64]
160 [BASE64]
161 $ hg debugdata base64 0
161 $ hg debugdata base64 0
162 W0JBU0U2NF0K (no-eol)
162 W0JBU0U2NF0K (no-eol)
163
163
164 $ hg cat -r . gzip
164 $ hg cat -r . gzip
165 [GZIP]
165 [GZIP]
166 $ hg debugdata gzip 0
166 $ hg debugdata gzip 0
167 x\x9c\x8bv\x8f\xf2\x0c\x88\xe5\x02\x00\x08\xc8\x01\xfd (no-eol) (esc)
167 x\x9c\x8bv\x8f\xf2\x0c\x88\xe5\x02\x00\x08\xc8\x01\xfd (no-eol) (esc)
168
168
169 $ hg cat -r . noop-base64
169 $ hg cat -r . noop-base64
170 [NOOP][BASE64]
170 [NOOP][BASE64]
171 $ hg debugdata noop-base64 0
171 $ hg debugdata noop-base64 0
172 W05PT1BdW0JBU0U2NF0K (no-eol)
172 W05PT1BdW0JBU0U2NF0K (no-eol)
173
173
174 $ hg cat -r . noop-gzip
174 $ hg cat -r . noop-gzip
175 [NOOP][GZIP]
175 [NOOP][GZIP]
176 $ hg debugdata noop-gzip 0
176 $ hg debugdata noop-gzip 0
177 x\x9c\x8b\xf6\xf3\xf7\x0f\x88\x8dv\x8f\xf2\x0c\x88\xe5\x02\x00\x1dH\x03\xf1 (no-eol) (esc)
177 x\x9c\x8b\xf6\xf3\xf7\x0f\x88\x8dv\x8f\xf2\x0c\x88\xe5\x02\x00\x1dH\x03\xf1 (no-eol) (esc)
178
178
179 $ hg cat -r . base64-gzip
179 $ hg cat -r . base64-gzip
180 [BASE64][GZIP]
180 [BASE64][GZIP]
181 $ hg debugdata base64-gzip 0
181 $ hg debugdata base64-gzip 0
182 eJyLdnIMdjUziY12j/IMiOUCACLBBDo= (no-eol)
182 eJyLdnIMdjUziY12j/IMiOUCACLBBDo= (no-eol)
183
183
184 $ hg cat -r . base64-gzip-noop
184 $ hg cat -r . base64-gzip-noop
185 [BASE64][GZIP][NOOP]
185 [BASE64][GZIP][NOOP]
186 $ hg debugdata base64-gzip-noop 0
186 $ hg debugdata base64-gzip-noop 0
187 eJyLdnIMdjUziY12j/IMiI328/cPiOUCAESjBi4= (no-eol)
187 eJyLdnIMdjUziY12j/IMiI328/cPiOUCAESjBi4= (no-eol)
188
188
189 # TEST: ensure a missing processor is handled
189 # TEST: ensure a missing processor is handled
190 $ echo '[FAIL][BASE64][GZIP][NOOP]' > fail-base64-gzip-noop
190 $ echo '[FAIL][BASE64][GZIP][NOOP]' > fail-base64-gzip-noop
191 $ hg commit -Aqm 'fail+base64+gzip+noop'
191 $ hg commit -Aqm 'fail+base64+gzip+noop'
192 abort: missing processor for flag '0x1'!
192 abort: missing processor for flag '0x1'!
193 [255]
193 [255]
194 $ rm fail-base64-gzip-noop
194 $ rm fail-base64-gzip-noop
195
195
196 # TEST: ensure we cannot register several flag processors on the same flag
196 # TEST: ensure we cannot register several flag processors on the same flag
197 $ cat >> .hg/hgrc << EOF
197 $ cat >> .hg/hgrc << EOF
198 > [extensions]
198 > [extensions]
199 > extension=$TESTDIR/flagprocessorext.py
199 > extension=$TESTDIR/flagprocessorext.py
200 > duplicate=$TESTDIR/flagprocessorext.py
200 > duplicate=$TESTDIR/flagprocessorext.py
201 > EOF
201 > EOF
202 $ hg debugrebuilddirstate
202 $ hg debugrebuilddirstate
203 Traceback (most recent call last):
203 Traceback (most recent call last):
204 File "*/mercurial/extensions.py", line *, in _runextsetup (glob)
204 File "*/mercurial/extensions.py", line *, in _runextsetup (glob)
205 extsetup(ui)
205 extsetup(ui)
206 File "*/tests/flagprocessorext.py", line *, in extsetup (glob)
206 File "*/tests/flagprocessorext.py", line *, in extsetup (glob)
207 validatehash,
207 validatehash,
208 File "*/mercurial/revlog.py", line *, in addflagprocessor (glob)
208 File "*/mercurial/revlog.py", line *, in addflagprocessor (glob)
209 _insertflagprocessor(flag, processor, _flagprocessors)
210 File "*/mercurial/revlog.py", line *, in _insertflagprocessor (glob)
209 raise error.Abort(msg)
211 raise error.Abort(msg)
210 Abort: cannot register multiple processors on flag '0x8'.
212 Abort: cannot register multiple processors on flag '0x8'.
211 *** failed to set up extension duplicate: cannot register multiple processors on flag '0x8'.
213 *** failed to set up extension duplicate: cannot register multiple processors on flag '0x8'.
212 $ hg st 2>&1 | egrep 'cannot register multiple processors|flagprocessorext'
214 $ hg st 2>&1 | egrep 'cannot register multiple processors|flagprocessorext'
213 File "*/tests/flagprocessorext.py", line *, in extsetup (glob)
215 File "*/tests/flagprocessorext.py", line *, in extsetup (glob)
214 Abort: cannot register multiple processors on flag '0x8'.
216 Abort: cannot register multiple processors on flag '0x8'.
215 *** failed to set up extension duplicate: cannot register multiple processors on flag '0x8'.
217 *** failed to set up extension duplicate: cannot register multiple processors on flag '0x8'.
216 File "*/tests/flagprocessorext.py", line *, in b64decode (glob)
218 File "*/tests/flagprocessorext.py", line *, in b64decode (glob)
217
219
218 $ cd ..
220 $ cd ..
219
221
220 # TEST: bundle repo
222 # TEST: bundle repo
221 $ hg init bundletest
223 $ hg init bundletest
222 $ cd bundletest
224 $ cd bundletest
223
225
224 $ cat >> .hg/hgrc << EOF
226 $ cat >> .hg/hgrc << EOF
225 > [extensions]
227 > [extensions]
226 > flagprocessor=$TESTDIR/flagprocessorext.py
228 > flagprocessor=$TESTDIR/flagprocessorext.py
227 > EOF
229 > EOF
228
230
229 $ for i in 0 single two three 4; do
231 $ for i in 0 single two three 4; do
230 > echo '[BASE64]a-bit-longer-'$i > base64
232 > echo '[BASE64]a-bit-longer-'$i > base64
231 > hg commit -m base64-$i -A base64
233 > hg commit -m base64-$i -A base64
232 > done
234 > done
233
235
234 $ hg update 2 -q
236 $ hg update 2 -q
235 $ echo '[BASE64]a-bit-longer-branching' > base64
237 $ echo '[BASE64]a-bit-longer-branching' > base64
236 $ hg commit -q -m branching
238 $ hg commit -q -m branching
237
239
238 #if repobundlerepo
240 #if repobundlerepo
239 $ hg bundle --base 1 bundle.hg
241 $ hg bundle --base 1 bundle.hg
240 4 changesets found
242 4 changesets found
241 $ hg --config extensions.strip= strip -r 2 --no-backup --force -q
243 $ hg --config extensions.strip= strip -r 2 --no-backup --force -q
242 $ hg -R bundle.hg log --stat -T '{rev} {desc}\n' base64
244 $ hg -R bundle.hg log --stat -T '{rev} {desc}\n' base64
243 5 branching
245 5 branching
244 base64 | 2 +-
246 base64 | 2 +-
245 1 files changed, 1 insertions(+), 1 deletions(-)
247 1 files changed, 1 insertions(+), 1 deletions(-)
246
248
247 4 base64-4
249 4 base64-4
248 base64 | 2 +-
250 base64 | 2 +-
249 1 files changed, 1 insertions(+), 1 deletions(-)
251 1 files changed, 1 insertions(+), 1 deletions(-)
250
252
251 3 base64-three
253 3 base64-three
252 base64 | 2 +-
254 base64 | 2 +-
253 1 files changed, 1 insertions(+), 1 deletions(-)
255 1 files changed, 1 insertions(+), 1 deletions(-)
254
256
255 2 base64-two
257 2 base64-two
256 base64 | 2 +-
258 base64 | 2 +-
257 1 files changed, 1 insertions(+), 1 deletions(-)
259 1 files changed, 1 insertions(+), 1 deletions(-)
258
260
259 1 base64-single
261 1 base64-single
260 base64 | 2 +-
262 base64 | 2 +-
261 1 files changed, 1 insertions(+), 1 deletions(-)
263 1 files changed, 1 insertions(+), 1 deletions(-)
262
264
263 0 base64-0
265 0 base64-0
264 base64 | 1 +
266 base64 | 1 +
265 1 files changed, 1 insertions(+), 0 deletions(-)
267 1 files changed, 1 insertions(+), 0 deletions(-)
266
268
267
269
268 $ hg bundle -R bundle.hg --base 1 bundle-again.hg -q
270 $ hg bundle -R bundle.hg --base 1 bundle-again.hg -q
269 $ hg -R bundle-again.hg log --stat -T '{rev} {desc}\n' base64
271 $ hg -R bundle-again.hg log --stat -T '{rev} {desc}\n' base64
270 5 branching
272 5 branching
271 base64 | 2 +-
273 base64 | 2 +-
272 1 files changed, 1 insertions(+), 1 deletions(-)
274 1 files changed, 1 insertions(+), 1 deletions(-)
273
275
274 4 base64-4
276 4 base64-4
275 base64 | 2 +-
277 base64 | 2 +-
276 1 files changed, 1 insertions(+), 1 deletions(-)
278 1 files changed, 1 insertions(+), 1 deletions(-)
277
279
278 3 base64-three
280 3 base64-three
279 base64 | 2 +-
281 base64 | 2 +-
280 1 files changed, 1 insertions(+), 1 deletions(-)
282 1 files changed, 1 insertions(+), 1 deletions(-)
281
283
282 2 base64-two
284 2 base64-two
283 base64 | 2 +-
285 base64 | 2 +-
284 1 files changed, 1 insertions(+), 1 deletions(-)
286 1 files changed, 1 insertions(+), 1 deletions(-)
285
287
286 1 base64-single
288 1 base64-single
287 base64 | 2 +-
289 base64 | 2 +-
288 1 files changed, 1 insertions(+), 1 deletions(-)
290 1 files changed, 1 insertions(+), 1 deletions(-)
289
291
290 0 base64-0
292 0 base64-0
291 base64 | 1 +
293 base64 | 1 +
292 1 files changed, 1 insertions(+), 0 deletions(-)
294 1 files changed, 1 insertions(+), 0 deletions(-)
293
295
294 $ rm bundle.hg bundle-again.hg
296 $ rm bundle.hg bundle-again.hg
295 #endif
297 #endif
296
298
297 # TEST: hg status
299 # TEST: hg status
298
300
299 $ hg status
301 $ hg status
300 $ hg diff
302 $ hg diff
General Comments 0
You need to be logged in to leave comments. Login now