##// END OF EJS Templates
obsolete: extract obsolescence marker pulling into a dedicated function...
Pierre-Yves David -
r19054:d5f968f7 default
parent child Browse files
Show More
@@ -1,2586 +1,2584 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 from node import hex, nullid, short
7 from node import hex, nullid, short
8 from i18n import _
8 from i18n import _
9 import peer, changegroup, subrepo, discovery, pushkey, obsolete, repoview
9 import peer, changegroup, subrepo, discovery, pushkey, obsolete, repoview
10 import changelog, dirstate, filelog, manifest, context, bookmarks, phases
10 import changelog, dirstate, filelog, manifest, context, bookmarks, phases
11 import lock, transaction, store, encoding, base85
11 import lock, transaction, store, encoding
12 import scmutil, util, extensions, hook, error, revset
12 import scmutil, util, extensions, hook, error, revset
13 import match as matchmod
13 import match as matchmod
14 import merge as mergemod
14 import merge as mergemod
15 import tags as tagsmod
15 import tags as tagsmod
16 from lock import release
16 from lock import release
17 import weakref, errno, os, time, inspect
17 import weakref, errno, os, time, inspect
18 import branchmap
18 import branchmap
19 propertycache = util.propertycache
19 propertycache = util.propertycache
20 filecache = scmutil.filecache
20 filecache = scmutil.filecache
21
21
22 class repofilecache(filecache):
22 class repofilecache(filecache):
23 """All filecache usage on repo are done for logic that should be unfiltered
23 """All filecache usage on repo are done for logic that should be unfiltered
24 """
24 """
25
25
26 def __get__(self, repo, type=None):
26 def __get__(self, repo, type=None):
27 return super(repofilecache, self).__get__(repo.unfiltered(), type)
27 return super(repofilecache, self).__get__(repo.unfiltered(), type)
28 def __set__(self, repo, value):
28 def __set__(self, repo, value):
29 return super(repofilecache, self).__set__(repo.unfiltered(), value)
29 return super(repofilecache, self).__set__(repo.unfiltered(), value)
30 def __delete__(self, repo):
30 def __delete__(self, repo):
31 return super(repofilecache, self).__delete__(repo.unfiltered())
31 return super(repofilecache, self).__delete__(repo.unfiltered())
32
32
33 class storecache(repofilecache):
33 class storecache(repofilecache):
34 """filecache for files in the store"""
34 """filecache for files in the store"""
35 def join(self, obj, fname):
35 def join(self, obj, fname):
36 return obj.sjoin(fname)
36 return obj.sjoin(fname)
37
37
38 class unfilteredpropertycache(propertycache):
38 class unfilteredpropertycache(propertycache):
39 """propertycache that apply to unfiltered repo only"""
39 """propertycache that apply to unfiltered repo only"""
40
40
41 def __get__(self, repo, type=None):
41 def __get__(self, repo, type=None):
42 return super(unfilteredpropertycache, self).__get__(repo.unfiltered())
42 return super(unfilteredpropertycache, self).__get__(repo.unfiltered())
43
43
44 class filteredpropertycache(propertycache):
44 class filteredpropertycache(propertycache):
45 """propertycache that must take filtering in account"""
45 """propertycache that must take filtering in account"""
46
46
47 def cachevalue(self, obj, value):
47 def cachevalue(self, obj, value):
48 object.__setattr__(obj, self.name, value)
48 object.__setattr__(obj, self.name, value)
49
49
50
50
51 def hasunfilteredcache(repo, name):
51 def hasunfilteredcache(repo, name):
52 """check if a repo has an unfilteredpropertycache value for <name>"""
52 """check if a repo has an unfilteredpropertycache value for <name>"""
53 return name in vars(repo.unfiltered())
53 return name in vars(repo.unfiltered())
54
54
55 def unfilteredmethod(orig):
55 def unfilteredmethod(orig):
56 """decorate method that always need to be run on unfiltered version"""
56 """decorate method that always need to be run on unfiltered version"""
57 def wrapper(repo, *args, **kwargs):
57 def wrapper(repo, *args, **kwargs):
58 return orig(repo.unfiltered(), *args, **kwargs)
58 return orig(repo.unfiltered(), *args, **kwargs)
59 return wrapper
59 return wrapper
60
60
61 MODERNCAPS = set(('lookup', 'branchmap', 'pushkey', 'known', 'getbundle'))
61 MODERNCAPS = set(('lookup', 'branchmap', 'pushkey', 'known', 'getbundle'))
62 LEGACYCAPS = MODERNCAPS.union(set(['changegroupsubset']))
62 LEGACYCAPS = MODERNCAPS.union(set(['changegroupsubset']))
63
63
64 class localpeer(peer.peerrepository):
64 class localpeer(peer.peerrepository):
65 '''peer for a local repo; reflects only the most recent API'''
65 '''peer for a local repo; reflects only the most recent API'''
66
66
67 def __init__(self, repo, caps=MODERNCAPS):
67 def __init__(self, repo, caps=MODERNCAPS):
68 peer.peerrepository.__init__(self)
68 peer.peerrepository.__init__(self)
69 self._repo = repo.filtered('served')
69 self._repo = repo.filtered('served')
70 self.ui = repo.ui
70 self.ui = repo.ui
71 self._caps = repo._restrictcapabilities(caps)
71 self._caps = repo._restrictcapabilities(caps)
72 self.requirements = repo.requirements
72 self.requirements = repo.requirements
73 self.supportedformats = repo.supportedformats
73 self.supportedformats = repo.supportedformats
74
74
75 def close(self):
75 def close(self):
76 self._repo.close()
76 self._repo.close()
77
77
78 def _capabilities(self):
78 def _capabilities(self):
79 return self._caps
79 return self._caps
80
80
81 def local(self):
81 def local(self):
82 return self._repo
82 return self._repo
83
83
84 def canpush(self):
84 def canpush(self):
85 return True
85 return True
86
86
87 def url(self):
87 def url(self):
88 return self._repo.url()
88 return self._repo.url()
89
89
90 def lookup(self, key):
90 def lookup(self, key):
91 return self._repo.lookup(key)
91 return self._repo.lookup(key)
92
92
93 def branchmap(self):
93 def branchmap(self):
94 return self._repo.branchmap()
94 return self._repo.branchmap()
95
95
96 def heads(self):
96 def heads(self):
97 return self._repo.heads()
97 return self._repo.heads()
98
98
99 def known(self, nodes):
99 def known(self, nodes):
100 return self._repo.known(nodes)
100 return self._repo.known(nodes)
101
101
102 def getbundle(self, source, heads=None, common=None):
102 def getbundle(self, source, heads=None, common=None):
103 return self._repo.getbundle(source, heads=heads, common=common)
103 return self._repo.getbundle(source, heads=heads, common=common)
104
104
105 # TODO We might want to move the next two calls into legacypeer and add
105 # TODO We might want to move the next two calls into legacypeer and add
106 # unbundle instead.
106 # unbundle instead.
107
107
108 def lock(self):
108 def lock(self):
109 return self._repo.lock()
109 return self._repo.lock()
110
110
111 def addchangegroup(self, cg, source, url):
111 def addchangegroup(self, cg, source, url):
112 return self._repo.addchangegroup(cg, source, url)
112 return self._repo.addchangegroup(cg, source, url)
113
113
114 def pushkey(self, namespace, key, old, new):
114 def pushkey(self, namespace, key, old, new):
115 return self._repo.pushkey(namespace, key, old, new)
115 return self._repo.pushkey(namespace, key, old, new)
116
116
117 def listkeys(self, namespace):
117 def listkeys(self, namespace):
118 return self._repo.listkeys(namespace)
118 return self._repo.listkeys(namespace)
119
119
120 def debugwireargs(self, one, two, three=None, four=None, five=None):
120 def debugwireargs(self, one, two, three=None, four=None, five=None):
121 '''used to test argument passing over the wire'''
121 '''used to test argument passing over the wire'''
122 return "%s %s %s %s %s" % (one, two, three, four, five)
122 return "%s %s %s %s %s" % (one, two, three, four, five)
123
123
124 class locallegacypeer(localpeer):
124 class locallegacypeer(localpeer):
125 '''peer extension which implements legacy methods too; used for tests with
125 '''peer extension which implements legacy methods too; used for tests with
126 restricted capabilities'''
126 restricted capabilities'''
127
127
128 def __init__(self, repo):
128 def __init__(self, repo):
129 localpeer.__init__(self, repo, caps=LEGACYCAPS)
129 localpeer.__init__(self, repo, caps=LEGACYCAPS)
130
130
131 def branches(self, nodes):
131 def branches(self, nodes):
132 return self._repo.branches(nodes)
132 return self._repo.branches(nodes)
133
133
134 def between(self, pairs):
134 def between(self, pairs):
135 return self._repo.between(pairs)
135 return self._repo.between(pairs)
136
136
137 def changegroup(self, basenodes, source):
137 def changegroup(self, basenodes, source):
138 return self._repo.changegroup(basenodes, source)
138 return self._repo.changegroup(basenodes, source)
139
139
140 def changegroupsubset(self, bases, heads, source):
140 def changegroupsubset(self, bases, heads, source):
141 return self._repo.changegroupsubset(bases, heads, source)
141 return self._repo.changegroupsubset(bases, heads, source)
142
142
143 class localrepository(object):
143 class localrepository(object):
144
144
145 supportedformats = set(('revlogv1', 'generaldelta'))
145 supportedformats = set(('revlogv1', 'generaldelta'))
146 supported = supportedformats | set(('store', 'fncache', 'shared',
146 supported = supportedformats | set(('store', 'fncache', 'shared',
147 'dotencode'))
147 'dotencode'))
148 openerreqs = set(('revlogv1', 'generaldelta'))
148 openerreqs = set(('revlogv1', 'generaldelta'))
149 requirements = ['revlogv1']
149 requirements = ['revlogv1']
150 filtername = None
150 filtername = None
151
151
152 def _baserequirements(self, create):
152 def _baserequirements(self, create):
153 return self.requirements[:]
153 return self.requirements[:]
154
154
155 def __init__(self, baseui, path=None, create=False):
155 def __init__(self, baseui, path=None, create=False):
156 self.wvfs = scmutil.vfs(path, expandpath=True, realpath=True)
156 self.wvfs = scmutil.vfs(path, expandpath=True, realpath=True)
157 self.wopener = self.wvfs
157 self.wopener = self.wvfs
158 self.root = self.wvfs.base
158 self.root = self.wvfs.base
159 self.path = self.wvfs.join(".hg")
159 self.path = self.wvfs.join(".hg")
160 self.origroot = path
160 self.origroot = path
161 self.auditor = scmutil.pathauditor(self.root, self._checknested)
161 self.auditor = scmutil.pathauditor(self.root, self._checknested)
162 self.vfs = scmutil.vfs(self.path)
162 self.vfs = scmutil.vfs(self.path)
163 self.opener = self.vfs
163 self.opener = self.vfs
164 self.baseui = baseui
164 self.baseui = baseui
165 self.ui = baseui.copy()
165 self.ui = baseui.copy()
166 # A list of callback to shape the phase if no data were found.
166 # A list of callback to shape the phase if no data were found.
167 # Callback are in the form: func(repo, roots) --> processed root.
167 # Callback are in the form: func(repo, roots) --> processed root.
168 # This list it to be filled by extension during repo setup
168 # This list it to be filled by extension during repo setup
169 self._phasedefaults = []
169 self._phasedefaults = []
170 try:
170 try:
171 self.ui.readconfig(self.join("hgrc"), self.root)
171 self.ui.readconfig(self.join("hgrc"), self.root)
172 extensions.loadall(self.ui)
172 extensions.loadall(self.ui)
173 except IOError:
173 except IOError:
174 pass
174 pass
175
175
176 if not self.vfs.isdir():
176 if not self.vfs.isdir():
177 if create:
177 if create:
178 if not self.wvfs.exists():
178 if not self.wvfs.exists():
179 self.wvfs.makedirs()
179 self.wvfs.makedirs()
180 self.vfs.makedir(notindexed=True)
180 self.vfs.makedir(notindexed=True)
181 requirements = self._baserequirements(create)
181 requirements = self._baserequirements(create)
182 if self.ui.configbool('format', 'usestore', True):
182 if self.ui.configbool('format', 'usestore', True):
183 self.vfs.mkdir("store")
183 self.vfs.mkdir("store")
184 requirements.append("store")
184 requirements.append("store")
185 if self.ui.configbool('format', 'usefncache', True):
185 if self.ui.configbool('format', 'usefncache', True):
186 requirements.append("fncache")
186 requirements.append("fncache")
187 if self.ui.configbool('format', 'dotencode', True):
187 if self.ui.configbool('format', 'dotencode', True):
188 requirements.append('dotencode')
188 requirements.append('dotencode')
189 # create an invalid changelog
189 # create an invalid changelog
190 self.vfs.append(
190 self.vfs.append(
191 "00changelog.i",
191 "00changelog.i",
192 '\0\0\0\2' # represents revlogv2
192 '\0\0\0\2' # represents revlogv2
193 ' dummy changelog to prevent using the old repo layout'
193 ' dummy changelog to prevent using the old repo layout'
194 )
194 )
195 if self.ui.configbool('format', 'generaldelta', False):
195 if self.ui.configbool('format', 'generaldelta', False):
196 requirements.append("generaldelta")
196 requirements.append("generaldelta")
197 requirements = set(requirements)
197 requirements = set(requirements)
198 else:
198 else:
199 raise error.RepoError(_("repository %s not found") % path)
199 raise error.RepoError(_("repository %s not found") % path)
200 elif create:
200 elif create:
201 raise error.RepoError(_("repository %s already exists") % path)
201 raise error.RepoError(_("repository %s already exists") % path)
202 else:
202 else:
203 try:
203 try:
204 requirements = scmutil.readrequires(self.vfs, self.supported)
204 requirements = scmutil.readrequires(self.vfs, self.supported)
205 except IOError, inst:
205 except IOError, inst:
206 if inst.errno != errno.ENOENT:
206 if inst.errno != errno.ENOENT:
207 raise
207 raise
208 requirements = set()
208 requirements = set()
209
209
210 self.sharedpath = self.path
210 self.sharedpath = self.path
211 try:
211 try:
212 vfs = scmutil.vfs(self.vfs.read("sharedpath").rstrip('\n'),
212 vfs = scmutil.vfs(self.vfs.read("sharedpath").rstrip('\n'),
213 realpath=True)
213 realpath=True)
214 s = vfs.base
214 s = vfs.base
215 if not vfs.exists():
215 if not vfs.exists():
216 raise error.RepoError(
216 raise error.RepoError(
217 _('.hg/sharedpath points to nonexistent directory %s') % s)
217 _('.hg/sharedpath points to nonexistent directory %s') % s)
218 self.sharedpath = s
218 self.sharedpath = s
219 except IOError, inst:
219 except IOError, inst:
220 if inst.errno != errno.ENOENT:
220 if inst.errno != errno.ENOENT:
221 raise
221 raise
222
222
223 self.store = store.store(requirements, self.sharedpath, scmutil.vfs)
223 self.store = store.store(requirements, self.sharedpath, scmutil.vfs)
224 self.spath = self.store.path
224 self.spath = self.store.path
225 self.svfs = self.store.vfs
225 self.svfs = self.store.vfs
226 self.sopener = self.svfs
226 self.sopener = self.svfs
227 self.sjoin = self.store.join
227 self.sjoin = self.store.join
228 self.vfs.createmode = self.store.createmode
228 self.vfs.createmode = self.store.createmode
229 self._applyrequirements(requirements)
229 self._applyrequirements(requirements)
230 if create:
230 if create:
231 self._writerequirements()
231 self._writerequirements()
232
232
233
233
234 self._branchcaches = {}
234 self._branchcaches = {}
235 self.filterpats = {}
235 self.filterpats = {}
236 self._datafilters = {}
236 self._datafilters = {}
237 self._transref = self._lockref = self._wlockref = None
237 self._transref = self._lockref = self._wlockref = None
238
238
239 # A cache for various files under .hg/ that tracks file changes,
239 # A cache for various files under .hg/ that tracks file changes,
240 # (used by the filecache decorator)
240 # (used by the filecache decorator)
241 #
241 #
242 # Maps a property name to its util.filecacheentry
242 # Maps a property name to its util.filecacheentry
243 self._filecache = {}
243 self._filecache = {}
244
244
245 # hold sets of revision to be filtered
245 # hold sets of revision to be filtered
246 # should be cleared when something might have changed the filter value:
246 # should be cleared when something might have changed the filter value:
247 # - new changesets,
247 # - new changesets,
248 # - phase change,
248 # - phase change,
249 # - new obsolescence marker,
249 # - new obsolescence marker,
250 # - working directory parent change,
250 # - working directory parent change,
251 # - bookmark changes
251 # - bookmark changes
252 self.filteredrevcache = {}
252 self.filteredrevcache = {}
253
253
254 def close(self):
254 def close(self):
255 pass
255 pass
256
256
257 def _restrictcapabilities(self, caps):
257 def _restrictcapabilities(self, caps):
258 return caps
258 return caps
259
259
260 def _applyrequirements(self, requirements):
260 def _applyrequirements(self, requirements):
261 self.requirements = requirements
261 self.requirements = requirements
262 self.sopener.options = dict((r, 1) for r in requirements
262 self.sopener.options = dict((r, 1) for r in requirements
263 if r in self.openerreqs)
263 if r in self.openerreqs)
264
264
265 def _writerequirements(self):
265 def _writerequirements(self):
266 reqfile = self.opener("requires", "w")
266 reqfile = self.opener("requires", "w")
267 for r in sorted(self.requirements):
267 for r in sorted(self.requirements):
268 reqfile.write("%s\n" % r)
268 reqfile.write("%s\n" % r)
269 reqfile.close()
269 reqfile.close()
270
270
271 def _checknested(self, path):
271 def _checknested(self, path):
272 """Determine if path is a legal nested repository."""
272 """Determine if path is a legal nested repository."""
273 if not path.startswith(self.root):
273 if not path.startswith(self.root):
274 return False
274 return False
275 subpath = path[len(self.root) + 1:]
275 subpath = path[len(self.root) + 1:]
276 normsubpath = util.pconvert(subpath)
276 normsubpath = util.pconvert(subpath)
277
277
278 # XXX: Checking against the current working copy is wrong in
278 # XXX: Checking against the current working copy is wrong in
279 # the sense that it can reject things like
279 # the sense that it can reject things like
280 #
280 #
281 # $ hg cat -r 10 sub/x.txt
281 # $ hg cat -r 10 sub/x.txt
282 #
282 #
283 # if sub/ is no longer a subrepository in the working copy
283 # if sub/ is no longer a subrepository in the working copy
284 # parent revision.
284 # parent revision.
285 #
285 #
286 # However, it can of course also allow things that would have
286 # However, it can of course also allow things that would have
287 # been rejected before, such as the above cat command if sub/
287 # been rejected before, such as the above cat command if sub/
288 # is a subrepository now, but was a normal directory before.
288 # is a subrepository now, but was a normal directory before.
289 # The old path auditor would have rejected by mistake since it
289 # The old path auditor would have rejected by mistake since it
290 # panics when it sees sub/.hg/.
290 # panics when it sees sub/.hg/.
291 #
291 #
292 # All in all, checking against the working copy seems sensible
292 # All in all, checking against the working copy seems sensible
293 # since we want to prevent access to nested repositories on
293 # since we want to prevent access to nested repositories on
294 # the filesystem *now*.
294 # the filesystem *now*.
295 ctx = self[None]
295 ctx = self[None]
296 parts = util.splitpath(subpath)
296 parts = util.splitpath(subpath)
297 while parts:
297 while parts:
298 prefix = '/'.join(parts)
298 prefix = '/'.join(parts)
299 if prefix in ctx.substate:
299 if prefix in ctx.substate:
300 if prefix == normsubpath:
300 if prefix == normsubpath:
301 return True
301 return True
302 else:
302 else:
303 sub = ctx.sub(prefix)
303 sub = ctx.sub(prefix)
304 return sub.checknested(subpath[len(prefix) + 1:])
304 return sub.checknested(subpath[len(prefix) + 1:])
305 else:
305 else:
306 parts.pop()
306 parts.pop()
307 return False
307 return False
308
308
309 def peer(self):
309 def peer(self):
310 return localpeer(self) # not cached to avoid reference cycle
310 return localpeer(self) # not cached to avoid reference cycle
311
311
312 def unfiltered(self):
312 def unfiltered(self):
313 """Return unfiltered version of the repository
313 """Return unfiltered version of the repository
314
314
315 Intended to be overwritten by filtered repo."""
315 Intended to be overwritten by filtered repo."""
316 return self
316 return self
317
317
318 def filtered(self, name):
318 def filtered(self, name):
319 """Return a filtered version of a repository"""
319 """Return a filtered version of a repository"""
320 # build a new class with the mixin and the current class
320 # build a new class with the mixin and the current class
321 # (possibly subclass of the repo)
321 # (possibly subclass of the repo)
322 class proxycls(repoview.repoview, self.unfiltered().__class__):
322 class proxycls(repoview.repoview, self.unfiltered().__class__):
323 pass
323 pass
324 return proxycls(self, name)
324 return proxycls(self, name)
325
325
326 @repofilecache('bookmarks')
326 @repofilecache('bookmarks')
327 def _bookmarks(self):
327 def _bookmarks(self):
328 return bookmarks.bmstore(self)
328 return bookmarks.bmstore(self)
329
329
330 @repofilecache('bookmarks.current')
330 @repofilecache('bookmarks.current')
331 def _bookmarkcurrent(self):
331 def _bookmarkcurrent(self):
332 return bookmarks.readcurrent(self)
332 return bookmarks.readcurrent(self)
333
333
334 def bookmarkheads(self, bookmark):
334 def bookmarkheads(self, bookmark):
335 name = bookmark.split('@', 1)[0]
335 name = bookmark.split('@', 1)[0]
336 heads = []
336 heads = []
337 for mark, n in self._bookmarks.iteritems():
337 for mark, n in self._bookmarks.iteritems():
338 if mark.split('@', 1)[0] == name:
338 if mark.split('@', 1)[0] == name:
339 heads.append(n)
339 heads.append(n)
340 return heads
340 return heads
341
341
342 @storecache('phaseroots')
342 @storecache('phaseroots')
343 def _phasecache(self):
343 def _phasecache(self):
344 return phases.phasecache(self, self._phasedefaults)
344 return phases.phasecache(self, self._phasedefaults)
345
345
346 @storecache('obsstore')
346 @storecache('obsstore')
347 def obsstore(self):
347 def obsstore(self):
348 store = obsolete.obsstore(self.sopener)
348 store = obsolete.obsstore(self.sopener)
349 if store and not obsolete._enabled:
349 if store and not obsolete._enabled:
350 # message is rare enough to not be translated
350 # message is rare enough to not be translated
351 msg = 'obsolete feature not enabled but %i markers found!\n'
351 msg = 'obsolete feature not enabled but %i markers found!\n'
352 self.ui.warn(msg % len(list(store)))
352 self.ui.warn(msg % len(list(store)))
353 return store
353 return store
354
354
355 @storecache('00changelog.i')
355 @storecache('00changelog.i')
356 def changelog(self):
356 def changelog(self):
357 c = changelog.changelog(self.sopener)
357 c = changelog.changelog(self.sopener)
358 if 'HG_PENDING' in os.environ:
358 if 'HG_PENDING' in os.environ:
359 p = os.environ['HG_PENDING']
359 p = os.environ['HG_PENDING']
360 if p.startswith(self.root):
360 if p.startswith(self.root):
361 c.readpending('00changelog.i.a')
361 c.readpending('00changelog.i.a')
362 return c
362 return c
363
363
364 @storecache('00manifest.i')
364 @storecache('00manifest.i')
365 def manifest(self):
365 def manifest(self):
366 return manifest.manifest(self.sopener)
366 return manifest.manifest(self.sopener)
367
367
368 @repofilecache('dirstate')
368 @repofilecache('dirstate')
369 def dirstate(self):
369 def dirstate(self):
370 warned = [0]
370 warned = [0]
371 def validate(node):
371 def validate(node):
372 try:
372 try:
373 self.changelog.rev(node)
373 self.changelog.rev(node)
374 return node
374 return node
375 except error.LookupError:
375 except error.LookupError:
376 if not warned[0]:
376 if not warned[0]:
377 warned[0] = True
377 warned[0] = True
378 self.ui.warn(_("warning: ignoring unknown"
378 self.ui.warn(_("warning: ignoring unknown"
379 " working parent %s!\n") % short(node))
379 " working parent %s!\n") % short(node))
380 return nullid
380 return nullid
381
381
382 return dirstate.dirstate(self.opener, self.ui, self.root, validate)
382 return dirstate.dirstate(self.opener, self.ui, self.root, validate)
383
383
384 def __getitem__(self, changeid):
384 def __getitem__(self, changeid):
385 if changeid is None:
385 if changeid is None:
386 return context.workingctx(self)
386 return context.workingctx(self)
387 return context.changectx(self, changeid)
387 return context.changectx(self, changeid)
388
388
389 def __contains__(self, changeid):
389 def __contains__(self, changeid):
390 try:
390 try:
391 return bool(self.lookup(changeid))
391 return bool(self.lookup(changeid))
392 except error.RepoLookupError:
392 except error.RepoLookupError:
393 return False
393 return False
394
394
395 def __nonzero__(self):
395 def __nonzero__(self):
396 return True
396 return True
397
397
398 def __len__(self):
398 def __len__(self):
399 return len(self.changelog)
399 return len(self.changelog)
400
400
401 def __iter__(self):
401 def __iter__(self):
402 return iter(self.changelog)
402 return iter(self.changelog)
403
403
404 def revs(self, expr, *args):
404 def revs(self, expr, *args):
405 '''Return a list of revisions matching the given revset'''
405 '''Return a list of revisions matching the given revset'''
406 expr = revset.formatspec(expr, *args)
406 expr = revset.formatspec(expr, *args)
407 m = revset.match(None, expr)
407 m = revset.match(None, expr)
408 return [r for r in m(self, list(self))]
408 return [r for r in m(self, list(self))]
409
409
410 def set(self, expr, *args):
410 def set(self, expr, *args):
411 '''
411 '''
412 Yield a context for each matching revision, after doing arg
412 Yield a context for each matching revision, after doing arg
413 replacement via revset.formatspec
413 replacement via revset.formatspec
414 '''
414 '''
415 for r in self.revs(expr, *args):
415 for r in self.revs(expr, *args):
416 yield self[r]
416 yield self[r]
417
417
418 def url(self):
418 def url(self):
419 return 'file:' + self.root
419 return 'file:' + self.root
420
420
421 def hook(self, name, throw=False, **args):
421 def hook(self, name, throw=False, **args):
422 return hook.hook(self.ui, self, name, throw, **args)
422 return hook.hook(self.ui, self, name, throw, **args)
423
423
424 @unfilteredmethod
424 @unfilteredmethod
425 def _tag(self, names, node, message, local, user, date, extra={}):
425 def _tag(self, names, node, message, local, user, date, extra={}):
426 if isinstance(names, str):
426 if isinstance(names, str):
427 names = (names,)
427 names = (names,)
428
428
429 branches = self.branchmap()
429 branches = self.branchmap()
430 for name in names:
430 for name in names:
431 self.hook('pretag', throw=True, node=hex(node), tag=name,
431 self.hook('pretag', throw=True, node=hex(node), tag=name,
432 local=local)
432 local=local)
433 if name in branches:
433 if name in branches:
434 self.ui.warn(_("warning: tag %s conflicts with existing"
434 self.ui.warn(_("warning: tag %s conflicts with existing"
435 " branch name\n") % name)
435 " branch name\n") % name)
436
436
437 def writetags(fp, names, munge, prevtags):
437 def writetags(fp, names, munge, prevtags):
438 fp.seek(0, 2)
438 fp.seek(0, 2)
439 if prevtags and prevtags[-1] != '\n':
439 if prevtags and prevtags[-1] != '\n':
440 fp.write('\n')
440 fp.write('\n')
441 for name in names:
441 for name in names:
442 m = munge and munge(name) or name
442 m = munge and munge(name) or name
443 if (self._tagscache.tagtypes and
443 if (self._tagscache.tagtypes and
444 name in self._tagscache.tagtypes):
444 name in self._tagscache.tagtypes):
445 old = self.tags().get(name, nullid)
445 old = self.tags().get(name, nullid)
446 fp.write('%s %s\n' % (hex(old), m))
446 fp.write('%s %s\n' % (hex(old), m))
447 fp.write('%s %s\n' % (hex(node), m))
447 fp.write('%s %s\n' % (hex(node), m))
448 fp.close()
448 fp.close()
449
449
450 prevtags = ''
450 prevtags = ''
451 if local:
451 if local:
452 try:
452 try:
453 fp = self.opener('localtags', 'r+')
453 fp = self.opener('localtags', 'r+')
454 except IOError:
454 except IOError:
455 fp = self.opener('localtags', 'a')
455 fp = self.opener('localtags', 'a')
456 else:
456 else:
457 prevtags = fp.read()
457 prevtags = fp.read()
458
458
459 # local tags are stored in the current charset
459 # local tags are stored in the current charset
460 writetags(fp, names, None, prevtags)
460 writetags(fp, names, None, prevtags)
461 for name in names:
461 for name in names:
462 self.hook('tag', node=hex(node), tag=name, local=local)
462 self.hook('tag', node=hex(node), tag=name, local=local)
463 return
463 return
464
464
465 try:
465 try:
466 fp = self.wfile('.hgtags', 'rb+')
466 fp = self.wfile('.hgtags', 'rb+')
467 except IOError, e:
467 except IOError, e:
468 if e.errno != errno.ENOENT:
468 if e.errno != errno.ENOENT:
469 raise
469 raise
470 fp = self.wfile('.hgtags', 'ab')
470 fp = self.wfile('.hgtags', 'ab')
471 else:
471 else:
472 prevtags = fp.read()
472 prevtags = fp.read()
473
473
474 # committed tags are stored in UTF-8
474 # committed tags are stored in UTF-8
475 writetags(fp, names, encoding.fromlocal, prevtags)
475 writetags(fp, names, encoding.fromlocal, prevtags)
476
476
477 fp.close()
477 fp.close()
478
478
479 self.invalidatecaches()
479 self.invalidatecaches()
480
480
481 if '.hgtags' not in self.dirstate:
481 if '.hgtags' not in self.dirstate:
482 self[None].add(['.hgtags'])
482 self[None].add(['.hgtags'])
483
483
484 m = matchmod.exact(self.root, '', ['.hgtags'])
484 m = matchmod.exact(self.root, '', ['.hgtags'])
485 tagnode = self.commit(message, user, date, extra=extra, match=m)
485 tagnode = self.commit(message, user, date, extra=extra, match=m)
486
486
487 for name in names:
487 for name in names:
488 self.hook('tag', node=hex(node), tag=name, local=local)
488 self.hook('tag', node=hex(node), tag=name, local=local)
489
489
490 return tagnode
490 return tagnode
491
491
492 def tag(self, names, node, message, local, user, date):
492 def tag(self, names, node, message, local, user, date):
493 '''tag a revision with one or more symbolic names.
493 '''tag a revision with one or more symbolic names.
494
494
495 names is a list of strings or, when adding a single tag, names may be a
495 names is a list of strings or, when adding a single tag, names may be a
496 string.
496 string.
497
497
498 if local is True, the tags are stored in a per-repository file.
498 if local is True, the tags are stored in a per-repository file.
499 otherwise, they are stored in the .hgtags file, and a new
499 otherwise, they are stored in the .hgtags file, and a new
500 changeset is committed with the change.
500 changeset is committed with the change.
501
501
502 keyword arguments:
502 keyword arguments:
503
503
504 local: whether to store tags in non-version-controlled file
504 local: whether to store tags in non-version-controlled file
505 (default False)
505 (default False)
506
506
507 message: commit message to use if committing
507 message: commit message to use if committing
508
508
509 user: name of user to use if committing
509 user: name of user to use if committing
510
510
511 date: date tuple to use if committing'''
511 date: date tuple to use if committing'''
512
512
513 if not local:
513 if not local:
514 for x in self.status()[:5]:
514 for x in self.status()[:5]:
515 if '.hgtags' in x:
515 if '.hgtags' in x:
516 raise util.Abort(_('working copy of .hgtags is changed '
516 raise util.Abort(_('working copy of .hgtags is changed '
517 '(please commit .hgtags manually)'))
517 '(please commit .hgtags manually)'))
518
518
519 self.tags() # instantiate the cache
519 self.tags() # instantiate the cache
520 self._tag(names, node, message, local, user, date)
520 self._tag(names, node, message, local, user, date)
521
521
522 @filteredpropertycache
522 @filteredpropertycache
523 def _tagscache(self):
523 def _tagscache(self):
524 '''Returns a tagscache object that contains various tags related
524 '''Returns a tagscache object that contains various tags related
525 caches.'''
525 caches.'''
526
526
527 # This simplifies its cache management by having one decorated
527 # This simplifies its cache management by having one decorated
528 # function (this one) and the rest simply fetch things from it.
528 # function (this one) and the rest simply fetch things from it.
529 class tagscache(object):
529 class tagscache(object):
530 def __init__(self):
530 def __init__(self):
531 # These two define the set of tags for this repository. tags
531 # These two define the set of tags for this repository. tags
532 # maps tag name to node; tagtypes maps tag name to 'global' or
532 # maps tag name to node; tagtypes maps tag name to 'global' or
533 # 'local'. (Global tags are defined by .hgtags across all
533 # 'local'. (Global tags are defined by .hgtags across all
534 # heads, and local tags are defined in .hg/localtags.)
534 # heads, and local tags are defined in .hg/localtags.)
535 # They constitute the in-memory cache of tags.
535 # They constitute the in-memory cache of tags.
536 self.tags = self.tagtypes = None
536 self.tags = self.tagtypes = None
537
537
538 self.nodetagscache = self.tagslist = None
538 self.nodetagscache = self.tagslist = None
539
539
540 cache = tagscache()
540 cache = tagscache()
541 cache.tags, cache.tagtypes = self._findtags()
541 cache.tags, cache.tagtypes = self._findtags()
542
542
543 return cache
543 return cache
544
544
545 def tags(self):
545 def tags(self):
546 '''return a mapping of tag to node'''
546 '''return a mapping of tag to node'''
547 t = {}
547 t = {}
548 if self.changelog.filteredrevs:
548 if self.changelog.filteredrevs:
549 tags, tt = self._findtags()
549 tags, tt = self._findtags()
550 else:
550 else:
551 tags = self._tagscache.tags
551 tags = self._tagscache.tags
552 for k, v in tags.iteritems():
552 for k, v in tags.iteritems():
553 try:
553 try:
554 # ignore tags to unknown nodes
554 # ignore tags to unknown nodes
555 self.changelog.rev(v)
555 self.changelog.rev(v)
556 t[k] = v
556 t[k] = v
557 except (error.LookupError, ValueError):
557 except (error.LookupError, ValueError):
558 pass
558 pass
559 return t
559 return t
560
560
561 def _findtags(self):
561 def _findtags(self):
562 '''Do the hard work of finding tags. Return a pair of dicts
562 '''Do the hard work of finding tags. Return a pair of dicts
563 (tags, tagtypes) where tags maps tag name to node, and tagtypes
563 (tags, tagtypes) where tags maps tag name to node, and tagtypes
564 maps tag name to a string like \'global\' or \'local\'.
564 maps tag name to a string like \'global\' or \'local\'.
565 Subclasses or extensions are free to add their own tags, but
565 Subclasses or extensions are free to add their own tags, but
566 should be aware that the returned dicts will be retained for the
566 should be aware that the returned dicts will be retained for the
567 duration of the localrepo object.'''
567 duration of the localrepo object.'''
568
568
569 # XXX what tagtype should subclasses/extensions use? Currently
569 # XXX what tagtype should subclasses/extensions use? Currently
570 # mq and bookmarks add tags, but do not set the tagtype at all.
570 # mq and bookmarks add tags, but do not set the tagtype at all.
571 # Should each extension invent its own tag type? Should there
571 # Should each extension invent its own tag type? Should there
572 # be one tagtype for all such "virtual" tags? Or is the status
572 # be one tagtype for all such "virtual" tags? Or is the status
573 # quo fine?
573 # quo fine?
574
574
575 alltags = {} # map tag name to (node, hist)
575 alltags = {} # map tag name to (node, hist)
576 tagtypes = {}
576 tagtypes = {}
577
577
578 tagsmod.findglobaltags(self.ui, self, alltags, tagtypes)
578 tagsmod.findglobaltags(self.ui, self, alltags, tagtypes)
579 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
579 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
580
580
581 # Build the return dicts. Have to re-encode tag names because
581 # Build the return dicts. Have to re-encode tag names because
582 # the tags module always uses UTF-8 (in order not to lose info
582 # the tags module always uses UTF-8 (in order not to lose info
583 # writing to the cache), but the rest of Mercurial wants them in
583 # writing to the cache), but the rest of Mercurial wants them in
584 # local encoding.
584 # local encoding.
585 tags = {}
585 tags = {}
586 for (name, (node, hist)) in alltags.iteritems():
586 for (name, (node, hist)) in alltags.iteritems():
587 if node != nullid:
587 if node != nullid:
588 tags[encoding.tolocal(name)] = node
588 tags[encoding.tolocal(name)] = node
589 tags['tip'] = self.changelog.tip()
589 tags['tip'] = self.changelog.tip()
590 tagtypes = dict([(encoding.tolocal(name), value)
590 tagtypes = dict([(encoding.tolocal(name), value)
591 for (name, value) in tagtypes.iteritems()])
591 for (name, value) in tagtypes.iteritems()])
592 return (tags, tagtypes)
592 return (tags, tagtypes)
593
593
594 def tagtype(self, tagname):
594 def tagtype(self, tagname):
595 '''
595 '''
596 return the type of the given tag. result can be:
596 return the type of the given tag. result can be:
597
597
598 'local' : a local tag
598 'local' : a local tag
599 'global' : a global tag
599 'global' : a global tag
600 None : tag does not exist
600 None : tag does not exist
601 '''
601 '''
602
602
603 return self._tagscache.tagtypes.get(tagname)
603 return self._tagscache.tagtypes.get(tagname)
604
604
605 def tagslist(self):
605 def tagslist(self):
606 '''return a list of tags ordered by revision'''
606 '''return a list of tags ordered by revision'''
607 if not self._tagscache.tagslist:
607 if not self._tagscache.tagslist:
608 l = []
608 l = []
609 for t, n in self.tags().iteritems():
609 for t, n in self.tags().iteritems():
610 r = self.changelog.rev(n)
610 r = self.changelog.rev(n)
611 l.append((r, t, n))
611 l.append((r, t, n))
612 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
612 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
613
613
614 return self._tagscache.tagslist
614 return self._tagscache.tagslist
615
615
616 def nodetags(self, node):
616 def nodetags(self, node):
617 '''return the tags associated with a node'''
617 '''return the tags associated with a node'''
618 if not self._tagscache.nodetagscache:
618 if not self._tagscache.nodetagscache:
619 nodetagscache = {}
619 nodetagscache = {}
620 for t, n in self._tagscache.tags.iteritems():
620 for t, n in self._tagscache.tags.iteritems():
621 nodetagscache.setdefault(n, []).append(t)
621 nodetagscache.setdefault(n, []).append(t)
622 for tags in nodetagscache.itervalues():
622 for tags in nodetagscache.itervalues():
623 tags.sort()
623 tags.sort()
624 self._tagscache.nodetagscache = nodetagscache
624 self._tagscache.nodetagscache = nodetagscache
625 return self._tagscache.nodetagscache.get(node, [])
625 return self._tagscache.nodetagscache.get(node, [])
626
626
627 def nodebookmarks(self, node):
627 def nodebookmarks(self, node):
628 marks = []
628 marks = []
629 for bookmark, n in self._bookmarks.iteritems():
629 for bookmark, n in self._bookmarks.iteritems():
630 if n == node:
630 if n == node:
631 marks.append(bookmark)
631 marks.append(bookmark)
632 return sorted(marks)
632 return sorted(marks)
633
633
634 def branchmap(self):
634 def branchmap(self):
635 '''returns a dictionary {branch: [branchheads]}'''
635 '''returns a dictionary {branch: [branchheads]}'''
636 branchmap.updatecache(self)
636 branchmap.updatecache(self)
637 return self._branchcaches[self.filtername]
637 return self._branchcaches[self.filtername]
638
638
639
639
640 def _branchtip(self, heads):
640 def _branchtip(self, heads):
641 '''return the tipmost branch head in heads'''
641 '''return the tipmost branch head in heads'''
642 tip = heads[-1]
642 tip = heads[-1]
643 for h in reversed(heads):
643 for h in reversed(heads):
644 if not self[h].closesbranch():
644 if not self[h].closesbranch():
645 tip = h
645 tip = h
646 break
646 break
647 return tip
647 return tip
648
648
649 def branchtip(self, branch):
649 def branchtip(self, branch):
650 '''return the tip node for a given branch'''
650 '''return the tip node for a given branch'''
651 if branch not in self.branchmap():
651 if branch not in self.branchmap():
652 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
652 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
653 return self._branchtip(self.branchmap()[branch])
653 return self._branchtip(self.branchmap()[branch])
654
654
655 def branchtags(self):
655 def branchtags(self):
656 '''return a dict where branch names map to the tipmost head of
656 '''return a dict where branch names map to the tipmost head of
657 the branch, open heads come before closed'''
657 the branch, open heads come before closed'''
658 bt = {}
658 bt = {}
659 for bn, heads in self.branchmap().iteritems():
659 for bn, heads in self.branchmap().iteritems():
660 bt[bn] = self._branchtip(heads)
660 bt[bn] = self._branchtip(heads)
661 return bt
661 return bt
662
662
663 def lookup(self, key):
663 def lookup(self, key):
664 return self[key].node()
664 return self[key].node()
665
665
666 def lookupbranch(self, key, remote=None):
666 def lookupbranch(self, key, remote=None):
667 repo = remote or self
667 repo = remote or self
668 if key in repo.branchmap():
668 if key in repo.branchmap():
669 return key
669 return key
670
670
671 repo = (remote and remote.local()) and remote or self
671 repo = (remote and remote.local()) and remote or self
672 return repo[key].branch()
672 return repo[key].branch()
673
673
674 def known(self, nodes):
674 def known(self, nodes):
675 nm = self.changelog.nodemap
675 nm = self.changelog.nodemap
676 pc = self._phasecache
676 pc = self._phasecache
677 result = []
677 result = []
678 for n in nodes:
678 for n in nodes:
679 r = nm.get(n)
679 r = nm.get(n)
680 resp = not (r is None or pc.phase(self, r) >= phases.secret)
680 resp = not (r is None or pc.phase(self, r) >= phases.secret)
681 result.append(resp)
681 result.append(resp)
682 return result
682 return result
683
683
684 def local(self):
684 def local(self):
685 return self
685 return self
686
686
687 def cancopy(self):
687 def cancopy(self):
688 return self.local() # so statichttprepo's override of local() works
688 return self.local() # so statichttprepo's override of local() works
689
689
690 def join(self, f):
690 def join(self, f):
691 return os.path.join(self.path, f)
691 return os.path.join(self.path, f)
692
692
693 def wjoin(self, f):
693 def wjoin(self, f):
694 return os.path.join(self.root, f)
694 return os.path.join(self.root, f)
695
695
696 def file(self, f):
696 def file(self, f):
697 if f[0] == '/':
697 if f[0] == '/':
698 f = f[1:]
698 f = f[1:]
699 return filelog.filelog(self.sopener, f)
699 return filelog.filelog(self.sopener, f)
700
700
701 def changectx(self, changeid):
701 def changectx(self, changeid):
702 return self[changeid]
702 return self[changeid]
703
703
704 def parents(self, changeid=None):
704 def parents(self, changeid=None):
705 '''get list of changectxs for parents of changeid'''
705 '''get list of changectxs for parents of changeid'''
706 return self[changeid].parents()
706 return self[changeid].parents()
707
707
708 def setparents(self, p1, p2=nullid):
708 def setparents(self, p1, p2=nullid):
709 copies = self.dirstate.setparents(p1, p2)
709 copies = self.dirstate.setparents(p1, p2)
710 pctx = self[p1]
710 pctx = self[p1]
711 if copies:
711 if copies:
712 # Adjust copy records, the dirstate cannot do it, it
712 # Adjust copy records, the dirstate cannot do it, it
713 # requires access to parents manifests. Preserve them
713 # requires access to parents manifests. Preserve them
714 # only for entries added to first parent.
714 # only for entries added to first parent.
715 for f in copies:
715 for f in copies:
716 if f not in pctx and copies[f] in pctx:
716 if f not in pctx and copies[f] in pctx:
717 self.dirstate.copy(copies[f], f)
717 self.dirstate.copy(copies[f], f)
718 if p2 == nullid:
718 if p2 == nullid:
719 for f, s in sorted(self.dirstate.copies().items()):
719 for f, s in sorted(self.dirstate.copies().items()):
720 if f not in pctx and s not in pctx:
720 if f not in pctx and s not in pctx:
721 self.dirstate.copy(None, f)
721 self.dirstate.copy(None, f)
722
722
723 def filectx(self, path, changeid=None, fileid=None):
723 def filectx(self, path, changeid=None, fileid=None):
724 """changeid can be a changeset revision, node, or tag.
724 """changeid can be a changeset revision, node, or tag.
725 fileid can be a file revision or node."""
725 fileid can be a file revision or node."""
726 return context.filectx(self, path, changeid, fileid)
726 return context.filectx(self, path, changeid, fileid)
727
727
728 def getcwd(self):
728 def getcwd(self):
729 return self.dirstate.getcwd()
729 return self.dirstate.getcwd()
730
730
731 def pathto(self, f, cwd=None):
731 def pathto(self, f, cwd=None):
732 return self.dirstate.pathto(f, cwd)
732 return self.dirstate.pathto(f, cwd)
733
733
734 def wfile(self, f, mode='r'):
734 def wfile(self, f, mode='r'):
735 return self.wopener(f, mode)
735 return self.wopener(f, mode)
736
736
737 def _link(self, f):
737 def _link(self, f):
738 return self.wvfs.islink(f)
738 return self.wvfs.islink(f)
739
739
740 def _loadfilter(self, filter):
740 def _loadfilter(self, filter):
741 if filter not in self.filterpats:
741 if filter not in self.filterpats:
742 l = []
742 l = []
743 for pat, cmd in self.ui.configitems(filter):
743 for pat, cmd in self.ui.configitems(filter):
744 if cmd == '!':
744 if cmd == '!':
745 continue
745 continue
746 mf = matchmod.match(self.root, '', [pat])
746 mf = matchmod.match(self.root, '', [pat])
747 fn = None
747 fn = None
748 params = cmd
748 params = cmd
749 for name, filterfn in self._datafilters.iteritems():
749 for name, filterfn in self._datafilters.iteritems():
750 if cmd.startswith(name):
750 if cmd.startswith(name):
751 fn = filterfn
751 fn = filterfn
752 params = cmd[len(name):].lstrip()
752 params = cmd[len(name):].lstrip()
753 break
753 break
754 if not fn:
754 if not fn:
755 fn = lambda s, c, **kwargs: util.filter(s, c)
755 fn = lambda s, c, **kwargs: util.filter(s, c)
756 # Wrap old filters not supporting keyword arguments
756 # Wrap old filters not supporting keyword arguments
757 if not inspect.getargspec(fn)[2]:
757 if not inspect.getargspec(fn)[2]:
758 oldfn = fn
758 oldfn = fn
759 fn = lambda s, c, **kwargs: oldfn(s, c)
759 fn = lambda s, c, **kwargs: oldfn(s, c)
760 l.append((mf, fn, params))
760 l.append((mf, fn, params))
761 self.filterpats[filter] = l
761 self.filterpats[filter] = l
762 return self.filterpats[filter]
762 return self.filterpats[filter]
763
763
764 def _filter(self, filterpats, filename, data):
764 def _filter(self, filterpats, filename, data):
765 for mf, fn, cmd in filterpats:
765 for mf, fn, cmd in filterpats:
766 if mf(filename):
766 if mf(filename):
767 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
767 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
768 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
768 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
769 break
769 break
770
770
771 return data
771 return data
772
772
773 @unfilteredpropertycache
773 @unfilteredpropertycache
774 def _encodefilterpats(self):
774 def _encodefilterpats(self):
775 return self._loadfilter('encode')
775 return self._loadfilter('encode')
776
776
777 @unfilteredpropertycache
777 @unfilteredpropertycache
778 def _decodefilterpats(self):
778 def _decodefilterpats(self):
779 return self._loadfilter('decode')
779 return self._loadfilter('decode')
780
780
781 def adddatafilter(self, name, filter):
781 def adddatafilter(self, name, filter):
782 self._datafilters[name] = filter
782 self._datafilters[name] = filter
783
783
784 def wread(self, filename):
784 def wread(self, filename):
785 if self._link(filename):
785 if self._link(filename):
786 data = self.wvfs.readlink(filename)
786 data = self.wvfs.readlink(filename)
787 else:
787 else:
788 data = self.wopener.read(filename)
788 data = self.wopener.read(filename)
789 return self._filter(self._encodefilterpats, filename, data)
789 return self._filter(self._encodefilterpats, filename, data)
790
790
791 def wwrite(self, filename, data, flags):
791 def wwrite(self, filename, data, flags):
792 data = self._filter(self._decodefilterpats, filename, data)
792 data = self._filter(self._decodefilterpats, filename, data)
793 if 'l' in flags:
793 if 'l' in flags:
794 self.wopener.symlink(data, filename)
794 self.wopener.symlink(data, filename)
795 else:
795 else:
796 self.wopener.write(filename, data)
796 self.wopener.write(filename, data)
797 if 'x' in flags:
797 if 'x' in flags:
798 self.wvfs.setflags(filename, False, True)
798 self.wvfs.setflags(filename, False, True)
799
799
800 def wwritedata(self, filename, data):
800 def wwritedata(self, filename, data):
801 return self._filter(self._decodefilterpats, filename, data)
801 return self._filter(self._decodefilterpats, filename, data)
802
802
803 def transaction(self, desc):
803 def transaction(self, desc):
804 tr = self._transref and self._transref() or None
804 tr = self._transref and self._transref() or None
805 if tr and tr.running():
805 if tr and tr.running():
806 return tr.nest()
806 return tr.nest()
807
807
808 # abort here if the journal already exists
808 # abort here if the journal already exists
809 if self.svfs.exists("journal"):
809 if self.svfs.exists("journal"):
810 raise error.RepoError(
810 raise error.RepoError(
811 _("abandoned transaction found - run hg recover"))
811 _("abandoned transaction found - run hg recover"))
812
812
813 self._writejournal(desc)
813 self._writejournal(desc)
814 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
814 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
815
815
816 tr = transaction.transaction(self.ui.warn, self.sopener,
816 tr = transaction.transaction(self.ui.warn, self.sopener,
817 self.sjoin("journal"),
817 self.sjoin("journal"),
818 aftertrans(renames),
818 aftertrans(renames),
819 self.store.createmode)
819 self.store.createmode)
820 self._transref = weakref.ref(tr)
820 self._transref = weakref.ref(tr)
821 return tr
821 return tr
822
822
823 def _journalfiles(self):
823 def _journalfiles(self):
824 return ((self.svfs, 'journal'),
824 return ((self.svfs, 'journal'),
825 (self.vfs, 'journal.dirstate'),
825 (self.vfs, 'journal.dirstate'),
826 (self.vfs, 'journal.branch'),
826 (self.vfs, 'journal.branch'),
827 (self.vfs, 'journal.desc'),
827 (self.vfs, 'journal.desc'),
828 (self.vfs, 'journal.bookmarks'),
828 (self.vfs, 'journal.bookmarks'),
829 (self.svfs, 'journal.phaseroots'))
829 (self.svfs, 'journal.phaseroots'))
830
830
831 def undofiles(self):
831 def undofiles(self):
832 return [vfs.join(undoname(x)) for vfs, x in self._journalfiles()]
832 return [vfs.join(undoname(x)) for vfs, x in self._journalfiles()]
833
833
834 def _writejournal(self, desc):
834 def _writejournal(self, desc):
835 self.opener.write("journal.dirstate",
835 self.opener.write("journal.dirstate",
836 self.opener.tryread("dirstate"))
836 self.opener.tryread("dirstate"))
837 self.opener.write("journal.branch",
837 self.opener.write("journal.branch",
838 encoding.fromlocal(self.dirstate.branch()))
838 encoding.fromlocal(self.dirstate.branch()))
839 self.opener.write("journal.desc",
839 self.opener.write("journal.desc",
840 "%d\n%s\n" % (len(self), desc))
840 "%d\n%s\n" % (len(self), desc))
841 self.opener.write("journal.bookmarks",
841 self.opener.write("journal.bookmarks",
842 self.opener.tryread("bookmarks"))
842 self.opener.tryread("bookmarks"))
843 self.sopener.write("journal.phaseroots",
843 self.sopener.write("journal.phaseroots",
844 self.sopener.tryread("phaseroots"))
844 self.sopener.tryread("phaseroots"))
845
845
846 def recover(self):
846 def recover(self):
847 lock = self.lock()
847 lock = self.lock()
848 try:
848 try:
849 if self.svfs.exists("journal"):
849 if self.svfs.exists("journal"):
850 self.ui.status(_("rolling back interrupted transaction\n"))
850 self.ui.status(_("rolling back interrupted transaction\n"))
851 transaction.rollback(self.sopener, self.sjoin("journal"),
851 transaction.rollback(self.sopener, self.sjoin("journal"),
852 self.ui.warn)
852 self.ui.warn)
853 self.invalidate()
853 self.invalidate()
854 return True
854 return True
855 else:
855 else:
856 self.ui.warn(_("no interrupted transaction available\n"))
856 self.ui.warn(_("no interrupted transaction available\n"))
857 return False
857 return False
858 finally:
858 finally:
859 lock.release()
859 lock.release()
860
860
861 def rollback(self, dryrun=False, force=False):
861 def rollback(self, dryrun=False, force=False):
862 wlock = lock = None
862 wlock = lock = None
863 try:
863 try:
864 wlock = self.wlock()
864 wlock = self.wlock()
865 lock = self.lock()
865 lock = self.lock()
866 if self.svfs.exists("undo"):
866 if self.svfs.exists("undo"):
867 return self._rollback(dryrun, force)
867 return self._rollback(dryrun, force)
868 else:
868 else:
869 self.ui.warn(_("no rollback information available\n"))
869 self.ui.warn(_("no rollback information available\n"))
870 return 1
870 return 1
871 finally:
871 finally:
872 release(lock, wlock)
872 release(lock, wlock)
873
873
874 @unfilteredmethod # Until we get smarter cache management
874 @unfilteredmethod # Until we get smarter cache management
875 def _rollback(self, dryrun, force):
875 def _rollback(self, dryrun, force):
876 ui = self.ui
876 ui = self.ui
877 try:
877 try:
878 args = self.opener.read('undo.desc').splitlines()
878 args = self.opener.read('undo.desc').splitlines()
879 (oldlen, desc, detail) = (int(args[0]), args[1], None)
879 (oldlen, desc, detail) = (int(args[0]), args[1], None)
880 if len(args) >= 3:
880 if len(args) >= 3:
881 detail = args[2]
881 detail = args[2]
882 oldtip = oldlen - 1
882 oldtip = oldlen - 1
883
883
884 if detail and ui.verbose:
884 if detail and ui.verbose:
885 msg = (_('repository tip rolled back to revision %s'
885 msg = (_('repository tip rolled back to revision %s'
886 ' (undo %s: %s)\n')
886 ' (undo %s: %s)\n')
887 % (oldtip, desc, detail))
887 % (oldtip, desc, detail))
888 else:
888 else:
889 msg = (_('repository tip rolled back to revision %s'
889 msg = (_('repository tip rolled back to revision %s'
890 ' (undo %s)\n')
890 ' (undo %s)\n')
891 % (oldtip, desc))
891 % (oldtip, desc))
892 except IOError:
892 except IOError:
893 msg = _('rolling back unknown transaction\n')
893 msg = _('rolling back unknown transaction\n')
894 desc = None
894 desc = None
895
895
896 if not force and self['.'] != self['tip'] and desc == 'commit':
896 if not force and self['.'] != self['tip'] and desc == 'commit':
897 raise util.Abort(
897 raise util.Abort(
898 _('rollback of last commit while not checked out '
898 _('rollback of last commit while not checked out '
899 'may lose data'), hint=_('use -f to force'))
899 'may lose data'), hint=_('use -f to force'))
900
900
901 ui.status(msg)
901 ui.status(msg)
902 if dryrun:
902 if dryrun:
903 return 0
903 return 0
904
904
905 parents = self.dirstate.parents()
905 parents = self.dirstate.parents()
906 self.destroying()
906 self.destroying()
907 transaction.rollback(self.sopener, self.sjoin('undo'), ui.warn)
907 transaction.rollback(self.sopener, self.sjoin('undo'), ui.warn)
908 if self.vfs.exists('undo.bookmarks'):
908 if self.vfs.exists('undo.bookmarks'):
909 self.vfs.rename('undo.bookmarks', 'bookmarks')
909 self.vfs.rename('undo.bookmarks', 'bookmarks')
910 if self.svfs.exists('undo.phaseroots'):
910 if self.svfs.exists('undo.phaseroots'):
911 self.svfs.rename('undo.phaseroots', 'phaseroots')
911 self.svfs.rename('undo.phaseroots', 'phaseroots')
912 self.invalidate()
912 self.invalidate()
913
913
914 parentgone = (parents[0] not in self.changelog.nodemap or
914 parentgone = (parents[0] not in self.changelog.nodemap or
915 parents[1] not in self.changelog.nodemap)
915 parents[1] not in self.changelog.nodemap)
916 if parentgone:
916 if parentgone:
917 self.vfs.rename('undo.dirstate', 'dirstate')
917 self.vfs.rename('undo.dirstate', 'dirstate')
918 try:
918 try:
919 branch = self.opener.read('undo.branch')
919 branch = self.opener.read('undo.branch')
920 self.dirstate.setbranch(encoding.tolocal(branch))
920 self.dirstate.setbranch(encoding.tolocal(branch))
921 except IOError:
921 except IOError:
922 ui.warn(_('named branch could not be reset: '
922 ui.warn(_('named branch could not be reset: '
923 'current branch is still \'%s\'\n')
923 'current branch is still \'%s\'\n')
924 % self.dirstate.branch())
924 % self.dirstate.branch())
925
925
926 self.dirstate.invalidate()
926 self.dirstate.invalidate()
927 parents = tuple([p.rev() for p in self.parents()])
927 parents = tuple([p.rev() for p in self.parents()])
928 if len(parents) > 1:
928 if len(parents) > 1:
929 ui.status(_('working directory now based on '
929 ui.status(_('working directory now based on '
930 'revisions %d and %d\n') % parents)
930 'revisions %d and %d\n') % parents)
931 else:
931 else:
932 ui.status(_('working directory now based on '
932 ui.status(_('working directory now based on '
933 'revision %d\n') % parents)
933 'revision %d\n') % parents)
934 # TODO: if we know which new heads may result from this rollback, pass
934 # TODO: if we know which new heads may result from this rollback, pass
935 # them to destroy(), which will prevent the branchhead cache from being
935 # them to destroy(), which will prevent the branchhead cache from being
936 # invalidated.
936 # invalidated.
937 self.destroyed()
937 self.destroyed()
938 return 0
938 return 0
939
939
940 def invalidatecaches(self):
940 def invalidatecaches(self):
941
941
942 if '_tagscache' in vars(self):
942 if '_tagscache' in vars(self):
943 # can't use delattr on proxy
943 # can't use delattr on proxy
944 del self.__dict__['_tagscache']
944 del self.__dict__['_tagscache']
945
945
946 self.unfiltered()._branchcaches.clear()
946 self.unfiltered()._branchcaches.clear()
947 self.invalidatevolatilesets()
947 self.invalidatevolatilesets()
948
948
949 def invalidatevolatilesets(self):
949 def invalidatevolatilesets(self):
950 self.filteredrevcache.clear()
950 self.filteredrevcache.clear()
951 obsolete.clearobscaches(self)
951 obsolete.clearobscaches(self)
952
952
953 def invalidatedirstate(self):
953 def invalidatedirstate(self):
954 '''Invalidates the dirstate, causing the next call to dirstate
954 '''Invalidates the dirstate, causing the next call to dirstate
955 to check if it was modified since the last time it was read,
955 to check if it was modified since the last time it was read,
956 rereading it if it has.
956 rereading it if it has.
957
957
958 This is different to dirstate.invalidate() that it doesn't always
958 This is different to dirstate.invalidate() that it doesn't always
959 rereads the dirstate. Use dirstate.invalidate() if you want to
959 rereads the dirstate. Use dirstate.invalidate() if you want to
960 explicitly read the dirstate again (i.e. restoring it to a previous
960 explicitly read the dirstate again (i.e. restoring it to a previous
961 known good state).'''
961 known good state).'''
962 if hasunfilteredcache(self, 'dirstate'):
962 if hasunfilteredcache(self, 'dirstate'):
963 for k in self.dirstate._filecache:
963 for k in self.dirstate._filecache:
964 try:
964 try:
965 delattr(self.dirstate, k)
965 delattr(self.dirstate, k)
966 except AttributeError:
966 except AttributeError:
967 pass
967 pass
968 delattr(self.unfiltered(), 'dirstate')
968 delattr(self.unfiltered(), 'dirstate')
969
969
970 def invalidate(self):
970 def invalidate(self):
971 unfiltered = self.unfiltered() # all file caches are stored unfiltered
971 unfiltered = self.unfiltered() # all file caches are stored unfiltered
972 for k in self._filecache:
972 for k in self._filecache:
973 # dirstate is invalidated separately in invalidatedirstate()
973 # dirstate is invalidated separately in invalidatedirstate()
974 if k == 'dirstate':
974 if k == 'dirstate':
975 continue
975 continue
976
976
977 try:
977 try:
978 delattr(unfiltered, k)
978 delattr(unfiltered, k)
979 except AttributeError:
979 except AttributeError:
980 pass
980 pass
981 self.invalidatecaches()
981 self.invalidatecaches()
982
982
983 def _lock(self, lockname, wait, releasefn, acquirefn, desc):
983 def _lock(self, lockname, wait, releasefn, acquirefn, desc):
984 try:
984 try:
985 l = lock.lock(lockname, 0, releasefn, desc=desc)
985 l = lock.lock(lockname, 0, releasefn, desc=desc)
986 except error.LockHeld, inst:
986 except error.LockHeld, inst:
987 if not wait:
987 if not wait:
988 raise
988 raise
989 self.ui.warn(_("waiting for lock on %s held by %r\n") %
989 self.ui.warn(_("waiting for lock on %s held by %r\n") %
990 (desc, inst.locker))
990 (desc, inst.locker))
991 # default to 600 seconds timeout
991 # default to 600 seconds timeout
992 l = lock.lock(lockname, int(self.ui.config("ui", "timeout", "600")),
992 l = lock.lock(lockname, int(self.ui.config("ui", "timeout", "600")),
993 releasefn, desc=desc)
993 releasefn, desc=desc)
994 if acquirefn:
994 if acquirefn:
995 acquirefn()
995 acquirefn()
996 return l
996 return l
997
997
998 def _afterlock(self, callback):
998 def _afterlock(self, callback):
999 """add a callback to the current repository lock.
999 """add a callback to the current repository lock.
1000
1000
1001 The callback will be executed on lock release."""
1001 The callback will be executed on lock release."""
1002 l = self._lockref and self._lockref()
1002 l = self._lockref and self._lockref()
1003 if l:
1003 if l:
1004 l.postrelease.append(callback)
1004 l.postrelease.append(callback)
1005 else:
1005 else:
1006 callback()
1006 callback()
1007
1007
1008 def lock(self, wait=True):
1008 def lock(self, wait=True):
1009 '''Lock the repository store (.hg/store) and return a weak reference
1009 '''Lock the repository store (.hg/store) and return a weak reference
1010 to the lock. Use this before modifying the store (e.g. committing or
1010 to the lock. Use this before modifying the store (e.g. committing or
1011 stripping). If you are opening a transaction, get a lock as well.)'''
1011 stripping). If you are opening a transaction, get a lock as well.)'''
1012 l = self._lockref and self._lockref()
1012 l = self._lockref and self._lockref()
1013 if l is not None and l.held:
1013 if l is not None and l.held:
1014 l.lock()
1014 l.lock()
1015 return l
1015 return l
1016
1016
1017 def unlock():
1017 def unlock():
1018 self.store.write()
1018 self.store.write()
1019 if hasunfilteredcache(self, '_phasecache'):
1019 if hasunfilteredcache(self, '_phasecache'):
1020 self._phasecache.write()
1020 self._phasecache.write()
1021 for k, ce in self._filecache.items():
1021 for k, ce in self._filecache.items():
1022 if k == 'dirstate' or k not in self.__dict__:
1022 if k == 'dirstate' or k not in self.__dict__:
1023 continue
1023 continue
1024 ce.refresh()
1024 ce.refresh()
1025
1025
1026 l = self._lock(self.sjoin("lock"), wait, unlock,
1026 l = self._lock(self.sjoin("lock"), wait, unlock,
1027 self.invalidate, _('repository %s') % self.origroot)
1027 self.invalidate, _('repository %s') % self.origroot)
1028 self._lockref = weakref.ref(l)
1028 self._lockref = weakref.ref(l)
1029 return l
1029 return l
1030
1030
1031 def wlock(self, wait=True):
1031 def wlock(self, wait=True):
1032 '''Lock the non-store parts of the repository (everything under
1032 '''Lock the non-store parts of the repository (everything under
1033 .hg except .hg/store) and return a weak reference to the lock.
1033 .hg except .hg/store) and return a weak reference to the lock.
1034 Use this before modifying files in .hg.'''
1034 Use this before modifying files in .hg.'''
1035 l = self._wlockref and self._wlockref()
1035 l = self._wlockref and self._wlockref()
1036 if l is not None and l.held:
1036 if l is not None and l.held:
1037 l.lock()
1037 l.lock()
1038 return l
1038 return l
1039
1039
1040 def unlock():
1040 def unlock():
1041 self.dirstate.write()
1041 self.dirstate.write()
1042 self._filecache['dirstate'].refresh()
1042 self._filecache['dirstate'].refresh()
1043
1043
1044 l = self._lock(self.join("wlock"), wait, unlock,
1044 l = self._lock(self.join("wlock"), wait, unlock,
1045 self.invalidatedirstate, _('working directory of %s') %
1045 self.invalidatedirstate, _('working directory of %s') %
1046 self.origroot)
1046 self.origroot)
1047 self._wlockref = weakref.ref(l)
1047 self._wlockref = weakref.ref(l)
1048 return l
1048 return l
1049
1049
1050 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
1050 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
1051 """
1051 """
1052 commit an individual file as part of a larger transaction
1052 commit an individual file as part of a larger transaction
1053 """
1053 """
1054
1054
1055 fname = fctx.path()
1055 fname = fctx.path()
1056 text = fctx.data()
1056 text = fctx.data()
1057 flog = self.file(fname)
1057 flog = self.file(fname)
1058 fparent1 = manifest1.get(fname, nullid)
1058 fparent1 = manifest1.get(fname, nullid)
1059 fparent2 = fparent2o = manifest2.get(fname, nullid)
1059 fparent2 = fparent2o = manifest2.get(fname, nullid)
1060
1060
1061 meta = {}
1061 meta = {}
1062 copy = fctx.renamed()
1062 copy = fctx.renamed()
1063 if copy and copy[0] != fname:
1063 if copy and copy[0] != fname:
1064 # Mark the new revision of this file as a copy of another
1064 # Mark the new revision of this file as a copy of another
1065 # file. This copy data will effectively act as a parent
1065 # file. This copy data will effectively act as a parent
1066 # of this new revision. If this is a merge, the first
1066 # of this new revision. If this is a merge, the first
1067 # parent will be the nullid (meaning "look up the copy data")
1067 # parent will be the nullid (meaning "look up the copy data")
1068 # and the second one will be the other parent. For example:
1068 # and the second one will be the other parent. For example:
1069 #
1069 #
1070 # 0 --- 1 --- 3 rev1 changes file foo
1070 # 0 --- 1 --- 3 rev1 changes file foo
1071 # \ / rev2 renames foo to bar and changes it
1071 # \ / rev2 renames foo to bar and changes it
1072 # \- 2 -/ rev3 should have bar with all changes and
1072 # \- 2 -/ rev3 should have bar with all changes and
1073 # should record that bar descends from
1073 # should record that bar descends from
1074 # bar in rev2 and foo in rev1
1074 # bar in rev2 and foo in rev1
1075 #
1075 #
1076 # this allows this merge to succeed:
1076 # this allows this merge to succeed:
1077 #
1077 #
1078 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
1078 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
1079 # \ / merging rev3 and rev4 should use bar@rev2
1079 # \ / merging rev3 and rev4 should use bar@rev2
1080 # \- 2 --- 4 as the merge base
1080 # \- 2 --- 4 as the merge base
1081 #
1081 #
1082
1082
1083 cfname = copy[0]
1083 cfname = copy[0]
1084 crev = manifest1.get(cfname)
1084 crev = manifest1.get(cfname)
1085 newfparent = fparent2
1085 newfparent = fparent2
1086
1086
1087 if manifest2: # branch merge
1087 if manifest2: # branch merge
1088 if fparent2 == nullid or crev is None: # copied on remote side
1088 if fparent2 == nullid or crev is None: # copied on remote side
1089 if cfname in manifest2:
1089 if cfname in manifest2:
1090 crev = manifest2[cfname]
1090 crev = manifest2[cfname]
1091 newfparent = fparent1
1091 newfparent = fparent1
1092
1092
1093 # find source in nearest ancestor if we've lost track
1093 # find source in nearest ancestor if we've lost track
1094 if not crev:
1094 if not crev:
1095 self.ui.debug(" %s: searching for copy revision for %s\n" %
1095 self.ui.debug(" %s: searching for copy revision for %s\n" %
1096 (fname, cfname))
1096 (fname, cfname))
1097 for ancestor in self[None].ancestors():
1097 for ancestor in self[None].ancestors():
1098 if cfname in ancestor:
1098 if cfname in ancestor:
1099 crev = ancestor[cfname].filenode()
1099 crev = ancestor[cfname].filenode()
1100 break
1100 break
1101
1101
1102 if crev:
1102 if crev:
1103 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
1103 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
1104 meta["copy"] = cfname
1104 meta["copy"] = cfname
1105 meta["copyrev"] = hex(crev)
1105 meta["copyrev"] = hex(crev)
1106 fparent1, fparent2 = nullid, newfparent
1106 fparent1, fparent2 = nullid, newfparent
1107 else:
1107 else:
1108 self.ui.warn(_("warning: can't find ancestor for '%s' "
1108 self.ui.warn(_("warning: can't find ancestor for '%s' "
1109 "copied from '%s'!\n") % (fname, cfname))
1109 "copied from '%s'!\n") % (fname, cfname))
1110
1110
1111 elif fparent2 != nullid:
1111 elif fparent2 != nullid:
1112 # is one parent an ancestor of the other?
1112 # is one parent an ancestor of the other?
1113 fparentancestor = flog.ancestor(fparent1, fparent2)
1113 fparentancestor = flog.ancestor(fparent1, fparent2)
1114 if fparentancestor == fparent1:
1114 if fparentancestor == fparent1:
1115 fparent1, fparent2 = fparent2, nullid
1115 fparent1, fparent2 = fparent2, nullid
1116 elif fparentancestor == fparent2:
1116 elif fparentancestor == fparent2:
1117 fparent2 = nullid
1117 fparent2 = nullid
1118
1118
1119 # is the file changed?
1119 # is the file changed?
1120 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
1120 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
1121 changelist.append(fname)
1121 changelist.append(fname)
1122 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
1122 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
1123
1123
1124 # are just the flags changed during merge?
1124 # are just the flags changed during merge?
1125 if fparent1 != fparent2o and manifest1.flags(fname) != fctx.flags():
1125 if fparent1 != fparent2o and manifest1.flags(fname) != fctx.flags():
1126 changelist.append(fname)
1126 changelist.append(fname)
1127
1127
1128 return fparent1
1128 return fparent1
1129
1129
1130 @unfilteredmethod
1130 @unfilteredmethod
1131 def commit(self, text="", user=None, date=None, match=None, force=False,
1131 def commit(self, text="", user=None, date=None, match=None, force=False,
1132 editor=False, extra={}):
1132 editor=False, extra={}):
1133 """Add a new revision to current repository.
1133 """Add a new revision to current repository.
1134
1134
1135 Revision information is gathered from the working directory,
1135 Revision information is gathered from the working directory,
1136 match can be used to filter the committed files. If editor is
1136 match can be used to filter the committed files. If editor is
1137 supplied, it is called to get a commit message.
1137 supplied, it is called to get a commit message.
1138 """
1138 """
1139
1139
1140 def fail(f, msg):
1140 def fail(f, msg):
1141 raise util.Abort('%s: %s' % (f, msg))
1141 raise util.Abort('%s: %s' % (f, msg))
1142
1142
1143 if not match:
1143 if not match:
1144 match = matchmod.always(self.root, '')
1144 match = matchmod.always(self.root, '')
1145
1145
1146 if not force:
1146 if not force:
1147 vdirs = []
1147 vdirs = []
1148 match.dir = vdirs.append
1148 match.dir = vdirs.append
1149 match.bad = fail
1149 match.bad = fail
1150
1150
1151 wlock = self.wlock()
1151 wlock = self.wlock()
1152 try:
1152 try:
1153 wctx = self[None]
1153 wctx = self[None]
1154 merge = len(wctx.parents()) > 1
1154 merge = len(wctx.parents()) > 1
1155
1155
1156 if (not force and merge and match and
1156 if (not force and merge and match and
1157 (match.files() or match.anypats())):
1157 (match.files() or match.anypats())):
1158 raise util.Abort(_('cannot partially commit a merge '
1158 raise util.Abort(_('cannot partially commit a merge '
1159 '(do not specify files or patterns)'))
1159 '(do not specify files or patterns)'))
1160
1160
1161 changes = self.status(match=match, clean=force)
1161 changes = self.status(match=match, clean=force)
1162 if force:
1162 if force:
1163 changes[0].extend(changes[6]) # mq may commit unchanged files
1163 changes[0].extend(changes[6]) # mq may commit unchanged files
1164
1164
1165 # check subrepos
1165 # check subrepos
1166 subs = []
1166 subs = []
1167 commitsubs = set()
1167 commitsubs = set()
1168 newstate = wctx.substate.copy()
1168 newstate = wctx.substate.copy()
1169 # only manage subrepos and .hgsubstate if .hgsub is present
1169 # only manage subrepos and .hgsubstate if .hgsub is present
1170 if '.hgsub' in wctx:
1170 if '.hgsub' in wctx:
1171 # we'll decide whether to track this ourselves, thanks
1171 # we'll decide whether to track this ourselves, thanks
1172 if '.hgsubstate' in changes[0]:
1172 if '.hgsubstate' in changes[0]:
1173 changes[0].remove('.hgsubstate')
1173 changes[0].remove('.hgsubstate')
1174 if '.hgsubstate' in changes[2]:
1174 if '.hgsubstate' in changes[2]:
1175 changes[2].remove('.hgsubstate')
1175 changes[2].remove('.hgsubstate')
1176
1176
1177 # compare current state to last committed state
1177 # compare current state to last committed state
1178 # build new substate based on last committed state
1178 # build new substate based on last committed state
1179 oldstate = wctx.p1().substate
1179 oldstate = wctx.p1().substate
1180 for s in sorted(newstate.keys()):
1180 for s in sorted(newstate.keys()):
1181 if not match(s):
1181 if not match(s):
1182 # ignore working copy, use old state if present
1182 # ignore working copy, use old state if present
1183 if s in oldstate:
1183 if s in oldstate:
1184 newstate[s] = oldstate[s]
1184 newstate[s] = oldstate[s]
1185 continue
1185 continue
1186 if not force:
1186 if not force:
1187 raise util.Abort(
1187 raise util.Abort(
1188 _("commit with new subrepo %s excluded") % s)
1188 _("commit with new subrepo %s excluded") % s)
1189 if wctx.sub(s).dirty(True):
1189 if wctx.sub(s).dirty(True):
1190 if not self.ui.configbool('ui', 'commitsubrepos'):
1190 if not self.ui.configbool('ui', 'commitsubrepos'):
1191 raise util.Abort(
1191 raise util.Abort(
1192 _("uncommitted changes in subrepo %s") % s,
1192 _("uncommitted changes in subrepo %s") % s,
1193 hint=_("use --subrepos for recursive commit"))
1193 hint=_("use --subrepos for recursive commit"))
1194 subs.append(s)
1194 subs.append(s)
1195 commitsubs.add(s)
1195 commitsubs.add(s)
1196 else:
1196 else:
1197 bs = wctx.sub(s).basestate()
1197 bs = wctx.sub(s).basestate()
1198 newstate[s] = (newstate[s][0], bs, newstate[s][2])
1198 newstate[s] = (newstate[s][0], bs, newstate[s][2])
1199 if oldstate.get(s, (None, None, None))[1] != bs:
1199 if oldstate.get(s, (None, None, None))[1] != bs:
1200 subs.append(s)
1200 subs.append(s)
1201
1201
1202 # check for removed subrepos
1202 # check for removed subrepos
1203 for p in wctx.parents():
1203 for p in wctx.parents():
1204 r = [s for s in p.substate if s not in newstate]
1204 r = [s for s in p.substate if s not in newstate]
1205 subs += [s for s in r if match(s)]
1205 subs += [s for s in r if match(s)]
1206 if subs:
1206 if subs:
1207 if (not match('.hgsub') and
1207 if (not match('.hgsub') and
1208 '.hgsub' in (wctx.modified() + wctx.added())):
1208 '.hgsub' in (wctx.modified() + wctx.added())):
1209 raise util.Abort(
1209 raise util.Abort(
1210 _("can't commit subrepos without .hgsub"))
1210 _("can't commit subrepos without .hgsub"))
1211 changes[0].insert(0, '.hgsubstate')
1211 changes[0].insert(0, '.hgsubstate')
1212
1212
1213 elif '.hgsub' in changes[2]:
1213 elif '.hgsub' in changes[2]:
1214 # clean up .hgsubstate when .hgsub is removed
1214 # clean up .hgsubstate when .hgsub is removed
1215 if ('.hgsubstate' in wctx and
1215 if ('.hgsubstate' in wctx and
1216 '.hgsubstate' not in changes[0] + changes[1] + changes[2]):
1216 '.hgsubstate' not in changes[0] + changes[1] + changes[2]):
1217 changes[2].insert(0, '.hgsubstate')
1217 changes[2].insert(0, '.hgsubstate')
1218
1218
1219 # make sure all explicit patterns are matched
1219 # make sure all explicit patterns are matched
1220 if not force and match.files():
1220 if not force and match.files():
1221 matched = set(changes[0] + changes[1] + changes[2])
1221 matched = set(changes[0] + changes[1] + changes[2])
1222
1222
1223 for f in match.files():
1223 for f in match.files():
1224 f = self.dirstate.normalize(f)
1224 f = self.dirstate.normalize(f)
1225 if f == '.' or f in matched or f in wctx.substate:
1225 if f == '.' or f in matched or f in wctx.substate:
1226 continue
1226 continue
1227 if f in changes[3]: # missing
1227 if f in changes[3]: # missing
1228 fail(f, _('file not found!'))
1228 fail(f, _('file not found!'))
1229 if f in vdirs: # visited directory
1229 if f in vdirs: # visited directory
1230 d = f + '/'
1230 d = f + '/'
1231 for mf in matched:
1231 for mf in matched:
1232 if mf.startswith(d):
1232 if mf.startswith(d):
1233 break
1233 break
1234 else:
1234 else:
1235 fail(f, _("no match under directory!"))
1235 fail(f, _("no match under directory!"))
1236 elif f not in self.dirstate:
1236 elif f not in self.dirstate:
1237 fail(f, _("file not tracked!"))
1237 fail(f, _("file not tracked!"))
1238
1238
1239 cctx = context.workingctx(self, text, user, date, extra, changes)
1239 cctx = context.workingctx(self, text, user, date, extra, changes)
1240
1240
1241 if (not force and not extra.get("close") and not merge
1241 if (not force and not extra.get("close") and not merge
1242 and not cctx.files()
1242 and not cctx.files()
1243 and wctx.branch() == wctx.p1().branch()):
1243 and wctx.branch() == wctx.p1().branch()):
1244 return None
1244 return None
1245
1245
1246 if merge and cctx.deleted():
1246 if merge and cctx.deleted():
1247 raise util.Abort(_("cannot commit merge with missing files"))
1247 raise util.Abort(_("cannot commit merge with missing files"))
1248
1248
1249 ms = mergemod.mergestate(self)
1249 ms = mergemod.mergestate(self)
1250 for f in changes[0]:
1250 for f in changes[0]:
1251 if f in ms and ms[f] == 'u':
1251 if f in ms and ms[f] == 'u':
1252 raise util.Abort(_("unresolved merge conflicts "
1252 raise util.Abort(_("unresolved merge conflicts "
1253 "(see hg help resolve)"))
1253 "(see hg help resolve)"))
1254
1254
1255 if editor:
1255 if editor:
1256 cctx._text = editor(self, cctx, subs)
1256 cctx._text = editor(self, cctx, subs)
1257 edited = (text != cctx._text)
1257 edited = (text != cctx._text)
1258
1258
1259 # commit subs and write new state
1259 # commit subs and write new state
1260 if subs:
1260 if subs:
1261 for s in sorted(commitsubs):
1261 for s in sorted(commitsubs):
1262 sub = wctx.sub(s)
1262 sub = wctx.sub(s)
1263 self.ui.status(_('committing subrepository %s\n') %
1263 self.ui.status(_('committing subrepository %s\n') %
1264 subrepo.subrelpath(sub))
1264 subrepo.subrelpath(sub))
1265 sr = sub.commit(cctx._text, user, date)
1265 sr = sub.commit(cctx._text, user, date)
1266 newstate[s] = (newstate[s][0], sr)
1266 newstate[s] = (newstate[s][0], sr)
1267 subrepo.writestate(self, newstate)
1267 subrepo.writestate(self, newstate)
1268
1268
1269 # Save commit message in case this transaction gets rolled back
1269 # Save commit message in case this transaction gets rolled back
1270 # (e.g. by a pretxncommit hook). Leave the content alone on
1270 # (e.g. by a pretxncommit hook). Leave the content alone on
1271 # the assumption that the user will use the same editor again.
1271 # the assumption that the user will use the same editor again.
1272 msgfn = self.savecommitmessage(cctx._text)
1272 msgfn = self.savecommitmessage(cctx._text)
1273
1273
1274 p1, p2 = self.dirstate.parents()
1274 p1, p2 = self.dirstate.parents()
1275 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1275 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1276 try:
1276 try:
1277 self.hook("precommit", throw=True, parent1=hookp1,
1277 self.hook("precommit", throw=True, parent1=hookp1,
1278 parent2=hookp2)
1278 parent2=hookp2)
1279 ret = self.commitctx(cctx, True)
1279 ret = self.commitctx(cctx, True)
1280 except: # re-raises
1280 except: # re-raises
1281 if edited:
1281 if edited:
1282 self.ui.write(
1282 self.ui.write(
1283 _('note: commit message saved in %s\n') % msgfn)
1283 _('note: commit message saved in %s\n') % msgfn)
1284 raise
1284 raise
1285
1285
1286 # update bookmarks, dirstate and mergestate
1286 # update bookmarks, dirstate and mergestate
1287 bookmarks.update(self, [p1, p2], ret)
1287 bookmarks.update(self, [p1, p2], ret)
1288 cctx.markcommitted(ret)
1288 cctx.markcommitted(ret)
1289 ms.reset()
1289 ms.reset()
1290 finally:
1290 finally:
1291 wlock.release()
1291 wlock.release()
1292
1292
1293 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
1293 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
1294 self.hook("commit", node=node, parent1=parent1, parent2=parent2)
1294 self.hook("commit", node=node, parent1=parent1, parent2=parent2)
1295 self._afterlock(commithook)
1295 self._afterlock(commithook)
1296 return ret
1296 return ret
1297
1297
1298 @unfilteredmethod
1298 @unfilteredmethod
1299 def commitctx(self, ctx, error=False):
1299 def commitctx(self, ctx, error=False):
1300 """Add a new revision to current repository.
1300 """Add a new revision to current repository.
1301 Revision information is passed via the context argument.
1301 Revision information is passed via the context argument.
1302 """
1302 """
1303
1303
1304 tr = lock = None
1304 tr = lock = None
1305 removed = list(ctx.removed())
1305 removed = list(ctx.removed())
1306 p1, p2 = ctx.p1(), ctx.p2()
1306 p1, p2 = ctx.p1(), ctx.p2()
1307 user = ctx.user()
1307 user = ctx.user()
1308
1308
1309 lock = self.lock()
1309 lock = self.lock()
1310 try:
1310 try:
1311 tr = self.transaction("commit")
1311 tr = self.transaction("commit")
1312 trp = weakref.proxy(tr)
1312 trp = weakref.proxy(tr)
1313
1313
1314 if ctx.files():
1314 if ctx.files():
1315 m1 = p1.manifest().copy()
1315 m1 = p1.manifest().copy()
1316 m2 = p2.manifest()
1316 m2 = p2.manifest()
1317
1317
1318 # check in files
1318 # check in files
1319 new = {}
1319 new = {}
1320 changed = []
1320 changed = []
1321 linkrev = len(self)
1321 linkrev = len(self)
1322 for f in sorted(ctx.modified() + ctx.added()):
1322 for f in sorted(ctx.modified() + ctx.added()):
1323 self.ui.note(f + "\n")
1323 self.ui.note(f + "\n")
1324 try:
1324 try:
1325 fctx = ctx[f]
1325 fctx = ctx[f]
1326 new[f] = self._filecommit(fctx, m1, m2, linkrev, trp,
1326 new[f] = self._filecommit(fctx, m1, m2, linkrev, trp,
1327 changed)
1327 changed)
1328 m1.set(f, fctx.flags())
1328 m1.set(f, fctx.flags())
1329 except OSError, inst:
1329 except OSError, inst:
1330 self.ui.warn(_("trouble committing %s!\n") % f)
1330 self.ui.warn(_("trouble committing %s!\n") % f)
1331 raise
1331 raise
1332 except IOError, inst:
1332 except IOError, inst:
1333 errcode = getattr(inst, 'errno', errno.ENOENT)
1333 errcode = getattr(inst, 'errno', errno.ENOENT)
1334 if error or errcode and errcode != errno.ENOENT:
1334 if error or errcode and errcode != errno.ENOENT:
1335 self.ui.warn(_("trouble committing %s!\n") % f)
1335 self.ui.warn(_("trouble committing %s!\n") % f)
1336 raise
1336 raise
1337 else:
1337 else:
1338 removed.append(f)
1338 removed.append(f)
1339
1339
1340 # update manifest
1340 # update manifest
1341 m1.update(new)
1341 m1.update(new)
1342 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1342 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1343 drop = [f for f in removed if f in m1]
1343 drop = [f for f in removed if f in m1]
1344 for f in drop:
1344 for f in drop:
1345 del m1[f]
1345 del m1[f]
1346 mn = self.manifest.add(m1, trp, linkrev, p1.manifestnode(),
1346 mn = self.manifest.add(m1, trp, linkrev, p1.manifestnode(),
1347 p2.manifestnode(), (new, drop))
1347 p2.manifestnode(), (new, drop))
1348 files = changed + removed
1348 files = changed + removed
1349 else:
1349 else:
1350 mn = p1.manifestnode()
1350 mn = p1.manifestnode()
1351 files = []
1351 files = []
1352
1352
1353 # update changelog
1353 # update changelog
1354 self.changelog.delayupdate()
1354 self.changelog.delayupdate()
1355 n = self.changelog.add(mn, files, ctx.description(),
1355 n = self.changelog.add(mn, files, ctx.description(),
1356 trp, p1.node(), p2.node(),
1356 trp, p1.node(), p2.node(),
1357 user, ctx.date(), ctx.extra().copy())
1357 user, ctx.date(), ctx.extra().copy())
1358 p = lambda: self.changelog.writepending() and self.root or ""
1358 p = lambda: self.changelog.writepending() and self.root or ""
1359 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1359 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1360 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1360 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1361 parent2=xp2, pending=p)
1361 parent2=xp2, pending=p)
1362 self.changelog.finalize(trp)
1362 self.changelog.finalize(trp)
1363 # set the new commit is proper phase
1363 # set the new commit is proper phase
1364 targetphase = phases.newcommitphase(self.ui)
1364 targetphase = phases.newcommitphase(self.ui)
1365 if targetphase:
1365 if targetphase:
1366 # retract boundary do not alter parent changeset.
1366 # retract boundary do not alter parent changeset.
1367 # if a parent have higher the resulting phase will
1367 # if a parent have higher the resulting phase will
1368 # be compliant anyway
1368 # be compliant anyway
1369 #
1369 #
1370 # if minimal phase was 0 we don't need to retract anything
1370 # if minimal phase was 0 we don't need to retract anything
1371 phases.retractboundary(self, targetphase, [n])
1371 phases.retractboundary(self, targetphase, [n])
1372 tr.close()
1372 tr.close()
1373 branchmap.updatecache(self.filtered('served'))
1373 branchmap.updatecache(self.filtered('served'))
1374 return n
1374 return n
1375 finally:
1375 finally:
1376 if tr:
1376 if tr:
1377 tr.release()
1377 tr.release()
1378 lock.release()
1378 lock.release()
1379
1379
1380 @unfilteredmethod
1380 @unfilteredmethod
1381 def destroying(self):
1381 def destroying(self):
1382 '''Inform the repository that nodes are about to be destroyed.
1382 '''Inform the repository that nodes are about to be destroyed.
1383 Intended for use by strip and rollback, so there's a common
1383 Intended for use by strip and rollback, so there's a common
1384 place for anything that has to be done before destroying history.
1384 place for anything that has to be done before destroying history.
1385
1385
1386 This is mostly useful for saving state that is in memory and waiting
1386 This is mostly useful for saving state that is in memory and waiting
1387 to be flushed when the current lock is released. Because a call to
1387 to be flushed when the current lock is released. Because a call to
1388 destroyed is imminent, the repo will be invalidated causing those
1388 destroyed is imminent, the repo will be invalidated causing those
1389 changes to stay in memory (waiting for the next unlock), or vanish
1389 changes to stay in memory (waiting for the next unlock), or vanish
1390 completely.
1390 completely.
1391 '''
1391 '''
1392 # When using the same lock to commit and strip, the phasecache is left
1392 # When using the same lock to commit and strip, the phasecache is left
1393 # dirty after committing. Then when we strip, the repo is invalidated,
1393 # dirty after committing. Then when we strip, the repo is invalidated,
1394 # causing those changes to disappear.
1394 # causing those changes to disappear.
1395 if '_phasecache' in vars(self):
1395 if '_phasecache' in vars(self):
1396 self._phasecache.write()
1396 self._phasecache.write()
1397
1397
1398 @unfilteredmethod
1398 @unfilteredmethod
1399 def destroyed(self):
1399 def destroyed(self):
1400 '''Inform the repository that nodes have been destroyed.
1400 '''Inform the repository that nodes have been destroyed.
1401 Intended for use by strip and rollback, so there's a common
1401 Intended for use by strip and rollback, so there's a common
1402 place for anything that has to be done after destroying history.
1402 place for anything that has to be done after destroying history.
1403 '''
1403 '''
1404 # When one tries to:
1404 # When one tries to:
1405 # 1) destroy nodes thus calling this method (e.g. strip)
1405 # 1) destroy nodes thus calling this method (e.g. strip)
1406 # 2) use phasecache somewhere (e.g. commit)
1406 # 2) use phasecache somewhere (e.g. commit)
1407 #
1407 #
1408 # then 2) will fail because the phasecache contains nodes that were
1408 # then 2) will fail because the phasecache contains nodes that were
1409 # removed. We can either remove phasecache from the filecache,
1409 # removed. We can either remove phasecache from the filecache,
1410 # causing it to reload next time it is accessed, or simply filter
1410 # causing it to reload next time it is accessed, or simply filter
1411 # the removed nodes now and write the updated cache.
1411 # the removed nodes now and write the updated cache.
1412 self._phasecache.filterunknown(self)
1412 self._phasecache.filterunknown(self)
1413 self._phasecache.write()
1413 self._phasecache.write()
1414
1414
1415 # update the 'served' branch cache to help read only server process
1415 # update the 'served' branch cache to help read only server process
1416 # Thanks to branchcache collaboration this is done from the nearest
1416 # Thanks to branchcache collaboration this is done from the nearest
1417 # filtered subset and it is expected to be fast.
1417 # filtered subset and it is expected to be fast.
1418 branchmap.updatecache(self.filtered('served'))
1418 branchmap.updatecache(self.filtered('served'))
1419
1419
1420 # Ensure the persistent tag cache is updated. Doing it now
1420 # Ensure the persistent tag cache is updated. Doing it now
1421 # means that the tag cache only has to worry about destroyed
1421 # means that the tag cache only has to worry about destroyed
1422 # heads immediately after a strip/rollback. That in turn
1422 # heads immediately after a strip/rollback. That in turn
1423 # guarantees that "cachetip == currenttip" (comparing both rev
1423 # guarantees that "cachetip == currenttip" (comparing both rev
1424 # and node) always means no nodes have been added or destroyed.
1424 # and node) always means no nodes have been added or destroyed.
1425
1425
1426 # XXX this is suboptimal when qrefresh'ing: we strip the current
1426 # XXX this is suboptimal when qrefresh'ing: we strip the current
1427 # head, refresh the tag cache, then immediately add a new head.
1427 # head, refresh the tag cache, then immediately add a new head.
1428 # But I think doing it this way is necessary for the "instant
1428 # But I think doing it this way is necessary for the "instant
1429 # tag cache retrieval" case to work.
1429 # tag cache retrieval" case to work.
1430 self.invalidate()
1430 self.invalidate()
1431
1431
1432 def walk(self, match, node=None):
1432 def walk(self, match, node=None):
1433 '''
1433 '''
1434 walk recursively through the directory tree or a given
1434 walk recursively through the directory tree or a given
1435 changeset, finding all files matched by the match
1435 changeset, finding all files matched by the match
1436 function
1436 function
1437 '''
1437 '''
1438 return self[node].walk(match)
1438 return self[node].walk(match)
1439
1439
1440 def status(self, node1='.', node2=None, match=None,
1440 def status(self, node1='.', node2=None, match=None,
1441 ignored=False, clean=False, unknown=False,
1441 ignored=False, clean=False, unknown=False,
1442 listsubrepos=False):
1442 listsubrepos=False):
1443 """return status of files between two nodes or node and working
1443 """return status of files between two nodes or node and working
1444 directory.
1444 directory.
1445
1445
1446 If node1 is None, use the first dirstate parent instead.
1446 If node1 is None, use the first dirstate parent instead.
1447 If node2 is None, compare node1 with working directory.
1447 If node2 is None, compare node1 with working directory.
1448 """
1448 """
1449
1449
1450 def mfmatches(ctx):
1450 def mfmatches(ctx):
1451 mf = ctx.manifest().copy()
1451 mf = ctx.manifest().copy()
1452 if match.always():
1452 if match.always():
1453 return mf
1453 return mf
1454 for fn in mf.keys():
1454 for fn in mf.keys():
1455 if not match(fn):
1455 if not match(fn):
1456 del mf[fn]
1456 del mf[fn]
1457 return mf
1457 return mf
1458
1458
1459 if isinstance(node1, context.changectx):
1459 if isinstance(node1, context.changectx):
1460 ctx1 = node1
1460 ctx1 = node1
1461 else:
1461 else:
1462 ctx1 = self[node1]
1462 ctx1 = self[node1]
1463 if isinstance(node2, context.changectx):
1463 if isinstance(node2, context.changectx):
1464 ctx2 = node2
1464 ctx2 = node2
1465 else:
1465 else:
1466 ctx2 = self[node2]
1466 ctx2 = self[node2]
1467
1467
1468 working = ctx2.rev() is None
1468 working = ctx2.rev() is None
1469 parentworking = working and ctx1 == self['.']
1469 parentworking = working and ctx1 == self['.']
1470 match = match or matchmod.always(self.root, self.getcwd())
1470 match = match or matchmod.always(self.root, self.getcwd())
1471 listignored, listclean, listunknown = ignored, clean, unknown
1471 listignored, listclean, listunknown = ignored, clean, unknown
1472
1472
1473 # load earliest manifest first for caching reasons
1473 # load earliest manifest first for caching reasons
1474 if not working and ctx2.rev() < ctx1.rev():
1474 if not working and ctx2.rev() < ctx1.rev():
1475 ctx2.manifest()
1475 ctx2.manifest()
1476
1476
1477 if not parentworking:
1477 if not parentworking:
1478 def bad(f, msg):
1478 def bad(f, msg):
1479 # 'f' may be a directory pattern from 'match.files()',
1479 # 'f' may be a directory pattern from 'match.files()',
1480 # so 'f not in ctx1' is not enough
1480 # so 'f not in ctx1' is not enough
1481 if f not in ctx1 and f not in ctx1.dirs():
1481 if f not in ctx1 and f not in ctx1.dirs():
1482 self.ui.warn('%s: %s\n' % (self.dirstate.pathto(f), msg))
1482 self.ui.warn('%s: %s\n' % (self.dirstate.pathto(f), msg))
1483 match.bad = bad
1483 match.bad = bad
1484
1484
1485 if working: # we need to scan the working dir
1485 if working: # we need to scan the working dir
1486 subrepos = []
1486 subrepos = []
1487 if '.hgsub' in self.dirstate:
1487 if '.hgsub' in self.dirstate:
1488 subrepos = sorted(ctx2.substate)
1488 subrepos = sorted(ctx2.substate)
1489 s = self.dirstate.status(match, subrepos, listignored,
1489 s = self.dirstate.status(match, subrepos, listignored,
1490 listclean, listunknown)
1490 listclean, listunknown)
1491 cmp, modified, added, removed, deleted, unknown, ignored, clean = s
1491 cmp, modified, added, removed, deleted, unknown, ignored, clean = s
1492
1492
1493 # check for any possibly clean files
1493 # check for any possibly clean files
1494 if parentworking and cmp:
1494 if parentworking and cmp:
1495 fixup = []
1495 fixup = []
1496 # do a full compare of any files that might have changed
1496 # do a full compare of any files that might have changed
1497 for f in sorted(cmp):
1497 for f in sorted(cmp):
1498 if (f not in ctx1 or ctx2.flags(f) != ctx1.flags(f)
1498 if (f not in ctx1 or ctx2.flags(f) != ctx1.flags(f)
1499 or ctx1[f].cmp(ctx2[f])):
1499 or ctx1[f].cmp(ctx2[f])):
1500 modified.append(f)
1500 modified.append(f)
1501 else:
1501 else:
1502 fixup.append(f)
1502 fixup.append(f)
1503
1503
1504 # update dirstate for files that are actually clean
1504 # update dirstate for files that are actually clean
1505 if fixup:
1505 if fixup:
1506 if listclean:
1506 if listclean:
1507 clean += fixup
1507 clean += fixup
1508
1508
1509 try:
1509 try:
1510 # updating the dirstate is optional
1510 # updating the dirstate is optional
1511 # so we don't wait on the lock
1511 # so we don't wait on the lock
1512 wlock = self.wlock(False)
1512 wlock = self.wlock(False)
1513 try:
1513 try:
1514 for f in fixup:
1514 for f in fixup:
1515 self.dirstate.normal(f)
1515 self.dirstate.normal(f)
1516 finally:
1516 finally:
1517 wlock.release()
1517 wlock.release()
1518 except error.LockError:
1518 except error.LockError:
1519 pass
1519 pass
1520
1520
1521 if not parentworking:
1521 if not parentworking:
1522 mf1 = mfmatches(ctx1)
1522 mf1 = mfmatches(ctx1)
1523 if working:
1523 if working:
1524 # we are comparing working dir against non-parent
1524 # we are comparing working dir against non-parent
1525 # generate a pseudo-manifest for the working dir
1525 # generate a pseudo-manifest for the working dir
1526 mf2 = mfmatches(self['.'])
1526 mf2 = mfmatches(self['.'])
1527 for f in cmp + modified + added:
1527 for f in cmp + modified + added:
1528 mf2[f] = None
1528 mf2[f] = None
1529 mf2.set(f, ctx2.flags(f))
1529 mf2.set(f, ctx2.flags(f))
1530 for f in removed:
1530 for f in removed:
1531 if f in mf2:
1531 if f in mf2:
1532 del mf2[f]
1532 del mf2[f]
1533 else:
1533 else:
1534 # we are comparing two revisions
1534 # we are comparing two revisions
1535 deleted, unknown, ignored = [], [], []
1535 deleted, unknown, ignored = [], [], []
1536 mf2 = mfmatches(ctx2)
1536 mf2 = mfmatches(ctx2)
1537
1537
1538 modified, added, clean = [], [], []
1538 modified, added, clean = [], [], []
1539 withflags = mf1.withflags() | mf2.withflags()
1539 withflags = mf1.withflags() | mf2.withflags()
1540 for fn, mf2node in mf2.iteritems():
1540 for fn, mf2node in mf2.iteritems():
1541 if fn in mf1:
1541 if fn in mf1:
1542 if (fn not in deleted and
1542 if (fn not in deleted and
1543 ((fn in withflags and mf1.flags(fn) != mf2.flags(fn)) or
1543 ((fn in withflags and mf1.flags(fn) != mf2.flags(fn)) or
1544 (mf1[fn] != mf2node and
1544 (mf1[fn] != mf2node and
1545 (mf2node or ctx1[fn].cmp(ctx2[fn]))))):
1545 (mf2node or ctx1[fn].cmp(ctx2[fn]))))):
1546 modified.append(fn)
1546 modified.append(fn)
1547 elif listclean:
1547 elif listclean:
1548 clean.append(fn)
1548 clean.append(fn)
1549 del mf1[fn]
1549 del mf1[fn]
1550 elif fn not in deleted:
1550 elif fn not in deleted:
1551 added.append(fn)
1551 added.append(fn)
1552 removed = mf1.keys()
1552 removed = mf1.keys()
1553
1553
1554 if working and modified and not self.dirstate._checklink:
1554 if working and modified and not self.dirstate._checklink:
1555 # Symlink placeholders may get non-symlink-like contents
1555 # Symlink placeholders may get non-symlink-like contents
1556 # via user error or dereferencing by NFS or Samba servers,
1556 # via user error or dereferencing by NFS or Samba servers,
1557 # so we filter out any placeholders that don't look like a
1557 # so we filter out any placeholders that don't look like a
1558 # symlink
1558 # symlink
1559 sane = []
1559 sane = []
1560 for f in modified:
1560 for f in modified:
1561 if ctx2.flags(f) == 'l':
1561 if ctx2.flags(f) == 'l':
1562 d = ctx2[f].data()
1562 d = ctx2[f].data()
1563 if len(d) >= 1024 or '\n' in d or util.binary(d):
1563 if len(d) >= 1024 or '\n' in d or util.binary(d):
1564 self.ui.debug('ignoring suspect symlink placeholder'
1564 self.ui.debug('ignoring suspect symlink placeholder'
1565 ' "%s"\n' % f)
1565 ' "%s"\n' % f)
1566 continue
1566 continue
1567 sane.append(f)
1567 sane.append(f)
1568 modified = sane
1568 modified = sane
1569
1569
1570 r = modified, added, removed, deleted, unknown, ignored, clean
1570 r = modified, added, removed, deleted, unknown, ignored, clean
1571
1571
1572 if listsubrepos:
1572 if listsubrepos:
1573 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
1573 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
1574 if working:
1574 if working:
1575 rev2 = None
1575 rev2 = None
1576 else:
1576 else:
1577 rev2 = ctx2.substate[subpath][1]
1577 rev2 = ctx2.substate[subpath][1]
1578 try:
1578 try:
1579 submatch = matchmod.narrowmatcher(subpath, match)
1579 submatch = matchmod.narrowmatcher(subpath, match)
1580 s = sub.status(rev2, match=submatch, ignored=listignored,
1580 s = sub.status(rev2, match=submatch, ignored=listignored,
1581 clean=listclean, unknown=listunknown,
1581 clean=listclean, unknown=listunknown,
1582 listsubrepos=True)
1582 listsubrepos=True)
1583 for rfiles, sfiles in zip(r, s):
1583 for rfiles, sfiles in zip(r, s):
1584 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
1584 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
1585 except error.LookupError:
1585 except error.LookupError:
1586 self.ui.status(_("skipping missing subrepository: %s\n")
1586 self.ui.status(_("skipping missing subrepository: %s\n")
1587 % subpath)
1587 % subpath)
1588
1588
1589 for l in r:
1589 for l in r:
1590 l.sort()
1590 l.sort()
1591 return r
1591 return r
1592
1592
1593 def heads(self, start=None):
1593 def heads(self, start=None):
1594 heads = self.changelog.heads(start)
1594 heads = self.changelog.heads(start)
1595 # sort the output in rev descending order
1595 # sort the output in rev descending order
1596 return sorted(heads, key=self.changelog.rev, reverse=True)
1596 return sorted(heads, key=self.changelog.rev, reverse=True)
1597
1597
1598 def branchheads(self, branch=None, start=None, closed=False):
1598 def branchheads(self, branch=None, start=None, closed=False):
1599 '''return a (possibly filtered) list of heads for the given branch
1599 '''return a (possibly filtered) list of heads for the given branch
1600
1600
1601 Heads are returned in topological order, from newest to oldest.
1601 Heads are returned in topological order, from newest to oldest.
1602 If branch is None, use the dirstate branch.
1602 If branch is None, use the dirstate branch.
1603 If start is not None, return only heads reachable from start.
1603 If start is not None, return only heads reachable from start.
1604 If closed is True, return heads that are marked as closed as well.
1604 If closed is True, return heads that are marked as closed as well.
1605 '''
1605 '''
1606 if branch is None:
1606 if branch is None:
1607 branch = self[None].branch()
1607 branch = self[None].branch()
1608 branches = self.branchmap()
1608 branches = self.branchmap()
1609 if branch not in branches:
1609 if branch not in branches:
1610 return []
1610 return []
1611 # the cache returns heads ordered lowest to highest
1611 # the cache returns heads ordered lowest to highest
1612 bheads = list(reversed(branches[branch]))
1612 bheads = list(reversed(branches[branch]))
1613 if start is not None:
1613 if start is not None:
1614 # filter out the heads that cannot be reached from startrev
1614 # filter out the heads that cannot be reached from startrev
1615 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1615 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1616 bheads = [h for h in bheads if h in fbheads]
1616 bheads = [h for h in bheads if h in fbheads]
1617 if not closed:
1617 if not closed:
1618 bheads = [h for h in bheads if not self[h].closesbranch()]
1618 bheads = [h for h in bheads if not self[h].closesbranch()]
1619 return bheads
1619 return bheads
1620
1620
1621 def branches(self, nodes):
1621 def branches(self, nodes):
1622 if not nodes:
1622 if not nodes:
1623 nodes = [self.changelog.tip()]
1623 nodes = [self.changelog.tip()]
1624 b = []
1624 b = []
1625 for n in nodes:
1625 for n in nodes:
1626 t = n
1626 t = n
1627 while True:
1627 while True:
1628 p = self.changelog.parents(n)
1628 p = self.changelog.parents(n)
1629 if p[1] != nullid or p[0] == nullid:
1629 if p[1] != nullid or p[0] == nullid:
1630 b.append((t, n, p[0], p[1]))
1630 b.append((t, n, p[0], p[1]))
1631 break
1631 break
1632 n = p[0]
1632 n = p[0]
1633 return b
1633 return b
1634
1634
1635 def between(self, pairs):
1635 def between(self, pairs):
1636 r = []
1636 r = []
1637
1637
1638 for top, bottom in pairs:
1638 for top, bottom in pairs:
1639 n, l, i = top, [], 0
1639 n, l, i = top, [], 0
1640 f = 1
1640 f = 1
1641
1641
1642 while n != bottom and n != nullid:
1642 while n != bottom and n != nullid:
1643 p = self.changelog.parents(n)[0]
1643 p = self.changelog.parents(n)[0]
1644 if i == f:
1644 if i == f:
1645 l.append(n)
1645 l.append(n)
1646 f = f * 2
1646 f = f * 2
1647 n = p
1647 n = p
1648 i += 1
1648 i += 1
1649
1649
1650 r.append(l)
1650 r.append(l)
1651
1651
1652 return r
1652 return r
1653
1653
1654 def pull(self, remote, heads=None, force=False):
1654 def pull(self, remote, heads=None, force=False):
1655 # don't open transaction for nothing or you break future useful
1655 # don't open transaction for nothing or you break future useful
1656 # rollback call
1656 # rollback call
1657 tr = None
1657 tr = None
1658 trname = 'pull\n' + util.hidepassword(remote.url())
1658 trname = 'pull\n' + util.hidepassword(remote.url())
1659 lock = self.lock()
1659 lock = self.lock()
1660 try:
1660 try:
1661 tmp = discovery.findcommonincoming(self, remote, heads=heads,
1661 tmp = discovery.findcommonincoming(self, remote, heads=heads,
1662 force=force)
1662 force=force)
1663 common, fetch, rheads = tmp
1663 common, fetch, rheads = tmp
1664 if not fetch:
1664 if not fetch:
1665 self.ui.status(_("no changes found\n"))
1665 self.ui.status(_("no changes found\n"))
1666 added = []
1666 added = []
1667 result = 0
1667 result = 0
1668 else:
1668 else:
1669 tr = self.transaction(trname)
1669 tr = self.transaction(trname)
1670 if heads is None and list(common) == [nullid]:
1670 if heads is None and list(common) == [nullid]:
1671 self.ui.status(_("requesting all changes\n"))
1671 self.ui.status(_("requesting all changes\n"))
1672 elif heads is None and remote.capable('changegroupsubset'):
1672 elif heads is None and remote.capable('changegroupsubset'):
1673 # issue1320, avoid a race if remote changed after discovery
1673 # issue1320, avoid a race if remote changed after discovery
1674 heads = rheads
1674 heads = rheads
1675
1675
1676 if remote.capable('getbundle'):
1676 if remote.capable('getbundle'):
1677 cg = remote.getbundle('pull', common=common,
1677 cg = remote.getbundle('pull', common=common,
1678 heads=heads or rheads)
1678 heads=heads or rheads)
1679 elif heads is None:
1679 elif heads is None:
1680 cg = remote.changegroup(fetch, 'pull')
1680 cg = remote.changegroup(fetch, 'pull')
1681 elif not remote.capable('changegroupsubset'):
1681 elif not remote.capable('changegroupsubset'):
1682 raise util.Abort(_("partial pull cannot be done because "
1682 raise util.Abort(_("partial pull cannot be done because "
1683 "other repository doesn't support "
1683 "other repository doesn't support "
1684 "changegroupsubset."))
1684 "changegroupsubset."))
1685 else:
1685 else:
1686 cg = remote.changegroupsubset(fetch, heads, 'pull')
1686 cg = remote.changegroupsubset(fetch, heads, 'pull')
1687 # we use unfiltered changelog here because hidden revision must
1687 # we use unfiltered changelog here because hidden revision must
1688 # be taken in account for phase synchronization. They may
1688 # be taken in account for phase synchronization. They may
1689 # becomes public and becomes visible again.
1689 # becomes public and becomes visible again.
1690 cl = self.unfiltered().changelog
1690 cl = self.unfiltered().changelog
1691 clstart = len(cl)
1691 clstart = len(cl)
1692 result = self.addchangegroup(cg, 'pull', remote.url())
1692 result = self.addchangegroup(cg, 'pull', remote.url())
1693 clend = len(cl)
1693 clend = len(cl)
1694 added = [cl.node(r) for r in xrange(clstart, clend)]
1694 added = [cl.node(r) for r in xrange(clstart, clend)]
1695
1695
1696 # compute target subset
1696 # compute target subset
1697 if heads is None:
1697 if heads is None:
1698 # We pulled every thing possible
1698 # We pulled every thing possible
1699 # sync on everything common
1699 # sync on everything common
1700 subset = common + added
1700 subset = common + added
1701 else:
1701 else:
1702 # We pulled a specific subset
1702 # We pulled a specific subset
1703 # sync on this subset
1703 # sync on this subset
1704 subset = heads
1704 subset = heads
1705
1705
1706 # Get remote phases data from remote
1706 # Get remote phases data from remote
1707 remotephases = remote.listkeys('phases')
1707 remotephases = remote.listkeys('phases')
1708 publishing = bool(remotephases.get('publishing', False))
1708 publishing = bool(remotephases.get('publishing', False))
1709 if remotephases and not publishing:
1709 if remotephases and not publishing:
1710 # remote is new and unpublishing
1710 # remote is new and unpublishing
1711 pheads, _dr = phases.analyzeremotephases(self, subset,
1711 pheads, _dr = phases.analyzeremotephases(self, subset,
1712 remotephases)
1712 remotephases)
1713 phases.advanceboundary(self, phases.public, pheads)
1713 phases.advanceboundary(self, phases.public, pheads)
1714 phases.advanceboundary(self, phases.draft, subset)
1714 phases.advanceboundary(self, phases.draft, subset)
1715 else:
1715 else:
1716 # Remote is old or publishing all common changesets
1716 # Remote is old or publishing all common changesets
1717 # should be seen as public
1717 # should be seen as public
1718 phases.advanceboundary(self, phases.public, subset)
1718 phases.advanceboundary(self, phases.public, subset)
1719
1719
1720 if obsolete._enabled:
1720 def gettransaction():
1721 self.ui.debug('fetching remote obsolete markers\n')
1721 if tr is None:
1722 remoteobs = remote.listkeys('obsolete')
1722 return self.transaction(trname)
1723 if 'dump0' in remoteobs:
1723 return tr
1724 if tr is None:
1724
1725 tr = self.transaction(trname)
1725 obstr = obsolete.syncpull(self, remote, gettransaction)
1726 for key in sorted(remoteobs, reverse=True):
1726 if obstr is not None:
1727 if key.startswith('dump'):
1727 tr = obstr
1728 data = base85.b85decode(remoteobs[key])
1728
1729 self.obsstore.mergemarkers(tr, data)
1730 self.invalidatevolatilesets()
1731 if tr is not None:
1729 if tr is not None:
1732 tr.close()
1730 tr.close()
1733 finally:
1731 finally:
1734 if tr is not None:
1732 if tr is not None:
1735 tr.release()
1733 tr.release()
1736 lock.release()
1734 lock.release()
1737
1735
1738 return result
1736 return result
1739
1737
1740 def checkpush(self, force, revs):
1738 def checkpush(self, force, revs):
1741 """Extensions can override this function if additional checks have
1739 """Extensions can override this function if additional checks have
1742 to be performed before pushing, or call it if they override push
1740 to be performed before pushing, or call it if they override push
1743 command.
1741 command.
1744 """
1742 """
1745 pass
1743 pass
1746
1744
1747 def push(self, remote, force=False, revs=None, newbranch=False):
1745 def push(self, remote, force=False, revs=None, newbranch=False):
1748 '''Push outgoing changesets (limited by revs) from the current
1746 '''Push outgoing changesets (limited by revs) from the current
1749 repository to remote. Return an integer:
1747 repository to remote. Return an integer:
1750 - None means nothing to push
1748 - None means nothing to push
1751 - 0 means HTTP error
1749 - 0 means HTTP error
1752 - 1 means we pushed and remote head count is unchanged *or*
1750 - 1 means we pushed and remote head count is unchanged *or*
1753 we have outgoing changesets but refused to push
1751 we have outgoing changesets but refused to push
1754 - other values as described by addchangegroup()
1752 - other values as described by addchangegroup()
1755 '''
1753 '''
1756 # there are two ways to push to remote repo:
1754 # there are two ways to push to remote repo:
1757 #
1755 #
1758 # addchangegroup assumes local user can lock remote
1756 # addchangegroup assumes local user can lock remote
1759 # repo (local filesystem, old ssh servers).
1757 # repo (local filesystem, old ssh servers).
1760 #
1758 #
1761 # unbundle assumes local user cannot lock remote repo (new ssh
1759 # unbundle assumes local user cannot lock remote repo (new ssh
1762 # servers, http servers).
1760 # servers, http servers).
1763
1761
1764 if not remote.canpush():
1762 if not remote.canpush():
1765 raise util.Abort(_("destination does not support push"))
1763 raise util.Abort(_("destination does not support push"))
1766 unfi = self.unfiltered()
1764 unfi = self.unfiltered()
1767 # get local lock as we might write phase data
1765 # get local lock as we might write phase data
1768 locallock = self.lock()
1766 locallock = self.lock()
1769 try:
1767 try:
1770 self.checkpush(force, revs)
1768 self.checkpush(force, revs)
1771 lock = None
1769 lock = None
1772 unbundle = remote.capable('unbundle')
1770 unbundle = remote.capable('unbundle')
1773 if not unbundle:
1771 if not unbundle:
1774 lock = remote.lock()
1772 lock = remote.lock()
1775 try:
1773 try:
1776 # discovery
1774 # discovery
1777 fci = discovery.findcommonincoming
1775 fci = discovery.findcommonincoming
1778 commoninc = fci(unfi, remote, force=force)
1776 commoninc = fci(unfi, remote, force=force)
1779 common, inc, remoteheads = commoninc
1777 common, inc, remoteheads = commoninc
1780 fco = discovery.findcommonoutgoing
1778 fco = discovery.findcommonoutgoing
1781 outgoing = fco(unfi, remote, onlyheads=revs,
1779 outgoing = fco(unfi, remote, onlyheads=revs,
1782 commoninc=commoninc, force=force)
1780 commoninc=commoninc, force=force)
1783
1781
1784
1782
1785 if not outgoing.missing:
1783 if not outgoing.missing:
1786 # nothing to push
1784 # nothing to push
1787 scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
1785 scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
1788 ret = None
1786 ret = None
1789 else:
1787 else:
1790 # something to push
1788 # something to push
1791 if not force:
1789 if not force:
1792 # if self.obsstore == False --> no obsolete
1790 # if self.obsstore == False --> no obsolete
1793 # then, save the iteration
1791 # then, save the iteration
1794 if unfi.obsstore:
1792 if unfi.obsstore:
1795 # this message are here for 80 char limit reason
1793 # this message are here for 80 char limit reason
1796 mso = _("push includes obsolete changeset: %s!")
1794 mso = _("push includes obsolete changeset: %s!")
1797 mst = "push includes %s changeset: %s!"
1795 mst = "push includes %s changeset: %s!"
1798 # plain versions for i18n tool to detect them
1796 # plain versions for i18n tool to detect them
1799 _("push includes unstable changeset: %s!")
1797 _("push includes unstable changeset: %s!")
1800 _("push includes bumped changeset: %s!")
1798 _("push includes bumped changeset: %s!")
1801 _("push includes divergent changeset: %s!")
1799 _("push includes divergent changeset: %s!")
1802 # If we are to push if there is at least one
1800 # If we are to push if there is at least one
1803 # obsolete or unstable changeset in missing, at
1801 # obsolete or unstable changeset in missing, at
1804 # least one of the missinghead will be obsolete or
1802 # least one of the missinghead will be obsolete or
1805 # unstable. So checking heads only is ok
1803 # unstable. So checking heads only is ok
1806 for node in outgoing.missingheads:
1804 for node in outgoing.missingheads:
1807 ctx = unfi[node]
1805 ctx = unfi[node]
1808 if ctx.obsolete():
1806 if ctx.obsolete():
1809 raise util.Abort(mso % ctx)
1807 raise util.Abort(mso % ctx)
1810 elif ctx.troubled():
1808 elif ctx.troubled():
1811 raise util.Abort(_(mst)
1809 raise util.Abort(_(mst)
1812 % (ctx.troubles()[0],
1810 % (ctx.troubles()[0],
1813 ctx))
1811 ctx))
1814 discovery.checkheads(unfi, remote, outgoing,
1812 discovery.checkheads(unfi, remote, outgoing,
1815 remoteheads, newbranch,
1813 remoteheads, newbranch,
1816 bool(inc))
1814 bool(inc))
1817
1815
1818 # create a changegroup from local
1816 # create a changegroup from local
1819 if revs is None and not outgoing.excluded:
1817 if revs is None and not outgoing.excluded:
1820 # push everything,
1818 # push everything,
1821 # use the fast path, no race possible on push
1819 # use the fast path, no race possible on push
1822 cg = self._changegroup(outgoing.missing, 'push')
1820 cg = self._changegroup(outgoing.missing, 'push')
1823 else:
1821 else:
1824 cg = self.getlocalbundle('push', outgoing)
1822 cg = self.getlocalbundle('push', outgoing)
1825
1823
1826 # apply changegroup to remote
1824 # apply changegroup to remote
1827 if unbundle:
1825 if unbundle:
1828 # local repo finds heads on server, finds out what
1826 # local repo finds heads on server, finds out what
1829 # revs it must push. once revs transferred, if server
1827 # revs it must push. once revs transferred, if server
1830 # finds it has different heads (someone else won
1828 # finds it has different heads (someone else won
1831 # commit/push race), server aborts.
1829 # commit/push race), server aborts.
1832 if force:
1830 if force:
1833 remoteheads = ['force']
1831 remoteheads = ['force']
1834 # ssh: return remote's addchangegroup()
1832 # ssh: return remote's addchangegroup()
1835 # http: return remote's addchangegroup() or 0 for error
1833 # http: return remote's addchangegroup() or 0 for error
1836 ret = remote.unbundle(cg, remoteheads, 'push')
1834 ret = remote.unbundle(cg, remoteheads, 'push')
1837 else:
1835 else:
1838 # we return an integer indicating remote head count
1836 # we return an integer indicating remote head count
1839 # change
1837 # change
1840 ret = remote.addchangegroup(cg, 'push', self.url())
1838 ret = remote.addchangegroup(cg, 'push', self.url())
1841
1839
1842 if ret:
1840 if ret:
1843 # push succeed, synchronize target of the push
1841 # push succeed, synchronize target of the push
1844 cheads = outgoing.missingheads
1842 cheads = outgoing.missingheads
1845 elif revs is None:
1843 elif revs is None:
1846 # All out push fails. synchronize all common
1844 # All out push fails. synchronize all common
1847 cheads = outgoing.commonheads
1845 cheads = outgoing.commonheads
1848 else:
1846 else:
1849 # I want cheads = heads(::missingheads and ::commonheads)
1847 # I want cheads = heads(::missingheads and ::commonheads)
1850 # (missingheads is revs with secret changeset filtered out)
1848 # (missingheads is revs with secret changeset filtered out)
1851 #
1849 #
1852 # This can be expressed as:
1850 # This can be expressed as:
1853 # cheads = ( (missingheads and ::commonheads)
1851 # cheads = ( (missingheads and ::commonheads)
1854 # + (commonheads and ::missingheads))"
1852 # + (commonheads and ::missingheads))"
1855 # )
1853 # )
1856 #
1854 #
1857 # while trying to push we already computed the following:
1855 # while trying to push we already computed the following:
1858 # common = (::commonheads)
1856 # common = (::commonheads)
1859 # missing = ((commonheads::missingheads) - commonheads)
1857 # missing = ((commonheads::missingheads) - commonheads)
1860 #
1858 #
1861 # We can pick:
1859 # We can pick:
1862 # * missingheads part of common (::commonheads)
1860 # * missingheads part of common (::commonheads)
1863 common = set(outgoing.common)
1861 common = set(outgoing.common)
1864 cheads = [node for node in revs if node in common]
1862 cheads = [node for node in revs if node in common]
1865 # and
1863 # and
1866 # * commonheads parents on missing
1864 # * commonheads parents on missing
1867 revset = unfi.set('%ln and parents(roots(%ln))',
1865 revset = unfi.set('%ln and parents(roots(%ln))',
1868 outgoing.commonheads,
1866 outgoing.commonheads,
1869 outgoing.missing)
1867 outgoing.missing)
1870 cheads.extend(c.node() for c in revset)
1868 cheads.extend(c.node() for c in revset)
1871 # even when we don't push, exchanging phase data is useful
1869 # even when we don't push, exchanging phase data is useful
1872 remotephases = remote.listkeys('phases')
1870 remotephases = remote.listkeys('phases')
1873 if (self.ui.configbool('ui', '_usedassubrepo', False)
1871 if (self.ui.configbool('ui', '_usedassubrepo', False)
1874 and remotephases # server supports phases
1872 and remotephases # server supports phases
1875 and ret is None # nothing was pushed
1873 and ret is None # nothing was pushed
1876 and remotephases.get('publishing', False)):
1874 and remotephases.get('publishing', False)):
1877 # When:
1875 # When:
1878 # - this is a subrepo push
1876 # - this is a subrepo push
1879 # - and remote support phase
1877 # - and remote support phase
1880 # - and no changeset was pushed
1878 # - and no changeset was pushed
1881 # - and remote is publishing
1879 # - and remote is publishing
1882 # We may be in issue 3871 case!
1880 # We may be in issue 3871 case!
1883 # We drop the possible phase synchronisation done by
1881 # We drop the possible phase synchronisation done by
1884 # courtesy to publish changesets possibly locally draft
1882 # courtesy to publish changesets possibly locally draft
1885 # on the remote.
1883 # on the remote.
1886 remotephases = {'publishing': 'True'}
1884 remotephases = {'publishing': 'True'}
1887 if not remotephases: # old server or public only repo
1885 if not remotephases: # old server or public only repo
1888 phases.advanceboundary(self, phases.public, cheads)
1886 phases.advanceboundary(self, phases.public, cheads)
1889 # don't push any phase data as there is nothing to push
1887 # don't push any phase data as there is nothing to push
1890 else:
1888 else:
1891 ana = phases.analyzeremotephases(self, cheads, remotephases)
1889 ana = phases.analyzeremotephases(self, cheads, remotephases)
1892 pheads, droots = ana
1890 pheads, droots = ana
1893 ### Apply remote phase on local
1891 ### Apply remote phase on local
1894 if remotephases.get('publishing', False):
1892 if remotephases.get('publishing', False):
1895 phases.advanceboundary(self, phases.public, cheads)
1893 phases.advanceboundary(self, phases.public, cheads)
1896 else: # publish = False
1894 else: # publish = False
1897 phases.advanceboundary(self, phases.public, pheads)
1895 phases.advanceboundary(self, phases.public, pheads)
1898 phases.advanceboundary(self, phases.draft, cheads)
1896 phases.advanceboundary(self, phases.draft, cheads)
1899 ### Apply local phase on remote
1897 ### Apply local phase on remote
1900
1898
1901 # Get the list of all revs draft on remote by public here.
1899 # Get the list of all revs draft on remote by public here.
1902 # XXX Beware that revset break if droots is not strictly
1900 # XXX Beware that revset break if droots is not strictly
1903 # XXX root we may want to ensure it is but it is costly
1901 # XXX root we may want to ensure it is but it is costly
1904 outdated = unfi.set('heads((%ln::%ln) and public())',
1902 outdated = unfi.set('heads((%ln::%ln) and public())',
1905 droots, cheads)
1903 droots, cheads)
1906 for newremotehead in outdated:
1904 for newremotehead in outdated:
1907 r = remote.pushkey('phases',
1905 r = remote.pushkey('phases',
1908 newremotehead.hex(),
1906 newremotehead.hex(),
1909 str(phases.draft),
1907 str(phases.draft),
1910 str(phases.public))
1908 str(phases.public))
1911 if not r:
1909 if not r:
1912 self.ui.warn(_('updating %s to public failed!\n')
1910 self.ui.warn(_('updating %s to public failed!\n')
1913 % newremotehead)
1911 % newremotehead)
1914 self.ui.debug('try to push obsolete markers to remote\n')
1912 self.ui.debug('try to push obsolete markers to remote\n')
1915 obsolete.syncpush(self, remote)
1913 obsolete.syncpush(self, remote)
1916 finally:
1914 finally:
1917 if lock is not None:
1915 if lock is not None:
1918 lock.release()
1916 lock.release()
1919 finally:
1917 finally:
1920 locallock.release()
1918 locallock.release()
1921
1919
1922 self.ui.debug("checking for updated bookmarks\n")
1920 self.ui.debug("checking for updated bookmarks\n")
1923 rb = remote.listkeys('bookmarks')
1921 rb = remote.listkeys('bookmarks')
1924 for k in rb.keys():
1922 for k in rb.keys():
1925 if k in unfi._bookmarks:
1923 if k in unfi._bookmarks:
1926 nr, nl = rb[k], hex(self._bookmarks[k])
1924 nr, nl = rb[k], hex(self._bookmarks[k])
1927 if nr in unfi:
1925 if nr in unfi:
1928 cr = unfi[nr]
1926 cr = unfi[nr]
1929 cl = unfi[nl]
1927 cl = unfi[nl]
1930 if bookmarks.validdest(unfi, cr, cl):
1928 if bookmarks.validdest(unfi, cr, cl):
1931 r = remote.pushkey('bookmarks', k, nr, nl)
1929 r = remote.pushkey('bookmarks', k, nr, nl)
1932 if r:
1930 if r:
1933 self.ui.status(_("updating bookmark %s\n") % k)
1931 self.ui.status(_("updating bookmark %s\n") % k)
1934 else:
1932 else:
1935 self.ui.warn(_('updating bookmark %s'
1933 self.ui.warn(_('updating bookmark %s'
1936 ' failed!\n') % k)
1934 ' failed!\n') % k)
1937
1935
1938 return ret
1936 return ret
1939
1937
1940 def changegroupinfo(self, nodes, source):
1938 def changegroupinfo(self, nodes, source):
1941 if self.ui.verbose or source == 'bundle':
1939 if self.ui.verbose or source == 'bundle':
1942 self.ui.status(_("%d changesets found\n") % len(nodes))
1940 self.ui.status(_("%d changesets found\n") % len(nodes))
1943 if self.ui.debugflag:
1941 if self.ui.debugflag:
1944 self.ui.debug("list of changesets:\n")
1942 self.ui.debug("list of changesets:\n")
1945 for node in nodes:
1943 for node in nodes:
1946 self.ui.debug("%s\n" % hex(node))
1944 self.ui.debug("%s\n" % hex(node))
1947
1945
1948 def changegroupsubset(self, bases, heads, source):
1946 def changegroupsubset(self, bases, heads, source):
1949 """Compute a changegroup consisting of all the nodes that are
1947 """Compute a changegroup consisting of all the nodes that are
1950 descendants of any of the bases and ancestors of any of the heads.
1948 descendants of any of the bases and ancestors of any of the heads.
1951 Return a chunkbuffer object whose read() method will return
1949 Return a chunkbuffer object whose read() method will return
1952 successive changegroup chunks.
1950 successive changegroup chunks.
1953
1951
1954 It is fairly complex as determining which filenodes and which
1952 It is fairly complex as determining which filenodes and which
1955 manifest nodes need to be included for the changeset to be complete
1953 manifest nodes need to be included for the changeset to be complete
1956 is non-trivial.
1954 is non-trivial.
1957
1955
1958 Another wrinkle is doing the reverse, figuring out which changeset in
1956 Another wrinkle is doing the reverse, figuring out which changeset in
1959 the changegroup a particular filenode or manifestnode belongs to.
1957 the changegroup a particular filenode or manifestnode belongs to.
1960 """
1958 """
1961 cl = self.changelog
1959 cl = self.changelog
1962 if not bases:
1960 if not bases:
1963 bases = [nullid]
1961 bases = [nullid]
1964 csets, bases, heads = cl.nodesbetween(bases, heads)
1962 csets, bases, heads = cl.nodesbetween(bases, heads)
1965 # We assume that all ancestors of bases are known
1963 # We assume that all ancestors of bases are known
1966 common = cl.ancestors([cl.rev(n) for n in bases])
1964 common = cl.ancestors([cl.rev(n) for n in bases])
1967 return self._changegroupsubset(common, csets, heads, source)
1965 return self._changegroupsubset(common, csets, heads, source)
1968
1966
1969 def getlocalbundle(self, source, outgoing):
1967 def getlocalbundle(self, source, outgoing):
1970 """Like getbundle, but taking a discovery.outgoing as an argument.
1968 """Like getbundle, but taking a discovery.outgoing as an argument.
1971
1969
1972 This is only implemented for local repos and reuses potentially
1970 This is only implemented for local repos and reuses potentially
1973 precomputed sets in outgoing."""
1971 precomputed sets in outgoing."""
1974 if not outgoing.missing:
1972 if not outgoing.missing:
1975 return None
1973 return None
1976 return self._changegroupsubset(outgoing.common,
1974 return self._changegroupsubset(outgoing.common,
1977 outgoing.missing,
1975 outgoing.missing,
1978 outgoing.missingheads,
1976 outgoing.missingheads,
1979 source)
1977 source)
1980
1978
1981 def getbundle(self, source, heads=None, common=None):
1979 def getbundle(self, source, heads=None, common=None):
1982 """Like changegroupsubset, but returns the set difference between the
1980 """Like changegroupsubset, but returns the set difference between the
1983 ancestors of heads and the ancestors common.
1981 ancestors of heads and the ancestors common.
1984
1982
1985 If heads is None, use the local heads. If common is None, use [nullid].
1983 If heads is None, use the local heads. If common is None, use [nullid].
1986
1984
1987 The nodes in common might not all be known locally due to the way the
1985 The nodes in common might not all be known locally due to the way the
1988 current discovery protocol works.
1986 current discovery protocol works.
1989 """
1987 """
1990 cl = self.changelog
1988 cl = self.changelog
1991 if common:
1989 if common:
1992 hasnode = cl.hasnode
1990 hasnode = cl.hasnode
1993 common = [n for n in common if hasnode(n)]
1991 common = [n for n in common if hasnode(n)]
1994 else:
1992 else:
1995 common = [nullid]
1993 common = [nullid]
1996 if not heads:
1994 if not heads:
1997 heads = cl.heads()
1995 heads = cl.heads()
1998 return self.getlocalbundle(source,
1996 return self.getlocalbundle(source,
1999 discovery.outgoing(cl, common, heads))
1997 discovery.outgoing(cl, common, heads))
2000
1998
2001 @unfilteredmethod
1999 @unfilteredmethod
2002 def _changegroupsubset(self, commonrevs, csets, heads, source):
2000 def _changegroupsubset(self, commonrevs, csets, heads, source):
2003
2001
2004 cl = self.changelog
2002 cl = self.changelog
2005 mf = self.manifest
2003 mf = self.manifest
2006 mfs = {} # needed manifests
2004 mfs = {} # needed manifests
2007 fnodes = {} # needed file nodes
2005 fnodes = {} # needed file nodes
2008 changedfiles = set()
2006 changedfiles = set()
2009 fstate = ['', {}]
2007 fstate = ['', {}]
2010 count = [0, 0]
2008 count = [0, 0]
2011
2009
2012 # can we go through the fast path ?
2010 # can we go through the fast path ?
2013 heads.sort()
2011 heads.sort()
2014 if heads == sorted(self.heads()):
2012 if heads == sorted(self.heads()):
2015 return self._changegroup(csets, source)
2013 return self._changegroup(csets, source)
2016
2014
2017 # slow path
2015 # slow path
2018 self.hook('preoutgoing', throw=True, source=source)
2016 self.hook('preoutgoing', throw=True, source=source)
2019 self.changegroupinfo(csets, source)
2017 self.changegroupinfo(csets, source)
2020
2018
2021 # filter any nodes that claim to be part of the known set
2019 # filter any nodes that claim to be part of the known set
2022 def prune(revlog, missing):
2020 def prune(revlog, missing):
2023 rr, rl = revlog.rev, revlog.linkrev
2021 rr, rl = revlog.rev, revlog.linkrev
2024 return [n for n in missing
2022 return [n for n in missing
2025 if rl(rr(n)) not in commonrevs]
2023 if rl(rr(n)) not in commonrevs]
2026
2024
2027 progress = self.ui.progress
2025 progress = self.ui.progress
2028 _bundling = _('bundling')
2026 _bundling = _('bundling')
2029 _changesets = _('changesets')
2027 _changesets = _('changesets')
2030 _manifests = _('manifests')
2028 _manifests = _('manifests')
2031 _files = _('files')
2029 _files = _('files')
2032
2030
2033 def lookup(revlog, x):
2031 def lookup(revlog, x):
2034 if revlog == cl:
2032 if revlog == cl:
2035 c = cl.read(x)
2033 c = cl.read(x)
2036 changedfiles.update(c[3])
2034 changedfiles.update(c[3])
2037 mfs.setdefault(c[0], x)
2035 mfs.setdefault(c[0], x)
2038 count[0] += 1
2036 count[0] += 1
2039 progress(_bundling, count[0],
2037 progress(_bundling, count[0],
2040 unit=_changesets, total=count[1])
2038 unit=_changesets, total=count[1])
2041 return x
2039 return x
2042 elif revlog == mf:
2040 elif revlog == mf:
2043 clnode = mfs[x]
2041 clnode = mfs[x]
2044 mdata = mf.readfast(x)
2042 mdata = mf.readfast(x)
2045 for f, n in mdata.iteritems():
2043 for f, n in mdata.iteritems():
2046 if f in changedfiles:
2044 if f in changedfiles:
2047 fnodes[f].setdefault(n, clnode)
2045 fnodes[f].setdefault(n, clnode)
2048 count[0] += 1
2046 count[0] += 1
2049 progress(_bundling, count[0],
2047 progress(_bundling, count[0],
2050 unit=_manifests, total=count[1])
2048 unit=_manifests, total=count[1])
2051 return clnode
2049 return clnode
2052 else:
2050 else:
2053 progress(_bundling, count[0], item=fstate[0],
2051 progress(_bundling, count[0], item=fstate[0],
2054 unit=_files, total=count[1])
2052 unit=_files, total=count[1])
2055 return fstate[1][x]
2053 return fstate[1][x]
2056
2054
2057 bundler = changegroup.bundle10(lookup)
2055 bundler = changegroup.bundle10(lookup)
2058 reorder = self.ui.config('bundle', 'reorder', 'auto')
2056 reorder = self.ui.config('bundle', 'reorder', 'auto')
2059 if reorder == 'auto':
2057 if reorder == 'auto':
2060 reorder = None
2058 reorder = None
2061 else:
2059 else:
2062 reorder = util.parsebool(reorder)
2060 reorder = util.parsebool(reorder)
2063
2061
2064 def gengroup():
2062 def gengroup():
2065 # Create a changenode group generator that will call our functions
2063 # Create a changenode group generator that will call our functions
2066 # back to lookup the owning changenode and collect information.
2064 # back to lookup the owning changenode and collect information.
2067 count[:] = [0, len(csets)]
2065 count[:] = [0, len(csets)]
2068 for chunk in cl.group(csets, bundler, reorder=reorder):
2066 for chunk in cl.group(csets, bundler, reorder=reorder):
2069 yield chunk
2067 yield chunk
2070 progress(_bundling, None)
2068 progress(_bundling, None)
2071
2069
2072 # Create a generator for the manifestnodes that calls our lookup
2070 # Create a generator for the manifestnodes that calls our lookup
2073 # and data collection functions back.
2071 # and data collection functions back.
2074 for f in changedfiles:
2072 for f in changedfiles:
2075 fnodes[f] = {}
2073 fnodes[f] = {}
2076 count[:] = [0, len(mfs)]
2074 count[:] = [0, len(mfs)]
2077 for chunk in mf.group(prune(mf, mfs), bundler, reorder=reorder):
2075 for chunk in mf.group(prune(mf, mfs), bundler, reorder=reorder):
2078 yield chunk
2076 yield chunk
2079 progress(_bundling, None)
2077 progress(_bundling, None)
2080
2078
2081 mfs.clear()
2079 mfs.clear()
2082
2080
2083 # Go through all our files in order sorted by name.
2081 # Go through all our files in order sorted by name.
2084 count[:] = [0, len(changedfiles)]
2082 count[:] = [0, len(changedfiles)]
2085 for fname in sorted(changedfiles):
2083 for fname in sorted(changedfiles):
2086 filerevlog = self.file(fname)
2084 filerevlog = self.file(fname)
2087 if not len(filerevlog):
2085 if not len(filerevlog):
2088 raise util.Abort(_("empty or missing revlog for %s")
2086 raise util.Abort(_("empty or missing revlog for %s")
2089 % fname)
2087 % fname)
2090 fstate[0] = fname
2088 fstate[0] = fname
2091 fstate[1] = fnodes.pop(fname, {})
2089 fstate[1] = fnodes.pop(fname, {})
2092
2090
2093 nodelist = prune(filerevlog, fstate[1])
2091 nodelist = prune(filerevlog, fstate[1])
2094 if nodelist:
2092 if nodelist:
2095 count[0] += 1
2093 count[0] += 1
2096 yield bundler.fileheader(fname)
2094 yield bundler.fileheader(fname)
2097 for chunk in filerevlog.group(nodelist, bundler, reorder):
2095 for chunk in filerevlog.group(nodelist, bundler, reorder):
2098 yield chunk
2096 yield chunk
2099
2097
2100 # Signal that no more groups are left.
2098 # Signal that no more groups are left.
2101 yield bundler.close()
2099 yield bundler.close()
2102 progress(_bundling, None)
2100 progress(_bundling, None)
2103
2101
2104 if csets:
2102 if csets:
2105 self.hook('outgoing', node=hex(csets[0]), source=source)
2103 self.hook('outgoing', node=hex(csets[0]), source=source)
2106
2104
2107 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
2105 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
2108
2106
2109 def changegroup(self, basenodes, source):
2107 def changegroup(self, basenodes, source):
2110 # to avoid a race we use changegroupsubset() (issue1320)
2108 # to avoid a race we use changegroupsubset() (issue1320)
2111 return self.changegroupsubset(basenodes, self.heads(), source)
2109 return self.changegroupsubset(basenodes, self.heads(), source)
2112
2110
2113 @unfilteredmethod
2111 @unfilteredmethod
2114 def _changegroup(self, nodes, source):
2112 def _changegroup(self, nodes, source):
2115 """Compute the changegroup of all nodes that we have that a recipient
2113 """Compute the changegroup of all nodes that we have that a recipient
2116 doesn't. Return a chunkbuffer object whose read() method will return
2114 doesn't. Return a chunkbuffer object whose read() method will return
2117 successive changegroup chunks.
2115 successive changegroup chunks.
2118
2116
2119 This is much easier than the previous function as we can assume that
2117 This is much easier than the previous function as we can assume that
2120 the recipient has any changenode we aren't sending them.
2118 the recipient has any changenode we aren't sending them.
2121
2119
2122 nodes is the set of nodes to send"""
2120 nodes is the set of nodes to send"""
2123
2121
2124 cl = self.changelog
2122 cl = self.changelog
2125 mf = self.manifest
2123 mf = self.manifest
2126 mfs = {}
2124 mfs = {}
2127 changedfiles = set()
2125 changedfiles = set()
2128 fstate = ['']
2126 fstate = ['']
2129 count = [0, 0]
2127 count = [0, 0]
2130
2128
2131 self.hook('preoutgoing', throw=True, source=source)
2129 self.hook('preoutgoing', throw=True, source=source)
2132 self.changegroupinfo(nodes, source)
2130 self.changegroupinfo(nodes, source)
2133
2131
2134 revset = set([cl.rev(n) for n in nodes])
2132 revset = set([cl.rev(n) for n in nodes])
2135
2133
2136 def gennodelst(log):
2134 def gennodelst(log):
2137 ln, llr = log.node, log.linkrev
2135 ln, llr = log.node, log.linkrev
2138 return [ln(r) for r in log if llr(r) in revset]
2136 return [ln(r) for r in log if llr(r) in revset]
2139
2137
2140 progress = self.ui.progress
2138 progress = self.ui.progress
2141 _bundling = _('bundling')
2139 _bundling = _('bundling')
2142 _changesets = _('changesets')
2140 _changesets = _('changesets')
2143 _manifests = _('manifests')
2141 _manifests = _('manifests')
2144 _files = _('files')
2142 _files = _('files')
2145
2143
2146 def lookup(revlog, x):
2144 def lookup(revlog, x):
2147 if revlog == cl:
2145 if revlog == cl:
2148 c = cl.read(x)
2146 c = cl.read(x)
2149 changedfiles.update(c[3])
2147 changedfiles.update(c[3])
2150 mfs.setdefault(c[0], x)
2148 mfs.setdefault(c[0], x)
2151 count[0] += 1
2149 count[0] += 1
2152 progress(_bundling, count[0],
2150 progress(_bundling, count[0],
2153 unit=_changesets, total=count[1])
2151 unit=_changesets, total=count[1])
2154 return x
2152 return x
2155 elif revlog == mf:
2153 elif revlog == mf:
2156 count[0] += 1
2154 count[0] += 1
2157 progress(_bundling, count[0],
2155 progress(_bundling, count[0],
2158 unit=_manifests, total=count[1])
2156 unit=_manifests, total=count[1])
2159 return cl.node(revlog.linkrev(revlog.rev(x)))
2157 return cl.node(revlog.linkrev(revlog.rev(x)))
2160 else:
2158 else:
2161 progress(_bundling, count[0], item=fstate[0],
2159 progress(_bundling, count[0], item=fstate[0],
2162 total=count[1], unit=_files)
2160 total=count[1], unit=_files)
2163 return cl.node(revlog.linkrev(revlog.rev(x)))
2161 return cl.node(revlog.linkrev(revlog.rev(x)))
2164
2162
2165 bundler = changegroup.bundle10(lookup)
2163 bundler = changegroup.bundle10(lookup)
2166 reorder = self.ui.config('bundle', 'reorder', 'auto')
2164 reorder = self.ui.config('bundle', 'reorder', 'auto')
2167 if reorder == 'auto':
2165 if reorder == 'auto':
2168 reorder = None
2166 reorder = None
2169 else:
2167 else:
2170 reorder = util.parsebool(reorder)
2168 reorder = util.parsebool(reorder)
2171
2169
2172 def gengroup():
2170 def gengroup():
2173 '''yield a sequence of changegroup chunks (strings)'''
2171 '''yield a sequence of changegroup chunks (strings)'''
2174 # construct a list of all changed files
2172 # construct a list of all changed files
2175
2173
2176 count[:] = [0, len(nodes)]
2174 count[:] = [0, len(nodes)]
2177 for chunk in cl.group(nodes, bundler, reorder=reorder):
2175 for chunk in cl.group(nodes, bundler, reorder=reorder):
2178 yield chunk
2176 yield chunk
2179 progress(_bundling, None)
2177 progress(_bundling, None)
2180
2178
2181 count[:] = [0, len(mfs)]
2179 count[:] = [0, len(mfs)]
2182 for chunk in mf.group(gennodelst(mf), bundler, reorder=reorder):
2180 for chunk in mf.group(gennodelst(mf), bundler, reorder=reorder):
2183 yield chunk
2181 yield chunk
2184 progress(_bundling, None)
2182 progress(_bundling, None)
2185
2183
2186 count[:] = [0, len(changedfiles)]
2184 count[:] = [0, len(changedfiles)]
2187 for fname in sorted(changedfiles):
2185 for fname in sorted(changedfiles):
2188 filerevlog = self.file(fname)
2186 filerevlog = self.file(fname)
2189 if not len(filerevlog):
2187 if not len(filerevlog):
2190 raise util.Abort(_("empty or missing revlog for %s")
2188 raise util.Abort(_("empty or missing revlog for %s")
2191 % fname)
2189 % fname)
2192 fstate[0] = fname
2190 fstate[0] = fname
2193 nodelist = gennodelst(filerevlog)
2191 nodelist = gennodelst(filerevlog)
2194 if nodelist:
2192 if nodelist:
2195 count[0] += 1
2193 count[0] += 1
2196 yield bundler.fileheader(fname)
2194 yield bundler.fileheader(fname)
2197 for chunk in filerevlog.group(nodelist, bundler, reorder):
2195 for chunk in filerevlog.group(nodelist, bundler, reorder):
2198 yield chunk
2196 yield chunk
2199 yield bundler.close()
2197 yield bundler.close()
2200 progress(_bundling, None)
2198 progress(_bundling, None)
2201
2199
2202 if nodes:
2200 if nodes:
2203 self.hook('outgoing', node=hex(nodes[0]), source=source)
2201 self.hook('outgoing', node=hex(nodes[0]), source=source)
2204
2202
2205 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
2203 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
2206
2204
2207 @unfilteredmethod
2205 @unfilteredmethod
2208 def addchangegroup(self, source, srctype, url, emptyok=False):
2206 def addchangegroup(self, source, srctype, url, emptyok=False):
2209 """Add the changegroup returned by source.read() to this repo.
2207 """Add the changegroup returned by source.read() to this repo.
2210 srctype is a string like 'push', 'pull', or 'unbundle'. url is
2208 srctype is a string like 'push', 'pull', or 'unbundle'. url is
2211 the URL of the repo where this changegroup is coming from.
2209 the URL of the repo where this changegroup is coming from.
2212
2210
2213 Return an integer summarizing the change to this repo:
2211 Return an integer summarizing the change to this repo:
2214 - nothing changed or no source: 0
2212 - nothing changed or no source: 0
2215 - more heads than before: 1+added heads (2..n)
2213 - more heads than before: 1+added heads (2..n)
2216 - fewer heads than before: -1-removed heads (-2..-n)
2214 - fewer heads than before: -1-removed heads (-2..-n)
2217 - number of heads stays the same: 1
2215 - number of heads stays the same: 1
2218 """
2216 """
2219 def csmap(x):
2217 def csmap(x):
2220 self.ui.debug("add changeset %s\n" % short(x))
2218 self.ui.debug("add changeset %s\n" % short(x))
2221 return len(cl)
2219 return len(cl)
2222
2220
2223 def revmap(x):
2221 def revmap(x):
2224 return cl.rev(x)
2222 return cl.rev(x)
2225
2223
2226 if not source:
2224 if not source:
2227 return 0
2225 return 0
2228
2226
2229 self.hook('prechangegroup', throw=True, source=srctype, url=url)
2227 self.hook('prechangegroup', throw=True, source=srctype, url=url)
2230
2228
2231 changesets = files = revisions = 0
2229 changesets = files = revisions = 0
2232 efiles = set()
2230 efiles = set()
2233
2231
2234 # write changelog data to temp files so concurrent readers will not see
2232 # write changelog data to temp files so concurrent readers will not see
2235 # inconsistent view
2233 # inconsistent view
2236 cl = self.changelog
2234 cl = self.changelog
2237 cl.delayupdate()
2235 cl.delayupdate()
2238 oldheads = cl.heads()
2236 oldheads = cl.heads()
2239
2237
2240 tr = self.transaction("\n".join([srctype, util.hidepassword(url)]))
2238 tr = self.transaction("\n".join([srctype, util.hidepassword(url)]))
2241 try:
2239 try:
2242 trp = weakref.proxy(tr)
2240 trp = weakref.proxy(tr)
2243 # pull off the changeset group
2241 # pull off the changeset group
2244 self.ui.status(_("adding changesets\n"))
2242 self.ui.status(_("adding changesets\n"))
2245 clstart = len(cl)
2243 clstart = len(cl)
2246 class prog(object):
2244 class prog(object):
2247 step = _('changesets')
2245 step = _('changesets')
2248 count = 1
2246 count = 1
2249 ui = self.ui
2247 ui = self.ui
2250 total = None
2248 total = None
2251 def __call__(self):
2249 def __call__(self):
2252 self.ui.progress(self.step, self.count, unit=_('chunks'),
2250 self.ui.progress(self.step, self.count, unit=_('chunks'),
2253 total=self.total)
2251 total=self.total)
2254 self.count += 1
2252 self.count += 1
2255 pr = prog()
2253 pr = prog()
2256 source.callback = pr
2254 source.callback = pr
2257
2255
2258 source.changelogheader()
2256 source.changelogheader()
2259 srccontent = cl.addgroup(source, csmap, trp)
2257 srccontent = cl.addgroup(source, csmap, trp)
2260 if not (srccontent or emptyok):
2258 if not (srccontent or emptyok):
2261 raise util.Abort(_("received changelog group is empty"))
2259 raise util.Abort(_("received changelog group is empty"))
2262 clend = len(cl)
2260 clend = len(cl)
2263 changesets = clend - clstart
2261 changesets = clend - clstart
2264 for c in xrange(clstart, clend):
2262 for c in xrange(clstart, clend):
2265 efiles.update(self[c].files())
2263 efiles.update(self[c].files())
2266 efiles = len(efiles)
2264 efiles = len(efiles)
2267 self.ui.progress(_('changesets'), None)
2265 self.ui.progress(_('changesets'), None)
2268
2266
2269 # pull off the manifest group
2267 # pull off the manifest group
2270 self.ui.status(_("adding manifests\n"))
2268 self.ui.status(_("adding manifests\n"))
2271 pr.step = _('manifests')
2269 pr.step = _('manifests')
2272 pr.count = 1
2270 pr.count = 1
2273 pr.total = changesets # manifests <= changesets
2271 pr.total = changesets # manifests <= changesets
2274 # no need to check for empty manifest group here:
2272 # no need to check for empty manifest group here:
2275 # if the result of the merge of 1 and 2 is the same in 3 and 4,
2273 # if the result of the merge of 1 and 2 is the same in 3 and 4,
2276 # no new manifest will be created and the manifest group will
2274 # no new manifest will be created and the manifest group will
2277 # be empty during the pull
2275 # be empty during the pull
2278 source.manifestheader()
2276 source.manifestheader()
2279 self.manifest.addgroup(source, revmap, trp)
2277 self.manifest.addgroup(source, revmap, trp)
2280 self.ui.progress(_('manifests'), None)
2278 self.ui.progress(_('manifests'), None)
2281
2279
2282 needfiles = {}
2280 needfiles = {}
2283 if self.ui.configbool('server', 'validate', default=False):
2281 if self.ui.configbool('server', 'validate', default=False):
2284 # validate incoming csets have their manifests
2282 # validate incoming csets have their manifests
2285 for cset in xrange(clstart, clend):
2283 for cset in xrange(clstart, clend):
2286 mfest = self.changelog.read(self.changelog.node(cset))[0]
2284 mfest = self.changelog.read(self.changelog.node(cset))[0]
2287 mfest = self.manifest.readdelta(mfest)
2285 mfest = self.manifest.readdelta(mfest)
2288 # store file nodes we must see
2286 # store file nodes we must see
2289 for f, n in mfest.iteritems():
2287 for f, n in mfest.iteritems():
2290 needfiles.setdefault(f, set()).add(n)
2288 needfiles.setdefault(f, set()).add(n)
2291
2289
2292 # process the files
2290 # process the files
2293 self.ui.status(_("adding file changes\n"))
2291 self.ui.status(_("adding file changes\n"))
2294 pr.step = _('files')
2292 pr.step = _('files')
2295 pr.count = 1
2293 pr.count = 1
2296 pr.total = efiles
2294 pr.total = efiles
2297 source.callback = None
2295 source.callback = None
2298
2296
2299 while True:
2297 while True:
2300 chunkdata = source.filelogheader()
2298 chunkdata = source.filelogheader()
2301 if not chunkdata:
2299 if not chunkdata:
2302 break
2300 break
2303 f = chunkdata["filename"]
2301 f = chunkdata["filename"]
2304 self.ui.debug("adding %s revisions\n" % f)
2302 self.ui.debug("adding %s revisions\n" % f)
2305 pr()
2303 pr()
2306 fl = self.file(f)
2304 fl = self.file(f)
2307 o = len(fl)
2305 o = len(fl)
2308 if not fl.addgroup(source, revmap, trp):
2306 if not fl.addgroup(source, revmap, trp):
2309 raise util.Abort(_("received file revlog group is empty"))
2307 raise util.Abort(_("received file revlog group is empty"))
2310 revisions += len(fl) - o
2308 revisions += len(fl) - o
2311 files += 1
2309 files += 1
2312 if f in needfiles:
2310 if f in needfiles:
2313 needs = needfiles[f]
2311 needs = needfiles[f]
2314 for new in xrange(o, len(fl)):
2312 for new in xrange(o, len(fl)):
2315 n = fl.node(new)
2313 n = fl.node(new)
2316 if n in needs:
2314 if n in needs:
2317 needs.remove(n)
2315 needs.remove(n)
2318 else:
2316 else:
2319 raise util.Abort(
2317 raise util.Abort(
2320 _("received spurious file revlog entry"))
2318 _("received spurious file revlog entry"))
2321 if not needs:
2319 if not needs:
2322 del needfiles[f]
2320 del needfiles[f]
2323 self.ui.progress(_('files'), None)
2321 self.ui.progress(_('files'), None)
2324
2322
2325 for f, needs in needfiles.iteritems():
2323 for f, needs in needfiles.iteritems():
2326 fl = self.file(f)
2324 fl = self.file(f)
2327 for n in needs:
2325 for n in needs:
2328 try:
2326 try:
2329 fl.rev(n)
2327 fl.rev(n)
2330 except error.LookupError:
2328 except error.LookupError:
2331 raise util.Abort(
2329 raise util.Abort(
2332 _('missing file data for %s:%s - run hg verify') %
2330 _('missing file data for %s:%s - run hg verify') %
2333 (f, hex(n)))
2331 (f, hex(n)))
2334
2332
2335 dh = 0
2333 dh = 0
2336 if oldheads:
2334 if oldheads:
2337 heads = cl.heads()
2335 heads = cl.heads()
2338 dh = len(heads) - len(oldheads)
2336 dh = len(heads) - len(oldheads)
2339 for h in heads:
2337 for h in heads:
2340 if h not in oldheads and self[h].closesbranch():
2338 if h not in oldheads and self[h].closesbranch():
2341 dh -= 1
2339 dh -= 1
2342 htext = ""
2340 htext = ""
2343 if dh:
2341 if dh:
2344 htext = _(" (%+d heads)") % dh
2342 htext = _(" (%+d heads)") % dh
2345
2343
2346 self.ui.status(_("added %d changesets"
2344 self.ui.status(_("added %d changesets"
2347 " with %d changes to %d files%s\n")
2345 " with %d changes to %d files%s\n")
2348 % (changesets, revisions, files, htext))
2346 % (changesets, revisions, files, htext))
2349 self.invalidatevolatilesets()
2347 self.invalidatevolatilesets()
2350
2348
2351 if changesets > 0:
2349 if changesets > 0:
2352 p = lambda: cl.writepending() and self.root or ""
2350 p = lambda: cl.writepending() and self.root or ""
2353 self.hook('pretxnchangegroup', throw=True,
2351 self.hook('pretxnchangegroup', throw=True,
2354 node=hex(cl.node(clstart)), source=srctype,
2352 node=hex(cl.node(clstart)), source=srctype,
2355 url=url, pending=p)
2353 url=url, pending=p)
2356
2354
2357 added = [cl.node(r) for r in xrange(clstart, clend)]
2355 added = [cl.node(r) for r in xrange(clstart, clend)]
2358 publishing = self.ui.configbool('phases', 'publish', True)
2356 publishing = self.ui.configbool('phases', 'publish', True)
2359 if srctype == 'push':
2357 if srctype == 'push':
2360 # Old server can not push the boundary themself.
2358 # Old server can not push the boundary themself.
2361 # New server won't push the boundary if changeset already
2359 # New server won't push the boundary if changeset already
2362 # existed locally as secrete
2360 # existed locally as secrete
2363 #
2361 #
2364 # We should not use added here but the list of all change in
2362 # We should not use added here but the list of all change in
2365 # the bundle
2363 # the bundle
2366 if publishing:
2364 if publishing:
2367 phases.advanceboundary(self, phases.public, srccontent)
2365 phases.advanceboundary(self, phases.public, srccontent)
2368 else:
2366 else:
2369 phases.advanceboundary(self, phases.draft, srccontent)
2367 phases.advanceboundary(self, phases.draft, srccontent)
2370 phases.retractboundary(self, phases.draft, added)
2368 phases.retractboundary(self, phases.draft, added)
2371 elif srctype != 'strip':
2369 elif srctype != 'strip':
2372 # publishing only alter behavior during push
2370 # publishing only alter behavior during push
2373 #
2371 #
2374 # strip should not touch boundary at all
2372 # strip should not touch boundary at all
2375 phases.retractboundary(self, phases.draft, added)
2373 phases.retractboundary(self, phases.draft, added)
2376
2374
2377 # make changelog see real files again
2375 # make changelog see real files again
2378 cl.finalize(trp)
2376 cl.finalize(trp)
2379
2377
2380 tr.close()
2378 tr.close()
2381
2379
2382 if changesets > 0:
2380 if changesets > 0:
2383 if srctype != 'strip':
2381 if srctype != 'strip':
2384 # During strip, branchcache is invalid but coming call to
2382 # During strip, branchcache is invalid but coming call to
2385 # `destroyed` will repair it.
2383 # `destroyed` will repair it.
2386 # In other case we can safely update cache on disk.
2384 # In other case we can safely update cache on disk.
2387 branchmap.updatecache(self.filtered('served'))
2385 branchmap.updatecache(self.filtered('served'))
2388 def runhooks():
2386 def runhooks():
2389 # forcefully update the on-disk branch cache
2387 # forcefully update the on-disk branch cache
2390 self.ui.debug("updating the branch cache\n")
2388 self.ui.debug("updating the branch cache\n")
2391 self.hook("changegroup", node=hex(cl.node(clstart)),
2389 self.hook("changegroup", node=hex(cl.node(clstart)),
2392 source=srctype, url=url)
2390 source=srctype, url=url)
2393
2391
2394 for n in added:
2392 for n in added:
2395 self.hook("incoming", node=hex(n), source=srctype,
2393 self.hook("incoming", node=hex(n), source=srctype,
2396 url=url)
2394 url=url)
2397
2395
2398 newheads = [h for h in self.heads() if h not in oldheads]
2396 newheads = [h for h in self.heads() if h not in oldheads]
2399 self.ui.log("incoming",
2397 self.ui.log("incoming",
2400 "%s incoming changes - new heads: %s\n",
2398 "%s incoming changes - new heads: %s\n",
2401 len(added),
2399 len(added),
2402 ', '.join([hex(c[:6]) for c in newheads]))
2400 ', '.join([hex(c[:6]) for c in newheads]))
2403 self._afterlock(runhooks)
2401 self._afterlock(runhooks)
2404
2402
2405 finally:
2403 finally:
2406 tr.release()
2404 tr.release()
2407 # never return 0 here:
2405 # never return 0 here:
2408 if dh < 0:
2406 if dh < 0:
2409 return dh - 1
2407 return dh - 1
2410 else:
2408 else:
2411 return dh + 1
2409 return dh + 1
2412
2410
2413 def stream_in(self, remote, requirements):
2411 def stream_in(self, remote, requirements):
2414 lock = self.lock()
2412 lock = self.lock()
2415 try:
2413 try:
2416 # Save remote branchmap. We will use it later
2414 # Save remote branchmap. We will use it later
2417 # to speed up branchcache creation
2415 # to speed up branchcache creation
2418 rbranchmap = None
2416 rbranchmap = None
2419 if remote.capable("branchmap"):
2417 if remote.capable("branchmap"):
2420 rbranchmap = remote.branchmap()
2418 rbranchmap = remote.branchmap()
2421
2419
2422 fp = remote.stream_out()
2420 fp = remote.stream_out()
2423 l = fp.readline()
2421 l = fp.readline()
2424 try:
2422 try:
2425 resp = int(l)
2423 resp = int(l)
2426 except ValueError:
2424 except ValueError:
2427 raise error.ResponseError(
2425 raise error.ResponseError(
2428 _('unexpected response from remote server:'), l)
2426 _('unexpected response from remote server:'), l)
2429 if resp == 1:
2427 if resp == 1:
2430 raise util.Abort(_('operation forbidden by server'))
2428 raise util.Abort(_('operation forbidden by server'))
2431 elif resp == 2:
2429 elif resp == 2:
2432 raise util.Abort(_('locking the remote repository failed'))
2430 raise util.Abort(_('locking the remote repository failed'))
2433 elif resp != 0:
2431 elif resp != 0:
2434 raise util.Abort(_('the server sent an unknown error code'))
2432 raise util.Abort(_('the server sent an unknown error code'))
2435 self.ui.status(_('streaming all changes\n'))
2433 self.ui.status(_('streaming all changes\n'))
2436 l = fp.readline()
2434 l = fp.readline()
2437 try:
2435 try:
2438 total_files, total_bytes = map(int, l.split(' ', 1))
2436 total_files, total_bytes = map(int, l.split(' ', 1))
2439 except (ValueError, TypeError):
2437 except (ValueError, TypeError):
2440 raise error.ResponseError(
2438 raise error.ResponseError(
2441 _('unexpected response from remote server:'), l)
2439 _('unexpected response from remote server:'), l)
2442 self.ui.status(_('%d files to transfer, %s of data\n') %
2440 self.ui.status(_('%d files to transfer, %s of data\n') %
2443 (total_files, util.bytecount(total_bytes)))
2441 (total_files, util.bytecount(total_bytes)))
2444 handled_bytes = 0
2442 handled_bytes = 0
2445 self.ui.progress(_('clone'), 0, total=total_bytes)
2443 self.ui.progress(_('clone'), 0, total=total_bytes)
2446 start = time.time()
2444 start = time.time()
2447 for i in xrange(total_files):
2445 for i in xrange(total_files):
2448 # XXX doesn't support '\n' or '\r' in filenames
2446 # XXX doesn't support '\n' or '\r' in filenames
2449 l = fp.readline()
2447 l = fp.readline()
2450 try:
2448 try:
2451 name, size = l.split('\0', 1)
2449 name, size = l.split('\0', 1)
2452 size = int(size)
2450 size = int(size)
2453 except (ValueError, TypeError):
2451 except (ValueError, TypeError):
2454 raise error.ResponseError(
2452 raise error.ResponseError(
2455 _('unexpected response from remote server:'), l)
2453 _('unexpected response from remote server:'), l)
2456 if self.ui.debugflag:
2454 if self.ui.debugflag:
2457 self.ui.debug('adding %s (%s)\n' %
2455 self.ui.debug('adding %s (%s)\n' %
2458 (name, util.bytecount(size)))
2456 (name, util.bytecount(size)))
2459 # for backwards compat, name was partially encoded
2457 # for backwards compat, name was partially encoded
2460 ofp = self.sopener(store.decodedir(name), 'w')
2458 ofp = self.sopener(store.decodedir(name), 'w')
2461 for chunk in util.filechunkiter(fp, limit=size):
2459 for chunk in util.filechunkiter(fp, limit=size):
2462 handled_bytes += len(chunk)
2460 handled_bytes += len(chunk)
2463 self.ui.progress(_('clone'), handled_bytes,
2461 self.ui.progress(_('clone'), handled_bytes,
2464 total=total_bytes)
2462 total=total_bytes)
2465 ofp.write(chunk)
2463 ofp.write(chunk)
2466 ofp.close()
2464 ofp.close()
2467 elapsed = time.time() - start
2465 elapsed = time.time() - start
2468 if elapsed <= 0:
2466 if elapsed <= 0:
2469 elapsed = 0.001
2467 elapsed = 0.001
2470 self.ui.progress(_('clone'), None)
2468 self.ui.progress(_('clone'), None)
2471 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
2469 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
2472 (util.bytecount(total_bytes), elapsed,
2470 (util.bytecount(total_bytes), elapsed,
2473 util.bytecount(total_bytes / elapsed)))
2471 util.bytecount(total_bytes / elapsed)))
2474
2472
2475 # new requirements = old non-format requirements +
2473 # new requirements = old non-format requirements +
2476 # new format-related
2474 # new format-related
2477 # requirements from the streamed-in repository
2475 # requirements from the streamed-in repository
2478 requirements.update(set(self.requirements) - self.supportedformats)
2476 requirements.update(set(self.requirements) - self.supportedformats)
2479 self._applyrequirements(requirements)
2477 self._applyrequirements(requirements)
2480 self._writerequirements()
2478 self._writerequirements()
2481
2479
2482 if rbranchmap:
2480 if rbranchmap:
2483 rbheads = []
2481 rbheads = []
2484 for bheads in rbranchmap.itervalues():
2482 for bheads in rbranchmap.itervalues():
2485 rbheads.extend(bheads)
2483 rbheads.extend(bheads)
2486
2484
2487 if rbheads:
2485 if rbheads:
2488 rtiprev = max((int(self.changelog.rev(node))
2486 rtiprev = max((int(self.changelog.rev(node))
2489 for node in rbheads))
2487 for node in rbheads))
2490 cache = branchmap.branchcache(rbranchmap,
2488 cache = branchmap.branchcache(rbranchmap,
2491 self[rtiprev].node(),
2489 self[rtiprev].node(),
2492 rtiprev)
2490 rtiprev)
2493 # Try to stick it as low as possible
2491 # Try to stick it as low as possible
2494 # filter above served are unlikely to be fetch from a clone
2492 # filter above served are unlikely to be fetch from a clone
2495 for candidate in ('base', 'immutable', 'served'):
2493 for candidate in ('base', 'immutable', 'served'):
2496 rview = self.filtered(candidate)
2494 rview = self.filtered(candidate)
2497 if cache.validfor(rview):
2495 if cache.validfor(rview):
2498 self._branchcaches[candidate] = cache
2496 self._branchcaches[candidate] = cache
2499 cache.write(rview)
2497 cache.write(rview)
2500 break
2498 break
2501 self.invalidate()
2499 self.invalidate()
2502 return len(self.heads()) + 1
2500 return len(self.heads()) + 1
2503 finally:
2501 finally:
2504 lock.release()
2502 lock.release()
2505
2503
2506 def clone(self, remote, heads=[], stream=False):
2504 def clone(self, remote, heads=[], stream=False):
2507 '''clone remote repository.
2505 '''clone remote repository.
2508
2506
2509 keyword arguments:
2507 keyword arguments:
2510 heads: list of revs to clone (forces use of pull)
2508 heads: list of revs to clone (forces use of pull)
2511 stream: use streaming clone if possible'''
2509 stream: use streaming clone if possible'''
2512
2510
2513 # now, all clients that can request uncompressed clones can
2511 # now, all clients that can request uncompressed clones can
2514 # read repo formats supported by all servers that can serve
2512 # read repo formats supported by all servers that can serve
2515 # them.
2513 # them.
2516
2514
2517 # if revlog format changes, client will have to check version
2515 # if revlog format changes, client will have to check version
2518 # and format flags on "stream" capability, and use
2516 # and format flags on "stream" capability, and use
2519 # uncompressed only if compatible.
2517 # uncompressed only if compatible.
2520
2518
2521 if not stream:
2519 if not stream:
2522 # if the server explicitly prefers to stream (for fast LANs)
2520 # if the server explicitly prefers to stream (for fast LANs)
2523 stream = remote.capable('stream-preferred')
2521 stream = remote.capable('stream-preferred')
2524
2522
2525 if stream and not heads:
2523 if stream and not heads:
2526 # 'stream' means remote revlog format is revlogv1 only
2524 # 'stream' means remote revlog format is revlogv1 only
2527 if remote.capable('stream'):
2525 if remote.capable('stream'):
2528 return self.stream_in(remote, set(('revlogv1',)))
2526 return self.stream_in(remote, set(('revlogv1',)))
2529 # otherwise, 'streamreqs' contains the remote revlog format
2527 # otherwise, 'streamreqs' contains the remote revlog format
2530 streamreqs = remote.capable('streamreqs')
2528 streamreqs = remote.capable('streamreqs')
2531 if streamreqs:
2529 if streamreqs:
2532 streamreqs = set(streamreqs.split(','))
2530 streamreqs = set(streamreqs.split(','))
2533 # if we support it, stream in and adjust our requirements
2531 # if we support it, stream in and adjust our requirements
2534 if not streamreqs - self.supportedformats:
2532 if not streamreqs - self.supportedformats:
2535 return self.stream_in(remote, streamreqs)
2533 return self.stream_in(remote, streamreqs)
2536 return self.pull(remote, heads)
2534 return self.pull(remote, heads)
2537
2535
2538 def pushkey(self, namespace, key, old, new):
2536 def pushkey(self, namespace, key, old, new):
2539 self.hook('prepushkey', throw=True, namespace=namespace, key=key,
2537 self.hook('prepushkey', throw=True, namespace=namespace, key=key,
2540 old=old, new=new)
2538 old=old, new=new)
2541 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
2539 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
2542 ret = pushkey.push(self, namespace, key, old, new)
2540 ret = pushkey.push(self, namespace, key, old, new)
2543 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
2541 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
2544 ret=ret)
2542 ret=ret)
2545 return ret
2543 return ret
2546
2544
2547 def listkeys(self, namespace):
2545 def listkeys(self, namespace):
2548 self.hook('prelistkeys', throw=True, namespace=namespace)
2546 self.hook('prelistkeys', throw=True, namespace=namespace)
2549 self.ui.debug('listing keys for "%s"\n' % namespace)
2547 self.ui.debug('listing keys for "%s"\n' % namespace)
2550 values = pushkey.list(self, namespace)
2548 values = pushkey.list(self, namespace)
2551 self.hook('listkeys', namespace=namespace, values=values)
2549 self.hook('listkeys', namespace=namespace, values=values)
2552 return values
2550 return values
2553
2551
2554 def debugwireargs(self, one, two, three=None, four=None, five=None):
2552 def debugwireargs(self, one, two, three=None, four=None, five=None):
2555 '''used to test argument passing over the wire'''
2553 '''used to test argument passing over the wire'''
2556 return "%s %s %s %s %s" % (one, two, three, four, five)
2554 return "%s %s %s %s %s" % (one, two, three, four, five)
2557
2555
2558 def savecommitmessage(self, text):
2556 def savecommitmessage(self, text):
2559 fp = self.opener('last-message.txt', 'wb')
2557 fp = self.opener('last-message.txt', 'wb')
2560 try:
2558 try:
2561 fp.write(text)
2559 fp.write(text)
2562 finally:
2560 finally:
2563 fp.close()
2561 fp.close()
2564 return self.pathto(fp.name[len(self.root) + 1:])
2562 return self.pathto(fp.name[len(self.root) + 1:])
2565
2563
2566 # used to avoid circular references so destructors work
2564 # used to avoid circular references so destructors work
2567 def aftertrans(files):
2565 def aftertrans(files):
2568 renamefiles = [tuple(t) for t in files]
2566 renamefiles = [tuple(t) for t in files]
2569 def a():
2567 def a():
2570 for vfs, src, dest in renamefiles:
2568 for vfs, src, dest in renamefiles:
2571 try:
2569 try:
2572 vfs.rename(src, dest)
2570 vfs.rename(src, dest)
2573 except OSError: # journal file does not yet exist
2571 except OSError: # journal file does not yet exist
2574 pass
2572 pass
2575 return a
2573 return a
2576
2574
2577 def undoname(fn):
2575 def undoname(fn):
2578 base, name = os.path.split(fn)
2576 base, name = os.path.split(fn)
2579 assert name.startswith('journal')
2577 assert name.startswith('journal')
2580 return os.path.join(base, name.replace('journal', 'undo', 1))
2578 return os.path.join(base, name.replace('journal', 'undo', 1))
2581
2579
2582 def instance(ui, path, create):
2580 def instance(ui, path, create):
2583 return localrepository(ui, util.urllocalpath(path), create)
2581 return localrepository(ui, util.urllocalpath(path), create)
2584
2582
2585 def islocal(path):
2583 def islocal(path):
2586 return True
2584 return True
@@ -1,798 +1,819 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 markers handling
9 """Obsolete markers 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 rewriting operations, and help
17 transformations performed by history rewriting operations, and help
18 building new tools to reconciliate conflicting rewriting actions. To
18 building new tools to reconciliate conflicting rewriting actions. To
19 facilitate conflicts resolution, markers include various annotations
19 facilitate conflicts 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 "precursor" and possible replacements are
23 The old obsoleted changeset is called "precursor" and possible replacements are
24 called "successors". Markers that used changeset X as a precursors are called
24 called "successors". Markers that used changeset X as a precursors are called
25 "successor markers of X" because they hold information about the successors of
25 "successor markers of X" because they hold information about the successors of
26 X. Markers that use changeset Y as a successors are call "precursor markers of
26 X. Markers that use changeset Y as a successors are call "precursor markers of
27 Y" because they hold information about the precursors of Y.
27 Y" because they hold information about the precursors of Y.
28
28
29 Examples:
29 Examples:
30
30
31 - When changeset A is replacement by a changeset A', one marker is stored:
31 - When changeset A is replacement by a changeset A', one marker is stored:
32
32
33 (A, (A'))
33 (A, (A'))
34
34
35 - When changesets A and B are folded into a new changeset C two markers are
35 - When changesets A and B are folded into a new changeset C two markers are
36 stored:
36 stored:
37
37
38 (A, (C,)) and (B, (C,))
38 (A, (C,)) and (B, (C,))
39
39
40 - When changeset A is simply "pruned" from the graph, a marker in create:
40 - When changeset A is simply "pruned" from the graph, a marker in create:
41
41
42 (A, ())
42 (A, ())
43
43
44 - When changeset A is split into B and C, a single marker are used:
44 - When changeset A is split into B and C, a single marker are used:
45
45
46 (A, (C, C))
46 (A, (C, C))
47
47
48 We use a single marker to distinct the "split" case from the "divergence"
48 We use a single marker to distinct the "split" case from the "divergence"
49 case. If two independents operation rewrite the same changeset A in to A' and
49 case. If two independents operation rewrite the same changeset A in to A' and
50 A'' when have an error case: divergent rewriting. We can detect it because
50 A'' when have an error case: divergent rewriting. We can detect it because
51 two markers will be created independently:
51 two markers will be created independently:
52
52
53 (A, (B,)) and (A, (C,))
53 (A, (B,)) and (A, (C,))
54
54
55 Format
55 Format
56 ------
56 ------
57
57
58 Markers are stored in an append-only file stored in
58 Markers are stored in an append-only file stored in
59 '.hg/store/obsstore'.
59 '.hg/store/obsstore'.
60
60
61 The file starts with a version header:
61 The file starts with a version header:
62
62
63 - 1 unsigned byte: version number, starting at zero.
63 - 1 unsigned byte: version number, starting at zero.
64
64
65
65
66 The header is followed by the markers. Each marker is made of:
66 The header is followed by the markers. Each marker is made of:
67
67
68 - 1 unsigned byte: number of new changesets "R", could be zero.
68 - 1 unsigned byte: number of new changesets "R", could be zero.
69
69
70 - 1 unsigned 32-bits integer: metadata size "M" in bytes.
70 - 1 unsigned 32-bits integer: metadata size "M" in bytes.
71
71
72 - 1 byte: a bit field. It is reserved for flags used in obsolete
72 - 1 byte: a bit field. It is reserved for flags used in obsolete
73 markers common operations, to avoid repeated decoding of metadata
73 markers common operations, to avoid repeated decoding of metadata
74 entries.
74 entries.
75
75
76 - 20 bytes: obsoleted changeset identifier.
76 - 20 bytes: obsoleted changeset identifier.
77
77
78 - N*20 bytes: new changesets identifiers.
78 - N*20 bytes: new changesets identifiers.
79
79
80 - M bytes: metadata as a sequence of nul-terminated strings. Each
80 - M bytes: metadata as a sequence of nul-terminated strings. Each
81 string contains a key and a value, separated by a color ':', without
81 string contains a key and a value, separated by a color ':', without
82 additional encoding. Keys cannot contain '\0' or ':' and values
82 additional encoding. Keys cannot contain '\0' or ':' and values
83 cannot contain '\0'.
83 cannot contain '\0'.
84 """
84 """
85 import struct
85 import struct
86 import util, base85, node
86 import util, base85, node
87 from i18n import _
87 from i18n import _
88
88
89 _pack = struct.pack
89 _pack = struct.pack
90 _unpack = struct.unpack
90 _unpack = struct.unpack
91
91
92 _SEEK_END = 2 # os.SEEK_END was introduced in Python 2.5
92 _SEEK_END = 2 # os.SEEK_END was introduced in Python 2.5
93
93
94 # the obsolete feature is not mature enough to be enabled by default.
94 # the obsolete feature is not mature enough to be enabled by default.
95 # you have to rely on third party extension extension to enable this.
95 # you have to rely on third party extension extension to enable this.
96 _enabled = False
96 _enabled = False
97
97
98 # data used for parsing and writing
98 # data used for parsing and writing
99 _fmversion = 0
99 _fmversion = 0
100 _fmfixed = '>BIB20s'
100 _fmfixed = '>BIB20s'
101 _fmnode = '20s'
101 _fmnode = '20s'
102 _fmfsize = struct.calcsize(_fmfixed)
102 _fmfsize = struct.calcsize(_fmfixed)
103 _fnodesize = struct.calcsize(_fmnode)
103 _fnodesize = struct.calcsize(_fmnode)
104
104
105 ### obsolescence marker flag
105 ### obsolescence marker flag
106
106
107 ## bumpedfix flag
107 ## bumpedfix flag
108 #
108 #
109 # When a changeset A' succeed to a changeset A which became public, we call A'
109 # When a changeset A' succeed to a changeset A which became public, we call A'
110 # "bumped" because it's a successors of a public changesets
110 # "bumped" because it's a successors of a public changesets
111 #
111 #
112 # o A' (bumped)
112 # o A' (bumped)
113 # |`:
113 # |`:
114 # | o A
114 # | o A
115 # |/
115 # |/
116 # o Z
116 # o Z
117 #
117 #
118 # The way to solve this situation is to create a new changeset Ad as children
118 # The way to solve this situation is to create a new changeset Ad as children
119 # of A. This changeset have the same content than A'. So the diff from A to A'
119 # of A. This changeset have the same content than A'. So the diff from A to A'
120 # is the same than the diff from A to Ad. Ad is marked as a successors of A'
120 # is the same than the diff from A to Ad. Ad is marked as a successors of A'
121 #
121 #
122 # o Ad
122 # o Ad
123 # |`:
123 # |`:
124 # | x A'
124 # | x A'
125 # |'|
125 # |'|
126 # o | A
126 # o | A
127 # |/
127 # |/
128 # o Z
128 # o Z
129 #
129 #
130 # But by transitivity Ad is also a successors of A. To avoid having Ad marked
130 # But by transitivity Ad is also a successors of A. To avoid having Ad marked
131 # as bumped too, we add the `bumpedfix` flag to the marker. <A', (Ad,)>.
131 # as bumped too, we add the `bumpedfix` flag to the marker. <A', (Ad,)>.
132 # This flag mean that the successors express the changes between the public and
132 # This flag mean that the successors express the changes between the public and
133 # bumped version and fix the situation, breaking the transitivity of
133 # bumped version and fix the situation, breaking the transitivity of
134 # "bumped" here.
134 # "bumped" here.
135 bumpedfix = 1
135 bumpedfix = 1
136
136
137 def _readmarkers(data):
137 def _readmarkers(data):
138 """Read and enumerate markers from raw data"""
138 """Read and enumerate markers from raw data"""
139 off = 0
139 off = 0
140 diskversion = _unpack('>B', data[off:off + 1])[0]
140 diskversion = _unpack('>B', data[off:off + 1])[0]
141 off += 1
141 off += 1
142 if diskversion != _fmversion:
142 if diskversion != _fmversion:
143 raise util.Abort(_('parsing obsolete marker: unknown version %r')
143 raise util.Abort(_('parsing obsolete marker: unknown version %r')
144 % diskversion)
144 % diskversion)
145
145
146 # Loop on markers
146 # Loop on markers
147 l = len(data)
147 l = len(data)
148 while off + _fmfsize <= l:
148 while off + _fmfsize <= l:
149 # read fixed part
149 # read fixed part
150 cur = data[off:off + _fmfsize]
150 cur = data[off:off + _fmfsize]
151 off += _fmfsize
151 off += _fmfsize
152 nbsuc, mdsize, flags, pre = _unpack(_fmfixed, cur)
152 nbsuc, mdsize, flags, pre = _unpack(_fmfixed, cur)
153 # read replacement
153 # read replacement
154 sucs = ()
154 sucs = ()
155 if nbsuc:
155 if nbsuc:
156 s = (_fnodesize * nbsuc)
156 s = (_fnodesize * nbsuc)
157 cur = data[off:off + s]
157 cur = data[off:off + s]
158 sucs = _unpack(_fmnode * nbsuc, cur)
158 sucs = _unpack(_fmnode * nbsuc, cur)
159 off += s
159 off += s
160 # read metadata
160 # read metadata
161 # (metadata will be decoded on demand)
161 # (metadata will be decoded on demand)
162 metadata = data[off:off + mdsize]
162 metadata = data[off:off + mdsize]
163 if len(metadata) != mdsize:
163 if len(metadata) != mdsize:
164 raise util.Abort(_('parsing obsolete marker: metadata is too '
164 raise util.Abort(_('parsing obsolete marker: metadata is too '
165 'short, %d bytes expected, got %d')
165 'short, %d bytes expected, got %d')
166 % (mdsize, len(metadata)))
166 % (mdsize, len(metadata)))
167 off += mdsize
167 off += mdsize
168 yield (pre, sucs, flags, metadata)
168 yield (pre, sucs, flags, metadata)
169
169
170 def encodemeta(meta):
170 def encodemeta(meta):
171 """Return encoded metadata string to string mapping.
171 """Return encoded metadata string to string mapping.
172
172
173 Assume no ':' in key and no '\0' in both key and value."""
173 Assume no ':' in key and no '\0' in both key and value."""
174 for key, value in meta.iteritems():
174 for key, value in meta.iteritems():
175 if ':' in key or '\0' in key:
175 if ':' in key or '\0' in key:
176 raise ValueError("':' and '\0' are forbidden in metadata key'")
176 raise ValueError("':' and '\0' are forbidden in metadata key'")
177 if '\0' in value:
177 if '\0' in value:
178 raise ValueError("':' are forbidden in metadata value'")
178 raise ValueError("':' are forbidden in metadata value'")
179 return '\0'.join(['%s:%s' % (k, meta[k]) for k in sorted(meta)])
179 return '\0'.join(['%s:%s' % (k, meta[k]) for k in sorted(meta)])
180
180
181 def decodemeta(data):
181 def decodemeta(data):
182 """Return string to string dictionary from encoded version."""
182 """Return string to string dictionary from encoded version."""
183 d = {}
183 d = {}
184 for l in data.split('\0'):
184 for l in data.split('\0'):
185 if l:
185 if l:
186 key, value = l.split(':')
186 key, value = l.split(':')
187 d[key] = value
187 d[key] = value
188 return d
188 return d
189
189
190 class marker(object):
190 class marker(object):
191 """Wrap obsolete marker raw data"""
191 """Wrap obsolete marker raw data"""
192
192
193 def __init__(self, repo, data):
193 def __init__(self, repo, data):
194 # the repo argument will be used to create changectx in later version
194 # the repo argument will be used to create changectx in later version
195 self._repo = repo
195 self._repo = repo
196 self._data = data
196 self._data = data
197 self._decodedmeta = None
197 self._decodedmeta = None
198
198
199 def precnode(self):
199 def precnode(self):
200 """Precursor changeset node identifier"""
200 """Precursor changeset node identifier"""
201 return self._data[0]
201 return self._data[0]
202
202
203 def succnodes(self):
203 def succnodes(self):
204 """List of successor changesets node identifiers"""
204 """List of successor changesets node identifiers"""
205 return self._data[1]
205 return self._data[1]
206
206
207 def metadata(self):
207 def metadata(self):
208 """Decoded metadata dictionary"""
208 """Decoded metadata dictionary"""
209 if self._decodedmeta is None:
209 if self._decodedmeta is None:
210 self._decodedmeta = decodemeta(self._data[3])
210 self._decodedmeta = decodemeta(self._data[3])
211 return self._decodedmeta
211 return self._decodedmeta
212
212
213 def date(self):
213 def date(self):
214 """Creation date as (unixtime, offset)"""
214 """Creation date as (unixtime, offset)"""
215 parts = self.metadata()['date'].split(' ')
215 parts = self.metadata()['date'].split(' ')
216 return (float(parts[0]), int(parts[1]))
216 return (float(parts[0]), int(parts[1]))
217
217
218 class obsstore(object):
218 class obsstore(object):
219 """Store obsolete markers
219 """Store obsolete markers
220
220
221 Markers can be accessed with two mappings:
221 Markers can be accessed with two mappings:
222 - precursors[x] -> set(markers on precursors edges of x)
222 - precursors[x] -> set(markers on precursors edges of x)
223 - successors[x] -> set(markers on successors edges of x)
223 - successors[x] -> set(markers on successors edges of x)
224 """
224 """
225
225
226 def __init__(self, sopener):
226 def __init__(self, sopener):
227 # caches for various obsolescence related cache
227 # caches for various obsolescence related cache
228 self.caches = {}
228 self.caches = {}
229 self._all = []
229 self._all = []
230 # new markers to serialize
230 # new markers to serialize
231 self.precursors = {}
231 self.precursors = {}
232 self.successors = {}
232 self.successors = {}
233 self.sopener = sopener
233 self.sopener = sopener
234 data = sopener.tryread('obsstore')
234 data = sopener.tryread('obsstore')
235 if data:
235 if data:
236 self._load(_readmarkers(data))
236 self._load(_readmarkers(data))
237
237
238 def __iter__(self):
238 def __iter__(self):
239 return iter(self._all)
239 return iter(self._all)
240
240
241 def __nonzero__(self):
241 def __nonzero__(self):
242 return bool(self._all)
242 return bool(self._all)
243
243
244 def create(self, transaction, prec, succs=(), flag=0, metadata=None):
244 def create(self, transaction, prec, succs=(), flag=0, metadata=None):
245 """obsolete: add a new obsolete marker
245 """obsolete: add a new obsolete marker
246
246
247 * ensuring it is hashable
247 * ensuring it is hashable
248 * check mandatory metadata
248 * check mandatory metadata
249 * encode metadata
249 * encode metadata
250 """
250 """
251 if metadata is None:
251 if metadata is None:
252 metadata = {}
252 metadata = {}
253 if 'date' not in metadata:
253 if 'date' not in metadata:
254 metadata['date'] = "%d %d" % util.makedate()
254 metadata['date'] = "%d %d" % util.makedate()
255 if len(prec) != 20:
255 if len(prec) != 20:
256 raise ValueError(prec)
256 raise ValueError(prec)
257 for succ in succs:
257 for succ in succs:
258 if len(succ) != 20:
258 if len(succ) != 20:
259 raise ValueError(succ)
259 raise ValueError(succ)
260 marker = (str(prec), tuple(succs), int(flag), encodemeta(metadata))
260 marker = (str(prec), tuple(succs), int(flag), encodemeta(metadata))
261 self.add(transaction, [marker])
261 self.add(transaction, [marker])
262
262
263 def add(self, transaction, markers):
263 def add(self, transaction, markers):
264 """Add new markers to the store
264 """Add new markers to the store
265
265
266 Take care of filtering duplicate.
266 Take care of filtering duplicate.
267 Return the number of new marker."""
267 Return the number of new marker."""
268 if not _enabled:
268 if not _enabled:
269 raise util.Abort('obsolete feature is not enabled on this repo')
269 raise util.Abort('obsolete feature is not enabled on this repo')
270 new = [m for m in markers if m not in self._all]
270 new = [m for m in markers if m not in self._all]
271 if new:
271 if new:
272 f = self.sopener('obsstore', 'ab')
272 f = self.sopener('obsstore', 'ab')
273 try:
273 try:
274 # Whether the file's current position is at the begin or at
274 # Whether the file's current position is at the begin or at
275 # the end after opening a file for appending is implementation
275 # the end after opening a file for appending is implementation
276 # defined. So we must seek to the end before calling tell(),
276 # defined. So we must seek to the end before calling tell(),
277 # or we may get a zero offset for non-zero sized files on
277 # or we may get a zero offset for non-zero sized files on
278 # some platforms (issue3543).
278 # some platforms (issue3543).
279 f.seek(0, _SEEK_END)
279 f.seek(0, _SEEK_END)
280 offset = f.tell()
280 offset = f.tell()
281 transaction.add('obsstore', offset)
281 transaction.add('obsstore', offset)
282 # offset == 0: new file - add the version header
282 # offset == 0: new file - add the version header
283 for bytes in _encodemarkers(new, offset == 0):
283 for bytes in _encodemarkers(new, offset == 0):
284 f.write(bytes)
284 f.write(bytes)
285 finally:
285 finally:
286 # XXX: f.close() == filecache invalidation == obsstore rebuilt.
286 # XXX: f.close() == filecache invalidation == obsstore rebuilt.
287 # call 'filecacheentry.refresh()' here
287 # call 'filecacheentry.refresh()' here
288 f.close()
288 f.close()
289 self._load(new)
289 self._load(new)
290 # new marker *may* have changed several set. invalidate the cache.
290 # new marker *may* have changed several set. invalidate the cache.
291 self.caches.clear()
291 self.caches.clear()
292 return len(new)
292 return len(new)
293
293
294 def mergemarkers(self, transaction, data):
294 def mergemarkers(self, transaction, data):
295 markers = _readmarkers(data)
295 markers = _readmarkers(data)
296 self.add(transaction, markers)
296 self.add(transaction, markers)
297
297
298 def _load(self, markers):
298 def _load(self, markers):
299 for mark in markers:
299 for mark in markers:
300 self._all.append(mark)
300 self._all.append(mark)
301 pre, sucs = mark[:2]
301 pre, sucs = mark[:2]
302 self.successors.setdefault(pre, set()).add(mark)
302 self.successors.setdefault(pre, set()).add(mark)
303 for suc in sucs:
303 for suc in sucs:
304 self.precursors.setdefault(suc, set()).add(mark)
304 self.precursors.setdefault(suc, set()).add(mark)
305 if node.nullid in self.precursors:
305 if node.nullid in self.precursors:
306 raise util.Abort(_('bad obsolescence marker detected: '
306 raise util.Abort(_('bad obsolescence marker detected: '
307 'invalid successors nullid'))
307 'invalid successors nullid'))
308
308
309 def _encodemarkers(markers, addheader=False):
309 def _encodemarkers(markers, addheader=False):
310 # Kept separate from flushmarkers(), it will be reused for
310 # Kept separate from flushmarkers(), it will be reused for
311 # markers exchange.
311 # markers exchange.
312 if addheader:
312 if addheader:
313 yield _pack('>B', _fmversion)
313 yield _pack('>B', _fmversion)
314 for marker in markers:
314 for marker in markers:
315 yield _encodeonemarker(marker)
315 yield _encodeonemarker(marker)
316
316
317
317
318 def _encodeonemarker(marker):
318 def _encodeonemarker(marker):
319 pre, sucs, flags, metadata = marker
319 pre, sucs, flags, metadata = marker
320 nbsuc = len(sucs)
320 nbsuc = len(sucs)
321 format = _fmfixed + (_fmnode * nbsuc)
321 format = _fmfixed + (_fmnode * nbsuc)
322 data = [nbsuc, len(metadata), flags, pre]
322 data = [nbsuc, len(metadata), flags, pre]
323 data.extend(sucs)
323 data.extend(sucs)
324 return _pack(format, *data) + metadata
324 return _pack(format, *data) + metadata
325
325
326 # arbitrary picked to fit into 8K limit from HTTP server
326 # arbitrary picked to fit into 8K limit from HTTP server
327 # you have to take in account:
327 # you have to take in account:
328 # - the version header
328 # - the version header
329 # - the base85 encoding
329 # - the base85 encoding
330 _maxpayload = 5300
330 _maxpayload = 5300
331
331
332 def listmarkers(repo):
332 def listmarkers(repo):
333 """List markers over pushkey"""
333 """List markers over pushkey"""
334 if not repo.obsstore:
334 if not repo.obsstore:
335 return {}
335 return {}
336 keys = {}
336 keys = {}
337 parts = []
337 parts = []
338 currentlen = _maxpayload * 2 # ensure we create a new part
338 currentlen = _maxpayload * 2 # ensure we create a new part
339 for marker in repo.obsstore:
339 for marker in repo.obsstore:
340 nextdata = _encodeonemarker(marker)
340 nextdata = _encodeonemarker(marker)
341 if (len(nextdata) + currentlen > _maxpayload):
341 if (len(nextdata) + currentlen > _maxpayload):
342 currentpart = []
342 currentpart = []
343 currentlen = 0
343 currentlen = 0
344 parts.append(currentpart)
344 parts.append(currentpart)
345 currentpart.append(nextdata)
345 currentpart.append(nextdata)
346 currentlen += len(nextdata)
346 currentlen += len(nextdata)
347 for idx, part in enumerate(reversed(parts)):
347 for idx, part in enumerate(reversed(parts)):
348 data = ''.join([_pack('>B', _fmversion)] + part)
348 data = ''.join([_pack('>B', _fmversion)] + part)
349 keys['dump%i' % idx] = base85.b85encode(data)
349 keys['dump%i' % idx] = base85.b85encode(data)
350 return keys
350 return keys
351
351
352 def pushmarker(repo, key, old, new):
352 def pushmarker(repo, key, old, new):
353 """Push markers over pushkey"""
353 """Push markers over pushkey"""
354 if not key.startswith('dump'):
354 if not key.startswith('dump'):
355 repo.ui.warn(_('unknown key: %r') % key)
355 repo.ui.warn(_('unknown key: %r') % key)
356 return 0
356 return 0
357 if old:
357 if old:
358 repo.ui.warn(_('unexpected old value') % key)
358 repo.ui.warn(_('unexpected old value') % key)
359 return 0
359 return 0
360 data = base85.b85decode(new)
360 data = base85.b85decode(new)
361 lock = repo.lock()
361 lock = repo.lock()
362 try:
362 try:
363 tr = repo.transaction('pushkey: obsolete markers')
363 tr = repo.transaction('pushkey: obsolete markers')
364 try:
364 try:
365 repo.obsstore.mergemarkers(tr, data)
365 repo.obsstore.mergemarkers(tr, data)
366 tr.close()
366 tr.close()
367 return 1
367 return 1
368 finally:
368 finally:
369 tr.release()
369 tr.release()
370 finally:
370 finally:
371 lock.release()
371 lock.release()
372
372
373 def syncpush(repo, remote):
373 def syncpush(repo, remote):
374 """utility function to push bookmark to a remote
374 """utility function to push bookmark to a remote
375
375
376 Exist mostly to allow overridding for experimentation purpose"""
376 Exist mostly to allow overridding for experimentation purpose"""
377 if (_enabled and repo.obsstore and
377 if (_enabled and repo.obsstore and
378 'obsolete' in remote.listkeys('namespaces')):
378 'obsolete' in remote.listkeys('namespaces')):
379 rslts = []
379 rslts = []
380 remotedata = repo.listkeys('obsolete')
380 remotedata = repo.listkeys('obsolete')
381 for key in sorted(remotedata, reverse=True):
381 for key in sorted(remotedata, reverse=True):
382 # reverse sort to ensure we end with dump0
382 # reverse sort to ensure we end with dump0
383 data = remotedata[key]
383 data = remotedata[key]
384 rslts.append(remote.pushkey('obsolete', key, '', data))
384 rslts.append(remote.pushkey('obsolete', key, '', data))
385 if [r for r in rslts if not r]:
385 if [r for r in rslts if not r]:
386 msg = _('failed to push some obsolete markers!\n')
386 msg = _('failed to push some obsolete markers!\n')
387 repo.ui.warn(msg)
387 repo.ui.warn(msg)
388
388
389 def syncpull(repo, remote, gettransaction):
390 """utility function to pull bookmark to a remote
391
392 The `gettransaction` is function that return the pull transaction, creating
393 one if necessary. We return the transaction to inform the calling code that
394 a new transaction have been created (when applicable).
395
396 Exists mostly to allow overridding for experimentation purpose"""
397 tr = None
398 if _enabled:
399 repo.ui.debug('fetching remote obsolete markers\n')
400 remoteobs = remote.listkeys('obsolete')
401 if 'dump0' in remoteobs:
402 tr = gettransaction()
403 for key in sorted(remoteobs, reverse=True):
404 if key.startswith('dump'):
405 data = base85.b85decode(remoteobs[key])
406 repo.obsstore.mergemarkers(tr, data)
407 repo.invalidatevolatilesets()
408 return tr
409
389 def allmarkers(repo):
410 def allmarkers(repo):
390 """all obsolete markers known in a repository"""
411 """all obsolete markers known in a repository"""
391 for markerdata in repo.obsstore:
412 for markerdata in repo.obsstore:
392 yield marker(repo, markerdata)
413 yield marker(repo, markerdata)
393
414
394 def precursormarkers(ctx):
415 def precursormarkers(ctx):
395 """obsolete marker marking this changeset as a successors"""
416 """obsolete marker marking this changeset as a successors"""
396 for data in ctx._repo.obsstore.precursors.get(ctx.node(), ()):
417 for data in ctx._repo.obsstore.precursors.get(ctx.node(), ()):
397 yield marker(ctx._repo, data)
418 yield marker(ctx._repo, data)
398
419
399 def successormarkers(ctx):
420 def successormarkers(ctx):
400 """obsolete marker making this changeset obsolete"""
421 """obsolete marker making this changeset obsolete"""
401 for data in ctx._repo.obsstore.successors.get(ctx.node(), ()):
422 for data in ctx._repo.obsstore.successors.get(ctx.node(), ()):
402 yield marker(ctx._repo, data)
423 yield marker(ctx._repo, data)
403
424
404 def allsuccessors(obsstore, nodes, ignoreflags=0):
425 def allsuccessors(obsstore, nodes, ignoreflags=0):
405 """Yield node for every successor of <nodes>.
426 """Yield node for every successor of <nodes>.
406
427
407 Some successors may be unknown locally.
428 Some successors may be unknown locally.
408
429
409 This is a linear yield unsuited to detecting split changesets."""
430 This is a linear yield unsuited to detecting split changesets."""
410 remaining = set(nodes)
431 remaining = set(nodes)
411 seen = set(remaining)
432 seen = set(remaining)
412 while remaining:
433 while remaining:
413 current = remaining.pop()
434 current = remaining.pop()
414 yield current
435 yield current
415 for mark in obsstore.successors.get(current, ()):
436 for mark in obsstore.successors.get(current, ()):
416 # ignore marker flagged with with specified flag
437 # ignore marker flagged with with specified flag
417 if mark[2] & ignoreflags:
438 if mark[2] & ignoreflags:
418 continue
439 continue
419 for suc in mark[1]:
440 for suc in mark[1]:
420 if suc not in seen:
441 if suc not in seen:
421 seen.add(suc)
442 seen.add(suc)
422 remaining.add(suc)
443 remaining.add(suc)
423
444
424 def foreground(repo, nodes):
445 def foreground(repo, nodes):
425 """return all nodes in the "foreground" of other node
446 """return all nodes in the "foreground" of other node
426
447
427 The foreground of a revision is anything reachable using parent -> children
448 The foreground of a revision is anything reachable using parent -> children
428 or precursor -> sucessor relation. It is very similars to "descendant" but
449 or precursor -> sucessor relation. It is very similars to "descendant" but
429 augmented with obsolescence information.
450 augmented with obsolescence information.
430
451
431 Beware that possible obsolescence cycle may result if complexe situation.
452 Beware that possible obsolescence cycle may result if complexe situation.
432 """
453 """
433 repo = repo.unfiltered()
454 repo = repo.unfiltered()
434 foreground = set(repo.set('%ln::', nodes))
455 foreground = set(repo.set('%ln::', nodes))
435 if repo.obsstore:
456 if repo.obsstore:
436 # We only need this complicated logic if there is obsolescence
457 # We only need this complicated logic if there is obsolescence
437 # XXX will probably deserve an optimised revset.
458 # XXX will probably deserve an optimised revset.
438 nm = repo.changelog.nodemap
459 nm = repo.changelog.nodemap
439 plen = -1
460 plen = -1
440 # compute the whole set of successors or descendants
461 # compute the whole set of successors or descendants
441 while len(foreground) != plen:
462 while len(foreground) != plen:
442 plen = len(foreground)
463 plen = len(foreground)
443 succs = set(c.node() for c in foreground)
464 succs = set(c.node() for c in foreground)
444 mutable = [c.node() for c in foreground if c.mutable()]
465 mutable = [c.node() for c in foreground if c.mutable()]
445 succs.update(allsuccessors(repo.obsstore, mutable))
466 succs.update(allsuccessors(repo.obsstore, mutable))
446 known = (n for n in succs if n in nm)
467 known = (n for n in succs if n in nm)
447 foreground = set(repo.set('%ln::', known))
468 foreground = set(repo.set('%ln::', known))
448 return set(c.node() for c in foreground)
469 return set(c.node() for c in foreground)
449
470
450
471
451 def successorssets(repo, initialnode, cache=None):
472 def successorssets(repo, initialnode, cache=None):
452 """Return all set of successors of initial nodes
473 """Return all set of successors of initial nodes
453
474
454 Successors set of changeset A are a group of revision that succeed A. It
475 Successors set of changeset A are a group of revision that succeed A. It
455 succeed A as a consistent whole, each revision being only partial
476 succeed A as a consistent whole, each revision being only partial
456 replacement. Successors set contains non-obsolete changeset only.
477 replacement. Successors set contains non-obsolete changeset only.
457
478
458 In most cases a changeset A have zero (changeset pruned) or a single
479 In most cases a changeset A have zero (changeset pruned) or a single
459 successors set that contains a single successor (changeset A replaced by
480 successors set that contains a single successor (changeset A replaced by
460 A')
481 A')
461
482
462 When changeset is split, it results successors set containing more than
483 When changeset is split, it results successors set containing more than
463 a single element. Divergent rewriting will result in multiple successors
484 a single element. Divergent rewriting will result in multiple successors
464 sets.
485 sets.
465
486
466 They are returned as a list of tuples containing all valid successors sets.
487 They are returned as a list of tuples containing all valid successors sets.
467
488
468 Final successors unknown locally are considered plain prune (obsoleted
489 Final successors unknown locally are considered plain prune (obsoleted
469 without successors).
490 without successors).
470
491
471 The optional `cache` parameter is a dictionary that may contains
492 The optional `cache` parameter is a dictionary that may contains
472 precomputed successors sets. It is meant to reuse the computation of
493 precomputed successors sets. It is meant to reuse the computation of
473 previous call to `successorssets` when multiple calls are made at the same
494 previous call to `successorssets` when multiple calls are made at the same
474 time. The cache dictionary is updated in place. The caller is responsible
495 time. The cache dictionary is updated in place. The caller is responsible
475 for its live spawn. Code that makes multiple calls to `successorssets`
496 for its live spawn. Code that makes multiple calls to `successorssets`
476 *must* use this cache mechanism or suffer terrible performances."""
497 *must* use this cache mechanism or suffer terrible performances."""
477
498
478 succmarkers = repo.obsstore.successors
499 succmarkers = repo.obsstore.successors
479
500
480 # Stack of nodes we search successors sets for
501 # Stack of nodes we search successors sets for
481 toproceed = [initialnode]
502 toproceed = [initialnode]
482 # set version of above list for fast loop detection
503 # set version of above list for fast loop detection
483 # element added to "toproceed" must be added here
504 # element added to "toproceed" must be added here
484 stackedset = set(toproceed)
505 stackedset = set(toproceed)
485 if cache is None:
506 if cache is None:
486 cache = {}
507 cache = {}
487
508
488 # This while loop is the flattened version of a recursive search for
509 # This while loop is the flattened version of a recursive search for
489 # successors sets
510 # successors sets
490 #
511 #
491 # def successorssets(x):
512 # def successorssets(x):
492 # successors = directsuccessors(x)
513 # successors = directsuccessors(x)
493 # ss = [[]]
514 # ss = [[]]
494 # for succ in directsuccessors(x):
515 # for succ in directsuccessors(x):
495 # # product as in itertools cartesian product
516 # # product as in itertools cartesian product
496 # ss = product(ss, successorssets(succ))
517 # ss = product(ss, successorssets(succ))
497 # return ss
518 # return ss
498 #
519 #
499 # But we can not use plain recursive calls here:
520 # But we can not use plain recursive calls here:
500 # - that would blow the python call stack
521 # - that would blow the python call stack
501 # - obsolescence markers may have cycles, we need to handle them.
522 # - obsolescence markers may have cycles, we need to handle them.
502 #
523 #
503 # The `toproceed` list act as our call stack. Every node we search
524 # The `toproceed` list act as our call stack. Every node we search
504 # successors set for are stacked there.
525 # successors set for are stacked there.
505 #
526 #
506 # The `stackedset` is set version of this stack used to check if a node is
527 # The `stackedset` is set version of this stack used to check if a node is
507 # already stacked. This check is used to detect cycles and prevent infinite
528 # already stacked. This check is used to detect cycles and prevent infinite
508 # loop.
529 # loop.
509 #
530 #
510 # successors set of all nodes are stored in the `cache` dictionary.
531 # successors set of all nodes are stored in the `cache` dictionary.
511 #
532 #
512 # After this while loop ends we use the cache to return the successors sets
533 # After this while loop ends we use the cache to return the successors sets
513 # for the node requested by the caller.
534 # for the node requested by the caller.
514 while toproceed:
535 while toproceed:
515 # Every iteration tries to compute the successors sets of the topmost
536 # Every iteration tries to compute the successors sets of the topmost
516 # node of the stack: CURRENT.
537 # node of the stack: CURRENT.
517 #
538 #
518 # There are four possible outcomes:
539 # There are four possible outcomes:
519 #
540 #
520 # 1) We already know the successors sets of CURRENT:
541 # 1) We already know the successors sets of CURRENT:
521 # -> mission accomplished, pop it from the stack.
542 # -> mission accomplished, pop it from the stack.
522 # 2) Node is not obsolete:
543 # 2) Node is not obsolete:
523 # -> the node is its own successors sets. Add it to the cache.
544 # -> the node is its own successors sets. Add it to the cache.
524 # 3) We do not know successors set of direct successors of CURRENT:
545 # 3) We do not know successors set of direct successors of CURRENT:
525 # -> We add those successors to the stack.
546 # -> We add those successors to the stack.
526 # 4) We know successors sets of all direct successors of CURRENT:
547 # 4) We know successors sets of all direct successors of CURRENT:
527 # -> We can compute CURRENT successors set and add it to the
548 # -> We can compute CURRENT successors set and add it to the
528 # cache.
549 # cache.
529 #
550 #
530 current = toproceed[-1]
551 current = toproceed[-1]
531 if current in cache:
552 if current in cache:
532 # case (1): We already know the successors sets
553 # case (1): We already know the successors sets
533 stackedset.remove(toproceed.pop())
554 stackedset.remove(toproceed.pop())
534 elif current not in succmarkers:
555 elif current not in succmarkers:
535 # case (2): The node is not obsolete.
556 # case (2): The node is not obsolete.
536 if current in repo:
557 if current in repo:
537 # We have a valid last successors.
558 # We have a valid last successors.
538 cache[current] = [(current,)]
559 cache[current] = [(current,)]
539 else:
560 else:
540 # Final obsolete version is unknown locally.
561 # Final obsolete version is unknown locally.
541 # Do not count that as a valid successors
562 # Do not count that as a valid successors
542 cache[current] = []
563 cache[current] = []
543 else:
564 else:
544 # cases (3) and (4)
565 # cases (3) and (4)
545 #
566 #
546 # We proceed in two phases. Phase 1 aims to distinguish case (3)
567 # We proceed in two phases. Phase 1 aims to distinguish case (3)
547 # from case (4):
568 # from case (4):
548 #
569 #
549 # For each direct successors of CURRENT, we check whether its
570 # For each direct successors of CURRENT, we check whether its
550 # successors sets are known. If they are not, we stack the
571 # successors sets are known. If they are not, we stack the
551 # unknown node and proceed to the next iteration of the while
572 # unknown node and proceed to the next iteration of the while
552 # loop. (case 3)
573 # loop. (case 3)
553 #
574 #
554 # During this step, we may detect obsolescence cycles: a node
575 # During this step, we may detect obsolescence cycles: a node
555 # with unknown successors sets but already in the call stack.
576 # with unknown successors sets but already in the call stack.
556 # In such a situation, we arbitrary set the successors sets of
577 # In such a situation, we arbitrary set the successors sets of
557 # the node to nothing (node pruned) to break the cycle.
578 # the node to nothing (node pruned) to break the cycle.
558 #
579 #
559 # If no break was encountered we proceed to phase 2.
580 # If no break was encountered we proceed to phase 2.
560 #
581 #
561 # Phase 2 computes successors sets of CURRENT (case 4); see details
582 # Phase 2 computes successors sets of CURRENT (case 4); see details
562 # in phase 2 itself.
583 # in phase 2 itself.
563 #
584 #
564 # Note the two levels of iteration in each phase.
585 # Note the two levels of iteration in each phase.
565 # - The first one handles obsolescence markers using CURRENT as
586 # - The first one handles obsolescence markers using CURRENT as
566 # precursor (successors markers of CURRENT).
587 # precursor (successors markers of CURRENT).
567 #
588 #
568 # Having multiple entry here means divergence.
589 # Having multiple entry here means divergence.
569 #
590 #
570 # - The second one handles successors defined in each marker.
591 # - The second one handles successors defined in each marker.
571 #
592 #
572 # Having none means pruned node, multiple successors means split,
593 # Having none means pruned node, multiple successors means split,
573 # single successors are standard replacement.
594 # single successors are standard replacement.
574 #
595 #
575 for mark in sorted(succmarkers[current]):
596 for mark in sorted(succmarkers[current]):
576 for suc in mark[1]:
597 for suc in mark[1]:
577 if suc not in cache:
598 if suc not in cache:
578 if suc in stackedset:
599 if suc in stackedset:
579 # cycle breaking
600 # cycle breaking
580 cache[suc] = []
601 cache[suc] = []
581 else:
602 else:
582 # case (3) If we have not computed successors sets
603 # case (3) If we have not computed successors sets
583 # of one of those successors we add it to the
604 # of one of those successors we add it to the
584 # `toproceed` stack and stop all work for this
605 # `toproceed` stack and stop all work for this
585 # iteration.
606 # iteration.
586 toproceed.append(suc)
607 toproceed.append(suc)
587 stackedset.add(suc)
608 stackedset.add(suc)
588 break
609 break
589 else:
610 else:
590 continue
611 continue
591 break
612 break
592 else:
613 else:
593 # case (4): we know all successors sets of all direct
614 # case (4): we know all successors sets of all direct
594 # successors
615 # successors
595 #
616 #
596 # Successors set contributed by each marker depends on the
617 # Successors set contributed by each marker depends on the
597 # successors sets of all its "successors" node.
618 # successors sets of all its "successors" node.
598 #
619 #
599 # Each different marker is a divergence in the obsolescence
620 # Each different marker is a divergence in the obsolescence
600 # history. It contributes successors sets distinct from other
621 # history. It contributes successors sets distinct from other
601 # markers.
622 # markers.
602 #
623 #
603 # Within a marker, a successor may have divergent successors
624 # Within a marker, a successor may have divergent successors
604 # sets. In such a case, the marker will contribute multiple
625 # sets. In such a case, the marker will contribute multiple
605 # divergent successors sets. If multiple successors have
626 # divergent successors sets. If multiple successors have
606 # divergent successors sets, a cartesian product is used.
627 # divergent successors sets, a cartesian product is used.
607 #
628 #
608 # At the end we post-process successors sets to remove
629 # At the end we post-process successors sets to remove
609 # duplicated entry and successors set that are strict subset of
630 # duplicated entry and successors set that are strict subset of
610 # another one.
631 # another one.
611 succssets = []
632 succssets = []
612 for mark in sorted(succmarkers[current]):
633 for mark in sorted(succmarkers[current]):
613 # successors sets contributed by this marker
634 # successors sets contributed by this marker
614 markss = [[]]
635 markss = [[]]
615 for suc in mark[1]:
636 for suc in mark[1]:
616 # cardinal product with previous successors
637 # cardinal product with previous successors
617 productresult = []
638 productresult = []
618 for prefix in markss:
639 for prefix in markss:
619 for suffix in cache[suc]:
640 for suffix in cache[suc]:
620 newss = list(prefix)
641 newss = list(prefix)
621 for part in suffix:
642 for part in suffix:
622 # do not duplicated entry in successors set
643 # do not duplicated entry in successors set
623 # first entry wins.
644 # first entry wins.
624 if part not in newss:
645 if part not in newss:
625 newss.append(part)
646 newss.append(part)
626 productresult.append(newss)
647 productresult.append(newss)
627 markss = productresult
648 markss = productresult
628 succssets.extend(markss)
649 succssets.extend(markss)
629 # remove duplicated and subset
650 # remove duplicated and subset
630 seen = []
651 seen = []
631 final = []
652 final = []
632 candidate = sorted(((set(s), s) for s in succssets if s),
653 candidate = sorted(((set(s), s) for s in succssets if s),
633 key=lambda x: len(x[1]), reverse=True)
654 key=lambda x: len(x[1]), reverse=True)
634 for setversion, listversion in candidate:
655 for setversion, listversion in candidate:
635 for seenset in seen:
656 for seenset in seen:
636 if setversion.issubset(seenset):
657 if setversion.issubset(seenset):
637 break
658 break
638 else:
659 else:
639 final.append(listversion)
660 final.append(listversion)
640 seen.append(setversion)
661 seen.append(setversion)
641 final.reverse() # put small successors set first
662 final.reverse() # put small successors set first
642 cache[current] = final
663 cache[current] = final
643 return cache[initialnode]
664 return cache[initialnode]
644
665
645 def _knownrevs(repo, nodes):
666 def _knownrevs(repo, nodes):
646 """yield revision numbers of known nodes passed in parameters
667 """yield revision numbers of known nodes passed in parameters
647
668
648 Unknown revisions are silently ignored."""
669 Unknown revisions are silently ignored."""
649 torev = repo.changelog.nodemap.get
670 torev = repo.changelog.nodemap.get
650 for n in nodes:
671 for n in nodes:
651 rev = torev(n)
672 rev = torev(n)
652 if rev is not None:
673 if rev is not None:
653 yield rev
674 yield rev
654
675
655 # mapping of 'set-name' -> <function to compute this set>
676 # mapping of 'set-name' -> <function to compute this set>
656 cachefuncs = {}
677 cachefuncs = {}
657 def cachefor(name):
678 def cachefor(name):
658 """Decorator to register a function as computing the cache for a set"""
679 """Decorator to register a function as computing the cache for a set"""
659 def decorator(func):
680 def decorator(func):
660 assert name not in cachefuncs
681 assert name not in cachefuncs
661 cachefuncs[name] = func
682 cachefuncs[name] = func
662 return func
683 return func
663 return decorator
684 return decorator
664
685
665 def getrevs(repo, name):
686 def getrevs(repo, name):
666 """Return the set of revision that belong to the <name> set
687 """Return the set of revision that belong to the <name> set
667
688
668 Such access may compute the set and cache it for future use"""
689 Such access may compute the set and cache it for future use"""
669 repo = repo.unfiltered()
690 repo = repo.unfiltered()
670 if not repo.obsstore:
691 if not repo.obsstore:
671 return ()
692 return ()
672 if name not in repo.obsstore.caches:
693 if name not in repo.obsstore.caches:
673 repo.obsstore.caches[name] = cachefuncs[name](repo)
694 repo.obsstore.caches[name] = cachefuncs[name](repo)
674 return repo.obsstore.caches[name]
695 return repo.obsstore.caches[name]
675
696
676 # To be simple we need to invalidate obsolescence cache when:
697 # To be simple we need to invalidate obsolescence cache when:
677 #
698 #
678 # - new changeset is added:
699 # - new changeset is added:
679 # - public phase is changed
700 # - public phase is changed
680 # - obsolescence marker are added
701 # - obsolescence marker are added
681 # - strip is used a repo
702 # - strip is used a repo
682 def clearobscaches(repo):
703 def clearobscaches(repo):
683 """Remove all obsolescence related cache from a repo
704 """Remove all obsolescence related cache from a repo
684
705
685 This remove all cache in obsstore is the obsstore already exist on the
706 This remove all cache in obsstore is the obsstore already exist on the
686 repo.
707 repo.
687
708
688 (We could be smarter here given the exact event that trigger the cache
709 (We could be smarter here given the exact event that trigger the cache
689 clearing)"""
710 clearing)"""
690 # only clear cache is there is obsstore data in this repo
711 # only clear cache is there is obsstore data in this repo
691 if 'obsstore' in repo._filecache:
712 if 'obsstore' in repo._filecache:
692 repo.obsstore.caches.clear()
713 repo.obsstore.caches.clear()
693
714
694 @cachefor('obsolete')
715 @cachefor('obsolete')
695 def _computeobsoleteset(repo):
716 def _computeobsoleteset(repo):
696 """the set of obsolete revisions"""
717 """the set of obsolete revisions"""
697 obs = set()
718 obs = set()
698 getrev = repo.changelog.nodemap.get
719 getrev = repo.changelog.nodemap.get
699 getphase = repo._phasecache.phase
720 getphase = repo._phasecache.phase
700 for node in repo.obsstore.successors:
721 for node in repo.obsstore.successors:
701 rev = getrev(node)
722 rev = getrev(node)
702 if rev is not None and getphase(repo, rev):
723 if rev is not None and getphase(repo, rev):
703 obs.add(rev)
724 obs.add(rev)
704 return obs
725 return obs
705
726
706 @cachefor('unstable')
727 @cachefor('unstable')
707 def _computeunstableset(repo):
728 def _computeunstableset(repo):
708 """the set of non obsolete revisions with obsolete parents"""
729 """the set of non obsolete revisions with obsolete parents"""
709 # revset is not efficient enough here
730 # revset is not efficient enough here
710 # we do (obsolete()::) - obsolete() by hand
731 # we do (obsolete()::) - obsolete() by hand
711 obs = getrevs(repo, 'obsolete')
732 obs = getrevs(repo, 'obsolete')
712 if not obs:
733 if not obs:
713 return set()
734 return set()
714 cl = repo.changelog
735 cl = repo.changelog
715 return set(r for r in cl.descendants(obs) if r not in obs)
736 return set(r for r in cl.descendants(obs) if r not in obs)
716
737
717 @cachefor('suspended')
738 @cachefor('suspended')
718 def _computesuspendedset(repo):
739 def _computesuspendedset(repo):
719 """the set of obsolete parents with non obsolete descendants"""
740 """the set of obsolete parents with non obsolete descendants"""
720 suspended = repo.changelog.ancestors(getrevs(repo, 'unstable'))
741 suspended = repo.changelog.ancestors(getrevs(repo, 'unstable'))
721 return set(r for r in getrevs(repo, 'obsolete') if r in suspended)
742 return set(r for r in getrevs(repo, 'obsolete') if r in suspended)
722
743
723 @cachefor('extinct')
744 @cachefor('extinct')
724 def _computeextinctset(repo):
745 def _computeextinctset(repo):
725 """the set of obsolete parents without non obsolete descendants"""
746 """the set of obsolete parents without non obsolete descendants"""
726 return getrevs(repo, 'obsolete') - getrevs(repo, 'suspended')
747 return getrevs(repo, 'obsolete') - getrevs(repo, 'suspended')
727
748
728
749
729 @cachefor('bumped')
750 @cachefor('bumped')
730 def _computebumpedset(repo):
751 def _computebumpedset(repo):
731 """the set of revs trying to obsolete public revisions"""
752 """the set of revs trying to obsolete public revisions"""
732 # get all possible bumped changesets
753 # get all possible bumped changesets
733 tonode = repo.changelog.node
754 tonode = repo.changelog.node
734 publicnodes = (tonode(r) for r in repo.revs('public()'))
755 publicnodes = (tonode(r) for r in repo.revs('public()'))
735 successors = allsuccessors(repo.obsstore, publicnodes,
756 successors = allsuccessors(repo.obsstore, publicnodes,
736 ignoreflags=bumpedfix)
757 ignoreflags=bumpedfix)
737 # revision public or already obsolete don't count as bumped
758 # revision public or already obsolete don't count as bumped
738 query = '%ld - obsolete() - public()'
759 query = '%ld - obsolete() - public()'
739 return set(repo.revs(query, _knownrevs(repo, successors)))
760 return set(repo.revs(query, _knownrevs(repo, successors)))
740
761
741 @cachefor('divergent')
762 @cachefor('divergent')
742 def _computedivergentset(repo):
763 def _computedivergentset(repo):
743 """the set of rev that compete to be the final successors of some revision.
764 """the set of rev that compete to be the final successors of some revision.
744 """
765 """
745 divergent = set()
766 divergent = set()
746 obsstore = repo.obsstore
767 obsstore = repo.obsstore
747 newermap = {}
768 newermap = {}
748 for ctx in repo.set('(not public()) - obsolete()'):
769 for ctx in repo.set('(not public()) - obsolete()'):
749 mark = obsstore.precursors.get(ctx.node(), ())
770 mark = obsstore.precursors.get(ctx.node(), ())
750 toprocess = set(mark)
771 toprocess = set(mark)
751 while toprocess:
772 while toprocess:
752 prec = toprocess.pop()[0]
773 prec = toprocess.pop()[0]
753 if prec not in newermap:
774 if prec not in newermap:
754 successorssets(repo, prec, newermap)
775 successorssets(repo, prec, newermap)
755 newer = [n for n in newermap[prec] if n]
776 newer = [n for n in newermap[prec] if n]
756 if len(newer) > 1:
777 if len(newer) > 1:
757 divergent.add(ctx.rev())
778 divergent.add(ctx.rev())
758 break
779 break
759 toprocess.update(obsstore.precursors.get(prec, ()))
780 toprocess.update(obsstore.precursors.get(prec, ()))
760 return divergent
781 return divergent
761
782
762
783
763 def createmarkers(repo, relations, flag=0, metadata=None):
784 def createmarkers(repo, relations, flag=0, metadata=None):
764 """Add obsolete markers between changesets in a repo
785 """Add obsolete markers between changesets in a repo
765
786
766 <relations> must be an iterable of (<old>, (<new>, ...)) tuple.
787 <relations> must be an iterable of (<old>, (<new>, ...)) tuple.
767 `old` and `news` are changectx.
788 `old` and `news` are changectx.
768
789
769 Trying to obsolete a public changeset will raise an exception.
790 Trying to obsolete a public changeset will raise an exception.
770
791
771 Current user and date are used except if specified otherwise in the
792 Current user and date are used except if specified otherwise in the
772 metadata attribute.
793 metadata attribute.
773
794
774 This function operates within a transaction of its own, but does
795 This function operates within a transaction of its own, but does
775 not take any lock on the repo.
796 not take any lock on the repo.
776 """
797 """
777 # prepare metadata
798 # prepare metadata
778 if metadata is None:
799 if metadata is None:
779 metadata = {}
800 metadata = {}
780 if 'date' not in metadata:
801 if 'date' not in metadata:
781 metadata['date'] = '%i %i' % util.makedate()
802 metadata['date'] = '%i %i' % util.makedate()
782 if 'user' not in metadata:
803 if 'user' not in metadata:
783 metadata['user'] = repo.ui.username()
804 metadata['user'] = repo.ui.username()
784 tr = repo.transaction('add-obsolescence-marker')
805 tr = repo.transaction('add-obsolescence-marker')
785 try:
806 try:
786 for prec, sucs in relations:
807 for prec, sucs in relations:
787 if not prec.mutable():
808 if not prec.mutable():
788 raise util.Abort("cannot obsolete immutable changeset: %s"
809 raise util.Abort("cannot obsolete immutable changeset: %s"
789 % prec)
810 % prec)
790 nprec = prec.node()
811 nprec = prec.node()
791 nsucs = tuple(s.node() for s in sucs)
812 nsucs = tuple(s.node() for s in sucs)
792 if nprec in nsucs:
813 if nprec in nsucs:
793 raise util.Abort("changeset %s cannot obsolete itself" % prec)
814 raise util.Abort("changeset %s cannot obsolete itself" % prec)
794 repo.obsstore.create(tr, nprec, nsucs, flag, metadata)
815 repo.obsstore.create(tr, nprec, nsucs, flag, metadata)
795 repo.filteredrevcache.clear()
816 repo.filteredrevcache.clear()
796 tr.close()
817 tr.close()
797 finally:
818 finally:
798 tr.release()
819 tr.release()
General Comments 0
You need to be logged in to leave comments. Login now