##// END OF EJS Templates
localrepo: push manifestlog and changelog construction code into store...
Augie Fackler -
r43175:3df3b139 default
parent child Browse files
Show More
@@ -1,3316 +1,3311 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,
32 color,
31 color,
33 context,
32 context,
34 dirstate,
33 dirstate,
35 dirstateguard,
34 dirstateguard,
36 discovery,
35 discovery,
37 encoding,
36 encoding,
38 error,
37 error,
39 exchange,
38 exchange,
40 extensions,
39 extensions,
41 filelog,
40 filelog,
42 hook,
41 hook,
43 lock as lockmod,
42 lock as lockmod,
44 manifest,
45 match as matchmod,
43 match as matchmod,
46 merge as mergemod,
44 merge as mergemod,
47 mergeutil,
45 mergeutil,
48 namespaces,
46 namespaces,
49 narrowspec,
47 narrowspec,
50 obsolete,
48 obsolete,
51 pathutil,
49 pathutil,
52 phases,
50 phases,
53 pushkey,
51 pushkey,
54 pycompat,
52 pycompat,
55 repoview,
53 repoview,
56 revset,
54 revset,
57 revsetlang,
55 revsetlang,
58 scmutil,
56 scmutil,
59 sparse,
57 sparse,
60 store as storemod,
58 store as storemod,
61 subrepoutil,
59 subrepoutil,
62 tags as tagsmod,
60 tags as tagsmod,
63 transaction,
61 transaction,
64 txnutil,
62 txnutil,
65 util,
63 util,
66 vfs as vfsmod,
64 vfs as vfsmod,
67 )
65 )
68
66
69 from .interfaces import (
67 from .interfaces import (
70 repository,
68 repository,
71 util as interfaceutil,
69 util as interfaceutil,
72 )
70 )
73
71
74 from .utils import (
72 from .utils import (
75 procutil,
73 procutil,
76 stringutil,
74 stringutil,
77 )
75 )
78
76
79 from .revlogutils import (
77 from .revlogutils import (
80 constants as revlogconst,
78 constants as revlogconst,
81 )
79 )
82
80
83 release = lockmod.release
81 release = lockmod.release
84 urlerr = util.urlerr
82 urlerr = util.urlerr
85 urlreq = util.urlreq
83 urlreq = util.urlreq
86
84
87 # set of (path, vfs-location) tuples. vfs-location is:
85 # set of (path, vfs-location) tuples. vfs-location is:
88 # - 'plain for vfs relative paths
86 # - 'plain for vfs relative paths
89 # - '' for svfs relative paths
87 # - '' for svfs relative paths
90 _cachedfiles = set()
88 _cachedfiles = set()
91
89
92 class _basefilecache(scmutil.filecache):
90 class _basefilecache(scmutil.filecache):
93 """All filecache usage on repo are done for logic that should be unfiltered
91 """All filecache usage on repo are done for logic that should be unfiltered
94 """
92 """
95 def __get__(self, repo, type=None):
93 def __get__(self, repo, type=None):
96 if repo is None:
94 if repo is None:
97 return self
95 return self
98 # proxy to unfiltered __dict__ since filtered repo has no entry
96 # proxy to unfiltered __dict__ since filtered repo has no entry
99 unfi = repo.unfiltered()
97 unfi = repo.unfiltered()
100 try:
98 try:
101 return unfi.__dict__[self.sname]
99 return unfi.__dict__[self.sname]
102 except KeyError:
100 except KeyError:
103 pass
101 pass
104 return super(_basefilecache, self).__get__(unfi, type)
102 return super(_basefilecache, self).__get__(unfi, type)
105
103
106 def set(self, repo, value):
104 def set(self, repo, value):
107 return super(_basefilecache, self).set(repo.unfiltered(), value)
105 return super(_basefilecache, self).set(repo.unfiltered(), value)
108
106
109 class repofilecache(_basefilecache):
107 class repofilecache(_basefilecache):
110 """filecache for files in .hg but outside of .hg/store"""
108 """filecache for files in .hg but outside of .hg/store"""
111 def __init__(self, *paths):
109 def __init__(self, *paths):
112 super(repofilecache, self).__init__(*paths)
110 super(repofilecache, self).__init__(*paths)
113 for path in paths:
111 for path in paths:
114 _cachedfiles.add((path, 'plain'))
112 _cachedfiles.add((path, 'plain'))
115
113
116 def join(self, obj, fname):
114 def join(self, obj, fname):
117 return obj.vfs.join(fname)
115 return obj.vfs.join(fname)
118
116
119 class storecache(_basefilecache):
117 class storecache(_basefilecache):
120 """filecache for files in the store"""
118 """filecache for files in the store"""
121 def __init__(self, *paths):
119 def __init__(self, *paths):
122 super(storecache, self).__init__(*paths)
120 super(storecache, self).__init__(*paths)
123 for path in paths:
121 for path in paths:
124 _cachedfiles.add((path, ''))
122 _cachedfiles.add((path, ''))
125
123
126 def join(self, obj, fname):
124 def join(self, obj, fname):
127 return obj.sjoin(fname)
125 return obj.sjoin(fname)
128
126
129 class mixedrepostorecache(_basefilecache):
127 class mixedrepostorecache(_basefilecache):
130 """filecache for a mix files in .hg/store and outside"""
128 """filecache for a mix files in .hg/store and outside"""
131 def __init__(self, *pathsandlocations):
129 def __init__(self, *pathsandlocations):
132 # scmutil.filecache only uses the path for passing back into our
130 # scmutil.filecache only uses the path for passing back into our
133 # join(), so we can safely pass a list of paths and locations
131 # join(), so we can safely pass a list of paths and locations
134 super(mixedrepostorecache, self).__init__(*pathsandlocations)
132 super(mixedrepostorecache, self).__init__(*pathsandlocations)
135 _cachedfiles.update(pathsandlocations)
133 _cachedfiles.update(pathsandlocations)
136
134
137 def join(self, obj, fnameandlocation):
135 def join(self, obj, fnameandlocation):
138 fname, location = fnameandlocation
136 fname, location = fnameandlocation
139 if location == 'plain':
137 if location == 'plain':
140 return obj.vfs.join(fname)
138 return obj.vfs.join(fname)
141 else:
139 else:
142 if location != '':
140 if location != '':
143 raise error.ProgrammingError('unexpected location: %s' %
141 raise error.ProgrammingError('unexpected location: %s' %
144 location)
142 location)
145 return obj.sjoin(fname)
143 return obj.sjoin(fname)
146
144
147 def isfilecached(repo, name):
145 def isfilecached(repo, name):
148 """check if a repo has already cached "name" filecache-ed property
146 """check if a repo has already cached "name" filecache-ed property
149
147
150 This returns (cachedobj-or-None, iscached) tuple.
148 This returns (cachedobj-or-None, iscached) tuple.
151 """
149 """
152 cacheentry = repo.unfiltered()._filecache.get(name, None)
150 cacheentry = repo.unfiltered()._filecache.get(name, None)
153 if not cacheentry:
151 if not cacheentry:
154 return None, False
152 return None, False
155 return cacheentry.obj, True
153 return cacheentry.obj, True
156
154
157 class unfilteredpropertycache(util.propertycache):
155 class unfilteredpropertycache(util.propertycache):
158 """propertycache that apply to unfiltered repo only"""
156 """propertycache that apply to unfiltered repo only"""
159
157
160 def __get__(self, repo, type=None):
158 def __get__(self, repo, type=None):
161 unfi = repo.unfiltered()
159 unfi = repo.unfiltered()
162 if unfi is repo:
160 if unfi is repo:
163 return super(unfilteredpropertycache, self).__get__(unfi)
161 return super(unfilteredpropertycache, self).__get__(unfi)
164 return getattr(unfi, self.name)
162 return getattr(unfi, self.name)
165
163
166 class filteredpropertycache(util.propertycache):
164 class filteredpropertycache(util.propertycache):
167 """propertycache that must take filtering in account"""
165 """propertycache that must take filtering in account"""
168
166
169 def cachevalue(self, obj, value):
167 def cachevalue(self, obj, value):
170 object.__setattr__(obj, self.name, value)
168 object.__setattr__(obj, self.name, value)
171
169
172
170
173 def hasunfilteredcache(repo, name):
171 def hasunfilteredcache(repo, name):
174 """check if a repo has an unfilteredpropertycache value for <name>"""
172 """check if a repo has an unfilteredpropertycache value for <name>"""
175 return name in vars(repo.unfiltered())
173 return name in vars(repo.unfiltered())
176
174
177 def unfilteredmethod(orig):
175 def unfilteredmethod(orig):
178 """decorate method that always need to be run on unfiltered version"""
176 """decorate method that always need to be run on unfiltered version"""
179 def wrapper(repo, *args, **kwargs):
177 def wrapper(repo, *args, **kwargs):
180 return orig(repo.unfiltered(), *args, **kwargs)
178 return orig(repo.unfiltered(), *args, **kwargs)
181 return wrapper
179 return wrapper
182
180
183 moderncaps = {'lookup', 'branchmap', 'pushkey', 'known', 'getbundle',
181 moderncaps = {'lookup', 'branchmap', 'pushkey', 'known', 'getbundle',
184 'unbundle'}
182 'unbundle'}
185 legacycaps = moderncaps.union({'changegroupsubset'})
183 legacycaps = moderncaps.union({'changegroupsubset'})
186
184
187 @interfaceutil.implementer(repository.ipeercommandexecutor)
185 @interfaceutil.implementer(repository.ipeercommandexecutor)
188 class localcommandexecutor(object):
186 class localcommandexecutor(object):
189 def __init__(self, peer):
187 def __init__(self, peer):
190 self._peer = peer
188 self._peer = peer
191 self._sent = False
189 self._sent = False
192 self._closed = False
190 self._closed = False
193
191
194 def __enter__(self):
192 def __enter__(self):
195 return self
193 return self
196
194
197 def __exit__(self, exctype, excvalue, exctb):
195 def __exit__(self, exctype, excvalue, exctb):
198 self.close()
196 self.close()
199
197
200 def callcommand(self, command, args):
198 def callcommand(self, command, args):
201 if self._sent:
199 if self._sent:
202 raise error.ProgrammingError('callcommand() cannot be used after '
200 raise error.ProgrammingError('callcommand() cannot be used after '
203 'sendcommands()')
201 'sendcommands()')
204
202
205 if self._closed:
203 if self._closed:
206 raise error.ProgrammingError('callcommand() cannot be used after '
204 raise error.ProgrammingError('callcommand() cannot be used after '
207 'close()')
205 'close()')
208
206
209 # We don't need to support anything fancy. Just call the named
207 # We don't need to support anything fancy. Just call the named
210 # method on the peer and return a resolved future.
208 # method on the peer and return a resolved future.
211 fn = getattr(self._peer, pycompat.sysstr(command))
209 fn = getattr(self._peer, pycompat.sysstr(command))
212
210
213 f = pycompat.futures.Future()
211 f = pycompat.futures.Future()
214
212
215 try:
213 try:
216 result = fn(**pycompat.strkwargs(args))
214 result = fn(**pycompat.strkwargs(args))
217 except Exception:
215 except Exception:
218 pycompat.future_set_exception_info(f, sys.exc_info()[1:])
216 pycompat.future_set_exception_info(f, sys.exc_info()[1:])
219 else:
217 else:
220 f.set_result(result)
218 f.set_result(result)
221
219
222 return f
220 return f
223
221
224 def sendcommands(self):
222 def sendcommands(self):
225 self._sent = True
223 self._sent = True
226
224
227 def close(self):
225 def close(self):
228 self._closed = True
226 self._closed = True
229
227
230 @interfaceutil.implementer(repository.ipeercommands)
228 @interfaceutil.implementer(repository.ipeercommands)
231 class localpeer(repository.peer):
229 class localpeer(repository.peer):
232 '''peer for a local repo; reflects only the most recent API'''
230 '''peer for a local repo; reflects only the most recent API'''
233
231
234 def __init__(self, repo, caps=None):
232 def __init__(self, repo, caps=None):
235 super(localpeer, self).__init__()
233 super(localpeer, self).__init__()
236
234
237 if caps is None:
235 if caps is None:
238 caps = moderncaps.copy()
236 caps = moderncaps.copy()
239 self._repo = repo.filtered('served')
237 self._repo = repo.filtered('served')
240 self.ui = repo.ui
238 self.ui = repo.ui
241 self._caps = repo._restrictcapabilities(caps)
239 self._caps = repo._restrictcapabilities(caps)
242
240
243 # Begin of _basepeer interface.
241 # Begin of _basepeer interface.
244
242
245 def url(self):
243 def url(self):
246 return self._repo.url()
244 return self._repo.url()
247
245
248 def local(self):
246 def local(self):
249 return self._repo
247 return self._repo
250
248
251 def peer(self):
249 def peer(self):
252 return self
250 return self
253
251
254 def canpush(self):
252 def canpush(self):
255 return True
253 return True
256
254
257 def close(self):
255 def close(self):
258 self._repo.close()
256 self._repo.close()
259
257
260 # End of _basepeer interface.
258 # End of _basepeer interface.
261
259
262 # Begin of _basewirecommands interface.
260 # Begin of _basewirecommands interface.
263
261
264 def branchmap(self):
262 def branchmap(self):
265 return self._repo.branchmap()
263 return self._repo.branchmap()
266
264
267 def capabilities(self):
265 def capabilities(self):
268 return self._caps
266 return self._caps
269
267
270 def clonebundles(self):
268 def clonebundles(self):
271 return self._repo.tryread('clonebundles.manifest')
269 return self._repo.tryread('clonebundles.manifest')
272
270
273 def debugwireargs(self, one, two, three=None, four=None, five=None):
271 def debugwireargs(self, one, two, three=None, four=None, five=None):
274 """Used to test argument passing over the wire"""
272 """Used to test argument passing over the wire"""
275 return "%s %s %s %s %s" % (one, two, pycompat.bytestr(three),
273 return "%s %s %s %s %s" % (one, two, pycompat.bytestr(three),
276 pycompat.bytestr(four),
274 pycompat.bytestr(four),
277 pycompat.bytestr(five))
275 pycompat.bytestr(five))
278
276
279 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
277 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
280 **kwargs):
278 **kwargs):
281 chunks = exchange.getbundlechunks(self._repo, source, heads=heads,
279 chunks = exchange.getbundlechunks(self._repo, source, heads=heads,
282 common=common, bundlecaps=bundlecaps,
280 common=common, bundlecaps=bundlecaps,
283 **kwargs)[1]
281 **kwargs)[1]
284 cb = util.chunkbuffer(chunks)
282 cb = util.chunkbuffer(chunks)
285
283
286 if exchange.bundle2requested(bundlecaps):
284 if exchange.bundle2requested(bundlecaps):
287 # When requesting a bundle2, getbundle returns a stream to make the
285 # When requesting a bundle2, getbundle returns a stream to make the
288 # wire level function happier. We need to build a proper object
286 # wire level function happier. We need to build a proper object
289 # from it in local peer.
287 # from it in local peer.
290 return bundle2.getunbundler(self.ui, cb)
288 return bundle2.getunbundler(self.ui, cb)
291 else:
289 else:
292 return changegroup.getunbundler('01', cb, None)
290 return changegroup.getunbundler('01', cb, None)
293
291
294 def heads(self):
292 def heads(self):
295 return self._repo.heads()
293 return self._repo.heads()
296
294
297 def known(self, nodes):
295 def known(self, nodes):
298 return self._repo.known(nodes)
296 return self._repo.known(nodes)
299
297
300 def listkeys(self, namespace):
298 def listkeys(self, namespace):
301 return self._repo.listkeys(namespace)
299 return self._repo.listkeys(namespace)
302
300
303 def lookup(self, key):
301 def lookup(self, key):
304 return self._repo.lookup(key)
302 return self._repo.lookup(key)
305
303
306 def pushkey(self, namespace, key, old, new):
304 def pushkey(self, namespace, key, old, new):
307 return self._repo.pushkey(namespace, key, old, new)
305 return self._repo.pushkey(namespace, key, old, new)
308
306
309 def stream_out(self):
307 def stream_out(self):
310 raise error.Abort(_('cannot perform stream clone against local '
308 raise error.Abort(_('cannot perform stream clone against local '
311 'peer'))
309 'peer'))
312
310
313 def unbundle(self, bundle, heads, url):
311 def unbundle(self, bundle, heads, url):
314 """apply a bundle on a repo
312 """apply a bundle on a repo
315
313
316 This function handles the repo locking itself."""
314 This function handles the repo locking itself."""
317 try:
315 try:
318 try:
316 try:
319 bundle = exchange.readbundle(self.ui, bundle, None)
317 bundle = exchange.readbundle(self.ui, bundle, None)
320 ret = exchange.unbundle(self._repo, bundle, heads, 'push', url)
318 ret = exchange.unbundle(self._repo, bundle, heads, 'push', url)
321 if util.safehasattr(ret, 'getchunks'):
319 if util.safehasattr(ret, 'getchunks'):
322 # This is a bundle20 object, turn it into an unbundler.
320 # This is a bundle20 object, turn it into an unbundler.
323 # This little dance should be dropped eventually when the
321 # This little dance should be dropped eventually when the
324 # API is finally improved.
322 # API is finally improved.
325 stream = util.chunkbuffer(ret.getchunks())
323 stream = util.chunkbuffer(ret.getchunks())
326 ret = bundle2.getunbundler(self.ui, stream)
324 ret = bundle2.getunbundler(self.ui, stream)
327 return ret
325 return ret
328 except Exception as exc:
326 except Exception as exc:
329 # If the exception contains output salvaged from a bundle2
327 # If the exception contains output salvaged from a bundle2
330 # reply, we need to make sure it is printed before continuing
328 # reply, we need to make sure it is printed before continuing
331 # to fail. So we build a bundle2 with such output and consume
329 # to fail. So we build a bundle2 with such output and consume
332 # it directly.
330 # it directly.
333 #
331 #
334 # This is not very elegant but allows a "simple" solution for
332 # This is not very elegant but allows a "simple" solution for
335 # issue4594
333 # issue4594
336 output = getattr(exc, '_bundle2salvagedoutput', ())
334 output = getattr(exc, '_bundle2salvagedoutput', ())
337 if output:
335 if output:
338 bundler = bundle2.bundle20(self._repo.ui)
336 bundler = bundle2.bundle20(self._repo.ui)
339 for out in output:
337 for out in output:
340 bundler.addpart(out)
338 bundler.addpart(out)
341 stream = util.chunkbuffer(bundler.getchunks())
339 stream = util.chunkbuffer(bundler.getchunks())
342 b = bundle2.getunbundler(self.ui, stream)
340 b = bundle2.getunbundler(self.ui, stream)
343 bundle2.processbundle(self._repo, b)
341 bundle2.processbundle(self._repo, b)
344 raise
342 raise
345 except error.PushRaced as exc:
343 except error.PushRaced as exc:
346 raise error.ResponseError(_('push failed:'),
344 raise error.ResponseError(_('push failed:'),
347 stringutil.forcebytestr(exc))
345 stringutil.forcebytestr(exc))
348
346
349 # End of _basewirecommands interface.
347 # End of _basewirecommands interface.
350
348
351 # Begin of peer interface.
349 # Begin of peer interface.
352
350
353 def commandexecutor(self):
351 def commandexecutor(self):
354 return localcommandexecutor(self)
352 return localcommandexecutor(self)
355
353
356 # End of peer interface.
354 # End of peer interface.
357
355
358 @interfaceutil.implementer(repository.ipeerlegacycommands)
356 @interfaceutil.implementer(repository.ipeerlegacycommands)
359 class locallegacypeer(localpeer):
357 class locallegacypeer(localpeer):
360 '''peer extension which implements legacy methods too; used for tests with
358 '''peer extension which implements legacy methods too; used for tests with
361 restricted capabilities'''
359 restricted capabilities'''
362
360
363 def __init__(self, repo):
361 def __init__(self, repo):
364 super(locallegacypeer, self).__init__(repo, caps=legacycaps)
362 super(locallegacypeer, self).__init__(repo, caps=legacycaps)
365
363
366 # Begin of baselegacywirecommands interface.
364 # Begin of baselegacywirecommands interface.
367
365
368 def between(self, pairs):
366 def between(self, pairs):
369 return self._repo.between(pairs)
367 return self._repo.between(pairs)
370
368
371 def branches(self, nodes):
369 def branches(self, nodes):
372 return self._repo.branches(nodes)
370 return self._repo.branches(nodes)
373
371
374 def changegroup(self, nodes, source):
372 def changegroup(self, nodes, source):
375 outgoing = discovery.outgoing(self._repo, missingroots=nodes,
373 outgoing = discovery.outgoing(self._repo, missingroots=nodes,
376 missingheads=self._repo.heads())
374 missingheads=self._repo.heads())
377 return changegroup.makechangegroup(self._repo, outgoing, '01', source)
375 return changegroup.makechangegroup(self._repo, outgoing, '01', source)
378
376
379 def changegroupsubset(self, bases, heads, source):
377 def changegroupsubset(self, bases, heads, source):
380 outgoing = discovery.outgoing(self._repo, missingroots=bases,
378 outgoing = discovery.outgoing(self._repo, missingroots=bases,
381 missingheads=heads)
379 missingheads=heads)
382 return changegroup.makechangegroup(self._repo, outgoing, '01', source)
380 return changegroup.makechangegroup(self._repo, outgoing, '01', source)
383
381
384 # End of baselegacywirecommands interface.
382 # End of baselegacywirecommands interface.
385
383
386 # Increment the sub-version when the revlog v2 format changes to lock out old
384 # Increment the sub-version when the revlog v2 format changes to lock out old
387 # clients.
385 # clients.
388 REVLOGV2_REQUIREMENT = 'exp-revlogv2.1'
386 REVLOGV2_REQUIREMENT = 'exp-revlogv2.1'
389
387
390 # A repository with the sparserevlog feature will have delta chains that
388 # A repository with the sparserevlog feature will have delta chains that
391 # can spread over a larger span. Sparse reading cuts these large spans into
389 # can spread over a larger span. Sparse reading cuts these large spans into
392 # pieces, so that each piece isn't too big.
390 # pieces, so that each piece isn't too big.
393 # Without the sparserevlog capability, reading from the repository could use
391 # Without the sparserevlog capability, reading from the repository could use
394 # huge amounts of memory, because the whole span would be read at once,
392 # huge amounts of memory, because the whole span would be read at once,
395 # including all the intermediate revisions that aren't pertinent for the chain.
393 # including all the intermediate revisions that aren't pertinent for the chain.
396 # This is why once a repository has enabled sparse-read, it becomes required.
394 # This is why once a repository has enabled sparse-read, it becomes required.
397 SPARSEREVLOG_REQUIREMENT = 'sparserevlog'
395 SPARSEREVLOG_REQUIREMENT = 'sparserevlog'
398
396
399 # Functions receiving (ui, features) that extensions can register to impact
397 # Functions receiving (ui, features) that extensions can register to impact
400 # the ability to load repositories with custom requirements. Only
398 # the ability to load repositories with custom requirements. Only
401 # functions defined in loaded extensions are called.
399 # functions defined in loaded extensions are called.
402 #
400 #
403 # The function receives a set of requirement strings that the repository
401 # The function receives a set of requirement strings that the repository
404 # is capable of opening. Functions will typically add elements to the
402 # is capable of opening. Functions will typically add elements to the
405 # set to reflect that the extension knows how to handle that requirements.
403 # set to reflect that the extension knows how to handle that requirements.
406 featuresetupfuncs = set()
404 featuresetupfuncs = set()
407
405
408 def makelocalrepository(baseui, path, intents=None):
406 def makelocalrepository(baseui, path, intents=None):
409 """Create a local repository object.
407 """Create a local repository object.
410
408
411 Given arguments needed to construct a local repository, this function
409 Given arguments needed to construct a local repository, this function
412 performs various early repository loading functionality (such as
410 performs various early repository loading functionality (such as
413 reading the ``.hg/requires`` and ``.hg/hgrc`` files), validates that
411 reading the ``.hg/requires`` and ``.hg/hgrc`` files), validates that
414 the repository can be opened, derives a type suitable for representing
412 the repository can be opened, derives a type suitable for representing
415 that repository, and returns an instance of it.
413 that repository, and returns an instance of it.
416
414
417 The returned object conforms to the ``repository.completelocalrepository``
415 The returned object conforms to the ``repository.completelocalrepository``
418 interface.
416 interface.
419
417
420 The repository type is derived by calling a series of factory functions
418 The repository type is derived by calling a series of factory functions
421 for each aspect/interface of the final repository. These are defined by
419 for each aspect/interface of the final repository. These are defined by
422 ``REPO_INTERFACES``.
420 ``REPO_INTERFACES``.
423
421
424 Each factory function is called to produce a type implementing a specific
422 Each factory function is called to produce a type implementing a specific
425 interface. The cumulative list of returned types will be combined into a
423 interface. The cumulative list of returned types will be combined into a
426 new type and that type will be instantiated to represent the local
424 new type and that type will be instantiated to represent the local
427 repository.
425 repository.
428
426
429 The factory functions each receive various state that may be consulted
427 The factory functions each receive various state that may be consulted
430 as part of deriving a type.
428 as part of deriving a type.
431
429
432 Extensions should wrap these factory functions to customize repository type
430 Extensions should wrap these factory functions to customize repository type
433 creation. Note that an extension's wrapped function may be called even if
431 creation. Note that an extension's wrapped function may be called even if
434 that extension is not loaded for the repo being constructed. Extensions
432 that extension is not loaded for the repo being constructed. Extensions
435 should check if their ``__name__`` appears in the
433 should check if their ``__name__`` appears in the
436 ``extensionmodulenames`` set passed to the factory function and no-op if
434 ``extensionmodulenames`` set passed to the factory function and no-op if
437 not.
435 not.
438 """
436 """
439 ui = baseui.copy()
437 ui = baseui.copy()
440 # Prevent copying repo configuration.
438 # Prevent copying repo configuration.
441 ui.copy = baseui.copy
439 ui.copy = baseui.copy
442
440
443 # Working directory VFS rooted at repository root.
441 # Working directory VFS rooted at repository root.
444 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
442 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
445
443
446 # Main VFS for .hg/ directory.
444 # Main VFS for .hg/ directory.
447 hgpath = wdirvfs.join(b'.hg')
445 hgpath = wdirvfs.join(b'.hg')
448 hgvfs = vfsmod.vfs(hgpath, cacheaudited=True)
446 hgvfs = vfsmod.vfs(hgpath, cacheaudited=True)
449
447
450 # The .hg/ path should exist and should be a directory. All other
448 # The .hg/ path should exist and should be a directory. All other
451 # cases are errors.
449 # cases are errors.
452 if not hgvfs.isdir():
450 if not hgvfs.isdir():
453 try:
451 try:
454 hgvfs.stat()
452 hgvfs.stat()
455 except OSError as e:
453 except OSError as e:
456 if e.errno != errno.ENOENT:
454 if e.errno != errno.ENOENT:
457 raise
455 raise
458
456
459 raise error.RepoError(_(b'repository %s not found') % path)
457 raise error.RepoError(_(b'repository %s not found') % path)
460
458
461 # .hg/requires file contains a newline-delimited list of
459 # .hg/requires file contains a newline-delimited list of
462 # features/capabilities the opener (us) must have in order to use
460 # features/capabilities the opener (us) must have in order to use
463 # the repository. This file was introduced in Mercurial 0.9.2,
461 # the repository. This file was introduced in Mercurial 0.9.2,
464 # which means very old repositories may not have one. We assume
462 # which means very old repositories may not have one. We assume
465 # a missing file translates to no requirements.
463 # a missing file translates to no requirements.
466 try:
464 try:
467 requirements = set(hgvfs.read(b'requires').splitlines())
465 requirements = set(hgvfs.read(b'requires').splitlines())
468 except IOError as e:
466 except IOError as e:
469 if e.errno != errno.ENOENT:
467 if e.errno != errno.ENOENT:
470 raise
468 raise
471 requirements = set()
469 requirements = set()
472
470
473 # The .hg/hgrc file may load extensions or contain config options
471 # The .hg/hgrc file may load extensions or contain config options
474 # that influence repository construction. Attempt to load it and
472 # that influence repository construction. Attempt to load it and
475 # process any new extensions that it may have pulled in.
473 # process any new extensions that it may have pulled in.
476 if loadhgrc(ui, wdirvfs, hgvfs, requirements):
474 if loadhgrc(ui, wdirvfs, hgvfs, requirements):
477 afterhgrcload(ui, wdirvfs, hgvfs, requirements)
475 afterhgrcload(ui, wdirvfs, hgvfs, requirements)
478 extensions.loadall(ui)
476 extensions.loadall(ui)
479 extensions.populateui(ui)
477 extensions.populateui(ui)
480
478
481 # Set of module names of extensions loaded for this repository.
479 # Set of module names of extensions loaded for this repository.
482 extensionmodulenames = {m.__name__ for n, m in extensions.extensions(ui)}
480 extensionmodulenames = {m.__name__ for n, m in extensions.extensions(ui)}
483
481
484 supportedrequirements = gathersupportedrequirements(ui)
482 supportedrequirements = gathersupportedrequirements(ui)
485
483
486 # We first validate the requirements are known.
484 # We first validate the requirements are known.
487 ensurerequirementsrecognized(requirements, supportedrequirements)
485 ensurerequirementsrecognized(requirements, supportedrequirements)
488
486
489 # Then we validate that the known set is reasonable to use together.
487 # Then we validate that the known set is reasonable to use together.
490 ensurerequirementscompatible(ui, requirements)
488 ensurerequirementscompatible(ui, requirements)
491
489
492 # TODO there are unhandled edge cases related to opening repositories with
490 # TODO there are unhandled edge cases related to opening repositories with
493 # shared storage. If storage is shared, we should also test for requirements
491 # shared storage. If storage is shared, we should also test for requirements
494 # compatibility in the pointed-to repo. This entails loading the .hg/hgrc in
492 # compatibility in the pointed-to repo. This entails loading the .hg/hgrc in
495 # that repo, as that repo may load extensions needed to open it. This is a
493 # that repo, as that repo may load extensions needed to open it. This is a
496 # bit complicated because we don't want the other hgrc to overwrite settings
494 # bit complicated because we don't want the other hgrc to overwrite settings
497 # in this hgrc.
495 # in this hgrc.
498 #
496 #
499 # This bug is somewhat mitigated by the fact that we copy the .hg/requires
497 # This bug is somewhat mitigated by the fact that we copy the .hg/requires
500 # file when sharing repos. But if a requirement is added after the share is
498 # file when sharing repos. But if a requirement is added after the share is
501 # performed, thereby introducing a new requirement for the opener, we may
499 # performed, thereby introducing a new requirement for the opener, we may
502 # will not see that and could encounter a run-time error interacting with
500 # will not see that and could encounter a run-time error interacting with
503 # that shared store since it has an unknown-to-us requirement.
501 # that shared store since it has an unknown-to-us requirement.
504
502
505 # At this point, we know we should be capable of opening the repository.
503 # At this point, we know we should be capable of opening the repository.
506 # Now get on with doing that.
504 # Now get on with doing that.
507
505
508 features = set()
506 features = set()
509
507
510 # The "store" part of the repository holds versioned data. How it is
508 # The "store" part of the repository holds versioned data. How it is
511 # accessed is determined by various requirements. The ``shared`` or
509 # accessed is determined by various requirements. The ``shared`` or
512 # ``relshared`` requirements indicate the store lives in the path contained
510 # ``relshared`` requirements indicate the store lives in the path contained
513 # in the ``.hg/sharedpath`` file. This is an absolute path for
511 # in the ``.hg/sharedpath`` file. This is an absolute path for
514 # ``shared`` and relative to ``.hg/`` for ``relshared``.
512 # ``shared`` and relative to ``.hg/`` for ``relshared``.
515 if b'shared' in requirements or b'relshared' in requirements:
513 if b'shared' in requirements or b'relshared' in requirements:
516 sharedpath = hgvfs.read(b'sharedpath').rstrip(b'\n')
514 sharedpath = hgvfs.read(b'sharedpath').rstrip(b'\n')
517 if b'relshared' in requirements:
515 if b'relshared' in requirements:
518 sharedpath = hgvfs.join(sharedpath)
516 sharedpath = hgvfs.join(sharedpath)
519
517
520 sharedvfs = vfsmod.vfs(sharedpath, realpath=True)
518 sharedvfs = vfsmod.vfs(sharedpath, realpath=True)
521
519
522 if not sharedvfs.exists():
520 if not sharedvfs.exists():
523 raise error.RepoError(_(b'.hg/sharedpath points to nonexistent '
521 raise error.RepoError(_(b'.hg/sharedpath points to nonexistent '
524 b'directory %s') % sharedvfs.base)
522 b'directory %s') % sharedvfs.base)
525
523
526 features.add(repository.REPO_FEATURE_SHARED_STORAGE)
524 features.add(repository.REPO_FEATURE_SHARED_STORAGE)
527
525
528 storebasepath = sharedvfs.base
526 storebasepath = sharedvfs.base
529 cachepath = sharedvfs.join(b'cache')
527 cachepath = sharedvfs.join(b'cache')
530 else:
528 else:
531 storebasepath = hgvfs.base
529 storebasepath = hgvfs.base
532 cachepath = hgvfs.join(b'cache')
530 cachepath = hgvfs.join(b'cache')
533 wcachepath = hgvfs.join(b'wcache')
531 wcachepath = hgvfs.join(b'wcache')
534
532
535
533
536 # The store has changed over time and the exact layout is dictated by
534 # The store has changed over time and the exact layout is dictated by
537 # requirements. The store interface abstracts differences across all
535 # requirements. The store interface abstracts differences across all
538 # of them.
536 # of them.
539 store = makestore(requirements, storebasepath,
537 store = makestore(requirements, storebasepath,
540 lambda base: vfsmod.vfs(base, cacheaudited=True))
538 lambda base: vfsmod.vfs(base, cacheaudited=True))
541 hgvfs.createmode = store.createmode
539 hgvfs.createmode = store.createmode
542
540
543 storevfs = store.vfs
541 storevfs = store.vfs
544 storevfs.options = resolvestorevfsoptions(ui, requirements, features)
542 storevfs.options = resolvestorevfsoptions(ui, requirements, features)
545
543
546 # The cache vfs is used to manage cache files.
544 # The cache vfs is used to manage cache files.
547 cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
545 cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
548 cachevfs.createmode = store.createmode
546 cachevfs.createmode = store.createmode
549 # The cache vfs is used to manage cache files related to the working copy
547 # The cache vfs is used to manage cache files related to the working copy
550 wcachevfs = vfsmod.vfs(wcachepath, cacheaudited=True)
548 wcachevfs = vfsmod.vfs(wcachepath, cacheaudited=True)
551 wcachevfs.createmode = store.createmode
549 wcachevfs.createmode = store.createmode
552
550
553 # Now resolve the type for the repository object. We do this by repeatedly
551 # Now resolve the type for the repository object. We do this by repeatedly
554 # calling a factory function to produces types for specific aspects of the
552 # calling a factory function to produces types for specific aspects of the
555 # repo's operation. The aggregate returned types are used as base classes
553 # repo's operation. The aggregate returned types are used as base classes
556 # for a dynamically-derived type, which will represent our new repository.
554 # for a dynamically-derived type, which will represent our new repository.
557
555
558 bases = []
556 bases = []
559 extrastate = {}
557 extrastate = {}
560
558
561 for iface, fn in REPO_INTERFACES:
559 for iface, fn in REPO_INTERFACES:
562 # We pass all potentially useful state to give extensions tons of
560 # We pass all potentially useful state to give extensions tons of
563 # flexibility.
561 # flexibility.
564 typ = fn()(ui=ui,
562 typ = fn()(ui=ui,
565 intents=intents,
563 intents=intents,
566 requirements=requirements,
564 requirements=requirements,
567 features=features,
565 features=features,
568 wdirvfs=wdirvfs,
566 wdirvfs=wdirvfs,
569 hgvfs=hgvfs,
567 hgvfs=hgvfs,
570 store=store,
568 store=store,
571 storevfs=storevfs,
569 storevfs=storevfs,
572 storeoptions=storevfs.options,
570 storeoptions=storevfs.options,
573 cachevfs=cachevfs,
571 cachevfs=cachevfs,
574 wcachevfs=wcachevfs,
572 wcachevfs=wcachevfs,
575 extensionmodulenames=extensionmodulenames,
573 extensionmodulenames=extensionmodulenames,
576 extrastate=extrastate,
574 extrastate=extrastate,
577 baseclasses=bases)
575 baseclasses=bases)
578
576
579 if not isinstance(typ, type):
577 if not isinstance(typ, type):
580 raise error.ProgrammingError('unable to construct type for %s' %
578 raise error.ProgrammingError('unable to construct type for %s' %
581 iface)
579 iface)
582
580
583 bases.append(typ)
581 bases.append(typ)
584
582
585 # type() allows you to use characters in type names that wouldn't be
583 # type() allows you to use characters in type names that wouldn't be
586 # recognized as Python symbols in source code. We abuse that to add
584 # recognized as Python symbols in source code. We abuse that to add
587 # rich information about our constructed repo.
585 # rich information about our constructed repo.
588 name = pycompat.sysstr(b'derivedrepo:%s<%s>' % (
586 name = pycompat.sysstr(b'derivedrepo:%s<%s>' % (
589 wdirvfs.base,
587 wdirvfs.base,
590 b','.join(sorted(requirements))))
588 b','.join(sorted(requirements))))
591
589
592 cls = type(name, tuple(bases), {})
590 cls = type(name, tuple(bases), {})
593
591
594 return cls(
592 return cls(
595 baseui=baseui,
593 baseui=baseui,
596 ui=ui,
594 ui=ui,
597 origroot=path,
595 origroot=path,
598 wdirvfs=wdirvfs,
596 wdirvfs=wdirvfs,
599 hgvfs=hgvfs,
597 hgvfs=hgvfs,
600 requirements=requirements,
598 requirements=requirements,
601 supportedrequirements=supportedrequirements,
599 supportedrequirements=supportedrequirements,
602 sharedpath=storebasepath,
600 sharedpath=storebasepath,
603 store=store,
601 store=store,
604 cachevfs=cachevfs,
602 cachevfs=cachevfs,
605 wcachevfs=wcachevfs,
603 wcachevfs=wcachevfs,
606 features=features,
604 features=features,
607 intents=intents)
605 intents=intents)
608
606
609 def loadhgrc(ui, wdirvfs, hgvfs, requirements):
607 def loadhgrc(ui, wdirvfs, hgvfs, requirements):
610 """Load hgrc files/content into a ui instance.
608 """Load hgrc files/content into a ui instance.
611
609
612 This is called during repository opening to load any additional
610 This is called during repository opening to load any additional
613 config files or settings relevant to the current repository.
611 config files or settings relevant to the current repository.
614
612
615 Returns a bool indicating whether any additional configs were loaded.
613 Returns a bool indicating whether any additional configs were loaded.
616
614
617 Extensions should monkeypatch this function to modify how per-repo
615 Extensions should monkeypatch this function to modify how per-repo
618 configs are loaded. For example, an extension may wish to pull in
616 configs are loaded. For example, an extension may wish to pull in
619 configs from alternate files or sources.
617 configs from alternate files or sources.
620 """
618 """
621 try:
619 try:
622 ui.readconfig(hgvfs.join(b'hgrc'), root=wdirvfs.base)
620 ui.readconfig(hgvfs.join(b'hgrc'), root=wdirvfs.base)
623 return True
621 return True
624 except IOError:
622 except IOError:
625 return False
623 return False
626
624
627 def afterhgrcload(ui, wdirvfs, hgvfs, requirements):
625 def afterhgrcload(ui, wdirvfs, hgvfs, requirements):
628 """Perform additional actions after .hg/hgrc is loaded.
626 """Perform additional actions after .hg/hgrc is loaded.
629
627
630 This function is called during repository loading immediately after
628 This function is called during repository loading immediately after
631 the .hg/hgrc file is loaded and before per-repo extensions are loaded.
629 the .hg/hgrc file is loaded and before per-repo extensions are loaded.
632
630
633 The function can be used to validate configs, automatically add
631 The function can be used to validate configs, automatically add
634 options (including extensions) based on requirements, etc.
632 options (including extensions) based on requirements, etc.
635 """
633 """
636
634
637 # Map of requirements to list of extensions to load automatically when
635 # Map of requirements to list of extensions to load automatically when
638 # requirement is present.
636 # requirement is present.
639 autoextensions = {
637 autoextensions = {
640 b'largefiles': [b'largefiles'],
638 b'largefiles': [b'largefiles'],
641 b'lfs': [b'lfs'],
639 b'lfs': [b'lfs'],
642 }
640 }
643
641
644 for requirement, names in sorted(autoextensions.items()):
642 for requirement, names in sorted(autoextensions.items()):
645 if requirement not in requirements:
643 if requirement not in requirements:
646 continue
644 continue
647
645
648 for name in names:
646 for name in names:
649 if not ui.hasconfig(b'extensions', name):
647 if not ui.hasconfig(b'extensions', name):
650 ui.setconfig(b'extensions', name, b'', source='autoload')
648 ui.setconfig(b'extensions', name, b'', source='autoload')
651
649
652 def gathersupportedrequirements(ui):
650 def gathersupportedrequirements(ui):
653 """Determine the complete set of recognized requirements."""
651 """Determine the complete set of recognized requirements."""
654 # Start with all requirements supported by this file.
652 # Start with all requirements supported by this file.
655 supported = set(localrepository._basesupported)
653 supported = set(localrepository._basesupported)
656
654
657 # Execute ``featuresetupfuncs`` entries if they belong to an extension
655 # Execute ``featuresetupfuncs`` entries if they belong to an extension
658 # relevant to this ui instance.
656 # relevant to this ui instance.
659 modules = {m.__name__ for n, m in extensions.extensions(ui)}
657 modules = {m.__name__ for n, m in extensions.extensions(ui)}
660
658
661 for fn in featuresetupfuncs:
659 for fn in featuresetupfuncs:
662 if fn.__module__ in modules:
660 if fn.__module__ in modules:
663 fn(ui, supported)
661 fn(ui, supported)
664
662
665 # Add derived requirements from registered compression engines.
663 # Add derived requirements from registered compression engines.
666 for name in util.compengines:
664 for name in util.compengines:
667 engine = util.compengines[name]
665 engine = util.compengines[name]
668 if engine.available() and engine.revlogheader():
666 if engine.available() and engine.revlogheader():
669 supported.add(b'exp-compression-%s' % name)
667 supported.add(b'exp-compression-%s' % name)
670 if engine.name() == 'zstd':
668 if engine.name() == 'zstd':
671 supported.add(b'revlog-compression-zstd')
669 supported.add(b'revlog-compression-zstd')
672
670
673 return supported
671 return supported
674
672
675 def ensurerequirementsrecognized(requirements, supported):
673 def ensurerequirementsrecognized(requirements, supported):
676 """Validate that a set of local requirements is recognized.
674 """Validate that a set of local requirements is recognized.
677
675
678 Receives a set of requirements. Raises an ``error.RepoError`` if there
676 Receives a set of requirements. Raises an ``error.RepoError`` if there
679 exists any requirement in that set that currently loaded code doesn't
677 exists any requirement in that set that currently loaded code doesn't
680 recognize.
678 recognize.
681
679
682 Returns a set of supported requirements.
680 Returns a set of supported requirements.
683 """
681 """
684 missing = set()
682 missing = set()
685
683
686 for requirement in requirements:
684 for requirement in requirements:
687 if requirement in supported:
685 if requirement in supported:
688 continue
686 continue
689
687
690 if not requirement or not requirement[0:1].isalnum():
688 if not requirement or not requirement[0:1].isalnum():
691 raise error.RequirementError(_(b'.hg/requires file is corrupt'))
689 raise error.RequirementError(_(b'.hg/requires file is corrupt'))
692
690
693 missing.add(requirement)
691 missing.add(requirement)
694
692
695 if missing:
693 if missing:
696 raise error.RequirementError(
694 raise error.RequirementError(
697 _(b'repository requires features unknown to this Mercurial: %s') %
695 _(b'repository requires features unknown to this Mercurial: %s') %
698 b' '.join(sorted(missing)),
696 b' '.join(sorted(missing)),
699 hint=_(b'see https://mercurial-scm.org/wiki/MissingRequirement '
697 hint=_(b'see https://mercurial-scm.org/wiki/MissingRequirement '
700 b'for more information'))
698 b'for more information'))
701
699
702 def ensurerequirementscompatible(ui, requirements):
700 def ensurerequirementscompatible(ui, requirements):
703 """Validates that a set of recognized requirements is mutually compatible.
701 """Validates that a set of recognized requirements is mutually compatible.
704
702
705 Some requirements may not be compatible with others or require
703 Some requirements may not be compatible with others or require
706 config options that aren't enabled. This function is called during
704 config options that aren't enabled. This function is called during
707 repository opening to ensure that the set of requirements needed
705 repository opening to ensure that the set of requirements needed
708 to open a repository is sane and compatible with config options.
706 to open a repository is sane and compatible with config options.
709
707
710 Extensions can monkeypatch this function to perform additional
708 Extensions can monkeypatch this function to perform additional
711 checking.
709 checking.
712
710
713 ``error.RepoError`` should be raised on failure.
711 ``error.RepoError`` should be raised on failure.
714 """
712 """
715 if b'exp-sparse' in requirements and not sparse.enabled:
713 if b'exp-sparse' in requirements and not sparse.enabled:
716 raise error.RepoError(_(b'repository is using sparse feature but '
714 raise error.RepoError(_(b'repository is using sparse feature but '
717 b'sparse is not enabled; enable the '
715 b'sparse is not enabled; enable the '
718 b'"sparse" extensions to access'))
716 b'"sparse" extensions to access'))
719
717
720 def makestore(requirements, path, vfstype):
718 def makestore(requirements, path, vfstype):
721 """Construct a storage object for a repository."""
719 """Construct a storage object for a repository."""
722 if b'store' in requirements:
720 if b'store' in requirements:
723 if b'fncache' in requirements:
721 if b'fncache' in requirements:
724 return storemod.fncachestore(path, vfstype,
722 return storemod.fncachestore(path, vfstype,
725 b'dotencode' in requirements)
723 b'dotencode' in requirements)
726
724
727 return storemod.encodedstore(path, vfstype)
725 return storemod.encodedstore(path, vfstype)
728
726
729 return storemod.basicstore(path, vfstype)
727 return storemod.basicstore(path, vfstype)
730
728
731 def resolvestorevfsoptions(ui, requirements, features):
729 def resolvestorevfsoptions(ui, requirements, features):
732 """Resolve the options to pass to the store vfs opener.
730 """Resolve the options to pass to the store vfs opener.
733
731
734 The returned dict is used to influence behavior of the storage layer.
732 The returned dict is used to influence behavior of the storage layer.
735 """
733 """
736 options = {}
734 options = {}
737
735
738 if b'treemanifest' in requirements:
736 if b'treemanifest' in requirements:
739 options[b'treemanifest'] = True
737 options[b'treemanifest'] = True
740
738
741 # experimental config: format.manifestcachesize
739 # experimental config: format.manifestcachesize
742 manifestcachesize = ui.configint(b'format', b'manifestcachesize')
740 manifestcachesize = ui.configint(b'format', b'manifestcachesize')
743 if manifestcachesize is not None:
741 if manifestcachesize is not None:
744 options[b'manifestcachesize'] = manifestcachesize
742 options[b'manifestcachesize'] = manifestcachesize
745
743
746 # In the absence of another requirement superseding a revlog-related
744 # In the absence of another requirement superseding a revlog-related
747 # requirement, we have to assume the repo is using revlog version 0.
745 # requirement, we have to assume the repo is using revlog version 0.
748 # This revlog format is super old and we don't bother trying to parse
746 # This revlog format is super old and we don't bother trying to parse
749 # opener options for it because those options wouldn't do anything
747 # opener options for it because those options wouldn't do anything
750 # meaningful on such old repos.
748 # meaningful on such old repos.
751 if b'revlogv1' in requirements or REVLOGV2_REQUIREMENT in requirements:
749 if b'revlogv1' in requirements or REVLOGV2_REQUIREMENT in requirements:
752 options.update(resolverevlogstorevfsoptions(ui, requirements, features))
750 options.update(resolverevlogstorevfsoptions(ui, requirements, features))
753
751
754 return options
752 return options
755
753
756 def resolverevlogstorevfsoptions(ui, requirements, features):
754 def resolverevlogstorevfsoptions(ui, requirements, features):
757 """Resolve opener options specific to revlogs."""
755 """Resolve opener options specific to revlogs."""
758
756
759 options = {}
757 options = {}
760 options[b'flagprocessors'] = {}
758 options[b'flagprocessors'] = {}
761
759
762 if b'revlogv1' in requirements:
760 if b'revlogv1' in requirements:
763 options[b'revlogv1'] = True
761 options[b'revlogv1'] = True
764 if REVLOGV2_REQUIREMENT in requirements:
762 if REVLOGV2_REQUIREMENT in requirements:
765 options[b'revlogv2'] = True
763 options[b'revlogv2'] = True
766
764
767 if b'generaldelta' in requirements:
765 if b'generaldelta' in requirements:
768 options[b'generaldelta'] = True
766 options[b'generaldelta'] = True
769
767
770 # experimental config: format.chunkcachesize
768 # experimental config: format.chunkcachesize
771 chunkcachesize = ui.configint(b'format', b'chunkcachesize')
769 chunkcachesize = ui.configint(b'format', b'chunkcachesize')
772 if chunkcachesize is not None:
770 if chunkcachesize is not None:
773 options[b'chunkcachesize'] = chunkcachesize
771 options[b'chunkcachesize'] = chunkcachesize
774
772
775 deltabothparents = ui.configbool(b'storage',
773 deltabothparents = ui.configbool(b'storage',
776 b'revlog.optimize-delta-parent-choice')
774 b'revlog.optimize-delta-parent-choice')
777 options[b'deltabothparents'] = deltabothparents
775 options[b'deltabothparents'] = deltabothparents
778
776
779 lazydelta = ui.configbool(b'storage', b'revlog.reuse-external-delta')
777 lazydelta = ui.configbool(b'storage', b'revlog.reuse-external-delta')
780 lazydeltabase = False
778 lazydeltabase = False
781 if lazydelta:
779 if lazydelta:
782 lazydeltabase = ui.configbool(b'storage',
780 lazydeltabase = ui.configbool(b'storage',
783 b'revlog.reuse-external-delta-parent')
781 b'revlog.reuse-external-delta-parent')
784 if lazydeltabase is None:
782 if lazydeltabase is None:
785 lazydeltabase = not scmutil.gddeltaconfig(ui)
783 lazydeltabase = not scmutil.gddeltaconfig(ui)
786 options[b'lazydelta'] = lazydelta
784 options[b'lazydelta'] = lazydelta
787 options[b'lazydeltabase'] = lazydeltabase
785 options[b'lazydeltabase'] = lazydeltabase
788
786
789 chainspan = ui.configbytes(b'experimental', b'maxdeltachainspan')
787 chainspan = ui.configbytes(b'experimental', b'maxdeltachainspan')
790 if 0 <= chainspan:
788 if 0 <= chainspan:
791 options[b'maxdeltachainspan'] = chainspan
789 options[b'maxdeltachainspan'] = chainspan
792
790
793 mmapindexthreshold = ui.configbytes(b'experimental',
791 mmapindexthreshold = ui.configbytes(b'experimental',
794 b'mmapindexthreshold')
792 b'mmapindexthreshold')
795 if mmapindexthreshold is not None:
793 if mmapindexthreshold is not None:
796 options[b'mmapindexthreshold'] = mmapindexthreshold
794 options[b'mmapindexthreshold'] = mmapindexthreshold
797
795
798 withsparseread = ui.configbool(b'experimental', b'sparse-read')
796 withsparseread = ui.configbool(b'experimental', b'sparse-read')
799 srdensitythres = float(ui.config(b'experimental',
797 srdensitythres = float(ui.config(b'experimental',
800 b'sparse-read.density-threshold'))
798 b'sparse-read.density-threshold'))
801 srmingapsize = ui.configbytes(b'experimental',
799 srmingapsize = ui.configbytes(b'experimental',
802 b'sparse-read.min-gap-size')
800 b'sparse-read.min-gap-size')
803 options[b'with-sparse-read'] = withsparseread
801 options[b'with-sparse-read'] = withsparseread
804 options[b'sparse-read-density-threshold'] = srdensitythres
802 options[b'sparse-read-density-threshold'] = srdensitythres
805 options[b'sparse-read-min-gap-size'] = srmingapsize
803 options[b'sparse-read-min-gap-size'] = srmingapsize
806
804
807 sparserevlog = SPARSEREVLOG_REQUIREMENT in requirements
805 sparserevlog = SPARSEREVLOG_REQUIREMENT in requirements
808 options[b'sparse-revlog'] = sparserevlog
806 options[b'sparse-revlog'] = sparserevlog
809 if sparserevlog:
807 if sparserevlog:
810 options[b'generaldelta'] = True
808 options[b'generaldelta'] = True
811
809
812 maxchainlen = None
810 maxchainlen = None
813 if sparserevlog:
811 if sparserevlog:
814 maxchainlen = revlogconst.SPARSE_REVLOG_MAX_CHAIN_LENGTH
812 maxchainlen = revlogconst.SPARSE_REVLOG_MAX_CHAIN_LENGTH
815 # experimental config: format.maxchainlen
813 # experimental config: format.maxchainlen
816 maxchainlen = ui.configint(b'format', b'maxchainlen', maxchainlen)
814 maxchainlen = ui.configint(b'format', b'maxchainlen', maxchainlen)
817 if maxchainlen is not None:
815 if maxchainlen is not None:
818 options[b'maxchainlen'] = maxchainlen
816 options[b'maxchainlen'] = maxchainlen
819
817
820 for r in requirements:
818 for r in requirements:
821 # we allow multiple compression engine requirement to co-exist because
819 # we allow multiple compression engine requirement to co-exist because
822 # strickly speaking, revlog seems to support mixed compression style.
820 # strickly speaking, revlog seems to support mixed compression style.
823 #
821 #
824 # The compression used for new entries will be "the last one"
822 # The compression used for new entries will be "the last one"
825 prefix = r.startswith
823 prefix = r.startswith
826 if prefix('revlog-compression-') or prefix('exp-compression-'):
824 if prefix('revlog-compression-') or prefix('exp-compression-'):
827 options[b'compengine'] = r.split('-', 2)[2]
825 options[b'compengine'] = r.split('-', 2)[2]
828
826
829 options[b'zlib.level'] = ui.configint(b'storage', b'revlog.zlib.level')
827 options[b'zlib.level'] = ui.configint(b'storage', b'revlog.zlib.level')
830 if options[b'zlib.level'] is not None:
828 if options[b'zlib.level'] is not None:
831 if not (0 <= options[b'zlib.level'] <= 9):
829 if not (0 <= options[b'zlib.level'] <= 9):
832 msg = _('invalid value for `storage.revlog.zlib.level` config: %d')
830 msg = _('invalid value for `storage.revlog.zlib.level` config: %d')
833 raise error.Abort(msg % options[b'zlib.level'])
831 raise error.Abort(msg % options[b'zlib.level'])
834 options[b'zstd.level'] = ui.configint(b'storage', b'revlog.zstd.level')
832 options[b'zstd.level'] = ui.configint(b'storage', b'revlog.zstd.level')
835 if options[b'zstd.level'] is not None:
833 if options[b'zstd.level'] is not None:
836 if not (0 <= options[b'zstd.level'] <= 22):
834 if not (0 <= options[b'zstd.level'] <= 22):
837 msg = _('invalid value for `storage.revlog.zstd.level` config: %d')
835 msg = _('invalid value for `storage.revlog.zstd.level` config: %d')
838 raise error.Abort(msg % options[b'zstd.level'])
836 raise error.Abort(msg % options[b'zstd.level'])
839
837
840 if repository.NARROW_REQUIREMENT in requirements:
838 if repository.NARROW_REQUIREMENT in requirements:
841 options[b'enableellipsis'] = True
839 options[b'enableellipsis'] = True
842
840
843 return options
841 return options
844
842
845 def makemain(**kwargs):
843 def makemain(**kwargs):
846 """Produce a type conforming to ``ilocalrepositorymain``."""
844 """Produce a type conforming to ``ilocalrepositorymain``."""
847 return localrepository
845 return localrepository
848
846
849 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
847 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
850 class revlogfilestorage(object):
848 class revlogfilestorage(object):
851 """File storage when using revlogs."""
849 """File storage when using revlogs."""
852
850
853 def file(self, path):
851 def file(self, path):
854 if path[0] == b'/':
852 if path[0] == b'/':
855 path = path[1:]
853 path = path[1:]
856
854
857 return filelog.filelog(self.svfs, path)
855 return filelog.filelog(self.svfs, path)
858
856
859 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
857 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
860 class revlognarrowfilestorage(object):
858 class revlognarrowfilestorage(object):
861 """File storage when using revlogs and narrow files."""
859 """File storage when using revlogs and narrow files."""
862
860
863 def file(self, path):
861 def file(self, path):
864 if path[0] == b'/':
862 if path[0] == b'/':
865 path = path[1:]
863 path = path[1:]
866
864
867 return filelog.narrowfilelog(self.svfs, path, self._storenarrowmatch)
865 return filelog.narrowfilelog(self.svfs, path, self._storenarrowmatch)
868
866
869 def makefilestorage(requirements, features, **kwargs):
867 def makefilestorage(requirements, features, **kwargs):
870 """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
868 """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
871 features.add(repository.REPO_FEATURE_REVLOG_FILE_STORAGE)
869 features.add(repository.REPO_FEATURE_REVLOG_FILE_STORAGE)
872 features.add(repository.REPO_FEATURE_STREAM_CLONE)
870 features.add(repository.REPO_FEATURE_STREAM_CLONE)
873
871
874 if repository.NARROW_REQUIREMENT in requirements:
872 if repository.NARROW_REQUIREMENT in requirements:
875 return revlognarrowfilestorage
873 return revlognarrowfilestorage
876 else:
874 else:
877 return revlogfilestorage
875 return revlogfilestorage
878
876
879 # List of repository interfaces and factory functions for them. Each
877 # List of repository interfaces and factory functions for them. Each
880 # will be called in order during ``makelocalrepository()`` to iteratively
878 # will be called in order during ``makelocalrepository()`` to iteratively
881 # derive the final type for a local repository instance. We capture the
879 # derive the final type for a local repository instance. We capture the
882 # function as a lambda so we don't hold a reference and the module-level
880 # function as a lambda so we don't hold a reference and the module-level
883 # functions can be wrapped.
881 # functions can be wrapped.
884 REPO_INTERFACES = [
882 REPO_INTERFACES = [
885 (repository.ilocalrepositorymain, lambda: makemain),
883 (repository.ilocalrepositorymain, lambda: makemain),
886 (repository.ilocalrepositoryfilestorage, lambda: makefilestorage),
884 (repository.ilocalrepositoryfilestorage, lambda: makefilestorage),
887 ]
885 ]
888
886
889 @interfaceutil.implementer(repository.ilocalrepositorymain)
887 @interfaceutil.implementer(repository.ilocalrepositorymain)
890 class localrepository(object):
888 class localrepository(object):
891 """Main class for representing local repositories.
889 """Main class for representing local repositories.
892
890
893 All local repositories are instances of this class.
891 All local repositories are instances of this class.
894
892
895 Constructed on its own, instances of this class are not usable as
893 Constructed on its own, instances of this class are not usable as
896 repository objects. To obtain a usable repository object, call
894 repository objects. To obtain a usable repository object, call
897 ``hg.repository()``, ``localrepo.instance()``, or
895 ``hg.repository()``, ``localrepo.instance()``, or
898 ``localrepo.makelocalrepository()``. The latter is the lowest-level.
896 ``localrepo.makelocalrepository()``. The latter is the lowest-level.
899 ``instance()`` adds support for creating new repositories.
897 ``instance()`` adds support for creating new repositories.
900 ``hg.repository()`` adds more extension integration, including calling
898 ``hg.repository()`` adds more extension integration, including calling
901 ``reposetup()``. Generally speaking, ``hg.repository()`` should be
899 ``reposetup()``. Generally speaking, ``hg.repository()`` should be
902 used.
900 used.
903 """
901 """
904
902
905 # obsolete experimental requirements:
903 # obsolete experimental requirements:
906 # - manifestv2: An experimental new manifest format that allowed
904 # - manifestv2: An experimental new manifest format that allowed
907 # for stem compression of long paths. Experiment ended up not
905 # for stem compression of long paths. Experiment ended up not
908 # being successful (repository sizes went up due to worse delta
906 # being successful (repository sizes went up due to worse delta
909 # chains), and the code was deleted in 4.6.
907 # chains), and the code was deleted in 4.6.
910 supportedformats = {
908 supportedformats = {
911 'revlogv1',
909 'revlogv1',
912 'generaldelta',
910 'generaldelta',
913 'treemanifest',
911 'treemanifest',
914 REVLOGV2_REQUIREMENT,
912 REVLOGV2_REQUIREMENT,
915 SPARSEREVLOG_REQUIREMENT,
913 SPARSEREVLOG_REQUIREMENT,
916 bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT,
914 bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT,
917 }
915 }
918 _basesupported = supportedformats | {
916 _basesupported = supportedformats | {
919 'store',
917 'store',
920 'fncache',
918 'fncache',
921 'shared',
919 'shared',
922 'relshared',
920 'relshared',
923 'dotencode',
921 'dotencode',
924 'exp-sparse',
922 'exp-sparse',
925 'internal-phase'
923 'internal-phase'
926 }
924 }
927
925
928 # list of prefix for file which can be written without 'wlock'
926 # list of prefix for file which can be written without 'wlock'
929 # Extensions should extend this list when needed
927 # Extensions should extend this list when needed
930 _wlockfreeprefix = {
928 _wlockfreeprefix = {
931 # We migh consider requiring 'wlock' for the next
929 # We migh consider requiring 'wlock' for the next
932 # two, but pretty much all the existing code assume
930 # two, but pretty much all the existing code assume
933 # wlock is not needed so we keep them excluded for
931 # wlock is not needed so we keep them excluded for
934 # now.
932 # now.
935 'hgrc',
933 'hgrc',
936 'requires',
934 'requires',
937 # XXX cache is a complicatged business someone
935 # XXX cache is a complicatged business someone
938 # should investigate this in depth at some point
936 # should investigate this in depth at some point
939 'cache/',
937 'cache/',
940 # XXX shouldn't be dirstate covered by the wlock?
938 # XXX shouldn't be dirstate covered by the wlock?
941 'dirstate',
939 'dirstate',
942 # XXX bisect was still a bit too messy at the time
940 # XXX bisect was still a bit too messy at the time
943 # this changeset was introduced. Someone should fix
941 # this changeset was introduced. Someone should fix
944 # the remainig bit and drop this line
942 # the remainig bit and drop this line
945 'bisect.state',
943 'bisect.state',
946 }
944 }
947
945
948 def __init__(self, baseui, ui, origroot, wdirvfs, hgvfs, requirements,
946 def __init__(self, baseui, ui, origroot, wdirvfs, hgvfs, requirements,
949 supportedrequirements, sharedpath, store, cachevfs, wcachevfs,
947 supportedrequirements, sharedpath, store, cachevfs, wcachevfs,
950 features, intents=None):
948 features, intents=None):
951 """Create a new local repository instance.
949 """Create a new local repository instance.
952
950
953 Most callers should use ``hg.repository()``, ``localrepo.instance()``,
951 Most callers should use ``hg.repository()``, ``localrepo.instance()``,
954 or ``localrepo.makelocalrepository()`` for obtaining a new repository
952 or ``localrepo.makelocalrepository()`` for obtaining a new repository
955 object.
953 object.
956
954
957 Arguments:
955 Arguments:
958
956
959 baseui
957 baseui
960 ``ui.ui`` instance that ``ui`` argument was based off of.
958 ``ui.ui`` instance that ``ui`` argument was based off of.
961
959
962 ui
960 ui
963 ``ui.ui`` instance for use by the repository.
961 ``ui.ui`` instance for use by the repository.
964
962
965 origroot
963 origroot
966 ``bytes`` path to working directory root of this repository.
964 ``bytes`` path to working directory root of this repository.
967
965
968 wdirvfs
966 wdirvfs
969 ``vfs.vfs`` rooted at the working directory.
967 ``vfs.vfs`` rooted at the working directory.
970
968
971 hgvfs
969 hgvfs
972 ``vfs.vfs`` rooted at .hg/
970 ``vfs.vfs`` rooted at .hg/
973
971
974 requirements
972 requirements
975 ``set`` of bytestrings representing repository opening requirements.
973 ``set`` of bytestrings representing repository opening requirements.
976
974
977 supportedrequirements
975 supportedrequirements
978 ``set`` of bytestrings representing repository requirements that we
976 ``set`` of bytestrings representing repository requirements that we
979 know how to open. May be a supetset of ``requirements``.
977 know how to open. May be a supetset of ``requirements``.
980
978
981 sharedpath
979 sharedpath
982 ``bytes`` Defining path to storage base directory. Points to a
980 ``bytes`` Defining path to storage base directory. Points to a
983 ``.hg/`` directory somewhere.
981 ``.hg/`` directory somewhere.
984
982
985 store
983 store
986 ``store.basicstore`` (or derived) instance providing access to
984 ``store.basicstore`` (or derived) instance providing access to
987 versioned storage.
985 versioned storage.
988
986
989 cachevfs
987 cachevfs
990 ``vfs.vfs`` used for cache files.
988 ``vfs.vfs`` used for cache files.
991
989
992 wcachevfs
990 wcachevfs
993 ``vfs.vfs`` used for cache files related to the working copy.
991 ``vfs.vfs`` used for cache files related to the working copy.
994
992
995 features
993 features
996 ``set`` of bytestrings defining features/capabilities of this
994 ``set`` of bytestrings defining features/capabilities of this
997 instance.
995 instance.
998
996
999 intents
997 intents
1000 ``set`` of system strings indicating what this repo will be used
998 ``set`` of system strings indicating what this repo will be used
1001 for.
999 for.
1002 """
1000 """
1003 self.baseui = baseui
1001 self.baseui = baseui
1004 self.ui = ui
1002 self.ui = ui
1005 self.origroot = origroot
1003 self.origroot = origroot
1006 # vfs rooted at working directory.
1004 # vfs rooted at working directory.
1007 self.wvfs = wdirvfs
1005 self.wvfs = wdirvfs
1008 self.root = wdirvfs.base
1006 self.root = wdirvfs.base
1009 # vfs rooted at .hg/. Used to access most non-store paths.
1007 # vfs rooted at .hg/. Used to access most non-store paths.
1010 self.vfs = hgvfs
1008 self.vfs = hgvfs
1011 self.path = hgvfs.base
1009 self.path = hgvfs.base
1012 self.requirements = requirements
1010 self.requirements = requirements
1013 self.supported = supportedrequirements
1011 self.supported = supportedrequirements
1014 self.sharedpath = sharedpath
1012 self.sharedpath = sharedpath
1015 self.store = store
1013 self.store = store
1016 self.cachevfs = cachevfs
1014 self.cachevfs = cachevfs
1017 self.wcachevfs = wcachevfs
1015 self.wcachevfs = wcachevfs
1018 self.features = features
1016 self.features = features
1019
1017
1020 self.filtername = None
1018 self.filtername = None
1021
1019
1022 if (self.ui.configbool('devel', 'all-warnings') or
1020 if (self.ui.configbool('devel', 'all-warnings') or
1023 self.ui.configbool('devel', 'check-locks')):
1021 self.ui.configbool('devel', 'check-locks')):
1024 self.vfs.audit = self._getvfsward(self.vfs.audit)
1022 self.vfs.audit = self._getvfsward(self.vfs.audit)
1025 # A list of callback to shape the phase if no data were found.
1023 # A list of callback to shape the phase if no data were found.
1026 # Callback are in the form: func(repo, roots) --> processed root.
1024 # Callback are in the form: func(repo, roots) --> processed root.
1027 # This list it to be filled by extension during repo setup
1025 # This list it to be filled by extension during repo setup
1028 self._phasedefaults = []
1026 self._phasedefaults = []
1029
1027
1030 color.setup(self.ui)
1028 color.setup(self.ui)
1031
1029
1032 self.spath = self.store.path
1030 self.spath = self.store.path
1033 self.svfs = self.store.vfs
1031 self.svfs = self.store.vfs
1034 self.sjoin = self.store.join
1032 self.sjoin = self.store.join
1035 if (self.ui.configbool('devel', 'all-warnings') or
1033 if (self.ui.configbool('devel', 'all-warnings') or
1036 self.ui.configbool('devel', 'check-locks')):
1034 self.ui.configbool('devel', 'check-locks')):
1037 if util.safehasattr(self.svfs, 'vfs'): # this is filtervfs
1035 if util.safehasattr(self.svfs, 'vfs'): # this is filtervfs
1038 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
1036 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
1039 else: # standard vfs
1037 else: # standard vfs
1040 self.svfs.audit = self._getsvfsward(self.svfs.audit)
1038 self.svfs.audit = self._getsvfsward(self.svfs.audit)
1041
1039
1042 self._dirstatevalidatewarned = False
1040 self._dirstatevalidatewarned = False
1043
1041
1044 self._branchcaches = branchmap.BranchMapCache()
1042 self._branchcaches = branchmap.BranchMapCache()
1045 self._revbranchcache = None
1043 self._revbranchcache = None
1046 self._filterpats = {}
1044 self._filterpats = {}
1047 self._datafilters = {}
1045 self._datafilters = {}
1048 self._transref = self._lockref = self._wlockref = None
1046 self._transref = self._lockref = self._wlockref = None
1049
1047
1050 # A cache for various files under .hg/ that tracks file changes,
1048 # A cache for various files under .hg/ that tracks file changes,
1051 # (used by the filecache decorator)
1049 # (used by the filecache decorator)
1052 #
1050 #
1053 # Maps a property name to its util.filecacheentry
1051 # Maps a property name to its util.filecacheentry
1054 self._filecache = {}
1052 self._filecache = {}
1055
1053
1056 # hold sets of revision to be filtered
1054 # hold sets of revision to be filtered
1057 # should be cleared when something might have changed the filter value:
1055 # should be cleared when something might have changed the filter value:
1058 # - new changesets,
1056 # - new changesets,
1059 # - phase change,
1057 # - phase change,
1060 # - new obsolescence marker,
1058 # - new obsolescence marker,
1061 # - working directory parent change,
1059 # - working directory parent change,
1062 # - bookmark changes
1060 # - bookmark changes
1063 self.filteredrevcache = {}
1061 self.filteredrevcache = {}
1064
1062
1065 # post-dirstate-status hooks
1063 # post-dirstate-status hooks
1066 self._postdsstatus = []
1064 self._postdsstatus = []
1067
1065
1068 # generic mapping between names and nodes
1066 # generic mapping between names and nodes
1069 self.names = namespaces.namespaces()
1067 self.names = namespaces.namespaces()
1070
1068
1071 # Key to signature value.
1069 # Key to signature value.
1072 self._sparsesignaturecache = {}
1070 self._sparsesignaturecache = {}
1073 # Signature to cached matcher instance.
1071 # Signature to cached matcher instance.
1074 self._sparsematchercache = {}
1072 self._sparsematchercache = {}
1075
1073
1076 self._extrafilterid = repoview.extrafilter(ui)
1074 self._extrafilterid = repoview.extrafilter(ui)
1077
1075
1078 def _getvfsward(self, origfunc):
1076 def _getvfsward(self, origfunc):
1079 """build a ward for self.vfs"""
1077 """build a ward for self.vfs"""
1080 rref = weakref.ref(self)
1078 rref = weakref.ref(self)
1081 def checkvfs(path, mode=None):
1079 def checkvfs(path, mode=None):
1082 ret = origfunc(path, mode=mode)
1080 ret = origfunc(path, mode=mode)
1083 repo = rref()
1081 repo = rref()
1084 if (repo is None
1082 if (repo is None
1085 or not util.safehasattr(repo, '_wlockref')
1083 or not util.safehasattr(repo, '_wlockref')
1086 or not util.safehasattr(repo, '_lockref')):
1084 or not util.safehasattr(repo, '_lockref')):
1087 return
1085 return
1088 if mode in (None, 'r', 'rb'):
1086 if mode in (None, 'r', 'rb'):
1089 return
1087 return
1090 if path.startswith(repo.path):
1088 if path.startswith(repo.path):
1091 # truncate name relative to the repository (.hg)
1089 # truncate name relative to the repository (.hg)
1092 path = path[len(repo.path) + 1:]
1090 path = path[len(repo.path) + 1:]
1093 if path.startswith('cache/'):
1091 if path.startswith('cache/'):
1094 msg = 'accessing cache with vfs instead of cachevfs: "%s"'
1092 msg = 'accessing cache with vfs instead of cachevfs: "%s"'
1095 repo.ui.develwarn(msg % path, stacklevel=3, config="cache-vfs")
1093 repo.ui.develwarn(msg % path, stacklevel=3, config="cache-vfs")
1096 if path.startswith('journal.') or path.startswith('undo.'):
1094 if path.startswith('journal.') or path.startswith('undo.'):
1097 # journal is covered by 'lock'
1095 # journal is covered by 'lock'
1098 if repo._currentlock(repo._lockref) is None:
1096 if repo._currentlock(repo._lockref) is None:
1099 repo.ui.develwarn('write with no lock: "%s"' % path,
1097 repo.ui.develwarn('write with no lock: "%s"' % path,
1100 stacklevel=3, config='check-locks')
1098 stacklevel=3, config='check-locks')
1101 elif repo._currentlock(repo._wlockref) is None:
1099 elif repo._currentlock(repo._wlockref) is None:
1102 # rest of vfs files are covered by 'wlock'
1100 # rest of vfs files are covered by 'wlock'
1103 #
1101 #
1104 # exclude special files
1102 # exclude special files
1105 for prefix in self._wlockfreeprefix:
1103 for prefix in self._wlockfreeprefix:
1106 if path.startswith(prefix):
1104 if path.startswith(prefix):
1107 return
1105 return
1108 repo.ui.develwarn('write with no wlock: "%s"' % path,
1106 repo.ui.develwarn('write with no wlock: "%s"' % path,
1109 stacklevel=3, config='check-locks')
1107 stacklevel=3, config='check-locks')
1110 return ret
1108 return ret
1111 return checkvfs
1109 return checkvfs
1112
1110
1113 def _getsvfsward(self, origfunc):
1111 def _getsvfsward(self, origfunc):
1114 """build a ward for self.svfs"""
1112 """build a ward for self.svfs"""
1115 rref = weakref.ref(self)
1113 rref = weakref.ref(self)
1116 def checksvfs(path, mode=None):
1114 def checksvfs(path, mode=None):
1117 ret = origfunc(path, mode=mode)
1115 ret = origfunc(path, mode=mode)
1118 repo = rref()
1116 repo = rref()
1119 if repo is None or not util.safehasattr(repo, '_lockref'):
1117 if repo is None or not util.safehasattr(repo, '_lockref'):
1120 return
1118 return
1121 if mode in (None, 'r', 'rb'):
1119 if mode in (None, 'r', 'rb'):
1122 return
1120 return
1123 if path.startswith(repo.sharedpath):
1121 if path.startswith(repo.sharedpath):
1124 # truncate name relative to the repository (.hg)
1122 # truncate name relative to the repository (.hg)
1125 path = path[len(repo.sharedpath) + 1:]
1123 path = path[len(repo.sharedpath) + 1:]
1126 if repo._currentlock(repo._lockref) is None:
1124 if repo._currentlock(repo._lockref) is None:
1127 repo.ui.develwarn('write with no lock: "%s"' % path,
1125 repo.ui.develwarn('write with no lock: "%s"' % path,
1128 stacklevel=4)
1126 stacklevel=4)
1129 return ret
1127 return ret
1130 return checksvfs
1128 return checksvfs
1131
1129
1132 def close(self):
1130 def close(self):
1133 self._writecaches()
1131 self._writecaches()
1134
1132
1135 def _writecaches(self):
1133 def _writecaches(self):
1136 if self._revbranchcache:
1134 if self._revbranchcache:
1137 self._revbranchcache.write()
1135 self._revbranchcache.write()
1138
1136
1139 def _restrictcapabilities(self, caps):
1137 def _restrictcapabilities(self, caps):
1140 if self.ui.configbool('experimental', 'bundle2-advertise'):
1138 if self.ui.configbool('experimental', 'bundle2-advertise'):
1141 caps = set(caps)
1139 caps = set(caps)
1142 capsblob = bundle2.encodecaps(bundle2.getrepocaps(self,
1140 capsblob = bundle2.encodecaps(bundle2.getrepocaps(self,
1143 role='client'))
1141 role='client'))
1144 caps.add('bundle2=' + urlreq.quote(capsblob))
1142 caps.add('bundle2=' + urlreq.quote(capsblob))
1145 return caps
1143 return caps
1146
1144
1147 def _writerequirements(self):
1145 def _writerequirements(self):
1148 scmutil.writerequires(self.vfs, self.requirements)
1146 scmutil.writerequires(self.vfs, self.requirements)
1149
1147
1150 # Don't cache auditor/nofsauditor, or you'll end up with reference cycle:
1148 # Don't cache auditor/nofsauditor, or you'll end up with reference cycle:
1151 # self -> auditor -> self._checknested -> self
1149 # self -> auditor -> self._checknested -> self
1152
1150
1153 @property
1151 @property
1154 def auditor(self):
1152 def auditor(self):
1155 # This is only used by context.workingctx.match in order to
1153 # This is only used by context.workingctx.match in order to
1156 # detect files in subrepos.
1154 # detect files in subrepos.
1157 return pathutil.pathauditor(self.root, callback=self._checknested)
1155 return pathutil.pathauditor(self.root, callback=self._checknested)
1158
1156
1159 @property
1157 @property
1160 def nofsauditor(self):
1158 def nofsauditor(self):
1161 # This is only used by context.basectx.match in order to detect
1159 # This is only used by context.basectx.match in order to detect
1162 # files in subrepos.
1160 # files in subrepos.
1163 return pathutil.pathauditor(self.root, callback=self._checknested,
1161 return pathutil.pathauditor(self.root, callback=self._checknested,
1164 realfs=False, cached=True)
1162 realfs=False, cached=True)
1165
1163
1166 def _checknested(self, path):
1164 def _checknested(self, path):
1167 """Determine if path is a legal nested repository."""
1165 """Determine if path is a legal nested repository."""
1168 if not path.startswith(self.root):
1166 if not path.startswith(self.root):
1169 return False
1167 return False
1170 subpath = path[len(self.root) + 1:]
1168 subpath = path[len(self.root) + 1:]
1171 normsubpath = util.pconvert(subpath)
1169 normsubpath = util.pconvert(subpath)
1172
1170
1173 # XXX: Checking against the current working copy is wrong in
1171 # XXX: Checking against the current working copy is wrong in
1174 # the sense that it can reject things like
1172 # the sense that it can reject things like
1175 #
1173 #
1176 # $ hg cat -r 10 sub/x.txt
1174 # $ hg cat -r 10 sub/x.txt
1177 #
1175 #
1178 # if sub/ is no longer a subrepository in the working copy
1176 # if sub/ is no longer a subrepository in the working copy
1179 # parent revision.
1177 # parent revision.
1180 #
1178 #
1181 # However, it can of course also allow things that would have
1179 # However, it can of course also allow things that would have
1182 # been rejected before, such as the above cat command if sub/
1180 # been rejected before, such as the above cat command if sub/
1183 # is a subrepository now, but was a normal directory before.
1181 # is a subrepository now, but was a normal directory before.
1184 # The old path auditor would have rejected by mistake since it
1182 # The old path auditor would have rejected by mistake since it
1185 # panics when it sees sub/.hg/.
1183 # panics when it sees sub/.hg/.
1186 #
1184 #
1187 # All in all, checking against the working copy seems sensible
1185 # All in all, checking against the working copy seems sensible
1188 # since we want to prevent access to nested repositories on
1186 # since we want to prevent access to nested repositories on
1189 # the filesystem *now*.
1187 # the filesystem *now*.
1190 ctx = self[None]
1188 ctx = self[None]
1191 parts = util.splitpath(subpath)
1189 parts = util.splitpath(subpath)
1192 while parts:
1190 while parts:
1193 prefix = '/'.join(parts)
1191 prefix = '/'.join(parts)
1194 if prefix in ctx.substate:
1192 if prefix in ctx.substate:
1195 if prefix == normsubpath:
1193 if prefix == normsubpath:
1196 return True
1194 return True
1197 else:
1195 else:
1198 sub = ctx.sub(prefix)
1196 sub = ctx.sub(prefix)
1199 return sub.checknested(subpath[len(prefix) + 1:])
1197 return sub.checknested(subpath[len(prefix) + 1:])
1200 else:
1198 else:
1201 parts.pop()
1199 parts.pop()
1202 return False
1200 return False
1203
1201
1204 def peer(self):
1202 def peer(self):
1205 return localpeer(self) # not cached to avoid reference cycle
1203 return localpeer(self) # not cached to avoid reference cycle
1206
1204
1207 def unfiltered(self):
1205 def unfiltered(self):
1208 """Return unfiltered version of the repository
1206 """Return unfiltered version of the repository
1209
1207
1210 Intended to be overwritten by filtered repo."""
1208 Intended to be overwritten by filtered repo."""
1211 return self
1209 return self
1212
1210
1213 def filtered(self, name, visibilityexceptions=None):
1211 def filtered(self, name, visibilityexceptions=None):
1214 """Return a filtered version of a repository
1212 """Return a filtered version of a repository
1215
1213
1216 The `name` parameter is the identifier of the requested view. This
1214 The `name` parameter is the identifier of the requested view. This
1217 will return a repoview object set "exactly" to the specified view.
1215 will return a repoview object set "exactly" to the specified view.
1218
1216
1219 This function does not apply recursive filtering to a repository. For
1217 This function does not apply recursive filtering to a repository. For
1220 example calling `repo.filtered("served")` will return a repoview using
1218 example calling `repo.filtered("served")` will return a repoview using
1221 the "served" view, regardless of the initial view used by `repo`.
1219 the "served" view, regardless of the initial view used by `repo`.
1222
1220
1223 In other word, there is always only one level of `repoview` "filtering".
1221 In other word, there is always only one level of `repoview` "filtering".
1224 """
1222 """
1225 if self._extrafilterid is not None and '%' not in name:
1223 if self._extrafilterid is not None and '%' not in name:
1226 name = name + '%' + self._extrafilterid
1224 name = name + '%' + self._extrafilterid
1227
1225
1228 cls = repoview.newtype(self.unfiltered().__class__)
1226 cls = repoview.newtype(self.unfiltered().__class__)
1229 return cls(self, name, visibilityexceptions)
1227 return cls(self, name, visibilityexceptions)
1230
1228
1231 @mixedrepostorecache(('bookmarks', 'plain'), ('bookmarks.current', 'plain'),
1229 @mixedrepostorecache(('bookmarks', 'plain'), ('bookmarks.current', 'plain'),
1232 ('bookmarks', ''), ('00changelog.i', ''))
1230 ('bookmarks', ''), ('00changelog.i', ''))
1233 def _bookmarks(self):
1231 def _bookmarks(self):
1234 # Since the multiple files involved in the transaction cannot be
1232 # Since the multiple files involved in the transaction cannot be
1235 # written atomically (with current repository format), there is a race
1233 # written atomically (with current repository format), there is a race
1236 # condition here.
1234 # condition here.
1237 #
1235 #
1238 # 1) changelog content A is read
1236 # 1) changelog content A is read
1239 # 2) outside transaction update changelog to content B
1237 # 2) outside transaction update changelog to content B
1240 # 3) outside transaction update bookmark file referring to content B
1238 # 3) outside transaction update bookmark file referring to content B
1241 # 4) bookmarks file content is read and filtered against changelog-A
1239 # 4) bookmarks file content is read and filtered against changelog-A
1242 #
1240 #
1243 # When this happens, bookmarks against nodes missing from A are dropped.
1241 # When this happens, bookmarks against nodes missing from A are dropped.
1244 #
1242 #
1245 # Having this happening during read is not great, but it become worse
1243 # Having this happening during read is not great, but it become worse
1246 # when this happen during write because the bookmarks to the "unknown"
1244 # when this happen during write because the bookmarks to the "unknown"
1247 # nodes will be dropped for good. However, writes happen within locks.
1245 # nodes will be dropped for good. However, writes happen within locks.
1248 # This locking makes it possible to have a race free consistent read.
1246 # This locking makes it possible to have a race free consistent read.
1249 # For this purpose data read from disc before locking are
1247 # For this purpose data read from disc before locking are
1250 # "invalidated" right after the locks are taken. This invalidations are
1248 # "invalidated" right after the locks are taken. This invalidations are
1251 # "light", the `filecache` mechanism keep the data in memory and will
1249 # "light", the `filecache` mechanism keep the data in memory and will
1252 # reuse them if the underlying files did not changed. Not parsing the
1250 # reuse them if the underlying files did not changed. Not parsing the
1253 # same data multiple times helps performances.
1251 # same data multiple times helps performances.
1254 #
1252 #
1255 # Unfortunately in the case describe above, the files tracked by the
1253 # Unfortunately in the case describe above, the files tracked by the
1256 # bookmarks file cache might not have changed, but the in-memory
1254 # bookmarks file cache might not have changed, but the in-memory
1257 # content is still "wrong" because we used an older changelog content
1255 # content is still "wrong" because we used an older changelog content
1258 # to process the on-disk data. So after locking, the changelog would be
1256 # to process the on-disk data. So after locking, the changelog would be
1259 # refreshed but `_bookmarks` would be preserved.
1257 # refreshed but `_bookmarks` would be preserved.
1260 # Adding `00changelog.i` to the list of tracked file is not
1258 # Adding `00changelog.i` to the list of tracked file is not
1261 # enough, because at the time we build the content for `_bookmarks` in
1259 # enough, because at the time we build the content for `_bookmarks` in
1262 # (4), the changelog file has already diverged from the content used
1260 # (4), the changelog file has already diverged from the content used
1263 # for loading `changelog` in (1)
1261 # for loading `changelog` in (1)
1264 #
1262 #
1265 # To prevent the issue, we force the changelog to be explicitly
1263 # To prevent the issue, we force the changelog to be explicitly
1266 # reloaded while computing `_bookmarks`. The data race can still happen
1264 # reloaded while computing `_bookmarks`. The data race can still happen
1267 # without the lock (with a narrower window), but it would no longer go
1265 # without the lock (with a narrower window), but it would no longer go
1268 # undetected during the lock time refresh.
1266 # undetected during the lock time refresh.
1269 #
1267 #
1270 # The new schedule is as follow
1268 # The new schedule is as follow
1271 #
1269 #
1272 # 1) filecache logic detect that `_bookmarks` needs to be computed
1270 # 1) filecache logic detect that `_bookmarks` needs to be computed
1273 # 2) cachestat for `bookmarks` and `changelog` are captured (for book)
1271 # 2) cachestat for `bookmarks` and `changelog` are captured (for book)
1274 # 3) We force `changelog` filecache to be tested
1272 # 3) We force `changelog` filecache to be tested
1275 # 4) cachestat for `changelog` are captured (for changelog)
1273 # 4) cachestat for `changelog` are captured (for changelog)
1276 # 5) `_bookmarks` is computed and cached
1274 # 5) `_bookmarks` is computed and cached
1277 #
1275 #
1278 # The step in (3) ensure we have a changelog at least as recent as the
1276 # The step in (3) ensure we have a changelog at least as recent as the
1279 # cache stat computed in (1). As a result at locking time:
1277 # cache stat computed in (1). As a result at locking time:
1280 # * if the changelog did not changed since (1) -> we can reuse the data
1278 # * if the changelog did not changed since (1) -> we can reuse the data
1281 # * otherwise -> the bookmarks get refreshed.
1279 # * otherwise -> the bookmarks get refreshed.
1282 self._refreshchangelog()
1280 self._refreshchangelog()
1283 return bookmarks.bmstore(self)
1281 return bookmarks.bmstore(self)
1284
1282
1285 def _refreshchangelog(self):
1283 def _refreshchangelog(self):
1286 """make sure the in memory changelog match the on-disk one"""
1284 """make sure the in memory changelog match the on-disk one"""
1287 if ('changelog' in vars(self) and self.currenttransaction() is None):
1285 if ('changelog' in vars(self) and self.currenttransaction() is None):
1288 del self.changelog
1286 del self.changelog
1289
1287
1290 @property
1288 @property
1291 def _activebookmark(self):
1289 def _activebookmark(self):
1292 return self._bookmarks.active
1290 return self._bookmarks.active
1293
1291
1294 # _phasesets depend on changelog. what we need is to call
1292 # _phasesets depend on changelog. what we need is to call
1295 # _phasecache.invalidate() if '00changelog.i' was changed, but it
1293 # _phasecache.invalidate() if '00changelog.i' was changed, but it
1296 # can't be easily expressed in filecache mechanism.
1294 # can't be easily expressed in filecache mechanism.
1297 @storecache('phaseroots', '00changelog.i')
1295 @storecache('phaseroots', '00changelog.i')
1298 def _phasecache(self):
1296 def _phasecache(self):
1299 return phases.phasecache(self, self._phasedefaults)
1297 return phases.phasecache(self, self._phasedefaults)
1300
1298
1301 @storecache('obsstore')
1299 @storecache('obsstore')
1302 def obsstore(self):
1300 def obsstore(self):
1303 return obsolete.makestore(self.ui, self)
1301 return obsolete.makestore(self.ui, self)
1304
1302
1305 @storecache('00changelog.i')
1303 @storecache('00changelog.i')
1306 def changelog(self):
1304 def changelog(self):
1307 return changelog.changelog(self.svfs,
1305 return self.store.changelog(txnutil.mayhavepending(self.root))
1308 trypending=txnutil.mayhavepending(self.root))
1309
1306
1310 @storecache('00manifest.i')
1307 @storecache('00manifest.i')
1311 def manifestlog(self):
1308 def manifestlog(self):
1312 rootstore = manifest.manifestrevlog(self.svfs)
1309 return self.store.manifestlog(self, self._storenarrowmatch)
1313 return manifest.manifestlog(self.svfs, self, rootstore,
1314 self._storenarrowmatch)
1315
1310
1316 @repofilecache('dirstate')
1311 @repofilecache('dirstate')
1317 def dirstate(self):
1312 def dirstate(self):
1318 return self._makedirstate()
1313 return self._makedirstate()
1319
1314
1320 def _makedirstate(self):
1315 def _makedirstate(self):
1321 """Extension point for wrapping the dirstate per-repo."""
1316 """Extension point for wrapping the dirstate per-repo."""
1322 sparsematchfn = lambda: sparse.matcher(self)
1317 sparsematchfn = lambda: sparse.matcher(self)
1323
1318
1324 return dirstate.dirstate(self.vfs, self.ui, self.root,
1319 return dirstate.dirstate(self.vfs, self.ui, self.root,
1325 self._dirstatevalidate, sparsematchfn)
1320 self._dirstatevalidate, sparsematchfn)
1326
1321
1327 def _dirstatevalidate(self, node):
1322 def _dirstatevalidate(self, node):
1328 try:
1323 try:
1329 self.changelog.rev(node)
1324 self.changelog.rev(node)
1330 return node
1325 return node
1331 except error.LookupError:
1326 except error.LookupError:
1332 if not self._dirstatevalidatewarned:
1327 if not self._dirstatevalidatewarned:
1333 self._dirstatevalidatewarned = True
1328 self._dirstatevalidatewarned = True
1334 self.ui.warn(_("warning: ignoring unknown"
1329 self.ui.warn(_("warning: ignoring unknown"
1335 " working parent %s!\n") % short(node))
1330 " working parent %s!\n") % short(node))
1336 return nullid
1331 return nullid
1337
1332
1338 @storecache(narrowspec.FILENAME)
1333 @storecache(narrowspec.FILENAME)
1339 def narrowpats(self):
1334 def narrowpats(self):
1340 """matcher patterns for this repository's narrowspec
1335 """matcher patterns for this repository's narrowspec
1341
1336
1342 A tuple of (includes, excludes).
1337 A tuple of (includes, excludes).
1343 """
1338 """
1344 return narrowspec.load(self)
1339 return narrowspec.load(self)
1345
1340
1346 @storecache(narrowspec.FILENAME)
1341 @storecache(narrowspec.FILENAME)
1347 def _storenarrowmatch(self):
1342 def _storenarrowmatch(self):
1348 if repository.NARROW_REQUIREMENT not in self.requirements:
1343 if repository.NARROW_REQUIREMENT not in self.requirements:
1349 return matchmod.always()
1344 return matchmod.always()
1350 include, exclude = self.narrowpats
1345 include, exclude = self.narrowpats
1351 return narrowspec.match(self.root, include=include, exclude=exclude)
1346 return narrowspec.match(self.root, include=include, exclude=exclude)
1352
1347
1353 @storecache(narrowspec.FILENAME)
1348 @storecache(narrowspec.FILENAME)
1354 def _narrowmatch(self):
1349 def _narrowmatch(self):
1355 if repository.NARROW_REQUIREMENT not in self.requirements:
1350 if repository.NARROW_REQUIREMENT not in self.requirements:
1356 return matchmod.always()
1351 return matchmod.always()
1357 narrowspec.checkworkingcopynarrowspec(self)
1352 narrowspec.checkworkingcopynarrowspec(self)
1358 include, exclude = self.narrowpats
1353 include, exclude = self.narrowpats
1359 return narrowspec.match(self.root, include=include, exclude=exclude)
1354 return narrowspec.match(self.root, include=include, exclude=exclude)
1360
1355
1361 def narrowmatch(self, match=None, includeexact=False):
1356 def narrowmatch(self, match=None, includeexact=False):
1362 """matcher corresponding the the repo's narrowspec
1357 """matcher corresponding the the repo's narrowspec
1363
1358
1364 If `match` is given, then that will be intersected with the narrow
1359 If `match` is given, then that will be intersected with the narrow
1365 matcher.
1360 matcher.
1366
1361
1367 If `includeexact` is True, then any exact matches from `match` will
1362 If `includeexact` is True, then any exact matches from `match` will
1368 be included even if they're outside the narrowspec.
1363 be included even if they're outside the narrowspec.
1369 """
1364 """
1370 if match:
1365 if match:
1371 if includeexact and not self._narrowmatch.always():
1366 if includeexact and not self._narrowmatch.always():
1372 # do not exclude explicitly-specified paths so that they can
1367 # do not exclude explicitly-specified paths so that they can
1373 # be warned later on
1368 # be warned later on
1374 em = matchmod.exact(match.files())
1369 em = matchmod.exact(match.files())
1375 nm = matchmod.unionmatcher([self._narrowmatch, em])
1370 nm = matchmod.unionmatcher([self._narrowmatch, em])
1376 return matchmod.intersectmatchers(match, nm)
1371 return matchmod.intersectmatchers(match, nm)
1377 return matchmod.intersectmatchers(match, self._narrowmatch)
1372 return matchmod.intersectmatchers(match, self._narrowmatch)
1378 return self._narrowmatch
1373 return self._narrowmatch
1379
1374
1380 def setnarrowpats(self, newincludes, newexcludes):
1375 def setnarrowpats(self, newincludes, newexcludes):
1381 narrowspec.save(self, newincludes, newexcludes)
1376 narrowspec.save(self, newincludes, newexcludes)
1382 self.invalidate(clearfilecache=True)
1377 self.invalidate(clearfilecache=True)
1383
1378
1384 def __getitem__(self, changeid):
1379 def __getitem__(self, changeid):
1385 if changeid is None:
1380 if changeid is None:
1386 return context.workingctx(self)
1381 return context.workingctx(self)
1387 if isinstance(changeid, context.basectx):
1382 if isinstance(changeid, context.basectx):
1388 return changeid
1383 return changeid
1389 if isinstance(changeid, slice):
1384 if isinstance(changeid, slice):
1390 # wdirrev isn't contiguous so the slice shouldn't include it
1385 # wdirrev isn't contiguous so the slice shouldn't include it
1391 return [self[i]
1386 return [self[i]
1392 for i in pycompat.xrange(*changeid.indices(len(self)))
1387 for i in pycompat.xrange(*changeid.indices(len(self)))
1393 if i not in self.changelog.filteredrevs]
1388 if i not in self.changelog.filteredrevs]
1394 try:
1389 try:
1395 if isinstance(changeid, int):
1390 if isinstance(changeid, int):
1396 node = self.changelog.node(changeid)
1391 node = self.changelog.node(changeid)
1397 rev = changeid
1392 rev = changeid
1398 elif changeid == 'null':
1393 elif changeid == 'null':
1399 node = nullid
1394 node = nullid
1400 rev = nullrev
1395 rev = nullrev
1401 elif changeid == 'tip':
1396 elif changeid == 'tip':
1402 node = self.changelog.tip()
1397 node = self.changelog.tip()
1403 rev = self.changelog.rev(node)
1398 rev = self.changelog.rev(node)
1404 elif changeid == '.':
1399 elif changeid == '.':
1405 # this is a hack to delay/avoid loading obsmarkers
1400 # this is a hack to delay/avoid loading obsmarkers
1406 # when we know that '.' won't be hidden
1401 # when we know that '.' won't be hidden
1407 node = self.dirstate.p1()
1402 node = self.dirstate.p1()
1408 rev = self.unfiltered().changelog.rev(node)
1403 rev = self.unfiltered().changelog.rev(node)
1409 elif len(changeid) == 20:
1404 elif len(changeid) == 20:
1410 try:
1405 try:
1411 node = changeid
1406 node = changeid
1412 rev = self.changelog.rev(changeid)
1407 rev = self.changelog.rev(changeid)
1413 except error.FilteredLookupError:
1408 except error.FilteredLookupError:
1414 changeid = hex(changeid) # for the error message
1409 changeid = hex(changeid) # for the error message
1415 raise
1410 raise
1416 except LookupError:
1411 except LookupError:
1417 # check if it might have come from damaged dirstate
1412 # check if it might have come from damaged dirstate
1418 #
1413 #
1419 # XXX we could avoid the unfiltered if we had a recognizable
1414 # XXX we could avoid the unfiltered if we had a recognizable
1420 # exception for filtered changeset access
1415 # exception for filtered changeset access
1421 if (self.local()
1416 if (self.local()
1422 and changeid in self.unfiltered().dirstate.parents()):
1417 and changeid in self.unfiltered().dirstate.parents()):
1423 msg = _("working directory has unknown parent '%s'!")
1418 msg = _("working directory has unknown parent '%s'!")
1424 raise error.Abort(msg % short(changeid))
1419 raise error.Abort(msg % short(changeid))
1425 changeid = hex(changeid) # for the error message
1420 changeid = hex(changeid) # for the error message
1426 raise
1421 raise
1427
1422
1428 elif len(changeid) == 40:
1423 elif len(changeid) == 40:
1429 node = bin(changeid)
1424 node = bin(changeid)
1430 rev = self.changelog.rev(node)
1425 rev = self.changelog.rev(node)
1431 else:
1426 else:
1432 raise error.ProgrammingError(
1427 raise error.ProgrammingError(
1433 "unsupported changeid '%s' of type %s" %
1428 "unsupported changeid '%s' of type %s" %
1434 (changeid, type(changeid)))
1429 (changeid, type(changeid)))
1435
1430
1436 return context.changectx(self, rev, node)
1431 return context.changectx(self, rev, node)
1437
1432
1438 except (error.FilteredIndexError, error.FilteredLookupError):
1433 except (error.FilteredIndexError, error.FilteredLookupError):
1439 raise error.FilteredRepoLookupError(_("filtered revision '%s'")
1434 raise error.FilteredRepoLookupError(_("filtered revision '%s'")
1440 % pycompat.bytestr(changeid))
1435 % pycompat.bytestr(changeid))
1441 except (IndexError, LookupError):
1436 except (IndexError, LookupError):
1442 raise error.RepoLookupError(
1437 raise error.RepoLookupError(
1443 _("unknown revision '%s'") % pycompat.bytestr(changeid))
1438 _("unknown revision '%s'") % pycompat.bytestr(changeid))
1444 except error.WdirUnsupported:
1439 except error.WdirUnsupported:
1445 return context.workingctx(self)
1440 return context.workingctx(self)
1446
1441
1447 def __contains__(self, changeid):
1442 def __contains__(self, changeid):
1448 """True if the given changeid exists
1443 """True if the given changeid exists
1449
1444
1450 error.AmbiguousPrefixLookupError is raised if an ambiguous node
1445 error.AmbiguousPrefixLookupError is raised if an ambiguous node
1451 specified.
1446 specified.
1452 """
1447 """
1453 try:
1448 try:
1454 self[changeid]
1449 self[changeid]
1455 return True
1450 return True
1456 except error.RepoLookupError:
1451 except error.RepoLookupError:
1457 return False
1452 return False
1458
1453
1459 def __nonzero__(self):
1454 def __nonzero__(self):
1460 return True
1455 return True
1461
1456
1462 __bool__ = __nonzero__
1457 __bool__ = __nonzero__
1463
1458
1464 def __len__(self):
1459 def __len__(self):
1465 # no need to pay the cost of repoview.changelog
1460 # no need to pay the cost of repoview.changelog
1466 unfi = self.unfiltered()
1461 unfi = self.unfiltered()
1467 return len(unfi.changelog)
1462 return len(unfi.changelog)
1468
1463
1469 def __iter__(self):
1464 def __iter__(self):
1470 return iter(self.changelog)
1465 return iter(self.changelog)
1471
1466
1472 def revs(self, expr, *args):
1467 def revs(self, expr, *args):
1473 '''Find revisions matching a revset.
1468 '''Find revisions matching a revset.
1474
1469
1475 The revset is specified as a string ``expr`` that may contain
1470 The revset is specified as a string ``expr`` that may contain
1476 %-formatting to escape certain types. See ``revsetlang.formatspec``.
1471 %-formatting to escape certain types. See ``revsetlang.formatspec``.
1477
1472
1478 Revset aliases from the configuration are not expanded. To expand
1473 Revset aliases from the configuration are not expanded. To expand
1479 user aliases, consider calling ``scmutil.revrange()`` or
1474 user aliases, consider calling ``scmutil.revrange()`` or
1480 ``repo.anyrevs([expr], user=True)``.
1475 ``repo.anyrevs([expr], user=True)``.
1481
1476
1482 Returns a revset.abstractsmartset, which is a list-like interface
1477 Returns a revset.abstractsmartset, which is a list-like interface
1483 that contains integer revisions.
1478 that contains integer revisions.
1484 '''
1479 '''
1485 tree = revsetlang.spectree(expr, *args)
1480 tree = revsetlang.spectree(expr, *args)
1486 return revset.makematcher(tree)(self)
1481 return revset.makematcher(tree)(self)
1487
1482
1488 def set(self, expr, *args):
1483 def set(self, expr, *args):
1489 '''Find revisions matching a revset and emit changectx instances.
1484 '''Find revisions matching a revset and emit changectx instances.
1490
1485
1491 This is a convenience wrapper around ``revs()`` that iterates the
1486 This is a convenience wrapper around ``revs()`` that iterates the
1492 result and is a generator of changectx instances.
1487 result and is a generator of changectx instances.
1493
1488
1494 Revset aliases from the configuration are not expanded. To expand
1489 Revset aliases from the configuration are not expanded. To expand
1495 user aliases, consider calling ``scmutil.revrange()``.
1490 user aliases, consider calling ``scmutil.revrange()``.
1496 '''
1491 '''
1497 for r in self.revs(expr, *args):
1492 for r in self.revs(expr, *args):
1498 yield self[r]
1493 yield self[r]
1499
1494
1500 def anyrevs(self, specs, user=False, localalias=None):
1495 def anyrevs(self, specs, user=False, localalias=None):
1501 '''Find revisions matching one of the given revsets.
1496 '''Find revisions matching one of the given revsets.
1502
1497
1503 Revset aliases from the configuration are not expanded by default. To
1498 Revset aliases from the configuration are not expanded by default. To
1504 expand user aliases, specify ``user=True``. To provide some local
1499 expand user aliases, specify ``user=True``. To provide some local
1505 definitions overriding user aliases, set ``localalias`` to
1500 definitions overriding user aliases, set ``localalias`` to
1506 ``{name: definitionstring}``.
1501 ``{name: definitionstring}``.
1507 '''
1502 '''
1508 if user:
1503 if user:
1509 m = revset.matchany(self.ui, specs,
1504 m = revset.matchany(self.ui, specs,
1510 lookup=revset.lookupfn(self),
1505 lookup=revset.lookupfn(self),
1511 localalias=localalias)
1506 localalias=localalias)
1512 else:
1507 else:
1513 m = revset.matchany(None, specs, localalias=localalias)
1508 m = revset.matchany(None, specs, localalias=localalias)
1514 return m(self)
1509 return m(self)
1515
1510
1516 def url(self):
1511 def url(self):
1517 return 'file:' + self.root
1512 return 'file:' + self.root
1518
1513
1519 def hook(self, name, throw=False, **args):
1514 def hook(self, name, throw=False, **args):
1520 """Call a hook, passing this repo instance.
1515 """Call a hook, passing this repo instance.
1521
1516
1522 This a convenience method to aid invoking hooks. Extensions likely
1517 This a convenience method to aid invoking hooks. Extensions likely
1523 won't call this unless they have registered a custom hook or are
1518 won't call this unless they have registered a custom hook or are
1524 replacing code that is expected to call a hook.
1519 replacing code that is expected to call a hook.
1525 """
1520 """
1526 return hook.hook(self.ui, self, name, throw, **args)
1521 return hook.hook(self.ui, self, name, throw, **args)
1527
1522
1528 @filteredpropertycache
1523 @filteredpropertycache
1529 def _tagscache(self):
1524 def _tagscache(self):
1530 '''Returns a tagscache object that contains various tags related
1525 '''Returns a tagscache object that contains various tags related
1531 caches.'''
1526 caches.'''
1532
1527
1533 # This simplifies its cache management by having one decorated
1528 # This simplifies its cache management by having one decorated
1534 # function (this one) and the rest simply fetch things from it.
1529 # function (this one) and the rest simply fetch things from it.
1535 class tagscache(object):
1530 class tagscache(object):
1536 def __init__(self):
1531 def __init__(self):
1537 # These two define the set of tags for this repository. tags
1532 # These two define the set of tags for this repository. tags
1538 # maps tag name to node; tagtypes maps tag name to 'global' or
1533 # maps tag name to node; tagtypes maps tag name to 'global' or
1539 # 'local'. (Global tags are defined by .hgtags across all
1534 # 'local'. (Global tags are defined by .hgtags across all
1540 # heads, and local tags are defined in .hg/localtags.)
1535 # heads, and local tags are defined in .hg/localtags.)
1541 # They constitute the in-memory cache of tags.
1536 # They constitute the in-memory cache of tags.
1542 self.tags = self.tagtypes = None
1537 self.tags = self.tagtypes = None
1543
1538
1544 self.nodetagscache = self.tagslist = None
1539 self.nodetagscache = self.tagslist = None
1545
1540
1546 cache = tagscache()
1541 cache = tagscache()
1547 cache.tags, cache.tagtypes = self._findtags()
1542 cache.tags, cache.tagtypes = self._findtags()
1548
1543
1549 return cache
1544 return cache
1550
1545
1551 def tags(self):
1546 def tags(self):
1552 '''return a mapping of tag to node'''
1547 '''return a mapping of tag to node'''
1553 t = {}
1548 t = {}
1554 if self.changelog.filteredrevs:
1549 if self.changelog.filteredrevs:
1555 tags, tt = self._findtags()
1550 tags, tt = self._findtags()
1556 else:
1551 else:
1557 tags = self._tagscache.tags
1552 tags = self._tagscache.tags
1558 rev = self.changelog.rev
1553 rev = self.changelog.rev
1559 for k, v in tags.iteritems():
1554 for k, v in tags.iteritems():
1560 try:
1555 try:
1561 # ignore tags to unknown nodes
1556 # ignore tags to unknown nodes
1562 rev(v)
1557 rev(v)
1563 t[k] = v
1558 t[k] = v
1564 except (error.LookupError, ValueError):
1559 except (error.LookupError, ValueError):
1565 pass
1560 pass
1566 return t
1561 return t
1567
1562
1568 def _findtags(self):
1563 def _findtags(self):
1569 '''Do the hard work of finding tags. Return a pair of dicts
1564 '''Do the hard work of finding tags. Return a pair of dicts
1570 (tags, tagtypes) where tags maps tag name to node, and tagtypes
1565 (tags, tagtypes) where tags maps tag name to node, and tagtypes
1571 maps tag name to a string like \'global\' or \'local\'.
1566 maps tag name to a string like \'global\' or \'local\'.
1572 Subclasses or extensions are free to add their own tags, but
1567 Subclasses or extensions are free to add their own tags, but
1573 should be aware that the returned dicts will be retained for the
1568 should be aware that the returned dicts will be retained for the
1574 duration of the localrepo object.'''
1569 duration of the localrepo object.'''
1575
1570
1576 # XXX what tagtype should subclasses/extensions use? Currently
1571 # XXX what tagtype should subclasses/extensions use? Currently
1577 # mq and bookmarks add tags, but do not set the tagtype at all.
1572 # mq and bookmarks add tags, but do not set the tagtype at all.
1578 # Should each extension invent its own tag type? Should there
1573 # Should each extension invent its own tag type? Should there
1579 # be one tagtype for all such "virtual" tags? Or is the status
1574 # be one tagtype for all such "virtual" tags? Or is the status
1580 # quo fine?
1575 # quo fine?
1581
1576
1582
1577
1583 # map tag name to (node, hist)
1578 # map tag name to (node, hist)
1584 alltags = tagsmod.findglobaltags(self.ui, self)
1579 alltags = tagsmod.findglobaltags(self.ui, self)
1585 # map tag name to tag type
1580 # map tag name to tag type
1586 tagtypes = dict((tag, 'global') for tag in alltags)
1581 tagtypes = dict((tag, 'global') for tag in alltags)
1587
1582
1588 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
1583 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
1589
1584
1590 # Build the return dicts. Have to re-encode tag names because
1585 # Build the return dicts. Have to re-encode tag names because
1591 # the tags module always uses UTF-8 (in order not to lose info
1586 # the tags module always uses UTF-8 (in order not to lose info
1592 # writing to the cache), but the rest of Mercurial wants them in
1587 # writing to the cache), but the rest of Mercurial wants them in
1593 # local encoding.
1588 # local encoding.
1594 tags = {}
1589 tags = {}
1595 for (name, (node, hist)) in alltags.iteritems():
1590 for (name, (node, hist)) in alltags.iteritems():
1596 if node != nullid:
1591 if node != nullid:
1597 tags[encoding.tolocal(name)] = node
1592 tags[encoding.tolocal(name)] = node
1598 tags['tip'] = self.changelog.tip()
1593 tags['tip'] = self.changelog.tip()
1599 tagtypes = dict([(encoding.tolocal(name), value)
1594 tagtypes = dict([(encoding.tolocal(name), value)
1600 for (name, value) in tagtypes.iteritems()])
1595 for (name, value) in tagtypes.iteritems()])
1601 return (tags, tagtypes)
1596 return (tags, tagtypes)
1602
1597
1603 def tagtype(self, tagname):
1598 def tagtype(self, tagname):
1604 '''
1599 '''
1605 return the type of the given tag. result can be:
1600 return the type of the given tag. result can be:
1606
1601
1607 'local' : a local tag
1602 'local' : a local tag
1608 'global' : a global tag
1603 'global' : a global tag
1609 None : tag does not exist
1604 None : tag does not exist
1610 '''
1605 '''
1611
1606
1612 return self._tagscache.tagtypes.get(tagname)
1607 return self._tagscache.tagtypes.get(tagname)
1613
1608
1614 def tagslist(self):
1609 def tagslist(self):
1615 '''return a list of tags ordered by revision'''
1610 '''return a list of tags ordered by revision'''
1616 if not self._tagscache.tagslist:
1611 if not self._tagscache.tagslist:
1617 l = []
1612 l = []
1618 for t, n in self.tags().iteritems():
1613 for t, n in self.tags().iteritems():
1619 l.append((self.changelog.rev(n), t, n))
1614 l.append((self.changelog.rev(n), t, n))
1620 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
1615 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
1621
1616
1622 return self._tagscache.tagslist
1617 return self._tagscache.tagslist
1623
1618
1624 def nodetags(self, node):
1619 def nodetags(self, node):
1625 '''return the tags associated with a node'''
1620 '''return the tags associated with a node'''
1626 if not self._tagscache.nodetagscache:
1621 if not self._tagscache.nodetagscache:
1627 nodetagscache = {}
1622 nodetagscache = {}
1628 for t, n in self._tagscache.tags.iteritems():
1623 for t, n in self._tagscache.tags.iteritems():
1629 nodetagscache.setdefault(n, []).append(t)
1624 nodetagscache.setdefault(n, []).append(t)
1630 for tags in nodetagscache.itervalues():
1625 for tags in nodetagscache.itervalues():
1631 tags.sort()
1626 tags.sort()
1632 self._tagscache.nodetagscache = nodetagscache
1627 self._tagscache.nodetagscache = nodetagscache
1633 return self._tagscache.nodetagscache.get(node, [])
1628 return self._tagscache.nodetagscache.get(node, [])
1634
1629
1635 def nodebookmarks(self, node):
1630 def nodebookmarks(self, node):
1636 """return the list of bookmarks pointing to the specified node"""
1631 """return the list of bookmarks pointing to the specified node"""
1637 return self._bookmarks.names(node)
1632 return self._bookmarks.names(node)
1638
1633
1639 def branchmap(self):
1634 def branchmap(self):
1640 '''returns a dictionary {branch: [branchheads]} with branchheads
1635 '''returns a dictionary {branch: [branchheads]} with branchheads
1641 ordered by increasing revision number'''
1636 ordered by increasing revision number'''
1642 return self._branchcaches[self]
1637 return self._branchcaches[self]
1643
1638
1644 @unfilteredmethod
1639 @unfilteredmethod
1645 def revbranchcache(self):
1640 def revbranchcache(self):
1646 if not self._revbranchcache:
1641 if not self._revbranchcache:
1647 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
1642 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
1648 return self._revbranchcache
1643 return self._revbranchcache
1649
1644
1650 def branchtip(self, branch, ignoremissing=False):
1645 def branchtip(self, branch, ignoremissing=False):
1651 '''return the tip node for a given branch
1646 '''return the tip node for a given branch
1652
1647
1653 If ignoremissing is True, then this method will not raise an error.
1648 If ignoremissing is True, then this method will not raise an error.
1654 This is helpful for callers that only expect None for a missing branch
1649 This is helpful for callers that only expect None for a missing branch
1655 (e.g. namespace).
1650 (e.g. namespace).
1656
1651
1657 '''
1652 '''
1658 try:
1653 try:
1659 return self.branchmap().branchtip(branch)
1654 return self.branchmap().branchtip(branch)
1660 except KeyError:
1655 except KeyError:
1661 if not ignoremissing:
1656 if not ignoremissing:
1662 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
1657 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
1663 else:
1658 else:
1664 pass
1659 pass
1665
1660
1666 def lookup(self, key):
1661 def lookup(self, key):
1667 node = scmutil.revsymbol(self, key).node()
1662 node = scmutil.revsymbol(self, key).node()
1668 if node is None:
1663 if node is None:
1669 raise error.RepoLookupError(_("unknown revision '%s'") % key)
1664 raise error.RepoLookupError(_("unknown revision '%s'") % key)
1670 return node
1665 return node
1671
1666
1672 def lookupbranch(self, key):
1667 def lookupbranch(self, key):
1673 if self.branchmap().hasbranch(key):
1668 if self.branchmap().hasbranch(key):
1674 return key
1669 return key
1675
1670
1676 return scmutil.revsymbol(self, key).branch()
1671 return scmutil.revsymbol(self, key).branch()
1677
1672
1678 def known(self, nodes):
1673 def known(self, nodes):
1679 cl = self.changelog
1674 cl = self.changelog
1680 nm = cl.nodemap
1675 nm = cl.nodemap
1681 filtered = cl.filteredrevs
1676 filtered = cl.filteredrevs
1682 result = []
1677 result = []
1683 for n in nodes:
1678 for n in nodes:
1684 r = nm.get(n)
1679 r = nm.get(n)
1685 resp = not (r is None or r in filtered)
1680 resp = not (r is None or r in filtered)
1686 result.append(resp)
1681 result.append(resp)
1687 return result
1682 return result
1688
1683
1689 def local(self):
1684 def local(self):
1690 return self
1685 return self
1691
1686
1692 def publishing(self):
1687 def publishing(self):
1693 # it's safe (and desirable) to trust the publish flag unconditionally
1688 # it's safe (and desirable) to trust the publish flag unconditionally
1694 # so that we don't finalize changes shared between users via ssh or nfs
1689 # so that we don't finalize changes shared between users via ssh or nfs
1695 return self.ui.configbool('phases', 'publish', untrusted=True)
1690 return self.ui.configbool('phases', 'publish', untrusted=True)
1696
1691
1697 def cancopy(self):
1692 def cancopy(self):
1698 # so statichttprepo's override of local() works
1693 # so statichttprepo's override of local() works
1699 if not self.local():
1694 if not self.local():
1700 return False
1695 return False
1701 if not self.publishing():
1696 if not self.publishing():
1702 return True
1697 return True
1703 # if publishing we can't copy if there is filtered content
1698 # if publishing we can't copy if there is filtered content
1704 return not self.filtered('visible').changelog.filteredrevs
1699 return not self.filtered('visible').changelog.filteredrevs
1705
1700
1706 def shared(self):
1701 def shared(self):
1707 '''the type of shared repository (None if not shared)'''
1702 '''the type of shared repository (None if not shared)'''
1708 if self.sharedpath != self.path:
1703 if self.sharedpath != self.path:
1709 return 'store'
1704 return 'store'
1710 return None
1705 return None
1711
1706
1712 def wjoin(self, f, *insidef):
1707 def wjoin(self, f, *insidef):
1713 return self.vfs.reljoin(self.root, f, *insidef)
1708 return self.vfs.reljoin(self.root, f, *insidef)
1714
1709
1715 def setparents(self, p1, p2=nullid):
1710 def setparents(self, p1, p2=nullid):
1716 with self.dirstate.parentchange():
1711 with self.dirstate.parentchange():
1717 copies = self.dirstate.setparents(p1, p2)
1712 copies = self.dirstate.setparents(p1, p2)
1718 pctx = self[p1]
1713 pctx = self[p1]
1719 if copies:
1714 if copies:
1720 # Adjust copy records, the dirstate cannot do it, it
1715 # Adjust copy records, the dirstate cannot do it, it
1721 # requires access to parents manifests. Preserve them
1716 # requires access to parents manifests. Preserve them
1722 # only for entries added to first parent.
1717 # only for entries added to first parent.
1723 for f in copies:
1718 for f in copies:
1724 if f not in pctx and copies[f] in pctx:
1719 if f not in pctx and copies[f] in pctx:
1725 self.dirstate.copy(copies[f], f)
1720 self.dirstate.copy(copies[f], f)
1726 if p2 == nullid:
1721 if p2 == nullid:
1727 for f, s in sorted(self.dirstate.copies().items()):
1722 for f, s in sorted(self.dirstate.copies().items()):
1728 if f not in pctx and s not in pctx:
1723 if f not in pctx and s not in pctx:
1729 self.dirstate.copy(None, f)
1724 self.dirstate.copy(None, f)
1730
1725
1731 def filectx(self, path, changeid=None, fileid=None, changectx=None):
1726 def filectx(self, path, changeid=None, fileid=None, changectx=None):
1732 """changeid must be a changeset revision, if specified.
1727 """changeid must be a changeset revision, if specified.
1733 fileid can be a file revision or node."""
1728 fileid can be a file revision or node."""
1734 return context.filectx(self, path, changeid, fileid,
1729 return context.filectx(self, path, changeid, fileid,
1735 changectx=changectx)
1730 changectx=changectx)
1736
1731
1737 def getcwd(self):
1732 def getcwd(self):
1738 return self.dirstate.getcwd()
1733 return self.dirstate.getcwd()
1739
1734
1740 def pathto(self, f, cwd=None):
1735 def pathto(self, f, cwd=None):
1741 return self.dirstate.pathto(f, cwd)
1736 return self.dirstate.pathto(f, cwd)
1742
1737
1743 def _loadfilter(self, filter):
1738 def _loadfilter(self, filter):
1744 if filter not in self._filterpats:
1739 if filter not in self._filterpats:
1745 l = []
1740 l = []
1746 for pat, cmd in self.ui.configitems(filter):
1741 for pat, cmd in self.ui.configitems(filter):
1747 if cmd == '!':
1742 if cmd == '!':
1748 continue
1743 continue
1749 mf = matchmod.match(self.root, '', [pat])
1744 mf = matchmod.match(self.root, '', [pat])
1750 fn = None
1745 fn = None
1751 params = cmd
1746 params = cmd
1752 for name, filterfn in self._datafilters.iteritems():
1747 for name, filterfn in self._datafilters.iteritems():
1753 if cmd.startswith(name):
1748 if cmd.startswith(name):
1754 fn = filterfn
1749 fn = filterfn
1755 params = cmd[len(name):].lstrip()
1750 params = cmd[len(name):].lstrip()
1756 break
1751 break
1757 if not fn:
1752 if not fn:
1758 fn = lambda s, c, **kwargs: procutil.filter(s, c)
1753 fn = lambda s, c, **kwargs: procutil.filter(s, c)
1759 # Wrap old filters not supporting keyword arguments
1754 # Wrap old filters not supporting keyword arguments
1760 if not pycompat.getargspec(fn)[2]:
1755 if not pycompat.getargspec(fn)[2]:
1761 oldfn = fn
1756 oldfn = fn
1762 fn = lambda s, c, **kwargs: oldfn(s, c)
1757 fn = lambda s, c, **kwargs: oldfn(s, c)
1763 l.append((mf, fn, params))
1758 l.append((mf, fn, params))
1764 self._filterpats[filter] = l
1759 self._filterpats[filter] = l
1765 return self._filterpats[filter]
1760 return self._filterpats[filter]
1766
1761
1767 def _filter(self, filterpats, filename, data):
1762 def _filter(self, filterpats, filename, data):
1768 for mf, fn, cmd in filterpats:
1763 for mf, fn, cmd in filterpats:
1769 if mf(filename):
1764 if mf(filename):
1770 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
1765 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
1771 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
1766 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
1772 break
1767 break
1773
1768
1774 return data
1769 return data
1775
1770
1776 @unfilteredpropertycache
1771 @unfilteredpropertycache
1777 def _encodefilterpats(self):
1772 def _encodefilterpats(self):
1778 return self._loadfilter('encode')
1773 return self._loadfilter('encode')
1779
1774
1780 @unfilteredpropertycache
1775 @unfilteredpropertycache
1781 def _decodefilterpats(self):
1776 def _decodefilterpats(self):
1782 return self._loadfilter('decode')
1777 return self._loadfilter('decode')
1783
1778
1784 def adddatafilter(self, name, filter):
1779 def adddatafilter(self, name, filter):
1785 self._datafilters[name] = filter
1780 self._datafilters[name] = filter
1786
1781
1787 def wread(self, filename):
1782 def wread(self, filename):
1788 if self.wvfs.islink(filename):
1783 if self.wvfs.islink(filename):
1789 data = self.wvfs.readlink(filename)
1784 data = self.wvfs.readlink(filename)
1790 else:
1785 else:
1791 data = self.wvfs.read(filename)
1786 data = self.wvfs.read(filename)
1792 return self._filter(self._encodefilterpats, filename, data)
1787 return self._filter(self._encodefilterpats, filename, data)
1793
1788
1794 def wwrite(self, filename, data, flags, backgroundclose=False, **kwargs):
1789 def wwrite(self, filename, data, flags, backgroundclose=False, **kwargs):
1795 """write ``data`` into ``filename`` in the working directory
1790 """write ``data`` into ``filename`` in the working directory
1796
1791
1797 This returns length of written (maybe decoded) data.
1792 This returns length of written (maybe decoded) data.
1798 """
1793 """
1799 data = self._filter(self._decodefilterpats, filename, data)
1794 data = self._filter(self._decodefilterpats, filename, data)
1800 if 'l' in flags:
1795 if 'l' in flags:
1801 self.wvfs.symlink(data, filename)
1796 self.wvfs.symlink(data, filename)
1802 else:
1797 else:
1803 self.wvfs.write(filename, data, backgroundclose=backgroundclose,
1798 self.wvfs.write(filename, data, backgroundclose=backgroundclose,
1804 **kwargs)
1799 **kwargs)
1805 if 'x' in flags:
1800 if 'x' in flags:
1806 self.wvfs.setflags(filename, False, True)
1801 self.wvfs.setflags(filename, False, True)
1807 else:
1802 else:
1808 self.wvfs.setflags(filename, False, False)
1803 self.wvfs.setflags(filename, False, False)
1809 return len(data)
1804 return len(data)
1810
1805
1811 def wwritedata(self, filename, data):
1806 def wwritedata(self, filename, data):
1812 return self._filter(self._decodefilterpats, filename, data)
1807 return self._filter(self._decodefilterpats, filename, data)
1813
1808
1814 def currenttransaction(self):
1809 def currenttransaction(self):
1815 """return the current transaction or None if non exists"""
1810 """return the current transaction or None if non exists"""
1816 if self._transref:
1811 if self._transref:
1817 tr = self._transref()
1812 tr = self._transref()
1818 else:
1813 else:
1819 tr = None
1814 tr = None
1820
1815
1821 if tr and tr.running():
1816 if tr and tr.running():
1822 return tr
1817 return tr
1823 return None
1818 return None
1824
1819
1825 def transaction(self, desc, report=None):
1820 def transaction(self, desc, report=None):
1826 if (self.ui.configbool('devel', 'all-warnings')
1821 if (self.ui.configbool('devel', 'all-warnings')
1827 or self.ui.configbool('devel', 'check-locks')):
1822 or self.ui.configbool('devel', 'check-locks')):
1828 if self._currentlock(self._lockref) is None:
1823 if self._currentlock(self._lockref) is None:
1829 raise error.ProgrammingError('transaction requires locking')
1824 raise error.ProgrammingError('transaction requires locking')
1830 tr = self.currenttransaction()
1825 tr = self.currenttransaction()
1831 if tr is not None:
1826 if tr is not None:
1832 return tr.nest(name=desc)
1827 return tr.nest(name=desc)
1833
1828
1834 # abort here if the journal already exists
1829 # abort here if the journal already exists
1835 if self.svfs.exists("journal"):
1830 if self.svfs.exists("journal"):
1836 raise error.RepoError(
1831 raise error.RepoError(
1837 _("abandoned transaction found"),
1832 _("abandoned transaction found"),
1838 hint=_("run 'hg recover' to clean up transaction"))
1833 hint=_("run 'hg recover' to clean up transaction"))
1839
1834
1840 idbase = "%.40f#%f" % (random.random(), time.time())
1835 idbase = "%.40f#%f" % (random.random(), time.time())
1841 ha = hex(hashlib.sha1(idbase).digest())
1836 ha = hex(hashlib.sha1(idbase).digest())
1842 txnid = 'TXN:' + ha
1837 txnid = 'TXN:' + ha
1843 self.hook('pretxnopen', throw=True, txnname=desc, txnid=txnid)
1838 self.hook('pretxnopen', throw=True, txnname=desc, txnid=txnid)
1844
1839
1845 self._writejournal(desc)
1840 self._writejournal(desc)
1846 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
1841 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
1847 if report:
1842 if report:
1848 rp = report
1843 rp = report
1849 else:
1844 else:
1850 rp = self.ui.warn
1845 rp = self.ui.warn
1851 vfsmap = {'plain': self.vfs, 'store': self.svfs} # root of .hg/
1846 vfsmap = {'plain': self.vfs, 'store': self.svfs} # root of .hg/
1852 # we must avoid cyclic reference between repo and transaction.
1847 # we must avoid cyclic reference between repo and transaction.
1853 reporef = weakref.ref(self)
1848 reporef = weakref.ref(self)
1854 # Code to track tag movement
1849 # Code to track tag movement
1855 #
1850 #
1856 # Since tags are all handled as file content, it is actually quite hard
1851 # Since tags are all handled as file content, it is actually quite hard
1857 # to track these movement from a code perspective. So we fallback to a
1852 # to track these movement from a code perspective. So we fallback to a
1858 # tracking at the repository level. One could envision to track changes
1853 # tracking at the repository level. One could envision to track changes
1859 # to the '.hgtags' file through changegroup apply but that fails to
1854 # to the '.hgtags' file through changegroup apply but that fails to
1860 # cope with case where transaction expose new heads without changegroup
1855 # cope with case where transaction expose new heads without changegroup
1861 # being involved (eg: phase movement).
1856 # being involved (eg: phase movement).
1862 #
1857 #
1863 # For now, We gate the feature behind a flag since this likely comes
1858 # For now, We gate the feature behind a flag since this likely comes
1864 # with performance impacts. The current code run more often than needed
1859 # with performance impacts. The current code run more often than needed
1865 # and do not use caches as much as it could. The current focus is on
1860 # and do not use caches as much as it could. The current focus is on
1866 # the behavior of the feature so we disable it by default. The flag
1861 # the behavior of the feature so we disable it by default. The flag
1867 # will be removed when we are happy with the performance impact.
1862 # will be removed when we are happy with the performance impact.
1868 #
1863 #
1869 # Once this feature is no longer experimental move the following
1864 # Once this feature is no longer experimental move the following
1870 # documentation to the appropriate help section:
1865 # documentation to the appropriate help section:
1871 #
1866 #
1872 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
1867 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
1873 # tags (new or changed or deleted tags). In addition the details of
1868 # tags (new or changed or deleted tags). In addition the details of
1874 # these changes are made available in a file at:
1869 # these changes are made available in a file at:
1875 # ``REPOROOT/.hg/changes/tags.changes``.
1870 # ``REPOROOT/.hg/changes/tags.changes``.
1876 # Make sure you check for HG_TAG_MOVED before reading that file as it
1871 # Make sure you check for HG_TAG_MOVED before reading that file as it
1877 # might exist from a previous transaction even if no tag were touched
1872 # might exist from a previous transaction even if no tag were touched
1878 # in this one. Changes are recorded in a line base format::
1873 # in this one. Changes are recorded in a line base format::
1879 #
1874 #
1880 # <action> <hex-node> <tag-name>\n
1875 # <action> <hex-node> <tag-name>\n
1881 #
1876 #
1882 # Actions are defined as follow:
1877 # Actions are defined as follow:
1883 # "-R": tag is removed,
1878 # "-R": tag is removed,
1884 # "+A": tag is added,
1879 # "+A": tag is added,
1885 # "-M": tag is moved (old value),
1880 # "-M": tag is moved (old value),
1886 # "+M": tag is moved (new value),
1881 # "+M": tag is moved (new value),
1887 tracktags = lambda x: None
1882 tracktags = lambda x: None
1888 # experimental config: experimental.hook-track-tags
1883 # experimental config: experimental.hook-track-tags
1889 shouldtracktags = self.ui.configbool('experimental', 'hook-track-tags')
1884 shouldtracktags = self.ui.configbool('experimental', 'hook-track-tags')
1890 if desc != 'strip' and shouldtracktags:
1885 if desc != 'strip' and shouldtracktags:
1891 oldheads = self.changelog.headrevs()
1886 oldheads = self.changelog.headrevs()
1892 def tracktags(tr2):
1887 def tracktags(tr2):
1893 repo = reporef()
1888 repo = reporef()
1894 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
1889 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
1895 newheads = repo.changelog.headrevs()
1890 newheads = repo.changelog.headrevs()
1896 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
1891 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
1897 # notes: we compare lists here.
1892 # notes: we compare lists here.
1898 # As we do it only once buiding set would not be cheaper
1893 # As we do it only once buiding set would not be cheaper
1899 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
1894 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
1900 if changes:
1895 if changes:
1901 tr2.hookargs['tag_moved'] = '1'
1896 tr2.hookargs['tag_moved'] = '1'
1902 with repo.vfs('changes/tags.changes', 'w',
1897 with repo.vfs('changes/tags.changes', 'w',
1903 atomictemp=True) as changesfile:
1898 atomictemp=True) as changesfile:
1904 # note: we do not register the file to the transaction
1899 # note: we do not register the file to the transaction
1905 # because we needs it to still exist on the transaction
1900 # because we needs it to still exist on the transaction
1906 # is close (for txnclose hooks)
1901 # is close (for txnclose hooks)
1907 tagsmod.writediff(changesfile, changes)
1902 tagsmod.writediff(changesfile, changes)
1908 def validate(tr2):
1903 def validate(tr2):
1909 """will run pre-closing hooks"""
1904 """will run pre-closing hooks"""
1910 # XXX the transaction API is a bit lacking here so we take a hacky
1905 # XXX the transaction API is a bit lacking here so we take a hacky
1911 # path for now
1906 # path for now
1912 #
1907 #
1913 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
1908 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
1914 # dict is copied before these run. In addition we needs the data
1909 # dict is copied before these run. In addition we needs the data
1915 # available to in memory hooks too.
1910 # available to in memory hooks too.
1916 #
1911 #
1917 # Moreover, we also need to make sure this runs before txnclose
1912 # Moreover, we also need to make sure this runs before txnclose
1918 # hooks and there is no "pending" mechanism that would execute
1913 # hooks and there is no "pending" mechanism that would execute
1919 # logic only if hooks are about to run.
1914 # logic only if hooks are about to run.
1920 #
1915 #
1921 # Fixing this limitation of the transaction is also needed to track
1916 # Fixing this limitation of the transaction is also needed to track
1922 # other families of changes (bookmarks, phases, obsolescence).
1917 # other families of changes (bookmarks, phases, obsolescence).
1923 #
1918 #
1924 # This will have to be fixed before we remove the experimental
1919 # This will have to be fixed before we remove the experimental
1925 # gating.
1920 # gating.
1926 tracktags(tr2)
1921 tracktags(tr2)
1927 repo = reporef()
1922 repo = reporef()
1928 if repo.ui.configbool('experimental', 'single-head-per-branch'):
1923 if repo.ui.configbool('experimental', 'single-head-per-branch'):
1929 scmutil.enforcesinglehead(repo, tr2, desc)
1924 scmutil.enforcesinglehead(repo, tr2, desc)
1930 if hook.hashook(repo.ui, 'pretxnclose-bookmark'):
1925 if hook.hashook(repo.ui, 'pretxnclose-bookmark'):
1931 for name, (old, new) in sorted(tr.changes['bookmarks'].items()):
1926 for name, (old, new) in sorted(tr.changes['bookmarks'].items()):
1932 args = tr.hookargs.copy()
1927 args = tr.hookargs.copy()
1933 args.update(bookmarks.preparehookargs(name, old, new))
1928 args.update(bookmarks.preparehookargs(name, old, new))
1934 repo.hook('pretxnclose-bookmark', throw=True,
1929 repo.hook('pretxnclose-bookmark', throw=True,
1935 **pycompat.strkwargs(args))
1930 **pycompat.strkwargs(args))
1936 if hook.hashook(repo.ui, 'pretxnclose-phase'):
1931 if hook.hashook(repo.ui, 'pretxnclose-phase'):
1937 cl = repo.unfiltered().changelog
1932 cl = repo.unfiltered().changelog
1938 for rev, (old, new) in tr.changes['phases'].items():
1933 for rev, (old, new) in tr.changes['phases'].items():
1939 args = tr.hookargs.copy()
1934 args = tr.hookargs.copy()
1940 node = hex(cl.node(rev))
1935 node = hex(cl.node(rev))
1941 args.update(phases.preparehookargs(node, old, new))
1936 args.update(phases.preparehookargs(node, old, new))
1942 repo.hook('pretxnclose-phase', throw=True,
1937 repo.hook('pretxnclose-phase', throw=True,
1943 **pycompat.strkwargs(args))
1938 **pycompat.strkwargs(args))
1944
1939
1945 repo.hook('pretxnclose', throw=True,
1940 repo.hook('pretxnclose', throw=True,
1946 **pycompat.strkwargs(tr.hookargs))
1941 **pycompat.strkwargs(tr.hookargs))
1947 def releasefn(tr, success):
1942 def releasefn(tr, success):
1948 repo = reporef()
1943 repo = reporef()
1949 if repo is None:
1944 if repo is None:
1950 # If the repo has been GC'd (and this release function is being
1945 # If the repo has been GC'd (and this release function is being
1951 # called from transaction.__del__), there's not much we can do,
1946 # called from transaction.__del__), there's not much we can do,
1952 # so just leave the unfinished transaction there and let the
1947 # so just leave the unfinished transaction there and let the
1953 # user run `hg recover`.
1948 # user run `hg recover`.
1954 return
1949 return
1955 if success:
1950 if success:
1956 # this should be explicitly invoked here, because
1951 # this should be explicitly invoked here, because
1957 # in-memory changes aren't written out at closing
1952 # in-memory changes aren't written out at closing
1958 # transaction, if tr.addfilegenerator (via
1953 # transaction, if tr.addfilegenerator (via
1959 # dirstate.write or so) isn't invoked while
1954 # dirstate.write or so) isn't invoked while
1960 # transaction running
1955 # transaction running
1961 repo.dirstate.write(None)
1956 repo.dirstate.write(None)
1962 else:
1957 else:
1963 # discard all changes (including ones already written
1958 # discard all changes (including ones already written
1964 # out) in this transaction
1959 # out) in this transaction
1965 narrowspec.restorebackup(self, 'journal.narrowspec')
1960 narrowspec.restorebackup(self, 'journal.narrowspec')
1966 narrowspec.restorewcbackup(self, 'journal.narrowspec.dirstate')
1961 narrowspec.restorewcbackup(self, 'journal.narrowspec.dirstate')
1967 repo.dirstate.restorebackup(None, 'journal.dirstate')
1962 repo.dirstate.restorebackup(None, 'journal.dirstate')
1968
1963
1969 repo.invalidate(clearfilecache=True)
1964 repo.invalidate(clearfilecache=True)
1970
1965
1971 tr = transaction.transaction(rp, self.svfs, vfsmap,
1966 tr = transaction.transaction(rp, self.svfs, vfsmap,
1972 "journal",
1967 "journal",
1973 "undo",
1968 "undo",
1974 aftertrans(renames),
1969 aftertrans(renames),
1975 self.store.createmode,
1970 self.store.createmode,
1976 validator=validate,
1971 validator=validate,
1977 releasefn=releasefn,
1972 releasefn=releasefn,
1978 checkambigfiles=_cachedfiles,
1973 checkambigfiles=_cachedfiles,
1979 name=desc)
1974 name=desc)
1980 tr.changes['origrepolen'] = len(self)
1975 tr.changes['origrepolen'] = len(self)
1981 tr.changes['obsmarkers'] = set()
1976 tr.changes['obsmarkers'] = set()
1982 tr.changes['phases'] = {}
1977 tr.changes['phases'] = {}
1983 tr.changes['bookmarks'] = {}
1978 tr.changes['bookmarks'] = {}
1984
1979
1985 tr.hookargs['txnid'] = txnid
1980 tr.hookargs['txnid'] = txnid
1986 tr.hookargs['txnname'] = desc
1981 tr.hookargs['txnname'] = desc
1987 # note: writing the fncache only during finalize mean that the file is
1982 # note: writing the fncache only during finalize mean that the file is
1988 # outdated when running hooks. As fncache is used for streaming clone,
1983 # outdated when running hooks. As fncache is used for streaming clone,
1989 # this is not expected to break anything that happen during the hooks.
1984 # this is not expected to break anything that happen during the hooks.
1990 tr.addfinalize('flush-fncache', self.store.write)
1985 tr.addfinalize('flush-fncache', self.store.write)
1991 def txnclosehook(tr2):
1986 def txnclosehook(tr2):
1992 """To be run if transaction is successful, will schedule a hook run
1987 """To be run if transaction is successful, will schedule a hook run
1993 """
1988 """
1994 # Don't reference tr2 in hook() so we don't hold a reference.
1989 # Don't reference tr2 in hook() so we don't hold a reference.
1995 # This reduces memory consumption when there are multiple
1990 # This reduces memory consumption when there are multiple
1996 # transactions per lock. This can likely go away if issue5045
1991 # transactions per lock. This can likely go away if issue5045
1997 # fixes the function accumulation.
1992 # fixes the function accumulation.
1998 hookargs = tr2.hookargs
1993 hookargs = tr2.hookargs
1999
1994
2000 def hookfunc():
1995 def hookfunc():
2001 repo = reporef()
1996 repo = reporef()
2002 if hook.hashook(repo.ui, 'txnclose-bookmark'):
1997 if hook.hashook(repo.ui, 'txnclose-bookmark'):
2003 bmchanges = sorted(tr.changes['bookmarks'].items())
1998 bmchanges = sorted(tr.changes['bookmarks'].items())
2004 for name, (old, new) in bmchanges:
1999 for name, (old, new) in bmchanges:
2005 args = tr.hookargs.copy()
2000 args = tr.hookargs.copy()
2006 args.update(bookmarks.preparehookargs(name, old, new))
2001 args.update(bookmarks.preparehookargs(name, old, new))
2007 repo.hook('txnclose-bookmark', throw=False,
2002 repo.hook('txnclose-bookmark', throw=False,
2008 **pycompat.strkwargs(args))
2003 **pycompat.strkwargs(args))
2009
2004
2010 if hook.hashook(repo.ui, 'txnclose-phase'):
2005 if hook.hashook(repo.ui, 'txnclose-phase'):
2011 cl = repo.unfiltered().changelog
2006 cl = repo.unfiltered().changelog
2012 phasemv = sorted(tr.changes['phases'].items())
2007 phasemv = sorted(tr.changes['phases'].items())
2013 for rev, (old, new) in phasemv:
2008 for rev, (old, new) in phasemv:
2014 args = tr.hookargs.copy()
2009 args = tr.hookargs.copy()
2015 node = hex(cl.node(rev))
2010 node = hex(cl.node(rev))
2016 args.update(phases.preparehookargs(node, old, new))
2011 args.update(phases.preparehookargs(node, old, new))
2017 repo.hook('txnclose-phase', throw=False,
2012 repo.hook('txnclose-phase', throw=False,
2018 **pycompat.strkwargs(args))
2013 **pycompat.strkwargs(args))
2019
2014
2020 repo.hook('txnclose', throw=False,
2015 repo.hook('txnclose', throw=False,
2021 **pycompat.strkwargs(hookargs))
2016 **pycompat.strkwargs(hookargs))
2022 reporef()._afterlock(hookfunc)
2017 reporef()._afterlock(hookfunc)
2023 tr.addfinalize('txnclose-hook', txnclosehook)
2018 tr.addfinalize('txnclose-hook', txnclosehook)
2024 # Include a leading "-" to make it happen before the transaction summary
2019 # Include a leading "-" to make it happen before the transaction summary
2025 # reports registered via scmutil.registersummarycallback() whose names
2020 # reports registered via scmutil.registersummarycallback() whose names
2026 # are 00-txnreport etc. That way, the caches will be warm when the
2021 # are 00-txnreport etc. That way, the caches will be warm when the
2027 # callbacks run.
2022 # callbacks run.
2028 tr.addpostclose('-warm-cache', self._buildcacheupdater(tr))
2023 tr.addpostclose('-warm-cache', self._buildcacheupdater(tr))
2029 def txnaborthook(tr2):
2024 def txnaborthook(tr2):
2030 """To be run if transaction is aborted
2025 """To be run if transaction is aborted
2031 """
2026 """
2032 reporef().hook('txnabort', throw=False,
2027 reporef().hook('txnabort', throw=False,
2033 **pycompat.strkwargs(tr2.hookargs))
2028 **pycompat.strkwargs(tr2.hookargs))
2034 tr.addabort('txnabort-hook', txnaborthook)
2029 tr.addabort('txnabort-hook', txnaborthook)
2035 # avoid eager cache invalidation. in-memory data should be identical
2030 # avoid eager cache invalidation. in-memory data should be identical
2036 # to stored data if transaction has no error.
2031 # to stored data if transaction has no error.
2037 tr.addpostclose('refresh-filecachestats', self._refreshfilecachestats)
2032 tr.addpostclose('refresh-filecachestats', self._refreshfilecachestats)
2038 self._transref = weakref.ref(tr)
2033 self._transref = weakref.ref(tr)
2039 scmutil.registersummarycallback(self, tr, desc)
2034 scmutil.registersummarycallback(self, tr, desc)
2040 return tr
2035 return tr
2041
2036
2042 def _journalfiles(self):
2037 def _journalfiles(self):
2043 return ((self.svfs, 'journal'),
2038 return ((self.svfs, 'journal'),
2044 (self.svfs, 'journal.narrowspec'),
2039 (self.svfs, 'journal.narrowspec'),
2045 (self.vfs, 'journal.narrowspec.dirstate'),
2040 (self.vfs, 'journal.narrowspec.dirstate'),
2046 (self.vfs, 'journal.dirstate'),
2041 (self.vfs, 'journal.dirstate'),
2047 (self.vfs, 'journal.branch'),
2042 (self.vfs, 'journal.branch'),
2048 (self.vfs, 'journal.desc'),
2043 (self.vfs, 'journal.desc'),
2049 (bookmarks.bookmarksvfs(self), 'journal.bookmarks'),
2044 (bookmarks.bookmarksvfs(self), 'journal.bookmarks'),
2050 (self.svfs, 'journal.phaseroots'))
2045 (self.svfs, 'journal.phaseroots'))
2051
2046
2052 def undofiles(self):
2047 def undofiles(self):
2053 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
2048 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
2054
2049
2055 @unfilteredmethod
2050 @unfilteredmethod
2056 def _writejournal(self, desc):
2051 def _writejournal(self, desc):
2057 self.dirstate.savebackup(None, 'journal.dirstate')
2052 self.dirstate.savebackup(None, 'journal.dirstate')
2058 narrowspec.savewcbackup(self, 'journal.narrowspec.dirstate')
2053 narrowspec.savewcbackup(self, 'journal.narrowspec.dirstate')
2059 narrowspec.savebackup(self, 'journal.narrowspec')
2054 narrowspec.savebackup(self, 'journal.narrowspec')
2060 self.vfs.write("journal.branch",
2055 self.vfs.write("journal.branch",
2061 encoding.fromlocal(self.dirstate.branch()))
2056 encoding.fromlocal(self.dirstate.branch()))
2062 self.vfs.write("journal.desc",
2057 self.vfs.write("journal.desc",
2063 "%d\n%s\n" % (len(self), desc))
2058 "%d\n%s\n" % (len(self), desc))
2064 bookmarksvfs = bookmarks.bookmarksvfs(self)
2059 bookmarksvfs = bookmarks.bookmarksvfs(self)
2065 bookmarksvfs.write("journal.bookmarks",
2060 bookmarksvfs.write("journal.bookmarks",
2066 bookmarksvfs.tryread("bookmarks"))
2061 bookmarksvfs.tryread("bookmarks"))
2067 self.svfs.write("journal.phaseroots",
2062 self.svfs.write("journal.phaseroots",
2068 self.svfs.tryread("phaseroots"))
2063 self.svfs.tryread("phaseroots"))
2069
2064
2070 def recover(self):
2065 def recover(self):
2071 with self.lock():
2066 with self.lock():
2072 if self.svfs.exists("journal"):
2067 if self.svfs.exists("journal"):
2073 self.ui.status(_("rolling back interrupted transaction\n"))
2068 self.ui.status(_("rolling back interrupted transaction\n"))
2074 vfsmap = {'': self.svfs,
2069 vfsmap = {'': self.svfs,
2075 'plain': self.vfs,}
2070 'plain': self.vfs,}
2076 transaction.rollback(self.svfs, vfsmap, "journal",
2071 transaction.rollback(self.svfs, vfsmap, "journal",
2077 self.ui.warn,
2072 self.ui.warn,
2078 checkambigfiles=_cachedfiles)
2073 checkambigfiles=_cachedfiles)
2079 self.invalidate()
2074 self.invalidate()
2080 return True
2075 return True
2081 else:
2076 else:
2082 self.ui.warn(_("no interrupted transaction available\n"))
2077 self.ui.warn(_("no interrupted transaction available\n"))
2083 return False
2078 return False
2084
2079
2085 def rollback(self, dryrun=False, force=False):
2080 def rollback(self, dryrun=False, force=False):
2086 wlock = lock = dsguard = None
2081 wlock = lock = dsguard = None
2087 try:
2082 try:
2088 wlock = self.wlock()
2083 wlock = self.wlock()
2089 lock = self.lock()
2084 lock = self.lock()
2090 if self.svfs.exists("undo"):
2085 if self.svfs.exists("undo"):
2091 dsguard = dirstateguard.dirstateguard(self, 'rollback')
2086 dsguard = dirstateguard.dirstateguard(self, 'rollback')
2092
2087
2093 return self._rollback(dryrun, force, dsguard)
2088 return self._rollback(dryrun, force, dsguard)
2094 else:
2089 else:
2095 self.ui.warn(_("no rollback information available\n"))
2090 self.ui.warn(_("no rollback information available\n"))
2096 return 1
2091 return 1
2097 finally:
2092 finally:
2098 release(dsguard, lock, wlock)
2093 release(dsguard, lock, wlock)
2099
2094
2100 @unfilteredmethod # Until we get smarter cache management
2095 @unfilteredmethod # Until we get smarter cache management
2101 def _rollback(self, dryrun, force, dsguard):
2096 def _rollback(self, dryrun, force, dsguard):
2102 ui = self.ui
2097 ui = self.ui
2103 try:
2098 try:
2104 args = self.vfs.read('undo.desc').splitlines()
2099 args = self.vfs.read('undo.desc').splitlines()
2105 (oldlen, desc, detail) = (int(args[0]), args[1], None)
2100 (oldlen, desc, detail) = (int(args[0]), args[1], None)
2106 if len(args) >= 3:
2101 if len(args) >= 3:
2107 detail = args[2]
2102 detail = args[2]
2108 oldtip = oldlen - 1
2103 oldtip = oldlen - 1
2109
2104
2110 if detail and ui.verbose:
2105 if detail and ui.verbose:
2111 msg = (_('repository tip rolled back to revision %d'
2106 msg = (_('repository tip rolled back to revision %d'
2112 ' (undo %s: %s)\n')
2107 ' (undo %s: %s)\n')
2113 % (oldtip, desc, detail))
2108 % (oldtip, desc, detail))
2114 else:
2109 else:
2115 msg = (_('repository tip rolled back to revision %d'
2110 msg = (_('repository tip rolled back to revision %d'
2116 ' (undo %s)\n')
2111 ' (undo %s)\n')
2117 % (oldtip, desc))
2112 % (oldtip, desc))
2118 except IOError:
2113 except IOError:
2119 msg = _('rolling back unknown transaction\n')
2114 msg = _('rolling back unknown transaction\n')
2120 desc = None
2115 desc = None
2121
2116
2122 if not force and self['.'] != self['tip'] and desc == 'commit':
2117 if not force and self['.'] != self['tip'] and desc == 'commit':
2123 raise error.Abort(
2118 raise error.Abort(
2124 _('rollback of last commit while not checked out '
2119 _('rollback of last commit while not checked out '
2125 'may lose data'), hint=_('use -f to force'))
2120 'may lose data'), hint=_('use -f to force'))
2126
2121
2127 ui.status(msg)
2122 ui.status(msg)
2128 if dryrun:
2123 if dryrun:
2129 return 0
2124 return 0
2130
2125
2131 parents = self.dirstate.parents()
2126 parents = self.dirstate.parents()
2132 self.destroying()
2127 self.destroying()
2133 vfsmap = {'plain': self.vfs, '': self.svfs}
2128 vfsmap = {'plain': self.vfs, '': self.svfs}
2134 transaction.rollback(self.svfs, vfsmap, 'undo', ui.warn,
2129 transaction.rollback(self.svfs, vfsmap, 'undo', ui.warn,
2135 checkambigfiles=_cachedfiles)
2130 checkambigfiles=_cachedfiles)
2136 bookmarksvfs = bookmarks.bookmarksvfs(self)
2131 bookmarksvfs = bookmarks.bookmarksvfs(self)
2137 if bookmarksvfs.exists('undo.bookmarks'):
2132 if bookmarksvfs.exists('undo.bookmarks'):
2138 bookmarksvfs.rename('undo.bookmarks', 'bookmarks', checkambig=True)
2133 bookmarksvfs.rename('undo.bookmarks', 'bookmarks', checkambig=True)
2139 if self.svfs.exists('undo.phaseroots'):
2134 if self.svfs.exists('undo.phaseroots'):
2140 self.svfs.rename('undo.phaseroots', 'phaseroots', checkambig=True)
2135 self.svfs.rename('undo.phaseroots', 'phaseroots', checkambig=True)
2141 self.invalidate()
2136 self.invalidate()
2142
2137
2143 parentgone = any(p not in self.changelog.nodemap for p in parents)
2138 parentgone = any(p not in self.changelog.nodemap for p in parents)
2144 if parentgone:
2139 if parentgone:
2145 # prevent dirstateguard from overwriting already restored one
2140 # prevent dirstateguard from overwriting already restored one
2146 dsguard.close()
2141 dsguard.close()
2147
2142
2148 narrowspec.restorebackup(self, 'undo.narrowspec')
2143 narrowspec.restorebackup(self, 'undo.narrowspec')
2149 narrowspec.restorewcbackup(self, 'undo.narrowspec.dirstate')
2144 narrowspec.restorewcbackup(self, 'undo.narrowspec.dirstate')
2150 self.dirstate.restorebackup(None, 'undo.dirstate')
2145 self.dirstate.restorebackup(None, 'undo.dirstate')
2151 try:
2146 try:
2152 branch = self.vfs.read('undo.branch')
2147 branch = self.vfs.read('undo.branch')
2153 self.dirstate.setbranch(encoding.tolocal(branch))
2148 self.dirstate.setbranch(encoding.tolocal(branch))
2154 except IOError:
2149 except IOError:
2155 ui.warn(_('named branch could not be reset: '
2150 ui.warn(_('named branch could not be reset: '
2156 'current branch is still \'%s\'\n')
2151 'current branch is still \'%s\'\n')
2157 % self.dirstate.branch())
2152 % self.dirstate.branch())
2158
2153
2159 parents = tuple([p.rev() for p in self[None].parents()])
2154 parents = tuple([p.rev() for p in self[None].parents()])
2160 if len(parents) > 1:
2155 if len(parents) > 1:
2161 ui.status(_('working directory now based on '
2156 ui.status(_('working directory now based on '
2162 'revisions %d and %d\n') % parents)
2157 'revisions %d and %d\n') % parents)
2163 else:
2158 else:
2164 ui.status(_('working directory now based on '
2159 ui.status(_('working directory now based on '
2165 'revision %d\n') % parents)
2160 'revision %d\n') % parents)
2166 mergemod.mergestate.clean(self, self['.'].node())
2161 mergemod.mergestate.clean(self, self['.'].node())
2167
2162
2168 # TODO: if we know which new heads may result from this rollback, pass
2163 # TODO: if we know which new heads may result from this rollback, pass
2169 # them to destroy(), which will prevent the branchhead cache from being
2164 # them to destroy(), which will prevent the branchhead cache from being
2170 # invalidated.
2165 # invalidated.
2171 self.destroyed()
2166 self.destroyed()
2172 return 0
2167 return 0
2173
2168
2174 def _buildcacheupdater(self, newtransaction):
2169 def _buildcacheupdater(self, newtransaction):
2175 """called during transaction to build the callback updating cache
2170 """called during transaction to build the callback updating cache
2176
2171
2177 Lives on the repository to help extension who might want to augment
2172 Lives on the repository to help extension who might want to augment
2178 this logic. For this purpose, the created transaction is passed to the
2173 this logic. For this purpose, the created transaction is passed to the
2179 method.
2174 method.
2180 """
2175 """
2181 # we must avoid cyclic reference between repo and transaction.
2176 # we must avoid cyclic reference between repo and transaction.
2182 reporef = weakref.ref(self)
2177 reporef = weakref.ref(self)
2183 def updater(tr):
2178 def updater(tr):
2184 repo = reporef()
2179 repo = reporef()
2185 repo.updatecaches(tr)
2180 repo.updatecaches(tr)
2186 return updater
2181 return updater
2187
2182
2188 @unfilteredmethod
2183 @unfilteredmethod
2189 def updatecaches(self, tr=None, full=False):
2184 def updatecaches(self, tr=None, full=False):
2190 """warm appropriate caches
2185 """warm appropriate caches
2191
2186
2192 If this function is called after a transaction closed. The transaction
2187 If this function is called after a transaction closed. The transaction
2193 will be available in the 'tr' argument. This can be used to selectively
2188 will be available in the 'tr' argument. This can be used to selectively
2194 update caches relevant to the changes in that transaction.
2189 update caches relevant to the changes in that transaction.
2195
2190
2196 If 'full' is set, make sure all caches the function knows about have
2191 If 'full' is set, make sure all caches the function knows about have
2197 up-to-date data. Even the ones usually loaded more lazily.
2192 up-to-date data. Even the ones usually loaded more lazily.
2198 """
2193 """
2199 if tr is not None and tr.hookargs.get('source') == 'strip':
2194 if tr is not None and tr.hookargs.get('source') == 'strip':
2200 # During strip, many caches are invalid but
2195 # During strip, many caches are invalid but
2201 # later call to `destroyed` will refresh them.
2196 # later call to `destroyed` will refresh them.
2202 return
2197 return
2203
2198
2204 if tr is None or tr.changes['origrepolen'] < len(self):
2199 if tr is None or tr.changes['origrepolen'] < len(self):
2205 # accessing the 'ser ved' branchmap should refresh all the others,
2200 # accessing the 'ser ved' branchmap should refresh all the others,
2206 self.ui.debug('updating the branch cache\n')
2201 self.ui.debug('updating the branch cache\n')
2207 self.filtered('served').branchmap()
2202 self.filtered('served').branchmap()
2208 self.filtered('served.hidden').branchmap()
2203 self.filtered('served.hidden').branchmap()
2209
2204
2210 if full:
2205 if full:
2211 unfi = self.unfiltered()
2206 unfi = self.unfiltered()
2212 rbc = unfi.revbranchcache()
2207 rbc = unfi.revbranchcache()
2213 for r in unfi.changelog:
2208 for r in unfi.changelog:
2214 rbc.branchinfo(r)
2209 rbc.branchinfo(r)
2215 rbc.write()
2210 rbc.write()
2216
2211
2217 # ensure the working copy parents are in the manifestfulltextcache
2212 # ensure the working copy parents are in the manifestfulltextcache
2218 for ctx in self['.'].parents():
2213 for ctx in self['.'].parents():
2219 ctx.manifest() # accessing the manifest is enough
2214 ctx.manifest() # accessing the manifest is enough
2220
2215
2221 # accessing fnode cache warms the cache
2216 # accessing fnode cache warms the cache
2222 tagsmod.fnoderevs(self.ui, unfi, unfi.changelog.revs())
2217 tagsmod.fnoderevs(self.ui, unfi, unfi.changelog.revs())
2223 # accessing tags warm the cache
2218 # accessing tags warm the cache
2224 self.tags()
2219 self.tags()
2225 self.filtered('served').tags()
2220 self.filtered('served').tags()
2226
2221
2227 # The `full` arg is documented as updating even the lazily-loaded
2222 # The `full` arg is documented as updating even the lazily-loaded
2228 # caches immediately, so we're forcing a write to cause these caches
2223 # caches immediately, so we're forcing a write to cause these caches
2229 # to be warmed up even if they haven't explicitly been requested
2224 # to be warmed up even if they haven't explicitly been requested
2230 # yet (if they've never been used by hg, they won't ever have been
2225 # yet (if they've never been used by hg, they won't ever have been
2231 # written, even if they're a subset of another kind of cache that
2226 # written, even if they're a subset of another kind of cache that
2232 # *has* been used).
2227 # *has* been used).
2233 for filt in repoview.filtertable.keys():
2228 for filt in repoview.filtertable.keys():
2234 filtered = self.filtered(filt)
2229 filtered = self.filtered(filt)
2235 filtered.branchmap().write(filtered)
2230 filtered.branchmap().write(filtered)
2236
2231
2237 def invalidatecaches(self):
2232 def invalidatecaches(self):
2238
2233
2239 if r'_tagscache' in vars(self):
2234 if r'_tagscache' in vars(self):
2240 # can't use delattr on proxy
2235 # can't use delattr on proxy
2241 del self.__dict__[r'_tagscache']
2236 del self.__dict__[r'_tagscache']
2242
2237
2243 self._branchcaches.clear()
2238 self._branchcaches.clear()
2244 self.invalidatevolatilesets()
2239 self.invalidatevolatilesets()
2245 self._sparsesignaturecache.clear()
2240 self._sparsesignaturecache.clear()
2246
2241
2247 def invalidatevolatilesets(self):
2242 def invalidatevolatilesets(self):
2248 self.filteredrevcache.clear()
2243 self.filteredrevcache.clear()
2249 obsolete.clearobscaches(self)
2244 obsolete.clearobscaches(self)
2250
2245
2251 def invalidatedirstate(self):
2246 def invalidatedirstate(self):
2252 '''Invalidates the dirstate, causing the next call to dirstate
2247 '''Invalidates the dirstate, causing the next call to dirstate
2253 to check if it was modified since the last time it was read,
2248 to check if it was modified since the last time it was read,
2254 rereading it if it has.
2249 rereading it if it has.
2255
2250
2256 This is different to dirstate.invalidate() that it doesn't always
2251 This is different to dirstate.invalidate() that it doesn't always
2257 rereads the dirstate. Use dirstate.invalidate() if you want to
2252 rereads the dirstate. Use dirstate.invalidate() if you want to
2258 explicitly read the dirstate again (i.e. restoring it to a previous
2253 explicitly read the dirstate again (i.e. restoring it to a previous
2259 known good state).'''
2254 known good state).'''
2260 if hasunfilteredcache(self, r'dirstate'):
2255 if hasunfilteredcache(self, r'dirstate'):
2261 for k in self.dirstate._filecache:
2256 for k in self.dirstate._filecache:
2262 try:
2257 try:
2263 delattr(self.dirstate, k)
2258 delattr(self.dirstate, k)
2264 except AttributeError:
2259 except AttributeError:
2265 pass
2260 pass
2266 delattr(self.unfiltered(), r'dirstate')
2261 delattr(self.unfiltered(), r'dirstate')
2267
2262
2268 def invalidate(self, clearfilecache=False):
2263 def invalidate(self, clearfilecache=False):
2269 '''Invalidates both store and non-store parts other than dirstate
2264 '''Invalidates both store and non-store parts other than dirstate
2270
2265
2271 If a transaction is running, invalidation of store is omitted,
2266 If a transaction is running, invalidation of store is omitted,
2272 because discarding in-memory changes might cause inconsistency
2267 because discarding in-memory changes might cause inconsistency
2273 (e.g. incomplete fncache causes unintentional failure, but
2268 (e.g. incomplete fncache causes unintentional failure, but
2274 redundant one doesn't).
2269 redundant one doesn't).
2275 '''
2270 '''
2276 unfiltered = self.unfiltered() # all file caches are stored unfiltered
2271 unfiltered = self.unfiltered() # all file caches are stored unfiltered
2277 for k in list(self._filecache.keys()):
2272 for k in list(self._filecache.keys()):
2278 # dirstate is invalidated separately in invalidatedirstate()
2273 # dirstate is invalidated separately in invalidatedirstate()
2279 if k == 'dirstate':
2274 if k == 'dirstate':
2280 continue
2275 continue
2281 if (k == 'changelog' and
2276 if (k == 'changelog' and
2282 self.currenttransaction() and
2277 self.currenttransaction() and
2283 self.changelog._delayed):
2278 self.changelog._delayed):
2284 # The changelog object may store unwritten revisions. We don't
2279 # The changelog object may store unwritten revisions. We don't
2285 # want to lose them.
2280 # want to lose them.
2286 # TODO: Solve the problem instead of working around it.
2281 # TODO: Solve the problem instead of working around it.
2287 continue
2282 continue
2288
2283
2289 if clearfilecache:
2284 if clearfilecache:
2290 del self._filecache[k]
2285 del self._filecache[k]
2291 try:
2286 try:
2292 delattr(unfiltered, k)
2287 delattr(unfiltered, k)
2293 except AttributeError:
2288 except AttributeError:
2294 pass
2289 pass
2295 self.invalidatecaches()
2290 self.invalidatecaches()
2296 if not self.currenttransaction():
2291 if not self.currenttransaction():
2297 # TODO: Changing contents of store outside transaction
2292 # TODO: Changing contents of store outside transaction
2298 # causes inconsistency. We should make in-memory store
2293 # causes inconsistency. We should make in-memory store
2299 # changes detectable, and abort if changed.
2294 # changes detectable, and abort if changed.
2300 self.store.invalidatecaches()
2295 self.store.invalidatecaches()
2301
2296
2302 def invalidateall(self):
2297 def invalidateall(self):
2303 '''Fully invalidates both store and non-store parts, causing the
2298 '''Fully invalidates both store and non-store parts, causing the
2304 subsequent operation to reread any outside changes.'''
2299 subsequent operation to reread any outside changes.'''
2305 # extension should hook this to invalidate its caches
2300 # extension should hook this to invalidate its caches
2306 self.invalidate()
2301 self.invalidate()
2307 self.invalidatedirstate()
2302 self.invalidatedirstate()
2308
2303
2309 @unfilteredmethod
2304 @unfilteredmethod
2310 def _refreshfilecachestats(self, tr):
2305 def _refreshfilecachestats(self, tr):
2311 """Reload stats of cached files so that they are flagged as valid"""
2306 """Reload stats of cached files so that they are flagged as valid"""
2312 for k, ce in self._filecache.items():
2307 for k, ce in self._filecache.items():
2313 k = pycompat.sysstr(k)
2308 k = pycompat.sysstr(k)
2314 if k == r'dirstate' or k not in self.__dict__:
2309 if k == r'dirstate' or k not in self.__dict__:
2315 continue
2310 continue
2316 ce.refresh()
2311 ce.refresh()
2317
2312
2318 def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc,
2313 def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc,
2319 inheritchecker=None, parentenvvar=None):
2314 inheritchecker=None, parentenvvar=None):
2320 parentlock = None
2315 parentlock = None
2321 # the contents of parentenvvar are used by the underlying lock to
2316 # the contents of parentenvvar are used by the underlying lock to
2322 # determine whether it can be inherited
2317 # determine whether it can be inherited
2323 if parentenvvar is not None:
2318 if parentenvvar is not None:
2324 parentlock = encoding.environ.get(parentenvvar)
2319 parentlock = encoding.environ.get(parentenvvar)
2325
2320
2326 timeout = 0
2321 timeout = 0
2327 warntimeout = 0
2322 warntimeout = 0
2328 if wait:
2323 if wait:
2329 timeout = self.ui.configint("ui", "timeout")
2324 timeout = self.ui.configint("ui", "timeout")
2330 warntimeout = self.ui.configint("ui", "timeout.warn")
2325 warntimeout = self.ui.configint("ui", "timeout.warn")
2331 # internal config: ui.signal-safe-lock
2326 # internal config: ui.signal-safe-lock
2332 signalsafe = self.ui.configbool('ui', 'signal-safe-lock')
2327 signalsafe = self.ui.configbool('ui', 'signal-safe-lock')
2333
2328
2334 l = lockmod.trylock(self.ui, vfs, lockname, timeout, warntimeout,
2329 l = lockmod.trylock(self.ui, vfs, lockname, timeout, warntimeout,
2335 releasefn=releasefn,
2330 releasefn=releasefn,
2336 acquirefn=acquirefn, desc=desc,
2331 acquirefn=acquirefn, desc=desc,
2337 inheritchecker=inheritchecker,
2332 inheritchecker=inheritchecker,
2338 parentlock=parentlock,
2333 parentlock=parentlock,
2339 signalsafe=signalsafe)
2334 signalsafe=signalsafe)
2340 return l
2335 return l
2341
2336
2342 def _afterlock(self, callback):
2337 def _afterlock(self, callback):
2343 """add a callback to be run when the repository is fully unlocked
2338 """add a callback to be run when the repository is fully unlocked
2344
2339
2345 The callback will be executed when the outermost lock is released
2340 The callback will be executed when the outermost lock is released
2346 (with wlock being higher level than 'lock')."""
2341 (with wlock being higher level than 'lock')."""
2347 for ref in (self._wlockref, self._lockref):
2342 for ref in (self._wlockref, self._lockref):
2348 l = ref and ref()
2343 l = ref and ref()
2349 if l and l.held:
2344 if l and l.held:
2350 l.postrelease.append(callback)
2345 l.postrelease.append(callback)
2351 break
2346 break
2352 else: # no lock have been found.
2347 else: # no lock have been found.
2353 callback()
2348 callback()
2354
2349
2355 def lock(self, wait=True):
2350 def lock(self, wait=True):
2356 '''Lock the repository store (.hg/store) and return a weak reference
2351 '''Lock the repository store (.hg/store) and return a weak reference
2357 to the lock. Use this before modifying the store (e.g. committing or
2352 to the lock. Use this before modifying the store (e.g. committing or
2358 stripping). If you are opening a transaction, get a lock as well.)
2353 stripping). If you are opening a transaction, get a lock as well.)
2359
2354
2360 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2355 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2361 'wlock' first to avoid a dead-lock hazard.'''
2356 'wlock' first to avoid a dead-lock hazard.'''
2362 l = self._currentlock(self._lockref)
2357 l = self._currentlock(self._lockref)
2363 if l is not None:
2358 if l is not None:
2364 l.lock()
2359 l.lock()
2365 return l
2360 return l
2366
2361
2367 l = self._lock(vfs=self.svfs,
2362 l = self._lock(vfs=self.svfs,
2368 lockname="lock",
2363 lockname="lock",
2369 wait=wait,
2364 wait=wait,
2370 releasefn=None,
2365 releasefn=None,
2371 acquirefn=self.invalidate,
2366 acquirefn=self.invalidate,
2372 desc=_('repository %s') % self.origroot)
2367 desc=_('repository %s') % self.origroot)
2373 self._lockref = weakref.ref(l)
2368 self._lockref = weakref.ref(l)
2374 return l
2369 return l
2375
2370
2376 def _wlockchecktransaction(self):
2371 def _wlockchecktransaction(self):
2377 if self.currenttransaction() is not None:
2372 if self.currenttransaction() is not None:
2378 raise error.LockInheritanceContractViolation(
2373 raise error.LockInheritanceContractViolation(
2379 'wlock cannot be inherited in the middle of a transaction')
2374 'wlock cannot be inherited in the middle of a transaction')
2380
2375
2381 def wlock(self, wait=True):
2376 def wlock(self, wait=True):
2382 '''Lock the non-store parts of the repository (everything under
2377 '''Lock the non-store parts of the repository (everything under
2383 .hg except .hg/store) and return a weak reference to the lock.
2378 .hg except .hg/store) and return a weak reference to the lock.
2384
2379
2385 Use this before modifying files in .hg.
2380 Use this before modifying files in .hg.
2386
2381
2387 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2382 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2388 'wlock' first to avoid a dead-lock hazard.'''
2383 'wlock' first to avoid a dead-lock hazard.'''
2389 l = self._wlockref and self._wlockref()
2384 l = self._wlockref and self._wlockref()
2390 if l is not None and l.held:
2385 if l is not None and l.held:
2391 l.lock()
2386 l.lock()
2392 return l
2387 return l
2393
2388
2394 # We do not need to check for non-waiting lock acquisition. Such
2389 # We do not need to check for non-waiting lock acquisition. Such
2395 # acquisition would not cause dead-lock as they would just fail.
2390 # acquisition would not cause dead-lock as they would just fail.
2396 if wait and (self.ui.configbool('devel', 'all-warnings')
2391 if wait and (self.ui.configbool('devel', 'all-warnings')
2397 or self.ui.configbool('devel', 'check-locks')):
2392 or self.ui.configbool('devel', 'check-locks')):
2398 if self._currentlock(self._lockref) is not None:
2393 if self._currentlock(self._lockref) is not None:
2399 self.ui.develwarn('"wlock" acquired after "lock"')
2394 self.ui.develwarn('"wlock" acquired after "lock"')
2400
2395
2401 def unlock():
2396 def unlock():
2402 if self.dirstate.pendingparentchange():
2397 if self.dirstate.pendingparentchange():
2403 self.dirstate.invalidate()
2398 self.dirstate.invalidate()
2404 else:
2399 else:
2405 self.dirstate.write(None)
2400 self.dirstate.write(None)
2406
2401
2407 self._filecache['dirstate'].refresh()
2402 self._filecache['dirstate'].refresh()
2408
2403
2409 l = self._lock(self.vfs, "wlock", wait, unlock,
2404 l = self._lock(self.vfs, "wlock", wait, unlock,
2410 self.invalidatedirstate, _('working directory of %s') %
2405 self.invalidatedirstate, _('working directory of %s') %
2411 self.origroot,
2406 self.origroot,
2412 inheritchecker=self._wlockchecktransaction,
2407 inheritchecker=self._wlockchecktransaction,
2413 parentenvvar='HG_WLOCK_LOCKER')
2408 parentenvvar='HG_WLOCK_LOCKER')
2414 self._wlockref = weakref.ref(l)
2409 self._wlockref = weakref.ref(l)
2415 return l
2410 return l
2416
2411
2417 def _currentlock(self, lockref):
2412 def _currentlock(self, lockref):
2418 """Returns the lock if it's held, or None if it's not."""
2413 """Returns the lock if it's held, or None if it's not."""
2419 if lockref is None:
2414 if lockref is None:
2420 return None
2415 return None
2421 l = lockref()
2416 l = lockref()
2422 if l is None or not l.held:
2417 if l is None or not l.held:
2423 return None
2418 return None
2424 return l
2419 return l
2425
2420
2426 def currentwlock(self):
2421 def currentwlock(self):
2427 """Returns the wlock if it's held, or None if it's not."""
2422 """Returns the wlock if it's held, or None if it's not."""
2428 return self._currentlock(self._wlockref)
2423 return self._currentlock(self._wlockref)
2429
2424
2430 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist,
2425 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist,
2431 includecopymeta):
2426 includecopymeta):
2432 """
2427 """
2433 commit an individual file as part of a larger transaction
2428 commit an individual file as part of a larger transaction
2434 """
2429 """
2435
2430
2436 fname = fctx.path()
2431 fname = fctx.path()
2437 fparent1 = manifest1.get(fname, nullid)
2432 fparent1 = manifest1.get(fname, nullid)
2438 fparent2 = manifest2.get(fname, nullid)
2433 fparent2 = manifest2.get(fname, nullid)
2439 if isinstance(fctx, context.filectx):
2434 if isinstance(fctx, context.filectx):
2440 node = fctx.filenode()
2435 node = fctx.filenode()
2441 if node in [fparent1, fparent2]:
2436 if node in [fparent1, fparent2]:
2442 self.ui.debug('reusing %s filelog entry\n' % fname)
2437 self.ui.debug('reusing %s filelog entry\n' % fname)
2443 if ((fparent1 != nullid and
2438 if ((fparent1 != nullid and
2444 manifest1.flags(fname) != fctx.flags()) or
2439 manifest1.flags(fname) != fctx.flags()) or
2445 (fparent2 != nullid and
2440 (fparent2 != nullid and
2446 manifest2.flags(fname) != fctx.flags())):
2441 manifest2.flags(fname) != fctx.flags())):
2447 changelist.append(fname)
2442 changelist.append(fname)
2448 return node
2443 return node
2449
2444
2450 flog = self.file(fname)
2445 flog = self.file(fname)
2451 meta = {}
2446 meta = {}
2452 cfname = fctx.copysource()
2447 cfname = fctx.copysource()
2453 if cfname and cfname != fname:
2448 if cfname and cfname != fname:
2454 # Mark the new revision of this file as a copy of another
2449 # Mark the new revision of this file as a copy of another
2455 # file. This copy data will effectively act as a parent
2450 # file. This copy data will effectively act as a parent
2456 # of this new revision. If this is a merge, the first
2451 # of this new revision. If this is a merge, the first
2457 # parent will be the nullid (meaning "look up the copy data")
2452 # parent will be the nullid (meaning "look up the copy data")
2458 # and the second one will be the other parent. For example:
2453 # and the second one will be the other parent. For example:
2459 #
2454 #
2460 # 0 --- 1 --- 3 rev1 changes file foo
2455 # 0 --- 1 --- 3 rev1 changes file foo
2461 # \ / rev2 renames foo to bar and changes it
2456 # \ / rev2 renames foo to bar and changes it
2462 # \- 2 -/ rev3 should have bar with all changes and
2457 # \- 2 -/ rev3 should have bar with all changes and
2463 # should record that bar descends from
2458 # should record that bar descends from
2464 # bar in rev2 and foo in rev1
2459 # bar in rev2 and foo in rev1
2465 #
2460 #
2466 # this allows this merge to succeed:
2461 # this allows this merge to succeed:
2467 #
2462 #
2468 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
2463 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
2469 # \ / merging rev3 and rev4 should use bar@rev2
2464 # \ / merging rev3 and rev4 should use bar@rev2
2470 # \- 2 --- 4 as the merge base
2465 # \- 2 --- 4 as the merge base
2471 #
2466 #
2472
2467
2473 cnode = manifest1.get(cfname)
2468 cnode = manifest1.get(cfname)
2474 newfparent = fparent2
2469 newfparent = fparent2
2475
2470
2476 if manifest2: # branch merge
2471 if manifest2: # branch merge
2477 if fparent2 == nullid or cnode is None: # copied on remote side
2472 if fparent2 == nullid or cnode is None: # copied on remote side
2478 if cfname in manifest2:
2473 if cfname in manifest2:
2479 cnode = manifest2[cfname]
2474 cnode = manifest2[cfname]
2480 newfparent = fparent1
2475 newfparent = fparent1
2481
2476
2482 # Here, we used to search backwards through history to try to find
2477 # Here, we used to search backwards through history to try to find
2483 # where the file copy came from if the source of a copy was not in
2478 # where the file copy came from if the source of a copy was not in
2484 # the parent directory. However, this doesn't actually make sense to
2479 # the parent directory. However, this doesn't actually make sense to
2485 # do (what does a copy from something not in your working copy even
2480 # do (what does a copy from something not in your working copy even
2486 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
2481 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
2487 # the user that copy information was dropped, so if they didn't
2482 # the user that copy information was dropped, so if they didn't
2488 # expect this outcome it can be fixed, but this is the correct
2483 # expect this outcome it can be fixed, but this is the correct
2489 # behavior in this circumstance.
2484 # behavior in this circumstance.
2490
2485
2491 if cnode:
2486 if cnode:
2492 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(cnode)))
2487 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(cnode)))
2493 if includecopymeta:
2488 if includecopymeta:
2494 meta["copy"] = cfname
2489 meta["copy"] = cfname
2495 meta["copyrev"] = hex(cnode)
2490 meta["copyrev"] = hex(cnode)
2496 fparent1, fparent2 = nullid, newfparent
2491 fparent1, fparent2 = nullid, newfparent
2497 else:
2492 else:
2498 self.ui.warn(_("warning: can't find ancestor for '%s' "
2493 self.ui.warn(_("warning: can't find ancestor for '%s' "
2499 "copied from '%s'!\n") % (fname, cfname))
2494 "copied from '%s'!\n") % (fname, cfname))
2500
2495
2501 elif fparent1 == nullid:
2496 elif fparent1 == nullid:
2502 fparent1, fparent2 = fparent2, nullid
2497 fparent1, fparent2 = fparent2, nullid
2503 elif fparent2 != nullid:
2498 elif fparent2 != nullid:
2504 # is one parent an ancestor of the other?
2499 # is one parent an ancestor of the other?
2505 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
2500 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
2506 if fparent1 in fparentancestors:
2501 if fparent1 in fparentancestors:
2507 fparent1, fparent2 = fparent2, nullid
2502 fparent1, fparent2 = fparent2, nullid
2508 elif fparent2 in fparentancestors:
2503 elif fparent2 in fparentancestors:
2509 fparent2 = nullid
2504 fparent2 = nullid
2510
2505
2511 # is the file changed?
2506 # is the file changed?
2512 text = fctx.data()
2507 text = fctx.data()
2513 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
2508 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
2514 changelist.append(fname)
2509 changelist.append(fname)
2515 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
2510 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
2516 # are just the flags changed during merge?
2511 # are just the flags changed during merge?
2517 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
2512 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
2518 changelist.append(fname)
2513 changelist.append(fname)
2519
2514
2520 return fparent1
2515 return fparent1
2521
2516
2522 def checkcommitpatterns(self, wctx, vdirs, match, status, fail):
2517 def checkcommitpatterns(self, wctx, vdirs, match, status, fail):
2523 """check for commit arguments that aren't committable"""
2518 """check for commit arguments that aren't committable"""
2524 if match.isexact() or match.prefix():
2519 if match.isexact() or match.prefix():
2525 matched = set(status.modified + status.added + status.removed)
2520 matched = set(status.modified + status.added + status.removed)
2526
2521
2527 for f in match.files():
2522 for f in match.files():
2528 f = self.dirstate.normalize(f)
2523 f = self.dirstate.normalize(f)
2529 if f == '.' or f in matched or f in wctx.substate:
2524 if f == '.' or f in matched or f in wctx.substate:
2530 continue
2525 continue
2531 if f in status.deleted:
2526 if f in status.deleted:
2532 fail(f, _('file not found!'))
2527 fail(f, _('file not found!'))
2533 if f in vdirs: # visited directory
2528 if f in vdirs: # visited directory
2534 d = f + '/'
2529 d = f + '/'
2535 for mf in matched:
2530 for mf in matched:
2536 if mf.startswith(d):
2531 if mf.startswith(d):
2537 break
2532 break
2538 else:
2533 else:
2539 fail(f, _("no match under directory!"))
2534 fail(f, _("no match under directory!"))
2540 elif f not in self.dirstate:
2535 elif f not in self.dirstate:
2541 fail(f, _("file not tracked!"))
2536 fail(f, _("file not tracked!"))
2542
2537
2543 @unfilteredmethod
2538 @unfilteredmethod
2544 def commit(self, text="", user=None, date=None, match=None, force=False,
2539 def commit(self, text="", user=None, date=None, match=None, force=False,
2545 editor=False, extra=None):
2540 editor=False, extra=None):
2546 """Add a new revision to current repository.
2541 """Add a new revision to current repository.
2547
2542
2548 Revision information is gathered from the working directory,
2543 Revision information is gathered from the working directory,
2549 match can be used to filter the committed files. If editor is
2544 match can be used to filter the committed files. If editor is
2550 supplied, it is called to get a commit message.
2545 supplied, it is called to get a commit message.
2551 """
2546 """
2552 if extra is None:
2547 if extra is None:
2553 extra = {}
2548 extra = {}
2554
2549
2555 def fail(f, msg):
2550 def fail(f, msg):
2556 raise error.Abort('%s: %s' % (f, msg))
2551 raise error.Abort('%s: %s' % (f, msg))
2557
2552
2558 if not match:
2553 if not match:
2559 match = matchmod.always()
2554 match = matchmod.always()
2560
2555
2561 if not force:
2556 if not force:
2562 vdirs = []
2557 vdirs = []
2563 match.explicitdir = vdirs.append
2558 match.explicitdir = vdirs.append
2564 match.bad = fail
2559 match.bad = fail
2565
2560
2566 # lock() for recent changelog (see issue4368)
2561 # lock() for recent changelog (see issue4368)
2567 with self.wlock(), self.lock():
2562 with self.wlock(), self.lock():
2568 wctx = self[None]
2563 wctx = self[None]
2569 merge = len(wctx.parents()) > 1
2564 merge = len(wctx.parents()) > 1
2570
2565
2571 if not force and merge and not match.always():
2566 if not force and merge and not match.always():
2572 raise error.Abort(_('cannot partially commit a merge '
2567 raise error.Abort(_('cannot partially commit a merge '
2573 '(do not specify files or patterns)'))
2568 '(do not specify files or patterns)'))
2574
2569
2575 status = self.status(match=match, clean=force)
2570 status = self.status(match=match, clean=force)
2576 if force:
2571 if force:
2577 status.modified.extend(status.clean) # mq may commit clean files
2572 status.modified.extend(status.clean) # mq may commit clean files
2578
2573
2579 # check subrepos
2574 # check subrepos
2580 subs, commitsubs, newstate = subrepoutil.precommit(
2575 subs, commitsubs, newstate = subrepoutil.precommit(
2581 self.ui, wctx, status, match, force=force)
2576 self.ui, wctx, status, match, force=force)
2582
2577
2583 # make sure all explicit patterns are matched
2578 # make sure all explicit patterns are matched
2584 if not force:
2579 if not force:
2585 self.checkcommitpatterns(wctx, vdirs, match, status, fail)
2580 self.checkcommitpatterns(wctx, vdirs, match, status, fail)
2586
2581
2587 cctx = context.workingcommitctx(self, status,
2582 cctx = context.workingcommitctx(self, status,
2588 text, user, date, extra)
2583 text, user, date, extra)
2589
2584
2590 # internal config: ui.allowemptycommit
2585 # internal config: ui.allowemptycommit
2591 allowemptycommit = (wctx.branch() != wctx.p1().branch()
2586 allowemptycommit = (wctx.branch() != wctx.p1().branch()
2592 or extra.get('close') or merge or cctx.files()
2587 or extra.get('close') or merge or cctx.files()
2593 or self.ui.configbool('ui', 'allowemptycommit'))
2588 or self.ui.configbool('ui', 'allowemptycommit'))
2594 if not allowemptycommit:
2589 if not allowemptycommit:
2595 return None
2590 return None
2596
2591
2597 if merge and cctx.deleted():
2592 if merge and cctx.deleted():
2598 raise error.Abort(_("cannot commit merge with missing files"))
2593 raise error.Abort(_("cannot commit merge with missing files"))
2599
2594
2600 ms = mergemod.mergestate.read(self)
2595 ms = mergemod.mergestate.read(self)
2601 mergeutil.checkunresolved(ms)
2596 mergeutil.checkunresolved(ms)
2602
2597
2603 if editor:
2598 if editor:
2604 cctx._text = editor(self, cctx, subs)
2599 cctx._text = editor(self, cctx, subs)
2605 edited = (text != cctx._text)
2600 edited = (text != cctx._text)
2606
2601
2607 # Save commit message in case this transaction gets rolled back
2602 # Save commit message in case this transaction gets rolled back
2608 # (e.g. by a pretxncommit hook). Leave the content alone on
2603 # (e.g. by a pretxncommit hook). Leave the content alone on
2609 # the assumption that the user will use the same editor again.
2604 # the assumption that the user will use the same editor again.
2610 msgfn = self.savecommitmessage(cctx._text)
2605 msgfn = self.savecommitmessage(cctx._text)
2611
2606
2612 # commit subs and write new state
2607 # commit subs and write new state
2613 if subs:
2608 if subs:
2614 uipathfn = scmutil.getuipathfn(self)
2609 uipathfn = scmutil.getuipathfn(self)
2615 for s in sorted(commitsubs):
2610 for s in sorted(commitsubs):
2616 sub = wctx.sub(s)
2611 sub = wctx.sub(s)
2617 self.ui.status(_('committing subrepository %s\n') %
2612 self.ui.status(_('committing subrepository %s\n') %
2618 uipathfn(subrepoutil.subrelpath(sub)))
2613 uipathfn(subrepoutil.subrelpath(sub)))
2619 sr = sub.commit(cctx._text, user, date)
2614 sr = sub.commit(cctx._text, user, date)
2620 newstate[s] = (newstate[s][0], sr)
2615 newstate[s] = (newstate[s][0], sr)
2621 subrepoutil.writestate(self, newstate)
2616 subrepoutil.writestate(self, newstate)
2622
2617
2623 p1, p2 = self.dirstate.parents()
2618 p1, p2 = self.dirstate.parents()
2624 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
2619 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
2625 try:
2620 try:
2626 self.hook("precommit", throw=True, parent1=hookp1,
2621 self.hook("precommit", throw=True, parent1=hookp1,
2627 parent2=hookp2)
2622 parent2=hookp2)
2628 with self.transaction('commit'):
2623 with self.transaction('commit'):
2629 ret = self.commitctx(cctx, True)
2624 ret = self.commitctx(cctx, True)
2630 # update bookmarks, dirstate and mergestate
2625 # update bookmarks, dirstate and mergestate
2631 bookmarks.update(self, [p1, p2], ret)
2626 bookmarks.update(self, [p1, p2], ret)
2632 cctx.markcommitted(ret)
2627 cctx.markcommitted(ret)
2633 ms.reset()
2628 ms.reset()
2634 except: # re-raises
2629 except: # re-raises
2635 if edited:
2630 if edited:
2636 self.ui.write(
2631 self.ui.write(
2637 _('note: commit message saved in %s\n') % msgfn)
2632 _('note: commit message saved in %s\n') % msgfn)
2638 raise
2633 raise
2639
2634
2640 def commithook():
2635 def commithook():
2641 # hack for command that use a temporary commit (eg: histedit)
2636 # hack for command that use a temporary commit (eg: histedit)
2642 # temporary commit got stripped before hook release
2637 # temporary commit got stripped before hook release
2643 if self.changelog.hasnode(ret):
2638 if self.changelog.hasnode(ret):
2644 self.hook("commit", node=hex(ret), parent1=hookp1,
2639 self.hook("commit", node=hex(ret), parent1=hookp1,
2645 parent2=hookp2)
2640 parent2=hookp2)
2646 self._afterlock(commithook)
2641 self._afterlock(commithook)
2647 return ret
2642 return ret
2648
2643
2649 @unfilteredmethod
2644 @unfilteredmethod
2650 def commitctx(self, ctx, error=False, origctx=None):
2645 def commitctx(self, ctx, error=False, origctx=None):
2651 """Add a new revision to current repository.
2646 """Add a new revision to current repository.
2652 Revision information is passed via the context argument.
2647 Revision information is passed via the context argument.
2653
2648
2654 ctx.files() should list all files involved in this commit, i.e.
2649 ctx.files() should list all files involved in this commit, i.e.
2655 modified/added/removed files. On merge, it may be wider than the
2650 modified/added/removed files. On merge, it may be wider than the
2656 ctx.files() to be committed, since any file nodes derived directly
2651 ctx.files() to be committed, since any file nodes derived directly
2657 from p1 or p2 are excluded from the committed ctx.files().
2652 from p1 or p2 are excluded from the committed ctx.files().
2658
2653
2659 origctx is for convert to work around the problem that bug
2654 origctx is for convert to work around the problem that bug
2660 fixes to the files list in changesets change hashes. For
2655 fixes to the files list in changesets change hashes. For
2661 convert to be the identity, it can pass an origctx and this
2656 convert to be the identity, it can pass an origctx and this
2662 function will use the same files list when it makes sense to
2657 function will use the same files list when it makes sense to
2663 do so.
2658 do so.
2664 """
2659 """
2665
2660
2666 p1, p2 = ctx.p1(), ctx.p2()
2661 p1, p2 = ctx.p1(), ctx.p2()
2667 user = ctx.user()
2662 user = ctx.user()
2668
2663
2669 writecopiesto = self.ui.config('experimental', 'copies.write-to')
2664 writecopiesto = self.ui.config('experimental', 'copies.write-to')
2670 writefilecopymeta = writecopiesto != 'changeset-only'
2665 writefilecopymeta = writecopiesto != 'changeset-only'
2671 writechangesetcopy = (writecopiesto in
2666 writechangesetcopy = (writecopiesto in
2672 ('changeset-only', 'compatibility'))
2667 ('changeset-only', 'compatibility'))
2673 p1copies, p2copies = None, None
2668 p1copies, p2copies = None, None
2674 if writechangesetcopy:
2669 if writechangesetcopy:
2675 p1copies = ctx.p1copies()
2670 p1copies = ctx.p1copies()
2676 p2copies = ctx.p2copies()
2671 p2copies = ctx.p2copies()
2677 filesadded, filesremoved = None, None
2672 filesadded, filesremoved = None, None
2678 with self.lock(), self.transaction("commit") as tr:
2673 with self.lock(), self.transaction("commit") as tr:
2679 trp = weakref.proxy(tr)
2674 trp = weakref.proxy(tr)
2680
2675
2681 if ctx.manifestnode():
2676 if ctx.manifestnode():
2682 # reuse an existing manifest revision
2677 # reuse an existing manifest revision
2683 self.ui.debug('reusing known manifest\n')
2678 self.ui.debug('reusing known manifest\n')
2684 mn = ctx.manifestnode()
2679 mn = ctx.manifestnode()
2685 files = ctx.files()
2680 files = ctx.files()
2686 if writechangesetcopy:
2681 if writechangesetcopy:
2687 filesadded = ctx.filesadded()
2682 filesadded = ctx.filesadded()
2688 filesremoved = ctx.filesremoved()
2683 filesremoved = ctx.filesremoved()
2689 elif ctx.files():
2684 elif ctx.files():
2690 m1ctx = p1.manifestctx()
2685 m1ctx = p1.manifestctx()
2691 m2ctx = p2.manifestctx()
2686 m2ctx = p2.manifestctx()
2692 mctx = m1ctx.copy()
2687 mctx = m1ctx.copy()
2693
2688
2694 m = mctx.read()
2689 m = mctx.read()
2695 m1 = m1ctx.read()
2690 m1 = m1ctx.read()
2696 m2 = m2ctx.read()
2691 m2 = m2ctx.read()
2697
2692
2698 # check in files
2693 # check in files
2699 added = []
2694 added = []
2700 changed = []
2695 changed = []
2701 removed = list(ctx.removed())
2696 removed = list(ctx.removed())
2702 linkrev = len(self)
2697 linkrev = len(self)
2703 self.ui.note(_("committing files:\n"))
2698 self.ui.note(_("committing files:\n"))
2704 uipathfn = scmutil.getuipathfn(self)
2699 uipathfn = scmutil.getuipathfn(self)
2705 for f in sorted(ctx.modified() + ctx.added()):
2700 for f in sorted(ctx.modified() + ctx.added()):
2706 self.ui.note(uipathfn(f) + "\n")
2701 self.ui.note(uipathfn(f) + "\n")
2707 try:
2702 try:
2708 fctx = ctx[f]
2703 fctx = ctx[f]
2709 if fctx is None:
2704 if fctx is None:
2710 removed.append(f)
2705 removed.append(f)
2711 else:
2706 else:
2712 added.append(f)
2707 added.append(f)
2713 m[f] = self._filecommit(fctx, m1, m2, linkrev,
2708 m[f] = self._filecommit(fctx, m1, m2, linkrev,
2714 trp, changed,
2709 trp, changed,
2715 writefilecopymeta)
2710 writefilecopymeta)
2716 m.setflag(f, fctx.flags())
2711 m.setflag(f, fctx.flags())
2717 except OSError:
2712 except OSError:
2718 self.ui.warn(_("trouble committing %s!\n") %
2713 self.ui.warn(_("trouble committing %s!\n") %
2719 uipathfn(f))
2714 uipathfn(f))
2720 raise
2715 raise
2721 except IOError as inst:
2716 except IOError as inst:
2722 errcode = getattr(inst, 'errno', errno.ENOENT)
2717 errcode = getattr(inst, 'errno', errno.ENOENT)
2723 if error or errcode and errcode != errno.ENOENT:
2718 if error or errcode and errcode != errno.ENOENT:
2724 self.ui.warn(_("trouble committing %s!\n") %
2719 self.ui.warn(_("trouble committing %s!\n") %
2725 uipathfn(f))
2720 uipathfn(f))
2726 raise
2721 raise
2727
2722
2728 # update manifest
2723 # update manifest
2729 removed = [f for f in removed if f in m1 or f in m2]
2724 removed = [f for f in removed if f in m1 or f in m2]
2730 drop = sorted([f for f in removed if f in m])
2725 drop = sorted([f for f in removed if f in m])
2731 for f in drop:
2726 for f in drop:
2732 del m[f]
2727 del m[f]
2733 if p2.rev() != nullrev:
2728 if p2.rev() != nullrev:
2734 @util.cachefunc
2729 @util.cachefunc
2735 def mas():
2730 def mas():
2736 p1n = p1.node()
2731 p1n = p1.node()
2737 p2n = p2.node()
2732 p2n = p2.node()
2738 cahs = self.changelog.commonancestorsheads(p1n, p2n)
2733 cahs = self.changelog.commonancestorsheads(p1n, p2n)
2739 if not cahs:
2734 if not cahs:
2740 cahs = [nullrev]
2735 cahs = [nullrev]
2741 return [self[r].manifest() for r in cahs]
2736 return [self[r].manifest() for r in cahs]
2742 def deletionfromparent(f):
2737 def deletionfromparent(f):
2743 # When a file is removed relative to p1 in a merge, this
2738 # When a file is removed relative to p1 in a merge, this
2744 # function determines whether the absence is due to a
2739 # function determines whether the absence is due to a
2745 # deletion from a parent, or whether the merge commit
2740 # deletion from a parent, or whether the merge commit
2746 # itself deletes the file. We decide this by doing a
2741 # itself deletes the file. We decide this by doing a
2747 # simplified three way merge of the manifest entry for
2742 # simplified three way merge of the manifest entry for
2748 # the file. There are two ways we decide the merge
2743 # the file. There are two ways we decide the merge
2749 # itself didn't delete a file:
2744 # itself didn't delete a file:
2750 # - neither parent (nor the merge) contain the file
2745 # - neither parent (nor the merge) contain the file
2751 # - exactly one parent contains the file, and that
2746 # - exactly one parent contains the file, and that
2752 # parent has the same filelog entry as the merge
2747 # parent has the same filelog entry as the merge
2753 # ancestor (or all of them if there two). In other
2748 # ancestor (or all of them if there two). In other
2754 # words, that parent left the file unchanged while the
2749 # words, that parent left the file unchanged while the
2755 # other one deleted it.
2750 # other one deleted it.
2756 # One way to think about this is that deleting a file is
2751 # One way to think about this is that deleting a file is
2757 # similar to emptying it, so the list of changed files
2752 # similar to emptying it, so the list of changed files
2758 # should be similar either way. The computation
2753 # should be similar either way. The computation
2759 # described above is not done directly in _filecommit
2754 # described above is not done directly in _filecommit
2760 # when creating the list of changed files, however
2755 # when creating the list of changed files, however
2761 # it does something very similar by comparing filelog
2756 # it does something very similar by comparing filelog
2762 # nodes.
2757 # nodes.
2763 if f in m1:
2758 if f in m1:
2764 return (f not in m2
2759 return (f not in m2
2765 and all(f in ma and ma.find(f) == m1.find(f)
2760 and all(f in ma and ma.find(f) == m1.find(f)
2766 for ma in mas()))
2761 for ma in mas()))
2767 elif f in m2:
2762 elif f in m2:
2768 return all(f in ma and ma.find(f) == m2.find(f)
2763 return all(f in ma and ma.find(f) == m2.find(f)
2769 for ma in mas())
2764 for ma in mas())
2770 else:
2765 else:
2771 return True
2766 return True
2772 removed = [f for f in removed if not deletionfromparent(f)]
2767 removed = [f for f in removed if not deletionfromparent(f)]
2773
2768
2774 files = changed + removed
2769 files = changed + removed
2775 md = None
2770 md = None
2776 if not files:
2771 if not files:
2777 # if no "files" actually changed in terms of the changelog,
2772 # if no "files" actually changed in terms of the changelog,
2778 # try hard to detect unmodified manifest entry so that the
2773 # try hard to detect unmodified manifest entry so that the
2779 # exact same commit can be reproduced later on convert.
2774 # exact same commit can be reproduced later on convert.
2780 md = m1.diff(m, scmutil.matchfiles(self, ctx.files()))
2775 md = m1.diff(m, scmutil.matchfiles(self, ctx.files()))
2781 if not files and md:
2776 if not files and md:
2782 self.ui.debug('not reusing manifest (no file change in '
2777 self.ui.debug('not reusing manifest (no file change in '
2783 'changelog, but manifest differs)\n')
2778 'changelog, but manifest differs)\n')
2784 if files or md:
2779 if files or md:
2785 self.ui.note(_("committing manifest\n"))
2780 self.ui.note(_("committing manifest\n"))
2786 # we're using narrowmatch here since it's already applied at
2781 # we're using narrowmatch here since it's already applied at
2787 # other stages (such as dirstate.walk), so we're already
2782 # other stages (such as dirstate.walk), so we're already
2788 # ignoring things outside of narrowspec in most cases. The
2783 # ignoring things outside of narrowspec in most cases. The
2789 # one case where we might have files outside the narrowspec
2784 # one case where we might have files outside the narrowspec
2790 # at this point is merges, and we already error out in the
2785 # at this point is merges, and we already error out in the
2791 # case where the merge has files outside of the narrowspec,
2786 # case where the merge has files outside of the narrowspec,
2792 # so this is safe.
2787 # so this is safe.
2793 mn = mctx.write(trp, linkrev,
2788 mn = mctx.write(trp, linkrev,
2794 p1.manifestnode(), p2.manifestnode(),
2789 p1.manifestnode(), p2.manifestnode(),
2795 added, drop, match=self.narrowmatch())
2790 added, drop, match=self.narrowmatch())
2796
2791
2797 if writechangesetcopy:
2792 if writechangesetcopy:
2798 filesadded = [f for f in changed
2793 filesadded = [f for f in changed
2799 if not (f in m1 or f in m2)]
2794 if not (f in m1 or f in m2)]
2800 filesremoved = removed
2795 filesremoved = removed
2801 else:
2796 else:
2802 self.ui.debug('reusing manifest from p1 (listed files '
2797 self.ui.debug('reusing manifest from p1 (listed files '
2803 'actually unchanged)\n')
2798 'actually unchanged)\n')
2804 mn = p1.manifestnode()
2799 mn = p1.manifestnode()
2805 else:
2800 else:
2806 self.ui.debug('reusing manifest from p1 (no file change)\n')
2801 self.ui.debug('reusing manifest from p1 (no file change)\n')
2807 mn = p1.manifestnode()
2802 mn = p1.manifestnode()
2808 files = []
2803 files = []
2809
2804
2810 if writecopiesto == 'changeset-only':
2805 if writecopiesto == 'changeset-only':
2811 # If writing only to changeset extras, use None to indicate that
2806 # If writing only to changeset extras, use None to indicate that
2812 # no entry should be written. If writing to both, write an empty
2807 # no entry should be written. If writing to both, write an empty
2813 # entry to prevent the reader from falling back to reading
2808 # entry to prevent the reader from falling back to reading
2814 # filelogs.
2809 # filelogs.
2815 p1copies = p1copies or None
2810 p1copies = p1copies or None
2816 p2copies = p2copies or None
2811 p2copies = p2copies or None
2817 filesadded = filesadded or None
2812 filesadded = filesadded or None
2818 filesremoved = filesremoved or None
2813 filesremoved = filesremoved or None
2819
2814
2820 if origctx and origctx.manifestnode() == mn:
2815 if origctx and origctx.manifestnode() == mn:
2821 files = origctx.files()
2816 files = origctx.files()
2822
2817
2823 # update changelog
2818 # update changelog
2824 self.ui.note(_("committing changelog\n"))
2819 self.ui.note(_("committing changelog\n"))
2825 self.changelog.delayupdate(tr)
2820 self.changelog.delayupdate(tr)
2826 n = self.changelog.add(mn, files, ctx.description(),
2821 n = self.changelog.add(mn, files, ctx.description(),
2827 trp, p1.node(), p2.node(),
2822 trp, p1.node(), p2.node(),
2828 user, ctx.date(), ctx.extra().copy(),
2823 user, ctx.date(), ctx.extra().copy(),
2829 p1copies, p2copies, filesadded, filesremoved)
2824 p1copies, p2copies, filesadded, filesremoved)
2830 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
2825 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
2831 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
2826 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
2832 parent2=xp2)
2827 parent2=xp2)
2833 # set the new commit is proper phase
2828 # set the new commit is proper phase
2834 targetphase = subrepoutil.newcommitphase(self.ui, ctx)
2829 targetphase = subrepoutil.newcommitphase(self.ui, ctx)
2835 if targetphase:
2830 if targetphase:
2836 # retract boundary do not alter parent changeset.
2831 # retract boundary do not alter parent changeset.
2837 # if a parent have higher the resulting phase will
2832 # if a parent have higher the resulting phase will
2838 # be compliant anyway
2833 # be compliant anyway
2839 #
2834 #
2840 # if minimal phase was 0 we don't need to retract anything
2835 # if minimal phase was 0 we don't need to retract anything
2841 phases.registernew(self, tr, targetphase, [n])
2836 phases.registernew(self, tr, targetphase, [n])
2842 return n
2837 return n
2843
2838
2844 @unfilteredmethod
2839 @unfilteredmethod
2845 def destroying(self):
2840 def destroying(self):
2846 '''Inform the repository that nodes are about to be destroyed.
2841 '''Inform the repository that nodes are about to be destroyed.
2847 Intended for use by strip and rollback, so there's a common
2842 Intended for use by strip and rollback, so there's a common
2848 place for anything that has to be done before destroying history.
2843 place for anything that has to be done before destroying history.
2849
2844
2850 This is mostly useful for saving state that is in memory and waiting
2845 This is mostly useful for saving state that is in memory and waiting
2851 to be flushed when the current lock is released. Because a call to
2846 to be flushed when the current lock is released. Because a call to
2852 destroyed is imminent, the repo will be invalidated causing those
2847 destroyed is imminent, the repo will be invalidated causing those
2853 changes to stay in memory (waiting for the next unlock), or vanish
2848 changes to stay in memory (waiting for the next unlock), or vanish
2854 completely.
2849 completely.
2855 '''
2850 '''
2856 # When using the same lock to commit and strip, the phasecache is left
2851 # When using the same lock to commit and strip, the phasecache is left
2857 # dirty after committing. Then when we strip, the repo is invalidated,
2852 # dirty after committing. Then when we strip, the repo is invalidated,
2858 # causing those changes to disappear.
2853 # causing those changes to disappear.
2859 if '_phasecache' in vars(self):
2854 if '_phasecache' in vars(self):
2860 self._phasecache.write()
2855 self._phasecache.write()
2861
2856
2862 @unfilteredmethod
2857 @unfilteredmethod
2863 def destroyed(self):
2858 def destroyed(self):
2864 '''Inform the repository that nodes have been destroyed.
2859 '''Inform the repository that nodes have been destroyed.
2865 Intended for use by strip and rollback, so there's a common
2860 Intended for use by strip and rollback, so there's a common
2866 place for anything that has to be done after destroying history.
2861 place for anything that has to be done after destroying history.
2867 '''
2862 '''
2868 # When one tries to:
2863 # When one tries to:
2869 # 1) destroy nodes thus calling this method (e.g. strip)
2864 # 1) destroy nodes thus calling this method (e.g. strip)
2870 # 2) use phasecache somewhere (e.g. commit)
2865 # 2) use phasecache somewhere (e.g. commit)
2871 #
2866 #
2872 # then 2) will fail because the phasecache contains nodes that were
2867 # then 2) will fail because the phasecache contains nodes that were
2873 # removed. We can either remove phasecache from the filecache,
2868 # removed. We can either remove phasecache from the filecache,
2874 # causing it to reload next time it is accessed, or simply filter
2869 # causing it to reload next time it is accessed, or simply filter
2875 # the removed nodes now and write the updated cache.
2870 # the removed nodes now and write the updated cache.
2876 self._phasecache.filterunknown(self)
2871 self._phasecache.filterunknown(self)
2877 self._phasecache.write()
2872 self._phasecache.write()
2878
2873
2879 # refresh all repository caches
2874 # refresh all repository caches
2880 self.updatecaches()
2875 self.updatecaches()
2881
2876
2882 # Ensure the persistent tag cache is updated. Doing it now
2877 # Ensure the persistent tag cache is updated. Doing it now
2883 # means that the tag cache only has to worry about destroyed
2878 # means that the tag cache only has to worry about destroyed
2884 # heads immediately after a strip/rollback. That in turn
2879 # heads immediately after a strip/rollback. That in turn
2885 # guarantees that "cachetip == currenttip" (comparing both rev
2880 # guarantees that "cachetip == currenttip" (comparing both rev
2886 # and node) always means no nodes have been added or destroyed.
2881 # and node) always means no nodes have been added or destroyed.
2887
2882
2888 # XXX this is suboptimal when qrefresh'ing: we strip the current
2883 # XXX this is suboptimal when qrefresh'ing: we strip the current
2889 # head, refresh the tag cache, then immediately add a new head.
2884 # head, refresh the tag cache, then immediately add a new head.
2890 # But I think doing it this way is necessary for the "instant
2885 # But I think doing it this way is necessary for the "instant
2891 # tag cache retrieval" case to work.
2886 # tag cache retrieval" case to work.
2892 self.invalidate()
2887 self.invalidate()
2893
2888
2894 def status(self, node1='.', node2=None, match=None,
2889 def status(self, node1='.', node2=None, match=None,
2895 ignored=False, clean=False, unknown=False,
2890 ignored=False, clean=False, unknown=False,
2896 listsubrepos=False):
2891 listsubrepos=False):
2897 '''a convenience method that calls node1.status(node2)'''
2892 '''a convenience method that calls node1.status(node2)'''
2898 return self[node1].status(node2, match, ignored, clean, unknown,
2893 return self[node1].status(node2, match, ignored, clean, unknown,
2899 listsubrepos)
2894 listsubrepos)
2900
2895
2901 def addpostdsstatus(self, ps):
2896 def addpostdsstatus(self, ps):
2902 """Add a callback to run within the wlock, at the point at which status
2897 """Add a callback to run within the wlock, at the point at which status
2903 fixups happen.
2898 fixups happen.
2904
2899
2905 On status completion, callback(wctx, status) will be called with the
2900 On status completion, callback(wctx, status) will be called with the
2906 wlock held, unless the dirstate has changed from underneath or the wlock
2901 wlock held, unless the dirstate has changed from underneath or the wlock
2907 couldn't be grabbed.
2902 couldn't be grabbed.
2908
2903
2909 Callbacks should not capture and use a cached copy of the dirstate --
2904 Callbacks should not capture and use a cached copy of the dirstate --
2910 it might change in the meanwhile. Instead, they should access the
2905 it might change in the meanwhile. Instead, they should access the
2911 dirstate via wctx.repo().dirstate.
2906 dirstate via wctx.repo().dirstate.
2912
2907
2913 This list is emptied out after each status run -- extensions should
2908 This list is emptied out after each status run -- extensions should
2914 make sure it adds to this list each time dirstate.status is called.
2909 make sure it adds to this list each time dirstate.status is called.
2915 Extensions should also make sure they don't call this for statuses
2910 Extensions should also make sure they don't call this for statuses
2916 that don't involve the dirstate.
2911 that don't involve the dirstate.
2917 """
2912 """
2918
2913
2919 # The list is located here for uniqueness reasons -- it is actually
2914 # The list is located here for uniqueness reasons -- it is actually
2920 # managed by the workingctx, but that isn't unique per-repo.
2915 # managed by the workingctx, but that isn't unique per-repo.
2921 self._postdsstatus.append(ps)
2916 self._postdsstatus.append(ps)
2922
2917
2923 def postdsstatus(self):
2918 def postdsstatus(self):
2924 """Used by workingctx to get the list of post-dirstate-status hooks."""
2919 """Used by workingctx to get the list of post-dirstate-status hooks."""
2925 return self._postdsstatus
2920 return self._postdsstatus
2926
2921
2927 def clearpostdsstatus(self):
2922 def clearpostdsstatus(self):
2928 """Used by workingctx to clear post-dirstate-status hooks."""
2923 """Used by workingctx to clear post-dirstate-status hooks."""
2929 del self._postdsstatus[:]
2924 del self._postdsstatus[:]
2930
2925
2931 def heads(self, start=None):
2926 def heads(self, start=None):
2932 if start is None:
2927 if start is None:
2933 cl = self.changelog
2928 cl = self.changelog
2934 headrevs = reversed(cl.headrevs())
2929 headrevs = reversed(cl.headrevs())
2935 return [cl.node(rev) for rev in headrevs]
2930 return [cl.node(rev) for rev in headrevs]
2936
2931
2937 heads = self.changelog.heads(start)
2932 heads = self.changelog.heads(start)
2938 # sort the output in rev descending order
2933 # sort the output in rev descending order
2939 return sorted(heads, key=self.changelog.rev, reverse=True)
2934 return sorted(heads, key=self.changelog.rev, reverse=True)
2940
2935
2941 def branchheads(self, branch=None, start=None, closed=False):
2936 def branchheads(self, branch=None, start=None, closed=False):
2942 '''return a (possibly filtered) list of heads for the given branch
2937 '''return a (possibly filtered) list of heads for the given branch
2943
2938
2944 Heads are returned in topological order, from newest to oldest.
2939 Heads are returned in topological order, from newest to oldest.
2945 If branch is None, use the dirstate branch.
2940 If branch is None, use the dirstate branch.
2946 If start is not None, return only heads reachable from start.
2941 If start is not None, return only heads reachable from start.
2947 If closed is True, return heads that are marked as closed as well.
2942 If closed is True, return heads that are marked as closed as well.
2948 '''
2943 '''
2949 if branch is None:
2944 if branch is None:
2950 branch = self[None].branch()
2945 branch = self[None].branch()
2951 branches = self.branchmap()
2946 branches = self.branchmap()
2952 if not branches.hasbranch(branch):
2947 if not branches.hasbranch(branch):
2953 return []
2948 return []
2954 # the cache returns heads ordered lowest to highest
2949 # the cache returns heads ordered lowest to highest
2955 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
2950 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
2956 if start is not None:
2951 if start is not None:
2957 # filter out the heads that cannot be reached from startrev
2952 # filter out the heads that cannot be reached from startrev
2958 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
2953 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
2959 bheads = [h for h in bheads if h in fbheads]
2954 bheads = [h for h in bheads if h in fbheads]
2960 return bheads
2955 return bheads
2961
2956
2962 def branches(self, nodes):
2957 def branches(self, nodes):
2963 if not nodes:
2958 if not nodes:
2964 nodes = [self.changelog.tip()]
2959 nodes = [self.changelog.tip()]
2965 b = []
2960 b = []
2966 for n in nodes:
2961 for n in nodes:
2967 t = n
2962 t = n
2968 while True:
2963 while True:
2969 p = self.changelog.parents(n)
2964 p = self.changelog.parents(n)
2970 if p[1] != nullid or p[0] == nullid:
2965 if p[1] != nullid or p[0] == nullid:
2971 b.append((t, n, p[0], p[1]))
2966 b.append((t, n, p[0], p[1]))
2972 break
2967 break
2973 n = p[0]
2968 n = p[0]
2974 return b
2969 return b
2975
2970
2976 def between(self, pairs):
2971 def between(self, pairs):
2977 r = []
2972 r = []
2978
2973
2979 for top, bottom in pairs:
2974 for top, bottom in pairs:
2980 n, l, i = top, [], 0
2975 n, l, i = top, [], 0
2981 f = 1
2976 f = 1
2982
2977
2983 while n != bottom and n != nullid:
2978 while n != bottom and n != nullid:
2984 p = self.changelog.parents(n)[0]
2979 p = self.changelog.parents(n)[0]
2985 if i == f:
2980 if i == f:
2986 l.append(n)
2981 l.append(n)
2987 f = f * 2
2982 f = f * 2
2988 n = p
2983 n = p
2989 i += 1
2984 i += 1
2990
2985
2991 r.append(l)
2986 r.append(l)
2992
2987
2993 return r
2988 return r
2994
2989
2995 def checkpush(self, pushop):
2990 def checkpush(self, pushop):
2996 """Extensions can override this function if additional checks have
2991 """Extensions can override this function if additional checks have
2997 to be performed before pushing, or call it if they override push
2992 to be performed before pushing, or call it if they override push
2998 command.
2993 command.
2999 """
2994 """
3000
2995
3001 @unfilteredpropertycache
2996 @unfilteredpropertycache
3002 def prepushoutgoinghooks(self):
2997 def prepushoutgoinghooks(self):
3003 """Return util.hooks consists of a pushop with repo, remote, outgoing
2998 """Return util.hooks consists of a pushop with repo, remote, outgoing
3004 methods, which are called before pushing changesets.
2999 methods, which are called before pushing changesets.
3005 """
3000 """
3006 return util.hooks()
3001 return util.hooks()
3007
3002
3008 def pushkey(self, namespace, key, old, new):
3003 def pushkey(self, namespace, key, old, new):
3009 try:
3004 try:
3010 tr = self.currenttransaction()
3005 tr = self.currenttransaction()
3011 hookargs = {}
3006 hookargs = {}
3012 if tr is not None:
3007 if tr is not None:
3013 hookargs.update(tr.hookargs)
3008 hookargs.update(tr.hookargs)
3014 hookargs = pycompat.strkwargs(hookargs)
3009 hookargs = pycompat.strkwargs(hookargs)
3015 hookargs[r'namespace'] = namespace
3010 hookargs[r'namespace'] = namespace
3016 hookargs[r'key'] = key
3011 hookargs[r'key'] = key
3017 hookargs[r'old'] = old
3012 hookargs[r'old'] = old
3018 hookargs[r'new'] = new
3013 hookargs[r'new'] = new
3019 self.hook('prepushkey', throw=True, **hookargs)
3014 self.hook('prepushkey', throw=True, **hookargs)
3020 except error.HookAbort as exc:
3015 except error.HookAbort as exc:
3021 self.ui.write_err(_("pushkey-abort: %s\n") % exc)
3016 self.ui.write_err(_("pushkey-abort: %s\n") % exc)
3022 if exc.hint:
3017 if exc.hint:
3023 self.ui.write_err(_("(%s)\n") % exc.hint)
3018 self.ui.write_err(_("(%s)\n") % exc.hint)
3024 return False
3019 return False
3025 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
3020 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
3026 ret = pushkey.push(self, namespace, key, old, new)
3021 ret = pushkey.push(self, namespace, key, old, new)
3027 def runhook():
3022 def runhook():
3028 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
3023 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
3029 ret=ret)
3024 ret=ret)
3030 self._afterlock(runhook)
3025 self._afterlock(runhook)
3031 return ret
3026 return ret
3032
3027
3033 def listkeys(self, namespace):
3028 def listkeys(self, namespace):
3034 self.hook('prelistkeys', throw=True, namespace=namespace)
3029 self.hook('prelistkeys', throw=True, namespace=namespace)
3035 self.ui.debug('listing keys for "%s"\n' % namespace)
3030 self.ui.debug('listing keys for "%s"\n' % namespace)
3036 values = pushkey.list(self, namespace)
3031 values = pushkey.list(self, namespace)
3037 self.hook('listkeys', namespace=namespace, values=values)
3032 self.hook('listkeys', namespace=namespace, values=values)
3038 return values
3033 return values
3039
3034
3040 def debugwireargs(self, one, two, three=None, four=None, five=None):
3035 def debugwireargs(self, one, two, three=None, four=None, five=None):
3041 '''used to test argument passing over the wire'''
3036 '''used to test argument passing over the wire'''
3042 return "%s %s %s %s %s" % (one, two, pycompat.bytestr(three),
3037 return "%s %s %s %s %s" % (one, two, pycompat.bytestr(three),
3043 pycompat.bytestr(four),
3038 pycompat.bytestr(four),
3044 pycompat.bytestr(five))
3039 pycompat.bytestr(five))
3045
3040
3046 def savecommitmessage(self, text):
3041 def savecommitmessage(self, text):
3047 fp = self.vfs('last-message.txt', 'wb')
3042 fp = self.vfs('last-message.txt', 'wb')
3048 try:
3043 try:
3049 fp.write(text)
3044 fp.write(text)
3050 finally:
3045 finally:
3051 fp.close()
3046 fp.close()
3052 return self.pathto(fp.name[len(self.root) + 1:])
3047 return self.pathto(fp.name[len(self.root) + 1:])
3053
3048
3054 # used to avoid circular references so destructors work
3049 # used to avoid circular references so destructors work
3055 def aftertrans(files):
3050 def aftertrans(files):
3056 renamefiles = [tuple(t) for t in files]
3051 renamefiles = [tuple(t) for t in files]
3057 def a():
3052 def a():
3058 for vfs, src, dest in renamefiles:
3053 for vfs, src, dest in renamefiles:
3059 # if src and dest refer to a same file, vfs.rename is a no-op,
3054 # if src and dest refer to a same file, vfs.rename is a no-op,
3060 # leaving both src and dest on disk. delete dest to make sure
3055 # leaving both src and dest on disk. delete dest to make sure
3061 # the rename couldn't be such a no-op.
3056 # the rename couldn't be such a no-op.
3062 vfs.tryunlink(dest)
3057 vfs.tryunlink(dest)
3063 try:
3058 try:
3064 vfs.rename(src, dest)
3059 vfs.rename(src, dest)
3065 except OSError: # journal file does not yet exist
3060 except OSError: # journal file does not yet exist
3066 pass
3061 pass
3067 return a
3062 return a
3068
3063
3069 def undoname(fn):
3064 def undoname(fn):
3070 base, name = os.path.split(fn)
3065 base, name = os.path.split(fn)
3071 assert name.startswith('journal')
3066 assert name.startswith('journal')
3072 return os.path.join(base, name.replace('journal', 'undo', 1))
3067 return os.path.join(base, name.replace('journal', 'undo', 1))
3073
3068
3074 def instance(ui, path, create, intents=None, createopts=None):
3069 def instance(ui, path, create, intents=None, createopts=None):
3075 localpath = util.urllocalpath(path)
3070 localpath = util.urllocalpath(path)
3076 if create:
3071 if create:
3077 createrepository(ui, localpath, createopts=createopts)
3072 createrepository(ui, localpath, createopts=createopts)
3078
3073
3079 return makelocalrepository(ui, localpath, intents=intents)
3074 return makelocalrepository(ui, localpath, intents=intents)
3080
3075
3081 def islocal(path):
3076 def islocal(path):
3082 return True
3077 return True
3083
3078
3084 def defaultcreateopts(ui, createopts=None):
3079 def defaultcreateopts(ui, createopts=None):
3085 """Populate the default creation options for a repository.
3080 """Populate the default creation options for a repository.
3086
3081
3087 A dictionary of explicitly requested creation options can be passed
3082 A dictionary of explicitly requested creation options can be passed
3088 in. Missing keys will be populated.
3083 in. Missing keys will be populated.
3089 """
3084 """
3090 createopts = dict(createopts or {})
3085 createopts = dict(createopts or {})
3091
3086
3092 if 'backend' not in createopts:
3087 if 'backend' not in createopts:
3093 # experimental config: storage.new-repo-backend
3088 # experimental config: storage.new-repo-backend
3094 createopts['backend'] = ui.config('storage', 'new-repo-backend')
3089 createopts['backend'] = ui.config('storage', 'new-repo-backend')
3095
3090
3096 return createopts
3091 return createopts
3097
3092
3098 def newreporequirements(ui, createopts):
3093 def newreporequirements(ui, createopts):
3099 """Determine the set of requirements for a new local repository.
3094 """Determine the set of requirements for a new local repository.
3100
3095
3101 Extensions can wrap this function to specify custom requirements for
3096 Extensions can wrap this function to specify custom requirements for
3102 new repositories.
3097 new repositories.
3103 """
3098 """
3104 # If the repo is being created from a shared repository, we copy
3099 # If the repo is being created from a shared repository, we copy
3105 # its requirements.
3100 # its requirements.
3106 if 'sharedrepo' in createopts:
3101 if 'sharedrepo' in createopts:
3107 requirements = set(createopts['sharedrepo'].requirements)
3102 requirements = set(createopts['sharedrepo'].requirements)
3108 if createopts.get('sharedrelative'):
3103 if createopts.get('sharedrelative'):
3109 requirements.add('relshared')
3104 requirements.add('relshared')
3110 else:
3105 else:
3111 requirements.add('shared')
3106 requirements.add('shared')
3112
3107
3113 return requirements
3108 return requirements
3114
3109
3115 if 'backend' not in createopts:
3110 if 'backend' not in createopts:
3116 raise error.ProgrammingError('backend key not present in createopts; '
3111 raise error.ProgrammingError('backend key not present in createopts; '
3117 'was defaultcreateopts() called?')
3112 'was defaultcreateopts() called?')
3118
3113
3119 if createopts['backend'] != 'revlogv1':
3114 if createopts['backend'] != 'revlogv1':
3120 raise error.Abort(_('unable to determine repository requirements for '
3115 raise error.Abort(_('unable to determine repository requirements for '
3121 'storage backend: %s') % createopts['backend'])
3116 'storage backend: %s') % createopts['backend'])
3122
3117
3123 requirements = {'revlogv1'}
3118 requirements = {'revlogv1'}
3124 if ui.configbool('format', 'usestore'):
3119 if ui.configbool('format', 'usestore'):
3125 requirements.add('store')
3120 requirements.add('store')
3126 if ui.configbool('format', 'usefncache'):
3121 if ui.configbool('format', 'usefncache'):
3127 requirements.add('fncache')
3122 requirements.add('fncache')
3128 if ui.configbool('format', 'dotencode'):
3123 if ui.configbool('format', 'dotencode'):
3129 requirements.add('dotencode')
3124 requirements.add('dotencode')
3130
3125
3131 compengine = ui.config('format', 'revlog-compression')
3126 compengine = ui.config('format', 'revlog-compression')
3132 if compengine not in util.compengines:
3127 if compengine not in util.compengines:
3133 raise error.Abort(_('compression engine %s defined by '
3128 raise error.Abort(_('compression engine %s defined by '
3134 'format.revlog-compression not available') %
3129 'format.revlog-compression not available') %
3135 compengine,
3130 compengine,
3136 hint=_('run "hg debuginstall" to list available '
3131 hint=_('run "hg debuginstall" to list available '
3137 'compression engines'))
3132 'compression engines'))
3138
3133
3139 # zlib is the historical default and doesn't need an explicit requirement.
3134 # zlib is the historical default and doesn't need an explicit requirement.
3140 elif compengine == 'zstd':
3135 elif compengine == 'zstd':
3141 requirements.add('revlog-compression-zstd')
3136 requirements.add('revlog-compression-zstd')
3142 elif compengine != 'zlib':
3137 elif compengine != 'zlib':
3143 requirements.add('exp-compression-%s' % compengine)
3138 requirements.add('exp-compression-%s' % compengine)
3144
3139
3145 if scmutil.gdinitconfig(ui):
3140 if scmutil.gdinitconfig(ui):
3146 requirements.add('generaldelta')
3141 requirements.add('generaldelta')
3147 if ui.configbool('format', 'sparse-revlog'):
3142 if ui.configbool('format', 'sparse-revlog'):
3148 requirements.add(SPARSEREVLOG_REQUIREMENT)
3143 requirements.add(SPARSEREVLOG_REQUIREMENT)
3149 if ui.configbool('experimental', 'treemanifest'):
3144 if ui.configbool('experimental', 'treemanifest'):
3150 requirements.add('treemanifest')
3145 requirements.add('treemanifest')
3151
3146
3152 revlogv2 = ui.config('experimental', 'revlogv2')
3147 revlogv2 = ui.config('experimental', 'revlogv2')
3153 if revlogv2 == 'enable-unstable-format-and-corrupt-my-data':
3148 if revlogv2 == 'enable-unstable-format-and-corrupt-my-data':
3154 requirements.remove('revlogv1')
3149 requirements.remove('revlogv1')
3155 # generaldelta is implied by revlogv2.
3150 # generaldelta is implied by revlogv2.
3156 requirements.discard('generaldelta')
3151 requirements.discard('generaldelta')
3157 requirements.add(REVLOGV2_REQUIREMENT)
3152 requirements.add(REVLOGV2_REQUIREMENT)
3158 # experimental config: format.internal-phase
3153 # experimental config: format.internal-phase
3159 if ui.configbool('format', 'internal-phase'):
3154 if ui.configbool('format', 'internal-phase'):
3160 requirements.add('internal-phase')
3155 requirements.add('internal-phase')
3161
3156
3162 if createopts.get('narrowfiles'):
3157 if createopts.get('narrowfiles'):
3163 requirements.add(repository.NARROW_REQUIREMENT)
3158 requirements.add(repository.NARROW_REQUIREMENT)
3164
3159
3165 if createopts.get('lfs'):
3160 if createopts.get('lfs'):
3166 requirements.add('lfs')
3161 requirements.add('lfs')
3167
3162
3168 if ui.configbool('format', 'bookmarks-in-store'):
3163 if ui.configbool('format', 'bookmarks-in-store'):
3169 requirements.add(bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT)
3164 requirements.add(bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT)
3170
3165
3171 return requirements
3166 return requirements
3172
3167
3173 def filterknowncreateopts(ui, createopts):
3168 def filterknowncreateopts(ui, createopts):
3174 """Filters a dict of repo creation options against options that are known.
3169 """Filters a dict of repo creation options against options that are known.
3175
3170
3176 Receives a dict of repo creation options and returns a dict of those
3171 Receives a dict of repo creation options and returns a dict of those
3177 options that we don't know how to handle.
3172 options that we don't know how to handle.
3178
3173
3179 This function is called as part of repository creation. If the
3174 This function is called as part of repository creation. If the
3180 returned dict contains any items, repository creation will not
3175 returned dict contains any items, repository creation will not
3181 be allowed, as it means there was a request to create a repository
3176 be allowed, as it means there was a request to create a repository
3182 with options not recognized by loaded code.
3177 with options not recognized by loaded code.
3183
3178
3184 Extensions can wrap this function to filter out creation options
3179 Extensions can wrap this function to filter out creation options
3185 they know how to handle.
3180 they know how to handle.
3186 """
3181 """
3187 known = {
3182 known = {
3188 'backend',
3183 'backend',
3189 'lfs',
3184 'lfs',
3190 'narrowfiles',
3185 'narrowfiles',
3191 'sharedrepo',
3186 'sharedrepo',
3192 'sharedrelative',
3187 'sharedrelative',
3193 'shareditems',
3188 'shareditems',
3194 'shallowfilestore',
3189 'shallowfilestore',
3195 }
3190 }
3196
3191
3197 return {k: v for k, v in createopts.items() if k not in known}
3192 return {k: v for k, v in createopts.items() if k not in known}
3198
3193
3199 def createrepository(ui, path, createopts=None):
3194 def createrepository(ui, path, createopts=None):
3200 """Create a new repository in a vfs.
3195 """Create a new repository in a vfs.
3201
3196
3202 ``path`` path to the new repo's working directory.
3197 ``path`` path to the new repo's working directory.
3203 ``createopts`` options for the new repository.
3198 ``createopts`` options for the new repository.
3204
3199
3205 The following keys for ``createopts`` are recognized:
3200 The following keys for ``createopts`` are recognized:
3206
3201
3207 backend
3202 backend
3208 The storage backend to use.
3203 The storage backend to use.
3209 lfs
3204 lfs
3210 Repository will be created with ``lfs`` requirement. The lfs extension
3205 Repository will be created with ``lfs`` requirement. The lfs extension
3211 will automatically be loaded when the repository is accessed.
3206 will automatically be loaded when the repository is accessed.
3212 narrowfiles
3207 narrowfiles
3213 Set up repository to support narrow file storage.
3208 Set up repository to support narrow file storage.
3214 sharedrepo
3209 sharedrepo
3215 Repository object from which storage should be shared.
3210 Repository object from which storage should be shared.
3216 sharedrelative
3211 sharedrelative
3217 Boolean indicating if the path to the shared repo should be
3212 Boolean indicating if the path to the shared repo should be
3218 stored as relative. By default, the pointer to the "parent" repo
3213 stored as relative. By default, the pointer to the "parent" repo
3219 is stored as an absolute path.
3214 is stored as an absolute path.
3220 shareditems
3215 shareditems
3221 Set of items to share to the new repository (in addition to storage).
3216 Set of items to share to the new repository (in addition to storage).
3222 shallowfilestore
3217 shallowfilestore
3223 Indicates that storage for files should be shallow (not all ancestor
3218 Indicates that storage for files should be shallow (not all ancestor
3224 revisions are known).
3219 revisions are known).
3225 """
3220 """
3226 createopts = defaultcreateopts(ui, createopts=createopts)
3221 createopts = defaultcreateopts(ui, createopts=createopts)
3227
3222
3228 unknownopts = filterknowncreateopts(ui, createopts)
3223 unknownopts = filterknowncreateopts(ui, createopts)
3229
3224
3230 if not isinstance(unknownopts, dict):
3225 if not isinstance(unknownopts, dict):
3231 raise error.ProgrammingError('filterknowncreateopts() did not return '
3226 raise error.ProgrammingError('filterknowncreateopts() did not return '
3232 'a dict')
3227 'a dict')
3233
3228
3234 if unknownopts:
3229 if unknownopts:
3235 raise error.Abort(_('unable to create repository because of unknown '
3230 raise error.Abort(_('unable to create repository because of unknown '
3236 'creation option: %s') %
3231 'creation option: %s') %
3237 ', '.join(sorted(unknownopts)),
3232 ', '.join(sorted(unknownopts)),
3238 hint=_('is a required extension not loaded?'))
3233 hint=_('is a required extension not loaded?'))
3239
3234
3240 requirements = newreporequirements(ui, createopts=createopts)
3235 requirements = newreporequirements(ui, createopts=createopts)
3241
3236
3242 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
3237 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
3243
3238
3244 hgvfs = vfsmod.vfs(wdirvfs.join(b'.hg'))
3239 hgvfs = vfsmod.vfs(wdirvfs.join(b'.hg'))
3245 if hgvfs.exists():
3240 if hgvfs.exists():
3246 raise error.RepoError(_('repository %s already exists') % path)
3241 raise error.RepoError(_('repository %s already exists') % path)
3247
3242
3248 if 'sharedrepo' in createopts:
3243 if 'sharedrepo' in createopts:
3249 sharedpath = createopts['sharedrepo'].sharedpath
3244 sharedpath = createopts['sharedrepo'].sharedpath
3250
3245
3251 if createopts.get('sharedrelative'):
3246 if createopts.get('sharedrelative'):
3252 try:
3247 try:
3253 sharedpath = os.path.relpath(sharedpath, hgvfs.base)
3248 sharedpath = os.path.relpath(sharedpath, hgvfs.base)
3254 except (IOError, ValueError) as e:
3249 except (IOError, ValueError) as e:
3255 # ValueError is raised on Windows if the drive letters differ
3250 # ValueError is raised on Windows if the drive letters differ
3256 # on each path.
3251 # on each path.
3257 raise error.Abort(_('cannot calculate relative path'),
3252 raise error.Abort(_('cannot calculate relative path'),
3258 hint=stringutil.forcebytestr(e))
3253 hint=stringutil.forcebytestr(e))
3259
3254
3260 if not wdirvfs.exists():
3255 if not wdirvfs.exists():
3261 wdirvfs.makedirs()
3256 wdirvfs.makedirs()
3262
3257
3263 hgvfs.makedir(notindexed=True)
3258 hgvfs.makedir(notindexed=True)
3264 if 'sharedrepo' not in createopts:
3259 if 'sharedrepo' not in createopts:
3265 hgvfs.mkdir(b'cache')
3260 hgvfs.mkdir(b'cache')
3266 hgvfs.mkdir(b'wcache')
3261 hgvfs.mkdir(b'wcache')
3267
3262
3268 if b'store' in requirements and 'sharedrepo' not in createopts:
3263 if b'store' in requirements and 'sharedrepo' not in createopts:
3269 hgvfs.mkdir(b'store')
3264 hgvfs.mkdir(b'store')
3270
3265
3271 # We create an invalid changelog outside the store so very old
3266 # We create an invalid changelog outside the store so very old
3272 # Mercurial versions (which didn't know about the requirements
3267 # Mercurial versions (which didn't know about the requirements
3273 # file) encounter an error on reading the changelog. This
3268 # file) encounter an error on reading the changelog. This
3274 # effectively locks out old clients and prevents them from
3269 # effectively locks out old clients and prevents them from
3275 # mucking with a repo in an unknown format.
3270 # mucking with a repo in an unknown format.
3276 #
3271 #
3277 # The revlog header has version 2, which won't be recognized by
3272 # The revlog header has version 2, which won't be recognized by
3278 # such old clients.
3273 # such old clients.
3279 hgvfs.append(b'00changelog.i',
3274 hgvfs.append(b'00changelog.i',
3280 b'\0\0\0\2 dummy changelog to prevent using the old repo '
3275 b'\0\0\0\2 dummy changelog to prevent using the old repo '
3281 b'layout')
3276 b'layout')
3282
3277
3283 scmutil.writerequires(hgvfs, requirements)
3278 scmutil.writerequires(hgvfs, requirements)
3284
3279
3285 # Write out file telling readers where to find the shared store.
3280 # Write out file telling readers where to find the shared store.
3286 if 'sharedrepo' in createopts:
3281 if 'sharedrepo' in createopts:
3287 hgvfs.write(b'sharedpath', sharedpath)
3282 hgvfs.write(b'sharedpath', sharedpath)
3288
3283
3289 if createopts.get('shareditems'):
3284 if createopts.get('shareditems'):
3290 shared = b'\n'.join(sorted(createopts['shareditems'])) + b'\n'
3285 shared = b'\n'.join(sorted(createopts['shareditems'])) + b'\n'
3291 hgvfs.write(b'shared', shared)
3286 hgvfs.write(b'shared', shared)
3292
3287
3293 def poisonrepository(repo):
3288 def poisonrepository(repo):
3294 """Poison a repository instance so it can no longer be used."""
3289 """Poison a repository instance so it can no longer be used."""
3295 # Perform any cleanup on the instance.
3290 # Perform any cleanup on the instance.
3296 repo.close()
3291 repo.close()
3297
3292
3298 # Our strategy is to replace the type of the object with one that
3293 # Our strategy is to replace the type of the object with one that
3299 # has all attribute lookups result in error.
3294 # has all attribute lookups result in error.
3300 #
3295 #
3301 # But we have to allow the close() method because some constructors
3296 # But we have to allow the close() method because some constructors
3302 # of repos call close() on repo references.
3297 # of repos call close() on repo references.
3303 class poisonedrepository(object):
3298 class poisonedrepository(object):
3304 def __getattribute__(self, item):
3299 def __getattribute__(self, item):
3305 if item == r'close':
3300 if item == r'close':
3306 return object.__getattribute__(self, item)
3301 return object.__getattribute__(self, item)
3307
3302
3308 raise error.ProgrammingError('repo instances should not be used '
3303 raise error.ProgrammingError('repo instances should not be used '
3309 'after unshare')
3304 'after unshare')
3310
3305
3311 def close(self):
3306 def close(self):
3312 pass
3307 pass
3313
3308
3314 # We may have a repoview, which intercepts __setattr__. So be sure
3309 # We may have a repoview, which intercepts __setattr__. So be sure
3315 # we operate at the lowest level possible.
3310 # we operate at the lowest level possible.
3316 object.__setattr__(repo, r'__class__', poisonedrepository)
3311 object.__setattr__(repo, r'__class__', poisonedrepository)
@@ -1,669 +1,679 b''
1 # store.py - repository store handling for Mercurial
1 # store.py - repository store handling for Mercurial
2 #
2 #
3 # Copyright 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2008 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 functools
11 import functools
12 import hashlib
12 import hashlib
13 import os
13 import os
14 import stat
14 import stat
15
15
16 from .i18n import _
16 from .i18n import _
17 from . import (
17 from . import (
18 changelog,
18 error,
19 error,
20 manifest,
19 node,
21 node,
20 policy,
22 policy,
21 pycompat,
23 pycompat,
22 util,
24 util,
23 vfs as vfsmod,
25 vfs as vfsmod,
24 )
26 )
25
27
26 parsers = policy.importmod(r'parsers')
28 parsers = policy.importmod(r'parsers')
27 # how much bytes should be read from fncache in one read
29 # how much bytes should be read from fncache in one read
28 # It is done to prevent loading large fncache files into memory
30 # It is done to prevent loading large fncache files into memory
29 fncache_chunksize = 10 ** 6
31 fncache_chunksize = 10 ** 6
30
32
31 def _matchtrackedpath(path, matcher):
33 def _matchtrackedpath(path, matcher):
32 """parses a fncache entry and returns whether the entry is tracking a path
34 """parses a fncache entry and returns whether the entry is tracking a path
33 matched by matcher or not.
35 matched by matcher or not.
34
36
35 If matcher is None, returns True"""
37 If matcher is None, returns True"""
36
38
37 if matcher is None:
39 if matcher is None:
38 return True
40 return True
39 path = decodedir(path)
41 path = decodedir(path)
40 if path.startswith('data/'):
42 if path.startswith('data/'):
41 return matcher(path[len('data/'):-len('.i')])
43 return matcher(path[len('data/'):-len('.i')])
42 elif path.startswith('meta/'):
44 elif path.startswith('meta/'):
43 return matcher.visitdir(path[len('meta/'):-len('/00manifest.i')])
45 return matcher.visitdir(path[len('meta/'):-len('/00manifest.i')])
44
46
45 raise error.ProgrammingError("cannot decode path %s" % path)
47 raise error.ProgrammingError("cannot decode path %s" % path)
46
48
47 # This avoids a collision between a file named foo and a dir named
49 # This avoids a collision between a file named foo and a dir named
48 # foo.i or foo.d
50 # foo.i or foo.d
49 def _encodedir(path):
51 def _encodedir(path):
50 '''
52 '''
51 >>> _encodedir(b'data/foo.i')
53 >>> _encodedir(b'data/foo.i')
52 'data/foo.i'
54 'data/foo.i'
53 >>> _encodedir(b'data/foo.i/bla.i')
55 >>> _encodedir(b'data/foo.i/bla.i')
54 'data/foo.i.hg/bla.i'
56 'data/foo.i.hg/bla.i'
55 >>> _encodedir(b'data/foo.i.hg/bla.i')
57 >>> _encodedir(b'data/foo.i.hg/bla.i')
56 'data/foo.i.hg.hg/bla.i'
58 'data/foo.i.hg.hg/bla.i'
57 >>> _encodedir(b'data/foo.i\\ndata/foo.i/bla.i\\ndata/foo.i.hg/bla.i\\n')
59 >>> _encodedir(b'data/foo.i\\ndata/foo.i/bla.i\\ndata/foo.i.hg/bla.i\\n')
58 'data/foo.i\\ndata/foo.i.hg/bla.i\\ndata/foo.i.hg.hg/bla.i\\n'
60 'data/foo.i\\ndata/foo.i.hg/bla.i\\ndata/foo.i.hg.hg/bla.i\\n'
59 '''
61 '''
60 return (path
62 return (path
61 .replace(".hg/", ".hg.hg/")
63 .replace(".hg/", ".hg.hg/")
62 .replace(".i/", ".i.hg/")
64 .replace(".i/", ".i.hg/")
63 .replace(".d/", ".d.hg/"))
65 .replace(".d/", ".d.hg/"))
64
66
65 encodedir = getattr(parsers, 'encodedir', _encodedir)
67 encodedir = getattr(parsers, 'encodedir', _encodedir)
66
68
67 def decodedir(path):
69 def decodedir(path):
68 '''
70 '''
69 >>> decodedir(b'data/foo.i')
71 >>> decodedir(b'data/foo.i')
70 'data/foo.i'
72 'data/foo.i'
71 >>> decodedir(b'data/foo.i.hg/bla.i')
73 >>> decodedir(b'data/foo.i.hg/bla.i')
72 'data/foo.i/bla.i'
74 'data/foo.i/bla.i'
73 >>> decodedir(b'data/foo.i.hg.hg/bla.i')
75 >>> decodedir(b'data/foo.i.hg.hg/bla.i')
74 'data/foo.i.hg/bla.i'
76 'data/foo.i.hg/bla.i'
75 '''
77 '''
76 if ".hg/" not in path:
78 if ".hg/" not in path:
77 return path
79 return path
78 return (path
80 return (path
79 .replace(".d.hg/", ".d/")
81 .replace(".d.hg/", ".d/")
80 .replace(".i.hg/", ".i/")
82 .replace(".i.hg/", ".i/")
81 .replace(".hg.hg/", ".hg/"))
83 .replace(".hg.hg/", ".hg/"))
82
84
83 def _reserved():
85 def _reserved():
84 ''' characters that are problematic for filesystems
86 ''' characters that are problematic for filesystems
85
87
86 * ascii escapes (0..31)
88 * ascii escapes (0..31)
87 * ascii hi (126..255)
89 * ascii hi (126..255)
88 * windows specials
90 * windows specials
89
91
90 these characters will be escaped by encodefunctions
92 these characters will be escaped by encodefunctions
91 '''
93 '''
92 winreserved = [ord(x) for x in u'\\:*?"<>|']
94 winreserved = [ord(x) for x in u'\\:*?"<>|']
93 for x in range(32):
95 for x in range(32):
94 yield x
96 yield x
95 for x in range(126, 256):
97 for x in range(126, 256):
96 yield x
98 yield x
97 for x in winreserved:
99 for x in winreserved:
98 yield x
100 yield x
99
101
100 def _buildencodefun():
102 def _buildencodefun():
101 '''
103 '''
102 >>> enc, dec = _buildencodefun()
104 >>> enc, dec = _buildencodefun()
103
105
104 >>> enc(b'nothing/special.txt')
106 >>> enc(b'nothing/special.txt')
105 'nothing/special.txt'
107 'nothing/special.txt'
106 >>> dec(b'nothing/special.txt')
108 >>> dec(b'nothing/special.txt')
107 'nothing/special.txt'
109 'nothing/special.txt'
108
110
109 >>> enc(b'HELLO')
111 >>> enc(b'HELLO')
110 '_h_e_l_l_o'
112 '_h_e_l_l_o'
111 >>> dec(b'_h_e_l_l_o')
113 >>> dec(b'_h_e_l_l_o')
112 'HELLO'
114 'HELLO'
113
115
114 >>> enc(b'hello:world?')
116 >>> enc(b'hello:world?')
115 'hello~3aworld~3f'
117 'hello~3aworld~3f'
116 >>> dec(b'hello~3aworld~3f')
118 >>> dec(b'hello~3aworld~3f')
117 'hello:world?'
119 'hello:world?'
118
120
119 >>> enc(b'the\\x07quick\\xADshot')
121 >>> enc(b'the\\x07quick\\xADshot')
120 'the~07quick~adshot'
122 'the~07quick~adshot'
121 >>> dec(b'the~07quick~adshot')
123 >>> dec(b'the~07quick~adshot')
122 'the\\x07quick\\xadshot'
124 'the\\x07quick\\xadshot'
123 '''
125 '''
124 e = '_'
126 e = '_'
125 xchr = pycompat.bytechr
127 xchr = pycompat.bytechr
126 asciistr = list(map(xchr, range(127)))
128 asciistr = list(map(xchr, range(127)))
127 capitals = list(range(ord("A"), ord("Z") + 1))
129 capitals = list(range(ord("A"), ord("Z") + 1))
128
130
129 cmap = dict((x, x) for x in asciistr)
131 cmap = dict((x, x) for x in asciistr)
130 for x in _reserved():
132 for x in _reserved():
131 cmap[xchr(x)] = "~%02x" % x
133 cmap[xchr(x)] = "~%02x" % x
132 for x in capitals + [ord(e)]:
134 for x in capitals + [ord(e)]:
133 cmap[xchr(x)] = e + xchr(x).lower()
135 cmap[xchr(x)] = e + xchr(x).lower()
134
136
135 dmap = {}
137 dmap = {}
136 for k, v in cmap.iteritems():
138 for k, v in cmap.iteritems():
137 dmap[v] = k
139 dmap[v] = k
138 def decode(s):
140 def decode(s):
139 i = 0
141 i = 0
140 while i < len(s):
142 while i < len(s):
141 for l in pycompat.xrange(1, 4):
143 for l in pycompat.xrange(1, 4):
142 try:
144 try:
143 yield dmap[s[i:i + l]]
145 yield dmap[s[i:i + l]]
144 i += l
146 i += l
145 break
147 break
146 except KeyError:
148 except KeyError:
147 pass
149 pass
148 else:
150 else:
149 raise KeyError
151 raise KeyError
150 return (lambda s: ''.join([cmap[s[c:c + 1]]
152 return (lambda s: ''.join([cmap[s[c:c + 1]]
151 for c in pycompat.xrange(len(s))]),
153 for c in pycompat.xrange(len(s))]),
152 lambda s: ''.join(list(decode(s))))
154 lambda s: ''.join(list(decode(s))))
153
155
154 _encodefname, _decodefname = _buildencodefun()
156 _encodefname, _decodefname = _buildencodefun()
155
157
156 def encodefilename(s):
158 def encodefilename(s):
157 '''
159 '''
158 >>> encodefilename(b'foo.i/bar.d/bla.hg/hi:world?/HELLO')
160 >>> encodefilename(b'foo.i/bar.d/bla.hg/hi:world?/HELLO')
159 'foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o'
161 'foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o'
160 '''
162 '''
161 return _encodefname(encodedir(s))
163 return _encodefname(encodedir(s))
162
164
163 def decodefilename(s):
165 def decodefilename(s):
164 '''
166 '''
165 >>> decodefilename(b'foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o')
167 >>> decodefilename(b'foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o')
166 'foo.i/bar.d/bla.hg/hi:world?/HELLO'
168 'foo.i/bar.d/bla.hg/hi:world?/HELLO'
167 '''
169 '''
168 return decodedir(_decodefname(s))
170 return decodedir(_decodefname(s))
169
171
170 def _buildlowerencodefun():
172 def _buildlowerencodefun():
171 '''
173 '''
172 >>> f = _buildlowerencodefun()
174 >>> f = _buildlowerencodefun()
173 >>> f(b'nothing/special.txt')
175 >>> f(b'nothing/special.txt')
174 'nothing/special.txt'
176 'nothing/special.txt'
175 >>> f(b'HELLO')
177 >>> f(b'HELLO')
176 'hello'
178 'hello'
177 >>> f(b'hello:world?')
179 >>> f(b'hello:world?')
178 'hello~3aworld~3f'
180 'hello~3aworld~3f'
179 >>> f(b'the\\x07quick\\xADshot')
181 >>> f(b'the\\x07quick\\xADshot')
180 'the~07quick~adshot'
182 'the~07quick~adshot'
181 '''
183 '''
182 xchr = pycompat.bytechr
184 xchr = pycompat.bytechr
183 cmap = dict([(xchr(x), xchr(x)) for x in pycompat.xrange(127)])
185 cmap = dict([(xchr(x), xchr(x)) for x in pycompat.xrange(127)])
184 for x in _reserved():
186 for x in _reserved():
185 cmap[xchr(x)] = "~%02x" % x
187 cmap[xchr(x)] = "~%02x" % x
186 for x in range(ord("A"), ord("Z") + 1):
188 for x in range(ord("A"), ord("Z") + 1):
187 cmap[xchr(x)] = xchr(x).lower()
189 cmap[xchr(x)] = xchr(x).lower()
188 def lowerencode(s):
190 def lowerencode(s):
189 return "".join([cmap[c] for c in pycompat.iterbytestr(s)])
191 return "".join([cmap[c] for c in pycompat.iterbytestr(s)])
190 return lowerencode
192 return lowerencode
191
193
192 lowerencode = getattr(parsers, 'lowerencode', None) or _buildlowerencodefun()
194 lowerencode = getattr(parsers, 'lowerencode', None) or _buildlowerencodefun()
193
195
194 # Windows reserved names: con, prn, aux, nul, com1..com9, lpt1..lpt9
196 # Windows reserved names: con, prn, aux, nul, com1..com9, lpt1..lpt9
195 _winres3 = ('aux', 'con', 'prn', 'nul') # length 3
197 _winres3 = ('aux', 'con', 'prn', 'nul') # length 3
196 _winres4 = ('com', 'lpt') # length 4 (with trailing 1..9)
198 _winres4 = ('com', 'lpt') # length 4 (with trailing 1..9)
197 def _auxencode(path, dotencode):
199 def _auxencode(path, dotencode):
198 '''
200 '''
199 Encodes filenames containing names reserved by Windows or which end in
201 Encodes filenames containing names reserved by Windows or which end in
200 period or space. Does not touch other single reserved characters c.
202 period or space. Does not touch other single reserved characters c.
201 Specifically, c in '\\:*?"<>|' or ord(c) <= 31 are *not* encoded here.
203 Specifically, c in '\\:*?"<>|' or ord(c) <= 31 are *not* encoded here.
202 Additionally encodes space or period at the beginning, if dotencode is
204 Additionally encodes space or period at the beginning, if dotencode is
203 True. Parameter path is assumed to be all lowercase.
205 True. Parameter path is assumed to be all lowercase.
204 A segment only needs encoding if a reserved name appears as a
206 A segment only needs encoding if a reserved name appears as a
205 basename (e.g. "aux", "aux.foo"). A directory or file named "foo.aux"
207 basename (e.g. "aux", "aux.foo"). A directory or file named "foo.aux"
206 doesn't need encoding.
208 doesn't need encoding.
207
209
208 >>> s = b'.foo/aux.txt/txt.aux/con/prn/nul/foo.'
210 >>> s = b'.foo/aux.txt/txt.aux/con/prn/nul/foo.'
209 >>> _auxencode(s.split(b'/'), True)
211 >>> _auxencode(s.split(b'/'), True)
210 ['~2efoo', 'au~78.txt', 'txt.aux', 'co~6e', 'pr~6e', 'nu~6c', 'foo~2e']
212 ['~2efoo', 'au~78.txt', 'txt.aux', 'co~6e', 'pr~6e', 'nu~6c', 'foo~2e']
211 >>> s = b'.com1com2/lpt9.lpt4.lpt1/conprn/com0/lpt0/foo.'
213 >>> s = b'.com1com2/lpt9.lpt4.lpt1/conprn/com0/lpt0/foo.'
212 >>> _auxencode(s.split(b'/'), False)
214 >>> _auxencode(s.split(b'/'), False)
213 ['.com1com2', 'lp~749.lpt4.lpt1', 'conprn', 'com0', 'lpt0', 'foo~2e']
215 ['.com1com2', 'lp~749.lpt4.lpt1', 'conprn', 'com0', 'lpt0', 'foo~2e']
214 >>> _auxencode([b'foo. '], True)
216 >>> _auxencode([b'foo. '], True)
215 ['foo.~20']
217 ['foo.~20']
216 >>> _auxencode([b' .foo'], True)
218 >>> _auxencode([b' .foo'], True)
217 ['~20.foo']
219 ['~20.foo']
218 '''
220 '''
219 for i, n in enumerate(path):
221 for i, n in enumerate(path):
220 if not n:
222 if not n:
221 continue
223 continue
222 if dotencode and n[0] in '. ':
224 if dotencode and n[0] in '. ':
223 n = "~%02x" % ord(n[0:1]) + n[1:]
225 n = "~%02x" % ord(n[0:1]) + n[1:]
224 path[i] = n
226 path[i] = n
225 else:
227 else:
226 l = n.find('.')
228 l = n.find('.')
227 if l == -1:
229 if l == -1:
228 l = len(n)
230 l = len(n)
229 if ((l == 3 and n[:3] in _winres3) or
231 if ((l == 3 and n[:3] in _winres3) or
230 (l == 4 and n[3:4] <= '9' and n[3:4] >= '1'
232 (l == 4 and n[3:4] <= '9' and n[3:4] >= '1'
231 and n[:3] in _winres4)):
233 and n[:3] in _winres4)):
232 # encode third letter ('aux' -> 'au~78')
234 # encode third letter ('aux' -> 'au~78')
233 ec = "~%02x" % ord(n[2:3])
235 ec = "~%02x" % ord(n[2:3])
234 n = n[0:2] + ec + n[3:]
236 n = n[0:2] + ec + n[3:]
235 path[i] = n
237 path[i] = n
236 if n[-1] in '. ':
238 if n[-1] in '. ':
237 # encode last period or space ('foo...' -> 'foo..~2e')
239 # encode last period or space ('foo...' -> 'foo..~2e')
238 path[i] = n[:-1] + "~%02x" % ord(n[-1:])
240 path[i] = n[:-1] + "~%02x" % ord(n[-1:])
239 return path
241 return path
240
242
241 _maxstorepathlen = 120
243 _maxstorepathlen = 120
242 _dirprefixlen = 8
244 _dirprefixlen = 8
243 _maxshortdirslen = 8 * (_dirprefixlen + 1) - 4
245 _maxshortdirslen = 8 * (_dirprefixlen + 1) - 4
244
246
245 def _hashencode(path, dotencode):
247 def _hashencode(path, dotencode):
246 digest = node.hex(hashlib.sha1(path).digest())
248 digest = node.hex(hashlib.sha1(path).digest())
247 le = lowerencode(path[5:]).split('/') # skips prefix 'data/' or 'meta/'
249 le = lowerencode(path[5:]).split('/') # skips prefix 'data/' or 'meta/'
248 parts = _auxencode(le, dotencode)
250 parts = _auxencode(le, dotencode)
249 basename = parts[-1]
251 basename = parts[-1]
250 _root, ext = os.path.splitext(basename)
252 _root, ext = os.path.splitext(basename)
251 sdirs = []
253 sdirs = []
252 sdirslen = 0
254 sdirslen = 0
253 for p in parts[:-1]:
255 for p in parts[:-1]:
254 d = p[:_dirprefixlen]
256 d = p[:_dirprefixlen]
255 if d[-1] in '. ':
257 if d[-1] in '. ':
256 # Windows can't access dirs ending in period or space
258 # Windows can't access dirs ending in period or space
257 d = d[:-1] + '_'
259 d = d[:-1] + '_'
258 if sdirslen == 0:
260 if sdirslen == 0:
259 t = len(d)
261 t = len(d)
260 else:
262 else:
261 t = sdirslen + 1 + len(d)
263 t = sdirslen + 1 + len(d)
262 if t > _maxshortdirslen:
264 if t > _maxshortdirslen:
263 break
265 break
264 sdirs.append(d)
266 sdirs.append(d)
265 sdirslen = t
267 sdirslen = t
266 dirs = '/'.join(sdirs)
268 dirs = '/'.join(sdirs)
267 if len(dirs) > 0:
269 if len(dirs) > 0:
268 dirs += '/'
270 dirs += '/'
269 res = 'dh/' + dirs + digest + ext
271 res = 'dh/' + dirs + digest + ext
270 spaceleft = _maxstorepathlen - len(res)
272 spaceleft = _maxstorepathlen - len(res)
271 if spaceleft > 0:
273 if spaceleft > 0:
272 filler = basename[:spaceleft]
274 filler = basename[:spaceleft]
273 res = 'dh/' + dirs + filler + digest + ext
275 res = 'dh/' + dirs + filler + digest + ext
274 return res
276 return res
275
277
276 def _hybridencode(path, dotencode):
278 def _hybridencode(path, dotencode):
277 '''encodes path with a length limit
279 '''encodes path with a length limit
278
280
279 Encodes all paths that begin with 'data/', according to the following.
281 Encodes all paths that begin with 'data/', according to the following.
280
282
281 Default encoding (reversible):
283 Default encoding (reversible):
282
284
283 Encodes all uppercase letters 'X' as '_x'. All reserved or illegal
285 Encodes all uppercase letters 'X' as '_x'. All reserved or illegal
284 characters are encoded as '~xx', where xx is the two digit hex code
286 characters are encoded as '~xx', where xx is the two digit hex code
285 of the character (see encodefilename).
287 of the character (see encodefilename).
286 Relevant path components consisting of Windows reserved filenames are
288 Relevant path components consisting of Windows reserved filenames are
287 masked by encoding the third character ('aux' -> 'au~78', see _auxencode).
289 masked by encoding the third character ('aux' -> 'au~78', see _auxencode).
288
290
289 Hashed encoding (not reversible):
291 Hashed encoding (not reversible):
290
292
291 If the default-encoded path is longer than _maxstorepathlen, a
293 If the default-encoded path is longer than _maxstorepathlen, a
292 non-reversible hybrid hashing of the path is done instead.
294 non-reversible hybrid hashing of the path is done instead.
293 This encoding uses up to _dirprefixlen characters of all directory
295 This encoding uses up to _dirprefixlen characters of all directory
294 levels of the lowerencoded path, but not more levels than can fit into
296 levels of the lowerencoded path, but not more levels than can fit into
295 _maxshortdirslen.
297 _maxshortdirslen.
296 Then follows the filler followed by the sha digest of the full path.
298 Then follows the filler followed by the sha digest of the full path.
297 The filler is the beginning of the basename of the lowerencoded path
299 The filler is the beginning of the basename of the lowerencoded path
298 (the basename is everything after the last path separator). The filler
300 (the basename is everything after the last path separator). The filler
299 is as long as possible, filling in characters from the basename until
301 is as long as possible, filling in characters from the basename until
300 the encoded path has _maxstorepathlen characters (or all chars of the
302 the encoded path has _maxstorepathlen characters (or all chars of the
301 basename have been taken).
303 basename have been taken).
302 The extension (e.g. '.i' or '.d') is preserved.
304 The extension (e.g. '.i' or '.d') is preserved.
303
305
304 The string 'data/' at the beginning is replaced with 'dh/', if the hashed
306 The string 'data/' at the beginning is replaced with 'dh/', if the hashed
305 encoding was used.
307 encoding was used.
306 '''
308 '''
307 path = encodedir(path)
309 path = encodedir(path)
308 ef = _encodefname(path).split('/')
310 ef = _encodefname(path).split('/')
309 res = '/'.join(_auxencode(ef, dotencode))
311 res = '/'.join(_auxencode(ef, dotencode))
310 if len(res) > _maxstorepathlen:
312 if len(res) > _maxstorepathlen:
311 res = _hashencode(path, dotencode)
313 res = _hashencode(path, dotencode)
312 return res
314 return res
313
315
314 def _pathencode(path):
316 def _pathencode(path):
315 de = encodedir(path)
317 de = encodedir(path)
316 if len(path) > _maxstorepathlen:
318 if len(path) > _maxstorepathlen:
317 return _hashencode(de, True)
319 return _hashencode(de, True)
318 ef = _encodefname(de).split('/')
320 ef = _encodefname(de).split('/')
319 res = '/'.join(_auxencode(ef, True))
321 res = '/'.join(_auxencode(ef, True))
320 if len(res) > _maxstorepathlen:
322 if len(res) > _maxstorepathlen:
321 return _hashencode(de, True)
323 return _hashencode(de, True)
322 return res
324 return res
323
325
324 _pathencode = getattr(parsers, 'pathencode', _pathencode)
326 _pathencode = getattr(parsers, 'pathencode', _pathencode)
325
327
326 def _plainhybridencode(f):
328 def _plainhybridencode(f):
327 return _hybridencode(f, False)
329 return _hybridencode(f, False)
328
330
329 def _calcmode(vfs):
331 def _calcmode(vfs):
330 try:
332 try:
331 # files in .hg/ will be created using this mode
333 # files in .hg/ will be created using this mode
332 mode = vfs.stat().st_mode
334 mode = vfs.stat().st_mode
333 # avoid some useless chmods
335 # avoid some useless chmods
334 if (0o777 & ~util.umask) == (0o777 & mode):
336 if (0o777 & ~util.umask) == (0o777 & mode):
335 mode = None
337 mode = None
336 except OSError:
338 except OSError:
337 mode = None
339 mode = None
338 return mode
340 return mode
339
341
340 _data = ('bookmarks narrowspec data meta 00manifest.d 00manifest.i'
342 _data = ('bookmarks narrowspec data meta 00manifest.d 00manifest.i'
341 ' 00changelog.d 00changelog.i phaseroots obsstore')
343 ' 00changelog.d 00changelog.i phaseroots obsstore')
342
344
343 def isrevlog(f, kind, st):
345 def isrevlog(f, kind, st):
344 return kind == stat.S_IFREG and f[-2:] in ('.i', '.d')
346 return kind == stat.S_IFREG and f[-2:] in ('.i', '.d')
345
347
346 class basicstore(object):
348 class basicstore(object):
347 '''base class for local repository stores'''
349 '''base class for local repository stores'''
348 def __init__(self, path, vfstype):
350 def __init__(self, path, vfstype):
349 vfs = vfstype(path)
351 vfs = vfstype(path)
350 self.path = vfs.base
352 self.path = vfs.base
351 self.createmode = _calcmode(vfs)
353 self.createmode = _calcmode(vfs)
352 vfs.createmode = self.createmode
354 vfs.createmode = self.createmode
353 self.rawvfs = vfs
355 self.rawvfs = vfs
354 self.vfs = vfsmod.filtervfs(vfs, encodedir)
356 self.vfs = vfsmod.filtervfs(vfs, encodedir)
355 self.opener = self.vfs
357 self.opener = self.vfs
356
358
357 def join(self, f):
359 def join(self, f):
358 return self.path + '/' + encodedir(f)
360 return self.path + '/' + encodedir(f)
359
361
360 def _walk(self, relpath, recurse, filefilter=isrevlog):
362 def _walk(self, relpath, recurse, filefilter=isrevlog):
361 '''yields (unencoded, encoded, size)'''
363 '''yields (unencoded, encoded, size)'''
362 path = self.path
364 path = self.path
363 if relpath:
365 if relpath:
364 path += '/' + relpath
366 path += '/' + relpath
365 striplen = len(self.path) + 1
367 striplen = len(self.path) + 1
366 l = []
368 l = []
367 if self.rawvfs.isdir(path):
369 if self.rawvfs.isdir(path):
368 visit = [path]
370 visit = [path]
369 readdir = self.rawvfs.readdir
371 readdir = self.rawvfs.readdir
370 while visit:
372 while visit:
371 p = visit.pop()
373 p = visit.pop()
372 for f, kind, st in readdir(p, stat=True):
374 for f, kind, st in readdir(p, stat=True):
373 fp = p + '/' + f
375 fp = p + '/' + f
374 if filefilter(f, kind, st):
376 if filefilter(f, kind, st):
375 n = util.pconvert(fp[striplen:])
377 n = util.pconvert(fp[striplen:])
376 l.append((decodedir(n), n, st.st_size))
378 l.append((decodedir(n), n, st.st_size))
377 elif kind == stat.S_IFDIR and recurse:
379 elif kind == stat.S_IFDIR and recurse:
378 visit.append(fp)
380 visit.append(fp)
379 l.sort()
381 l.sort()
380 return l
382 return l
381
383
384 def changelog(self, trypending):
385 return changelog.changelog(self.vfs, trypending=trypending)
386
387 def manifestlog(self, repo, storenarrowmatch):
388 rootstore = manifest.manifestrevlog(self.vfs)
389 return manifest.manifestlog(
390 self.vfs, repo, rootstore, storenarrowmatch)
391
382 def datafiles(self, matcher=None):
392 def datafiles(self, matcher=None):
383 return self._walk('data', True) + self._walk('meta', True)
393 return self._walk('data', True) + self._walk('meta', True)
384
394
385 def topfiles(self):
395 def topfiles(self):
386 # yield manifest before changelog
396 # yield manifest before changelog
387 return reversed(self._walk('', False))
397 return reversed(self._walk('', False))
388
398
389 def walk(self, matcher=None):
399 def walk(self, matcher=None):
390 '''yields (unencoded, encoded, size)
400 '''yields (unencoded, encoded, size)
391
401
392 if a matcher is passed, storage files of only those tracked paths
402 if a matcher is passed, storage files of only those tracked paths
393 are passed with matches the matcher
403 are passed with matches the matcher
394 '''
404 '''
395 # yield data files first
405 # yield data files first
396 for x in self.datafiles(matcher):
406 for x in self.datafiles(matcher):
397 yield x
407 yield x
398 for x in self.topfiles():
408 for x in self.topfiles():
399 yield x
409 yield x
400
410
401 def copylist(self):
411 def copylist(self):
402 return ['requires'] + _data.split()
412 return ['requires'] + _data.split()
403
413
404 def write(self, tr):
414 def write(self, tr):
405 pass
415 pass
406
416
407 def invalidatecaches(self):
417 def invalidatecaches(self):
408 pass
418 pass
409
419
410 def markremoved(self, fn):
420 def markremoved(self, fn):
411 pass
421 pass
412
422
413 def __contains__(self, path):
423 def __contains__(self, path):
414 '''Checks if the store contains path'''
424 '''Checks if the store contains path'''
415 path = "/".join(("data", path))
425 path = "/".join(("data", path))
416 # file?
426 # file?
417 if self.vfs.exists(path + ".i"):
427 if self.vfs.exists(path + ".i"):
418 return True
428 return True
419 # dir?
429 # dir?
420 if not path.endswith("/"):
430 if not path.endswith("/"):
421 path = path + "/"
431 path = path + "/"
422 return self.vfs.exists(path)
432 return self.vfs.exists(path)
423
433
424 class encodedstore(basicstore):
434 class encodedstore(basicstore):
425 def __init__(self, path, vfstype):
435 def __init__(self, path, vfstype):
426 vfs = vfstype(path + '/store')
436 vfs = vfstype(path + '/store')
427 self.path = vfs.base
437 self.path = vfs.base
428 self.createmode = _calcmode(vfs)
438 self.createmode = _calcmode(vfs)
429 vfs.createmode = self.createmode
439 vfs.createmode = self.createmode
430 self.rawvfs = vfs
440 self.rawvfs = vfs
431 self.vfs = vfsmod.filtervfs(vfs, encodefilename)
441 self.vfs = vfsmod.filtervfs(vfs, encodefilename)
432 self.opener = self.vfs
442 self.opener = self.vfs
433
443
434 def datafiles(self, matcher=None):
444 def datafiles(self, matcher=None):
435 for a, b, size in super(encodedstore, self).datafiles():
445 for a, b, size in super(encodedstore, self).datafiles():
436 try:
446 try:
437 a = decodefilename(a)
447 a = decodefilename(a)
438 except KeyError:
448 except KeyError:
439 a = None
449 a = None
440 if a is not None and not _matchtrackedpath(a, matcher):
450 if a is not None and not _matchtrackedpath(a, matcher):
441 continue
451 continue
442 yield a, b, size
452 yield a, b, size
443
453
444 def join(self, f):
454 def join(self, f):
445 return self.path + '/' + encodefilename(f)
455 return self.path + '/' + encodefilename(f)
446
456
447 def copylist(self):
457 def copylist(self):
448 return (['requires', '00changelog.i'] +
458 return (['requires', '00changelog.i'] +
449 ['store/' + f for f in _data.split()])
459 ['store/' + f for f in _data.split()])
450
460
451 class fncache(object):
461 class fncache(object):
452 # the filename used to be partially encoded
462 # the filename used to be partially encoded
453 # hence the encodedir/decodedir dance
463 # hence the encodedir/decodedir dance
454 def __init__(self, vfs):
464 def __init__(self, vfs):
455 self.vfs = vfs
465 self.vfs = vfs
456 self.entries = None
466 self.entries = None
457 self._dirty = False
467 self._dirty = False
458 # set of new additions to fncache
468 # set of new additions to fncache
459 self.addls = set()
469 self.addls = set()
460
470
461 def ensureloaded(self, warn=None):
471 def ensureloaded(self, warn=None):
462 '''read the fncache file if not already read.
472 '''read the fncache file if not already read.
463
473
464 If the file on disk is corrupted, raise. If warn is provided,
474 If the file on disk is corrupted, raise. If warn is provided,
465 warn and keep going instead.'''
475 warn and keep going instead.'''
466 if self.entries is None:
476 if self.entries is None:
467 self._load(warn)
477 self._load(warn)
468
478
469 def _load(self, warn=None):
479 def _load(self, warn=None):
470 '''fill the entries from the fncache file'''
480 '''fill the entries from the fncache file'''
471 self._dirty = False
481 self._dirty = False
472 try:
482 try:
473 fp = self.vfs('fncache', mode='rb')
483 fp = self.vfs('fncache', mode='rb')
474 except IOError:
484 except IOError:
475 # skip nonexistent file
485 # skip nonexistent file
476 self.entries = set()
486 self.entries = set()
477 return
487 return
478
488
479 self.entries = set()
489 self.entries = set()
480 chunk = b''
490 chunk = b''
481 for c in iter(functools.partial(fp.read, fncache_chunksize), b''):
491 for c in iter(functools.partial(fp.read, fncache_chunksize), b''):
482 chunk += c
492 chunk += c
483 try:
493 try:
484 p = chunk.rindex(b'\n')
494 p = chunk.rindex(b'\n')
485 self.entries.update(decodedir(chunk[:p + 1]).splitlines())
495 self.entries.update(decodedir(chunk[:p + 1]).splitlines())
486 chunk = chunk[p + 1:]
496 chunk = chunk[p + 1:]
487 except ValueError:
497 except ValueError:
488 # substring '\n' not found, maybe the entry is bigger than the
498 # substring '\n' not found, maybe the entry is bigger than the
489 # chunksize, so let's keep iterating
499 # chunksize, so let's keep iterating
490 pass
500 pass
491
501
492 if chunk:
502 if chunk:
493 msg = _("fncache does not ends with a newline")
503 msg = _("fncache does not ends with a newline")
494 if warn:
504 if warn:
495 warn(msg + '\n')
505 warn(msg + '\n')
496 else:
506 else:
497 raise error.Abort(msg,
507 raise error.Abort(msg,
498 hint=_("use 'hg debugrebuildfncache' to "
508 hint=_("use 'hg debugrebuildfncache' to "
499 "rebuild the fncache"))
509 "rebuild the fncache"))
500 self._checkentries(fp, warn)
510 self._checkentries(fp, warn)
501 fp.close()
511 fp.close()
502
512
503 def _checkentries(self, fp, warn):
513 def _checkentries(self, fp, warn):
504 """ make sure there is no empty string in entries """
514 """ make sure there is no empty string in entries """
505 if '' in self.entries:
515 if '' in self.entries:
506 fp.seek(0)
516 fp.seek(0)
507 for n, line in enumerate(util.iterfile(fp)):
517 for n, line in enumerate(util.iterfile(fp)):
508 if not line.rstrip('\n'):
518 if not line.rstrip('\n'):
509 t = _('invalid entry in fncache, line %d') % (n + 1)
519 t = _('invalid entry in fncache, line %d') % (n + 1)
510 if warn:
520 if warn:
511 warn(t + '\n')
521 warn(t + '\n')
512 else:
522 else:
513 raise error.Abort(t)
523 raise error.Abort(t)
514
524
515 def write(self, tr):
525 def write(self, tr):
516 if self._dirty:
526 if self._dirty:
517 assert self.entries is not None
527 assert self.entries is not None
518 self.entries = self.entries | self.addls
528 self.entries = self.entries | self.addls
519 self.addls = set()
529 self.addls = set()
520 tr.addbackup('fncache')
530 tr.addbackup('fncache')
521 fp = self.vfs('fncache', mode='wb', atomictemp=True)
531 fp = self.vfs('fncache', mode='wb', atomictemp=True)
522 if self.entries:
532 if self.entries:
523 fp.write(encodedir('\n'.join(self.entries) + '\n'))
533 fp.write(encodedir('\n'.join(self.entries) + '\n'))
524 fp.close()
534 fp.close()
525 self._dirty = False
535 self._dirty = False
526 if self.addls:
536 if self.addls:
527 # if we have just new entries, let's append them to the fncache
537 # if we have just new entries, let's append them to the fncache
528 tr.addbackup('fncache')
538 tr.addbackup('fncache')
529 fp = self.vfs('fncache', mode='ab', atomictemp=True)
539 fp = self.vfs('fncache', mode='ab', atomictemp=True)
530 if self.addls:
540 if self.addls:
531 fp.write(encodedir('\n'.join(self.addls) + '\n'))
541 fp.write(encodedir('\n'.join(self.addls) + '\n'))
532 fp.close()
542 fp.close()
533 self.entries = None
543 self.entries = None
534 self.addls = set()
544 self.addls = set()
535
545
536 def add(self, fn):
546 def add(self, fn):
537 if self.entries is None:
547 if self.entries is None:
538 self._load()
548 self._load()
539 if fn not in self.entries:
549 if fn not in self.entries:
540 self.addls.add(fn)
550 self.addls.add(fn)
541
551
542 def remove(self, fn):
552 def remove(self, fn):
543 if self.entries is None:
553 if self.entries is None:
544 self._load()
554 self._load()
545 if fn in self.addls:
555 if fn in self.addls:
546 self.addls.remove(fn)
556 self.addls.remove(fn)
547 return
557 return
548 try:
558 try:
549 self.entries.remove(fn)
559 self.entries.remove(fn)
550 self._dirty = True
560 self._dirty = True
551 except KeyError:
561 except KeyError:
552 pass
562 pass
553
563
554 def __contains__(self, fn):
564 def __contains__(self, fn):
555 if fn in self.addls:
565 if fn in self.addls:
556 return True
566 return True
557 if self.entries is None:
567 if self.entries is None:
558 self._load()
568 self._load()
559 return fn in self.entries
569 return fn in self.entries
560
570
561 def __iter__(self):
571 def __iter__(self):
562 if self.entries is None:
572 if self.entries is None:
563 self._load()
573 self._load()
564 return iter(self.entries | self.addls)
574 return iter(self.entries | self.addls)
565
575
566 class _fncachevfs(vfsmod.proxyvfs):
576 class _fncachevfs(vfsmod.proxyvfs):
567 def __init__(self, vfs, fnc, encode):
577 def __init__(self, vfs, fnc, encode):
568 vfsmod.proxyvfs.__init__(self, vfs)
578 vfsmod.proxyvfs.__init__(self, vfs)
569 self.fncache = fnc
579 self.fncache = fnc
570 self.encode = encode
580 self.encode = encode
571
581
572 def __call__(self, path, mode='r', *args, **kw):
582 def __call__(self, path, mode='r', *args, **kw):
573 encoded = self.encode(path)
583 encoded = self.encode(path)
574 if mode not in ('r', 'rb') and (path.startswith('data/') or
584 if mode not in ('r', 'rb') and (path.startswith('data/') or
575 path.startswith('meta/')):
585 path.startswith('meta/')):
576 # do not trigger a fncache load when adding a file that already is
586 # do not trigger a fncache load when adding a file that already is
577 # known to exist.
587 # known to exist.
578 notload = self.fncache.entries is None and self.vfs.exists(encoded)
588 notload = self.fncache.entries is None and self.vfs.exists(encoded)
579 if notload and 'a' in mode and not self.vfs.stat(encoded).st_size:
589 if notload and 'a' in mode and not self.vfs.stat(encoded).st_size:
580 # when appending to an existing file, if the file has size zero,
590 # when appending to an existing file, if the file has size zero,
581 # it should be considered as missing. Such zero-size files are
591 # it should be considered as missing. Such zero-size files are
582 # the result of truncation when a transaction is aborted.
592 # the result of truncation when a transaction is aborted.
583 notload = False
593 notload = False
584 if not notload:
594 if not notload:
585 self.fncache.add(path)
595 self.fncache.add(path)
586 return self.vfs(encoded, mode, *args, **kw)
596 return self.vfs(encoded, mode, *args, **kw)
587
597
588 def join(self, path):
598 def join(self, path):
589 if path:
599 if path:
590 return self.vfs.join(self.encode(path))
600 return self.vfs.join(self.encode(path))
591 else:
601 else:
592 return self.vfs.join(path)
602 return self.vfs.join(path)
593
603
594 class fncachestore(basicstore):
604 class fncachestore(basicstore):
595 def __init__(self, path, vfstype, dotencode):
605 def __init__(self, path, vfstype, dotencode):
596 if dotencode:
606 if dotencode:
597 encode = _pathencode
607 encode = _pathencode
598 else:
608 else:
599 encode = _plainhybridencode
609 encode = _plainhybridencode
600 self.encode = encode
610 self.encode = encode
601 vfs = vfstype(path + '/store')
611 vfs = vfstype(path + '/store')
602 self.path = vfs.base
612 self.path = vfs.base
603 self.pathsep = self.path + '/'
613 self.pathsep = self.path + '/'
604 self.createmode = _calcmode(vfs)
614 self.createmode = _calcmode(vfs)
605 vfs.createmode = self.createmode
615 vfs.createmode = self.createmode
606 self.rawvfs = vfs
616 self.rawvfs = vfs
607 fnc = fncache(vfs)
617 fnc = fncache(vfs)
608 self.fncache = fnc
618 self.fncache = fnc
609 self.vfs = _fncachevfs(vfs, fnc, encode)
619 self.vfs = _fncachevfs(vfs, fnc, encode)
610 self.opener = self.vfs
620 self.opener = self.vfs
611
621
612 def join(self, f):
622 def join(self, f):
613 return self.pathsep + self.encode(f)
623 return self.pathsep + self.encode(f)
614
624
615 def getsize(self, path):
625 def getsize(self, path):
616 return self.rawvfs.stat(path).st_size
626 return self.rawvfs.stat(path).st_size
617
627
618 def datafiles(self, matcher=None):
628 def datafiles(self, matcher=None):
619 for f in sorted(self.fncache):
629 for f in sorted(self.fncache):
620 if not _matchtrackedpath(f, matcher):
630 if not _matchtrackedpath(f, matcher):
621 continue
631 continue
622 ef = self.encode(f)
632 ef = self.encode(f)
623 try:
633 try:
624 yield f, ef, self.getsize(ef)
634 yield f, ef, self.getsize(ef)
625 except OSError as err:
635 except OSError as err:
626 if err.errno != errno.ENOENT:
636 if err.errno != errno.ENOENT:
627 raise
637 raise
628
638
629 def copylist(self):
639 def copylist(self):
630 d = ('bookmarks narrowspec data meta dh fncache phaseroots obsstore'
640 d = ('bookmarks narrowspec data meta dh fncache phaseroots obsstore'
631 ' 00manifest.d 00manifest.i 00changelog.d 00changelog.i')
641 ' 00manifest.d 00manifest.i 00changelog.d 00changelog.i')
632 return (['requires', '00changelog.i'] +
642 return (['requires', '00changelog.i'] +
633 ['store/' + f for f in d.split()])
643 ['store/' + f for f in d.split()])
634
644
635 def write(self, tr):
645 def write(self, tr):
636 self.fncache.write(tr)
646 self.fncache.write(tr)
637
647
638 def invalidatecaches(self):
648 def invalidatecaches(self):
639 self.fncache.entries = None
649 self.fncache.entries = None
640 self.fncache.addls = set()
650 self.fncache.addls = set()
641
651
642 def markremoved(self, fn):
652 def markremoved(self, fn):
643 self.fncache.remove(fn)
653 self.fncache.remove(fn)
644
654
645 def _exists(self, f):
655 def _exists(self, f):
646 ef = self.encode(f)
656 ef = self.encode(f)
647 try:
657 try:
648 self.getsize(ef)
658 self.getsize(ef)
649 return True
659 return True
650 except OSError as err:
660 except OSError as err:
651 if err.errno != errno.ENOENT:
661 if err.errno != errno.ENOENT:
652 raise
662 raise
653 # nonexistent entry
663 # nonexistent entry
654 return False
664 return False
655
665
656 def __contains__(self, path):
666 def __contains__(self, path):
657 '''Checks if the store contains path'''
667 '''Checks if the store contains path'''
658 path = "/".join(("data", path))
668 path = "/".join(("data", path))
659 # check for files (exact match)
669 # check for files (exact match)
660 e = path + '.i'
670 e = path + '.i'
661 if e in self.fncache and self._exists(e):
671 if e in self.fncache and self._exists(e):
662 return True
672 return True
663 # now check for directories (prefix match)
673 # now check for directories (prefix match)
664 if not path.endswith('/'):
674 if not path.endswith('/'):
665 path += '/'
675 path += '/'
666 for e in self.fncache:
676 for e in self.fncache:
667 if e.startswith(path) and self._exists(e):
677 if e.startswith(path) and self._exists(e):
668 return True
678 return True
669 return False
679 return False
General Comments 0
You need to be logged in to leave comments. Login now