##// END OF EJS Templates
revset: fix aliases with 0 or more than 2 parameters...
Mads Kiilerich -
r14723:b9faf94e stable
parent child Browse files
Show More
@@ -1,1045 +1,1021
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
9 import parser, util, error, discovery, hbisect
10 import bookmarks as bookmarksmod
10 import bookmarks as bookmarksmod
11 import match as matchmod
11 import match as matchmod
12 from i18n import _
12 from i18n import _
13
13
14 elements = {
14 elements = {
15 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
15 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
16 "~": (18, None, ("ancestor", 18)),
16 "~": (18, None, ("ancestor", 18)),
17 "^": (18, None, ("parent", 18), ("parentpost", 18)),
17 "^": (18, None, ("parent", 18), ("parentpost", 18)),
18 "-": (5, ("negate", 19), ("minus", 5)),
18 "-": (5, ("negate", 19), ("minus", 5)),
19 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
19 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
20 ("dagrangepost", 17)),
20 ("dagrangepost", 17)),
21 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
21 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
22 ("dagrangepost", 17)),
22 ("dagrangepost", 17)),
23 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
23 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
24 "not": (10, ("not", 10)),
24 "not": (10, ("not", 10)),
25 "!": (10, ("not", 10)),
25 "!": (10, ("not", 10)),
26 "and": (5, None, ("and", 5)),
26 "and": (5, None, ("and", 5)),
27 "&": (5, None, ("and", 5)),
27 "&": (5, None, ("and", 5)),
28 "or": (4, None, ("or", 4)),
28 "or": (4, None, ("or", 4)),
29 "|": (4, None, ("or", 4)),
29 "|": (4, None, ("or", 4)),
30 "+": (4, None, ("or", 4)),
30 "+": (4, None, ("or", 4)),
31 ",": (2, None, ("list", 2)),
31 ",": (2, None, ("list", 2)),
32 ")": (0, None, None),
32 ")": (0, None, None),
33 "symbol": (0, ("symbol",), None),
33 "symbol": (0, ("symbol",), None),
34 "string": (0, ("string",), None),
34 "string": (0, ("string",), None),
35 "end": (0, None, None),
35 "end": (0, None, None),
36 }
36 }
37
37
38 keywords = set(['and', 'or', 'not'])
38 keywords = set(['and', 'or', 'not'])
39
39
40 def tokenize(program):
40 def tokenize(program):
41 pos, l = 0, len(program)
41 pos, l = 0, len(program)
42 while pos < l:
42 while pos < l:
43 c = program[pos]
43 c = program[pos]
44 if c.isspace(): # skip inter-token whitespace
44 if c.isspace(): # skip inter-token whitespace
45 pass
45 pass
46 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
46 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
47 yield ('::', None, pos)
47 yield ('::', None, pos)
48 pos += 1 # skip ahead
48 pos += 1 # skip ahead
49 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
49 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
50 yield ('..', None, pos)
50 yield ('..', None, pos)
51 pos += 1 # skip ahead
51 pos += 1 # skip ahead
52 elif c in "():,-|&+!~^": # handle simple operators
52 elif c in "():,-|&+!~^": # handle simple operators
53 yield (c, None, pos)
53 yield (c, None, pos)
54 elif (c in '"\'' or c == 'r' and
54 elif (c in '"\'' or c == 'r' and
55 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
55 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
56 if c == 'r':
56 if c == 'r':
57 pos += 1
57 pos += 1
58 c = program[pos]
58 c = program[pos]
59 decode = lambda x: x
59 decode = lambda x: x
60 else:
60 else:
61 decode = lambda x: x.decode('string-escape')
61 decode = lambda x: x.decode('string-escape')
62 pos += 1
62 pos += 1
63 s = pos
63 s = pos
64 while pos < l: # find closing quote
64 while pos < l: # find closing quote
65 d = program[pos]
65 d = program[pos]
66 if d == '\\': # skip over escaped characters
66 if d == '\\': # skip over escaped characters
67 pos += 2
67 pos += 2
68 continue
68 continue
69 if d == c:
69 if d == c:
70 yield ('string', decode(program[s:pos]), s)
70 yield ('string', decode(program[s:pos]), s)
71 break
71 break
72 pos += 1
72 pos += 1
73 else:
73 else:
74 raise error.ParseError(_("unterminated string"), s)
74 raise error.ParseError(_("unterminated string"), s)
75 elif c.isalnum() or c in '._' or ord(c) > 127: # gather up a symbol/keyword
75 elif c.isalnum() or c in '._' or ord(c) > 127: # gather up a symbol/keyword
76 s = pos
76 s = pos
77 pos += 1
77 pos += 1
78 while pos < l: # find end of symbol
78 while pos < l: # find end of symbol
79 d = program[pos]
79 d = program[pos]
80 if not (d.isalnum() or d in "._" or ord(d) > 127):
80 if not (d.isalnum() or d in "._" or ord(d) > 127):
81 break
81 break
82 if d == '.' and program[pos - 1] == '.': # special case for ..
82 if d == '.' and program[pos - 1] == '.': # special case for ..
83 pos -= 1
83 pos -= 1
84 break
84 break
85 pos += 1
85 pos += 1
86 sym = program[s:pos]
86 sym = program[s:pos]
87 if sym in keywords: # operator keywords
87 if sym in keywords: # operator keywords
88 yield (sym, None, s)
88 yield (sym, None, s)
89 else:
89 else:
90 yield ('symbol', sym, s)
90 yield ('symbol', sym, s)
91 pos -= 1
91 pos -= 1
92 else:
92 else:
93 raise error.ParseError(_("syntax error"), pos)
93 raise error.ParseError(_("syntax error"), pos)
94 pos += 1
94 pos += 1
95 yield ('end', None, pos)
95 yield ('end', None, pos)
96
96
97 # helpers
97 # helpers
98
98
99 def getstring(x, err):
99 def getstring(x, err):
100 if x and (x[0] == 'string' or x[0] == 'symbol'):
100 if x and (x[0] == 'string' or x[0] == 'symbol'):
101 return x[1]
101 return x[1]
102 raise error.ParseError(err)
102 raise error.ParseError(err)
103
103
104 def getlist(x):
104 def getlist(x):
105 if not x:
105 if not x:
106 return []
106 return []
107 if x[0] == 'list':
107 if x[0] == 'list':
108 return getlist(x[1]) + [x[2]]
108 return getlist(x[1]) + [x[2]]
109 return [x]
109 return [x]
110
110
111 def getargs(x, min, max, err):
111 def getargs(x, min, max, err):
112 l = getlist(x)
112 l = getlist(x)
113 if len(l) < min or len(l) > max:
113 if len(l) < min or len(l) > max:
114 raise error.ParseError(err)
114 raise error.ParseError(err)
115 return l
115 return l
116
116
117 def getset(repo, subset, x):
117 def getset(repo, subset, x):
118 if not x:
118 if not x:
119 raise error.ParseError(_("missing argument"))
119 raise error.ParseError(_("missing argument"))
120 return methods[x[0]](repo, subset, *x[1:])
120 return methods[x[0]](repo, subset, *x[1:])
121
121
122 # operator methods
122 # operator methods
123
123
124 def stringset(repo, subset, x):
124 def stringset(repo, subset, x):
125 x = repo[x].rev()
125 x = repo[x].rev()
126 if x == -1 and len(subset) == len(repo):
126 if x == -1 and len(subset) == len(repo):
127 return [-1]
127 return [-1]
128 if len(subset) == len(repo) or x in subset:
128 if len(subset) == len(repo) or x in subset:
129 return [x]
129 return [x]
130 return []
130 return []
131
131
132 def symbolset(repo, subset, x):
132 def symbolset(repo, subset, x):
133 if x in symbols:
133 if x in symbols:
134 raise error.ParseError(_("can't use %s here") % x)
134 raise error.ParseError(_("can't use %s here") % x)
135 return stringset(repo, subset, x)
135 return stringset(repo, subset, x)
136
136
137 def rangeset(repo, subset, x, y):
137 def rangeset(repo, subset, x, y):
138 m = getset(repo, subset, x)
138 m = getset(repo, subset, x)
139 if not m:
139 if not m:
140 m = getset(repo, range(len(repo)), x)
140 m = getset(repo, range(len(repo)), x)
141
141
142 n = getset(repo, subset, y)
142 n = getset(repo, subset, y)
143 if not n:
143 if not n:
144 n = getset(repo, range(len(repo)), y)
144 n = getset(repo, range(len(repo)), y)
145
145
146 if not m or not n:
146 if not m or not n:
147 return []
147 return []
148 m, n = m[0], n[-1]
148 m, n = m[0], n[-1]
149
149
150 if m < n:
150 if m < n:
151 r = range(m, n + 1)
151 r = range(m, n + 1)
152 else:
152 else:
153 r = range(m, n - 1, -1)
153 r = range(m, n - 1, -1)
154 s = set(subset)
154 s = set(subset)
155 return [x for x in r if x in s]
155 return [x for x in r if x in s]
156
156
157 def andset(repo, subset, x, y):
157 def andset(repo, subset, x, y):
158 return getset(repo, getset(repo, subset, x), y)
158 return getset(repo, getset(repo, subset, x), y)
159
159
160 def orset(repo, subset, x, y):
160 def orset(repo, subset, x, y):
161 xl = getset(repo, subset, x)
161 xl = getset(repo, subset, x)
162 s = set(xl)
162 s = set(xl)
163 yl = getset(repo, [r for r in subset if r not in s], y)
163 yl = getset(repo, [r for r in subset if r not in s], y)
164 return xl + yl
164 return xl + yl
165
165
166 def notset(repo, subset, x):
166 def notset(repo, subset, x):
167 s = set(getset(repo, subset, x))
167 s = set(getset(repo, subset, x))
168 return [r for r in subset if r not in s]
168 return [r for r in subset if r not in s]
169
169
170 def listset(repo, subset, a, b):
170 def listset(repo, subset, a, b):
171 raise error.ParseError(_("can't use a list in this context"))
171 raise error.ParseError(_("can't use a list in this context"))
172
172
173 def func(repo, subset, a, b):
173 def func(repo, subset, a, b):
174 if a[0] == 'symbol' and a[1] in symbols:
174 if a[0] == 'symbol' and a[1] in symbols:
175 return symbols[a[1]](repo, subset, b)
175 return symbols[a[1]](repo, subset, b)
176 raise error.ParseError(_("not a function: %s") % a[1])
176 raise error.ParseError(_("not a function: %s") % a[1])
177
177
178 # functions
178 # functions
179
179
180 def adds(repo, subset, x):
180 def adds(repo, subset, x):
181 """``adds(pattern)``
181 """``adds(pattern)``
182 Changesets that add a file matching pattern.
182 Changesets that add a file matching pattern.
183 """
183 """
184 # i18n: "adds" is a keyword
184 # i18n: "adds" is a keyword
185 pat = getstring(x, _("adds requires a pattern"))
185 pat = getstring(x, _("adds requires a pattern"))
186 return checkstatus(repo, subset, pat, 1)
186 return checkstatus(repo, subset, pat, 1)
187
187
188 def ancestor(repo, subset, x):
188 def ancestor(repo, subset, x):
189 """``ancestor(single, single)``
189 """``ancestor(single, single)``
190 Greatest common ancestor of the two changesets.
190 Greatest common ancestor of the two changesets.
191 """
191 """
192 # i18n: "ancestor" is a keyword
192 # i18n: "ancestor" is a keyword
193 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
193 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
194 r = range(len(repo))
194 r = range(len(repo))
195 a = getset(repo, r, l[0])
195 a = getset(repo, r, l[0])
196 b = getset(repo, r, l[1])
196 b = getset(repo, r, l[1])
197 if len(a) != 1 or len(b) != 1:
197 if len(a) != 1 or len(b) != 1:
198 # i18n: "ancestor" is a keyword
198 # i18n: "ancestor" is a keyword
199 raise error.ParseError(_("ancestor arguments must be single revisions"))
199 raise error.ParseError(_("ancestor arguments must be single revisions"))
200 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
200 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
201
201
202 return [r for r in an if r in subset]
202 return [r for r in an if r in subset]
203
203
204 def ancestors(repo, subset, x):
204 def ancestors(repo, subset, x):
205 """``ancestors(set)``
205 """``ancestors(set)``
206 Changesets that are ancestors of a changeset in set.
206 Changesets that are ancestors of a changeset in set.
207 """
207 """
208 args = getset(repo, range(len(repo)), x)
208 args = getset(repo, range(len(repo)), x)
209 if not args:
209 if not args:
210 return []
210 return []
211 s = set(repo.changelog.ancestors(*args)) | set(args)
211 s = set(repo.changelog.ancestors(*args)) | set(args)
212 return [r for r in subset if r in s]
212 return [r for r in subset if r in s]
213
213
214 def ancestorspec(repo, subset, x, n):
214 def ancestorspec(repo, subset, x, n):
215 """``set~n``
215 """``set~n``
216 Changesets that are the Nth ancestor (first parents only) of a changeset in set.
216 Changesets that are the Nth ancestor (first parents only) of a changeset in set.
217 """
217 """
218 try:
218 try:
219 n = int(n[1])
219 n = int(n[1])
220 except ValueError:
220 except ValueError:
221 raise error.ParseError(_("~ expects a number"))
221 raise error.ParseError(_("~ expects a number"))
222 ps = set()
222 ps = set()
223 cl = repo.changelog
223 cl = repo.changelog
224 for r in getset(repo, subset, x):
224 for r in getset(repo, subset, x):
225 for i in range(n):
225 for i in range(n):
226 r = cl.parentrevs(r)[0]
226 r = cl.parentrevs(r)[0]
227 ps.add(r)
227 ps.add(r)
228 return [r for r in subset if r in ps]
228 return [r for r in subset if r in ps]
229
229
230 def author(repo, subset, x):
230 def author(repo, subset, x):
231 """``author(string)``
231 """``author(string)``
232 Alias for ``user(string)``.
232 Alias for ``user(string)``.
233 """
233 """
234 # i18n: "author" is a keyword
234 # i18n: "author" is a keyword
235 n = getstring(x, _("author requires a string")).lower()
235 n = getstring(x, _("author requires a string")).lower()
236 return [r for r in subset if n in repo[r].user().lower()]
236 return [r for r in subset if n in repo[r].user().lower()]
237
237
238 def bisected(repo, subset, x):
238 def bisected(repo, subset, x):
239 """``bisected(string)``
239 """``bisected(string)``
240 Changesets marked in the specified bisect state (good, bad, skip).
240 Changesets marked in the specified bisect state (good, bad, skip).
241 """
241 """
242 state = getstring(x, _("bisect requires a string")).lower()
242 state = getstring(x, _("bisect requires a string")).lower()
243 if state not in ('good', 'bad', 'skip', 'unknown'):
243 if state not in ('good', 'bad', 'skip', 'unknown'):
244 raise error.ParseError(_('invalid bisect state'))
244 raise error.ParseError(_('invalid bisect state'))
245 marked = set(repo.changelog.rev(n) for n in hbisect.load_state(repo)[state])
245 marked = set(repo.changelog.rev(n) for n in hbisect.load_state(repo)[state])
246 return [r for r in subset if r in marked]
246 return [r for r in subset if r in marked]
247
247
248 def bookmark(repo, subset, x):
248 def bookmark(repo, subset, x):
249 """``bookmark([name])``
249 """``bookmark([name])``
250 The named bookmark or all bookmarks.
250 The named bookmark or all bookmarks.
251 """
251 """
252 # i18n: "bookmark" is a keyword
252 # i18n: "bookmark" is a keyword
253 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
253 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
254 if args:
254 if args:
255 bm = getstring(args[0],
255 bm = getstring(args[0],
256 # i18n: "bookmark" is a keyword
256 # i18n: "bookmark" is a keyword
257 _('the argument to bookmark must be a string'))
257 _('the argument to bookmark must be a string'))
258 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
258 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
259 if not bmrev:
259 if not bmrev:
260 raise util.Abort(_("bookmark '%s' does not exist") % bm)
260 raise util.Abort(_("bookmark '%s' does not exist") % bm)
261 bmrev = repo[bmrev].rev()
261 bmrev = repo[bmrev].rev()
262 return [r for r in subset if r == bmrev]
262 return [r for r in subset if r == bmrev]
263 bms = set([repo[r].rev()
263 bms = set([repo[r].rev()
264 for r in bookmarksmod.listbookmarks(repo).values()])
264 for r in bookmarksmod.listbookmarks(repo).values()])
265 return [r for r in subset if r in bms]
265 return [r for r in subset if r in bms]
266
266
267 def branch(repo, subset, x):
267 def branch(repo, subset, x):
268 """``branch(string or set)``
268 """``branch(string or set)``
269 All changesets belonging to the given branch or the branches of the given
269 All changesets belonging to the given branch or the branches of the given
270 changesets.
270 changesets.
271 """
271 """
272 try:
272 try:
273 b = getstring(x, '')
273 b = getstring(x, '')
274 if b in repo.branchmap():
274 if b in repo.branchmap():
275 return [r for r in subset if repo[r].branch() == b]
275 return [r for r in subset if repo[r].branch() == b]
276 except error.ParseError:
276 except error.ParseError:
277 # not a string, but another revspec, e.g. tip()
277 # not a string, but another revspec, e.g. tip()
278 pass
278 pass
279
279
280 s = getset(repo, range(len(repo)), x)
280 s = getset(repo, range(len(repo)), x)
281 b = set()
281 b = set()
282 for r in s:
282 for r in s:
283 b.add(repo[r].branch())
283 b.add(repo[r].branch())
284 s = set(s)
284 s = set(s)
285 return [r for r in subset if r in s or repo[r].branch() in b]
285 return [r for r in subset if r in s or repo[r].branch() in b]
286
286
287 def checkstatus(repo, subset, pat, field):
287 def checkstatus(repo, subset, pat, field):
288 m = matchmod.match(repo.root, repo.getcwd(), [pat])
288 m = matchmod.match(repo.root, repo.getcwd(), [pat])
289 s = []
289 s = []
290 fast = (m.files() == [pat])
290 fast = (m.files() == [pat])
291 for r in subset:
291 for r in subset:
292 c = repo[r]
292 c = repo[r]
293 if fast:
293 if fast:
294 if pat not in c.files():
294 if pat not in c.files():
295 continue
295 continue
296 else:
296 else:
297 for f in c.files():
297 for f in c.files():
298 if m(f):
298 if m(f):
299 break
299 break
300 else:
300 else:
301 continue
301 continue
302 files = repo.status(c.p1().node(), c.node())[field]
302 files = repo.status(c.p1().node(), c.node())[field]
303 if fast:
303 if fast:
304 if pat in files:
304 if pat in files:
305 s.append(r)
305 s.append(r)
306 else:
306 else:
307 for f in files:
307 for f in files:
308 if m(f):
308 if m(f):
309 s.append(r)
309 s.append(r)
310 break
310 break
311 return s
311 return s
312
312
313 def children(repo, subset, x):
313 def children(repo, subset, x):
314 """``children(set)``
314 """``children(set)``
315 Child changesets of changesets in set.
315 Child changesets of changesets in set.
316 """
316 """
317 cs = set()
317 cs = set()
318 cl = repo.changelog
318 cl = repo.changelog
319 s = set(getset(repo, range(len(repo)), x))
319 s = set(getset(repo, range(len(repo)), x))
320 for r in xrange(0, len(repo)):
320 for r in xrange(0, len(repo)):
321 for p in cl.parentrevs(r):
321 for p in cl.parentrevs(r):
322 if p in s:
322 if p in s:
323 cs.add(r)
323 cs.add(r)
324 return [r for r in subset if r in cs]
324 return [r for r in subset if r in cs]
325
325
326 def closed(repo, subset, x):
326 def closed(repo, subset, x):
327 """``closed()``
327 """``closed()``
328 Changeset is closed.
328 Changeset is closed.
329 """
329 """
330 # i18n: "closed" is a keyword
330 # i18n: "closed" is a keyword
331 getargs(x, 0, 0, _("closed takes no arguments"))
331 getargs(x, 0, 0, _("closed takes no arguments"))
332 return [r for r in subset if repo[r].extra().get('close')]
332 return [r for r in subset if repo[r].extra().get('close')]
333
333
334 def contains(repo, subset, x):
334 def contains(repo, subset, x):
335 """``contains(pattern)``
335 """``contains(pattern)``
336 Revision contains a file matching pattern. See :hg:`help patterns`
336 Revision contains a file matching pattern. See :hg:`help patterns`
337 for information about file patterns.
337 for information about file patterns.
338 """
338 """
339 # i18n: "contains" is a keyword
339 # i18n: "contains" is a keyword
340 pat = getstring(x, _("contains requires a pattern"))
340 pat = getstring(x, _("contains requires a pattern"))
341 m = matchmod.match(repo.root, repo.getcwd(), [pat])
341 m = matchmod.match(repo.root, repo.getcwd(), [pat])
342 s = []
342 s = []
343 if m.files() == [pat]:
343 if m.files() == [pat]:
344 for r in subset:
344 for r in subset:
345 if pat in repo[r]:
345 if pat in repo[r]:
346 s.append(r)
346 s.append(r)
347 else:
347 else:
348 for r in subset:
348 for r in subset:
349 for f in repo[r].manifest():
349 for f in repo[r].manifest():
350 if m(f):
350 if m(f):
351 s.append(r)
351 s.append(r)
352 break
352 break
353 return s
353 return s
354
354
355 def date(repo, subset, x):
355 def date(repo, subset, x):
356 """``date(interval)``
356 """``date(interval)``
357 Changesets within the interval, see :hg:`help dates`.
357 Changesets within the interval, see :hg:`help dates`.
358 """
358 """
359 # i18n: "date" is a keyword
359 # i18n: "date" is a keyword
360 ds = getstring(x, _("date requires a string"))
360 ds = getstring(x, _("date requires a string"))
361 dm = util.matchdate(ds)
361 dm = util.matchdate(ds)
362 return [r for r in subset if dm(repo[r].date()[0])]
362 return [r for r in subset if dm(repo[r].date()[0])]
363
363
364 def desc(repo, subset, x):
364 def desc(repo, subset, x):
365 """``desc(string)``
365 """``desc(string)``
366 Search commit message for string. The match is case-insensitive.
366 Search commit message for string. The match is case-insensitive.
367 """
367 """
368 # i18n: "desc" is a keyword
368 # i18n: "desc" is a keyword
369 ds = getstring(x, _("desc requires a string")).lower()
369 ds = getstring(x, _("desc requires a string")).lower()
370 l = []
370 l = []
371 for r in subset:
371 for r in subset:
372 c = repo[r]
372 c = repo[r]
373 if ds in c.description().lower():
373 if ds in c.description().lower():
374 l.append(r)
374 l.append(r)
375 return l
375 return l
376
376
377 def descendants(repo, subset, x):
377 def descendants(repo, subset, x):
378 """``descendants(set)``
378 """``descendants(set)``
379 Changesets which are descendants of changesets in set.
379 Changesets which are descendants of changesets in set.
380 """
380 """
381 args = getset(repo, range(len(repo)), x)
381 args = getset(repo, range(len(repo)), x)
382 if not args:
382 if not args:
383 return []
383 return []
384 s = set(repo.changelog.descendants(*args)) | set(args)
384 s = set(repo.changelog.descendants(*args)) | set(args)
385 return [r for r in subset if r in s]
385 return [r for r in subset if r in s]
386
386
387 def filelog(repo, subset, x):
387 def filelog(repo, subset, x):
388 """``filelog(pattern)``
388 """``filelog(pattern)``
389 Changesets connected to the specified filelog.
389 Changesets connected to the specified filelog.
390 """
390 """
391
391
392 pat = getstring(x, _("filelog requires a pattern"))
392 pat = getstring(x, _("filelog requires a pattern"))
393 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath')
393 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath')
394 s = set()
394 s = set()
395
395
396 if not m.anypats():
396 if not m.anypats():
397 for f in m.files():
397 for f in m.files():
398 fl = repo.file(f)
398 fl = repo.file(f)
399 for fr in fl:
399 for fr in fl:
400 s.add(fl.linkrev(fr))
400 s.add(fl.linkrev(fr))
401 else:
401 else:
402 for f in repo[None]:
402 for f in repo[None]:
403 if m(f):
403 if m(f):
404 fl = repo.file(f)
404 fl = repo.file(f)
405 for fr in fl:
405 for fr in fl:
406 s.add(fl.linkrev(fr))
406 s.add(fl.linkrev(fr))
407
407
408 return [r for r in subset if r in s]
408 return [r for r in subset if r in s]
409
409
410 def follow(repo, subset, x):
410 def follow(repo, subset, x):
411 """``follow([file])``
411 """``follow([file])``
412 An alias for ``::.`` (ancestors of the working copy's first parent).
412 An alias for ``::.`` (ancestors of the working copy's first parent).
413 If a filename is specified, the history of the given file is followed,
413 If a filename is specified, the history of the given file is followed,
414 including copies.
414 including copies.
415 """
415 """
416 # i18n: "follow" is a keyword
416 # i18n: "follow" is a keyword
417 l = getargs(x, 0, 1, _("follow takes no arguments or a filename"))
417 l = getargs(x, 0, 1, _("follow takes no arguments or a filename"))
418 p = repo['.'].rev()
418 p = repo['.'].rev()
419 if l:
419 if l:
420 x = getstring(l[0], _("follow expected a filename"))
420 x = getstring(l[0], _("follow expected a filename"))
421 s = set(ctx.rev() for ctx in repo['.'][x].ancestors())
421 s = set(ctx.rev() for ctx in repo['.'][x].ancestors())
422 else:
422 else:
423 s = set(repo.changelog.ancestors(p))
423 s = set(repo.changelog.ancestors(p))
424
424
425 s |= set([p])
425 s |= set([p])
426 return [r for r in subset if r in s]
426 return [r for r in subset if r in s]
427
427
428 def followfile(repo, subset, x):
428 def followfile(repo, subset, x):
429 """``follow()``
429 """``follow()``
430 An alias for ``::.`` (ancestors of the working copy's first parent).
430 An alias for ``::.`` (ancestors of the working copy's first parent).
431 """
431 """
432 # i18n: "follow" is a keyword
432 # i18n: "follow" is a keyword
433 getargs(x, 0, 0, _("follow takes no arguments"))
433 getargs(x, 0, 0, _("follow takes no arguments"))
434 p = repo['.'].rev()
434 p = repo['.'].rev()
435 s = set(repo.changelog.ancestors(p)) | set([p])
435 s = set(repo.changelog.ancestors(p)) | set([p])
436 return [r for r in subset if r in s]
436 return [r for r in subset if r in s]
437
437
438 def getall(repo, subset, x):
438 def getall(repo, subset, x):
439 """``all()``
439 """``all()``
440 All changesets, the same as ``0:tip``.
440 All changesets, the same as ``0:tip``.
441 """
441 """
442 # i18n: "all" is a keyword
442 # i18n: "all" is a keyword
443 getargs(x, 0, 0, _("all takes no arguments"))
443 getargs(x, 0, 0, _("all takes no arguments"))
444 return subset
444 return subset
445
445
446 def grep(repo, subset, x):
446 def grep(repo, subset, x):
447 """``grep(regex)``
447 """``grep(regex)``
448 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
448 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
449 to ensure special escape characters are handled correctly. Unlike
449 to ensure special escape characters are handled correctly. Unlike
450 ``keyword(string)``, the match is case-sensitive.
450 ``keyword(string)``, the match is case-sensitive.
451 """
451 """
452 try:
452 try:
453 # i18n: "grep" is a keyword
453 # i18n: "grep" is a keyword
454 gr = re.compile(getstring(x, _("grep requires a string")))
454 gr = re.compile(getstring(x, _("grep requires a string")))
455 except re.error, e:
455 except re.error, e:
456 raise error.ParseError(_('invalid match pattern: %s') % e)
456 raise error.ParseError(_('invalid match pattern: %s') % e)
457 l = []
457 l = []
458 for r in subset:
458 for r in subset:
459 c = repo[r]
459 c = repo[r]
460 for e in c.files() + [c.user(), c.description()]:
460 for e in c.files() + [c.user(), c.description()]:
461 if gr.search(e):
461 if gr.search(e):
462 l.append(r)
462 l.append(r)
463 break
463 break
464 return l
464 return l
465
465
466 def hasfile(repo, subset, x):
466 def hasfile(repo, subset, x):
467 """``file(pattern)``
467 """``file(pattern)``
468 Changesets affecting files matched by pattern.
468 Changesets affecting files matched by pattern.
469 """
469 """
470 # i18n: "file" is a keyword
470 # i18n: "file" is a keyword
471 pat = getstring(x, _("file requires a pattern"))
471 pat = getstring(x, _("file requires a pattern"))
472 m = matchmod.match(repo.root, repo.getcwd(), [pat])
472 m = matchmod.match(repo.root, repo.getcwd(), [pat])
473 s = []
473 s = []
474 for r in subset:
474 for r in subset:
475 for f in repo[r].files():
475 for f in repo[r].files():
476 if m(f):
476 if m(f):
477 s.append(r)
477 s.append(r)
478 break
478 break
479 return s
479 return s
480
480
481 def head(repo, subset, x):
481 def head(repo, subset, x):
482 """``head()``
482 """``head()``
483 Changeset is a named branch head.
483 Changeset is a named branch head.
484 """
484 """
485 # i18n: "head" is a keyword
485 # i18n: "head" is a keyword
486 getargs(x, 0, 0, _("head takes no arguments"))
486 getargs(x, 0, 0, _("head takes no arguments"))
487 hs = set()
487 hs = set()
488 for b, ls in repo.branchmap().iteritems():
488 for b, ls in repo.branchmap().iteritems():
489 hs.update(repo[h].rev() for h in ls)
489 hs.update(repo[h].rev() for h in ls)
490 return [r for r in subset if r in hs]
490 return [r for r in subset if r in hs]
491
491
492 def heads(repo, subset, x):
492 def heads(repo, subset, x):
493 """``heads(set)``
493 """``heads(set)``
494 Members of set with no children in set.
494 Members of set with no children in set.
495 """
495 """
496 s = getset(repo, subset, x)
496 s = getset(repo, subset, x)
497 ps = set(parents(repo, subset, x))
497 ps = set(parents(repo, subset, x))
498 return [r for r in s if r not in ps]
498 return [r for r in s if r not in ps]
499
499
500 def keyword(repo, subset, x):
500 def keyword(repo, subset, x):
501 """``keyword(string)``
501 """``keyword(string)``
502 Search commit message, user name, and names of changed files for
502 Search commit message, user name, and names of changed files for
503 string. The match is case-insensitive.
503 string. The match is case-insensitive.
504 """
504 """
505 # i18n: "keyword" is a keyword
505 # i18n: "keyword" is a keyword
506 kw = getstring(x, _("keyword requires a string")).lower()
506 kw = getstring(x, _("keyword requires a string")).lower()
507 l = []
507 l = []
508 for r in subset:
508 for r in subset:
509 c = repo[r]
509 c = repo[r]
510 t = " ".join(c.files() + [c.user(), c.description()])
510 t = " ".join(c.files() + [c.user(), c.description()])
511 if kw in t.lower():
511 if kw in t.lower():
512 l.append(r)
512 l.append(r)
513 return l
513 return l
514
514
515 def limit(repo, subset, x):
515 def limit(repo, subset, x):
516 """``limit(set, n)``
516 """``limit(set, n)``
517 First n members of set.
517 First n members of set.
518 """
518 """
519 # i18n: "limit" is a keyword
519 # i18n: "limit" is a keyword
520 l = getargs(x, 2, 2, _("limit requires two arguments"))
520 l = getargs(x, 2, 2, _("limit requires two arguments"))
521 try:
521 try:
522 # i18n: "limit" is a keyword
522 # i18n: "limit" is a keyword
523 lim = int(getstring(l[1], _("limit requires a number")))
523 lim = int(getstring(l[1], _("limit requires a number")))
524 except ValueError:
524 except ValueError:
525 # i18n: "limit" is a keyword
525 # i18n: "limit" is a keyword
526 raise error.ParseError(_("limit expects a number"))
526 raise error.ParseError(_("limit expects a number"))
527 ss = set(subset)
527 ss = set(subset)
528 os = getset(repo, range(len(repo)), l[0])[:lim]
528 os = getset(repo, range(len(repo)), l[0])[:lim]
529 return [r for r in os if r in ss]
529 return [r for r in os if r in ss]
530
530
531 def last(repo, subset, x):
531 def last(repo, subset, x):
532 """``last(set, n)``
532 """``last(set, n)``
533 Last n members of set.
533 Last n members of set.
534 """
534 """
535 # i18n: "last" is a keyword
535 # i18n: "last" is a keyword
536 l = getargs(x, 2, 2, _("last requires two arguments"))
536 l = getargs(x, 2, 2, _("last requires two arguments"))
537 try:
537 try:
538 # i18n: "last" is a keyword
538 # i18n: "last" is a keyword
539 lim = int(getstring(l[1], _("last requires a number")))
539 lim = int(getstring(l[1], _("last requires a number")))
540 except ValueError:
540 except ValueError:
541 # i18n: "last" is a keyword
541 # i18n: "last" is a keyword
542 raise error.ParseError(_("last expects a number"))
542 raise error.ParseError(_("last expects a number"))
543 ss = set(subset)
543 ss = set(subset)
544 os = getset(repo, range(len(repo)), l[0])[-lim:]
544 os = getset(repo, range(len(repo)), l[0])[-lim:]
545 return [r for r in os if r in ss]
545 return [r for r in os if r in ss]
546
546
547 def maxrev(repo, subset, x):
547 def maxrev(repo, subset, x):
548 """``max(set)``
548 """``max(set)``
549 Changeset with highest revision number in set.
549 Changeset with highest revision number in set.
550 """
550 """
551 os = getset(repo, range(len(repo)), x)
551 os = getset(repo, range(len(repo)), x)
552 if os:
552 if os:
553 m = max(os)
553 m = max(os)
554 if m in subset:
554 if m in subset:
555 return [m]
555 return [m]
556 return []
556 return []
557
557
558 def merge(repo, subset, x):
558 def merge(repo, subset, x):
559 """``merge()``
559 """``merge()``
560 Changeset is a merge changeset.
560 Changeset is a merge changeset.
561 """
561 """
562 # i18n: "merge" is a keyword
562 # i18n: "merge" is a keyword
563 getargs(x, 0, 0, _("merge takes no arguments"))
563 getargs(x, 0, 0, _("merge takes no arguments"))
564 cl = repo.changelog
564 cl = repo.changelog
565 return [r for r in subset if cl.parentrevs(r)[1] != -1]
565 return [r for r in subset if cl.parentrevs(r)[1] != -1]
566
566
567 def minrev(repo, subset, x):
567 def minrev(repo, subset, x):
568 """``min(set)``
568 """``min(set)``
569 Changeset with lowest revision number in set.
569 Changeset with lowest revision number in set.
570 """
570 """
571 os = getset(repo, range(len(repo)), x)
571 os = getset(repo, range(len(repo)), x)
572 if os:
572 if os:
573 m = min(os)
573 m = min(os)
574 if m in subset:
574 if m in subset:
575 return [m]
575 return [m]
576 return []
576 return []
577
577
578 def modifies(repo, subset, x):
578 def modifies(repo, subset, x):
579 """``modifies(pattern)``
579 """``modifies(pattern)``
580 Changesets modifying files matched by pattern.
580 Changesets modifying files matched by pattern.
581 """
581 """
582 # i18n: "modifies" is a keyword
582 # i18n: "modifies" is a keyword
583 pat = getstring(x, _("modifies requires a pattern"))
583 pat = getstring(x, _("modifies requires a pattern"))
584 return checkstatus(repo, subset, pat, 0)
584 return checkstatus(repo, subset, pat, 0)
585
585
586 def node(repo, subset, x):
586 def node(repo, subset, x):
587 """``id(string)``
587 """``id(string)``
588 Revision non-ambiguously specified by the given hex string prefix.
588 Revision non-ambiguously specified by the given hex string prefix.
589 """
589 """
590 # i18n: "id" is a keyword
590 # i18n: "id" is a keyword
591 l = getargs(x, 1, 1, _("id requires one argument"))
591 l = getargs(x, 1, 1, _("id requires one argument"))
592 # i18n: "id" is a keyword
592 # i18n: "id" is a keyword
593 n = getstring(l[0], _("id requires a string"))
593 n = getstring(l[0], _("id requires a string"))
594 if len(n) == 40:
594 if len(n) == 40:
595 rn = repo[n].rev()
595 rn = repo[n].rev()
596 else:
596 else:
597 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
597 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
598 return [r for r in subset if r == rn]
598 return [r for r in subset if r == rn]
599
599
600 def outgoing(repo, subset, x):
600 def outgoing(repo, subset, x):
601 """``outgoing([path])``
601 """``outgoing([path])``
602 Changesets not found in the specified destination repository, or the
602 Changesets not found in the specified destination repository, or the
603 default push location.
603 default push location.
604 """
604 """
605 import hg # avoid start-up nasties
605 import hg # avoid start-up nasties
606 # i18n: "outgoing" is a keyword
606 # i18n: "outgoing" is a keyword
607 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
607 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
608 # i18n: "outgoing" is a keyword
608 # i18n: "outgoing" is a keyword
609 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
609 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
610 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
610 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
611 dest, branches = hg.parseurl(dest)
611 dest, branches = hg.parseurl(dest)
612 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
612 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
613 if revs:
613 if revs:
614 revs = [repo.lookup(rev) for rev in revs]
614 revs = [repo.lookup(rev) for rev in revs]
615 other = hg.peer(repo, {}, dest)
615 other = hg.peer(repo, {}, dest)
616 repo.ui.pushbuffer()
616 repo.ui.pushbuffer()
617 common, outheads = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
617 common, outheads = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
618 repo.ui.popbuffer()
618 repo.ui.popbuffer()
619 cl = repo.changelog
619 cl = repo.changelog
620 o = set([cl.rev(r) for r in repo.changelog.findmissing(common, outheads)])
620 o = set([cl.rev(r) for r in repo.changelog.findmissing(common, outheads)])
621 return [r for r in subset if r in o]
621 return [r for r in subset if r in o]
622
622
623 def p1(repo, subset, x):
623 def p1(repo, subset, x):
624 """``p1([set])``
624 """``p1([set])``
625 First parent of changesets in set, or the working directory.
625 First parent of changesets in set, or the working directory.
626 """
626 """
627 if x is None:
627 if x is None:
628 p = repo[x].p1().rev()
628 p = repo[x].p1().rev()
629 return [r for r in subset if r == p]
629 return [r for r in subset if r == p]
630
630
631 ps = set()
631 ps = set()
632 cl = repo.changelog
632 cl = repo.changelog
633 for r in getset(repo, range(len(repo)), x):
633 for r in getset(repo, range(len(repo)), x):
634 ps.add(cl.parentrevs(r)[0])
634 ps.add(cl.parentrevs(r)[0])
635 return [r for r in subset if r in ps]
635 return [r for r in subset if r in ps]
636
636
637 def p2(repo, subset, x):
637 def p2(repo, subset, x):
638 """``p2([set])``
638 """``p2([set])``
639 Second parent of changesets in set, or the working directory.
639 Second parent of changesets in set, or the working directory.
640 """
640 """
641 if x is None:
641 if x is None:
642 ps = repo[x].parents()
642 ps = repo[x].parents()
643 try:
643 try:
644 p = ps[1].rev()
644 p = ps[1].rev()
645 return [r for r in subset if r == p]
645 return [r for r in subset if r == p]
646 except IndexError:
646 except IndexError:
647 return []
647 return []
648
648
649 ps = set()
649 ps = set()
650 cl = repo.changelog
650 cl = repo.changelog
651 for r in getset(repo, range(len(repo)), x):
651 for r in getset(repo, range(len(repo)), x):
652 ps.add(cl.parentrevs(r)[1])
652 ps.add(cl.parentrevs(r)[1])
653 return [r for r in subset if r in ps]
653 return [r for r in subset if r in ps]
654
654
655 def parents(repo, subset, x):
655 def parents(repo, subset, x):
656 """``parents([set])``
656 """``parents([set])``
657 The set of all parents for all changesets in set, or the working directory.
657 The set of all parents for all changesets in set, or the working directory.
658 """
658 """
659 if x is None:
659 if x is None:
660 ps = tuple(p.rev() for p in repo[x].parents())
660 ps = tuple(p.rev() for p in repo[x].parents())
661 return [r for r in subset if r in ps]
661 return [r for r in subset if r in ps]
662
662
663 ps = set()
663 ps = set()
664 cl = repo.changelog
664 cl = repo.changelog
665 for r in getset(repo, range(len(repo)), x):
665 for r in getset(repo, range(len(repo)), x):
666 ps.update(cl.parentrevs(r))
666 ps.update(cl.parentrevs(r))
667 return [r for r in subset if r in ps]
667 return [r for r in subset if r in ps]
668
668
669 def parentspec(repo, subset, x, n):
669 def parentspec(repo, subset, x, n):
670 """``set^0``
670 """``set^0``
671 The set.
671 The set.
672 ``set^1`` (or ``set^``), ``set^2``
672 ``set^1`` (or ``set^``), ``set^2``
673 First or second parent, respectively, of all changesets in set.
673 First or second parent, respectively, of all changesets in set.
674 """
674 """
675 try:
675 try:
676 n = int(n[1])
676 n = int(n[1])
677 if n not in (0, 1, 2):
677 if n not in (0, 1, 2):
678 raise ValueError
678 raise ValueError
679 except ValueError:
679 except ValueError:
680 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
680 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
681 ps = set()
681 ps = set()
682 cl = repo.changelog
682 cl = repo.changelog
683 for r in getset(repo, subset, x):
683 for r in getset(repo, subset, x):
684 if n == 0:
684 if n == 0:
685 ps.add(r)
685 ps.add(r)
686 elif n == 1:
686 elif n == 1:
687 ps.add(cl.parentrevs(r)[0])
687 ps.add(cl.parentrevs(r)[0])
688 elif n == 2:
688 elif n == 2:
689 parents = cl.parentrevs(r)
689 parents = cl.parentrevs(r)
690 if len(parents) > 1:
690 if len(parents) > 1:
691 ps.add(parents[1])
691 ps.add(parents[1])
692 return [r for r in subset if r in ps]
692 return [r for r in subset if r in ps]
693
693
694 def present(repo, subset, x):
694 def present(repo, subset, x):
695 """``present(set)``
695 """``present(set)``
696 An empty set, if any revision in set isn't found; otherwise,
696 An empty set, if any revision in set isn't found; otherwise,
697 all revisions in set.
697 all revisions in set.
698 """
698 """
699 try:
699 try:
700 return getset(repo, subset, x)
700 return getset(repo, subset, x)
701 except error.RepoLookupError:
701 except error.RepoLookupError:
702 return []
702 return []
703
703
704 def removes(repo, subset, x):
704 def removes(repo, subset, x):
705 """``removes(pattern)``
705 """``removes(pattern)``
706 Changesets which remove files matching pattern.
706 Changesets which remove files matching pattern.
707 """
707 """
708 # i18n: "removes" is a keyword
708 # i18n: "removes" is a keyword
709 pat = getstring(x, _("removes requires a pattern"))
709 pat = getstring(x, _("removes requires a pattern"))
710 return checkstatus(repo, subset, pat, 2)
710 return checkstatus(repo, subset, pat, 2)
711
711
712 def rev(repo, subset, x):
712 def rev(repo, subset, x):
713 """``rev(number)``
713 """``rev(number)``
714 Revision with the given numeric identifier.
714 Revision with the given numeric identifier.
715 """
715 """
716 # i18n: "rev" is a keyword
716 # i18n: "rev" is a keyword
717 l = getargs(x, 1, 1, _("rev requires one argument"))
717 l = getargs(x, 1, 1, _("rev requires one argument"))
718 try:
718 try:
719 # i18n: "rev" is a keyword
719 # i18n: "rev" is a keyword
720 l = int(getstring(l[0], _("rev requires a number")))
720 l = int(getstring(l[0], _("rev requires a number")))
721 except ValueError:
721 except ValueError:
722 # i18n: "rev" is a keyword
722 # i18n: "rev" is a keyword
723 raise error.ParseError(_("rev expects a number"))
723 raise error.ParseError(_("rev expects a number"))
724 return [r for r in subset if r == l]
724 return [r for r in subset if r == l]
725
725
726 def reverse(repo, subset, x):
726 def reverse(repo, subset, x):
727 """``reverse(set)``
727 """``reverse(set)``
728 Reverse order of set.
728 Reverse order of set.
729 """
729 """
730 l = getset(repo, subset, x)
730 l = getset(repo, subset, x)
731 l.reverse()
731 l.reverse()
732 return l
732 return l
733
733
734 def roots(repo, subset, x):
734 def roots(repo, subset, x):
735 """``roots(set)``
735 """``roots(set)``
736 Changesets with no parent changeset in set.
736 Changesets with no parent changeset in set.
737 """
737 """
738 s = getset(repo, subset, x)
738 s = getset(repo, subset, x)
739 cs = set(children(repo, subset, x))
739 cs = set(children(repo, subset, x))
740 return [r for r in s if r not in cs]
740 return [r for r in s if r not in cs]
741
741
742 def sort(repo, subset, x):
742 def sort(repo, subset, x):
743 """``sort(set[, [-]key...])``
743 """``sort(set[, [-]key...])``
744 Sort set by keys. The default sort order is ascending, specify a key
744 Sort set by keys. The default sort order is ascending, specify a key
745 as ``-key`` to sort in descending order.
745 as ``-key`` to sort in descending order.
746
746
747 The keys can be:
747 The keys can be:
748
748
749 - ``rev`` for the revision number,
749 - ``rev`` for the revision number,
750 - ``branch`` for the branch name,
750 - ``branch`` for the branch name,
751 - ``desc`` for the commit message (description),
751 - ``desc`` for the commit message (description),
752 - ``user`` for user name (``author`` can be used as an alias),
752 - ``user`` for user name (``author`` can be used as an alias),
753 - ``date`` for the commit date
753 - ``date`` for the commit date
754 """
754 """
755 # i18n: "sort" is a keyword
755 # i18n: "sort" is a keyword
756 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
756 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
757 keys = "rev"
757 keys = "rev"
758 if len(l) == 2:
758 if len(l) == 2:
759 keys = getstring(l[1], _("sort spec must be a string"))
759 keys = getstring(l[1], _("sort spec must be a string"))
760
760
761 s = l[0]
761 s = l[0]
762 keys = keys.split()
762 keys = keys.split()
763 l = []
763 l = []
764 def invert(s):
764 def invert(s):
765 return "".join(chr(255 - ord(c)) for c in s)
765 return "".join(chr(255 - ord(c)) for c in s)
766 for r in getset(repo, subset, s):
766 for r in getset(repo, subset, s):
767 c = repo[r]
767 c = repo[r]
768 e = []
768 e = []
769 for k in keys:
769 for k in keys:
770 if k == 'rev':
770 if k == 'rev':
771 e.append(r)
771 e.append(r)
772 elif k == '-rev':
772 elif k == '-rev':
773 e.append(-r)
773 e.append(-r)
774 elif k == 'branch':
774 elif k == 'branch':
775 e.append(c.branch())
775 e.append(c.branch())
776 elif k == '-branch':
776 elif k == '-branch':
777 e.append(invert(c.branch()))
777 e.append(invert(c.branch()))
778 elif k == 'desc':
778 elif k == 'desc':
779 e.append(c.description())
779 e.append(c.description())
780 elif k == '-desc':
780 elif k == '-desc':
781 e.append(invert(c.description()))
781 e.append(invert(c.description()))
782 elif k in 'user author':
782 elif k in 'user author':
783 e.append(c.user())
783 e.append(c.user())
784 elif k in '-user -author':
784 elif k in '-user -author':
785 e.append(invert(c.user()))
785 e.append(invert(c.user()))
786 elif k == 'date':
786 elif k == 'date':
787 e.append(c.date()[0])
787 e.append(c.date()[0])
788 elif k == '-date':
788 elif k == '-date':
789 e.append(-c.date()[0])
789 e.append(-c.date()[0])
790 else:
790 else:
791 raise error.ParseError(_("unknown sort key %r") % k)
791 raise error.ParseError(_("unknown sort key %r") % k)
792 e.append(r)
792 e.append(r)
793 l.append(e)
793 l.append(e)
794 l.sort()
794 l.sort()
795 return [e[-1] for e in l]
795 return [e[-1] for e in l]
796
796
797 def tag(repo, subset, x):
797 def tag(repo, subset, x):
798 """``tag([name])``
798 """``tag([name])``
799 The specified tag by name, or all tagged revisions if no name is given.
799 The specified tag by name, or all tagged revisions if no name is given.
800 """
800 """
801 # i18n: "tag" is a keyword
801 # i18n: "tag" is a keyword
802 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
802 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
803 cl = repo.changelog
803 cl = repo.changelog
804 if args:
804 if args:
805 tn = getstring(args[0],
805 tn = getstring(args[0],
806 # i18n: "tag" is a keyword
806 # i18n: "tag" is a keyword
807 _('the argument to tag must be a string'))
807 _('the argument to tag must be a string'))
808 if not repo.tags().get(tn, None):
808 if not repo.tags().get(tn, None):
809 raise util.Abort(_("tag '%s' does not exist") % tn)
809 raise util.Abort(_("tag '%s' does not exist") % tn)
810 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
810 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
811 else:
811 else:
812 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
812 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
813 return [r for r in subset if r in s]
813 return [r for r in subset if r in s]
814
814
815 def tagged(repo, subset, x):
815 def tagged(repo, subset, x):
816 return tag(repo, subset, x)
816 return tag(repo, subset, x)
817
817
818 def user(repo, subset, x):
818 def user(repo, subset, x):
819 """``user(string)``
819 """``user(string)``
820 User name contains string. The match is case-insensitive.
820 User name contains string. The match is case-insensitive.
821 """
821 """
822 return author(repo, subset, x)
822 return author(repo, subset, x)
823
823
824 symbols = {
824 symbols = {
825 "adds": adds,
825 "adds": adds,
826 "all": getall,
826 "all": getall,
827 "ancestor": ancestor,
827 "ancestor": ancestor,
828 "ancestors": ancestors,
828 "ancestors": ancestors,
829 "author": author,
829 "author": author,
830 "bisected": bisected,
830 "bisected": bisected,
831 "bookmark": bookmark,
831 "bookmark": bookmark,
832 "branch": branch,
832 "branch": branch,
833 "children": children,
833 "children": children,
834 "closed": closed,
834 "closed": closed,
835 "contains": contains,
835 "contains": contains,
836 "date": date,
836 "date": date,
837 "desc": desc,
837 "desc": desc,
838 "descendants": descendants,
838 "descendants": descendants,
839 "file": hasfile,
839 "file": hasfile,
840 "filelog": filelog,
840 "filelog": filelog,
841 "follow": follow,
841 "follow": follow,
842 "grep": grep,
842 "grep": grep,
843 "head": head,
843 "head": head,
844 "heads": heads,
844 "heads": heads,
845 "id": node,
845 "id": node,
846 "keyword": keyword,
846 "keyword": keyword,
847 "last": last,
847 "last": last,
848 "limit": limit,
848 "limit": limit,
849 "max": maxrev,
849 "max": maxrev,
850 "merge": merge,
850 "merge": merge,
851 "min": minrev,
851 "min": minrev,
852 "modifies": modifies,
852 "modifies": modifies,
853 "outgoing": outgoing,
853 "outgoing": outgoing,
854 "p1": p1,
854 "p1": p1,
855 "p2": p2,
855 "p2": p2,
856 "parents": parents,
856 "parents": parents,
857 "present": present,
857 "present": present,
858 "removes": removes,
858 "removes": removes,
859 "rev": rev,
859 "rev": rev,
860 "reverse": reverse,
860 "reverse": reverse,
861 "roots": roots,
861 "roots": roots,
862 "sort": sort,
862 "sort": sort,
863 "tag": tag,
863 "tag": tag,
864 "tagged": tagged,
864 "tagged": tagged,
865 "user": user,
865 "user": user,
866 }
866 }
867
867
868 methods = {
868 methods = {
869 "range": rangeset,
869 "range": rangeset,
870 "string": stringset,
870 "string": stringset,
871 "symbol": symbolset,
871 "symbol": symbolset,
872 "and": andset,
872 "and": andset,
873 "or": orset,
873 "or": orset,
874 "not": notset,
874 "not": notset,
875 "list": listset,
875 "list": listset,
876 "func": func,
876 "func": func,
877 "ancestor": ancestorspec,
877 "ancestor": ancestorspec,
878 "parent": parentspec,
878 "parent": parentspec,
879 "parentpost": p1,
879 "parentpost": p1,
880 }
880 }
881
881
882 def optimize(x, small):
882 def optimize(x, small):
883 if x is None:
883 if x is None:
884 return 0, x
884 return 0, x
885
885
886 smallbonus = 1
886 smallbonus = 1
887 if small:
887 if small:
888 smallbonus = .5
888 smallbonus = .5
889
889
890 op = x[0]
890 op = x[0]
891 if op == 'minus':
891 if op == 'minus':
892 return optimize(('and', x[1], ('not', x[2])), small)
892 return optimize(('and', x[1], ('not', x[2])), small)
893 elif op == 'dagrange':
893 elif op == 'dagrange':
894 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
894 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
895 ('func', ('symbol', 'ancestors'), x[2])), small)
895 ('func', ('symbol', 'ancestors'), x[2])), small)
896 elif op == 'dagrangepre':
896 elif op == 'dagrangepre':
897 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
897 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
898 elif op == 'dagrangepost':
898 elif op == 'dagrangepost':
899 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
899 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
900 elif op == 'rangepre':
900 elif op == 'rangepre':
901 return optimize(('range', ('string', '0'), x[1]), small)
901 return optimize(('range', ('string', '0'), x[1]), small)
902 elif op == 'rangepost':
902 elif op == 'rangepost':
903 return optimize(('range', x[1], ('string', 'tip')), small)
903 return optimize(('range', x[1], ('string', 'tip')), small)
904 elif op == 'negate':
904 elif op == 'negate':
905 return optimize(('string',
905 return optimize(('string',
906 '-' + getstring(x[1], _("can't negate that"))), small)
906 '-' + getstring(x[1], _("can't negate that"))), small)
907 elif op in 'string symbol negate':
907 elif op in 'string symbol negate':
908 return smallbonus, x # single revisions are small
908 return smallbonus, x # single revisions are small
909 elif op == 'and' or op == 'dagrange':
909 elif op == 'and' or op == 'dagrange':
910 wa, ta = optimize(x[1], True)
910 wa, ta = optimize(x[1], True)
911 wb, tb = optimize(x[2], True)
911 wb, tb = optimize(x[2], True)
912 w = min(wa, wb)
912 w = min(wa, wb)
913 if wa > wb:
913 if wa > wb:
914 return w, (op, tb, ta)
914 return w, (op, tb, ta)
915 return w, (op, ta, tb)
915 return w, (op, ta, tb)
916 elif op == 'or':
916 elif op == 'or':
917 wa, ta = optimize(x[1], False)
917 wa, ta = optimize(x[1], False)
918 wb, tb = optimize(x[2], False)
918 wb, tb = optimize(x[2], False)
919 if wb < wa:
919 if wb < wa:
920 wb, wa = wa, wb
920 wb, wa = wa, wb
921 return max(wa, wb), (op, ta, tb)
921 return max(wa, wb), (op, ta, tb)
922 elif op == 'not':
922 elif op == 'not':
923 o = optimize(x[1], not small)
923 o = optimize(x[1], not small)
924 return o[0], (op, o[1])
924 return o[0], (op, o[1])
925 elif op == 'parentpost':
925 elif op == 'parentpost':
926 o = optimize(x[1], small)
926 o = optimize(x[1], small)
927 return o[0], (op, o[1])
927 return o[0], (op, o[1])
928 elif op == 'group':
928 elif op == 'group':
929 return optimize(x[1], small)
929 return optimize(x[1], small)
930 elif op in 'range list parent ancestorspec':
930 elif op in 'range list parent ancestorspec':
931 wa, ta = optimize(x[1], small)
931 wa, ta = optimize(x[1], small)
932 wb, tb = optimize(x[2], small)
932 wb, tb = optimize(x[2], small)
933 return wa + wb, (op, ta, tb)
933 return wa + wb, (op, ta, tb)
934 elif op == 'func':
934 elif op == 'func':
935 f = getstring(x[1], _("not a symbol"))
935 f = getstring(x[1], _("not a symbol"))
936 wa, ta = optimize(x[2], small)
936 wa, ta = optimize(x[2], small)
937 if f in ("author branch closed date desc file grep keyword "
937 if f in ("author branch closed date desc file grep keyword "
938 "outgoing user"):
938 "outgoing user"):
939 w = 10 # slow
939 w = 10 # slow
940 elif f in "modifies adds removes":
940 elif f in "modifies adds removes":
941 w = 30 # slower
941 w = 30 # slower
942 elif f == "contains":
942 elif f == "contains":
943 w = 100 # very slow
943 w = 100 # very slow
944 elif f == "ancestor":
944 elif f == "ancestor":
945 w = 1 * smallbonus
945 w = 1 * smallbonus
946 elif f in "reverse limit":
946 elif f in "reverse limit":
947 w = 0
947 w = 0
948 elif f in "sort":
948 elif f in "sort":
949 w = 10 # assume most sorts look at changelog
949 w = 10 # assume most sorts look at changelog
950 else:
950 else:
951 w = 1
951 w = 1
952 return w + wa, (op, x[1], ta)
952 return w + wa, (op, x[1], ta)
953 return 1, x
953 return 1, x
954
954
955 class revsetalias(object):
955 class revsetalias(object):
956 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
956 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
957 args = ()
957 args = None
958
958
959 def __init__(self, token, value):
959 def __init__(self, name, value):
960 '''Aliases like:
960 '''Aliases like:
961
961
962 h = heads(default)
962 h = heads(default)
963 b($1) = ancestors($1) - ancestors(default)
963 b($1) = ancestors($1) - ancestors(default)
964 '''
964 '''
965 if isinstance(token, tuple):
965 if isinstance(name, tuple): # parameter substitution
966 self.type, self.name = token
966 self.tree = name
967 else:
967 self.replacement = value
968 m = self.funcre.search(token)
968 else: # alias definition
969 m = self.funcre.search(name)
969 if m:
970 if m:
970 self.type = 'func'
971 self.tree = ('func', ('symbol', m.group(1)))
971 self.name = m.group(1)
972 self.args = [x.strip() for x in m.group(2).split(',')]
972 self.args = [x.strip() for x in m.group(2).split(',')]
973 for arg in self.args:
974 value = value.replace(arg, repr(arg))
973 else:
975 else:
974 self.type = 'symbol'
976 self.tree = ('symbol', name)
975 self.name = token
976
977
977 if isinstance(value, str):
978 for arg in self.args:
979 value = value.replace(arg, repr(arg))
980 self.replacement, pos = parse(value)
978 self.replacement, pos = parse(value)
981 if pos != len(value):
979 if pos != len(value):
982 raise error.ParseError(_('invalid token'), pos)
980 raise error.ParseError(_('invalid token'), pos)
983 else:
984 self.replacement = value
985
986 def match(self, tree):
987 if not tree:
988 return False
989 if tree == (self.type, self.name):
990 return True
991 if tree[0] != self.type:
992 return False
993 if len(tree) > 1 and tree[1] != ('symbol', self.name):
994 return False
995 # 'func' + funcname + args
996 if ((self.args and len(tree) != 3) or
997 (len(self.args) == 1 and tree[2][0] == 'list') or
998 (len(self.args) > 1 and (tree[2][0] != 'list' or
999 len(tree[2]) - 1 != len(self.args)))):
1000 raise error.ParseError(_('invalid amount of arguments'),
1001 len(tree) - 2)
1002 return True
1003
1004 def replace(self, tree):
1005 if tree == (self.type, self.name):
1006 return self.replacement
1007 result = self.replacement
1008 def getsubtree(i):
1009 if tree[2][0] == 'list':
1010 return tree[2][i + 1]
1011 return tree[i + 2]
1012 for i, v in enumerate(self.args):
1013 valalias = revsetalias(('string', v), getsubtree(i))
1014 result = valalias.process(result)
1015 return result
1016
981
1017 def process(self, tree):
982 def process(self, tree):
1018 if self.match(tree):
1019 return self.replace(tree)
1020 if isinstance(tree, tuple):
983 if isinstance(tree, tuple):
984 if self.args is None:
985 if tree == self.tree:
986 return self.replacement
987 elif tree[:2] == self.tree:
988 l = getlist(tree[2])
989 if len(l) != len(self.args):
990 raise error.ParseError(
991 _('invalid number of arguments: %s') % len(l))
992 result = self.replacement
993 for a, v in zip(self.args, l):
994 valalias = revsetalias(('string', a), v)
995 result = valalias.process(result)
996 return result
1021 return tuple(map(self.process, tree))
997 return tuple(map(self.process, tree))
1022 return tree
998 return tree
1023
999
1024 def findaliases(ui, tree):
1000 def findaliases(ui, tree):
1025 for k, v in ui.configitems('revsetalias'):
1001 for k, v in ui.configitems('revsetalias'):
1026 alias = revsetalias(k, v)
1002 alias = revsetalias(k, v)
1027 tree = alias.process(tree)
1003 tree = alias.process(tree)
1028 return tree
1004 return tree
1029
1005
1030 parse = parser.parser(tokenize, elements).parse
1006 parse = parser.parser(tokenize, elements).parse
1031
1007
1032 def match(ui, spec):
1008 def match(ui, spec):
1033 if not spec:
1009 if not spec:
1034 raise error.ParseError(_("empty query"))
1010 raise error.ParseError(_("empty query"))
1035 tree, pos = parse(spec)
1011 tree, pos = parse(spec)
1036 if (pos != len(spec)):
1012 if (pos != len(spec)):
1037 raise error.ParseError(_("invalid token"), pos)
1013 raise error.ParseError(_("invalid token"), pos)
1038 tree = findaliases(ui, tree)
1014 tree = findaliases(ui, tree)
1039 weight, tree = optimize(tree, True)
1015 weight, tree = optimize(tree, True)
1040 def mfunc(repo, subset):
1016 def mfunc(repo, subset):
1041 return getset(repo, subset, tree)
1017 return getset(repo, subset, tree)
1042 return mfunc
1018 return mfunc
1043
1019
1044 # tell hggettext to extract docstrings from these functions:
1020 # tell hggettext to extract docstrings from these functions:
1045 i18nfunctions = symbols.values()
1021 i18nfunctions = symbols.values()
@@ -1,447 +1,465
1 $ HGENCODING=utf-8
1 $ HGENCODING=utf-8
2 $ export HGENCODING
2 $ export HGENCODING
3
3
4 $ try() {
4 $ try() {
5 > hg debugrevspec --debug "$@"
5 > hg debugrevspec --debug "$@"
6 > }
6 > }
7
7
8 $ log() {
8 $ log() {
9 > hg log --template '{rev}\n' -r "$1"
9 > hg log --template '{rev}\n' -r "$1"
10 > }
10 > }
11
11
12 $ hg init repo
12 $ hg init repo
13 $ cd repo
13 $ cd repo
14
14
15 $ echo a > a
15 $ echo a > a
16 $ hg branch a
16 $ hg branch a
17 marked working directory as branch a
17 marked working directory as branch a
18 $ hg ci -Aqm0
18 $ hg ci -Aqm0
19
19
20 $ echo b > b
20 $ echo b > b
21 $ hg branch b
21 $ hg branch b
22 marked working directory as branch b
22 marked working directory as branch b
23 $ hg ci -Aqm1
23 $ hg ci -Aqm1
24
24
25 $ rm a
25 $ rm a
26 $ hg branch a-b-c-
26 $ hg branch a-b-c-
27 marked working directory as branch a-b-c-
27 marked working directory as branch a-b-c-
28 $ hg ci -Aqm2 -u Bob
28 $ hg ci -Aqm2 -u Bob
29
29
30 $ hg co 1
30 $ hg co 1
31 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
31 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
32 $ hg branch +a+b+c+
32 $ hg branch +a+b+c+
33 marked working directory as branch +a+b+c+
33 marked working directory as branch +a+b+c+
34 $ hg ci -Aqm3
34 $ hg ci -Aqm3
35
35
36 $ hg co 2 # interleave
36 $ hg co 2 # interleave
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 $ echo bb > b
38 $ echo bb > b
39 $ hg branch -- -a-b-c-
39 $ hg branch -- -a-b-c-
40 marked working directory as branch -a-b-c-
40 marked working directory as branch -a-b-c-
41 $ hg ci -Aqm4 -d "May 12 2005"
41 $ hg ci -Aqm4 -d "May 12 2005"
42
42
43 $ hg co 3
43 $ hg co 3
44 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
44 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
45 $ hg branch /a/b/c/
45 $ hg branch /a/b/c/
46 marked working directory as branch /a/b/c/
46 marked working directory as branch /a/b/c/
47 $ hg ci -Aqm"5 bug"
47 $ hg ci -Aqm"5 bug"
48
48
49 $ hg merge 4
49 $ hg merge 4
50 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
50 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
51 (branch merge, don't forget to commit)
51 (branch merge, don't forget to commit)
52 $ hg branch _a_b_c_
52 $ hg branch _a_b_c_
53 marked working directory as branch _a_b_c_
53 marked working directory as branch _a_b_c_
54 $ hg ci -Aqm"6 issue619"
54 $ hg ci -Aqm"6 issue619"
55
55
56 $ hg branch .a.b.c.
56 $ hg branch .a.b.c.
57 marked working directory as branch .a.b.c.
57 marked working directory as branch .a.b.c.
58 $ hg ci -Aqm7
58 $ hg ci -Aqm7
59
59
60 $ hg branch all
60 $ hg branch all
61 marked working directory as branch all
61 marked working directory as branch all
62 $ hg ci --close-branch -Aqm8
62 $ hg ci --close-branch -Aqm8
63 abort: can only close branch heads
63 abort: can only close branch heads
64 [255]
64 [255]
65
65
66 $ hg co 4
66 $ hg co 4
67 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
67 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
68 $ hg branch Γ©
68 $ hg branch Γ©
69 marked working directory as branch \xc3\xa9 (esc)
69 marked working directory as branch \xc3\xa9 (esc)
70 $ hg ci -Aqm9
70 $ hg ci -Aqm9
71
71
72 $ hg tag -r6 1.0
72 $ hg tag -r6 1.0
73
73
74 $ hg clone --quiet -U -r 7 . ../remote1
74 $ hg clone --quiet -U -r 7 . ../remote1
75 $ hg clone --quiet -U -r 8 . ../remote2
75 $ hg clone --quiet -U -r 8 . ../remote2
76 $ echo "[paths]" >> .hg/hgrc
76 $ echo "[paths]" >> .hg/hgrc
77 $ echo "default = ../remote1" >> .hg/hgrc
77 $ echo "default = ../remote1" >> .hg/hgrc
78
78
79 names that should work without quoting
79 names that should work without quoting
80
80
81 $ try a
81 $ try a
82 ('symbol', 'a')
82 ('symbol', 'a')
83 0
83 0
84 $ try b-a
84 $ try b-a
85 ('minus', ('symbol', 'b'), ('symbol', 'a'))
85 ('minus', ('symbol', 'b'), ('symbol', 'a'))
86 1
86 1
87 $ try _a_b_c_
87 $ try _a_b_c_
88 ('symbol', '_a_b_c_')
88 ('symbol', '_a_b_c_')
89 6
89 6
90 $ try _a_b_c_-a
90 $ try _a_b_c_-a
91 ('minus', ('symbol', '_a_b_c_'), ('symbol', 'a'))
91 ('minus', ('symbol', '_a_b_c_'), ('symbol', 'a'))
92 6
92 6
93 $ try .a.b.c.
93 $ try .a.b.c.
94 ('symbol', '.a.b.c.')
94 ('symbol', '.a.b.c.')
95 7
95 7
96 $ try .a.b.c.-a
96 $ try .a.b.c.-a
97 ('minus', ('symbol', '.a.b.c.'), ('symbol', 'a'))
97 ('minus', ('symbol', '.a.b.c.'), ('symbol', 'a'))
98 7
98 7
99 $ try -- '-a-b-c-' # complains
99 $ try -- '-a-b-c-' # complains
100 hg: parse error at 7: not a prefix: end
100 hg: parse error at 7: not a prefix: end
101 [255]
101 [255]
102 $ log -a-b-c- # succeeds with fallback
102 $ log -a-b-c- # succeeds with fallback
103 4
103 4
104 $ try -- -a-b-c--a # complains
104 $ try -- -a-b-c--a # complains
105 ('minus', ('minus', ('minus', ('negate', ('symbol', 'a')), ('symbol', 'b')), ('symbol', 'c')), ('negate', ('symbol', 'a')))
105 ('minus', ('minus', ('minus', ('negate', ('symbol', 'a')), ('symbol', 'b')), ('symbol', 'c')), ('negate', ('symbol', 'a')))
106 abort: unknown revision '-a'!
106 abort: unknown revision '-a'!
107 [255]
107 [255]
108 $ try Γ©
108 $ try Γ©
109 ('symbol', '\xc3\xa9')
109 ('symbol', '\xc3\xa9')
110 9
110 9
111
111
112 quoting needed
112 quoting needed
113
113
114 $ try '"-a-b-c-"-a'
114 $ try '"-a-b-c-"-a'
115 ('minus', ('string', '-a-b-c-'), ('symbol', 'a'))
115 ('minus', ('string', '-a-b-c-'), ('symbol', 'a'))
116 4
116 4
117
117
118 $ log '1 or 2'
118 $ log '1 or 2'
119 1
119 1
120 2
120 2
121 $ log '1|2'
121 $ log '1|2'
122 1
122 1
123 2
123 2
124 $ log '1 and 2'
124 $ log '1 and 2'
125 $ log '1&2'
125 $ log '1&2'
126 $ try '1&2|3' # precedence - and is higher
126 $ try '1&2|3' # precedence - and is higher
127 ('or', ('and', ('symbol', '1'), ('symbol', '2')), ('symbol', '3'))
127 ('or', ('and', ('symbol', '1'), ('symbol', '2')), ('symbol', '3'))
128 3
128 3
129 $ try '1|2&3'
129 $ try '1|2&3'
130 ('or', ('symbol', '1'), ('and', ('symbol', '2'), ('symbol', '3')))
130 ('or', ('symbol', '1'), ('and', ('symbol', '2'), ('symbol', '3')))
131 1
131 1
132 $ try '1&2&3' # associativity
132 $ try '1&2&3' # associativity
133 ('and', ('and', ('symbol', '1'), ('symbol', '2')), ('symbol', '3'))
133 ('and', ('and', ('symbol', '1'), ('symbol', '2')), ('symbol', '3'))
134 $ try '1|(2|3)'
134 $ try '1|(2|3)'
135 ('or', ('symbol', '1'), ('group', ('or', ('symbol', '2'), ('symbol', '3'))))
135 ('or', ('symbol', '1'), ('group', ('or', ('symbol', '2'), ('symbol', '3'))))
136 1
136 1
137 2
137 2
138 3
138 3
139 $ log '1.0' # tag
139 $ log '1.0' # tag
140 6
140 6
141 $ log 'a' # branch
141 $ log 'a' # branch
142 0
142 0
143 $ log '2785f51ee'
143 $ log '2785f51ee'
144 0
144 0
145 $ log 'date(2005)'
145 $ log 'date(2005)'
146 4
146 4
147 $ log 'date(this is a test)'
147 $ log 'date(this is a test)'
148 hg: parse error at 10: unexpected token: symbol
148 hg: parse error at 10: unexpected token: symbol
149 [255]
149 [255]
150 $ log 'date()'
150 $ log 'date()'
151 hg: parse error: date requires a string
151 hg: parse error: date requires a string
152 [255]
152 [255]
153 $ log 'date'
153 $ log 'date'
154 hg: parse error: can't use date here
154 hg: parse error: can't use date here
155 [255]
155 [255]
156 $ log 'date('
156 $ log 'date('
157 hg: parse error at 5: not a prefix: end
157 hg: parse error at 5: not a prefix: end
158 [255]
158 [255]
159 $ log 'date(tip)'
159 $ log 'date(tip)'
160 abort: invalid date: 'tip'
160 abort: invalid date: 'tip'
161 [255]
161 [255]
162 $ log '"date"'
162 $ log '"date"'
163 abort: unknown revision 'date'!
163 abort: unknown revision 'date'!
164 [255]
164 [255]
165 $ log 'date(2005) and 1::'
165 $ log 'date(2005) and 1::'
166 4
166 4
167
167
168 $ log 'ancestor(1)'
168 $ log 'ancestor(1)'
169 hg: parse error: ancestor requires two arguments
169 hg: parse error: ancestor requires two arguments
170 [255]
170 [255]
171 $ log 'ancestor(4,5)'
171 $ log 'ancestor(4,5)'
172 1
172 1
173 $ log 'ancestor(4,5) and 4'
173 $ log 'ancestor(4,5) and 4'
174 $ log 'ancestors(5)'
174 $ log 'ancestors(5)'
175 0
175 0
176 1
176 1
177 3
177 3
178 5
178 5
179 $ log 'author(bob)'
179 $ log 'author(bob)'
180 2
180 2
181 $ log 'branch(Γ©)'
181 $ log 'branch(Γ©)'
182 8
182 8
183 9
183 9
184 $ log 'children(ancestor(4,5))'
184 $ log 'children(ancestor(4,5))'
185 2
185 2
186 3
186 3
187 $ log 'closed()'
187 $ log 'closed()'
188 $ log 'contains(a)'
188 $ log 'contains(a)'
189 0
189 0
190 1
190 1
191 3
191 3
192 5
192 5
193 $ log 'desc(B)'
193 $ log 'desc(B)'
194 5
194 5
195 $ log 'descendants(2 or 3)'
195 $ log 'descendants(2 or 3)'
196 2
196 2
197 3
197 3
198 4
198 4
199 5
199 5
200 6
200 6
201 7
201 7
202 8
202 8
203 9
203 9
204 $ log 'file(b)'
204 $ log 'file(b)'
205 1
205 1
206 4
206 4
207 $ log 'follow()'
207 $ log 'follow()'
208 0
208 0
209 1
209 1
210 2
210 2
211 4
211 4
212 8
212 8
213 9
213 9
214 $ log 'grep("issue\d+")'
214 $ log 'grep("issue\d+")'
215 6
215 6
216 $ try 'grep("(")' # invalid regular expression
216 $ try 'grep("(")' # invalid regular expression
217 ('func', ('symbol', 'grep'), ('string', '('))
217 ('func', ('symbol', 'grep'), ('string', '('))
218 hg: parse error: invalid match pattern: unbalanced parenthesis
218 hg: parse error: invalid match pattern: unbalanced parenthesis
219 [255]
219 [255]
220 $ try 'grep("\bissue\d+")'
220 $ try 'grep("\bissue\d+")'
221 ('func', ('symbol', 'grep'), ('string', '\x08issue\\d+'))
221 ('func', ('symbol', 'grep'), ('string', '\x08issue\\d+'))
222 $ try 'grep(r"\bissue\d+")'
222 $ try 'grep(r"\bissue\d+")'
223 ('func', ('symbol', 'grep'), ('string', '\\bissue\\d+'))
223 ('func', ('symbol', 'grep'), ('string', '\\bissue\\d+'))
224 6
224 6
225 $ try 'grep(r"\")'
225 $ try 'grep(r"\")'
226 hg: parse error at 7: unterminated string
226 hg: parse error at 7: unterminated string
227 [255]
227 [255]
228 $ log 'head()'
228 $ log 'head()'
229 0
229 0
230 1
230 1
231 2
231 2
232 3
232 3
233 4
233 4
234 5
234 5
235 6
235 6
236 7
236 7
237 9
237 9
238 $ log 'heads(6::)'
238 $ log 'heads(6::)'
239 7
239 7
240 $ log 'keyword(issue)'
240 $ log 'keyword(issue)'
241 6
241 6
242 $ log 'limit(head(), 1)'
242 $ log 'limit(head(), 1)'
243 0
243 0
244 $ log 'max(contains(a))'
244 $ log 'max(contains(a))'
245 5
245 5
246 $ log 'min(contains(a))'
246 $ log 'min(contains(a))'
247 0
247 0
248 $ log 'merge()'
248 $ log 'merge()'
249 6
249 6
250 $ log 'modifies(b)'
250 $ log 'modifies(b)'
251 4
251 4
252 $ log 'id(5)'
252 $ log 'id(5)'
253 2
253 2
254 $ log 'outgoing()'
254 $ log 'outgoing()'
255 8
255 8
256 9
256 9
257 $ log 'outgoing("../remote1")'
257 $ log 'outgoing("../remote1")'
258 8
258 8
259 9
259 9
260 $ log 'outgoing("../remote2")'
260 $ log 'outgoing("../remote2")'
261 3
261 3
262 5
262 5
263 6
263 6
264 7
264 7
265 9
265 9
266 $ log 'p1(merge())'
266 $ log 'p1(merge())'
267 5
267 5
268 $ log 'p2(merge())'
268 $ log 'p2(merge())'
269 4
269 4
270 $ log 'parents(merge())'
270 $ log 'parents(merge())'
271 4
271 4
272 5
272 5
273 $ log 'removes(a)'
273 $ log 'removes(a)'
274 2
274 2
275 6
275 6
276 $ log 'roots(all())'
276 $ log 'roots(all())'
277 0
277 0
278 $ log 'reverse(2 or 3 or 4 or 5)'
278 $ log 'reverse(2 or 3 or 4 or 5)'
279 5
279 5
280 4
280 4
281 3
281 3
282 2
282 2
283 $ log 'rev(5)'
283 $ log 'rev(5)'
284 5
284 5
285 $ log 'sort(limit(reverse(all()), 3))'
285 $ log 'sort(limit(reverse(all()), 3))'
286 7
286 7
287 8
287 8
288 9
288 9
289 $ log 'sort(2 or 3 or 4 or 5, date)'
289 $ log 'sort(2 or 3 or 4 or 5, date)'
290 2
290 2
291 3
291 3
292 5
292 5
293 4
293 4
294 $ log 'tagged()'
294 $ log 'tagged()'
295 6
295 6
296 $ log 'tag()'
296 $ log 'tag()'
297 6
297 6
298 $ log 'tag(1.0)'
298 $ log 'tag(1.0)'
299 6
299 6
300 $ log 'tag(tip)'
300 $ log 'tag(tip)'
301 9
301 9
302 $ log 'tag(unknown)'
302 $ log 'tag(unknown)'
303 abort: tag 'unknown' does not exist
303 abort: tag 'unknown' does not exist
304 [255]
304 [255]
305 $ log 'branch(unknown)'
305 $ log 'branch(unknown)'
306 abort: unknown revision 'unknown'!
306 abort: unknown revision 'unknown'!
307 [255]
307 [255]
308 $ log 'user(bob)'
308 $ log 'user(bob)'
309 2
309 2
310
310
311 $ log '4::8'
311 $ log '4::8'
312 4
312 4
313 8
313 8
314 $ log '4:8'
314 $ log '4:8'
315 4
315 4
316 5
316 5
317 6
317 6
318 7
318 7
319 8
319 8
320
320
321 $ log 'sort(!merge() & (modifies(b) | user(bob) | keyword(bug) | keyword(issue) & 1::9), "-date")'
321 $ log 'sort(!merge() & (modifies(b) | user(bob) | keyword(bug) | keyword(issue) & 1::9), "-date")'
322 4
322 4
323 2
323 2
324 5
324 5
325
325
326 $ log 'not 0 and 0:2'
326 $ log 'not 0 and 0:2'
327 1
327 1
328 2
328 2
329 $ log 'not 1 and 0:2'
329 $ log 'not 1 and 0:2'
330 0
330 0
331 2
331 2
332 $ log 'not 2 and 0:2'
332 $ log 'not 2 and 0:2'
333 0
333 0
334 1
334 1
335 $ log '(1 and 2)::'
335 $ log '(1 and 2)::'
336 $ log '(1 and 2):'
336 $ log '(1 and 2):'
337 $ log '(1 and 2):3'
337 $ log '(1 and 2):3'
338 $ log 'sort(head(), -rev)'
338 $ log 'sort(head(), -rev)'
339 9
339 9
340 7
340 7
341 6
341 6
342 5
342 5
343 4
343 4
344 3
344 3
345 2
345 2
346 1
346 1
347 0
347 0
348 $ log '4::8 - 8'
348 $ log '4::8 - 8'
349 4
349 4
350
350
351 issue2437
351 issue2437
352
352
353 $ log '3 and p1(5)'
353 $ log '3 and p1(5)'
354 3
354 3
355 $ log '4 and p2(6)'
355 $ log '4 and p2(6)'
356 4
356 4
357 $ log '1 and parents(:2)'
357 $ log '1 and parents(:2)'
358 1
358 1
359 $ log '2 and children(1:)'
359 $ log '2 and children(1:)'
360 2
360 2
361 $ log 'roots(all()) or roots(all())'
361 $ log 'roots(all()) or roots(all())'
362 0
362 0
363 $ log 'heads(branch(Γ©)) or heads(branch(Γ©))'
363 $ log 'heads(branch(Γ©)) or heads(branch(Γ©))'
364 9
364 9
365 $ log 'ancestors(8) and (heads(branch("-a-b-c-")) or heads(branch(Γ©)))'
365 $ log 'ancestors(8) and (heads(branch("-a-b-c-")) or heads(branch(Γ©)))'
366 4
366 4
367
367
368 issue2654: report a parse error if the revset was not completely parsed
368 issue2654: report a parse error if the revset was not completely parsed
369
369
370 $ log '1 OR 2'
370 $ log '1 OR 2'
371 hg: parse error at 2: invalid token
371 hg: parse error at 2: invalid token
372 [255]
372 [255]
373
373
374 or operator should preserve ordering:
374 or operator should preserve ordering:
375 $ log 'reverse(2::4) or tip'
375 $ log 'reverse(2::4) or tip'
376 4
376 4
377 2
377 2
378 9
378 9
379
379
380 parentrevspec
380 parentrevspec
381
381
382 $ log 'merge()^0'
382 $ log 'merge()^0'
383 6
383 6
384 $ log 'merge()^'
384 $ log 'merge()^'
385 5
385 5
386 $ log 'merge()^1'
386 $ log 'merge()^1'
387 5
387 5
388 $ log 'merge()^2'
388 $ log 'merge()^2'
389 4
389 4
390 $ log 'merge()^^'
390 $ log 'merge()^^'
391 3
391 3
392 $ log 'merge()^1^'
392 $ log 'merge()^1^'
393 3
393 3
394 $ log 'merge()^^^'
394 $ log 'merge()^^^'
395 1
395 1
396
396
397 $ log 'merge()~0'
397 $ log 'merge()~0'
398 6
398 6
399 $ log 'merge()~1'
399 $ log 'merge()~1'
400 5
400 5
401 $ log 'merge()~2'
401 $ log 'merge()~2'
402 3
402 3
403 $ log 'merge()~2^1'
403 $ log 'merge()~2^1'
404 1
404 1
405 $ log 'merge()~3'
405 $ log 'merge()~3'
406 1
406 1
407
407
408 $ log '(-3:tip)^'
408 $ log '(-3:tip)^'
409 4
409 4
410 6
410 6
411 8
411 8
412
412
413 $ log 'tip^foo'
413 $ log 'tip^foo'
414 hg: parse error: ^ expects a number 0, 1, or 2
414 hg: parse error: ^ expects a number 0, 1, or 2
415 [255]
415 [255]
416
416
417 aliases:
417 aliases:
418
418
419 $ echo '[revsetalias]' >> .hg/hgrc
419 $ echo '[revsetalias]' >> .hg/hgrc
420 $ echo 'm = merge()' >> .hg/hgrc
420 $ echo 'm = merge()' >> .hg/hgrc
421 $ echo 'd($1) = reverse(sort($1, date))' >> .hg/hgrc
421 $ echo 'd($1) = reverse(sort($1, date))' >> .hg/hgrc
422 $ echo 'rs(ARG1, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
422 $ echo 'rs(ARG1, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
423 $ echo 'rs4(ARG1, ARGA, ARGB, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
423
424
424 $ try m
425 $ try m
425 ('symbol', 'm')
426 ('symbol', 'm')
426 ('func', ('symbol', 'merge'), None)
427 ('func', ('symbol', 'merge'), None)
427 6
428 6
428 $ try 'd(2:5)'
429 $ try 'd(2:5)'
429 ('func', ('symbol', 'd'), ('range', ('symbol', '2'), ('symbol', '5')))
430 ('func', ('symbol', 'd'), ('range', ('symbol', '2'), ('symbol', '5')))
430 ('func', ('symbol', 'reverse'), ('func', ('symbol', 'sort'), ('list', ('range', ('symbol', '2'), ('symbol', '5')), ('symbol', 'date'))))
431 ('func', ('symbol', 'reverse'), ('func', ('symbol', 'sort'), ('list', ('range', ('symbol', '2'), ('symbol', '5')), ('symbol', 'date'))))
431 4
432 4
432 5
433 5
433 3
434 3
434 2
435 2
435 $ try 'rs(2 or 3, date)'
436 $ try 'rs(2 or 3, date)'
436 ('func', ('symbol', 'rs'), ('list', ('or', ('symbol', '2'), ('symbol', '3')), ('symbol', 'date')))
437 ('func', ('symbol', 'rs'), ('list', ('or', ('symbol', '2'), ('symbol', '3')), ('symbol', 'date')))
437 ('func', ('symbol', 'reverse'), ('func', ('symbol', 'sort'), ('list', ('or', ('symbol', '2'), ('symbol', '3')), ('symbol', 'date'))))
438 ('func', ('symbol', 'reverse'), ('func', ('symbol', 'sort'), ('list', ('or', ('symbol', '2'), ('symbol', '3')), ('symbol', 'date'))))
438 3
439 3
439 2
440 2
441 $ try 'rs()'
442 ('func', ('symbol', 'rs'), None)
443 hg: parse error: invalid number of arguments: 0
444 [255]
445 $ try 'rs(2)'
446 ('func', ('symbol', 'rs'), ('symbol', '2'))
447 hg: parse error: invalid number of arguments: 1
448 [255]
449 $ try 'rs(2, data, 7)'
450 ('func', ('symbol', 'rs'), ('list', ('list', ('symbol', '2'), ('symbol', 'data')), ('symbol', '7')))
451 hg: parse error: invalid number of arguments: 3
452 [255]
453 $ try 'rs4(2 or 3, x, x, date)'
454 ('func', ('symbol', 'rs4'), ('list', ('list', ('list', ('or', ('symbol', '2'), ('symbol', '3')), ('symbol', 'x')), ('symbol', 'x')), ('symbol', 'date')))
455 ('func', ('symbol', 'reverse'), ('func', ('symbol', 'sort'), ('list', ('or', ('symbol', '2'), ('symbol', '3')), ('symbol', 'date'))))
456 3
457 2
440
458
441 issue2549 - correct optimizations
459 issue2549 - correct optimizations
442
460
443 $ log 'limit(1 or 2 or 3, 2) and not 2'
461 $ log 'limit(1 or 2 or 3, 2) and not 2'
444 1
462 1
445 $ log 'max(1 or 2) and not 2'
463 $ log 'max(1 or 2) and not 2'
446 $ log 'min(1 or 2) and not 1'
464 $ log 'min(1 or 2) and not 1'
447 $ log 'last(1 or 2, 1) and not 2'
465 $ log 'last(1 or 2, 1) and not 2'
General Comments 0
You need to be logged in to leave comments. Login now