##// END OF EJS Templates
obsolete: compute extinct changesets...
Pierre-Yves David -
r17173:c621f84d default
parent child Browse files
Show More
@@ -1,1299 +1,1314 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
14
15 propertycache = util.propertycache
15 propertycache = util.propertycache
16
16
17 class changectx(object):
17 class changectx(object):
18 """A changecontext object makes access to data related to a particular
18 """A changecontext object makes access to data related to a particular
19 changeset convenient."""
19 changeset convenient."""
20 def __init__(self, repo, changeid=''):
20 def __init__(self, repo, changeid=''):
21 """changeid is a revision number, node, or tag"""
21 """changeid is a revision number, node, or tag"""
22 if changeid == '':
22 if changeid == '':
23 changeid = '.'
23 changeid = '.'
24 self._repo = repo
24 self._repo = repo
25
25
26 if isinstance(changeid, int):
26 if isinstance(changeid, int):
27 self._rev = changeid
27 self._rev = changeid
28 self._node = repo.changelog.node(changeid)
28 self._node = repo.changelog.node(changeid)
29 return
29 return
30 if isinstance(changeid, long):
30 if isinstance(changeid, long):
31 changeid = str(changeid)
31 changeid = str(changeid)
32 if changeid == '.':
32 if changeid == '.':
33 self._node = repo.dirstate.p1()
33 self._node = repo.dirstate.p1()
34 self._rev = repo.changelog.rev(self._node)
34 self._rev = repo.changelog.rev(self._node)
35 return
35 return
36 if changeid == 'null':
36 if changeid == 'null':
37 self._node = nullid
37 self._node = nullid
38 self._rev = nullrev
38 self._rev = nullrev
39 return
39 return
40 if changeid == 'tip':
40 if changeid == 'tip':
41 self._rev = len(repo.changelog) - 1
41 self._rev = len(repo.changelog) - 1
42 self._node = repo.changelog.node(self._rev)
42 self._node = repo.changelog.node(self._rev)
43 return
43 return
44 if len(changeid) == 20:
44 if len(changeid) == 20:
45 try:
45 try:
46 self._node = changeid
46 self._node = changeid
47 self._rev = repo.changelog.rev(changeid)
47 self._rev = repo.changelog.rev(changeid)
48 return
48 return
49 except LookupError:
49 except LookupError:
50 pass
50 pass
51
51
52 try:
52 try:
53 r = int(changeid)
53 r = int(changeid)
54 if str(r) != changeid:
54 if str(r) != changeid:
55 raise ValueError
55 raise ValueError
56 l = len(repo.changelog)
56 l = len(repo.changelog)
57 if r < 0:
57 if r < 0:
58 r += l
58 r += l
59 if r < 0 or r >= l:
59 if r < 0 or r >= l:
60 raise ValueError
60 raise ValueError
61 self._rev = r
61 self._rev = r
62 self._node = repo.changelog.node(r)
62 self._node = repo.changelog.node(r)
63 return
63 return
64 except (ValueError, OverflowError):
64 except (ValueError, OverflowError):
65 pass
65 pass
66
66
67 if len(changeid) == 40:
67 if len(changeid) == 40:
68 try:
68 try:
69 self._node = bin(changeid)
69 self._node = bin(changeid)
70 self._rev = repo.changelog.rev(self._node)
70 self._rev = repo.changelog.rev(self._node)
71 return
71 return
72 except (TypeError, LookupError):
72 except (TypeError, LookupError):
73 pass
73 pass
74
74
75 if changeid in repo._bookmarks:
75 if changeid in repo._bookmarks:
76 self._node = repo._bookmarks[changeid]
76 self._node = repo._bookmarks[changeid]
77 self._rev = repo.changelog.rev(self._node)
77 self._rev = repo.changelog.rev(self._node)
78 return
78 return
79 if changeid in repo._tagscache.tags:
79 if changeid in repo._tagscache.tags:
80 self._node = repo._tagscache.tags[changeid]
80 self._node = repo._tagscache.tags[changeid]
81 self._rev = repo.changelog.rev(self._node)
81 self._rev = repo.changelog.rev(self._node)
82 return
82 return
83 try:
83 try:
84 self._node = repo.branchtip(changeid)
84 self._node = repo.branchtip(changeid)
85 self._rev = repo.changelog.rev(self._node)
85 self._rev = repo.changelog.rev(self._node)
86 return
86 return
87 except error.RepoLookupError:
87 except error.RepoLookupError:
88 pass
88 pass
89
89
90 self._node = repo.changelog._partialmatch(changeid)
90 self._node = repo.changelog._partialmatch(changeid)
91 if self._node is not None:
91 if self._node is not None:
92 self._rev = repo.changelog.rev(self._node)
92 self._rev = repo.changelog.rev(self._node)
93 return
93 return
94
94
95 # lookup failed
95 # lookup failed
96 # check if it might have come from damaged dirstate
96 # check if it might have come from damaged dirstate
97 if changeid in repo.dirstate.parents():
97 if changeid in repo.dirstate.parents():
98 raise error.Abort(_("working directory has unknown parent '%s'!")
98 raise error.Abort(_("working directory has unknown parent '%s'!")
99 % short(changeid))
99 % short(changeid))
100 try:
100 try:
101 if len(changeid) == 20:
101 if len(changeid) == 20:
102 changeid = hex(changeid)
102 changeid = hex(changeid)
103 except TypeError:
103 except TypeError:
104 pass
104 pass
105 raise error.RepoLookupError(
105 raise error.RepoLookupError(
106 _("unknown revision '%s'") % changeid)
106 _("unknown revision '%s'") % changeid)
107
107
108 def __str__(self):
108 def __str__(self):
109 return short(self.node())
109 return short(self.node())
110
110
111 def __int__(self):
111 def __int__(self):
112 return self.rev()
112 return self.rev()
113
113
114 def __repr__(self):
114 def __repr__(self):
115 return "<changectx %s>" % str(self)
115 return "<changectx %s>" % str(self)
116
116
117 def __hash__(self):
117 def __hash__(self):
118 try:
118 try:
119 return hash(self._rev)
119 return hash(self._rev)
120 except AttributeError:
120 except AttributeError:
121 return id(self)
121 return id(self)
122
122
123 def __eq__(self, other):
123 def __eq__(self, other):
124 try:
124 try:
125 return self._rev == other._rev
125 return self._rev == other._rev
126 except AttributeError:
126 except AttributeError:
127 return False
127 return False
128
128
129 def __ne__(self, other):
129 def __ne__(self, other):
130 return not (self == other)
130 return not (self == other)
131
131
132 def __nonzero__(self):
132 def __nonzero__(self):
133 return self._rev != nullrev
133 return self._rev != nullrev
134
134
135 @propertycache
135 @propertycache
136 def _changeset(self):
136 def _changeset(self):
137 return self._repo.changelog.read(self.rev())
137 return self._repo.changelog.read(self.rev())
138
138
139 @propertycache
139 @propertycache
140 def _manifest(self):
140 def _manifest(self):
141 return self._repo.manifest.read(self._changeset[0])
141 return self._repo.manifest.read(self._changeset[0])
142
142
143 @propertycache
143 @propertycache
144 def _manifestdelta(self):
144 def _manifestdelta(self):
145 return self._repo.manifest.readdelta(self._changeset[0])
145 return self._repo.manifest.readdelta(self._changeset[0])
146
146
147 @propertycache
147 @propertycache
148 def _parents(self):
148 def _parents(self):
149 p = self._repo.changelog.parentrevs(self._rev)
149 p = self._repo.changelog.parentrevs(self._rev)
150 if p[1] == nullrev:
150 if p[1] == nullrev:
151 p = p[:-1]
151 p = p[:-1]
152 return [changectx(self._repo, x) for x in p]
152 return [changectx(self._repo, x) for x in p]
153
153
154 @propertycache
154 @propertycache
155 def substate(self):
155 def substate(self):
156 return subrepo.state(self, self._repo.ui)
156 return subrepo.state(self, self._repo.ui)
157
157
158 def __contains__(self, key):
158 def __contains__(self, key):
159 return key in self._manifest
159 return key in self._manifest
160
160
161 def __getitem__(self, key):
161 def __getitem__(self, key):
162 return self.filectx(key)
162 return self.filectx(key)
163
163
164 def __iter__(self):
164 def __iter__(self):
165 for f in sorted(self._manifest):
165 for f in sorted(self._manifest):
166 yield f
166 yield f
167
167
168 def changeset(self):
168 def changeset(self):
169 return self._changeset
169 return self._changeset
170 def manifest(self):
170 def manifest(self):
171 return self._manifest
171 return self._manifest
172 def manifestnode(self):
172 def manifestnode(self):
173 return self._changeset[0]
173 return self._changeset[0]
174
174
175 def rev(self):
175 def rev(self):
176 return self._rev
176 return self._rev
177 def node(self):
177 def node(self):
178 return self._node
178 return self._node
179 def hex(self):
179 def hex(self):
180 return hex(self._node)
180 return hex(self._node)
181 def user(self):
181 def user(self):
182 return self._changeset[1]
182 return self._changeset[1]
183 def date(self):
183 def date(self):
184 return self._changeset[2]
184 return self._changeset[2]
185 def files(self):
185 def files(self):
186 return self._changeset[3]
186 return self._changeset[3]
187 def description(self):
187 def description(self):
188 return self._changeset[4]
188 return self._changeset[4]
189 def branch(self):
189 def branch(self):
190 return encoding.tolocal(self._changeset[5].get("branch"))
190 return encoding.tolocal(self._changeset[5].get("branch"))
191 def closesbranch(self):
191 def closesbranch(self):
192 return 'close' in self._changeset[5]
192 return 'close' in self._changeset[5]
193 def extra(self):
193 def extra(self):
194 return self._changeset[5]
194 return self._changeset[5]
195 def tags(self):
195 def tags(self):
196 return self._repo.nodetags(self._node)
196 return self._repo.nodetags(self._node)
197 def bookmarks(self):
197 def bookmarks(self):
198 return self._repo.nodebookmarks(self._node)
198 return self._repo.nodebookmarks(self._node)
199 def phase(self):
199 def phase(self):
200 return self._repo._phasecache.phase(self._repo, self._rev)
200 return self._repo._phasecache.phase(self._repo, self._rev)
201 def phasestr(self):
201 def phasestr(self):
202 return phases.phasenames[self.phase()]
202 return phases.phasenames[self.phase()]
203 def mutable(self):
203 def mutable(self):
204 return self.phase() > phases.public
204 return self.phase() > phases.public
205 def hidden(self):
205 def hidden(self):
206 return self._rev in self._repo.changelog.hiddenrevs
206 return self._rev in self._repo.changelog.hiddenrevs
207
207
208 def parents(self):
208 def parents(self):
209 """return contexts for each parent changeset"""
209 """return contexts for each parent changeset"""
210 return self._parents
210 return self._parents
211
211
212 def p1(self):
212 def p1(self):
213 return self._parents[0]
213 return self._parents[0]
214
214
215 def p2(self):
215 def p2(self):
216 if len(self._parents) == 2:
216 if len(self._parents) == 2:
217 return self._parents[1]
217 return self._parents[1]
218 return changectx(self._repo, -1)
218 return changectx(self._repo, -1)
219
219
220 def children(self):
220 def children(self):
221 """return contexts for each child changeset"""
221 """return contexts for each child changeset"""
222 c = self._repo.changelog.children(self._node)
222 c = self._repo.changelog.children(self._node)
223 return [changectx(self._repo, x) for x in c]
223 return [changectx(self._repo, x) for x in c]
224
224
225 def ancestors(self):
225 def ancestors(self):
226 for a in self._repo.changelog.ancestors([self._rev]):
226 for a in self._repo.changelog.ancestors([self._rev]):
227 yield changectx(self._repo, a)
227 yield changectx(self._repo, a)
228
228
229 def descendants(self):
229 def descendants(self):
230 for d in self._repo.changelog.descendants([self._rev]):
230 for d in self._repo.changelog.descendants([self._rev]):
231 yield changectx(self._repo, d)
231 yield changectx(self._repo, d)
232
232
233 def obsolete(self):
233 def obsolete(self):
234 """True if the changeset is obsolete"""
234 """True if the changeset is obsolete"""
235 return (self.node() in self._repo.obsstore.precursors
235 return (self.node() in self._repo.obsstore.precursors
236 and self.phase() > phases.public)
236 and self.phase() > phases.public)
237
237
238 def extinct(self):
239 """True if the changeset is extinct"""
240 # We should just compute a cache a check againts it.
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
238 def unstable(self):
253 def unstable(self):
239 """True if the changeset is not obsolete but it's ancestor are"""
254 """True if the changeset is not obsolete but it's ancestor are"""
240 # We should just compute /(obsolete()::) - obsolete()/
255 # We should just compute /(obsolete()::) - obsolete()/
241 # and keep it in a cache.
256 # and keep it in a cache.
242 #
257 #
243 # But this naive implementation does not require cache
258 # But this naive implementation does not require cache
244 if self.phase() <= phases.public:
259 if self.phase() <= phases.public:
245 return False
260 return False
246 if self.obsolete():
261 if self.obsolete():
247 return False
262 return False
248 for anc in self.ancestors():
263 for anc in self.ancestors():
249 if anc.obsolete():
264 if anc.obsolete():
250 return True
265 return True
251 return False
266 return False
252
267
253 def _fileinfo(self, path):
268 def _fileinfo(self, path):
254 if '_manifest' in self.__dict__:
269 if '_manifest' in self.__dict__:
255 try:
270 try:
256 return self._manifest[path], self._manifest.flags(path)
271 return self._manifest[path], self._manifest.flags(path)
257 except KeyError:
272 except KeyError:
258 raise error.LookupError(self._node, path,
273 raise error.LookupError(self._node, path,
259 _('not found in manifest'))
274 _('not found in manifest'))
260 if '_manifestdelta' in self.__dict__ or path in self.files():
275 if '_manifestdelta' in self.__dict__ or path in self.files():
261 if path in self._manifestdelta:
276 if path in self._manifestdelta:
262 return (self._manifestdelta[path],
277 return (self._manifestdelta[path],
263 self._manifestdelta.flags(path))
278 self._manifestdelta.flags(path))
264 node, flag = self._repo.manifest.find(self._changeset[0], path)
279 node, flag = self._repo.manifest.find(self._changeset[0], path)
265 if not node:
280 if not node:
266 raise error.LookupError(self._node, path,
281 raise error.LookupError(self._node, path,
267 _('not found in manifest'))
282 _('not found in manifest'))
268
283
269 return node, flag
284 return node, flag
270
285
271 def filenode(self, path):
286 def filenode(self, path):
272 return self._fileinfo(path)[0]
287 return self._fileinfo(path)[0]
273
288
274 def flags(self, path):
289 def flags(self, path):
275 try:
290 try:
276 return self._fileinfo(path)[1]
291 return self._fileinfo(path)[1]
277 except error.LookupError:
292 except error.LookupError:
278 return ''
293 return ''
279
294
280 def filectx(self, path, fileid=None, filelog=None):
295 def filectx(self, path, fileid=None, filelog=None):
281 """get a file context from this changeset"""
296 """get a file context from this changeset"""
282 if fileid is None:
297 if fileid is None:
283 fileid = self.filenode(path)
298 fileid = self.filenode(path)
284 return filectx(self._repo, path, fileid=fileid,
299 return filectx(self._repo, path, fileid=fileid,
285 changectx=self, filelog=filelog)
300 changectx=self, filelog=filelog)
286
301
287 def ancestor(self, c2):
302 def ancestor(self, c2):
288 """
303 """
289 return the ancestor context of self and c2
304 return the ancestor context of self and c2
290 """
305 """
291 # deal with workingctxs
306 # deal with workingctxs
292 n2 = c2._node
307 n2 = c2._node
293 if n2 is None:
308 if n2 is None:
294 n2 = c2._parents[0]._node
309 n2 = c2._parents[0]._node
295 n = self._repo.changelog.ancestor(self._node, n2)
310 n = self._repo.changelog.ancestor(self._node, n2)
296 return changectx(self._repo, n)
311 return changectx(self._repo, n)
297
312
298 def walk(self, match):
313 def walk(self, match):
299 fset = set(match.files())
314 fset = set(match.files())
300 # for dirstate.walk, files=['.'] means "walk the whole tree".
315 # for dirstate.walk, files=['.'] means "walk the whole tree".
301 # follow that here, too
316 # follow that here, too
302 fset.discard('.')
317 fset.discard('.')
303 for fn in self:
318 for fn in self:
304 if fn in fset:
319 if fn in fset:
305 # specified pattern is the exact name
320 # specified pattern is the exact name
306 fset.remove(fn)
321 fset.remove(fn)
307 if match(fn):
322 if match(fn):
308 yield fn
323 yield fn
309 for fn in sorted(fset):
324 for fn in sorted(fset):
310 if fn in self._dirs:
325 if fn in self._dirs:
311 # specified pattern is a directory
326 # specified pattern is a directory
312 continue
327 continue
313 if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
328 if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
314 yield fn
329 yield fn
315
330
316 def sub(self, path):
331 def sub(self, path):
317 return subrepo.subrepo(self, path)
332 return subrepo.subrepo(self, path)
318
333
319 def match(self, pats=[], include=None, exclude=None, default='glob'):
334 def match(self, pats=[], include=None, exclude=None, default='glob'):
320 r = self._repo
335 r = self._repo
321 return matchmod.match(r.root, r.getcwd(), pats,
336 return matchmod.match(r.root, r.getcwd(), pats,
322 include, exclude, default,
337 include, exclude, default,
323 auditor=r.auditor, ctx=self)
338 auditor=r.auditor, ctx=self)
324
339
325 def diff(self, ctx2=None, match=None, **opts):
340 def diff(self, ctx2=None, match=None, **opts):
326 """Returns a diff generator for the given contexts and matcher"""
341 """Returns a diff generator for the given contexts and matcher"""
327 if ctx2 is None:
342 if ctx2 is None:
328 ctx2 = self.p1()
343 ctx2 = self.p1()
329 if ctx2 is not None and not isinstance(ctx2, changectx):
344 if ctx2 is not None and not isinstance(ctx2, changectx):
330 ctx2 = self._repo[ctx2]
345 ctx2 = self._repo[ctx2]
331 diffopts = patch.diffopts(self._repo.ui, opts)
346 diffopts = patch.diffopts(self._repo.ui, opts)
332 return patch.diff(self._repo, ctx2.node(), self.node(),
347 return patch.diff(self._repo, ctx2.node(), self.node(),
333 match=match, opts=diffopts)
348 match=match, opts=diffopts)
334
349
335 @propertycache
350 @propertycache
336 def _dirs(self):
351 def _dirs(self):
337 dirs = set()
352 dirs = set()
338 for f in self._manifest:
353 for f in self._manifest:
339 pos = f.rfind('/')
354 pos = f.rfind('/')
340 while pos != -1:
355 while pos != -1:
341 f = f[:pos]
356 f = f[:pos]
342 if f in dirs:
357 if f in dirs:
343 break # dirs already contains this and above
358 break # dirs already contains this and above
344 dirs.add(f)
359 dirs.add(f)
345 pos = f.rfind('/')
360 pos = f.rfind('/')
346 return dirs
361 return dirs
347
362
348 def dirs(self):
363 def dirs(self):
349 return self._dirs
364 return self._dirs
350
365
351 class filectx(object):
366 class filectx(object):
352 """A filecontext object makes access to data related to a particular
367 """A filecontext object makes access to data related to a particular
353 filerevision convenient."""
368 filerevision convenient."""
354 def __init__(self, repo, path, changeid=None, fileid=None,
369 def __init__(self, repo, path, changeid=None, fileid=None,
355 filelog=None, changectx=None):
370 filelog=None, changectx=None):
356 """changeid can be a changeset revision, node, or tag.
371 """changeid can be a changeset revision, node, or tag.
357 fileid can be a file revision or node."""
372 fileid can be a file revision or node."""
358 self._repo = repo
373 self._repo = repo
359 self._path = path
374 self._path = path
360
375
361 assert (changeid is not None
376 assert (changeid is not None
362 or fileid is not None
377 or fileid is not None
363 or changectx is not None), \
378 or changectx is not None), \
364 ("bad args: changeid=%r, fileid=%r, changectx=%r"
379 ("bad args: changeid=%r, fileid=%r, changectx=%r"
365 % (changeid, fileid, changectx))
380 % (changeid, fileid, changectx))
366
381
367 if filelog:
382 if filelog:
368 self._filelog = filelog
383 self._filelog = filelog
369
384
370 if changeid is not None:
385 if changeid is not None:
371 self._changeid = changeid
386 self._changeid = changeid
372 if changectx is not None:
387 if changectx is not None:
373 self._changectx = changectx
388 self._changectx = changectx
374 if fileid is not None:
389 if fileid is not None:
375 self._fileid = fileid
390 self._fileid = fileid
376
391
377 @propertycache
392 @propertycache
378 def _changectx(self):
393 def _changectx(self):
379 return changectx(self._repo, self._changeid)
394 return changectx(self._repo, self._changeid)
380
395
381 @propertycache
396 @propertycache
382 def _filelog(self):
397 def _filelog(self):
383 return self._repo.file(self._path)
398 return self._repo.file(self._path)
384
399
385 @propertycache
400 @propertycache
386 def _changeid(self):
401 def _changeid(self):
387 if '_changectx' in self.__dict__:
402 if '_changectx' in self.__dict__:
388 return self._changectx.rev()
403 return self._changectx.rev()
389 else:
404 else:
390 return self._filelog.linkrev(self._filerev)
405 return self._filelog.linkrev(self._filerev)
391
406
392 @propertycache
407 @propertycache
393 def _filenode(self):
408 def _filenode(self):
394 if '_fileid' in self.__dict__:
409 if '_fileid' in self.__dict__:
395 return self._filelog.lookup(self._fileid)
410 return self._filelog.lookup(self._fileid)
396 else:
411 else:
397 return self._changectx.filenode(self._path)
412 return self._changectx.filenode(self._path)
398
413
399 @propertycache
414 @propertycache
400 def _filerev(self):
415 def _filerev(self):
401 return self._filelog.rev(self._filenode)
416 return self._filelog.rev(self._filenode)
402
417
403 @propertycache
418 @propertycache
404 def _repopath(self):
419 def _repopath(self):
405 return self._path
420 return self._path
406
421
407 def __nonzero__(self):
422 def __nonzero__(self):
408 try:
423 try:
409 self._filenode
424 self._filenode
410 return True
425 return True
411 except error.LookupError:
426 except error.LookupError:
412 # file is missing
427 # file is missing
413 return False
428 return False
414
429
415 def __str__(self):
430 def __str__(self):
416 return "%s@%s" % (self.path(), short(self.node()))
431 return "%s@%s" % (self.path(), short(self.node()))
417
432
418 def __repr__(self):
433 def __repr__(self):
419 return "<filectx %s>" % str(self)
434 return "<filectx %s>" % str(self)
420
435
421 def __hash__(self):
436 def __hash__(self):
422 try:
437 try:
423 return hash((self._path, self._filenode))
438 return hash((self._path, self._filenode))
424 except AttributeError:
439 except AttributeError:
425 return id(self)
440 return id(self)
426
441
427 def __eq__(self, other):
442 def __eq__(self, other):
428 try:
443 try:
429 return (self._path == other._path
444 return (self._path == other._path
430 and self._filenode == other._filenode)
445 and self._filenode == other._filenode)
431 except AttributeError:
446 except AttributeError:
432 return False
447 return False
433
448
434 def __ne__(self, other):
449 def __ne__(self, other):
435 return not (self == other)
450 return not (self == other)
436
451
437 def filectx(self, fileid):
452 def filectx(self, fileid):
438 '''opens an arbitrary revision of the file without
453 '''opens an arbitrary revision of the file without
439 opening a new filelog'''
454 opening a new filelog'''
440 return filectx(self._repo, self._path, fileid=fileid,
455 return filectx(self._repo, self._path, fileid=fileid,
441 filelog=self._filelog)
456 filelog=self._filelog)
442
457
443 def filerev(self):
458 def filerev(self):
444 return self._filerev
459 return self._filerev
445 def filenode(self):
460 def filenode(self):
446 return self._filenode
461 return self._filenode
447 def flags(self):
462 def flags(self):
448 return self._changectx.flags(self._path)
463 return self._changectx.flags(self._path)
449 def filelog(self):
464 def filelog(self):
450 return self._filelog
465 return self._filelog
451
466
452 def rev(self):
467 def rev(self):
453 if '_changectx' in self.__dict__:
468 if '_changectx' in self.__dict__:
454 return self._changectx.rev()
469 return self._changectx.rev()
455 if '_changeid' in self.__dict__:
470 if '_changeid' in self.__dict__:
456 return self._changectx.rev()
471 return self._changectx.rev()
457 return self._filelog.linkrev(self._filerev)
472 return self._filelog.linkrev(self._filerev)
458
473
459 def linkrev(self):
474 def linkrev(self):
460 return self._filelog.linkrev(self._filerev)
475 return self._filelog.linkrev(self._filerev)
461 def node(self):
476 def node(self):
462 return self._changectx.node()
477 return self._changectx.node()
463 def hex(self):
478 def hex(self):
464 return hex(self.node())
479 return hex(self.node())
465 def user(self):
480 def user(self):
466 return self._changectx.user()
481 return self._changectx.user()
467 def date(self):
482 def date(self):
468 return self._changectx.date()
483 return self._changectx.date()
469 def files(self):
484 def files(self):
470 return self._changectx.files()
485 return self._changectx.files()
471 def description(self):
486 def description(self):
472 return self._changectx.description()
487 return self._changectx.description()
473 def branch(self):
488 def branch(self):
474 return self._changectx.branch()
489 return self._changectx.branch()
475 def extra(self):
490 def extra(self):
476 return self._changectx.extra()
491 return self._changectx.extra()
477 def manifest(self):
492 def manifest(self):
478 return self._changectx.manifest()
493 return self._changectx.manifest()
479 def changectx(self):
494 def changectx(self):
480 return self._changectx
495 return self._changectx
481
496
482 def data(self):
497 def data(self):
483 return self._filelog.read(self._filenode)
498 return self._filelog.read(self._filenode)
484 def path(self):
499 def path(self):
485 return self._path
500 return self._path
486 def size(self):
501 def size(self):
487 return self._filelog.size(self._filerev)
502 return self._filelog.size(self._filerev)
488
503
489 def isbinary(self):
504 def isbinary(self):
490 try:
505 try:
491 return util.binary(self.data())
506 return util.binary(self.data())
492 except IOError:
507 except IOError:
493 return False
508 return False
494
509
495 def cmp(self, fctx):
510 def cmp(self, fctx):
496 """compare with other file context
511 """compare with other file context
497
512
498 returns True if different than fctx.
513 returns True if different than fctx.
499 """
514 """
500 if (fctx._filerev is None
515 if (fctx._filerev is None
501 and (self._repo._encodefilterpats
516 and (self._repo._encodefilterpats
502 # if file data starts with '\1\n', empty metadata block is
517 # if file data starts with '\1\n', empty metadata block is
503 # prepended, which adds 4 bytes to filelog.size().
518 # prepended, which adds 4 bytes to filelog.size().
504 or self.size() - 4 == fctx.size())
519 or self.size() - 4 == fctx.size())
505 or self.size() == fctx.size()):
520 or self.size() == fctx.size()):
506 return self._filelog.cmp(self._filenode, fctx.data())
521 return self._filelog.cmp(self._filenode, fctx.data())
507
522
508 return True
523 return True
509
524
510 def renamed(self):
525 def renamed(self):
511 """check if file was actually renamed in this changeset revision
526 """check if file was actually renamed in this changeset revision
512
527
513 If rename logged in file revision, we report copy for changeset only
528 If rename logged in file revision, we report copy for changeset only
514 if file revisions linkrev points back to the changeset in question
529 if file revisions linkrev points back to the changeset in question
515 or both changeset parents contain different file revisions.
530 or both changeset parents contain different file revisions.
516 """
531 """
517
532
518 renamed = self._filelog.renamed(self._filenode)
533 renamed = self._filelog.renamed(self._filenode)
519 if not renamed:
534 if not renamed:
520 return renamed
535 return renamed
521
536
522 if self.rev() == self.linkrev():
537 if self.rev() == self.linkrev():
523 return renamed
538 return renamed
524
539
525 name = self.path()
540 name = self.path()
526 fnode = self._filenode
541 fnode = self._filenode
527 for p in self._changectx.parents():
542 for p in self._changectx.parents():
528 try:
543 try:
529 if fnode == p.filenode(name):
544 if fnode == p.filenode(name):
530 return None
545 return None
531 except error.LookupError:
546 except error.LookupError:
532 pass
547 pass
533 return renamed
548 return renamed
534
549
535 def parents(self):
550 def parents(self):
536 p = self._path
551 p = self._path
537 fl = self._filelog
552 fl = self._filelog
538 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
553 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
539
554
540 r = self._filelog.renamed(self._filenode)
555 r = self._filelog.renamed(self._filenode)
541 if r:
556 if r:
542 pl[0] = (r[0], r[1], None)
557 pl[0] = (r[0], r[1], None)
543
558
544 return [filectx(self._repo, p, fileid=n, filelog=l)
559 return [filectx(self._repo, p, fileid=n, filelog=l)
545 for p, n, l in pl if n != nullid]
560 for p, n, l in pl if n != nullid]
546
561
547 def p1(self):
562 def p1(self):
548 return self.parents()[0]
563 return self.parents()[0]
549
564
550 def p2(self):
565 def p2(self):
551 p = self.parents()
566 p = self.parents()
552 if len(p) == 2:
567 if len(p) == 2:
553 return p[1]
568 return p[1]
554 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
569 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
555
570
556 def children(self):
571 def children(self):
557 # hard for renames
572 # hard for renames
558 c = self._filelog.children(self._filenode)
573 c = self._filelog.children(self._filenode)
559 return [filectx(self._repo, self._path, fileid=x,
574 return [filectx(self._repo, self._path, fileid=x,
560 filelog=self._filelog) for x in c]
575 filelog=self._filelog) for x in c]
561
576
562 def annotate(self, follow=False, linenumber=None, diffopts=None):
577 def annotate(self, follow=False, linenumber=None, diffopts=None):
563 '''returns a list of tuples of (ctx, line) for each line
578 '''returns a list of tuples of (ctx, line) for each line
564 in the file, where ctx is the filectx of the node where
579 in the file, where ctx is the filectx of the node where
565 that line was last changed.
580 that line was last changed.
566 This returns tuples of ((ctx, linenumber), line) for each line,
581 This returns tuples of ((ctx, linenumber), line) for each line,
567 if "linenumber" parameter is NOT "None".
582 if "linenumber" parameter is NOT "None".
568 In such tuples, linenumber means one at the first appearance
583 In such tuples, linenumber means one at the first appearance
569 in the managed file.
584 in the managed file.
570 To reduce annotation cost,
585 To reduce annotation cost,
571 this returns fixed value(False is used) as linenumber,
586 this returns fixed value(False is used) as linenumber,
572 if "linenumber" parameter is "False".'''
587 if "linenumber" parameter is "False".'''
573
588
574 def decorate_compat(text, rev):
589 def decorate_compat(text, rev):
575 return ([rev] * len(text.splitlines()), text)
590 return ([rev] * len(text.splitlines()), text)
576
591
577 def without_linenumber(text, rev):
592 def without_linenumber(text, rev):
578 return ([(rev, False)] * len(text.splitlines()), text)
593 return ([(rev, False)] * len(text.splitlines()), text)
579
594
580 def with_linenumber(text, rev):
595 def with_linenumber(text, rev):
581 size = len(text.splitlines())
596 size = len(text.splitlines())
582 return ([(rev, i) for i in xrange(1, size + 1)], text)
597 return ([(rev, i) for i in xrange(1, size + 1)], text)
583
598
584 decorate = (((linenumber is None) and decorate_compat) or
599 decorate = (((linenumber is None) and decorate_compat) or
585 (linenumber and with_linenumber) or
600 (linenumber and with_linenumber) or
586 without_linenumber)
601 without_linenumber)
587
602
588 def pair(parent, child):
603 def pair(parent, child):
589 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
604 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
590 refine=True)
605 refine=True)
591 for (a1, a2, b1, b2), t in blocks:
606 for (a1, a2, b1, b2), t in blocks:
592 # Changed blocks ('!') or blocks made only of blank lines ('~')
607 # Changed blocks ('!') or blocks made only of blank lines ('~')
593 # belong to the child.
608 # belong to the child.
594 if t == '=':
609 if t == '=':
595 child[0][b1:b2] = parent[0][a1:a2]
610 child[0][b1:b2] = parent[0][a1:a2]
596 return child
611 return child
597
612
598 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
613 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
599 def getctx(path, fileid):
614 def getctx(path, fileid):
600 log = path == self._path and self._filelog or getlog(path)
615 log = path == self._path and self._filelog or getlog(path)
601 return filectx(self._repo, path, fileid=fileid, filelog=log)
616 return filectx(self._repo, path, fileid=fileid, filelog=log)
602 getctx = util.lrucachefunc(getctx)
617 getctx = util.lrucachefunc(getctx)
603
618
604 def parents(f):
619 def parents(f):
605 # we want to reuse filectx objects as much as possible
620 # we want to reuse filectx objects as much as possible
606 p = f._path
621 p = f._path
607 if f._filerev is None: # working dir
622 if f._filerev is None: # working dir
608 pl = [(n.path(), n.filerev()) for n in f.parents()]
623 pl = [(n.path(), n.filerev()) for n in f.parents()]
609 else:
624 else:
610 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
625 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
611
626
612 if follow:
627 if follow:
613 r = f.renamed()
628 r = f.renamed()
614 if r:
629 if r:
615 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
630 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
616
631
617 return [getctx(p, n) for p, n in pl if n != nullrev]
632 return [getctx(p, n) for p, n in pl if n != nullrev]
618
633
619 # use linkrev to find the first changeset where self appeared
634 # use linkrev to find the first changeset where self appeared
620 if self.rev() != self.linkrev():
635 if self.rev() != self.linkrev():
621 base = self.filectx(self.filerev())
636 base = self.filectx(self.filerev())
622 else:
637 else:
623 base = self
638 base = self
624
639
625 # This algorithm would prefer to be recursive, but Python is a
640 # This algorithm would prefer to be recursive, but Python is a
626 # bit recursion-hostile. Instead we do an iterative
641 # bit recursion-hostile. Instead we do an iterative
627 # depth-first search.
642 # depth-first search.
628
643
629 visit = [base]
644 visit = [base]
630 hist = {}
645 hist = {}
631 pcache = {}
646 pcache = {}
632 needed = {base: 1}
647 needed = {base: 1}
633 while visit:
648 while visit:
634 f = visit[-1]
649 f = visit[-1]
635 if f not in pcache:
650 if f not in pcache:
636 pcache[f] = parents(f)
651 pcache[f] = parents(f)
637
652
638 ready = True
653 ready = True
639 pl = pcache[f]
654 pl = pcache[f]
640 for p in pl:
655 for p in pl:
641 if p not in hist:
656 if p not in hist:
642 ready = False
657 ready = False
643 visit.append(p)
658 visit.append(p)
644 needed[p] = needed.get(p, 0) + 1
659 needed[p] = needed.get(p, 0) + 1
645 if ready:
660 if ready:
646 visit.pop()
661 visit.pop()
647 curr = decorate(f.data(), f)
662 curr = decorate(f.data(), f)
648 for p in pl:
663 for p in pl:
649 curr = pair(hist[p], curr)
664 curr = pair(hist[p], curr)
650 if needed[p] == 1:
665 if needed[p] == 1:
651 del hist[p]
666 del hist[p]
652 else:
667 else:
653 needed[p] -= 1
668 needed[p] -= 1
654
669
655 hist[f] = curr
670 hist[f] = curr
656 pcache[f] = []
671 pcache[f] = []
657
672
658 return zip(hist[base][0], hist[base][1].splitlines(True))
673 return zip(hist[base][0], hist[base][1].splitlines(True))
659
674
660 def ancestor(self, fc2, actx):
675 def ancestor(self, fc2, actx):
661 """
676 """
662 find the common ancestor file context, if any, of self, and fc2
677 find the common ancestor file context, if any, of self, and fc2
663
678
664 actx must be the changectx of the common ancestor
679 actx must be the changectx of the common ancestor
665 of self's and fc2's respective changesets.
680 of self's and fc2's respective changesets.
666 """
681 """
667
682
668 # the easy case: no (relevant) renames
683 # the easy case: no (relevant) renames
669 if fc2.path() == self.path() and self.path() in actx:
684 if fc2.path() == self.path() and self.path() in actx:
670 return actx[self.path()]
685 return actx[self.path()]
671
686
672 # the next easiest cases: unambiguous predecessor (name trumps
687 # the next easiest cases: unambiguous predecessor (name trumps
673 # history)
688 # history)
674 if self.path() in actx and fc2.path() not in actx:
689 if self.path() in actx and fc2.path() not in actx:
675 return actx[self.path()]
690 return actx[self.path()]
676 if fc2.path() in actx and self.path() not in actx:
691 if fc2.path() in actx and self.path() not in actx:
677 return actx[fc2.path()]
692 return actx[fc2.path()]
678
693
679 # prime the ancestor cache for the working directory
694 # prime the ancestor cache for the working directory
680 acache = {}
695 acache = {}
681 for c in (self, fc2):
696 for c in (self, fc2):
682 if c._filerev is None:
697 if c._filerev is None:
683 pl = [(n.path(), n.filenode()) for n in c.parents()]
698 pl = [(n.path(), n.filenode()) for n in c.parents()]
684 acache[(c._path, None)] = pl
699 acache[(c._path, None)] = pl
685
700
686 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
701 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
687 def parents(vertex):
702 def parents(vertex):
688 if vertex in acache:
703 if vertex in acache:
689 return acache[vertex]
704 return acache[vertex]
690 f, n = vertex
705 f, n = vertex
691 if f not in flcache:
706 if f not in flcache:
692 flcache[f] = self._repo.file(f)
707 flcache[f] = self._repo.file(f)
693 fl = flcache[f]
708 fl = flcache[f]
694 pl = [(f, p) for p in fl.parents(n) if p != nullid]
709 pl = [(f, p) for p in fl.parents(n) if p != nullid]
695 re = fl.renamed(n)
710 re = fl.renamed(n)
696 if re:
711 if re:
697 pl.append(re)
712 pl.append(re)
698 acache[vertex] = pl
713 acache[vertex] = pl
699 return pl
714 return pl
700
715
701 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
716 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
702 v = ancestor.ancestor(a, b, parents)
717 v = ancestor.ancestor(a, b, parents)
703 if v:
718 if v:
704 f, n = v
719 f, n = v
705 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
720 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
706
721
707 return None
722 return None
708
723
709 def ancestors(self, followfirst=False):
724 def ancestors(self, followfirst=False):
710 visit = {}
725 visit = {}
711 c = self
726 c = self
712 cut = followfirst and 1 or None
727 cut = followfirst and 1 or None
713 while True:
728 while True:
714 for parent in c.parents()[:cut]:
729 for parent in c.parents()[:cut]:
715 visit[(parent.rev(), parent.node())] = parent
730 visit[(parent.rev(), parent.node())] = parent
716 if not visit:
731 if not visit:
717 break
732 break
718 c = visit.pop(max(visit))
733 c = visit.pop(max(visit))
719 yield c
734 yield c
720
735
721 def copies(self, c2):
736 def copies(self, c2):
722 if not util.safehasattr(self, "_copycache"):
737 if not util.safehasattr(self, "_copycache"):
723 self._copycache = {}
738 self._copycache = {}
724 sc2 = str(c2)
739 sc2 = str(c2)
725 if sc2 not in self._copycache:
740 if sc2 not in self._copycache:
726 self._copycache[sc2] = copies.pathcopies(c2)
741 self._copycache[sc2] = copies.pathcopies(c2)
727 return self._copycache[sc2]
742 return self._copycache[sc2]
728
743
729 class workingctx(changectx):
744 class workingctx(changectx):
730 """A workingctx object makes access to data related to
745 """A workingctx object makes access to data related to
731 the current working directory convenient.
746 the current working directory convenient.
732 date - any valid date string or (unixtime, offset), or None.
747 date - any valid date string or (unixtime, offset), or None.
733 user - username string, or None.
748 user - username string, or None.
734 extra - a dictionary of extra values, or None.
749 extra - a dictionary of extra values, or None.
735 changes - a list of file lists as returned by localrepo.status()
750 changes - a list of file lists as returned by localrepo.status()
736 or None to use the repository status.
751 or None to use the repository status.
737 """
752 """
738 def __init__(self, repo, text="", user=None, date=None, extra=None,
753 def __init__(self, repo, text="", user=None, date=None, extra=None,
739 changes=None):
754 changes=None):
740 self._repo = repo
755 self._repo = repo
741 self._rev = None
756 self._rev = None
742 self._node = None
757 self._node = None
743 self._text = text
758 self._text = text
744 if date:
759 if date:
745 self._date = util.parsedate(date)
760 self._date = util.parsedate(date)
746 if user:
761 if user:
747 self._user = user
762 self._user = user
748 if changes:
763 if changes:
749 self._status = list(changes[:4])
764 self._status = list(changes[:4])
750 self._unknown = changes[4]
765 self._unknown = changes[4]
751 self._ignored = changes[5]
766 self._ignored = changes[5]
752 self._clean = changes[6]
767 self._clean = changes[6]
753 else:
768 else:
754 self._unknown = None
769 self._unknown = None
755 self._ignored = None
770 self._ignored = None
756 self._clean = None
771 self._clean = None
757
772
758 self._extra = {}
773 self._extra = {}
759 if extra:
774 if extra:
760 self._extra = extra.copy()
775 self._extra = extra.copy()
761 if 'branch' not in self._extra:
776 if 'branch' not in self._extra:
762 try:
777 try:
763 branch = encoding.fromlocal(self._repo.dirstate.branch())
778 branch = encoding.fromlocal(self._repo.dirstate.branch())
764 except UnicodeDecodeError:
779 except UnicodeDecodeError:
765 raise util.Abort(_('branch name not in UTF-8!'))
780 raise util.Abort(_('branch name not in UTF-8!'))
766 self._extra['branch'] = branch
781 self._extra['branch'] = branch
767 if self._extra['branch'] == '':
782 if self._extra['branch'] == '':
768 self._extra['branch'] = 'default'
783 self._extra['branch'] = 'default'
769
784
770 def __str__(self):
785 def __str__(self):
771 return str(self._parents[0]) + "+"
786 return str(self._parents[0]) + "+"
772
787
773 def __repr__(self):
788 def __repr__(self):
774 return "<workingctx %s>" % str(self)
789 return "<workingctx %s>" % str(self)
775
790
776 def __nonzero__(self):
791 def __nonzero__(self):
777 return True
792 return True
778
793
779 def __contains__(self, key):
794 def __contains__(self, key):
780 return self._repo.dirstate[key] not in "?r"
795 return self._repo.dirstate[key] not in "?r"
781
796
782 def _buildflagfunc(self):
797 def _buildflagfunc(self):
783 # Create a fallback function for getting file flags when the
798 # Create a fallback function for getting file flags when the
784 # filesystem doesn't support them
799 # filesystem doesn't support them
785
800
786 copiesget = self._repo.dirstate.copies().get
801 copiesget = self._repo.dirstate.copies().get
787
802
788 if len(self._parents) < 2:
803 if len(self._parents) < 2:
789 # when we have one parent, it's easy: copy from parent
804 # when we have one parent, it's easy: copy from parent
790 man = self._parents[0].manifest()
805 man = self._parents[0].manifest()
791 def func(f):
806 def func(f):
792 f = copiesget(f, f)
807 f = copiesget(f, f)
793 return man.flags(f)
808 return man.flags(f)
794 else:
809 else:
795 # merges are tricky: we try to reconstruct the unstored
810 # merges are tricky: we try to reconstruct the unstored
796 # result from the merge (issue1802)
811 # result from the merge (issue1802)
797 p1, p2 = self._parents
812 p1, p2 = self._parents
798 pa = p1.ancestor(p2)
813 pa = p1.ancestor(p2)
799 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
814 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
800
815
801 def func(f):
816 def func(f):
802 f = copiesget(f, f) # may be wrong for merges with copies
817 f = copiesget(f, f) # may be wrong for merges with copies
803 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
818 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
804 if fl1 == fl2:
819 if fl1 == fl2:
805 return fl1
820 return fl1
806 if fl1 == fla:
821 if fl1 == fla:
807 return fl2
822 return fl2
808 if fl2 == fla:
823 if fl2 == fla:
809 return fl1
824 return fl1
810 return '' # punt for conflicts
825 return '' # punt for conflicts
811
826
812 return func
827 return func
813
828
814 @propertycache
829 @propertycache
815 def _flagfunc(self):
830 def _flagfunc(self):
816 return self._repo.dirstate.flagfunc(self._buildflagfunc)
831 return self._repo.dirstate.flagfunc(self._buildflagfunc)
817
832
818 @propertycache
833 @propertycache
819 def _manifest(self):
834 def _manifest(self):
820 """generate a manifest corresponding to the working directory"""
835 """generate a manifest corresponding to the working directory"""
821
836
822 man = self._parents[0].manifest().copy()
837 man = self._parents[0].manifest().copy()
823 if len(self._parents) > 1:
838 if len(self._parents) > 1:
824 man2 = self.p2().manifest()
839 man2 = self.p2().manifest()
825 def getman(f):
840 def getman(f):
826 if f in man:
841 if f in man:
827 return man
842 return man
828 return man2
843 return man2
829 else:
844 else:
830 getman = lambda f: man
845 getman = lambda f: man
831
846
832 copied = self._repo.dirstate.copies()
847 copied = self._repo.dirstate.copies()
833 ff = self._flagfunc
848 ff = self._flagfunc
834 modified, added, removed, deleted = self._status
849 modified, added, removed, deleted = self._status
835 for i, l in (("a", added), ("m", modified)):
850 for i, l in (("a", added), ("m", modified)):
836 for f in l:
851 for f in l:
837 orig = copied.get(f, f)
852 orig = copied.get(f, f)
838 man[f] = getman(orig).get(orig, nullid) + i
853 man[f] = getman(orig).get(orig, nullid) + i
839 try:
854 try:
840 man.set(f, ff(f))
855 man.set(f, ff(f))
841 except OSError:
856 except OSError:
842 pass
857 pass
843
858
844 for f in deleted + removed:
859 for f in deleted + removed:
845 if f in man:
860 if f in man:
846 del man[f]
861 del man[f]
847
862
848 return man
863 return man
849
864
850 def __iter__(self):
865 def __iter__(self):
851 d = self._repo.dirstate
866 d = self._repo.dirstate
852 for f in d:
867 for f in d:
853 if d[f] != 'r':
868 if d[f] != 'r':
854 yield f
869 yield f
855
870
856 @propertycache
871 @propertycache
857 def _status(self):
872 def _status(self):
858 return self._repo.status()[:4]
873 return self._repo.status()[:4]
859
874
860 @propertycache
875 @propertycache
861 def _user(self):
876 def _user(self):
862 return self._repo.ui.username()
877 return self._repo.ui.username()
863
878
864 @propertycache
879 @propertycache
865 def _date(self):
880 def _date(self):
866 return util.makedate()
881 return util.makedate()
867
882
868 @propertycache
883 @propertycache
869 def _parents(self):
884 def _parents(self):
870 p = self._repo.dirstate.parents()
885 p = self._repo.dirstate.parents()
871 if p[1] == nullid:
886 if p[1] == nullid:
872 p = p[:-1]
887 p = p[:-1]
873 self._parents = [changectx(self._repo, x) for x in p]
888 self._parents = [changectx(self._repo, x) for x in p]
874 return self._parents
889 return self._parents
875
890
876 def status(self, ignored=False, clean=False, unknown=False):
891 def status(self, ignored=False, clean=False, unknown=False):
877 """Explicit status query
892 """Explicit status query
878 Unless this method is used to query the working copy status, the
893 Unless this method is used to query the working copy status, the
879 _status property will implicitly read the status using its default
894 _status property will implicitly read the status using its default
880 arguments."""
895 arguments."""
881 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
896 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
882 self._unknown = self._ignored = self._clean = None
897 self._unknown = self._ignored = self._clean = None
883 if unknown:
898 if unknown:
884 self._unknown = stat[4]
899 self._unknown = stat[4]
885 if ignored:
900 if ignored:
886 self._ignored = stat[5]
901 self._ignored = stat[5]
887 if clean:
902 if clean:
888 self._clean = stat[6]
903 self._clean = stat[6]
889 self._status = stat[:4]
904 self._status = stat[:4]
890 return stat
905 return stat
891
906
892 def manifest(self):
907 def manifest(self):
893 return self._manifest
908 return self._manifest
894 def user(self):
909 def user(self):
895 return self._user or self._repo.ui.username()
910 return self._user or self._repo.ui.username()
896 def date(self):
911 def date(self):
897 return self._date
912 return self._date
898 def description(self):
913 def description(self):
899 return self._text
914 return self._text
900 def files(self):
915 def files(self):
901 return sorted(self._status[0] + self._status[1] + self._status[2])
916 return sorted(self._status[0] + self._status[1] + self._status[2])
902
917
903 def modified(self):
918 def modified(self):
904 return self._status[0]
919 return self._status[0]
905 def added(self):
920 def added(self):
906 return self._status[1]
921 return self._status[1]
907 def removed(self):
922 def removed(self):
908 return self._status[2]
923 return self._status[2]
909 def deleted(self):
924 def deleted(self):
910 return self._status[3]
925 return self._status[3]
911 def unknown(self):
926 def unknown(self):
912 assert self._unknown is not None # must call status first
927 assert self._unknown is not None # must call status first
913 return self._unknown
928 return self._unknown
914 def ignored(self):
929 def ignored(self):
915 assert self._ignored is not None # must call status first
930 assert self._ignored is not None # must call status first
916 return self._ignored
931 return self._ignored
917 def clean(self):
932 def clean(self):
918 assert self._clean is not None # must call status first
933 assert self._clean is not None # must call status first
919 return self._clean
934 return self._clean
920 def branch(self):
935 def branch(self):
921 return encoding.tolocal(self._extra['branch'])
936 return encoding.tolocal(self._extra['branch'])
922 def closesbranch(self):
937 def closesbranch(self):
923 return 'close' in self._extra
938 return 'close' in self._extra
924 def extra(self):
939 def extra(self):
925 return self._extra
940 return self._extra
926
941
927 def tags(self):
942 def tags(self):
928 t = []
943 t = []
929 for p in self.parents():
944 for p in self.parents():
930 t.extend(p.tags())
945 t.extend(p.tags())
931 return t
946 return t
932
947
933 def bookmarks(self):
948 def bookmarks(self):
934 b = []
949 b = []
935 for p in self.parents():
950 for p in self.parents():
936 b.extend(p.bookmarks())
951 b.extend(p.bookmarks())
937 return b
952 return b
938
953
939 def phase(self):
954 def phase(self):
940 phase = phases.draft # default phase to draft
955 phase = phases.draft # default phase to draft
941 for p in self.parents():
956 for p in self.parents():
942 phase = max(phase, p.phase())
957 phase = max(phase, p.phase())
943 return phase
958 return phase
944
959
945 def hidden(self):
960 def hidden(self):
946 return False
961 return False
947
962
948 def children(self):
963 def children(self):
949 return []
964 return []
950
965
951 def flags(self, path):
966 def flags(self, path):
952 if '_manifest' in self.__dict__:
967 if '_manifest' in self.__dict__:
953 try:
968 try:
954 return self._manifest.flags(path)
969 return self._manifest.flags(path)
955 except KeyError:
970 except KeyError:
956 return ''
971 return ''
957
972
958 try:
973 try:
959 return self._flagfunc(path)
974 return self._flagfunc(path)
960 except OSError:
975 except OSError:
961 return ''
976 return ''
962
977
963 def filectx(self, path, filelog=None):
978 def filectx(self, path, filelog=None):
964 """get a file context from the working directory"""
979 """get a file context from the working directory"""
965 return workingfilectx(self._repo, path, workingctx=self,
980 return workingfilectx(self._repo, path, workingctx=self,
966 filelog=filelog)
981 filelog=filelog)
967
982
968 def ancestor(self, c2):
983 def ancestor(self, c2):
969 """return the ancestor context of self and c2"""
984 """return the ancestor context of self and c2"""
970 return self._parents[0].ancestor(c2) # punt on two parents for now
985 return self._parents[0].ancestor(c2) # punt on two parents for now
971
986
972 def walk(self, match):
987 def walk(self, match):
973 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
988 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
974 True, False))
989 True, False))
975
990
976 def dirty(self, missing=False, merge=True, branch=True):
991 def dirty(self, missing=False, merge=True, branch=True):
977 "check whether a working directory is modified"
992 "check whether a working directory is modified"
978 # check subrepos first
993 # check subrepos first
979 for s in self.substate:
994 for s in self.substate:
980 if self.sub(s).dirty():
995 if self.sub(s).dirty():
981 return True
996 return True
982 # check current working dir
997 # check current working dir
983 return ((merge and self.p2()) or
998 return ((merge and self.p2()) or
984 (branch and self.branch() != self.p1().branch()) or
999 (branch and self.branch() != self.p1().branch()) or
985 self.modified() or self.added() or self.removed() or
1000 self.modified() or self.added() or self.removed() or
986 (missing and self.deleted()))
1001 (missing and self.deleted()))
987
1002
988 def add(self, list, prefix=""):
1003 def add(self, list, prefix=""):
989 join = lambda f: os.path.join(prefix, f)
1004 join = lambda f: os.path.join(prefix, f)
990 wlock = self._repo.wlock()
1005 wlock = self._repo.wlock()
991 ui, ds = self._repo.ui, self._repo.dirstate
1006 ui, ds = self._repo.ui, self._repo.dirstate
992 try:
1007 try:
993 rejected = []
1008 rejected = []
994 for f in list:
1009 for f in list:
995 scmutil.checkportable(ui, join(f))
1010 scmutil.checkportable(ui, join(f))
996 p = self._repo.wjoin(f)
1011 p = self._repo.wjoin(f)
997 try:
1012 try:
998 st = os.lstat(p)
1013 st = os.lstat(p)
999 except OSError:
1014 except OSError:
1000 ui.warn(_("%s does not exist!\n") % join(f))
1015 ui.warn(_("%s does not exist!\n") % join(f))
1001 rejected.append(f)
1016 rejected.append(f)
1002 continue
1017 continue
1003 if st.st_size > 10000000:
1018 if st.st_size > 10000000:
1004 ui.warn(_("%s: up to %d MB of RAM may be required "
1019 ui.warn(_("%s: up to %d MB of RAM may be required "
1005 "to manage this file\n"
1020 "to manage this file\n"
1006 "(use 'hg revert %s' to cancel the "
1021 "(use 'hg revert %s' to cancel the "
1007 "pending addition)\n")
1022 "pending addition)\n")
1008 % (f, 3 * st.st_size // 1000000, join(f)))
1023 % (f, 3 * st.st_size // 1000000, join(f)))
1009 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1024 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1010 ui.warn(_("%s not added: only files and symlinks "
1025 ui.warn(_("%s not added: only files and symlinks "
1011 "supported currently\n") % join(f))
1026 "supported currently\n") % join(f))
1012 rejected.append(p)
1027 rejected.append(p)
1013 elif ds[f] in 'amn':
1028 elif ds[f] in 'amn':
1014 ui.warn(_("%s already tracked!\n") % join(f))
1029 ui.warn(_("%s already tracked!\n") % join(f))
1015 elif ds[f] == 'r':
1030 elif ds[f] == 'r':
1016 ds.normallookup(f)
1031 ds.normallookup(f)
1017 else:
1032 else:
1018 ds.add(f)
1033 ds.add(f)
1019 return rejected
1034 return rejected
1020 finally:
1035 finally:
1021 wlock.release()
1036 wlock.release()
1022
1037
1023 def forget(self, files, prefix=""):
1038 def forget(self, files, prefix=""):
1024 join = lambda f: os.path.join(prefix, f)
1039 join = lambda f: os.path.join(prefix, f)
1025 wlock = self._repo.wlock()
1040 wlock = self._repo.wlock()
1026 try:
1041 try:
1027 rejected = []
1042 rejected = []
1028 for f in files:
1043 for f in files:
1029 if f not in self._repo.dirstate:
1044 if f not in self._repo.dirstate:
1030 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1045 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1031 rejected.append(f)
1046 rejected.append(f)
1032 elif self._repo.dirstate[f] != 'a':
1047 elif self._repo.dirstate[f] != 'a':
1033 self._repo.dirstate.remove(f)
1048 self._repo.dirstate.remove(f)
1034 else:
1049 else:
1035 self._repo.dirstate.drop(f)
1050 self._repo.dirstate.drop(f)
1036 return rejected
1051 return rejected
1037 finally:
1052 finally:
1038 wlock.release()
1053 wlock.release()
1039
1054
1040 def ancestors(self):
1055 def ancestors(self):
1041 for a in self._repo.changelog.ancestors(
1056 for a in self._repo.changelog.ancestors(
1042 [p.rev() for p in self._parents]):
1057 [p.rev() for p in self._parents]):
1043 yield changectx(self._repo, a)
1058 yield changectx(self._repo, a)
1044
1059
1045 def undelete(self, list):
1060 def undelete(self, list):
1046 pctxs = self.parents()
1061 pctxs = self.parents()
1047 wlock = self._repo.wlock()
1062 wlock = self._repo.wlock()
1048 try:
1063 try:
1049 for f in list:
1064 for f in list:
1050 if self._repo.dirstate[f] != 'r':
1065 if self._repo.dirstate[f] != 'r':
1051 self._repo.ui.warn(_("%s not removed!\n") % f)
1066 self._repo.ui.warn(_("%s not removed!\n") % f)
1052 else:
1067 else:
1053 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1068 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1054 t = fctx.data()
1069 t = fctx.data()
1055 self._repo.wwrite(f, t, fctx.flags())
1070 self._repo.wwrite(f, t, fctx.flags())
1056 self._repo.dirstate.normal(f)
1071 self._repo.dirstate.normal(f)
1057 finally:
1072 finally:
1058 wlock.release()
1073 wlock.release()
1059
1074
1060 def copy(self, source, dest):
1075 def copy(self, source, dest):
1061 p = self._repo.wjoin(dest)
1076 p = self._repo.wjoin(dest)
1062 if not os.path.lexists(p):
1077 if not os.path.lexists(p):
1063 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1078 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1064 elif not (os.path.isfile(p) or os.path.islink(p)):
1079 elif not (os.path.isfile(p) or os.path.islink(p)):
1065 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1080 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1066 "symbolic link\n") % dest)
1081 "symbolic link\n") % dest)
1067 else:
1082 else:
1068 wlock = self._repo.wlock()
1083 wlock = self._repo.wlock()
1069 try:
1084 try:
1070 if self._repo.dirstate[dest] in '?r':
1085 if self._repo.dirstate[dest] in '?r':
1071 self._repo.dirstate.add(dest)
1086 self._repo.dirstate.add(dest)
1072 self._repo.dirstate.copy(source, dest)
1087 self._repo.dirstate.copy(source, dest)
1073 finally:
1088 finally:
1074 wlock.release()
1089 wlock.release()
1075
1090
1076 def dirs(self):
1091 def dirs(self):
1077 return set(self._repo.dirstate.dirs())
1092 return set(self._repo.dirstate.dirs())
1078
1093
1079 class workingfilectx(filectx):
1094 class workingfilectx(filectx):
1080 """A workingfilectx object makes access to data related to a particular
1095 """A workingfilectx object makes access to data related to a particular
1081 file in the working directory convenient."""
1096 file in the working directory convenient."""
1082 def __init__(self, repo, path, filelog=None, workingctx=None):
1097 def __init__(self, repo, path, filelog=None, workingctx=None):
1083 """changeid can be a changeset revision, node, or tag.
1098 """changeid can be a changeset revision, node, or tag.
1084 fileid can be a file revision or node."""
1099 fileid can be a file revision or node."""
1085 self._repo = repo
1100 self._repo = repo
1086 self._path = path
1101 self._path = path
1087 self._changeid = None
1102 self._changeid = None
1088 self._filerev = self._filenode = None
1103 self._filerev = self._filenode = None
1089
1104
1090 if filelog:
1105 if filelog:
1091 self._filelog = filelog
1106 self._filelog = filelog
1092 if workingctx:
1107 if workingctx:
1093 self._changectx = workingctx
1108 self._changectx = workingctx
1094
1109
1095 @propertycache
1110 @propertycache
1096 def _changectx(self):
1111 def _changectx(self):
1097 return workingctx(self._repo)
1112 return workingctx(self._repo)
1098
1113
1099 def __nonzero__(self):
1114 def __nonzero__(self):
1100 return True
1115 return True
1101
1116
1102 def __str__(self):
1117 def __str__(self):
1103 return "%s@%s" % (self.path(), self._changectx)
1118 return "%s@%s" % (self.path(), self._changectx)
1104
1119
1105 def __repr__(self):
1120 def __repr__(self):
1106 return "<workingfilectx %s>" % str(self)
1121 return "<workingfilectx %s>" % str(self)
1107
1122
1108 def data(self):
1123 def data(self):
1109 return self._repo.wread(self._path)
1124 return self._repo.wread(self._path)
1110 def renamed(self):
1125 def renamed(self):
1111 rp = self._repo.dirstate.copied(self._path)
1126 rp = self._repo.dirstate.copied(self._path)
1112 if not rp:
1127 if not rp:
1113 return None
1128 return None
1114 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1129 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1115
1130
1116 def parents(self):
1131 def parents(self):
1117 '''return parent filectxs, following copies if necessary'''
1132 '''return parent filectxs, following copies if necessary'''
1118 def filenode(ctx, path):
1133 def filenode(ctx, path):
1119 return ctx._manifest.get(path, nullid)
1134 return ctx._manifest.get(path, nullid)
1120
1135
1121 path = self._path
1136 path = self._path
1122 fl = self._filelog
1137 fl = self._filelog
1123 pcl = self._changectx._parents
1138 pcl = self._changectx._parents
1124 renamed = self.renamed()
1139 renamed = self.renamed()
1125
1140
1126 if renamed:
1141 if renamed:
1127 pl = [renamed + (None,)]
1142 pl = [renamed + (None,)]
1128 else:
1143 else:
1129 pl = [(path, filenode(pcl[0], path), fl)]
1144 pl = [(path, filenode(pcl[0], path), fl)]
1130
1145
1131 for pc in pcl[1:]:
1146 for pc in pcl[1:]:
1132 pl.append((path, filenode(pc, path), fl))
1147 pl.append((path, filenode(pc, path), fl))
1133
1148
1134 return [filectx(self._repo, p, fileid=n, filelog=l)
1149 return [filectx(self._repo, p, fileid=n, filelog=l)
1135 for p, n, l in pl if n != nullid]
1150 for p, n, l in pl if n != nullid]
1136
1151
1137 def children(self):
1152 def children(self):
1138 return []
1153 return []
1139
1154
1140 def size(self):
1155 def size(self):
1141 return os.lstat(self._repo.wjoin(self._path)).st_size
1156 return os.lstat(self._repo.wjoin(self._path)).st_size
1142 def date(self):
1157 def date(self):
1143 t, tz = self._changectx.date()
1158 t, tz = self._changectx.date()
1144 try:
1159 try:
1145 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
1160 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
1146 except OSError, err:
1161 except OSError, err:
1147 if err.errno != errno.ENOENT:
1162 if err.errno != errno.ENOENT:
1148 raise
1163 raise
1149 return (t, tz)
1164 return (t, tz)
1150
1165
1151 def cmp(self, fctx):
1166 def cmp(self, fctx):
1152 """compare with other file context
1167 """compare with other file context
1153
1168
1154 returns True if different than fctx.
1169 returns True if different than fctx.
1155 """
1170 """
1156 # fctx should be a filectx (not a wfctx)
1171 # fctx should be a filectx (not a wfctx)
1157 # invert comparison to reuse the same code path
1172 # invert comparison to reuse the same code path
1158 return fctx.cmp(self)
1173 return fctx.cmp(self)
1159
1174
1160 class memctx(object):
1175 class memctx(object):
1161 """Use memctx to perform in-memory commits via localrepo.commitctx().
1176 """Use memctx to perform in-memory commits via localrepo.commitctx().
1162
1177
1163 Revision information is supplied at initialization time while
1178 Revision information is supplied at initialization time while
1164 related files data and is made available through a callback
1179 related files data and is made available through a callback
1165 mechanism. 'repo' is the current localrepo, 'parents' is a
1180 mechanism. 'repo' is the current localrepo, 'parents' is a
1166 sequence of two parent revisions identifiers (pass None for every
1181 sequence of two parent revisions identifiers (pass None for every
1167 missing parent), 'text' is the commit message and 'files' lists
1182 missing parent), 'text' is the commit message and 'files' lists
1168 names of files touched by the revision (normalized and relative to
1183 names of files touched by the revision (normalized and relative to
1169 repository root).
1184 repository root).
1170
1185
1171 filectxfn(repo, memctx, path) is a callable receiving the
1186 filectxfn(repo, memctx, path) is a callable receiving the
1172 repository, the current memctx object and the normalized path of
1187 repository, the current memctx object and the normalized path of
1173 requested file, relative to repository root. It is fired by the
1188 requested file, relative to repository root. It is fired by the
1174 commit function for every file in 'files', but calls order is
1189 commit function for every file in 'files', but calls order is
1175 undefined. If the file is available in the revision being
1190 undefined. If the file is available in the revision being
1176 committed (updated or added), filectxfn returns a memfilectx
1191 committed (updated or added), filectxfn returns a memfilectx
1177 object. If the file was removed, filectxfn raises an
1192 object. If the file was removed, filectxfn raises an
1178 IOError. Moved files are represented by marking the source file
1193 IOError. Moved files are represented by marking the source file
1179 removed and the new file added with copy information (see
1194 removed and the new file added with copy information (see
1180 memfilectx).
1195 memfilectx).
1181
1196
1182 user receives the committer name and defaults to current
1197 user receives the committer name and defaults to current
1183 repository username, date is the commit date in any format
1198 repository username, date is the commit date in any format
1184 supported by util.parsedate() and defaults to current date, extra
1199 supported by util.parsedate() and defaults to current date, extra
1185 is a dictionary of metadata or is left empty.
1200 is a dictionary of metadata or is left empty.
1186 """
1201 """
1187 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1202 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1188 date=None, extra=None):
1203 date=None, extra=None):
1189 self._repo = repo
1204 self._repo = repo
1190 self._rev = None
1205 self._rev = None
1191 self._node = None
1206 self._node = None
1192 self._text = text
1207 self._text = text
1193 self._date = date and util.parsedate(date) or util.makedate()
1208 self._date = date and util.parsedate(date) or util.makedate()
1194 self._user = user
1209 self._user = user
1195 parents = [(p or nullid) for p in parents]
1210 parents = [(p or nullid) for p in parents]
1196 p1, p2 = parents
1211 p1, p2 = parents
1197 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1212 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1198 files = sorted(set(files))
1213 files = sorted(set(files))
1199 self._status = [files, [], [], [], []]
1214 self._status = [files, [], [], [], []]
1200 self._filectxfn = filectxfn
1215 self._filectxfn = filectxfn
1201
1216
1202 self._extra = extra and extra.copy() or {}
1217 self._extra = extra and extra.copy() or {}
1203 if self._extra.get('branch', '') == '':
1218 if self._extra.get('branch', '') == '':
1204 self._extra['branch'] = 'default'
1219 self._extra['branch'] = 'default'
1205
1220
1206 def __str__(self):
1221 def __str__(self):
1207 return str(self._parents[0]) + "+"
1222 return str(self._parents[0]) + "+"
1208
1223
1209 def __int__(self):
1224 def __int__(self):
1210 return self._rev
1225 return self._rev
1211
1226
1212 def __nonzero__(self):
1227 def __nonzero__(self):
1213 return True
1228 return True
1214
1229
1215 def __getitem__(self, key):
1230 def __getitem__(self, key):
1216 return self.filectx(key)
1231 return self.filectx(key)
1217
1232
1218 def p1(self):
1233 def p1(self):
1219 return self._parents[0]
1234 return self._parents[0]
1220 def p2(self):
1235 def p2(self):
1221 return self._parents[1]
1236 return self._parents[1]
1222
1237
1223 def user(self):
1238 def user(self):
1224 return self._user or self._repo.ui.username()
1239 return self._user or self._repo.ui.username()
1225 def date(self):
1240 def date(self):
1226 return self._date
1241 return self._date
1227 def description(self):
1242 def description(self):
1228 return self._text
1243 return self._text
1229 def files(self):
1244 def files(self):
1230 return self.modified()
1245 return self.modified()
1231 def modified(self):
1246 def modified(self):
1232 return self._status[0]
1247 return self._status[0]
1233 def added(self):
1248 def added(self):
1234 return self._status[1]
1249 return self._status[1]
1235 def removed(self):
1250 def removed(self):
1236 return self._status[2]
1251 return self._status[2]
1237 def deleted(self):
1252 def deleted(self):
1238 return self._status[3]
1253 return self._status[3]
1239 def unknown(self):
1254 def unknown(self):
1240 return self._status[4]
1255 return self._status[4]
1241 def ignored(self):
1256 def ignored(self):
1242 return self._status[5]
1257 return self._status[5]
1243 def clean(self):
1258 def clean(self):
1244 return self._status[6]
1259 return self._status[6]
1245 def branch(self):
1260 def branch(self):
1246 return encoding.tolocal(self._extra['branch'])
1261 return encoding.tolocal(self._extra['branch'])
1247 def extra(self):
1262 def extra(self):
1248 return self._extra
1263 return self._extra
1249 def flags(self, f):
1264 def flags(self, f):
1250 return self[f].flags()
1265 return self[f].flags()
1251
1266
1252 def parents(self):
1267 def parents(self):
1253 """return contexts for each parent changeset"""
1268 """return contexts for each parent changeset"""
1254 return self._parents
1269 return self._parents
1255
1270
1256 def filectx(self, path, filelog=None):
1271 def filectx(self, path, filelog=None):
1257 """get a file context from the working directory"""
1272 """get a file context from the working directory"""
1258 return self._filectxfn(self._repo, self, path)
1273 return self._filectxfn(self._repo, self, path)
1259
1274
1260 def commit(self):
1275 def commit(self):
1261 """commit context to the repo"""
1276 """commit context to the repo"""
1262 return self._repo.commitctx(self)
1277 return self._repo.commitctx(self)
1263
1278
1264 class memfilectx(object):
1279 class memfilectx(object):
1265 """memfilectx represents an in-memory file to commit.
1280 """memfilectx represents an in-memory file to commit.
1266
1281
1267 See memctx for more details.
1282 See memctx for more details.
1268 """
1283 """
1269 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1284 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1270 """
1285 """
1271 path is the normalized file path relative to repository root.
1286 path is the normalized file path relative to repository root.
1272 data is the file content as a string.
1287 data is the file content as a string.
1273 islink is True if the file is a symbolic link.
1288 islink is True if the file is a symbolic link.
1274 isexec is True if the file is executable.
1289 isexec is True if the file is executable.
1275 copied is the source file path if current file was copied in the
1290 copied is the source file path if current file was copied in the
1276 revision being committed, or None."""
1291 revision being committed, or None."""
1277 self._path = path
1292 self._path = path
1278 self._data = data
1293 self._data = data
1279 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1294 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1280 self._copied = None
1295 self._copied = None
1281 if copied:
1296 if copied:
1282 self._copied = (copied, nullid)
1297 self._copied = (copied, nullid)
1283
1298
1284 def __nonzero__(self):
1299 def __nonzero__(self):
1285 return True
1300 return True
1286 def __str__(self):
1301 def __str__(self):
1287 return "%s@%s" % (self.path(), self._changectx)
1302 return "%s@%s" % (self.path(), self._changectx)
1288 def path(self):
1303 def path(self):
1289 return self._path
1304 return self._path
1290 def data(self):
1305 def data(self):
1291 return self._data
1306 return self._data
1292 def flags(self):
1307 def flags(self):
1293 return self._flags
1308 return self._flags
1294 def isexec(self):
1309 def isexec(self):
1295 return 'x' in self._flags
1310 return 'x' in self._flags
1296 def islink(self):
1311 def islink(self):
1297 return 'l' in self._flags
1312 return 'l' in self._flags
1298 def renamed(self):
1313 def renamed(self):
1299 return self._copied
1314 return self._copied
@@ -1,1745 +1,1753 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
15
16 def _revancestors(repo, revs, followfirst):
16 def _revancestors(repo, revs, followfirst):
17 """Like revlog.ancestors(), but supports followfirst."""
17 """Like revlog.ancestors(), but supports followfirst."""
18 cut = followfirst and 1 or None
18 cut = followfirst and 1 or None
19 cl = repo.changelog
19 cl = repo.changelog
20 visit = util.deque(revs)
20 visit = util.deque(revs)
21 seen = set([node.nullrev])
21 seen = set([node.nullrev])
22 while visit:
22 while visit:
23 for parent in cl.parentrevs(visit.popleft())[:cut]:
23 for parent in cl.parentrevs(visit.popleft())[:cut]:
24 if parent not in seen:
24 if parent not in seen:
25 visit.append(parent)
25 visit.append(parent)
26 seen.add(parent)
26 seen.add(parent)
27 yield parent
27 yield parent
28
28
29 def _revdescendants(repo, revs, followfirst):
29 def _revdescendants(repo, revs, followfirst):
30 """Like revlog.descendants() but supports followfirst."""
30 """Like revlog.descendants() but supports followfirst."""
31 cut = followfirst and 1 or None
31 cut = followfirst and 1 or None
32 cl = repo.changelog
32 cl = repo.changelog
33 first = min(revs)
33 first = min(revs)
34 nullrev = node.nullrev
34 nullrev = node.nullrev
35 if first == nullrev:
35 if first == nullrev:
36 # Are there nodes with a null first parent and a non-null
36 # Are there nodes with a null first parent and a non-null
37 # second one? Maybe. Do we care? Probably not.
37 # second one? Maybe. Do we care? Probably not.
38 for i in cl:
38 for i in cl:
39 yield i
39 yield i
40 return
40 return
41
41
42 seen = set(revs)
42 seen = set(revs)
43 for i in xrange(first + 1, len(cl)):
43 for i in xrange(first + 1, len(cl)):
44 for x in cl.parentrevs(i)[:cut]:
44 for x in cl.parentrevs(i)[:cut]:
45 if x != nullrev and x in seen:
45 if x != nullrev and x in seen:
46 seen.add(i)
46 seen.add(i)
47 yield i
47 yield i
48 break
48 break
49
49
50 def _revsbetween(repo, roots, heads):
50 def _revsbetween(repo, roots, heads):
51 """Return all paths between roots and heads, inclusive of both endpoint
51 """Return all paths between roots and heads, inclusive of both endpoint
52 sets."""
52 sets."""
53 if not roots:
53 if not roots:
54 return []
54 return []
55 parentrevs = repo.changelog.parentrevs
55 parentrevs = repo.changelog.parentrevs
56 visit = heads[:]
56 visit = heads[:]
57 reachable = set()
57 reachable = set()
58 seen = {}
58 seen = {}
59 minroot = min(roots)
59 minroot = min(roots)
60 roots = set(roots)
60 roots = set(roots)
61 # open-code the post-order traversal due to the tiny size of
61 # open-code the post-order traversal due to the tiny size of
62 # sys.getrecursionlimit()
62 # sys.getrecursionlimit()
63 while visit:
63 while visit:
64 rev = visit.pop()
64 rev = visit.pop()
65 if rev in roots:
65 if rev in roots:
66 reachable.add(rev)
66 reachable.add(rev)
67 parents = parentrevs(rev)
67 parents = parentrevs(rev)
68 seen[rev] = parents
68 seen[rev] = parents
69 for parent in parents:
69 for parent in parents:
70 if parent >= minroot and parent not in seen:
70 if parent >= minroot and parent not in seen:
71 visit.append(parent)
71 visit.append(parent)
72 if not reachable:
72 if not reachable:
73 return []
73 return []
74 for rev in sorted(seen):
74 for rev in sorted(seen):
75 for parent in seen[rev]:
75 for parent in seen[rev]:
76 if parent in reachable:
76 if parent in reachable:
77 reachable.add(rev)
77 reachable.add(rev)
78 return sorted(reachable)
78 return sorted(reachable)
79
79
80 elements = {
80 elements = {
81 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
81 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
82 "~": (18, None, ("ancestor", 18)),
82 "~": (18, None, ("ancestor", 18)),
83 "^": (18, None, ("parent", 18), ("parentpost", 18)),
83 "^": (18, None, ("parent", 18), ("parentpost", 18)),
84 "-": (5, ("negate", 19), ("minus", 5)),
84 "-": (5, ("negate", 19), ("minus", 5)),
85 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
85 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
86 ("dagrangepost", 17)),
86 ("dagrangepost", 17)),
87 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
87 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
88 ("dagrangepost", 17)),
88 ("dagrangepost", 17)),
89 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
89 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
90 "not": (10, ("not", 10)),
90 "not": (10, ("not", 10)),
91 "!": (10, ("not", 10)),
91 "!": (10, ("not", 10)),
92 "and": (5, None, ("and", 5)),
92 "and": (5, None, ("and", 5)),
93 "&": (5, None, ("and", 5)),
93 "&": (5, None, ("and", 5)),
94 "or": (4, None, ("or", 4)),
94 "or": (4, None, ("or", 4)),
95 "|": (4, None, ("or", 4)),
95 "|": (4, None, ("or", 4)),
96 "+": (4, None, ("or", 4)),
96 "+": (4, None, ("or", 4)),
97 ",": (2, None, ("list", 2)),
97 ",": (2, None, ("list", 2)),
98 ")": (0, None, None),
98 ")": (0, None, None),
99 "symbol": (0, ("symbol",), None),
99 "symbol": (0, ("symbol",), None),
100 "string": (0, ("string",), None),
100 "string": (0, ("string",), None),
101 "end": (0, None, None),
101 "end": (0, None, None),
102 }
102 }
103
103
104 keywords = set(['and', 'or', 'not'])
104 keywords = set(['and', 'or', 'not'])
105
105
106 def tokenize(program):
106 def tokenize(program):
107 pos, l = 0, len(program)
107 pos, l = 0, len(program)
108 while pos < l:
108 while pos < l:
109 c = program[pos]
109 c = program[pos]
110 if c.isspace(): # skip inter-token whitespace
110 if c.isspace(): # skip inter-token whitespace
111 pass
111 pass
112 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
112 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
113 yield ('::', None, pos)
113 yield ('::', None, pos)
114 pos += 1 # skip ahead
114 pos += 1 # skip ahead
115 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
115 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
116 yield ('..', None, pos)
116 yield ('..', None, pos)
117 pos += 1 # skip ahead
117 pos += 1 # skip ahead
118 elif c in "():,-|&+!~^": # handle simple operators
118 elif c in "():,-|&+!~^": # handle simple operators
119 yield (c, None, pos)
119 yield (c, None, pos)
120 elif (c in '"\'' or c == 'r' and
120 elif (c in '"\'' or c == 'r' and
121 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
121 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
122 if c == 'r':
122 if c == 'r':
123 pos += 1
123 pos += 1
124 c = program[pos]
124 c = program[pos]
125 decode = lambda x: x
125 decode = lambda x: x
126 else:
126 else:
127 decode = lambda x: x.decode('string-escape')
127 decode = lambda x: x.decode('string-escape')
128 pos += 1
128 pos += 1
129 s = pos
129 s = pos
130 while pos < l: # find closing quote
130 while pos < l: # find closing quote
131 d = program[pos]
131 d = program[pos]
132 if d == '\\': # skip over escaped characters
132 if d == '\\': # skip over escaped characters
133 pos += 2
133 pos += 2
134 continue
134 continue
135 if d == c:
135 if d == c:
136 yield ('string', decode(program[s:pos]), s)
136 yield ('string', decode(program[s:pos]), s)
137 break
137 break
138 pos += 1
138 pos += 1
139 else:
139 else:
140 raise error.ParseError(_("unterminated string"), s)
140 raise error.ParseError(_("unterminated string"), s)
141 # gather up a symbol/keyword
141 # gather up a symbol/keyword
142 elif c.isalnum() or c in '._' or ord(c) > 127:
142 elif c.isalnum() or c in '._' or ord(c) > 127:
143 s = pos
143 s = pos
144 pos += 1
144 pos += 1
145 while pos < l: # find end of symbol
145 while pos < l: # find end of symbol
146 d = program[pos]
146 d = program[pos]
147 if not (d.isalnum() or d in "._/" or ord(d) > 127):
147 if not (d.isalnum() or d in "._/" or ord(d) > 127):
148 break
148 break
149 if d == '.' and program[pos - 1] == '.': # special case for ..
149 if d == '.' and program[pos - 1] == '.': # special case for ..
150 pos -= 1
150 pos -= 1
151 break
151 break
152 pos += 1
152 pos += 1
153 sym = program[s:pos]
153 sym = program[s:pos]
154 if sym in keywords: # operator keywords
154 if sym in keywords: # operator keywords
155 yield (sym, None, s)
155 yield (sym, None, s)
156 else:
156 else:
157 yield ('symbol', sym, s)
157 yield ('symbol', sym, s)
158 pos -= 1
158 pos -= 1
159 else:
159 else:
160 raise error.ParseError(_("syntax error"), pos)
160 raise error.ParseError(_("syntax error"), pos)
161 pos += 1
161 pos += 1
162 yield ('end', None, pos)
162 yield ('end', None, pos)
163
163
164 # helpers
164 # helpers
165
165
166 def getstring(x, err):
166 def getstring(x, err):
167 if x and (x[0] == 'string' or x[0] == 'symbol'):
167 if x and (x[0] == 'string' or x[0] == 'symbol'):
168 return x[1]
168 return x[1]
169 raise error.ParseError(err)
169 raise error.ParseError(err)
170
170
171 def getlist(x):
171 def getlist(x):
172 if not x:
172 if not x:
173 return []
173 return []
174 if x[0] == 'list':
174 if x[0] == 'list':
175 return getlist(x[1]) + [x[2]]
175 return getlist(x[1]) + [x[2]]
176 return [x]
176 return [x]
177
177
178 def getargs(x, min, max, err):
178 def getargs(x, min, max, err):
179 l = getlist(x)
179 l = getlist(x)
180 if len(l) < min or (max >= 0 and len(l) > max):
180 if len(l) < min or (max >= 0 and len(l) > max):
181 raise error.ParseError(err)
181 raise error.ParseError(err)
182 return l
182 return l
183
183
184 def getset(repo, subset, x):
184 def getset(repo, subset, x):
185 if not x:
185 if not x:
186 raise error.ParseError(_("missing argument"))
186 raise error.ParseError(_("missing argument"))
187 return methods[x[0]](repo, subset, *x[1:])
187 return methods[x[0]](repo, subset, *x[1:])
188
188
189 def _getrevsource(repo, r):
189 def _getrevsource(repo, r):
190 extra = repo[r].extra()
190 extra = repo[r].extra()
191 for label in ('source', 'transplant_source', 'rebase_source'):
191 for label in ('source', 'transplant_source', 'rebase_source'):
192 if label in extra:
192 if label in extra:
193 try:
193 try:
194 return repo[extra[label]].rev()
194 return repo[extra[label]].rev()
195 except error.RepoLookupError:
195 except error.RepoLookupError:
196 pass
196 pass
197 return None
197 return None
198
198
199 # operator methods
199 # operator methods
200
200
201 def stringset(repo, subset, x):
201 def stringset(repo, subset, x):
202 x = repo[x].rev()
202 x = repo[x].rev()
203 if x == -1 and len(subset) == len(repo):
203 if x == -1 and len(subset) == len(repo):
204 return [-1]
204 return [-1]
205 if len(subset) == len(repo) or x in subset:
205 if len(subset) == len(repo) or x in subset:
206 return [x]
206 return [x]
207 return []
207 return []
208
208
209 def symbolset(repo, subset, x):
209 def symbolset(repo, subset, x):
210 if x in symbols:
210 if x in symbols:
211 raise error.ParseError(_("can't use %s here") % x)
211 raise error.ParseError(_("can't use %s here") % x)
212 return stringset(repo, subset, x)
212 return stringset(repo, subset, x)
213
213
214 def rangeset(repo, subset, x, y):
214 def rangeset(repo, subset, x, y):
215 m = getset(repo, subset, x)
215 m = getset(repo, subset, x)
216 if not m:
216 if not m:
217 m = getset(repo, range(len(repo)), x)
217 m = getset(repo, range(len(repo)), x)
218
218
219 n = getset(repo, subset, y)
219 n = getset(repo, subset, y)
220 if not n:
220 if not n:
221 n = getset(repo, range(len(repo)), y)
221 n = getset(repo, range(len(repo)), y)
222
222
223 if not m or not n:
223 if not m or not n:
224 return []
224 return []
225 m, n = m[0], n[-1]
225 m, n = m[0], n[-1]
226
226
227 if m < n:
227 if m < n:
228 r = range(m, n + 1)
228 r = range(m, n + 1)
229 else:
229 else:
230 r = range(m, n - 1, -1)
230 r = range(m, n - 1, -1)
231 s = set(subset)
231 s = set(subset)
232 return [x for x in r if x in s]
232 return [x for x in r if x in s]
233
233
234 def dagrange(repo, subset, x, y):
234 def dagrange(repo, subset, x, y):
235 if subset:
235 if subset:
236 r = range(len(repo))
236 r = range(len(repo))
237 xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y))
237 xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y))
238 s = set(subset)
238 s = set(subset)
239 return [r for r in xs if r in s]
239 return [r for r in xs if r in s]
240 return []
240 return []
241
241
242 def andset(repo, subset, x, y):
242 def andset(repo, subset, x, y):
243 return getset(repo, getset(repo, subset, x), y)
243 return getset(repo, getset(repo, subset, x), y)
244
244
245 def orset(repo, subset, x, y):
245 def orset(repo, subset, x, y):
246 xl = getset(repo, subset, x)
246 xl = getset(repo, subset, x)
247 s = set(xl)
247 s = set(xl)
248 yl = getset(repo, [r for r in subset if r not in s], y)
248 yl = getset(repo, [r for r in subset if r not in s], y)
249 return xl + yl
249 return xl + yl
250
250
251 def notset(repo, subset, x):
251 def notset(repo, subset, x):
252 s = set(getset(repo, subset, x))
252 s = set(getset(repo, subset, x))
253 return [r for r in subset if r not in s]
253 return [r for r in subset if r not in s]
254
254
255 def listset(repo, subset, a, b):
255 def listset(repo, subset, a, b):
256 raise error.ParseError(_("can't use a list in this context"))
256 raise error.ParseError(_("can't use a list in this context"))
257
257
258 def func(repo, subset, a, b):
258 def func(repo, subset, a, b):
259 if a[0] == 'symbol' and a[1] in symbols:
259 if a[0] == 'symbol' and a[1] in symbols:
260 return symbols[a[1]](repo, subset, b)
260 return symbols[a[1]](repo, subset, b)
261 raise error.ParseError(_("not a function: %s") % a[1])
261 raise error.ParseError(_("not a function: %s") % a[1])
262
262
263 # functions
263 # functions
264
264
265 def adds(repo, subset, x):
265 def adds(repo, subset, x):
266 """``adds(pattern)``
266 """``adds(pattern)``
267 Changesets that add a file matching pattern.
267 Changesets that add a file matching pattern.
268 """
268 """
269 # i18n: "adds" is a keyword
269 # i18n: "adds" is a keyword
270 pat = getstring(x, _("adds requires a pattern"))
270 pat = getstring(x, _("adds requires a pattern"))
271 return checkstatus(repo, subset, pat, 1)
271 return checkstatus(repo, subset, pat, 1)
272
272
273 def ancestor(repo, subset, x):
273 def ancestor(repo, subset, x):
274 """``ancestor(single, single)``
274 """``ancestor(single, single)``
275 Greatest common ancestor of the two changesets.
275 Greatest common ancestor of the two changesets.
276 """
276 """
277 # i18n: "ancestor" is a keyword
277 # i18n: "ancestor" is a keyword
278 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
278 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
279 r = range(len(repo))
279 r = range(len(repo))
280 a = getset(repo, r, l[0])
280 a = getset(repo, r, l[0])
281 b = getset(repo, r, l[1])
281 b = getset(repo, r, l[1])
282 if len(a) != 1 or len(b) != 1:
282 if len(a) != 1 or len(b) != 1:
283 # i18n: "ancestor" is a keyword
283 # i18n: "ancestor" is a keyword
284 raise error.ParseError(_("ancestor arguments must be single revisions"))
284 raise error.ParseError(_("ancestor arguments must be single revisions"))
285 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
285 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
286
286
287 return [r for r in an if r in subset]
287 return [r for r in an if r in subset]
288
288
289 def _ancestors(repo, subset, x, followfirst=False):
289 def _ancestors(repo, subset, x, followfirst=False):
290 args = getset(repo, range(len(repo)), x)
290 args = getset(repo, range(len(repo)), x)
291 if not args:
291 if not args:
292 return []
292 return []
293 s = set(_revancestors(repo, args, followfirst)) | set(args)
293 s = set(_revancestors(repo, args, followfirst)) | set(args)
294 return [r for r in subset if r in s]
294 return [r for r in subset if r in s]
295
295
296 def ancestors(repo, subset, x):
296 def ancestors(repo, subset, x):
297 """``ancestors(set)``
297 """``ancestors(set)``
298 Changesets that are ancestors of a changeset in set.
298 Changesets that are ancestors of a changeset in set.
299 """
299 """
300 return _ancestors(repo, subset, x)
300 return _ancestors(repo, subset, x)
301
301
302 def _firstancestors(repo, subset, x):
302 def _firstancestors(repo, subset, x):
303 # ``_firstancestors(set)``
303 # ``_firstancestors(set)``
304 # Like ``ancestors(set)`` but follows only the first parents.
304 # Like ``ancestors(set)`` but follows only the first parents.
305 return _ancestors(repo, subset, x, followfirst=True)
305 return _ancestors(repo, subset, x, followfirst=True)
306
306
307 def ancestorspec(repo, subset, x, n):
307 def ancestorspec(repo, subset, x, n):
308 """``set~n``
308 """``set~n``
309 Changesets that are the Nth ancestor (first parents only) of a changeset
309 Changesets that are the Nth ancestor (first parents only) of a changeset
310 in set.
310 in set.
311 """
311 """
312 try:
312 try:
313 n = int(n[1])
313 n = int(n[1])
314 except (TypeError, ValueError):
314 except (TypeError, ValueError):
315 raise error.ParseError(_("~ expects a number"))
315 raise error.ParseError(_("~ expects a number"))
316 ps = set()
316 ps = set()
317 cl = repo.changelog
317 cl = repo.changelog
318 for r in getset(repo, subset, x):
318 for r in getset(repo, subset, x):
319 for i in range(n):
319 for i in range(n):
320 r = cl.parentrevs(r)[0]
320 r = cl.parentrevs(r)[0]
321 ps.add(r)
321 ps.add(r)
322 return [r for r in subset if r in ps]
322 return [r for r in subset if r in ps]
323
323
324 def author(repo, subset, x):
324 def author(repo, subset, x):
325 """``author(string)``
325 """``author(string)``
326 Alias for ``user(string)``.
326 Alias for ``user(string)``.
327 """
327 """
328 # i18n: "author" is a keyword
328 # i18n: "author" is a keyword
329 n = encoding.lower(getstring(x, _("author requires a string")))
329 n = encoding.lower(getstring(x, _("author requires a string")))
330 kind, pattern, matcher = _substringmatcher(n)
330 kind, pattern, matcher = _substringmatcher(n)
331 return [r for r in subset if matcher(encoding.lower(repo[r].user()))]
331 return [r for r in subset if matcher(encoding.lower(repo[r].user()))]
332
332
333 def bisect(repo, subset, x):
333 def bisect(repo, subset, x):
334 """``bisect(string)``
334 """``bisect(string)``
335 Changesets marked in the specified bisect status:
335 Changesets marked in the specified bisect status:
336
336
337 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
337 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
338 - ``goods``, ``bads`` : csets topologicaly good/bad
338 - ``goods``, ``bads`` : csets topologicaly good/bad
339 - ``range`` : csets taking part in the bisection
339 - ``range`` : csets taking part in the bisection
340 - ``pruned`` : csets that are goods, bads or skipped
340 - ``pruned`` : csets that are goods, bads or skipped
341 - ``untested`` : csets whose fate is yet unknown
341 - ``untested`` : csets whose fate is yet unknown
342 - ``ignored`` : csets ignored due to DAG topology
342 - ``ignored`` : csets ignored due to DAG topology
343 - ``current`` : the cset currently being bisected
343 - ``current`` : the cset currently being bisected
344 """
344 """
345 status = getstring(x, _("bisect requires a string")).lower()
345 status = getstring(x, _("bisect requires a string")).lower()
346 state = set(hbisect.get(repo, status))
346 state = set(hbisect.get(repo, status))
347 return [r for r in subset if r in state]
347 return [r for r in subset if r in state]
348
348
349 # Backward-compatibility
349 # Backward-compatibility
350 # - no help entry so that we do not advertise it any more
350 # - no help entry so that we do not advertise it any more
351 def bisected(repo, subset, x):
351 def bisected(repo, subset, x):
352 return bisect(repo, subset, x)
352 return bisect(repo, subset, x)
353
353
354 def bookmark(repo, subset, x):
354 def bookmark(repo, subset, x):
355 """``bookmark([name])``
355 """``bookmark([name])``
356 The named bookmark or all bookmarks.
356 The named bookmark or all bookmarks.
357
357
358 If `name` starts with `re:`, the remainder of the name is treated as
358 If `name` starts with `re:`, the remainder of the name is treated as
359 a regular expression. To match a bookmark that actually starts with `re:`,
359 a regular expression. To match a bookmark that actually starts with `re:`,
360 use the prefix `literal:`.
360 use the prefix `literal:`.
361 """
361 """
362 # i18n: "bookmark" is a keyword
362 # i18n: "bookmark" is a keyword
363 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
363 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
364 if args:
364 if args:
365 bm = getstring(args[0],
365 bm = getstring(args[0],
366 # i18n: "bookmark" is a keyword
366 # i18n: "bookmark" is a keyword
367 _('the argument to bookmark must be a string'))
367 _('the argument to bookmark must be a string'))
368 kind, pattern, matcher = _stringmatcher(bm)
368 kind, pattern, matcher = _stringmatcher(bm)
369 if kind == 'literal':
369 if kind == 'literal':
370 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
370 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
371 if not bmrev:
371 if not bmrev:
372 raise util.Abort(_("bookmark '%s' does not exist") % bm)
372 raise util.Abort(_("bookmark '%s' does not exist") % bm)
373 bmrev = repo[bmrev].rev()
373 bmrev = repo[bmrev].rev()
374 return [r for r in subset if r == bmrev]
374 return [r for r in subset if r == bmrev]
375 else:
375 else:
376 matchrevs = set()
376 matchrevs = set()
377 for name, bmrev in bookmarksmod.listbookmarks(repo).iteritems():
377 for name, bmrev in bookmarksmod.listbookmarks(repo).iteritems():
378 if matcher(name):
378 if matcher(name):
379 matchrevs.add(bmrev)
379 matchrevs.add(bmrev)
380 if not matchrevs:
380 if not matchrevs:
381 raise util.Abort(_("no bookmarks exist that match '%s'")
381 raise util.Abort(_("no bookmarks exist that match '%s'")
382 % pattern)
382 % pattern)
383 bmrevs = set()
383 bmrevs = set()
384 for bmrev in matchrevs:
384 for bmrev in matchrevs:
385 bmrevs.add(repo[bmrev].rev())
385 bmrevs.add(repo[bmrev].rev())
386 return [r for r in subset if r in bmrevs]
386 return [r for r in subset if r in bmrevs]
387
387
388 bms = set([repo[r].rev()
388 bms = set([repo[r].rev()
389 for r in bookmarksmod.listbookmarks(repo).values()])
389 for r in bookmarksmod.listbookmarks(repo).values()])
390 return [r for r in subset if r in bms]
390 return [r for r in subset if r in bms]
391
391
392 def branch(repo, subset, x):
392 def branch(repo, subset, x):
393 """``branch(string or set)``
393 """``branch(string or set)``
394 All changesets belonging to the given branch or the branches of the given
394 All changesets belonging to the given branch or the branches of the given
395 changesets.
395 changesets.
396
396
397 If `string` starts with `re:`, the remainder of the name is treated as
397 If `string` starts with `re:`, the remainder of the name is treated as
398 a regular expression. To match a branch that actually starts with `re:`,
398 a regular expression. To match a branch that actually starts with `re:`,
399 use the prefix `literal:`.
399 use the prefix `literal:`.
400 """
400 """
401 try:
401 try:
402 b = getstring(x, '')
402 b = getstring(x, '')
403 except error.ParseError:
403 except error.ParseError:
404 # not a string, but another revspec, e.g. tip()
404 # not a string, but another revspec, e.g. tip()
405 pass
405 pass
406 else:
406 else:
407 kind, pattern, matcher = _stringmatcher(b)
407 kind, pattern, matcher = _stringmatcher(b)
408 if kind == 'literal':
408 if kind == 'literal':
409 # note: falls through to the revspec case if no branch with
409 # note: falls through to the revspec case if no branch with
410 # this name exists
410 # this name exists
411 if pattern in repo.branchmap():
411 if pattern in repo.branchmap():
412 return [r for r in subset if matcher(repo[r].branch())]
412 return [r for r in subset if matcher(repo[r].branch())]
413 else:
413 else:
414 return [r for r in subset if matcher(repo[r].branch())]
414 return [r for r in subset if matcher(repo[r].branch())]
415
415
416 s = getset(repo, range(len(repo)), x)
416 s = getset(repo, range(len(repo)), x)
417 b = set()
417 b = set()
418 for r in s:
418 for r in s:
419 b.add(repo[r].branch())
419 b.add(repo[r].branch())
420 s = set(s)
420 s = set(s)
421 return [r for r in subset if r in s or repo[r].branch() in b]
421 return [r for r in subset if r in s or repo[r].branch() in b]
422
422
423 def checkstatus(repo, subset, pat, field):
423 def checkstatus(repo, subset, pat, field):
424 m = None
424 m = None
425 s = []
425 s = []
426 hasset = matchmod.patkind(pat) == 'set'
426 hasset = matchmod.patkind(pat) == 'set'
427 fname = None
427 fname = None
428 for r in subset:
428 for r in subset:
429 c = repo[r]
429 c = repo[r]
430 if not m or hasset:
430 if not m or hasset:
431 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
431 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
432 if not m.anypats() and len(m.files()) == 1:
432 if not m.anypats() and len(m.files()) == 1:
433 fname = m.files()[0]
433 fname = m.files()[0]
434 if fname is not None:
434 if fname is not None:
435 if fname not in c.files():
435 if fname not in c.files():
436 continue
436 continue
437 else:
437 else:
438 for f in c.files():
438 for f in c.files():
439 if m(f):
439 if m(f):
440 break
440 break
441 else:
441 else:
442 continue
442 continue
443 files = repo.status(c.p1().node(), c.node())[field]
443 files = repo.status(c.p1().node(), c.node())[field]
444 if fname is not None:
444 if fname is not None:
445 if fname in files:
445 if fname in files:
446 s.append(r)
446 s.append(r)
447 else:
447 else:
448 for f in files:
448 for f in files:
449 if m(f):
449 if m(f):
450 s.append(r)
450 s.append(r)
451 break
451 break
452 return s
452 return s
453
453
454 def _children(repo, narrow, parentset):
454 def _children(repo, narrow, parentset):
455 cs = set()
455 cs = set()
456 pr = repo.changelog.parentrevs
456 pr = repo.changelog.parentrevs
457 for r in narrow:
457 for r in narrow:
458 for p in pr(r):
458 for p in pr(r):
459 if p in parentset:
459 if p in parentset:
460 cs.add(r)
460 cs.add(r)
461 return cs
461 return cs
462
462
463 def children(repo, subset, x):
463 def children(repo, subset, x):
464 """``children(set)``
464 """``children(set)``
465 Child changesets of changesets in set.
465 Child changesets of changesets in set.
466 """
466 """
467 s = set(getset(repo, range(len(repo)), x))
467 s = set(getset(repo, range(len(repo)), x))
468 cs = _children(repo, subset, s)
468 cs = _children(repo, subset, s)
469 return [r for r in subset if r in cs]
469 return [r for r in subset if r in cs]
470
470
471 def closed(repo, subset, x):
471 def closed(repo, subset, x):
472 """``closed()``
472 """``closed()``
473 Changeset is closed.
473 Changeset is closed.
474 """
474 """
475 # i18n: "closed" is a keyword
475 # i18n: "closed" is a keyword
476 getargs(x, 0, 0, _("closed takes no arguments"))
476 getargs(x, 0, 0, _("closed takes no arguments"))
477 return [r for r in subset if repo[r].closesbranch()]
477 return [r for r in subset if repo[r].closesbranch()]
478
478
479 def contains(repo, subset, x):
479 def contains(repo, subset, x):
480 """``contains(pattern)``
480 """``contains(pattern)``
481 Revision contains a file matching pattern. See :hg:`help patterns`
481 Revision contains a file matching pattern. See :hg:`help patterns`
482 for information about file patterns.
482 for information about file patterns.
483 """
483 """
484 # i18n: "contains" is a keyword
484 # i18n: "contains" is a keyword
485 pat = getstring(x, _("contains requires a pattern"))
485 pat = getstring(x, _("contains requires a pattern"))
486 m = None
486 m = None
487 s = []
487 s = []
488 if not matchmod.patkind(pat):
488 if not matchmod.patkind(pat):
489 for r in subset:
489 for r in subset:
490 if pat in repo[r]:
490 if pat in repo[r]:
491 s.append(r)
491 s.append(r)
492 else:
492 else:
493 for r in subset:
493 for r in subset:
494 c = repo[r]
494 c = repo[r]
495 if not m or matchmod.patkind(pat) == 'set':
495 if not m or matchmod.patkind(pat) == 'set':
496 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
496 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
497 for f in c.manifest():
497 for f in c.manifest():
498 if m(f):
498 if m(f):
499 s.append(r)
499 s.append(r)
500 break
500 break
501 return s
501 return s
502
502
503 def converted(repo, subset, x):
503 def converted(repo, subset, x):
504 """``converted([id])``
504 """``converted([id])``
505 Changesets converted from the given identifier in the old repository if
505 Changesets converted from the given identifier in the old repository if
506 present, or all converted changesets if no identifier is specified.
506 present, or all converted changesets if no identifier is specified.
507 """
507 """
508
508
509 # There is exactly no chance of resolving the revision, so do a simple
509 # There is exactly no chance of resolving the revision, so do a simple
510 # string compare and hope for the best
510 # string compare and hope for the best
511
511
512 # i18n: "converted" is a keyword
512 # i18n: "converted" is a keyword
513 rev = None
513 rev = None
514 l = getargs(x, 0, 1, _('converted takes one or no arguments'))
514 l = getargs(x, 0, 1, _('converted takes one or no arguments'))
515 if l:
515 if l:
516 rev = getstring(l[0], _('converted requires a revision'))
516 rev = getstring(l[0], _('converted requires a revision'))
517
517
518 def _matchvalue(r):
518 def _matchvalue(r):
519 source = repo[r].extra().get('convert_revision', None)
519 source = repo[r].extra().get('convert_revision', None)
520 return source is not None and (rev is None or source.startswith(rev))
520 return source is not None and (rev is None or source.startswith(rev))
521
521
522 return [r for r in subset if _matchvalue(r)]
522 return [r for r in subset if _matchvalue(r)]
523
523
524 def date(repo, subset, x):
524 def date(repo, subset, x):
525 """``date(interval)``
525 """``date(interval)``
526 Changesets within the interval, see :hg:`help dates`.
526 Changesets within the interval, see :hg:`help dates`.
527 """
527 """
528 # i18n: "date" is a keyword
528 # i18n: "date" is a keyword
529 ds = getstring(x, _("date requires a string"))
529 ds = getstring(x, _("date requires a string"))
530 dm = util.matchdate(ds)
530 dm = util.matchdate(ds)
531 return [r for r in subset if dm(repo[r].date()[0])]
531 return [r for r in subset if dm(repo[r].date()[0])]
532
532
533 def desc(repo, subset, x):
533 def desc(repo, subset, x):
534 """``desc(string)``
534 """``desc(string)``
535 Search commit message for string. The match is case-insensitive.
535 Search commit message for string. The match is case-insensitive.
536 """
536 """
537 # i18n: "desc" is a keyword
537 # i18n: "desc" is a keyword
538 ds = encoding.lower(getstring(x, _("desc requires a string")))
538 ds = encoding.lower(getstring(x, _("desc requires a string")))
539 l = []
539 l = []
540 for r in subset:
540 for r in subset:
541 c = repo[r]
541 c = repo[r]
542 if ds in encoding.lower(c.description()):
542 if ds in encoding.lower(c.description()):
543 l.append(r)
543 l.append(r)
544 return l
544 return l
545
545
546 def _descendants(repo, subset, x, followfirst=False):
546 def _descendants(repo, subset, x, followfirst=False):
547 args = getset(repo, range(len(repo)), x)
547 args = getset(repo, range(len(repo)), x)
548 if not args:
548 if not args:
549 return []
549 return []
550 s = set(_revdescendants(repo, args, followfirst)) | set(args)
550 s = set(_revdescendants(repo, args, followfirst)) | set(args)
551 return [r for r in subset if r in s]
551 return [r for r in subset if r in s]
552
552
553 def descendants(repo, subset, x):
553 def descendants(repo, subset, x):
554 """``descendants(set)``
554 """``descendants(set)``
555 Changesets which are descendants of changesets in set.
555 Changesets which are descendants of changesets in set.
556 """
556 """
557 return _descendants(repo, subset, x)
557 return _descendants(repo, subset, x)
558
558
559 def _firstdescendants(repo, subset, x):
559 def _firstdescendants(repo, subset, x):
560 # ``_firstdescendants(set)``
560 # ``_firstdescendants(set)``
561 # Like ``descendants(set)`` but follows only the first parents.
561 # Like ``descendants(set)`` but follows only the first parents.
562 return _descendants(repo, subset, x, followfirst=True)
562 return _descendants(repo, subset, x, followfirst=True)
563
563
564 def draft(repo, subset, x):
564 def draft(repo, subset, x):
565 """``draft()``
565 """``draft()``
566 Changeset in draft phase."""
566 Changeset in draft phase."""
567 getargs(x, 0, 0, _("draft takes no arguments"))
567 getargs(x, 0, 0, _("draft takes no arguments"))
568 pc = repo._phasecache
568 pc = repo._phasecache
569 return [r for r in subset if pc.phase(repo, r) == phases.draft]
569 return [r for r in subset if pc.phase(repo, r) == phases.draft]
570
570
571 def extinct(repo, subset, x):
572 """``extinct()``
573 obsolete changeset with obsolete descendant only."""
574 getargs(x, 0, 0, _("obsolete takes no arguments"))
575 extinctset = set(repo.revs('(obsolete()::) - (::(not obsolete()))'))
576 return [r for r in subset if r in extinctset]
577
571 def extra(repo, subset, x):
578 def extra(repo, subset, x):
572 """``extra(label, [value])``
579 """``extra(label, [value])``
573 Changesets with the given label in the extra metadata, with the given
580 Changesets with the given label in the extra metadata, with the given
574 optional value.
581 optional value.
575
582
576 If `value` starts with `re:`, the remainder of the value is treated as
583 If `value` starts with `re:`, the remainder of the value is treated as
577 a regular expression. To match a value that actually starts with `re:`,
584 a regular expression. To match a value that actually starts with `re:`,
578 use the prefix `literal:`.
585 use the prefix `literal:`.
579 """
586 """
580
587
581 l = getargs(x, 1, 2, _('extra takes at least 1 and at most 2 arguments'))
588 l = getargs(x, 1, 2, _('extra takes at least 1 and at most 2 arguments'))
582 label = getstring(l[0], _('first argument to extra must be a string'))
589 label = getstring(l[0], _('first argument to extra must be a string'))
583 value = None
590 value = None
584
591
585 if len(l) > 1:
592 if len(l) > 1:
586 value = getstring(l[1], _('second argument to extra must be a string'))
593 value = getstring(l[1], _('second argument to extra must be a string'))
587 kind, value, matcher = _stringmatcher(value)
594 kind, value, matcher = _stringmatcher(value)
588
595
589 def _matchvalue(r):
596 def _matchvalue(r):
590 extra = repo[r].extra()
597 extra = repo[r].extra()
591 return label in extra and (value is None or matcher(extra[label]))
598 return label in extra and (value is None or matcher(extra[label]))
592
599
593 return [r for r in subset if _matchvalue(r)]
600 return [r for r in subset if _matchvalue(r)]
594
601
595 def filelog(repo, subset, x):
602 def filelog(repo, subset, x):
596 """``filelog(pattern)``
603 """``filelog(pattern)``
597 Changesets connected to the specified filelog.
604 Changesets connected to the specified filelog.
598 """
605 """
599
606
600 pat = getstring(x, _("filelog requires a pattern"))
607 pat = getstring(x, _("filelog requires a pattern"))
601 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath',
608 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath',
602 ctx=repo[None])
609 ctx=repo[None])
603 s = set()
610 s = set()
604
611
605 if not matchmod.patkind(pat):
612 if not matchmod.patkind(pat):
606 for f in m.files():
613 for f in m.files():
607 fl = repo.file(f)
614 fl = repo.file(f)
608 for fr in fl:
615 for fr in fl:
609 s.add(fl.linkrev(fr))
616 s.add(fl.linkrev(fr))
610 else:
617 else:
611 for f in repo[None]:
618 for f in repo[None]:
612 if m(f):
619 if m(f):
613 fl = repo.file(f)
620 fl = repo.file(f)
614 for fr in fl:
621 for fr in fl:
615 s.add(fl.linkrev(fr))
622 s.add(fl.linkrev(fr))
616
623
617 return [r for r in subset if r in s]
624 return [r for r in subset if r in s]
618
625
619 def first(repo, subset, x):
626 def first(repo, subset, x):
620 """``first(set, [n])``
627 """``first(set, [n])``
621 An alias for limit().
628 An alias for limit().
622 """
629 """
623 return limit(repo, subset, x)
630 return limit(repo, subset, x)
624
631
625 def _follow(repo, subset, x, name, followfirst=False):
632 def _follow(repo, subset, x, name, followfirst=False):
626 l = getargs(x, 0, 1, _("%s takes no arguments or a filename") % name)
633 l = getargs(x, 0, 1, _("%s takes no arguments or a filename") % name)
627 c = repo['.']
634 c = repo['.']
628 if l:
635 if l:
629 x = getstring(l[0], _("%s expected a filename") % name)
636 x = getstring(l[0], _("%s expected a filename") % name)
630 if x in c:
637 if x in c:
631 cx = c[x]
638 cx = c[x]
632 s = set(ctx.rev() for ctx in cx.ancestors(followfirst=followfirst))
639 s = set(ctx.rev() for ctx in cx.ancestors(followfirst=followfirst))
633 # include the revision responsible for the most recent version
640 # include the revision responsible for the most recent version
634 s.add(cx.linkrev())
641 s.add(cx.linkrev())
635 else:
642 else:
636 return []
643 return []
637 else:
644 else:
638 s = set(_revancestors(repo, [c.rev()], followfirst)) | set([c.rev()])
645 s = set(_revancestors(repo, [c.rev()], followfirst)) | set([c.rev()])
639
646
640 return [r for r in subset if r in s]
647 return [r for r in subset if r in s]
641
648
642 def follow(repo, subset, x):
649 def follow(repo, subset, x):
643 """``follow([file])``
650 """``follow([file])``
644 An alias for ``::.`` (ancestors of the working copy's first parent).
651 An alias for ``::.`` (ancestors of the working copy's first parent).
645 If a filename is specified, the history of the given file is followed,
652 If a filename is specified, the history of the given file is followed,
646 including copies.
653 including copies.
647 """
654 """
648 return _follow(repo, subset, x, 'follow')
655 return _follow(repo, subset, x, 'follow')
649
656
650 def _followfirst(repo, subset, x):
657 def _followfirst(repo, subset, x):
651 # ``followfirst([file])``
658 # ``followfirst([file])``
652 # Like ``follow([file])`` but follows only the first parent of
659 # Like ``follow([file])`` but follows only the first parent of
653 # every revision or file revision.
660 # every revision or file revision.
654 return _follow(repo, subset, x, '_followfirst', followfirst=True)
661 return _follow(repo, subset, x, '_followfirst', followfirst=True)
655
662
656 def getall(repo, subset, x):
663 def getall(repo, subset, x):
657 """``all()``
664 """``all()``
658 All changesets, the same as ``0:tip``.
665 All changesets, the same as ``0:tip``.
659 """
666 """
660 # i18n: "all" is a keyword
667 # i18n: "all" is a keyword
661 getargs(x, 0, 0, _("all takes no arguments"))
668 getargs(x, 0, 0, _("all takes no arguments"))
662 return subset
669 return subset
663
670
664 def grep(repo, subset, x):
671 def grep(repo, subset, x):
665 """``grep(regex)``
672 """``grep(regex)``
666 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
673 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
667 to ensure special escape characters are handled correctly. Unlike
674 to ensure special escape characters are handled correctly. Unlike
668 ``keyword(string)``, the match is case-sensitive.
675 ``keyword(string)``, the match is case-sensitive.
669 """
676 """
670 try:
677 try:
671 # i18n: "grep" is a keyword
678 # i18n: "grep" is a keyword
672 gr = re.compile(getstring(x, _("grep requires a string")))
679 gr = re.compile(getstring(x, _("grep requires a string")))
673 except re.error, e:
680 except re.error, e:
674 raise error.ParseError(_('invalid match pattern: %s') % e)
681 raise error.ParseError(_('invalid match pattern: %s') % e)
675 l = []
682 l = []
676 for r in subset:
683 for r in subset:
677 c = repo[r]
684 c = repo[r]
678 for e in c.files() + [c.user(), c.description()]:
685 for e in c.files() + [c.user(), c.description()]:
679 if gr.search(e):
686 if gr.search(e):
680 l.append(r)
687 l.append(r)
681 break
688 break
682 return l
689 return l
683
690
684 def _matchfiles(repo, subset, x):
691 def _matchfiles(repo, subset, x):
685 # _matchfiles takes a revset list of prefixed arguments:
692 # _matchfiles takes a revset list of prefixed arguments:
686 #
693 #
687 # [p:foo, i:bar, x:baz]
694 # [p:foo, i:bar, x:baz]
688 #
695 #
689 # builds a match object from them and filters subset. Allowed
696 # builds a match object from them and filters subset. Allowed
690 # prefixes are 'p:' for regular patterns, 'i:' for include
697 # prefixes are 'p:' for regular patterns, 'i:' for include
691 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
698 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
692 # a revision identifier, or the empty string to reference the
699 # a revision identifier, or the empty string to reference the
693 # working directory, from which the match object is
700 # working directory, from which the match object is
694 # initialized. Use 'd:' to set the default matching mode, default
701 # initialized. Use 'd:' to set the default matching mode, default
695 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
702 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
696
703
697 # i18n: "_matchfiles" is a keyword
704 # i18n: "_matchfiles" is a keyword
698 l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
705 l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
699 pats, inc, exc = [], [], []
706 pats, inc, exc = [], [], []
700 hasset = False
707 hasset = False
701 rev, default = None, None
708 rev, default = None, None
702 for arg in l:
709 for arg in l:
703 s = getstring(arg, _("_matchfiles requires string arguments"))
710 s = getstring(arg, _("_matchfiles requires string arguments"))
704 prefix, value = s[:2], s[2:]
711 prefix, value = s[:2], s[2:]
705 if prefix == 'p:':
712 if prefix == 'p:':
706 pats.append(value)
713 pats.append(value)
707 elif prefix == 'i:':
714 elif prefix == 'i:':
708 inc.append(value)
715 inc.append(value)
709 elif prefix == 'x:':
716 elif prefix == 'x:':
710 exc.append(value)
717 exc.append(value)
711 elif prefix == 'r:':
718 elif prefix == 'r:':
712 if rev is not None:
719 if rev is not None:
713 raise error.ParseError(_('_matchfiles expected at most one '
720 raise error.ParseError(_('_matchfiles expected at most one '
714 'revision'))
721 'revision'))
715 rev = value
722 rev = value
716 elif prefix == 'd:':
723 elif prefix == 'd:':
717 if default is not None:
724 if default is not None:
718 raise error.ParseError(_('_matchfiles expected at most one '
725 raise error.ParseError(_('_matchfiles expected at most one '
719 'default mode'))
726 'default mode'))
720 default = value
727 default = value
721 else:
728 else:
722 raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
729 raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
723 if not hasset and matchmod.patkind(value) == 'set':
730 if not hasset and matchmod.patkind(value) == 'set':
724 hasset = True
731 hasset = True
725 if not default:
732 if not default:
726 default = 'glob'
733 default = 'glob'
727 m = None
734 m = None
728 s = []
735 s = []
729 for r in subset:
736 for r in subset:
730 c = repo[r]
737 c = repo[r]
731 if not m or (hasset and rev is None):
738 if not m or (hasset and rev is None):
732 ctx = c
739 ctx = c
733 if rev is not None:
740 if rev is not None:
734 ctx = repo[rev or None]
741 ctx = repo[rev or None]
735 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
742 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
736 exclude=exc, ctx=ctx, default=default)
743 exclude=exc, ctx=ctx, default=default)
737 for f in c.files():
744 for f in c.files():
738 if m(f):
745 if m(f):
739 s.append(r)
746 s.append(r)
740 break
747 break
741 return s
748 return s
742
749
743 def hasfile(repo, subset, x):
750 def hasfile(repo, subset, x):
744 """``file(pattern)``
751 """``file(pattern)``
745 Changesets affecting files matched by pattern.
752 Changesets affecting files matched by pattern.
746 """
753 """
747 # i18n: "file" is a keyword
754 # i18n: "file" is a keyword
748 pat = getstring(x, _("file requires a pattern"))
755 pat = getstring(x, _("file requires a pattern"))
749 return _matchfiles(repo, subset, ('string', 'p:' + pat))
756 return _matchfiles(repo, subset, ('string', 'p:' + pat))
750
757
751 def head(repo, subset, x):
758 def head(repo, subset, x):
752 """``head()``
759 """``head()``
753 Changeset is a named branch head.
760 Changeset is a named branch head.
754 """
761 """
755 # i18n: "head" is a keyword
762 # i18n: "head" is a keyword
756 getargs(x, 0, 0, _("head takes no arguments"))
763 getargs(x, 0, 0, _("head takes no arguments"))
757 hs = set()
764 hs = set()
758 for b, ls in repo.branchmap().iteritems():
765 for b, ls in repo.branchmap().iteritems():
759 hs.update(repo[h].rev() for h in ls)
766 hs.update(repo[h].rev() for h in ls)
760 return [r for r in subset if r in hs]
767 return [r for r in subset if r in hs]
761
768
762 def heads(repo, subset, x):
769 def heads(repo, subset, x):
763 """``heads(set)``
770 """``heads(set)``
764 Members of set with no children in set.
771 Members of set with no children in set.
765 """
772 """
766 s = getset(repo, subset, x)
773 s = getset(repo, subset, x)
767 ps = set(parents(repo, subset, x))
774 ps = set(parents(repo, subset, x))
768 return [r for r in s if r not in ps]
775 return [r for r in s if r not in ps]
769
776
770 def keyword(repo, subset, x):
777 def keyword(repo, subset, x):
771 """``keyword(string)``
778 """``keyword(string)``
772 Search commit message, user name, and names of changed files for
779 Search commit message, user name, and names of changed files for
773 string. The match is case-insensitive.
780 string. The match is case-insensitive.
774 """
781 """
775 # i18n: "keyword" is a keyword
782 # i18n: "keyword" is a keyword
776 kw = encoding.lower(getstring(x, _("keyword requires a string")))
783 kw = encoding.lower(getstring(x, _("keyword requires a string")))
777 l = []
784 l = []
778 for r in subset:
785 for r in subset:
779 c = repo[r]
786 c = repo[r]
780 t = " ".join(c.files() + [c.user(), c.description()])
787 t = " ".join(c.files() + [c.user(), c.description()])
781 if kw in encoding.lower(t):
788 if kw in encoding.lower(t):
782 l.append(r)
789 l.append(r)
783 return l
790 return l
784
791
785 def limit(repo, subset, x):
792 def limit(repo, subset, x):
786 """``limit(set, [n])``
793 """``limit(set, [n])``
787 First n members of set, defaulting to 1.
794 First n members of set, defaulting to 1.
788 """
795 """
789 # i18n: "limit" is a keyword
796 # i18n: "limit" is a keyword
790 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
797 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
791 try:
798 try:
792 lim = 1
799 lim = 1
793 if len(l) == 2:
800 if len(l) == 2:
794 # i18n: "limit" is a keyword
801 # i18n: "limit" is a keyword
795 lim = int(getstring(l[1], _("limit requires a number")))
802 lim = int(getstring(l[1], _("limit requires a number")))
796 except (TypeError, ValueError):
803 except (TypeError, ValueError):
797 # i18n: "limit" is a keyword
804 # i18n: "limit" is a keyword
798 raise error.ParseError(_("limit expects a number"))
805 raise error.ParseError(_("limit expects a number"))
799 ss = set(subset)
806 ss = set(subset)
800 os = getset(repo, range(len(repo)), l[0])[:lim]
807 os = getset(repo, range(len(repo)), l[0])[:lim]
801 return [r for r in os if r in ss]
808 return [r for r in os if r in ss]
802
809
803 def last(repo, subset, x):
810 def last(repo, subset, x):
804 """``last(set, [n])``
811 """``last(set, [n])``
805 Last n members of set, defaulting to 1.
812 Last n members of set, defaulting to 1.
806 """
813 """
807 # i18n: "last" is a keyword
814 # i18n: "last" is a keyword
808 l = getargs(x, 1, 2, _("last requires one or two arguments"))
815 l = getargs(x, 1, 2, _("last requires one or two arguments"))
809 try:
816 try:
810 lim = 1
817 lim = 1
811 if len(l) == 2:
818 if len(l) == 2:
812 # i18n: "last" is a keyword
819 # i18n: "last" is a keyword
813 lim = int(getstring(l[1], _("last requires a number")))
820 lim = int(getstring(l[1], _("last requires a number")))
814 except (TypeError, ValueError):
821 except (TypeError, ValueError):
815 # i18n: "last" is a keyword
822 # i18n: "last" is a keyword
816 raise error.ParseError(_("last expects a number"))
823 raise error.ParseError(_("last expects a number"))
817 ss = set(subset)
824 ss = set(subset)
818 os = getset(repo, range(len(repo)), l[0])[-lim:]
825 os = getset(repo, range(len(repo)), l[0])[-lim:]
819 return [r for r in os if r in ss]
826 return [r for r in os if r in ss]
820
827
821 def maxrev(repo, subset, x):
828 def maxrev(repo, subset, x):
822 """``max(set)``
829 """``max(set)``
823 Changeset with highest revision number in set.
830 Changeset with highest revision number in set.
824 """
831 """
825 os = getset(repo, range(len(repo)), x)
832 os = getset(repo, range(len(repo)), x)
826 if os:
833 if os:
827 m = max(os)
834 m = max(os)
828 if m in subset:
835 if m in subset:
829 return [m]
836 return [m]
830 return []
837 return []
831
838
832 def merge(repo, subset, x):
839 def merge(repo, subset, x):
833 """``merge()``
840 """``merge()``
834 Changeset is a merge changeset.
841 Changeset is a merge changeset.
835 """
842 """
836 # i18n: "merge" is a keyword
843 # i18n: "merge" is a keyword
837 getargs(x, 0, 0, _("merge takes no arguments"))
844 getargs(x, 0, 0, _("merge takes no arguments"))
838 cl = repo.changelog
845 cl = repo.changelog
839 return [r for r in subset if cl.parentrevs(r)[1] != -1]
846 return [r for r in subset if cl.parentrevs(r)[1] != -1]
840
847
841 def minrev(repo, subset, x):
848 def minrev(repo, subset, x):
842 """``min(set)``
849 """``min(set)``
843 Changeset with lowest revision number in set.
850 Changeset with lowest revision number in set.
844 """
851 """
845 os = getset(repo, range(len(repo)), x)
852 os = getset(repo, range(len(repo)), x)
846 if os:
853 if os:
847 m = min(os)
854 m = min(os)
848 if m in subset:
855 if m in subset:
849 return [m]
856 return [m]
850 return []
857 return []
851
858
852 def modifies(repo, subset, x):
859 def modifies(repo, subset, x):
853 """``modifies(pattern)``
860 """``modifies(pattern)``
854 Changesets modifying files matched by pattern.
861 Changesets modifying files matched by pattern.
855 """
862 """
856 # i18n: "modifies" is a keyword
863 # i18n: "modifies" is a keyword
857 pat = getstring(x, _("modifies requires a pattern"))
864 pat = getstring(x, _("modifies requires a pattern"))
858 return checkstatus(repo, subset, pat, 0)
865 return checkstatus(repo, subset, pat, 0)
859
866
860 def node_(repo, subset, x):
867 def node_(repo, subset, x):
861 """``id(string)``
868 """``id(string)``
862 Revision non-ambiguously specified by the given hex string prefix.
869 Revision non-ambiguously specified by the given hex string prefix.
863 """
870 """
864 # i18n: "id" is a keyword
871 # i18n: "id" is a keyword
865 l = getargs(x, 1, 1, _("id requires one argument"))
872 l = getargs(x, 1, 1, _("id requires one argument"))
866 # i18n: "id" is a keyword
873 # i18n: "id" is a keyword
867 n = getstring(l[0], _("id requires a string"))
874 n = getstring(l[0], _("id requires a string"))
868 if len(n) == 40:
875 if len(n) == 40:
869 rn = repo[n].rev()
876 rn = repo[n].rev()
870 else:
877 else:
871 rn = None
878 rn = None
872 pm = repo.changelog._partialmatch(n)
879 pm = repo.changelog._partialmatch(n)
873 if pm is not None:
880 if pm is not None:
874 rn = repo.changelog.rev(pm)
881 rn = repo.changelog.rev(pm)
875
882
876 return [r for r in subset if r == rn]
883 return [r for r in subset if r == rn]
877
884
878 def obsolete(repo, subset, x):
885 def obsolete(repo, subset, x):
879 """``obsolete()``
886 """``obsolete()``
880 Mutable changeset with a newer version."""
887 Mutable changeset with a newer version."""
881 getargs(x, 0, 0, _("obsolete takes no arguments"))
888 getargs(x, 0, 0, _("obsolete takes no arguments"))
882 return [r for r in subset if repo[r].obsolete()]
889 return [r for r in subset if repo[r].obsolete()]
883
890
884 def outgoing(repo, subset, x):
891 def outgoing(repo, subset, x):
885 """``outgoing([path])``
892 """``outgoing([path])``
886 Changesets not found in the specified destination repository, or the
893 Changesets not found in the specified destination repository, or the
887 default push location.
894 default push location.
888 """
895 """
889 import hg # avoid start-up nasties
896 import hg # avoid start-up nasties
890 # i18n: "outgoing" is a keyword
897 # i18n: "outgoing" is a keyword
891 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
898 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
892 # i18n: "outgoing" is a keyword
899 # i18n: "outgoing" is a keyword
893 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
900 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
894 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
901 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
895 dest, branches = hg.parseurl(dest)
902 dest, branches = hg.parseurl(dest)
896 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
903 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
897 if revs:
904 if revs:
898 revs = [repo.lookup(rev) for rev in revs]
905 revs = [repo.lookup(rev) for rev in revs]
899 other = hg.peer(repo, {}, dest)
906 other = hg.peer(repo, {}, dest)
900 repo.ui.pushbuffer()
907 repo.ui.pushbuffer()
901 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
908 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
902 repo.ui.popbuffer()
909 repo.ui.popbuffer()
903 cl = repo.changelog
910 cl = repo.changelog
904 o = set([cl.rev(r) for r in outgoing.missing])
911 o = set([cl.rev(r) for r in outgoing.missing])
905 return [r for r in subset if r in o]
912 return [r for r in subset if r in o]
906
913
907 def p1(repo, subset, x):
914 def p1(repo, subset, x):
908 """``p1([set])``
915 """``p1([set])``
909 First parent of changesets in set, or the working directory.
916 First parent of changesets in set, or the working directory.
910 """
917 """
911 if x is None:
918 if x is None:
912 p = repo[x].p1().rev()
919 p = repo[x].p1().rev()
913 return [r for r in subset if r == p]
920 return [r for r in subset if r == p]
914
921
915 ps = set()
922 ps = set()
916 cl = repo.changelog
923 cl = repo.changelog
917 for r in getset(repo, range(len(repo)), x):
924 for r in getset(repo, range(len(repo)), x):
918 ps.add(cl.parentrevs(r)[0])
925 ps.add(cl.parentrevs(r)[0])
919 return [r for r in subset if r in ps]
926 return [r for r in subset if r in ps]
920
927
921 def p2(repo, subset, x):
928 def p2(repo, subset, x):
922 """``p2([set])``
929 """``p2([set])``
923 Second parent of changesets in set, or the working directory.
930 Second parent of changesets in set, or the working directory.
924 """
931 """
925 if x is None:
932 if x is None:
926 ps = repo[x].parents()
933 ps = repo[x].parents()
927 try:
934 try:
928 p = ps[1].rev()
935 p = ps[1].rev()
929 return [r for r in subset if r == p]
936 return [r for r in subset if r == p]
930 except IndexError:
937 except IndexError:
931 return []
938 return []
932
939
933 ps = set()
940 ps = set()
934 cl = repo.changelog
941 cl = repo.changelog
935 for r in getset(repo, range(len(repo)), x):
942 for r in getset(repo, range(len(repo)), x):
936 ps.add(cl.parentrevs(r)[1])
943 ps.add(cl.parentrevs(r)[1])
937 return [r for r in subset if r in ps]
944 return [r for r in subset if r in ps]
938
945
939 def parents(repo, subset, x):
946 def parents(repo, subset, x):
940 """``parents([set])``
947 """``parents([set])``
941 The set of all parents for all changesets in set, or the working directory.
948 The set of all parents for all changesets in set, or the working directory.
942 """
949 """
943 if x is None:
950 if x is None:
944 ps = tuple(p.rev() for p in repo[x].parents())
951 ps = tuple(p.rev() for p in repo[x].parents())
945 return [r for r in subset if r in ps]
952 return [r for r in subset if r in ps]
946
953
947 ps = set()
954 ps = set()
948 cl = repo.changelog
955 cl = repo.changelog
949 for r in getset(repo, range(len(repo)), x):
956 for r in getset(repo, range(len(repo)), x):
950 ps.update(cl.parentrevs(r))
957 ps.update(cl.parentrevs(r))
951 return [r for r in subset if r in ps]
958 return [r for r in subset if r in ps]
952
959
953 def parentspec(repo, subset, x, n):
960 def parentspec(repo, subset, x, n):
954 """``set^0``
961 """``set^0``
955 The set.
962 The set.
956 ``set^1`` (or ``set^``), ``set^2``
963 ``set^1`` (or ``set^``), ``set^2``
957 First or second parent, respectively, of all changesets in set.
964 First or second parent, respectively, of all changesets in set.
958 """
965 """
959 try:
966 try:
960 n = int(n[1])
967 n = int(n[1])
961 if n not in (0, 1, 2):
968 if n not in (0, 1, 2):
962 raise ValueError
969 raise ValueError
963 except (TypeError, ValueError):
970 except (TypeError, ValueError):
964 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
971 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
965 ps = set()
972 ps = set()
966 cl = repo.changelog
973 cl = repo.changelog
967 for r in getset(repo, subset, x):
974 for r in getset(repo, subset, x):
968 if n == 0:
975 if n == 0:
969 ps.add(r)
976 ps.add(r)
970 elif n == 1:
977 elif n == 1:
971 ps.add(cl.parentrevs(r)[0])
978 ps.add(cl.parentrevs(r)[0])
972 elif n == 2:
979 elif n == 2:
973 parents = cl.parentrevs(r)
980 parents = cl.parentrevs(r)
974 if len(parents) > 1:
981 if len(parents) > 1:
975 ps.add(parents[1])
982 ps.add(parents[1])
976 return [r for r in subset if r in ps]
983 return [r for r in subset if r in ps]
977
984
978 def present(repo, subset, x):
985 def present(repo, subset, x):
979 """``present(set)``
986 """``present(set)``
980 An empty set, if any revision in set isn't found; otherwise,
987 An empty set, if any revision in set isn't found; otherwise,
981 all revisions in set.
988 all revisions in set.
982
989
983 If any of specified revisions is not present in the local repository,
990 If any of specified revisions is not present in the local repository,
984 the query is normally aborted. But this predicate allows the query
991 the query is normally aborted. But this predicate allows the query
985 to continue even in such cases.
992 to continue even in such cases.
986 """
993 """
987 try:
994 try:
988 return getset(repo, subset, x)
995 return getset(repo, subset, x)
989 except error.RepoLookupError:
996 except error.RepoLookupError:
990 return []
997 return []
991
998
992 def public(repo, subset, x):
999 def public(repo, subset, x):
993 """``public()``
1000 """``public()``
994 Changeset in public phase."""
1001 Changeset in public phase."""
995 getargs(x, 0, 0, _("public takes no arguments"))
1002 getargs(x, 0, 0, _("public takes no arguments"))
996 pc = repo._phasecache
1003 pc = repo._phasecache
997 return [r for r in subset if pc.phase(repo, r) == phases.public]
1004 return [r for r in subset if pc.phase(repo, r) == phases.public]
998
1005
999 def remote(repo, subset, x):
1006 def remote(repo, subset, x):
1000 """``remote([id [,path]])``
1007 """``remote([id [,path]])``
1001 Local revision that corresponds to the given identifier in a
1008 Local revision that corresponds to the given identifier in a
1002 remote repository, if present. Here, the '.' identifier is a
1009 remote repository, if present. Here, the '.' identifier is a
1003 synonym for the current local branch.
1010 synonym for the current local branch.
1004 """
1011 """
1005
1012
1006 import hg # avoid start-up nasties
1013 import hg # avoid start-up nasties
1007 # i18n: "remote" is a keyword
1014 # i18n: "remote" is a keyword
1008 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
1015 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
1009
1016
1010 q = '.'
1017 q = '.'
1011 if len(l) > 0:
1018 if len(l) > 0:
1012 # i18n: "remote" is a keyword
1019 # i18n: "remote" is a keyword
1013 q = getstring(l[0], _("remote requires a string id"))
1020 q = getstring(l[0], _("remote requires a string id"))
1014 if q == '.':
1021 if q == '.':
1015 q = repo['.'].branch()
1022 q = repo['.'].branch()
1016
1023
1017 dest = ''
1024 dest = ''
1018 if len(l) > 1:
1025 if len(l) > 1:
1019 # i18n: "remote" is a keyword
1026 # i18n: "remote" is a keyword
1020 dest = getstring(l[1], _("remote requires a repository path"))
1027 dest = getstring(l[1], _("remote requires a repository path"))
1021 dest = repo.ui.expandpath(dest or 'default')
1028 dest = repo.ui.expandpath(dest or 'default')
1022 dest, branches = hg.parseurl(dest)
1029 dest, branches = hg.parseurl(dest)
1023 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1030 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1024 if revs:
1031 if revs:
1025 revs = [repo.lookup(rev) for rev in revs]
1032 revs = [repo.lookup(rev) for rev in revs]
1026 other = hg.peer(repo, {}, dest)
1033 other = hg.peer(repo, {}, dest)
1027 n = other.lookup(q)
1034 n = other.lookup(q)
1028 if n in repo:
1035 if n in repo:
1029 r = repo[n].rev()
1036 r = repo[n].rev()
1030 if r in subset:
1037 if r in subset:
1031 return [r]
1038 return [r]
1032 return []
1039 return []
1033
1040
1034 def removes(repo, subset, x):
1041 def removes(repo, subset, x):
1035 """``removes(pattern)``
1042 """``removes(pattern)``
1036 Changesets which remove files matching pattern.
1043 Changesets which remove files matching pattern.
1037 """
1044 """
1038 # i18n: "removes" is a keyword
1045 # i18n: "removes" is a keyword
1039 pat = getstring(x, _("removes requires a pattern"))
1046 pat = getstring(x, _("removes requires a pattern"))
1040 return checkstatus(repo, subset, pat, 2)
1047 return checkstatus(repo, subset, pat, 2)
1041
1048
1042 def rev(repo, subset, x):
1049 def rev(repo, subset, x):
1043 """``rev(number)``
1050 """``rev(number)``
1044 Revision with the given numeric identifier.
1051 Revision with the given numeric identifier.
1045 """
1052 """
1046 # i18n: "rev" is a keyword
1053 # i18n: "rev" is a keyword
1047 l = getargs(x, 1, 1, _("rev requires one argument"))
1054 l = getargs(x, 1, 1, _("rev requires one argument"))
1048 try:
1055 try:
1049 # i18n: "rev" is a keyword
1056 # i18n: "rev" is a keyword
1050 l = int(getstring(l[0], _("rev requires a number")))
1057 l = int(getstring(l[0], _("rev requires a number")))
1051 except (TypeError, ValueError):
1058 except (TypeError, ValueError):
1052 # i18n: "rev" is a keyword
1059 # i18n: "rev" is a keyword
1053 raise error.ParseError(_("rev expects a number"))
1060 raise error.ParseError(_("rev expects a number"))
1054 return [r for r in subset if r == l]
1061 return [r for r in subset if r == l]
1055
1062
1056 def matching(repo, subset, x):
1063 def matching(repo, subset, x):
1057 """``matching(revision [, field])``
1064 """``matching(revision [, field])``
1058 Changesets in which a given set of fields match the set of fields in the
1065 Changesets in which a given set of fields match the set of fields in the
1059 selected revision or set.
1066 selected revision or set.
1060
1067
1061 To match more than one field pass the list of fields to match separated
1068 To match more than one field pass the list of fields to match separated
1062 by spaces (e.g. ``author description``).
1069 by spaces (e.g. ``author description``).
1063
1070
1064 Valid fields are most regular revision fields and some special fields.
1071 Valid fields are most regular revision fields and some special fields.
1065
1072
1066 Regular revision fields are ``description``, ``author``, ``branch``,
1073 Regular revision fields are ``description``, ``author``, ``branch``,
1067 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1074 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1068 and ``diff``.
1075 and ``diff``.
1069 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1076 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1070 contents of the revision. Two revisions matching their ``diff`` will
1077 contents of the revision. Two revisions matching their ``diff`` will
1071 also match their ``files``.
1078 also match their ``files``.
1072
1079
1073 Special fields are ``summary`` and ``metadata``:
1080 Special fields are ``summary`` and ``metadata``:
1074 ``summary`` matches the first line of the description.
1081 ``summary`` matches the first line of the description.
1075 ``metadata`` is equivalent to matching ``description user date``
1082 ``metadata`` is equivalent to matching ``description user date``
1076 (i.e. it matches the main metadata fields).
1083 (i.e. it matches the main metadata fields).
1077
1084
1078 ``metadata`` is the default field which is used when no fields are
1085 ``metadata`` is the default field which is used when no fields are
1079 specified. You can match more than one field at a time.
1086 specified. You can match more than one field at a time.
1080 """
1087 """
1081 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1088 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1082
1089
1083 revs = getset(repo, xrange(len(repo)), l[0])
1090 revs = getset(repo, xrange(len(repo)), l[0])
1084
1091
1085 fieldlist = ['metadata']
1092 fieldlist = ['metadata']
1086 if len(l) > 1:
1093 if len(l) > 1:
1087 fieldlist = getstring(l[1],
1094 fieldlist = getstring(l[1],
1088 _("matching requires a string "
1095 _("matching requires a string "
1089 "as its second argument")).split()
1096 "as its second argument")).split()
1090
1097
1091 # Make sure that there are no repeated fields,
1098 # Make sure that there are no repeated fields,
1092 # expand the 'special' 'metadata' field type
1099 # expand the 'special' 'metadata' field type
1093 # and check the 'files' whenever we check the 'diff'
1100 # and check the 'files' whenever we check the 'diff'
1094 fields = []
1101 fields = []
1095 for field in fieldlist:
1102 for field in fieldlist:
1096 if field == 'metadata':
1103 if field == 'metadata':
1097 fields += ['user', 'description', 'date']
1104 fields += ['user', 'description', 'date']
1098 elif field == 'diff':
1105 elif field == 'diff':
1099 # a revision matching the diff must also match the files
1106 # a revision matching the diff must also match the files
1100 # since matching the diff is very costly, make sure to
1107 # since matching the diff is very costly, make sure to
1101 # also match the files first
1108 # also match the files first
1102 fields += ['files', 'diff']
1109 fields += ['files', 'diff']
1103 else:
1110 else:
1104 if field == 'author':
1111 if field == 'author':
1105 field = 'user'
1112 field = 'user'
1106 fields.append(field)
1113 fields.append(field)
1107 fields = set(fields)
1114 fields = set(fields)
1108 if 'summary' in fields and 'description' in fields:
1115 if 'summary' in fields and 'description' in fields:
1109 # If a revision matches its description it also matches its summary
1116 # If a revision matches its description it also matches its summary
1110 fields.discard('summary')
1117 fields.discard('summary')
1111
1118
1112 # We may want to match more than one field
1119 # We may want to match more than one field
1113 # Not all fields take the same amount of time to be matched
1120 # Not all fields take the same amount of time to be matched
1114 # Sort the selected fields in order of increasing matching cost
1121 # Sort the selected fields in order of increasing matching cost
1115 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1122 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1116 'files', 'description', 'substate', 'diff']
1123 'files', 'description', 'substate', 'diff']
1117 def fieldkeyfunc(f):
1124 def fieldkeyfunc(f):
1118 try:
1125 try:
1119 return fieldorder.index(f)
1126 return fieldorder.index(f)
1120 except ValueError:
1127 except ValueError:
1121 # assume an unknown field is very costly
1128 # assume an unknown field is very costly
1122 return len(fieldorder)
1129 return len(fieldorder)
1123 fields = list(fields)
1130 fields = list(fields)
1124 fields.sort(key=fieldkeyfunc)
1131 fields.sort(key=fieldkeyfunc)
1125
1132
1126 # Each field will be matched with its own "getfield" function
1133 # Each field will be matched with its own "getfield" function
1127 # which will be added to the getfieldfuncs array of functions
1134 # which will be added to the getfieldfuncs array of functions
1128 getfieldfuncs = []
1135 getfieldfuncs = []
1129 _funcs = {
1136 _funcs = {
1130 'user': lambda r: repo[r].user(),
1137 'user': lambda r: repo[r].user(),
1131 'branch': lambda r: repo[r].branch(),
1138 'branch': lambda r: repo[r].branch(),
1132 'date': lambda r: repo[r].date(),
1139 'date': lambda r: repo[r].date(),
1133 'description': lambda r: repo[r].description(),
1140 'description': lambda r: repo[r].description(),
1134 'files': lambda r: repo[r].files(),
1141 'files': lambda r: repo[r].files(),
1135 'parents': lambda r: repo[r].parents(),
1142 'parents': lambda r: repo[r].parents(),
1136 'phase': lambda r: repo[r].phase(),
1143 'phase': lambda r: repo[r].phase(),
1137 'substate': lambda r: repo[r].substate,
1144 'substate': lambda r: repo[r].substate,
1138 'summary': lambda r: repo[r].description().splitlines()[0],
1145 'summary': lambda r: repo[r].description().splitlines()[0],
1139 'diff': lambda r: list(repo[r].diff(git=True),)
1146 'diff': lambda r: list(repo[r].diff(git=True),)
1140 }
1147 }
1141 for info in fields:
1148 for info in fields:
1142 getfield = _funcs.get(info, None)
1149 getfield = _funcs.get(info, None)
1143 if getfield is None:
1150 if getfield is None:
1144 raise error.ParseError(
1151 raise error.ParseError(
1145 _("unexpected field name passed to matching: %s") % info)
1152 _("unexpected field name passed to matching: %s") % info)
1146 getfieldfuncs.append(getfield)
1153 getfieldfuncs.append(getfield)
1147 # convert the getfield array of functions into a "getinfo" function
1154 # convert the getfield array of functions into a "getinfo" function
1148 # which returns an array of field values (or a single value if there
1155 # which returns an array of field values (or a single value if there
1149 # is only one field to match)
1156 # is only one field to match)
1150 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1157 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1151
1158
1152 matches = set()
1159 matches = set()
1153 for rev in revs:
1160 for rev in revs:
1154 target = getinfo(rev)
1161 target = getinfo(rev)
1155 for r in subset:
1162 for r in subset:
1156 match = True
1163 match = True
1157 for n, f in enumerate(getfieldfuncs):
1164 for n, f in enumerate(getfieldfuncs):
1158 if target[n] != f(r):
1165 if target[n] != f(r):
1159 match = False
1166 match = False
1160 break
1167 break
1161 if match:
1168 if match:
1162 matches.add(r)
1169 matches.add(r)
1163 return [r for r in subset if r in matches]
1170 return [r for r in subset if r in matches]
1164
1171
1165 def reverse(repo, subset, x):
1172 def reverse(repo, subset, x):
1166 """``reverse(set)``
1173 """``reverse(set)``
1167 Reverse order of set.
1174 Reverse order of set.
1168 """
1175 """
1169 l = getset(repo, subset, x)
1176 l = getset(repo, subset, x)
1170 if not isinstance(l, list):
1177 if not isinstance(l, list):
1171 l = list(l)
1178 l = list(l)
1172 l.reverse()
1179 l.reverse()
1173 return l
1180 return l
1174
1181
1175 def roots(repo, subset, x):
1182 def roots(repo, subset, x):
1176 """``roots(set)``
1183 """``roots(set)``
1177 Changesets in set with no parent changeset in set.
1184 Changesets in set with no parent changeset in set.
1178 """
1185 """
1179 s = set(getset(repo, xrange(len(repo)), x))
1186 s = set(getset(repo, xrange(len(repo)), x))
1180 subset = [r for r in subset if r in s]
1187 subset = [r for r in subset if r in s]
1181 cs = _children(repo, subset, s)
1188 cs = _children(repo, subset, s)
1182 return [r for r in subset if r not in cs]
1189 return [r for r in subset if r not in cs]
1183
1190
1184 def secret(repo, subset, x):
1191 def secret(repo, subset, x):
1185 """``secret()``
1192 """``secret()``
1186 Changeset in secret phase."""
1193 Changeset in secret phase."""
1187 getargs(x, 0, 0, _("secret takes no arguments"))
1194 getargs(x, 0, 0, _("secret takes no arguments"))
1188 pc = repo._phasecache
1195 pc = repo._phasecache
1189 return [r for r in subset if pc.phase(repo, r) == phases.secret]
1196 return [r for r in subset if pc.phase(repo, r) == phases.secret]
1190
1197
1191 def sort(repo, subset, x):
1198 def sort(repo, subset, x):
1192 """``sort(set[, [-]key...])``
1199 """``sort(set[, [-]key...])``
1193 Sort set by keys. The default sort order is ascending, specify a key
1200 Sort set by keys. The default sort order is ascending, specify a key
1194 as ``-key`` to sort in descending order.
1201 as ``-key`` to sort in descending order.
1195
1202
1196 The keys can be:
1203 The keys can be:
1197
1204
1198 - ``rev`` for the revision number,
1205 - ``rev`` for the revision number,
1199 - ``branch`` for the branch name,
1206 - ``branch`` for the branch name,
1200 - ``desc`` for the commit message (description),
1207 - ``desc`` for the commit message (description),
1201 - ``user`` for user name (``author`` can be used as an alias),
1208 - ``user`` for user name (``author`` can be used as an alias),
1202 - ``date`` for the commit date
1209 - ``date`` for the commit date
1203 """
1210 """
1204 # i18n: "sort" is a keyword
1211 # i18n: "sort" is a keyword
1205 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1212 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1206 keys = "rev"
1213 keys = "rev"
1207 if len(l) == 2:
1214 if len(l) == 2:
1208 keys = getstring(l[1], _("sort spec must be a string"))
1215 keys = getstring(l[1], _("sort spec must be a string"))
1209
1216
1210 s = l[0]
1217 s = l[0]
1211 keys = keys.split()
1218 keys = keys.split()
1212 l = []
1219 l = []
1213 def invert(s):
1220 def invert(s):
1214 return "".join(chr(255 - ord(c)) for c in s)
1221 return "".join(chr(255 - ord(c)) for c in s)
1215 for r in getset(repo, subset, s):
1222 for r in getset(repo, subset, s):
1216 c = repo[r]
1223 c = repo[r]
1217 e = []
1224 e = []
1218 for k in keys:
1225 for k in keys:
1219 if k == 'rev':
1226 if k == 'rev':
1220 e.append(r)
1227 e.append(r)
1221 elif k == '-rev':
1228 elif k == '-rev':
1222 e.append(-r)
1229 e.append(-r)
1223 elif k == 'branch':
1230 elif k == 'branch':
1224 e.append(c.branch())
1231 e.append(c.branch())
1225 elif k == '-branch':
1232 elif k == '-branch':
1226 e.append(invert(c.branch()))
1233 e.append(invert(c.branch()))
1227 elif k == 'desc':
1234 elif k == 'desc':
1228 e.append(c.description())
1235 e.append(c.description())
1229 elif k == '-desc':
1236 elif k == '-desc':
1230 e.append(invert(c.description()))
1237 e.append(invert(c.description()))
1231 elif k in 'user author':
1238 elif k in 'user author':
1232 e.append(c.user())
1239 e.append(c.user())
1233 elif k in '-user -author':
1240 elif k in '-user -author':
1234 e.append(invert(c.user()))
1241 e.append(invert(c.user()))
1235 elif k == 'date':
1242 elif k == 'date':
1236 e.append(c.date()[0])
1243 e.append(c.date()[0])
1237 elif k == '-date':
1244 elif k == '-date':
1238 e.append(-c.date()[0])
1245 e.append(-c.date()[0])
1239 else:
1246 else:
1240 raise error.ParseError(_("unknown sort key %r") % k)
1247 raise error.ParseError(_("unknown sort key %r") % k)
1241 e.append(r)
1248 e.append(r)
1242 l.append(e)
1249 l.append(e)
1243 l.sort()
1250 l.sort()
1244 return [e[-1] for e in l]
1251 return [e[-1] for e in l]
1245
1252
1246 def _stringmatcher(pattern):
1253 def _stringmatcher(pattern):
1247 """
1254 """
1248 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1255 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1249 returns the matcher name, pattern, and matcher function.
1256 returns the matcher name, pattern, and matcher function.
1250 missing or unknown prefixes are treated as literal matches.
1257 missing or unknown prefixes are treated as literal matches.
1251
1258
1252 helper for tests:
1259 helper for tests:
1253 >>> def test(pattern, *tests):
1260 >>> def test(pattern, *tests):
1254 ... kind, pattern, matcher = _stringmatcher(pattern)
1261 ... kind, pattern, matcher = _stringmatcher(pattern)
1255 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1262 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1256
1263
1257 exact matching (no prefix):
1264 exact matching (no prefix):
1258 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1265 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1259 ('literal', 'abcdefg', [False, False, True])
1266 ('literal', 'abcdefg', [False, False, True])
1260
1267
1261 regex matching ('re:' prefix)
1268 regex matching ('re:' prefix)
1262 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1269 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1263 ('re', 'a.+b', [False, False, True])
1270 ('re', 'a.+b', [False, False, True])
1264
1271
1265 force exact matches ('literal:' prefix)
1272 force exact matches ('literal:' prefix)
1266 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1273 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1267 ('literal', 're:foobar', [False, True])
1274 ('literal', 're:foobar', [False, True])
1268
1275
1269 unknown prefixes are ignored and treated as literals
1276 unknown prefixes are ignored and treated as literals
1270 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1277 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1271 ('literal', 'foo:bar', [False, False, True])
1278 ('literal', 'foo:bar', [False, False, True])
1272 """
1279 """
1273 if pattern.startswith('re:'):
1280 if pattern.startswith('re:'):
1274 pattern = pattern[3:]
1281 pattern = pattern[3:]
1275 try:
1282 try:
1276 regex = re.compile(pattern)
1283 regex = re.compile(pattern)
1277 except re.error, e:
1284 except re.error, e:
1278 raise error.ParseError(_('invalid regular expression: %s')
1285 raise error.ParseError(_('invalid regular expression: %s')
1279 % e)
1286 % e)
1280 return 're', pattern, regex.search
1287 return 're', pattern, regex.search
1281 elif pattern.startswith('literal:'):
1288 elif pattern.startswith('literal:'):
1282 pattern = pattern[8:]
1289 pattern = pattern[8:]
1283 return 'literal', pattern, pattern.__eq__
1290 return 'literal', pattern, pattern.__eq__
1284
1291
1285 def _substringmatcher(pattern):
1292 def _substringmatcher(pattern):
1286 kind, pattern, matcher = _stringmatcher(pattern)
1293 kind, pattern, matcher = _stringmatcher(pattern)
1287 if kind == 'literal':
1294 if kind == 'literal':
1288 matcher = lambda s: pattern in s
1295 matcher = lambda s: pattern in s
1289 return kind, pattern, matcher
1296 return kind, pattern, matcher
1290
1297
1291 def tag(repo, subset, x):
1298 def tag(repo, subset, x):
1292 """``tag([name])``
1299 """``tag([name])``
1293 The specified tag by name, or all tagged revisions if no name is given.
1300 The specified tag by name, or all tagged revisions if no name is given.
1294 """
1301 """
1295 # i18n: "tag" is a keyword
1302 # i18n: "tag" is a keyword
1296 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1303 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1297 cl = repo.changelog
1304 cl = repo.changelog
1298 if args:
1305 if args:
1299 pattern = getstring(args[0],
1306 pattern = getstring(args[0],
1300 # i18n: "tag" is a keyword
1307 # i18n: "tag" is a keyword
1301 _('the argument to tag must be a string'))
1308 _('the argument to tag must be a string'))
1302 kind, pattern, matcher = _stringmatcher(pattern)
1309 kind, pattern, matcher = _stringmatcher(pattern)
1303 if kind == 'literal':
1310 if kind == 'literal':
1304 # avoid resolving all tags
1311 # avoid resolving all tags
1305 tn = repo._tagscache.tags.get(pattern, None)
1312 tn = repo._tagscache.tags.get(pattern, None)
1306 if tn is None:
1313 if tn is None:
1307 raise util.Abort(_("tag '%s' does not exist") % pattern)
1314 raise util.Abort(_("tag '%s' does not exist") % pattern)
1308 s = set([repo[tn].rev()])
1315 s = set([repo[tn].rev()])
1309 else:
1316 else:
1310 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
1317 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
1311 if not s:
1318 if not s:
1312 raise util.Abort(_("no tags exist that match '%s'") % pattern)
1319 raise util.Abort(_("no tags exist that match '%s'") % pattern)
1313 else:
1320 else:
1314 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1321 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1315 return [r for r in subset if r in s]
1322 return [r for r in subset if r in s]
1316
1323
1317 def tagged(repo, subset, x):
1324 def tagged(repo, subset, x):
1318 return tag(repo, subset, x)
1325 return tag(repo, subset, x)
1319
1326
1320 def unstable(repo, subset, x):
1327 def unstable(repo, subset, x):
1321 """``unstable()``
1328 """``unstable()``
1322 Unstable changesets are non-obsolete with obsolete descendants."""
1329 Unstable changesets are non-obsolete with obsolete descendants."""
1323 getargs(x, 0, 0, _("obsolete takes no arguments"))
1330 getargs(x, 0, 0, _("obsolete takes no arguments"))
1324 unstableset = set(repo.revs('(obsolete()::) - obsolete()'))
1331 unstableset = set(repo.revs('(obsolete()::) - obsolete()'))
1325 return [r for r in subset if r in unstableset]
1332 return [r for r in subset if r in unstableset]
1326
1333
1327
1334
1328 def user(repo, subset, x):
1335 def user(repo, subset, x):
1329 """``user(string)``
1336 """``user(string)``
1330 User name contains string. The match is case-insensitive.
1337 User name contains string. The match is case-insensitive.
1331
1338
1332 If `string` starts with `re:`, the remainder of the string is treated as
1339 If `string` starts with `re:`, the remainder of the string is treated as
1333 a regular expression. To match a user that actually contains `re:`, use
1340 a regular expression. To match a user that actually contains `re:`, use
1334 the prefix `literal:`.
1341 the prefix `literal:`.
1335 """
1342 """
1336 return author(repo, subset, x)
1343 return author(repo, subset, x)
1337
1344
1338 # for internal use
1345 # for internal use
1339 def _list(repo, subset, x):
1346 def _list(repo, subset, x):
1340 s = getstring(x, "internal error")
1347 s = getstring(x, "internal error")
1341 if not s:
1348 if not s:
1342 return []
1349 return []
1343 if not isinstance(subset, set):
1350 if not isinstance(subset, set):
1344 subset = set(subset)
1351 subset = set(subset)
1345 ls = [repo[r].rev() for r in s.split('\0')]
1352 ls = [repo[r].rev() for r in s.split('\0')]
1346 return [r for r in ls if r in subset]
1353 return [r for r in ls if r in subset]
1347
1354
1348 symbols = {
1355 symbols = {
1349 "adds": adds,
1356 "adds": adds,
1350 "all": getall,
1357 "all": getall,
1351 "ancestor": ancestor,
1358 "ancestor": ancestor,
1352 "ancestors": ancestors,
1359 "ancestors": ancestors,
1353 "_firstancestors": _firstancestors,
1360 "_firstancestors": _firstancestors,
1354 "author": author,
1361 "author": author,
1355 "bisect": bisect,
1362 "bisect": bisect,
1356 "bisected": bisected,
1363 "bisected": bisected,
1357 "bookmark": bookmark,
1364 "bookmark": bookmark,
1358 "branch": branch,
1365 "branch": branch,
1359 "children": children,
1366 "children": children,
1360 "closed": closed,
1367 "closed": closed,
1361 "contains": contains,
1368 "contains": contains,
1362 "converted": converted,
1369 "converted": converted,
1363 "date": date,
1370 "date": date,
1364 "desc": desc,
1371 "desc": desc,
1365 "descendants": descendants,
1372 "descendants": descendants,
1366 "_firstdescendants": _firstdescendants,
1373 "_firstdescendants": _firstdescendants,
1367 "draft": draft,
1374 "draft": draft,
1375 "extinct": extinct,
1368 "extra": extra,
1376 "extra": extra,
1369 "file": hasfile,
1377 "file": hasfile,
1370 "filelog": filelog,
1378 "filelog": filelog,
1371 "first": first,
1379 "first": first,
1372 "follow": follow,
1380 "follow": follow,
1373 "_followfirst": _followfirst,
1381 "_followfirst": _followfirst,
1374 "grep": grep,
1382 "grep": grep,
1375 "head": head,
1383 "head": head,
1376 "heads": heads,
1384 "heads": heads,
1377 "id": node_,
1385 "id": node_,
1378 "keyword": keyword,
1386 "keyword": keyword,
1379 "last": last,
1387 "last": last,
1380 "limit": limit,
1388 "limit": limit,
1381 "_matchfiles": _matchfiles,
1389 "_matchfiles": _matchfiles,
1382 "max": maxrev,
1390 "max": maxrev,
1383 "merge": merge,
1391 "merge": merge,
1384 "min": minrev,
1392 "min": minrev,
1385 "modifies": modifies,
1393 "modifies": modifies,
1386 "obsolete": obsolete,
1394 "obsolete": obsolete,
1387 "outgoing": outgoing,
1395 "outgoing": outgoing,
1388 "p1": p1,
1396 "p1": p1,
1389 "p2": p2,
1397 "p2": p2,
1390 "parents": parents,
1398 "parents": parents,
1391 "present": present,
1399 "present": present,
1392 "public": public,
1400 "public": public,
1393 "remote": remote,
1401 "remote": remote,
1394 "removes": removes,
1402 "removes": removes,
1395 "rev": rev,
1403 "rev": rev,
1396 "reverse": reverse,
1404 "reverse": reverse,
1397 "roots": roots,
1405 "roots": roots,
1398 "sort": sort,
1406 "sort": sort,
1399 "secret": secret,
1407 "secret": secret,
1400 "matching": matching,
1408 "matching": matching,
1401 "tag": tag,
1409 "tag": tag,
1402 "tagged": tagged,
1410 "tagged": tagged,
1403 "user": user,
1411 "user": user,
1404 "unstable": unstable,
1412 "unstable": unstable,
1405 "_list": _list,
1413 "_list": _list,
1406 }
1414 }
1407
1415
1408 methods = {
1416 methods = {
1409 "range": rangeset,
1417 "range": rangeset,
1410 "dagrange": dagrange,
1418 "dagrange": dagrange,
1411 "string": stringset,
1419 "string": stringset,
1412 "symbol": symbolset,
1420 "symbol": symbolset,
1413 "and": andset,
1421 "and": andset,
1414 "or": orset,
1422 "or": orset,
1415 "not": notset,
1423 "not": notset,
1416 "list": listset,
1424 "list": listset,
1417 "func": func,
1425 "func": func,
1418 "ancestor": ancestorspec,
1426 "ancestor": ancestorspec,
1419 "parent": parentspec,
1427 "parent": parentspec,
1420 "parentpost": p1,
1428 "parentpost": p1,
1421 }
1429 }
1422
1430
1423 def optimize(x, small):
1431 def optimize(x, small):
1424 if x is None:
1432 if x is None:
1425 return 0, x
1433 return 0, x
1426
1434
1427 smallbonus = 1
1435 smallbonus = 1
1428 if small:
1436 if small:
1429 smallbonus = .5
1437 smallbonus = .5
1430
1438
1431 op = x[0]
1439 op = x[0]
1432 if op == 'minus':
1440 if op == 'minus':
1433 return optimize(('and', x[1], ('not', x[2])), small)
1441 return optimize(('and', x[1], ('not', x[2])), small)
1434 elif op == 'dagrangepre':
1442 elif op == 'dagrangepre':
1435 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
1443 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
1436 elif op == 'dagrangepost':
1444 elif op == 'dagrangepost':
1437 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1445 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1438 elif op == 'rangepre':
1446 elif op == 'rangepre':
1439 return optimize(('range', ('string', '0'), x[1]), small)
1447 return optimize(('range', ('string', '0'), x[1]), small)
1440 elif op == 'rangepost':
1448 elif op == 'rangepost':
1441 return optimize(('range', x[1], ('string', 'tip')), small)
1449 return optimize(('range', x[1], ('string', 'tip')), small)
1442 elif op == 'negate':
1450 elif op == 'negate':
1443 return optimize(('string',
1451 return optimize(('string',
1444 '-' + getstring(x[1], _("can't negate that"))), small)
1452 '-' + getstring(x[1], _("can't negate that"))), small)
1445 elif op in 'string symbol negate':
1453 elif op in 'string symbol negate':
1446 return smallbonus, x # single revisions are small
1454 return smallbonus, x # single revisions are small
1447 elif op == 'and':
1455 elif op == 'and':
1448 wa, ta = optimize(x[1], True)
1456 wa, ta = optimize(x[1], True)
1449 wb, tb = optimize(x[2], True)
1457 wb, tb = optimize(x[2], True)
1450 w = min(wa, wb)
1458 w = min(wa, wb)
1451 if wa > wb:
1459 if wa > wb:
1452 return w, (op, tb, ta)
1460 return w, (op, tb, ta)
1453 return w, (op, ta, tb)
1461 return w, (op, ta, tb)
1454 elif op == 'or':
1462 elif op == 'or':
1455 wa, ta = optimize(x[1], False)
1463 wa, ta = optimize(x[1], False)
1456 wb, tb = optimize(x[2], False)
1464 wb, tb = optimize(x[2], False)
1457 if wb < wa:
1465 if wb < wa:
1458 wb, wa = wa, wb
1466 wb, wa = wa, wb
1459 return max(wa, wb), (op, ta, tb)
1467 return max(wa, wb), (op, ta, tb)
1460 elif op == 'not':
1468 elif op == 'not':
1461 o = optimize(x[1], not small)
1469 o = optimize(x[1], not small)
1462 return o[0], (op, o[1])
1470 return o[0], (op, o[1])
1463 elif op == 'parentpost':
1471 elif op == 'parentpost':
1464 o = optimize(x[1], small)
1472 o = optimize(x[1], small)
1465 return o[0], (op, o[1])
1473 return o[0], (op, o[1])
1466 elif op == 'group':
1474 elif op == 'group':
1467 return optimize(x[1], small)
1475 return optimize(x[1], small)
1468 elif op in 'dagrange range list parent ancestorspec':
1476 elif op in 'dagrange range list parent ancestorspec':
1469 if op == 'parent':
1477 if op == 'parent':
1470 # x^:y means (x^) : y, not x ^ (:y)
1478 # x^:y means (x^) : y, not x ^ (:y)
1471 post = ('parentpost', x[1])
1479 post = ('parentpost', x[1])
1472 if x[2][0] == 'dagrangepre':
1480 if x[2][0] == 'dagrangepre':
1473 return optimize(('dagrange', post, x[2][1]), small)
1481 return optimize(('dagrange', post, x[2][1]), small)
1474 elif x[2][0] == 'rangepre':
1482 elif x[2][0] == 'rangepre':
1475 return optimize(('range', post, x[2][1]), small)
1483 return optimize(('range', post, x[2][1]), small)
1476
1484
1477 wa, ta = optimize(x[1], small)
1485 wa, ta = optimize(x[1], small)
1478 wb, tb = optimize(x[2], small)
1486 wb, tb = optimize(x[2], small)
1479 return wa + wb, (op, ta, tb)
1487 return wa + wb, (op, ta, tb)
1480 elif op == 'func':
1488 elif op == 'func':
1481 f = getstring(x[1], _("not a symbol"))
1489 f = getstring(x[1], _("not a symbol"))
1482 wa, ta = optimize(x[2], small)
1490 wa, ta = optimize(x[2], small)
1483 if f in ("author branch closed date desc file grep keyword "
1491 if f in ("author branch closed date desc file grep keyword "
1484 "outgoing user"):
1492 "outgoing user"):
1485 w = 10 # slow
1493 w = 10 # slow
1486 elif f in "modifies adds removes":
1494 elif f in "modifies adds removes":
1487 w = 30 # slower
1495 w = 30 # slower
1488 elif f == "contains":
1496 elif f == "contains":
1489 w = 100 # very slow
1497 w = 100 # very slow
1490 elif f == "ancestor":
1498 elif f == "ancestor":
1491 w = 1 * smallbonus
1499 w = 1 * smallbonus
1492 elif f in "reverse limit first":
1500 elif f in "reverse limit first":
1493 w = 0
1501 w = 0
1494 elif f in "sort":
1502 elif f in "sort":
1495 w = 10 # assume most sorts look at changelog
1503 w = 10 # assume most sorts look at changelog
1496 else:
1504 else:
1497 w = 1
1505 w = 1
1498 return w + wa, (op, x[1], ta)
1506 return w + wa, (op, x[1], ta)
1499 return 1, x
1507 return 1, x
1500
1508
1501 _aliasarg = ('func', ('symbol', '_aliasarg'))
1509 _aliasarg = ('func', ('symbol', '_aliasarg'))
1502 def _getaliasarg(tree):
1510 def _getaliasarg(tree):
1503 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
1511 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
1504 return X, None otherwise.
1512 return X, None otherwise.
1505 """
1513 """
1506 if (len(tree) == 3 and tree[:2] == _aliasarg
1514 if (len(tree) == 3 and tree[:2] == _aliasarg
1507 and tree[2][0] == 'string'):
1515 and tree[2][0] == 'string'):
1508 return tree[2][1]
1516 return tree[2][1]
1509 return None
1517 return None
1510
1518
1511 def _checkaliasarg(tree, known=None):
1519 def _checkaliasarg(tree, known=None):
1512 """Check tree contains no _aliasarg construct or only ones which
1520 """Check tree contains no _aliasarg construct or only ones which
1513 value is in known. Used to avoid alias placeholders injection.
1521 value is in known. Used to avoid alias placeholders injection.
1514 """
1522 """
1515 if isinstance(tree, tuple):
1523 if isinstance(tree, tuple):
1516 arg = _getaliasarg(tree)
1524 arg = _getaliasarg(tree)
1517 if arg is not None and (not known or arg not in known):
1525 if arg is not None and (not known or arg not in known):
1518 raise error.ParseError(_("not a function: %s") % '_aliasarg')
1526 raise error.ParseError(_("not a function: %s") % '_aliasarg')
1519 for t in tree:
1527 for t in tree:
1520 _checkaliasarg(t, known)
1528 _checkaliasarg(t, known)
1521
1529
1522 class revsetalias(object):
1530 class revsetalias(object):
1523 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1531 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1524 args = None
1532 args = None
1525
1533
1526 def __init__(self, name, value):
1534 def __init__(self, name, value):
1527 '''Aliases like:
1535 '''Aliases like:
1528
1536
1529 h = heads(default)
1537 h = heads(default)
1530 b($1) = ancestors($1) - ancestors(default)
1538 b($1) = ancestors($1) - ancestors(default)
1531 '''
1539 '''
1532 m = self.funcre.search(name)
1540 m = self.funcre.search(name)
1533 if m:
1541 if m:
1534 self.name = m.group(1)
1542 self.name = m.group(1)
1535 self.tree = ('func', ('symbol', m.group(1)))
1543 self.tree = ('func', ('symbol', m.group(1)))
1536 self.args = [x.strip() for x in m.group(2).split(',')]
1544 self.args = [x.strip() for x in m.group(2).split(',')]
1537 for arg in self.args:
1545 for arg in self.args:
1538 # _aliasarg() is an unknown symbol only used separate
1546 # _aliasarg() is an unknown symbol only used separate
1539 # alias argument placeholders from regular strings.
1547 # alias argument placeholders from regular strings.
1540 value = value.replace(arg, '_aliasarg(%r)' % (arg,))
1548 value = value.replace(arg, '_aliasarg(%r)' % (arg,))
1541 else:
1549 else:
1542 self.name = name
1550 self.name = name
1543 self.tree = ('symbol', name)
1551 self.tree = ('symbol', name)
1544
1552
1545 self.replacement, pos = parse(value)
1553 self.replacement, pos = parse(value)
1546 if pos != len(value):
1554 if pos != len(value):
1547 raise error.ParseError(_('invalid token'), pos)
1555 raise error.ParseError(_('invalid token'), pos)
1548 # Check for placeholder injection
1556 # Check for placeholder injection
1549 _checkaliasarg(self.replacement, self.args)
1557 _checkaliasarg(self.replacement, self.args)
1550
1558
1551 def _getalias(aliases, tree):
1559 def _getalias(aliases, tree):
1552 """If tree looks like an unexpanded alias, return it. Return None
1560 """If tree looks like an unexpanded alias, return it. Return None
1553 otherwise.
1561 otherwise.
1554 """
1562 """
1555 if isinstance(tree, tuple) and tree:
1563 if isinstance(tree, tuple) and tree:
1556 if tree[0] == 'symbol' and len(tree) == 2:
1564 if tree[0] == 'symbol' and len(tree) == 2:
1557 name = tree[1]
1565 name = tree[1]
1558 alias = aliases.get(name)
1566 alias = aliases.get(name)
1559 if alias and alias.args is None and alias.tree == tree:
1567 if alias and alias.args is None and alias.tree == tree:
1560 return alias
1568 return alias
1561 if tree[0] == 'func' and len(tree) > 1:
1569 if tree[0] == 'func' and len(tree) > 1:
1562 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
1570 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
1563 name = tree[1][1]
1571 name = tree[1][1]
1564 alias = aliases.get(name)
1572 alias = aliases.get(name)
1565 if alias and alias.args is not None and alias.tree == tree[:2]:
1573 if alias and alias.args is not None and alias.tree == tree[:2]:
1566 return alias
1574 return alias
1567 return None
1575 return None
1568
1576
1569 def _expandargs(tree, args):
1577 def _expandargs(tree, args):
1570 """Replace _aliasarg instances with the substitution value of the
1578 """Replace _aliasarg instances with the substitution value of the
1571 same name in args, recursively.
1579 same name in args, recursively.
1572 """
1580 """
1573 if not tree or not isinstance(tree, tuple):
1581 if not tree or not isinstance(tree, tuple):
1574 return tree
1582 return tree
1575 arg = _getaliasarg(tree)
1583 arg = _getaliasarg(tree)
1576 if arg is not None:
1584 if arg is not None:
1577 return args[arg]
1585 return args[arg]
1578 return tuple(_expandargs(t, args) for t in tree)
1586 return tuple(_expandargs(t, args) for t in tree)
1579
1587
1580 def _expandaliases(aliases, tree, expanding, cache):
1588 def _expandaliases(aliases, tree, expanding, cache):
1581 """Expand aliases in tree, recursively.
1589 """Expand aliases in tree, recursively.
1582
1590
1583 'aliases' is a dictionary mapping user defined aliases to
1591 'aliases' is a dictionary mapping user defined aliases to
1584 revsetalias objects.
1592 revsetalias objects.
1585 """
1593 """
1586 if not isinstance(tree, tuple):
1594 if not isinstance(tree, tuple):
1587 # Do not expand raw strings
1595 # Do not expand raw strings
1588 return tree
1596 return tree
1589 alias = _getalias(aliases, tree)
1597 alias = _getalias(aliases, tree)
1590 if alias is not None:
1598 if alias is not None:
1591 if alias in expanding:
1599 if alias in expanding:
1592 raise error.ParseError(_('infinite expansion of revset alias "%s" '
1600 raise error.ParseError(_('infinite expansion of revset alias "%s" '
1593 'detected') % alias.name)
1601 'detected') % alias.name)
1594 expanding.append(alias)
1602 expanding.append(alias)
1595 if alias.name not in cache:
1603 if alias.name not in cache:
1596 cache[alias.name] = _expandaliases(aliases, alias.replacement,
1604 cache[alias.name] = _expandaliases(aliases, alias.replacement,
1597 expanding, cache)
1605 expanding, cache)
1598 result = cache[alias.name]
1606 result = cache[alias.name]
1599 expanding.pop()
1607 expanding.pop()
1600 if alias.args is not None:
1608 if alias.args is not None:
1601 l = getlist(tree[2])
1609 l = getlist(tree[2])
1602 if len(l) != len(alias.args):
1610 if len(l) != len(alias.args):
1603 raise error.ParseError(
1611 raise error.ParseError(
1604 _('invalid number of arguments: %s') % len(l))
1612 _('invalid number of arguments: %s') % len(l))
1605 l = [_expandaliases(aliases, a, [], cache) for a in l]
1613 l = [_expandaliases(aliases, a, [], cache) for a in l]
1606 result = _expandargs(result, dict(zip(alias.args, l)))
1614 result = _expandargs(result, dict(zip(alias.args, l)))
1607 else:
1615 else:
1608 result = tuple(_expandaliases(aliases, t, expanding, cache)
1616 result = tuple(_expandaliases(aliases, t, expanding, cache)
1609 for t in tree)
1617 for t in tree)
1610 return result
1618 return result
1611
1619
1612 def findaliases(ui, tree):
1620 def findaliases(ui, tree):
1613 _checkaliasarg(tree)
1621 _checkaliasarg(tree)
1614 aliases = {}
1622 aliases = {}
1615 for k, v in ui.configitems('revsetalias'):
1623 for k, v in ui.configitems('revsetalias'):
1616 alias = revsetalias(k, v)
1624 alias = revsetalias(k, v)
1617 aliases[alias.name] = alias
1625 aliases[alias.name] = alias
1618 return _expandaliases(aliases, tree, [], {})
1626 return _expandaliases(aliases, tree, [], {})
1619
1627
1620 parse = parser.parser(tokenize, elements).parse
1628 parse = parser.parser(tokenize, elements).parse
1621
1629
1622 def match(ui, spec):
1630 def match(ui, spec):
1623 if not spec:
1631 if not spec:
1624 raise error.ParseError(_("empty query"))
1632 raise error.ParseError(_("empty query"))
1625 tree, pos = parse(spec)
1633 tree, pos = parse(spec)
1626 if (pos != len(spec)):
1634 if (pos != len(spec)):
1627 raise error.ParseError(_("invalid token"), pos)
1635 raise error.ParseError(_("invalid token"), pos)
1628 if ui:
1636 if ui:
1629 tree = findaliases(ui, tree)
1637 tree = findaliases(ui, tree)
1630 weight, tree = optimize(tree, True)
1638 weight, tree = optimize(tree, True)
1631 def mfunc(repo, subset):
1639 def mfunc(repo, subset):
1632 return getset(repo, subset, tree)
1640 return getset(repo, subset, tree)
1633 return mfunc
1641 return mfunc
1634
1642
1635 def formatspec(expr, *args):
1643 def formatspec(expr, *args):
1636 '''
1644 '''
1637 This is a convenience function for using revsets internally, and
1645 This is a convenience function for using revsets internally, and
1638 escapes arguments appropriately. Aliases are intentionally ignored
1646 escapes arguments appropriately. Aliases are intentionally ignored
1639 so that intended expression behavior isn't accidentally subverted.
1647 so that intended expression behavior isn't accidentally subverted.
1640
1648
1641 Supported arguments:
1649 Supported arguments:
1642
1650
1643 %r = revset expression, parenthesized
1651 %r = revset expression, parenthesized
1644 %d = int(arg), no quoting
1652 %d = int(arg), no quoting
1645 %s = string(arg), escaped and single-quoted
1653 %s = string(arg), escaped and single-quoted
1646 %b = arg.branch(), escaped and single-quoted
1654 %b = arg.branch(), escaped and single-quoted
1647 %n = hex(arg), single-quoted
1655 %n = hex(arg), single-quoted
1648 %% = a literal '%'
1656 %% = a literal '%'
1649
1657
1650 Prefixing the type with 'l' specifies a parenthesized list of that type.
1658 Prefixing the type with 'l' specifies a parenthesized list of that type.
1651
1659
1652 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1660 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1653 '(10 or 11):: and ((this()) or (that()))'
1661 '(10 or 11):: and ((this()) or (that()))'
1654 >>> formatspec('%d:: and not %d::', 10, 20)
1662 >>> formatspec('%d:: and not %d::', 10, 20)
1655 '10:: and not 20::'
1663 '10:: and not 20::'
1656 >>> formatspec('%ld or %ld', [], [1])
1664 >>> formatspec('%ld or %ld', [], [1])
1657 "_list('') or 1"
1665 "_list('') or 1"
1658 >>> formatspec('keyword(%s)', 'foo\\xe9')
1666 >>> formatspec('keyword(%s)', 'foo\\xe9')
1659 "keyword('foo\\\\xe9')"
1667 "keyword('foo\\\\xe9')"
1660 >>> b = lambda: 'default'
1668 >>> b = lambda: 'default'
1661 >>> b.branch = b
1669 >>> b.branch = b
1662 >>> formatspec('branch(%b)', b)
1670 >>> formatspec('branch(%b)', b)
1663 "branch('default')"
1671 "branch('default')"
1664 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1672 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1665 "root(_list('a\\x00b\\x00c\\x00d'))"
1673 "root(_list('a\\x00b\\x00c\\x00d'))"
1666 '''
1674 '''
1667
1675
1668 def quote(s):
1676 def quote(s):
1669 return repr(str(s))
1677 return repr(str(s))
1670
1678
1671 def argtype(c, arg):
1679 def argtype(c, arg):
1672 if c == 'd':
1680 if c == 'd':
1673 return str(int(arg))
1681 return str(int(arg))
1674 elif c == 's':
1682 elif c == 's':
1675 return quote(arg)
1683 return quote(arg)
1676 elif c == 'r':
1684 elif c == 'r':
1677 parse(arg) # make sure syntax errors are confined
1685 parse(arg) # make sure syntax errors are confined
1678 return '(%s)' % arg
1686 return '(%s)' % arg
1679 elif c == 'n':
1687 elif c == 'n':
1680 return quote(node.hex(arg))
1688 return quote(node.hex(arg))
1681 elif c == 'b':
1689 elif c == 'b':
1682 return quote(arg.branch())
1690 return quote(arg.branch())
1683
1691
1684 def listexp(s, t):
1692 def listexp(s, t):
1685 l = len(s)
1693 l = len(s)
1686 if l == 0:
1694 if l == 0:
1687 return "_list('')"
1695 return "_list('')"
1688 elif l == 1:
1696 elif l == 1:
1689 return argtype(t, s[0])
1697 return argtype(t, s[0])
1690 elif t == 'd':
1698 elif t == 'd':
1691 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1699 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1692 elif t == 's':
1700 elif t == 's':
1693 return "_list('%s')" % "\0".join(s)
1701 return "_list('%s')" % "\0".join(s)
1694 elif t == 'n':
1702 elif t == 'n':
1695 return "_list('%s')" % "\0".join(node.hex(a) for a in s)
1703 return "_list('%s')" % "\0".join(node.hex(a) for a in s)
1696 elif t == 'b':
1704 elif t == 'b':
1697 return "_list('%s')" % "\0".join(a.branch() for a in s)
1705 return "_list('%s')" % "\0".join(a.branch() for a in s)
1698
1706
1699 m = l // 2
1707 m = l // 2
1700 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1708 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1701
1709
1702 ret = ''
1710 ret = ''
1703 pos = 0
1711 pos = 0
1704 arg = 0
1712 arg = 0
1705 while pos < len(expr):
1713 while pos < len(expr):
1706 c = expr[pos]
1714 c = expr[pos]
1707 if c == '%':
1715 if c == '%':
1708 pos += 1
1716 pos += 1
1709 d = expr[pos]
1717 d = expr[pos]
1710 if d == '%':
1718 if d == '%':
1711 ret += d
1719 ret += d
1712 elif d in 'dsnbr':
1720 elif d in 'dsnbr':
1713 ret += argtype(d, args[arg])
1721 ret += argtype(d, args[arg])
1714 arg += 1
1722 arg += 1
1715 elif d == 'l':
1723 elif d == 'l':
1716 # a list of some type
1724 # a list of some type
1717 pos += 1
1725 pos += 1
1718 d = expr[pos]
1726 d = expr[pos]
1719 ret += listexp(list(args[arg]), d)
1727 ret += listexp(list(args[arg]), d)
1720 arg += 1
1728 arg += 1
1721 else:
1729 else:
1722 raise util.Abort('unexpected revspec format character %s' % d)
1730 raise util.Abort('unexpected revspec format character %s' % d)
1723 else:
1731 else:
1724 ret += c
1732 ret += c
1725 pos += 1
1733 pos += 1
1726
1734
1727 return ret
1735 return ret
1728
1736
1729 def prettyformat(tree):
1737 def prettyformat(tree):
1730 def _prettyformat(tree, level, lines):
1738 def _prettyformat(tree, level, lines):
1731 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
1739 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
1732 lines.append((level, str(tree)))
1740 lines.append((level, str(tree)))
1733 else:
1741 else:
1734 lines.append((level, '(%s' % tree[0]))
1742 lines.append((level, '(%s' % tree[0]))
1735 for s in tree[1:]:
1743 for s in tree[1:]:
1736 _prettyformat(s, level + 1, lines)
1744 _prettyformat(s, level + 1, lines)
1737 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
1745 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
1738
1746
1739 lines = []
1747 lines = []
1740 _prettyformat(tree, 0, lines)
1748 _prettyformat(tree, 0, lines)
1741 output = '\n'.join((' '*l + s) for l, s in lines)
1749 output = '\n'.join((' '*l + s) for l, s in lines)
1742 return output
1750 return output
1743
1751
1744 # tell hggettext to extract docstrings from these functions:
1752 # tell hggettext to extract docstrings from these functions:
1745 i18nfunctions = symbols.values()
1753 i18nfunctions = symbols.values()
@@ -1,322 +1,332 b''
1 $ cat >> $HGRCPATH << EOF
1 $ cat >> $HGRCPATH << EOF
2 > [extensions]
2 > [extensions]
3 > graphlog=
3 > graphlog=
4 > [phases]
4 > [phases]
5 > # public changeset are not obsolete
5 > # public changeset are not obsolete
6 > publish=false
6 > publish=false
7 > EOF
7 > EOF
8 $ mkcommit() {
8 $ mkcommit() {
9 > echo "$1" > "$1"
9 > echo "$1" > "$1"
10 > hg add "$1"
10 > hg add "$1"
11 > hg ci -m "add $1"
11 > hg ci -m "add $1"
12 > }
12 > }
13 $ getid() {
13 $ getid() {
14 > hg id --debug -ir "desc('$1')"
14 > hg id --debug -ir "desc('$1')"
15 > }
15 > }
16
16
17
17
18 $ hg init tmpa
18 $ hg init tmpa
19 $ cd tmpa
19 $ cd tmpa
20
20
21 Killing a single changeset without replacement
21 Killing a single changeset without replacement
22
22
23 $ mkcommit kill_me
23 $ mkcommit kill_me
24 $ hg debugobsolete -d '0 0' `getid kill_me` -u babar
24 $ hg debugobsolete -d '0 0' `getid kill_me` -u babar
25 $ hg debugobsolete
25 $ hg debugobsolete
26 97b7c2d76b1845ed3eb988cd612611e72406cef0 0 {'date': '0 0', 'user': 'babar'}
26 97b7c2d76b1845ed3eb988cd612611e72406cef0 0 {'date': '0 0', 'user': 'babar'}
27 $ cd ..
27 $ cd ..
28
28
29 Killing a single changeset with replacement
29 Killing a single changeset with replacement
30
30
31 $ hg init tmpb
31 $ hg init tmpb
32 $ cd tmpb
32 $ cd tmpb
33 $ mkcommit a
33 $ mkcommit a
34 $ mkcommit b
34 $ mkcommit b
35 $ mkcommit original_c
35 $ mkcommit original_c
36 $ hg up "desc('b')"
36 $ hg up "desc('b')"
37 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
37 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
38 $ mkcommit new_c
38 $ mkcommit new_c
39 created new head
39 created new head
40 $ hg debugobsolete `getid original_c` `getid new_c` -d '56 12'
40 $ hg debugobsolete `getid original_c` `getid new_c` -d '56 12'
41 $ hg debugobsolete
41 $ hg debugobsolete
42 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
42 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
43
43
44 do it again (it read the obsstore before adding new changeset)
44 do it again (it read the obsstore before adding new changeset)
45
45
46 $ hg up '.^'
46 $ hg up '.^'
47 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
47 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
48 $ mkcommit new_2_c
48 $ mkcommit new_2_c
49 created new head
49 created new head
50 $ hg debugobsolete -d '1337 0' `getid new_c` `getid new_2_c`
50 $ hg debugobsolete -d '1337 0' `getid new_c` `getid new_2_c`
51 $ hg debugobsolete
51 $ hg debugobsolete
52 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
52 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
53 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
53 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
54
54
55 Register two markers with a missing node
55 Register two markers with a missing node
56
56
57 $ hg up '.^'
57 $ hg up '.^'
58 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
58 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
59 $ mkcommit new_3_c
59 $ mkcommit new_3_c
60 created new head
60 created new head
61 $ hg debugobsolete -d '1338 0' `getid new_2_c` 1337133713371337133713371337133713371337
61 $ hg debugobsolete -d '1338 0' `getid new_2_c` 1337133713371337133713371337133713371337
62 $ hg debugobsolete -d '1339 0' 1337133713371337133713371337133713371337 `getid new_3_c`
62 $ hg debugobsolete -d '1339 0' 1337133713371337133713371337133713371337 `getid new_3_c`
63 $ hg debugobsolete
63 $ hg debugobsolete
64 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
64 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
65 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
65 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
66 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
66 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
67 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
67 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
68
68
69 Check that graphlog detect that a changeset is obsolete:
69 Check that graphlog detect that a changeset is obsolete:
70
70
71 $ hg glog
71 $ hg glog
72 @ changeset: 5:5601fb93a350
72 @ changeset: 5:5601fb93a350
73 | tag: tip
73 | tag: tip
74 | parent: 1:7c3bad9141dc
74 | parent: 1:7c3bad9141dc
75 | user: test
75 | user: test
76 | date: Thu Jan 01 00:00:00 1970 +0000
76 | date: Thu Jan 01 00:00:00 1970 +0000
77 | summary: add new_3_c
77 | summary: add new_3_c
78 |
78 |
79 | x changeset: 4:ca819180edb9
79 | x changeset: 4:ca819180edb9
80 |/ parent: 1:7c3bad9141dc
80 |/ parent: 1:7c3bad9141dc
81 | user: test
81 | user: test
82 | date: Thu Jan 01 00:00:00 1970 +0000
82 | date: Thu Jan 01 00:00:00 1970 +0000
83 | summary: add new_2_c
83 | summary: add new_2_c
84 |
84 |
85 | x changeset: 3:cdbce2fbb163
85 | x changeset: 3:cdbce2fbb163
86 |/ parent: 1:7c3bad9141dc
86 |/ parent: 1:7c3bad9141dc
87 | user: test
87 | user: test
88 | date: Thu Jan 01 00:00:00 1970 +0000
88 | date: Thu Jan 01 00:00:00 1970 +0000
89 | summary: add new_c
89 | summary: add new_c
90 |
90 |
91 | x changeset: 2:245bde4270cd
91 | x changeset: 2:245bde4270cd
92 |/ user: test
92 |/ user: test
93 | date: Thu Jan 01 00:00:00 1970 +0000
93 | date: Thu Jan 01 00:00:00 1970 +0000
94 | summary: add original_c
94 | summary: add original_c
95 |
95 |
96 o changeset: 1:7c3bad9141dc
96 o changeset: 1:7c3bad9141dc
97 | user: test
97 | user: test
98 | date: Thu Jan 01 00:00:00 1970 +0000
98 | date: Thu Jan 01 00:00:00 1970 +0000
99 | summary: add b
99 | summary: add b
100 |
100 |
101 o changeset: 0:1f0dee641bb7
101 o changeset: 0:1f0dee641bb7
102 user: test
102 user: test
103 date: Thu Jan 01 00:00:00 1970 +0000
103 date: Thu Jan 01 00:00:00 1970 +0000
104 summary: add a
104 summary: add a
105
105
106
106
107 Check that public changeset are not accounted as obsolete:
107 Check that public changeset are not accounted as obsolete:
108
108
109 $ hg phase --public 2
109 $ hg phase --public 2
110 $ hg --config 'extensions.graphlog=' glog
110 $ hg --config 'extensions.graphlog=' glog
111 @ changeset: 5:5601fb93a350
111 @ changeset: 5:5601fb93a350
112 | tag: tip
112 | tag: tip
113 | parent: 1:7c3bad9141dc
113 | parent: 1:7c3bad9141dc
114 | user: test
114 | user: test
115 | date: Thu Jan 01 00:00:00 1970 +0000
115 | date: Thu Jan 01 00:00:00 1970 +0000
116 | summary: add new_3_c
116 | summary: add new_3_c
117 |
117 |
118 | x changeset: 4:ca819180edb9
118 | x changeset: 4:ca819180edb9
119 |/ parent: 1:7c3bad9141dc
119 |/ parent: 1:7c3bad9141dc
120 | user: test
120 | user: test
121 | date: Thu Jan 01 00:00:00 1970 +0000
121 | date: Thu Jan 01 00:00:00 1970 +0000
122 | summary: add new_2_c
122 | summary: add new_2_c
123 |
123 |
124 | x changeset: 3:cdbce2fbb163
124 | x changeset: 3:cdbce2fbb163
125 |/ parent: 1:7c3bad9141dc
125 |/ parent: 1:7c3bad9141dc
126 | user: test
126 | user: test
127 | date: Thu Jan 01 00:00:00 1970 +0000
127 | date: Thu Jan 01 00:00:00 1970 +0000
128 | summary: add new_c
128 | summary: add new_c
129 |
129 |
130 | o changeset: 2:245bde4270cd
130 | o changeset: 2:245bde4270cd
131 |/ user: test
131 |/ user: test
132 | date: Thu Jan 01 00:00:00 1970 +0000
132 | date: Thu Jan 01 00:00:00 1970 +0000
133 | summary: add original_c
133 | summary: add original_c
134 |
134 |
135 o changeset: 1:7c3bad9141dc
135 o changeset: 1:7c3bad9141dc
136 | user: test
136 | user: test
137 | date: Thu Jan 01 00:00:00 1970 +0000
137 | date: Thu Jan 01 00:00:00 1970 +0000
138 | summary: add b
138 | summary: add b
139 |
139 |
140 o changeset: 0:1f0dee641bb7
140 o changeset: 0:1f0dee641bb7
141 user: test
141 user: test
142 date: Thu Jan 01 00:00:00 1970 +0000
142 date: Thu Jan 01 00:00:00 1970 +0000
143 summary: add a
143 summary: add a
144
144
145
145
146 $ cd ..
146 $ cd ..
147
147
148 Exchange Test
148 Exchange Test
149 ============================
149 ============================
150
150
151 Destination repo does not have any data
151 Destination repo does not have any data
152 ---------------------------------------
152 ---------------------------------------
153
153
154 Try to pull markers
154 Try to pull markers
155
155
156 $ hg init tmpc
156 $ hg init tmpc
157 $ cd tmpc
157 $ cd tmpc
158 $ hg pull ../tmpb
158 $ hg pull ../tmpb
159 pulling from ../tmpb
159 pulling from ../tmpb
160 requesting all changes
160 requesting all changes
161 adding changesets
161 adding changesets
162 adding manifests
162 adding manifests
163 adding file changes
163 adding file changes
164 added 6 changesets with 6 changes to 6 files (+3 heads)
164 added 6 changesets with 6 changes to 6 files (+3 heads)
165 (run 'hg heads' to see heads, 'hg merge' to merge)
165 (run 'hg heads' to see heads, 'hg merge' to merge)
166 $ hg debugobsolete
166 $ hg debugobsolete
167 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
167 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
168 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
168 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
169 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
169 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
170 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
170 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
171
171
172 Rollback//Transaction support
172 Rollback//Transaction support
173
173
174 $ hg debugobsolete -d '1340 0' aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
174 $ hg debugobsolete -d '1340 0' aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
175 $ hg debugobsolete
175 $ hg debugobsolete
176 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
176 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
177 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
177 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
178 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
178 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
179 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
179 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
180 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 0 {'date': '1340 0', 'user': 'test'}
180 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 0 {'date': '1340 0', 'user': 'test'}
181 $ hg rollback -n
181 $ hg rollback -n
182 repository tip rolled back to revision 5 (undo debugobsolete)
182 repository tip rolled back to revision 5 (undo debugobsolete)
183 $ hg rollback
183 $ hg rollback
184 repository tip rolled back to revision 5 (undo debugobsolete)
184 repository tip rolled back to revision 5 (undo debugobsolete)
185 $ hg debugobsolete
185 $ hg debugobsolete
186 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
186 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
187 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
187 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
188 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
188 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
189 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
189 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
190
190
191 $ cd ..
191 $ cd ..
192
192
193 Try to pull markers
193 Try to pull markers
194
194
195 $ hg init tmpd
195 $ hg init tmpd
196 $ hg -R tmpb push tmpd
196 $ hg -R tmpb push tmpd
197 pushing to tmpd
197 pushing to tmpd
198 searching for changes
198 searching for changes
199 abort: push includes an obsolete changeset: ca819180edb9!
199 abort: push includes an obsolete changeset: ca819180edb9!
200 [255]
200 [255]
201 $ hg -R tmpd debugobsolete
201 $ hg -R tmpd debugobsolete
202 $ hg -R tmpb push tmpd --rev 'not obsolete()'
202 $ hg -R tmpb push tmpd --rev 'not obsolete()'
203 pushing to tmpd
203 pushing to tmpd
204 searching for changes
204 searching for changes
205 adding changesets
205 adding changesets
206 adding manifests
206 adding manifests
207 adding file changes
207 adding file changes
208 added 4 changesets with 4 changes to 4 files (+1 heads)
208 added 4 changesets with 4 changes to 4 files (+1 heads)
209 $ hg -R tmpd debugobsolete
209 $ hg -R tmpd debugobsolete
210 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
210 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
211 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
211 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
212 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
212 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
213 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
213 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
214
214
215
215
216 Destination repo have existing data
216 Destination repo have existing data
217 ---------------------------------------
217 ---------------------------------------
218
218
219 On pull
219 On pull
220
220
221 $ hg init tmpe
221 $ hg init tmpe
222 $ cd tmpe
222 $ cd tmpe
223 $ hg debugobsolete -d '1339 0' 2448244824482448244824482448244824482448 1339133913391339133913391339133913391339
223 $ hg debugobsolete -d '1339 0' 2448244824482448244824482448244824482448 1339133913391339133913391339133913391339
224 $ hg pull ../tmpb
224 $ hg pull ../tmpb
225 pulling from ../tmpb
225 pulling from ../tmpb
226 requesting all changes
226 requesting all changes
227 adding changesets
227 adding changesets
228 adding manifests
228 adding manifests
229 adding file changes
229 adding file changes
230 added 6 changesets with 6 changes to 6 files (+3 heads)
230 added 6 changesets with 6 changes to 6 files (+3 heads)
231 (run 'hg heads' to see heads, 'hg merge' to merge)
231 (run 'hg heads' to see heads, 'hg merge' to merge)
232 $ hg debugobsolete
232 $ hg debugobsolete
233 2448244824482448244824482448244824482448 1339133913391339133913391339133913391339 0 {'date': '1339 0', 'user': 'test'}
233 2448244824482448244824482448244824482448 1339133913391339133913391339133913391339 0 {'date': '1339 0', 'user': 'test'}
234 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
234 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
235 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
235 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
236 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
236 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
237 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
237 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
238
238
239
239
240 On push
240 On push
241
241
242 $ hg push ../tmpc
242 $ hg push ../tmpc
243 pushing to ../tmpc
243 pushing to ../tmpc
244 searching for changes
244 searching for changes
245 no changes found
245 no changes found
246 [1]
246 [1]
247 $ hg -R ../tmpc debugobsolete
247 $ hg -R ../tmpc debugobsolete
248 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
248 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
249 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
249 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
250 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
250 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
251 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
251 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
252 2448244824482448244824482448244824482448 1339133913391339133913391339133913391339 0 {'date': '1339 0', 'user': 'test'}
252 2448244824482448244824482448244824482448 1339133913391339133913391339133913391339 0 {'date': '1339 0', 'user': 'test'}
253
253
254 detect outgoing obsolete and unstable
254 detect outgoing obsolete and unstable
255 ---------------------------------------
255 ---------------------------------------
256
256
257 $ hg glog
257 $ hg glog
258 o changeset: 5:5601fb93a350
258 o changeset: 5:5601fb93a350
259 | tag: tip
259 | tag: tip
260 | parent: 1:7c3bad9141dc
260 | parent: 1:7c3bad9141dc
261 | user: test
261 | user: test
262 | date: Thu Jan 01 00:00:00 1970 +0000
262 | date: Thu Jan 01 00:00:00 1970 +0000
263 | summary: add new_3_c
263 | summary: add new_3_c
264 |
264 |
265 | x changeset: 4:ca819180edb9
265 | x changeset: 4:ca819180edb9
266 |/ parent: 1:7c3bad9141dc
266 |/ parent: 1:7c3bad9141dc
267 | user: test
267 | user: test
268 | date: Thu Jan 01 00:00:00 1970 +0000
268 | date: Thu Jan 01 00:00:00 1970 +0000
269 | summary: add new_2_c
269 | summary: add new_2_c
270 |
270 |
271 | x changeset: 3:cdbce2fbb163
271 | x changeset: 3:cdbce2fbb163
272 |/ parent: 1:7c3bad9141dc
272 |/ parent: 1:7c3bad9141dc
273 | user: test
273 | user: test
274 | date: Thu Jan 01 00:00:00 1970 +0000
274 | date: Thu Jan 01 00:00:00 1970 +0000
275 | summary: add new_c
275 | summary: add new_c
276 |
276 |
277 | o changeset: 2:245bde4270cd
277 | o changeset: 2:245bde4270cd
278 |/ user: test
278 |/ user: test
279 | date: Thu Jan 01 00:00:00 1970 +0000
279 | date: Thu Jan 01 00:00:00 1970 +0000
280 | summary: add original_c
280 | summary: add original_c
281 |
281 |
282 o changeset: 1:7c3bad9141dc
282 o changeset: 1:7c3bad9141dc
283 | user: test
283 | user: test
284 | date: Thu Jan 01 00:00:00 1970 +0000
284 | date: Thu Jan 01 00:00:00 1970 +0000
285 | summary: add b
285 | summary: add b
286 |
286 |
287 o changeset: 0:1f0dee641bb7
287 o changeset: 0:1f0dee641bb7
288 user: test
288 user: test
289 date: Thu Jan 01 00:00:00 1970 +0000
289 date: Thu Jan 01 00:00:00 1970 +0000
290 summary: add a
290 summary: add a
291
291
292 $ hg up -q 'desc("new_2_c")'
292 $ hg up -q 'desc("new_2_c")'
293 $ mkcommit original_d
293 $ mkcommit original_d
294 $ hg glog -r '::unstable()'
294 $ hg glog -r '::unstable()'
295 @ changeset: 6:7878242aeece
295 @ changeset: 6:7878242aeece
296 | tag: tip
296 | tag: tip
297 | parent: 4:ca819180edb9
297 | parent: 4:ca819180edb9
298 | user: test
298 | user: test
299 | date: Thu Jan 01 00:00:00 1970 +0000
299 | date: Thu Jan 01 00:00:00 1970 +0000
300 | summary: add original_d
300 | summary: add original_d
301 |
301 |
302 x changeset: 4:ca819180edb9
302 x changeset: 4:ca819180edb9
303 | parent: 1:7c3bad9141dc
303 | parent: 1:7c3bad9141dc
304 | user: test
304 | user: test
305 | date: Thu Jan 01 00:00:00 1970 +0000
305 | date: Thu Jan 01 00:00:00 1970 +0000
306 | summary: add new_2_c
306 | summary: add new_2_c
307 |
307 |
308 o changeset: 1:7c3bad9141dc
308 o changeset: 1:7c3bad9141dc
309 | user: test
309 | user: test
310 | date: Thu Jan 01 00:00:00 1970 +0000
310 | date: Thu Jan 01 00:00:00 1970 +0000
311 | summary: add b
311 | summary: add b
312 |
312 |
313 o changeset: 0:1f0dee641bb7
313 o changeset: 0:1f0dee641bb7
314 user: test
314 user: test
315 date: Thu Jan 01 00:00:00 1970 +0000
315 date: Thu Jan 01 00:00:00 1970 +0000
316 summary: add a
316 summary: add a
317
317
318 $ hg push ../tmpc/
318 $ hg push ../tmpc/
319 pushing to ../tmpc/
319 pushing to ../tmpc/
320 searching for changes
320 searching for changes
321 abort: push includes an unstable changeset: 7878242aeece!
321 abort: push includes an unstable changeset: 7878242aeece!
322 [255]
322 [255]
323
324 Test that extinct changeset are properly detected
325
326 $ hg log -r 'extinct()'
327 changeset: 3:cdbce2fbb163
328 parent: 1:7c3bad9141dc
329 user: test
330 date: Thu Jan 01 00:00:00 1970 +0000
331 summary: add new_c
332
General Comments 0
You need to be logged in to leave comments. Login now