##// END OF EJS Templates
obsolete: introduce caches for all meaningful sets...
Pierre-Yves David -
r17469:fb72eec7 default
parent child Browse files
Show More
@@ -1,1313 +1,1291 b''
1 # context.py - changeset and file context objects for mercurial
1 # context.py - changeset and file context objects for mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import nullid, nullrev, short, hex, bin
8 from node import nullid, nullrev, short, hex, bin
9 from i18n import _
9 from i18n import _
10 import ancestor, mdiff, error, util, scmutil, subrepo, patch, encoding, phases
10 import ancestor, mdiff, error, util, scmutil, subrepo, patch, encoding, phases
11 import copies
11 import copies
12 import match as matchmod
12 import match as matchmod
13 import os, errno, stat
13 import os, errno, stat
14 import obsolete as obsmod
14
15
15 propertycache = util.propertycache
16 propertycache = util.propertycache
16
17
17 class changectx(object):
18 class changectx(object):
18 """A changecontext object makes access to data related to a particular
19 """A changecontext object makes access to data related to a particular
19 changeset convenient."""
20 changeset convenient."""
20 def __init__(self, repo, changeid=''):
21 def __init__(self, repo, changeid=''):
21 """changeid is a revision number, node, or tag"""
22 """changeid is a revision number, node, or tag"""
22 if changeid == '':
23 if changeid == '':
23 changeid = '.'
24 changeid = '.'
24 self._repo = repo
25 self._repo = repo
25
26
26 if isinstance(changeid, int):
27 if isinstance(changeid, int):
27 self._rev = changeid
28 self._rev = changeid
28 self._node = repo.changelog.node(changeid)
29 self._node = repo.changelog.node(changeid)
29 return
30 return
30 if isinstance(changeid, long):
31 if isinstance(changeid, long):
31 changeid = str(changeid)
32 changeid = str(changeid)
32 if changeid == '.':
33 if changeid == '.':
33 self._node = repo.dirstate.p1()
34 self._node = repo.dirstate.p1()
34 self._rev = repo.changelog.rev(self._node)
35 self._rev = repo.changelog.rev(self._node)
35 return
36 return
36 if changeid == 'null':
37 if changeid == 'null':
37 self._node = nullid
38 self._node = nullid
38 self._rev = nullrev
39 self._rev = nullrev
39 return
40 return
40 if changeid == 'tip':
41 if changeid == 'tip':
41 self._rev = len(repo.changelog) - 1
42 self._rev = len(repo.changelog) - 1
42 self._node = repo.changelog.node(self._rev)
43 self._node = repo.changelog.node(self._rev)
43 return
44 return
44 if len(changeid) == 20:
45 if len(changeid) == 20:
45 try:
46 try:
46 self._node = changeid
47 self._node = changeid
47 self._rev = repo.changelog.rev(changeid)
48 self._rev = repo.changelog.rev(changeid)
48 return
49 return
49 except LookupError:
50 except LookupError:
50 pass
51 pass
51
52
52 try:
53 try:
53 r = int(changeid)
54 r = int(changeid)
54 if str(r) != changeid:
55 if str(r) != changeid:
55 raise ValueError
56 raise ValueError
56 l = len(repo.changelog)
57 l = len(repo.changelog)
57 if r < 0:
58 if r < 0:
58 r += l
59 r += l
59 if r < 0 or r >= l:
60 if r < 0 or r >= l:
60 raise ValueError
61 raise ValueError
61 self._rev = r
62 self._rev = r
62 self._node = repo.changelog.node(r)
63 self._node = repo.changelog.node(r)
63 return
64 return
64 except (ValueError, OverflowError):
65 except (ValueError, OverflowError):
65 pass
66 pass
66
67
67 if len(changeid) == 40:
68 if len(changeid) == 40:
68 try:
69 try:
69 self._node = bin(changeid)
70 self._node = bin(changeid)
70 self._rev = repo.changelog.rev(self._node)
71 self._rev = repo.changelog.rev(self._node)
71 return
72 return
72 except (TypeError, LookupError):
73 except (TypeError, LookupError):
73 pass
74 pass
74
75
75 if changeid in repo._bookmarks:
76 if changeid in repo._bookmarks:
76 self._node = repo._bookmarks[changeid]
77 self._node = repo._bookmarks[changeid]
77 self._rev = repo.changelog.rev(self._node)
78 self._rev = repo.changelog.rev(self._node)
78 return
79 return
79 if changeid in repo._tagscache.tags:
80 if changeid in repo._tagscache.tags:
80 self._node = repo._tagscache.tags[changeid]
81 self._node = repo._tagscache.tags[changeid]
81 self._rev = repo.changelog.rev(self._node)
82 self._rev = repo.changelog.rev(self._node)
82 return
83 return
83 try:
84 try:
84 self._node = repo.branchtip(changeid)
85 self._node = repo.branchtip(changeid)
85 self._rev = repo.changelog.rev(self._node)
86 self._rev = repo.changelog.rev(self._node)
86 return
87 return
87 except error.RepoLookupError:
88 except error.RepoLookupError:
88 pass
89 pass
89
90
90 self._node = repo.changelog._partialmatch(changeid)
91 self._node = repo.changelog._partialmatch(changeid)
91 if self._node is not None:
92 if self._node is not None:
92 self._rev = repo.changelog.rev(self._node)
93 self._rev = repo.changelog.rev(self._node)
93 return
94 return
94
95
95 # lookup failed
96 # lookup failed
96 # check if it might have come from damaged dirstate
97 # check if it might have come from damaged dirstate
97 if changeid in repo.dirstate.parents():
98 if changeid in repo.dirstate.parents():
98 raise error.Abort(_("working directory has unknown parent '%s'!")
99 raise error.Abort(_("working directory has unknown parent '%s'!")
99 % short(changeid))
100 % short(changeid))
100 try:
101 try:
101 if len(changeid) == 20:
102 if len(changeid) == 20:
102 changeid = hex(changeid)
103 changeid = hex(changeid)
103 except TypeError:
104 except TypeError:
104 pass
105 pass
105 raise error.RepoLookupError(
106 raise error.RepoLookupError(
106 _("unknown revision '%s'") % changeid)
107 _("unknown revision '%s'") % changeid)
107
108
108 def __str__(self):
109 def __str__(self):
109 return short(self.node())
110 return short(self.node())
110
111
111 def __int__(self):
112 def __int__(self):
112 return self.rev()
113 return self.rev()
113
114
114 def __repr__(self):
115 def __repr__(self):
115 return "<changectx %s>" % str(self)
116 return "<changectx %s>" % str(self)
116
117
117 def __hash__(self):
118 def __hash__(self):
118 try:
119 try:
119 return hash(self._rev)
120 return hash(self._rev)
120 except AttributeError:
121 except AttributeError:
121 return id(self)
122 return id(self)
122
123
123 def __eq__(self, other):
124 def __eq__(self, other):
124 try:
125 try:
125 return self._rev == other._rev
126 return self._rev == other._rev
126 except AttributeError:
127 except AttributeError:
127 return False
128 return False
128
129
129 def __ne__(self, other):
130 def __ne__(self, other):
130 return not (self == other)
131 return not (self == other)
131
132
132 def __nonzero__(self):
133 def __nonzero__(self):
133 return self._rev != nullrev
134 return self._rev != nullrev
134
135
135 @propertycache
136 @propertycache
136 def _changeset(self):
137 def _changeset(self):
137 return self._repo.changelog.read(self.rev())
138 return self._repo.changelog.read(self.rev())
138
139
139 @propertycache
140 @propertycache
140 def _manifest(self):
141 def _manifest(self):
141 return self._repo.manifest.read(self._changeset[0])
142 return self._repo.manifest.read(self._changeset[0])
142
143
143 @propertycache
144 @propertycache
144 def _manifestdelta(self):
145 def _manifestdelta(self):
145 return self._repo.manifest.readdelta(self._changeset[0])
146 return self._repo.manifest.readdelta(self._changeset[0])
146
147
147 @propertycache
148 @propertycache
148 def _parents(self):
149 def _parents(self):
149 p = self._repo.changelog.parentrevs(self._rev)
150 p = self._repo.changelog.parentrevs(self._rev)
150 if p[1] == nullrev:
151 if p[1] == nullrev:
151 p = p[:-1]
152 p = p[:-1]
152 return [changectx(self._repo, x) for x in p]
153 return [changectx(self._repo, x) for x in p]
153
154
154 @propertycache
155 @propertycache
155 def substate(self):
156 def substate(self):
156 return subrepo.state(self, self._repo.ui)
157 return subrepo.state(self, self._repo.ui)
157
158
158 def __contains__(self, key):
159 def __contains__(self, key):
159 return key in self._manifest
160 return key in self._manifest
160
161
161 def __getitem__(self, key):
162 def __getitem__(self, key):
162 return self.filectx(key)
163 return self.filectx(key)
163
164
164 def __iter__(self):
165 def __iter__(self):
165 for f in sorted(self._manifest):
166 for f in sorted(self._manifest):
166 yield f
167 yield f
167
168
168 def changeset(self):
169 def changeset(self):
169 return self._changeset
170 return self._changeset
170 def manifest(self):
171 def manifest(self):
171 return self._manifest
172 return self._manifest
172 def manifestnode(self):
173 def manifestnode(self):
173 return self._changeset[0]
174 return self._changeset[0]
174
175
175 def rev(self):
176 def rev(self):
176 return self._rev
177 return self._rev
177 def node(self):
178 def node(self):
178 return self._node
179 return self._node
179 def hex(self):
180 def hex(self):
180 return hex(self._node)
181 return hex(self._node)
181 def user(self):
182 def user(self):
182 return self._changeset[1]
183 return self._changeset[1]
183 def date(self):
184 def date(self):
184 return self._changeset[2]
185 return self._changeset[2]
185 def files(self):
186 def files(self):
186 return self._changeset[3]
187 return self._changeset[3]
187 def description(self):
188 def description(self):
188 return self._changeset[4]
189 return self._changeset[4]
189 def branch(self):
190 def branch(self):
190 return encoding.tolocal(self._changeset[5].get("branch"))
191 return encoding.tolocal(self._changeset[5].get("branch"))
191 def closesbranch(self):
192 def closesbranch(self):
192 return 'close' in self._changeset[5]
193 return 'close' in self._changeset[5]
193 def extra(self):
194 def extra(self):
194 return self._changeset[5]
195 return self._changeset[5]
195 def tags(self):
196 def tags(self):
196 return self._repo.nodetags(self._node)
197 return self._repo.nodetags(self._node)
197 def bookmarks(self):
198 def bookmarks(self):
198 return self._repo.nodebookmarks(self._node)
199 return self._repo.nodebookmarks(self._node)
199 def phase(self):
200 def phase(self):
200 return self._repo._phasecache.phase(self._repo, self._rev)
201 return self._repo._phasecache.phase(self._repo, self._rev)
201 def phasestr(self):
202 def phasestr(self):
202 return phases.phasenames[self.phase()]
203 return phases.phasenames[self.phase()]
203 def mutable(self):
204 def mutable(self):
204 return self.phase() > phases.public
205 return self.phase() > phases.public
205 def hidden(self):
206 def hidden(self):
206 return self._rev in self._repo.hiddenrevs
207 return self._rev in self._repo.hiddenrevs
207
208
208 def parents(self):
209 def parents(self):
209 """return contexts for each parent changeset"""
210 """return contexts for each parent changeset"""
210 return self._parents
211 return self._parents
211
212
212 def p1(self):
213 def p1(self):
213 return self._parents[0]
214 return self._parents[0]
214
215
215 def p2(self):
216 def p2(self):
216 if len(self._parents) == 2:
217 if len(self._parents) == 2:
217 return self._parents[1]
218 return self._parents[1]
218 return changectx(self._repo, -1)
219 return changectx(self._repo, -1)
219
220
220 def children(self):
221 def children(self):
221 """return contexts for each child changeset"""
222 """return contexts for each child changeset"""
222 c = self._repo.changelog.children(self._node)
223 c = self._repo.changelog.children(self._node)
223 return [changectx(self._repo, x) for x in c]
224 return [changectx(self._repo, x) for x in c]
224
225
225 def ancestors(self):
226 def ancestors(self):
226 for a in self._repo.changelog.ancestors([self._rev]):
227 for a in self._repo.changelog.ancestors([self._rev]):
227 yield changectx(self._repo, a)
228 yield changectx(self._repo, a)
228
229
229 def descendants(self):
230 def descendants(self):
230 for d in self._repo.changelog.descendants([self._rev]):
231 for d in self._repo.changelog.descendants([self._rev]):
231 yield changectx(self._repo, d)
232 yield changectx(self._repo, d)
232
233
233 def obsolete(self):
234 def obsolete(self):
234 """True if the changeset is obsolete"""
235 """True if the changeset is obsolete"""
235 return (self.node() in self._repo.obsstore.precursors
236 return self.rev() in obsmod.getobscache(self._repo, 'obsolete')
236 and self.phase() > phases.public)
237
237
238 def extinct(self):
238 def extinct(self):
239 """True if the changeset is extinct"""
239 """True if the changeset is extinct"""
240 # We should just compute a cache and check against it.
240 return self.rev() in obsmod.getobscache(self._repo, 'extinct')
241 # See revset implementation for details.
242 #
243 # But this naive implementation does not require cache
244 if self.phase() <= phases.public:
245 return False
246 if not self.obsolete():
247 return False
248 for desc in self.descendants():
249 if not desc.obsolete():
250 return False
251 return True
252
241
253 def unstable(self):
242 def unstable(self):
254 """True if the changeset is not obsolete but it's ancestor are"""
243 """True if the changeset is not obsolete but it's ancestor are"""
255 # We should just compute /(obsolete()::) - obsolete()/
244 return self.rev() in obsmod.getobscache(self._repo, 'unstable')
256 # and keep it in a cache.
257 #
258 # But this naive implementation does not require cache
259 if self.phase() <= phases.public:
260 return False
261 if self.obsolete():
262 return False
263 for anc in self.ancestors():
264 if anc.obsolete():
265 return True
266 return False
267
245
268 def _fileinfo(self, path):
246 def _fileinfo(self, path):
269 if '_manifest' in self.__dict__:
247 if '_manifest' in self.__dict__:
270 try:
248 try:
271 return self._manifest[path], self._manifest.flags(path)
249 return self._manifest[path], self._manifest.flags(path)
272 except KeyError:
250 except KeyError:
273 raise error.LookupError(self._node, path,
251 raise error.LookupError(self._node, path,
274 _('not found in manifest'))
252 _('not found in manifest'))
275 if '_manifestdelta' in self.__dict__ or path in self.files():
253 if '_manifestdelta' in self.__dict__ or path in self.files():
276 if path in self._manifestdelta:
254 if path in self._manifestdelta:
277 return (self._manifestdelta[path],
255 return (self._manifestdelta[path],
278 self._manifestdelta.flags(path))
256 self._manifestdelta.flags(path))
279 node, flag = self._repo.manifest.find(self._changeset[0], path)
257 node, flag = self._repo.manifest.find(self._changeset[0], path)
280 if not node:
258 if not node:
281 raise error.LookupError(self._node, path,
259 raise error.LookupError(self._node, path,
282 _('not found in manifest'))
260 _('not found in manifest'))
283
261
284 return node, flag
262 return node, flag
285
263
286 def filenode(self, path):
264 def filenode(self, path):
287 return self._fileinfo(path)[0]
265 return self._fileinfo(path)[0]
288
266
289 def flags(self, path):
267 def flags(self, path):
290 try:
268 try:
291 return self._fileinfo(path)[1]
269 return self._fileinfo(path)[1]
292 except error.LookupError:
270 except error.LookupError:
293 return ''
271 return ''
294
272
295 def filectx(self, path, fileid=None, filelog=None):
273 def filectx(self, path, fileid=None, filelog=None):
296 """get a file context from this changeset"""
274 """get a file context from this changeset"""
297 if fileid is None:
275 if fileid is None:
298 fileid = self.filenode(path)
276 fileid = self.filenode(path)
299 return filectx(self._repo, path, fileid=fileid,
277 return filectx(self._repo, path, fileid=fileid,
300 changectx=self, filelog=filelog)
278 changectx=self, filelog=filelog)
301
279
302 def ancestor(self, c2):
280 def ancestor(self, c2):
303 """
281 """
304 return the ancestor context of self and c2
282 return the ancestor context of self and c2
305 """
283 """
306 # deal with workingctxs
284 # deal with workingctxs
307 n2 = c2._node
285 n2 = c2._node
308 if n2 is None:
286 if n2 is None:
309 n2 = c2._parents[0]._node
287 n2 = c2._parents[0]._node
310 n = self._repo.changelog.ancestor(self._node, n2)
288 n = self._repo.changelog.ancestor(self._node, n2)
311 return changectx(self._repo, n)
289 return changectx(self._repo, n)
312
290
313 def walk(self, match):
291 def walk(self, match):
314 fset = set(match.files())
292 fset = set(match.files())
315 # for dirstate.walk, files=['.'] means "walk the whole tree".
293 # for dirstate.walk, files=['.'] means "walk the whole tree".
316 # follow that here, too
294 # follow that here, too
317 fset.discard('.')
295 fset.discard('.')
318 for fn in self:
296 for fn in self:
319 if fn in fset:
297 if fn in fset:
320 # specified pattern is the exact name
298 # specified pattern is the exact name
321 fset.remove(fn)
299 fset.remove(fn)
322 if match(fn):
300 if match(fn):
323 yield fn
301 yield fn
324 for fn in sorted(fset):
302 for fn in sorted(fset):
325 if fn in self._dirs:
303 if fn in self._dirs:
326 # specified pattern is a directory
304 # specified pattern is a directory
327 continue
305 continue
328 if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
306 if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
329 yield fn
307 yield fn
330
308
331 def sub(self, path):
309 def sub(self, path):
332 return subrepo.subrepo(self, path)
310 return subrepo.subrepo(self, path)
333
311
334 def match(self, pats=[], include=None, exclude=None, default='glob'):
312 def match(self, pats=[], include=None, exclude=None, default='glob'):
335 r = self._repo
313 r = self._repo
336 return matchmod.match(r.root, r.getcwd(), pats,
314 return matchmod.match(r.root, r.getcwd(), pats,
337 include, exclude, default,
315 include, exclude, default,
338 auditor=r.auditor, ctx=self)
316 auditor=r.auditor, ctx=self)
339
317
340 def diff(self, ctx2=None, match=None, **opts):
318 def diff(self, ctx2=None, match=None, **opts):
341 """Returns a diff generator for the given contexts and matcher"""
319 """Returns a diff generator for the given contexts and matcher"""
342 if ctx2 is None:
320 if ctx2 is None:
343 ctx2 = self.p1()
321 ctx2 = self.p1()
344 if ctx2 is not None and not isinstance(ctx2, changectx):
322 if ctx2 is not None and not isinstance(ctx2, changectx):
345 ctx2 = self._repo[ctx2]
323 ctx2 = self._repo[ctx2]
346 diffopts = patch.diffopts(self._repo.ui, opts)
324 diffopts = patch.diffopts(self._repo.ui, opts)
347 return patch.diff(self._repo, ctx2.node(), self.node(),
325 return patch.diff(self._repo, ctx2.node(), self.node(),
348 match=match, opts=diffopts)
326 match=match, opts=diffopts)
349
327
350 @propertycache
328 @propertycache
351 def _dirs(self):
329 def _dirs(self):
352 dirs = set()
330 dirs = set()
353 for f in self._manifest:
331 for f in self._manifest:
354 pos = f.rfind('/')
332 pos = f.rfind('/')
355 while pos != -1:
333 while pos != -1:
356 f = f[:pos]
334 f = f[:pos]
357 if f in dirs:
335 if f in dirs:
358 break # dirs already contains this and above
336 break # dirs already contains this and above
359 dirs.add(f)
337 dirs.add(f)
360 pos = f.rfind('/')
338 pos = f.rfind('/')
361 return dirs
339 return dirs
362
340
363 def dirs(self):
341 def dirs(self):
364 return self._dirs
342 return self._dirs
365
343
366 class filectx(object):
344 class filectx(object):
367 """A filecontext object makes access to data related to a particular
345 """A filecontext object makes access to data related to a particular
368 filerevision convenient."""
346 filerevision convenient."""
369 def __init__(self, repo, path, changeid=None, fileid=None,
347 def __init__(self, repo, path, changeid=None, fileid=None,
370 filelog=None, changectx=None):
348 filelog=None, changectx=None):
371 """changeid can be a changeset revision, node, or tag.
349 """changeid can be a changeset revision, node, or tag.
372 fileid can be a file revision or node."""
350 fileid can be a file revision or node."""
373 self._repo = repo
351 self._repo = repo
374 self._path = path
352 self._path = path
375
353
376 assert (changeid is not None
354 assert (changeid is not None
377 or fileid is not None
355 or fileid is not None
378 or changectx is not None), \
356 or changectx is not None), \
379 ("bad args: changeid=%r, fileid=%r, changectx=%r"
357 ("bad args: changeid=%r, fileid=%r, changectx=%r"
380 % (changeid, fileid, changectx))
358 % (changeid, fileid, changectx))
381
359
382 if filelog:
360 if filelog:
383 self._filelog = filelog
361 self._filelog = filelog
384
362
385 if changeid is not None:
363 if changeid is not None:
386 self._changeid = changeid
364 self._changeid = changeid
387 if changectx is not None:
365 if changectx is not None:
388 self._changectx = changectx
366 self._changectx = changectx
389 if fileid is not None:
367 if fileid is not None:
390 self._fileid = fileid
368 self._fileid = fileid
391
369
392 @propertycache
370 @propertycache
393 def _changectx(self):
371 def _changectx(self):
394 return changectx(self._repo, self._changeid)
372 return changectx(self._repo, self._changeid)
395
373
396 @propertycache
374 @propertycache
397 def _filelog(self):
375 def _filelog(self):
398 return self._repo.file(self._path)
376 return self._repo.file(self._path)
399
377
400 @propertycache
378 @propertycache
401 def _changeid(self):
379 def _changeid(self):
402 if '_changectx' in self.__dict__:
380 if '_changectx' in self.__dict__:
403 return self._changectx.rev()
381 return self._changectx.rev()
404 else:
382 else:
405 return self._filelog.linkrev(self._filerev)
383 return self._filelog.linkrev(self._filerev)
406
384
407 @propertycache
385 @propertycache
408 def _filenode(self):
386 def _filenode(self):
409 if '_fileid' in self.__dict__:
387 if '_fileid' in self.__dict__:
410 return self._filelog.lookup(self._fileid)
388 return self._filelog.lookup(self._fileid)
411 else:
389 else:
412 return self._changectx.filenode(self._path)
390 return self._changectx.filenode(self._path)
413
391
414 @propertycache
392 @propertycache
415 def _filerev(self):
393 def _filerev(self):
416 return self._filelog.rev(self._filenode)
394 return self._filelog.rev(self._filenode)
417
395
418 @propertycache
396 @propertycache
419 def _repopath(self):
397 def _repopath(self):
420 return self._path
398 return self._path
421
399
422 def __nonzero__(self):
400 def __nonzero__(self):
423 try:
401 try:
424 self._filenode
402 self._filenode
425 return True
403 return True
426 except error.LookupError:
404 except error.LookupError:
427 # file is missing
405 # file is missing
428 return False
406 return False
429
407
430 def __str__(self):
408 def __str__(self):
431 return "%s@%s" % (self.path(), short(self.node()))
409 return "%s@%s" % (self.path(), short(self.node()))
432
410
433 def __repr__(self):
411 def __repr__(self):
434 return "<filectx %s>" % str(self)
412 return "<filectx %s>" % str(self)
435
413
436 def __hash__(self):
414 def __hash__(self):
437 try:
415 try:
438 return hash((self._path, self._filenode))
416 return hash((self._path, self._filenode))
439 except AttributeError:
417 except AttributeError:
440 return id(self)
418 return id(self)
441
419
442 def __eq__(self, other):
420 def __eq__(self, other):
443 try:
421 try:
444 return (self._path == other._path
422 return (self._path == other._path
445 and self._filenode == other._filenode)
423 and self._filenode == other._filenode)
446 except AttributeError:
424 except AttributeError:
447 return False
425 return False
448
426
449 def __ne__(self, other):
427 def __ne__(self, other):
450 return not (self == other)
428 return not (self == other)
451
429
452 def filectx(self, fileid):
430 def filectx(self, fileid):
453 '''opens an arbitrary revision of the file without
431 '''opens an arbitrary revision of the file without
454 opening a new filelog'''
432 opening a new filelog'''
455 return filectx(self._repo, self._path, fileid=fileid,
433 return filectx(self._repo, self._path, fileid=fileid,
456 filelog=self._filelog)
434 filelog=self._filelog)
457
435
458 def filerev(self):
436 def filerev(self):
459 return self._filerev
437 return self._filerev
460 def filenode(self):
438 def filenode(self):
461 return self._filenode
439 return self._filenode
462 def flags(self):
440 def flags(self):
463 return self._changectx.flags(self._path)
441 return self._changectx.flags(self._path)
464 def filelog(self):
442 def filelog(self):
465 return self._filelog
443 return self._filelog
466
444
467 def rev(self):
445 def rev(self):
468 if '_changectx' in self.__dict__:
446 if '_changectx' in self.__dict__:
469 return self._changectx.rev()
447 return self._changectx.rev()
470 if '_changeid' in self.__dict__:
448 if '_changeid' in self.__dict__:
471 return self._changectx.rev()
449 return self._changectx.rev()
472 return self._filelog.linkrev(self._filerev)
450 return self._filelog.linkrev(self._filerev)
473
451
474 def linkrev(self):
452 def linkrev(self):
475 return self._filelog.linkrev(self._filerev)
453 return self._filelog.linkrev(self._filerev)
476 def node(self):
454 def node(self):
477 return self._changectx.node()
455 return self._changectx.node()
478 def hex(self):
456 def hex(self):
479 return hex(self.node())
457 return hex(self.node())
480 def user(self):
458 def user(self):
481 return self._changectx.user()
459 return self._changectx.user()
482 def date(self):
460 def date(self):
483 return self._changectx.date()
461 return self._changectx.date()
484 def files(self):
462 def files(self):
485 return self._changectx.files()
463 return self._changectx.files()
486 def description(self):
464 def description(self):
487 return self._changectx.description()
465 return self._changectx.description()
488 def branch(self):
466 def branch(self):
489 return self._changectx.branch()
467 return self._changectx.branch()
490 def extra(self):
468 def extra(self):
491 return self._changectx.extra()
469 return self._changectx.extra()
492 def manifest(self):
470 def manifest(self):
493 return self._changectx.manifest()
471 return self._changectx.manifest()
494 def changectx(self):
472 def changectx(self):
495 return self._changectx
473 return self._changectx
496
474
497 def data(self):
475 def data(self):
498 return self._filelog.read(self._filenode)
476 return self._filelog.read(self._filenode)
499 def path(self):
477 def path(self):
500 return self._path
478 return self._path
501 def size(self):
479 def size(self):
502 return self._filelog.size(self._filerev)
480 return self._filelog.size(self._filerev)
503
481
504 def isbinary(self):
482 def isbinary(self):
505 try:
483 try:
506 return util.binary(self.data())
484 return util.binary(self.data())
507 except IOError:
485 except IOError:
508 return False
486 return False
509
487
510 def cmp(self, fctx):
488 def cmp(self, fctx):
511 """compare with other file context
489 """compare with other file context
512
490
513 returns True if different than fctx.
491 returns True if different than fctx.
514 """
492 """
515 if (fctx._filerev is None
493 if (fctx._filerev is None
516 and (self._repo._encodefilterpats
494 and (self._repo._encodefilterpats
517 # if file data starts with '\1\n', empty metadata block is
495 # if file data starts with '\1\n', empty metadata block is
518 # prepended, which adds 4 bytes to filelog.size().
496 # prepended, which adds 4 bytes to filelog.size().
519 or self.size() - 4 == fctx.size())
497 or self.size() - 4 == fctx.size())
520 or self.size() == fctx.size()):
498 or self.size() == fctx.size()):
521 return self._filelog.cmp(self._filenode, fctx.data())
499 return self._filelog.cmp(self._filenode, fctx.data())
522
500
523 return True
501 return True
524
502
525 def renamed(self):
503 def renamed(self):
526 """check if file was actually renamed in this changeset revision
504 """check if file was actually renamed in this changeset revision
527
505
528 If rename logged in file revision, we report copy for changeset only
506 If rename logged in file revision, we report copy for changeset only
529 if file revisions linkrev points back to the changeset in question
507 if file revisions linkrev points back to the changeset in question
530 or both changeset parents contain different file revisions.
508 or both changeset parents contain different file revisions.
531 """
509 """
532
510
533 renamed = self._filelog.renamed(self._filenode)
511 renamed = self._filelog.renamed(self._filenode)
534 if not renamed:
512 if not renamed:
535 return renamed
513 return renamed
536
514
537 if self.rev() == self.linkrev():
515 if self.rev() == self.linkrev():
538 return renamed
516 return renamed
539
517
540 name = self.path()
518 name = self.path()
541 fnode = self._filenode
519 fnode = self._filenode
542 for p in self._changectx.parents():
520 for p in self._changectx.parents():
543 try:
521 try:
544 if fnode == p.filenode(name):
522 if fnode == p.filenode(name):
545 return None
523 return None
546 except error.LookupError:
524 except error.LookupError:
547 pass
525 pass
548 return renamed
526 return renamed
549
527
550 def parents(self):
528 def parents(self):
551 p = self._path
529 p = self._path
552 fl = self._filelog
530 fl = self._filelog
553 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
531 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
554
532
555 r = self._filelog.renamed(self._filenode)
533 r = self._filelog.renamed(self._filenode)
556 if r:
534 if r:
557 pl[0] = (r[0], r[1], None)
535 pl[0] = (r[0], r[1], None)
558
536
559 return [filectx(self._repo, p, fileid=n, filelog=l)
537 return [filectx(self._repo, p, fileid=n, filelog=l)
560 for p, n, l in pl if n != nullid]
538 for p, n, l in pl if n != nullid]
561
539
562 def p1(self):
540 def p1(self):
563 return self.parents()[0]
541 return self.parents()[0]
564
542
565 def p2(self):
543 def p2(self):
566 p = self.parents()
544 p = self.parents()
567 if len(p) == 2:
545 if len(p) == 2:
568 return p[1]
546 return p[1]
569 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
547 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
570
548
571 def children(self):
549 def children(self):
572 # hard for renames
550 # hard for renames
573 c = self._filelog.children(self._filenode)
551 c = self._filelog.children(self._filenode)
574 return [filectx(self._repo, self._path, fileid=x,
552 return [filectx(self._repo, self._path, fileid=x,
575 filelog=self._filelog) for x in c]
553 filelog=self._filelog) for x in c]
576
554
577 def annotate(self, follow=False, linenumber=None, diffopts=None):
555 def annotate(self, follow=False, linenumber=None, diffopts=None):
578 '''returns a list of tuples of (ctx, line) for each line
556 '''returns a list of tuples of (ctx, line) for each line
579 in the file, where ctx is the filectx of the node where
557 in the file, where ctx is the filectx of the node where
580 that line was last changed.
558 that line was last changed.
581 This returns tuples of ((ctx, linenumber), line) for each line,
559 This returns tuples of ((ctx, linenumber), line) for each line,
582 if "linenumber" parameter is NOT "None".
560 if "linenumber" parameter is NOT "None".
583 In such tuples, linenumber means one at the first appearance
561 In such tuples, linenumber means one at the first appearance
584 in the managed file.
562 in the managed file.
585 To reduce annotation cost,
563 To reduce annotation cost,
586 this returns fixed value(False is used) as linenumber,
564 this returns fixed value(False is used) as linenumber,
587 if "linenumber" parameter is "False".'''
565 if "linenumber" parameter is "False".'''
588
566
589 def decorate_compat(text, rev):
567 def decorate_compat(text, rev):
590 return ([rev] * len(text.splitlines()), text)
568 return ([rev] * len(text.splitlines()), text)
591
569
592 def without_linenumber(text, rev):
570 def without_linenumber(text, rev):
593 return ([(rev, False)] * len(text.splitlines()), text)
571 return ([(rev, False)] * len(text.splitlines()), text)
594
572
595 def with_linenumber(text, rev):
573 def with_linenumber(text, rev):
596 size = len(text.splitlines())
574 size = len(text.splitlines())
597 return ([(rev, i) for i in xrange(1, size + 1)], text)
575 return ([(rev, i) for i in xrange(1, size + 1)], text)
598
576
599 decorate = (((linenumber is None) and decorate_compat) or
577 decorate = (((linenumber is None) and decorate_compat) or
600 (linenumber and with_linenumber) or
578 (linenumber and with_linenumber) or
601 without_linenumber)
579 without_linenumber)
602
580
603 def pair(parent, child):
581 def pair(parent, child):
604 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
582 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
605 refine=True)
583 refine=True)
606 for (a1, a2, b1, b2), t in blocks:
584 for (a1, a2, b1, b2), t in blocks:
607 # Changed blocks ('!') or blocks made only of blank lines ('~')
585 # Changed blocks ('!') or blocks made only of blank lines ('~')
608 # belong to the child.
586 # belong to the child.
609 if t == '=':
587 if t == '=':
610 child[0][b1:b2] = parent[0][a1:a2]
588 child[0][b1:b2] = parent[0][a1:a2]
611 return child
589 return child
612
590
613 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
591 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
614 def getctx(path, fileid):
592 def getctx(path, fileid):
615 log = path == self._path and self._filelog or getlog(path)
593 log = path == self._path and self._filelog or getlog(path)
616 return filectx(self._repo, path, fileid=fileid, filelog=log)
594 return filectx(self._repo, path, fileid=fileid, filelog=log)
617 getctx = util.lrucachefunc(getctx)
595 getctx = util.lrucachefunc(getctx)
618
596
619 def parents(f):
597 def parents(f):
620 # we want to reuse filectx objects as much as possible
598 # we want to reuse filectx objects as much as possible
621 p = f._path
599 p = f._path
622 if f._filerev is None: # working dir
600 if f._filerev is None: # working dir
623 pl = [(n.path(), n.filerev()) for n in f.parents()]
601 pl = [(n.path(), n.filerev()) for n in f.parents()]
624 else:
602 else:
625 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
603 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
626
604
627 if follow:
605 if follow:
628 r = f.renamed()
606 r = f.renamed()
629 if r:
607 if r:
630 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
608 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
631
609
632 return [getctx(p, n) for p, n in pl if n != nullrev]
610 return [getctx(p, n) for p, n in pl if n != nullrev]
633
611
634 # use linkrev to find the first changeset where self appeared
612 # use linkrev to find the first changeset where self appeared
635 if self.rev() != self.linkrev():
613 if self.rev() != self.linkrev():
636 base = self.filectx(self.filerev())
614 base = self.filectx(self.filerev())
637 else:
615 else:
638 base = self
616 base = self
639
617
640 # This algorithm would prefer to be recursive, but Python is a
618 # This algorithm would prefer to be recursive, but Python is a
641 # bit recursion-hostile. Instead we do an iterative
619 # bit recursion-hostile. Instead we do an iterative
642 # depth-first search.
620 # depth-first search.
643
621
644 visit = [base]
622 visit = [base]
645 hist = {}
623 hist = {}
646 pcache = {}
624 pcache = {}
647 needed = {base: 1}
625 needed = {base: 1}
648 while visit:
626 while visit:
649 f = visit[-1]
627 f = visit[-1]
650 if f not in pcache:
628 if f not in pcache:
651 pcache[f] = parents(f)
629 pcache[f] = parents(f)
652
630
653 ready = True
631 ready = True
654 pl = pcache[f]
632 pl = pcache[f]
655 for p in pl:
633 for p in pl:
656 if p not in hist:
634 if p not in hist:
657 ready = False
635 ready = False
658 visit.append(p)
636 visit.append(p)
659 needed[p] = needed.get(p, 0) + 1
637 needed[p] = needed.get(p, 0) + 1
660 if ready:
638 if ready:
661 visit.pop()
639 visit.pop()
662 curr = decorate(f.data(), f)
640 curr = decorate(f.data(), f)
663 for p in pl:
641 for p in pl:
664 curr = pair(hist[p], curr)
642 curr = pair(hist[p], curr)
665 if needed[p] == 1:
643 if needed[p] == 1:
666 del hist[p]
644 del hist[p]
667 else:
645 else:
668 needed[p] -= 1
646 needed[p] -= 1
669
647
670 hist[f] = curr
648 hist[f] = curr
671 pcache[f] = []
649 pcache[f] = []
672
650
673 return zip(hist[base][0], hist[base][1].splitlines(True))
651 return zip(hist[base][0], hist[base][1].splitlines(True))
674
652
675 def ancestor(self, fc2, actx):
653 def ancestor(self, fc2, actx):
676 """
654 """
677 find the common ancestor file context, if any, of self, and fc2
655 find the common ancestor file context, if any, of self, and fc2
678
656
679 actx must be the changectx of the common ancestor
657 actx must be the changectx of the common ancestor
680 of self's and fc2's respective changesets.
658 of self's and fc2's respective changesets.
681 """
659 """
682
660
683 # the easy case: no (relevant) renames
661 # the easy case: no (relevant) renames
684 if fc2.path() == self.path() and self.path() in actx:
662 if fc2.path() == self.path() and self.path() in actx:
685 return actx[self.path()]
663 return actx[self.path()]
686
664
687 # the next easiest cases: unambiguous predecessor (name trumps
665 # the next easiest cases: unambiguous predecessor (name trumps
688 # history)
666 # history)
689 if self.path() in actx and fc2.path() not in actx:
667 if self.path() in actx and fc2.path() not in actx:
690 return actx[self.path()]
668 return actx[self.path()]
691 if fc2.path() in actx and self.path() not in actx:
669 if fc2.path() in actx and self.path() not in actx:
692 return actx[fc2.path()]
670 return actx[fc2.path()]
693
671
694 # prime the ancestor cache for the working directory
672 # prime the ancestor cache for the working directory
695 acache = {}
673 acache = {}
696 for c in (self, fc2):
674 for c in (self, fc2):
697 if c._filerev is None:
675 if c._filerev is None:
698 pl = [(n.path(), n.filenode()) for n in c.parents()]
676 pl = [(n.path(), n.filenode()) for n in c.parents()]
699 acache[(c._path, None)] = pl
677 acache[(c._path, None)] = pl
700
678
701 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
679 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
702 def parents(vertex):
680 def parents(vertex):
703 if vertex in acache:
681 if vertex in acache:
704 return acache[vertex]
682 return acache[vertex]
705 f, n = vertex
683 f, n = vertex
706 if f not in flcache:
684 if f not in flcache:
707 flcache[f] = self._repo.file(f)
685 flcache[f] = self._repo.file(f)
708 fl = flcache[f]
686 fl = flcache[f]
709 pl = [(f, p) for p in fl.parents(n) if p != nullid]
687 pl = [(f, p) for p in fl.parents(n) if p != nullid]
710 re = fl.renamed(n)
688 re = fl.renamed(n)
711 if re:
689 if re:
712 pl.append(re)
690 pl.append(re)
713 acache[vertex] = pl
691 acache[vertex] = pl
714 return pl
692 return pl
715
693
716 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
694 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
717 v = ancestor.ancestor(a, b, parents)
695 v = ancestor.ancestor(a, b, parents)
718 if v:
696 if v:
719 f, n = v
697 f, n = v
720 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
698 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
721
699
722 return None
700 return None
723
701
724 def ancestors(self, followfirst=False):
702 def ancestors(self, followfirst=False):
725 visit = {}
703 visit = {}
726 c = self
704 c = self
727 cut = followfirst and 1 or None
705 cut = followfirst and 1 or None
728 while True:
706 while True:
729 for parent in c.parents()[:cut]:
707 for parent in c.parents()[:cut]:
730 visit[(parent.rev(), parent.node())] = parent
708 visit[(parent.rev(), parent.node())] = parent
731 if not visit:
709 if not visit:
732 break
710 break
733 c = visit.pop(max(visit))
711 c = visit.pop(max(visit))
734 yield c
712 yield c
735
713
736 def copies(self, c2):
714 def copies(self, c2):
737 if not util.safehasattr(self, "_copycache"):
715 if not util.safehasattr(self, "_copycache"):
738 self._copycache = {}
716 self._copycache = {}
739 sc2 = str(c2)
717 sc2 = str(c2)
740 if sc2 not in self._copycache:
718 if sc2 not in self._copycache:
741 self._copycache[sc2] = copies.pathcopies(c2)
719 self._copycache[sc2] = copies.pathcopies(c2)
742 return self._copycache[sc2]
720 return self._copycache[sc2]
743
721
744 class workingctx(changectx):
722 class workingctx(changectx):
745 """A workingctx object makes access to data related to
723 """A workingctx object makes access to data related to
746 the current working directory convenient.
724 the current working directory convenient.
747 date - any valid date string or (unixtime, offset), or None.
725 date - any valid date string or (unixtime, offset), or None.
748 user - username string, or None.
726 user - username string, or None.
749 extra - a dictionary of extra values, or None.
727 extra - a dictionary of extra values, or None.
750 changes - a list of file lists as returned by localrepo.status()
728 changes - a list of file lists as returned by localrepo.status()
751 or None to use the repository status.
729 or None to use the repository status.
752 """
730 """
753 def __init__(self, repo, text="", user=None, date=None, extra=None,
731 def __init__(self, repo, text="", user=None, date=None, extra=None,
754 changes=None):
732 changes=None):
755 self._repo = repo
733 self._repo = repo
756 self._rev = None
734 self._rev = None
757 self._node = None
735 self._node = None
758 self._text = text
736 self._text = text
759 if date:
737 if date:
760 self._date = util.parsedate(date)
738 self._date = util.parsedate(date)
761 if user:
739 if user:
762 self._user = user
740 self._user = user
763 if changes:
741 if changes:
764 self._status = list(changes[:4])
742 self._status = list(changes[:4])
765 self._unknown = changes[4]
743 self._unknown = changes[4]
766 self._ignored = changes[5]
744 self._ignored = changes[5]
767 self._clean = changes[6]
745 self._clean = changes[6]
768 else:
746 else:
769 self._unknown = None
747 self._unknown = None
770 self._ignored = None
748 self._ignored = None
771 self._clean = None
749 self._clean = None
772
750
773 self._extra = {}
751 self._extra = {}
774 if extra:
752 if extra:
775 self._extra = extra.copy()
753 self._extra = extra.copy()
776 if 'branch' not in self._extra:
754 if 'branch' not in self._extra:
777 try:
755 try:
778 branch = encoding.fromlocal(self._repo.dirstate.branch())
756 branch = encoding.fromlocal(self._repo.dirstate.branch())
779 except UnicodeDecodeError:
757 except UnicodeDecodeError:
780 raise util.Abort(_('branch name not in UTF-8!'))
758 raise util.Abort(_('branch name not in UTF-8!'))
781 self._extra['branch'] = branch
759 self._extra['branch'] = branch
782 if self._extra['branch'] == '':
760 if self._extra['branch'] == '':
783 self._extra['branch'] = 'default'
761 self._extra['branch'] = 'default'
784
762
785 def __str__(self):
763 def __str__(self):
786 return str(self._parents[0]) + "+"
764 return str(self._parents[0]) + "+"
787
765
788 def __repr__(self):
766 def __repr__(self):
789 return "<workingctx %s>" % str(self)
767 return "<workingctx %s>" % str(self)
790
768
791 def __nonzero__(self):
769 def __nonzero__(self):
792 return True
770 return True
793
771
794 def __contains__(self, key):
772 def __contains__(self, key):
795 return self._repo.dirstate[key] not in "?r"
773 return self._repo.dirstate[key] not in "?r"
796
774
797 def _buildflagfunc(self):
775 def _buildflagfunc(self):
798 # Create a fallback function for getting file flags when the
776 # Create a fallback function for getting file flags when the
799 # filesystem doesn't support them
777 # filesystem doesn't support them
800
778
801 copiesget = self._repo.dirstate.copies().get
779 copiesget = self._repo.dirstate.copies().get
802
780
803 if len(self._parents) < 2:
781 if len(self._parents) < 2:
804 # when we have one parent, it's easy: copy from parent
782 # when we have one parent, it's easy: copy from parent
805 man = self._parents[0].manifest()
783 man = self._parents[0].manifest()
806 def func(f):
784 def func(f):
807 f = copiesget(f, f)
785 f = copiesget(f, f)
808 return man.flags(f)
786 return man.flags(f)
809 else:
787 else:
810 # merges are tricky: we try to reconstruct the unstored
788 # merges are tricky: we try to reconstruct the unstored
811 # result from the merge (issue1802)
789 # result from the merge (issue1802)
812 p1, p2 = self._parents
790 p1, p2 = self._parents
813 pa = p1.ancestor(p2)
791 pa = p1.ancestor(p2)
814 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
792 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
815
793
816 def func(f):
794 def func(f):
817 f = copiesget(f, f) # may be wrong for merges with copies
795 f = copiesget(f, f) # may be wrong for merges with copies
818 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
796 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
819 if fl1 == fl2:
797 if fl1 == fl2:
820 return fl1
798 return fl1
821 if fl1 == fla:
799 if fl1 == fla:
822 return fl2
800 return fl2
823 if fl2 == fla:
801 if fl2 == fla:
824 return fl1
802 return fl1
825 return '' # punt for conflicts
803 return '' # punt for conflicts
826
804
827 return func
805 return func
828
806
829 @propertycache
807 @propertycache
830 def _flagfunc(self):
808 def _flagfunc(self):
831 return self._repo.dirstate.flagfunc(self._buildflagfunc)
809 return self._repo.dirstate.flagfunc(self._buildflagfunc)
832
810
833 @propertycache
811 @propertycache
834 def _manifest(self):
812 def _manifest(self):
835 """generate a manifest corresponding to the working directory"""
813 """generate a manifest corresponding to the working directory"""
836
814
837 man = self._parents[0].manifest().copy()
815 man = self._parents[0].manifest().copy()
838 if len(self._parents) > 1:
816 if len(self._parents) > 1:
839 man2 = self.p2().manifest()
817 man2 = self.p2().manifest()
840 def getman(f):
818 def getman(f):
841 if f in man:
819 if f in man:
842 return man
820 return man
843 return man2
821 return man2
844 else:
822 else:
845 getman = lambda f: man
823 getman = lambda f: man
846
824
847 copied = self._repo.dirstate.copies()
825 copied = self._repo.dirstate.copies()
848 ff = self._flagfunc
826 ff = self._flagfunc
849 modified, added, removed, deleted = self._status
827 modified, added, removed, deleted = self._status
850 for i, l in (("a", added), ("m", modified)):
828 for i, l in (("a", added), ("m", modified)):
851 for f in l:
829 for f in l:
852 orig = copied.get(f, f)
830 orig = copied.get(f, f)
853 man[f] = getman(orig).get(orig, nullid) + i
831 man[f] = getman(orig).get(orig, nullid) + i
854 try:
832 try:
855 man.set(f, ff(f))
833 man.set(f, ff(f))
856 except OSError:
834 except OSError:
857 pass
835 pass
858
836
859 for f in deleted + removed:
837 for f in deleted + removed:
860 if f in man:
838 if f in man:
861 del man[f]
839 del man[f]
862
840
863 return man
841 return man
864
842
865 def __iter__(self):
843 def __iter__(self):
866 d = self._repo.dirstate
844 d = self._repo.dirstate
867 for f in d:
845 for f in d:
868 if d[f] != 'r':
846 if d[f] != 'r':
869 yield f
847 yield f
870
848
871 @propertycache
849 @propertycache
872 def _status(self):
850 def _status(self):
873 return self._repo.status()[:4]
851 return self._repo.status()[:4]
874
852
875 @propertycache
853 @propertycache
876 def _user(self):
854 def _user(self):
877 return self._repo.ui.username()
855 return self._repo.ui.username()
878
856
879 @propertycache
857 @propertycache
880 def _date(self):
858 def _date(self):
881 return util.makedate()
859 return util.makedate()
882
860
883 @propertycache
861 @propertycache
884 def _parents(self):
862 def _parents(self):
885 p = self._repo.dirstate.parents()
863 p = self._repo.dirstate.parents()
886 if p[1] == nullid:
864 if p[1] == nullid:
887 p = p[:-1]
865 p = p[:-1]
888 return [changectx(self._repo, x) for x in p]
866 return [changectx(self._repo, x) for x in p]
889
867
890 def status(self, ignored=False, clean=False, unknown=False):
868 def status(self, ignored=False, clean=False, unknown=False):
891 """Explicit status query
869 """Explicit status query
892 Unless this method is used to query the working copy status, the
870 Unless this method is used to query the working copy status, the
893 _status property will implicitly read the status using its default
871 _status property will implicitly read the status using its default
894 arguments."""
872 arguments."""
895 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
873 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
896 self._unknown = self._ignored = self._clean = None
874 self._unknown = self._ignored = self._clean = None
897 if unknown:
875 if unknown:
898 self._unknown = stat[4]
876 self._unknown = stat[4]
899 if ignored:
877 if ignored:
900 self._ignored = stat[5]
878 self._ignored = stat[5]
901 if clean:
879 if clean:
902 self._clean = stat[6]
880 self._clean = stat[6]
903 self._status = stat[:4]
881 self._status = stat[:4]
904 return stat
882 return stat
905
883
906 def manifest(self):
884 def manifest(self):
907 return self._manifest
885 return self._manifest
908 def user(self):
886 def user(self):
909 return self._user or self._repo.ui.username()
887 return self._user or self._repo.ui.username()
910 def date(self):
888 def date(self):
911 return self._date
889 return self._date
912 def description(self):
890 def description(self):
913 return self._text
891 return self._text
914 def files(self):
892 def files(self):
915 return sorted(self._status[0] + self._status[1] + self._status[2])
893 return sorted(self._status[0] + self._status[1] + self._status[2])
916
894
917 def modified(self):
895 def modified(self):
918 return self._status[0]
896 return self._status[0]
919 def added(self):
897 def added(self):
920 return self._status[1]
898 return self._status[1]
921 def removed(self):
899 def removed(self):
922 return self._status[2]
900 return self._status[2]
923 def deleted(self):
901 def deleted(self):
924 return self._status[3]
902 return self._status[3]
925 def unknown(self):
903 def unknown(self):
926 assert self._unknown is not None # must call status first
904 assert self._unknown is not None # must call status first
927 return self._unknown
905 return self._unknown
928 def ignored(self):
906 def ignored(self):
929 assert self._ignored is not None # must call status first
907 assert self._ignored is not None # must call status first
930 return self._ignored
908 return self._ignored
931 def clean(self):
909 def clean(self):
932 assert self._clean is not None # must call status first
910 assert self._clean is not None # must call status first
933 return self._clean
911 return self._clean
934 def branch(self):
912 def branch(self):
935 return encoding.tolocal(self._extra['branch'])
913 return encoding.tolocal(self._extra['branch'])
936 def closesbranch(self):
914 def closesbranch(self):
937 return 'close' in self._extra
915 return 'close' in self._extra
938 def extra(self):
916 def extra(self):
939 return self._extra
917 return self._extra
940
918
941 def tags(self):
919 def tags(self):
942 t = []
920 t = []
943 for p in self.parents():
921 for p in self.parents():
944 t.extend(p.tags())
922 t.extend(p.tags())
945 return t
923 return t
946
924
947 def bookmarks(self):
925 def bookmarks(self):
948 b = []
926 b = []
949 for p in self.parents():
927 for p in self.parents():
950 b.extend(p.bookmarks())
928 b.extend(p.bookmarks())
951 return b
929 return b
952
930
953 def phase(self):
931 def phase(self):
954 phase = phases.draft # default phase to draft
932 phase = phases.draft # default phase to draft
955 for p in self.parents():
933 for p in self.parents():
956 phase = max(phase, p.phase())
934 phase = max(phase, p.phase())
957 return phase
935 return phase
958
936
959 def hidden(self):
937 def hidden(self):
960 return False
938 return False
961
939
962 def children(self):
940 def children(self):
963 return []
941 return []
964
942
965 def flags(self, path):
943 def flags(self, path):
966 if '_manifest' in self.__dict__:
944 if '_manifest' in self.__dict__:
967 try:
945 try:
968 return self._manifest.flags(path)
946 return self._manifest.flags(path)
969 except KeyError:
947 except KeyError:
970 return ''
948 return ''
971
949
972 try:
950 try:
973 return self._flagfunc(path)
951 return self._flagfunc(path)
974 except OSError:
952 except OSError:
975 return ''
953 return ''
976
954
977 def filectx(self, path, filelog=None):
955 def filectx(self, path, filelog=None):
978 """get a file context from the working directory"""
956 """get a file context from the working directory"""
979 return workingfilectx(self._repo, path, workingctx=self,
957 return workingfilectx(self._repo, path, workingctx=self,
980 filelog=filelog)
958 filelog=filelog)
981
959
982 def ancestor(self, c2):
960 def ancestor(self, c2):
983 """return the ancestor context of self and c2"""
961 """return the ancestor context of self and c2"""
984 return self._parents[0].ancestor(c2) # punt on two parents for now
962 return self._parents[0].ancestor(c2) # punt on two parents for now
985
963
986 def walk(self, match):
964 def walk(self, match):
987 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
965 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
988 True, False))
966 True, False))
989
967
990 def dirty(self, missing=False, merge=True, branch=True):
968 def dirty(self, missing=False, merge=True, branch=True):
991 "check whether a working directory is modified"
969 "check whether a working directory is modified"
992 # check subrepos first
970 # check subrepos first
993 for s in self.substate:
971 for s in self.substate:
994 if self.sub(s).dirty():
972 if self.sub(s).dirty():
995 return True
973 return True
996 # check current working dir
974 # check current working dir
997 return ((merge and self.p2()) or
975 return ((merge and self.p2()) or
998 (branch and self.branch() != self.p1().branch()) or
976 (branch and self.branch() != self.p1().branch()) or
999 self.modified() or self.added() or self.removed() or
977 self.modified() or self.added() or self.removed() or
1000 (missing and self.deleted()))
978 (missing and self.deleted()))
1001
979
1002 def add(self, list, prefix=""):
980 def add(self, list, prefix=""):
1003 join = lambda f: os.path.join(prefix, f)
981 join = lambda f: os.path.join(prefix, f)
1004 wlock = self._repo.wlock()
982 wlock = self._repo.wlock()
1005 ui, ds = self._repo.ui, self._repo.dirstate
983 ui, ds = self._repo.ui, self._repo.dirstate
1006 try:
984 try:
1007 rejected = []
985 rejected = []
1008 for f in list:
986 for f in list:
1009 scmutil.checkportable(ui, join(f))
987 scmutil.checkportable(ui, join(f))
1010 p = self._repo.wjoin(f)
988 p = self._repo.wjoin(f)
1011 try:
989 try:
1012 st = os.lstat(p)
990 st = os.lstat(p)
1013 except OSError:
991 except OSError:
1014 ui.warn(_("%s does not exist!\n") % join(f))
992 ui.warn(_("%s does not exist!\n") % join(f))
1015 rejected.append(f)
993 rejected.append(f)
1016 continue
994 continue
1017 if st.st_size > 10000000:
995 if st.st_size > 10000000:
1018 ui.warn(_("%s: up to %d MB of RAM may be required "
996 ui.warn(_("%s: up to %d MB of RAM may be required "
1019 "to manage this file\n"
997 "to manage this file\n"
1020 "(use 'hg revert %s' to cancel the "
998 "(use 'hg revert %s' to cancel the "
1021 "pending addition)\n")
999 "pending addition)\n")
1022 % (f, 3 * st.st_size // 1000000, join(f)))
1000 % (f, 3 * st.st_size // 1000000, join(f)))
1023 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1001 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1024 ui.warn(_("%s not added: only files and symlinks "
1002 ui.warn(_("%s not added: only files and symlinks "
1025 "supported currently\n") % join(f))
1003 "supported currently\n") % join(f))
1026 rejected.append(p)
1004 rejected.append(p)
1027 elif ds[f] in 'amn':
1005 elif ds[f] in 'amn':
1028 ui.warn(_("%s already tracked!\n") % join(f))
1006 ui.warn(_("%s already tracked!\n") % join(f))
1029 elif ds[f] == 'r':
1007 elif ds[f] == 'r':
1030 ds.normallookup(f)
1008 ds.normallookup(f)
1031 else:
1009 else:
1032 ds.add(f)
1010 ds.add(f)
1033 return rejected
1011 return rejected
1034 finally:
1012 finally:
1035 wlock.release()
1013 wlock.release()
1036
1014
1037 def forget(self, files, prefix=""):
1015 def forget(self, files, prefix=""):
1038 join = lambda f: os.path.join(prefix, f)
1016 join = lambda f: os.path.join(prefix, f)
1039 wlock = self._repo.wlock()
1017 wlock = self._repo.wlock()
1040 try:
1018 try:
1041 rejected = []
1019 rejected = []
1042 for f in files:
1020 for f in files:
1043 if f not in self._repo.dirstate:
1021 if f not in self._repo.dirstate:
1044 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1022 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1045 rejected.append(f)
1023 rejected.append(f)
1046 elif self._repo.dirstate[f] != 'a':
1024 elif self._repo.dirstate[f] != 'a':
1047 self._repo.dirstate.remove(f)
1025 self._repo.dirstate.remove(f)
1048 else:
1026 else:
1049 self._repo.dirstate.drop(f)
1027 self._repo.dirstate.drop(f)
1050 return rejected
1028 return rejected
1051 finally:
1029 finally:
1052 wlock.release()
1030 wlock.release()
1053
1031
1054 def ancestors(self):
1032 def ancestors(self):
1055 for a in self._repo.changelog.ancestors(
1033 for a in self._repo.changelog.ancestors(
1056 [p.rev() for p in self._parents]):
1034 [p.rev() for p in self._parents]):
1057 yield changectx(self._repo, a)
1035 yield changectx(self._repo, a)
1058
1036
1059 def undelete(self, list):
1037 def undelete(self, list):
1060 pctxs = self.parents()
1038 pctxs = self.parents()
1061 wlock = self._repo.wlock()
1039 wlock = self._repo.wlock()
1062 try:
1040 try:
1063 for f in list:
1041 for f in list:
1064 if self._repo.dirstate[f] != 'r':
1042 if self._repo.dirstate[f] != 'r':
1065 self._repo.ui.warn(_("%s not removed!\n") % f)
1043 self._repo.ui.warn(_("%s not removed!\n") % f)
1066 else:
1044 else:
1067 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1045 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1068 t = fctx.data()
1046 t = fctx.data()
1069 self._repo.wwrite(f, t, fctx.flags())
1047 self._repo.wwrite(f, t, fctx.flags())
1070 self._repo.dirstate.normal(f)
1048 self._repo.dirstate.normal(f)
1071 finally:
1049 finally:
1072 wlock.release()
1050 wlock.release()
1073
1051
1074 def copy(self, source, dest):
1052 def copy(self, source, dest):
1075 p = self._repo.wjoin(dest)
1053 p = self._repo.wjoin(dest)
1076 if not os.path.lexists(p):
1054 if not os.path.lexists(p):
1077 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1055 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1078 elif not (os.path.isfile(p) or os.path.islink(p)):
1056 elif not (os.path.isfile(p) or os.path.islink(p)):
1079 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1057 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1080 "symbolic link\n") % dest)
1058 "symbolic link\n") % dest)
1081 else:
1059 else:
1082 wlock = self._repo.wlock()
1060 wlock = self._repo.wlock()
1083 try:
1061 try:
1084 if self._repo.dirstate[dest] in '?r':
1062 if self._repo.dirstate[dest] in '?r':
1085 self._repo.dirstate.add(dest)
1063 self._repo.dirstate.add(dest)
1086 self._repo.dirstate.copy(source, dest)
1064 self._repo.dirstate.copy(source, dest)
1087 finally:
1065 finally:
1088 wlock.release()
1066 wlock.release()
1089
1067
1090 def dirs(self):
1068 def dirs(self):
1091 return set(self._repo.dirstate.dirs())
1069 return set(self._repo.dirstate.dirs())
1092
1070
1093 class workingfilectx(filectx):
1071 class workingfilectx(filectx):
1094 """A workingfilectx object makes access to data related to a particular
1072 """A workingfilectx object makes access to data related to a particular
1095 file in the working directory convenient."""
1073 file in the working directory convenient."""
1096 def __init__(self, repo, path, filelog=None, workingctx=None):
1074 def __init__(self, repo, path, filelog=None, workingctx=None):
1097 """changeid can be a changeset revision, node, or tag.
1075 """changeid can be a changeset revision, node, or tag.
1098 fileid can be a file revision or node."""
1076 fileid can be a file revision or node."""
1099 self._repo = repo
1077 self._repo = repo
1100 self._path = path
1078 self._path = path
1101 self._changeid = None
1079 self._changeid = None
1102 self._filerev = self._filenode = None
1080 self._filerev = self._filenode = None
1103
1081
1104 if filelog:
1082 if filelog:
1105 self._filelog = filelog
1083 self._filelog = filelog
1106 if workingctx:
1084 if workingctx:
1107 self._changectx = workingctx
1085 self._changectx = workingctx
1108
1086
1109 @propertycache
1087 @propertycache
1110 def _changectx(self):
1088 def _changectx(self):
1111 return workingctx(self._repo)
1089 return workingctx(self._repo)
1112
1090
1113 def __nonzero__(self):
1091 def __nonzero__(self):
1114 return True
1092 return True
1115
1093
1116 def __str__(self):
1094 def __str__(self):
1117 return "%s@%s" % (self.path(), self._changectx)
1095 return "%s@%s" % (self.path(), self._changectx)
1118
1096
1119 def __repr__(self):
1097 def __repr__(self):
1120 return "<workingfilectx %s>" % str(self)
1098 return "<workingfilectx %s>" % str(self)
1121
1099
1122 def data(self):
1100 def data(self):
1123 return self._repo.wread(self._path)
1101 return self._repo.wread(self._path)
1124 def renamed(self):
1102 def renamed(self):
1125 rp = self._repo.dirstate.copied(self._path)
1103 rp = self._repo.dirstate.copied(self._path)
1126 if not rp:
1104 if not rp:
1127 return None
1105 return None
1128 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1106 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1129
1107
1130 def parents(self):
1108 def parents(self):
1131 '''return parent filectxs, following copies if necessary'''
1109 '''return parent filectxs, following copies if necessary'''
1132 def filenode(ctx, path):
1110 def filenode(ctx, path):
1133 return ctx._manifest.get(path, nullid)
1111 return ctx._manifest.get(path, nullid)
1134
1112
1135 path = self._path
1113 path = self._path
1136 fl = self._filelog
1114 fl = self._filelog
1137 pcl = self._changectx._parents
1115 pcl = self._changectx._parents
1138 renamed = self.renamed()
1116 renamed = self.renamed()
1139
1117
1140 if renamed:
1118 if renamed:
1141 pl = [renamed + (None,)]
1119 pl = [renamed + (None,)]
1142 else:
1120 else:
1143 pl = [(path, filenode(pcl[0], path), fl)]
1121 pl = [(path, filenode(pcl[0], path), fl)]
1144
1122
1145 for pc in pcl[1:]:
1123 for pc in pcl[1:]:
1146 pl.append((path, filenode(pc, path), fl))
1124 pl.append((path, filenode(pc, path), fl))
1147
1125
1148 return [filectx(self._repo, p, fileid=n, filelog=l)
1126 return [filectx(self._repo, p, fileid=n, filelog=l)
1149 for p, n, l in pl if n != nullid]
1127 for p, n, l in pl if n != nullid]
1150
1128
1151 def children(self):
1129 def children(self):
1152 return []
1130 return []
1153
1131
1154 def size(self):
1132 def size(self):
1155 return os.lstat(self._repo.wjoin(self._path)).st_size
1133 return os.lstat(self._repo.wjoin(self._path)).st_size
1156 def date(self):
1134 def date(self):
1157 t, tz = self._changectx.date()
1135 t, tz = self._changectx.date()
1158 try:
1136 try:
1159 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
1137 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
1160 except OSError, err:
1138 except OSError, err:
1161 if err.errno != errno.ENOENT:
1139 if err.errno != errno.ENOENT:
1162 raise
1140 raise
1163 return (t, tz)
1141 return (t, tz)
1164
1142
1165 def cmp(self, fctx):
1143 def cmp(self, fctx):
1166 """compare with other file context
1144 """compare with other file context
1167
1145
1168 returns True if different than fctx.
1146 returns True if different than fctx.
1169 """
1147 """
1170 # fctx should be a filectx (not a workingfilectx)
1148 # fctx should be a filectx (not a workingfilectx)
1171 # invert comparison to reuse the same code path
1149 # invert comparison to reuse the same code path
1172 return fctx.cmp(self)
1150 return fctx.cmp(self)
1173
1151
1174 class memctx(object):
1152 class memctx(object):
1175 """Use memctx to perform in-memory commits via localrepo.commitctx().
1153 """Use memctx to perform in-memory commits via localrepo.commitctx().
1176
1154
1177 Revision information is supplied at initialization time while
1155 Revision information is supplied at initialization time while
1178 related files data and is made available through a callback
1156 related files data and is made available through a callback
1179 mechanism. 'repo' is the current localrepo, 'parents' is a
1157 mechanism. 'repo' is the current localrepo, 'parents' is a
1180 sequence of two parent revisions identifiers (pass None for every
1158 sequence of two parent revisions identifiers (pass None for every
1181 missing parent), 'text' is the commit message and 'files' lists
1159 missing parent), 'text' is the commit message and 'files' lists
1182 names of files touched by the revision (normalized and relative to
1160 names of files touched by the revision (normalized and relative to
1183 repository root).
1161 repository root).
1184
1162
1185 filectxfn(repo, memctx, path) is a callable receiving the
1163 filectxfn(repo, memctx, path) is a callable receiving the
1186 repository, the current memctx object and the normalized path of
1164 repository, the current memctx object and the normalized path of
1187 requested file, relative to repository root. It is fired by the
1165 requested file, relative to repository root. It is fired by the
1188 commit function for every file in 'files', but calls order is
1166 commit function for every file in 'files', but calls order is
1189 undefined. If the file is available in the revision being
1167 undefined. If the file is available in the revision being
1190 committed (updated or added), filectxfn returns a memfilectx
1168 committed (updated or added), filectxfn returns a memfilectx
1191 object. If the file was removed, filectxfn raises an
1169 object. If the file was removed, filectxfn raises an
1192 IOError. Moved files are represented by marking the source file
1170 IOError. Moved files are represented by marking the source file
1193 removed and the new file added with copy information (see
1171 removed and the new file added with copy information (see
1194 memfilectx).
1172 memfilectx).
1195
1173
1196 user receives the committer name and defaults to current
1174 user receives the committer name and defaults to current
1197 repository username, date is the commit date in any format
1175 repository username, date is the commit date in any format
1198 supported by util.parsedate() and defaults to current date, extra
1176 supported by util.parsedate() and defaults to current date, extra
1199 is a dictionary of metadata or is left empty.
1177 is a dictionary of metadata or is left empty.
1200 """
1178 """
1201 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1179 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1202 date=None, extra=None):
1180 date=None, extra=None):
1203 self._repo = repo
1181 self._repo = repo
1204 self._rev = None
1182 self._rev = None
1205 self._node = None
1183 self._node = None
1206 self._text = text
1184 self._text = text
1207 self._date = date and util.parsedate(date) or util.makedate()
1185 self._date = date and util.parsedate(date) or util.makedate()
1208 self._user = user
1186 self._user = user
1209 parents = [(p or nullid) for p in parents]
1187 parents = [(p or nullid) for p in parents]
1210 p1, p2 = parents
1188 p1, p2 = parents
1211 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1189 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1212 files = sorted(set(files))
1190 files = sorted(set(files))
1213 self._status = [files, [], [], [], []]
1191 self._status = [files, [], [], [], []]
1214 self._filectxfn = filectxfn
1192 self._filectxfn = filectxfn
1215
1193
1216 self._extra = extra and extra.copy() or {}
1194 self._extra = extra and extra.copy() or {}
1217 if self._extra.get('branch', '') == '':
1195 if self._extra.get('branch', '') == '':
1218 self._extra['branch'] = 'default'
1196 self._extra['branch'] = 'default'
1219
1197
1220 def __str__(self):
1198 def __str__(self):
1221 return str(self._parents[0]) + "+"
1199 return str(self._parents[0]) + "+"
1222
1200
1223 def __int__(self):
1201 def __int__(self):
1224 return self._rev
1202 return self._rev
1225
1203
1226 def __nonzero__(self):
1204 def __nonzero__(self):
1227 return True
1205 return True
1228
1206
1229 def __getitem__(self, key):
1207 def __getitem__(self, key):
1230 return self.filectx(key)
1208 return self.filectx(key)
1231
1209
1232 def p1(self):
1210 def p1(self):
1233 return self._parents[0]
1211 return self._parents[0]
1234 def p2(self):
1212 def p2(self):
1235 return self._parents[1]
1213 return self._parents[1]
1236
1214
1237 def user(self):
1215 def user(self):
1238 return self._user or self._repo.ui.username()
1216 return self._user or self._repo.ui.username()
1239 def date(self):
1217 def date(self):
1240 return self._date
1218 return self._date
1241 def description(self):
1219 def description(self):
1242 return self._text
1220 return self._text
1243 def files(self):
1221 def files(self):
1244 return self.modified()
1222 return self.modified()
1245 def modified(self):
1223 def modified(self):
1246 return self._status[0]
1224 return self._status[0]
1247 def added(self):
1225 def added(self):
1248 return self._status[1]
1226 return self._status[1]
1249 def removed(self):
1227 def removed(self):
1250 return self._status[2]
1228 return self._status[2]
1251 def deleted(self):
1229 def deleted(self):
1252 return self._status[3]
1230 return self._status[3]
1253 def unknown(self):
1231 def unknown(self):
1254 return self._status[4]
1232 return self._status[4]
1255 def ignored(self):
1233 def ignored(self):
1256 return self._status[5]
1234 return self._status[5]
1257 def clean(self):
1235 def clean(self):
1258 return self._status[6]
1236 return self._status[6]
1259 def branch(self):
1237 def branch(self):
1260 return encoding.tolocal(self._extra['branch'])
1238 return encoding.tolocal(self._extra['branch'])
1261 def extra(self):
1239 def extra(self):
1262 return self._extra
1240 return self._extra
1263 def flags(self, f):
1241 def flags(self, f):
1264 return self[f].flags()
1242 return self[f].flags()
1265
1243
1266 def parents(self):
1244 def parents(self):
1267 """return contexts for each parent changeset"""
1245 """return contexts for each parent changeset"""
1268 return self._parents
1246 return self._parents
1269
1247
1270 def filectx(self, path, filelog=None):
1248 def filectx(self, path, filelog=None):
1271 """get a file context from the working directory"""
1249 """get a file context from the working directory"""
1272 return self._filectxfn(self._repo, self, path)
1250 return self._filectxfn(self._repo, self, path)
1273
1251
1274 def commit(self):
1252 def commit(self):
1275 """commit context to the repo"""
1253 """commit context to the repo"""
1276 return self._repo.commitctx(self)
1254 return self._repo.commitctx(self)
1277
1255
1278 class memfilectx(object):
1256 class memfilectx(object):
1279 """memfilectx represents an in-memory file to commit.
1257 """memfilectx represents an in-memory file to commit.
1280
1258
1281 See memctx for more details.
1259 See memctx for more details.
1282 """
1260 """
1283 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1261 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1284 """
1262 """
1285 path is the normalized file path relative to repository root.
1263 path is the normalized file path relative to repository root.
1286 data is the file content as a string.
1264 data is the file content as a string.
1287 islink is True if the file is a symbolic link.
1265 islink is True if the file is a symbolic link.
1288 isexec is True if the file is executable.
1266 isexec is True if the file is executable.
1289 copied is the source file path if current file was copied in the
1267 copied is the source file path if current file was copied in the
1290 revision being committed, or None."""
1268 revision being committed, or None."""
1291 self._path = path
1269 self._path = path
1292 self._data = data
1270 self._data = data
1293 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1271 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1294 self._copied = None
1272 self._copied = None
1295 if copied:
1273 if copied:
1296 self._copied = (copied, nullid)
1274 self._copied = (copied, nullid)
1297
1275
1298 def __nonzero__(self):
1276 def __nonzero__(self):
1299 return True
1277 return True
1300 def __str__(self):
1278 def __str__(self):
1301 return "%s@%s" % (self.path(), self._changectx)
1279 return "%s@%s" % (self.path(), self._changectx)
1302 def path(self):
1280 def path(self):
1303 return self._path
1281 return self._path
1304 def data(self):
1282 def data(self):
1305 return self._data
1283 return self._data
1306 def flags(self):
1284 def flags(self):
1307 return self._flags
1285 return self._flags
1308 def isexec(self):
1286 def isexec(self):
1309 return 'x' in self._flags
1287 return 'x' in self._flags
1310 def islink(self):
1288 def islink(self):
1311 return 'l' in self._flags
1289 return 'l' in self._flags
1312 def renamed(self):
1290 def renamed(self):
1313 return self._copied
1291 return self._copied
@@ -1,2608 +1,2610 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 bin, hex, nullid, nullrev, short
7 from node import bin, hex, nullid, nullrev, short
8 from i18n import _
8 from i18n import _
9 import peer, changegroup, subrepo, discovery, pushkey, obsolete
9 import peer, changegroup, subrepo, discovery, pushkey, obsolete
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, base85
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 propertycache = util.propertycache
18 propertycache = util.propertycache
19 filecache = scmutil.filecache
19 filecache = scmutil.filecache
20
20
21 class storecache(filecache):
21 class storecache(filecache):
22 """filecache for files in the store"""
22 """filecache for files in the store"""
23 def join(self, obj, fname):
23 def join(self, obj, fname):
24 return obj.sjoin(fname)
24 return obj.sjoin(fname)
25
25
26 MODERNCAPS = set(('lookup', 'branchmap', 'pushkey', 'known', 'getbundle'))
26 MODERNCAPS = set(('lookup', 'branchmap', 'pushkey', 'known', 'getbundle'))
27 LEGACYCAPS = MODERNCAPS.union(set(['changegroupsubset']))
27 LEGACYCAPS = MODERNCAPS.union(set(['changegroupsubset']))
28
28
29 class localpeer(peer.peerrepository):
29 class localpeer(peer.peerrepository):
30 '''peer for a local repo; reflects only the most recent API'''
30 '''peer for a local repo; reflects only the most recent API'''
31
31
32 def __init__(self, repo, caps=MODERNCAPS):
32 def __init__(self, repo, caps=MODERNCAPS):
33 peer.peerrepository.__init__(self)
33 peer.peerrepository.__init__(self)
34 self._repo = repo
34 self._repo = repo
35 self.ui = repo.ui
35 self.ui = repo.ui
36 self._caps = repo._restrictcapabilities(caps)
36 self._caps = repo._restrictcapabilities(caps)
37 self.requirements = repo.requirements
37 self.requirements = repo.requirements
38 self.supportedformats = repo.supportedformats
38 self.supportedformats = repo.supportedformats
39
39
40 def close(self):
40 def close(self):
41 self._repo.close()
41 self._repo.close()
42
42
43 def _capabilities(self):
43 def _capabilities(self):
44 return self._caps
44 return self._caps
45
45
46 def local(self):
46 def local(self):
47 return self._repo
47 return self._repo
48
48
49 def canpush(self):
49 def canpush(self):
50 return True
50 return True
51
51
52 def url(self):
52 def url(self):
53 return self._repo.url()
53 return self._repo.url()
54
54
55 def lookup(self, key):
55 def lookup(self, key):
56 return self._repo.lookup(key)
56 return self._repo.lookup(key)
57
57
58 def branchmap(self):
58 def branchmap(self):
59 return discovery.visiblebranchmap(self._repo)
59 return discovery.visiblebranchmap(self._repo)
60
60
61 def heads(self):
61 def heads(self):
62 return discovery.visibleheads(self._repo)
62 return discovery.visibleheads(self._repo)
63
63
64 def known(self, nodes):
64 def known(self, nodes):
65 return self._repo.known(nodes)
65 return self._repo.known(nodes)
66
66
67 def getbundle(self, source, heads=None, common=None):
67 def getbundle(self, source, heads=None, common=None):
68 return self._repo.getbundle(source, heads=heads, common=common)
68 return self._repo.getbundle(source, heads=heads, common=common)
69
69
70 # TODO We might want to move the next two calls into legacypeer and add
70 # TODO We might want to move the next two calls into legacypeer and add
71 # unbundle instead.
71 # unbundle instead.
72
72
73 def lock(self):
73 def lock(self):
74 return self._repo.lock()
74 return self._repo.lock()
75
75
76 def addchangegroup(self, cg, source, url):
76 def addchangegroup(self, cg, source, url):
77 return self._repo.addchangegroup(cg, source, url)
77 return self._repo.addchangegroup(cg, source, url)
78
78
79 def pushkey(self, namespace, key, old, new):
79 def pushkey(self, namespace, key, old, new):
80 return self._repo.pushkey(namespace, key, old, new)
80 return self._repo.pushkey(namespace, key, old, new)
81
81
82 def listkeys(self, namespace):
82 def listkeys(self, namespace):
83 return self._repo.listkeys(namespace)
83 return self._repo.listkeys(namespace)
84
84
85 def debugwireargs(self, one, two, three=None, four=None, five=None):
85 def debugwireargs(self, one, two, three=None, four=None, five=None):
86 '''used to test argument passing over the wire'''
86 '''used to test argument passing over the wire'''
87 return "%s %s %s %s %s" % (one, two, three, four, five)
87 return "%s %s %s %s %s" % (one, two, three, four, five)
88
88
89 class locallegacypeer(localpeer):
89 class locallegacypeer(localpeer):
90 '''peer extension which implements legacy methods too; used for tests with
90 '''peer extension which implements legacy methods too; used for tests with
91 restricted capabilities'''
91 restricted capabilities'''
92
92
93 def __init__(self, repo):
93 def __init__(self, repo):
94 localpeer.__init__(self, repo, caps=LEGACYCAPS)
94 localpeer.__init__(self, repo, caps=LEGACYCAPS)
95
95
96 def branches(self, nodes):
96 def branches(self, nodes):
97 return self._repo.branches(nodes)
97 return self._repo.branches(nodes)
98
98
99 def between(self, pairs):
99 def between(self, pairs):
100 return self._repo.between(pairs)
100 return self._repo.between(pairs)
101
101
102 def changegroup(self, basenodes, source):
102 def changegroup(self, basenodes, source):
103 return self._repo.changegroup(basenodes, source)
103 return self._repo.changegroup(basenodes, source)
104
104
105 def changegroupsubset(self, bases, heads, source):
105 def changegroupsubset(self, bases, heads, source):
106 return self._repo.changegroupsubset(bases, heads, source)
106 return self._repo.changegroupsubset(bases, heads, source)
107
107
108 class localrepository(object):
108 class localrepository(object):
109
109
110 supportedformats = set(('revlogv1', 'generaldelta'))
110 supportedformats = set(('revlogv1', 'generaldelta'))
111 supported = supportedformats | set(('store', 'fncache', 'shared',
111 supported = supportedformats | set(('store', 'fncache', 'shared',
112 'dotencode'))
112 'dotencode'))
113 openerreqs = set(('revlogv1', 'generaldelta'))
113 openerreqs = set(('revlogv1', 'generaldelta'))
114 requirements = ['revlogv1']
114 requirements = ['revlogv1']
115
115
116 def _baserequirements(self, create):
116 def _baserequirements(self, create):
117 return self.requirements[:]
117 return self.requirements[:]
118
118
119 def __init__(self, baseui, path=None, create=False):
119 def __init__(self, baseui, path=None, create=False):
120 self.wopener = scmutil.opener(path, expand=True)
120 self.wopener = scmutil.opener(path, expand=True)
121 self.wvfs = self.wopener
121 self.wvfs = self.wopener
122 self.root = self.wvfs.base
122 self.root = self.wvfs.base
123 self.path = self.wvfs.join(".hg")
123 self.path = self.wvfs.join(".hg")
124 self.origroot = path
124 self.origroot = path
125 self.auditor = scmutil.pathauditor(self.root, self._checknested)
125 self.auditor = scmutil.pathauditor(self.root, self._checknested)
126 self.opener = scmutil.opener(self.path)
126 self.opener = scmutil.opener(self.path)
127 self.vfs = self.opener
127 self.vfs = self.opener
128 self.baseui = baseui
128 self.baseui = baseui
129 self.ui = baseui.copy()
129 self.ui = baseui.copy()
130 # A list of callback to shape the phase if no data were found.
130 # A list of callback to shape the phase if no data were found.
131 # Callback are in the form: func(repo, roots) --> processed root.
131 # Callback are in the form: func(repo, roots) --> processed root.
132 # This list it to be filled by extension during repo setup
132 # This list it to be filled by extension during repo setup
133 self._phasedefaults = []
133 self._phasedefaults = []
134 try:
134 try:
135 self.ui.readconfig(self.join("hgrc"), self.root)
135 self.ui.readconfig(self.join("hgrc"), self.root)
136 extensions.loadall(self.ui)
136 extensions.loadall(self.ui)
137 except IOError:
137 except IOError:
138 pass
138 pass
139
139
140 if not self.vfs.isdir():
140 if not self.vfs.isdir():
141 if create:
141 if create:
142 if not self.wvfs.exists():
142 if not self.wvfs.exists():
143 self.wvfs.makedirs()
143 self.wvfs.makedirs()
144 self.vfs.makedir(notindexed=True)
144 self.vfs.makedir(notindexed=True)
145 requirements = self._baserequirements(create)
145 requirements = self._baserequirements(create)
146 if self.ui.configbool('format', 'usestore', True):
146 if self.ui.configbool('format', 'usestore', True):
147 self.vfs.mkdir("store")
147 self.vfs.mkdir("store")
148 requirements.append("store")
148 requirements.append("store")
149 if self.ui.configbool('format', 'usefncache', True):
149 if self.ui.configbool('format', 'usefncache', True):
150 requirements.append("fncache")
150 requirements.append("fncache")
151 if self.ui.configbool('format', 'dotencode', True):
151 if self.ui.configbool('format', 'dotencode', True):
152 requirements.append('dotencode')
152 requirements.append('dotencode')
153 # create an invalid changelog
153 # create an invalid changelog
154 self.vfs.append(
154 self.vfs.append(
155 "00changelog.i",
155 "00changelog.i",
156 '\0\0\0\2' # represents revlogv2
156 '\0\0\0\2' # represents revlogv2
157 ' dummy changelog to prevent using the old repo layout'
157 ' dummy changelog to prevent using the old repo layout'
158 )
158 )
159 if self.ui.configbool('format', 'generaldelta', False):
159 if self.ui.configbool('format', 'generaldelta', False):
160 requirements.append("generaldelta")
160 requirements.append("generaldelta")
161 requirements = set(requirements)
161 requirements = set(requirements)
162 else:
162 else:
163 raise error.RepoError(_("repository %s not found") % path)
163 raise error.RepoError(_("repository %s not found") % path)
164 elif create:
164 elif create:
165 raise error.RepoError(_("repository %s already exists") % path)
165 raise error.RepoError(_("repository %s already exists") % path)
166 else:
166 else:
167 try:
167 try:
168 requirements = scmutil.readrequires(self.vfs, self.supported)
168 requirements = scmutil.readrequires(self.vfs, self.supported)
169 except IOError, inst:
169 except IOError, inst:
170 if inst.errno != errno.ENOENT:
170 if inst.errno != errno.ENOENT:
171 raise
171 raise
172 requirements = set()
172 requirements = set()
173
173
174 self.sharedpath = self.path
174 self.sharedpath = self.path
175 try:
175 try:
176 s = os.path.realpath(self.opener.read("sharedpath").rstrip('\n'))
176 s = os.path.realpath(self.opener.read("sharedpath").rstrip('\n'))
177 if not os.path.exists(s):
177 if not os.path.exists(s):
178 raise error.RepoError(
178 raise error.RepoError(
179 _('.hg/sharedpath points to nonexistent directory %s') % s)
179 _('.hg/sharedpath points to nonexistent directory %s') % s)
180 self.sharedpath = s
180 self.sharedpath = s
181 except IOError, inst:
181 except IOError, inst:
182 if inst.errno != errno.ENOENT:
182 if inst.errno != errno.ENOENT:
183 raise
183 raise
184
184
185 self.store = store.store(requirements, self.sharedpath, scmutil.opener)
185 self.store = store.store(requirements, self.sharedpath, scmutil.opener)
186 self.spath = self.store.path
186 self.spath = self.store.path
187 self.sopener = self.store.opener
187 self.sopener = self.store.opener
188 self.svfs = self.sopener
188 self.svfs = self.sopener
189 self.sjoin = self.store.join
189 self.sjoin = self.store.join
190 self.opener.createmode = self.store.createmode
190 self.opener.createmode = self.store.createmode
191 self._applyrequirements(requirements)
191 self._applyrequirements(requirements)
192 if create:
192 if create:
193 self._writerequirements()
193 self._writerequirements()
194
194
195
195
196 self._branchcache = None
196 self._branchcache = None
197 self._branchcachetip = None
197 self._branchcachetip = None
198 self.filterpats = {}
198 self.filterpats = {}
199 self._datafilters = {}
199 self._datafilters = {}
200 self._transref = self._lockref = self._wlockref = None
200 self._transref = self._lockref = self._wlockref = None
201
201
202 # A cache for various files under .hg/ that tracks file changes,
202 # A cache for various files under .hg/ that tracks file changes,
203 # (used by the filecache decorator)
203 # (used by the filecache decorator)
204 #
204 #
205 # Maps a property name to its util.filecacheentry
205 # Maps a property name to its util.filecacheentry
206 self._filecache = {}
206 self._filecache = {}
207
207
208 def close(self):
208 def close(self):
209 pass
209 pass
210
210
211 def _restrictcapabilities(self, caps):
211 def _restrictcapabilities(self, caps):
212 return caps
212 return caps
213
213
214 def _applyrequirements(self, requirements):
214 def _applyrequirements(self, requirements):
215 self.requirements = requirements
215 self.requirements = requirements
216 self.sopener.options = dict((r, 1) for r in requirements
216 self.sopener.options = dict((r, 1) for r in requirements
217 if r in self.openerreqs)
217 if r in self.openerreqs)
218
218
219 def _writerequirements(self):
219 def _writerequirements(self):
220 reqfile = self.opener("requires", "w")
220 reqfile = self.opener("requires", "w")
221 for r in self.requirements:
221 for r in self.requirements:
222 reqfile.write("%s\n" % r)
222 reqfile.write("%s\n" % r)
223 reqfile.close()
223 reqfile.close()
224
224
225 def _checknested(self, path):
225 def _checknested(self, path):
226 """Determine if path is a legal nested repository."""
226 """Determine if path is a legal nested repository."""
227 if not path.startswith(self.root):
227 if not path.startswith(self.root):
228 return False
228 return False
229 subpath = path[len(self.root) + 1:]
229 subpath = path[len(self.root) + 1:]
230 normsubpath = util.pconvert(subpath)
230 normsubpath = util.pconvert(subpath)
231
231
232 # XXX: Checking against the current working copy is wrong in
232 # XXX: Checking against the current working copy is wrong in
233 # the sense that it can reject things like
233 # the sense that it can reject things like
234 #
234 #
235 # $ hg cat -r 10 sub/x.txt
235 # $ hg cat -r 10 sub/x.txt
236 #
236 #
237 # if sub/ is no longer a subrepository in the working copy
237 # if sub/ is no longer a subrepository in the working copy
238 # parent revision.
238 # parent revision.
239 #
239 #
240 # However, it can of course also allow things that would have
240 # However, it can of course also allow things that would have
241 # been rejected before, such as the above cat command if sub/
241 # been rejected before, such as the above cat command if sub/
242 # is a subrepository now, but was a normal directory before.
242 # is a subrepository now, but was a normal directory before.
243 # The old path auditor would have rejected by mistake since it
243 # The old path auditor would have rejected by mistake since it
244 # panics when it sees sub/.hg/.
244 # panics when it sees sub/.hg/.
245 #
245 #
246 # All in all, checking against the working copy seems sensible
246 # All in all, checking against the working copy seems sensible
247 # since we want to prevent access to nested repositories on
247 # since we want to prevent access to nested repositories on
248 # the filesystem *now*.
248 # the filesystem *now*.
249 ctx = self[None]
249 ctx = self[None]
250 parts = util.splitpath(subpath)
250 parts = util.splitpath(subpath)
251 while parts:
251 while parts:
252 prefix = '/'.join(parts)
252 prefix = '/'.join(parts)
253 if prefix in ctx.substate:
253 if prefix in ctx.substate:
254 if prefix == normsubpath:
254 if prefix == normsubpath:
255 return True
255 return True
256 else:
256 else:
257 sub = ctx.sub(prefix)
257 sub = ctx.sub(prefix)
258 return sub.checknested(subpath[len(prefix) + 1:])
258 return sub.checknested(subpath[len(prefix) + 1:])
259 else:
259 else:
260 parts.pop()
260 parts.pop()
261 return False
261 return False
262
262
263 def peer(self):
263 def peer(self):
264 return localpeer(self) # not cached to avoid reference cycle
264 return localpeer(self) # not cached to avoid reference cycle
265
265
266 @filecache('bookmarks')
266 @filecache('bookmarks')
267 def _bookmarks(self):
267 def _bookmarks(self):
268 return bookmarks.read(self)
268 return bookmarks.read(self)
269
269
270 @filecache('bookmarks.current')
270 @filecache('bookmarks.current')
271 def _bookmarkcurrent(self):
271 def _bookmarkcurrent(self):
272 return bookmarks.readcurrent(self)
272 return bookmarks.readcurrent(self)
273
273
274 def _writebookmarks(self, marks):
274 def _writebookmarks(self, marks):
275 bookmarks.write(self)
275 bookmarks.write(self)
276
276
277 def bookmarkheads(self, bookmark):
277 def bookmarkheads(self, bookmark):
278 name = bookmark.split('@', 1)[0]
278 name = bookmark.split('@', 1)[0]
279 heads = []
279 heads = []
280 for mark, n in self._bookmarks.iteritems():
280 for mark, n in self._bookmarks.iteritems():
281 if mark.split('@', 1)[0] == name:
281 if mark.split('@', 1)[0] == name:
282 heads.append(n)
282 heads.append(n)
283 return heads
283 return heads
284
284
285 @storecache('phaseroots')
285 @storecache('phaseroots')
286 def _phasecache(self):
286 def _phasecache(self):
287 return phases.phasecache(self, self._phasedefaults)
287 return phases.phasecache(self, self._phasedefaults)
288
288
289 @storecache('obsstore')
289 @storecache('obsstore')
290 def obsstore(self):
290 def obsstore(self):
291 store = obsolete.obsstore(self.sopener)
291 store = obsolete.obsstore(self.sopener)
292 if store and not obsolete._enabled:
292 if store and not obsolete._enabled:
293 # message is rare enough to not be translated
293 # message is rare enough to not be translated
294 msg = 'obsolete feature not enabled but %i markers found!\n'
294 msg = 'obsolete feature not enabled but %i markers found!\n'
295 self.ui.warn(msg % len(list(store)))
295 self.ui.warn(msg % len(list(store)))
296 return store
296 return store
297
297
298 @propertycache
298 @propertycache
299 def hiddenrevs(self):
299 def hiddenrevs(self):
300 """hiddenrevs: revs that should be hidden by command and tools
300 """hiddenrevs: revs that should be hidden by command and tools
301
301
302 This set is carried on the repo to ease initialisation and lazy
302 This set is carried on the repo to ease initialisation and lazy
303 loading; it'll probably move back to changelog for efficiency and
303 loading; it'll probably move back to changelog for efficiency and
304 consistency reason
304 consistency reason
305
305
306 Note that the hiddenrevs will needs invalidations when
306 Note that the hiddenrevs will needs invalidations when
307 - a new changesets is added (possible unstable above extinct)
307 - a new changesets is added (possible unstable above extinct)
308 - a new obsolete marker is added (possible new extinct changeset)
308 - a new obsolete marker is added (possible new extinct changeset)
309
309
310 hidden changesets cannot have non-hidden descendants
310 hidden changesets cannot have non-hidden descendants
311 """
311 """
312 hidden = set()
312 hidden = set()
313 if self.obsstore:
313 if self.obsstore:
314 ### hide extinct changeset that are not accessible by any mean
314 ### hide extinct changeset that are not accessible by any mean
315 hiddenquery = 'extinct() - ::(. + bookmark() + tagged())'
315 hiddenquery = 'extinct() - ::(. + bookmark() + tagged())'
316 hidden.update(self.revs(hiddenquery))
316 hidden.update(self.revs(hiddenquery))
317 return hidden
317 return hidden
318
318
319 @storecache('00changelog.i')
319 @storecache('00changelog.i')
320 def changelog(self):
320 def changelog(self):
321 c = changelog.changelog(self.sopener)
321 c = changelog.changelog(self.sopener)
322 if 'HG_PENDING' in os.environ:
322 if 'HG_PENDING' in os.environ:
323 p = os.environ['HG_PENDING']
323 p = os.environ['HG_PENDING']
324 if p.startswith(self.root):
324 if p.startswith(self.root):
325 c.readpending('00changelog.i.a')
325 c.readpending('00changelog.i.a')
326 return c
326 return c
327
327
328 @storecache('00manifest.i')
328 @storecache('00manifest.i')
329 def manifest(self):
329 def manifest(self):
330 return manifest.manifest(self.sopener)
330 return manifest.manifest(self.sopener)
331
331
332 @filecache('dirstate')
332 @filecache('dirstate')
333 def dirstate(self):
333 def dirstate(self):
334 warned = [0]
334 warned = [0]
335 def validate(node):
335 def validate(node):
336 try:
336 try:
337 self.changelog.rev(node)
337 self.changelog.rev(node)
338 return node
338 return node
339 except error.LookupError:
339 except error.LookupError:
340 if not warned[0]:
340 if not warned[0]:
341 warned[0] = True
341 warned[0] = True
342 self.ui.warn(_("warning: ignoring unknown"
342 self.ui.warn(_("warning: ignoring unknown"
343 " working parent %s!\n") % short(node))
343 " working parent %s!\n") % short(node))
344 return nullid
344 return nullid
345
345
346 return dirstate.dirstate(self.opener, self.ui, self.root, validate)
346 return dirstate.dirstate(self.opener, self.ui, self.root, validate)
347
347
348 def __getitem__(self, changeid):
348 def __getitem__(self, changeid):
349 if changeid is None:
349 if changeid is None:
350 return context.workingctx(self)
350 return context.workingctx(self)
351 return context.changectx(self, changeid)
351 return context.changectx(self, changeid)
352
352
353 def __contains__(self, changeid):
353 def __contains__(self, changeid):
354 try:
354 try:
355 return bool(self.lookup(changeid))
355 return bool(self.lookup(changeid))
356 except error.RepoLookupError:
356 except error.RepoLookupError:
357 return False
357 return False
358
358
359 def __nonzero__(self):
359 def __nonzero__(self):
360 return True
360 return True
361
361
362 def __len__(self):
362 def __len__(self):
363 return len(self.changelog)
363 return len(self.changelog)
364
364
365 def __iter__(self):
365 def __iter__(self):
366 for i in xrange(len(self)):
366 for i in xrange(len(self)):
367 yield i
367 yield i
368
368
369 def revs(self, expr, *args):
369 def revs(self, expr, *args):
370 '''Return a list of revisions matching the given revset'''
370 '''Return a list of revisions matching the given revset'''
371 expr = revset.formatspec(expr, *args)
371 expr = revset.formatspec(expr, *args)
372 m = revset.match(None, expr)
372 m = revset.match(None, expr)
373 return [r for r in m(self, range(len(self)))]
373 return [r for r in m(self, range(len(self)))]
374
374
375 def set(self, expr, *args):
375 def set(self, expr, *args):
376 '''
376 '''
377 Yield a context for each matching revision, after doing arg
377 Yield a context for each matching revision, after doing arg
378 replacement via revset.formatspec
378 replacement via revset.formatspec
379 '''
379 '''
380 for r in self.revs(expr, *args):
380 for r in self.revs(expr, *args):
381 yield self[r]
381 yield self[r]
382
382
383 def url(self):
383 def url(self):
384 return 'file:' + self.root
384 return 'file:' + self.root
385
385
386 def hook(self, name, throw=False, **args):
386 def hook(self, name, throw=False, **args):
387 return hook.hook(self.ui, self, name, throw, **args)
387 return hook.hook(self.ui, self, name, throw, **args)
388
388
389 tag_disallowed = ':\r\n'
389 tag_disallowed = ':\r\n'
390
390
391 def _tag(self, names, node, message, local, user, date, extra={}):
391 def _tag(self, names, node, message, local, user, date, extra={}):
392 if isinstance(names, str):
392 if isinstance(names, str):
393 allchars = names
393 allchars = names
394 names = (names,)
394 names = (names,)
395 else:
395 else:
396 allchars = ''.join(names)
396 allchars = ''.join(names)
397 for c in self.tag_disallowed:
397 for c in self.tag_disallowed:
398 if c in allchars:
398 if c in allchars:
399 raise util.Abort(_('%r cannot be used in a tag name') % c)
399 raise util.Abort(_('%r cannot be used in a tag name') % c)
400
400
401 branches = self.branchmap()
401 branches = self.branchmap()
402 for name in names:
402 for name in names:
403 self.hook('pretag', throw=True, node=hex(node), tag=name,
403 self.hook('pretag', throw=True, node=hex(node), tag=name,
404 local=local)
404 local=local)
405 if name in branches:
405 if name in branches:
406 self.ui.warn(_("warning: tag %s conflicts with existing"
406 self.ui.warn(_("warning: tag %s conflicts with existing"
407 " branch name\n") % name)
407 " branch name\n") % name)
408
408
409 def writetags(fp, names, munge, prevtags):
409 def writetags(fp, names, munge, prevtags):
410 fp.seek(0, 2)
410 fp.seek(0, 2)
411 if prevtags and prevtags[-1] != '\n':
411 if prevtags and prevtags[-1] != '\n':
412 fp.write('\n')
412 fp.write('\n')
413 for name in names:
413 for name in names:
414 m = munge and munge(name) or name
414 m = munge and munge(name) or name
415 if (self._tagscache.tagtypes and
415 if (self._tagscache.tagtypes and
416 name in self._tagscache.tagtypes):
416 name in self._tagscache.tagtypes):
417 old = self.tags().get(name, nullid)
417 old = self.tags().get(name, nullid)
418 fp.write('%s %s\n' % (hex(old), m))
418 fp.write('%s %s\n' % (hex(old), m))
419 fp.write('%s %s\n' % (hex(node), m))
419 fp.write('%s %s\n' % (hex(node), m))
420 fp.close()
420 fp.close()
421
421
422 prevtags = ''
422 prevtags = ''
423 if local:
423 if local:
424 try:
424 try:
425 fp = self.opener('localtags', 'r+')
425 fp = self.opener('localtags', 'r+')
426 except IOError:
426 except IOError:
427 fp = self.opener('localtags', 'a')
427 fp = self.opener('localtags', 'a')
428 else:
428 else:
429 prevtags = fp.read()
429 prevtags = fp.read()
430
430
431 # local tags are stored in the current charset
431 # local tags are stored in the current charset
432 writetags(fp, names, None, prevtags)
432 writetags(fp, names, None, prevtags)
433 for name in names:
433 for name in names:
434 self.hook('tag', node=hex(node), tag=name, local=local)
434 self.hook('tag', node=hex(node), tag=name, local=local)
435 return
435 return
436
436
437 try:
437 try:
438 fp = self.wfile('.hgtags', 'rb+')
438 fp = self.wfile('.hgtags', 'rb+')
439 except IOError, e:
439 except IOError, e:
440 if e.errno != errno.ENOENT:
440 if e.errno != errno.ENOENT:
441 raise
441 raise
442 fp = self.wfile('.hgtags', 'ab')
442 fp = self.wfile('.hgtags', 'ab')
443 else:
443 else:
444 prevtags = fp.read()
444 prevtags = fp.read()
445
445
446 # committed tags are stored in UTF-8
446 # committed tags are stored in UTF-8
447 writetags(fp, names, encoding.fromlocal, prevtags)
447 writetags(fp, names, encoding.fromlocal, prevtags)
448
448
449 fp.close()
449 fp.close()
450
450
451 self.invalidatecaches()
451 self.invalidatecaches()
452
452
453 if '.hgtags' not in self.dirstate:
453 if '.hgtags' not in self.dirstate:
454 self[None].add(['.hgtags'])
454 self[None].add(['.hgtags'])
455
455
456 m = matchmod.exact(self.root, '', ['.hgtags'])
456 m = matchmod.exact(self.root, '', ['.hgtags'])
457 tagnode = self.commit(message, user, date, extra=extra, match=m)
457 tagnode = self.commit(message, user, date, extra=extra, match=m)
458
458
459 for name in names:
459 for name in names:
460 self.hook('tag', node=hex(node), tag=name, local=local)
460 self.hook('tag', node=hex(node), tag=name, local=local)
461
461
462 return tagnode
462 return tagnode
463
463
464 def tag(self, names, node, message, local, user, date):
464 def tag(self, names, node, message, local, user, date):
465 '''tag a revision with one or more symbolic names.
465 '''tag a revision with one or more symbolic names.
466
466
467 names is a list of strings or, when adding a single tag, names may be a
467 names is a list of strings or, when adding a single tag, names may be a
468 string.
468 string.
469
469
470 if local is True, the tags are stored in a per-repository file.
470 if local is True, the tags are stored in a per-repository file.
471 otherwise, they are stored in the .hgtags file, and a new
471 otherwise, they are stored in the .hgtags file, and a new
472 changeset is committed with the change.
472 changeset is committed with the change.
473
473
474 keyword arguments:
474 keyword arguments:
475
475
476 local: whether to store tags in non-version-controlled file
476 local: whether to store tags in non-version-controlled file
477 (default False)
477 (default False)
478
478
479 message: commit message to use if committing
479 message: commit message to use if committing
480
480
481 user: name of user to use if committing
481 user: name of user to use if committing
482
482
483 date: date tuple to use if committing'''
483 date: date tuple to use if committing'''
484
484
485 if not local:
485 if not local:
486 for x in self.status()[:5]:
486 for x in self.status()[:5]:
487 if '.hgtags' in x:
487 if '.hgtags' in x:
488 raise util.Abort(_('working copy of .hgtags is changed '
488 raise util.Abort(_('working copy of .hgtags is changed '
489 '(please commit .hgtags manually)'))
489 '(please commit .hgtags manually)'))
490
490
491 self.tags() # instantiate the cache
491 self.tags() # instantiate the cache
492 self._tag(names, node, message, local, user, date)
492 self._tag(names, node, message, local, user, date)
493
493
494 @propertycache
494 @propertycache
495 def _tagscache(self):
495 def _tagscache(self):
496 '''Returns a tagscache object that contains various tags related
496 '''Returns a tagscache object that contains various tags related
497 caches.'''
497 caches.'''
498
498
499 # This simplifies its cache management by having one decorated
499 # This simplifies its cache management by having one decorated
500 # function (this one) and the rest simply fetch things from it.
500 # function (this one) and the rest simply fetch things from it.
501 class tagscache(object):
501 class tagscache(object):
502 def __init__(self):
502 def __init__(self):
503 # These two define the set of tags for this repository. tags
503 # These two define the set of tags for this repository. tags
504 # maps tag name to node; tagtypes maps tag name to 'global' or
504 # maps tag name to node; tagtypes maps tag name to 'global' or
505 # 'local'. (Global tags are defined by .hgtags across all
505 # 'local'. (Global tags are defined by .hgtags across all
506 # heads, and local tags are defined in .hg/localtags.)
506 # heads, and local tags are defined in .hg/localtags.)
507 # They constitute the in-memory cache of tags.
507 # They constitute the in-memory cache of tags.
508 self.tags = self.tagtypes = None
508 self.tags = self.tagtypes = None
509
509
510 self.nodetagscache = self.tagslist = None
510 self.nodetagscache = self.tagslist = None
511
511
512 cache = tagscache()
512 cache = tagscache()
513 cache.tags, cache.tagtypes = self._findtags()
513 cache.tags, cache.tagtypes = self._findtags()
514
514
515 return cache
515 return cache
516
516
517 def tags(self):
517 def tags(self):
518 '''return a mapping of tag to node'''
518 '''return a mapping of tag to node'''
519 t = {}
519 t = {}
520 for k, v in self._tagscache.tags.iteritems():
520 for k, v in self._tagscache.tags.iteritems():
521 try:
521 try:
522 # ignore tags to unknown nodes
522 # ignore tags to unknown nodes
523 self.changelog.rev(v)
523 self.changelog.rev(v)
524 t[k] = v
524 t[k] = v
525 except (error.LookupError, ValueError):
525 except (error.LookupError, ValueError):
526 pass
526 pass
527 return t
527 return t
528
528
529 def _findtags(self):
529 def _findtags(self):
530 '''Do the hard work of finding tags. Return a pair of dicts
530 '''Do the hard work of finding tags. Return a pair of dicts
531 (tags, tagtypes) where tags maps tag name to node, and tagtypes
531 (tags, tagtypes) where tags maps tag name to node, and tagtypes
532 maps tag name to a string like \'global\' or \'local\'.
532 maps tag name to a string like \'global\' or \'local\'.
533 Subclasses or extensions are free to add their own tags, but
533 Subclasses or extensions are free to add their own tags, but
534 should be aware that the returned dicts will be retained for the
534 should be aware that the returned dicts will be retained for the
535 duration of the localrepo object.'''
535 duration of the localrepo object.'''
536
536
537 # XXX what tagtype should subclasses/extensions use? Currently
537 # XXX what tagtype should subclasses/extensions use? Currently
538 # mq and bookmarks add tags, but do not set the tagtype at all.
538 # mq and bookmarks add tags, but do not set the tagtype at all.
539 # Should each extension invent its own tag type? Should there
539 # Should each extension invent its own tag type? Should there
540 # be one tagtype for all such "virtual" tags? Or is the status
540 # be one tagtype for all such "virtual" tags? Or is the status
541 # quo fine?
541 # quo fine?
542
542
543 alltags = {} # map tag name to (node, hist)
543 alltags = {} # map tag name to (node, hist)
544 tagtypes = {}
544 tagtypes = {}
545
545
546 tagsmod.findglobaltags(self.ui, self, alltags, tagtypes)
546 tagsmod.findglobaltags(self.ui, self, alltags, tagtypes)
547 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
547 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
548
548
549 # Build the return dicts. Have to re-encode tag names because
549 # Build the return dicts. Have to re-encode tag names because
550 # the tags module always uses UTF-8 (in order not to lose info
550 # the tags module always uses UTF-8 (in order not to lose info
551 # writing to the cache), but the rest of Mercurial wants them in
551 # writing to the cache), but the rest of Mercurial wants them in
552 # local encoding.
552 # local encoding.
553 tags = {}
553 tags = {}
554 for (name, (node, hist)) in alltags.iteritems():
554 for (name, (node, hist)) in alltags.iteritems():
555 if node != nullid:
555 if node != nullid:
556 tags[encoding.tolocal(name)] = node
556 tags[encoding.tolocal(name)] = node
557 tags['tip'] = self.changelog.tip()
557 tags['tip'] = self.changelog.tip()
558 tagtypes = dict([(encoding.tolocal(name), value)
558 tagtypes = dict([(encoding.tolocal(name), value)
559 for (name, value) in tagtypes.iteritems()])
559 for (name, value) in tagtypes.iteritems()])
560 return (tags, tagtypes)
560 return (tags, tagtypes)
561
561
562 def tagtype(self, tagname):
562 def tagtype(self, tagname):
563 '''
563 '''
564 return the type of the given tag. result can be:
564 return the type of the given tag. result can be:
565
565
566 'local' : a local tag
566 'local' : a local tag
567 'global' : a global tag
567 'global' : a global tag
568 None : tag does not exist
568 None : tag does not exist
569 '''
569 '''
570
570
571 return self._tagscache.tagtypes.get(tagname)
571 return self._tagscache.tagtypes.get(tagname)
572
572
573 def tagslist(self):
573 def tagslist(self):
574 '''return a list of tags ordered by revision'''
574 '''return a list of tags ordered by revision'''
575 if not self._tagscache.tagslist:
575 if not self._tagscache.tagslist:
576 l = []
576 l = []
577 for t, n in self.tags().iteritems():
577 for t, n in self.tags().iteritems():
578 r = self.changelog.rev(n)
578 r = self.changelog.rev(n)
579 l.append((r, t, n))
579 l.append((r, t, n))
580 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
580 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
581
581
582 return self._tagscache.tagslist
582 return self._tagscache.tagslist
583
583
584 def nodetags(self, node):
584 def nodetags(self, node):
585 '''return the tags associated with a node'''
585 '''return the tags associated with a node'''
586 if not self._tagscache.nodetagscache:
586 if not self._tagscache.nodetagscache:
587 nodetagscache = {}
587 nodetagscache = {}
588 for t, n in self._tagscache.tags.iteritems():
588 for t, n in self._tagscache.tags.iteritems():
589 nodetagscache.setdefault(n, []).append(t)
589 nodetagscache.setdefault(n, []).append(t)
590 for tags in nodetagscache.itervalues():
590 for tags in nodetagscache.itervalues():
591 tags.sort()
591 tags.sort()
592 self._tagscache.nodetagscache = nodetagscache
592 self._tagscache.nodetagscache = nodetagscache
593 return self._tagscache.nodetagscache.get(node, [])
593 return self._tagscache.nodetagscache.get(node, [])
594
594
595 def nodebookmarks(self, node):
595 def nodebookmarks(self, node):
596 marks = []
596 marks = []
597 for bookmark, n in self._bookmarks.iteritems():
597 for bookmark, n in self._bookmarks.iteritems():
598 if n == node:
598 if n == node:
599 marks.append(bookmark)
599 marks.append(bookmark)
600 return sorted(marks)
600 return sorted(marks)
601
601
602 def _branchtags(self, partial, lrev):
602 def _branchtags(self, partial, lrev):
603 # TODO: rename this function?
603 # TODO: rename this function?
604 tiprev = len(self) - 1
604 tiprev = len(self) - 1
605 if lrev != tiprev:
605 if lrev != tiprev:
606 ctxgen = (self[r] for r in xrange(lrev + 1, tiprev + 1))
606 ctxgen = (self[r] for r in xrange(lrev + 1, tiprev + 1))
607 self._updatebranchcache(partial, ctxgen)
607 self._updatebranchcache(partial, ctxgen)
608 self._writebranchcache(partial, self.changelog.tip(), tiprev)
608 self._writebranchcache(partial, self.changelog.tip(), tiprev)
609
609
610 return partial
610 return partial
611
611
612 def updatebranchcache(self):
612 def updatebranchcache(self):
613 tip = self.changelog.tip()
613 tip = self.changelog.tip()
614 if self._branchcache is not None and self._branchcachetip == tip:
614 if self._branchcache is not None and self._branchcachetip == tip:
615 return
615 return
616
616
617 oldtip = self._branchcachetip
617 oldtip = self._branchcachetip
618 self._branchcachetip = tip
618 self._branchcachetip = tip
619 if oldtip is None or oldtip not in self.changelog.nodemap:
619 if oldtip is None or oldtip not in self.changelog.nodemap:
620 partial, last, lrev = self._readbranchcache()
620 partial, last, lrev = self._readbranchcache()
621 else:
621 else:
622 lrev = self.changelog.rev(oldtip)
622 lrev = self.changelog.rev(oldtip)
623 partial = self._branchcache
623 partial = self._branchcache
624
624
625 self._branchtags(partial, lrev)
625 self._branchtags(partial, lrev)
626 # this private cache holds all heads (not just the branch tips)
626 # this private cache holds all heads (not just the branch tips)
627 self._branchcache = partial
627 self._branchcache = partial
628
628
629 def branchmap(self):
629 def branchmap(self):
630 '''returns a dictionary {branch: [branchheads]}'''
630 '''returns a dictionary {branch: [branchheads]}'''
631 self.updatebranchcache()
631 self.updatebranchcache()
632 return self._branchcache
632 return self._branchcache
633
633
634 def _branchtip(self, heads):
634 def _branchtip(self, heads):
635 '''return the tipmost branch head in heads'''
635 '''return the tipmost branch head in heads'''
636 tip = heads[-1]
636 tip = heads[-1]
637 for h in reversed(heads):
637 for h in reversed(heads):
638 if not self[h].closesbranch():
638 if not self[h].closesbranch():
639 tip = h
639 tip = h
640 break
640 break
641 return tip
641 return tip
642
642
643 def branchtip(self, branch):
643 def branchtip(self, branch):
644 '''return the tip node for a given branch'''
644 '''return the tip node for a given branch'''
645 if branch not in self.branchmap():
645 if branch not in self.branchmap():
646 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
646 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
647 return self._branchtip(self.branchmap()[branch])
647 return self._branchtip(self.branchmap()[branch])
648
648
649 def branchtags(self):
649 def branchtags(self):
650 '''return a dict where branch names map to the tipmost head of
650 '''return a dict where branch names map to the tipmost head of
651 the branch, open heads come before closed'''
651 the branch, open heads come before closed'''
652 bt = {}
652 bt = {}
653 for bn, heads in self.branchmap().iteritems():
653 for bn, heads in self.branchmap().iteritems():
654 bt[bn] = self._branchtip(heads)
654 bt[bn] = self._branchtip(heads)
655 return bt
655 return bt
656
656
657 def _readbranchcache(self):
657 def _readbranchcache(self):
658 partial = {}
658 partial = {}
659 try:
659 try:
660 f = self.opener("cache/branchheads")
660 f = self.opener("cache/branchheads")
661 lines = f.read().split('\n')
661 lines = f.read().split('\n')
662 f.close()
662 f.close()
663 except (IOError, OSError):
663 except (IOError, OSError):
664 return {}, nullid, nullrev
664 return {}, nullid, nullrev
665
665
666 try:
666 try:
667 last, lrev = lines.pop(0).split(" ", 1)
667 last, lrev = lines.pop(0).split(" ", 1)
668 last, lrev = bin(last), int(lrev)
668 last, lrev = bin(last), int(lrev)
669 if lrev >= len(self) or self[lrev].node() != last:
669 if lrev >= len(self) or self[lrev].node() != last:
670 # invalidate the cache
670 # invalidate the cache
671 raise ValueError('invalidating branch cache (tip differs)')
671 raise ValueError('invalidating branch cache (tip differs)')
672 for l in lines:
672 for l in lines:
673 if not l:
673 if not l:
674 continue
674 continue
675 node, label = l.split(" ", 1)
675 node, label = l.split(" ", 1)
676 label = encoding.tolocal(label.strip())
676 label = encoding.tolocal(label.strip())
677 if not node in self:
677 if not node in self:
678 raise ValueError('invalidating branch cache because node '+
678 raise ValueError('invalidating branch cache because node '+
679 '%s does not exist' % node)
679 '%s does not exist' % node)
680 partial.setdefault(label, []).append(bin(node))
680 partial.setdefault(label, []).append(bin(node))
681 except KeyboardInterrupt:
681 except KeyboardInterrupt:
682 raise
682 raise
683 except Exception, inst:
683 except Exception, inst:
684 if self.ui.debugflag:
684 if self.ui.debugflag:
685 self.ui.warn(str(inst), '\n')
685 self.ui.warn(str(inst), '\n')
686 partial, last, lrev = {}, nullid, nullrev
686 partial, last, lrev = {}, nullid, nullrev
687 return partial, last, lrev
687 return partial, last, lrev
688
688
689 def _writebranchcache(self, branches, tip, tiprev):
689 def _writebranchcache(self, branches, tip, tiprev):
690 try:
690 try:
691 f = self.opener("cache/branchheads", "w", atomictemp=True)
691 f = self.opener("cache/branchheads", "w", atomictemp=True)
692 f.write("%s %s\n" % (hex(tip), tiprev))
692 f.write("%s %s\n" % (hex(tip), tiprev))
693 for label, nodes in branches.iteritems():
693 for label, nodes in branches.iteritems():
694 for node in nodes:
694 for node in nodes:
695 f.write("%s %s\n" % (hex(node), encoding.fromlocal(label)))
695 f.write("%s %s\n" % (hex(node), encoding.fromlocal(label)))
696 f.close()
696 f.close()
697 except (IOError, OSError):
697 except (IOError, OSError):
698 pass
698 pass
699
699
700 def _updatebranchcache(self, partial, ctxgen):
700 def _updatebranchcache(self, partial, ctxgen):
701 """Given a branchhead cache, partial, that may have extra nodes or be
701 """Given a branchhead cache, partial, that may have extra nodes or be
702 missing heads, and a generator of nodes that are at least a superset of
702 missing heads, and a generator of nodes that are at least a superset of
703 heads missing, this function updates partial to be correct.
703 heads missing, this function updates partial to be correct.
704 """
704 """
705 # collect new branch entries
705 # collect new branch entries
706 newbranches = {}
706 newbranches = {}
707 for c in ctxgen:
707 for c in ctxgen:
708 newbranches.setdefault(c.branch(), []).append(c.node())
708 newbranches.setdefault(c.branch(), []).append(c.node())
709 # if older branchheads are reachable from new ones, they aren't
709 # if older branchheads are reachable from new ones, they aren't
710 # really branchheads. Note checking parents is insufficient:
710 # really branchheads. Note checking parents is insufficient:
711 # 1 (branch a) -> 2 (branch b) -> 3 (branch a)
711 # 1 (branch a) -> 2 (branch b) -> 3 (branch a)
712 for branch, newnodes in newbranches.iteritems():
712 for branch, newnodes in newbranches.iteritems():
713 bheads = partial.setdefault(branch, [])
713 bheads = partial.setdefault(branch, [])
714 # Remove candidate heads that no longer are in the repo (e.g., as
714 # Remove candidate heads that no longer are in the repo (e.g., as
715 # the result of a strip that just happened). Avoid using 'node in
715 # the result of a strip that just happened). Avoid using 'node in
716 # self' here because that dives down into branchcache code somewhat
716 # self' here because that dives down into branchcache code somewhat
717 # recursively.
717 # recursively.
718 bheadrevs = [self.changelog.rev(node) for node in bheads
718 bheadrevs = [self.changelog.rev(node) for node in bheads
719 if self.changelog.hasnode(node)]
719 if self.changelog.hasnode(node)]
720 newheadrevs = [self.changelog.rev(node) for node in newnodes
720 newheadrevs = [self.changelog.rev(node) for node in newnodes
721 if self.changelog.hasnode(node)]
721 if self.changelog.hasnode(node)]
722 ctxisnew = bheadrevs and min(newheadrevs) > max(bheadrevs)
722 ctxisnew = bheadrevs and min(newheadrevs) > max(bheadrevs)
723 # Remove duplicates - nodes that are in newheadrevs and are already
723 # Remove duplicates - nodes that are in newheadrevs and are already
724 # in bheadrevs. This can happen if you strip a node whose parent
724 # in bheadrevs. This can happen if you strip a node whose parent
725 # was already a head (because they're on different branches).
725 # was already a head (because they're on different branches).
726 bheadrevs = sorted(set(bheadrevs).union(newheadrevs))
726 bheadrevs = sorted(set(bheadrevs).union(newheadrevs))
727
727
728 # Starting from tip means fewer passes over reachable. If we know
728 # Starting from tip means fewer passes over reachable. If we know
729 # the new candidates are not ancestors of existing heads, we don't
729 # the new candidates are not ancestors of existing heads, we don't
730 # have to examine ancestors of existing heads
730 # have to examine ancestors of existing heads
731 if ctxisnew:
731 if ctxisnew:
732 iterrevs = sorted(newheadrevs)
732 iterrevs = sorted(newheadrevs)
733 else:
733 else:
734 iterrevs = list(bheadrevs)
734 iterrevs = list(bheadrevs)
735
735
736 # This loop prunes out two kinds of heads - heads that are
736 # This loop prunes out two kinds of heads - heads that are
737 # superseded by a head in newheadrevs, and newheadrevs that are not
737 # superseded by a head in newheadrevs, and newheadrevs that are not
738 # heads because an existing head is their descendant.
738 # heads because an existing head is their descendant.
739 while iterrevs:
739 while iterrevs:
740 latest = iterrevs.pop()
740 latest = iterrevs.pop()
741 if latest not in bheadrevs:
741 if latest not in bheadrevs:
742 continue
742 continue
743 ancestors = set(self.changelog.ancestors([latest],
743 ancestors = set(self.changelog.ancestors([latest],
744 bheadrevs[0]))
744 bheadrevs[0]))
745 if ancestors:
745 if ancestors:
746 bheadrevs = [b for b in bheadrevs if b not in ancestors]
746 bheadrevs = [b for b in bheadrevs if b not in ancestors]
747 partial[branch] = [self.changelog.node(rev) for rev in bheadrevs]
747 partial[branch] = [self.changelog.node(rev) for rev in bheadrevs]
748
748
749 # There may be branches that cease to exist when the last commit in the
749 # There may be branches that cease to exist when the last commit in the
750 # branch was stripped. This code filters them out. Note that the
750 # branch was stripped. This code filters them out. Note that the
751 # branch that ceased to exist may not be in newbranches because
751 # branch that ceased to exist may not be in newbranches because
752 # newbranches is the set of candidate heads, which when you strip the
752 # newbranches is the set of candidate heads, which when you strip the
753 # last commit in a branch will be the parent branch.
753 # last commit in a branch will be the parent branch.
754 for branch in partial.keys():
754 for branch in partial.keys():
755 nodes = [head for head in partial[branch]
755 nodes = [head for head in partial[branch]
756 if self.changelog.hasnode(head)]
756 if self.changelog.hasnode(head)]
757 if not nodes:
757 if not nodes:
758 del partial[branch]
758 del partial[branch]
759
759
760 def lookup(self, key):
760 def lookup(self, key):
761 return self[key].node()
761 return self[key].node()
762
762
763 def lookupbranch(self, key, remote=None):
763 def lookupbranch(self, key, remote=None):
764 repo = remote or self
764 repo = remote or self
765 if key in repo.branchmap():
765 if key in repo.branchmap():
766 return key
766 return key
767
767
768 repo = (remote and remote.local()) and remote or self
768 repo = (remote and remote.local()) and remote or self
769 return repo[key].branch()
769 return repo[key].branch()
770
770
771 def known(self, nodes):
771 def known(self, nodes):
772 nm = self.changelog.nodemap
772 nm = self.changelog.nodemap
773 pc = self._phasecache
773 pc = self._phasecache
774 result = []
774 result = []
775 for n in nodes:
775 for n in nodes:
776 r = nm.get(n)
776 r = nm.get(n)
777 resp = not (r is None or pc.phase(self, r) >= phases.secret)
777 resp = not (r is None or pc.phase(self, r) >= phases.secret)
778 result.append(resp)
778 result.append(resp)
779 return result
779 return result
780
780
781 def local(self):
781 def local(self):
782 return self
782 return self
783
783
784 def cancopy(self):
784 def cancopy(self):
785 return self.local() # so statichttprepo's override of local() works
785 return self.local() # so statichttprepo's override of local() works
786
786
787 def join(self, f):
787 def join(self, f):
788 return os.path.join(self.path, f)
788 return os.path.join(self.path, f)
789
789
790 def wjoin(self, f):
790 def wjoin(self, f):
791 return os.path.join(self.root, f)
791 return os.path.join(self.root, f)
792
792
793 def file(self, f):
793 def file(self, f):
794 if f[0] == '/':
794 if f[0] == '/':
795 f = f[1:]
795 f = f[1:]
796 return filelog.filelog(self.sopener, f)
796 return filelog.filelog(self.sopener, f)
797
797
798 def changectx(self, changeid):
798 def changectx(self, changeid):
799 return self[changeid]
799 return self[changeid]
800
800
801 def parents(self, changeid=None):
801 def parents(self, changeid=None):
802 '''get list of changectxs for parents of changeid'''
802 '''get list of changectxs for parents of changeid'''
803 return self[changeid].parents()
803 return self[changeid].parents()
804
804
805 def setparents(self, p1, p2=nullid):
805 def setparents(self, p1, p2=nullid):
806 copies = self.dirstate.setparents(p1, p2)
806 copies = self.dirstate.setparents(p1, p2)
807 if copies:
807 if copies:
808 # Adjust copy records, the dirstate cannot do it, it
808 # Adjust copy records, the dirstate cannot do it, it
809 # requires access to parents manifests. Preserve them
809 # requires access to parents manifests. Preserve them
810 # only for entries added to first parent.
810 # only for entries added to first parent.
811 pctx = self[p1]
811 pctx = self[p1]
812 for f in copies:
812 for f in copies:
813 if f not in pctx and copies[f] in pctx:
813 if f not in pctx and copies[f] in pctx:
814 self.dirstate.copy(copies[f], f)
814 self.dirstate.copy(copies[f], f)
815
815
816 def filectx(self, path, changeid=None, fileid=None):
816 def filectx(self, path, changeid=None, fileid=None):
817 """changeid can be a changeset revision, node, or tag.
817 """changeid can be a changeset revision, node, or tag.
818 fileid can be a file revision or node."""
818 fileid can be a file revision or node."""
819 return context.filectx(self, path, changeid, fileid)
819 return context.filectx(self, path, changeid, fileid)
820
820
821 def getcwd(self):
821 def getcwd(self):
822 return self.dirstate.getcwd()
822 return self.dirstate.getcwd()
823
823
824 def pathto(self, f, cwd=None):
824 def pathto(self, f, cwd=None):
825 return self.dirstate.pathto(f, cwd)
825 return self.dirstate.pathto(f, cwd)
826
826
827 def wfile(self, f, mode='r'):
827 def wfile(self, f, mode='r'):
828 return self.wopener(f, mode)
828 return self.wopener(f, mode)
829
829
830 def _link(self, f):
830 def _link(self, f):
831 return os.path.islink(self.wjoin(f))
831 return os.path.islink(self.wjoin(f))
832
832
833 def _loadfilter(self, filter):
833 def _loadfilter(self, filter):
834 if filter not in self.filterpats:
834 if filter not in self.filterpats:
835 l = []
835 l = []
836 for pat, cmd in self.ui.configitems(filter):
836 for pat, cmd in self.ui.configitems(filter):
837 if cmd == '!':
837 if cmd == '!':
838 continue
838 continue
839 mf = matchmod.match(self.root, '', [pat])
839 mf = matchmod.match(self.root, '', [pat])
840 fn = None
840 fn = None
841 params = cmd
841 params = cmd
842 for name, filterfn in self._datafilters.iteritems():
842 for name, filterfn in self._datafilters.iteritems():
843 if cmd.startswith(name):
843 if cmd.startswith(name):
844 fn = filterfn
844 fn = filterfn
845 params = cmd[len(name):].lstrip()
845 params = cmd[len(name):].lstrip()
846 break
846 break
847 if not fn:
847 if not fn:
848 fn = lambda s, c, **kwargs: util.filter(s, c)
848 fn = lambda s, c, **kwargs: util.filter(s, c)
849 # Wrap old filters not supporting keyword arguments
849 # Wrap old filters not supporting keyword arguments
850 if not inspect.getargspec(fn)[2]:
850 if not inspect.getargspec(fn)[2]:
851 oldfn = fn
851 oldfn = fn
852 fn = lambda s, c, **kwargs: oldfn(s, c)
852 fn = lambda s, c, **kwargs: oldfn(s, c)
853 l.append((mf, fn, params))
853 l.append((mf, fn, params))
854 self.filterpats[filter] = l
854 self.filterpats[filter] = l
855 return self.filterpats[filter]
855 return self.filterpats[filter]
856
856
857 def _filter(self, filterpats, filename, data):
857 def _filter(self, filterpats, filename, data):
858 for mf, fn, cmd in filterpats:
858 for mf, fn, cmd in filterpats:
859 if mf(filename):
859 if mf(filename):
860 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
860 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
861 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
861 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
862 break
862 break
863
863
864 return data
864 return data
865
865
866 @propertycache
866 @propertycache
867 def _encodefilterpats(self):
867 def _encodefilterpats(self):
868 return self._loadfilter('encode')
868 return self._loadfilter('encode')
869
869
870 @propertycache
870 @propertycache
871 def _decodefilterpats(self):
871 def _decodefilterpats(self):
872 return self._loadfilter('decode')
872 return self._loadfilter('decode')
873
873
874 def adddatafilter(self, name, filter):
874 def adddatafilter(self, name, filter):
875 self._datafilters[name] = filter
875 self._datafilters[name] = filter
876
876
877 def wread(self, filename):
877 def wread(self, filename):
878 if self._link(filename):
878 if self._link(filename):
879 data = os.readlink(self.wjoin(filename))
879 data = os.readlink(self.wjoin(filename))
880 else:
880 else:
881 data = self.wopener.read(filename)
881 data = self.wopener.read(filename)
882 return self._filter(self._encodefilterpats, filename, data)
882 return self._filter(self._encodefilterpats, filename, data)
883
883
884 def wwrite(self, filename, data, flags):
884 def wwrite(self, filename, data, flags):
885 data = self._filter(self._decodefilterpats, filename, data)
885 data = self._filter(self._decodefilterpats, filename, data)
886 if 'l' in flags:
886 if 'l' in flags:
887 self.wopener.symlink(data, filename)
887 self.wopener.symlink(data, filename)
888 else:
888 else:
889 self.wopener.write(filename, data)
889 self.wopener.write(filename, data)
890 if 'x' in flags:
890 if 'x' in flags:
891 util.setflags(self.wjoin(filename), False, True)
891 util.setflags(self.wjoin(filename), False, True)
892
892
893 def wwritedata(self, filename, data):
893 def wwritedata(self, filename, data):
894 return self._filter(self._decodefilterpats, filename, data)
894 return self._filter(self._decodefilterpats, filename, data)
895
895
896 def transaction(self, desc):
896 def transaction(self, desc):
897 tr = self._transref and self._transref() or None
897 tr = self._transref and self._transref() or None
898 if tr and tr.running():
898 if tr and tr.running():
899 return tr.nest()
899 return tr.nest()
900
900
901 # abort here if the journal already exists
901 # abort here if the journal already exists
902 if os.path.exists(self.sjoin("journal")):
902 if os.path.exists(self.sjoin("journal")):
903 raise error.RepoError(
903 raise error.RepoError(
904 _("abandoned transaction found - run hg recover"))
904 _("abandoned transaction found - run hg recover"))
905
905
906 self._writejournal(desc)
906 self._writejournal(desc)
907 renames = [(x, undoname(x)) for x in self._journalfiles()]
907 renames = [(x, undoname(x)) for x in self._journalfiles()]
908
908
909 tr = transaction.transaction(self.ui.warn, self.sopener,
909 tr = transaction.transaction(self.ui.warn, self.sopener,
910 self.sjoin("journal"),
910 self.sjoin("journal"),
911 aftertrans(renames),
911 aftertrans(renames),
912 self.store.createmode)
912 self.store.createmode)
913 self._transref = weakref.ref(tr)
913 self._transref = weakref.ref(tr)
914 return tr
914 return tr
915
915
916 def _journalfiles(self):
916 def _journalfiles(self):
917 return (self.sjoin('journal'), self.join('journal.dirstate'),
917 return (self.sjoin('journal'), self.join('journal.dirstate'),
918 self.join('journal.branch'), self.join('journal.desc'),
918 self.join('journal.branch'), self.join('journal.desc'),
919 self.join('journal.bookmarks'),
919 self.join('journal.bookmarks'),
920 self.sjoin('journal.phaseroots'))
920 self.sjoin('journal.phaseroots'))
921
921
922 def undofiles(self):
922 def undofiles(self):
923 return [undoname(x) for x in self._journalfiles()]
923 return [undoname(x) for x in self._journalfiles()]
924
924
925 def _writejournal(self, desc):
925 def _writejournal(self, desc):
926 self.opener.write("journal.dirstate",
926 self.opener.write("journal.dirstate",
927 self.opener.tryread("dirstate"))
927 self.opener.tryread("dirstate"))
928 self.opener.write("journal.branch",
928 self.opener.write("journal.branch",
929 encoding.fromlocal(self.dirstate.branch()))
929 encoding.fromlocal(self.dirstate.branch()))
930 self.opener.write("journal.desc",
930 self.opener.write("journal.desc",
931 "%d\n%s\n" % (len(self), desc))
931 "%d\n%s\n" % (len(self), desc))
932 self.opener.write("journal.bookmarks",
932 self.opener.write("journal.bookmarks",
933 self.opener.tryread("bookmarks"))
933 self.opener.tryread("bookmarks"))
934 self.sopener.write("journal.phaseroots",
934 self.sopener.write("journal.phaseroots",
935 self.sopener.tryread("phaseroots"))
935 self.sopener.tryread("phaseroots"))
936
936
937 def recover(self):
937 def recover(self):
938 lock = self.lock()
938 lock = self.lock()
939 try:
939 try:
940 if os.path.exists(self.sjoin("journal")):
940 if os.path.exists(self.sjoin("journal")):
941 self.ui.status(_("rolling back interrupted transaction\n"))
941 self.ui.status(_("rolling back interrupted transaction\n"))
942 transaction.rollback(self.sopener, self.sjoin("journal"),
942 transaction.rollback(self.sopener, self.sjoin("journal"),
943 self.ui.warn)
943 self.ui.warn)
944 self.invalidate()
944 self.invalidate()
945 return True
945 return True
946 else:
946 else:
947 self.ui.warn(_("no interrupted transaction available\n"))
947 self.ui.warn(_("no interrupted transaction available\n"))
948 return False
948 return False
949 finally:
949 finally:
950 lock.release()
950 lock.release()
951
951
952 def rollback(self, dryrun=False, force=False):
952 def rollback(self, dryrun=False, force=False):
953 wlock = lock = None
953 wlock = lock = None
954 try:
954 try:
955 wlock = self.wlock()
955 wlock = self.wlock()
956 lock = self.lock()
956 lock = self.lock()
957 if os.path.exists(self.sjoin("undo")):
957 if os.path.exists(self.sjoin("undo")):
958 return self._rollback(dryrun, force)
958 return self._rollback(dryrun, force)
959 else:
959 else:
960 self.ui.warn(_("no rollback information available\n"))
960 self.ui.warn(_("no rollback information available\n"))
961 return 1
961 return 1
962 finally:
962 finally:
963 release(lock, wlock)
963 release(lock, wlock)
964
964
965 def _rollback(self, dryrun, force):
965 def _rollback(self, dryrun, force):
966 ui = self.ui
966 ui = self.ui
967 try:
967 try:
968 args = self.opener.read('undo.desc').splitlines()
968 args = self.opener.read('undo.desc').splitlines()
969 (oldlen, desc, detail) = (int(args[0]), args[1], None)
969 (oldlen, desc, detail) = (int(args[0]), args[1], None)
970 if len(args) >= 3:
970 if len(args) >= 3:
971 detail = args[2]
971 detail = args[2]
972 oldtip = oldlen - 1
972 oldtip = oldlen - 1
973
973
974 if detail and ui.verbose:
974 if detail and ui.verbose:
975 msg = (_('repository tip rolled back to revision %s'
975 msg = (_('repository tip rolled back to revision %s'
976 ' (undo %s: %s)\n')
976 ' (undo %s: %s)\n')
977 % (oldtip, desc, detail))
977 % (oldtip, desc, detail))
978 else:
978 else:
979 msg = (_('repository tip rolled back to revision %s'
979 msg = (_('repository tip rolled back to revision %s'
980 ' (undo %s)\n')
980 ' (undo %s)\n')
981 % (oldtip, desc))
981 % (oldtip, desc))
982 except IOError:
982 except IOError:
983 msg = _('rolling back unknown transaction\n')
983 msg = _('rolling back unknown transaction\n')
984 desc = None
984 desc = None
985
985
986 if not force and self['.'] != self['tip'] and desc == 'commit':
986 if not force and self['.'] != self['tip'] and desc == 'commit':
987 raise util.Abort(
987 raise util.Abort(
988 _('rollback of last commit while not checked out '
988 _('rollback of last commit while not checked out '
989 'may lose data'), hint=_('use -f to force'))
989 'may lose data'), hint=_('use -f to force'))
990
990
991 ui.status(msg)
991 ui.status(msg)
992 if dryrun:
992 if dryrun:
993 return 0
993 return 0
994
994
995 parents = self.dirstate.parents()
995 parents = self.dirstate.parents()
996 transaction.rollback(self.sopener, self.sjoin('undo'), ui.warn)
996 transaction.rollback(self.sopener, self.sjoin('undo'), ui.warn)
997 if os.path.exists(self.join('undo.bookmarks')):
997 if os.path.exists(self.join('undo.bookmarks')):
998 util.rename(self.join('undo.bookmarks'),
998 util.rename(self.join('undo.bookmarks'),
999 self.join('bookmarks'))
999 self.join('bookmarks'))
1000 if os.path.exists(self.sjoin('undo.phaseroots')):
1000 if os.path.exists(self.sjoin('undo.phaseroots')):
1001 util.rename(self.sjoin('undo.phaseroots'),
1001 util.rename(self.sjoin('undo.phaseroots'),
1002 self.sjoin('phaseroots'))
1002 self.sjoin('phaseroots'))
1003 self.invalidate()
1003 self.invalidate()
1004
1004
1005 # Discard all cache entries to force reloading everything.
1005 # Discard all cache entries to force reloading everything.
1006 self._filecache.clear()
1006 self._filecache.clear()
1007
1007
1008 parentgone = (parents[0] not in self.changelog.nodemap or
1008 parentgone = (parents[0] not in self.changelog.nodemap or
1009 parents[1] not in self.changelog.nodemap)
1009 parents[1] not in self.changelog.nodemap)
1010 if parentgone:
1010 if parentgone:
1011 util.rename(self.join('undo.dirstate'), self.join('dirstate'))
1011 util.rename(self.join('undo.dirstate'), self.join('dirstate'))
1012 try:
1012 try:
1013 branch = self.opener.read('undo.branch')
1013 branch = self.opener.read('undo.branch')
1014 self.dirstate.setbranch(encoding.tolocal(branch))
1014 self.dirstate.setbranch(encoding.tolocal(branch))
1015 except IOError:
1015 except IOError:
1016 ui.warn(_('named branch could not be reset: '
1016 ui.warn(_('named branch could not be reset: '
1017 'current branch is still \'%s\'\n')
1017 'current branch is still \'%s\'\n')
1018 % self.dirstate.branch())
1018 % self.dirstate.branch())
1019
1019
1020 self.dirstate.invalidate()
1020 self.dirstate.invalidate()
1021 parents = tuple([p.rev() for p in self.parents()])
1021 parents = tuple([p.rev() for p in self.parents()])
1022 if len(parents) > 1:
1022 if len(parents) > 1:
1023 ui.status(_('working directory now based on '
1023 ui.status(_('working directory now based on '
1024 'revisions %d and %d\n') % parents)
1024 'revisions %d and %d\n') % parents)
1025 else:
1025 else:
1026 ui.status(_('working directory now based on '
1026 ui.status(_('working directory now based on '
1027 'revision %d\n') % parents)
1027 'revision %d\n') % parents)
1028 # TODO: if we know which new heads may result from this rollback, pass
1028 # TODO: if we know which new heads may result from this rollback, pass
1029 # them to destroy(), which will prevent the branchhead cache from being
1029 # them to destroy(), which will prevent the branchhead cache from being
1030 # invalidated.
1030 # invalidated.
1031 self.destroyed()
1031 self.destroyed()
1032 return 0
1032 return 0
1033
1033
1034 def invalidatecaches(self):
1034 def invalidatecaches(self):
1035 def delcache(name):
1035 def delcache(name):
1036 try:
1036 try:
1037 delattr(self, name)
1037 delattr(self, name)
1038 except AttributeError:
1038 except AttributeError:
1039 pass
1039 pass
1040
1040
1041 delcache('_tagscache')
1041 delcache('_tagscache')
1042
1042
1043 self._branchcache = None # in UTF-8
1043 self._branchcache = None # in UTF-8
1044 self._branchcachetip = None
1044 self._branchcachetip = None
1045 obsolete.clearobscaches(self)
1045
1046
1046 def invalidatedirstate(self):
1047 def invalidatedirstate(self):
1047 '''Invalidates the dirstate, causing the next call to dirstate
1048 '''Invalidates the dirstate, causing the next call to dirstate
1048 to check if it was modified since the last time it was read,
1049 to check if it was modified since the last time it was read,
1049 rereading it if it has.
1050 rereading it if it has.
1050
1051
1051 This is different to dirstate.invalidate() that it doesn't always
1052 This is different to dirstate.invalidate() that it doesn't always
1052 rereads the dirstate. Use dirstate.invalidate() if you want to
1053 rereads the dirstate. Use dirstate.invalidate() if you want to
1053 explicitly read the dirstate again (i.e. restoring it to a previous
1054 explicitly read the dirstate again (i.e. restoring it to a previous
1054 known good state).'''
1055 known good state).'''
1055 if 'dirstate' in self.__dict__:
1056 if 'dirstate' in self.__dict__:
1056 for k in self.dirstate._filecache:
1057 for k in self.dirstate._filecache:
1057 try:
1058 try:
1058 delattr(self.dirstate, k)
1059 delattr(self.dirstate, k)
1059 except AttributeError:
1060 except AttributeError:
1060 pass
1061 pass
1061 delattr(self, 'dirstate')
1062 delattr(self, 'dirstate')
1062
1063
1063 def invalidate(self):
1064 def invalidate(self):
1064 for k in self._filecache:
1065 for k in self._filecache:
1065 # dirstate is invalidated separately in invalidatedirstate()
1066 # dirstate is invalidated separately in invalidatedirstate()
1066 if k == 'dirstate':
1067 if k == 'dirstate':
1067 continue
1068 continue
1068
1069
1069 try:
1070 try:
1070 delattr(self, k)
1071 delattr(self, k)
1071 except AttributeError:
1072 except AttributeError:
1072 pass
1073 pass
1073 self.invalidatecaches()
1074 self.invalidatecaches()
1074
1075
1075 def _lock(self, lockname, wait, releasefn, acquirefn, desc):
1076 def _lock(self, lockname, wait, releasefn, acquirefn, desc):
1076 try:
1077 try:
1077 l = lock.lock(lockname, 0, releasefn, desc=desc)
1078 l = lock.lock(lockname, 0, releasefn, desc=desc)
1078 except error.LockHeld, inst:
1079 except error.LockHeld, inst:
1079 if not wait:
1080 if not wait:
1080 raise
1081 raise
1081 self.ui.warn(_("waiting for lock on %s held by %r\n") %
1082 self.ui.warn(_("waiting for lock on %s held by %r\n") %
1082 (desc, inst.locker))
1083 (desc, inst.locker))
1083 # default to 600 seconds timeout
1084 # default to 600 seconds timeout
1084 l = lock.lock(lockname, int(self.ui.config("ui", "timeout", "600")),
1085 l = lock.lock(lockname, int(self.ui.config("ui", "timeout", "600")),
1085 releasefn, desc=desc)
1086 releasefn, desc=desc)
1086 if acquirefn:
1087 if acquirefn:
1087 acquirefn()
1088 acquirefn()
1088 return l
1089 return l
1089
1090
1090 def _afterlock(self, callback):
1091 def _afterlock(self, callback):
1091 """add a callback to the current repository lock.
1092 """add a callback to the current repository lock.
1092
1093
1093 The callback will be executed on lock release."""
1094 The callback will be executed on lock release."""
1094 l = self._lockref and self._lockref()
1095 l = self._lockref and self._lockref()
1095 if l:
1096 if l:
1096 l.postrelease.append(callback)
1097 l.postrelease.append(callback)
1097 else:
1098 else:
1098 callback()
1099 callback()
1099
1100
1100 def lock(self, wait=True):
1101 def lock(self, wait=True):
1101 '''Lock the repository store (.hg/store) and return a weak reference
1102 '''Lock the repository store (.hg/store) and return a weak reference
1102 to the lock. Use this before modifying the store (e.g. committing or
1103 to the lock. Use this before modifying the store (e.g. committing or
1103 stripping). If you are opening a transaction, get a lock as well.)'''
1104 stripping). If you are opening a transaction, get a lock as well.)'''
1104 l = self._lockref and self._lockref()
1105 l = self._lockref and self._lockref()
1105 if l is not None and l.held:
1106 if l is not None and l.held:
1106 l.lock()
1107 l.lock()
1107 return l
1108 return l
1108
1109
1109 def unlock():
1110 def unlock():
1110 self.store.write()
1111 self.store.write()
1111 if '_phasecache' in vars(self):
1112 if '_phasecache' in vars(self):
1112 self._phasecache.write()
1113 self._phasecache.write()
1113 for k, ce in self._filecache.items():
1114 for k, ce in self._filecache.items():
1114 if k == 'dirstate':
1115 if k == 'dirstate':
1115 continue
1116 continue
1116 ce.refresh()
1117 ce.refresh()
1117
1118
1118 l = self._lock(self.sjoin("lock"), wait, unlock,
1119 l = self._lock(self.sjoin("lock"), wait, unlock,
1119 self.invalidate, _('repository %s') % self.origroot)
1120 self.invalidate, _('repository %s') % self.origroot)
1120 self._lockref = weakref.ref(l)
1121 self._lockref = weakref.ref(l)
1121 return l
1122 return l
1122
1123
1123 def wlock(self, wait=True):
1124 def wlock(self, wait=True):
1124 '''Lock the non-store parts of the repository (everything under
1125 '''Lock the non-store parts of the repository (everything under
1125 .hg except .hg/store) and return a weak reference to the lock.
1126 .hg except .hg/store) and return a weak reference to the lock.
1126 Use this before modifying files in .hg.'''
1127 Use this before modifying files in .hg.'''
1127 l = self._wlockref and self._wlockref()
1128 l = self._wlockref and self._wlockref()
1128 if l is not None and l.held:
1129 if l is not None and l.held:
1129 l.lock()
1130 l.lock()
1130 return l
1131 return l
1131
1132
1132 def unlock():
1133 def unlock():
1133 self.dirstate.write()
1134 self.dirstate.write()
1134 ce = self._filecache.get('dirstate')
1135 ce = self._filecache.get('dirstate')
1135 if ce:
1136 if ce:
1136 ce.refresh()
1137 ce.refresh()
1137
1138
1138 l = self._lock(self.join("wlock"), wait, unlock,
1139 l = self._lock(self.join("wlock"), wait, unlock,
1139 self.invalidatedirstate, _('working directory of %s') %
1140 self.invalidatedirstate, _('working directory of %s') %
1140 self.origroot)
1141 self.origroot)
1141 self._wlockref = weakref.ref(l)
1142 self._wlockref = weakref.ref(l)
1142 return l
1143 return l
1143
1144
1144 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
1145 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
1145 """
1146 """
1146 commit an individual file as part of a larger transaction
1147 commit an individual file as part of a larger transaction
1147 """
1148 """
1148
1149
1149 fname = fctx.path()
1150 fname = fctx.path()
1150 text = fctx.data()
1151 text = fctx.data()
1151 flog = self.file(fname)
1152 flog = self.file(fname)
1152 fparent1 = manifest1.get(fname, nullid)
1153 fparent1 = manifest1.get(fname, nullid)
1153 fparent2 = fparent2o = manifest2.get(fname, nullid)
1154 fparent2 = fparent2o = manifest2.get(fname, nullid)
1154
1155
1155 meta = {}
1156 meta = {}
1156 copy = fctx.renamed()
1157 copy = fctx.renamed()
1157 if copy and copy[0] != fname:
1158 if copy and copy[0] != fname:
1158 # Mark the new revision of this file as a copy of another
1159 # Mark the new revision of this file as a copy of another
1159 # file. This copy data will effectively act as a parent
1160 # file. This copy data will effectively act as a parent
1160 # of this new revision. If this is a merge, the first
1161 # of this new revision. If this is a merge, the first
1161 # parent will be the nullid (meaning "look up the copy data")
1162 # parent will be the nullid (meaning "look up the copy data")
1162 # and the second one will be the other parent. For example:
1163 # and the second one will be the other parent. For example:
1163 #
1164 #
1164 # 0 --- 1 --- 3 rev1 changes file foo
1165 # 0 --- 1 --- 3 rev1 changes file foo
1165 # \ / rev2 renames foo to bar and changes it
1166 # \ / rev2 renames foo to bar and changes it
1166 # \- 2 -/ rev3 should have bar with all changes and
1167 # \- 2 -/ rev3 should have bar with all changes and
1167 # should record that bar descends from
1168 # should record that bar descends from
1168 # bar in rev2 and foo in rev1
1169 # bar in rev2 and foo in rev1
1169 #
1170 #
1170 # this allows this merge to succeed:
1171 # this allows this merge to succeed:
1171 #
1172 #
1172 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
1173 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
1173 # \ / merging rev3 and rev4 should use bar@rev2
1174 # \ / merging rev3 and rev4 should use bar@rev2
1174 # \- 2 --- 4 as the merge base
1175 # \- 2 --- 4 as the merge base
1175 #
1176 #
1176
1177
1177 cfname = copy[0]
1178 cfname = copy[0]
1178 crev = manifest1.get(cfname)
1179 crev = manifest1.get(cfname)
1179 newfparent = fparent2
1180 newfparent = fparent2
1180
1181
1181 if manifest2: # branch merge
1182 if manifest2: # branch merge
1182 if fparent2 == nullid or crev is None: # copied on remote side
1183 if fparent2 == nullid or crev is None: # copied on remote side
1183 if cfname in manifest2:
1184 if cfname in manifest2:
1184 crev = manifest2[cfname]
1185 crev = manifest2[cfname]
1185 newfparent = fparent1
1186 newfparent = fparent1
1186
1187
1187 # find source in nearest ancestor if we've lost track
1188 # find source in nearest ancestor if we've lost track
1188 if not crev:
1189 if not crev:
1189 self.ui.debug(" %s: searching for copy revision for %s\n" %
1190 self.ui.debug(" %s: searching for copy revision for %s\n" %
1190 (fname, cfname))
1191 (fname, cfname))
1191 for ancestor in self[None].ancestors():
1192 for ancestor in self[None].ancestors():
1192 if cfname in ancestor:
1193 if cfname in ancestor:
1193 crev = ancestor[cfname].filenode()
1194 crev = ancestor[cfname].filenode()
1194 break
1195 break
1195
1196
1196 if crev:
1197 if crev:
1197 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
1198 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
1198 meta["copy"] = cfname
1199 meta["copy"] = cfname
1199 meta["copyrev"] = hex(crev)
1200 meta["copyrev"] = hex(crev)
1200 fparent1, fparent2 = nullid, newfparent
1201 fparent1, fparent2 = nullid, newfparent
1201 else:
1202 else:
1202 self.ui.warn(_("warning: can't find ancestor for '%s' "
1203 self.ui.warn(_("warning: can't find ancestor for '%s' "
1203 "copied from '%s'!\n") % (fname, cfname))
1204 "copied from '%s'!\n") % (fname, cfname))
1204
1205
1205 elif fparent2 != nullid:
1206 elif fparent2 != nullid:
1206 # is one parent an ancestor of the other?
1207 # is one parent an ancestor of the other?
1207 fparentancestor = flog.ancestor(fparent1, fparent2)
1208 fparentancestor = flog.ancestor(fparent1, fparent2)
1208 if fparentancestor == fparent1:
1209 if fparentancestor == fparent1:
1209 fparent1, fparent2 = fparent2, nullid
1210 fparent1, fparent2 = fparent2, nullid
1210 elif fparentancestor == fparent2:
1211 elif fparentancestor == fparent2:
1211 fparent2 = nullid
1212 fparent2 = nullid
1212
1213
1213 # is the file changed?
1214 # is the file changed?
1214 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
1215 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
1215 changelist.append(fname)
1216 changelist.append(fname)
1216 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
1217 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
1217
1218
1218 # are just the flags changed during merge?
1219 # are just the flags changed during merge?
1219 if fparent1 != fparent2o and manifest1.flags(fname) != fctx.flags():
1220 if fparent1 != fparent2o and manifest1.flags(fname) != fctx.flags():
1220 changelist.append(fname)
1221 changelist.append(fname)
1221
1222
1222 return fparent1
1223 return fparent1
1223
1224
1224 def commit(self, text="", user=None, date=None, match=None, force=False,
1225 def commit(self, text="", user=None, date=None, match=None, force=False,
1225 editor=False, extra={}):
1226 editor=False, extra={}):
1226 """Add a new revision to current repository.
1227 """Add a new revision to current repository.
1227
1228
1228 Revision information is gathered from the working directory,
1229 Revision information is gathered from the working directory,
1229 match can be used to filter the committed files. If editor is
1230 match can be used to filter the committed files. If editor is
1230 supplied, it is called to get a commit message.
1231 supplied, it is called to get a commit message.
1231 """
1232 """
1232
1233
1233 def fail(f, msg):
1234 def fail(f, msg):
1234 raise util.Abort('%s: %s' % (f, msg))
1235 raise util.Abort('%s: %s' % (f, msg))
1235
1236
1236 if not match:
1237 if not match:
1237 match = matchmod.always(self.root, '')
1238 match = matchmod.always(self.root, '')
1238
1239
1239 if not force:
1240 if not force:
1240 vdirs = []
1241 vdirs = []
1241 match.dir = vdirs.append
1242 match.dir = vdirs.append
1242 match.bad = fail
1243 match.bad = fail
1243
1244
1244 wlock = self.wlock()
1245 wlock = self.wlock()
1245 try:
1246 try:
1246 wctx = self[None]
1247 wctx = self[None]
1247 merge = len(wctx.parents()) > 1
1248 merge = len(wctx.parents()) > 1
1248
1249
1249 if (not force and merge and match and
1250 if (not force and merge and match and
1250 (match.files() or match.anypats())):
1251 (match.files() or match.anypats())):
1251 raise util.Abort(_('cannot partially commit a merge '
1252 raise util.Abort(_('cannot partially commit a merge '
1252 '(do not specify files or patterns)'))
1253 '(do not specify files or patterns)'))
1253
1254
1254 changes = self.status(match=match, clean=force)
1255 changes = self.status(match=match, clean=force)
1255 if force:
1256 if force:
1256 changes[0].extend(changes[6]) # mq may commit unchanged files
1257 changes[0].extend(changes[6]) # mq may commit unchanged files
1257
1258
1258 # check subrepos
1259 # check subrepos
1259 subs = []
1260 subs = []
1260 commitsubs = set()
1261 commitsubs = set()
1261 newstate = wctx.substate.copy()
1262 newstate = wctx.substate.copy()
1262 # only manage subrepos and .hgsubstate if .hgsub is present
1263 # only manage subrepos and .hgsubstate if .hgsub is present
1263 if '.hgsub' in wctx:
1264 if '.hgsub' in wctx:
1264 # we'll decide whether to track this ourselves, thanks
1265 # we'll decide whether to track this ourselves, thanks
1265 if '.hgsubstate' in changes[0]:
1266 if '.hgsubstate' in changes[0]:
1266 changes[0].remove('.hgsubstate')
1267 changes[0].remove('.hgsubstate')
1267 if '.hgsubstate' in changes[2]:
1268 if '.hgsubstate' in changes[2]:
1268 changes[2].remove('.hgsubstate')
1269 changes[2].remove('.hgsubstate')
1269
1270
1270 # compare current state to last committed state
1271 # compare current state to last committed state
1271 # build new substate based on last committed state
1272 # build new substate based on last committed state
1272 oldstate = wctx.p1().substate
1273 oldstate = wctx.p1().substate
1273 for s in sorted(newstate.keys()):
1274 for s in sorted(newstate.keys()):
1274 if not match(s):
1275 if not match(s):
1275 # ignore working copy, use old state if present
1276 # ignore working copy, use old state if present
1276 if s in oldstate:
1277 if s in oldstate:
1277 newstate[s] = oldstate[s]
1278 newstate[s] = oldstate[s]
1278 continue
1279 continue
1279 if not force:
1280 if not force:
1280 raise util.Abort(
1281 raise util.Abort(
1281 _("commit with new subrepo %s excluded") % s)
1282 _("commit with new subrepo %s excluded") % s)
1282 if wctx.sub(s).dirty(True):
1283 if wctx.sub(s).dirty(True):
1283 if not self.ui.configbool('ui', 'commitsubrepos'):
1284 if not self.ui.configbool('ui', 'commitsubrepos'):
1284 raise util.Abort(
1285 raise util.Abort(
1285 _("uncommitted changes in subrepo %s") % s,
1286 _("uncommitted changes in subrepo %s") % s,
1286 hint=_("use --subrepos for recursive commit"))
1287 hint=_("use --subrepos for recursive commit"))
1287 subs.append(s)
1288 subs.append(s)
1288 commitsubs.add(s)
1289 commitsubs.add(s)
1289 else:
1290 else:
1290 bs = wctx.sub(s).basestate()
1291 bs = wctx.sub(s).basestate()
1291 newstate[s] = (newstate[s][0], bs, newstate[s][2])
1292 newstate[s] = (newstate[s][0], bs, newstate[s][2])
1292 if oldstate.get(s, (None, None, None))[1] != bs:
1293 if oldstate.get(s, (None, None, None))[1] != bs:
1293 subs.append(s)
1294 subs.append(s)
1294
1295
1295 # check for removed subrepos
1296 # check for removed subrepos
1296 for p in wctx.parents():
1297 for p in wctx.parents():
1297 r = [s for s in p.substate if s not in newstate]
1298 r = [s for s in p.substate if s not in newstate]
1298 subs += [s for s in r if match(s)]
1299 subs += [s for s in r if match(s)]
1299 if subs:
1300 if subs:
1300 if (not match('.hgsub') and
1301 if (not match('.hgsub') and
1301 '.hgsub' in (wctx.modified() + wctx.added())):
1302 '.hgsub' in (wctx.modified() + wctx.added())):
1302 raise util.Abort(
1303 raise util.Abort(
1303 _("can't commit subrepos without .hgsub"))
1304 _("can't commit subrepos without .hgsub"))
1304 changes[0].insert(0, '.hgsubstate')
1305 changes[0].insert(0, '.hgsubstate')
1305
1306
1306 elif '.hgsub' in changes[2]:
1307 elif '.hgsub' in changes[2]:
1307 # clean up .hgsubstate when .hgsub is removed
1308 # clean up .hgsubstate when .hgsub is removed
1308 if ('.hgsubstate' in wctx and
1309 if ('.hgsubstate' in wctx and
1309 '.hgsubstate' not in changes[0] + changes[1] + changes[2]):
1310 '.hgsubstate' not in changes[0] + changes[1] + changes[2]):
1310 changes[2].insert(0, '.hgsubstate')
1311 changes[2].insert(0, '.hgsubstate')
1311
1312
1312 # make sure all explicit patterns are matched
1313 # make sure all explicit patterns are matched
1313 if not force and match.files():
1314 if not force and match.files():
1314 matched = set(changes[0] + changes[1] + changes[2])
1315 matched = set(changes[0] + changes[1] + changes[2])
1315
1316
1316 for f in match.files():
1317 for f in match.files():
1317 f = self.dirstate.normalize(f)
1318 f = self.dirstate.normalize(f)
1318 if f == '.' or f in matched or f in wctx.substate:
1319 if f == '.' or f in matched or f in wctx.substate:
1319 continue
1320 continue
1320 if f in changes[3]: # missing
1321 if f in changes[3]: # missing
1321 fail(f, _('file not found!'))
1322 fail(f, _('file not found!'))
1322 if f in vdirs: # visited directory
1323 if f in vdirs: # visited directory
1323 d = f + '/'
1324 d = f + '/'
1324 for mf in matched:
1325 for mf in matched:
1325 if mf.startswith(d):
1326 if mf.startswith(d):
1326 break
1327 break
1327 else:
1328 else:
1328 fail(f, _("no match under directory!"))
1329 fail(f, _("no match under directory!"))
1329 elif f not in self.dirstate:
1330 elif f not in self.dirstate:
1330 fail(f, _("file not tracked!"))
1331 fail(f, _("file not tracked!"))
1331
1332
1332 if (not force and not extra.get("close") and not merge
1333 if (not force and not extra.get("close") and not merge
1333 and not (changes[0] or changes[1] or changes[2])
1334 and not (changes[0] or changes[1] or changes[2])
1334 and wctx.branch() == wctx.p1().branch()):
1335 and wctx.branch() == wctx.p1().branch()):
1335 return None
1336 return None
1336
1337
1337 if merge and changes[3]:
1338 if merge and changes[3]:
1338 raise util.Abort(_("cannot commit merge with missing files"))
1339 raise util.Abort(_("cannot commit merge with missing files"))
1339
1340
1340 ms = mergemod.mergestate(self)
1341 ms = mergemod.mergestate(self)
1341 for f in changes[0]:
1342 for f in changes[0]:
1342 if f in ms and ms[f] == 'u':
1343 if f in ms and ms[f] == 'u':
1343 raise util.Abort(_("unresolved merge conflicts "
1344 raise util.Abort(_("unresolved merge conflicts "
1344 "(see hg help resolve)"))
1345 "(see hg help resolve)"))
1345
1346
1346 cctx = context.workingctx(self, text, user, date, extra, changes)
1347 cctx = context.workingctx(self, text, user, date, extra, changes)
1347 if editor:
1348 if editor:
1348 cctx._text = editor(self, cctx, subs)
1349 cctx._text = editor(self, cctx, subs)
1349 edited = (text != cctx._text)
1350 edited = (text != cctx._text)
1350
1351
1351 # commit subs and write new state
1352 # commit subs and write new state
1352 if subs:
1353 if subs:
1353 for s in sorted(commitsubs):
1354 for s in sorted(commitsubs):
1354 sub = wctx.sub(s)
1355 sub = wctx.sub(s)
1355 self.ui.status(_('committing subrepository %s\n') %
1356 self.ui.status(_('committing subrepository %s\n') %
1356 subrepo.subrelpath(sub))
1357 subrepo.subrelpath(sub))
1357 sr = sub.commit(cctx._text, user, date)
1358 sr = sub.commit(cctx._text, user, date)
1358 newstate[s] = (newstate[s][0], sr)
1359 newstate[s] = (newstate[s][0], sr)
1359 subrepo.writestate(self, newstate)
1360 subrepo.writestate(self, newstate)
1360
1361
1361 # Save commit message in case this transaction gets rolled back
1362 # Save commit message in case this transaction gets rolled back
1362 # (e.g. by a pretxncommit hook). Leave the content alone on
1363 # (e.g. by a pretxncommit hook). Leave the content alone on
1363 # the assumption that the user will use the same editor again.
1364 # the assumption that the user will use the same editor again.
1364 msgfn = self.savecommitmessage(cctx._text)
1365 msgfn = self.savecommitmessage(cctx._text)
1365
1366
1366 p1, p2 = self.dirstate.parents()
1367 p1, p2 = self.dirstate.parents()
1367 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1368 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1368 try:
1369 try:
1369 self.hook("precommit", throw=True, parent1=hookp1,
1370 self.hook("precommit", throw=True, parent1=hookp1,
1370 parent2=hookp2)
1371 parent2=hookp2)
1371 ret = self.commitctx(cctx, True)
1372 ret = self.commitctx(cctx, True)
1372 except: # re-raises
1373 except: # re-raises
1373 if edited:
1374 if edited:
1374 self.ui.write(
1375 self.ui.write(
1375 _('note: commit message saved in %s\n') % msgfn)
1376 _('note: commit message saved in %s\n') % msgfn)
1376 raise
1377 raise
1377
1378
1378 # update bookmarks, dirstate and mergestate
1379 # update bookmarks, dirstate and mergestate
1379 bookmarks.update(self, [p1, p2], ret)
1380 bookmarks.update(self, [p1, p2], ret)
1380 for f in changes[0] + changes[1]:
1381 for f in changes[0] + changes[1]:
1381 self.dirstate.normal(f)
1382 self.dirstate.normal(f)
1382 for f in changes[2]:
1383 for f in changes[2]:
1383 self.dirstate.drop(f)
1384 self.dirstate.drop(f)
1384 self.dirstate.setparents(ret)
1385 self.dirstate.setparents(ret)
1385 ms.reset()
1386 ms.reset()
1386 finally:
1387 finally:
1387 wlock.release()
1388 wlock.release()
1388
1389
1389 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
1390 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
1390 self.hook("commit", node=node, parent1=parent1, parent2=parent2)
1391 self.hook("commit", node=node, parent1=parent1, parent2=parent2)
1391 self._afterlock(commithook)
1392 self._afterlock(commithook)
1392 return ret
1393 return ret
1393
1394
1394 def commitctx(self, ctx, error=False):
1395 def commitctx(self, ctx, error=False):
1395 """Add a new revision to current repository.
1396 """Add a new revision to current repository.
1396 Revision information is passed via the context argument.
1397 Revision information is passed via the context argument.
1397 """
1398 """
1398
1399
1399 tr = lock = None
1400 tr = lock = None
1400 removed = list(ctx.removed())
1401 removed = list(ctx.removed())
1401 p1, p2 = ctx.p1(), ctx.p2()
1402 p1, p2 = ctx.p1(), ctx.p2()
1402 user = ctx.user()
1403 user = ctx.user()
1403
1404
1404 lock = self.lock()
1405 lock = self.lock()
1405 try:
1406 try:
1406 tr = self.transaction("commit")
1407 tr = self.transaction("commit")
1407 trp = weakref.proxy(tr)
1408 trp = weakref.proxy(tr)
1408
1409
1409 if ctx.files():
1410 if ctx.files():
1410 m1 = p1.manifest().copy()
1411 m1 = p1.manifest().copy()
1411 m2 = p2.manifest()
1412 m2 = p2.manifest()
1412
1413
1413 # check in files
1414 # check in files
1414 new = {}
1415 new = {}
1415 changed = []
1416 changed = []
1416 linkrev = len(self)
1417 linkrev = len(self)
1417 for f in sorted(ctx.modified() + ctx.added()):
1418 for f in sorted(ctx.modified() + ctx.added()):
1418 self.ui.note(f + "\n")
1419 self.ui.note(f + "\n")
1419 try:
1420 try:
1420 fctx = ctx[f]
1421 fctx = ctx[f]
1421 new[f] = self._filecommit(fctx, m1, m2, linkrev, trp,
1422 new[f] = self._filecommit(fctx, m1, m2, linkrev, trp,
1422 changed)
1423 changed)
1423 m1.set(f, fctx.flags())
1424 m1.set(f, fctx.flags())
1424 except OSError, inst:
1425 except OSError, inst:
1425 self.ui.warn(_("trouble committing %s!\n") % f)
1426 self.ui.warn(_("trouble committing %s!\n") % f)
1426 raise
1427 raise
1427 except IOError, inst:
1428 except IOError, inst:
1428 errcode = getattr(inst, 'errno', errno.ENOENT)
1429 errcode = getattr(inst, 'errno', errno.ENOENT)
1429 if error or errcode and errcode != errno.ENOENT:
1430 if error or errcode and errcode != errno.ENOENT:
1430 self.ui.warn(_("trouble committing %s!\n") % f)
1431 self.ui.warn(_("trouble committing %s!\n") % f)
1431 raise
1432 raise
1432 else:
1433 else:
1433 removed.append(f)
1434 removed.append(f)
1434
1435
1435 # update manifest
1436 # update manifest
1436 m1.update(new)
1437 m1.update(new)
1437 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1438 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1438 drop = [f for f in removed if f in m1]
1439 drop = [f for f in removed if f in m1]
1439 for f in drop:
1440 for f in drop:
1440 del m1[f]
1441 del m1[f]
1441 mn = self.manifest.add(m1, trp, linkrev, p1.manifestnode(),
1442 mn = self.manifest.add(m1, trp, linkrev, p1.manifestnode(),
1442 p2.manifestnode(), (new, drop))
1443 p2.manifestnode(), (new, drop))
1443 files = changed + removed
1444 files = changed + removed
1444 else:
1445 else:
1445 mn = p1.manifestnode()
1446 mn = p1.manifestnode()
1446 files = []
1447 files = []
1447
1448
1448 # update changelog
1449 # update changelog
1449 self.changelog.delayupdate()
1450 self.changelog.delayupdate()
1450 n = self.changelog.add(mn, files, ctx.description(),
1451 n = self.changelog.add(mn, files, ctx.description(),
1451 trp, p1.node(), p2.node(),
1452 trp, p1.node(), p2.node(),
1452 user, ctx.date(), ctx.extra().copy())
1453 user, ctx.date(), ctx.extra().copy())
1453 p = lambda: self.changelog.writepending() and self.root or ""
1454 p = lambda: self.changelog.writepending() and self.root or ""
1454 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1455 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1455 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1456 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1456 parent2=xp2, pending=p)
1457 parent2=xp2, pending=p)
1457 self.changelog.finalize(trp)
1458 self.changelog.finalize(trp)
1458 # set the new commit is proper phase
1459 # set the new commit is proper phase
1459 targetphase = phases.newcommitphase(self.ui)
1460 targetphase = phases.newcommitphase(self.ui)
1460 if targetphase:
1461 if targetphase:
1461 # retract boundary do not alter parent changeset.
1462 # retract boundary do not alter parent changeset.
1462 # if a parent have higher the resulting phase will
1463 # if a parent have higher the resulting phase will
1463 # be compliant anyway
1464 # be compliant anyway
1464 #
1465 #
1465 # if minimal phase was 0 we don't need to retract anything
1466 # if minimal phase was 0 we don't need to retract anything
1466 phases.retractboundary(self, targetphase, [n])
1467 phases.retractboundary(self, targetphase, [n])
1467 tr.close()
1468 tr.close()
1468 self.updatebranchcache()
1469 self.updatebranchcache()
1469 return n
1470 return n
1470 finally:
1471 finally:
1471 if tr:
1472 if tr:
1472 tr.release()
1473 tr.release()
1473 lock.release()
1474 lock.release()
1474
1475
1475 def destroyed(self, newheadnodes=None):
1476 def destroyed(self, newheadnodes=None):
1476 '''Inform the repository that nodes have been destroyed.
1477 '''Inform the repository that nodes have been destroyed.
1477 Intended for use by strip and rollback, so there's a common
1478 Intended for use by strip and rollback, so there's a common
1478 place for anything that has to be done after destroying history.
1479 place for anything that has to be done after destroying history.
1479
1480
1480 If you know the branchheadcache was uptodate before nodes were removed
1481 If you know the branchheadcache was uptodate before nodes were removed
1481 and you also know the set of candidate new heads that may have resulted
1482 and you also know the set of candidate new heads that may have resulted
1482 from the destruction, you can set newheadnodes. This will enable the
1483 from the destruction, you can set newheadnodes. This will enable the
1483 code to update the branchheads cache, rather than having future code
1484 code to update the branchheads cache, rather than having future code
1484 decide it's invalid and regenerating it from scratch.
1485 decide it's invalid and regenerating it from scratch.
1485 '''
1486 '''
1486 # If we have info, newheadnodes, on how to update the branch cache, do
1487 # If we have info, newheadnodes, on how to update the branch cache, do
1487 # it, Otherwise, since nodes were destroyed, the cache is stale and this
1488 # it, Otherwise, since nodes were destroyed, the cache is stale and this
1488 # will be caught the next time it is read.
1489 # will be caught the next time it is read.
1489 if newheadnodes:
1490 if newheadnodes:
1490 tiprev = len(self) - 1
1491 tiprev = len(self) - 1
1491 ctxgen = (self[node] for node in newheadnodes
1492 ctxgen = (self[node] for node in newheadnodes
1492 if self.changelog.hasnode(node))
1493 if self.changelog.hasnode(node))
1493 self._updatebranchcache(self._branchcache, ctxgen)
1494 self._updatebranchcache(self._branchcache, ctxgen)
1494 self._writebranchcache(self._branchcache, self.changelog.tip(),
1495 self._writebranchcache(self._branchcache, self.changelog.tip(),
1495 tiprev)
1496 tiprev)
1496
1497
1497 # Ensure the persistent tag cache is updated. Doing it now
1498 # Ensure the persistent tag cache is updated. Doing it now
1498 # means that the tag cache only has to worry about destroyed
1499 # means that the tag cache only has to worry about destroyed
1499 # heads immediately after a strip/rollback. That in turn
1500 # heads immediately after a strip/rollback. That in turn
1500 # guarantees that "cachetip == currenttip" (comparing both rev
1501 # guarantees that "cachetip == currenttip" (comparing both rev
1501 # and node) always means no nodes have been added or destroyed.
1502 # and node) always means no nodes have been added or destroyed.
1502
1503
1503 # XXX this is suboptimal when qrefresh'ing: we strip the current
1504 # XXX this is suboptimal when qrefresh'ing: we strip the current
1504 # head, refresh the tag cache, then immediately add a new head.
1505 # head, refresh the tag cache, then immediately add a new head.
1505 # But I think doing it this way is necessary for the "instant
1506 # But I think doing it this way is necessary for the "instant
1506 # tag cache retrieval" case to work.
1507 # tag cache retrieval" case to work.
1507 self.invalidatecaches()
1508 self.invalidatecaches()
1508
1509
1509 # Discard all cache entries to force reloading everything.
1510 # Discard all cache entries to force reloading everything.
1510 self._filecache.clear()
1511 self._filecache.clear()
1511
1512
1512 def walk(self, match, node=None):
1513 def walk(self, match, node=None):
1513 '''
1514 '''
1514 walk recursively through the directory tree or a given
1515 walk recursively through the directory tree or a given
1515 changeset, finding all files matched by the match
1516 changeset, finding all files matched by the match
1516 function
1517 function
1517 '''
1518 '''
1518 return self[node].walk(match)
1519 return self[node].walk(match)
1519
1520
1520 def status(self, node1='.', node2=None, match=None,
1521 def status(self, node1='.', node2=None, match=None,
1521 ignored=False, clean=False, unknown=False,
1522 ignored=False, clean=False, unknown=False,
1522 listsubrepos=False):
1523 listsubrepos=False):
1523 """return status of files between two nodes or node and working
1524 """return status of files between two nodes or node and working
1524 directory.
1525 directory.
1525
1526
1526 If node1 is None, use the first dirstate parent instead.
1527 If node1 is None, use the first dirstate parent instead.
1527 If node2 is None, compare node1 with working directory.
1528 If node2 is None, compare node1 with working directory.
1528 """
1529 """
1529
1530
1530 def mfmatches(ctx):
1531 def mfmatches(ctx):
1531 mf = ctx.manifest().copy()
1532 mf = ctx.manifest().copy()
1532 if match.always():
1533 if match.always():
1533 return mf
1534 return mf
1534 for fn in mf.keys():
1535 for fn in mf.keys():
1535 if not match(fn):
1536 if not match(fn):
1536 del mf[fn]
1537 del mf[fn]
1537 return mf
1538 return mf
1538
1539
1539 if isinstance(node1, context.changectx):
1540 if isinstance(node1, context.changectx):
1540 ctx1 = node1
1541 ctx1 = node1
1541 else:
1542 else:
1542 ctx1 = self[node1]
1543 ctx1 = self[node1]
1543 if isinstance(node2, context.changectx):
1544 if isinstance(node2, context.changectx):
1544 ctx2 = node2
1545 ctx2 = node2
1545 else:
1546 else:
1546 ctx2 = self[node2]
1547 ctx2 = self[node2]
1547
1548
1548 working = ctx2.rev() is None
1549 working = ctx2.rev() is None
1549 parentworking = working and ctx1 == self['.']
1550 parentworking = working and ctx1 == self['.']
1550 match = match or matchmod.always(self.root, self.getcwd())
1551 match = match or matchmod.always(self.root, self.getcwd())
1551 listignored, listclean, listunknown = ignored, clean, unknown
1552 listignored, listclean, listunknown = ignored, clean, unknown
1552
1553
1553 # load earliest manifest first for caching reasons
1554 # load earliest manifest first for caching reasons
1554 if not working and ctx2.rev() < ctx1.rev():
1555 if not working and ctx2.rev() < ctx1.rev():
1555 ctx2.manifest()
1556 ctx2.manifest()
1556
1557
1557 if not parentworking:
1558 if not parentworking:
1558 def bad(f, msg):
1559 def bad(f, msg):
1559 # 'f' may be a directory pattern from 'match.files()',
1560 # 'f' may be a directory pattern from 'match.files()',
1560 # so 'f not in ctx1' is not enough
1561 # so 'f not in ctx1' is not enough
1561 if f not in ctx1 and f not in ctx1.dirs():
1562 if f not in ctx1 and f not in ctx1.dirs():
1562 self.ui.warn('%s: %s\n' % (self.dirstate.pathto(f), msg))
1563 self.ui.warn('%s: %s\n' % (self.dirstate.pathto(f), msg))
1563 match.bad = bad
1564 match.bad = bad
1564
1565
1565 if working: # we need to scan the working dir
1566 if working: # we need to scan the working dir
1566 subrepos = []
1567 subrepos = []
1567 if '.hgsub' in self.dirstate:
1568 if '.hgsub' in self.dirstate:
1568 subrepos = ctx2.substate.keys()
1569 subrepos = ctx2.substate.keys()
1569 s = self.dirstate.status(match, subrepos, listignored,
1570 s = self.dirstate.status(match, subrepos, listignored,
1570 listclean, listunknown)
1571 listclean, listunknown)
1571 cmp, modified, added, removed, deleted, unknown, ignored, clean = s
1572 cmp, modified, added, removed, deleted, unknown, ignored, clean = s
1572
1573
1573 # check for any possibly clean files
1574 # check for any possibly clean files
1574 if parentworking and cmp:
1575 if parentworking and cmp:
1575 fixup = []
1576 fixup = []
1576 # do a full compare of any files that might have changed
1577 # do a full compare of any files that might have changed
1577 for f in sorted(cmp):
1578 for f in sorted(cmp):
1578 if (f not in ctx1 or ctx2.flags(f) != ctx1.flags(f)
1579 if (f not in ctx1 or ctx2.flags(f) != ctx1.flags(f)
1579 or ctx1[f].cmp(ctx2[f])):
1580 or ctx1[f].cmp(ctx2[f])):
1580 modified.append(f)
1581 modified.append(f)
1581 else:
1582 else:
1582 fixup.append(f)
1583 fixup.append(f)
1583
1584
1584 # update dirstate for files that are actually clean
1585 # update dirstate for files that are actually clean
1585 if fixup:
1586 if fixup:
1586 if listclean:
1587 if listclean:
1587 clean += fixup
1588 clean += fixup
1588
1589
1589 try:
1590 try:
1590 # updating the dirstate is optional
1591 # updating the dirstate is optional
1591 # so we don't wait on the lock
1592 # so we don't wait on the lock
1592 wlock = self.wlock(False)
1593 wlock = self.wlock(False)
1593 try:
1594 try:
1594 for f in fixup:
1595 for f in fixup:
1595 self.dirstate.normal(f)
1596 self.dirstate.normal(f)
1596 finally:
1597 finally:
1597 wlock.release()
1598 wlock.release()
1598 except error.LockError:
1599 except error.LockError:
1599 pass
1600 pass
1600
1601
1601 if not parentworking:
1602 if not parentworking:
1602 mf1 = mfmatches(ctx1)
1603 mf1 = mfmatches(ctx1)
1603 if working:
1604 if working:
1604 # we are comparing working dir against non-parent
1605 # we are comparing working dir against non-parent
1605 # generate a pseudo-manifest for the working dir
1606 # generate a pseudo-manifest for the working dir
1606 mf2 = mfmatches(self['.'])
1607 mf2 = mfmatches(self['.'])
1607 for f in cmp + modified + added:
1608 for f in cmp + modified + added:
1608 mf2[f] = None
1609 mf2[f] = None
1609 mf2.set(f, ctx2.flags(f))
1610 mf2.set(f, ctx2.flags(f))
1610 for f in removed:
1611 for f in removed:
1611 if f in mf2:
1612 if f in mf2:
1612 del mf2[f]
1613 del mf2[f]
1613 else:
1614 else:
1614 # we are comparing two revisions
1615 # we are comparing two revisions
1615 deleted, unknown, ignored = [], [], []
1616 deleted, unknown, ignored = [], [], []
1616 mf2 = mfmatches(ctx2)
1617 mf2 = mfmatches(ctx2)
1617
1618
1618 modified, added, clean = [], [], []
1619 modified, added, clean = [], [], []
1619 withflags = mf1.withflags() | mf2.withflags()
1620 withflags = mf1.withflags() | mf2.withflags()
1620 for fn in mf2:
1621 for fn in mf2:
1621 if fn in mf1:
1622 if fn in mf1:
1622 if (fn not in deleted and
1623 if (fn not in deleted and
1623 ((fn in withflags and mf1.flags(fn) != mf2.flags(fn)) or
1624 ((fn in withflags and mf1.flags(fn) != mf2.flags(fn)) or
1624 (mf1[fn] != mf2[fn] and
1625 (mf1[fn] != mf2[fn] and
1625 (mf2[fn] or ctx1[fn].cmp(ctx2[fn]))))):
1626 (mf2[fn] or ctx1[fn].cmp(ctx2[fn]))))):
1626 modified.append(fn)
1627 modified.append(fn)
1627 elif listclean:
1628 elif listclean:
1628 clean.append(fn)
1629 clean.append(fn)
1629 del mf1[fn]
1630 del mf1[fn]
1630 elif fn not in deleted:
1631 elif fn not in deleted:
1631 added.append(fn)
1632 added.append(fn)
1632 removed = mf1.keys()
1633 removed = mf1.keys()
1633
1634
1634 if working and modified and not self.dirstate._checklink:
1635 if working and modified and not self.dirstate._checklink:
1635 # Symlink placeholders may get non-symlink-like contents
1636 # Symlink placeholders may get non-symlink-like contents
1636 # via user error or dereferencing by NFS or Samba servers,
1637 # via user error or dereferencing by NFS or Samba servers,
1637 # so we filter out any placeholders that don't look like a
1638 # so we filter out any placeholders that don't look like a
1638 # symlink
1639 # symlink
1639 sane = []
1640 sane = []
1640 for f in modified:
1641 for f in modified:
1641 if ctx2.flags(f) == 'l':
1642 if ctx2.flags(f) == 'l':
1642 d = ctx2[f].data()
1643 d = ctx2[f].data()
1643 if len(d) >= 1024 or '\n' in d or util.binary(d):
1644 if len(d) >= 1024 or '\n' in d or util.binary(d):
1644 self.ui.debug('ignoring suspect symlink placeholder'
1645 self.ui.debug('ignoring suspect symlink placeholder'
1645 ' "%s"\n' % f)
1646 ' "%s"\n' % f)
1646 continue
1647 continue
1647 sane.append(f)
1648 sane.append(f)
1648 modified = sane
1649 modified = sane
1649
1650
1650 r = modified, added, removed, deleted, unknown, ignored, clean
1651 r = modified, added, removed, deleted, unknown, ignored, clean
1651
1652
1652 if listsubrepos:
1653 if listsubrepos:
1653 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
1654 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
1654 if working:
1655 if working:
1655 rev2 = None
1656 rev2 = None
1656 else:
1657 else:
1657 rev2 = ctx2.substate[subpath][1]
1658 rev2 = ctx2.substate[subpath][1]
1658 try:
1659 try:
1659 submatch = matchmod.narrowmatcher(subpath, match)
1660 submatch = matchmod.narrowmatcher(subpath, match)
1660 s = sub.status(rev2, match=submatch, ignored=listignored,
1661 s = sub.status(rev2, match=submatch, ignored=listignored,
1661 clean=listclean, unknown=listunknown,
1662 clean=listclean, unknown=listunknown,
1662 listsubrepos=True)
1663 listsubrepos=True)
1663 for rfiles, sfiles in zip(r, s):
1664 for rfiles, sfiles in zip(r, s):
1664 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
1665 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
1665 except error.LookupError:
1666 except error.LookupError:
1666 self.ui.status(_("skipping missing subrepository: %s\n")
1667 self.ui.status(_("skipping missing subrepository: %s\n")
1667 % subpath)
1668 % subpath)
1668
1669
1669 for l in r:
1670 for l in r:
1670 l.sort()
1671 l.sort()
1671 return r
1672 return r
1672
1673
1673 def heads(self, start=None):
1674 def heads(self, start=None):
1674 heads = self.changelog.heads(start)
1675 heads = self.changelog.heads(start)
1675 # sort the output in rev descending order
1676 # sort the output in rev descending order
1676 return sorted(heads, key=self.changelog.rev, reverse=True)
1677 return sorted(heads, key=self.changelog.rev, reverse=True)
1677
1678
1678 def branchheads(self, branch=None, start=None, closed=False):
1679 def branchheads(self, branch=None, start=None, closed=False):
1679 '''return a (possibly filtered) list of heads for the given branch
1680 '''return a (possibly filtered) list of heads for the given branch
1680
1681
1681 Heads are returned in topological order, from newest to oldest.
1682 Heads are returned in topological order, from newest to oldest.
1682 If branch is None, use the dirstate branch.
1683 If branch is None, use the dirstate branch.
1683 If start is not None, return only heads reachable from start.
1684 If start is not None, return only heads reachable from start.
1684 If closed is True, return heads that are marked as closed as well.
1685 If closed is True, return heads that are marked as closed as well.
1685 '''
1686 '''
1686 if branch is None:
1687 if branch is None:
1687 branch = self[None].branch()
1688 branch = self[None].branch()
1688 branches = self.branchmap()
1689 branches = self.branchmap()
1689 if branch not in branches:
1690 if branch not in branches:
1690 return []
1691 return []
1691 # the cache returns heads ordered lowest to highest
1692 # the cache returns heads ordered lowest to highest
1692 bheads = list(reversed(branches[branch]))
1693 bheads = list(reversed(branches[branch]))
1693 if start is not None:
1694 if start is not None:
1694 # filter out the heads that cannot be reached from startrev
1695 # filter out the heads that cannot be reached from startrev
1695 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1696 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1696 bheads = [h for h in bheads if h in fbheads]
1697 bheads = [h for h in bheads if h in fbheads]
1697 if not closed:
1698 if not closed:
1698 bheads = [h for h in bheads if not self[h].closesbranch()]
1699 bheads = [h for h in bheads if not self[h].closesbranch()]
1699 return bheads
1700 return bheads
1700
1701
1701 def branches(self, nodes):
1702 def branches(self, nodes):
1702 if not nodes:
1703 if not nodes:
1703 nodes = [self.changelog.tip()]
1704 nodes = [self.changelog.tip()]
1704 b = []
1705 b = []
1705 for n in nodes:
1706 for n in nodes:
1706 t = n
1707 t = n
1707 while True:
1708 while True:
1708 p = self.changelog.parents(n)
1709 p = self.changelog.parents(n)
1709 if p[1] != nullid or p[0] == nullid:
1710 if p[1] != nullid or p[0] == nullid:
1710 b.append((t, n, p[0], p[1]))
1711 b.append((t, n, p[0], p[1]))
1711 break
1712 break
1712 n = p[0]
1713 n = p[0]
1713 return b
1714 return b
1714
1715
1715 def between(self, pairs):
1716 def between(self, pairs):
1716 r = []
1717 r = []
1717
1718
1718 for top, bottom in pairs:
1719 for top, bottom in pairs:
1719 n, l, i = top, [], 0
1720 n, l, i = top, [], 0
1720 f = 1
1721 f = 1
1721
1722
1722 while n != bottom and n != nullid:
1723 while n != bottom and n != nullid:
1723 p = self.changelog.parents(n)[0]
1724 p = self.changelog.parents(n)[0]
1724 if i == f:
1725 if i == f:
1725 l.append(n)
1726 l.append(n)
1726 f = f * 2
1727 f = f * 2
1727 n = p
1728 n = p
1728 i += 1
1729 i += 1
1729
1730
1730 r.append(l)
1731 r.append(l)
1731
1732
1732 return r
1733 return r
1733
1734
1734 def pull(self, remote, heads=None, force=False):
1735 def pull(self, remote, heads=None, force=False):
1735 # don't open transaction for nothing or you break future useful
1736 # don't open transaction for nothing or you break future useful
1736 # rollback call
1737 # rollback call
1737 tr = None
1738 tr = None
1738 trname = 'pull\n' + util.hidepassword(remote.url())
1739 trname = 'pull\n' + util.hidepassword(remote.url())
1739 lock = self.lock()
1740 lock = self.lock()
1740 try:
1741 try:
1741 tmp = discovery.findcommonincoming(self, remote, heads=heads,
1742 tmp = discovery.findcommonincoming(self, remote, heads=heads,
1742 force=force)
1743 force=force)
1743 common, fetch, rheads = tmp
1744 common, fetch, rheads = tmp
1744 if not fetch:
1745 if not fetch:
1745 self.ui.status(_("no changes found\n"))
1746 self.ui.status(_("no changes found\n"))
1746 added = []
1747 added = []
1747 result = 0
1748 result = 0
1748 else:
1749 else:
1749 tr = self.transaction(trname)
1750 tr = self.transaction(trname)
1750 if heads is None and list(common) == [nullid]:
1751 if heads is None and list(common) == [nullid]:
1751 self.ui.status(_("requesting all changes\n"))
1752 self.ui.status(_("requesting all changes\n"))
1752 elif heads is None and remote.capable('changegroupsubset'):
1753 elif heads is None and remote.capable('changegroupsubset'):
1753 # issue1320, avoid a race if remote changed after discovery
1754 # issue1320, avoid a race if remote changed after discovery
1754 heads = rheads
1755 heads = rheads
1755
1756
1756 if remote.capable('getbundle'):
1757 if remote.capable('getbundle'):
1757 cg = remote.getbundle('pull', common=common,
1758 cg = remote.getbundle('pull', common=common,
1758 heads=heads or rheads)
1759 heads=heads or rheads)
1759 elif heads is None:
1760 elif heads is None:
1760 cg = remote.changegroup(fetch, 'pull')
1761 cg = remote.changegroup(fetch, 'pull')
1761 elif not remote.capable('changegroupsubset'):
1762 elif not remote.capable('changegroupsubset'):
1762 raise util.Abort(_("partial pull cannot be done because "
1763 raise util.Abort(_("partial pull cannot be done because "
1763 "other repository doesn't support "
1764 "other repository doesn't support "
1764 "changegroupsubset."))
1765 "changegroupsubset."))
1765 else:
1766 else:
1766 cg = remote.changegroupsubset(fetch, heads, 'pull')
1767 cg = remote.changegroupsubset(fetch, heads, 'pull')
1767 clstart = len(self.changelog)
1768 clstart = len(self.changelog)
1768 result = self.addchangegroup(cg, 'pull', remote.url())
1769 result = self.addchangegroup(cg, 'pull', remote.url())
1769 clend = len(self.changelog)
1770 clend = len(self.changelog)
1770 added = [self.changelog.node(r) for r in xrange(clstart, clend)]
1771 added = [self.changelog.node(r) for r in xrange(clstart, clend)]
1771
1772
1772 # compute target subset
1773 # compute target subset
1773 if heads is None:
1774 if heads is None:
1774 # We pulled every thing possible
1775 # We pulled every thing possible
1775 # sync on everything common
1776 # sync on everything common
1776 subset = common + added
1777 subset = common + added
1777 else:
1778 else:
1778 # We pulled a specific subset
1779 # We pulled a specific subset
1779 # sync on this subset
1780 # sync on this subset
1780 subset = heads
1781 subset = heads
1781
1782
1782 # Get remote phases data from remote
1783 # Get remote phases data from remote
1783 remotephases = remote.listkeys('phases')
1784 remotephases = remote.listkeys('phases')
1784 publishing = bool(remotephases.get('publishing', False))
1785 publishing = bool(remotephases.get('publishing', False))
1785 if remotephases and not publishing:
1786 if remotephases and not publishing:
1786 # remote is new and unpublishing
1787 # remote is new and unpublishing
1787 pheads, _dr = phases.analyzeremotephases(self, subset,
1788 pheads, _dr = phases.analyzeremotephases(self, subset,
1788 remotephases)
1789 remotephases)
1789 phases.advanceboundary(self, phases.public, pheads)
1790 phases.advanceboundary(self, phases.public, pheads)
1790 phases.advanceboundary(self, phases.draft, subset)
1791 phases.advanceboundary(self, phases.draft, subset)
1791 else:
1792 else:
1792 # Remote is old or publishing all common changesets
1793 # Remote is old or publishing all common changesets
1793 # should be seen as public
1794 # should be seen as public
1794 phases.advanceboundary(self, phases.public, subset)
1795 phases.advanceboundary(self, phases.public, subset)
1795
1796
1796 if obsolete._enabled:
1797 if obsolete._enabled:
1797 self.ui.debug('fetching remote obsolete markers')
1798 self.ui.debug('fetching remote obsolete markers')
1798 remoteobs = remote.listkeys('obsolete')
1799 remoteobs = remote.listkeys('obsolete')
1799 if 'dump0' in remoteobs:
1800 if 'dump0' in remoteobs:
1800 if tr is None:
1801 if tr is None:
1801 tr = self.transaction(trname)
1802 tr = self.transaction(trname)
1802 for key in sorted(remoteobs, reverse=True):
1803 for key in sorted(remoteobs, reverse=True):
1803 if key.startswith('dump'):
1804 if key.startswith('dump'):
1804 data = base85.b85decode(remoteobs[key])
1805 data = base85.b85decode(remoteobs[key])
1805 self.obsstore.mergemarkers(tr, data)
1806 self.obsstore.mergemarkers(tr, data)
1806 if tr is not None:
1807 if tr is not None:
1807 tr.close()
1808 tr.close()
1808 finally:
1809 finally:
1809 if tr is not None:
1810 if tr is not None:
1810 tr.release()
1811 tr.release()
1811 lock.release()
1812 lock.release()
1812
1813
1813 return result
1814 return result
1814
1815
1815 def checkpush(self, force, revs):
1816 def checkpush(self, force, revs):
1816 """Extensions can override this function if additional checks have
1817 """Extensions can override this function if additional checks have
1817 to be performed before pushing, or call it if they override push
1818 to be performed before pushing, or call it if they override push
1818 command.
1819 command.
1819 """
1820 """
1820 pass
1821 pass
1821
1822
1822 def push(self, remote, force=False, revs=None, newbranch=False):
1823 def push(self, remote, force=False, revs=None, newbranch=False):
1823 '''Push outgoing changesets (limited by revs) from the current
1824 '''Push outgoing changesets (limited by revs) from the current
1824 repository to remote. Return an integer:
1825 repository to remote. Return an integer:
1825 - None means nothing to push
1826 - None means nothing to push
1826 - 0 means HTTP error
1827 - 0 means HTTP error
1827 - 1 means we pushed and remote head count is unchanged *or*
1828 - 1 means we pushed and remote head count is unchanged *or*
1828 we have outgoing changesets but refused to push
1829 we have outgoing changesets but refused to push
1829 - other values as described by addchangegroup()
1830 - other values as described by addchangegroup()
1830 '''
1831 '''
1831 # there are two ways to push to remote repo:
1832 # there are two ways to push to remote repo:
1832 #
1833 #
1833 # addchangegroup assumes local user can lock remote
1834 # addchangegroup assumes local user can lock remote
1834 # repo (local filesystem, old ssh servers).
1835 # repo (local filesystem, old ssh servers).
1835 #
1836 #
1836 # unbundle assumes local user cannot lock remote repo (new ssh
1837 # unbundle assumes local user cannot lock remote repo (new ssh
1837 # servers, http servers).
1838 # servers, http servers).
1838
1839
1839 if not remote.canpush():
1840 if not remote.canpush():
1840 raise util.Abort(_("destination does not support push"))
1841 raise util.Abort(_("destination does not support push"))
1841 # get local lock as we might write phase data
1842 # get local lock as we might write phase data
1842 locallock = self.lock()
1843 locallock = self.lock()
1843 try:
1844 try:
1844 self.checkpush(force, revs)
1845 self.checkpush(force, revs)
1845 lock = None
1846 lock = None
1846 unbundle = remote.capable('unbundle')
1847 unbundle = remote.capable('unbundle')
1847 if not unbundle:
1848 if not unbundle:
1848 lock = remote.lock()
1849 lock = remote.lock()
1849 try:
1850 try:
1850 # discovery
1851 # discovery
1851 fci = discovery.findcommonincoming
1852 fci = discovery.findcommonincoming
1852 commoninc = fci(self, remote, force=force)
1853 commoninc = fci(self, remote, force=force)
1853 common, inc, remoteheads = commoninc
1854 common, inc, remoteheads = commoninc
1854 fco = discovery.findcommonoutgoing
1855 fco = discovery.findcommonoutgoing
1855 outgoing = fco(self, remote, onlyheads=revs,
1856 outgoing = fco(self, remote, onlyheads=revs,
1856 commoninc=commoninc, force=force)
1857 commoninc=commoninc, force=force)
1857
1858
1858
1859
1859 if not outgoing.missing:
1860 if not outgoing.missing:
1860 # nothing to push
1861 # nothing to push
1861 scmutil.nochangesfound(self.ui, self, outgoing.excluded)
1862 scmutil.nochangesfound(self.ui, self, outgoing.excluded)
1862 ret = None
1863 ret = None
1863 else:
1864 else:
1864 # something to push
1865 # something to push
1865 if not force:
1866 if not force:
1866 # if self.obsstore == False --> no obsolete
1867 # if self.obsstore == False --> no obsolete
1867 # then, save the iteration
1868 # then, save the iteration
1868 if self.obsstore:
1869 if self.obsstore:
1869 # this message are here for 80 char limit reason
1870 # this message are here for 80 char limit reason
1870 mso = _("push includes an obsolete changeset: %s!")
1871 mso = _("push includes an obsolete changeset: %s!")
1871 msu = _("push includes an unstable changeset: %s!")
1872 msu = _("push includes an unstable changeset: %s!")
1872 # If we are to push if there is at least one
1873 # If we are to push if there is at least one
1873 # obsolete or unstable changeset in missing, at
1874 # obsolete or unstable changeset in missing, at
1874 # least one of the missinghead will be obsolete or
1875 # least one of the missinghead will be obsolete or
1875 # unstable. So checking heads only is ok
1876 # unstable. So checking heads only is ok
1876 for node in outgoing.missingheads:
1877 for node in outgoing.missingheads:
1877 ctx = self[node]
1878 ctx = self[node]
1878 if ctx.obsolete():
1879 if ctx.obsolete():
1879 raise util.Abort(_(mso) % ctx)
1880 raise util.Abort(_(mso) % ctx)
1880 elif ctx.unstable():
1881 elif ctx.unstable():
1881 raise util.Abort(_(msu) % ctx)
1882 raise util.Abort(_(msu) % ctx)
1882 discovery.checkheads(self, remote, outgoing,
1883 discovery.checkheads(self, remote, outgoing,
1883 remoteheads, newbranch,
1884 remoteheads, newbranch,
1884 bool(inc))
1885 bool(inc))
1885
1886
1886 # create a changegroup from local
1887 # create a changegroup from local
1887 if revs is None and not outgoing.excluded:
1888 if revs is None and not outgoing.excluded:
1888 # push everything,
1889 # push everything,
1889 # use the fast path, no race possible on push
1890 # use the fast path, no race possible on push
1890 cg = self._changegroup(outgoing.missing, 'push')
1891 cg = self._changegroup(outgoing.missing, 'push')
1891 else:
1892 else:
1892 cg = self.getlocalbundle('push', outgoing)
1893 cg = self.getlocalbundle('push', outgoing)
1893
1894
1894 # apply changegroup to remote
1895 # apply changegroup to remote
1895 if unbundle:
1896 if unbundle:
1896 # local repo finds heads on server, finds out what
1897 # local repo finds heads on server, finds out what
1897 # revs it must push. once revs transferred, if server
1898 # revs it must push. once revs transferred, if server
1898 # finds it has different heads (someone else won
1899 # finds it has different heads (someone else won
1899 # commit/push race), server aborts.
1900 # commit/push race), server aborts.
1900 if force:
1901 if force:
1901 remoteheads = ['force']
1902 remoteheads = ['force']
1902 # ssh: return remote's addchangegroup()
1903 # ssh: return remote's addchangegroup()
1903 # http: return remote's addchangegroup() or 0 for error
1904 # http: return remote's addchangegroup() or 0 for error
1904 ret = remote.unbundle(cg, remoteheads, 'push')
1905 ret = remote.unbundle(cg, remoteheads, 'push')
1905 else:
1906 else:
1906 # we return an integer indicating remote head count
1907 # we return an integer indicating remote head count
1907 # change
1908 # change
1908 ret = remote.addchangegroup(cg, 'push', self.url())
1909 ret = remote.addchangegroup(cg, 'push', self.url())
1909
1910
1910 if ret:
1911 if ret:
1911 # push succeed, synchronize target of the push
1912 # push succeed, synchronize target of the push
1912 cheads = outgoing.missingheads
1913 cheads = outgoing.missingheads
1913 elif revs is None:
1914 elif revs is None:
1914 # All out push fails. synchronize all common
1915 # All out push fails. synchronize all common
1915 cheads = outgoing.commonheads
1916 cheads = outgoing.commonheads
1916 else:
1917 else:
1917 # I want cheads = heads(::missingheads and ::commonheads)
1918 # I want cheads = heads(::missingheads and ::commonheads)
1918 # (missingheads is revs with secret changeset filtered out)
1919 # (missingheads is revs with secret changeset filtered out)
1919 #
1920 #
1920 # This can be expressed as:
1921 # This can be expressed as:
1921 # cheads = ( (missingheads and ::commonheads)
1922 # cheads = ( (missingheads and ::commonheads)
1922 # + (commonheads and ::missingheads))"
1923 # + (commonheads and ::missingheads))"
1923 # )
1924 # )
1924 #
1925 #
1925 # while trying to push we already computed the following:
1926 # while trying to push we already computed the following:
1926 # common = (::commonheads)
1927 # common = (::commonheads)
1927 # missing = ((commonheads::missingheads) - commonheads)
1928 # missing = ((commonheads::missingheads) - commonheads)
1928 #
1929 #
1929 # We can pick:
1930 # We can pick:
1930 # * missingheads part of common (::commonheads)
1931 # * missingheads part of common (::commonheads)
1931 common = set(outgoing.common)
1932 common = set(outgoing.common)
1932 cheads = [node for node in revs if node in common]
1933 cheads = [node for node in revs if node in common]
1933 # and
1934 # and
1934 # * commonheads parents on missing
1935 # * commonheads parents on missing
1935 revset = self.set('%ln and parents(roots(%ln))',
1936 revset = self.set('%ln and parents(roots(%ln))',
1936 outgoing.commonheads,
1937 outgoing.commonheads,
1937 outgoing.missing)
1938 outgoing.missing)
1938 cheads.extend(c.node() for c in revset)
1939 cheads.extend(c.node() for c in revset)
1939 # even when we don't push, exchanging phase data is useful
1940 # even when we don't push, exchanging phase data is useful
1940 remotephases = remote.listkeys('phases')
1941 remotephases = remote.listkeys('phases')
1941 if not remotephases: # old server or public only repo
1942 if not remotephases: # old server or public only repo
1942 phases.advanceboundary(self, phases.public, cheads)
1943 phases.advanceboundary(self, phases.public, cheads)
1943 # don't push any phase data as there is nothing to push
1944 # don't push any phase data as there is nothing to push
1944 else:
1945 else:
1945 ana = phases.analyzeremotephases(self, cheads, remotephases)
1946 ana = phases.analyzeremotephases(self, cheads, remotephases)
1946 pheads, droots = ana
1947 pheads, droots = ana
1947 ### Apply remote phase on local
1948 ### Apply remote phase on local
1948 if remotephases.get('publishing', False):
1949 if remotephases.get('publishing', False):
1949 phases.advanceboundary(self, phases.public, cheads)
1950 phases.advanceboundary(self, phases.public, cheads)
1950 else: # publish = False
1951 else: # publish = False
1951 phases.advanceboundary(self, phases.public, pheads)
1952 phases.advanceboundary(self, phases.public, pheads)
1952 phases.advanceboundary(self, phases.draft, cheads)
1953 phases.advanceboundary(self, phases.draft, cheads)
1953 ### Apply local phase on remote
1954 ### Apply local phase on remote
1954
1955
1955 # Get the list of all revs draft on remote by public here.
1956 # Get the list of all revs draft on remote by public here.
1956 # XXX Beware that revset break if droots is not strictly
1957 # XXX Beware that revset break if droots is not strictly
1957 # XXX root we may want to ensure it is but it is costly
1958 # XXX root we may want to ensure it is but it is costly
1958 outdated = self.set('heads((%ln::%ln) and public())',
1959 outdated = self.set('heads((%ln::%ln) and public())',
1959 droots, cheads)
1960 droots, cheads)
1960 for newremotehead in outdated:
1961 for newremotehead in outdated:
1961 r = remote.pushkey('phases',
1962 r = remote.pushkey('phases',
1962 newremotehead.hex(),
1963 newremotehead.hex(),
1963 str(phases.draft),
1964 str(phases.draft),
1964 str(phases.public))
1965 str(phases.public))
1965 if not r:
1966 if not r:
1966 self.ui.warn(_('updating %s to public failed!\n')
1967 self.ui.warn(_('updating %s to public failed!\n')
1967 % newremotehead)
1968 % newremotehead)
1968 self.ui.debug('try to push obsolete markers to remote\n')
1969 self.ui.debug('try to push obsolete markers to remote\n')
1969 if (obsolete._enabled and self.obsstore and
1970 if (obsolete._enabled and self.obsstore and
1970 'obsolete' in remote.listkeys('namespaces')):
1971 'obsolete' in remote.listkeys('namespaces')):
1971 rslts = []
1972 rslts = []
1972 remotedata = self.listkeys('obsolete')
1973 remotedata = self.listkeys('obsolete')
1973 for key in sorted(remotedata, reverse=True):
1974 for key in sorted(remotedata, reverse=True):
1974 # reverse sort to ensure we end with dump0
1975 # reverse sort to ensure we end with dump0
1975 data = remotedata[key]
1976 data = remotedata[key]
1976 rslts.append(remote.pushkey('obsolete', key, '', data))
1977 rslts.append(remote.pushkey('obsolete', key, '', data))
1977 if [r for r in rslts if not r]:
1978 if [r for r in rslts if not r]:
1978 msg = _('failed to push some obsolete markers!\n')
1979 msg = _('failed to push some obsolete markers!\n')
1979 self.ui.warn(msg)
1980 self.ui.warn(msg)
1980 finally:
1981 finally:
1981 if lock is not None:
1982 if lock is not None:
1982 lock.release()
1983 lock.release()
1983 finally:
1984 finally:
1984 locallock.release()
1985 locallock.release()
1985
1986
1986 self.ui.debug("checking for updated bookmarks\n")
1987 self.ui.debug("checking for updated bookmarks\n")
1987 rb = remote.listkeys('bookmarks')
1988 rb = remote.listkeys('bookmarks')
1988 for k in rb.keys():
1989 for k in rb.keys():
1989 if k in self._bookmarks:
1990 if k in self._bookmarks:
1990 nr, nl = rb[k], hex(self._bookmarks[k])
1991 nr, nl = rb[k], hex(self._bookmarks[k])
1991 if nr in self:
1992 if nr in self:
1992 cr = self[nr]
1993 cr = self[nr]
1993 cl = self[nl]
1994 cl = self[nl]
1994 if cl in cr.descendants():
1995 if cl in cr.descendants():
1995 r = remote.pushkey('bookmarks', k, nr, nl)
1996 r = remote.pushkey('bookmarks', k, nr, nl)
1996 if r:
1997 if r:
1997 self.ui.status(_("updating bookmark %s\n") % k)
1998 self.ui.status(_("updating bookmark %s\n") % k)
1998 else:
1999 else:
1999 self.ui.warn(_('updating bookmark %s'
2000 self.ui.warn(_('updating bookmark %s'
2000 ' failed!\n') % k)
2001 ' failed!\n') % k)
2001
2002
2002 return ret
2003 return ret
2003
2004
2004 def changegroupinfo(self, nodes, source):
2005 def changegroupinfo(self, nodes, source):
2005 if self.ui.verbose or source == 'bundle':
2006 if self.ui.verbose or source == 'bundle':
2006 self.ui.status(_("%d changesets found\n") % len(nodes))
2007 self.ui.status(_("%d changesets found\n") % len(nodes))
2007 if self.ui.debugflag:
2008 if self.ui.debugflag:
2008 self.ui.debug("list of changesets:\n")
2009 self.ui.debug("list of changesets:\n")
2009 for node in nodes:
2010 for node in nodes:
2010 self.ui.debug("%s\n" % hex(node))
2011 self.ui.debug("%s\n" % hex(node))
2011
2012
2012 def changegroupsubset(self, bases, heads, source):
2013 def changegroupsubset(self, bases, heads, source):
2013 """Compute a changegroup consisting of all the nodes that are
2014 """Compute a changegroup consisting of all the nodes that are
2014 descendants of any of the bases and ancestors of any of the heads.
2015 descendants of any of the bases and ancestors of any of the heads.
2015 Return a chunkbuffer object whose read() method will return
2016 Return a chunkbuffer object whose read() method will return
2016 successive changegroup chunks.
2017 successive changegroup chunks.
2017
2018
2018 It is fairly complex as determining which filenodes and which
2019 It is fairly complex as determining which filenodes and which
2019 manifest nodes need to be included for the changeset to be complete
2020 manifest nodes need to be included for the changeset to be complete
2020 is non-trivial.
2021 is non-trivial.
2021
2022
2022 Another wrinkle is doing the reverse, figuring out which changeset in
2023 Another wrinkle is doing the reverse, figuring out which changeset in
2023 the changegroup a particular filenode or manifestnode belongs to.
2024 the changegroup a particular filenode or manifestnode belongs to.
2024 """
2025 """
2025 cl = self.changelog
2026 cl = self.changelog
2026 if not bases:
2027 if not bases:
2027 bases = [nullid]
2028 bases = [nullid]
2028 csets, bases, heads = cl.nodesbetween(bases, heads)
2029 csets, bases, heads = cl.nodesbetween(bases, heads)
2029 # We assume that all ancestors of bases are known
2030 # We assume that all ancestors of bases are known
2030 common = set(cl.ancestors([cl.rev(n) for n in bases]))
2031 common = set(cl.ancestors([cl.rev(n) for n in bases]))
2031 return self._changegroupsubset(common, csets, heads, source)
2032 return self._changegroupsubset(common, csets, heads, source)
2032
2033
2033 def getlocalbundle(self, source, outgoing):
2034 def getlocalbundle(self, source, outgoing):
2034 """Like getbundle, but taking a discovery.outgoing as an argument.
2035 """Like getbundle, but taking a discovery.outgoing as an argument.
2035
2036
2036 This is only implemented for local repos and reuses potentially
2037 This is only implemented for local repos and reuses potentially
2037 precomputed sets in outgoing."""
2038 precomputed sets in outgoing."""
2038 if not outgoing.missing:
2039 if not outgoing.missing:
2039 return None
2040 return None
2040 return self._changegroupsubset(outgoing.common,
2041 return self._changegroupsubset(outgoing.common,
2041 outgoing.missing,
2042 outgoing.missing,
2042 outgoing.missingheads,
2043 outgoing.missingheads,
2043 source)
2044 source)
2044
2045
2045 def getbundle(self, source, heads=None, common=None):
2046 def getbundle(self, source, heads=None, common=None):
2046 """Like changegroupsubset, but returns the set difference between the
2047 """Like changegroupsubset, but returns the set difference between the
2047 ancestors of heads and the ancestors common.
2048 ancestors of heads and the ancestors common.
2048
2049
2049 If heads is None, use the local heads. If common is None, use [nullid].
2050 If heads is None, use the local heads. If common is None, use [nullid].
2050
2051
2051 The nodes in common might not all be known locally due to the way the
2052 The nodes in common might not all be known locally due to the way the
2052 current discovery protocol works.
2053 current discovery protocol works.
2053 """
2054 """
2054 cl = self.changelog
2055 cl = self.changelog
2055 if common:
2056 if common:
2056 nm = cl.nodemap
2057 nm = cl.nodemap
2057 common = [n for n in common if n in nm]
2058 common = [n for n in common if n in nm]
2058 else:
2059 else:
2059 common = [nullid]
2060 common = [nullid]
2060 if not heads:
2061 if not heads:
2061 heads = cl.heads()
2062 heads = cl.heads()
2062 return self.getlocalbundle(source,
2063 return self.getlocalbundle(source,
2063 discovery.outgoing(cl, common, heads))
2064 discovery.outgoing(cl, common, heads))
2064
2065
2065 def _changegroupsubset(self, commonrevs, csets, heads, source):
2066 def _changegroupsubset(self, commonrevs, csets, heads, source):
2066
2067
2067 cl = self.changelog
2068 cl = self.changelog
2068 mf = self.manifest
2069 mf = self.manifest
2069 mfs = {} # needed manifests
2070 mfs = {} # needed manifests
2070 fnodes = {} # needed file nodes
2071 fnodes = {} # needed file nodes
2071 changedfiles = set()
2072 changedfiles = set()
2072 fstate = ['', {}]
2073 fstate = ['', {}]
2073 count = [0, 0]
2074 count = [0, 0]
2074
2075
2075 # can we go through the fast path ?
2076 # can we go through the fast path ?
2076 heads.sort()
2077 heads.sort()
2077 if heads == sorted(self.heads()):
2078 if heads == sorted(self.heads()):
2078 return self._changegroup(csets, source)
2079 return self._changegroup(csets, source)
2079
2080
2080 # slow path
2081 # slow path
2081 self.hook('preoutgoing', throw=True, source=source)
2082 self.hook('preoutgoing', throw=True, source=source)
2082 self.changegroupinfo(csets, source)
2083 self.changegroupinfo(csets, source)
2083
2084
2084 # filter any nodes that claim to be part of the known set
2085 # filter any nodes that claim to be part of the known set
2085 def prune(revlog, missing):
2086 def prune(revlog, missing):
2086 rr, rl = revlog.rev, revlog.linkrev
2087 rr, rl = revlog.rev, revlog.linkrev
2087 return [n for n in missing
2088 return [n for n in missing
2088 if rl(rr(n)) not in commonrevs]
2089 if rl(rr(n)) not in commonrevs]
2089
2090
2090 progress = self.ui.progress
2091 progress = self.ui.progress
2091 _bundling = _('bundling')
2092 _bundling = _('bundling')
2092 _changesets = _('changesets')
2093 _changesets = _('changesets')
2093 _manifests = _('manifests')
2094 _manifests = _('manifests')
2094 _files = _('files')
2095 _files = _('files')
2095
2096
2096 def lookup(revlog, x):
2097 def lookup(revlog, x):
2097 if revlog == cl:
2098 if revlog == cl:
2098 c = cl.read(x)
2099 c = cl.read(x)
2099 changedfiles.update(c[3])
2100 changedfiles.update(c[3])
2100 mfs.setdefault(c[0], x)
2101 mfs.setdefault(c[0], x)
2101 count[0] += 1
2102 count[0] += 1
2102 progress(_bundling, count[0],
2103 progress(_bundling, count[0],
2103 unit=_changesets, total=count[1])
2104 unit=_changesets, total=count[1])
2104 return x
2105 return x
2105 elif revlog == mf:
2106 elif revlog == mf:
2106 clnode = mfs[x]
2107 clnode = mfs[x]
2107 mdata = mf.readfast(x)
2108 mdata = mf.readfast(x)
2108 for f, n in mdata.iteritems():
2109 for f, n in mdata.iteritems():
2109 if f in changedfiles:
2110 if f in changedfiles:
2110 fnodes[f].setdefault(n, clnode)
2111 fnodes[f].setdefault(n, clnode)
2111 count[0] += 1
2112 count[0] += 1
2112 progress(_bundling, count[0],
2113 progress(_bundling, count[0],
2113 unit=_manifests, total=count[1])
2114 unit=_manifests, total=count[1])
2114 return clnode
2115 return clnode
2115 else:
2116 else:
2116 progress(_bundling, count[0], item=fstate[0],
2117 progress(_bundling, count[0], item=fstate[0],
2117 unit=_files, total=count[1])
2118 unit=_files, total=count[1])
2118 return fstate[1][x]
2119 return fstate[1][x]
2119
2120
2120 bundler = changegroup.bundle10(lookup)
2121 bundler = changegroup.bundle10(lookup)
2121 reorder = self.ui.config('bundle', 'reorder', 'auto')
2122 reorder = self.ui.config('bundle', 'reorder', 'auto')
2122 if reorder == 'auto':
2123 if reorder == 'auto':
2123 reorder = None
2124 reorder = None
2124 else:
2125 else:
2125 reorder = util.parsebool(reorder)
2126 reorder = util.parsebool(reorder)
2126
2127
2127 def gengroup():
2128 def gengroup():
2128 # Create a changenode group generator that will call our functions
2129 # Create a changenode group generator that will call our functions
2129 # back to lookup the owning changenode and collect information.
2130 # back to lookup the owning changenode and collect information.
2130 count[:] = [0, len(csets)]
2131 count[:] = [0, len(csets)]
2131 for chunk in cl.group(csets, bundler, reorder=reorder):
2132 for chunk in cl.group(csets, bundler, reorder=reorder):
2132 yield chunk
2133 yield chunk
2133 progress(_bundling, None)
2134 progress(_bundling, None)
2134
2135
2135 # Create a generator for the manifestnodes that calls our lookup
2136 # Create a generator for the manifestnodes that calls our lookup
2136 # and data collection functions back.
2137 # and data collection functions back.
2137 for f in changedfiles:
2138 for f in changedfiles:
2138 fnodes[f] = {}
2139 fnodes[f] = {}
2139 count[:] = [0, len(mfs)]
2140 count[:] = [0, len(mfs)]
2140 for chunk in mf.group(prune(mf, mfs), bundler, reorder=reorder):
2141 for chunk in mf.group(prune(mf, mfs), bundler, reorder=reorder):
2141 yield chunk
2142 yield chunk
2142 progress(_bundling, None)
2143 progress(_bundling, None)
2143
2144
2144 mfs.clear()
2145 mfs.clear()
2145
2146
2146 # Go through all our files in order sorted by name.
2147 # Go through all our files in order sorted by name.
2147 count[:] = [0, len(changedfiles)]
2148 count[:] = [0, len(changedfiles)]
2148 for fname in sorted(changedfiles):
2149 for fname in sorted(changedfiles):
2149 filerevlog = self.file(fname)
2150 filerevlog = self.file(fname)
2150 if not len(filerevlog):
2151 if not len(filerevlog):
2151 raise util.Abort(_("empty or missing revlog for %s")
2152 raise util.Abort(_("empty or missing revlog for %s")
2152 % fname)
2153 % fname)
2153 fstate[0] = fname
2154 fstate[0] = fname
2154 fstate[1] = fnodes.pop(fname, {})
2155 fstate[1] = fnodes.pop(fname, {})
2155
2156
2156 nodelist = prune(filerevlog, fstate[1])
2157 nodelist = prune(filerevlog, fstate[1])
2157 if nodelist:
2158 if nodelist:
2158 count[0] += 1
2159 count[0] += 1
2159 yield bundler.fileheader(fname)
2160 yield bundler.fileheader(fname)
2160 for chunk in filerevlog.group(nodelist, bundler, reorder):
2161 for chunk in filerevlog.group(nodelist, bundler, reorder):
2161 yield chunk
2162 yield chunk
2162
2163
2163 # Signal that no more groups are left.
2164 # Signal that no more groups are left.
2164 yield bundler.close()
2165 yield bundler.close()
2165 progress(_bundling, None)
2166 progress(_bundling, None)
2166
2167
2167 if csets:
2168 if csets:
2168 self.hook('outgoing', node=hex(csets[0]), source=source)
2169 self.hook('outgoing', node=hex(csets[0]), source=source)
2169
2170
2170 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
2171 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
2171
2172
2172 def changegroup(self, basenodes, source):
2173 def changegroup(self, basenodes, source):
2173 # to avoid a race we use changegroupsubset() (issue1320)
2174 # to avoid a race we use changegroupsubset() (issue1320)
2174 return self.changegroupsubset(basenodes, self.heads(), source)
2175 return self.changegroupsubset(basenodes, self.heads(), source)
2175
2176
2176 def _changegroup(self, nodes, source):
2177 def _changegroup(self, nodes, source):
2177 """Compute the changegroup of all nodes that we have that a recipient
2178 """Compute the changegroup of all nodes that we have that a recipient
2178 doesn't. Return a chunkbuffer object whose read() method will return
2179 doesn't. Return a chunkbuffer object whose read() method will return
2179 successive changegroup chunks.
2180 successive changegroup chunks.
2180
2181
2181 This is much easier than the previous function as we can assume that
2182 This is much easier than the previous function as we can assume that
2182 the recipient has any changenode we aren't sending them.
2183 the recipient has any changenode we aren't sending them.
2183
2184
2184 nodes is the set of nodes to send"""
2185 nodes is the set of nodes to send"""
2185
2186
2186 cl = self.changelog
2187 cl = self.changelog
2187 mf = self.manifest
2188 mf = self.manifest
2188 mfs = {}
2189 mfs = {}
2189 changedfiles = set()
2190 changedfiles = set()
2190 fstate = ['']
2191 fstate = ['']
2191 count = [0, 0]
2192 count = [0, 0]
2192
2193
2193 self.hook('preoutgoing', throw=True, source=source)
2194 self.hook('preoutgoing', throw=True, source=source)
2194 self.changegroupinfo(nodes, source)
2195 self.changegroupinfo(nodes, source)
2195
2196
2196 revset = set([cl.rev(n) for n in nodes])
2197 revset = set([cl.rev(n) for n in nodes])
2197
2198
2198 def gennodelst(log):
2199 def gennodelst(log):
2199 ln, llr = log.node, log.linkrev
2200 ln, llr = log.node, log.linkrev
2200 return [ln(r) for r in log if llr(r) in revset]
2201 return [ln(r) for r in log if llr(r) in revset]
2201
2202
2202 progress = self.ui.progress
2203 progress = self.ui.progress
2203 _bundling = _('bundling')
2204 _bundling = _('bundling')
2204 _changesets = _('changesets')
2205 _changesets = _('changesets')
2205 _manifests = _('manifests')
2206 _manifests = _('manifests')
2206 _files = _('files')
2207 _files = _('files')
2207
2208
2208 def lookup(revlog, x):
2209 def lookup(revlog, x):
2209 if revlog == cl:
2210 if revlog == cl:
2210 c = cl.read(x)
2211 c = cl.read(x)
2211 changedfiles.update(c[3])
2212 changedfiles.update(c[3])
2212 mfs.setdefault(c[0], x)
2213 mfs.setdefault(c[0], x)
2213 count[0] += 1
2214 count[0] += 1
2214 progress(_bundling, count[0],
2215 progress(_bundling, count[0],
2215 unit=_changesets, total=count[1])
2216 unit=_changesets, total=count[1])
2216 return x
2217 return x
2217 elif revlog == mf:
2218 elif revlog == mf:
2218 count[0] += 1
2219 count[0] += 1
2219 progress(_bundling, count[0],
2220 progress(_bundling, count[0],
2220 unit=_manifests, total=count[1])
2221 unit=_manifests, total=count[1])
2221 return cl.node(revlog.linkrev(revlog.rev(x)))
2222 return cl.node(revlog.linkrev(revlog.rev(x)))
2222 else:
2223 else:
2223 progress(_bundling, count[0], item=fstate[0],
2224 progress(_bundling, count[0], item=fstate[0],
2224 total=count[1], unit=_files)
2225 total=count[1], unit=_files)
2225 return cl.node(revlog.linkrev(revlog.rev(x)))
2226 return cl.node(revlog.linkrev(revlog.rev(x)))
2226
2227
2227 bundler = changegroup.bundle10(lookup)
2228 bundler = changegroup.bundle10(lookup)
2228 reorder = self.ui.config('bundle', 'reorder', 'auto')
2229 reorder = self.ui.config('bundle', 'reorder', 'auto')
2229 if reorder == 'auto':
2230 if reorder == 'auto':
2230 reorder = None
2231 reorder = None
2231 else:
2232 else:
2232 reorder = util.parsebool(reorder)
2233 reorder = util.parsebool(reorder)
2233
2234
2234 def gengroup():
2235 def gengroup():
2235 '''yield a sequence of changegroup chunks (strings)'''
2236 '''yield a sequence of changegroup chunks (strings)'''
2236 # construct a list of all changed files
2237 # construct a list of all changed files
2237
2238
2238 count[:] = [0, len(nodes)]
2239 count[:] = [0, len(nodes)]
2239 for chunk in cl.group(nodes, bundler, reorder=reorder):
2240 for chunk in cl.group(nodes, bundler, reorder=reorder):
2240 yield chunk
2241 yield chunk
2241 progress(_bundling, None)
2242 progress(_bundling, None)
2242
2243
2243 count[:] = [0, len(mfs)]
2244 count[:] = [0, len(mfs)]
2244 for chunk in mf.group(gennodelst(mf), bundler, reorder=reorder):
2245 for chunk in mf.group(gennodelst(mf), bundler, reorder=reorder):
2245 yield chunk
2246 yield chunk
2246 progress(_bundling, None)
2247 progress(_bundling, None)
2247
2248
2248 count[:] = [0, len(changedfiles)]
2249 count[:] = [0, len(changedfiles)]
2249 for fname in sorted(changedfiles):
2250 for fname in sorted(changedfiles):
2250 filerevlog = self.file(fname)
2251 filerevlog = self.file(fname)
2251 if not len(filerevlog):
2252 if not len(filerevlog):
2252 raise util.Abort(_("empty or missing revlog for %s")
2253 raise util.Abort(_("empty or missing revlog for %s")
2253 % fname)
2254 % fname)
2254 fstate[0] = fname
2255 fstate[0] = fname
2255 nodelist = gennodelst(filerevlog)
2256 nodelist = gennodelst(filerevlog)
2256 if nodelist:
2257 if nodelist:
2257 count[0] += 1
2258 count[0] += 1
2258 yield bundler.fileheader(fname)
2259 yield bundler.fileheader(fname)
2259 for chunk in filerevlog.group(nodelist, bundler, reorder):
2260 for chunk in filerevlog.group(nodelist, bundler, reorder):
2260 yield chunk
2261 yield chunk
2261 yield bundler.close()
2262 yield bundler.close()
2262 progress(_bundling, None)
2263 progress(_bundling, None)
2263
2264
2264 if nodes:
2265 if nodes:
2265 self.hook('outgoing', node=hex(nodes[0]), source=source)
2266 self.hook('outgoing', node=hex(nodes[0]), source=source)
2266
2267
2267 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
2268 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
2268
2269
2269 def addchangegroup(self, source, srctype, url, emptyok=False):
2270 def addchangegroup(self, source, srctype, url, emptyok=False):
2270 """Add the changegroup returned by source.read() to this repo.
2271 """Add the changegroup returned by source.read() to this repo.
2271 srctype is a string like 'push', 'pull', or 'unbundle'. url is
2272 srctype is a string like 'push', 'pull', or 'unbundle'. url is
2272 the URL of the repo where this changegroup is coming from.
2273 the URL of the repo where this changegroup is coming from.
2273
2274
2274 Return an integer summarizing the change to this repo:
2275 Return an integer summarizing the change to this repo:
2275 - nothing changed or no source: 0
2276 - nothing changed or no source: 0
2276 - more heads than before: 1+added heads (2..n)
2277 - more heads than before: 1+added heads (2..n)
2277 - fewer heads than before: -1-removed heads (-2..-n)
2278 - fewer heads than before: -1-removed heads (-2..-n)
2278 - number of heads stays the same: 1
2279 - number of heads stays the same: 1
2279 """
2280 """
2280 def csmap(x):
2281 def csmap(x):
2281 self.ui.debug("add changeset %s\n" % short(x))
2282 self.ui.debug("add changeset %s\n" % short(x))
2282 return len(cl)
2283 return len(cl)
2283
2284
2284 def revmap(x):
2285 def revmap(x):
2285 return cl.rev(x)
2286 return cl.rev(x)
2286
2287
2287 if not source:
2288 if not source:
2288 return 0
2289 return 0
2289
2290
2290 self.hook('prechangegroup', throw=True, source=srctype, url=url)
2291 self.hook('prechangegroup', throw=True, source=srctype, url=url)
2291
2292
2292 changesets = files = revisions = 0
2293 changesets = files = revisions = 0
2293 efiles = set()
2294 efiles = set()
2294
2295
2295 # write changelog data to temp files so concurrent readers will not see
2296 # write changelog data to temp files so concurrent readers will not see
2296 # inconsistent view
2297 # inconsistent view
2297 cl = self.changelog
2298 cl = self.changelog
2298 cl.delayupdate()
2299 cl.delayupdate()
2299 oldheads = cl.heads()
2300 oldheads = cl.heads()
2300
2301
2301 tr = self.transaction("\n".join([srctype, util.hidepassword(url)]))
2302 tr = self.transaction("\n".join([srctype, util.hidepassword(url)]))
2302 try:
2303 try:
2303 trp = weakref.proxy(tr)
2304 trp = weakref.proxy(tr)
2304 # pull off the changeset group
2305 # pull off the changeset group
2305 self.ui.status(_("adding changesets\n"))
2306 self.ui.status(_("adding changesets\n"))
2306 clstart = len(cl)
2307 clstart = len(cl)
2307 class prog(object):
2308 class prog(object):
2308 step = _('changesets')
2309 step = _('changesets')
2309 count = 1
2310 count = 1
2310 ui = self.ui
2311 ui = self.ui
2311 total = None
2312 total = None
2312 def __call__(self):
2313 def __call__(self):
2313 self.ui.progress(self.step, self.count, unit=_('chunks'),
2314 self.ui.progress(self.step, self.count, unit=_('chunks'),
2314 total=self.total)
2315 total=self.total)
2315 self.count += 1
2316 self.count += 1
2316 pr = prog()
2317 pr = prog()
2317 source.callback = pr
2318 source.callback = pr
2318
2319
2319 source.changelogheader()
2320 source.changelogheader()
2320 srccontent = cl.addgroup(source, csmap, trp)
2321 srccontent = cl.addgroup(source, csmap, trp)
2321 if not (srccontent or emptyok):
2322 if not (srccontent or emptyok):
2322 raise util.Abort(_("received changelog group is empty"))
2323 raise util.Abort(_("received changelog group is empty"))
2323 clend = len(cl)
2324 clend = len(cl)
2324 changesets = clend - clstart
2325 changesets = clend - clstart
2325 for c in xrange(clstart, clend):
2326 for c in xrange(clstart, clend):
2326 efiles.update(self[c].files())
2327 efiles.update(self[c].files())
2327 efiles = len(efiles)
2328 efiles = len(efiles)
2328 self.ui.progress(_('changesets'), None)
2329 self.ui.progress(_('changesets'), None)
2329
2330
2330 # pull off the manifest group
2331 # pull off the manifest group
2331 self.ui.status(_("adding manifests\n"))
2332 self.ui.status(_("adding manifests\n"))
2332 pr.step = _('manifests')
2333 pr.step = _('manifests')
2333 pr.count = 1
2334 pr.count = 1
2334 pr.total = changesets # manifests <= changesets
2335 pr.total = changesets # manifests <= changesets
2335 # no need to check for empty manifest group here:
2336 # no need to check for empty manifest group here:
2336 # if the result of the merge of 1 and 2 is the same in 3 and 4,
2337 # if the result of the merge of 1 and 2 is the same in 3 and 4,
2337 # no new manifest will be created and the manifest group will
2338 # no new manifest will be created and the manifest group will
2338 # be empty during the pull
2339 # be empty during the pull
2339 source.manifestheader()
2340 source.manifestheader()
2340 self.manifest.addgroup(source, revmap, trp)
2341 self.manifest.addgroup(source, revmap, trp)
2341 self.ui.progress(_('manifests'), None)
2342 self.ui.progress(_('manifests'), None)
2342
2343
2343 needfiles = {}
2344 needfiles = {}
2344 if self.ui.configbool('server', 'validate', default=False):
2345 if self.ui.configbool('server', 'validate', default=False):
2345 # validate incoming csets have their manifests
2346 # validate incoming csets have their manifests
2346 for cset in xrange(clstart, clend):
2347 for cset in xrange(clstart, clend):
2347 mfest = self.changelog.read(self.changelog.node(cset))[0]
2348 mfest = self.changelog.read(self.changelog.node(cset))[0]
2348 mfest = self.manifest.readdelta(mfest)
2349 mfest = self.manifest.readdelta(mfest)
2349 # store file nodes we must see
2350 # store file nodes we must see
2350 for f, n in mfest.iteritems():
2351 for f, n in mfest.iteritems():
2351 needfiles.setdefault(f, set()).add(n)
2352 needfiles.setdefault(f, set()).add(n)
2352
2353
2353 # process the files
2354 # process the files
2354 self.ui.status(_("adding file changes\n"))
2355 self.ui.status(_("adding file changes\n"))
2355 pr.step = _('files')
2356 pr.step = _('files')
2356 pr.count = 1
2357 pr.count = 1
2357 pr.total = efiles
2358 pr.total = efiles
2358 source.callback = None
2359 source.callback = None
2359
2360
2360 while True:
2361 while True:
2361 chunkdata = source.filelogheader()
2362 chunkdata = source.filelogheader()
2362 if not chunkdata:
2363 if not chunkdata:
2363 break
2364 break
2364 f = chunkdata["filename"]
2365 f = chunkdata["filename"]
2365 self.ui.debug("adding %s revisions\n" % f)
2366 self.ui.debug("adding %s revisions\n" % f)
2366 pr()
2367 pr()
2367 fl = self.file(f)
2368 fl = self.file(f)
2368 o = len(fl)
2369 o = len(fl)
2369 if not fl.addgroup(source, revmap, trp):
2370 if not fl.addgroup(source, revmap, trp):
2370 raise util.Abort(_("received file revlog group is empty"))
2371 raise util.Abort(_("received file revlog group is empty"))
2371 revisions += len(fl) - o
2372 revisions += len(fl) - o
2372 files += 1
2373 files += 1
2373 if f in needfiles:
2374 if f in needfiles:
2374 needs = needfiles[f]
2375 needs = needfiles[f]
2375 for new in xrange(o, len(fl)):
2376 for new in xrange(o, len(fl)):
2376 n = fl.node(new)
2377 n = fl.node(new)
2377 if n in needs:
2378 if n in needs:
2378 needs.remove(n)
2379 needs.remove(n)
2379 if not needs:
2380 if not needs:
2380 del needfiles[f]
2381 del needfiles[f]
2381 self.ui.progress(_('files'), None)
2382 self.ui.progress(_('files'), None)
2382
2383
2383 for f, needs in needfiles.iteritems():
2384 for f, needs in needfiles.iteritems():
2384 fl = self.file(f)
2385 fl = self.file(f)
2385 for n in needs:
2386 for n in needs:
2386 try:
2387 try:
2387 fl.rev(n)
2388 fl.rev(n)
2388 except error.LookupError:
2389 except error.LookupError:
2389 raise util.Abort(
2390 raise util.Abort(
2390 _('missing file data for %s:%s - run hg verify') %
2391 _('missing file data for %s:%s - run hg verify') %
2391 (f, hex(n)))
2392 (f, hex(n)))
2392
2393
2393 dh = 0
2394 dh = 0
2394 if oldheads:
2395 if oldheads:
2395 heads = cl.heads()
2396 heads = cl.heads()
2396 dh = len(heads) - len(oldheads)
2397 dh = len(heads) - len(oldheads)
2397 for h in heads:
2398 for h in heads:
2398 if h not in oldheads and self[h].closesbranch():
2399 if h not in oldheads and self[h].closesbranch():
2399 dh -= 1
2400 dh -= 1
2400 htext = ""
2401 htext = ""
2401 if dh:
2402 if dh:
2402 htext = _(" (%+d heads)") % dh
2403 htext = _(" (%+d heads)") % dh
2403
2404
2404 self.ui.status(_("added %d changesets"
2405 self.ui.status(_("added %d changesets"
2405 " with %d changes to %d files%s\n")
2406 " with %d changes to %d files%s\n")
2406 % (changesets, revisions, files, htext))
2407 % (changesets, revisions, files, htext))
2408 obsolete.clearobscaches(self)
2407
2409
2408 if changesets > 0:
2410 if changesets > 0:
2409 p = lambda: cl.writepending() and self.root or ""
2411 p = lambda: cl.writepending() and self.root or ""
2410 self.hook('pretxnchangegroup', throw=True,
2412 self.hook('pretxnchangegroup', throw=True,
2411 node=hex(cl.node(clstart)), source=srctype,
2413 node=hex(cl.node(clstart)), source=srctype,
2412 url=url, pending=p)
2414 url=url, pending=p)
2413
2415
2414 added = [cl.node(r) for r in xrange(clstart, clend)]
2416 added = [cl.node(r) for r in xrange(clstart, clend)]
2415 publishing = self.ui.configbool('phases', 'publish', True)
2417 publishing = self.ui.configbool('phases', 'publish', True)
2416 if srctype == 'push':
2418 if srctype == 'push':
2417 # Old server can not push the boundary themself.
2419 # Old server can not push the boundary themself.
2418 # New server won't push the boundary if changeset already
2420 # New server won't push the boundary if changeset already
2419 # existed locally as secrete
2421 # existed locally as secrete
2420 #
2422 #
2421 # We should not use added here but the list of all change in
2423 # We should not use added here but the list of all change in
2422 # the bundle
2424 # the bundle
2423 if publishing:
2425 if publishing:
2424 phases.advanceboundary(self, phases.public, srccontent)
2426 phases.advanceboundary(self, phases.public, srccontent)
2425 else:
2427 else:
2426 phases.advanceboundary(self, phases.draft, srccontent)
2428 phases.advanceboundary(self, phases.draft, srccontent)
2427 phases.retractboundary(self, phases.draft, added)
2429 phases.retractboundary(self, phases.draft, added)
2428 elif srctype != 'strip':
2430 elif srctype != 'strip':
2429 # publishing only alter behavior during push
2431 # publishing only alter behavior during push
2430 #
2432 #
2431 # strip should not touch boundary at all
2433 # strip should not touch boundary at all
2432 phases.retractboundary(self, phases.draft, added)
2434 phases.retractboundary(self, phases.draft, added)
2433
2435
2434 # make changelog see real files again
2436 # make changelog see real files again
2435 cl.finalize(trp)
2437 cl.finalize(trp)
2436
2438
2437 tr.close()
2439 tr.close()
2438
2440
2439 if changesets > 0:
2441 if changesets > 0:
2440 def runhooks():
2442 def runhooks():
2441 # forcefully update the on-disk branch cache
2443 # forcefully update the on-disk branch cache
2442 self.ui.debug("updating the branch cache\n")
2444 self.ui.debug("updating the branch cache\n")
2443 self.updatebranchcache()
2445 self.updatebranchcache()
2444 self.hook("changegroup", node=hex(cl.node(clstart)),
2446 self.hook("changegroup", node=hex(cl.node(clstart)),
2445 source=srctype, url=url)
2447 source=srctype, url=url)
2446
2448
2447 for n in added:
2449 for n in added:
2448 self.hook("incoming", node=hex(n), source=srctype,
2450 self.hook("incoming", node=hex(n), source=srctype,
2449 url=url)
2451 url=url)
2450 self._afterlock(runhooks)
2452 self._afterlock(runhooks)
2451
2453
2452 finally:
2454 finally:
2453 tr.release()
2455 tr.release()
2454 # never return 0 here:
2456 # never return 0 here:
2455 if dh < 0:
2457 if dh < 0:
2456 return dh - 1
2458 return dh - 1
2457 else:
2459 else:
2458 return dh + 1
2460 return dh + 1
2459
2461
2460 def stream_in(self, remote, requirements):
2462 def stream_in(self, remote, requirements):
2461 lock = self.lock()
2463 lock = self.lock()
2462 try:
2464 try:
2463 fp = remote.stream_out()
2465 fp = remote.stream_out()
2464 l = fp.readline()
2466 l = fp.readline()
2465 try:
2467 try:
2466 resp = int(l)
2468 resp = int(l)
2467 except ValueError:
2469 except ValueError:
2468 raise error.ResponseError(
2470 raise error.ResponseError(
2469 _('unexpected response from remote server:'), l)
2471 _('unexpected response from remote server:'), l)
2470 if resp == 1:
2472 if resp == 1:
2471 raise util.Abort(_('operation forbidden by server'))
2473 raise util.Abort(_('operation forbidden by server'))
2472 elif resp == 2:
2474 elif resp == 2:
2473 raise util.Abort(_('locking the remote repository failed'))
2475 raise util.Abort(_('locking the remote repository failed'))
2474 elif resp != 0:
2476 elif resp != 0:
2475 raise util.Abort(_('the server sent an unknown error code'))
2477 raise util.Abort(_('the server sent an unknown error code'))
2476 self.ui.status(_('streaming all changes\n'))
2478 self.ui.status(_('streaming all changes\n'))
2477 l = fp.readline()
2479 l = fp.readline()
2478 try:
2480 try:
2479 total_files, total_bytes = map(int, l.split(' ', 1))
2481 total_files, total_bytes = map(int, l.split(' ', 1))
2480 except (ValueError, TypeError):
2482 except (ValueError, TypeError):
2481 raise error.ResponseError(
2483 raise error.ResponseError(
2482 _('unexpected response from remote server:'), l)
2484 _('unexpected response from remote server:'), l)
2483 self.ui.status(_('%d files to transfer, %s of data\n') %
2485 self.ui.status(_('%d files to transfer, %s of data\n') %
2484 (total_files, util.bytecount(total_bytes)))
2486 (total_files, util.bytecount(total_bytes)))
2485 handled_bytes = 0
2487 handled_bytes = 0
2486 self.ui.progress(_('clone'), 0, total=total_bytes)
2488 self.ui.progress(_('clone'), 0, total=total_bytes)
2487 start = time.time()
2489 start = time.time()
2488 for i in xrange(total_files):
2490 for i in xrange(total_files):
2489 # XXX doesn't support '\n' or '\r' in filenames
2491 # XXX doesn't support '\n' or '\r' in filenames
2490 l = fp.readline()
2492 l = fp.readline()
2491 try:
2493 try:
2492 name, size = l.split('\0', 1)
2494 name, size = l.split('\0', 1)
2493 size = int(size)
2495 size = int(size)
2494 except (ValueError, TypeError):
2496 except (ValueError, TypeError):
2495 raise error.ResponseError(
2497 raise error.ResponseError(
2496 _('unexpected response from remote server:'), l)
2498 _('unexpected response from remote server:'), l)
2497 if self.ui.debugflag:
2499 if self.ui.debugflag:
2498 self.ui.debug('adding %s (%s)\n' %
2500 self.ui.debug('adding %s (%s)\n' %
2499 (name, util.bytecount(size)))
2501 (name, util.bytecount(size)))
2500 # for backwards compat, name was partially encoded
2502 # for backwards compat, name was partially encoded
2501 ofp = self.sopener(store.decodedir(name), 'w')
2503 ofp = self.sopener(store.decodedir(name), 'w')
2502 for chunk in util.filechunkiter(fp, limit=size):
2504 for chunk in util.filechunkiter(fp, limit=size):
2503 handled_bytes += len(chunk)
2505 handled_bytes += len(chunk)
2504 self.ui.progress(_('clone'), handled_bytes,
2506 self.ui.progress(_('clone'), handled_bytes,
2505 total=total_bytes)
2507 total=total_bytes)
2506 ofp.write(chunk)
2508 ofp.write(chunk)
2507 ofp.close()
2509 ofp.close()
2508 elapsed = time.time() - start
2510 elapsed = time.time() - start
2509 if elapsed <= 0:
2511 if elapsed <= 0:
2510 elapsed = 0.001
2512 elapsed = 0.001
2511 self.ui.progress(_('clone'), None)
2513 self.ui.progress(_('clone'), None)
2512 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
2514 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
2513 (util.bytecount(total_bytes), elapsed,
2515 (util.bytecount(total_bytes), elapsed,
2514 util.bytecount(total_bytes / elapsed)))
2516 util.bytecount(total_bytes / elapsed)))
2515
2517
2516 # new requirements = old non-format requirements +
2518 # new requirements = old non-format requirements +
2517 # new format-related
2519 # new format-related
2518 # requirements from the streamed-in repository
2520 # requirements from the streamed-in repository
2519 requirements.update(set(self.requirements) - self.supportedformats)
2521 requirements.update(set(self.requirements) - self.supportedformats)
2520 self._applyrequirements(requirements)
2522 self._applyrequirements(requirements)
2521 self._writerequirements()
2523 self._writerequirements()
2522
2524
2523 self.invalidate()
2525 self.invalidate()
2524 return len(self.heads()) + 1
2526 return len(self.heads()) + 1
2525 finally:
2527 finally:
2526 lock.release()
2528 lock.release()
2527
2529
2528 def clone(self, remote, heads=[], stream=False):
2530 def clone(self, remote, heads=[], stream=False):
2529 '''clone remote repository.
2531 '''clone remote repository.
2530
2532
2531 keyword arguments:
2533 keyword arguments:
2532 heads: list of revs to clone (forces use of pull)
2534 heads: list of revs to clone (forces use of pull)
2533 stream: use streaming clone if possible'''
2535 stream: use streaming clone if possible'''
2534
2536
2535 # now, all clients that can request uncompressed clones can
2537 # now, all clients that can request uncompressed clones can
2536 # read repo formats supported by all servers that can serve
2538 # read repo formats supported by all servers that can serve
2537 # them.
2539 # them.
2538
2540
2539 # if revlog format changes, client will have to check version
2541 # if revlog format changes, client will have to check version
2540 # and format flags on "stream" capability, and use
2542 # and format flags on "stream" capability, and use
2541 # uncompressed only if compatible.
2543 # uncompressed only if compatible.
2542
2544
2543 if not stream:
2545 if not stream:
2544 # if the server explicitly prefers to stream (for fast LANs)
2546 # if the server explicitly prefers to stream (for fast LANs)
2545 stream = remote.capable('stream-preferred')
2547 stream = remote.capable('stream-preferred')
2546
2548
2547 if stream and not heads:
2549 if stream and not heads:
2548 # 'stream' means remote revlog format is revlogv1 only
2550 # 'stream' means remote revlog format is revlogv1 only
2549 if remote.capable('stream'):
2551 if remote.capable('stream'):
2550 return self.stream_in(remote, set(('revlogv1',)))
2552 return self.stream_in(remote, set(('revlogv1',)))
2551 # otherwise, 'streamreqs' contains the remote revlog format
2553 # otherwise, 'streamreqs' contains the remote revlog format
2552 streamreqs = remote.capable('streamreqs')
2554 streamreqs = remote.capable('streamreqs')
2553 if streamreqs:
2555 if streamreqs:
2554 streamreqs = set(streamreqs.split(','))
2556 streamreqs = set(streamreqs.split(','))
2555 # if we support it, stream in and adjust our requirements
2557 # if we support it, stream in and adjust our requirements
2556 if not streamreqs - self.supportedformats:
2558 if not streamreqs - self.supportedformats:
2557 return self.stream_in(remote, streamreqs)
2559 return self.stream_in(remote, streamreqs)
2558 return self.pull(remote, heads)
2560 return self.pull(remote, heads)
2559
2561
2560 def pushkey(self, namespace, key, old, new):
2562 def pushkey(self, namespace, key, old, new):
2561 self.hook('prepushkey', throw=True, namespace=namespace, key=key,
2563 self.hook('prepushkey', throw=True, namespace=namespace, key=key,
2562 old=old, new=new)
2564 old=old, new=new)
2563 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
2565 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
2564 ret = pushkey.push(self, namespace, key, old, new)
2566 ret = pushkey.push(self, namespace, key, old, new)
2565 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
2567 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
2566 ret=ret)
2568 ret=ret)
2567 return ret
2569 return ret
2568
2570
2569 def listkeys(self, namespace):
2571 def listkeys(self, namespace):
2570 self.hook('prelistkeys', throw=True, namespace=namespace)
2572 self.hook('prelistkeys', throw=True, namespace=namespace)
2571 self.ui.debug('listing keys for "%s"\n' % namespace)
2573 self.ui.debug('listing keys for "%s"\n' % namespace)
2572 values = pushkey.list(self, namespace)
2574 values = pushkey.list(self, namespace)
2573 self.hook('listkeys', namespace=namespace, values=values)
2575 self.hook('listkeys', namespace=namespace, values=values)
2574 return values
2576 return values
2575
2577
2576 def debugwireargs(self, one, two, three=None, four=None, five=None):
2578 def debugwireargs(self, one, two, three=None, four=None, five=None):
2577 '''used to test argument passing over the wire'''
2579 '''used to test argument passing over the wire'''
2578 return "%s %s %s %s %s" % (one, two, three, four, five)
2580 return "%s %s %s %s %s" % (one, two, three, four, five)
2579
2581
2580 def savecommitmessage(self, text):
2582 def savecommitmessage(self, text):
2581 fp = self.opener('last-message.txt', 'wb')
2583 fp = self.opener('last-message.txt', 'wb')
2582 try:
2584 try:
2583 fp.write(text)
2585 fp.write(text)
2584 finally:
2586 finally:
2585 fp.close()
2587 fp.close()
2586 return self.pathto(fp.name[len(self.root)+1:])
2588 return self.pathto(fp.name[len(self.root)+1:])
2587
2589
2588 # used to avoid circular references so destructors work
2590 # used to avoid circular references so destructors work
2589 def aftertrans(files):
2591 def aftertrans(files):
2590 renamefiles = [tuple(t) for t in files]
2592 renamefiles = [tuple(t) for t in files]
2591 def a():
2593 def a():
2592 for src, dest in renamefiles:
2594 for src, dest in renamefiles:
2593 try:
2595 try:
2594 util.rename(src, dest)
2596 util.rename(src, dest)
2595 except OSError: # journal file does not yet exist
2597 except OSError: # journal file does not yet exist
2596 pass
2598 pass
2597 return a
2599 return a
2598
2600
2599 def undoname(fn):
2601 def undoname(fn):
2600 base, name = os.path.split(fn)
2602 base, name = os.path.split(fn)
2601 assert name.startswith('journal')
2603 assert name.startswith('journal')
2602 return os.path.join(base, name.replace('journal', 'undo', 1))
2604 return os.path.join(base, name.replace('journal', 'undo', 1))
2603
2605
2604 def instance(ui, path, create):
2606 def instance(ui, path, create):
2605 return localrepository(ui, util.urllocalpath(path), create)
2607 return localrepository(ui, util.urllocalpath(path), create)
2606
2608
2607 def islocal(path):
2609 def islocal(path):
2608 return True
2610 return True
@@ -1,329 +1,397 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
23
24 Format
24 Format
25 ------
25 ------
26
26
27 Markers are stored in an append-only file stored in
27 Markers are stored in an append-only file stored in
28 '.hg/store/obsstore'.
28 '.hg/store/obsstore'.
29
29
30 The file starts with a version header:
30 The file starts with a version header:
31
31
32 - 1 unsigned byte: version number, starting at zero.
32 - 1 unsigned byte: version number, starting at zero.
33
33
34
34
35 The header is followed by the markers. Each marker is made of:
35 The header is followed by the markers. Each marker is made of:
36
36
37 - 1 unsigned byte: number of new changesets "R", could be zero.
37 - 1 unsigned byte: number of new changesets "R", could be zero.
38
38
39 - 1 unsigned 32-bits integer: metadata size "M" in bytes.
39 - 1 unsigned 32-bits integer: metadata size "M" in bytes.
40
40
41 - 1 byte: a bit field. It is reserved for flags used in obsolete
41 - 1 byte: a bit field. It is reserved for flags used in obsolete
42 markers common operations, to avoid repeated decoding of metadata
42 markers common operations, to avoid repeated decoding of metadata
43 entries.
43 entries.
44
44
45 - 20 bytes: obsoleted changeset identifier.
45 - 20 bytes: obsoleted changeset identifier.
46
46
47 - N*20 bytes: new changesets identifiers.
47 - N*20 bytes: new changesets identifiers.
48
48
49 - M bytes: metadata as a sequence of nul-terminated strings. Each
49 - M bytes: metadata as a sequence of nul-terminated strings. Each
50 string contains a key and a value, separated by a color ':', without
50 string contains a key and a value, separated by a color ':', without
51 additional encoding. Keys cannot contain '\0' or ':' and values
51 additional encoding. Keys cannot contain '\0' or ':' and values
52 cannot contain '\0'.
52 cannot contain '\0'.
53 """
53 """
54 import struct
54 import struct
55 import util, base85
55 import util, base85
56 from i18n import _
56 from i18n import _
57
57
58 _pack = struct.pack
58 _pack = struct.pack
59 _unpack = struct.unpack
59 _unpack = struct.unpack
60
60
61 _SEEK_END = 2 # os.SEEK_END was introduced in Python 2.5
61 _SEEK_END = 2 # os.SEEK_END was introduced in Python 2.5
62
62
63 # the obsolete feature is not mature enough to be enabled by default.
63 # the obsolete feature is not mature enough to be enabled by default.
64 # you have to rely on third party extension extension to enable this.
64 # you have to rely on third party extension extension to enable this.
65 _enabled = False
65 _enabled = False
66
66
67 # data used for parsing and writing
67 # data used for parsing and writing
68 _fmversion = 0
68 _fmversion = 0
69 _fmfixed = '>BIB20s'
69 _fmfixed = '>BIB20s'
70 _fmnode = '20s'
70 _fmnode = '20s'
71 _fmfsize = struct.calcsize(_fmfixed)
71 _fmfsize = struct.calcsize(_fmfixed)
72 _fnodesize = struct.calcsize(_fmnode)
72 _fnodesize = struct.calcsize(_fmnode)
73
73
74 def _readmarkers(data):
74 def _readmarkers(data):
75 """Read and enumerate markers from raw data"""
75 """Read and enumerate markers from raw data"""
76 off = 0
76 off = 0
77 diskversion = _unpack('>B', data[off:off + 1])[0]
77 diskversion = _unpack('>B', data[off:off + 1])[0]
78 off += 1
78 off += 1
79 if diskversion != _fmversion:
79 if diskversion != _fmversion:
80 raise util.Abort(_('parsing obsolete marker: unknown version %r')
80 raise util.Abort(_('parsing obsolete marker: unknown version %r')
81 % diskversion)
81 % diskversion)
82
82
83 # Loop on markers
83 # Loop on markers
84 l = len(data)
84 l = len(data)
85 while off + _fmfsize <= l:
85 while off + _fmfsize <= l:
86 # read fixed part
86 # read fixed part
87 cur = data[off:off + _fmfsize]
87 cur = data[off:off + _fmfsize]
88 off += _fmfsize
88 off += _fmfsize
89 nbsuc, mdsize, flags, pre = _unpack(_fmfixed, cur)
89 nbsuc, mdsize, flags, pre = _unpack(_fmfixed, cur)
90 # read replacement
90 # read replacement
91 sucs = ()
91 sucs = ()
92 if nbsuc:
92 if nbsuc:
93 s = (_fnodesize * nbsuc)
93 s = (_fnodesize * nbsuc)
94 cur = data[off:off + s]
94 cur = data[off:off + s]
95 sucs = _unpack(_fmnode * nbsuc, cur)
95 sucs = _unpack(_fmnode * nbsuc, cur)
96 off += s
96 off += s
97 # read metadata
97 # read metadata
98 # (metadata will be decoded on demand)
98 # (metadata will be decoded on demand)
99 metadata = data[off:off + mdsize]
99 metadata = data[off:off + mdsize]
100 if len(metadata) != mdsize:
100 if len(metadata) != mdsize:
101 raise util.Abort(_('parsing obsolete marker: metadata is too '
101 raise util.Abort(_('parsing obsolete marker: metadata is too '
102 'short, %d bytes expected, got %d')
102 'short, %d bytes expected, got %d')
103 % (mdsize, len(metadata)))
103 % (mdsize, len(metadata)))
104 off += mdsize
104 off += mdsize
105 yield (pre, sucs, flags, metadata)
105 yield (pre, sucs, flags, metadata)
106
106
107 def encodemeta(meta):
107 def encodemeta(meta):
108 """Return encoded metadata string to string mapping.
108 """Return encoded metadata string to string mapping.
109
109
110 Assume no ':' in key and no '\0' in both key and value."""
110 Assume no ':' in key and no '\0' in both key and value."""
111 for key, value in meta.iteritems():
111 for key, value in meta.iteritems():
112 if ':' in key or '\0' in key:
112 if ':' in key or '\0' in key:
113 raise ValueError("':' and '\0' are forbidden in metadata key'")
113 raise ValueError("':' and '\0' are forbidden in metadata key'")
114 if '\0' in value:
114 if '\0' in value:
115 raise ValueError("':' are forbidden in metadata value'")
115 raise ValueError("':' are forbidden in metadata value'")
116 return '\0'.join(['%s:%s' % (k, meta[k]) for k in sorted(meta)])
116 return '\0'.join(['%s:%s' % (k, meta[k]) for k in sorted(meta)])
117
117
118 def decodemeta(data):
118 def decodemeta(data):
119 """Return string to string dictionary from encoded version."""
119 """Return string to string dictionary from encoded version."""
120 d = {}
120 d = {}
121 for l in data.split('\0'):
121 for l in data.split('\0'):
122 if l:
122 if l:
123 key, value = l.split(':')
123 key, value = l.split(':')
124 d[key] = value
124 d[key] = value
125 return d
125 return d
126
126
127 class marker(object):
127 class marker(object):
128 """Wrap obsolete marker raw data"""
128 """Wrap obsolete marker raw data"""
129
129
130 def __init__(self, repo, data):
130 def __init__(self, repo, data):
131 # the repo argument will be used to create changectx in later version
131 # the repo argument will be used to create changectx in later version
132 self._repo = repo
132 self._repo = repo
133 self._data = data
133 self._data = data
134 self._decodedmeta = None
134 self._decodedmeta = None
135
135
136 def precnode(self):
136 def precnode(self):
137 """Precursor changeset node identifier"""
137 """Precursor changeset node identifier"""
138 return self._data[0]
138 return self._data[0]
139
139
140 def succnodes(self):
140 def succnodes(self):
141 """List of successor changesets node identifiers"""
141 """List of successor changesets node identifiers"""
142 return self._data[1]
142 return self._data[1]
143
143
144 def metadata(self):
144 def metadata(self):
145 """Decoded metadata dictionary"""
145 """Decoded metadata dictionary"""
146 if self._decodedmeta is None:
146 if self._decodedmeta is None:
147 self._decodedmeta = decodemeta(self._data[3])
147 self._decodedmeta = decodemeta(self._data[3])
148 return self._decodedmeta
148 return self._decodedmeta
149
149
150 def date(self):
150 def date(self):
151 """Creation date as (unixtime, offset)"""
151 """Creation date as (unixtime, offset)"""
152 parts = self.metadata()['date'].split(' ')
152 parts = self.metadata()['date'].split(' ')
153 return (float(parts[0]), int(parts[1]))
153 return (float(parts[0]), int(parts[1]))
154
154
155 class obsstore(object):
155 class obsstore(object):
156 """Store obsolete markers
156 """Store obsolete markers
157
157
158 Markers can be accessed with two mappings:
158 Markers can be accessed with two mappings:
159 - precursors: old -> set(new)
159 - precursors: old -> set(new)
160 - successors: new -> set(old)
160 - successors: new -> set(old)
161 """
161 """
162
162
163 def __init__(self, sopener):
163 def __init__(self, sopener):
164 # caches for various obsolescence related cache
165 self.caches = {}
164 self._all = []
166 self._all = []
165 # new markers to serialize
167 # new markers to serialize
166 self.precursors = {}
168 self.precursors = {}
167 self.successors = {}
169 self.successors = {}
168 self.sopener = sopener
170 self.sopener = sopener
169 data = sopener.tryread('obsstore')
171 data = sopener.tryread('obsstore')
170 if data:
172 if data:
171 self._load(_readmarkers(data))
173 self._load(_readmarkers(data))
172
174
173 def __iter__(self):
175 def __iter__(self):
174 return iter(self._all)
176 return iter(self._all)
175
177
176 def __nonzero__(self):
178 def __nonzero__(self):
177 return bool(self._all)
179 return bool(self._all)
178
180
179 def create(self, transaction, prec, succs=(), flag=0, metadata=None):
181 def create(self, transaction, prec, succs=(), flag=0, metadata=None):
180 """obsolete: add a new obsolete marker
182 """obsolete: add a new obsolete marker
181
183
182 * ensuring it is hashable
184 * ensuring it is hashable
183 * check mandatory metadata
185 * check mandatory metadata
184 * encode metadata
186 * encode metadata
185 """
187 """
186 if metadata is None:
188 if metadata is None:
187 metadata = {}
189 metadata = {}
188 if len(prec) != 20:
190 if len(prec) != 20:
189 raise ValueError(prec)
191 raise ValueError(prec)
190 for succ in succs:
192 for succ in succs:
191 if len(succ) != 20:
193 if len(succ) != 20:
192 raise ValueError(succ)
194 raise ValueError(succ)
193 marker = (str(prec), tuple(succs), int(flag), encodemeta(metadata))
195 marker = (str(prec), tuple(succs), int(flag), encodemeta(metadata))
194 self.add(transaction, [marker])
196 self.add(transaction, [marker])
195
197
196 def add(self, transaction, markers):
198 def add(self, transaction, markers):
197 """Add new markers to the store
199 """Add new markers to the store
198
200
199 Take care of filtering duplicate.
201 Take care of filtering duplicate.
200 Return the number of new marker."""
202 Return the number of new marker."""
201 if not _enabled:
203 if not _enabled:
202 raise util.Abort('obsolete feature is not enabled on this repo')
204 raise util.Abort('obsolete feature is not enabled on this repo')
203 new = [m for m in markers if m not in self._all]
205 new = [m for m in markers if m not in self._all]
204 if new:
206 if new:
205 f = self.sopener('obsstore', 'ab')
207 f = self.sopener('obsstore', 'ab')
206 try:
208 try:
207 # Whether the file's current position is at the begin or at
209 # Whether the file's current position is at the begin or at
208 # the end after opening a file for appending is implementation
210 # the end after opening a file for appending is implementation
209 # defined. So we must seek to the end before calling tell(),
211 # defined. So we must seek to the end before calling tell(),
210 # or we may get a zero offset for non-zero sized files on
212 # or we may get a zero offset for non-zero sized files on
211 # some platforms (issue3543).
213 # some platforms (issue3543).
212 f.seek(0, _SEEK_END)
214 f.seek(0, _SEEK_END)
213 offset = f.tell()
215 offset = f.tell()
214 transaction.add('obsstore', offset)
216 transaction.add('obsstore', offset)
215 # offset == 0: new file - add the version header
217 # offset == 0: new file - add the version header
216 for bytes in _encodemarkers(new, offset == 0):
218 for bytes in _encodemarkers(new, offset == 0):
217 f.write(bytes)
219 f.write(bytes)
218 finally:
220 finally:
219 # XXX: f.close() == filecache invalidation == obsstore rebuilt.
221 # XXX: f.close() == filecache invalidation == obsstore rebuilt.
220 # call 'filecacheentry.refresh()' here
222 # call 'filecacheentry.refresh()' here
221 f.close()
223 f.close()
222 self._load(new)
224 self._load(new)
225 # new marker *may* have changed several set. invalidate the cache.
226 self.caches.clear()
223 return len(new)
227 return len(new)
224
228
225 def mergemarkers(self, transation, data):
229 def mergemarkers(self, transation, data):
226 markers = _readmarkers(data)
230 markers = _readmarkers(data)
227 self.add(transation, markers)
231 self.add(transation, markers)
228
232
229 def _load(self, markers):
233 def _load(self, markers):
230 for mark in markers:
234 for mark in markers:
231 self._all.append(mark)
235 self._all.append(mark)
232 pre, sucs = mark[:2]
236 pre, sucs = mark[:2]
233 self.precursors.setdefault(pre, set()).add(mark)
237 self.precursors.setdefault(pre, set()).add(mark)
234 for suc in sucs:
238 for suc in sucs:
235 self.successors.setdefault(suc, set()).add(mark)
239 self.successors.setdefault(suc, set()).add(mark)
236
240
237 def _encodemarkers(markers, addheader=False):
241 def _encodemarkers(markers, addheader=False):
238 # Kept separate from flushmarkers(), it will be reused for
242 # Kept separate from flushmarkers(), it will be reused for
239 # markers exchange.
243 # markers exchange.
240 if addheader:
244 if addheader:
241 yield _pack('>B', _fmversion)
245 yield _pack('>B', _fmversion)
242 for marker in markers:
246 for marker in markers:
243 yield _encodeonemarker(marker)
247 yield _encodeonemarker(marker)
244
248
245
249
246 def _encodeonemarker(marker):
250 def _encodeonemarker(marker):
247 pre, sucs, flags, metadata = marker
251 pre, sucs, flags, metadata = marker
248 nbsuc = len(sucs)
252 nbsuc = len(sucs)
249 format = _fmfixed + (_fmnode * nbsuc)
253 format = _fmfixed + (_fmnode * nbsuc)
250 data = [nbsuc, len(metadata), flags, pre]
254 data = [nbsuc, len(metadata), flags, pre]
251 data.extend(sucs)
255 data.extend(sucs)
252 return _pack(format, *data) + metadata
256 return _pack(format, *data) + metadata
253
257
254 # arbitrary picked to fit into 8K limit from HTTP server
258 # arbitrary picked to fit into 8K limit from HTTP server
255 # you have to take in account:
259 # you have to take in account:
256 # - the version header
260 # - the version header
257 # - the base85 encoding
261 # - the base85 encoding
258 _maxpayload = 5300
262 _maxpayload = 5300
259
263
260 def listmarkers(repo):
264 def listmarkers(repo):
261 """List markers over pushkey"""
265 """List markers over pushkey"""
262 if not repo.obsstore:
266 if not repo.obsstore:
263 return {}
267 return {}
264 keys = {}
268 keys = {}
265 parts = []
269 parts = []
266 currentlen = _maxpayload * 2 # ensure we create a new part
270 currentlen = _maxpayload * 2 # ensure we create a new part
267 for marker in repo.obsstore:
271 for marker in repo.obsstore:
268 nextdata = _encodeonemarker(marker)
272 nextdata = _encodeonemarker(marker)
269 if (len(nextdata) + currentlen > _maxpayload):
273 if (len(nextdata) + currentlen > _maxpayload):
270 currentpart = []
274 currentpart = []
271 currentlen = 0
275 currentlen = 0
272 parts.append(currentpart)
276 parts.append(currentpart)
273 currentpart.append(nextdata)
277 currentpart.append(nextdata)
274 currentlen += len(nextdata)
278 currentlen += len(nextdata)
275 for idx, part in enumerate(reversed(parts)):
279 for idx, part in enumerate(reversed(parts)):
276 data = ''.join([_pack('>B', _fmversion)] + part)
280 data = ''.join([_pack('>B', _fmversion)] + part)
277 keys['dump%i' % idx] = base85.b85encode(data)
281 keys['dump%i' % idx] = base85.b85encode(data)
278 return keys
282 return keys
279
283
280 def pushmarker(repo, key, old, new):
284 def pushmarker(repo, key, old, new):
281 """Push markers over pushkey"""
285 """Push markers over pushkey"""
282 if not key.startswith('dump'):
286 if not key.startswith('dump'):
283 repo.ui.warn(_('unknown key: %r') % key)
287 repo.ui.warn(_('unknown key: %r') % key)
284 return 0
288 return 0
285 if old:
289 if old:
286 repo.ui.warn(_('unexpected old value') % key)
290 repo.ui.warn(_('unexpected old value') % key)
287 return 0
291 return 0
288 data = base85.b85decode(new)
292 data = base85.b85decode(new)
289 lock = repo.lock()
293 lock = repo.lock()
290 try:
294 try:
291 tr = repo.transaction('pushkey: obsolete markers')
295 tr = repo.transaction('pushkey: obsolete markers')
292 try:
296 try:
293 repo.obsstore.mergemarkers(tr, data)
297 repo.obsstore.mergemarkers(tr, data)
294 tr.close()
298 tr.close()
295 return 1
299 return 1
296 finally:
300 finally:
297 tr.release()
301 tr.release()
298 finally:
302 finally:
299 lock.release()
303 lock.release()
300
304
301 def allmarkers(repo):
305 def allmarkers(repo):
302 """all obsolete markers known in a repository"""
306 """all obsolete markers known in a repository"""
303 for markerdata in repo.obsstore:
307 for markerdata in repo.obsstore:
304 yield marker(repo, markerdata)
308 yield marker(repo, markerdata)
305
309
306 def precursormarkers(ctx):
310 def precursormarkers(ctx):
307 """obsolete marker making this changeset obsolete"""
311 """obsolete marker making this changeset obsolete"""
308 for data in ctx._repo.obsstore.precursors.get(ctx.node(), ()):
312 for data in ctx._repo.obsstore.precursors.get(ctx.node(), ()):
309 yield marker(ctx._repo, data)
313 yield marker(ctx._repo, data)
310
314
311 def successormarkers(ctx):
315 def successormarkers(ctx):
312 """obsolete marker marking this changeset as a successors"""
316 """obsolete marker marking this changeset as a successors"""
313 for data in ctx._repo.obsstore.successors.get(ctx.node(), ()):
317 for data in ctx._repo.obsstore.successors.get(ctx.node(), ()):
314 yield marker(ctx._repo, data)
318 yield marker(ctx._repo, data)
315
319
316 def anysuccessors(obsstore, node):
320 def anysuccessors(obsstore, node):
317 """Yield every successor of <node>
321 """Yield every successor of <node>
318
322
319 This is a linear yield unsuitable to detect split changesets."""
323 This is a linear yield unsuitable to detect split changesets."""
320 remaining = set([node])
324 remaining = set([node])
321 seen = set(remaining)
325 seen = set(remaining)
322 while remaining:
326 while remaining:
323 current = remaining.pop()
327 current = remaining.pop()
324 yield current
328 yield current
325 for mark in obsstore.precursors.get(current, ()):
329 for mark in obsstore.precursors.get(current, ()):
326 for suc in mark[1]:
330 for suc in mark[1]:
327 if suc not in seen:
331 if suc not in seen:
328 seen.add(suc)
332 seen.add(suc)
329 remaining.add(suc)
333 remaining.add(suc)
334
335 # mapping of 'set-name' -> <function to computer this set>
336 cachefuncs = {}
337 def cachefor(name):
338 """Decorator to register a function as computing the cache for a set"""
339 def decorator(func):
340 assert name not in cachefuncs
341 cachefuncs[name] = func
342 return func
343 return decorator
344
345 def getobscache(repo, name):
346 """Return the set of revision that belong to the <name> set
347
348 Such access may compute the set and cache it for future use"""
349 if not repo.obsstore:
350 return ()
351 if name not in repo.obsstore.caches:
352 repo.obsstore.caches[name] = cachefuncs[name](repo)
353 return repo.obsstore.caches[name]
354
355 # To be simple we need to invalidate obsolescence cache when:
356 #
357 # - new changeset is added:
358 # - public phase is changed
359 # - obsolescence marker are added
360 # - strip is used a repo
361 def clearobscaches(repo):
362 """Remove all obsolescence related cache from a repo
363
364 This remove all cache in obsstore is the obsstore already exist on the
365 repo.
366
367 (We could be smarter here given the exact event that trigger the cache
368 clearing)"""
369 # only clear cache is there is obsstore data in this repo
370 if 'obsstore' in repo._filecache:
371 repo.obsstore.caches.clear()
372
373 @cachefor('obsolete')
374 def _computeobsoleteset(repo):
375 """the set of obsolete revisions"""
376 obs = set()
377 nm = repo.changelog.nodemap
378 for prec in repo.obsstore.precursors:
379 rev = nm.get(prec)
380 if rev is not None:
381 obs.add(rev)
382 return set(repo.revs('%ld - public()', obs))
383
384 @cachefor('unstable')
385 def _computeunstableset(repo):
386 """the set of non obsolete revisions with obsolete parents"""
387 return set(repo.revs('(obsolete()::) - obsolete()'))
388
389 @cachefor('suspended')
390 def _computesuspendedset(repo):
391 """the set of obsolete parents with non obsolete descendants"""
392 return set(repo.revs('obsolete() and obsolete()::unstable()'))
393
394 @cachefor('extinct')
395 def _computeextinctset(repo):
396 """the set of obsolete parents without non obsolete descendants"""
397 return set(repo.revs('obsolete() - obsolete()::unstable()'))
@@ -1,387 +1,390 b''
1 """ Mercurial phases support code
1 """ Mercurial phases support code
2
2
3 ---
3 ---
4
4
5 Copyright 2011 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
5 Copyright 2011 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
6 Logilab SA <contact@logilab.fr>
6 Logilab SA <contact@logilab.fr>
7 Augie Fackler <durin42@gmail.com>
7 Augie Fackler <durin42@gmail.com>
8
8
9 This software may be used and distributed according to the terms
9 This software may be used and distributed according to the terms
10 of the GNU General Public License version 2 or any later version.
10 of the GNU General Public License version 2 or any later version.
11
11
12 ---
12 ---
13
13
14 This module implements most phase logic in mercurial.
14 This module implements most phase logic in mercurial.
15
15
16
16
17 Basic Concept
17 Basic Concept
18 =============
18 =============
19
19
20 A 'changeset phase' is an indicator that tells us how a changeset is
20 A 'changeset phase' is an indicator that tells us how a changeset is
21 manipulated and communicated. The details of each phase is described
21 manipulated and communicated. The details of each phase is described
22 below, here we describe the properties they have in common.
22 below, here we describe the properties they have in common.
23
23
24 Like bookmarks, phases are not stored in history and thus are not
24 Like bookmarks, phases are not stored in history and thus are not
25 permanent and leave no audit trail.
25 permanent and leave no audit trail.
26
26
27 First, no changeset can be in two phases at once. Phases are ordered,
27 First, no changeset can be in two phases at once. Phases are ordered,
28 so they can be considered from lowest to highest. The default, lowest
28 so they can be considered from lowest to highest. The default, lowest
29 phase is 'public' - this is the normal phase of existing changesets. A
29 phase is 'public' - this is the normal phase of existing changesets. A
30 child changeset can not be in a lower phase than its parents.
30 child changeset can not be in a lower phase than its parents.
31
31
32 These phases share a hierarchy of traits:
32 These phases share a hierarchy of traits:
33
33
34 immutable shared
34 immutable shared
35 public: X X
35 public: X X
36 draft: X
36 draft: X
37 secret:
37 secret:
38
38
39 Local commits are draft by default.
39 Local commits are draft by default.
40
40
41 Phase Movement and Exchange
41 Phase Movement and Exchange
42 ===========================
42 ===========================
43
43
44 Phase data is exchanged by pushkey on pull and push. Some servers have
44 Phase data is exchanged by pushkey on pull and push. Some servers have
45 a publish option set, we call such a server a "publishing server".
45 a publish option set, we call such a server a "publishing server".
46 Pushing a draft changeset to a publishing server changes the phase to
46 Pushing a draft changeset to a publishing server changes the phase to
47 public.
47 public.
48
48
49 A small list of fact/rules define the exchange of phase:
49 A small list of fact/rules define the exchange of phase:
50
50
51 * old client never changes server states
51 * old client never changes server states
52 * pull never changes server states
52 * pull never changes server states
53 * publish and old server changesets are seen as public by client
53 * publish and old server changesets are seen as public by client
54 * any secret changeset seen in another repository is lowered to at
54 * any secret changeset seen in another repository is lowered to at
55 least draft
55 least draft
56
56
57 Here is the final table summing up the 49 possible use cases of phase
57 Here is the final table summing up the 49 possible use cases of phase
58 exchange:
58 exchange:
59
59
60 server
60 server
61 old publish non-publish
61 old publish non-publish
62 N X N D P N D P
62 N X N D P N D P
63 old client
63 old client
64 pull
64 pull
65 N - X/X - X/D X/P - X/D X/P
65 N - X/X - X/D X/P - X/D X/P
66 X - X/X - X/D X/P - X/D X/P
66 X - X/X - X/D X/P - X/D X/P
67 push
67 push
68 X X/X X/X X/P X/P X/P X/D X/D X/P
68 X X/X X/X X/P X/P X/P X/D X/D X/P
69 new client
69 new client
70 pull
70 pull
71 N - P/X - P/D P/P - D/D P/P
71 N - P/X - P/D P/P - D/D P/P
72 D - P/X - P/D P/P - D/D P/P
72 D - P/X - P/D P/P - D/D P/P
73 P - P/X - P/D P/P - P/D P/P
73 P - P/X - P/D P/P - P/D P/P
74 push
74 push
75 D P/X P/X P/P P/P P/P D/D D/D P/P
75 D P/X P/X P/P P/P P/P D/D D/D P/P
76 P P/X P/X P/P P/P P/P P/P P/P P/P
76 P P/X P/X P/P P/P P/P P/P P/P P/P
77
77
78 Legend:
78 Legend:
79
79
80 A/B = final state on client / state on server
80 A/B = final state on client / state on server
81
81
82 * N = new/not present,
82 * N = new/not present,
83 * P = public,
83 * P = public,
84 * D = draft,
84 * D = draft,
85 * X = not tracked (i.e., the old client or server has no internal
85 * X = not tracked (i.e., the old client or server has no internal
86 way of recording the phase.)
86 way of recording the phase.)
87
87
88 passive = only pushes
88 passive = only pushes
89
89
90
90
91 A cell here can be read like this:
91 A cell here can be read like this:
92
92
93 "When a new client pushes a draft changeset (D) to a publishing
93 "When a new client pushes a draft changeset (D) to a publishing
94 server where it's not present (N), it's marked public on both
94 server where it's not present (N), it's marked public on both
95 sides (P/P)."
95 sides (P/P)."
96
96
97 Note: old client behave as a publishing server with draft only content
97 Note: old client behave as a publishing server with draft only content
98 - other people see it as public
98 - other people see it as public
99 - content is pushed as draft
99 - content is pushed as draft
100
100
101 """
101 """
102
102
103 import errno
103 import errno
104 from node import nullid, nullrev, bin, hex, short
104 from node import nullid, nullrev, bin, hex, short
105 from i18n import _
105 from i18n import _
106 import util
106 import util
107 import obsolete
107
108
108 allphases = public, draft, secret = range(3)
109 allphases = public, draft, secret = range(3)
109 trackedphases = allphases[1:]
110 trackedphases = allphases[1:]
110 phasenames = ['public', 'draft', 'secret']
111 phasenames = ['public', 'draft', 'secret']
111
112
112 def _filterunknown(ui, changelog, phaseroots):
113 def _filterunknown(ui, changelog, phaseroots):
113 """remove unknown nodes from the phase boundary
114 """remove unknown nodes from the phase boundary
114
115
115 Nothing is lost as unknown nodes only hold data for their descendants.
116 Nothing is lost as unknown nodes only hold data for their descendants.
116 """
117 """
117 updated = False
118 updated = False
118 nodemap = changelog.nodemap # to filter unknown nodes
119 nodemap = changelog.nodemap # to filter unknown nodes
119 for phase, nodes in enumerate(phaseroots):
120 for phase, nodes in enumerate(phaseroots):
120 missing = [node for node in nodes if node not in nodemap]
121 missing = [node for node in nodes if node not in nodemap]
121 if missing:
122 if missing:
122 for mnode in missing:
123 for mnode in missing:
123 ui.debug(
124 ui.debug(
124 'removing unknown node %s from %i-phase boundary\n'
125 'removing unknown node %s from %i-phase boundary\n'
125 % (short(mnode), phase))
126 % (short(mnode), phase))
126 nodes.symmetric_difference_update(missing)
127 nodes.symmetric_difference_update(missing)
127 updated = True
128 updated = True
128 return updated
129 return updated
129
130
130 def _readroots(repo, phasedefaults=None):
131 def _readroots(repo, phasedefaults=None):
131 """Read phase roots from disk
132 """Read phase roots from disk
132
133
133 phasedefaults is a list of fn(repo, roots) callable, which are
134 phasedefaults is a list of fn(repo, roots) callable, which are
134 executed if the phase roots file does not exist. When phases are
135 executed if the phase roots file does not exist. When phases are
135 being initialized on an existing repository, this could be used to
136 being initialized on an existing repository, this could be used to
136 set selected changesets phase to something else than public.
137 set selected changesets phase to something else than public.
137
138
138 Return (roots, dirty) where dirty is true if roots differ from
139 Return (roots, dirty) where dirty is true if roots differ from
139 what is being stored.
140 what is being stored.
140 """
141 """
141 dirty = False
142 dirty = False
142 roots = [set() for i in allphases]
143 roots = [set() for i in allphases]
143 try:
144 try:
144 f = repo.sopener('phaseroots')
145 f = repo.sopener('phaseroots')
145 try:
146 try:
146 for line in f:
147 for line in f:
147 phase, nh = line.split()
148 phase, nh = line.split()
148 roots[int(phase)].add(bin(nh))
149 roots[int(phase)].add(bin(nh))
149 finally:
150 finally:
150 f.close()
151 f.close()
151 except IOError, inst:
152 except IOError, inst:
152 if inst.errno != errno.ENOENT:
153 if inst.errno != errno.ENOENT:
153 raise
154 raise
154 if phasedefaults:
155 if phasedefaults:
155 for f in phasedefaults:
156 for f in phasedefaults:
156 roots = f(repo, roots)
157 roots = f(repo, roots)
157 dirty = True
158 dirty = True
158 if _filterunknown(repo.ui, repo.changelog, roots):
159 if _filterunknown(repo.ui, repo.changelog, roots):
159 dirty = True
160 dirty = True
160 return roots, dirty
161 return roots, dirty
161
162
162 class phasecache(object):
163 class phasecache(object):
163 def __init__(self, repo, phasedefaults, _load=True):
164 def __init__(self, repo, phasedefaults, _load=True):
164 if _load:
165 if _load:
165 # Cheap trick to allow shallow-copy without copy module
166 # Cheap trick to allow shallow-copy without copy module
166 self.phaseroots, self.dirty = _readroots(repo, phasedefaults)
167 self.phaseroots, self.dirty = _readroots(repo, phasedefaults)
167 self.opener = repo.sopener
168 self.opener = repo.sopener
168 self._phaserevs = None
169 self._phaserevs = None
169
170
170 def copy(self):
171 def copy(self):
171 # Shallow copy meant to ensure isolation in
172 # Shallow copy meant to ensure isolation in
172 # advance/retractboundary(), nothing more.
173 # advance/retractboundary(), nothing more.
173 ph = phasecache(None, None, _load=False)
174 ph = phasecache(None, None, _load=False)
174 ph.phaseroots = self.phaseroots[:]
175 ph.phaseroots = self.phaseroots[:]
175 ph.dirty = self.dirty
176 ph.dirty = self.dirty
176 ph.opener = self.opener
177 ph.opener = self.opener
177 ph._phaserevs = self._phaserevs
178 ph._phaserevs = self._phaserevs
178 return ph
179 return ph
179
180
180 def replace(self, phcache):
181 def replace(self, phcache):
181 for a in 'phaseroots dirty opener _phaserevs'.split():
182 for a in 'phaseroots dirty opener _phaserevs'.split():
182 setattr(self, a, getattr(phcache, a))
183 setattr(self, a, getattr(phcache, a))
183
184
184 def getphaserevs(self, repo, rebuild=False):
185 def getphaserevs(self, repo, rebuild=False):
185 if rebuild or self._phaserevs is None:
186 if rebuild or self._phaserevs is None:
186 revs = [public] * len(repo.changelog)
187 revs = [public] * len(repo.changelog)
187 for phase in trackedphases:
188 for phase in trackedphases:
188 roots = map(repo.changelog.rev, self.phaseroots[phase])
189 roots = map(repo.changelog.rev, self.phaseroots[phase])
189 if roots:
190 if roots:
190 for rev in roots:
191 for rev in roots:
191 revs[rev] = phase
192 revs[rev] = phase
192 for rev in repo.changelog.descendants(roots):
193 for rev in repo.changelog.descendants(roots):
193 revs[rev] = phase
194 revs[rev] = phase
194 self._phaserevs = revs
195 self._phaserevs = revs
195 return self._phaserevs
196 return self._phaserevs
196
197
197 def phase(self, repo, rev):
198 def phase(self, repo, rev):
198 # We need a repo argument here to be able to build _phaserevs
199 # We need a repo argument here to be able to build _phaserevs
199 # if necessary. The repository instance is not stored in
200 # if necessary. The repository instance is not stored in
200 # phasecache to avoid reference cycles. The changelog instance
201 # phasecache to avoid reference cycles. The changelog instance
201 # is not stored because it is a filecache() property and can
202 # is not stored because it is a filecache() property and can
202 # be replaced without us being notified.
203 # be replaced without us being notified.
203 if rev == nullrev:
204 if rev == nullrev:
204 return public
205 return public
205 if self._phaserevs is None or rev >= len(self._phaserevs):
206 if self._phaserevs is None or rev >= len(self._phaserevs):
206 self._phaserevs = self.getphaserevs(repo, rebuild=True)
207 self._phaserevs = self.getphaserevs(repo, rebuild=True)
207 return self._phaserevs[rev]
208 return self._phaserevs[rev]
208
209
209 def write(self):
210 def write(self):
210 if not self.dirty:
211 if not self.dirty:
211 return
212 return
212 f = self.opener('phaseroots', 'w', atomictemp=True)
213 f = self.opener('phaseroots', 'w', atomictemp=True)
213 try:
214 try:
214 for phase, roots in enumerate(self.phaseroots):
215 for phase, roots in enumerate(self.phaseroots):
215 for h in roots:
216 for h in roots:
216 f.write('%i %s\n' % (phase, hex(h)))
217 f.write('%i %s\n' % (phase, hex(h)))
217 finally:
218 finally:
218 f.close()
219 f.close()
219 self.dirty = False
220 self.dirty = False
220
221
221 def _updateroots(self, phase, newroots):
222 def _updateroots(self, phase, newroots):
222 self.phaseroots[phase] = newroots
223 self.phaseroots[phase] = newroots
223 self._phaserevs = None
224 self._phaserevs = None
224 self.dirty = True
225 self.dirty = True
225
226
226 def advanceboundary(self, repo, targetphase, nodes):
227 def advanceboundary(self, repo, targetphase, nodes):
227 # Be careful to preserve shallow-copied values: do not update
228 # Be careful to preserve shallow-copied values: do not update
228 # phaseroots values, replace them.
229 # phaseroots values, replace them.
229
230
230 delroots = [] # set of root deleted by this path
231 delroots = [] # set of root deleted by this path
231 for phase in xrange(targetphase + 1, len(allphases)):
232 for phase in xrange(targetphase + 1, len(allphases)):
232 # filter nodes that are not in a compatible phase already
233 # filter nodes that are not in a compatible phase already
233 nodes = [n for n in nodes
234 nodes = [n for n in nodes
234 if self.phase(repo, repo[n].rev()) >= phase]
235 if self.phase(repo, repo[n].rev()) >= phase]
235 if not nodes:
236 if not nodes:
236 break # no roots to move anymore
237 break # no roots to move anymore
237 olds = self.phaseroots[phase]
238 olds = self.phaseroots[phase]
238 roots = set(ctx.node() for ctx in repo.set(
239 roots = set(ctx.node() for ctx in repo.set(
239 'roots((%ln::) - (%ln::%ln))', olds, olds, nodes))
240 'roots((%ln::) - (%ln::%ln))', olds, olds, nodes))
240 if olds != roots:
241 if olds != roots:
241 self._updateroots(phase, roots)
242 self._updateroots(phase, roots)
242 # some roots may need to be declared for lower phases
243 # some roots may need to be declared for lower phases
243 delroots.extend(olds - roots)
244 delroots.extend(olds - roots)
244 # declare deleted root in the target phase
245 # declare deleted root in the target phase
245 if targetphase != 0:
246 if targetphase != 0:
246 self.retractboundary(repo, targetphase, delroots)
247 self.retractboundary(repo, targetphase, delroots)
248 obsolete.clearobscaches(repo)
247
249
248 def retractboundary(self, repo, targetphase, nodes):
250 def retractboundary(self, repo, targetphase, nodes):
249 # Be careful to preserve shallow-copied values: do not update
251 # Be careful to preserve shallow-copied values: do not update
250 # phaseroots values, replace them.
252 # phaseroots values, replace them.
251
253
252 currentroots = self.phaseroots[targetphase]
254 currentroots = self.phaseroots[targetphase]
253 newroots = [n for n in nodes
255 newroots = [n for n in nodes
254 if self.phase(repo, repo[n].rev()) < targetphase]
256 if self.phase(repo, repo[n].rev()) < targetphase]
255 if newroots:
257 if newroots:
256 if nullid in newroots:
258 if nullid in newroots:
257 raise util.Abort(_('cannot change null revision phase'))
259 raise util.Abort(_('cannot change null revision phase'))
258 currentroots = currentroots.copy()
260 currentroots = currentroots.copy()
259 currentroots.update(newroots)
261 currentroots.update(newroots)
260 ctxs = repo.set('roots(%ln::)', currentroots)
262 ctxs = repo.set('roots(%ln::)', currentroots)
261 currentroots.intersection_update(ctx.node() for ctx in ctxs)
263 currentroots.intersection_update(ctx.node() for ctx in ctxs)
262 self._updateroots(targetphase, currentroots)
264 self._updateroots(targetphase, currentroots)
265 obsolete.clearobscaches(repo)
263
266
264 def advanceboundary(repo, targetphase, nodes):
267 def advanceboundary(repo, targetphase, nodes):
265 """Add nodes to a phase changing other nodes phases if necessary.
268 """Add nodes to a phase changing other nodes phases if necessary.
266
269
267 This function move boundary *forward* this means that all nodes
270 This function move boundary *forward* this means that all nodes
268 are set in the target phase or kept in a *lower* phase.
271 are set in the target phase or kept in a *lower* phase.
269
272
270 Simplify boundary to contains phase roots only."""
273 Simplify boundary to contains phase roots only."""
271 phcache = repo._phasecache.copy()
274 phcache = repo._phasecache.copy()
272 phcache.advanceboundary(repo, targetphase, nodes)
275 phcache.advanceboundary(repo, targetphase, nodes)
273 repo._phasecache.replace(phcache)
276 repo._phasecache.replace(phcache)
274
277
275 def retractboundary(repo, targetphase, nodes):
278 def retractboundary(repo, targetphase, nodes):
276 """Set nodes back to a phase changing other nodes phases if
279 """Set nodes back to a phase changing other nodes phases if
277 necessary.
280 necessary.
278
281
279 This function move boundary *backward* this means that all nodes
282 This function move boundary *backward* this means that all nodes
280 are set in the target phase or kept in a *higher* phase.
283 are set in the target phase or kept in a *higher* phase.
281
284
282 Simplify boundary to contains phase roots only."""
285 Simplify boundary to contains phase roots only."""
283 phcache = repo._phasecache.copy()
286 phcache = repo._phasecache.copy()
284 phcache.retractboundary(repo, targetphase, nodes)
287 phcache.retractboundary(repo, targetphase, nodes)
285 repo._phasecache.replace(phcache)
288 repo._phasecache.replace(phcache)
286
289
287 def listphases(repo):
290 def listphases(repo):
288 """List phases root for serialization over pushkey"""
291 """List phases root for serialization over pushkey"""
289 keys = {}
292 keys = {}
290 value = '%i' % draft
293 value = '%i' % draft
291 for root in repo._phasecache.phaseroots[draft]:
294 for root in repo._phasecache.phaseroots[draft]:
292 keys[hex(root)] = value
295 keys[hex(root)] = value
293
296
294 if repo.ui.configbool('phases', 'publish', True):
297 if repo.ui.configbool('phases', 'publish', True):
295 # Add an extra data to let remote know we are a publishing
298 # Add an extra data to let remote know we are a publishing
296 # repo. Publishing repo can't just pretend they are old repo.
299 # repo. Publishing repo can't just pretend they are old repo.
297 # When pushing to a publishing repo, the client still need to
300 # When pushing to a publishing repo, the client still need to
298 # push phase boundary
301 # push phase boundary
299 #
302 #
300 # Push do not only push changeset. It also push phase data.
303 # Push do not only push changeset. It also push phase data.
301 # New phase data may apply to common changeset which won't be
304 # New phase data may apply to common changeset which won't be
302 # push (as they are common). Here is a very simple example:
305 # push (as they are common). Here is a very simple example:
303 #
306 #
304 # 1) repo A push changeset X as draft to repo B
307 # 1) repo A push changeset X as draft to repo B
305 # 2) repo B make changeset X public
308 # 2) repo B make changeset X public
306 # 3) repo B push to repo A. X is not pushed but the data that
309 # 3) repo B push to repo A. X is not pushed but the data that
307 # X as now public should
310 # X as now public should
308 #
311 #
309 # The server can't handle it on it's own as it has no idea of
312 # The server can't handle it on it's own as it has no idea of
310 # client phase data.
313 # client phase data.
311 keys['publishing'] = 'True'
314 keys['publishing'] = 'True'
312 return keys
315 return keys
313
316
314 def pushphase(repo, nhex, oldphasestr, newphasestr):
317 def pushphase(repo, nhex, oldphasestr, newphasestr):
315 """List phases root for serialisation over pushkey"""
318 """List phases root for serialisation over pushkey"""
316 lock = repo.lock()
319 lock = repo.lock()
317 try:
320 try:
318 currentphase = repo[nhex].phase()
321 currentphase = repo[nhex].phase()
319 newphase = abs(int(newphasestr)) # let's avoid negative index surprise
322 newphase = abs(int(newphasestr)) # let's avoid negative index surprise
320 oldphase = abs(int(oldphasestr)) # let's avoid negative index surprise
323 oldphase = abs(int(oldphasestr)) # let's avoid negative index surprise
321 if currentphase == oldphase and newphase < oldphase:
324 if currentphase == oldphase and newphase < oldphase:
322 advanceboundary(repo, newphase, [bin(nhex)])
325 advanceboundary(repo, newphase, [bin(nhex)])
323 return 1
326 return 1
324 elif currentphase == newphase:
327 elif currentphase == newphase:
325 # raced, but got correct result
328 # raced, but got correct result
326 return 1
329 return 1
327 else:
330 else:
328 return 0
331 return 0
329 finally:
332 finally:
330 lock.release()
333 lock.release()
331
334
332 def analyzeremotephases(repo, subset, roots):
335 def analyzeremotephases(repo, subset, roots):
333 """Compute phases heads and root in a subset of node from root dict
336 """Compute phases heads and root in a subset of node from root dict
334
337
335 * subset is heads of the subset
338 * subset is heads of the subset
336 * roots is {<nodeid> => phase} mapping. key and value are string.
339 * roots is {<nodeid> => phase} mapping. key and value are string.
337
340
338 Accept unknown element input
341 Accept unknown element input
339 """
342 """
340 # build list from dictionary
343 # build list from dictionary
341 draftroots = []
344 draftroots = []
342 nodemap = repo.changelog.nodemap # to filter unknown nodes
345 nodemap = repo.changelog.nodemap # to filter unknown nodes
343 for nhex, phase in roots.iteritems():
346 for nhex, phase in roots.iteritems():
344 if nhex == 'publishing': # ignore data related to publish option
347 if nhex == 'publishing': # ignore data related to publish option
345 continue
348 continue
346 node = bin(nhex)
349 node = bin(nhex)
347 phase = int(phase)
350 phase = int(phase)
348 if phase == 0:
351 if phase == 0:
349 if node != nullid:
352 if node != nullid:
350 repo.ui.warn(_('ignoring inconsistent public root'
353 repo.ui.warn(_('ignoring inconsistent public root'
351 ' from remote: %s\n') % nhex)
354 ' from remote: %s\n') % nhex)
352 elif phase == 1:
355 elif phase == 1:
353 if node in nodemap:
356 if node in nodemap:
354 draftroots.append(node)
357 draftroots.append(node)
355 else:
358 else:
356 repo.ui.warn(_('ignoring unexpected root from remote: %i %s\n')
359 repo.ui.warn(_('ignoring unexpected root from remote: %i %s\n')
357 % (phase, nhex))
360 % (phase, nhex))
358 # compute heads
361 # compute heads
359 publicheads = newheads(repo, subset, draftroots)
362 publicheads = newheads(repo, subset, draftroots)
360 return publicheads, draftroots
363 return publicheads, draftroots
361
364
362 def newheads(repo, heads, roots):
365 def newheads(repo, heads, roots):
363 """compute new head of a subset minus another
366 """compute new head of a subset minus another
364
367
365 * `heads`: define the first subset
368 * `heads`: define the first subset
366 * `roots`: define the second we subtract from the first"""
369 * `roots`: define the second we subtract from the first"""
367 revset = repo.set('heads((%ln + parents(%ln)) - (%ln::%ln))',
370 revset = repo.set('heads((%ln + parents(%ln)) - (%ln::%ln))',
368 heads, roots, roots, heads)
371 heads, roots, roots, heads)
369 return [c.node() for c in revset]
372 return [c.node() for c in revset]
370
373
371
374
372 def newcommitphase(ui):
375 def newcommitphase(ui):
373 """helper to get the target phase of new commit
376 """helper to get the target phase of new commit
374
377
375 Handle all possible values for the phases.new-commit options.
378 Handle all possible values for the phases.new-commit options.
376
379
377 """
380 """
378 v = ui.config('phases', 'new-commit', draft)
381 v = ui.config('phases', 'new-commit', draft)
379 try:
382 try:
380 return phasenames.index(v)
383 return phasenames.index(v)
381 except ValueError:
384 except ValueError:
382 try:
385 try:
383 return int(v)
386 return int(v)
384 except ValueError:
387 except ValueError:
385 msg = _("phases.new-commit: not a valid phase name ('%s')")
388 msg = _("phases.new-commit: not a valid phase name ('%s')")
386 raise error.ConfigError(msg % v)
389 raise error.ConfigError(msg % v)
387
390
@@ -1,1865 +1,1867 b''
1 # revset.py - revision set queries for mercurial
1 # revset.py - revision set queries for mercurial
2 #
2 #
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import re
8 import re
9 import parser, util, error, discovery, hbisect, phases
9 import parser, util, error, discovery, hbisect, phases
10 import node
10 import node
11 import bookmarks as bookmarksmod
11 import bookmarks as bookmarksmod
12 import match as matchmod
12 import match as matchmod
13 from i18n import _
13 from i18n import _
14 import encoding
14 import encoding
15 import obsolete as obsmod
15
16
16 def _revancestors(repo, revs, followfirst):
17 def _revancestors(repo, revs, followfirst):
17 """Like revlog.ancestors(), but supports followfirst."""
18 """Like revlog.ancestors(), but supports followfirst."""
18 cut = followfirst and 1 or None
19 cut = followfirst and 1 or None
19 cl = repo.changelog
20 cl = repo.changelog
20 visit = util.deque(revs)
21 visit = util.deque(revs)
21 seen = set([node.nullrev])
22 seen = set([node.nullrev])
22 while visit:
23 while visit:
23 for parent in cl.parentrevs(visit.popleft())[:cut]:
24 for parent in cl.parentrevs(visit.popleft())[:cut]:
24 if parent not in seen:
25 if parent not in seen:
25 visit.append(parent)
26 visit.append(parent)
26 seen.add(parent)
27 seen.add(parent)
27 yield parent
28 yield parent
28
29
29 def _revdescendants(repo, revs, followfirst):
30 def _revdescendants(repo, revs, followfirst):
30 """Like revlog.descendants() but supports followfirst."""
31 """Like revlog.descendants() but supports followfirst."""
31 cut = followfirst and 1 or None
32 cut = followfirst and 1 or None
32 cl = repo.changelog
33 cl = repo.changelog
33 first = min(revs)
34 first = min(revs)
34 nullrev = node.nullrev
35 nullrev = node.nullrev
35 if first == nullrev:
36 if first == nullrev:
36 # Are there nodes with a null first parent and a non-null
37 # Are there nodes with a null first parent and a non-null
37 # second one? Maybe. Do we care? Probably not.
38 # second one? Maybe. Do we care? Probably not.
38 for i in cl:
39 for i in cl:
39 yield i
40 yield i
40 return
41 return
41
42
42 seen = set(revs)
43 seen = set(revs)
43 for i in xrange(first + 1, len(cl)):
44 for i in xrange(first + 1, len(cl)):
44 for x in cl.parentrevs(i)[:cut]:
45 for x in cl.parentrevs(i)[:cut]:
45 if x != nullrev and x in seen:
46 if x != nullrev and x in seen:
46 seen.add(i)
47 seen.add(i)
47 yield i
48 yield i
48 break
49 break
49
50
50 def _revsbetween(repo, roots, heads):
51 def _revsbetween(repo, roots, heads):
51 """Return all paths between roots and heads, inclusive of both endpoint
52 """Return all paths between roots and heads, inclusive of both endpoint
52 sets."""
53 sets."""
53 if not roots:
54 if not roots:
54 return []
55 return []
55 parentrevs = repo.changelog.parentrevs
56 parentrevs = repo.changelog.parentrevs
56 visit = heads[:]
57 visit = heads[:]
57 reachable = set()
58 reachable = set()
58 seen = {}
59 seen = {}
59 minroot = min(roots)
60 minroot = min(roots)
60 roots = set(roots)
61 roots = set(roots)
61 # open-code the post-order traversal due to the tiny size of
62 # open-code the post-order traversal due to the tiny size of
62 # sys.getrecursionlimit()
63 # sys.getrecursionlimit()
63 while visit:
64 while visit:
64 rev = visit.pop()
65 rev = visit.pop()
65 if rev in roots:
66 if rev in roots:
66 reachable.add(rev)
67 reachable.add(rev)
67 parents = parentrevs(rev)
68 parents = parentrevs(rev)
68 seen[rev] = parents
69 seen[rev] = parents
69 for parent in parents:
70 for parent in parents:
70 if parent >= minroot and parent not in seen:
71 if parent >= minroot and parent not in seen:
71 visit.append(parent)
72 visit.append(parent)
72 if not reachable:
73 if not reachable:
73 return []
74 return []
74 for rev in sorted(seen):
75 for rev in sorted(seen):
75 for parent in seen[rev]:
76 for parent in seen[rev]:
76 if parent in reachable:
77 if parent in reachable:
77 reachable.add(rev)
78 reachable.add(rev)
78 return sorted(reachable)
79 return sorted(reachable)
79
80
80 elements = {
81 elements = {
81 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
82 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
82 "~": (18, None, ("ancestor", 18)),
83 "~": (18, None, ("ancestor", 18)),
83 "^": (18, None, ("parent", 18), ("parentpost", 18)),
84 "^": (18, None, ("parent", 18), ("parentpost", 18)),
84 "-": (5, ("negate", 19), ("minus", 5)),
85 "-": (5, ("negate", 19), ("minus", 5)),
85 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
86 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
86 ("dagrangepost", 17)),
87 ("dagrangepost", 17)),
87 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
88 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
88 ("dagrangepost", 17)),
89 ("dagrangepost", 17)),
89 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
90 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
90 "not": (10, ("not", 10)),
91 "not": (10, ("not", 10)),
91 "!": (10, ("not", 10)),
92 "!": (10, ("not", 10)),
92 "and": (5, None, ("and", 5)),
93 "and": (5, None, ("and", 5)),
93 "&": (5, None, ("and", 5)),
94 "&": (5, None, ("and", 5)),
94 "or": (4, None, ("or", 4)),
95 "or": (4, None, ("or", 4)),
95 "|": (4, None, ("or", 4)),
96 "|": (4, None, ("or", 4)),
96 "+": (4, None, ("or", 4)),
97 "+": (4, None, ("or", 4)),
97 ",": (2, None, ("list", 2)),
98 ",": (2, None, ("list", 2)),
98 ")": (0, None, None),
99 ")": (0, None, None),
99 "symbol": (0, ("symbol",), None),
100 "symbol": (0, ("symbol",), None),
100 "string": (0, ("string",), None),
101 "string": (0, ("string",), None),
101 "end": (0, None, None),
102 "end": (0, None, None),
102 }
103 }
103
104
104 keywords = set(['and', 'or', 'not'])
105 keywords = set(['and', 'or', 'not'])
105
106
106 def tokenize(program):
107 def tokenize(program):
107 pos, l = 0, len(program)
108 pos, l = 0, len(program)
108 while pos < l:
109 while pos < l:
109 c = program[pos]
110 c = program[pos]
110 if c.isspace(): # skip inter-token whitespace
111 if c.isspace(): # skip inter-token whitespace
111 pass
112 pass
112 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
113 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
113 yield ('::', None, pos)
114 yield ('::', None, pos)
114 pos += 1 # skip ahead
115 pos += 1 # skip ahead
115 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
116 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
116 yield ('..', None, pos)
117 yield ('..', None, pos)
117 pos += 1 # skip ahead
118 pos += 1 # skip ahead
118 elif c in "():,-|&+!~^": # handle simple operators
119 elif c in "():,-|&+!~^": # handle simple operators
119 yield (c, None, pos)
120 yield (c, None, pos)
120 elif (c in '"\'' or c == 'r' and
121 elif (c in '"\'' or c == 'r' and
121 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
122 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
122 if c == 'r':
123 if c == 'r':
123 pos += 1
124 pos += 1
124 c = program[pos]
125 c = program[pos]
125 decode = lambda x: x
126 decode = lambda x: x
126 else:
127 else:
127 decode = lambda x: x.decode('string-escape')
128 decode = lambda x: x.decode('string-escape')
128 pos += 1
129 pos += 1
129 s = pos
130 s = pos
130 while pos < l: # find closing quote
131 while pos < l: # find closing quote
131 d = program[pos]
132 d = program[pos]
132 if d == '\\': # skip over escaped characters
133 if d == '\\': # skip over escaped characters
133 pos += 2
134 pos += 2
134 continue
135 continue
135 if d == c:
136 if d == c:
136 yield ('string', decode(program[s:pos]), s)
137 yield ('string', decode(program[s:pos]), s)
137 break
138 break
138 pos += 1
139 pos += 1
139 else:
140 else:
140 raise error.ParseError(_("unterminated string"), s)
141 raise error.ParseError(_("unterminated string"), s)
141 # gather up a symbol/keyword
142 # gather up a symbol/keyword
142 elif c.isalnum() or c in '._' or ord(c) > 127:
143 elif c.isalnum() or c in '._' or ord(c) > 127:
143 s = pos
144 s = pos
144 pos += 1
145 pos += 1
145 while pos < l: # find end of symbol
146 while pos < l: # find end of symbol
146 d = program[pos]
147 d = program[pos]
147 if not (d.isalnum() or d in "._/" or ord(d) > 127):
148 if not (d.isalnum() or d in "._/" or ord(d) > 127):
148 break
149 break
149 if d == '.' and program[pos - 1] == '.': # special case for ..
150 if d == '.' and program[pos - 1] == '.': # special case for ..
150 pos -= 1
151 pos -= 1
151 break
152 break
152 pos += 1
153 pos += 1
153 sym = program[s:pos]
154 sym = program[s:pos]
154 if sym in keywords: # operator keywords
155 if sym in keywords: # operator keywords
155 yield (sym, None, s)
156 yield (sym, None, s)
156 else:
157 else:
157 yield ('symbol', sym, s)
158 yield ('symbol', sym, s)
158 pos -= 1
159 pos -= 1
159 else:
160 else:
160 raise error.ParseError(_("syntax error"), pos)
161 raise error.ParseError(_("syntax error"), pos)
161 pos += 1
162 pos += 1
162 yield ('end', None, pos)
163 yield ('end', None, pos)
163
164
164 # helpers
165 # helpers
165
166
166 def getstring(x, err):
167 def getstring(x, err):
167 if x and (x[0] == 'string' or x[0] == 'symbol'):
168 if x and (x[0] == 'string' or x[0] == 'symbol'):
168 return x[1]
169 return x[1]
169 raise error.ParseError(err)
170 raise error.ParseError(err)
170
171
171 def getlist(x):
172 def getlist(x):
172 if not x:
173 if not x:
173 return []
174 return []
174 if x[0] == 'list':
175 if x[0] == 'list':
175 return getlist(x[1]) + [x[2]]
176 return getlist(x[1]) + [x[2]]
176 return [x]
177 return [x]
177
178
178 def getargs(x, min, max, err):
179 def getargs(x, min, max, err):
179 l = getlist(x)
180 l = getlist(x)
180 if len(l) < min or (max >= 0 and len(l) > max):
181 if len(l) < min or (max >= 0 and len(l) > max):
181 raise error.ParseError(err)
182 raise error.ParseError(err)
182 return l
183 return l
183
184
184 def getset(repo, subset, x):
185 def getset(repo, subset, x):
185 if not x:
186 if not x:
186 raise error.ParseError(_("missing argument"))
187 raise error.ParseError(_("missing argument"))
187 return methods[x[0]](repo, subset, *x[1:])
188 return methods[x[0]](repo, subset, *x[1:])
188
189
189 def _getrevsource(repo, r):
190 def _getrevsource(repo, r):
190 extra = repo[r].extra()
191 extra = repo[r].extra()
191 for label in ('source', 'transplant_source', 'rebase_source'):
192 for label in ('source', 'transplant_source', 'rebase_source'):
192 if label in extra:
193 if label in extra:
193 try:
194 try:
194 return repo[extra[label]].rev()
195 return repo[extra[label]].rev()
195 except error.RepoLookupError:
196 except error.RepoLookupError:
196 pass
197 pass
197 return None
198 return None
198
199
199 # operator methods
200 # operator methods
200
201
201 def stringset(repo, subset, x):
202 def stringset(repo, subset, x):
202 x = repo[x].rev()
203 x = repo[x].rev()
203 if x == -1 and len(subset) == len(repo):
204 if x == -1 and len(subset) == len(repo):
204 return [-1]
205 return [-1]
205 if len(subset) == len(repo) or x in subset:
206 if len(subset) == len(repo) or x in subset:
206 return [x]
207 return [x]
207 return []
208 return []
208
209
209 def symbolset(repo, subset, x):
210 def symbolset(repo, subset, x):
210 if x in symbols:
211 if x in symbols:
211 raise error.ParseError(_("can't use %s here") % x)
212 raise error.ParseError(_("can't use %s here") % x)
212 return stringset(repo, subset, x)
213 return stringset(repo, subset, x)
213
214
214 def rangeset(repo, subset, x, y):
215 def rangeset(repo, subset, x, y):
215 m = getset(repo, subset, x)
216 m = getset(repo, subset, x)
216 if not m:
217 if not m:
217 m = getset(repo, range(len(repo)), x)
218 m = getset(repo, range(len(repo)), x)
218
219
219 n = getset(repo, subset, y)
220 n = getset(repo, subset, y)
220 if not n:
221 if not n:
221 n = getset(repo, range(len(repo)), y)
222 n = getset(repo, range(len(repo)), y)
222
223
223 if not m or not n:
224 if not m or not n:
224 return []
225 return []
225 m, n = m[0], n[-1]
226 m, n = m[0], n[-1]
226
227
227 if m < n:
228 if m < n:
228 r = range(m, n + 1)
229 r = range(m, n + 1)
229 else:
230 else:
230 r = range(m, n - 1, -1)
231 r = range(m, n - 1, -1)
231 s = set(subset)
232 s = set(subset)
232 return [x for x in r if x in s]
233 return [x for x in r if x in s]
233
234
234 def dagrange(repo, subset, x, y):
235 def dagrange(repo, subset, x, y):
235 if subset:
236 if subset:
236 r = range(len(repo))
237 r = range(len(repo))
237 xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y))
238 xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y))
238 s = set(subset)
239 s = set(subset)
239 return [r for r in xs if r in s]
240 return [r for r in xs if r in s]
240 return []
241 return []
241
242
242 def andset(repo, subset, x, y):
243 def andset(repo, subset, x, y):
243 return getset(repo, getset(repo, subset, x), y)
244 return getset(repo, getset(repo, subset, x), y)
244
245
245 def orset(repo, subset, x, y):
246 def orset(repo, subset, x, y):
246 xl = getset(repo, subset, x)
247 xl = getset(repo, subset, x)
247 s = set(xl)
248 s = set(xl)
248 yl = getset(repo, [r for r in subset if r not in s], y)
249 yl = getset(repo, [r for r in subset if r not in s], y)
249 return xl + yl
250 return xl + yl
250
251
251 def notset(repo, subset, x):
252 def notset(repo, subset, x):
252 s = set(getset(repo, subset, x))
253 s = set(getset(repo, subset, x))
253 return [r for r in subset if r not in s]
254 return [r for r in subset if r not in s]
254
255
255 def listset(repo, subset, a, b):
256 def listset(repo, subset, a, b):
256 raise error.ParseError(_("can't use a list in this context"))
257 raise error.ParseError(_("can't use a list in this context"))
257
258
258 def func(repo, subset, a, b):
259 def func(repo, subset, a, b):
259 if a[0] == 'symbol' and a[1] in symbols:
260 if a[0] == 'symbol' and a[1] in symbols:
260 return symbols[a[1]](repo, subset, b)
261 return symbols[a[1]](repo, subset, b)
261 raise error.ParseError(_("not a function: %s") % a[1])
262 raise error.ParseError(_("not a function: %s") % a[1])
262
263
263 # functions
264 # functions
264
265
265 def adds(repo, subset, x):
266 def adds(repo, subset, x):
266 """``adds(pattern)``
267 """``adds(pattern)``
267 Changesets that add a file matching pattern.
268 Changesets that add a file matching pattern.
268 """
269 """
269 # i18n: "adds" is a keyword
270 # i18n: "adds" is a keyword
270 pat = getstring(x, _("adds requires a pattern"))
271 pat = getstring(x, _("adds requires a pattern"))
271 return checkstatus(repo, subset, pat, 1)
272 return checkstatus(repo, subset, pat, 1)
272
273
273 def ancestor(repo, subset, x):
274 def ancestor(repo, subset, x):
274 """``ancestor(single, single)``
275 """``ancestor(single, single)``
275 Greatest common ancestor of the two changesets.
276 Greatest common ancestor of the two changesets.
276 """
277 """
277 # i18n: "ancestor" is a keyword
278 # i18n: "ancestor" is a keyword
278 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
279 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
279 r = range(len(repo))
280 r = range(len(repo))
280 a = getset(repo, r, l[0])
281 a = getset(repo, r, l[0])
281 b = getset(repo, r, l[1])
282 b = getset(repo, r, l[1])
282 if len(a) != 1 or len(b) != 1:
283 if len(a) != 1 or len(b) != 1:
283 # i18n: "ancestor" is a keyword
284 # i18n: "ancestor" is a keyword
284 raise error.ParseError(_("ancestor arguments must be single revisions"))
285 raise error.ParseError(_("ancestor arguments must be single revisions"))
285 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
286 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
286
287
287 return [r for r in an if r in subset]
288 return [r for r in an if r in subset]
288
289
289 def _ancestors(repo, subset, x, followfirst=False):
290 def _ancestors(repo, subset, x, followfirst=False):
290 args = getset(repo, range(len(repo)), x)
291 args = getset(repo, range(len(repo)), x)
291 if not args:
292 if not args:
292 return []
293 return []
293 s = set(_revancestors(repo, args, followfirst)) | set(args)
294 s = set(_revancestors(repo, args, followfirst)) | set(args)
294 return [r for r in subset if r in s]
295 return [r for r in subset if r in s]
295
296
296 def ancestors(repo, subset, x):
297 def ancestors(repo, subset, x):
297 """``ancestors(set)``
298 """``ancestors(set)``
298 Changesets that are ancestors of a changeset in set.
299 Changesets that are ancestors of a changeset in set.
299 """
300 """
300 return _ancestors(repo, subset, x)
301 return _ancestors(repo, subset, x)
301
302
302 def _firstancestors(repo, subset, x):
303 def _firstancestors(repo, subset, x):
303 # ``_firstancestors(set)``
304 # ``_firstancestors(set)``
304 # Like ``ancestors(set)`` but follows only the first parents.
305 # Like ``ancestors(set)`` but follows only the first parents.
305 return _ancestors(repo, subset, x, followfirst=True)
306 return _ancestors(repo, subset, x, followfirst=True)
306
307
307 def ancestorspec(repo, subset, x, n):
308 def ancestorspec(repo, subset, x, n):
308 """``set~n``
309 """``set~n``
309 Changesets that are the Nth ancestor (first parents only) of a changeset
310 Changesets that are the Nth ancestor (first parents only) of a changeset
310 in set.
311 in set.
311 """
312 """
312 try:
313 try:
313 n = int(n[1])
314 n = int(n[1])
314 except (TypeError, ValueError):
315 except (TypeError, ValueError):
315 raise error.ParseError(_("~ expects a number"))
316 raise error.ParseError(_("~ expects a number"))
316 ps = set()
317 ps = set()
317 cl = repo.changelog
318 cl = repo.changelog
318 for r in getset(repo, subset, x):
319 for r in getset(repo, subset, x):
319 for i in range(n):
320 for i in range(n):
320 r = cl.parentrevs(r)[0]
321 r = cl.parentrevs(r)[0]
321 ps.add(r)
322 ps.add(r)
322 return [r for r in subset if r in ps]
323 return [r for r in subset if r in ps]
323
324
324 def author(repo, subset, x):
325 def author(repo, subset, x):
325 """``author(string)``
326 """``author(string)``
326 Alias for ``user(string)``.
327 Alias for ``user(string)``.
327 """
328 """
328 # i18n: "author" is a keyword
329 # i18n: "author" is a keyword
329 n = encoding.lower(getstring(x, _("author requires a string")))
330 n = encoding.lower(getstring(x, _("author requires a string")))
330 kind, pattern, matcher = _substringmatcher(n)
331 kind, pattern, matcher = _substringmatcher(n)
331 return [r for r in subset if matcher(encoding.lower(repo[r].user()))]
332 return [r for r in subset if matcher(encoding.lower(repo[r].user()))]
332
333
333 def bisect(repo, subset, x):
334 def bisect(repo, subset, x):
334 """``bisect(string)``
335 """``bisect(string)``
335 Changesets marked in the specified bisect status:
336 Changesets marked in the specified bisect status:
336
337
337 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
338 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
338 - ``goods``, ``bads`` : csets topologically good/bad
339 - ``goods``, ``bads`` : csets topologically good/bad
339 - ``range`` : csets taking part in the bisection
340 - ``range`` : csets taking part in the bisection
340 - ``pruned`` : csets that are goods, bads or skipped
341 - ``pruned`` : csets that are goods, bads or skipped
341 - ``untested`` : csets whose fate is yet unknown
342 - ``untested`` : csets whose fate is yet unknown
342 - ``ignored`` : csets ignored due to DAG topology
343 - ``ignored`` : csets ignored due to DAG topology
343 - ``current`` : the cset currently being bisected
344 - ``current`` : the cset currently being bisected
344 """
345 """
345 # i18n: "bisect" is a keyword
346 # i18n: "bisect" is a keyword
346 status = getstring(x, _("bisect requires a string")).lower()
347 status = getstring(x, _("bisect requires a string")).lower()
347 state = set(hbisect.get(repo, status))
348 state = set(hbisect.get(repo, status))
348 return [r for r in subset if r in state]
349 return [r for r in subset if r in state]
349
350
350 # Backward-compatibility
351 # Backward-compatibility
351 # - no help entry so that we do not advertise it any more
352 # - no help entry so that we do not advertise it any more
352 def bisected(repo, subset, x):
353 def bisected(repo, subset, x):
353 return bisect(repo, subset, x)
354 return bisect(repo, subset, x)
354
355
355 def bookmark(repo, subset, x):
356 def bookmark(repo, subset, x):
356 """``bookmark([name])``
357 """``bookmark([name])``
357 The named bookmark or all bookmarks.
358 The named bookmark or all bookmarks.
358
359
359 If `name` starts with `re:`, the remainder of the name is treated as
360 If `name` starts with `re:`, the remainder of the name is treated as
360 a regular expression. To match a bookmark that actually starts with `re:`,
361 a regular expression. To match a bookmark that actually starts with `re:`,
361 use the prefix `literal:`.
362 use the prefix `literal:`.
362 """
363 """
363 # i18n: "bookmark" is a keyword
364 # i18n: "bookmark" is a keyword
364 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
365 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
365 if args:
366 if args:
366 bm = getstring(args[0],
367 bm = getstring(args[0],
367 # i18n: "bookmark" is a keyword
368 # i18n: "bookmark" is a keyword
368 _('the argument to bookmark must be a string'))
369 _('the argument to bookmark must be a string'))
369 kind, pattern, matcher = _stringmatcher(bm)
370 kind, pattern, matcher = _stringmatcher(bm)
370 if kind == 'literal':
371 if kind == 'literal':
371 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
372 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
372 if not bmrev:
373 if not bmrev:
373 raise util.Abort(_("bookmark '%s' does not exist") % bm)
374 raise util.Abort(_("bookmark '%s' does not exist") % bm)
374 bmrev = repo[bmrev].rev()
375 bmrev = repo[bmrev].rev()
375 return [r for r in subset if r == bmrev]
376 return [r for r in subset if r == bmrev]
376 else:
377 else:
377 matchrevs = set()
378 matchrevs = set()
378 for name, bmrev in bookmarksmod.listbookmarks(repo).iteritems():
379 for name, bmrev in bookmarksmod.listbookmarks(repo).iteritems():
379 if matcher(name):
380 if matcher(name):
380 matchrevs.add(bmrev)
381 matchrevs.add(bmrev)
381 if not matchrevs:
382 if not matchrevs:
382 raise util.Abort(_("no bookmarks exist that match '%s'")
383 raise util.Abort(_("no bookmarks exist that match '%s'")
383 % pattern)
384 % pattern)
384 bmrevs = set()
385 bmrevs = set()
385 for bmrev in matchrevs:
386 for bmrev in matchrevs:
386 bmrevs.add(repo[bmrev].rev())
387 bmrevs.add(repo[bmrev].rev())
387 return [r for r in subset if r in bmrevs]
388 return [r for r in subset if r in bmrevs]
388
389
389 bms = set([repo[r].rev()
390 bms = set([repo[r].rev()
390 for r in bookmarksmod.listbookmarks(repo).values()])
391 for r in bookmarksmod.listbookmarks(repo).values()])
391 return [r for r in subset if r in bms]
392 return [r for r in subset if r in bms]
392
393
393 def branch(repo, subset, x):
394 def branch(repo, subset, x):
394 """``branch(string or set)``
395 """``branch(string or set)``
395 All changesets belonging to the given branch or the branches of the given
396 All changesets belonging to the given branch or the branches of the given
396 changesets.
397 changesets.
397
398
398 If `string` starts with `re:`, the remainder of the name is treated as
399 If `string` starts with `re:`, the remainder of the name is treated as
399 a regular expression. To match a branch that actually starts with `re:`,
400 a regular expression. To match a branch that actually starts with `re:`,
400 use the prefix `literal:`.
401 use the prefix `literal:`.
401 """
402 """
402 try:
403 try:
403 b = getstring(x, '')
404 b = getstring(x, '')
404 except error.ParseError:
405 except error.ParseError:
405 # not a string, but another revspec, e.g. tip()
406 # not a string, but another revspec, e.g. tip()
406 pass
407 pass
407 else:
408 else:
408 kind, pattern, matcher = _stringmatcher(b)
409 kind, pattern, matcher = _stringmatcher(b)
409 if kind == 'literal':
410 if kind == 'literal':
410 # note: falls through to the revspec case if no branch with
411 # note: falls through to the revspec case if no branch with
411 # this name exists
412 # this name exists
412 if pattern in repo.branchmap():
413 if pattern in repo.branchmap():
413 return [r for r in subset if matcher(repo[r].branch())]
414 return [r for r in subset if matcher(repo[r].branch())]
414 else:
415 else:
415 return [r for r in subset if matcher(repo[r].branch())]
416 return [r for r in subset if matcher(repo[r].branch())]
416
417
417 s = getset(repo, range(len(repo)), x)
418 s = getset(repo, range(len(repo)), x)
418 b = set()
419 b = set()
419 for r in s:
420 for r in s:
420 b.add(repo[r].branch())
421 b.add(repo[r].branch())
421 s = set(s)
422 s = set(s)
422 return [r for r in subset if r in s or repo[r].branch() in b]
423 return [r for r in subset if r in s or repo[r].branch() in b]
423
424
424 def checkstatus(repo, subset, pat, field):
425 def checkstatus(repo, subset, pat, field):
425 m = None
426 m = None
426 s = []
427 s = []
427 hasset = matchmod.patkind(pat) == 'set'
428 hasset = matchmod.patkind(pat) == 'set'
428 fname = None
429 fname = None
429 for r in subset:
430 for r in subset:
430 c = repo[r]
431 c = repo[r]
431 if not m or hasset:
432 if not m or hasset:
432 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
433 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
433 if not m.anypats() and len(m.files()) == 1:
434 if not m.anypats() and len(m.files()) == 1:
434 fname = m.files()[0]
435 fname = m.files()[0]
435 if fname is not None:
436 if fname is not None:
436 if fname not in c.files():
437 if fname not in c.files():
437 continue
438 continue
438 else:
439 else:
439 for f in c.files():
440 for f in c.files():
440 if m(f):
441 if m(f):
441 break
442 break
442 else:
443 else:
443 continue
444 continue
444 files = repo.status(c.p1().node(), c.node())[field]
445 files = repo.status(c.p1().node(), c.node())[field]
445 if fname is not None:
446 if fname is not None:
446 if fname in files:
447 if fname in files:
447 s.append(r)
448 s.append(r)
448 else:
449 else:
449 for f in files:
450 for f in files:
450 if m(f):
451 if m(f):
451 s.append(r)
452 s.append(r)
452 break
453 break
453 return s
454 return s
454
455
455 def _children(repo, narrow, parentset):
456 def _children(repo, narrow, parentset):
456 cs = set()
457 cs = set()
457 pr = repo.changelog.parentrevs
458 pr = repo.changelog.parentrevs
458 for r in narrow:
459 for r in narrow:
459 for p in pr(r):
460 for p in pr(r):
460 if p in parentset:
461 if p in parentset:
461 cs.add(r)
462 cs.add(r)
462 return cs
463 return cs
463
464
464 def children(repo, subset, x):
465 def children(repo, subset, x):
465 """``children(set)``
466 """``children(set)``
466 Child changesets of changesets in set.
467 Child changesets of changesets in set.
467 """
468 """
468 s = set(getset(repo, range(len(repo)), x))
469 s = set(getset(repo, range(len(repo)), x))
469 cs = _children(repo, subset, s)
470 cs = _children(repo, subset, s)
470 return [r for r in subset if r in cs]
471 return [r for r in subset if r in cs]
471
472
472 def closed(repo, subset, x):
473 def closed(repo, subset, x):
473 """``closed()``
474 """``closed()``
474 Changeset is closed.
475 Changeset is closed.
475 """
476 """
476 # i18n: "closed" is a keyword
477 # i18n: "closed" is a keyword
477 getargs(x, 0, 0, _("closed takes no arguments"))
478 getargs(x, 0, 0, _("closed takes no arguments"))
478 return [r for r in subset if repo[r].closesbranch()]
479 return [r for r in subset if repo[r].closesbranch()]
479
480
480 def contains(repo, subset, x):
481 def contains(repo, subset, x):
481 """``contains(pattern)``
482 """``contains(pattern)``
482 Revision contains a file matching pattern. See :hg:`help patterns`
483 Revision contains a file matching pattern. See :hg:`help patterns`
483 for information about file patterns.
484 for information about file patterns.
484 """
485 """
485 # i18n: "contains" is a keyword
486 # i18n: "contains" is a keyword
486 pat = getstring(x, _("contains requires a pattern"))
487 pat = getstring(x, _("contains requires a pattern"))
487 m = None
488 m = None
488 s = []
489 s = []
489 if not matchmod.patkind(pat):
490 if not matchmod.patkind(pat):
490 for r in subset:
491 for r in subset:
491 if pat in repo[r]:
492 if pat in repo[r]:
492 s.append(r)
493 s.append(r)
493 else:
494 else:
494 for r in subset:
495 for r in subset:
495 c = repo[r]
496 c = repo[r]
496 if not m or matchmod.patkind(pat) == 'set':
497 if not m or matchmod.patkind(pat) == 'set':
497 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
498 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
498 for f in c.manifest():
499 for f in c.manifest():
499 if m(f):
500 if m(f):
500 s.append(r)
501 s.append(r)
501 break
502 break
502 return s
503 return s
503
504
504 def converted(repo, subset, x):
505 def converted(repo, subset, x):
505 """``converted([id])``
506 """``converted([id])``
506 Changesets converted from the given identifier in the old repository if
507 Changesets converted from the given identifier in the old repository if
507 present, or all converted changesets if no identifier is specified.
508 present, or all converted changesets if no identifier is specified.
508 """
509 """
509
510
510 # There is exactly no chance of resolving the revision, so do a simple
511 # There is exactly no chance of resolving the revision, so do a simple
511 # string compare and hope for the best
512 # string compare and hope for the best
512
513
513 rev = None
514 rev = None
514 # i18n: "converted" is a keyword
515 # i18n: "converted" is a keyword
515 l = getargs(x, 0, 1, _('converted takes one or no arguments'))
516 l = getargs(x, 0, 1, _('converted takes one or no arguments'))
516 if l:
517 if l:
517 # i18n: "converted" is a keyword
518 # i18n: "converted" is a keyword
518 rev = getstring(l[0], _('converted requires a revision'))
519 rev = getstring(l[0], _('converted requires a revision'))
519
520
520 def _matchvalue(r):
521 def _matchvalue(r):
521 source = repo[r].extra().get('convert_revision', None)
522 source = repo[r].extra().get('convert_revision', None)
522 return source is not None and (rev is None or source.startswith(rev))
523 return source is not None and (rev is None or source.startswith(rev))
523
524
524 return [r for r in subset if _matchvalue(r)]
525 return [r for r in subset if _matchvalue(r)]
525
526
526 def date(repo, subset, x):
527 def date(repo, subset, x):
527 """``date(interval)``
528 """``date(interval)``
528 Changesets within the interval, see :hg:`help dates`.
529 Changesets within the interval, see :hg:`help dates`.
529 """
530 """
530 # i18n: "date" is a keyword
531 # i18n: "date" is a keyword
531 ds = getstring(x, _("date requires a string"))
532 ds = getstring(x, _("date requires a string"))
532 dm = util.matchdate(ds)
533 dm = util.matchdate(ds)
533 return [r for r in subset if dm(repo[r].date()[0])]
534 return [r for r in subset if dm(repo[r].date()[0])]
534
535
535 def desc(repo, subset, x):
536 def desc(repo, subset, x):
536 """``desc(string)``
537 """``desc(string)``
537 Search commit message for string. The match is case-insensitive.
538 Search commit message for string. The match is case-insensitive.
538 """
539 """
539 # i18n: "desc" is a keyword
540 # i18n: "desc" is a keyword
540 ds = encoding.lower(getstring(x, _("desc requires a string")))
541 ds = encoding.lower(getstring(x, _("desc requires a string")))
541 l = []
542 l = []
542 for r in subset:
543 for r in subset:
543 c = repo[r]
544 c = repo[r]
544 if ds in encoding.lower(c.description()):
545 if ds in encoding.lower(c.description()):
545 l.append(r)
546 l.append(r)
546 return l
547 return l
547
548
548 def _descendants(repo, subset, x, followfirst=False):
549 def _descendants(repo, subset, x, followfirst=False):
549 args = getset(repo, range(len(repo)), x)
550 args = getset(repo, range(len(repo)), x)
550 if not args:
551 if not args:
551 return []
552 return []
552 s = set(_revdescendants(repo, args, followfirst)) | set(args)
553 s = set(_revdescendants(repo, args, followfirst)) | set(args)
553 return [r for r in subset if r in s]
554 return [r for r in subset if r in s]
554
555
555 def descendants(repo, subset, x):
556 def descendants(repo, subset, x):
556 """``descendants(set)``
557 """``descendants(set)``
557 Changesets which are descendants of changesets in set.
558 Changesets which are descendants of changesets in set.
558 """
559 """
559 return _descendants(repo, subset, x)
560 return _descendants(repo, subset, x)
560
561
561 def _firstdescendants(repo, subset, x):
562 def _firstdescendants(repo, subset, x):
562 # ``_firstdescendants(set)``
563 # ``_firstdescendants(set)``
563 # Like ``descendants(set)`` but follows only the first parents.
564 # Like ``descendants(set)`` but follows only the first parents.
564 return _descendants(repo, subset, x, followfirst=True)
565 return _descendants(repo, subset, x, followfirst=True)
565
566
566 def destination(repo, subset, x):
567 def destination(repo, subset, x):
567 """``destination([set])``
568 """``destination([set])``
568 Changesets that were created by a graft, transplant or rebase operation,
569 Changesets that were created by a graft, transplant or rebase operation,
569 with the given revisions specified as the source. Omitting the optional set
570 with the given revisions specified as the source. Omitting the optional set
570 is the same as passing all().
571 is the same as passing all().
571 """
572 """
572 if x is not None:
573 if x is not None:
573 args = set(getset(repo, range(len(repo)), x))
574 args = set(getset(repo, range(len(repo)), x))
574 else:
575 else:
575 args = set(getall(repo, range(len(repo)), x))
576 args = set(getall(repo, range(len(repo)), x))
576
577
577 dests = set()
578 dests = set()
578
579
579 # subset contains all of the possible destinations that can be returned, so
580 # subset contains all of the possible destinations that can be returned, so
580 # iterate over them and see if their source(s) were provided in the args.
581 # iterate over them and see if their source(s) were provided in the args.
581 # Even if the immediate src of r is not in the args, src's source (or
582 # Even if the immediate src of r is not in the args, src's source (or
582 # further back) may be. Scanning back further than the immediate src allows
583 # further back) may be. Scanning back further than the immediate src allows
583 # transitive transplants and rebases to yield the same results as transitive
584 # transitive transplants and rebases to yield the same results as transitive
584 # grafts.
585 # grafts.
585 for r in subset:
586 for r in subset:
586 src = _getrevsource(repo, r)
587 src = _getrevsource(repo, r)
587 lineage = None
588 lineage = None
588
589
589 while src is not None:
590 while src is not None:
590 if lineage is None:
591 if lineage is None:
591 lineage = list()
592 lineage = list()
592
593
593 lineage.append(r)
594 lineage.append(r)
594
595
595 # The visited lineage is a match if the current source is in the arg
596 # The visited lineage is a match if the current source is in the arg
596 # set. Since every candidate dest is visited by way of iterating
597 # set. Since every candidate dest is visited by way of iterating
597 # subset, any dests further back in the lineage will be tested by a
598 # subset, any dests further back in the lineage will be tested by a
598 # different iteration over subset. Likewise, if the src was already
599 # different iteration over subset. Likewise, if the src was already
599 # selected, the current lineage can be selected without going back
600 # selected, the current lineage can be selected without going back
600 # further.
601 # further.
601 if src in args or src in dests:
602 if src in args or src in dests:
602 dests.update(lineage)
603 dests.update(lineage)
603 break
604 break
604
605
605 r = src
606 r = src
606 src = _getrevsource(repo, r)
607 src = _getrevsource(repo, r)
607
608
608 return [r for r in subset if r in dests]
609 return [r for r in subset if r in dests]
609
610
610 def draft(repo, subset, x):
611 def draft(repo, subset, x):
611 """``draft()``
612 """``draft()``
612 Changeset in draft phase."""
613 Changeset in draft phase."""
613 # i18n: "draft" is a keyword
614 # i18n: "draft" is a keyword
614 getargs(x, 0, 0, _("draft takes no arguments"))
615 getargs(x, 0, 0, _("draft takes no arguments"))
615 pc = repo._phasecache
616 pc = repo._phasecache
616 return [r for r in subset if pc.phase(repo, r) == phases.draft]
617 return [r for r in subset if pc.phase(repo, r) == phases.draft]
617
618
618 def extinct(repo, subset, x):
619 def extinct(repo, subset, x):
619 """``extinct()``
620 """``extinct()``
620 Obsolete changesets with obsolete descendants only.
621 Obsolete changesets with obsolete descendants only.
621 """
622 """
622 # i18n: "extinct" is a keyword
623 # i18n: "extinct" is a keyword
623 getargs(x, 0, 0, _("extinct takes no arguments"))
624 getargs(x, 0, 0, _("extinct takes no arguments"))
624 extinctset = set(repo.revs('(obsolete()::) - (::(not obsolete()))'))
625 extincts = obsmod.getobscache(repo, 'extinct')
625 return [r for r in subset if r in extinctset]
626 return [r for r in subset if r in extincts]
626
627
627 def extra(repo, subset, x):
628 def extra(repo, subset, x):
628 """``extra(label, [value])``
629 """``extra(label, [value])``
629 Changesets with the given label in the extra metadata, with the given
630 Changesets with the given label in the extra metadata, with the given
630 optional value.
631 optional value.
631
632
632 If `value` starts with `re:`, the remainder of the value is treated as
633 If `value` starts with `re:`, the remainder of the value is treated as
633 a regular expression. To match a value that actually starts with `re:`,
634 a regular expression. To match a value that actually starts with `re:`,
634 use the prefix `literal:`.
635 use the prefix `literal:`.
635 """
636 """
636
637
637 # i18n: "extra" is a keyword
638 # i18n: "extra" is a keyword
638 l = getargs(x, 1, 2, _('extra takes at least 1 and at most 2 arguments'))
639 l = getargs(x, 1, 2, _('extra takes at least 1 and at most 2 arguments'))
639 # i18n: "extra" is a keyword
640 # i18n: "extra" is a keyword
640 label = getstring(l[0], _('first argument to extra must be a string'))
641 label = getstring(l[0], _('first argument to extra must be a string'))
641 value = None
642 value = None
642
643
643 if len(l) > 1:
644 if len(l) > 1:
644 # i18n: "extra" is a keyword
645 # i18n: "extra" is a keyword
645 value = getstring(l[1], _('second argument to extra must be a string'))
646 value = getstring(l[1], _('second argument to extra must be a string'))
646 kind, value, matcher = _stringmatcher(value)
647 kind, value, matcher = _stringmatcher(value)
647
648
648 def _matchvalue(r):
649 def _matchvalue(r):
649 extra = repo[r].extra()
650 extra = repo[r].extra()
650 return label in extra and (value is None or matcher(extra[label]))
651 return label in extra and (value is None or matcher(extra[label]))
651
652
652 return [r for r in subset if _matchvalue(r)]
653 return [r for r in subset if _matchvalue(r)]
653
654
654 def filelog(repo, subset, x):
655 def filelog(repo, subset, x):
655 """``filelog(pattern)``
656 """``filelog(pattern)``
656 Changesets connected to the specified filelog.
657 Changesets connected to the specified filelog.
657
658
658 For performance reasons, ``filelog()`` does not show every changeset
659 For performance reasons, ``filelog()`` does not show every changeset
659 that affects the requested file(s). See :hg:`help log` for details. For
660 that affects the requested file(s). See :hg:`help log` for details. For
660 a slower, more accurate result, use ``file()``.
661 a slower, more accurate result, use ``file()``.
661 """
662 """
662
663
663 # i18n: "filelog" is a keyword
664 # i18n: "filelog" is a keyword
664 pat = getstring(x, _("filelog requires a pattern"))
665 pat = getstring(x, _("filelog requires a pattern"))
665 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath',
666 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath',
666 ctx=repo[None])
667 ctx=repo[None])
667 s = set()
668 s = set()
668
669
669 if not matchmod.patkind(pat):
670 if not matchmod.patkind(pat):
670 for f in m.files():
671 for f in m.files():
671 fl = repo.file(f)
672 fl = repo.file(f)
672 for fr in fl:
673 for fr in fl:
673 s.add(fl.linkrev(fr))
674 s.add(fl.linkrev(fr))
674 else:
675 else:
675 for f in repo[None]:
676 for f in repo[None]:
676 if m(f):
677 if m(f):
677 fl = repo.file(f)
678 fl = repo.file(f)
678 for fr in fl:
679 for fr in fl:
679 s.add(fl.linkrev(fr))
680 s.add(fl.linkrev(fr))
680
681
681 return [r for r in subset if r in s]
682 return [r for r in subset if r in s]
682
683
683 def first(repo, subset, x):
684 def first(repo, subset, x):
684 """``first(set, [n])``
685 """``first(set, [n])``
685 An alias for limit().
686 An alias for limit().
686 """
687 """
687 return limit(repo, subset, x)
688 return limit(repo, subset, x)
688
689
689 def _follow(repo, subset, x, name, followfirst=False):
690 def _follow(repo, subset, x, name, followfirst=False):
690 l = getargs(x, 0, 1, _("%s takes no arguments or a filename") % name)
691 l = getargs(x, 0, 1, _("%s takes no arguments or a filename") % name)
691 c = repo['.']
692 c = repo['.']
692 if l:
693 if l:
693 x = getstring(l[0], _("%s expected a filename") % name)
694 x = getstring(l[0], _("%s expected a filename") % name)
694 if x in c:
695 if x in c:
695 cx = c[x]
696 cx = c[x]
696 s = set(ctx.rev() for ctx in cx.ancestors(followfirst=followfirst))
697 s = set(ctx.rev() for ctx in cx.ancestors(followfirst=followfirst))
697 # include the revision responsible for the most recent version
698 # include the revision responsible for the most recent version
698 s.add(cx.linkrev())
699 s.add(cx.linkrev())
699 else:
700 else:
700 return []
701 return []
701 else:
702 else:
702 s = set(_revancestors(repo, [c.rev()], followfirst)) | set([c.rev()])
703 s = set(_revancestors(repo, [c.rev()], followfirst)) | set([c.rev()])
703
704
704 return [r for r in subset if r in s]
705 return [r for r in subset if r in s]
705
706
706 def follow(repo, subset, x):
707 def follow(repo, subset, x):
707 """``follow([file])``
708 """``follow([file])``
708 An alias for ``::.`` (ancestors of the working copy's first parent).
709 An alias for ``::.`` (ancestors of the working copy's first parent).
709 If a filename is specified, the history of the given file is followed,
710 If a filename is specified, the history of the given file is followed,
710 including copies.
711 including copies.
711 """
712 """
712 return _follow(repo, subset, x, 'follow')
713 return _follow(repo, subset, x, 'follow')
713
714
714 def _followfirst(repo, subset, x):
715 def _followfirst(repo, subset, x):
715 # ``followfirst([file])``
716 # ``followfirst([file])``
716 # Like ``follow([file])`` but follows only the first parent of
717 # Like ``follow([file])`` but follows only the first parent of
717 # every revision or file revision.
718 # every revision or file revision.
718 return _follow(repo, subset, x, '_followfirst', followfirst=True)
719 return _follow(repo, subset, x, '_followfirst', followfirst=True)
719
720
720 def getall(repo, subset, x):
721 def getall(repo, subset, x):
721 """``all()``
722 """``all()``
722 All changesets, the same as ``0:tip``.
723 All changesets, the same as ``0:tip``.
723 """
724 """
724 # i18n: "all" is a keyword
725 # i18n: "all" is a keyword
725 getargs(x, 0, 0, _("all takes no arguments"))
726 getargs(x, 0, 0, _("all takes no arguments"))
726 return subset
727 return subset
727
728
728 def grep(repo, subset, x):
729 def grep(repo, subset, x):
729 """``grep(regex)``
730 """``grep(regex)``
730 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
731 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
731 to ensure special escape characters are handled correctly. Unlike
732 to ensure special escape characters are handled correctly. Unlike
732 ``keyword(string)``, the match is case-sensitive.
733 ``keyword(string)``, the match is case-sensitive.
733 """
734 """
734 try:
735 try:
735 # i18n: "grep" is a keyword
736 # i18n: "grep" is a keyword
736 gr = re.compile(getstring(x, _("grep requires a string")))
737 gr = re.compile(getstring(x, _("grep requires a string")))
737 except re.error, e:
738 except re.error, e:
738 raise error.ParseError(_('invalid match pattern: %s') % e)
739 raise error.ParseError(_('invalid match pattern: %s') % e)
739 l = []
740 l = []
740 for r in subset:
741 for r in subset:
741 c = repo[r]
742 c = repo[r]
742 for e in c.files() + [c.user(), c.description()]:
743 for e in c.files() + [c.user(), c.description()]:
743 if gr.search(e):
744 if gr.search(e):
744 l.append(r)
745 l.append(r)
745 break
746 break
746 return l
747 return l
747
748
748 def _matchfiles(repo, subset, x):
749 def _matchfiles(repo, subset, x):
749 # _matchfiles takes a revset list of prefixed arguments:
750 # _matchfiles takes a revset list of prefixed arguments:
750 #
751 #
751 # [p:foo, i:bar, x:baz]
752 # [p:foo, i:bar, x:baz]
752 #
753 #
753 # builds a match object from them and filters subset. Allowed
754 # builds a match object from them and filters subset. Allowed
754 # prefixes are 'p:' for regular patterns, 'i:' for include
755 # prefixes are 'p:' for regular patterns, 'i:' for include
755 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
756 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
756 # a revision identifier, or the empty string to reference the
757 # a revision identifier, or the empty string to reference the
757 # working directory, from which the match object is
758 # working directory, from which the match object is
758 # initialized. Use 'd:' to set the default matching mode, default
759 # initialized. Use 'd:' to set the default matching mode, default
759 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
760 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
760
761
761 # i18n: "_matchfiles" is a keyword
762 # i18n: "_matchfiles" is a keyword
762 l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
763 l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
763 pats, inc, exc = [], [], []
764 pats, inc, exc = [], [], []
764 hasset = False
765 hasset = False
765 rev, default = None, None
766 rev, default = None, None
766 for arg in l:
767 for arg in l:
767 # i18n: "_matchfiles" is a keyword
768 # i18n: "_matchfiles" is a keyword
768 s = getstring(arg, _("_matchfiles requires string arguments"))
769 s = getstring(arg, _("_matchfiles requires string arguments"))
769 prefix, value = s[:2], s[2:]
770 prefix, value = s[:2], s[2:]
770 if prefix == 'p:':
771 if prefix == 'p:':
771 pats.append(value)
772 pats.append(value)
772 elif prefix == 'i:':
773 elif prefix == 'i:':
773 inc.append(value)
774 inc.append(value)
774 elif prefix == 'x:':
775 elif prefix == 'x:':
775 exc.append(value)
776 exc.append(value)
776 elif prefix == 'r:':
777 elif prefix == 'r:':
777 if rev is not None:
778 if rev is not None:
778 # i18n: "_matchfiles" is a keyword
779 # i18n: "_matchfiles" is a keyword
779 raise error.ParseError(_('_matchfiles expected at most one '
780 raise error.ParseError(_('_matchfiles expected at most one '
780 'revision'))
781 'revision'))
781 rev = value
782 rev = value
782 elif prefix == 'd:':
783 elif prefix == 'd:':
783 if default is not None:
784 if default is not None:
784 # i18n: "_matchfiles" is a keyword
785 # i18n: "_matchfiles" is a keyword
785 raise error.ParseError(_('_matchfiles expected at most one '
786 raise error.ParseError(_('_matchfiles expected at most one '
786 'default mode'))
787 'default mode'))
787 default = value
788 default = value
788 else:
789 else:
789 # i18n: "_matchfiles" is a keyword
790 # i18n: "_matchfiles" is a keyword
790 raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
791 raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
791 if not hasset and matchmod.patkind(value) == 'set':
792 if not hasset and matchmod.patkind(value) == 'set':
792 hasset = True
793 hasset = True
793 if not default:
794 if not default:
794 default = 'glob'
795 default = 'glob'
795 m = None
796 m = None
796 s = []
797 s = []
797 for r in subset:
798 for r in subset:
798 c = repo[r]
799 c = repo[r]
799 if not m or (hasset and rev is None):
800 if not m or (hasset and rev is None):
800 ctx = c
801 ctx = c
801 if rev is not None:
802 if rev is not None:
802 ctx = repo[rev or None]
803 ctx = repo[rev or None]
803 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
804 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
804 exclude=exc, ctx=ctx, default=default)
805 exclude=exc, ctx=ctx, default=default)
805 for f in c.files():
806 for f in c.files():
806 if m(f):
807 if m(f):
807 s.append(r)
808 s.append(r)
808 break
809 break
809 return s
810 return s
810
811
811 def hasfile(repo, subset, x):
812 def hasfile(repo, subset, x):
812 """``file(pattern)``
813 """``file(pattern)``
813 Changesets affecting files matched by pattern.
814 Changesets affecting files matched by pattern.
814
815
815 For a faster but less accurate result, consider using ``filelog()``
816 For a faster but less accurate result, consider using ``filelog()``
816 instead.
817 instead.
817 """
818 """
818 # i18n: "file" is a keyword
819 # i18n: "file" is a keyword
819 pat = getstring(x, _("file requires a pattern"))
820 pat = getstring(x, _("file requires a pattern"))
820 return _matchfiles(repo, subset, ('string', 'p:' + pat))
821 return _matchfiles(repo, subset, ('string', 'p:' + pat))
821
822
822 def head(repo, subset, x):
823 def head(repo, subset, x):
823 """``head()``
824 """``head()``
824 Changeset is a named branch head.
825 Changeset is a named branch head.
825 """
826 """
826 # i18n: "head" is a keyword
827 # i18n: "head" is a keyword
827 getargs(x, 0, 0, _("head takes no arguments"))
828 getargs(x, 0, 0, _("head takes no arguments"))
828 hs = set()
829 hs = set()
829 for b, ls in repo.branchmap().iteritems():
830 for b, ls in repo.branchmap().iteritems():
830 hs.update(repo[h].rev() for h in ls)
831 hs.update(repo[h].rev() for h in ls)
831 return [r for r in subset if r in hs]
832 return [r for r in subset if r in hs]
832
833
833 def heads(repo, subset, x):
834 def heads(repo, subset, x):
834 """``heads(set)``
835 """``heads(set)``
835 Members of set with no children in set.
836 Members of set with no children in set.
836 """
837 """
837 s = getset(repo, subset, x)
838 s = getset(repo, subset, x)
838 ps = set(parents(repo, subset, x))
839 ps = set(parents(repo, subset, x))
839 return [r for r in s if r not in ps]
840 return [r for r in s if r not in ps]
840
841
841 def hidden(repo, subset, x):
842 def hidden(repo, subset, x):
842 """``hidden()``
843 """``hidden()``
843 Hidden changesets.
844 Hidden changesets.
844 """
845 """
845 # i18n: "hidden" is a keyword
846 # i18n: "hidden" is a keyword
846 getargs(x, 0, 0, _("hidden takes no arguments"))
847 getargs(x, 0, 0, _("hidden takes no arguments"))
847 return [r for r in subset if r in repo.hiddenrevs]
848 return [r for r in subset if r in repo.hiddenrevs]
848
849
849 def keyword(repo, subset, x):
850 def keyword(repo, subset, x):
850 """``keyword(string)``
851 """``keyword(string)``
851 Search commit message, user name, and names of changed files for
852 Search commit message, user name, and names of changed files for
852 string. The match is case-insensitive.
853 string. The match is case-insensitive.
853 """
854 """
854 # i18n: "keyword" is a keyword
855 # i18n: "keyword" is a keyword
855 kw = encoding.lower(getstring(x, _("keyword requires a string")))
856 kw = encoding.lower(getstring(x, _("keyword requires a string")))
856 l = []
857 l = []
857 for r in subset:
858 for r in subset:
858 c = repo[r]
859 c = repo[r]
859 t = " ".join(c.files() + [c.user(), c.description()])
860 t = " ".join(c.files() + [c.user(), c.description()])
860 if kw in encoding.lower(t):
861 if kw in encoding.lower(t):
861 l.append(r)
862 l.append(r)
862 return l
863 return l
863
864
864 def limit(repo, subset, x):
865 def limit(repo, subset, x):
865 """``limit(set, [n])``
866 """``limit(set, [n])``
866 First n members of set, defaulting to 1.
867 First n members of set, defaulting to 1.
867 """
868 """
868 # i18n: "limit" is a keyword
869 # i18n: "limit" is a keyword
869 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
870 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
870 try:
871 try:
871 lim = 1
872 lim = 1
872 if len(l) == 2:
873 if len(l) == 2:
873 # i18n: "limit" is a keyword
874 # i18n: "limit" is a keyword
874 lim = int(getstring(l[1], _("limit requires a number")))
875 lim = int(getstring(l[1], _("limit requires a number")))
875 except (TypeError, ValueError):
876 except (TypeError, ValueError):
876 # i18n: "limit" is a keyword
877 # i18n: "limit" is a keyword
877 raise error.ParseError(_("limit expects a number"))
878 raise error.ParseError(_("limit expects a number"))
878 ss = set(subset)
879 ss = set(subset)
879 os = getset(repo, range(len(repo)), l[0])[:lim]
880 os = getset(repo, range(len(repo)), l[0])[:lim]
880 return [r for r in os if r in ss]
881 return [r for r in os if r in ss]
881
882
882 def last(repo, subset, x):
883 def last(repo, subset, x):
883 """``last(set, [n])``
884 """``last(set, [n])``
884 Last n members of set, defaulting to 1.
885 Last n members of set, defaulting to 1.
885 """
886 """
886 # i18n: "last" is a keyword
887 # i18n: "last" is a keyword
887 l = getargs(x, 1, 2, _("last requires one or two arguments"))
888 l = getargs(x, 1, 2, _("last requires one or two arguments"))
888 try:
889 try:
889 lim = 1
890 lim = 1
890 if len(l) == 2:
891 if len(l) == 2:
891 # i18n: "last" is a keyword
892 # i18n: "last" is a keyword
892 lim = int(getstring(l[1], _("last requires a number")))
893 lim = int(getstring(l[1], _("last requires a number")))
893 except (TypeError, ValueError):
894 except (TypeError, ValueError):
894 # i18n: "last" is a keyword
895 # i18n: "last" is a keyword
895 raise error.ParseError(_("last expects a number"))
896 raise error.ParseError(_("last expects a number"))
896 ss = set(subset)
897 ss = set(subset)
897 os = getset(repo, range(len(repo)), l[0])[-lim:]
898 os = getset(repo, range(len(repo)), l[0])[-lim:]
898 return [r for r in os if r in ss]
899 return [r for r in os if r in ss]
899
900
900 def maxrev(repo, subset, x):
901 def maxrev(repo, subset, x):
901 """``max(set)``
902 """``max(set)``
902 Changeset with highest revision number in set.
903 Changeset with highest revision number in set.
903 """
904 """
904 os = getset(repo, range(len(repo)), x)
905 os = getset(repo, range(len(repo)), x)
905 if os:
906 if os:
906 m = max(os)
907 m = max(os)
907 if m in subset:
908 if m in subset:
908 return [m]
909 return [m]
909 return []
910 return []
910
911
911 def merge(repo, subset, x):
912 def merge(repo, subset, x):
912 """``merge()``
913 """``merge()``
913 Changeset is a merge changeset.
914 Changeset is a merge changeset.
914 """
915 """
915 # i18n: "merge" is a keyword
916 # i18n: "merge" is a keyword
916 getargs(x, 0, 0, _("merge takes no arguments"))
917 getargs(x, 0, 0, _("merge takes no arguments"))
917 cl = repo.changelog
918 cl = repo.changelog
918 return [r for r in subset if cl.parentrevs(r)[1] != -1]
919 return [r for r in subset if cl.parentrevs(r)[1] != -1]
919
920
920 def minrev(repo, subset, x):
921 def minrev(repo, subset, x):
921 """``min(set)``
922 """``min(set)``
922 Changeset with lowest revision number in set.
923 Changeset with lowest revision number in set.
923 """
924 """
924 os = getset(repo, range(len(repo)), x)
925 os = getset(repo, range(len(repo)), x)
925 if os:
926 if os:
926 m = min(os)
927 m = min(os)
927 if m in subset:
928 if m in subset:
928 return [m]
929 return [m]
929 return []
930 return []
930
931
931 def modifies(repo, subset, x):
932 def modifies(repo, subset, x):
932 """``modifies(pattern)``
933 """``modifies(pattern)``
933 Changesets modifying files matched by pattern.
934 Changesets modifying files matched by pattern.
934 """
935 """
935 # i18n: "modifies" is a keyword
936 # i18n: "modifies" is a keyword
936 pat = getstring(x, _("modifies requires a pattern"))
937 pat = getstring(x, _("modifies requires a pattern"))
937 return checkstatus(repo, subset, pat, 0)
938 return checkstatus(repo, subset, pat, 0)
938
939
939 def node_(repo, subset, x):
940 def node_(repo, subset, x):
940 """``id(string)``
941 """``id(string)``
941 Revision non-ambiguously specified by the given hex string prefix.
942 Revision non-ambiguously specified by the given hex string prefix.
942 """
943 """
943 # i18n: "id" is a keyword
944 # i18n: "id" is a keyword
944 l = getargs(x, 1, 1, _("id requires one argument"))
945 l = getargs(x, 1, 1, _("id requires one argument"))
945 # i18n: "id" is a keyword
946 # i18n: "id" is a keyword
946 n = getstring(l[0], _("id requires a string"))
947 n = getstring(l[0], _("id requires a string"))
947 if len(n) == 40:
948 if len(n) == 40:
948 rn = repo[n].rev()
949 rn = repo[n].rev()
949 else:
950 else:
950 rn = None
951 rn = None
951 pm = repo.changelog._partialmatch(n)
952 pm = repo.changelog._partialmatch(n)
952 if pm is not None:
953 if pm is not None:
953 rn = repo.changelog.rev(pm)
954 rn = repo.changelog.rev(pm)
954
955
955 return [r for r in subset if r == rn]
956 return [r for r in subset if r == rn]
956
957
957 def obsolete(repo, subset, x):
958 def obsolete(repo, subset, x):
958 """``obsolete()``
959 """``obsolete()``
959 Mutable changeset with a newer version."""
960 Mutable changeset with a newer version."""
960 # i18n: "obsolete" is a keyword
961 # i18n: "obsolete" is a keyword
961 getargs(x, 0, 0, _("obsolete takes no arguments"))
962 getargs(x, 0, 0, _("obsolete takes no arguments"))
962 return [r for r in subset if repo[r].obsolete()]
963 obsoletes = obsmod.getobscache(repo, 'obsolete')
964 return [r for r in subset if r in obsoletes]
963
965
964 def origin(repo, subset, x):
966 def origin(repo, subset, x):
965 """``origin([set])``
967 """``origin([set])``
966 Changesets that were specified as a source for the grafts, transplants or
968 Changesets that were specified as a source for the grafts, transplants or
967 rebases that created the given revisions. Omitting the optional set is the
969 rebases that created the given revisions. Omitting the optional set is the
968 same as passing all(). If a changeset created by these operations is itself
970 same as passing all(). If a changeset created by these operations is itself
969 specified as a source for one of these operations, only the source changeset
971 specified as a source for one of these operations, only the source changeset
970 for the first operation is selected.
972 for the first operation is selected.
971 """
973 """
972 if x is not None:
974 if x is not None:
973 args = set(getset(repo, range(len(repo)), x))
975 args = set(getset(repo, range(len(repo)), x))
974 else:
976 else:
975 args = set(getall(repo, range(len(repo)), x))
977 args = set(getall(repo, range(len(repo)), x))
976
978
977 def _firstsrc(rev):
979 def _firstsrc(rev):
978 src = _getrevsource(repo, rev)
980 src = _getrevsource(repo, rev)
979 if src is None:
981 if src is None:
980 return None
982 return None
981
983
982 while True:
984 while True:
983 prev = _getrevsource(repo, src)
985 prev = _getrevsource(repo, src)
984
986
985 if prev is None:
987 if prev is None:
986 return src
988 return src
987 src = prev
989 src = prev
988
990
989 o = set([_firstsrc(r) for r in args])
991 o = set([_firstsrc(r) for r in args])
990 return [r for r in subset if r in o]
992 return [r for r in subset if r in o]
991
993
992 def outgoing(repo, subset, x):
994 def outgoing(repo, subset, x):
993 """``outgoing([path])``
995 """``outgoing([path])``
994 Changesets not found in the specified destination repository, or the
996 Changesets not found in the specified destination repository, or the
995 default push location.
997 default push location.
996 """
998 """
997 import hg # avoid start-up nasties
999 import hg # avoid start-up nasties
998 # i18n: "outgoing" is a keyword
1000 # i18n: "outgoing" is a keyword
999 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
1001 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
1000 # i18n: "outgoing" is a keyword
1002 # i18n: "outgoing" is a keyword
1001 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
1003 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
1002 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
1004 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
1003 dest, branches = hg.parseurl(dest)
1005 dest, branches = hg.parseurl(dest)
1004 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1006 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1005 if revs:
1007 if revs:
1006 revs = [repo.lookup(rev) for rev in revs]
1008 revs = [repo.lookup(rev) for rev in revs]
1007 other = hg.peer(repo, {}, dest)
1009 other = hg.peer(repo, {}, dest)
1008 repo.ui.pushbuffer()
1010 repo.ui.pushbuffer()
1009 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1011 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1010 repo.ui.popbuffer()
1012 repo.ui.popbuffer()
1011 cl = repo.changelog
1013 cl = repo.changelog
1012 o = set([cl.rev(r) for r in outgoing.missing])
1014 o = set([cl.rev(r) for r in outgoing.missing])
1013 return [r for r in subset if r in o]
1015 return [r for r in subset if r in o]
1014
1016
1015 def p1(repo, subset, x):
1017 def p1(repo, subset, x):
1016 """``p1([set])``
1018 """``p1([set])``
1017 First parent of changesets in set, or the working directory.
1019 First parent of changesets in set, or the working directory.
1018 """
1020 """
1019 if x is None:
1021 if x is None:
1020 p = repo[x].p1().rev()
1022 p = repo[x].p1().rev()
1021 return [r for r in subset if r == p]
1023 return [r for r in subset if r == p]
1022
1024
1023 ps = set()
1025 ps = set()
1024 cl = repo.changelog
1026 cl = repo.changelog
1025 for r in getset(repo, range(len(repo)), x):
1027 for r in getset(repo, range(len(repo)), x):
1026 ps.add(cl.parentrevs(r)[0])
1028 ps.add(cl.parentrevs(r)[0])
1027 return [r for r in subset if r in ps]
1029 return [r for r in subset if r in ps]
1028
1030
1029 def p2(repo, subset, x):
1031 def p2(repo, subset, x):
1030 """``p2([set])``
1032 """``p2([set])``
1031 Second parent of changesets in set, or the working directory.
1033 Second parent of changesets in set, or the working directory.
1032 """
1034 """
1033 if x is None:
1035 if x is None:
1034 ps = repo[x].parents()
1036 ps = repo[x].parents()
1035 try:
1037 try:
1036 p = ps[1].rev()
1038 p = ps[1].rev()
1037 return [r for r in subset if r == p]
1039 return [r for r in subset if r == p]
1038 except IndexError:
1040 except IndexError:
1039 return []
1041 return []
1040
1042
1041 ps = set()
1043 ps = set()
1042 cl = repo.changelog
1044 cl = repo.changelog
1043 for r in getset(repo, range(len(repo)), x):
1045 for r in getset(repo, range(len(repo)), x):
1044 ps.add(cl.parentrevs(r)[1])
1046 ps.add(cl.parentrevs(r)[1])
1045 return [r for r in subset if r in ps]
1047 return [r for r in subset if r in ps]
1046
1048
1047 def parents(repo, subset, x):
1049 def parents(repo, subset, x):
1048 """``parents([set])``
1050 """``parents([set])``
1049 The set of all parents for all changesets in set, or the working directory.
1051 The set of all parents for all changesets in set, or the working directory.
1050 """
1052 """
1051 if x is None:
1053 if x is None:
1052 ps = tuple(p.rev() for p in repo[x].parents())
1054 ps = tuple(p.rev() for p in repo[x].parents())
1053 return [r for r in subset if r in ps]
1055 return [r for r in subset if r in ps]
1054
1056
1055 ps = set()
1057 ps = set()
1056 cl = repo.changelog
1058 cl = repo.changelog
1057 for r in getset(repo, range(len(repo)), x):
1059 for r in getset(repo, range(len(repo)), x):
1058 ps.update(cl.parentrevs(r))
1060 ps.update(cl.parentrevs(r))
1059 return [r for r in subset if r in ps]
1061 return [r for r in subset if r in ps]
1060
1062
1061 def parentspec(repo, subset, x, n):
1063 def parentspec(repo, subset, x, n):
1062 """``set^0``
1064 """``set^0``
1063 The set.
1065 The set.
1064 ``set^1`` (or ``set^``), ``set^2``
1066 ``set^1`` (or ``set^``), ``set^2``
1065 First or second parent, respectively, of all changesets in set.
1067 First or second parent, respectively, of all changesets in set.
1066 """
1068 """
1067 try:
1069 try:
1068 n = int(n[1])
1070 n = int(n[1])
1069 if n not in (0, 1, 2):
1071 if n not in (0, 1, 2):
1070 raise ValueError
1072 raise ValueError
1071 except (TypeError, ValueError):
1073 except (TypeError, ValueError):
1072 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
1074 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
1073 ps = set()
1075 ps = set()
1074 cl = repo.changelog
1076 cl = repo.changelog
1075 for r in getset(repo, subset, x):
1077 for r in getset(repo, subset, x):
1076 if n == 0:
1078 if n == 0:
1077 ps.add(r)
1079 ps.add(r)
1078 elif n == 1:
1080 elif n == 1:
1079 ps.add(cl.parentrevs(r)[0])
1081 ps.add(cl.parentrevs(r)[0])
1080 elif n == 2:
1082 elif n == 2:
1081 parents = cl.parentrevs(r)
1083 parents = cl.parentrevs(r)
1082 if len(parents) > 1:
1084 if len(parents) > 1:
1083 ps.add(parents[1])
1085 ps.add(parents[1])
1084 return [r for r in subset if r in ps]
1086 return [r for r in subset if r in ps]
1085
1087
1086 def present(repo, subset, x):
1088 def present(repo, subset, x):
1087 """``present(set)``
1089 """``present(set)``
1088 An empty set, if any revision in set isn't found; otherwise,
1090 An empty set, if any revision in set isn't found; otherwise,
1089 all revisions in set.
1091 all revisions in set.
1090
1092
1091 If any of specified revisions is not present in the local repository,
1093 If any of specified revisions is not present in the local repository,
1092 the query is normally aborted. But this predicate allows the query
1094 the query is normally aborted. But this predicate allows the query
1093 to continue even in such cases.
1095 to continue even in such cases.
1094 """
1096 """
1095 try:
1097 try:
1096 return getset(repo, subset, x)
1098 return getset(repo, subset, x)
1097 except error.RepoLookupError:
1099 except error.RepoLookupError:
1098 return []
1100 return []
1099
1101
1100 def public(repo, subset, x):
1102 def public(repo, subset, x):
1101 """``public()``
1103 """``public()``
1102 Changeset in public phase."""
1104 Changeset in public phase."""
1103 # i18n: "public" is a keyword
1105 # i18n: "public" is a keyword
1104 getargs(x, 0, 0, _("public takes no arguments"))
1106 getargs(x, 0, 0, _("public takes no arguments"))
1105 pc = repo._phasecache
1107 pc = repo._phasecache
1106 return [r for r in subset if pc.phase(repo, r) == phases.public]
1108 return [r for r in subset if pc.phase(repo, r) == phases.public]
1107
1109
1108 def remote(repo, subset, x):
1110 def remote(repo, subset, x):
1109 """``remote([id [,path]])``
1111 """``remote([id [,path]])``
1110 Local revision that corresponds to the given identifier in a
1112 Local revision that corresponds to the given identifier in a
1111 remote repository, if present. Here, the '.' identifier is a
1113 remote repository, if present. Here, the '.' identifier is a
1112 synonym for the current local branch.
1114 synonym for the current local branch.
1113 """
1115 """
1114
1116
1115 import hg # avoid start-up nasties
1117 import hg # avoid start-up nasties
1116 # i18n: "remote" is a keyword
1118 # i18n: "remote" is a keyword
1117 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
1119 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
1118
1120
1119 q = '.'
1121 q = '.'
1120 if len(l) > 0:
1122 if len(l) > 0:
1121 # i18n: "remote" is a keyword
1123 # i18n: "remote" is a keyword
1122 q = getstring(l[0], _("remote requires a string id"))
1124 q = getstring(l[0], _("remote requires a string id"))
1123 if q == '.':
1125 if q == '.':
1124 q = repo['.'].branch()
1126 q = repo['.'].branch()
1125
1127
1126 dest = ''
1128 dest = ''
1127 if len(l) > 1:
1129 if len(l) > 1:
1128 # i18n: "remote" is a keyword
1130 # i18n: "remote" is a keyword
1129 dest = getstring(l[1], _("remote requires a repository path"))
1131 dest = getstring(l[1], _("remote requires a repository path"))
1130 dest = repo.ui.expandpath(dest or 'default')
1132 dest = repo.ui.expandpath(dest or 'default')
1131 dest, branches = hg.parseurl(dest)
1133 dest, branches = hg.parseurl(dest)
1132 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1134 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1133 if revs:
1135 if revs:
1134 revs = [repo.lookup(rev) for rev in revs]
1136 revs = [repo.lookup(rev) for rev in revs]
1135 other = hg.peer(repo, {}, dest)
1137 other = hg.peer(repo, {}, dest)
1136 n = other.lookup(q)
1138 n = other.lookup(q)
1137 if n in repo:
1139 if n in repo:
1138 r = repo[n].rev()
1140 r = repo[n].rev()
1139 if r in subset:
1141 if r in subset:
1140 return [r]
1142 return [r]
1141 return []
1143 return []
1142
1144
1143 def removes(repo, subset, x):
1145 def removes(repo, subset, x):
1144 """``removes(pattern)``
1146 """``removes(pattern)``
1145 Changesets which remove files matching pattern.
1147 Changesets which remove files matching pattern.
1146 """
1148 """
1147 # i18n: "removes" is a keyword
1149 # i18n: "removes" is a keyword
1148 pat = getstring(x, _("removes requires a pattern"))
1150 pat = getstring(x, _("removes requires a pattern"))
1149 return checkstatus(repo, subset, pat, 2)
1151 return checkstatus(repo, subset, pat, 2)
1150
1152
1151 def rev(repo, subset, x):
1153 def rev(repo, subset, x):
1152 """``rev(number)``
1154 """``rev(number)``
1153 Revision with the given numeric identifier.
1155 Revision with the given numeric identifier.
1154 """
1156 """
1155 # i18n: "rev" is a keyword
1157 # i18n: "rev" is a keyword
1156 l = getargs(x, 1, 1, _("rev requires one argument"))
1158 l = getargs(x, 1, 1, _("rev requires one argument"))
1157 try:
1159 try:
1158 # i18n: "rev" is a keyword
1160 # i18n: "rev" is a keyword
1159 l = int(getstring(l[0], _("rev requires a number")))
1161 l = int(getstring(l[0], _("rev requires a number")))
1160 except (TypeError, ValueError):
1162 except (TypeError, ValueError):
1161 # i18n: "rev" is a keyword
1163 # i18n: "rev" is a keyword
1162 raise error.ParseError(_("rev expects a number"))
1164 raise error.ParseError(_("rev expects a number"))
1163 return [r for r in subset if r == l]
1165 return [r for r in subset if r == l]
1164
1166
1165 def matching(repo, subset, x):
1167 def matching(repo, subset, x):
1166 """``matching(revision [, field])``
1168 """``matching(revision [, field])``
1167 Changesets in which a given set of fields match the set of fields in the
1169 Changesets in which a given set of fields match the set of fields in the
1168 selected revision or set.
1170 selected revision or set.
1169
1171
1170 To match more than one field pass the list of fields to match separated
1172 To match more than one field pass the list of fields to match separated
1171 by spaces (e.g. ``author description``).
1173 by spaces (e.g. ``author description``).
1172
1174
1173 Valid fields are most regular revision fields and some special fields.
1175 Valid fields are most regular revision fields and some special fields.
1174
1176
1175 Regular revision fields are ``description``, ``author``, ``branch``,
1177 Regular revision fields are ``description``, ``author``, ``branch``,
1176 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1178 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1177 and ``diff``.
1179 and ``diff``.
1178 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1180 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1179 contents of the revision. Two revisions matching their ``diff`` will
1181 contents of the revision. Two revisions matching their ``diff`` will
1180 also match their ``files``.
1182 also match their ``files``.
1181
1183
1182 Special fields are ``summary`` and ``metadata``:
1184 Special fields are ``summary`` and ``metadata``:
1183 ``summary`` matches the first line of the description.
1185 ``summary`` matches the first line of the description.
1184 ``metadata`` is equivalent to matching ``description user date``
1186 ``metadata`` is equivalent to matching ``description user date``
1185 (i.e. it matches the main metadata fields).
1187 (i.e. it matches the main metadata fields).
1186
1188
1187 ``metadata`` is the default field which is used when no fields are
1189 ``metadata`` is the default field which is used when no fields are
1188 specified. You can match more than one field at a time.
1190 specified. You can match more than one field at a time.
1189 """
1191 """
1190 # i18n: "matching" is a keyword
1192 # i18n: "matching" is a keyword
1191 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1193 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1192
1194
1193 revs = getset(repo, xrange(len(repo)), l[0])
1195 revs = getset(repo, xrange(len(repo)), l[0])
1194
1196
1195 fieldlist = ['metadata']
1197 fieldlist = ['metadata']
1196 if len(l) > 1:
1198 if len(l) > 1:
1197 fieldlist = getstring(l[1],
1199 fieldlist = getstring(l[1],
1198 # i18n: "matching" is a keyword
1200 # i18n: "matching" is a keyword
1199 _("matching requires a string "
1201 _("matching requires a string "
1200 "as its second argument")).split()
1202 "as its second argument")).split()
1201
1203
1202 # Make sure that there are no repeated fields,
1204 # Make sure that there are no repeated fields,
1203 # expand the 'special' 'metadata' field type
1205 # expand the 'special' 'metadata' field type
1204 # and check the 'files' whenever we check the 'diff'
1206 # and check the 'files' whenever we check the 'diff'
1205 fields = []
1207 fields = []
1206 for field in fieldlist:
1208 for field in fieldlist:
1207 if field == 'metadata':
1209 if field == 'metadata':
1208 fields += ['user', 'description', 'date']
1210 fields += ['user', 'description', 'date']
1209 elif field == 'diff':
1211 elif field == 'diff':
1210 # a revision matching the diff must also match the files
1212 # a revision matching the diff must also match the files
1211 # since matching the diff is very costly, make sure to
1213 # since matching the diff is very costly, make sure to
1212 # also match the files first
1214 # also match the files first
1213 fields += ['files', 'diff']
1215 fields += ['files', 'diff']
1214 else:
1216 else:
1215 if field == 'author':
1217 if field == 'author':
1216 field = 'user'
1218 field = 'user'
1217 fields.append(field)
1219 fields.append(field)
1218 fields = set(fields)
1220 fields = set(fields)
1219 if 'summary' in fields and 'description' in fields:
1221 if 'summary' in fields and 'description' in fields:
1220 # If a revision matches its description it also matches its summary
1222 # If a revision matches its description it also matches its summary
1221 fields.discard('summary')
1223 fields.discard('summary')
1222
1224
1223 # We may want to match more than one field
1225 # We may want to match more than one field
1224 # Not all fields take the same amount of time to be matched
1226 # Not all fields take the same amount of time to be matched
1225 # Sort the selected fields in order of increasing matching cost
1227 # Sort the selected fields in order of increasing matching cost
1226 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1228 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1227 'files', 'description', 'substate', 'diff']
1229 'files', 'description', 'substate', 'diff']
1228 def fieldkeyfunc(f):
1230 def fieldkeyfunc(f):
1229 try:
1231 try:
1230 return fieldorder.index(f)
1232 return fieldorder.index(f)
1231 except ValueError:
1233 except ValueError:
1232 # assume an unknown field is very costly
1234 # assume an unknown field is very costly
1233 return len(fieldorder)
1235 return len(fieldorder)
1234 fields = list(fields)
1236 fields = list(fields)
1235 fields.sort(key=fieldkeyfunc)
1237 fields.sort(key=fieldkeyfunc)
1236
1238
1237 # Each field will be matched with its own "getfield" function
1239 # Each field will be matched with its own "getfield" function
1238 # which will be added to the getfieldfuncs array of functions
1240 # which will be added to the getfieldfuncs array of functions
1239 getfieldfuncs = []
1241 getfieldfuncs = []
1240 _funcs = {
1242 _funcs = {
1241 'user': lambda r: repo[r].user(),
1243 'user': lambda r: repo[r].user(),
1242 'branch': lambda r: repo[r].branch(),
1244 'branch': lambda r: repo[r].branch(),
1243 'date': lambda r: repo[r].date(),
1245 'date': lambda r: repo[r].date(),
1244 'description': lambda r: repo[r].description(),
1246 'description': lambda r: repo[r].description(),
1245 'files': lambda r: repo[r].files(),
1247 'files': lambda r: repo[r].files(),
1246 'parents': lambda r: repo[r].parents(),
1248 'parents': lambda r: repo[r].parents(),
1247 'phase': lambda r: repo[r].phase(),
1249 'phase': lambda r: repo[r].phase(),
1248 'substate': lambda r: repo[r].substate,
1250 'substate': lambda r: repo[r].substate,
1249 'summary': lambda r: repo[r].description().splitlines()[0],
1251 'summary': lambda r: repo[r].description().splitlines()[0],
1250 'diff': lambda r: list(repo[r].diff(git=True),)
1252 'diff': lambda r: list(repo[r].diff(git=True),)
1251 }
1253 }
1252 for info in fields:
1254 for info in fields:
1253 getfield = _funcs.get(info, None)
1255 getfield = _funcs.get(info, None)
1254 if getfield is None:
1256 if getfield is None:
1255 raise error.ParseError(
1257 raise error.ParseError(
1256 # i18n: "matching" is a keyword
1258 # i18n: "matching" is a keyword
1257 _("unexpected field name passed to matching: %s") % info)
1259 _("unexpected field name passed to matching: %s") % info)
1258 getfieldfuncs.append(getfield)
1260 getfieldfuncs.append(getfield)
1259 # convert the getfield array of functions into a "getinfo" function
1261 # convert the getfield array of functions into a "getinfo" function
1260 # which returns an array of field values (or a single value if there
1262 # which returns an array of field values (or a single value if there
1261 # is only one field to match)
1263 # is only one field to match)
1262 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1264 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1263
1265
1264 matches = set()
1266 matches = set()
1265 for rev in revs:
1267 for rev in revs:
1266 target = getinfo(rev)
1268 target = getinfo(rev)
1267 for r in subset:
1269 for r in subset:
1268 match = True
1270 match = True
1269 for n, f in enumerate(getfieldfuncs):
1271 for n, f in enumerate(getfieldfuncs):
1270 if target[n] != f(r):
1272 if target[n] != f(r):
1271 match = False
1273 match = False
1272 break
1274 break
1273 if match:
1275 if match:
1274 matches.add(r)
1276 matches.add(r)
1275 return [r for r in subset if r in matches]
1277 return [r for r in subset if r in matches]
1276
1278
1277 def reverse(repo, subset, x):
1279 def reverse(repo, subset, x):
1278 """``reverse(set)``
1280 """``reverse(set)``
1279 Reverse order of set.
1281 Reverse order of set.
1280 """
1282 """
1281 l = getset(repo, subset, x)
1283 l = getset(repo, subset, x)
1282 if not isinstance(l, list):
1284 if not isinstance(l, list):
1283 l = list(l)
1285 l = list(l)
1284 l.reverse()
1286 l.reverse()
1285 return l
1287 return l
1286
1288
1287 def roots(repo, subset, x):
1289 def roots(repo, subset, x):
1288 """``roots(set)``
1290 """``roots(set)``
1289 Changesets in set with no parent changeset in set.
1291 Changesets in set with no parent changeset in set.
1290 """
1292 """
1291 s = set(getset(repo, xrange(len(repo)), x))
1293 s = set(getset(repo, xrange(len(repo)), x))
1292 subset = [r for r in subset if r in s]
1294 subset = [r for r in subset if r in s]
1293 cs = _children(repo, subset, s)
1295 cs = _children(repo, subset, s)
1294 return [r for r in subset if r not in cs]
1296 return [r for r in subset if r not in cs]
1295
1297
1296 def secret(repo, subset, x):
1298 def secret(repo, subset, x):
1297 """``secret()``
1299 """``secret()``
1298 Changeset in secret phase."""
1300 Changeset in secret phase."""
1299 # i18n: "secret" is a keyword
1301 # i18n: "secret" is a keyword
1300 getargs(x, 0, 0, _("secret takes no arguments"))
1302 getargs(x, 0, 0, _("secret takes no arguments"))
1301 pc = repo._phasecache
1303 pc = repo._phasecache
1302 return [r for r in subset if pc.phase(repo, r) == phases.secret]
1304 return [r for r in subset if pc.phase(repo, r) == phases.secret]
1303
1305
1304 def sort(repo, subset, x):
1306 def sort(repo, subset, x):
1305 """``sort(set[, [-]key...])``
1307 """``sort(set[, [-]key...])``
1306 Sort set by keys. The default sort order is ascending, specify a key
1308 Sort set by keys. The default sort order is ascending, specify a key
1307 as ``-key`` to sort in descending order.
1309 as ``-key`` to sort in descending order.
1308
1310
1309 The keys can be:
1311 The keys can be:
1310
1312
1311 - ``rev`` for the revision number,
1313 - ``rev`` for the revision number,
1312 - ``branch`` for the branch name,
1314 - ``branch`` for the branch name,
1313 - ``desc`` for the commit message (description),
1315 - ``desc`` for the commit message (description),
1314 - ``user`` for user name (``author`` can be used as an alias),
1316 - ``user`` for user name (``author`` can be used as an alias),
1315 - ``date`` for the commit date
1317 - ``date`` for the commit date
1316 """
1318 """
1317 # i18n: "sort" is a keyword
1319 # i18n: "sort" is a keyword
1318 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1320 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1319 keys = "rev"
1321 keys = "rev"
1320 if len(l) == 2:
1322 if len(l) == 2:
1321 # i18n: "sort" is a keyword
1323 # i18n: "sort" is a keyword
1322 keys = getstring(l[1], _("sort spec must be a string"))
1324 keys = getstring(l[1], _("sort spec must be a string"))
1323
1325
1324 s = l[0]
1326 s = l[0]
1325 keys = keys.split()
1327 keys = keys.split()
1326 l = []
1328 l = []
1327 def invert(s):
1329 def invert(s):
1328 return "".join(chr(255 - ord(c)) for c in s)
1330 return "".join(chr(255 - ord(c)) for c in s)
1329 for r in getset(repo, subset, s):
1331 for r in getset(repo, subset, s):
1330 c = repo[r]
1332 c = repo[r]
1331 e = []
1333 e = []
1332 for k in keys:
1334 for k in keys:
1333 if k == 'rev':
1335 if k == 'rev':
1334 e.append(r)
1336 e.append(r)
1335 elif k == '-rev':
1337 elif k == '-rev':
1336 e.append(-r)
1338 e.append(-r)
1337 elif k == 'branch':
1339 elif k == 'branch':
1338 e.append(c.branch())
1340 e.append(c.branch())
1339 elif k == '-branch':
1341 elif k == '-branch':
1340 e.append(invert(c.branch()))
1342 e.append(invert(c.branch()))
1341 elif k == 'desc':
1343 elif k == 'desc':
1342 e.append(c.description())
1344 e.append(c.description())
1343 elif k == '-desc':
1345 elif k == '-desc':
1344 e.append(invert(c.description()))
1346 e.append(invert(c.description()))
1345 elif k in 'user author':
1347 elif k in 'user author':
1346 e.append(c.user())
1348 e.append(c.user())
1347 elif k in '-user -author':
1349 elif k in '-user -author':
1348 e.append(invert(c.user()))
1350 e.append(invert(c.user()))
1349 elif k == 'date':
1351 elif k == 'date':
1350 e.append(c.date()[0])
1352 e.append(c.date()[0])
1351 elif k == '-date':
1353 elif k == '-date':
1352 e.append(-c.date()[0])
1354 e.append(-c.date()[0])
1353 else:
1355 else:
1354 raise error.ParseError(_("unknown sort key %r") % k)
1356 raise error.ParseError(_("unknown sort key %r") % k)
1355 e.append(r)
1357 e.append(r)
1356 l.append(e)
1358 l.append(e)
1357 l.sort()
1359 l.sort()
1358 return [e[-1] for e in l]
1360 return [e[-1] for e in l]
1359
1361
1360 def _stringmatcher(pattern):
1362 def _stringmatcher(pattern):
1361 """
1363 """
1362 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1364 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1363 returns the matcher name, pattern, and matcher function.
1365 returns the matcher name, pattern, and matcher function.
1364 missing or unknown prefixes are treated as literal matches.
1366 missing or unknown prefixes are treated as literal matches.
1365
1367
1366 helper for tests:
1368 helper for tests:
1367 >>> def test(pattern, *tests):
1369 >>> def test(pattern, *tests):
1368 ... kind, pattern, matcher = _stringmatcher(pattern)
1370 ... kind, pattern, matcher = _stringmatcher(pattern)
1369 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1371 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1370
1372
1371 exact matching (no prefix):
1373 exact matching (no prefix):
1372 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1374 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1373 ('literal', 'abcdefg', [False, False, True])
1375 ('literal', 'abcdefg', [False, False, True])
1374
1376
1375 regex matching ('re:' prefix)
1377 regex matching ('re:' prefix)
1376 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1378 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1377 ('re', 'a.+b', [False, False, True])
1379 ('re', 'a.+b', [False, False, True])
1378
1380
1379 force exact matches ('literal:' prefix)
1381 force exact matches ('literal:' prefix)
1380 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1382 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1381 ('literal', 're:foobar', [False, True])
1383 ('literal', 're:foobar', [False, True])
1382
1384
1383 unknown prefixes are ignored and treated as literals
1385 unknown prefixes are ignored and treated as literals
1384 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1386 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1385 ('literal', 'foo:bar', [False, False, True])
1387 ('literal', 'foo:bar', [False, False, True])
1386 """
1388 """
1387 if pattern.startswith('re:'):
1389 if pattern.startswith('re:'):
1388 pattern = pattern[3:]
1390 pattern = pattern[3:]
1389 try:
1391 try:
1390 regex = re.compile(pattern)
1392 regex = re.compile(pattern)
1391 except re.error, e:
1393 except re.error, e:
1392 raise error.ParseError(_('invalid regular expression: %s')
1394 raise error.ParseError(_('invalid regular expression: %s')
1393 % e)
1395 % e)
1394 return 're', pattern, regex.search
1396 return 're', pattern, regex.search
1395 elif pattern.startswith('literal:'):
1397 elif pattern.startswith('literal:'):
1396 pattern = pattern[8:]
1398 pattern = pattern[8:]
1397 return 'literal', pattern, pattern.__eq__
1399 return 'literal', pattern, pattern.__eq__
1398
1400
1399 def _substringmatcher(pattern):
1401 def _substringmatcher(pattern):
1400 kind, pattern, matcher = _stringmatcher(pattern)
1402 kind, pattern, matcher = _stringmatcher(pattern)
1401 if kind == 'literal':
1403 if kind == 'literal':
1402 matcher = lambda s: pattern in s
1404 matcher = lambda s: pattern in s
1403 return kind, pattern, matcher
1405 return kind, pattern, matcher
1404
1406
1405 def tag(repo, subset, x):
1407 def tag(repo, subset, x):
1406 """``tag([name])``
1408 """``tag([name])``
1407 The specified tag by name, or all tagged revisions if no name is given.
1409 The specified tag by name, or all tagged revisions if no name is given.
1408 """
1410 """
1409 # i18n: "tag" is a keyword
1411 # i18n: "tag" is a keyword
1410 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1412 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1411 cl = repo.changelog
1413 cl = repo.changelog
1412 if args:
1414 if args:
1413 pattern = getstring(args[0],
1415 pattern = getstring(args[0],
1414 # i18n: "tag" is a keyword
1416 # i18n: "tag" is a keyword
1415 _('the argument to tag must be a string'))
1417 _('the argument to tag must be a string'))
1416 kind, pattern, matcher = _stringmatcher(pattern)
1418 kind, pattern, matcher = _stringmatcher(pattern)
1417 if kind == 'literal':
1419 if kind == 'literal':
1418 # avoid resolving all tags
1420 # avoid resolving all tags
1419 tn = repo._tagscache.tags.get(pattern, None)
1421 tn = repo._tagscache.tags.get(pattern, None)
1420 if tn is None:
1422 if tn is None:
1421 raise util.Abort(_("tag '%s' does not exist") % pattern)
1423 raise util.Abort(_("tag '%s' does not exist") % pattern)
1422 s = set([repo[tn].rev()])
1424 s = set([repo[tn].rev()])
1423 else:
1425 else:
1424 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
1426 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
1425 if not s:
1427 if not s:
1426 raise util.Abort(_("no tags exist that match '%s'") % pattern)
1428 raise util.Abort(_("no tags exist that match '%s'") % pattern)
1427 else:
1429 else:
1428 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1430 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1429 return [r for r in subset if r in s]
1431 return [r for r in subset if r in s]
1430
1432
1431 def tagged(repo, subset, x):
1433 def tagged(repo, subset, x):
1432 return tag(repo, subset, x)
1434 return tag(repo, subset, x)
1433
1435
1434 def unstable(repo, subset, x):
1436 def unstable(repo, subset, x):
1435 """``unstable()``
1437 """``unstable()``
1436 Non-obsolete changesets with obsolete ancestors.
1438 Non-obsolete changesets with obsolete ancestors.
1437 """
1439 """
1438 # i18n: "unstable" is a keyword
1440 # i18n: "unstable" is a keyword
1439 getargs(x, 0, 0, _("unstable takes no arguments"))
1441 getargs(x, 0, 0, _("unstable takes no arguments"))
1440 unstableset = set(repo.revs('(obsolete()::) - obsolete()'))
1442 unstables = obsmod.getobscache(repo, 'unstable')
1441 return [r for r in subset if r in unstableset]
1443 return [r for r in subset if r in unstables]
1442
1444
1443
1445
1444 def user(repo, subset, x):
1446 def user(repo, subset, x):
1445 """``user(string)``
1447 """``user(string)``
1446 User name contains string. The match is case-insensitive.
1448 User name contains string. The match is case-insensitive.
1447
1449
1448 If `string` starts with `re:`, the remainder of the string is treated as
1450 If `string` starts with `re:`, the remainder of the string is treated as
1449 a regular expression. To match a user that actually contains `re:`, use
1451 a regular expression. To match a user that actually contains `re:`, use
1450 the prefix `literal:`.
1452 the prefix `literal:`.
1451 """
1453 """
1452 return author(repo, subset, x)
1454 return author(repo, subset, x)
1453
1455
1454 # for internal use
1456 # for internal use
1455 def _list(repo, subset, x):
1457 def _list(repo, subset, x):
1456 s = getstring(x, "internal error")
1458 s = getstring(x, "internal error")
1457 if not s:
1459 if not s:
1458 return []
1460 return []
1459 if not isinstance(subset, set):
1461 if not isinstance(subset, set):
1460 subset = set(subset)
1462 subset = set(subset)
1461 ls = [repo[r].rev() for r in s.split('\0')]
1463 ls = [repo[r].rev() for r in s.split('\0')]
1462 return [r for r in ls if r in subset]
1464 return [r for r in ls if r in subset]
1463
1465
1464 symbols = {
1466 symbols = {
1465 "adds": adds,
1467 "adds": adds,
1466 "all": getall,
1468 "all": getall,
1467 "ancestor": ancestor,
1469 "ancestor": ancestor,
1468 "ancestors": ancestors,
1470 "ancestors": ancestors,
1469 "_firstancestors": _firstancestors,
1471 "_firstancestors": _firstancestors,
1470 "author": author,
1472 "author": author,
1471 "bisect": bisect,
1473 "bisect": bisect,
1472 "bisected": bisected,
1474 "bisected": bisected,
1473 "bookmark": bookmark,
1475 "bookmark": bookmark,
1474 "branch": branch,
1476 "branch": branch,
1475 "children": children,
1477 "children": children,
1476 "closed": closed,
1478 "closed": closed,
1477 "contains": contains,
1479 "contains": contains,
1478 "converted": converted,
1480 "converted": converted,
1479 "date": date,
1481 "date": date,
1480 "desc": desc,
1482 "desc": desc,
1481 "descendants": descendants,
1483 "descendants": descendants,
1482 "_firstdescendants": _firstdescendants,
1484 "_firstdescendants": _firstdescendants,
1483 "destination": destination,
1485 "destination": destination,
1484 "draft": draft,
1486 "draft": draft,
1485 "extinct": extinct,
1487 "extinct": extinct,
1486 "extra": extra,
1488 "extra": extra,
1487 "file": hasfile,
1489 "file": hasfile,
1488 "filelog": filelog,
1490 "filelog": filelog,
1489 "first": first,
1491 "first": first,
1490 "follow": follow,
1492 "follow": follow,
1491 "_followfirst": _followfirst,
1493 "_followfirst": _followfirst,
1492 "grep": grep,
1494 "grep": grep,
1493 "head": head,
1495 "head": head,
1494 "heads": heads,
1496 "heads": heads,
1495 "hidden": hidden,
1497 "hidden": hidden,
1496 "id": node_,
1498 "id": node_,
1497 "keyword": keyword,
1499 "keyword": keyword,
1498 "last": last,
1500 "last": last,
1499 "limit": limit,
1501 "limit": limit,
1500 "_matchfiles": _matchfiles,
1502 "_matchfiles": _matchfiles,
1501 "max": maxrev,
1503 "max": maxrev,
1502 "merge": merge,
1504 "merge": merge,
1503 "min": minrev,
1505 "min": minrev,
1504 "modifies": modifies,
1506 "modifies": modifies,
1505 "obsolete": obsolete,
1507 "obsolete": obsolete,
1506 "origin": origin,
1508 "origin": origin,
1507 "outgoing": outgoing,
1509 "outgoing": outgoing,
1508 "p1": p1,
1510 "p1": p1,
1509 "p2": p2,
1511 "p2": p2,
1510 "parents": parents,
1512 "parents": parents,
1511 "present": present,
1513 "present": present,
1512 "public": public,
1514 "public": public,
1513 "remote": remote,
1515 "remote": remote,
1514 "removes": removes,
1516 "removes": removes,
1515 "rev": rev,
1517 "rev": rev,
1516 "reverse": reverse,
1518 "reverse": reverse,
1517 "roots": roots,
1519 "roots": roots,
1518 "sort": sort,
1520 "sort": sort,
1519 "secret": secret,
1521 "secret": secret,
1520 "matching": matching,
1522 "matching": matching,
1521 "tag": tag,
1523 "tag": tag,
1522 "tagged": tagged,
1524 "tagged": tagged,
1523 "user": user,
1525 "user": user,
1524 "unstable": unstable,
1526 "unstable": unstable,
1525 "_list": _list,
1527 "_list": _list,
1526 }
1528 }
1527
1529
1528 methods = {
1530 methods = {
1529 "range": rangeset,
1531 "range": rangeset,
1530 "dagrange": dagrange,
1532 "dagrange": dagrange,
1531 "string": stringset,
1533 "string": stringset,
1532 "symbol": symbolset,
1534 "symbol": symbolset,
1533 "and": andset,
1535 "and": andset,
1534 "or": orset,
1536 "or": orset,
1535 "not": notset,
1537 "not": notset,
1536 "list": listset,
1538 "list": listset,
1537 "func": func,
1539 "func": func,
1538 "ancestor": ancestorspec,
1540 "ancestor": ancestorspec,
1539 "parent": parentspec,
1541 "parent": parentspec,
1540 "parentpost": p1,
1542 "parentpost": p1,
1541 }
1543 }
1542
1544
1543 def optimize(x, small):
1545 def optimize(x, small):
1544 if x is None:
1546 if x is None:
1545 return 0, x
1547 return 0, x
1546
1548
1547 smallbonus = 1
1549 smallbonus = 1
1548 if small:
1550 if small:
1549 smallbonus = .5
1551 smallbonus = .5
1550
1552
1551 op = x[0]
1553 op = x[0]
1552 if op == 'minus':
1554 if op == 'minus':
1553 return optimize(('and', x[1], ('not', x[2])), small)
1555 return optimize(('and', x[1], ('not', x[2])), small)
1554 elif op == 'dagrangepre':
1556 elif op == 'dagrangepre':
1555 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
1557 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
1556 elif op == 'dagrangepost':
1558 elif op == 'dagrangepost':
1557 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1559 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1558 elif op == 'rangepre':
1560 elif op == 'rangepre':
1559 return optimize(('range', ('string', '0'), x[1]), small)
1561 return optimize(('range', ('string', '0'), x[1]), small)
1560 elif op == 'rangepost':
1562 elif op == 'rangepost':
1561 return optimize(('range', x[1], ('string', 'tip')), small)
1563 return optimize(('range', x[1], ('string', 'tip')), small)
1562 elif op == 'negate':
1564 elif op == 'negate':
1563 return optimize(('string',
1565 return optimize(('string',
1564 '-' + getstring(x[1], _("can't negate that"))), small)
1566 '-' + getstring(x[1], _("can't negate that"))), small)
1565 elif op in 'string symbol negate':
1567 elif op in 'string symbol negate':
1566 return smallbonus, x # single revisions are small
1568 return smallbonus, x # single revisions are small
1567 elif op == 'and':
1569 elif op == 'and':
1568 wa, ta = optimize(x[1], True)
1570 wa, ta = optimize(x[1], True)
1569 wb, tb = optimize(x[2], True)
1571 wb, tb = optimize(x[2], True)
1570 w = min(wa, wb)
1572 w = min(wa, wb)
1571 if wa > wb:
1573 if wa > wb:
1572 return w, (op, tb, ta)
1574 return w, (op, tb, ta)
1573 return w, (op, ta, tb)
1575 return w, (op, ta, tb)
1574 elif op == 'or':
1576 elif op == 'or':
1575 wa, ta = optimize(x[1], False)
1577 wa, ta = optimize(x[1], False)
1576 wb, tb = optimize(x[2], False)
1578 wb, tb = optimize(x[2], False)
1577 if wb < wa:
1579 if wb < wa:
1578 wb, wa = wa, wb
1580 wb, wa = wa, wb
1579 return max(wa, wb), (op, ta, tb)
1581 return max(wa, wb), (op, ta, tb)
1580 elif op == 'not':
1582 elif op == 'not':
1581 o = optimize(x[1], not small)
1583 o = optimize(x[1], not small)
1582 return o[0], (op, o[1])
1584 return o[0], (op, o[1])
1583 elif op == 'parentpost':
1585 elif op == 'parentpost':
1584 o = optimize(x[1], small)
1586 o = optimize(x[1], small)
1585 return o[0], (op, o[1])
1587 return o[0], (op, o[1])
1586 elif op == 'group':
1588 elif op == 'group':
1587 return optimize(x[1], small)
1589 return optimize(x[1], small)
1588 elif op in 'dagrange range list parent ancestorspec':
1590 elif op in 'dagrange range list parent ancestorspec':
1589 if op == 'parent':
1591 if op == 'parent':
1590 # x^:y means (x^) : y, not x ^ (:y)
1592 # x^:y means (x^) : y, not x ^ (:y)
1591 post = ('parentpost', x[1])
1593 post = ('parentpost', x[1])
1592 if x[2][0] == 'dagrangepre':
1594 if x[2][0] == 'dagrangepre':
1593 return optimize(('dagrange', post, x[2][1]), small)
1595 return optimize(('dagrange', post, x[2][1]), small)
1594 elif x[2][0] == 'rangepre':
1596 elif x[2][0] == 'rangepre':
1595 return optimize(('range', post, x[2][1]), small)
1597 return optimize(('range', post, x[2][1]), small)
1596
1598
1597 wa, ta = optimize(x[1], small)
1599 wa, ta = optimize(x[1], small)
1598 wb, tb = optimize(x[2], small)
1600 wb, tb = optimize(x[2], small)
1599 return wa + wb, (op, ta, tb)
1601 return wa + wb, (op, ta, tb)
1600 elif op == 'func':
1602 elif op == 'func':
1601 f = getstring(x[1], _("not a symbol"))
1603 f = getstring(x[1], _("not a symbol"))
1602 wa, ta = optimize(x[2], small)
1604 wa, ta = optimize(x[2], small)
1603 if f in ("author branch closed date desc file grep keyword "
1605 if f in ("author branch closed date desc file grep keyword "
1604 "outgoing user"):
1606 "outgoing user"):
1605 w = 10 # slow
1607 w = 10 # slow
1606 elif f in "modifies adds removes":
1608 elif f in "modifies adds removes":
1607 w = 30 # slower
1609 w = 30 # slower
1608 elif f == "contains":
1610 elif f == "contains":
1609 w = 100 # very slow
1611 w = 100 # very slow
1610 elif f == "ancestor":
1612 elif f == "ancestor":
1611 w = 1 * smallbonus
1613 w = 1 * smallbonus
1612 elif f in "reverse limit first":
1614 elif f in "reverse limit first":
1613 w = 0
1615 w = 0
1614 elif f in "sort":
1616 elif f in "sort":
1615 w = 10 # assume most sorts look at changelog
1617 w = 10 # assume most sorts look at changelog
1616 else:
1618 else:
1617 w = 1
1619 w = 1
1618 return w + wa, (op, x[1], ta)
1620 return w + wa, (op, x[1], ta)
1619 return 1, x
1621 return 1, x
1620
1622
1621 _aliasarg = ('func', ('symbol', '_aliasarg'))
1623 _aliasarg = ('func', ('symbol', '_aliasarg'))
1622 def _getaliasarg(tree):
1624 def _getaliasarg(tree):
1623 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
1625 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
1624 return X, None otherwise.
1626 return X, None otherwise.
1625 """
1627 """
1626 if (len(tree) == 3 and tree[:2] == _aliasarg
1628 if (len(tree) == 3 and tree[:2] == _aliasarg
1627 and tree[2][0] == 'string'):
1629 and tree[2][0] == 'string'):
1628 return tree[2][1]
1630 return tree[2][1]
1629 return None
1631 return None
1630
1632
1631 def _checkaliasarg(tree, known=None):
1633 def _checkaliasarg(tree, known=None):
1632 """Check tree contains no _aliasarg construct or only ones which
1634 """Check tree contains no _aliasarg construct or only ones which
1633 value is in known. Used to avoid alias placeholders injection.
1635 value is in known. Used to avoid alias placeholders injection.
1634 """
1636 """
1635 if isinstance(tree, tuple):
1637 if isinstance(tree, tuple):
1636 arg = _getaliasarg(tree)
1638 arg = _getaliasarg(tree)
1637 if arg is not None and (not known or arg not in known):
1639 if arg is not None and (not known or arg not in known):
1638 raise error.ParseError(_("not a function: %s") % '_aliasarg')
1640 raise error.ParseError(_("not a function: %s") % '_aliasarg')
1639 for t in tree:
1641 for t in tree:
1640 _checkaliasarg(t, known)
1642 _checkaliasarg(t, known)
1641
1643
1642 class revsetalias(object):
1644 class revsetalias(object):
1643 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1645 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1644 args = None
1646 args = None
1645
1647
1646 def __init__(self, name, value):
1648 def __init__(self, name, value):
1647 '''Aliases like:
1649 '''Aliases like:
1648
1650
1649 h = heads(default)
1651 h = heads(default)
1650 b($1) = ancestors($1) - ancestors(default)
1652 b($1) = ancestors($1) - ancestors(default)
1651 '''
1653 '''
1652 m = self.funcre.search(name)
1654 m = self.funcre.search(name)
1653 if m:
1655 if m:
1654 self.name = m.group(1)
1656 self.name = m.group(1)
1655 self.tree = ('func', ('symbol', m.group(1)))
1657 self.tree = ('func', ('symbol', m.group(1)))
1656 self.args = [x.strip() for x in m.group(2).split(',')]
1658 self.args = [x.strip() for x in m.group(2).split(',')]
1657 for arg in self.args:
1659 for arg in self.args:
1658 # _aliasarg() is an unknown symbol only used separate
1660 # _aliasarg() is an unknown symbol only used separate
1659 # alias argument placeholders from regular strings.
1661 # alias argument placeholders from regular strings.
1660 value = value.replace(arg, '_aliasarg(%r)' % (arg,))
1662 value = value.replace(arg, '_aliasarg(%r)' % (arg,))
1661 else:
1663 else:
1662 self.name = name
1664 self.name = name
1663 self.tree = ('symbol', name)
1665 self.tree = ('symbol', name)
1664
1666
1665 self.replacement, pos = parse(value)
1667 self.replacement, pos = parse(value)
1666 if pos != len(value):
1668 if pos != len(value):
1667 raise error.ParseError(_('invalid token'), pos)
1669 raise error.ParseError(_('invalid token'), pos)
1668 # Check for placeholder injection
1670 # Check for placeholder injection
1669 _checkaliasarg(self.replacement, self.args)
1671 _checkaliasarg(self.replacement, self.args)
1670
1672
1671 def _getalias(aliases, tree):
1673 def _getalias(aliases, tree):
1672 """If tree looks like an unexpanded alias, return it. Return None
1674 """If tree looks like an unexpanded alias, return it. Return None
1673 otherwise.
1675 otherwise.
1674 """
1676 """
1675 if isinstance(tree, tuple) and tree:
1677 if isinstance(tree, tuple) and tree:
1676 if tree[0] == 'symbol' and len(tree) == 2:
1678 if tree[0] == 'symbol' and len(tree) == 2:
1677 name = tree[1]
1679 name = tree[1]
1678 alias = aliases.get(name)
1680 alias = aliases.get(name)
1679 if alias and alias.args is None and alias.tree == tree:
1681 if alias and alias.args is None and alias.tree == tree:
1680 return alias
1682 return alias
1681 if tree[0] == 'func' and len(tree) > 1:
1683 if tree[0] == 'func' and len(tree) > 1:
1682 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
1684 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
1683 name = tree[1][1]
1685 name = tree[1][1]
1684 alias = aliases.get(name)
1686 alias = aliases.get(name)
1685 if alias and alias.args is not None and alias.tree == tree[:2]:
1687 if alias and alias.args is not None and alias.tree == tree[:2]:
1686 return alias
1688 return alias
1687 return None
1689 return None
1688
1690
1689 def _expandargs(tree, args):
1691 def _expandargs(tree, args):
1690 """Replace _aliasarg instances with the substitution value of the
1692 """Replace _aliasarg instances with the substitution value of the
1691 same name in args, recursively.
1693 same name in args, recursively.
1692 """
1694 """
1693 if not tree or not isinstance(tree, tuple):
1695 if not tree or not isinstance(tree, tuple):
1694 return tree
1696 return tree
1695 arg = _getaliasarg(tree)
1697 arg = _getaliasarg(tree)
1696 if arg is not None:
1698 if arg is not None:
1697 return args[arg]
1699 return args[arg]
1698 return tuple(_expandargs(t, args) for t in tree)
1700 return tuple(_expandargs(t, args) for t in tree)
1699
1701
1700 def _expandaliases(aliases, tree, expanding, cache):
1702 def _expandaliases(aliases, tree, expanding, cache):
1701 """Expand aliases in tree, recursively.
1703 """Expand aliases in tree, recursively.
1702
1704
1703 'aliases' is a dictionary mapping user defined aliases to
1705 'aliases' is a dictionary mapping user defined aliases to
1704 revsetalias objects.
1706 revsetalias objects.
1705 """
1707 """
1706 if not isinstance(tree, tuple):
1708 if not isinstance(tree, tuple):
1707 # Do not expand raw strings
1709 # Do not expand raw strings
1708 return tree
1710 return tree
1709 alias = _getalias(aliases, tree)
1711 alias = _getalias(aliases, tree)
1710 if alias is not None:
1712 if alias is not None:
1711 if alias in expanding:
1713 if alias in expanding:
1712 raise error.ParseError(_('infinite expansion of revset alias "%s" '
1714 raise error.ParseError(_('infinite expansion of revset alias "%s" '
1713 'detected') % alias.name)
1715 'detected') % alias.name)
1714 expanding.append(alias)
1716 expanding.append(alias)
1715 if alias.name not in cache:
1717 if alias.name not in cache:
1716 cache[alias.name] = _expandaliases(aliases, alias.replacement,
1718 cache[alias.name] = _expandaliases(aliases, alias.replacement,
1717 expanding, cache)
1719 expanding, cache)
1718 result = cache[alias.name]
1720 result = cache[alias.name]
1719 expanding.pop()
1721 expanding.pop()
1720 if alias.args is not None:
1722 if alias.args is not None:
1721 l = getlist(tree[2])
1723 l = getlist(tree[2])
1722 if len(l) != len(alias.args):
1724 if len(l) != len(alias.args):
1723 raise error.ParseError(
1725 raise error.ParseError(
1724 _('invalid number of arguments: %s') % len(l))
1726 _('invalid number of arguments: %s') % len(l))
1725 l = [_expandaliases(aliases, a, [], cache) for a in l]
1727 l = [_expandaliases(aliases, a, [], cache) for a in l]
1726 result = _expandargs(result, dict(zip(alias.args, l)))
1728 result = _expandargs(result, dict(zip(alias.args, l)))
1727 else:
1729 else:
1728 result = tuple(_expandaliases(aliases, t, expanding, cache)
1730 result = tuple(_expandaliases(aliases, t, expanding, cache)
1729 for t in tree)
1731 for t in tree)
1730 return result
1732 return result
1731
1733
1732 def findaliases(ui, tree):
1734 def findaliases(ui, tree):
1733 _checkaliasarg(tree)
1735 _checkaliasarg(tree)
1734 aliases = {}
1736 aliases = {}
1735 for k, v in ui.configitems('revsetalias'):
1737 for k, v in ui.configitems('revsetalias'):
1736 alias = revsetalias(k, v)
1738 alias = revsetalias(k, v)
1737 aliases[alias.name] = alias
1739 aliases[alias.name] = alias
1738 return _expandaliases(aliases, tree, [], {})
1740 return _expandaliases(aliases, tree, [], {})
1739
1741
1740 parse = parser.parser(tokenize, elements).parse
1742 parse = parser.parser(tokenize, elements).parse
1741
1743
1742 def match(ui, spec):
1744 def match(ui, spec):
1743 if not spec:
1745 if not spec:
1744 raise error.ParseError(_("empty query"))
1746 raise error.ParseError(_("empty query"))
1745 tree, pos = parse(spec)
1747 tree, pos = parse(spec)
1746 if (pos != len(spec)):
1748 if (pos != len(spec)):
1747 raise error.ParseError(_("invalid token"), pos)
1749 raise error.ParseError(_("invalid token"), pos)
1748 if ui:
1750 if ui:
1749 tree = findaliases(ui, tree)
1751 tree = findaliases(ui, tree)
1750 weight, tree = optimize(tree, True)
1752 weight, tree = optimize(tree, True)
1751 def mfunc(repo, subset):
1753 def mfunc(repo, subset):
1752 return getset(repo, subset, tree)
1754 return getset(repo, subset, tree)
1753 return mfunc
1755 return mfunc
1754
1756
1755 def formatspec(expr, *args):
1757 def formatspec(expr, *args):
1756 '''
1758 '''
1757 This is a convenience function for using revsets internally, and
1759 This is a convenience function for using revsets internally, and
1758 escapes arguments appropriately. Aliases are intentionally ignored
1760 escapes arguments appropriately. Aliases are intentionally ignored
1759 so that intended expression behavior isn't accidentally subverted.
1761 so that intended expression behavior isn't accidentally subverted.
1760
1762
1761 Supported arguments:
1763 Supported arguments:
1762
1764
1763 %r = revset expression, parenthesized
1765 %r = revset expression, parenthesized
1764 %d = int(arg), no quoting
1766 %d = int(arg), no quoting
1765 %s = string(arg), escaped and single-quoted
1767 %s = string(arg), escaped and single-quoted
1766 %b = arg.branch(), escaped and single-quoted
1768 %b = arg.branch(), escaped and single-quoted
1767 %n = hex(arg), single-quoted
1769 %n = hex(arg), single-quoted
1768 %% = a literal '%'
1770 %% = a literal '%'
1769
1771
1770 Prefixing the type with 'l' specifies a parenthesized list of that type.
1772 Prefixing the type with 'l' specifies a parenthesized list of that type.
1771
1773
1772 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1774 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1773 '(10 or 11):: and ((this()) or (that()))'
1775 '(10 or 11):: and ((this()) or (that()))'
1774 >>> formatspec('%d:: and not %d::', 10, 20)
1776 >>> formatspec('%d:: and not %d::', 10, 20)
1775 '10:: and not 20::'
1777 '10:: and not 20::'
1776 >>> formatspec('%ld or %ld', [], [1])
1778 >>> formatspec('%ld or %ld', [], [1])
1777 "_list('') or 1"
1779 "_list('') or 1"
1778 >>> formatspec('keyword(%s)', 'foo\\xe9')
1780 >>> formatspec('keyword(%s)', 'foo\\xe9')
1779 "keyword('foo\\\\xe9')"
1781 "keyword('foo\\\\xe9')"
1780 >>> b = lambda: 'default'
1782 >>> b = lambda: 'default'
1781 >>> b.branch = b
1783 >>> b.branch = b
1782 >>> formatspec('branch(%b)', b)
1784 >>> formatspec('branch(%b)', b)
1783 "branch('default')"
1785 "branch('default')"
1784 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1786 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1785 "root(_list('a\\x00b\\x00c\\x00d'))"
1787 "root(_list('a\\x00b\\x00c\\x00d'))"
1786 '''
1788 '''
1787
1789
1788 def quote(s):
1790 def quote(s):
1789 return repr(str(s))
1791 return repr(str(s))
1790
1792
1791 def argtype(c, arg):
1793 def argtype(c, arg):
1792 if c == 'd':
1794 if c == 'd':
1793 return str(int(arg))
1795 return str(int(arg))
1794 elif c == 's':
1796 elif c == 's':
1795 return quote(arg)
1797 return quote(arg)
1796 elif c == 'r':
1798 elif c == 'r':
1797 parse(arg) # make sure syntax errors are confined
1799 parse(arg) # make sure syntax errors are confined
1798 return '(%s)' % arg
1800 return '(%s)' % arg
1799 elif c == 'n':
1801 elif c == 'n':
1800 return quote(node.hex(arg))
1802 return quote(node.hex(arg))
1801 elif c == 'b':
1803 elif c == 'b':
1802 return quote(arg.branch())
1804 return quote(arg.branch())
1803
1805
1804 def listexp(s, t):
1806 def listexp(s, t):
1805 l = len(s)
1807 l = len(s)
1806 if l == 0:
1808 if l == 0:
1807 return "_list('')"
1809 return "_list('')"
1808 elif l == 1:
1810 elif l == 1:
1809 return argtype(t, s[0])
1811 return argtype(t, s[0])
1810 elif t == 'd':
1812 elif t == 'd':
1811 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1813 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1812 elif t == 's':
1814 elif t == 's':
1813 return "_list('%s')" % "\0".join(s)
1815 return "_list('%s')" % "\0".join(s)
1814 elif t == 'n':
1816 elif t == 'n':
1815 return "_list('%s')" % "\0".join(node.hex(a) for a in s)
1817 return "_list('%s')" % "\0".join(node.hex(a) for a in s)
1816 elif t == 'b':
1818 elif t == 'b':
1817 return "_list('%s')" % "\0".join(a.branch() for a in s)
1819 return "_list('%s')" % "\0".join(a.branch() for a in s)
1818
1820
1819 m = l // 2
1821 m = l // 2
1820 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1822 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1821
1823
1822 ret = ''
1824 ret = ''
1823 pos = 0
1825 pos = 0
1824 arg = 0
1826 arg = 0
1825 while pos < len(expr):
1827 while pos < len(expr):
1826 c = expr[pos]
1828 c = expr[pos]
1827 if c == '%':
1829 if c == '%':
1828 pos += 1
1830 pos += 1
1829 d = expr[pos]
1831 d = expr[pos]
1830 if d == '%':
1832 if d == '%':
1831 ret += d
1833 ret += d
1832 elif d in 'dsnbr':
1834 elif d in 'dsnbr':
1833 ret += argtype(d, args[arg])
1835 ret += argtype(d, args[arg])
1834 arg += 1
1836 arg += 1
1835 elif d == 'l':
1837 elif d == 'l':
1836 # a list of some type
1838 # a list of some type
1837 pos += 1
1839 pos += 1
1838 d = expr[pos]
1840 d = expr[pos]
1839 ret += listexp(list(args[arg]), d)
1841 ret += listexp(list(args[arg]), d)
1840 arg += 1
1842 arg += 1
1841 else:
1843 else:
1842 raise util.Abort('unexpected revspec format character %s' % d)
1844 raise util.Abort('unexpected revspec format character %s' % d)
1843 else:
1845 else:
1844 ret += c
1846 ret += c
1845 pos += 1
1847 pos += 1
1846
1848
1847 return ret
1849 return ret
1848
1850
1849 def prettyformat(tree):
1851 def prettyformat(tree):
1850 def _prettyformat(tree, level, lines):
1852 def _prettyformat(tree, level, lines):
1851 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
1853 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
1852 lines.append((level, str(tree)))
1854 lines.append((level, str(tree)))
1853 else:
1855 else:
1854 lines.append((level, '(%s' % tree[0]))
1856 lines.append((level, '(%s' % tree[0]))
1855 for s in tree[1:]:
1857 for s in tree[1:]:
1856 _prettyformat(s, level + 1, lines)
1858 _prettyformat(s, level + 1, lines)
1857 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
1859 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
1858
1860
1859 lines = []
1861 lines = []
1860 _prettyformat(tree, 0, lines)
1862 _prettyformat(tree, 0, lines)
1861 output = '\n'.join((' '*l + s) for l, s in lines)
1863 output = '\n'.join((' '*l + s) for l, s in lines)
1862 return output
1864 return output
1863
1865
1864 # tell hggettext to extract docstrings from these functions:
1866 # tell hggettext to extract docstrings from these functions:
1865 i18nfunctions = symbols.values()
1867 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now