##// END OF EJS Templates
obsolete: move obsstore creation logic from localrepo...
Gregory Szorc -
r32729:c8177792 default
parent child Browse files
Show More
@@ -1,2089 +1,2075 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 inspect
12 import inspect
13 import os
13 import os
14 import random
14 import random
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 hex,
20 hex,
21 nullid,
21 nullid,
22 short,
22 short,
23 )
23 )
24 from . import (
24 from . import (
25 bookmarks,
25 bookmarks,
26 branchmap,
26 branchmap,
27 bundle2,
27 bundle2,
28 changegroup,
28 changegroup,
29 changelog,
29 changelog,
30 color,
30 color,
31 context,
31 context,
32 dirstate,
32 dirstate,
33 dirstateguard,
33 dirstateguard,
34 encoding,
34 encoding,
35 error,
35 error,
36 exchange,
36 exchange,
37 extensions,
37 extensions,
38 filelog,
38 filelog,
39 hook,
39 hook,
40 lock as lockmod,
40 lock as lockmod,
41 manifest,
41 manifest,
42 match as matchmod,
42 match as matchmod,
43 merge as mergemod,
43 merge as mergemod,
44 mergeutil,
44 mergeutil,
45 namespaces,
45 namespaces,
46 obsolete,
46 obsolete,
47 pathutil,
47 pathutil,
48 peer,
48 peer,
49 phases,
49 phases,
50 pushkey,
50 pushkey,
51 pycompat,
51 pycompat,
52 repoview,
52 repoview,
53 revset,
53 revset,
54 revsetlang,
54 revsetlang,
55 scmutil,
55 scmutil,
56 store,
56 store,
57 subrepo,
57 subrepo,
58 tags as tagsmod,
58 tags as tagsmod,
59 transaction,
59 transaction,
60 txnutil,
60 txnutil,
61 util,
61 util,
62 vfs as vfsmod,
62 vfs as vfsmod,
63 )
63 )
64
64
65 release = lockmod.release
65 release = lockmod.release
66 urlerr = util.urlerr
66 urlerr = util.urlerr
67 urlreq = util.urlreq
67 urlreq = util.urlreq
68
68
69 class repofilecache(scmutil.filecache):
69 class repofilecache(scmutil.filecache):
70 """All filecache usage on repo are done for logic that should be unfiltered
70 """All filecache usage on repo are done for logic that should be unfiltered
71 """
71 """
72
72
73 def join(self, obj, fname):
73 def join(self, obj, fname):
74 return obj.vfs.join(fname)
74 return obj.vfs.join(fname)
75 def __get__(self, repo, type=None):
75 def __get__(self, repo, type=None):
76 if repo is None:
76 if repo is None:
77 return self
77 return self
78 return super(repofilecache, self).__get__(repo.unfiltered(), type)
78 return super(repofilecache, self).__get__(repo.unfiltered(), type)
79 def __set__(self, repo, value):
79 def __set__(self, repo, value):
80 return super(repofilecache, self).__set__(repo.unfiltered(), value)
80 return super(repofilecache, self).__set__(repo.unfiltered(), value)
81 def __delete__(self, repo):
81 def __delete__(self, repo):
82 return super(repofilecache, self).__delete__(repo.unfiltered())
82 return super(repofilecache, self).__delete__(repo.unfiltered())
83
83
84 class storecache(repofilecache):
84 class storecache(repofilecache):
85 """filecache for files in the store"""
85 """filecache for files in the store"""
86 def join(self, obj, fname):
86 def join(self, obj, fname):
87 return obj.sjoin(fname)
87 return obj.sjoin(fname)
88
88
89 class unfilteredpropertycache(util.propertycache):
89 class unfilteredpropertycache(util.propertycache):
90 """propertycache that apply to unfiltered repo only"""
90 """propertycache that apply to unfiltered repo only"""
91
91
92 def __get__(self, repo, type=None):
92 def __get__(self, repo, type=None):
93 unfi = repo.unfiltered()
93 unfi = repo.unfiltered()
94 if unfi is repo:
94 if unfi is repo:
95 return super(unfilteredpropertycache, self).__get__(unfi)
95 return super(unfilteredpropertycache, self).__get__(unfi)
96 return getattr(unfi, self.name)
96 return getattr(unfi, self.name)
97
97
98 class filteredpropertycache(util.propertycache):
98 class filteredpropertycache(util.propertycache):
99 """propertycache that must take filtering in account"""
99 """propertycache that must take filtering in account"""
100
100
101 def cachevalue(self, obj, value):
101 def cachevalue(self, obj, value):
102 object.__setattr__(obj, self.name, value)
102 object.__setattr__(obj, self.name, value)
103
103
104
104
105 def hasunfilteredcache(repo, name):
105 def hasunfilteredcache(repo, name):
106 """check if a repo has an unfilteredpropertycache value for <name>"""
106 """check if a repo has an unfilteredpropertycache value for <name>"""
107 return name in vars(repo.unfiltered())
107 return name in vars(repo.unfiltered())
108
108
109 def unfilteredmethod(orig):
109 def unfilteredmethod(orig):
110 """decorate method that always need to be run on unfiltered version"""
110 """decorate method that always need to be run on unfiltered version"""
111 def wrapper(repo, *args, **kwargs):
111 def wrapper(repo, *args, **kwargs):
112 return orig(repo.unfiltered(), *args, **kwargs)
112 return orig(repo.unfiltered(), *args, **kwargs)
113 return wrapper
113 return wrapper
114
114
115 moderncaps = {'lookup', 'branchmap', 'pushkey', 'known', 'getbundle',
115 moderncaps = {'lookup', 'branchmap', 'pushkey', 'known', 'getbundle',
116 'unbundle'}
116 'unbundle'}
117 legacycaps = moderncaps.union({'changegroupsubset'})
117 legacycaps = moderncaps.union({'changegroupsubset'})
118
118
119 class localpeer(peer.peerrepository):
119 class localpeer(peer.peerrepository):
120 '''peer for a local repo; reflects only the most recent API'''
120 '''peer for a local repo; reflects only the most recent API'''
121
121
122 def __init__(self, repo, caps=None):
122 def __init__(self, repo, caps=None):
123 if caps is None:
123 if caps is None:
124 caps = moderncaps.copy()
124 caps = moderncaps.copy()
125 peer.peerrepository.__init__(self)
125 peer.peerrepository.__init__(self)
126 self._repo = repo.filtered('served')
126 self._repo = repo.filtered('served')
127 self.ui = repo.ui
127 self.ui = repo.ui
128 self._caps = repo._restrictcapabilities(caps)
128 self._caps = repo._restrictcapabilities(caps)
129 self.requirements = repo.requirements
129 self.requirements = repo.requirements
130 self.supportedformats = repo.supportedformats
130 self.supportedformats = repo.supportedformats
131
131
132 def close(self):
132 def close(self):
133 self._repo.close()
133 self._repo.close()
134
134
135 def _capabilities(self):
135 def _capabilities(self):
136 return self._caps
136 return self._caps
137
137
138 def local(self):
138 def local(self):
139 return self._repo
139 return self._repo
140
140
141 def canpush(self):
141 def canpush(self):
142 return True
142 return True
143
143
144 def url(self):
144 def url(self):
145 return self._repo.url()
145 return self._repo.url()
146
146
147 def lookup(self, key):
147 def lookup(self, key):
148 return self._repo.lookup(key)
148 return self._repo.lookup(key)
149
149
150 def branchmap(self):
150 def branchmap(self):
151 return self._repo.branchmap()
151 return self._repo.branchmap()
152
152
153 def heads(self):
153 def heads(self):
154 return self._repo.heads()
154 return self._repo.heads()
155
155
156 def known(self, nodes):
156 def known(self, nodes):
157 return self._repo.known(nodes)
157 return self._repo.known(nodes)
158
158
159 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
159 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
160 **kwargs):
160 **kwargs):
161 chunks = exchange.getbundlechunks(self._repo, source, heads=heads,
161 chunks = exchange.getbundlechunks(self._repo, source, heads=heads,
162 common=common, bundlecaps=bundlecaps,
162 common=common, bundlecaps=bundlecaps,
163 **kwargs)
163 **kwargs)
164 cb = util.chunkbuffer(chunks)
164 cb = util.chunkbuffer(chunks)
165
165
166 if exchange.bundle2requested(bundlecaps):
166 if exchange.bundle2requested(bundlecaps):
167 # When requesting a bundle2, getbundle returns a stream to make the
167 # When requesting a bundle2, getbundle returns a stream to make the
168 # wire level function happier. We need to build a proper object
168 # wire level function happier. We need to build a proper object
169 # from it in local peer.
169 # from it in local peer.
170 return bundle2.getunbundler(self.ui, cb)
170 return bundle2.getunbundler(self.ui, cb)
171 else:
171 else:
172 return changegroup.getunbundler('01', cb, None)
172 return changegroup.getunbundler('01', cb, None)
173
173
174 # TODO We might want to move the next two calls into legacypeer and add
174 # TODO We might want to move the next two calls into legacypeer and add
175 # unbundle instead.
175 # unbundle instead.
176
176
177 def unbundle(self, cg, heads, url):
177 def unbundle(self, cg, heads, url):
178 """apply a bundle on a repo
178 """apply a bundle on a repo
179
179
180 This function handles the repo locking itself."""
180 This function handles the repo locking itself."""
181 try:
181 try:
182 try:
182 try:
183 cg = exchange.readbundle(self.ui, cg, None)
183 cg = exchange.readbundle(self.ui, cg, None)
184 ret = exchange.unbundle(self._repo, cg, heads, 'push', url)
184 ret = exchange.unbundle(self._repo, cg, heads, 'push', url)
185 if util.safehasattr(ret, 'getchunks'):
185 if util.safehasattr(ret, 'getchunks'):
186 # This is a bundle20 object, turn it into an unbundler.
186 # This is a bundle20 object, turn it into an unbundler.
187 # This little dance should be dropped eventually when the
187 # This little dance should be dropped eventually when the
188 # API is finally improved.
188 # API is finally improved.
189 stream = util.chunkbuffer(ret.getchunks())
189 stream = util.chunkbuffer(ret.getchunks())
190 ret = bundle2.getunbundler(self.ui, stream)
190 ret = bundle2.getunbundler(self.ui, stream)
191 return ret
191 return ret
192 except Exception as exc:
192 except Exception as exc:
193 # If the exception contains output salvaged from a bundle2
193 # If the exception contains output salvaged from a bundle2
194 # reply, we need to make sure it is printed before continuing
194 # reply, we need to make sure it is printed before continuing
195 # to fail. So we build a bundle2 with such output and consume
195 # to fail. So we build a bundle2 with such output and consume
196 # it directly.
196 # it directly.
197 #
197 #
198 # This is not very elegant but allows a "simple" solution for
198 # This is not very elegant but allows a "simple" solution for
199 # issue4594
199 # issue4594
200 output = getattr(exc, '_bundle2salvagedoutput', ())
200 output = getattr(exc, '_bundle2salvagedoutput', ())
201 if output:
201 if output:
202 bundler = bundle2.bundle20(self._repo.ui)
202 bundler = bundle2.bundle20(self._repo.ui)
203 for out in output:
203 for out in output:
204 bundler.addpart(out)
204 bundler.addpart(out)
205 stream = util.chunkbuffer(bundler.getchunks())
205 stream = util.chunkbuffer(bundler.getchunks())
206 b = bundle2.getunbundler(self.ui, stream)
206 b = bundle2.getunbundler(self.ui, stream)
207 bundle2.processbundle(self._repo, b)
207 bundle2.processbundle(self._repo, b)
208 raise
208 raise
209 except error.PushRaced as exc:
209 except error.PushRaced as exc:
210 raise error.ResponseError(_('push failed:'), str(exc))
210 raise error.ResponseError(_('push failed:'), str(exc))
211
211
212 def lock(self):
212 def lock(self):
213 return self._repo.lock()
213 return self._repo.lock()
214
214
215 def addchangegroup(self, cg, source, url):
215 def addchangegroup(self, cg, source, url):
216 return cg.apply(self._repo, source, url)
216 return cg.apply(self._repo, source, url)
217
217
218 def pushkey(self, namespace, key, old, new):
218 def pushkey(self, namespace, key, old, new):
219 return self._repo.pushkey(namespace, key, old, new)
219 return self._repo.pushkey(namespace, key, old, new)
220
220
221 def listkeys(self, namespace):
221 def listkeys(self, namespace):
222 return self._repo.listkeys(namespace)
222 return self._repo.listkeys(namespace)
223
223
224 def debugwireargs(self, one, two, three=None, four=None, five=None):
224 def debugwireargs(self, one, two, three=None, four=None, five=None):
225 '''used to test argument passing over the wire'''
225 '''used to test argument passing over the wire'''
226 return "%s %s %s %s %s" % (one, two, three, four, five)
226 return "%s %s %s %s %s" % (one, two, three, four, five)
227
227
228 class locallegacypeer(localpeer):
228 class locallegacypeer(localpeer):
229 '''peer extension which implements legacy methods too; used for tests with
229 '''peer extension which implements legacy methods too; used for tests with
230 restricted capabilities'''
230 restricted capabilities'''
231
231
232 def __init__(self, repo):
232 def __init__(self, repo):
233 localpeer.__init__(self, repo, caps=legacycaps)
233 localpeer.__init__(self, repo, caps=legacycaps)
234
234
235 def branches(self, nodes):
235 def branches(self, nodes):
236 return self._repo.branches(nodes)
236 return self._repo.branches(nodes)
237
237
238 def between(self, pairs):
238 def between(self, pairs):
239 return self._repo.between(pairs)
239 return self._repo.between(pairs)
240
240
241 def changegroup(self, basenodes, source):
241 def changegroup(self, basenodes, source):
242 return changegroup.changegroup(self._repo, basenodes, source)
242 return changegroup.changegroup(self._repo, basenodes, source)
243
243
244 def changegroupsubset(self, bases, heads, source):
244 def changegroupsubset(self, bases, heads, source):
245 return changegroup.changegroupsubset(self._repo, bases, heads, source)
245 return changegroup.changegroupsubset(self._repo, bases, heads, source)
246
246
247 # Increment the sub-version when the revlog v2 format changes to lock out old
247 # Increment the sub-version when the revlog v2 format changes to lock out old
248 # clients.
248 # clients.
249 REVLOGV2_REQUIREMENT = 'exp-revlogv2.0'
249 REVLOGV2_REQUIREMENT = 'exp-revlogv2.0'
250
250
251 class localrepository(object):
251 class localrepository(object):
252
252
253 supportedformats = {
253 supportedformats = {
254 'revlogv1',
254 'revlogv1',
255 'generaldelta',
255 'generaldelta',
256 'treemanifest',
256 'treemanifest',
257 'manifestv2',
257 'manifestv2',
258 REVLOGV2_REQUIREMENT,
258 REVLOGV2_REQUIREMENT,
259 }
259 }
260 _basesupported = supportedformats | {
260 _basesupported = supportedformats | {
261 'store',
261 'store',
262 'fncache',
262 'fncache',
263 'shared',
263 'shared',
264 'relshared',
264 'relshared',
265 'dotencode',
265 'dotencode',
266 }
266 }
267 openerreqs = {
267 openerreqs = {
268 'revlogv1',
268 'revlogv1',
269 'generaldelta',
269 'generaldelta',
270 'treemanifest',
270 'treemanifest',
271 'manifestv2',
271 'manifestv2',
272 }
272 }
273 filtername = None
273 filtername = None
274
274
275 # a list of (ui, featureset) functions.
275 # a list of (ui, featureset) functions.
276 # only functions defined in module of enabled extensions are invoked
276 # only functions defined in module of enabled extensions are invoked
277 featuresetupfuncs = set()
277 featuresetupfuncs = set()
278
278
279 def __init__(self, baseui, path, create=False):
279 def __init__(self, baseui, path, create=False):
280 self.requirements = set()
280 self.requirements = set()
281 # wvfs: rooted at the repository root, used to access the working copy
281 # wvfs: rooted at the repository root, used to access the working copy
282 self.wvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
282 self.wvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
283 # vfs: rooted at .hg, used to access repo files outside of .hg/store
283 # vfs: rooted at .hg, used to access repo files outside of .hg/store
284 self.vfs = None
284 self.vfs = None
285 # svfs: usually rooted at .hg/store, used to access repository history
285 # svfs: usually rooted at .hg/store, used to access repository history
286 # If this is a shared repository, this vfs may point to another
286 # If this is a shared repository, this vfs may point to another
287 # repository's .hg/store directory.
287 # repository's .hg/store directory.
288 self.svfs = None
288 self.svfs = None
289 self.root = self.wvfs.base
289 self.root = self.wvfs.base
290 self.path = self.wvfs.join(".hg")
290 self.path = self.wvfs.join(".hg")
291 self.origroot = path
291 self.origroot = path
292 self.auditor = pathutil.pathauditor(self.root, self._checknested)
292 self.auditor = pathutil.pathauditor(self.root, self._checknested)
293 self.nofsauditor = pathutil.pathauditor(self.root, self._checknested,
293 self.nofsauditor = pathutil.pathauditor(self.root, self._checknested,
294 realfs=False)
294 realfs=False)
295 self.vfs = vfsmod.vfs(self.path)
295 self.vfs = vfsmod.vfs(self.path)
296 self.baseui = baseui
296 self.baseui = baseui
297 self.ui = baseui.copy()
297 self.ui = baseui.copy()
298 self.ui.copy = baseui.copy # prevent copying repo configuration
298 self.ui.copy = baseui.copy # prevent copying repo configuration
299 # A list of callback to shape the phase if no data were found.
299 # A list of callback to shape the phase if no data were found.
300 # Callback are in the form: func(repo, roots) --> processed root.
300 # Callback are in the form: func(repo, roots) --> processed root.
301 # This list it to be filled by extension during repo setup
301 # This list it to be filled by extension during repo setup
302 self._phasedefaults = []
302 self._phasedefaults = []
303 try:
303 try:
304 self.ui.readconfig(self.vfs.join("hgrc"), self.root)
304 self.ui.readconfig(self.vfs.join("hgrc"), self.root)
305 self._loadextensions()
305 self._loadextensions()
306 except IOError:
306 except IOError:
307 pass
307 pass
308
308
309 if self.featuresetupfuncs:
309 if self.featuresetupfuncs:
310 self.supported = set(self._basesupported) # use private copy
310 self.supported = set(self._basesupported) # use private copy
311 extmods = set(m.__name__ for n, m
311 extmods = set(m.__name__ for n, m
312 in extensions.extensions(self.ui))
312 in extensions.extensions(self.ui))
313 for setupfunc in self.featuresetupfuncs:
313 for setupfunc in self.featuresetupfuncs:
314 if setupfunc.__module__ in extmods:
314 if setupfunc.__module__ in extmods:
315 setupfunc(self.ui, self.supported)
315 setupfunc(self.ui, self.supported)
316 else:
316 else:
317 self.supported = self._basesupported
317 self.supported = self._basesupported
318 color.setup(self.ui)
318 color.setup(self.ui)
319
319
320 # Add compression engines.
320 # Add compression engines.
321 for name in util.compengines:
321 for name in util.compengines:
322 engine = util.compengines[name]
322 engine = util.compengines[name]
323 if engine.revlogheader():
323 if engine.revlogheader():
324 self.supported.add('exp-compression-%s' % name)
324 self.supported.add('exp-compression-%s' % name)
325
325
326 if not self.vfs.isdir():
326 if not self.vfs.isdir():
327 if create:
327 if create:
328 self.requirements = newreporequirements(self)
328 self.requirements = newreporequirements(self)
329
329
330 if not self.wvfs.exists():
330 if not self.wvfs.exists():
331 self.wvfs.makedirs()
331 self.wvfs.makedirs()
332 self.vfs.makedir(notindexed=True)
332 self.vfs.makedir(notindexed=True)
333
333
334 if 'store' in self.requirements:
334 if 'store' in self.requirements:
335 self.vfs.mkdir("store")
335 self.vfs.mkdir("store")
336
336
337 # create an invalid changelog
337 # create an invalid changelog
338 self.vfs.append(
338 self.vfs.append(
339 "00changelog.i",
339 "00changelog.i",
340 '\0\0\0\2' # represents revlogv2
340 '\0\0\0\2' # represents revlogv2
341 ' dummy changelog to prevent using the old repo layout'
341 ' dummy changelog to prevent using the old repo layout'
342 )
342 )
343 else:
343 else:
344 raise error.RepoError(_("repository %s not found") % path)
344 raise error.RepoError(_("repository %s not found") % path)
345 elif create:
345 elif create:
346 raise error.RepoError(_("repository %s already exists") % path)
346 raise error.RepoError(_("repository %s already exists") % path)
347 else:
347 else:
348 try:
348 try:
349 self.requirements = scmutil.readrequires(
349 self.requirements = scmutil.readrequires(
350 self.vfs, self.supported)
350 self.vfs, self.supported)
351 except IOError as inst:
351 except IOError as inst:
352 if inst.errno != errno.ENOENT:
352 if inst.errno != errno.ENOENT:
353 raise
353 raise
354
354
355 self.sharedpath = self.path
355 self.sharedpath = self.path
356 try:
356 try:
357 sharedpath = self.vfs.read("sharedpath").rstrip('\n')
357 sharedpath = self.vfs.read("sharedpath").rstrip('\n')
358 if 'relshared' in self.requirements:
358 if 'relshared' in self.requirements:
359 sharedpath = self.vfs.join(sharedpath)
359 sharedpath = self.vfs.join(sharedpath)
360 vfs = vfsmod.vfs(sharedpath, realpath=True)
360 vfs = vfsmod.vfs(sharedpath, realpath=True)
361 s = vfs.base
361 s = vfs.base
362 if not vfs.exists():
362 if not vfs.exists():
363 raise error.RepoError(
363 raise error.RepoError(
364 _('.hg/sharedpath points to nonexistent directory %s') % s)
364 _('.hg/sharedpath points to nonexistent directory %s') % s)
365 self.sharedpath = s
365 self.sharedpath = s
366 except IOError as inst:
366 except IOError as inst:
367 if inst.errno != errno.ENOENT:
367 if inst.errno != errno.ENOENT:
368 raise
368 raise
369
369
370 self.store = store.store(
370 self.store = store.store(
371 self.requirements, self.sharedpath, vfsmod.vfs)
371 self.requirements, self.sharedpath, vfsmod.vfs)
372 self.spath = self.store.path
372 self.spath = self.store.path
373 self.svfs = self.store.vfs
373 self.svfs = self.store.vfs
374 self.sjoin = self.store.join
374 self.sjoin = self.store.join
375 self.vfs.createmode = self.store.createmode
375 self.vfs.createmode = self.store.createmode
376 self._applyopenerreqs()
376 self._applyopenerreqs()
377 if create:
377 if create:
378 self._writerequirements()
378 self._writerequirements()
379
379
380 self._dirstatevalidatewarned = False
380 self._dirstatevalidatewarned = False
381
381
382 self._branchcaches = {}
382 self._branchcaches = {}
383 self._revbranchcache = None
383 self._revbranchcache = None
384 self.filterpats = {}
384 self.filterpats = {}
385 self._datafilters = {}
385 self._datafilters = {}
386 self._transref = self._lockref = self._wlockref = None
386 self._transref = self._lockref = self._wlockref = None
387
387
388 # A cache for various files under .hg/ that tracks file changes,
388 # A cache for various files under .hg/ that tracks file changes,
389 # (used by the filecache decorator)
389 # (used by the filecache decorator)
390 #
390 #
391 # Maps a property name to its util.filecacheentry
391 # Maps a property name to its util.filecacheentry
392 self._filecache = {}
392 self._filecache = {}
393
393
394 # hold sets of revision to be filtered
394 # hold sets of revision to be filtered
395 # should be cleared when something might have changed the filter value:
395 # should be cleared when something might have changed the filter value:
396 # - new changesets,
396 # - new changesets,
397 # - phase change,
397 # - phase change,
398 # - new obsolescence marker,
398 # - new obsolescence marker,
399 # - working directory parent change,
399 # - working directory parent change,
400 # - bookmark changes
400 # - bookmark changes
401 self.filteredrevcache = {}
401 self.filteredrevcache = {}
402
402
403 # generic mapping between names and nodes
403 # generic mapping between names and nodes
404 self.names = namespaces.namespaces()
404 self.names = namespaces.namespaces()
405
405
406 def close(self):
406 def close(self):
407 self._writecaches()
407 self._writecaches()
408
408
409 def _loadextensions(self):
409 def _loadextensions(self):
410 extensions.loadall(self.ui)
410 extensions.loadall(self.ui)
411
411
412 def _writecaches(self):
412 def _writecaches(self):
413 if self._revbranchcache:
413 if self._revbranchcache:
414 self._revbranchcache.write()
414 self._revbranchcache.write()
415
415
416 def _restrictcapabilities(self, caps):
416 def _restrictcapabilities(self, caps):
417 if self.ui.configbool('experimental', 'bundle2-advertise', True):
417 if self.ui.configbool('experimental', 'bundle2-advertise', True):
418 caps = set(caps)
418 caps = set(caps)
419 capsblob = bundle2.encodecaps(bundle2.getrepocaps(self))
419 capsblob = bundle2.encodecaps(bundle2.getrepocaps(self))
420 caps.add('bundle2=' + urlreq.quote(capsblob))
420 caps.add('bundle2=' + urlreq.quote(capsblob))
421 return caps
421 return caps
422
422
423 def _applyopenerreqs(self):
423 def _applyopenerreqs(self):
424 self.svfs.options = dict((r, 1) for r in self.requirements
424 self.svfs.options = dict((r, 1) for r in self.requirements
425 if r in self.openerreqs)
425 if r in self.openerreqs)
426 # experimental config: format.chunkcachesize
426 # experimental config: format.chunkcachesize
427 chunkcachesize = self.ui.configint('format', 'chunkcachesize')
427 chunkcachesize = self.ui.configint('format', 'chunkcachesize')
428 if chunkcachesize is not None:
428 if chunkcachesize is not None:
429 self.svfs.options['chunkcachesize'] = chunkcachesize
429 self.svfs.options['chunkcachesize'] = chunkcachesize
430 # experimental config: format.maxchainlen
430 # experimental config: format.maxchainlen
431 maxchainlen = self.ui.configint('format', 'maxchainlen')
431 maxchainlen = self.ui.configint('format', 'maxchainlen')
432 if maxchainlen is not None:
432 if maxchainlen is not None:
433 self.svfs.options['maxchainlen'] = maxchainlen
433 self.svfs.options['maxchainlen'] = maxchainlen
434 # experimental config: format.manifestcachesize
434 # experimental config: format.manifestcachesize
435 manifestcachesize = self.ui.configint('format', 'manifestcachesize')
435 manifestcachesize = self.ui.configint('format', 'manifestcachesize')
436 if manifestcachesize is not None:
436 if manifestcachesize is not None:
437 self.svfs.options['manifestcachesize'] = manifestcachesize
437 self.svfs.options['manifestcachesize'] = manifestcachesize
438 # experimental config: format.aggressivemergedeltas
438 # experimental config: format.aggressivemergedeltas
439 aggressivemergedeltas = self.ui.configbool('format',
439 aggressivemergedeltas = self.ui.configbool('format',
440 'aggressivemergedeltas', False)
440 'aggressivemergedeltas', False)
441 self.svfs.options['aggressivemergedeltas'] = aggressivemergedeltas
441 self.svfs.options['aggressivemergedeltas'] = aggressivemergedeltas
442 self.svfs.options['lazydeltabase'] = not scmutil.gddeltaconfig(self.ui)
442 self.svfs.options['lazydeltabase'] = not scmutil.gddeltaconfig(self.ui)
443
443
444 for r in self.requirements:
444 for r in self.requirements:
445 if r.startswith('exp-compression-'):
445 if r.startswith('exp-compression-'):
446 self.svfs.options['compengine'] = r[len('exp-compression-'):]
446 self.svfs.options['compengine'] = r[len('exp-compression-'):]
447
447
448 # TODO move "revlogv2" to openerreqs once finalized.
448 # TODO move "revlogv2" to openerreqs once finalized.
449 if REVLOGV2_REQUIREMENT in self.requirements:
449 if REVLOGV2_REQUIREMENT in self.requirements:
450 self.svfs.options['revlogv2'] = True
450 self.svfs.options['revlogv2'] = True
451
451
452 def _writerequirements(self):
452 def _writerequirements(self):
453 scmutil.writerequires(self.vfs, self.requirements)
453 scmutil.writerequires(self.vfs, self.requirements)
454
454
455 def _checknested(self, path):
455 def _checknested(self, path):
456 """Determine if path is a legal nested repository."""
456 """Determine if path is a legal nested repository."""
457 if not path.startswith(self.root):
457 if not path.startswith(self.root):
458 return False
458 return False
459 subpath = path[len(self.root) + 1:]
459 subpath = path[len(self.root) + 1:]
460 normsubpath = util.pconvert(subpath)
460 normsubpath = util.pconvert(subpath)
461
461
462 # XXX: Checking against the current working copy is wrong in
462 # XXX: Checking against the current working copy is wrong in
463 # the sense that it can reject things like
463 # the sense that it can reject things like
464 #
464 #
465 # $ hg cat -r 10 sub/x.txt
465 # $ hg cat -r 10 sub/x.txt
466 #
466 #
467 # if sub/ is no longer a subrepository in the working copy
467 # if sub/ is no longer a subrepository in the working copy
468 # parent revision.
468 # parent revision.
469 #
469 #
470 # However, it can of course also allow things that would have
470 # However, it can of course also allow things that would have
471 # been rejected before, such as the above cat command if sub/
471 # been rejected before, such as the above cat command if sub/
472 # is a subrepository now, but was a normal directory before.
472 # is a subrepository now, but was a normal directory before.
473 # The old path auditor would have rejected by mistake since it
473 # The old path auditor would have rejected by mistake since it
474 # panics when it sees sub/.hg/.
474 # panics when it sees sub/.hg/.
475 #
475 #
476 # All in all, checking against the working copy seems sensible
476 # All in all, checking against the working copy seems sensible
477 # since we want to prevent access to nested repositories on
477 # since we want to prevent access to nested repositories on
478 # the filesystem *now*.
478 # the filesystem *now*.
479 ctx = self[None]
479 ctx = self[None]
480 parts = util.splitpath(subpath)
480 parts = util.splitpath(subpath)
481 while parts:
481 while parts:
482 prefix = '/'.join(parts)
482 prefix = '/'.join(parts)
483 if prefix in ctx.substate:
483 if prefix in ctx.substate:
484 if prefix == normsubpath:
484 if prefix == normsubpath:
485 return True
485 return True
486 else:
486 else:
487 sub = ctx.sub(prefix)
487 sub = ctx.sub(prefix)
488 return sub.checknested(subpath[len(prefix) + 1:])
488 return sub.checknested(subpath[len(prefix) + 1:])
489 else:
489 else:
490 parts.pop()
490 parts.pop()
491 return False
491 return False
492
492
493 def peer(self):
493 def peer(self):
494 return localpeer(self) # not cached to avoid reference cycle
494 return localpeer(self) # not cached to avoid reference cycle
495
495
496 def unfiltered(self):
496 def unfiltered(self):
497 """Return unfiltered version of the repository
497 """Return unfiltered version of the repository
498
498
499 Intended to be overwritten by filtered repo."""
499 Intended to be overwritten by filtered repo."""
500 return self
500 return self
501
501
502 def filtered(self, name):
502 def filtered(self, name):
503 """Return a filtered version of a repository"""
503 """Return a filtered version of a repository"""
504 # build a new class with the mixin and the current class
504 # build a new class with the mixin and the current class
505 # (possibly subclass of the repo)
505 # (possibly subclass of the repo)
506 class filteredrepo(repoview.repoview, self.unfiltered().__class__):
506 class filteredrepo(repoview.repoview, self.unfiltered().__class__):
507 pass
507 pass
508 return filteredrepo(self, name)
508 return filteredrepo(self, name)
509
509
510 @repofilecache('bookmarks', 'bookmarks.current')
510 @repofilecache('bookmarks', 'bookmarks.current')
511 def _bookmarks(self):
511 def _bookmarks(self):
512 return bookmarks.bmstore(self)
512 return bookmarks.bmstore(self)
513
513
514 @property
514 @property
515 def _activebookmark(self):
515 def _activebookmark(self):
516 return self._bookmarks.active
516 return self._bookmarks.active
517
517
518 # _phaserevs and _phasesets depend on changelog. what we need is to
518 # _phaserevs and _phasesets depend on changelog. what we need is to
519 # call _phasecache.invalidate() if '00changelog.i' was changed, but it
519 # call _phasecache.invalidate() if '00changelog.i' was changed, but it
520 # can't be easily expressed in filecache mechanism.
520 # can't be easily expressed in filecache mechanism.
521 @storecache('phaseroots', '00changelog.i')
521 @storecache('phaseroots', '00changelog.i')
522 def _phasecache(self):
522 def _phasecache(self):
523 return phases.phasecache(self, self._phasedefaults)
523 return phases.phasecache(self, self._phasedefaults)
524
524
525 @storecache('obsstore')
525 @storecache('obsstore')
526 def obsstore(self):
526 def obsstore(self):
527 # read default format for new obsstore.
527 return obsolete.makestore(self.ui, self)
528 # developer config: format.obsstore-version
529 defaultformat = self.ui.configint('format', 'obsstore-version', None)
530 # rely on obsstore class default when possible.
531 kwargs = {}
532 if defaultformat is not None:
533 kwargs['defaultformat'] = defaultformat
534 readonly = not obsolete.isenabled(self, obsolete.createmarkersopt)
535 store = obsolete.obsstore(self.svfs, readonly=readonly,
536 **kwargs)
537 if store and readonly:
538 self.ui.warn(
539 _('obsolete feature not enabled but %i markers found!\n')
540 % len(list(store)))
541 return store
542
528
543 @storecache('00changelog.i')
529 @storecache('00changelog.i')
544 def changelog(self):
530 def changelog(self):
545 return changelog.changelog(self.svfs,
531 return changelog.changelog(self.svfs,
546 trypending=txnutil.mayhavepending(self.root))
532 trypending=txnutil.mayhavepending(self.root))
547
533
548 def _constructmanifest(self):
534 def _constructmanifest(self):
549 # This is a temporary function while we migrate from manifest to
535 # This is a temporary function while we migrate from manifest to
550 # manifestlog. It allows bundlerepo and unionrepo to intercept the
536 # manifestlog. It allows bundlerepo and unionrepo to intercept the
551 # manifest creation.
537 # manifest creation.
552 return manifest.manifestrevlog(self.svfs)
538 return manifest.manifestrevlog(self.svfs)
553
539
554 @storecache('00manifest.i')
540 @storecache('00manifest.i')
555 def manifestlog(self):
541 def manifestlog(self):
556 return manifest.manifestlog(self.svfs, self)
542 return manifest.manifestlog(self.svfs, self)
557
543
558 @repofilecache('dirstate')
544 @repofilecache('dirstate')
559 def dirstate(self):
545 def dirstate(self):
560 return dirstate.dirstate(self.vfs, self.ui, self.root,
546 return dirstate.dirstate(self.vfs, self.ui, self.root,
561 self._dirstatevalidate)
547 self._dirstatevalidate)
562
548
563 def _dirstatevalidate(self, node):
549 def _dirstatevalidate(self, node):
564 try:
550 try:
565 self.changelog.rev(node)
551 self.changelog.rev(node)
566 return node
552 return node
567 except error.LookupError:
553 except error.LookupError:
568 if not self._dirstatevalidatewarned:
554 if not self._dirstatevalidatewarned:
569 self._dirstatevalidatewarned = True
555 self._dirstatevalidatewarned = True
570 self.ui.warn(_("warning: ignoring unknown"
556 self.ui.warn(_("warning: ignoring unknown"
571 " working parent %s!\n") % short(node))
557 " working parent %s!\n") % short(node))
572 return nullid
558 return nullid
573
559
574 def __getitem__(self, changeid):
560 def __getitem__(self, changeid):
575 if changeid is None:
561 if changeid is None:
576 return context.workingctx(self)
562 return context.workingctx(self)
577 if isinstance(changeid, slice):
563 if isinstance(changeid, slice):
578 # wdirrev isn't contiguous so the slice shouldn't include it
564 # wdirrev isn't contiguous so the slice shouldn't include it
579 return [context.changectx(self, i)
565 return [context.changectx(self, i)
580 for i in xrange(*changeid.indices(len(self)))
566 for i in xrange(*changeid.indices(len(self)))
581 if i not in self.changelog.filteredrevs]
567 if i not in self.changelog.filteredrevs]
582 try:
568 try:
583 return context.changectx(self, changeid)
569 return context.changectx(self, changeid)
584 except error.WdirUnsupported:
570 except error.WdirUnsupported:
585 return context.workingctx(self)
571 return context.workingctx(self)
586
572
587 def __contains__(self, changeid):
573 def __contains__(self, changeid):
588 """True if the given changeid exists
574 """True if the given changeid exists
589
575
590 error.LookupError is raised if an ambiguous node specified.
576 error.LookupError is raised if an ambiguous node specified.
591 """
577 """
592 try:
578 try:
593 self[changeid]
579 self[changeid]
594 return True
580 return True
595 except error.RepoLookupError:
581 except error.RepoLookupError:
596 return False
582 return False
597
583
598 def __nonzero__(self):
584 def __nonzero__(self):
599 return True
585 return True
600
586
601 __bool__ = __nonzero__
587 __bool__ = __nonzero__
602
588
603 def __len__(self):
589 def __len__(self):
604 return len(self.changelog)
590 return len(self.changelog)
605
591
606 def __iter__(self):
592 def __iter__(self):
607 return iter(self.changelog)
593 return iter(self.changelog)
608
594
609 def revs(self, expr, *args):
595 def revs(self, expr, *args):
610 '''Find revisions matching a revset.
596 '''Find revisions matching a revset.
611
597
612 The revset is specified as a string ``expr`` that may contain
598 The revset is specified as a string ``expr`` that may contain
613 %-formatting to escape certain types. See ``revsetlang.formatspec``.
599 %-formatting to escape certain types. See ``revsetlang.formatspec``.
614
600
615 Revset aliases from the configuration are not expanded. To expand
601 Revset aliases from the configuration are not expanded. To expand
616 user aliases, consider calling ``scmutil.revrange()`` or
602 user aliases, consider calling ``scmutil.revrange()`` or
617 ``repo.anyrevs([expr], user=True)``.
603 ``repo.anyrevs([expr], user=True)``.
618
604
619 Returns a revset.abstractsmartset, which is a list-like interface
605 Returns a revset.abstractsmartset, which is a list-like interface
620 that contains integer revisions.
606 that contains integer revisions.
621 '''
607 '''
622 expr = revsetlang.formatspec(expr, *args)
608 expr = revsetlang.formatspec(expr, *args)
623 m = revset.match(None, expr)
609 m = revset.match(None, expr)
624 return m(self)
610 return m(self)
625
611
626 def set(self, expr, *args):
612 def set(self, expr, *args):
627 '''Find revisions matching a revset and emit changectx instances.
613 '''Find revisions matching a revset and emit changectx instances.
628
614
629 This is a convenience wrapper around ``revs()`` that iterates the
615 This is a convenience wrapper around ``revs()`` that iterates the
630 result and is a generator of changectx instances.
616 result and is a generator of changectx instances.
631
617
632 Revset aliases from the configuration are not expanded. To expand
618 Revset aliases from the configuration are not expanded. To expand
633 user aliases, consider calling ``scmutil.revrange()``.
619 user aliases, consider calling ``scmutil.revrange()``.
634 '''
620 '''
635 for r in self.revs(expr, *args):
621 for r in self.revs(expr, *args):
636 yield self[r]
622 yield self[r]
637
623
638 def anyrevs(self, specs, user=False):
624 def anyrevs(self, specs, user=False):
639 '''Find revisions matching one of the given revsets.
625 '''Find revisions matching one of the given revsets.
640
626
641 Revset aliases from the configuration are not expanded by default. To
627 Revset aliases from the configuration are not expanded by default. To
642 expand user aliases, specify ``user=True``.
628 expand user aliases, specify ``user=True``.
643 '''
629 '''
644 if user:
630 if user:
645 m = revset.matchany(self.ui, specs, repo=self)
631 m = revset.matchany(self.ui, specs, repo=self)
646 else:
632 else:
647 m = revset.matchany(None, specs)
633 m = revset.matchany(None, specs)
648 return m(self)
634 return m(self)
649
635
650 def url(self):
636 def url(self):
651 return 'file:' + self.root
637 return 'file:' + self.root
652
638
653 def hook(self, name, throw=False, **args):
639 def hook(self, name, throw=False, **args):
654 """Call a hook, passing this repo instance.
640 """Call a hook, passing this repo instance.
655
641
656 This a convenience method to aid invoking hooks. Extensions likely
642 This a convenience method to aid invoking hooks. Extensions likely
657 won't call this unless they have registered a custom hook or are
643 won't call this unless they have registered a custom hook or are
658 replacing code that is expected to call a hook.
644 replacing code that is expected to call a hook.
659 """
645 """
660 return hook.hook(self.ui, self, name, throw, **args)
646 return hook.hook(self.ui, self, name, throw, **args)
661
647
662 @filteredpropertycache
648 @filteredpropertycache
663 def _tagscache(self):
649 def _tagscache(self):
664 '''Returns a tagscache object that contains various tags related
650 '''Returns a tagscache object that contains various tags related
665 caches.'''
651 caches.'''
666
652
667 # This simplifies its cache management by having one decorated
653 # This simplifies its cache management by having one decorated
668 # function (this one) and the rest simply fetch things from it.
654 # function (this one) and the rest simply fetch things from it.
669 class tagscache(object):
655 class tagscache(object):
670 def __init__(self):
656 def __init__(self):
671 # These two define the set of tags for this repository. tags
657 # These two define the set of tags for this repository. tags
672 # maps tag name to node; tagtypes maps tag name to 'global' or
658 # maps tag name to node; tagtypes maps tag name to 'global' or
673 # 'local'. (Global tags are defined by .hgtags across all
659 # 'local'. (Global tags are defined by .hgtags across all
674 # heads, and local tags are defined in .hg/localtags.)
660 # heads, and local tags are defined in .hg/localtags.)
675 # They constitute the in-memory cache of tags.
661 # They constitute the in-memory cache of tags.
676 self.tags = self.tagtypes = None
662 self.tags = self.tagtypes = None
677
663
678 self.nodetagscache = self.tagslist = None
664 self.nodetagscache = self.tagslist = None
679
665
680 cache = tagscache()
666 cache = tagscache()
681 cache.tags, cache.tagtypes = self._findtags()
667 cache.tags, cache.tagtypes = self._findtags()
682
668
683 return cache
669 return cache
684
670
685 def tags(self):
671 def tags(self):
686 '''return a mapping of tag to node'''
672 '''return a mapping of tag to node'''
687 t = {}
673 t = {}
688 if self.changelog.filteredrevs:
674 if self.changelog.filteredrevs:
689 tags, tt = self._findtags()
675 tags, tt = self._findtags()
690 else:
676 else:
691 tags = self._tagscache.tags
677 tags = self._tagscache.tags
692 for k, v in tags.iteritems():
678 for k, v in tags.iteritems():
693 try:
679 try:
694 # ignore tags to unknown nodes
680 # ignore tags to unknown nodes
695 self.changelog.rev(v)
681 self.changelog.rev(v)
696 t[k] = v
682 t[k] = v
697 except (error.LookupError, ValueError):
683 except (error.LookupError, ValueError):
698 pass
684 pass
699 return t
685 return t
700
686
701 def _findtags(self):
687 def _findtags(self):
702 '''Do the hard work of finding tags. Return a pair of dicts
688 '''Do the hard work of finding tags. Return a pair of dicts
703 (tags, tagtypes) where tags maps tag name to node, and tagtypes
689 (tags, tagtypes) where tags maps tag name to node, and tagtypes
704 maps tag name to a string like \'global\' or \'local\'.
690 maps tag name to a string like \'global\' or \'local\'.
705 Subclasses or extensions are free to add their own tags, but
691 Subclasses or extensions are free to add their own tags, but
706 should be aware that the returned dicts will be retained for the
692 should be aware that the returned dicts will be retained for the
707 duration of the localrepo object.'''
693 duration of the localrepo object.'''
708
694
709 # XXX what tagtype should subclasses/extensions use? Currently
695 # XXX what tagtype should subclasses/extensions use? Currently
710 # mq and bookmarks add tags, but do not set the tagtype at all.
696 # mq and bookmarks add tags, but do not set the tagtype at all.
711 # Should each extension invent its own tag type? Should there
697 # Should each extension invent its own tag type? Should there
712 # be one tagtype for all such "virtual" tags? Or is the status
698 # be one tagtype for all such "virtual" tags? Or is the status
713 # quo fine?
699 # quo fine?
714
700
715
701
716 # map tag name to (node, hist)
702 # map tag name to (node, hist)
717 alltags = tagsmod.findglobaltags(self.ui, self)
703 alltags = tagsmod.findglobaltags(self.ui, self)
718 # map tag name to tag type
704 # map tag name to tag type
719 tagtypes = dict((tag, 'global') for tag in alltags)
705 tagtypes = dict((tag, 'global') for tag in alltags)
720
706
721 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
707 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
722
708
723 # Build the return dicts. Have to re-encode tag names because
709 # Build the return dicts. Have to re-encode tag names because
724 # the tags module always uses UTF-8 (in order not to lose info
710 # the tags module always uses UTF-8 (in order not to lose info
725 # writing to the cache), but the rest of Mercurial wants them in
711 # writing to the cache), but the rest of Mercurial wants them in
726 # local encoding.
712 # local encoding.
727 tags = {}
713 tags = {}
728 for (name, (node, hist)) in alltags.iteritems():
714 for (name, (node, hist)) in alltags.iteritems():
729 if node != nullid:
715 if node != nullid:
730 tags[encoding.tolocal(name)] = node
716 tags[encoding.tolocal(name)] = node
731 tags['tip'] = self.changelog.tip()
717 tags['tip'] = self.changelog.tip()
732 tagtypes = dict([(encoding.tolocal(name), value)
718 tagtypes = dict([(encoding.tolocal(name), value)
733 for (name, value) in tagtypes.iteritems()])
719 for (name, value) in tagtypes.iteritems()])
734 return (tags, tagtypes)
720 return (tags, tagtypes)
735
721
736 def tagtype(self, tagname):
722 def tagtype(self, tagname):
737 '''
723 '''
738 return the type of the given tag. result can be:
724 return the type of the given tag. result can be:
739
725
740 'local' : a local tag
726 'local' : a local tag
741 'global' : a global tag
727 'global' : a global tag
742 None : tag does not exist
728 None : tag does not exist
743 '''
729 '''
744
730
745 return self._tagscache.tagtypes.get(tagname)
731 return self._tagscache.tagtypes.get(tagname)
746
732
747 def tagslist(self):
733 def tagslist(self):
748 '''return a list of tags ordered by revision'''
734 '''return a list of tags ordered by revision'''
749 if not self._tagscache.tagslist:
735 if not self._tagscache.tagslist:
750 l = []
736 l = []
751 for t, n in self.tags().iteritems():
737 for t, n in self.tags().iteritems():
752 l.append((self.changelog.rev(n), t, n))
738 l.append((self.changelog.rev(n), t, n))
753 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
739 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
754
740
755 return self._tagscache.tagslist
741 return self._tagscache.tagslist
756
742
757 def nodetags(self, node):
743 def nodetags(self, node):
758 '''return the tags associated with a node'''
744 '''return the tags associated with a node'''
759 if not self._tagscache.nodetagscache:
745 if not self._tagscache.nodetagscache:
760 nodetagscache = {}
746 nodetagscache = {}
761 for t, n in self._tagscache.tags.iteritems():
747 for t, n in self._tagscache.tags.iteritems():
762 nodetagscache.setdefault(n, []).append(t)
748 nodetagscache.setdefault(n, []).append(t)
763 for tags in nodetagscache.itervalues():
749 for tags in nodetagscache.itervalues():
764 tags.sort()
750 tags.sort()
765 self._tagscache.nodetagscache = nodetagscache
751 self._tagscache.nodetagscache = nodetagscache
766 return self._tagscache.nodetagscache.get(node, [])
752 return self._tagscache.nodetagscache.get(node, [])
767
753
768 def nodebookmarks(self, node):
754 def nodebookmarks(self, node):
769 """return the list of bookmarks pointing to the specified node"""
755 """return the list of bookmarks pointing to the specified node"""
770 marks = []
756 marks = []
771 for bookmark, n in self._bookmarks.iteritems():
757 for bookmark, n in self._bookmarks.iteritems():
772 if n == node:
758 if n == node:
773 marks.append(bookmark)
759 marks.append(bookmark)
774 return sorted(marks)
760 return sorted(marks)
775
761
776 def branchmap(self):
762 def branchmap(self):
777 '''returns a dictionary {branch: [branchheads]} with branchheads
763 '''returns a dictionary {branch: [branchheads]} with branchheads
778 ordered by increasing revision number'''
764 ordered by increasing revision number'''
779 branchmap.updatecache(self)
765 branchmap.updatecache(self)
780 return self._branchcaches[self.filtername]
766 return self._branchcaches[self.filtername]
781
767
782 @unfilteredmethod
768 @unfilteredmethod
783 def revbranchcache(self):
769 def revbranchcache(self):
784 if not self._revbranchcache:
770 if not self._revbranchcache:
785 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
771 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
786 return self._revbranchcache
772 return self._revbranchcache
787
773
788 def branchtip(self, branch, ignoremissing=False):
774 def branchtip(self, branch, ignoremissing=False):
789 '''return the tip node for a given branch
775 '''return the tip node for a given branch
790
776
791 If ignoremissing is True, then this method will not raise an error.
777 If ignoremissing is True, then this method will not raise an error.
792 This is helpful for callers that only expect None for a missing branch
778 This is helpful for callers that only expect None for a missing branch
793 (e.g. namespace).
779 (e.g. namespace).
794
780
795 '''
781 '''
796 try:
782 try:
797 return self.branchmap().branchtip(branch)
783 return self.branchmap().branchtip(branch)
798 except KeyError:
784 except KeyError:
799 if not ignoremissing:
785 if not ignoremissing:
800 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
786 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
801 else:
787 else:
802 pass
788 pass
803
789
804 def lookup(self, key):
790 def lookup(self, key):
805 return self[key].node()
791 return self[key].node()
806
792
807 def lookupbranch(self, key, remote=None):
793 def lookupbranch(self, key, remote=None):
808 repo = remote or self
794 repo = remote or self
809 if key in repo.branchmap():
795 if key in repo.branchmap():
810 return key
796 return key
811
797
812 repo = (remote and remote.local()) and remote or self
798 repo = (remote and remote.local()) and remote or self
813 return repo[key].branch()
799 return repo[key].branch()
814
800
815 def known(self, nodes):
801 def known(self, nodes):
816 cl = self.changelog
802 cl = self.changelog
817 nm = cl.nodemap
803 nm = cl.nodemap
818 filtered = cl.filteredrevs
804 filtered = cl.filteredrevs
819 result = []
805 result = []
820 for n in nodes:
806 for n in nodes:
821 r = nm.get(n)
807 r = nm.get(n)
822 resp = not (r is None or r in filtered)
808 resp = not (r is None or r in filtered)
823 result.append(resp)
809 result.append(resp)
824 return result
810 return result
825
811
826 def local(self):
812 def local(self):
827 return self
813 return self
828
814
829 def publishing(self):
815 def publishing(self):
830 # it's safe (and desirable) to trust the publish flag unconditionally
816 # it's safe (and desirable) to trust the publish flag unconditionally
831 # so that we don't finalize changes shared between users via ssh or nfs
817 # so that we don't finalize changes shared between users via ssh or nfs
832 return self.ui.configbool('phases', 'publish', True, untrusted=True)
818 return self.ui.configbool('phases', 'publish', True, untrusted=True)
833
819
834 def cancopy(self):
820 def cancopy(self):
835 # so statichttprepo's override of local() works
821 # so statichttprepo's override of local() works
836 if not self.local():
822 if not self.local():
837 return False
823 return False
838 if not self.publishing():
824 if not self.publishing():
839 return True
825 return True
840 # if publishing we can't copy if there is filtered content
826 # if publishing we can't copy if there is filtered content
841 return not self.filtered('visible').changelog.filteredrevs
827 return not self.filtered('visible').changelog.filteredrevs
842
828
843 def shared(self):
829 def shared(self):
844 '''the type of shared repository (None if not shared)'''
830 '''the type of shared repository (None if not shared)'''
845 if self.sharedpath != self.path:
831 if self.sharedpath != self.path:
846 return 'store'
832 return 'store'
847 return None
833 return None
848
834
849 def wjoin(self, f, *insidef):
835 def wjoin(self, f, *insidef):
850 return self.vfs.reljoin(self.root, f, *insidef)
836 return self.vfs.reljoin(self.root, f, *insidef)
851
837
852 def file(self, f):
838 def file(self, f):
853 if f[0] == '/':
839 if f[0] == '/':
854 f = f[1:]
840 f = f[1:]
855 return filelog.filelog(self.svfs, f)
841 return filelog.filelog(self.svfs, f)
856
842
857 def changectx(self, changeid):
843 def changectx(self, changeid):
858 return self[changeid]
844 return self[changeid]
859
845
860 def setparents(self, p1, p2=nullid):
846 def setparents(self, p1, p2=nullid):
861 with self.dirstate.parentchange():
847 with self.dirstate.parentchange():
862 copies = self.dirstate.setparents(p1, p2)
848 copies = self.dirstate.setparents(p1, p2)
863 pctx = self[p1]
849 pctx = self[p1]
864 if copies:
850 if copies:
865 # Adjust copy records, the dirstate cannot do it, it
851 # Adjust copy records, the dirstate cannot do it, it
866 # requires access to parents manifests. Preserve them
852 # requires access to parents manifests. Preserve them
867 # only for entries added to first parent.
853 # only for entries added to first parent.
868 for f in copies:
854 for f in copies:
869 if f not in pctx and copies[f] in pctx:
855 if f not in pctx and copies[f] in pctx:
870 self.dirstate.copy(copies[f], f)
856 self.dirstate.copy(copies[f], f)
871 if p2 == nullid:
857 if p2 == nullid:
872 for f, s in sorted(self.dirstate.copies().items()):
858 for f, s in sorted(self.dirstate.copies().items()):
873 if f not in pctx and s not in pctx:
859 if f not in pctx and s not in pctx:
874 self.dirstate.copy(None, f)
860 self.dirstate.copy(None, f)
875
861
876 def filectx(self, path, changeid=None, fileid=None):
862 def filectx(self, path, changeid=None, fileid=None):
877 """changeid can be a changeset revision, node, or tag.
863 """changeid can be a changeset revision, node, or tag.
878 fileid can be a file revision or node."""
864 fileid can be a file revision or node."""
879 return context.filectx(self, path, changeid, fileid)
865 return context.filectx(self, path, changeid, fileid)
880
866
881 def getcwd(self):
867 def getcwd(self):
882 return self.dirstate.getcwd()
868 return self.dirstate.getcwd()
883
869
884 def pathto(self, f, cwd=None):
870 def pathto(self, f, cwd=None):
885 return self.dirstate.pathto(f, cwd)
871 return self.dirstate.pathto(f, cwd)
886
872
887 def _loadfilter(self, filter):
873 def _loadfilter(self, filter):
888 if filter not in self.filterpats:
874 if filter not in self.filterpats:
889 l = []
875 l = []
890 for pat, cmd in self.ui.configitems(filter):
876 for pat, cmd in self.ui.configitems(filter):
891 if cmd == '!':
877 if cmd == '!':
892 continue
878 continue
893 mf = matchmod.match(self.root, '', [pat])
879 mf = matchmod.match(self.root, '', [pat])
894 fn = None
880 fn = None
895 params = cmd
881 params = cmd
896 for name, filterfn in self._datafilters.iteritems():
882 for name, filterfn in self._datafilters.iteritems():
897 if cmd.startswith(name):
883 if cmd.startswith(name):
898 fn = filterfn
884 fn = filterfn
899 params = cmd[len(name):].lstrip()
885 params = cmd[len(name):].lstrip()
900 break
886 break
901 if not fn:
887 if not fn:
902 fn = lambda s, c, **kwargs: util.filter(s, c)
888 fn = lambda s, c, **kwargs: util.filter(s, c)
903 # Wrap old filters not supporting keyword arguments
889 # Wrap old filters not supporting keyword arguments
904 if not inspect.getargspec(fn)[2]:
890 if not inspect.getargspec(fn)[2]:
905 oldfn = fn
891 oldfn = fn
906 fn = lambda s, c, **kwargs: oldfn(s, c)
892 fn = lambda s, c, **kwargs: oldfn(s, c)
907 l.append((mf, fn, params))
893 l.append((mf, fn, params))
908 self.filterpats[filter] = l
894 self.filterpats[filter] = l
909 return self.filterpats[filter]
895 return self.filterpats[filter]
910
896
911 def _filter(self, filterpats, filename, data):
897 def _filter(self, filterpats, filename, data):
912 for mf, fn, cmd in filterpats:
898 for mf, fn, cmd in filterpats:
913 if mf(filename):
899 if mf(filename):
914 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
900 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
915 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
901 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
916 break
902 break
917
903
918 return data
904 return data
919
905
920 @unfilteredpropertycache
906 @unfilteredpropertycache
921 def _encodefilterpats(self):
907 def _encodefilterpats(self):
922 return self._loadfilter('encode')
908 return self._loadfilter('encode')
923
909
924 @unfilteredpropertycache
910 @unfilteredpropertycache
925 def _decodefilterpats(self):
911 def _decodefilterpats(self):
926 return self._loadfilter('decode')
912 return self._loadfilter('decode')
927
913
928 def adddatafilter(self, name, filter):
914 def adddatafilter(self, name, filter):
929 self._datafilters[name] = filter
915 self._datafilters[name] = filter
930
916
931 def wread(self, filename):
917 def wread(self, filename):
932 if self.wvfs.islink(filename):
918 if self.wvfs.islink(filename):
933 data = self.wvfs.readlink(filename)
919 data = self.wvfs.readlink(filename)
934 else:
920 else:
935 data = self.wvfs.read(filename)
921 data = self.wvfs.read(filename)
936 return self._filter(self._encodefilterpats, filename, data)
922 return self._filter(self._encodefilterpats, filename, data)
937
923
938 def wwrite(self, filename, data, flags, backgroundclose=False):
924 def wwrite(self, filename, data, flags, backgroundclose=False):
939 """write ``data`` into ``filename`` in the working directory
925 """write ``data`` into ``filename`` in the working directory
940
926
941 This returns length of written (maybe decoded) data.
927 This returns length of written (maybe decoded) data.
942 """
928 """
943 data = self._filter(self._decodefilterpats, filename, data)
929 data = self._filter(self._decodefilterpats, filename, data)
944 if 'l' in flags:
930 if 'l' in flags:
945 self.wvfs.symlink(data, filename)
931 self.wvfs.symlink(data, filename)
946 else:
932 else:
947 self.wvfs.write(filename, data, backgroundclose=backgroundclose)
933 self.wvfs.write(filename, data, backgroundclose=backgroundclose)
948 if 'x' in flags:
934 if 'x' in flags:
949 self.wvfs.setflags(filename, False, True)
935 self.wvfs.setflags(filename, False, True)
950 return len(data)
936 return len(data)
951
937
952 def wwritedata(self, filename, data):
938 def wwritedata(self, filename, data):
953 return self._filter(self._decodefilterpats, filename, data)
939 return self._filter(self._decodefilterpats, filename, data)
954
940
955 def currenttransaction(self):
941 def currenttransaction(self):
956 """return the current transaction or None if non exists"""
942 """return the current transaction or None if non exists"""
957 if self._transref:
943 if self._transref:
958 tr = self._transref()
944 tr = self._transref()
959 else:
945 else:
960 tr = None
946 tr = None
961
947
962 if tr and tr.running():
948 if tr and tr.running():
963 return tr
949 return tr
964 return None
950 return None
965
951
966 def transaction(self, desc, report=None):
952 def transaction(self, desc, report=None):
967 if (self.ui.configbool('devel', 'all-warnings')
953 if (self.ui.configbool('devel', 'all-warnings')
968 or self.ui.configbool('devel', 'check-locks')):
954 or self.ui.configbool('devel', 'check-locks')):
969 if self._currentlock(self._lockref) is None:
955 if self._currentlock(self._lockref) is None:
970 raise error.ProgrammingError('transaction requires locking')
956 raise error.ProgrammingError('transaction requires locking')
971 tr = self.currenttransaction()
957 tr = self.currenttransaction()
972 if tr is not None:
958 if tr is not None:
973 return tr.nest()
959 return tr.nest()
974
960
975 # abort here if the journal already exists
961 # abort here if the journal already exists
976 if self.svfs.exists("journal"):
962 if self.svfs.exists("journal"):
977 raise error.RepoError(
963 raise error.RepoError(
978 _("abandoned transaction found"),
964 _("abandoned transaction found"),
979 hint=_("run 'hg recover' to clean up transaction"))
965 hint=_("run 'hg recover' to clean up transaction"))
980
966
981 idbase = "%.40f#%f" % (random.random(), time.time())
967 idbase = "%.40f#%f" % (random.random(), time.time())
982 ha = hex(hashlib.sha1(idbase).digest())
968 ha = hex(hashlib.sha1(idbase).digest())
983 txnid = 'TXN:' + ha
969 txnid = 'TXN:' + ha
984 self.hook('pretxnopen', throw=True, txnname=desc, txnid=txnid)
970 self.hook('pretxnopen', throw=True, txnname=desc, txnid=txnid)
985
971
986 self._writejournal(desc)
972 self._writejournal(desc)
987 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
973 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
988 if report:
974 if report:
989 rp = report
975 rp = report
990 else:
976 else:
991 rp = self.ui.warn
977 rp = self.ui.warn
992 vfsmap = {'plain': self.vfs} # root of .hg/
978 vfsmap = {'plain': self.vfs} # root of .hg/
993 # we must avoid cyclic reference between repo and transaction.
979 # we must avoid cyclic reference between repo and transaction.
994 reporef = weakref.ref(self)
980 reporef = weakref.ref(self)
995 # Code to track tag movement
981 # Code to track tag movement
996 #
982 #
997 # Since tags are all handled as file content, it is actually quite hard
983 # Since tags are all handled as file content, it is actually quite hard
998 # to track these movement from a code perspective. So we fallback to a
984 # to track these movement from a code perspective. So we fallback to a
999 # tracking at the repository level. One could envision to track changes
985 # tracking at the repository level. One could envision to track changes
1000 # to the '.hgtags' file through changegroup apply but that fails to
986 # to the '.hgtags' file through changegroup apply but that fails to
1001 # cope with case where transaction expose new heads without changegroup
987 # cope with case where transaction expose new heads without changegroup
1002 # being involved (eg: phase movement).
988 # being involved (eg: phase movement).
1003 #
989 #
1004 # For now, We gate the feature behind a flag since this likely comes
990 # For now, We gate the feature behind a flag since this likely comes
1005 # with performance impacts. The current code run more often than needed
991 # with performance impacts. The current code run more often than needed
1006 # and do not use caches as much as it could. The current focus is on
992 # and do not use caches as much as it could. The current focus is on
1007 # the behavior of the feature so we disable it by default. The flag
993 # the behavior of the feature so we disable it by default. The flag
1008 # will be removed when we are happy with the performance impact.
994 # will be removed when we are happy with the performance impact.
1009 #
995 #
1010 # Once this feature is no longer experimental move the following
996 # Once this feature is no longer experimental move the following
1011 # documentation to the appropriate help section:
997 # documentation to the appropriate help section:
1012 #
998 #
1013 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
999 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
1014 # tags (new or changed or deleted tags). In addition the details of
1000 # tags (new or changed or deleted tags). In addition the details of
1015 # these changes are made available in a file at:
1001 # these changes are made available in a file at:
1016 # ``REPOROOT/.hg/changes/tags.changes``.
1002 # ``REPOROOT/.hg/changes/tags.changes``.
1017 # Make sure you check for HG_TAG_MOVED before reading that file as it
1003 # Make sure you check for HG_TAG_MOVED before reading that file as it
1018 # might exist from a previous transaction even if no tag were touched
1004 # might exist from a previous transaction even if no tag were touched
1019 # in this one. Changes are recorded in a line base format::
1005 # in this one. Changes are recorded in a line base format::
1020 #
1006 #
1021 # <action> <hex-node> <tag-name>\n
1007 # <action> <hex-node> <tag-name>\n
1022 #
1008 #
1023 # Actions are defined as follow:
1009 # Actions are defined as follow:
1024 # "-R": tag is removed,
1010 # "-R": tag is removed,
1025 # "+A": tag is added,
1011 # "+A": tag is added,
1026 # "-M": tag is moved (old value),
1012 # "-M": tag is moved (old value),
1027 # "+M": tag is moved (new value),
1013 # "+M": tag is moved (new value),
1028 tracktags = lambda x: None
1014 tracktags = lambda x: None
1029 # experimental config: experimental.hook-track-tags
1015 # experimental config: experimental.hook-track-tags
1030 shouldtracktags = self.ui.configbool('experimental', 'hook-track-tags',
1016 shouldtracktags = self.ui.configbool('experimental', 'hook-track-tags',
1031 False)
1017 False)
1032 if desc != 'strip' and shouldtracktags:
1018 if desc != 'strip' and shouldtracktags:
1033 oldheads = self.changelog.headrevs()
1019 oldheads = self.changelog.headrevs()
1034 def tracktags(tr2):
1020 def tracktags(tr2):
1035 repo = reporef()
1021 repo = reporef()
1036 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
1022 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
1037 newheads = repo.changelog.headrevs()
1023 newheads = repo.changelog.headrevs()
1038 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
1024 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
1039 # notes: we compare lists here.
1025 # notes: we compare lists here.
1040 # As we do it only once buiding set would not be cheaper
1026 # As we do it only once buiding set would not be cheaper
1041 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
1027 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
1042 if changes:
1028 if changes:
1043 tr2.hookargs['tag_moved'] = '1'
1029 tr2.hookargs['tag_moved'] = '1'
1044 with repo.vfs('changes/tags.changes', 'w',
1030 with repo.vfs('changes/tags.changes', 'w',
1045 atomictemp=True) as changesfile:
1031 atomictemp=True) as changesfile:
1046 # note: we do not register the file to the transaction
1032 # note: we do not register the file to the transaction
1047 # because we needs it to still exist on the transaction
1033 # because we needs it to still exist on the transaction
1048 # is close (for txnclose hooks)
1034 # is close (for txnclose hooks)
1049 tagsmod.writediff(changesfile, changes)
1035 tagsmod.writediff(changesfile, changes)
1050 def validate(tr2):
1036 def validate(tr2):
1051 """will run pre-closing hooks"""
1037 """will run pre-closing hooks"""
1052 # XXX the transaction API is a bit lacking here so we take a hacky
1038 # XXX the transaction API is a bit lacking here so we take a hacky
1053 # path for now
1039 # path for now
1054 #
1040 #
1055 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
1041 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
1056 # dict is copied before these run. In addition we needs the data
1042 # dict is copied before these run. In addition we needs the data
1057 # available to in memory hooks too.
1043 # available to in memory hooks too.
1058 #
1044 #
1059 # Moreover, we also need to make sure this runs before txnclose
1045 # Moreover, we also need to make sure this runs before txnclose
1060 # hooks and there is no "pending" mechanism that would execute
1046 # hooks and there is no "pending" mechanism that would execute
1061 # logic only if hooks are about to run.
1047 # logic only if hooks are about to run.
1062 #
1048 #
1063 # Fixing this limitation of the transaction is also needed to track
1049 # Fixing this limitation of the transaction is also needed to track
1064 # other families of changes (bookmarks, phases, obsolescence).
1050 # other families of changes (bookmarks, phases, obsolescence).
1065 #
1051 #
1066 # This will have to be fixed before we remove the experimental
1052 # This will have to be fixed before we remove the experimental
1067 # gating.
1053 # gating.
1068 tracktags(tr2)
1054 tracktags(tr2)
1069 reporef().hook('pretxnclose', throw=True,
1055 reporef().hook('pretxnclose', throw=True,
1070 txnname=desc, **pycompat.strkwargs(tr.hookargs))
1056 txnname=desc, **pycompat.strkwargs(tr.hookargs))
1071 def releasefn(tr, success):
1057 def releasefn(tr, success):
1072 repo = reporef()
1058 repo = reporef()
1073 if success:
1059 if success:
1074 # this should be explicitly invoked here, because
1060 # this should be explicitly invoked here, because
1075 # in-memory changes aren't written out at closing
1061 # in-memory changes aren't written out at closing
1076 # transaction, if tr.addfilegenerator (via
1062 # transaction, if tr.addfilegenerator (via
1077 # dirstate.write or so) isn't invoked while
1063 # dirstate.write or so) isn't invoked while
1078 # transaction running
1064 # transaction running
1079 repo.dirstate.write(None)
1065 repo.dirstate.write(None)
1080 else:
1066 else:
1081 # discard all changes (including ones already written
1067 # discard all changes (including ones already written
1082 # out) in this transaction
1068 # out) in this transaction
1083 repo.dirstate.restorebackup(None, prefix='journal.')
1069 repo.dirstate.restorebackup(None, prefix='journal.')
1084
1070
1085 repo.invalidate(clearfilecache=True)
1071 repo.invalidate(clearfilecache=True)
1086
1072
1087 tr = transaction.transaction(rp, self.svfs, vfsmap,
1073 tr = transaction.transaction(rp, self.svfs, vfsmap,
1088 "journal",
1074 "journal",
1089 "undo",
1075 "undo",
1090 aftertrans(renames),
1076 aftertrans(renames),
1091 self.store.createmode,
1077 self.store.createmode,
1092 validator=validate,
1078 validator=validate,
1093 releasefn=releasefn)
1079 releasefn=releasefn)
1094 tr.changes['revs'] = set()
1080 tr.changes['revs'] = set()
1095
1081
1096 tr.hookargs['txnid'] = txnid
1082 tr.hookargs['txnid'] = txnid
1097 # note: writing the fncache only during finalize mean that the file is
1083 # note: writing the fncache only during finalize mean that the file is
1098 # outdated when running hooks. As fncache is used for streaming clone,
1084 # outdated when running hooks. As fncache is used for streaming clone,
1099 # this is not expected to break anything that happen during the hooks.
1085 # this is not expected to break anything that happen during the hooks.
1100 tr.addfinalize('flush-fncache', self.store.write)
1086 tr.addfinalize('flush-fncache', self.store.write)
1101 def txnclosehook(tr2):
1087 def txnclosehook(tr2):
1102 """To be run if transaction is successful, will schedule a hook run
1088 """To be run if transaction is successful, will schedule a hook run
1103 """
1089 """
1104 # Don't reference tr2 in hook() so we don't hold a reference.
1090 # Don't reference tr2 in hook() so we don't hold a reference.
1105 # This reduces memory consumption when there are multiple
1091 # This reduces memory consumption when there are multiple
1106 # transactions per lock. This can likely go away if issue5045
1092 # transactions per lock. This can likely go away if issue5045
1107 # fixes the function accumulation.
1093 # fixes the function accumulation.
1108 hookargs = tr2.hookargs
1094 hookargs = tr2.hookargs
1109
1095
1110 def hook():
1096 def hook():
1111 reporef().hook('txnclose', throw=False, txnname=desc,
1097 reporef().hook('txnclose', throw=False, txnname=desc,
1112 **pycompat.strkwargs(hookargs))
1098 **pycompat.strkwargs(hookargs))
1113 reporef()._afterlock(hook)
1099 reporef()._afterlock(hook)
1114 tr.addfinalize('txnclose-hook', txnclosehook)
1100 tr.addfinalize('txnclose-hook', txnclosehook)
1115 tr.addpostclose('warms-cache', self._buildcacheupdater(tr))
1101 tr.addpostclose('warms-cache', self._buildcacheupdater(tr))
1116 def txnaborthook(tr2):
1102 def txnaborthook(tr2):
1117 """To be run if transaction is aborted
1103 """To be run if transaction is aborted
1118 """
1104 """
1119 reporef().hook('txnabort', throw=False, txnname=desc,
1105 reporef().hook('txnabort', throw=False, txnname=desc,
1120 **tr2.hookargs)
1106 **tr2.hookargs)
1121 tr.addabort('txnabort-hook', txnaborthook)
1107 tr.addabort('txnabort-hook', txnaborthook)
1122 # avoid eager cache invalidation. in-memory data should be identical
1108 # avoid eager cache invalidation. in-memory data should be identical
1123 # to stored data if transaction has no error.
1109 # to stored data if transaction has no error.
1124 tr.addpostclose('refresh-filecachestats', self._refreshfilecachestats)
1110 tr.addpostclose('refresh-filecachestats', self._refreshfilecachestats)
1125 self._transref = weakref.ref(tr)
1111 self._transref = weakref.ref(tr)
1126 return tr
1112 return tr
1127
1113
1128 def _journalfiles(self):
1114 def _journalfiles(self):
1129 return ((self.svfs, 'journal'),
1115 return ((self.svfs, 'journal'),
1130 (self.vfs, 'journal.dirstate'),
1116 (self.vfs, 'journal.dirstate'),
1131 (self.vfs, 'journal.branch'),
1117 (self.vfs, 'journal.branch'),
1132 (self.vfs, 'journal.desc'),
1118 (self.vfs, 'journal.desc'),
1133 (self.vfs, 'journal.bookmarks'),
1119 (self.vfs, 'journal.bookmarks'),
1134 (self.svfs, 'journal.phaseroots'))
1120 (self.svfs, 'journal.phaseroots'))
1135
1121
1136 def undofiles(self):
1122 def undofiles(self):
1137 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
1123 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
1138
1124
1139 @unfilteredmethod
1125 @unfilteredmethod
1140 def _writejournal(self, desc):
1126 def _writejournal(self, desc):
1141 self.dirstate.savebackup(None, prefix='journal.')
1127 self.dirstate.savebackup(None, prefix='journal.')
1142 self.vfs.write("journal.branch",
1128 self.vfs.write("journal.branch",
1143 encoding.fromlocal(self.dirstate.branch()))
1129 encoding.fromlocal(self.dirstate.branch()))
1144 self.vfs.write("journal.desc",
1130 self.vfs.write("journal.desc",
1145 "%d\n%s\n" % (len(self), desc))
1131 "%d\n%s\n" % (len(self), desc))
1146 self.vfs.write("journal.bookmarks",
1132 self.vfs.write("journal.bookmarks",
1147 self.vfs.tryread("bookmarks"))
1133 self.vfs.tryread("bookmarks"))
1148 self.svfs.write("journal.phaseroots",
1134 self.svfs.write("journal.phaseroots",
1149 self.svfs.tryread("phaseroots"))
1135 self.svfs.tryread("phaseroots"))
1150
1136
1151 def recover(self):
1137 def recover(self):
1152 with self.lock():
1138 with self.lock():
1153 if self.svfs.exists("journal"):
1139 if self.svfs.exists("journal"):
1154 self.ui.status(_("rolling back interrupted transaction\n"))
1140 self.ui.status(_("rolling back interrupted transaction\n"))
1155 vfsmap = {'': self.svfs,
1141 vfsmap = {'': self.svfs,
1156 'plain': self.vfs,}
1142 'plain': self.vfs,}
1157 transaction.rollback(self.svfs, vfsmap, "journal",
1143 transaction.rollback(self.svfs, vfsmap, "journal",
1158 self.ui.warn)
1144 self.ui.warn)
1159 self.invalidate()
1145 self.invalidate()
1160 return True
1146 return True
1161 else:
1147 else:
1162 self.ui.warn(_("no interrupted transaction available\n"))
1148 self.ui.warn(_("no interrupted transaction available\n"))
1163 return False
1149 return False
1164
1150
1165 def rollback(self, dryrun=False, force=False):
1151 def rollback(self, dryrun=False, force=False):
1166 wlock = lock = dsguard = None
1152 wlock = lock = dsguard = None
1167 try:
1153 try:
1168 wlock = self.wlock()
1154 wlock = self.wlock()
1169 lock = self.lock()
1155 lock = self.lock()
1170 if self.svfs.exists("undo"):
1156 if self.svfs.exists("undo"):
1171 dsguard = dirstateguard.dirstateguard(self, 'rollback')
1157 dsguard = dirstateguard.dirstateguard(self, 'rollback')
1172
1158
1173 return self._rollback(dryrun, force, dsguard)
1159 return self._rollback(dryrun, force, dsguard)
1174 else:
1160 else:
1175 self.ui.warn(_("no rollback information available\n"))
1161 self.ui.warn(_("no rollback information available\n"))
1176 return 1
1162 return 1
1177 finally:
1163 finally:
1178 release(dsguard, lock, wlock)
1164 release(dsguard, lock, wlock)
1179
1165
1180 @unfilteredmethod # Until we get smarter cache management
1166 @unfilteredmethod # Until we get smarter cache management
1181 def _rollback(self, dryrun, force, dsguard):
1167 def _rollback(self, dryrun, force, dsguard):
1182 ui = self.ui
1168 ui = self.ui
1183 try:
1169 try:
1184 args = self.vfs.read('undo.desc').splitlines()
1170 args = self.vfs.read('undo.desc').splitlines()
1185 (oldlen, desc, detail) = (int(args[0]), args[1], None)
1171 (oldlen, desc, detail) = (int(args[0]), args[1], None)
1186 if len(args) >= 3:
1172 if len(args) >= 3:
1187 detail = args[2]
1173 detail = args[2]
1188 oldtip = oldlen - 1
1174 oldtip = oldlen - 1
1189
1175
1190 if detail and ui.verbose:
1176 if detail and ui.verbose:
1191 msg = (_('repository tip rolled back to revision %s'
1177 msg = (_('repository tip rolled back to revision %s'
1192 ' (undo %s: %s)\n')
1178 ' (undo %s: %s)\n')
1193 % (oldtip, desc, detail))
1179 % (oldtip, desc, detail))
1194 else:
1180 else:
1195 msg = (_('repository tip rolled back to revision %s'
1181 msg = (_('repository tip rolled back to revision %s'
1196 ' (undo %s)\n')
1182 ' (undo %s)\n')
1197 % (oldtip, desc))
1183 % (oldtip, desc))
1198 except IOError:
1184 except IOError:
1199 msg = _('rolling back unknown transaction\n')
1185 msg = _('rolling back unknown transaction\n')
1200 desc = None
1186 desc = None
1201
1187
1202 if not force and self['.'] != self['tip'] and desc == 'commit':
1188 if not force and self['.'] != self['tip'] and desc == 'commit':
1203 raise error.Abort(
1189 raise error.Abort(
1204 _('rollback of last commit while not checked out '
1190 _('rollback of last commit while not checked out '
1205 'may lose data'), hint=_('use -f to force'))
1191 'may lose data'), hint=_('use -f to force'))
1206
1192
1207 ui.status(msg)
1193 ui.status(msg)
1208 if dryrun:
1194 if dryrun:
1209 return 0
1195 return 0
1210
1196
1211 parents = self.dirstate.parents()
1197 parents = self.dirstate.parents()
1212 self.destroying()
1198 self.destroying()
1213 vfsmap = {'plain': self.vfs, '': self.svfs}
1199 vfsmap = {'plain': self.vfs, '': self.svfs}
1214 transaction.rollback(self.svfs, vfsmap, 'undo', ui.warn)
1200 transaction.rollback(self.svfs, vfsmap, 'undo', ui.warn)
1215 if self.vfs.exists('undo.bookmarks'):
1201 if self.vfs.exists('undo.bookmarks'):
1216 self.vfs.rename('undo.bookmarks', 'bookmarks', checkambig=True)
1202 self.vfs.rename('undo.bookmarks', 'bookmarks', checkambig=True)
1217 if self.svfs.exists('undo.phaseroots'):
1203 if self.svfs.exists('undo.phaseroots'):
1218 self.svfs.rename('undo.phaseroots', 'phaseroots', checkambig=True)
1204 self.svfs.rename('undo.phaseroots', 'phaseroots', checkambig=True)
1219 self.invalidate()
1205 self.invalidate()
1220
1206
1221 parentgone = (parents[0] not in self.changelog.nodemap or
1207 parentgone = (parents[0] not in self.changelog.nodemap or
1222 parents[1] not in self.changelog.nodemap)
1208 parents[1] not in self.changelog.nodemap)
1223 if parentgone:
1209 if parentgone:
1224 # prevent dirstateguard from overwriting already restored one
1210 # prevent dirstateguard from overwriting already restored one
1225 dsguard.close()
1211 dsguard.close()
1226
1212
1227 self.dirstate.restorebackup(None, prefix='undo.')
1213 self.dirstate.restorebackup(None, prefix='undo.')
1228 try:
1214 try:
1229 branch = self.vfs.read('undo.branch')
1215 branch = self.vfs.read('undo.branch')
1230 self.dirstate.setbranch(encoding.tolocal(branch))
1216 self.dirstate.setbranch(encoding.tolocal(branch))
1231 except IOError:
1217 except IOError:
1232 ui.warn(_('named branch could not be reset: '
1218 ui.warn(_('named branch could not be reset: '
1233 'current branch is still \'%s\'\n')
1219 'current branch is still \'%s\'\n')
1234 % self.dirstate.branch())
1220 % self.dirstate.branch())
1235
1221
1236 parents = tuple([p.rev() for p in self[None].parents()])
1222 parents = tuple([p.rev() for p in self[None].parents()])
1237 if len(parents) > 1:
1223 if len(parents) > 1:
1238 ui.status(_('working directory now based on '
1224 ui.status(_('working directory now based on '
1239 'revisions %d and %d\n') % parents)
1225 'revisions %d and %d\n') % parents)
1240 else:
1226 else:
1241 ui.status(_('working directory now based on '
1227 ui.status(_('working directory now based on '
1242 'revision %d\n') % parents)
1228 'revision %d\n') % parents)
1243 mergemod.mergestate.clean(self, self['.'].node())
1229 mergemod.mergestate.clean(self, self['.'].node())
1244
1230
1245 # TODO: if we know which new heads may result from this rollback, pass
1231 # TODO: if we know which new heads may result from this rollback, pass
1246 # them to destroy(), which will prevent the branchhead cache from being
1232 # them to destroy(), which will prevent the branchhead cache from being
1247 # invalidated.
1233 # invalidated.
1248 self.destroyed()
1234 self.destroyed()
1249 return 0
1235 return 0
1250
1236
1251 def _buildcacheupdater(self, newtransaction):
1237 def _buildcacheupdater(self, newtransaction):
1252 """called during transaction to build the callback updating cache
1238 """called during transaction to build the callback updating cache
1253
1239
1254 Lives on the repository to help extension who might want to augment
1240 Lives on the repository to help extension who might want to augment
1255 this logic. For this purpose, the created transaction is passed to the
1241 this logic. For this purpose, the created transaction is passed to the
1256 method.
1242 method.
1257 """
1243 """
1258 # we must avoid cyclic reference between repo and transaction.
1244 # we must avoid cyclic reference between repo and transaction.
1259 reporef = weakref.ref(self)
1245 reporef = weakref.ref(self)
1260 def updater(tr):
1246 def updater(tr):
1261 repo = reporef()
1247 repo = reporef()
1262 repo.updatecaches(tr)
1248 repo.updatecaches(tr)
1263 return updater
1249 return updater
1264
1250
1265 @unfilteredmethod
1251 @unfilteredmethod
1266 def updatecaches(self, tr=None):
1252 def updatecaches(self, tr=None):
1267 """warm appropriate caches
1253 """warm appropriate caches
1268
1254
1269 If this function is called after a transaction closed. The transaction
1255 If this function is called after a transaction closed. The transaction
1270 will be available in the 'tr' argument. This can be used to selectively
1256 will be available in the 'tr' argument. This can be used to selectively
1271 update caches relevant to the changes in that transaction.
1257 update caches relevant to the changes in that transaction.
1272 """
1258 """
1273 if tr is not None and tr.hookargs.get('source') == 'strip':
1259 if tr is not None and tr.hookargs.get('source') == 'strip':
1274 # During strip, many caches are invalid but
1260 # During strip, many caches are invalid but
1275 # later call to `destroyed` will refresh them.
1261 # later call to `destroyed` will refresh them.
1276 return
1262 return
1277
1263
1278 if tr is None or tr.changes['revs']:
1264 if tr is None or tr.changes['revs']:
1279 # updating the unfiltered branchmap should refresh all the others,
1265 # updating the unfiltered branchmap should refresh all the others,
1280 self.ui.debug('updating the branch cache\n')
1266 self.ui.debug('updating the branch cache\n')
1281 branchmap.updatecache(self.filtered('served'))
1267 branchmap.updatecache(self.filtered('served'))
1282
1268
1283 def invalidatecaches(self):
1269 def invalidatecaches(self):
1284
1270
1285 if '_tagscache' in vars(self):
1271 if '_tagscache' in vars(self):
1286 # can't use delattr on proxy
1272 # can't use delattr on proxy
1287 del self.__dict__['_tagscache']
1273 del self.__dict__['_tagscache']
1288
1274
1289 self.unfiltered()._branchcaches.clear()
1275 self.unfiltered()._branchcaches.clear()
1290 self.invalidatevolatilesets()
1276 self.invalidatevolatilesets()
1291
1277
1292 def invalidatevolatilesets(self):
1278 def invalidatevolatilesets(self):
1293 self.filteredrevcache.clear()
1279 self.filteredrevcache.clear()
1294 obsolete.clearobscaches(self)
1280 obsolete.clearobscaches(self)
1295
1281
1296 def invalidatedirstate(self):
1282 def invalidatedirstate(self):
1297 '''Invalidates the dirstate, causing the next call to dirstate
1283 '''Invalidates the dirstate, causing the next call to dirstate
1298 to check if it was modified since the last time it was read,
1284 to check if it was modified since the last time it was read,
1299 rereading it if it has.
1285 rereading it if it has.
1300
1286
1301 This is different to dirstate.invalidate() that it doesn't always
1287 This is different to dirstate.invalidate() that it doesn't always
1302 rereads the dirstate. Use dirstate.invalidate() if you want to
1288 rereads the dirstate. Use dirstate.invalidate() if you want to
1303 explicitly read the dirstate again (i.e. restoring it to a previous
1289 explicitly read the dirstate again (i.e. restoring it to a previous
1304 known good state).'''
1290 known good state).'''
1305 if hasunfilteredcache(self, 'dirstate'):
1291 if hasunfilteredcache(self, 'dirstate'):
1306 for k in self.dirstate._filecache:
1292 for k in self.dirstate._filecache:
1307 try:
1293 try:
1308 delattr(self.dirstate, k)
1294 delattr(self.dirstate, k)
1309 except AttributeError:
1295 except AttributeError:
1310 pass
1296 pass
1311 delattr(self.unfiltered(), 'dirstate')
1297 delattr(self.unfiltered(), 'dirstate')
1312
1298
1313 def invalidate(self, clearfilecache=False):
1299 def invalidate(self, clearfilecache=False):
1314 '''Invalidates both store and non-store parts other than dirstate
1300 '''Invalidates both store and non-store parts other than dirstate
1315
1301
1316 If a transaction is running, invalidation of store is omitted,
1302 If a transaction is running, invalidation of store is omitted,
1317 because discarding in-memory changes might cause inconsistency
1303 because discarding in-memory changes might cause inconsistency
1318 (e.g. incomplete fncache causes unintentional failure, but
1304 (e.g. incomplete fncache causes unintentional failure, but
1319 redundant one doesn't).
1305 redundant one doesn't).
1320 '''
1306 '''
1321 unfiltered = self.unfiltered() # all file caches are stored unfiltered
1307 unfiltered = self.unfiltered() # all file caches are stored unfiltered
1322 for k in list(self._filecache.keys()):
1308 for k in list(self._filecache.keys()):
1323 # dirstate is invalidated separately in invalidatedirstate()
1309 # dirstate is invalidated separately in invalidatedirstate()
1324 if k == 'dirstate':
1310 if k == 'dirstate':
1325 continue
1311 continue
1326
1312
1327 if clearfilecache:
1313 if clearfilecache:
1328 del self._filecache[k]
1314 del self._filecache[k]
1329 try:
1315 try:
1330 delattr(unfiltered, k)
1316 delattr(unfiltered, k)
1331 except AttributeError:
1317 except AttributeError:
1332 pass
1318 pass
1333 self.invalidatecaches()
1319 self.invalidatecaches()
1334 if not self.currenttransaction():
1320 if not self.currenttransaction():
1335 # TODO: Changing contents of store outside transaction
1321 # TODO: Changing contents of store outside transaction
1336 # causes inconsistency. We should make in-memory store
1322 # causes inconsistency. We should make in-memory store
1337 # changes detectable, and abort if changed.
1323 # changes detectable, and abort if changed.
1338 self.store.invalidatecaches()
1324 self.store.invalidatecaches()
1339
1325
1340 def invalidateall(self):
1326 def invalidateall(self):
1341 '''Fully invalidates both store and non-store parts, causing the
1327 '''Fully invalidates both store and non-store parts, causing the
1342 subsequent operation to reread any outside changes.'''
1328 subsequent operation to reread any outside changes.'''
1343 # extension should hook this to invalidate its caches
1329 # extension should hook this to invalidate its caches
1344 self.invalidate()
1330 self.invalidate()
1345 self.invalidatedirstate()
1331 self.invalidatedirstate()
1346
1332
1347 @unfilteredmethod
1333 @unfilteredmethod
1348 def _refreshfilecachestats(self, tr):
1334 def _refreshfilecachestats(self, tr):
1349 """Reload stats of cached files so that they are flagged as valid"""
1335 """Reload stats of cached files so that they are flagged as valid"""
1350 for k, ce in self._filecache.items():
1336 for k, ce in self._filecache.items():
1351 if k == 'dirstate' or k not in self.__dict__:
1337 if k == 'dirstate' or k not in self.__dict__:
1352 continue
1338 continue
1353 ce.refresh()
1339 ce.refresh()
1354
1340
1355 def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc,
1341 def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc,
1356 inheritchecker=None, parentenvvar=None):
1342 inheritchecker=None, parentenvvar=None):
1357 parentlock = None
1343 parentlock = None
1358 # the contents of parentenvvar are used by the underlying lock to
1344 # the contents of parentenvvar are used by the underlying lock to
1359 # determine whether it can be inherited
1345 # determine whether it can be inherited
1360 if parentenvvar is not None:
1346 if parentenvvar is not None:
1361 parentlock = encoding.environ.get(parentenvvar)
1347 parentlock = encoding.environ.get(parentenvvar)
1362 try:
1348 try:
1363 l = lockmod.lock(vfs, lockname, 0, releasefn=releasefn,
1349 l = lockmod.lock(vfs, lockname, 0, releasefn=releasefn,
1364 acquirefn=acquirefn, desc=desc,
1350 acquirefn=acquirefn, desc=desc,
1365 inheritchecker=inheritchecker,
1351 inheritchecker=inheritchecker,
1366 parentlock=parentlock)
1352 parentlock=parentlock)
1367 except error.LockHeld as inst:
1353 except error.LockHeld as inst:
1368 if not wait:
1354 if not wait:
1369 raise
1355 raise
1370 # show more details for new-style locks
1356 # show more details for new-style locks
1371 if ':' in inst.locker:
1357 if ':' in inst.locker:
1372 host, pid = inst.locker.split(":", 1)
1358 host, pid = inst.locker.split(":", 1)
1373 self.ui.warn(
1359 self.ui.warn(
1374 _("waiting for lock on %s held by process %r "
1360 _("waiting for lock on %s held by process %r "
1375 "on host %r\n") % (desc, pid, host))
1361 "on host %r\n") % (desc, pid, host))
1376 else:
1362 else:
1377 self.ui.warn(_("waiting for lock on %s held by %r\n") %
1363 self.ui.warn(_("waiting for lock on %s held by %r\n") %
1378 (desc, inst.locker))
1364 (desc, inst.locker))
1379 # default to 600 seconds timeout
1365 # default to 600 seconds timeout
1380 l = lockmod.lock(vfs, lockname,
1366 l = lockmod.lock(vfs, lockname,
1381 int(self.ui.config("ui", "timeout", "600")),
1367 int(self.ui.config("ui", "timeout", "600")),
1382 releasefn=releasefn, acquirefn=acquirefn,
1368 releasefn=releasefn, acquirefn=acquirefn,
1383 desc=desc)
1369 desc=desc)
1384 self.ui.warn(_("got lock after %s seconds\n") % l.delay)
1370 self.ui.warn(_("got lock after %s seconds\n") % l.delay)
1385 return l
1371 return l
1386
1372
1387 def _afterlock(self, callback):
1373 def _afterlock(self, callback):
1388 """add a callback to be run when the repository is fully unlocked
1374 """add a callback to be run when the repository is fully unlocked
1389
1375
1390 The callback will be executed when the outermost lock is released
1376 The callback will be executed when the outermost lock is released
1391 (with wlock being higher level than 'lock')."""
1377 (with wlock being higher level than 'lock')."""
1392 for ref in (self._wlockref, self._lockref):
1378 for ref in (self._wlockref, self._lockref):
1393 l = ref and ref()
1379 l = ref and ref()
1394 if l and l.held:
1380 if l and l.held:
1395 l.postrelease.append(callback)
1381 l.postrelease.append(callback)
1396 break
1382 break
1397 else: # no lock have been found.
1383 else: # no lock have been found.
1398 callback()
1384 callback()
1399
1385
1400 def lock(self, wait=True):
1386 def lock(self, wait=True):
1401 '''Lock the repository store (.hg/store) and return a weak reference
1387 '''Lock the repository store (.hg/store) and return a weak reference
1402 to the lock. Use this before modifying the store (e.g. committing or
1388 to the lock. Use this before modifying the store (e.g. committing or
1403 stripping). If you are opening a transaction, get a lock as well.)
1389 stripping). If you are opening a transaction, get a lock as well.)
1404
1390
1405 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1391 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1406 'wlock' first to avoid a dead-lock hazard.'''
1392 'wlock' first to avoid a dead-lock hazard.'''
1407 l = self._currentlock(self._lockref)
1393 l = self._currentlock(self._lockref)
1408 if l is not None:
1394 if l is not None:
1409 l.lock()
1395 l.lock()
1410 return l
1396 return l
1411
1397
1412 l = self._lock(self.svfs, "lock", wait, None,
1398 l = self._lock(self.svfs, "lock", wait, None,
1413 self.invalidate, _('repository %s') % self.origroot)
1399 self.invalidate, _('repository %s') % self.origroot)
1414 self._lockref = weakref.ref(l)
1400 self._lockref = weakref.ref(l)
1415 return l
1401 return l
1416
1402
1417 def _wlockchecktransaction(self):
1403 def _wlockchecktransaction(self):
1418 if self.currenttransaction() is not None:
1404 if self.currenttransaction() is not None:
1419 raise error.LockInheritanceContractViolation(
1405 raise error.LockInheritanceContractViolation(
1420 'wlock cannot be inherited in the middle of a transaction')
1406 'wlock cannot be inherited in the middle of a transaction')
1421
1407
1422 def wlock(self, wait=True):
1408 def wlock(self, wait=True):
1423 '''Lock the non-store parts of the repository (everything under
1409 '''Lock the non-store parts of the repository (everything under
1424 .hg except .hg/store) and return a weak reference to the lock.
1410 .hg except .hg/store) and return a weak reference to the lock.
1425
1411
1426 Use this before modifying files in .hg.
1412 Use this before modifying files in .hg.
1427
1413
1428 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1414 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1429 'wlock' first to avoid a dead-lock hazard.'''
1415 'wlock' first to avoid a dead-lock hazard.'''
1430 l = self._wlockref and self._wlockref()
1416 l = self._wlockref and self._wlockref()
1431 if l is not None and l.held:
1417 if l is not None and l.held:
1432 l.lock()
1418 l.lock()
1433 return l
1419 return l
1434
1420
1435 # We do not need to check for non-waiting lock acquisition. Such
1421 # We do not need to check for non-waiting lock acquisition. Such
1436 # acquisition would not cause dead-lock as they would just fail.
1422 # acquisition would not cause dead-lock as they would just fail.
1437 if wait and (self.ui.configbool('devel', 'all-warnings')
1423 if wait and (self.ui.configbool('devel', 'all-warnings')
1438 or self.ui.configbool('devel', 'check-locks')):
1424 or self.ui.configbool('devel', 'check-locks')):
1439 if self._currentlock(self._lockref) is not None:
1425 if self._currentlock(self._lockref) is not None:
1440 self.ui.develwarn('"wlock" acquired after "lock"')
1426 self.ui.develwarn('"wlock" acquired after "lock"')
1441
1427
1442 def unlock():
1428 def unlock():
1443 if self.dirstate.pendingparentchange():
1429 if self.dirstate.pendingparentchange():
1444 self.dirstate.invalidate()
1430 self.dirstate.invalidate()
1445 else:
1431 else:
1446 self.dirstate.write(None)
1432 self.dirstate.write(None)
1447
1433
1448 self._filecache['dirstate'].refresh()
1434 self._filecache['dirstate'].refresh()
1449
1435
1450 l = self._lock(self.vfs, "wlock", wait, unlock,
1436 l = self._lock(self.vfs, "wlock", wait, unlock,
1451 self.invalidatedirstate, _('working directory of %s') %
1437 self.invalidatedirstate, _('working directory of %s') %
1452 self.origroot,
1438 self.origroot,
1453 inheritchecker=self._wlockchecktransaction,
1439 inheritchecker=self._wlockchecktransaction,
1454 parentenvvar='HG_WLOCK_LOCKER')
1440 parentenvvar='HG_WLOCK_LOCKER')
1455 self._wlockref = weakref.ref(l)
1441 self._wlockref = weakref.ref(l)
1456 return l
1442 return l
1457
1443
1458 def _currentlock(self, lockref):
1444 def _currentlock(self, lockref):
1459 """Returns the lock if it's held, or None if it's not."""
1445 """Returns the lock if it's held, or None if it's not."""
1460 if lockref is None:
1446 if lockref is None:
1461 return None
1447 return None
1462 l = lockref()
1448 l = lockref()
1463 if l is None or not l.held:
1449 if l is None or not l.held:
1464 return None
1450 return None
1465 return l
1451 return l
1466
1452
1467 def currentwlock(self):
1453 def currentwlock(self):
1468 """Returns the wlock if it's held, or None if it's not."""
1454 """Returns the wlock if it's held, or None if it's not."""
1469 return self._currentlock(self._wlockref)
1455 return self._currentlock(self._wlockref)
1470
1456
1471 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
1457 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
1472 """
1458 """
1473 commit an individual file as part of a larger transaction
1459 commit an individual file as part of a larger transaction
1474 """
1460 """
1475
1461
1476 fname = fctx.path()
1462 fname = fctx.path()
1477 fparent1 = manifest1.get(fname, nullid)
1463 fparent1 = manifest1.get(fname, nullid)
1478 fparent2 = manifest2.get(fname, nullid)
1464 fparent2 = manifest2.get(fname, nullid)
1479 if isinstance(fctx, context.filectx):
1465 if isinstance(fctx, context.filectx):
1480 node = fctx.filenode()
1466 node = fctx.filenode()
1481 if node in [fparent1, fparent2]:
1467 if node in [fparent1, fparent2]:
1482 self.ui.debug('reusing %s filelog entry\n' % fname)
1468 self.ui.debug('reusing %s filelog entry\n' % fname)
1483 if manifest1.flags(fname) != fctx.flags():
1469 if manifest1.flags(fname) != fctx.flags():
1484 changelist.append(fname)
1470 changelist.append(fname)
1485 return node
1471 return node
1486
1472
1487 flog = self.file(fname)
1473 flog = self.file(fname)
1488 meta = {}
1474 meta = {}
1489 copy = fctx.renamed()
1475 copy = fctx.renamed()
1490 if copy and copy[0] != fname:
1476 if copy and copy[0] != fname:
1491 # Mark the new revision of this file as a copy of another
1477 # Mark the new revision of this file as a copy of another
1492 # file. This copy data will effectively act as a parent
1478 # file. This copy data will effectively act as a parent
1493 # of this new revision. If this is a merge, the first
1479 # of this new revision. If this is a merge, the first
1494 # parent will be the nullid (meaning "look up the copy data")
1480 # parent will be the nullid (meaning "look up the copy data")
1495 # and the second one will be the other parent. For example:
1481 # and the second one will be the other parent. For example:
1496 #
1482 #
1497 # 0 --- 1 --- 3 rev1 changes file foo
1483 # 0 --- 1 --- 3 rev1 changes file foo
1498 # \ / rev2 renames foo to bar and changes it
1484 # \ / rev2 renames foo to bar and changes it
1499 # \- 2 -/ rev3 should have bar with all changes and
1485 # \- 2 -/ rev3 should have bar with all changes and
1500 # should record that bar descends from
1486 # should record that bar descends from
1501 # bar in rev2 and foo in rev1
1487 # bar in rev2 and foo in rev1
1502 #
1488 #
1503 # this allows this merge to succeed:
1489 # this allows this merge to succeed:
1504 #
1490 #
1505 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
1491 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
1506 # \ / merging rev3 and rev4 should use bar@rev2
1492 # \ / merging rev3 and rev4 should use bar@rev2
1507 # \- 2 --- 4 as the merge base
1493 # \- 2 --- 4 as the merge base
1508 #
1494 #
1509
1495
1510 cfname = copy[0]
1496 cfname = copy[0]
1511 crev = manifest1.get(cfname)
1497 crev = manifest1.get(cfname)
1512 newfparent = fparent2
1498 newfparent = fparent2
1513
1499
1514 if manifest2: # branch merge
1500 if manifest2: # branch merge
1515 if fparent2 == nullid or crev is None: # copied on remote side
1501 if fparent2 == nullid or crev is None: # copied on remote side
1516 if cfname in manifest2:
1502 if cfname in manifest2:
1517 crev = manifest2[cfname]
1503 crev = manifest2[cfname]
1518 newfparent = fparent1
1504 newfparent = fparent1
1519
1505
1520 # Here, we used to search backwards through history to try to find
1506 # Here, we used to search backwards through history to try to find
1521 # where the file copy came from if the source of a copy was not in
1507 # where the file copy came from if the source of a copy was not in
1522 # the parent directory. However, this doesn't actually make sense to
1508 # the parent directory. However, this doesn't actually make sense to
1523 # do (what does a copy from something not in your working copy even
1509 # do (what does a copy from something not in your working copy even
1524 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
1510 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
1525 # the user that copy information was dropped, so if they didn't
1511 # the user that copy information was dropped, so if they didn't
1526 # expect this outcome it can be fixed, but this is the correct
1512 # expect this outcome it can be fixed, but this is the correct
1527 # behavior in this circumstance.
1513 # behavior in this circumstance.
1528
1514
1529 if crev:
1515 if crev:
1530 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
1516 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
1531 meta["copy"] = cfname
1517 meta["copy"] = cfname
1532 meta["copyrev"] = hex(crev)
1518 meta["copyrev"] = hex(crev)
1533 fparent1, fparent2 = nullid, newfparent
1519 fparent1, fparent2 = nullid, newfparent
1534 else:
1520 else:
1535 self.ui.warn(_("warning: can't find ancestor for '%s' "
1521 self.ui.warn(_("warning: can't find ancestor for '%s' "
1536 "copied from '%s'!\n") % (fname, cfname))
1522 "copied from '%s'!\n") % (fname, cfname))
1537
1523
1538 elif fparent1 == nullid:
1524 elif fparent1 == nullid:
1539 fparent1, fparent2 = fparent2, nullid
1525 fparent1, fparent2 = fparent2, nullid
1540 elif fparent2 != nullid:
1526 elif fparent2 != nullid:
1541 # is one parent an ancestor of the other?
1527 # is one parent an ancestor of the other?
1542 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
1528 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
1543 if fparent1 in fparentancestors:
1529 if fparent1 in fparentancestors:
1544 fparent1, fparent2 = fparent2, nullid
1530 fparent1, fparent2 = fparent2, nullid
1545 elif fparent2 in fparentancestors:
1531 elif fparent2 in fparentancestors:
1546 fparent2 = nullid
1532 fparent2 = nullid
1547
1533
1548 # is the file changed?
1534 # is the file changed?
1549 text = fctx.data()
1535 text = fctx.data()
1550 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
1536 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
1551 changelist.append(fname)
1537 changelist.append(fname)
1552 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
1538 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
1553 # are just the flags changed during merge?
1539 # are just the flags changed during merge?
1554 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
1540 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
1555 changelist.append(fname)
1541 changelist.append(fname)
1556
1542
1557 return fparent1
1543 return fparent1
1558
1544
1559 def checkcommitpatterns(self, wctx, vdirs, match, status, fail):
1545 def checkcommitpatterns(self, wctx, vdirs, match, status, fail):
1560 """check for commit arguments that aren't committable"""
1546 """check for commit arguments that aren't committable"""
1561 if match.isexact() or match.prefix():
1547 if match.isexact() or match.prefix():
1562 matched = set(status.modified + status.added + status.removed)
1548 matched = set(status.modified + status.added + status.removed)
1563
1549
1564 for f in match.files():
1550 for f in match.files():
1565 f = self.dirstate.normalize(f)
1551 f = self.dirstate.normalize(f)
1566 if f == '.' or f in matched or f in wctx.substate:
1552 if f == '.' or f in matched or f in wctx.substate:
1567 continue
1553 continue
1568 if f in status.deleted:
1554 if f in status.deleted:
1569 fail(f, _('file not found!'))
1555 fail(f, _('file not found!'))
1570 if f in vdirs: # visited directory
1556 if f in vdirs: # visited directory
1571 d = f + '/'
1557 d = f + '/'
1572 for mf in matched:
1558 for mf in matched:
1573 if mf.startswith(d):
1559 if mf.startswith(d):
1574 break
1560 break
1575 else:
1561 else:
1576 fail(f, _("no match under directory!"))
1562 fail(f, _("no match under directory!"))
1577 elif f not in self.dirstate:
1563 elif f not in self.dirstate:
1578 fail(f, _("file not tracked!"))
1564 fail(f, _("file not tracked!"))
1579
1565
1580 @unfilteredmethod
1566 @unfilteredmethod
1581 def commit(self, text="", user=None, date=None, match=None, force=False,
1567 def commit(self, text="", user=None, date=None, match=None, force=False,
1582 editor=False, extra=None):
1568 editor=False, extra=None):
1583 """Add a new revision to current repository.
1569 """Add a new revision to current repository.
1584
1570
1585 Revision information is gathered from the working directory,
1571 Revision information is gathered from the working directory,
1586 match can be used to filter the committed files. If editor is
1572 match can be used to filter the committed files. If editor is
1587 supplied, it is called to get a commit message.
1573 supplied, it is called to get a commit message.
1588 """
1574 """
1589 if extra is None:
1575 if extra is None:
1590 extra = {}
1576 extra = {}
1591
1577
1592 def fail(f, msg):
1578 def fail(f, msg):
1593 raise error.Abort('%s: %s' % (f, msg))
1579 raise error.Abort('%s: %s' % (f, msg))
1594
1580
1595 if not match:
1581 if not match:
1596 match = matchmod.always(self.root, '')
1582 match = matchmod.always(self.root, '')
1597
1583
1598 if not force:
1584 if not force:
1599 vdirs = []
1585 vdirs = []
1600 match.explicitdir = vdirs.append
1586 match.explicitdir = vdirs.append
1601 match.bad = fail
1587 match.bad = fail
1602
1588
1603 wlock = lock = tr = None
1589 wlock = lock = tr = None
1604 try:
1590 try:
1605 wlock = self.wlock()
1591 wlock = self.wlock()
1606 lock = self.lock() # for recent changelog (see issue4368)
1592 lock = self.lock() # for recent changelog (see issue4368)
1607
1593
1608 wctx = self[None]
1594 wctx = self[None]
1609 merge = len(wctx.parents()) > 1
1595 merge = len(wctx.parents()) > 1
1610
1596
1611 if not force and merge and not match.always():
1597 if not force and merge and not match.always():
1612 raise error.Abort(_('cannot partially commit a merge '
1598 raise error.Abort(_('cannot partially commit a merge '
1613 '(do not specify files or patterns)'))
1599 '(do not specify files or patterns)'))
1614
1600
1615 status = self.status(match=match, clean=force)
1601 status = self.status(match=match, clean=force)
1616 if force:
1602 if force:
1617 status.modified.extend(status.clean) # mq may commit clean files
1603 status.modified.extend(status.clean) # mq may commit clean files
1618
1604
1619 # check subrepos
1605 # check subrepos
1620 subs = []
1606 subs = []
1621 commitsubs = set()
1607 commitsubs = set()
1622 newstate = wctx.substate.copy()
1608 newstate = wctx.substate.copy()
1623 # only manage subrepos and .hgsubstate if .hgsub is present
1609 # only manage subrepos and .hgsubstate if .hgsub is present
1624 if '.hgsub' in wctx:
1610 if '.hgsub' in wctx:
1625 # we'll decide whether to track this ourselves, thanks
1611 # we'll decide whether to track this ourselves, thanks
1626 for c in status.modified, status.added, status.removed:
1612 for c in status.modified, status.added, status.removed:
1627 if '.hgsubstate' in c:
1613 if '.hgsubstate' in c:
1628 c.remove('.hgsubstate')
1614 c.remove('.hgsubstate')
1629
1615
1630 # compare current state to last committed state
1616 # compare current state to last committed state
1631 # build new substate based on last committed state
1617 # build new substate based on last committed state
1632 oldstate = wctx.p1().substate
1618 oldstate = wctx.p1().substate
1633 for s in sorted(newstate.keys()):
1619 for s in sorted(newstate.keys()):
1634 if not match(s):
1620 if not match(s):
1635 # ignore working copy, use old state if present
1621 # ignore working copy, use old state if present
1636 if s in oldstate:
1622 if s in oldstate:
1637 newstate[s] = oldstate[s]
1623 newstate[s] = oldstate[s]
1638 continue
1624 continue
1639 if not force:
1625 if not force:
1640 raise error.Abort(
1626 raise error.Abort(
1641 _("commit with new subrepo %s excluded") % s)
1627 _("commit with new subrepo %s excluded") % s)
1642 dirtyreason = wctx.sub(s).dirtyreason(True)
1628 dirtyreason = wctx.sub(s).dirtyreason(True)
1643 if dirtyreason:
1629 if dirtyreason:
1644 if not self.ui.configbool('ui', 'commitsubrepos'):
1630 if not self.ui.configbool('ui', 'commitsubrepos'):
1645 raise error.Abort(dirtyreason,
1631 raise error.Abort(dirtyreason,
1646 hint=_("use --subrepos for recursive commit"))
1632 hint=_("use --subrepos for recursive commit"))
1647 subs.append(s)
1633 subs.append(s)
1648 commitsubs.add(s)
1634 commitsubs.add(s)
1649 else:
1635 else:
1650 bs = wctx.sub(s).basestate()
1636 bs = wctx.sub(s).basestate()
1651 newstate[s] = (newstate[s][0], bs, newstate[s][2])
1637 newstate[s] = (newstate[s][0], bs, newstate[s][2])
1652 if oldstate.get(s, (None, None, None))[1] != bs:
1638 if oldstate.get(s, (None, None, None))[1] != bs:
1653 subs.append(s)
1639 subs.append(s)
1654
1640
1655 # check for removed subrepos
1641 # check for removed subrepos
1656 for p in wctx.parents():
1642 for p in wctx.parents():
1657 r = [s for s in p.substate if s not in newstate]
1643 r = [s for s in p.substate if s not in newstate]
1658 subs += [s for s in r if match(s)]
1644 subs += [s for s in r if match(s)]
1659 if subs:
1645 if subs:
1660 if (not match('.hgsub') and
1646 if (not match('.hgsub') and
1661 '.hgsub' in (wctx.modified() + wctx.added())):
1647 '.hgsub' in (wctx.modified() + wctx.added())):
1662 raise error.Abort(
1648 raise error.Abort(
1663 _("can't commit subrepos without .hgsub"))
1649 _("can't commit subrepos without .hgsub"))
1664 status.modified.insert(0, '.hgsubstate')
1650 status.modified.insert(0, '.hgsubstate')
1665
1651
1666 elif '.hgsub' in status.removed:
1652 elif '.hgsub' in status.removed:
1667 # clean up .hgsubstate when .hgsub is removed
1653 # clean up .hgsubstate when .hgsub is removed
1668 if ('.hgsubstate' in wctx and
1654 if ('.hgsubstate' in wctx and
1669 '.hgsubstate' not in (status.modified + status.added +
1655 '.hgsubstate' not in (status.modified + status.added +
1670 status.removed)):
1656 status.removed)):
1671 status.removed.insert(0, '.hgsubstate')
1657 status.removed.insert(0, '.hgsubstate')
1672
1658
1673 # make sure all explicit patterns are matched
1659 # make sure all explicit patterns are matched
1674 if not force:
1660 if not force:
1675 self.checkcommitpatterns(wctx, vdirs, match, status, fail)
1661 self.checkcommitpatterns(wctx, vdirs, match, status, fail)
1676
1662
1677 cctx = context.workingcommitctx(self, status,
1663 cctx = context.workingcommitctx(self, status,
1678 text, user, date, extra)
1664 text, user, date, extra)
1679
1665
1680 # internal config: ui.allowemptycommit
1666 # internal config: ui.allowemptycommit
1681 allowemptycommit = (wctx.branch() != wctx.p1().branch()
1667 allowemptycommit = (wctx.branch() != wctx.p1().branch()
1682 or extra.get('close') or merge or cctx.files()
1668 or extra.get('close') or merge or cctx.files()
1683 or self.ui.configbool('ui', 'allowemptycommit'))
1669 or self.ui.configbool('ui', 'allowemptycommit'))
1684 if not allowemptycommit:
1670 if not allowemptycommit:
1685 return None
1671 return None
1686
1672
1687 if merge and cctx.deleted():
1673 if merge and cctx.deleted():
1688 raise error.Abort(_("cannot commit merge with missing files"))
1674 raise error.Abort(_("cannot commit merge with missing files"))
1689
1675
1690 ms = mergemod.mergestate.read(self)
1676 ms = mergemod.mergestate.read(self)
1691 mergeutil.checkunresolved(ms)
1677 mergeutil.checkunresolved(ms)
1692
1678
1693 if editor:
1679 if editor:
1694 cctx._text = editor(self, cctx, subs)
1680 cctx._text = editor(self, cctx, subs)
1695 edited = (text != cctx._text)
1681 edited = (text != cctx._text)
1696
1682
1697 # Save commit message in case this transaction gets rolled back
1683 # Save commit message in case this transaction gets rolled back
1698 # (e.g. by a pretxncommit hook). Leave the content alone on
1684 # (e.g. by a pretxncommit hook). Leave the content alone on
1699 # the assumption that the user will use the same editor again.
1685 # the assumption that the user will use the same editor again.
1700 msgfn = self.savecommitmessage(cctx._text)
1686 msgfn = self.savecommitmessage(cctx._text)
1701
1687
1702 # commit subs and write new state
1688 # commit subs and write new state
1703 if subs:
1689 if subs:
1704 for s in sorted(commitsubs):
1690 for s in sorted(commitsubs):
1705 sub = wctx.sub(s)
1691 sub = wctx.sub(s)
1706 self.ui.status(_('committing subrepository %s\n') %
1692 self.ui.status(_('committing subrepository %s\n') %
1707 subrepo.subrelpath(sub))
1693 subrepo.subrelpath(sub))
1708 sr = sub.commit(cctx._text, user, date)
1694 sr = sub.commit(cctx._text, user, date)
1709 newstate[s] = (newstate[s][0], sr)
1695 newstate[s] = (newstate[s][0], sr)
1710 subrepo.writestate(self, newstate)
1696 subrepo.writestate(self, newstate)
1711
1697
1712 p1, p2 = self.dirstate.parents()
1698 p1, p2 = self.dirstate.parents()
1713 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1699 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1714 try:
1700 try:
1715 self.hook("precommit", throw=True, parent1=hookp1,
1701 self.hook("precommit", throw=True, parent1=hookp1,
1716 parent2=hookp2)
1702 parent2=hookp2)
1717 tr = self.transaction('commit')
1703 tr = self.transaction('commit')
1718 ret = self.commitctx(cctx, True)
1704 ret = self.commitctx(cctx, True)
1719 except: # re-raises
1705 except: # re-raises
1720 if edited:
1706 if edited:
1721 self.ui.write(
1707 self.ui.write(
1722 _('note: commit message saved in %s\n') % msgfn)
1708 _('note: commit message saved in %s\n') % msgfn)
1723 raise
1709 raise
1724 # update bookmarks, dirstate and mergestate
1710 # update bookmarks, dirstate and mergestate
1725 bookmarks.update(self, [p1, p2], ret)
1711 bookmarks.update(self, [p1, p2], ret)
1726 cctx.markcommitted(ret)
1712 cctx.markcommitted(ret)
1727 ms.reset()
1713 ms.reset()
1728 tr.close()
1714 tr.close()
1729
1715
1730 finally:
1716 finally:
1731 lockmod.release(tr, lock, wlock)
1717 lockmod.release(tr, lock, wlock)
1732
1718
1733 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
1719 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
1734 # hack for command that use a temporary commit (eg: histedit)
1720 # hack for command that use a temporary commit (eg: histedit)
1735 # temporary commit got stripped before hook release
1721 # temporary commit got stripped before hook release
1736 if self.changelog.hasnode(ret):
1722 if self.changelog.hasnode(ret):
1737 self.hook("commit", node=node, parent1=parent1,
1723 self.hook("commit", node=node, parent1=parent1,
1738 parent2=parent2)
1724 parent2=parent2)
1739 self._afterlock(commithook)
1725 self._afterlock(commithook)
1740 return ret
1726 return ret
1741
1727
1742 @unfilteredmethod
1728 @unfilteredmethod
1743 def commitctx(self, ctx, error=False):
1729 def commitctx(self, ctx, error=False):
1744 """Add a new revision to current repository.
1730 """Add a new revision to current repository.
1745 Revision information is passed via the context argument.
1731 Revision information is passed via the context argument.
1746 """
1732 """
1747
1733
1748 tr = None
1734 tr = None
1749 p1, p2 = ctx.p1(), ctx.p2()
1735 p1, p2 = ctx.p1(), ctx.p2()
1750 user = ctx.user()
1736 user = ctx.user()
1751
1737
1752 lock = self.lock()
1738 lock = self.lock()
1753 try:
1739 try:
1754 tr = self.transaction("commit")
1740 tr = self.transaction("commit")
1755 trp = weakref.proxy(tr)
1741 trp = weakref.proxy(tr)
1756
1742
1757 if ctx.manifestnode():
1743 if ctx.manifestnode():
1758 # reuse an existing manifest revision
1744 # reuse an existing manifest revision
1759 mn = ctx.manifestnode()
1745 mn = ctx.manifestnode()
1760 files = ctx.files()
1746 files = ctx.files()
1761 elif ctx.files():
1747 elif ctx.files():
1762 m1ctx = p1.manifestctx()
1748 m1ctx = p1.manifestctx()
1763 m2ctx = p2.manifestctx()
1749 m2ctx = p2.manifestctx()
1764 mctx = m1ctx.copy()
1750 mctx = m1ctx.copy()
1765
1751
1766 m = mctx.read()
1752 m = mctx.read()
1767 m1 = m1ctx.read()
1753 m1 = m1ctx.read()
1768 m2 = m2ctx.read()
1754 m2 = m2ctx.read()
1769
1755
1770 # check in files
1756 # check in files
1771 added = []
1757 added = []
1772 changed = []
1758 changed = []
1773 removed = list(ctx.removed())
1759 removed = list(ctx.removed())
1774 linkrev = len(self)
1760 linkrev = len(self)
1775 self.ui.note(_("committing files:\n"))
1761 self.ui.note(_("committing files:\n"))
1776 for f in sorted(ctx.modified() + ctx.added()):
1762 for f in sorted(ctx.modified() + ctx.added()):
1777 self.ui.note(f + "\n")
1763 self.ui.note(f + "\n")
1778 try:
1764 try:
1779 fctx = ctx[f]
1765 fctx = ctx[f]
1780 if fctx is None:
1766 if fctx is None:
1781 removed.append(f)
1767 removed.append(f)
1782 else:
1768 else:
1783 added.append(f)
1769 added.append(f)
1784 m[f] = self._filecommit(fctx, m1, m2, linkrev,
1770 m[f] = self._filecommit(fctx, m1, m2, linkrev,
1785 trp, changed)
1771 trp, changed)
1786 m.setflag(f, fctx.flags())
1772 m.setflag(f, fctx.flags())
1787 except OSError as inst:
1773 except OSError as inst:
1788 self.ui.warn(_("trouble committing %s!\n") % f)
1774 self.ui.warn(_("trouble committing %s!\n") % f)
1789 raise
1775 raise
1790 except IOError as inst:
1776 except IOError as inst:
1791 errcode = getattr(inst, 'errno', errno.ENOENT)
1777 errcode = getattr(inst, 'errno', errno.ENOENT)
1792 if error or errcode and errcode != errno.ENOENT:
1778 if error or errcode and errcode != errno.ENOENT:
1793 self.ui.warn(_("trouble committing %s!\n") % f)
1779 self.ui.warn(_("trouble committing %s!\n") % f)
1794 raise
1780 raise
1795
1781
1796 # update manifest
1782 # update manifest
1797 self.ui.note(_("committing manifest\n"))
1783 self.ui.note(_("committing manifest\n"))
1798 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1784 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1799 drop = [f for f in removed if f in m]
1785 drop = [f for f in removed if f in m]
1800 for f in drop:
1786 for f in drop:
1801 del m[f]
1787 del m[f]
1802 mn = mctx.write(trp, linkrev,
1788 mn = mctx.write(trp, linkrev,
1803 p1.manifestnode(), p2.manifestnode(),
1789 p1.manifestnode(), p2.manifestnode(),
1804 added, drop)
1790 added, drop)
1805 files = changed + removed
1791 files = changed + removed
1806 else:
1792 else:
1807 mn = p1.manifestnode()
1793 mn = p1.manifestnode()
1808 files = []
1794 files = []
1809
1795
1810 # update changelog
1796 # update changelog
1811 self.ui.note(_("committing changelog\n"))
1797 self.ui.note(_("committing changelog\n"))
1812 self.changelog.delayupdate(tr)
1798 self.changelog.delayupdate(tr)
1813 n = self.changelog.add(mn, files, ctx.description(),
1799 n = self.changelog.add(mn, files, ctx.description(),
1814 trp, p1.node(), p2.node(),
1800 trp, p1.node(), p2.node(),
1815 user, ctx.date(), ctx.extra().copy())
1801 user, ctx.date(), ctx.extra().copy())
1816 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1802 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1817 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1803 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1818 parent2=xp2)
1804 parent2=xp2)
1819 # set the new commit is proper phase
1805 # set the new commit is proper phase
1820 targetphase = subrepo.newcommitphase(self.ui, ctx)
1806 targetphase = subrepo.newcommitphase(self.ui, ctx)
1821 if targetphase:
1807 if targetphase:
1822 # retract boundary do not alter parent changeset.
1808 # retract boundary do not alter parent changeset.
1823 # if a parent have higher the resulting phase will
1809 # if a parent have higher the resulting phase will
1824 # be compliant anyway
1810 # be compliant anyway
1825 #
1811 #
1826 # if minimal phase was 0 we don't need to retract anything
1812 # if minimal phase was 0 we don't need to retract anything
1827 phases.retractboundary(self, tr, targetphase, [n])
1813 phases.retractboundary(self, tr, targetphase, [n])
1828 tr.close()
1814 tr.close()
1829 return n
1815 return n
1830 finally:
1816 finally:
1831 if tr:
1817 if tr:
1832 tr.release()
1818 tr.release()
1833 lock.release()
1819 lock.release()
1834
1820
1835 @unfilteredmethod
1821 @unfilteredmethod
1836 def destroying(self):
1822 def destroying(self):
1837 '''Inform the repository that nodes are about to be destroyed.
1823 '''Inform the repository that nodes are about to be destroyed.
1838 Intended for use by strip and rollback, so there's a common
1824 Intended for use by strip and rollback, so there's a common
1839 place for anything that has to be done before destroying history.
1825 place for anything that has to be done before destroying history.
1840
1826
1841 This is mostly useful for saving state that is in memory and waiting
1827 This is mostly useful for saving state that is in memory and waiting
1842 to be flushed when the current lock is released. Because a call to
1828 to be flushed when the current lock is released. Because a call to
1843 destroyed is imminent, the repo will be invalidated causing those
1829 destroyed is imminent, the repo will be invalidated causing those
1844 changes to stay in memory (waiting for the next unlock), or vanish
1830 changes to stay in memory (waiting for the next unlock), or vanish
1845 completely.
1831 completely.
1846 '''
1832 '''
1847 # When using the same lock to commit and strip, the phasecache is left
1833 # When using the same lock to commit and strip, the phasecache is left
1848 # dirty after committing. Then when we strip, the repo is invalidated,
1834 # dirty after committing. Then when we strip, the repo is invalidated,
1849 # causing those changes to disappear.
1835 # causing those changes to disappear.
1850 if '_phasecache' in vars(self):
1836 if '_phasecache' in vars(self):
1851 self._phasecache.write()
1837 self._phasecache.write()
1852
1838
1853 @unfilteredmethod
1839 @unfilteredmethod
1854 def destroyed(self):
1840 def destroyed(self):
1855 '''Inform the repository that nodes have been destroyed.
1841 '''Inform the repository that nodes have been destroyed.
1856 Intended for use by strip and rollback, so there's a common
1842 Intended for use by strip and rollback, so there's a common
1857 place for anything that has to be done after destroying history.
1843 place for anything that has to be done after destroying history.
1858 '''
1844 '''
1859 # When one tries to:
1845 # When one tries to:
1860 # 1) destroy nodes thus calling this method (e.g. strip)
1846 # 1) destroy nodes thus calling this method (e.g. strip)
1861 # 2) use phasecache somewhere (e.g. commit)
1847 # 2) use phasecache somewhere (e.g. commit)
1862 #
1848 #
1863 # then 2) will fail because the phasecache contains nodes that were
1849 # then 2) will fail because the phasecache contains nodes that were
1864 # removed. We can either remove phasecache from the filecache,
1850 # removed. We can either remove phasecache from the filecache,
1865 # causing it to reload next time it is accessed, or simply filter
1851 # causing it to reload next time it is accessed, or simply filter
1866 # the removed nodes now and write the updated cache.
1852 # the removed nodes now and write the updated cache.
1867 self._phasecache.filterunknown(self)
1853 self._phasecache.filterunknown(self)
1868 self._phasecache.write()
1854 self._phasecache.write()
1869
1855
1870 # refresh all repository caches
1856 # refresh all repository caches
1871 self.updatecaches()
1857 self.updatecaches()
1872
1858
1873 # Ensure the persistent tag cache is updated. Doing it now
1859 # Ensure the persistent tag cache is updated. Doing it now
1874 # means that the tag cache only has to worry about destroyed
1860 # means that the tag cache only has to worry about destroyed
1875 # heads immediately after a strip/rollback. That in turn
1861 # heads immediately after a strip/rollback. That in turn
1876 # guarantees that "cachetip == currenttip" (comparing both rev
1862 # guarantees that "cachetip == currenttip" (comparing both rev
1877 # and node) always means no nodes have been added or destroyed.
1863 # and node) always means no nodes have been added or destroyed.
1878
1864
1879 # XXX this is suboptimal when qrefresh'ing: we strip the current
1865 # XXX this is suboptimal when qrefresh'ing: we strip the current
1880 # head, refresh the tag cache, then immediately add a new head.
1866 # head, refresh the tag cache, then immediately add a new head.
1881 # But I think doing it this way is necessary for the "instant
1867 # But I think doing it this way is necessary for the "instant
1882 # tag cache retrieval" case to work.
1868 # tag cache retrieval" case to work.
1883 self.invalidate()
1869 self.invalidate()
1884
1870
1885 def walk(self, match, node=None):
1871 def walk(self, match, node=None):
1886 '''
1872 '''
1887 walk recursively through the directory tree or a given
1873 walk recursively through the directory tree or a given
1888 changeset, finding all files matched by the match
1874 changeset, finding all files matched by the match
1889 function
1875 function
1890 '''
1876 '''
1891 self.ui.deprecwarn('use repo[node].walk instead of repo.walk', '4.3')
1877 self.ui.deprecwarn('use repo[node].walk instead of repo.walk', '4.3')
1892 return self[node].walk(match)
1878 return self[node].walk(match)
1893
1879
1894 def status(self, node1='.', node2=None, match=None,
1880 def status(self, node1='.', node2=None, match=None,
1895 ignored=False, clean=False, unknown=False,
1881 ignored=False, clean=False, unknown=False,
1896 listsubrepos=False):
1882 listsubrepos=False):
1897 '''a convenience method that calls node1.status(node2)'''
1883 '''a convenience method that calls node1.status(node2)'''
1898 return self[node1].status(node2, match, ignored, clean, unknown,
1884 return self[node1].status(node2, match, ignored, clean, unknown,
1899 listsubrepos)
1885 listsubrepos)
1900
1886
1901 def heads(self, start=None):
1887 def heads(self, start=None):
1902 if start is None:
1888 if start is None:
1903 cl = self.changelog
1889 cl = self.changelog
1904 headrevs = reversed(cl.headrevs())
1890 headrevs = reversed(cl.headrevs())
1905 return [cl.node(rev) for rev in headrevs]
1891 return [cl.node(rev) for rev in headrevs]
1906
1892
1907 heads = self.changelog.heads(start)
1893 heads = self.changelog.heads(start)
1908 # sort the output in rev descending order
1894 # sort the output in rev descending order
1909 return sorted(heads, key=self.changelog.rev, reverse=True)
1895 return sorted(heads, key=self.changelog.rev, reverse=True)
1910
1896
1911 def branchheads(self, branch=None, start=None, closed=False):
1897 def branchheads(self, branch=None, start=None, closed=False):
1912 '''return a (possibly filtered) list of heads for the given branch
1898 '''return a (possibly filtered) list of heads for the given branch
1913
1899
1914 Heads are returned in topological order, from newest to oldest.
1900 Heads are returned in topological order, from newest to oldest.
1915 If branch is None, use the dirstate branch.
1901 If branch is None, use the dirstate branch.
1916 If start is not None, return only heads reachable from start.
1902 If start is not None, return only heads reachable from start.
1917 If closed is True, return heads that are marked as closed as well.
1903 If closed is True, return heads that are marked as closed as well.
1918 '''
1904 '''
1919 if branch is None:
1905 if branch is None:
1920 branch = self[None].branch()
1906 branch = self[None].branch()
1921 branches = self.branchmap()
1907 branches = self.branchmap()
1922 if branch not in branches:
1908 if branch not in branches:
1923 return []
1909 return []
1924 # the cache returns heads ordered lowest to highest
1910 # the cache returns heads ordered lowest to highest
1925 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
1911 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
1926 if start is not None:
1912 if start is not None:
1927 # filter out the heads that cannot be reached from startrev
1913 # filter out the heads that cannot be reached from startrev
1928 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1914 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1929 bheads = [h for h in bheads if h in fbheads]
1915 bheads = [h for h in bheads if h in fbheads]
1930 return bheads
1916 return bheads
1931
1917
1932 def branches(self, nodes):
1918 def branches(self, nodes):
1933 if not nodes:
1919 if not nodes:
1934 nodes = [self.changelog.tip()]
1920 nodes = [self.changelog.tip()]
1935 b = []
1921 b = []
1936 for n in nodes:
1922 for n in nodes:
1937 t = n
1923 t = n
1938 while True:
1924 while True:
1939 p = self.changelog.parents(n)
1925 p = self.changelog.parents(n)
1940 if p[1] != nullid or p[0] == nullid:
1926 if p[1] != nullid or p[0] == nullid:
1941 b.append((t, n, p[0], p[1]))
1927 b.append((t, n, p[0], p[1]))
1942 break
1928 break
1943 n = p[0]
1929 n = p[0]
1944 return b
1930 return b
1945
1931
1946 def between(self, pairs):
1932 def between(self, pairs):
1947 r = []
1933 r = []
1948
1934
1949 for top, bottom in pairs:
1935 for top, bottom in pairs:
1950 n, l, i = top, [], 0
1936 n, l, i = top, [], 0
1951 f = 1
1937 f = 1
1952
1938
1953 while n != bottom and n != nullid:
1939 while n != bottom and n != nullid:
1954 p = self.changelog.parents(n)[0]
1940 p = self.changelog.parents(n)[0]
1955 if i == f:
1941 if i == f:
1956 l.append(n)
1942 l.append(n)
1957 f = f * 2
1943 f = f * 2
1958 n = p
1944 n = p
1959 i += 1
1945 i += 1
1960
1946
1961 r.append(l)
1947 r.append(l)
1962
1948
1963 return r
1949 return r
1964
1950
1965 def checkpush(self, pushop):
1951 def checkpush(self, pushop):
1966 """Extensions can override this function if additional checks have
1952 """Extensions can override this function if additional checks have
1967 to be performed before pushing, or call it if they override push
1953 to be performed before pushing, or call it if they override push
1968 command.
1954 command.
1969 """
1955 """
1970 pass
1956 pass
1971
1957
1972 @unfilteredpropertycache
1958 @unfilteredpropertycache
1973 def prepushoutgoinghooks(self):
1959 def prepushoutgoinghooks(self):
1974 """Return util.hooks consists of a pushop with repo, remote, outgoing
1960 """Return util.hooks consists of a pushop with repo, remote, outgoing
1975 methods, which are called before pushing changesets.
1961 methods, which are called before pushing changesets.
1976 """
1962 """
1977 return util.hooks()
1963 return util.hooks()
1978
1964
1979 def pushkey(self, namespace, key, old, new):
1965 def pushkey(self, namespace, key, old, new):
1980 try:
1966 try:
1981 tr = self.currenttransaction()
1967 tr = self.currenttransaction()
1982 hookargs = {}
1968 hookargs = {}
1983 if tr is not None:
1969 if tr is not None:
1984 hookargs.update(tr.hookargs)
1970 hookargs.update(tr.hookargs)
1985 hookargs['namespace'] = namespace
1971 hookargs['namespace'] = namespace
1986 hookargs['key'] = key
1972 hookargs['key'] = key
1987 hookargs['old'] = old
1973 hookargs['old'] = old
1988 hookargs['new'] = new
1974 hookargs['new'] = new
1989 self.hook('prepushkey', throw=True, **hookargs)
1975 self.hook('prepushkey', throw=True, **hookargs)
1990 except error.HookAbort as exc:
1976 except error.HookAbort as exc:
1991 self.ui.write_err(_("pushkey-abort: %s\n") % exc)
1977 self.ui.write_err(_("pushkey-abort: %s\n") % exc)
1992 if exc.hint:
1978 if exc.hint:
1993 self.ui.write_err(_("(%s)\n") % exc.hint)
1979 self.ui.write_err(_("(%s)\n") % exc.hint)
1994 return False
1980 return False
1995 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
1981 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
1996 ret = pushkey.push(self, namespace, key, old, new)
1982 ret = pushkey.push(self, namespace, key, old, new)
1997 def runhook():
1983 def runhook():
1998 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
1984 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
1999 ret=ret)
1985 ret=ret)
2000 self._afterlock(runhook)
1986 self._afterlock(runhook)
2001 return ret
1987 return ret
2002
1988
2003 def listkeys(self, namespace):
1989 def listkeys(self, namespace):
2004 self.hook('prelistkeys', throw=True, namespace=namespace)
1990 self.hook('prelistkeys', throw=True, namespace=namespace)
2005 self.ui.debug('listing keys for "%s"\n' % namespace)
1991 self.ui.debug('listing keys for "%s"\n' % namespace)
2006 values = pushkey.list(self, namespace)
1992 values = pushkey.list(self, namespace)
2007 self.hook('listkeys', namespace=namespace, values=values)
1993 self.hook('listkeys', namespace=namespace, values=values)
2008 return values
1994 return values
2009
1995
2010 def debugwireargs(self, one, two, three=None, four=None, five=None):
1996 def debugwireargs(self, one, two, three=None, four=None, five=None):
2011 '''used to test argument passing over the wire'''
1997 '''used to test argument passing over the wire'''
2012 return "%s %s %s %s %s" % (one, two, three, four, five)
1998 return "%s %s %s %s %s" % (one, two, three, four, five)
2013
1999
2014 def savecommitmessage(self, text):
2000 def savecommitmessage(self, text):
2015 fp = self.vfs('last-message.txt', 'wb')
2001 fp = self.vfs('last-message.txt', 'wb')
2016 try:
2002 try:
2017 fp.write(text)
2003 fp.write(text)
2018 finally:
2004 finally:
2019 fp.close()
2005 fp.close()
2020 return self.pathto(fp.name[len(self.root) + 1:])
2006 return self.pathto(fp.name[len(self.root) + 1:])
2021
2007
2022 # used to avoid circular references so destructors work
2008 # used to avoid circular references so destructors work
2023 def aftertrans(files):
2009 def aftertrans(files):
2024 renamefiles = [tuple(t) for t in files]
2010 renamefiles = [tuple(t) for t in files]
2025 def a():
2011 def a():
2026 for vfs, src, dest in renamefiles:
2012 for vfs, src, dest in renamefiles:
2027 # if src and dest refer to a same file, vfs.rename is a no-op,
2013 # if src and dest refer to a same file, vfs.rename is a no-op,
2028 # leaving both src and dest on disk. delete dest to make sure
2014 # leaving both src and dest on disk. delete dest to make sure
2029 # the rename couldn't be such a no-op.
2015 # the rename couldn't be such a no-op.
2030 vfs.tryunlink(dest)
2016 vfs.tryunlink(dest)
2031 try:
2017 try:
2032 vfs.rename(src, dest)
2018 vfs.rename(src, dest)
2033 except OSError: # journal file does not yet exist
2019 except OSError: # journal file does not yet exist
2034 pass
2020 pass
2035 return a
2021 return a
2036
2022
2037 def undoname(fn):
2023 def undoname(fn):
2038 base, name = os.path.split(fn)
2024 base, name = os.path.split(fn)
2039 assert name.startswith('journal')
2025 assert name.startswith('journal')
2040 return os.path.join(base, name.replace('journal', 'undo', 1))
2026 return os.path.join(base, name.replace('journal', 'undo', 1))
2041
2027
2042 def instance(ui, path, create):
2028 def instance(ui, path, create):
2043 return localrepository(ui, util.urllocalpath(path), create)
2029 return localrepository(ui, util.urllocalpath(path), create)
2044
2030
2045 def islocal(path):
2031 def islocal(path):
2046 return True
2032 return True
2047
2033
2048 def newreporequirements(repo):
2034 def newreporequirements(repo):
2049 """Determine the set of requirements for a new local repository.
2035 """Determine the set of requirements for a new local repository.
2050
2036
2051 Extensions can wrap this function to specify custom requirements for
2037 Extensions can wrap this function to specify custom requirements for
2052 new repositories.
2038 new repositories.
2053 """
2039 """
2054 ui = repo.ui
2040 ui = repo.ui
2055 requirements = {'revlogv1'}
2041 requirements = {'revlogv1'}
2056 if ui.configbool('format', 'usestore', True):
2042 if ui.configbool('format', 'usestore', True):
2057 requirements.add('store')
2043 requirements.add('store')
2058 if ui.configbool('format', 'usefncache', True):
2044 if ui.configbool('format', 'usefncache', True):
2059 requirements.add('fncache')
2045 requirements.add('fncache')
2060 if ui.configbool('format', 'dotencode', True):
2046 if ui.configbool('format', 'dotencode', True):
2061 requirements.add('dotencode')
2047 requirements.add('dotencode')
2062
2048
2063 compengine = ui.config('experimental', 'format.compression', 'zlib')
2049 compengine = ui.config('experimental', 'format.compression', 'zlib')
2064 if compengine not in util.compengines:
2050 if compengine not in util.compengines:
2065 raise error.Abort(_('compression engine %s defined by '
2051 raise error.Abort(_('compression engine %s defined by '
2066 'experimental.format.compression not available') %
2052 'experimental.format.compression not available') %
2067 compengine,
2053 compengine,
2068 hint=_('run "hg debuginstall" to list available '
2054 hint=_('run "hg debuginstall" to list available '
2069 'compression engines'))
2055 'compression engines'))
2070
2056
2071 # zlib is the historical default and doesn't need an explicit requirement.
2057 # zlib is the historical default and doesn't need an explicit requirement.
2072 if compengine != 'zlib':
2058 if compengine != 'zlib':
2073 requirements.add('exp-compression-%s' % compengine)
2059 requirements.add('exp-compression-%s' % compengine)
2074
2060
2075 if scmutil.gdinitconfig(ui):
2061 if scmutil.gdinitconfig(ui):
2076 requirements.add('generaldelta')
2062 requirements.add('generaldelta')
2077 if ui.configbool('experimental', 'treemanifest', False):
2063 if ui.configbool('experimental', 'treemanifest', False):
2078 requirements.add('treemanifest')
2064 requirements.add('treemanifest')
2079 if ui.configbool('experimental', 'manifestv2', False):
2065 if ui.configbool('experimental', 'manifestv2', False):
2080 requirements.add('manifestv2')
2066 requirements.add('manifestv2')
2081
2067
2082 revlogv2 = ui.config('experimental', 'revlogv2')
2068 revlogv2 = ui.config('experimental', 'revlogv2')
2083 if revlogv2 == 'enable-unstable-format-and-corrupt-my-data':
2069 if revlogv2 == 'enable-unstable-format-and-corrupt-my-data':
2084 requirements.remove('revlogv1')
2070 requirements.remove('revlogv1')
2085 # generaldelta is implied by revlogv2.
2071 # generaldelta is implied by revlogv2.
2086 requirements.discard('generaldelta')
2072 requirements.discard('generaldelta')
2087 requirements.add(REVLOGV2_REQUIREMENT)
2073 requirements.add(REVLOGV2_REQUIREMENT)
2088
2074
2089 return requirements
2075 return requirements
@@ -1,1442 +1,1458 b''
1 # obsolete.py - obsolete markers handling
1 # obsolete.py - obsolete markers handling
2 #
2 #
3 # Copyright 2012 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
3 # Copyright 2012 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
4 # Logilab SA <contact@logilab.fr>
4 # Logilab SA <contact@logilab.fr>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 """Obsolete marker handling
9 """Obsolete marker handling
10
10
11 An obsolete marker maps an old changeset to a list of new
11 An obsolete marker maps an old changeset to a list of new
12 changesets. If the list of new changesets is empty, the old changeset
12 changesets. If the list of new changesets is empty, the old changeset
13 is said to be "killed". Otherwise, the old changeset is being
13 is said to be "killed". Otherwise, the old changeset is being
14 "replaced" by the new changesets.
14 "replaced" by the new changesets.
15
15
16 Obsolete markers can be used to record and distribute changeset graph
16 Obsolete markers can be used to record and distribute changeset graph
17 transformations performed by history rewrite operations, and help
17 transformations performed by history rewrite operations, and help
18 building new tools to reconcile conflicting rewrite actions. To
18 building new tools to reconcile conflicting rewrite actions. To
19 facilitate conflict resolution, markers include various annotations
19 facilitate conflict resolution, markers include various annotations
20 besides old and news changeset identifiers, such as creation date or
20 besides old and news changeset identifiers, such as creation date or
21 author name.
21 author name.
22
22
23 The old obsoleted changeset is called a "precursor" and possible
23 The old obsoleted changeset is called a "precursor" and possible
24 replacements are called "successors". Markers that used changeset X as
24 replacements are called "successors". Markers that used changeset X as
25 a precursor are called "successor markers of X" because they hold
25 a precursor are called "successor markers of X" because they hold
26 information about the successors of X. Markers that use changeset Y as
26 information about the successors of X. Markers that use changeset Y as
27 a successors are call "precursor markers of Y" because they hold
27 a successors are call "precursor markers of Y" because they hold
28 information about the precursors of Y.
28 information about the precursors of Y.
29
29
30 Examples:
30 Examples:
31
31
32 - When changeset A is replaced by changeset A', one marker is stored:
32 - When changeset A is replaced by changeset A', one marker is stored:
33
33
34 (A, (A',))
34 (A, (A',))
35
35
36 - When changesets A and B are folded into a new changeset C, two markers are
36 - When changesets A and B are folded into a new changeset C, two markers are
37 stored:
37 stored:
38
38
39 (A, (C,)) and (B, (C,))
39 (A, (C,)) and (B, (C,))
40
40
41 - When changeset A is simply "pruned" from the graph, a marker is created:
41 - When changeset A is simply "pruned" from the graph, a marker is created:
42
42
43 (A, ())
43 (A, ())
44
44
45 - When changeset A is split into B and C, a single marker is used:
45 - When changeset A is split into B and C, a single marker is used:
46
46
47 (A, (B, C))
47 (A, (B, C))
48
48
49 We use a single marker to distinguish the "split" case from the "divergence"
49 We use a single marker to distinguish the "split" case from the "divergence"
50 case. If two independent operations rewrite the same changeset A in to A' and
50 case. If two independent operations rewrite the same changeset A in to A' and
51 A'', we have an error case: divergent rewriting. We can detect it because
51 A'', we have an error case: divergent rewriting. We can detect it because
52 two markers will be created independently:
52 two markers will be created independently:
53
53
54 (A, (B,)) and (A, (C,))
54 (A, (B,)) and (A, (C,))
55
55
56 Format
56 Format
57 ------
57 ------
58
58
59 Markers are stored in an append-only file stored in
59 Markers are stored in an append-only file stored in
60 '.hg/store/obsstore'.
60 '.hg/store/obsstore'.
61
61
62 The file starts with a version header:
62 The file starts with a version header:
63
63
64 - 1 unsigned byte: version number, starting at zero.
64 - 1 unsigned byte: version number, starting at zero.
65
65
66 The header is followed by the markers. Marker format depend of the version. See
66 The header is followed by the markers. Marker format depend of the version. See
67 comment associated with each format for details.
67 comment associated with each format for details.
68
68
69 """
69 """
70 from __future__ import absolute_import
70 from __future__ import absolute_import
71
71
72 import errno
72 import errno
73 import struct
73 import struct
74
74
75 from .i18n import _
75 from .i18n import _
76 from . import (
76 from . import (
77 error,
77 error,
78 node,
78 node,
79 phases,
79 phases,
80 policy,
80 policy,
81 util,
81 util,
82 )
82 )
83
83
84 parsers = policy.importmod(r'parsers')
84 parsers = policy.importmod(r'parsers')
85
85
86 _pack = struct.pack
86 _pack = struct.pack
87 _unpack = struct.unpack
87 _unpack = struct.unpack
88 _calcsize = struct.calcsize
88 _calcsize = struct.calcsize
89 propertycache = util.propertycache
89 propertycache = util.propertycache
90
90
91 # the obsolete feature is not mature enough to be enabled by default.
91 # the obsolete feature is not mature enough to be enabled by default.
92 # you have to rely on third party extension extension to enable this.
92 # you have to rely on third party extension extension to enable this.
93 _enabled = False
93 _enabled = False
94
94
95 # Options for obsolescence
95 # Options for obsolescence
96 createmarkersopt = 'createmarkers'
96 createmarkersopt = 'createmarkers'
97 allowunstableopt = 'allowunstable'
97 allowunstableopt = 'allowunstable'
98 exchangeopt = 'exchange'
98 exchangeopt = 'exchange'
99
99
100 def isenabled(repo, option):
100 def isenabled(repo, option):
101 """Returns True if the given repository has the given obsolete option
101 """Returns True if the given repository has the given obsolete option
102 enabled.
102 enabled.
103 """
103 """
104 result = set(repo.ui.configlist('experimental', 'evolution'))
104 result = set(repo.ui.configlist('experimental', 'evolution'))
105 if 'all' in result:
105 if 'all' in result:
106 return True
106 return True
107
107
108 # For migration purposes, temporarily return true if the config hasn't been
108 # For migration purposes, temporarily return true if the config hasn't been
109 # set but _enabled is true.
109 # set but _enabled is true.
110 if len(result) == 0 and _enabled:
110 if len(result) == 0 and _enabled:
111 return True
111 return True
112
112
113 # createmarkers must be enabled if other options are enabled
113 # createmarkers must be enabled if other options are enabled
114 if ((allowunstableopt in result or exchangeopt in result) and
114 if ((allowunstableopt in result or exchangeopt in result) and
115 not createmarkersopt in result):
115 not createmarkersopt in result):
116 raise error.Abort(_("'createmarkers' obsolete option must be enabled "
116 raise error.Abort(_("'createmarkers' obsolete option must be enabled "
117 "if other obsolete options are enabled"))
117 "if other obsolete options are enabled"))
118
118
119 return option in result
119 return option in result
120
120
121 ### obsolescence marker flag
121 ### obsolescence marker flag
122
122
123 ## bumpedfix flag
123 ## bumpedfix flag
124 #
124 #
125 # When a changeset A' succeed to a changeset A which became public, we call A'
125 # When a changeset A' succeed to a changeset A which became public, we call A'
126 # "bumped" because it's a successors of a public changesets
126 # "bumped" because it's a successors of a public changesets
127 #
127 #
128 # o A' (bumped)
128 # o A' (bumped)
129 # |`:
129 # |`:
130 # | o A
130 # | o A
131 # |/
131 # |/
132 # o Z
132 # o Z
133 #
133 #
134 # The way to solve this situation is to create a new changeset Ad as children
134 # The way to solve this situation is to create a new changeset Ad as children
135 # of A. This changeset have the same content than A'. So the diff from A to A'
135 # of A. This changeset have the same content than A'. So the diff from A to A'
136 # is the same than the diff from A to Ad. Ad is marked as a successors of A'
136 # is the same than the diff from A to Ad. Ad is marked as a successors of A'
137 #
137 #
138 # o Ad
138 # o Ad
139 # |`:
139 # |`:
140 # | x A'
140 # | x A'
141 # |'|
141 # |'|
142 # o | A
142 # o | A
143 # |/
143 # |/
144 # o Z
144 # o Z
145 #
145 #
146 # But by transitivity Ad is also a successors of A. To avoid having Ad marked
146 # But by transitivity Ad is also a successors of A. To avoid having Ad marked
147 # as bumped too, we add the `bumpedfix` flag to the marker. <A', (Ad,)>.
147 # as bumped too, we add the `bumpedfix` flag to the marker. <A', (Ad,)>.
148 # This flag mean that the successors express the changes between the public and
148 # This flag mean that the successors express the changes between the public and
149 # bumped version and fix the situation, breaking the transitivity of
149 # bumped version and fix the situation, breaking the transitivity of
150 # "bumped" here.
150 # "bumped" here.
151 bumpedfix = 1
151 bumpedfix = 1
152 usingsha256 = 2
152 usingsha256 = 2
153
153
154 ## Parsing and writing of version "0"
154 ## Parsing and writing of version "0"
155 #
155 #
156 # The header is followed by the markers. Each marker is made of:
156 # The header is followed by the markers. Each marker is made of:
157 #
157 #
158 # - 1 uint8 : number of new changesets "N", can be zero.
158 # - 1 uint8 : number of new changesets "N", can be zero.
159 #
159 #
160 # - 1 uint32: metadata size "M" in bytes.
160 # - 1 uint32: metadata size "M" in bytes.
161 #
161 #
162 # - 1 byte: a bit field. It is reserved for flags used in common
162 # - 1 byte: a bit field. It is reserved for flags used in common
163 # obsolete marker operations, to avoid repeated decoding of metadata
163 # obsolete marker operations, to avoid repeated decoding of metadata
164 # entries.
164 # entries.
165 #
165 #
166 # - 20 bytes: obsoleted changeset identifier.
166 # - 20 bytes: obsoleted changeset identifier.
167 #
167 #
168 # - N*20 bytes: new changesets identifiers.
168 # - N*20 bytes: new changesets identifiers.
169 #
169 #
170 # - M bytes: metadata as a sequence of nul-terminated strings. Each
170 # - M bytes: metadata as a sequence of nul-terminated strings. Each
171 # string contains a key and a value, separated by a colon ':', without
171 # string contains a key and a value, separated by a colon ':', without
172 # additional encoding. Keys cannot contain '\0' or ':' and values
172 # additional encoding. Keys cannot contain '\0' or ':' and values
173 # cannot contain '\0'.
173 # cannot contain '\0'.
174 _fm0version = 0
174 _fm0version = 0
175 _fm0fixed = '>BIB20s'
175 _fm0fixed = '>BIB20s'
176 _fm0node = '20s'
176 _fm0node = '20s'
177 _fm0fsize = _calcsize(_fm0fixed)
177 _fm0fsize = _calcsize(_fm0fixed)
178 _fm0fnodesize = _calcsize(_fm0node)
178 _fm0fnodesize = _calcsize(_fm0node)
179
179
180 def _fm0readmarkers(data, off):
180 def _fm0readmarkers(data, off):
181 # Loop on markers
181 # Loop on markers
182 l = len(data)
182 l = len(data)
183 while off + _fm0fsize <= l:
183 while off + _fm0fsize <= l:
184 # read fixed part
184 # read fixed part
185 cur = data[off:off + _fm0fsize]
185 cur = data[off:off + _fm0fsize]
186 off += _fm0fsize
186 off += _fm0fsize
187 numsuc, mdsize, flags, pre = _unpack(_fm0fixed, cur)
187 numsuc, mdsize, flags, pre = _unpack(_fm0fixed, cur)
188 # read replacement
188 # read replacement
189 sucs = ()
189 sucs = ()
190 if numsuc:
190 if numsuc:
191 s = (_fm0fnodesize * numsuc)
191 s = (_fm0fnodesize * numsuc)
192 cur = data[off:off + s]
192 cur = data[off:off + s]
193 sucs = _unpack(_fm0node * numsuc, cur)
193 sucs = _unpack(_fm0node * numsuc, cur)
194 off += s
194 off += s
195 # read metadata
195 # read metadata
196 # (metadata will be decoded on demand)
196 # (metadata will be decoded on demand)
197 metadata = data[off:off + mdsize]
197 metadata = data[off:off + mdsize]
198 if len(metadata) != mdsize:
198 if len(metadata) != mdsize:
199 raise error.Abort(_('parsing obsolete marker: metadata is too '
199 raise error.Abort(_('parsing obsolete marker: metadata is too '
200 'short, %d bytes expected, got %d')
200 'short, %d bytes expected, got %d')
201 % (mdsize, len(metadata)))
201 % (mdsize, len(metadata)))
202 off += mdsize
202 off += mdsize
203 metadata = _fm0decodemeta(metadata)
203 metadata = _fm0decodemeta(metadata)
204 try:
204 try:
205 when, offset = metadata.pop('date', '0 0').split(' ')
205 when, offset = metadata.pop('date', '0 0').split(' ')
206 date = float(when), int(offset)
206 date = float(when), int(offset)
207 except ValueError:
207 except ValueError:
208 date = (0., 0)
208 date = (0., 0)
209 parents = None
209 parents = None
210 if 'p2' in metadata:
210 if 'p2' in metadata:
211 parents = (metadata.pop('p1', None), metadata.pop('p2', None))
211 parents = (metadata.pop('p1', None), metadata.pop('p2', None))
212 elif 'p1' in metadata:
212 elif 'p1' in metadata:
213 parents = (metadata.pop('p1', None),)
213 parents = (metadata.pop('p1', None),)
214 elif 'p0' in metadata:
214 elif 'p0' in metadata:
215 parents = ()
215 parents = ()
216 if parents is not None:
216 if parents is not None:
217 try:
217 try:
218 parents = tuple(node.bin(p) for p in parents)
218 parents = tuple(node.bin(p) for p in parents)
219 # if parent content is not a nodeid, drop the data
219 # if parent content is not a nodeid, drop the data
220 for p in parents:
220 for p in parents:
221 if len(p) != 20:
221 if len(p) != 20:
222 parents = None
222 parents = None
223 break
223 break
224 except TypeError:
224 except TypeError:
225 # if content cannot be translated to nodeid drop the data.
225 # if content cannot be translated to nodeid drop the data.
226 parents = None
226 parents = None
227
227
228 metadata = tuple(sorted(metadata.iteritems()))
228 metadata = tuple(sorted(metadata.iteritems()))
229
229
230 yield (pre, sucs, flags, metadata, date, parents)
230 yield (pre, sucs, flags, metadata, date, parents)
231
231
232 def _fm0encodeonemarker(marker):
232 def _fm0encodeonemarker(marker):
233 pre, sucs, flags, metadata, date, parents = marker
233 pre, sucs, flags, metadata, date, parents = marker
234 if flags & usingsha256:
234 if flags & usingsha256:
235 raise error.Abort(_('cannot handle sha256 with old obsstore format'))
235 raise error.Abort(_('cannot handle sha256 with old obsstore format'))
236 metadata = dict(metadata)
236 metadata = dict(metadata)
237 time, tz = date
237 time, tz = date
238 metadata['date'] = '%r %i' % (time, tz)
238 metadata['date'] = '%r %i' % (time, tz)
239 if parents is not None:
239 if parents is not None:
240 if not parents:
240 if not parents:
241 # mark that we explicitly recorded no parents
241 # mark that we explicitly recorded no parents
242 metadata['p0'] = ''
242 metadata['p0'] = ''
243 for i, p in enumerate(parents, 1):
243 for i, p in enumerate(parents, 1):
244 metadata['p%i' % i] = node.hex(p)
244 metadata['p%i' % i] = node.hex(p)
245 metadata = _fm0encodemeta(metadata)
245 metadata = _fm0encodemeta(metadata)
246 numsuc = len(sucs)
246 numsuc = len(sucs)
247 format = _fm0fixed + (_fm0node * numsuc)
247 format = _fm0fixed + (_fm0node * numsuc)
248 data = [numsuc, len(metadata), flags, pre]
248 data = [numsuc, len(metadata), flags, pre]
249 data.extend(sucs)
249 data.extend(sucs)
250 return _pack(format, *data) + metadata
250 return _pack(format, *data) + metadata
251
251
252 def _fm0encodemeta(meta):
252 def _fm0encodemeta(meta):
253 """Return encoded metadata string to string mapping.
253 """Return encoded metadata string to string mapping.
254
254
255 Assume no ':' in key and no '\0' in both key and value."""
255 Assume no ':' in key and no '\0' in both key and value."""
256 for key, value in meta.iteritems():
256 for key, value in meta.iteritems():
257 if ':' in key or '\0' in key:
257 if ':' in key or '\0' in key:
258 raise ValueError("':' and '\0' are forbidden in metadata key'")
258 raise ValueError("':' and '\0' are forbidden in metadata key'")
259 if '\0' in value:
259 if '\0' in value:
260 raise ValueError("':' is forbidden in metadata value'")
260 raise ValueError("':' is forbidden in metadata value'")
261 return '\0'.join(['%s:%s' % (k, meta[k]) for k in sorted(meta)])
261 return '\0'.join(['%s:%s' % (k, meta[k]) for k in sorted(meta)])
262
262
263 def _fm0decodemeta(data):
263 def _fm0decodemeta(data):
264 """Return string to string dictionary from encoded version."""
264 """Return string to string dictionary from encoded version."""
265 d = {}
265 d = {}
266 for l in data.split('\0'):
266 for l in data.split('\0'):
267 if l:
267 if l:
268 key, value = l.split(':')
268 key, value = l.split(':')
269 d[key] = value
269 d[key] = value
270 return d
270 return d
271
271
272 ## Parsing and writing of version "1"
272 ## Parsing and writing of version "1"
273 #
273 #
274 # The header is followed by the markers. Each marker is made of:
274 # The header is followed by the markers. Each marker is made of:
275 #
275 #
276 # - uint32: total size of the marker (including this field)
276 # - uint32: total size of the marker (including this field)
277 #
277 #
278 # - float64: date in seconds since epoch
278 # - float64: date in seconds since epoch
279 #
279 #
280 # - int16: timezone offset in minutes
280 # - int16: timezone offset in minutes
281 #
281 #
282 # - uint16: a bit field. It is reserved for flags used in common
282 # - uint16: a bit field. It is reserved for flags used in common
283 # obsolete marker operations, to avoid repeated decoding of metadata
283 # obsolete marker operations, to avoid repeated decoding of metadata
284 # entries.
284 # entries.
285 #
285 #
286 # - uint8: number of successors "N", can be zero.
286 # - uint8: number of successors "N", can be zero.
287 #
287 #
288 # - uint8: number of parents "P", can be zero.
288 # - uint8: number of parents "P", can be zero.
289 #
289 #
290 # 0: parents data stored but no parent,
290 # 0: parents data stored but no parent,
291 # 1: one parent stored,
291 # 1: one parent stored,
292 # 2: two parents stored,
292 # 2: two parents stored,
293 # 3: no parent data stored
293 # 3: no parent data stored
294 #
294 #
295 # - uint8: number of metadata entries M
295 # - uint8: number of metadata entries M
296 #
296 #
297 # - 20 or 32 bytes: precursor changeset identifier.
297 # - 20 or 32 bytes: precursor changeset identifier.
298 #
298 #
299 # - N*(20 or 32) bytes: successors changesets identifiers.
299 # - N*(20 or 32) bytes: successors changesets identifiers.
300 #
300 #
301 # - P*(20 or 32) bytes: parents of the precursors changesets.
301 # - P*(20 or 32) bytes: parents of the precursors changesets.
302 #
302 #
303 # - M*(uint8, uint8): size of all metadata entries (key and value)
303 # - M*(uint8, uint8): size of all metadata entries (key and value)
304 #
304 #
305 # - remaining bytes: the metadata, each (key, value) pair after the other.
305 # - remaining bytes: the metadata, each (key, value) pair after the other.
306 _fm1version = 1
306 _fm1version = 1
307 _fm1fixed = '>IdhHBBB20s'
307 _fm1fixed = '>IdhHBBB20s'
308 _fm1nodesha1 = '20s'
308 _fm1nodesha1 = '20s'
309 _fm1nodesha256 = '32s'
309 _fm1nodesha256 = '32s'
310 _fm1nodesha1size = _calcsize(_fm1nodesha1)
310 _fm1nodesha1size = _calcsize(_fm1nodesha1)
311 _fm1nodesha256size = _calcsize(_fm1nodesha256)
311 _fm1nodesha256size = _calcsize(_fm1nodesha256)
312 _fm1fsize = _calcsize(_fm1fixed)
312 _fm1fsize = _calcsize(_fm1fixed)
313 _fm1parentnone = 3
313 _fm1parentnone = 3
314 _fm1parentshift = 14
314 _fm1parentshift = 14
315 _fm1parentmask = (_fm1parentnone << _fm1parentshift)
315 _fm1parentmask = (_fm1parentnone << _fm1parentshift)
316 _fm1metapair = 'BB'
316 _fm1metapair = 'BB'
317 _fm1metapairsize = _calcsize('BB')
317 _fm1metapairsize = _calcsize('BB')
318
318
319 def _fm1purereadmarkers(data, off):
319 def _fm1purereadmarkers(data, off):
320 # make some global constants local for performance
320 # make some global constants local for performance
321 noneflag = _fm1parentnone
321 noneflag = _fm1parentnone
322 sha2flag = usingsha256
322 sha2flag = usingsha256
323 sha1size = _fm1nodesha1size
323 sha1size = _fm1nodesha1size
324 sha2size = _fm1nodesha256size
324 sha2size = _fm1nodesha256size
325 sha1fmt = _fm1nodesha1
325 sha1fmt = _fm1nodesha1
326 sha2fmt = _fm1nodesha256
326 sha2fmt = _fm1nodesha256
327 metasize = _fm1metapairsize
327 metasize = _fm1metapairsize
328 metafmt = _fm1metapair
328 metafmt = _fm1metapair
329 fsize = _fm1fsize
329 fsize = _fm1fsize
330 unpack = _unpack
330 unpack = _unpack
331
331
332 # Loop on markers
332 # Loop on markers
333 stop = len(data) - _fm1fsize
333 stop = len(data) - _fm1fsize
334 ufixed = struct.Struct(_fm1fixed).unpack
334 ufixed = struct.Struct(_fm1fixed).unpack
335
335
336 while off <= stop:
336 while off <= stop:
337 # read fixed part
337 # read fixed part
338 o1 = off + fsize
338 o1 = off + fsize
339 t, secs, tz, flags, numsuc, numpar, nummeta, prec = ufixed(data[off:o1])
339 t, secs, tz, flags, numsuc, numpar, nummeta, prec = ufixed(data[off:o1])
340
340
341 if flags & sha2flag:
341 if flags & sha2flag:
342 # FIXME: prec was read as a SHA1, needs to be amended
342 # FIXME: prec was read as a SHA1, needs to be amended
343
343
344 # read 0 or more successors
344 # read 0 or more successors
345 if numsuc == 1:
345 if numsuc == 1:
346 o2 = o1 + sha2size
346 o2 = o1 + sha2size
347 sucs = (data[o1:o2],)
347 sucs = (data[o1:o2],)
348 else:
348 else:
349 o2 = o1 + sha2size * numsuc
349 o2 = o1 + sha2size * numsuc
350 sucs = unpack(sha2fmt * numsuc, data[o1:o2])
350 sucs = unpack(sha2fmt * numsuc, data[o1:o2])
351
351
352 # read parents
352 # read parents
353 if numpar == noneflag:
353 if numpar == noneflag:
354 o3 = o2
354 o3 = o2
355 parents = None
355 parents = None
356 elif numpar == 1:
356 elif numpar == 1:
357 o3 = o2 + sha2size
357 o3 = o2 + sha2size
358 parents = (data[o2:o3],)
358 parents = (data[o2:o3],)
359 else:
359 else:
360 o3 = o2 + sha2size * numpar
360 o3 = o2 + sha2size * numpar
361 parents = unpack(sha2fmt * numpar, data[o2:o3])
361 parents = unpack(sha2fmt * numpar, data[o2:o3])
362 else:
362 else:
363 # read 0 or more successors
363 # read 0 or more successors
364 if numsuc == 1:
364 if numsuc == 1:
365 o2 = o1 + sha1size
365 o2 = o1 + sha1size
366 sucs = (data[o1:o2],)
366 sucs = (data[o1:o2],)
367 else:
367 else:
368 o2 = o1 + sha1size * numsuc
368 o2 = o1 + sha1size * numsuc
369 sucs = unpack(sha1fmt * numsuc, data[o1:o2])
369 sucs = unpack(sha1fmt * numsuc, data[o1:o2])
370
370
371 # read parents
371 # read parents
372 if numpar == noneflag:
372 if numpar == noneflag:
373 o3 = o2
373 o3 = o2
374 parents = None
374 parents = None
375 elif numpar == 1:
375 elif numpar == 1:
376 o3 = o2 + sha1size
376 o3 = o2 + sha1size
377 parents = (data[o2:o3],)
377 parents = (data[o2:o3],)
378 else:
378 else:
379 o3 = o2 + sha1size * numpar
379 o3 = o2 + sha1size * numpar
380 parents = unpack(sha1fmt * numpar, data[o2:o3])
380 parents = unpack(sha1fmt * numpar, data[o2:o3])
381
381
382 # read metadata
382 # read metadata
383 off = o3 + metasize * nummeta
383 off = o3 + metasize * nummeta
384 metapairsize = unpack('>' + (metafmt * nummeta), data[o3:off])
384 metapairsize = unpack('>' + (metafmt * nummeta), data[o3:off])
385 metadata = []
385 metadata = []
386 for idx in xrange(0, len(metapairsize), 2):
386 for idx in xrange(0, len(metapairsize), 2):
387 o1 = off + metapairsize[idx]
387 o1 = off + metapairsize[idx]
388 o2 = o1 + metapairsize[idx + 1]
388 o2 = o1 + metapairsize[idx + 1]
389 metadata.append((data[off:o1], data[o1:o2]))
389 metadata.append((data[off:o1], data[o1:o2]))
390 off = o2
390 off = o2
391
391
392 yield (prec, sucs, flags, tuple(metadata), (secs, tz * 60), parents)
392 yield (prec, sucs, flags, tuple(metadata), (secs, tz * 60), parents)
393
393
394 def _fm1encodeonemarker(marker):
394 def _fm1encodeonemarker(marker):
395 pre, sucs, flags, metadata, date, parents = marker
395 pre, sucs, flags, metadata, date, parents = marker
396 # determine node size
396 # determine node size
397 _fm1node = _fm1nodesha1
397 _fm1node = _fm1nodesha1
398 if flags & usingsha256:
398 if flags & usingsha256:
399 _fm1node = _fm1nodesha256
399 _fm1node = _fm1nodesha256
400 numsuc = len(sucs)
400 numsuc = len(sucs)
401 numextranodes = numsuc
401 numextranodes = numsuc
402 if parents is None:
402 if parents is None:
403 numpar = _fm1parentnone
403 numpar = _fm1parentnone
404 else:
404 else:
405 numpar = len(parents)
405 numpar = len(parents)
406 numextranodes += numpar
406 numextranodes += numpar
407 formatnodes = _fm1node * numextranodes
407 formatnodes = _fm1node * numextranodes
408 formatmeta = _fm1metapair * len(metadata)
408 formatmeta = _fm1metapair * len(metadata)
409 format = _fm1fixed + formatnodes + formatmeta
409 format = _fm1fixed + formatnodes + formatmeta
410 # tz is stored in minutes so we divide by 60
410 # tz is stored in minutes so we divide by 60
411 tz = date[1]//60
411 tz = date[1]//60
412 data = [None, date[0], tz, flags, numsuc, numpar, len(metadata), pre]
412 data = [None, date[0], tz, flags, numsuc, numpar, len(metadata), pre]
413 data.extend(sucs)
413 data.extend(sucs)
414 if parents is not None:
414 if parents is not None:
415 data.extend(parents)
415 data.extend(parents)
416 totalsize = _calcsize(format)
416 totalsize = _calcsize(format)
417 for key, value in metadata:
417 for key, value in metadata:
418 lk = len(key)
418 lk = len(key)
419 lv = len(value)
419 lv = len(value)
420 data.append(lk)
420 data.append(lk)
421 data.append(lv)
421 data.append(lv)
422 totalsize += lk + lv
422 totalsize += lk + lv
423 data[0] = totalsize
423 data[0] = totalsize
424 data = [_pack(format, *data)]
424 data = [_pack(format, *data)]
425 for key, value in metadata:
425 for key, value in metadata:
426 data.append(key)
426 data.append(key)
427 data.append(value)
427 data.append(value)
428 return ''.join(data)
428 return ''.join(data)
429
429
430 def _fm1readmarkers(data, off):
430 def _fm1readmarkers(data, off):
431 native = getattr(parsers, 'fm1readmarkers', None)
431 native = getattr(parsers, 'fm1readmarkers', None)
432 if not native:
432 if not native:
433 return _fm1purereadmarkers(data, off)
433 return _fm1purereadmarkers(data, off)
434 stop = len(data) - _fm1fsize
434 stop = len(data) - _fm1fsize
435 return native(data, off, stop)
435 return native(data, off, stop)
436
436
437 # mapping to read/write various marker formats
437 # mapping to read/write various marker formats
438 # <version> -> (decoder, encoder)
438 # <version> -> (decoder, encoder)
439 formats = {_fm0version: (_fm0readmarkers, _fm0encodeonemarker),
439 formats = {_fm0version: (_fm0readmarkers, _fm0encodeonemarker),
440 _fm1version: (_fm1readmarkers, _fm1encodeonemarker)}
440 _fm1version: (_fm1readmarkers, _fm1encodeonemarker)}
441
441
442 def _readmarkerversion(data):
442 def _readmarkerversion(data):
443 return _unpack('>B', data[0:1])[0]
443 return _unpack('>B', data[0:1])[0]
444
444
445 @util.nogc
445 @util.nogc
446 def _readmarkers(data):
446 def _readmarkers(data):
447 """Read and enumerate markers from raw data"""
447 """Read and enumerate markers from raw data"""
448 diskversion = _readmarkerversion(data)
448 diskversion = _readmarkerversion(data)
449 off = 1
449 off = 1
450 if diskversion not in formats:
450 if diskversion not in formats:
451 msg = _('parsing obsolete marker: unknown version %r') % diskversion
451 msg = _('parsing obsolete marker: unknown version %r') % diskversion
452 raise error.UnknownVersion(msg, version=diskversion)
452 raise error.UnknownVersion(msg, version=diskversion)
453 return diskversion, formats[diskversion][0](data, off)
453 return diskversion, formats[diskversion][0](data, off)
454
454
455 def encodeheader(version=_fm0version):
455 def encodeheader(version=_fm0version):
456 return _pack('>B', version)
456 return _pack('>B', version)
457
457
458 def encodemarkers(markers, addheader=False, version=_fm0version):
458 def encodemarkers(markers, addheader=False, version=_fm0version):
459 # Kept separate from flushmarkers(), it will be reused for
459 # Kept separate from flushmarkers(), it will be reused for
460 # markers exchange.
460 # markers exchange.
461 encodeone = formats[version][1]
461 encodeone = formats[version][1]
462 if addheader:
462 if addheader:
463 yield encodeheader(version)
463 yield encodeheader(version)
464 for marker in markers:
464 for marker in markers:
465 yield encodeone(marker)
465 yield encodeone(marker)
466
466
467
467
468 class marker(object):
468 class marker(object):
469 """Wrap obsolete marker raw data"""
469 """Wrap obsolete marker raw data"""
470
470
471 def __init__(self, repo, data):
471 def __init__(self, repo, data):
472 # the repo argument will be used to create changectx in later version
472 # the repo argument will be used to create changectx in later version
473 self._repo = repo
473 self._repo = repo
474 self._data = data
474 self._data = data
475 self._decodedmeta = None
475 self._decodedmeta = None
476
476
477 def __hash__(self):
477 def __hash__(self):
478 return hash(self._data)
478 return hash(self._data)
479
479
480 def __eq__(self, other):
480 def __eq__(self, other):
481 if type(other) != type(self):
481 if type(other) != type(self):
482 return False
482 return False
483 return self._data == other._data
483 return self._data == other._data
484
484
485 def precnode(self):
485 def precnode(self):
486 """Precursor changeset node identifier"""
486 """Precursor changeset node identifier"""
487 return self._data[0]
487 return self._data[0]
488
488
489 def succnodes(self):
489 def succnodes(self):
490 """List of successor changesets node identifiers"""
490 """List of successor changesets node identifiers"""
491 return self._data[1]
491 return self._data[1]
492
492
493 def parentnodes(self):
493 def parentnodes(self):
494 """Parents of the precursors (None if not recorded)"""
494 """Parents of the precursors (None if not recorded)"""
495 return self._data[5]
495 return self._data[5]
496
496
497 def metadata(self):
497 def metadata(self):
498 """Decoded metadata dictionary"""
498 """Decoded metadata dictionary"""
499 return dict(self._data[3])
499 return dict(self._data[3])
500
500
501 def date(self):
501 def date(self):
502 """Creation date as (unixtime, offset)"""
502 """Creation date as (unixtime, offset)"""
503 return self._data[4]
503 return self._data[4]
504
504
505 def flags(self):
505 def flags(self):
506 """The flags field of the marker"""
506 """The flags field of the marker"""
507 return self._data[2]
507 return self._data[2]
508
508
509 @util.nogc
509 @util.nogc
510 def _addsuccessors(successors, markers):
510 def _addsuccessors(successors, markers):
511 for mark in markers:
511 for mark in markers:
512 successors.setdefault(mark[0], set()).add(mark)
512 successors.setdefault(mark[0], set()).add(mark)
513
513
514 @util.nogc
514 @util.nogc
515 def _addprecursors(precursors, markers):
515 def _addprecursors(precursors, markers):
516 for mark in markers:
516 for mark in markers:
517 for suc in mark[1]:
517 for suc in mark[1]:
518 precursors.setdefault(suc, set()).add(mark)
518 precursors.setdefault(suc, set()).add(mark)
519
519
520 @util.nogc
520 @util.nogc
521 def _addchildren(children, markers):
521 def _addchildren(children, markers):
522 for mark in markers:
522 for mark in markers:
523 parents = mark[5]
523 parents = mark[5]
524 if parents is not None:
524 if parents is not None:
525 for p in parents:
525 for p in parents:
526 children.setdefault(p, set()).add(mark)
526 children.setdefault(p, set()).add(mark)
527
527
528 def _checkinvalidmarkers(markers):
528 def _checkinvalidmarkers(markers):
529 """search for marker with invalid data and raise error if needed
529 """search for marker with invalid data and raise error if needed
530
530
531 Exist as a separated function to allow the evolve extension for a more
531 Exist as a separated function to allow the evolve extension for a more
532 subtle handling.
532 subtle handling.
533 """
533 """
534 for mark in markers:
534 for mark in markers:
535 if node.nullid in mark[1]:
535 if node.nullid in mark[1]:
536 raise error.Abort(_('bad obsolescence marker detected: '
536 raise error.Abort(_('bad obsolescence marker detected: '
537 'invalid successors nullid'))
537 'invalid successors nullid'))
538
538
539 class obsstore(object):
539 class obsstore(object):
540 """Store obsolete markers
540 """Store obsolete markers
541
541
542 Markers can be accessed with two mappings:
542 Markers can be accessed with two mappings:
543 - precursors[x] -> set(markers on precursors edges of x)
543 - precursors[x] -> set(markers on precursors edges of x)
544 - successors[x] -> set(markers on successors edges of x)
544 - successors[x] -> set(markers on successors edges of x)
545 - children[x] -> set(markers on precursors edges of children(x)
545 - children[x] -> set(markers on precursors edges of children(x)
546 """
546 """
547
547
548 fields = ('prec', 'succs', 'flag', 'meta', 'date', 'parents')
548 fields = ('prec', 'succs', 'flag', 'meta', 'date', 'parents')
549 # prec: nodeid, precursor changesets
549 # prec: nodeid, precursor changesets
550 # succs: tuple of nodeid, successor changesets (0-N length)
550 # succs: tuple of nodeid, successor changesets (0-N length)
551 # flag: integer, flag field carrying modifier for the markers (see doc)
551 # flag: integer, flag field carrying modifier for the markers (see doc)
552 # meta: binary blob, encoded metadata dictionary
552 # meta: binary blob, encoded metadata dictionary
553 # date: (float, int) tuple, date of marker creation
553 # date: (float, int) tuple, date of marker creation
554 # parents: (tuple of nodeid) or None, parents of precursors
554 # parents: (tuple of nodeid) or None, parents of precursors
555 # None is used when no data has been recorded
555 # None is used when no data has been recorded
556
556
557 def __init__(self, svfs, defaultformat=_fm1version, readonly=False):
557 def __init__(self, svfs, defaultformat=_fm1version, readonly=False):
558 # caches for various obsolescence related cache
558 # caches for various obsolescence related cache
559 self.caches = {}
559 self.caches = {}
560 self.svfs = svfs
560 self.svfs = svfs
561 self._defaultformat = defaultformat
561 self._defaultformat = defaultformat
562 self._readonly = readonly
562 self._readonly = readonly
563
563
564 def __iter__(self):
564 def __iter__(self):
565 return iter(self._all)
565 return iter(self._all)
566
566
567 def __len__(self):
567 def __len__(self):
568 return len(self._all)
568 return len(self._all)
569
569
570 def __nonzero__(self):
570 def __nonzero__(self):
571 if not self._cached('_all'):
571 if not self._cached('_all'):
572 try:
572 try:
573 return self.svfs.stat('obsstore').st_size > 1
573 return self.svfs.stat('obsstore').st_size > 1
574 except OSError as inst:
574 except OSError as inst:
575 if inst.errno != errno.ENOENT:
575 if inst.errno != errno.ENOENT:
576 raise
576 raise
577 # just build an empty _all list if no obsstore exists, which
577 # just build an empty _all list if no obsstore exists, which
578 # avoids further stat() syscalls
578 # avoids further stat() syscalls
579 pass
579 pass
580 return bool(self._all)
580 return bool(self._all)
581
581
582 __bool__ = __nonzero__
582 __bool__ = __nonzero__
583
583
584 @property
584 @property
585 def readonly(self):
585 def readonly(self):
586 """True if marker creation is disabled
586 """True if marker creation is disabled
587
587
588 Remove me in the future when obsolete marker is always on."""
588 Remove me in the future when obsolete marker is always on."""
589 return self._readonly
589 return self._readonly
590
590
591 def create(self, transaction, prec, succs=(), flag=0, parents=None,
591 def create(self, transaction, prec, succs=(), flag=0, parents=None,
592 date=None, metadata=None, ui=None):
592 date=None, metadata=None, ui=None):
593 """obsolete: add a new obsolete marker
593 """obsolete: add a new obsolete marker
594
594
595 * ensuring it is hashable
595 * ensuring it is hashable
596 * check mandatory metadata
596 * check mandatory metadata
597 * encode metadata
597 * encode metadata
598
598
599 If you are a human writing code creating marker you want to use the
599 If you are a human writing code creating marker you want to use the
600 `createmarkers` function in this module instead.
600 `createmarkers` function in this module instead.
601
601
602 return True if a new marker have been added, False if the markers
602 return True if a new marker have been added, False if the markers
603 already existed (no op).
603 already existed (no op).
604 """
604 """
605 if metadata is None:
605 if metadata is None:
606 metadata = {}
606 metadata = {}
607 if date is None:
607 if date is None:
608 if 'date' in metadata:
608 if 'date' in metadata:
609 # as a courtesy for out-of-tree extensions
609 # as a courtesy for out-of-tree extensions
610 date = util.parsedate(metadata.pop('date'))
610 date = util.parsedate(metadata.pop('date'))
611 elif ui is not None:
611 elif ui is not None:
612 date = ui.configdate('devel', 'default-date')
612 date = ui.configdate('devel', 'default-date')
613 if date is None:
613 if date is None:
614 date = util.makedate()
614 date = util.makedate()
615 else:
615 else:
616 date = util.makedate()
616 date = util.makedate()
617 if len(prec) != 20:
617 if len(prec) != 20:
618 raise ValueError(prec)
618 raise ValueError(prec)
619 for succ in succs:
619 for succ in succs:
620 if len(succ) != 20:
620 if len(succ) != 20:
621 raise ValueError(succ)
621 raise ValueError(succ)
622 if prec in succs:
622 if prec in succs:
623 raise ValueError(_('in-marker cycle with %s') % node.hex(prec))
623 raise ValueError(_('in-marker cycle with %s') % node.hex(prec))
624
624
625 metadata = tuple(sorted(metadata.iteritems()))
625 metadata = tuple(sorted(metadata.iteritems()))
626
626
627 marker = (str(prec), tuple(succs), int(flag), metadata, date, parents)
627 marker = (str(prec), tuple(succs), int(flag), metadata, date, parents)
628 return bool(self.add(transaction, [marker]))
628 return bool(self.add(transaction, [marker]))
629
629
630 def add(self, transaction, markers):
630 def add(self, transaction, markers):
631 """Add new markers to the store
631 """Add new markers to the store
632
632
633 Take care of filtering duplicate.
633 Take care of filtering duplicate.
634 Return the number of new marker."""
634 Return the number of new marker."""
635 if self._readonly:
635 if self._readonly:
636 raise error.Abort(_('creating obsolete markers is not enabled on '
636 raise error.Abort(_('creating obsolete markers is not enabled on '
637 'this repo'))
637 'this repo'))
638 known = set(self._all)
638 known = set(self._all)
639 new = []
639 new = []
640 for m in markers:
640 for m in markers:
641 if m not in known:
641 if m not in known:
642 known.add(m)
642 known.add(m)
643 new.append(m)
643 new.append(m)
644 if new:
644 if new:
645 f = self.svfs('obsstore', 'ab')
645 f = self.svfs('obsstore', 'ab')
646 try:
646 try:
647 offset = f.tell()
647 offset = f.tell()
648 transaction.add('obsstore', offset)
648 transaction.add('obsstore', offset)
649 # offset == 0: new file - add the version header
649 # offset == 0: new file - add the version header
650 for bytes in encodemarkers(new, offset == 0, self._version):
650 for bytes in encodemarkers(new, offset == 0, self._version):
651 f.write(bytes)
651 f.write(bytes)
652 finally:
652 finally:
653 # XXX: f.close() == filecache invalidation == obsstore rebuilt.
653 # XXX: f.close() == filecache invalidation == obsstore rebuilt.
654 # call 'filecacheentry.refresh()' here
654 # call 'filecacheentry.refresh()' here
655 f.close()
655 f.close()
656 self._addmarkers(new)
656 self._addmarkers(new)
657 # new marker *may* have changed several set. invalidate the cache.
657 # new marker *may* have changed several set. invalidate the cache.
658 self.caches.clear()
658 self.caches.clear()
659 # records the number of new markers for the transaction hooks
659 # records the number of new markers for the transaction hooks
660 previous = int(transaction.hookargs.get('new_obsmarkers', '0'))
660 previous = int(transaction.hookargs.get('new_obsmarkers', '0'))
661 transaction.hookargs['new_obsmarkers'] = str(previous + len(new))
661 transaction.hookargs['new_obsmarkers'] = str(previous + len(new))
662 return len(new)
662 return len(new)
663
663
664 def mergemarkers(self, transaction, data):
664 def mergemarkers(self, transaction, data):
665 """merge a binary stream of markers inside the obsstore
665 """merge a binary stream of markers inside the obsstore
666
666
667 Returns the number of new markers added."""
667 Returns the number of new markers added."""
668 version, markers = _readmarkers(data)
668 version, markers = _readmarkers(data)
669 return self.add(transaction, markers)
669 return self.add(transaction, markers)
670
670
671 @propertycache
671 @propertycache
672 def _data(self):
672 def _data(self):
673 return self.svfs.tryread('obsstore')
673 return self.svfs.tryread('obsstore')
674
674
675 @propertycache
675 @propertycache
676 def _version(self):
676 def _version(self):
677 if len(self._data) >= 1:
677 if len(self._data) >= 1:
678 return _readmarkerversion(self._data)
678 return _readmarkerversion(self._data)
679 else:
679 else:
680 return self._defaultformat
680 return self._defaultformat
681
681
682 @propertycache
682 @propertycache
683 def _all(self):
683 def _all(self):
684 data = self._data
684 data = self._data
685 if not data:
685 if not data:
686 return []
686 return []
687 self._version, markers = _readmarkers(data)
687 self._version, markers = _readmarkers(data)
688 markers = list(markers)
688 markers = list(markers)
689 _checkinvalidmarkers(markers)
689 _checkinvalidmarkers(markers)
690 return markers
690 return markers
691
691
692 @propertycache
692 @propertycache
693 def successors(self):
693 def successors(self):
694 successors = {}
694 successors = {}
695 _addsuccessors(successors, self._all)
695 _addsuccessors(successors, self._all)
696 return successors
696 return successors
697
697
698 @propertycache
698 @propertycache
699 def precursors(self):
699 def precursors(self):
700 precursors = {}
700 precursors = {}
701 _addprecursors(precursors, self._all)
701 _addprecursors(precursors, self._all)
702 return precursors
702 return precursors
703
703
704 @propertycache
704 @propertycache
705 def children(self):
705 def children(self):
706 children = {}
706 children = {}
707 _addchildren(children, self._all)
707 _addchildren(children, self._all)
708 return children
708 return children
709
709
710 def _cached(self, attr):
710 def _cached(self, attr):
711 return attr in self.__dict__
711 return attr in self.__dict__
712
712
713 def _addmarkers(self, markers):
713 def _addmarkers(self, markers):
714 markers = list(markers) # to allow repeated iteration
714 markers = list(markers) # to allow repeated iteration
715 self._all.extend(markers)
715 self._all.extend(markers)
716 if self._cached('successors'):
716 if self._cached('successors'):
717 _addsuccessors(self.successors, markers)
717 _addsuccessors(self.successors, markers)
718 if self._cached('precursors'):
718 if self._cached('precursors'):
719 _addprecursors(self.precursors, markers)
719 _addprecursors(self.precursors, markers)
720 if self._cached('children'):
720 if self._cached('children'):
721 _addchildren(self.children, markers)
721 _addchildren(self.children, markers)
722 _checkinvalidmarkers(markers)
722 _checkinvalidmarkers(markers)
723
723
724 def relevantmarkers(self, nodes):
724 def relevantmarkers(self, nodes):
725 """return a set of all obsolescence markers relevant to a set of nodes.
725 """return a set of all obsolescence markers relevant to a set of nodes.
726
726
727 "relevant" to a set of nodes mean:
727 "relevant" to a set of nodes mean:
728
728
729 - marker that use this changeset as successor
729 - marker that use this changeset as successor
730 - prune marker of direct children on this changeset
730 - prune marker of direct children on this changeset
731 - recursive application of the two rules on precursors of these markers
731 - recursive application of the two rules on precursors of these markers
732
732
733 It is a set so you cannot rely on order."""
733 It is a set so you cannot rely on order."""
734
734
735 pendingnodes = set(nodes)
735 pendingnodes = set(nodes)
736 seenmarkers = set()
736 seenmarkers = set()
737 seennodes = set(pendingnodes)
737 seennodes = set(pendingnodes)
738 precursorsmarkers = self.precursors
738 precursorsmarkers = self.precursors
739 succsmarkers = self.successors
739 succsmarkers = self.successors
740 children = self.children
740 children = self.children
741 while pendingnodes:
741 while pendingnodes:
742 direct = set()
742 direct = set()
743 for current in pendingnodes:
743 for current in pendingnodes:
744 direct.update(precursorsmarkers.get(current, ()))
744 direct.update(precursorsmarkers.get(current, ()))
745 pruned = [m for m in children.get(current, ()) if not m[1]]
745 pruned = [m for m in children.get(current, ()) if not m[1]]
746 direct.update(pruned)
746 direct.update(pruned)
747 pruned = [m for m in succsmarkers.get(current, ()) if not m[1]]
747 pruned = [m for m in succsmarkers.get(current, ()) if not m[1]]
748 direct.update(pruned)
748 direct.update(pruned)
749 direct -= seenmarkers
749 direct -= seenmarkers
750 pendingnodes = set([m[0] for m in direct])
750 pendingnodes = set([m[0] for m in direct])
751 seenmarkers |= direct
751 seenmarkers |= direct
752 pendingnodes -= seennodes
752 pendingnodes -= seennodes
753 seennodes |= pendingnodes
753 seennodes |= pendingnodes
754 return seenmarkers
754 return seenmarkers
755
755
756 def makestore(ui, repo):
757 """Create an obsstore instance from a repo."""
758 # read default format for new obsstore.
759 # developer config: format.obsstore-version
760 defaultformat = ui.configint('format', 'obsstore-version', None)
761 # rely on obsstore class default when possible.
762 kwargs = {}
763 if defaultformat is not None:
764 kwargs['defaultformat'] = defaultformat
765 readonly = not isenabled(repo, createmarkersopt)
766 store = obsstore(repo.svfs, readonly=readonly, **kwargs)
767 if store and readonly:
768 ui.warn(_('obsolete feature not enabled but %i markers found!\n')
769 % len(list(store)))
770 return store
771
756 def _filterprunes(markers):
772 def _filterprunes(markers):
757 """return a set with no prune markers"""
773 """return a set with no prune markers"""
758 return set(m for m in markers if m[1])
774 return set(m for m in markers if m[1])
759
775
760 def exclusivemarkers(repo, nodes):
776 def exclusivemarkers(repo, nodes):
761 """set of markers relevant to "nodes" but no other locally-known nodes
777 """set of markers relevant to "nodes" but no other locally-known nodes
762
778
763 This function compute the set of markers "exclusive" to a locally-known
779 This function compute the set of markers "exclusive" to a locally-known
764 node. This means we walk the markers starting from <nodes> until we reach a
780 node. This means we walk the markers starting from <nodes> until we reach a
765 locally-known precursors outside of <nodes>. Element of <nodes> with
781 locally-known precursors outside of <nodes>. Element of <nodes> with
766 locally-known successors outside of <nodes> are ignored (since their
782 locally-known successors outside of <nodes> are ignored (since their
767 precursors markers are also relevant to these successors).
783 precursors markers are also relevant to these successors).
768
784
769 For example:
785 For example:
770
786
771 # (A0 rewritten as A1)
787 # (A0 rewritten as A1)
772 #
788 #
773 # A0 <-1- A1 # Marker "1" is exclusive to A1
789 # A0 <-1- A1 # Marker "1" is exclusive to A1
774
790
775 or
791 or
776
792
777 # (A0 rewritten as AX; AX rewritten as A1; AX is unkown locally)
793 # (A0 rewritten as AX; AX rewritten as A1; AX is unkown locally)
778 #
794 #
779 # <-1- A0 <-2- AX <-3- A1 # Marker "2,3" are exclusive to A1
795 # <-1- A0 <-2- AX <-3- A1 # Marker "2,3" are exclusive to A1
780
796
781 or
797 or
782
798
783 # (A0 has unknown precursors, A0 rewritten as A1 and A2 (divergence))
799 # (A0 has unknown precursors, A0 rewritten as A1 and A2 (divergence))
784 #
800 #
785 # <-2- A1 # Marker "2" is exclusive to A0,A1
801 # <-2- A1 # Marker "2" is exclusive to A0,A1
786 # /
802 # /
787 # <-1- A0
803 # <-1- A0
788 # \
804 # \
789 # <-3- A2 # Marker "3" is exclusive to A0,A2
805 # <-3- A2 # Marker "3" is exclusive to A0,A2
790 #
806 #
791 # in addition:
807 # in addition:
792 #
808 #
793 # Markers "2,3" are exclusive to A1,A2
809 # Markers "2,3" are exclusive to A1,A2
794 # Markers "1,2,3" are exclusive to A0,A1,A2
810 # Markers "1,2,3" are exclusive to A0,A1,A2
795
811
796 See test/test-obsolete-bundle-strip.t for more examples.
812 See test/test-obsolete-bundle-strip.t for more examples.
797
813
798 An example usage is strip. When stripping a changeset, we also want to
814 An example usage is strip. When stripping a changeset, we also want to
799 strip the markers exclusive to this changeset. Otherwise we would have
815 strip the markers exclusive to this changeset. Otherwise we would have
800 "dangling"" obsolescence markers from its precursors: Obsolescence markers
816 "dangling"" obsolescence markers from its precursors: Obsolescence markers
801 marking a node as obsolete without any successors available locally.
817 marking a node as obsolete without any successors available locally.
802
818
803 As for relevant markers, the prune markers for children will be followed.
819 As for relevant markers, the prune markers for children will be followed.
804 Of course, they will only be followed if the pruned children is
820 Of course, they will only be followed if the pruned children is
805 locally-known. Since the prune markers are relevant to the pruned node.
821 locally-known. Since the prune markers are relevant to the pruned node.
806 However, while prune markers are considered relevant to the parent of the
822 However, while prune markers are considered relevant to the parent of the
807 pruned changesets, prune markers for locally-known changeset (with no
823 pruned changesets, prune markers for locally-known changeset (with no
808 successors) are considered exclusive to the pruned nodes. This allows
824 successors) are considered exclusive to the pruned nodes. This allows
809 to strip the prune markers (with the rest of the exclusive chain) alongside
825 to strip the prune markers (with the rest of the exclusive chain) alongside
810 the pruned changesets.
826 the pruned changesets.
811 """
827 """
812 # running on a filtered repository would be dangerous as markers could be
828 # running on a filtered repository would be dangerous as markers could be
813 # reported as exclusive when they are relevant for other filtered nodes.
829 # reported as exclusive when they are relevant for other filtered nodes.
814 unfi = repo.unfiltered()
830 unfi = repo.unfiltered()
815
831
816 # shortcut to various useful item
832 # shortcut to various useful item
817 nm = unfi.changelog.nodemap
833 nm = unfi.changelog.nodemap
818 precursorsmarkers = unfi.obsstore.precursors
834 precursorsmarkers = unfi.obsstore.precursors
819 successormarkers = unfi.obsstore.successors
835 successormarkers = unfi.obsstore.successors
820 childrenmarkers = unfi.obsstore.children
836 childrenmarkers = unfi.obsstore.children
821
837
822 # exclusive markers (return of the function)
838 # exclusive markers (return of the function)
823 exclmarkers = set()
839 exclmarkers = set()
824 # we need fast membership testing
840 # we need fast membership testing
825 nodes = set(nodes)
841 nodes = set(nodes)
826 # looking for head in the obshistory
842 # looking for head in the obshistory
827 #
843 #
828 # XXX we are ignoring all issues in regard with cycle for now.
844 # XXX we are ignoring all issues in regard with cycle for now.
829 stack = [n for n in nodes if not _filterprunes(successormarkers.get(n, ()))]
845 stack = [n for n in nodes if not _filterprunes(successormarkers.get(n, ()))]
830 stack.sort()
846 stack.sort()
831 # nodes already stacked
847 # nodes already stacked
832 seennodes = set(stack)
848 seennodes = set(stack)
833 while stack:
849 while stack:
834 current = stack.pop()
850 current = stack.pop()
835 # fetch precursors markers
851 # fetch precursors markers
836 markers = list(precursorsmarkers.get(current, ()))
852 markers = list(precursorsmarkers.get(current, ()))
837 # extend the list with prune markers
853 # extend the list with prune markers
838 for mark in successormarkers.get(current, ()):
854 for mark in successormarkers.get(current, ()):
839 if not mark[1]:
855 if not mark[1]:
840 markers.append(mark)
856 markers.append(mark)
841 # and markers from children (looking for prune)
857 # and markers from children (looking for prune)
842 for mark in childrenmarkers.get(current, ()):
858 for mark in childrenmarkers.get(current, ()):
843 if not mark[1]:
859 if not mark[1]:
844 markers.append(mark)
860 markers.append(mark)
845 # traverse the markers
861 # traverse the markers
846 for mark in markers:
862 for mark in markers:
847 if mark in exclmarkers:
863 if mark in exclmarkers:
848 # markers already selected
864 # markers already selected
849 continue
865 continue
850
866
851 # If the markers is about the current node, select it
867 # If the markers is about the current node, select it
852 #
868 #
853 # (this delay the addition of markers from children)
869 # (this delay the addition of markers from children)
854 if mark[1] or mark[0] == current:
870 if mark[1] or mark[0] == current:
855 exclmarkers.add(mark)
871 exclmarkers.add(mark)
856
872
857 # should we keep traversing through the precursors?
873 # should we keep traversing through the precursors?
858 prec = mark[0]
874 prec = mark[0]
859
875
860 # nodes in the stack or already processed
876 # nodes in the stack or already processed
861 if prec in seennodes:
877 if prec in seennodes:
862 continue
878 continue
863
879
864 # is this a locally known node ?
880 # is this a locally known node ?
865 known = prec in nm
881 known = prec in nm
866 # if locally-known and not in the <nodes> set the traversal
882 # if locally-known and not in the <nodes> set the traversal
867 # stop here.
883 # stop here.
868 if known and prec not in nodes:
884 if known and prec not in nodes:
869 continue
885 continue
870
886
871 # do not keep going if there are unselected markers pointing to this
887 # do not keep going if there are unselected markers pointing to this
872 # nodes. If we end up traversing these unselected markers later the
888 # nodes. If we end up traversing these unselected markers later the
873 # node will be taken care of at that point.
889 # node will be taken care of at that point.
874 precmarkers = _filterprunes(successormarkers.get(prec))
890 precmarkers = _filterprunes(successormarkers.get(prec))
875 if precmarkers.issubset(exclmarkers):
891 if precmarkers.issubset(exclmarkers):
876 seennodes.add(prec)
892 seennodes.add(prec)
877 stack.append(prec)
893 stack.append(prec)
878
894
879 return exclmarkers
895 return exclmarkers
880
896
881 def commonversion(versions):
897 def commonversion(versions):
882 """Return the newest version listed in both versions and our local formats.
898 """Return the newest version listed in both versions and our local formats.
883
899
884 Returns None if no common version exists.
900 Returns None if no common version exists.
885 """
901 """
886 versions.sort(reverse=True)
902 versions.sort(reverse=True)
887 # search for highest version known on both side
903 # search for highest version known on both side
888 for v in versions:
904 for v in versions:
889 if v in formats:
905 if v in formats:
890 return v
906 return v
891 return None
907 return None
892
908
893 # arbitrary picked to fit into 8K limit from HTTP server
909 # arbitrary picked to fit into 8K limit from HTTP server
894 # you have to take in account:
910 # you have to take in account:
895 # - the version header
911 # - the version header
896 # - the base85 encoding
912 # - the base85 encoding
897 _maxpayload = 5300
913 _maxpayload = 5300
898
914
899 def _pushkeyescape(markers):
915 def _pushkeyescape(markers):
900 """encode markers into a dict suitable for pushkey exchange
916 """encode markers into a dict suitable for pushkey exchange
901
917
902 - binary data is base85 encoded
918 - binary data is base85 encoded
903 - split in chunks smaller than 5300 bytes"""
919 - split in chunks smaller than 5300 bytes"""
904 keys = {}
920 keys = {}
905 parts = []
921 parts = []
906 currentlen = _maxpayload * 2 # ensure we create a new part
922 currentlen = _maxpayload * 2 # ensure we create a new part
907 for marker in markers:
923 for marker in markers:
908 nextdata = _fm0encodeonemarker(marker)
924 nextdata = _fm0encodeonemarker(marker)
909 if (len(nextdata) + currentlen > _maxpayload):
925 if (len(nextdata) + currentlen > _maxpayload):
910 currentpart = []
926 currentpart = []
911 currentlen = 0
927 currentlen = 0
912 parts.append(currentpart)
928 parts.append(currentpart)
913 currentpart.append(nextdata)
929 currentpart.append(nextdata)
914 currentlen += len(nextdata)
930 currentlen += len(nextdata)
915 for idx, part in enumerate(reversed(parts)):
931 for idx, part in enumerate(reversed(parts)):
916 data = ''.join([_pack('>B', _fm0version)] + part)
932 data = ''.join([_pack('>B', _fm0version)] + part)
917 keys['dump%i' % idx] = util.b85encode(data)
933 keys['dump%i' % idx] = util.b85encode(data)
918 return keys
934 return keys
919
935
920 def listmarkers(repo):
936 def listmarkers(repo):
921 """List markers over pushkey"""
937 """List markers over pushkey"""
922 if not repo.obsstore:
938 if not repo.obsstore:
923 return {}
939 return {}
924 return _pushkeyescape(sorted(repo.obsstore))
940 return _pushkeyescape(sorted(repo.obsstore))
925
941
926 def pushmarker(repo, key, old, new):
942 def pushmarker(repo, key, old, new):
927 """Push markers over pushkey"""
943 """Push markers over pushkey"""
928 if not key.startswith('dump'):
944 if not key.startswith('dump'):
929 repo.ui.warn(_('unknown key: %r') % key)
945 repo.ui.warn(_('unknown key: %r') % key)
930 return 0
946 return 0
931 if old:
947 if old:
932 repo.ui.warn(_('unexpected old value for %r') % key)
948 repo.ui.warn(_('unexpected old value for %r') % key)
933 return 0
949 return 0
934 data = util.b85decode(new)
950 data = util.b85decode(new)
935 lock = repo.lock()
951 lock = repo.lock()
936 try:
952 try:
937 tr = repo.transaction('pushkey: obsolete markers')
953 tr = repo.transaction('pushkey: obsolete markers')
938 try:
954 try:
939 repo.obsstore.mergemarkers(tr, data)
955 repo.obsstore.mergemarkers(tr, data)
940 repo.invalidatevolatilesets()
956 repo.invalidatevolatilesets()
941 tr.close()
957 tr.close()
942 return 1
958 return 1
943 finally:
959 finally:
944 tr.release()
960 tr.release()
945 finally:
961 finally:
946 lock.release()
962 lock.release()
947
963
948 def getmarkers(repo, nodes=None, exclusive=False):
964 def getmarkers(repo, nodes=None, exclusive=False):
949 """returns markers known in a repository
965 """returns markers known in a repository
950
966
951 If <nodes> is specified, only markers "relevant" to those nodes are are
967 If <nodes> is specified, only markers "relevant" to those nodes are are
952 returned"""
968 returned"""
953 if nodes is None:
969 if nodes is None:
954 rawmarkers = repo.obsstore
970 rawmarkers = repo.obsstore
955 elif exclusive:
971 elif exclusive:
956 rawmarkers = exclusivemarkers(repo, nodes)
972 rawmarkers = exclusivemarkers(repo, nodes)
957 else:
973 else:
958 rawmarkers = repo.obsstore.relevantmarkers(nodes)
974 rawmarkers = repo.obsstore.relevantmarkers(nodes)
959
975
960 for markerdata in rawmarkers:
976 for markerdata in rawmarkers:
961 yield marker(repo, markerdata)
977 yield marker(repo, markerdata)
962
978
963 def relevantmarkers(repo, node):
979 def relevantmarkers(repo, node):
964 """all obsolete markers relevant to some revision"""
980 """all obsolete markers relevant to some revision"""
965 for markerdata in repo.obsstore.relevantmarkers(node):
981 for markerdata in repo.obsstore.relevantmarkers(node):
966 yield marker(repo, markerdata)
982 yield marker(repo, markerdata)
967
983
968
984
969 def precursormarkers(ctx):
985 def precursormarkers(ctx):
970 """obsolete marker marking this changeset as a successors"""
986 """obsolete marker marking this changeset as a successors"""
971 for data in ctx.repo().obsstore.precursors.get(ctx.node(), ()):
987 for data in ctx.repo().obsstore.precursors.get(ctx.node(), ()):
972 yield marker(ctx.repo(), data)
988 yield marker(ctx.repo(), data)
973
989
974 def successormarkers(ctx):
990 def successormarkers(ctx):
975 """obsolete marker making this changeset obsolete"""
991 """obsolete marker making this changeset obsolete"""
976 for data in ctx.repo().obsstore.successors.get(ctx.node(), ()):
992 for data in ctx.repo().obsstore.successors.get(ctx.node(), ()):
977 yield marker(ctx.repo(), data)
993 yield marker(ctx.repo(), data)
978
994
979 def allsuccessors(obsstore, nodes, ignoreflags=0):
995 def allsuccessors(obsstore, nodes, ignoreflags=0):
980 """Yield node for every successor of <nodes>.
996 """Yield node for every successor of <nodes>.
981
997
982 Some successors may be unknown locally.
998 Some successors may be unknown locally.
983
999
984 This is a linear yield unsuited to detecting split changesets. It includes
1000 This is a linear yield unsuited to detecting split changesets. It includes
985 initial nodes too."""
1001 initial nodes too."""
986 remaining = set(nodes)
1002 remaining = set(nodes)
987 seen = set(remaining)
1003 seen = set(remaining)
988 while remaining:
1004 while remaining:
989 current = remaining.pop()
1005 current = remaining.pop()
990 yield current
1006 yield current
991 for mark in obsstore.successors.get(current, ()):
1007 for mark in obsstore.successors.get(current, ()):
992 # ignore marker flagged with specified flag
1008 # ignore marker flagged with specified flag
993 if mark[2] & ignoreflags:
1009 if mark[2] & ignoreflags:
994 continue
1010 continue
995 for suc in mark[1]:
1011 for suc in mark[1]:
996 if suc not in seen:
1012 if suc not in seen:
997 seen.add(suc)
1013 seen.add(suc)
998 remaining.add(suc)
1014 remaining.add(suc)
999
1015
1000 def allprecursors(obsstore, nodes, ignoreflags=0):
1016 def allprecursors(obsstore, nodes, ignoreflags=0):
1001 """Yield node for every precursors of <nodes>.
1017 """Yield node for every precursors of <nodes>.
1002
1018
1003 Some precursors may be unknown locally.
1019 Some precursors may be unknown locally.
1004
1020
1005 This is a linear yield unsuited to detecting folded changesets. It includes
1021 This is a linear yield unsuited to detecting folded changesets. It includes
1006 initial nodes too."""
1022 initial nodes too."""
1007
1023
1008 remaining = set(nodes)
1024 remaining = set(nodes)
1009 seen = set(remaining)
1025 seen = set(remaining)
1010 while remaining:
1026 while remaining:
1011 current = remaining.pop()
1027 current = remaining.pop()
1012 yield current
1028 yield current
1013 for mark in obsstore.precursors.get(current, ()):
1029 for mark in obsstore.precursors.get(current, ()):
1014 # ignore marker flagged with specified flag
1030 # ignore marker flagged with specified flag
1015 if mark[2] & ignoreflags:
1031 if mark[2] & ignoreflags:
1016 continue
1032 continue
1017 suc = mark[0]
1033 suc = mark[0]
1018 if suc not in seen:
1034 if suc not in seen:
1019 seen.add(suc)
1035 seen.add(suc)
1020 remaining.add(suc)
1036 remaining.add(suc)
1021
1037
1022 def foreground(repo, nodes):
1038 def foreground(repo, nodes):
1023 """return all nodes in the "foreground" of other node
1039 """return all nodes in the "foreground" of other node
1024
1040
1025 The foreground of a revision is anything reachable using parent -> children
1041 The foreground of a revision is anything reachable using parent -> children
1026 or precursor -> successor relation. It is very similar to "descendant" but
1042 or precursor -> successor relation. It is very similar to "descendant" but
1027 augmented with obsolescence information.
1043 augmented with obsolescence information.
1028
1044
1029 Beware that possible obsolescence cycle may result if complex situation.
1045 Beware that possible obsolescence cycle may result if complex situation.
1030 """
1046 """
1031 repo = repo.unfiltered()
1047 repo = repo.unfiltered()
1032 foreground = set(repo.set('%ln::', nodes))
1048 foreground = set(repo.set('%ln::', nodes))
1033 if repo.obsstore:
1049 if repo.obsstore:
1034 # We only need this complicated logic if there is obsolescence
1050 # We only need this complicated logic if there is obsolescence
1035 # XXX will probably deserve an optimised revset.
1051 # XXX will probably deserve an optimised revset.
1036 nm = repo.changelog.nodemap
1052 nm = repo.changelog.nodemap
1037 plen = -1
1053 plen = -1
1038 # compute the whole set of successors or descendants
1054 # compute the whole set of successors or descendants
1039 while len(foreground) != plen:
1055 while len(foreground) != plen:
1040 plen = len(foreground)
1056 plen = len(foreground)
1041 succs = set(c.node() for c in foreground)
1057 succs = set(c.node() for c in foreground)
1042 mutable = [c.node() for c in foreground if c.mutable()]
1058 mutable = [c.node() for c in foreground if c.mutable()]
1043 succs.update(allsuccessors(repo.obsstore, mutable))
1059 succs.update(allsuccessors(repo.obsstore, mutable))
1044 known = (n for n in succs if n in nm)
1060 known = (n for n in succs if n in nm)
1045 foreground = set(repo.set('%ln::', known))
1061 foreground = set(repo.set('%ln::', known))
1046 return set(c.node() for c in foreground)
1062 return set(c.node() for c in foreground)
1047
1063
1048
1064
1049 def successorssets(repo, initialnode, cache=None):
1065 def successorssets(repo, initialnode, cache=None):
1050 """Return set of all latest successors of initial nodes
1066 """Return set of all latest successors of initial nodes
1051
1067
1052 The successors set of a changeset A are the group of revisions that succeed
1068 The successors set of a changeset A are the group of revisions that succeed
1053 A. It succeeds A as a consistent whole, each revision being only a partial
1069 A. It succeeds A as a consistent whole, each revision being only a partial
1054 replacement. The successors set contains non-obsolete changesets only.
1070 replacement. The successors set contains non-obsolete changesets only.
1055
1071
1056 This function returns the full list of successor sets which is why it
1072 This function returns the full list of successor sets which is why it
1057 returns a list of tuples and not just a single tuple. Each tuple is a valid
1073 returns a list of tuples and not just a single tuple. Each tuple is a valid
1058 successors set. Note that (A,) may be a valid successors set for changeset A
1074 successors set. Note that (A,) may be a valid successors set for changeset A
1059 (see below).
1075 (see below).
1060
1076
1061 In most cases, a changeset A will have a single element (e.g. the changeset
1077 In most cases, a changeset A will have a single element (e.g. the changeset
1062 A is replaced by A') in its successors set. Though, it is also common for a
1078 A is replaced by A') in its successors set. Though, it is also common for a
1063 changeset A to have no elements in its successor set (e.g. the changeset
1079 changeset A to have no elements in its successor set (e.g. the changeset
1064 has been pruned). Therefore, the returned list of successors sets will be
1080 has been pruned). Therefore, the returned list of successors sets will be
1065 [(A',)] or [], respectively.
1081 [(A',)] or [], respectively.
1066
1082
1067 When a changeset A is split into A' and B', however, it will result in a
1083 When a changeset A is split into A' and B', however, it will result in a
1068 successors set containing more than a single element, i.e. [(A',B')].
1084 successors set containing more than a single element, i.e. [(A',B')].
1069 Divergent changesets will result in multiple successors sets, i.e. [(A',),
1085 Divergent changesets will result in multiple successors sets, i.e. [(A',),
1070 (A'')].
1086 (A'')].
1071
1087
1072 If a changeset A is not obsolete, then it will conceptually have no
1088 If a changeset A is not obsolete, then it will conceptually have no
1073 successors set. To distinguish this from a pruned changeset, the successor
1089 successors set. To distinguish this from a pruned changeset, the successor
1074 set will contain itself only, i.e. [(A,)].
1090 set will contain itself only, i.e. [(A,)].
1075
1091
1076 Finally, successors unknown locally are considered to be pruned (obsoleted
1092 Finally, successors unknown locally are considered to be pruned (obsoleted
1077 without any successors).
1093 without any successors).
1078
1094
1079 The optional `cache` parameter is a dictionary that may contain precomputed
1095 The optional `cache` parameter is a dictionary that may contain precomputed
1080 successors sets. It is meant to reuse the computation of a previous call to
1096 successors sets. It is meant to reuse the computation of a previous call to
1081 `successorssets` when multiple calls are made at the same time. The cache
1097 `successorssets` when multiple calls are made at the same time. The cache
1082 dictionary is updated in place. The caller is responsible for its life
1098 dictionary is updated in place. The caller is responsible for its life
1083 span. Code that makes multiple calls to `successorssets` *must* use this
1099 span. Code that makes multiple calls to `successorssets` *must* use this
1084 cache mechanism or suffer terrible performance.
1100 cache mechanism or suffer terrible performance.
1085 """
1101 """
1086
1102
1087 succmarkers = repo.obsstore.successors
1103 succmarkers = repo.obsstore.successors
1088
1104
1089 # Stack of nodes we search successors sets for
1105 # Stack of nodes we search successors sets for
1090 toproceed = [initialnode]
1106 toproceed = [initialnode]
1091 # set version of above list for fast loop detection
1107 # set version of above list for fast loop detection
1092 # element added to "toproceed" must be added here
1108 # element added to "toproceed" must be added here
1093 stackedset = set(toproceed)
1109 stackedset = set(toproceed)
1094 if cache is None:
1110 if cache is None:
1095 cache = {}
1111 cache = {}
1096
1112
1097 # This while loop is the flattened version of a recursive search for
1113 # This while loop is the flattened version of a recursive search for
1098 # successors sets
1114 # successors sets
1099 #
1115 #
1100 # def successorssets(x):
1116 # def successorssets(x):
1101 # successors = directsuccessors(x)
1117 # successors = directsuccessors(x)
1102 # ss = [[]]
1118 # ss = [[]]
1103 # for succ in directsuccessors(x):
1119 # for succ in directsuccessors(x):
1104 # # product as in itertools cartesian product
1120 # # product as in itertools cartesian product
1105 # ss = product(ss, successorssets(succ))
1121 # ss = product(ss, successorssets(succ))
1106 # return ss
1122 # return ss
1107 #
1123 #
1108 # But we can not use plain recursive calls here:
1124 # But we can not use plain recursive calls here:
1109 # - that would blow the python call stack
1125 # - that would blow the python call stack
1110 # - obsolescence markers may have cycles, we need to handle them.
1126 # - obsolescence markers may have cycles, we need to handle them.
1111 #
1127 #
1112 # The `toproceed` list act as our call stack. Every node we search
1128 # The `toproceed` list act as our call stack. Every node we search
1113 # successors set for are stacked there.
1129 # successors set for are stacked there.
1114 #
1130 #
1115 # The `stackedset` is set version of this stack used to check if a node is
1131 # The `stackedset` is set version of this stack used to check if a node is
1116 # already stacked. This check is used to detect cycles and prevent infinite
1132 # already stacked. This check is used to detect cycles and prevent infinite
1117 # loop.
1133 # loop.
1118 #
1134 #
1119 # successors set of all nodes are stored in the `cache` dictionary.
1135 # successors set of all nodes are stored in the `cache` dictionary.
1120 #
1136 #
1121 # After this while loop ends we use the cache to return the successors sets
1137 # After this while loop ends we use the cache to return the successors sets
1122 # for the node requested by the caller.
1138 # for the node requested by the caller.
1123 while toproceed:
1139 while toproceed:
1124 # Every iteration tries to compute the successors sets of the topmost
1140 # Every iteration tries to compute the successors sets of the topmost
1125 # node of the stack: CURRENT.
1141 # node of the stack: CURRENT.
1126 #
1142 #
1127 # There are four possible outcomes:
1143 # There are four possible outcomes:
1128 #
1144 #
1129 # 1) We already know the successors sets of CURRENT:
1145 # 1) We already know the successors sets of CURRENT:
1130 # -> mission accomplished, pop it from the stack.
1146 # -> mission accomplished, pop it from the stack.
1131 # 2) Node is not obsolete:
1147 # 2) Node is not obsolete:
1132 # -> the node is its own successors sets. Add it to the cache.
1148 # -> the node is its own successors sets. Add it to the cache.
1133 # 3) We do not know successors set of direct successors of CURRENT:
1149 # 3) We do not know successors set of direct successors of CURRENT:
1134 # -> We add those successors to the stack.
1150 # -> We add those successors to the stack.
1135 # 4) We know successors sets of all direct successors of CURRENT:
1151 # 4) We know successors sets of all direct successors of CURRENT:
1136 # -> We can compute CURRENT successors set and add it to the
1152 # -> We can compute CURRENT successors set and add it to the
1137 # cache.
1153 # cache.
1138 #
1154 #
1139 current = toproceed[-1]
1155 current = toproceed[-1]
1140 if current in cache:
1156 if current in cache:
1141 # case (1): We already know the successors sets
1157 # case (1): We already know the successors sets
1142 stackedset.remove(toproceed.pop())
1158 stackedset.remove(toproceed.pop())
1143 elif current not in succmarkers:
1159 elif current not in succmarkers:
1144 # case (2): The node is not obsolete.
1160 # case (2): The node is not obsolete.
1145 if current in repo:
1161 if current in repo:
1146 # We have a valid last successors.
1162 # We have a valid last successors.
1147 cache[current] = [(current,)]
1163 cache[current] = [(current,)]
1148 else:
1164 else:
1149 # Final obsolete version is unknown locally.
1165 # Final obsolete version is unknown locally.
1150 # Do not count that as a valid successors
1166 # Do not count that as a valid successors
1151 cache[current] = []
1167 cache[current] = []
1152 else:
1168 else:
1153 # cases (3) and (4)
1169 # cases (3) and (4)
1154 #
1170 #
1155 # We proceed in two phases. Phase 1 aims to distinguish case (3)
1171 # We proceed in two phases. Phase 1 aims to distinguish case (3)
1156 # from case (4):
1172 # from case (4):
1157 #
1173 #
1158 # For each direct successors of CURRENT, we check whether its
1174 # For each direct successors of CURRENT, we check whether its
1159 # successors sets are known. If they are not, we stack the
1175 # successors sets are known. If they are not, we stack the
1160 # unknown node and proceed to the next iteration of the while
1176 # unknown node and proceed to the next iteration of the while
1161 # loop. (case 3)
1177 # loop. (case 3)
1162 #
1178 #
1163 # During this step, we may detect obsolescence cycles: a node
1179 # During this step, we may detect obsolescence cycles: a node
1164 # with unknown successors sets but already in the call stack.
1180 # with unknown successors sets but already in the call stack.
1165 # In such a situation, we arbitrary set the successors sets of
1181 # In such a situation, we arbitrary set the successors sets of
1166 # the node to nothing (node pruned) to break the cycle.
1182 # the node to nothing (node pruned) to break the cycle.
1167 #
1183 #
1168 # If no break was encountered we proceed to phase 2.
1184 # If no break was encountered we proceed to phase 2.
1169 #
1185 #
1170 # Phase 2 computes successors sets of CURRENT (case 4); see details
1186 # Phase 2 computes successors sets of CURRENT (case 4); see details
1171 # in phase 2 itself.
1187 # in phase 2 itself.
1172 #
1188 #
1173 # Note the two levels of iteration in each phase.
1189 # Note the two levels of iteration in each phase.
1174 # - The first one handles obsolescence markers using CURRENT as
1190 # - The first one handles obsolescence markers using CURRENT as
1175 # precursor (successors markers of CURRENT).
1191 # precursor (successors markers of CURRENT).
1176 #
1192 #
1177 # Having multiple entry here means divergence.
1193 # Having multiple entry here means divergence.
1178 #
1194 #
1179 # - The second one handles successors defined in each marker.
1195 # - The second one handles successors defined in each marker.
1180 #
1196 #
1181 # Having none means pruned node, multiple successors means split,
1197 # Having none means pruned node, multiple successors means split,
1182 # single successors are standard replacement.
1198 # single successors are standard replacement.
1183 #
1199 #
1184 for mark in sorted(succmarkers[current]):
1200 for mark in sorted(succmarkers[current]):
1185 for suc in mark[1]:
1201 for suc in mark[1]:
1186 if suc not in cache:
1202 if suc not in cache:
1187 if suc in stackedset:
1203 if suc in stackedset:
1188 # cycle breaking
1204 # cycle breaking
1189 cache[suc] = []
1205 cache[suc] = []
1190 else:
1206 else:
1191 # case (3) If we have not computed successors sets
1207 # case (3) If we have not computed successors sets
1192 # of one of those successors we add it to the
1208 # of one of those successors we add it to the
1193 # `toproceed` stack and stop all work for this
1209 # `toproceed` stack and stop all work for this
1194 # iteration.
1210 # iteration.
1195 toproceed.append(suc)
1211 toproceed.append(suc)
1196 stackedset.add(suc)
1212 stackedset.add(suc)
1197 break
1213 break
1198 else:
1214 else:
1199 continue
1215 continue
1200 break
1216 break
1201 else:
1217 else:
1202 # case (4): we know all successors sets of all direct
1218 # case (4): we know all successors sets of all direct
1203 # successors
1219 # successors
1204 #
1220 #
1205 # Successors set contributed by each marker depends on the
1221 # Successors set contributed by each marker depends on the
1206 # successors sets of all its "successors" node.
1222 # successors sets of all its "successors" node.
1207 #
1223 #
1208 # Each different marker is a divergence in the obsolescence
1224 # Each different marker is a divergence in the obsolescence
1209 # history. It contributes successors sets distinct from other
1225 # history. It contributes successors sets distinct from other
1210 # markers.
1226 # markers.
1211 #
1227 #
1212 # Within a marker, a successor may have divergent successors
1228 # Within a marker, a successor may have divergent successors
1213 # sets. In such a case, the marker will contribute multiple
1229 # sets. In such a case, the marker will contribute multiple
1214 # divergent successors sets. If multiple successors have
1230 # divergent successors sets. If multiple successors have
1215 # divergent successors sets, a Cartesian product is used.
1231 # divergent successors sets, a Cartesian product is used.
1216 #
1232 #
1217 # At the end we post-process successors sets to remove
1233 # At the end we post-process successors sets to remove
1218 # duplicated entry and successors set that are strict subset of
1234 # duplicated entry and successors set that are strict subset of
1219 # another one.
1235 # another one.
1220 succssets = []
1236 succssets = []
1221 for mark in sorted(succmarkers[current]):
1237 for mark in sorted(succmarkers[current]):
1222 # successors sets contributed by this marker
1238 # successors sets contributed by this marker
1223 markss = [[]]
1239 markss = [[]]
1224 for suc in mark[1]:
1240 for suc in mark[1]:
1225 # cardinal product with previous successors
1241 # cardinal product with previous successors
1226 productresult = []
1242 productresult = []
1227 for prefix in markss:
1243 for prefix in markss:
1228 for suffix in cache[suc]:
1244 for suffix in cache[suc]:
1229 newss = list(prefix)
1245 newss = list(prefix)
1230 for part in suffix:
1246 for part in suffix:
1231 # do not duplicated entry in successors set
1247 # do not duplicated entry in successors set
1232 # first entry wins.
1248 # first entry wins.
1233 if part not in newss:
1249 if part not in newss:
1234 newss.append(part)
1250 newss.append(part)
1235 productresult.append(newss)
1251 productresult.append(newss)
1236 markss = productresult
1252 markss = productresult
1237 succssets.extend(markss)
1253 succssets.extend(markss)
1238 # remove duplicated and subset
1254 # remove duplicated and subset
1239 seen = []
1255 seen = []
1240 final = []
1256 final = []
1241 candidate = sorted(((set(s), s) for s in succssets if s),
1257 candidate = sorted(((set(s), s) for s in succssets if s),
1242 key=lambda x: len(x[1]), reverse=True)
1258 key=lambda x: len(x[1]), reverse=True)
1243 for setversion, listversion in candidate:
1259 for setversion, listversion in candidate:
1244 for seenset in seen:
1260 for seenset in seen:
1245 if setversion.issubset(seenset):
1261 if setversion.issubset(seenset):
1246 break
1262 break
1247 else:
1263 else:
1248 final.append(listversion)
1264 final.append(listversion)
1249 seen.append(setversion)
1265 seen.append(setversion)
1250 final.reverse() # put small successors set first
1266 final.reverse() # put small successors set first
1251 cache[current] = final
1267 cache[current] = final
1252 return cache[initialnode]
1268 return cache[initialnode]
1253
1269
1254 # mapping of 'set-name' -> <function to compute this set>
1270 # mapping of 'set-name' -> <function to compute this set>
1255 cachefuncs = {}
1271 cachefuncs = {}
1256 def cachefor(name):
1272 def cachefor(name):
1257 """Decorator to register a function as computing the cache for a set"""
1273 """Decorator to register a function as computing the cache for a set"""
1258 def decorator(func):
1274 def decorator(func):
1259 assert name not in cachefuncs
1275 assert name not in cachefuncs
1260 cachefuncs[name] = func
1276 cachefuncs[name] = func
1261 return func
1277 return func
1262 return decorator
1278 return decorator
1263
1279
1264 def getrevs(repo, name):
1280 def getrevs(repo, name):
1265 """Return the set of revision that belong to the <name> set
1281 """Return the set of revision that belong to the <name> set
1266
1282
1267 Such access may compute the set and cache it for future use"""
1283 Such access may compute the set and cache it for future use"""
1268 repo = repo.unfiltered()
1284 repo = repo.unfiltered()
1269 if not repo.obsstore:
1285 if not repo.obsstore:
1270 return frozenset()
1286 return frozenset()
1271 if name not in repo.obsstore.caches:
1287 if name not in repo.obsstore.caches:
1272 repo.obsstore.caches[name] = cachefuncs[name](repo)
1288 repo.obsstore.caches[name] = cachefuncs[name](repo)
1273 return repo.obsstore.caches[name]
1289 return repo.obsstore.caches[name]
1274
1290
1275 # To be simple we need to invalidate obsolescence cache when:
1291 # To be simple we need to invalidate obsolescence cache when:
1276 #
1292 #
1277 # - new changeset is added:
1293 # - new changeset is added:
1278 # - public phase is changed
1294 # - public phase is changed
1279 # - obsolescence marker are added
1295 # - obsolescence marker are added
1280 # - strip is used a repo
1296 # - strip is used a repo
1281 def clearobscaches(repo):
1297 def clearobscaches(repo):
1282 """Remove all obsolescence related cache from a repo
1298 """Remove all obsolescence related cache from a repo
1283
1299
1284 This remove all cache in obsstore is the obsstore already exist on the
1300 This remove all cache in obsstore is the obsstore already exist on the
1285 repo.
1301 repo.
1286
1302
1287 (We could be smarter here given the exact event that trigger the cache
1303 (We could be smarter here given the exact event that trigger the cache
1288 clearing)"""
1304 clearing)"""
1289 # only clear cache is there is obsstore data in this repo
1305 # only clear cache is there is obsstore data in this repo
1290 if 'obsstore' in repo._filecache:
1306 if 'obsstore' in repo._filecache:
1291 repo.obsstore.caches.clear()
1307 repo.obsstore.caches.clear()
1292
1308
1293 @cachefor('obsolete')
1309 @cachefor('obsolete')
1294 def _computeobsoleteset(repo):
1310 def _computeobsoleteset(repo):
1295 """the set of obsolete revisions"""
1311 """the set of obsolete revisions"""
1296 getnode = repo.changelog.node
1312 getnode = repo.changelog.node
1297 notpublic = repo._phasecache.getrevset(repo, (phases.draft, phases.secret))
1313 notpublic = repo._phasecache.getrevset(repo, (phases.draft, phases.secret))
1298 isobs = repo.obsstore.successors.__contains__
1314 isobs = repo.obsstore.successors.__contains__
1299 obs = set(r for r in notpublic if isobs(getnode(r)))
1315 obs = set(r for r in notpublic if isobs(getnode(r)))
1300 return obs
1316 return obs
1301
1317
1302 @cachefor('unstable')
1318 @cachefor('unstable')
1303 def _computeunstableset(repo):
1319 def _computeunstableset(repo):
1304 """the set of non obsolete revisions with obsolete parents"""
1320 """the set of non obsolete revisions with obsolete parents"""
1305 revs = [(ctx.rev(), ctx) for ctx in
1321 revs = [(ctx.rev(), ctx) for ctx in
1306 repo.set('(not public()) and (not obsolete())')]
1322 repo.set('(not public()) and (not obsolete())')]
1307 revs.sort(key=lambda x:x[0])
1323 revs.sort(key=lambda x:x[0])
1308 unstable = set()
1324 unstable = set()
1309 for rev, ctx in revs:
1325 for rev, ctx in revs:
1310 # A rev is unstable if one of its parent is obsolete or unstable
1326 # A rev is unstable if one of its parent is obsolete or unstable
1311 # this works since we traverse following growing rev order
1327 # this works since we traverse following growing rev order
1312 if any((x.obsolete() or (x.rev() in unstable))
1328 if any((x.obsolete() or (x.rev() in unstable))
1313 for x in ctx.parents()):
1329 for x in ctx.parents()):
1314 unstable.add(rev)
1330 unstable.add(rev)
1315 return unstable
1331 return unstable
1316
1332
1317 @cachefor('suspended')
1333 @cachefor('suspended')
1318 def _computesuspendedset(repo):
1334 def _computesuspendedset(repo):
1319 """the set of obsolete parents with non obsolete descendants"""
1335 """the set of obsolete parents with non obsolete descendants"""
1320 suspended = repo.changelog.ancestors(getrevs(repo, 'unstable'))
1336 suspended = repo.changelog.ancestors(getrevs(repo, 'unstable'))
1321 return set(r for r in getrevs(repo, 'obsolete') if r in suspended)
1337 return set(r for r in getrevs(repo, 'obsolete') if r in suspended)
1322
1338
1323 @cachefor('extinct')
1339 @cachefor('extinct')
1324 def _computeextinctset(repo):
1340 def _computeextinctset(repo):
1325 """the set of obsolete parents without non obsolete descendants"""
1341 """the set of obsolete parents without non obsolete descendants"""
1326 return getrevs(repo, 'obsolete') - getrevs(repo, 'suspended')
1342 return getrevs(repo, 'obsolete') - getrevs(repo, 'suspended')
1327
1343
1328
1344
1329 @cachefor('bumped')
1345 @cachefor('bumped')
1330 def _computebumpedset(repo):
1346 def _computebumpedset(repo):
1331 """the set of revs trying to obsolete public revisions"""
1347 """the set of revs trying to obsolete public revisions"""
1332 bumped = set()
1348 bumped = set()
1333 # util function (avoid attribute lookup in the loop)
1349 # util function (avoid attribute lookup in the loop)
1334 phase = repo._phasecache.phase # would be faster to grab the full list
1350 phase = repo._phasecache.phase # would be faster to grab the full list
1335 public = phases.public
1351 public = phases.public
1336 cl = repo.changelog
1352 cl = repo.changelog
1337 torev = cl.nodemap.get
1353 torev = cl.nodemap.get
1338 for ctx in repo.set('(not public()) and (not obsolete())'):
1354 for ctx in repo.set('(not public()) and (not obsolete())'):
1339 rev = ctx.rev()
1355 rev = ctx.rev()
1340 # We only evaluate mutable, non-obsolete revision
1356 # We only evaluate mutable, non-obsolete revision
1341 node = ctx.node()
1357 node = ctx.node()
1342 # (future) A cache of precursors may worth if split is very common
1358 # (future) A cache of precursors may worth if split is very common
1343 for pnode in allprecursors(repo.obsstore, [node],
1359 for pnode in allprecursors(repo.obsstore, [node],
1344 ignoreflags=bumpedfix):
1360 ignoreflags=bumpedfix):
1345 prev = torev(pnode) # unfiltered! but so is phasecache
1361 prev = torev(pnode) # unfiltered! but so is phasecache
1346 if (prev is not None) and (phase(repo, prev) <= public):
1362 if (prev is not None) and (phase(repo, prev) <= public):
1347 # we have a public precursor
1363 # we have a public precursor
1348 bumped.add(rev)
1364 bumped.add(rev)
1349 break # Next draft!
1365 break # Next draft!
1350 return bumped
1366 return bumped
1351
1367
1352 @cachefor('divergent')
1368 @cachefor('divergent')
1353 def _computedivergentset(repo):
1369 def _computedivergentset(repo):
1354 """the set of rev that compete to be the final successors of some revision.
1370 """the set of rev that compete to be the final successors of some revision.
1355 """
1371 """
1356 divergent = set()
1372 divergent = set()
1357 obsstore = repo.obsstore
1373 obsstore = repo.obsstore
1358 newermap = {}
1374 newermap = {}
1359 for ctx in repo.set('(not public()) - obsolete()'):
1375 for ctx in repo.set('(not public()) - obsolete()'):
1360 mark = obsstore.precursors.get(ctx.node(), ())
1376 mark = obsstore.precursors.get(ctx.node(), ())
1361 toprocess = set(mark)
1377 toprocess = set(mark)
1362 seen = set()
1378 seen = set()
1363 while toprocess:
1379 while toprocess:
1364 prec = toprocess.pop()[0]
1380 prec = toprocess.pop()[0]
1365 if prec in seen:
1381 if prec in seen:
1366 continue # emergency cycle hanging prevention
1382 continue # emergency cycle hanging prevention
1367 seen.add(prec)
1383 seen.add(prec)
1368 if prec not in newermap:
1384 if prec not in newermap:
1369 successorssets(repo, prec, newermap)
1385 successorssets(repo, prec, newermap)
1370 newer = [n for n in newermap[prec] if n]
1386 newer = [n for n in newermap[prec] if n]
1371 if len(newer) > 1:
1387 if len(newer) > 1:
1372 divergent.add(ctx.rev())
1388 divergent.add(ctx.rev())
1373 break
1389 break
1374 toprocess.update(obsstore.precursors.get(prec, ()))
1390 toprocess.update(obsstore.precursors.get(prec, ()))
1375 return divergent
1391 return divergent
1376
1392
1377
1393
1378 def createmarkers(repo, relations, flag=0, date=None, metadata=None,
1394 def createmarkers(repo, relations, flag=0, date=None, metadata=None,
1379 operation=None):
1395 operation=None):
1380 """Add obsolete markers between changesets in a repo
1396 """Add obsolete markers between changesets in a repo
1381
1397
1382 <relations> must be an iterable of (<old>, (<new>, ...)[,{metadata}])
1398 <relations> must be an iterable of (<old>, (<new>, ...)[,{metadata}])
1383 tuple. `old` and `news` are changectx. metadata is an optional dictionary
1399 tuple. `old` and `news` are changectx. metadata is an optional dictionary
1384 containing metadata for this marker only. It is merged with the global
1400 containing metadata for this marker only. It is merged with the global
1385 metadata specified through the `metadata` argument of this function,
1401 metadata specified through the `metadata` argument of this function,
1386
1402
1387 Trying to obsolete a public changeset will raise an exception.
1403 Trying to obsolete a public changeset will raise an exception.
1388
1404
1389 Current user and date are used except if specified otherwise in the
1405 Current user and date are used except if specified otherwise in the
1390 metadata attribute.
1406 metadata attribute.
1391
1407
1392 This function operates within a transaction of its own, but does
1408 This function operates within a transaction of its own, but does
1393 not take any lock on the repo.
1409 not take any lock on the repo.
1394 """
1410 """
1395 # prepare metadata
1411 # prepare metadata
1396 if metadata is None:
1412 if metadata is None:
1397 metadata = {}
1413 metadata = {}
1398 if 'user' not in metadata:
1414 if 'user' not in metadata:
1399 metadata['user'] = repo.ui.username()
1415 metadata['user'] = repo.ui.username()
1400 useoperation = repo.ui.configbool('experimental',
1416 useoperation = repo.ui.configbool('experimental',
1401 'evolution.track-operation',
1417 'evolution.track-operation',
1402 False)
1418 False)
1403 if useoperation and operation:
1419 if useoperation and operation:
1404 metadata['operation'] = operation
1420 metadata['operation'] = operation
1405 tr = repo.transaction('add-obsolescence-marker')
1421 tr = repo.transaction('add-obsolescence-marker')
1406 try:
1422 try:
1407 markerargs = []
1423 markerargs = []
1408 for rel in relations:
1424 for rel in relations:
1409 prec = rel[0]
1425 prec = rel[0]
1410 sucs = rel[1]
1426 sucs = rel[1]
1411 localmetadata = metadata.copy()
1427 localmetadata = metadata.copy()
1412 if 2 < len(rel):
1428 if 2 < len(rel):
1413 localmetadata.update(rel[2])
1429 localmetadata.update(rel[2])
1414
1430
1415 if not prec.mutable():
1431 if not prec.mutable():
1416 raise error.Abort(_("cannot obsolete public changeset: %s")
1432 raise error.Abort(_("cannot obsolete public changeset: %s")
1417 % prec,
1433 % prec,
1418 hint="see 'hg help phases' for details")
1434 hint="see 'hg help phases' for details")
1419 nprec = prec.node()
1435 nprec = prec.node()
1420 nsucs = tuple(s.node() for s in sucs)
1436 nsucs = tuple(s.node() for s in sucs)
1421 npare = None
1437 npare = None
1422 if not nsucs:
1438 if not nsucs:
1423 npare = tuple(p.node() for p in prec.parents())
1439 npare = tuple(p.node() for p in prec.parents())
1424 if nprec in nsucs:
1440 if nprec in nsucs:
1425 raise error.Abort(_("changeset %s cannot obsolete itself")
1441 raise error.Abort(_("changeset %s cannot obsolete itself")
1426 % prec)
1442 % prec)
1427
1443
1428 # Creating the marker causes the hidden cache to become invalid,
1444 # Creating the marker causes the hidden cache to become invalid,
1429 # which causes recomputation when we ask for prec.parents() above.
1445 # which causes recomputation when we ask for prec.parents() above.
1430 # Resulting in n^2 behavior. So let's prepare all of the args
1446 # Resulting in n^2 behavior. So let's prepare all of the args
1431 # first, then create the markers.
1447 # first, then create the markers.
1432 markerargs.append((nprec, nsucs, npare, localmetadata))
1448 markerargs.append((nprec, nsucs, npare, localmetadata))
1433
1449
1434 for args in markerargs:
1450 for args in markerargs:
1435 nprec, nsucs, npare, localmetadata = args
1451 nprec, nsucs, npare, localmetadata = args
1436 repo.obsstore.create(tr, nprec, nsucs, flag, parents=npare,
1452 repo.obsstore.create(tr, nprec, nsucs, flag, parents=npare,
1437 date=date, metadata=localmetadata,
1453 date=date, metadata=localmetadata,
1438 ui=repo.ui)
1454 ui=repo.ui)
1439 repo.filteredrevcache.clear()
1455 repo.filteredrevcache.clear()
1440 tr.close()
1456 tr.close()
1441 finally:
1457 finally:
1442 tr.release()
1458 tr.release()
General Comments 0
You need to be logged in to leave comments. Login now