##// END OF EJS Templates
revset: fix alias substitution recursion (issue3240)...
Patrick Mezard -
r16096:b8be4506 default
parent child Browse files
Show More
@@ -1,1225 +1,1264 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 as nodemod
10 import node as nodemod
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 elements = {
16 elements = {
17 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
17 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
18 "~": (18, None, ("ancestor", 18)),
18 "~": (18, None, ("ancestor", 18)),
19 "^": (18, None, ("parent", 18), ("parentpost", 18)),
19 "^": (18, None, ("parent", 18), ("parentpost", 18)),
20 "-": (5, ("negate", 19), ("minus", 5)),
20 "-": (5, ("negate", 19), ("minus", 5)),
21 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
21 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
22 ("dagrangepost", 17)),
22 ("dagrangepost", 17)),
23 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
23 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
24 ("dagrangepost", 17)),
24 ("dagrangepost", 17)),
25 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
25 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
26 "not": (10, ("not", 10)),
26 "not": (10, ("not", 10)),
27 "!": (10, ("not", 10)),
27 "!": (10, ("not", 10)),
28 "and": (5, None, ("and", 5)),
28 "and": (5, None, ("and", 5)),
29 "&": (5, None, ("and", 5)),
29 "&": (5, None, ("and", 5)),
30 "or": (4, None, ("or", 4)),
30 "or": (4, None, ("or", 4)),
31 "|": (4, None, ("or", 4)),
31 "|": (4, None, ("or", 4)),
32 "+": (4, None, ("or", 4)),
32 "+": (4, None, ("or", 4)),
33 ",": (2, None, ("list", 2)),
33 ",": (2, None, ("list", 2)),
34 ")": (0, None, None),
34 ")": (0, None, None),
35 "symbol": (0, ("symbol",), None),
35 "symbol": (0, ("symbol",), None),
36 "string": (0, ("string",), None),
36 "string": (0, ("string",), None),
37 "end": (0, None, None),
37 "end": (0, None, None),
38 }
38 }
39
39
40 keywords = set(['and', 'or', 'not'])
40 keywords = set(['and', 'or', 'not'])
41
41
42 def tokenize(program):
42 def tokenize(program):
43 pos, l = 0, len(program)
43 pos, l = 0, len(program)
44 while pos < l:
44 while pos < l:
45 c = program[pos]
45 c = program[pos]
46 if c.isspace(): # skip inter-token whitespace
46 if c.isspace(): # skip inter-token whitespace
47 pass
47 pass
48 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
48 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
49 yield ('::', None, pos)
49 yield ('::', None, pos)
50 pos += 1 # skip ahead
50 pos += 1 # skip ahead
51 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
51 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
52 yield ('..', None, pos)
52 yield ('..', None, pos)
53 pos += 1 # skip ahead
53 pos += 1 # skip ahead
54 elif c in "():,-|&+!~^": # handle simple operators
54 elif c in "():,-|&+!~^": # handle simple operators
55 yield (c, None, pos)
55 yield (c, None, pos)
56 elif (c in '"\'' or c == 'r' and
56 elif (c in '"\'' or c == 'r' and
57 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
57 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
58 if c == 'r':
58 if c == 'r':
59 pos += 1
59 pos += 1
60 c = program[pos]
60 c = program[pos]
61 decode = lambda x: x
61 decode = lambda x: x
62 else:
62 else:
63 decode = lambda x: x.decode('string-escape')
63 decode = lambda x: x.decode('string-escape')
64 pos += 1
64 pos += 1
65 s = pos
65 s = pos
66 while pos < l: # find closing quote
66 while pos < l: # find closing quote
67 d = program[pos]
67 d = program[pos]
68 if d == '\\': # skip over escaped characters
68 if d == '\\': # skip over escaped characters
69 pos += 2
69 pos += 2
70 continue
70 continue
71 if d == c:
71 if d == c:
72 yield ('string', decode(program[s:pos]), s)
72 yield ('string', decode(program[s:pos]), s)
73 break
73 break
74 pos += 1
74 pos += 1
75 else:
75 else:
76 raise error.ParseError(_("unterminated string"), s)
76 raise error.ParseError(_("unterminated string"), s)
77 elif c.isalnum() or c in '._' or ord(c) > 127: # gather up a symbol/keyword
77 elif c.isalnum() or c in '._' or ord(c) > 127: # gather up a symbol/keyword
78 s = pos
78 s = pos
79 pos += 1
79 pos += 1
80 while pos < l: # find end of symbol
80 while pos < l: # find end of symbol
81 d = program[pos]
81 d = program[pos]
82 if not (d.isalnum() or d in "._/" or ord(d) > 127):
82 if not (d.isalnum() or d in "._/" or ord(d) > 127):
83 break
83 break
84 if d == '.' and program[pos - 1] == '.': # special case for ..
84 if d == '.' and program[pos - 1] == '.': # special case for ..
85 pos -= 1
85 pos -= 1
86 break
86 break
87 pos += 1
87 pos += 1
88 sym = program[s:pos]
88 sym = program[s:pos]
89 if sym in keywords: # operator keywords
89 if sym in keywords: # operator keywords
90 yield (sym, None, s)
90 yield (sym, None, s)
91 else:
91 else:
92 yield ('symbol', sym, s)
92 yield ('symbol', sym, s)
93 pos -= 1
93 pos -= 1
94 else:
94 else:
95 raise error.ParseError(_("syntax error"), pos)
95 raise error.ParseError(_("syntax error"), pos)
96 pos += 1
96 pos += 1
97 yield ('end', None, pos)
97 yield ('end', None, pos)
98
98
99 # helpers
99 # helpers
100
100
101 def getstring(x, err):
101 def getstring(x, err):
102 if x and (x[0] == 'string' or x[0] == 'symbol'):
102 if x and (x[0] == 'string' or x[0] == 'symbol'):
103 return x[1]
103 return x[1]
104 raise error.ParseError(err)
104 raise error.ParseError(err)
105
105
106 def getlist(x):
106 def getlist(x):
107 if not x:
107 if not x:
108 return []
108 return []
109 if x[0] == 'list':
109 if x[0] == 'list':
110 return getlist(x[1]) + [x[2]]
110 return getlist(x[1]) + [x[2]]
111 return [x]
111 return [x]
112
112
113 def getargs(x, min, max, err):
113 def getargs(x, min, max, err):
114 l = getlist(x)
114 l = getlist(x)
115 if len(l) < min or len(l) > max:
115 if len(l) < min or len(l) > max:
116 raise error.ParseError(err)
116 raise error.ParseError(err)
117 return l
117 return l
118
118
119 def getset(repo, subset, x):
119 def getset(repo, subset, x):
120 if not x:
120 if not x:
121 raise error.ParseError(_("missing argument"))
121 raise error.ParseError(_("missing argument"))
122 return methods[x[0]](repo, subset, *x[1:])
122 return methods[x[0]](repo, subset, *x[1:])
123
123
124 # operator methods
124 # operator methods
125
125
126 def stringset(repo, subset, x):
126 def stringset(repo, subset, x):
127 x = repo[x].rev()
127 x = repo[x].rev()
128 if x == -1 and len(subset) == len(repo):
128 if x == -1 and len(subset) == len(repo):
129 return [-1]
129 return [-1]
130 if len(subset) == len(repo) or x in subset:
130 if len(subset) == len(repo) or x in subset:
131 return [x]
131 return [x]
132 return []
132 return []
133
133
134 def symbolset(repo, subset, x):
134 def symbolset(repo, subset, x):
135 if x in symbols:
135 if x in symbols:
136 raise error.ParseError(_("can't use %s here") % x)
136 raise error.ParseError(_("can't use %s here") % x)
137 return stringset(repo, subset, x)
137 return stringset(repo, subset, x)
138
138
139 def rangeset(repo, subset, x, y):
139 def rangeset(repo, subset, x, y):
140 m = getset(repo, subset, x)
140 m = getset(repo, subset, x)
141 if not m:
141 if not m:
142 m = getset(repo, range(len(repo)), x)
142 m = getset(repo, range(len(repo)), x)
143
143
144 n = getset(repo, subset, y)
144 n = getset(repo, subset, y)
145 if not n:
145 if not n:
146 n = getset(repo, range(len(repo)), y)
146 n = getset(repo, range(len(repo)), y)
147
147
148 if not m or not n:
148 if not m or not n:
149 return []
149 return []
150 m, n = m[0], n[-1]
150 m, n = m[0], n[-1]
151
151
152 if m < n:
152 if m < n:
153 r = range(m, n + 1)
153 r = range(m, n + 1)
154 else:
154 else:
155 r = range(m, n - 1, -1)
155 r = range(m, n - 1, -1)
156 s = set(subset)
156 s = set(subset)
157 return [x for x in r if x in s]
157 return [x for x in r if x in s]
158
158
159 def andset(repo, subset, x, y):
159 def andset(repo, subset, x, y):
160 return getset(repo, getset(repo, subset, x), y)
160 return getset(repo, getset(repo, subset, x), y)
161
161
162 def orset(repo, subset, x, y):
162 def orset(repo, subset, x, y):
163 xl = getset(repo, subset, x)
163 xl = getset(repo, subset, x)
164 s = set(xl)
164 s = set(xl)
165 yl = getset(repo, [r for r in subset if r not in s], y)
165 yl = getset(repo, [r for r in subset if r not in s], y)
166 return xl + yl
166 return xl + yl
167
167
168 def notset(repo, subset, x):
168 def notset(repo, subset, x):
169 s = set(getset(repo, subset, x))
169 s = set(getset(repo, subset, x))
170 return [r for r in subset if r not in s]
170 return [r for r in subset if r not in s]
171
171
172 def listset(repo, subset, a, b):
172 def listset(repo, subset, a, b):
173 raise error.ParseError(_("can't use a list in this context"))
173 raise error.ParseError(_("can't use a list in this context"))
174
174
175 def func(repo, subset, a, b):
175 def func(repo, subset, a, b):
176 if a[0] == 'symbol' and a[1] in symbols:
176 if a[0] == 'symbol' and a[1] in symbols:
177 return symbols[a[1]](repo, subset, b)
177 return symbols[a[1]](repo, subset, b)
178 raise error.ParseError(_("not a function: %s") % a[1])
178 raise error.ParseError(_("not a function: %s") % a[1])
179
179
180 # functions
180 # functions
181
181
182 def adds(repo, subset, x):
182 def adds(repo, subset, x):
183 """``adds(pattern)``
183 """``adds(pattern)``
184 Changesets that add a file matching pattern.
184 Changesets that add a file matching pattern.
185 """
185 """
186 # i18n: "adds" is a keyword
186 # i18n: "adds" is a keyword
187 pat = getstring(x, _("adds requires a pattern"))
187 pat = getstring(x, _("adds requires a pattern"))
188 return checkstatus(repo, subset, pat, 1)
188 return checkstatus(repo, subset, pat, 1)
189
189
190 def ancestor(repo, subset, x):
190 def ancestor(repo, subset, x):
191 """``ancestor(single, single)``
191 """``ancestor(single, single)``
192 Greatest common ancestor of the two changesets.
192 Greatest common ancestor of the two changesets.
193 """
193 """
194 # i18n: "ancestor" is a keyword
194 # i18n: "ancestor" is a keyword
195 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
195 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
196 r = range(len(repo))
196 r = range(len(repo))
197 a = getset(repo, r, l[0])
197 a = getset(repo, r, l[0])
198 b = getset(repo, r, l[1])
198 b = getset(repo, r, l[1])
199 if len(a) != 1 or len(b) != 1:
199 if len(a) != 1 or len(b) != 1:
200 # i18n: "ancestor" is a keyword
200 # i18n: "ancestor" is a keyword
201 raise error.ParseError(_("ancestor arguments must be single revisions"))
201 raise error.ParseError(_("ancestor arguments must be single revisions"))
202 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
202 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
203
203
204 return [r for r in an if r in subset]
204 return [r for r in an if r in subset]
205
205
206 def ancestors(repo, subset, x):
206 def ancestors(repo, subset, x):
207 """``ancestors(set)``
207 """``ancestors(set)``
208 Changesets that are ancestors of a changeset in set.
208 Changesets that are ancestors of a changeset in set.
209 """
209 """
210 args = getset(repo, range(len(repo)), x)
210 args = getset(repo, range(len(repo)), x)
211 if not args:
211 if not args:
212 return []
212 return []
213 s = set(repo.changelog.ancestors(*args)) | set(args)
213 s = set(repo.changelog.ancestors(*args)) | set(args)
214 return [r for r in subset if r in s]
214 return [r for r in subset if r in s]
215
215
216 def ancestorspec(repo, subset, x, n):
216 def ancestorspec(repo, subset, x, n):
217 """``set~n``
217 """``set~n``
218 Changesets that are the Nth ancestor (first parents only) of a changeset in set.
218 Changesets that are the Nth ancestor (first parents only) of a changeset in set.
219 """
219 """
220 try:
220 try:
221 n = int(n[1])
221 n = int(n[1])
222 except (TypeError, ValueError):
222 except (TypeError, ValueError):
223 raise error.ParseError(_("~ expects a number"))
223 raise error.ParseError(_("~ expects a number"))
224 ps = set()
224 ps = set()
225 cl = repo.changelog
225 cl = repo.changelog
226 for r in getset(repo, subset, x):
226 for r in getset(repo, subset, x):
227 for i in range(n):
227 for i in range(n):
228 r = cl.parentrevs(r)[0]
228 r = cl.parentrevs(r)[0]
229 ps.add(r)
229 ps.add(r)
230 return [r for r in subset if r in ps]
230 return [r for r in subset if r in ps]
231
231
232 def author(repo, subset, x):
232 def author(repo, subset, x):
233 """``author(string)``
233 """``author(string)``
234 Alias for ``user(string)``.
234 Alias for ``user(string)``.
235 """
235 """
236 # i18n: "author" is a keyword
236 # i18n: "author" is a keyword
237 n = encoding.lower(getstring(x, _("author requires a string")))
237 n = encoding.lower(getstring(x, _("author requires a string")))
238 return [r for r in subset if n in encoding.lower(repo[r].user())]
238 return [r for r in subset if n in encoding.lower(repo[r].user())]
239
239
240 def bisect(repo, subset, x):
240 def bisect(repo, subset, x):
241 """``bisect(string)``
241 """``bisect(string)``
242 Changesets marked in the specified bisect status:
242 Changesets marked in the specified bisect status:
243
243
244 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
244 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
245 - ``goods``, ``bads`` : csets topologicaly good/bad
245 - ``goods``, ``bads`` : csets topologicaly good/bad
246 - ``range`` : csets taking part in the bisection
246 - ``range`` : csets taking part in the bisection
247 - ``pruned`` : csets that are goods, bads or skipped
247 - ``pruned`` : csets that are goods, bads or skipped
248 - ``untested`` : csets whose fate is yet unknown
248 - ``untested`` : csets whose fate is yet unknown
249 - ``ignored`` : csets ignored due to DAG topology
249 - ``ignored`` : csets ignored due to DAG topology
250 """
250 """
251 status = getstring(x, _("bisect requires a string")).lower()
251 status = getstring(x, _("bisect requires a string")).lower()
252 return [r for r in subset if r in hbisect.get(repo, status)]
252 return [r for r in subset if r in hbisect.get(repo, status)]
253
253
254 # Backward-compatibility
254 # Backward-compatibility
255 # - no help entry so that we do not advertise it any more
255 # - no help entry so that we do not advertise it any more
256 def bisected(repo, subset, x):
256 def bisected(repo, subset, x):
257 return bisect(repo, subset, x)
257 return bisect(repo, subset, x)
258
258
259 def bookmark(repo, subset, x):
259 def bookmark(repo, subset, x):
260 """``bookmark([name])``
260 """``bookmark([name])``
261 The named bookmark or all bookmarks.
261 The named bookmark or all bookmarks.
262 """
262 """
263 # i18n: "bookmark" is a keyword
263 # i18n: "bookmark" is a keyword
264 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
264 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
265 if args:
265 if args:
266 bm = getstring(args[0],
266 bm = getstring(args[0],
267 # i18n: "bookmark" is a keyword
267 # i18n: "bookmark" is a keyword
268 _('the argument to bookmark must be a string'))
268 _('the argument to bookmark must be a string'))
269 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
269 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
270 if not bmrev:
270 if not bmrev:
271 raise util.Abort(_("bookmark '%s' does not exist") % bm)
271 raise util.Abort(_("bookmark '%s' does not exist") % bm)
272 bmrev = repo[bmrev].rev()
272 bmrev = repo[bmrev].rev()
273 return [r for r in subset if r == bmrev]
273 return [r for r in subset if r == bmrev]
274 bms = set([repo[r].rev()
274 bms = set([repo[r].rev()
275 for r in bookmarksmod.listbookmarks(repo).values()])
275 for r in bookmarksmod.listbookmarks(repo).values()])
276 return [r for r in subset if r in bms]
276 return [r for r in subset if r in bms]
277
277
278 def branch(repo, subset, x):
278 def branch(repo, subset, x):
279 """``branch(string or set)``
279 """``branch(string or set)``
280 All changesets belonging to the given branch or the branches of the given
280 All changesets belonging to the given branch or the branches of the given
281 changesets.
281 changesets.
282 """
282 """
283 try:
283 try:
284 b = getstring(x, '')
284 b = getstring(x, '')
285 if b in repo.branchmap():
285 if b in repo.branchmap():
286 return [r for r in subset if repo[r].branch() == b]
286 return [r for r in subset if repo[r].branch() == b]
287 except error.ParseError:
287 except error.ParseError:
288 # not a string, but another revspec, e.g. tip()
288 # not a string, but another revspec, e.g. tip()
289 pass
289 pass
290
290
291 s = getset(repo, range(len(repo)), x)
291 s = getset(repo, range(len(repo)), x)
292 b = set()
292 b = set()
293 for r in s:
293 for r in s:
294 b.add(repo[r].branch())
294 b.add(repo[r].branch())
295 s = set(s)
295 s = set(s)
296 return [r for r in subset if r in s or repo[r].branch() in b]
296 return [r for r in subset if r in s or repo[r].branch() in b]
297
297
298 def checkstatus(repo, subset, pat, field):
298 def checkstatus(repo, subset, pat, field):
299 m = None
299 m = None
300 s = []
300 s = []
301 fast = not matchmod.patkind(pat)
301 fast = not matchmod.patkind(pat)
302 for r in subset:
302 for r in subset:
303 c = repo[r]
303 c = repo[r]
304 if fast:
304 if fast:
305 if pat not in c.files():
305 if pat not in c.files():
306 continue
306 continue
307 else:
307 else:
308 if not m or matchmod.patkind(pat) == 'set':
308 if not m or matchmod.patkind(pat) == 'set':
309 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
309 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
310 for f in c.files():
310 for f in c.files():
311 if m(f):
311 if m(f):
312 break
312 break
313 else:
313 else:
314 continue
314 continue
315 files = repo.status(c.p1().node(), c.node())[field]
315 files = repo.status(c.p1().node(), c.node())[field]
316 if fast:
316 if fast:
317 if pat in files:
317 if pat in files:
318 s.append(r)
318 s.append(r)
319 else:
319 else:
320 for f in files:
320 for f in files:
321 if m(f):
321 if m(f):
322 s.append(r)
322 s.append(r)
323 break
323 break
324 return s
324 return s
325
325
326 def _children(repo, narrow, s):
326 def _children(repo, narrow, s):
327 cs = set()
327 cs = set()
328 pr = repo.changelog.parentrevs
328 pr = repo.changelog.parentrevs
329 s = set(s)
329 s = set(s)
330 for r in xrange(len(repo)):
330 for r in xrange(len(repo)):
331 for p in pr(r):
331 for p in pr(r):
332 if p in s:
332 if p in s:
333 cs.add(r)
333 cs.add(r)
334 return cs
334 return cs
335
335
336 def children(repo, subset, x):
336 def children(repo, subset, x):
337 """``children(set)``
337 """``children(set)``
338 Child changesets of changesets in set.
338 Child changesets of changesets in set.
339 """
339 """
340 s = getset(repo, range(len(repo)), x)
340 s = getset(repo, range(len(repo)), x)
341 cs = _children(repo, subset, s)
341 cs = _children(repo, subset, s)
342 return [r for r in subset if r in cs]
342 return [r for r in subset if r in cs]
343
343
344 def closed(repo, subset, x):
344 def closed(repo, subset, x):
345 """``closed()``
345 """``closed()``
346 Changeset is closed.
346 Changeset is closed.
347 """
347 """
348 # i18n: "closed" is a keyword
348 # i18n: "closed" is a keyword
349 getargs(x, 0, 0, _("closed takes no arguments"))
349 getargs(x, 0, 0, _("closed takes no arguments"))
350 return [r for r in subset if repo[r].extra().get('close')]
350 return [r for r in subset if repo[r].extra().get('close')]
351
351
352 def contains(repo, subset, x):
352 def contains(repo, subset, x):
353 """``contains(pattern)``
353 """``contains(pattern)``
354 Revision contains a file matching pattern. See :hg:`help patterns`
354 Revision contains a file matching pattern. See :hg:`help patterns`
355 for information about file patterns.
355 for information about file patterns.
356 """
356 """
357 # i18n: "contains" is a keyword
357 # i18n: "contains" is a keyword
358 pat = getstring(x, _("contains requires a pattern"))
358 pat = getstring(x, _("contains requires a pattern"))
359 m = None
359 m = None
360 s = []
360 s = []
361 if not matchmod.patkind(pat):
361 if not matchmod.patkind(pat):
362 for r in subset:
362 for r in subset:
363 if pat in repo[r]:
363 if pat in repo[r]:
364 s.append(r)
364 s.append(r)
365 else:
365 else:
366 for r in subset:
366 for r in subset:
367 c = repo[r]
367 c = repo[r]
368 if not m or matchmod.patkind(pat) == 'set':
368 if not m or matchmod.patkind(pat) == 'set':
369 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
369 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
370 for f in c.manifest():
370 for f in c.manifest():
371 if m(f):
371 if m(f):
372 s.append(r)
372 s.append(r)
373 break
373 break
374 return s
374 return s
375
375
376 def date(repo, subset, x):
376 def date(repo, subset, x):
377 """``date(interval)``
377 """``date(interval)``
378 Changesets within the interval, see :hg:`help dates`.
378 Changesets within the interval, see :hg:`help dates`.
379 """
379 """
380 # i18n: "date" is a keyword
380 # i18n: "date" is a keyword
381 ds = getstring(x, _("date requires a string"))
381 ds = getstring(x, _("date requires a string"))
382 dm = util.matchdate(ds)
382 dm = util.matchdate(ds)
383 return [r for r in subset if dm(repo[r].date()[0])]
383 return [r for r in subset if dm(repo[r].date()[0])]
384
384
385 def desc(repo, subset, x):
385 def desc(repo, subset, x):
386 """``desc(string)``
386 """``desc(string)``
387 Search commit message for string. The match is case-insensitive.
387 Search commit message for string. The match is case-insensitive.
388 """
388 """
389 # i18n: "desc" is a keyword
389 # i18n: "desc" is a keyword
390 ds = encoding.lower(getstring(x, _("desc requires a string")))
390 ds = encoding.lower(getstring(x, _("desc requires a string")))
391 l = []
391 l = []
392 for r in subset:
392 for r in subset:
393 c = repo[r]
393 c = repo[r]
394 if ds in encoding.lower(c.description()):
394 if ds in encoding.lower(c.description()):
395 l.append(r)
395 l.append(r)
396 return l
396 return l
397
397
398 def descendants(repo, subset, x):
398 def descendants(repo, subset, x):
399 """``descendants(set)``
399 """``descendants(set)``
400 Changesets which are descendants of changesets in set.
400 Changesets which are descendants of changesets in set.
401 """
401 """
402 args = getset(repo, range(len(repo)), x)
402 args = getset(repo, range(len(repo)), x)
403 if not args:
403 if not args:
404 return []
404 return []
405 s = set(repo.changelog.descendants(*args)) | set(args)
405 s = set(repo.changelog.descendants(*args)) | set(args)
406 return [r for r in subset if r in s]
406 return [r for r in subset if r in s]
407
407
408 def draft(repo, subset, x):
408 def draft(repo, subset, x):
409 """``draft()``
409 """``draft()``
410 Changeset in draft phase."""
410 Changeset in draft phase."""
411 getargs(x, 0, 0, _("draft takes no arguments"))
411 getargs(x, 0, 0, _("draft takes no arguments"))
412 return [r for r in subset if repo._phaserev[r] == phases.draft]
412 return [r for r in subset if repo._phaserev[r] == phases.draft]
413
413
414 def filelog(repo, subset, x):
414 def filelog(repo, subset, x):
415 """``filelog(pattern)``
415 """``filelog(pattern)``
416 Changesets connected to the specified filelog.
416 Changesets connected to the specified filelog.
417 """
417 """
418
418
419 pat = getstring(x, _("filelog requires a pattern"))
419 pat = getstring(x, _("filelog requires a pattern"))
420 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath',
420 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath',
421 ctx=repo[None])
421 ctx=repo[None])
422 s = set()
422 s = set()
423
423
424 if not matchmod.patkind(pat):
424 if not matchmod.patkind(pat):
425 for f in m.files():
425 for f in m.files():
426 fl = repo.file(f)
426 fl = repo.file(f)
427 for fr in fl:
427 for fr in fl:
428 s.add(fl.linkrev(fr))
428 s.add(fl.linkrev(fr))
429 else:
429 else:
430 for f in repo[None]:
430 for f in repo[None]:
431 if m(f):
431 if m(f):
432 fl = repo.file(f)
432 fl = repo.file(f)
433 for fr in fl:
433 for fr in fl:
434 s.add(fl.linkrev(fr))
434 s.add(fl.linkrev(fr))
435
435
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 first(repo, subset, x):
438 def first(repo, subset, x):
439 """``first(set, [n])``
439 """``first(set, [n])``
440 An alias for limit().
440 An alias for limit().
441 """
441 """
442 return limit(repo, subset, x)
442 return limit(repo, subset, x)
443
443
444 def follow(repo, subset, x):
444 def follow(repo, subset, x):
445 """``follow([file])``
445 """``follow([file])``
446 An alias for ``::.`` (ancestors of the working copy's first parent).
446 An alias for ``::.`` (ancestors of the working copy's first parent).
447 If a filename is specified, the history of the given file is followed,
447 If a filename is specified, the history of the given file is followed,
448 including copies.
448 including copies.
449 """
449 """
450 # i18n: "follow" is a keyword
450 # i18n: "follow" is a keyword
451 l = getargs(x, 0, 1, _("follow takes no arguments or a filename"))
451 l = getargs(x, 0, 1, _("follow takes no arguments or a filename"))
452 c = repo['.']
452 c = repo['.']
453 if l:
453 if l:
454 x = getstring(l[0], _("follow expected a filename"))
454 x = getstring(l[0], _("follow expected a filename"))
455 if x in c:
455 if x in c:
456 cx = c[x]
456 cx = c[x]
457 s = set(ctx.rev() for ctx in cx.ancestors())
457 s = set(ctx.rev() for ctx in cx.ancestors())
458 # include the revision responsible for the most recent version
458 # include the revision responsible for the most recent version
459 s.add(cx.linkrev())
459 s.add(cx.linkrev())
460 else:
460 else:
461 return []
461 return []
462 else:
462 else:
463 s = set(repo.changelog.ancestors(c.rev()))
463 s = set(repo.changelog.ancestors(c.rev()))
464 s.add(c.rev())
464 s.add(c.rev())
465
465
466 return [r for r in subset if r in s]
466 return [r for r in subset if r in s]
467
467
468 def getall(repo, subset, x):
468 def getall(repo, subset, x):
469 """``all()``
469 """``all()``
470 All changesets, the same as ``0:tip``.
470 All changesets, the same as ``0:tip``.
471 """
471 """
472 # i18n: "all" is a keyword
472 # i18n: "all" is a keyword
473 getargs(x, 0, 0, _("all takes no arguments"))
473 getargs(x, 0, 0, _("all takes no arguments"))
474 return subset
474 return subset
475
475
476 def grep(repo, subset, x):
476 def grep(repo, subset, x):
477 """``grep(regex)``
477 """``grep(regex)``
478 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
478 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
479 to ensure special escape characters are handled correctly. Unlike
479 to ensure special escape characters are handled correctly. Unlike
480 ``keyword(string)``, the match is case-sensitive.
480 ``keyword(string)``, the match is case-sensitive.
481 """
481 """
482 try:
482 try:
483 # i18n: "grep" is a keyword
483 # i18n: "grep" is a keyword
484 gr = re.compile(getstring(x, _("grep requires a string")))
484 gr = re.compile(getstring(x, _("grep requires a string")))
485 except re.error, e:
485 except re.error, e:
486 raise error.ParseError(_('invalid match pattern: %s') % e)
486 raise error.ParseError(_('invalid match pattern: %s') % e)
487 l = []
487 l = []
488 for r in subset:
488 for r in subset:
489 c = repo[r]
489 c = repo[r]
490 for e in c.files() + [c.user(), c.description()]:
490 for e in c.files() + [c.user(), c.description()]:
491 if gr.search(e):
491 if gr.search(e):
492 l.append(r)
492 l.append(r)
493 break
493 break
494 return l
494 return l
495
495
496 def hasfile(repo, subset, x):
496 def hasfile(repo, subset, x):
497 """``file(pattern)``
497 """``file(pattern)``
498 Changesets affecting files matched by pattern.
498 Changesets affecting files matched by pattern.
499 """
499 """
500 # i18n: "file" is a keyword
500 # i18n: "file" is a keyword
501 pat = getstring(x, _("file requires a pattern"))
501 pat = getstring(x, _("file requires a pattern"))
502 m = None
502 m = None
503 s = []
503 s = []
504 for r in subset:
504 for r in subset:
505 c = repo[r]
505 c = repo[r]
506 if not m or matchmod.patkind(pat) == 'set':
506 if not m or matchmod.patkind(pat) == 'set':
507 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
507 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
508 for f in c.files():
508 for f in c.files():
509 if m(f):
509 if m(f):
510 s.append(r)
510 s.append(r)
511 break
511 break
512 return s
512 return s
513
513
514 def head(repo, subset, x):
514 def head(repo, subset, x):
515 """``head()``
515 """``head()``
516 Changeset is a named branch head.
516 Changeset is a named branch head.
517 """
517 """
518 # i18n: "head" is a keyword
518 # i18n: "head" is a keyword
519 getargs(x, 0, 0, _("head takes no arguments"))
519 getargs(x, 0, 0, _("head takes no arguments"))
520 hs = set()
520 hs = set()
521 for b, ls in repo.branchmap().iteritems():
521 for b, ls in repo.branchmap().iteritems():
522 hs.update(repo[h].rev() for h in ls)
522 hs.update(repo[h].rev() for h in ls)
523 return [r for r in subset if r in hs]
523 return [r for r in subset if r in hs]
524
524
525 def heads(repo, subset, x):
525 def heads(repo, subset, x):
526 """``heads(set)``
526 """``heads(set)``
527 Members of set with no children in set.
527 Members of set with no children in set.
528 """
528 """
529 s = getset(repo, subset, x)
529 s = getset(repo, subset, x)
530 ps = set(parents(repo, subset, x))
530 ps = set(parents(repo, subset, x))
531 return [r for r in s if r not in ps]
531 return [r for r in s if r not in ps]
532
532
533 def keyword(repo, subset, x):
533 def keyword(repo, subset, x):
534 """``keyword(string)``
534 """``keyword(string)``
535 Search commit message, user name, and names of changed files for
535 Search commit message, user name, and names of changed files for
536 string. The match is case-insensitive.
536 string. The match is case-insensitive.
537 """
537 """
538 # i18n: "keyword" is a keyword
538 # i18n: "keyword" is a keyword
539 kw = encoding.lower(getstring(x, _("keyword requires a string")))
539 kw = encoding.lower(getstring(x, _("keyword requires a string")))
540 l = []
540 l = []
541 for r in subset:
541 for r in subset:
542 c = repo[r]
542 c = repo[r]
543 t = " ".join(c.files() + [c.user(), c.description()])
543 t = " ".join(c.files() + [c.user(), c.description()])
544 if kw in encoding.lower(t):
544 if kw in encoding.lower(t):
545 l.append(r)
545 l.append(r)
546 return l
546 return l
547
547
548 def limit(repo, subset, x):
548 def limit(repo, subset, x):
549 """``limit(set, [n])``
549 """``limit(set, [n])``
550 First n members of set, defaulting to 1.
550 First n members of set, defaulting to 1.
551 """
551 """
552 # i18n: "limit" is a keyword
552 # i18n: "limit" is a keyword
553 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
553 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
554 try:
554 try:
555 lim = 1
555 lim = 1
556 if len(l) == 2:
556 if len(l) == 2:
557 # i18n: "limit" is a keyword
557 # i18n: "limit" is a keyword
558 lim = int(getstring(l[1], _("limit requires a number")))
558 lim = int(getstring(l[1], _("limit requires a number")))
559 except (TypeError, ValueError):
559 except (TypeError, ValueError):
560 # i18n: "limit" is a keyword
560 # i18n: "limit" is a keyword
561 raise error.ParseError(_("limit expects a number"))
561 raise error.ParseError(_("limit expects a number"))
562 ss = set(subset)
562 ss = set(subset)
563 os = getset(repo, range(len(repo)), l[0])[:lim]
563 os = getset(repo, range(len(repo)), l[0])[:lim]
564 return [r for r in os if r in ss]
564 return [r for r in os if r in ss]
565
565
566 def last(repo, subset, x):
566 def last(repo, subset, x):
567 """``last(set, [n])``
567 """``last(set, [n])``
568 Last n members of set, defaulting to 1.
568 Last n members of set, defaulting to 1.
569 """
569 """
570 # i18n: "last" is a keyword
570 # i18n: "last" is a keyword
571 l = getargs(x, 1, 2, _("last requires one or two arguments"))
571 l = getargs(x, 1, 2, _("last requires one or two arguments"))
572 try:
572 try:
573 lim = 1
573 lim = 1
574 if len(l) == 2:
574 if len(l) == 2:
575 # i18n: "last" is a keyword
575 # i18n: "last" is a keyword
576 lim = int(getstring(l[1], _("last requires a number")))
576 lim = int(getstring(l[1], _("last requires a number")))
577 except (TypeError, ValueError):
577 except (TypeError, ValueError):
578 # i18n: "last" is a keyword
578 # i18n: "last" is a keyword
579 raise error.ParseError(_("last expects a number"))
579 raise error.ParseError(_("last expects a number"))
580 ss = set(subset)
580 ss = set(subset)
581 os = getset(repo, range(len(repo)), l[0])[-lim:]
581 os = getset(repo, range(len(repo)), l[0])[-lim:]
582 return [r for r in os if r in ss]
582 return [r for r in os if r in ss]
583
583
584 def maxrev(repo, subset, x):
584 def maxrev(repo, subset, x):
585 """``max(set)``
585 """``max(set)``
586 Changeset with highest revision number in set.
586 Changeset with highest revision number in set.
587 """
587 """
588 os = getset(repo, range(len(repo)), x)
588 os = getset(repo, range(len(repo)), x)
589 if os:
589 if os:
590 m = max(os)
590 m = max(os)
591 if m in subset:
591 if m in subset:
592 return [m]
592 return [m]
593 return []
593 return []
594
594
595 def merge(repo, subset, x):
595 def merge(repo, subset, x):
596 """``merge()``
596 """``merge()``
597 Changeset is a merge changeset.
597 Changeset is a merge changeset.
598 """
598 """
599 # i18n: "merge" is a keyword
599 # i18n: "merge" is a keyword
600 getargs(x, 0, 0, _("merge takes no arguments"))
600 getargs(x, 0, 0, _("merge takes no arguments"))
601 cl = repo.changelog
601 cl = repo.changelog
602 return [r for r in subset if cl.parentrevs(r)[1] != -1]
602 return [r for r in subset if cl.parentrevs(r)[1] != -1]
603
603
604 def minrev(repo, subset, x):
604 def minrev(repo, subset, x):
605 """``min(set)``
605 """``min(set)``
606 Changeset with lowest revision number in set.
606 Changeset with lowest revision number in set.
607 """
607 """
608 os = getset(repo, range(len(repo)), x)
608 os = getset(repo, range(len(repo)), x)
609 if os:
609 if os:
610 m = min(os)
610 m = min(os)
611 if m in subset:
611 if m in subset:
612 return [m]
612 return [m]
613 return []
613 return []
614
614
615 def modifies(repo, subset, x):
615 def modifies(repo, subset, x):
616 """``modifies(pattern)``
616 """``modifies(pattern)``
617 Changesets modifying files matched by pattern.
617 Changesets modifying files matched by pattern.
618 """
618 """
619 # i18n: "modifies" is a keyword
619 # i18n: "modifies" is a keyword
620 pat = getstring(x, _("modifies requires a pattern"))
620 pat = getstring(x, _("modifies requires a pattern"))
621 return checkstatus(repo, subset, pat, 0)
621 return checkstatus(repo, subset, pat, 0)
622
622
623 def node(repo, subset, x):
623 def node(repo, subset, x):
624 """``id(string)``
624 """``id(string)``
625 Revision non-ambiguously specified by the given hex string prefix.
625 Revision non-ambiguously specified by the given hex string prefix.
626 """
626 """
627 # i18n: "id" is a keyword
627 # i18n: "id" is a keyword
628 l = getargs(x, 1, 1, _("id requires one argument"))
628 l = getargs(x, 1, 1, _("id requires one argument"))
629 # i18n: "id" is a keyword
629 # i18n: "id" is a keyword
630 n = getstring(l[0], _("id requires a string"))
630 n = getstring(l[0], _("id requires a string"))
631 if len(n) == 40:
631 if len(n) == 40:
632 rn = repo[n].rev()
632 rn = repo[n].rev()
633 else:
633 else:
634 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
634 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
635 return [r for r in subset if r == rn]
635 return [r for r in subset if r == rn]
636
636
637 def outgoing(repo, subset, x):
637 def outgoing(repo, subset, x):
638 """``outgoing([path])``
638 """``outgoing([path])``
639 Changesets not found in the specified destination repository, or the
639 Changesets not found in the specified destination repository, or the
640 default push location.
640 default push location.
641 """
641 """
642 import hg # avoid start-up nasties
642 import hg # avoid start-up nasties
643 # i18n: "outgoing" is a keyword
643 # i18n: "outgoing" is a keyword
644 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
644 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
645 # i18n: "outgoing" is a keyword
645 # i18n: "outgoing" is a keyword
646 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
646 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
647 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
647 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
648 dest, branches = hg.parseurl(dest)
648 dest, branches = hg.parseurl(dest)
649 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
649 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
650 if revs:
650 if revs:
651 revs = [repo.lookup(rev) for rev in revs]
651 revs = [repo.lookup(rev) for rev in revs]
652 other = hg.peer(repo, {}, dest)
652 other = hg.peer(repo, {}, dest)
653 repo.ui.pushbuffer()
653 repo.ui.pushbuffer()
654 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
654 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
655 repo.ui.popbuffer()
655 repo.ui.popbuffer()
656 cl = repo.changelog
656 cl = repo.changelog
657 o = set([cl.rev(r) for r in outgoing.missing])
657 o = set([cl.rev(r) for r in outgoing.missing])
658 return [r for r in subset if r in o]
658 return [r for r in subset if r in o]
659
659
660 def p1(repo, subset, x):
660 def p1(repo, subset, x):
661 """``p1([set])``
661 """``p1([set])``
662 First parent of changesets in set, or the working directory.
662 First parent of changesets in set, or the working directory.
663 """
663 """
664 if x is None:
664 if x is None:
665 p = repo[x].p1().rev()
665 p = repo[x].p1().rev()
666 return [r for r in subset if r == p]
666 return [r for r in subset if r == p]
667
667
668 ps = set()
668 ps = set()
669 cl = repo.changelog
669 cl = repo.changelog
670 for r in getset(repo, range(len(repo)), x):
670 for r in getset(repo, range(len(repo)), x):
671 ps.add(cl.parentrevs(r)[0])
671 ps.add(cl.parentrevs(r)[0])
672 return [r for r in subset if r in ps]
672 return [r for r in subset if r in ps]
673
673
674 def p2(repo, subset, x):
674 def p2(repo, subset, x):
675 """``p2([set])``
675 """``p2([set])``
676 Second parent of changesets in set, or the working directory.
676 Second parent of changesets in set, or the working directory.
677 """
677 """
678 if x is None:
678 if x is None:
679 ps = repo[x].parents()
679 ps = repo[x].parents()
680 try:
680 try:
681 p = ps[1].rev()
681 p = ps[1].rev()
682 return [r for r in subset if r == p]
682 return [r for r in subset if r == p]
683 except IndexError:
683 except IndexError:
684 return []
684 return []
685
685
686 ps = set()
686 ps = set()
687 cl = repo.changelog
687 cl = repo.changelog
688 for r in getset(repo, range(len(repo)), x):
688 for r in getset(repo, range(len(repo)), x):
689 ps.add(cl.parentrevs(r)[1])
689 ps.add(cl.parentrevs(r)[1])
690 return [r for r in subset if r in ps]
690 return [r for r in subset if r in ps]
691
691
692 def parents(repo, subset, x):
692 def parents(repo, subset, x):
693 """``parents([set])``
693 """``parents([set])``
694 The set of all parents for all changesets in set, or the working directory.
694 The set of all parents for all changesets in set, or the working directory.
695 """
695 """
696 if x is None:
696 if x is None:
697 ps = tuple(p.rev() for p in repo[x].parents())
697 ps = tuple(p.rev() for p in repo[x].parents())
698 return [r for r in subset if r in ps]
698 return [r for r in subset if r in ps]
699
699
700 ps = set()
700 ps = set()
701 cl = repo.changelog
701 cl = repo.changelog
702 for r in getset(repo, range(len(repo)), x):
702 for r in getset(repo, range(len(repo)), x):
703 ps.update(cl.parentrevs(r))
703 ps.update(cl.parentrevs(r))
704 return [r for r in subset if r in ps]
704 return [r for r in subset if r in ps]
705
705
706 def parentspec(repo, subset, x, n):
706 def parentspec(repo, subset, x, n):
707 """``set^0``
707 """``set^0``
708 The set.
708 The set.
709 ``set^1`` (or ``set^``), ``set^2``
709 ``set^1`` (or ``set^``), ``set^2``
710 First or second parent, respectively, of all changesets in set.
710 First or second parent, respectively, of all changesets in set.
711 """
711 """
712 try:
712 try:
713 n = int(n[1])
713 n = int(n[1])
714 if n not in (0, 1, 2):
714 if n not in (0, 1, 2):
715 raise ValueError
715 raise ValueError
716 except (TypeError, ValueError):
716 except (TypeError, ValueError):
717 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
717 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
718 ps = set()
718 ps = set()
719 cl = repo.changelog
719 cl = repo.changelog
720 for r in getset(repo, subset, x):
720 for r in getset(repo, subset, x):
721 if n == 0:
721 if n == 0:
722 ps.add(r)
722 ps.add(r)
723 elif n == 1:
723 elif n == 1:
724 ps.add(cl.parentrevs(r)[0])
724 ps.add(cl.parentrevs(r)[0])
725 elif n == 2:
725 elif n == 2:
726 parents = cl.parentrevs(r)
726 parents = cl.parentrevs(r)
727 if len(parents) > 1:
727 if len(parents) > 1:
728 ps.add(parents[1])
728 ps.add(parents[1])
729 return [r for r in subset if r in ps]
729 return [r for r in subset if r in ps]
730
730
731 def present(repo, subset, x):
731 def present(repo, subset, x):
732 """``present(set)``
732 """``present(set)``
733 An empty set, if any revision in set isn't found; otherwise,
733 An empty set, if any revision in set isn't found; otherwise,
734 all revisions in set.
734 all revisions in set.
735 """
735 """
736 try:
736 try:
737 return getset(repo, subset, x)
737 return getset(repo, subset, x)
738 except error.RepoLookupError:
738 except error.RepoLookupError:
739 return []
739 return []
740
740
741 def public(repo, subset, x):
741 def public(repo, subset, x):
742 """``public()``
742 """``public()``
743 Changeset in public phase."""
743 Changeset in public phase."""
744 getargs(x, 0, 0, _("public takes no arguments"))
744 getargs(x, 0, 0, _("public takes no arguments"))
745 return [r for r in subset if repo._phaserev[r] == phases.public]
745 return [r for r in subset if repo._phaserev[r] == phases.public]
746
746
747 def remote(repo, subset, x):
747 def remote(repo, subset, x):
748 """``remote([id [,path]])``
748 """``remote([id [,path]])``
749 Local revision that corresponds to the given identifier in a
749 Local revision that corresponds to the given identifier in a
750 remote repository, if present. Here, the '.' identifier is a
750 remote repository, if present. Here, the '.' identifier is a
751 synonym for the current local branch.
751 synonym for the current local branch.
752 """
752 """
753
753
754 import hg # avoid start-up nasties
754 import hg # avoid start-up nasties
755 # i18n: "remote" is a keyword
755 # i18n: "remote" is a keyword
756 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
756 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
757
757
758 q = '.'
758 q = '.'
759 if len(l) > 0:
759 if len(l) > 0:
760 # i18n: "remote" is a keyword
760 # i18n: "remote" is a keyword
761 q = getstring(l[0], _("remote requires a string id"))
761 q = getstring(l[0], _("remote requires a string id"))
762 if q == '.':
762 if q == '.':
763 q = repo['.'].branch()
763 q = repo['.'].branch()
764
764
765 dest = ''
765 dest = ''
766 if len(l) > 1:
766 if len(l) > 1:
767 # i18n: "remote" is a keyword
767 # i18n: "remote" is a keyword
768 dest = getstring(l[1], _("remote requires a repository path"))
768 dest = getstring(l[1], _("remote requires a repository path"))
769 dest = repo.ui.expandpath(dest or 'default')
769 dest = repo.ui.expandpath(dest or 'default')
770 dest, branches = hg.parseurl(dest)
770 dest, branches = hg.parseurl(dest)
771 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
771 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
772 if revs:
772 if revs:
773 revs = [repo.lookup(rev) for rev in revs]
773 revs = [repo.lookup(rev) for rev in revs]
774 other = hg.peer(repo, {}, dest)
774 other = hg.peer(repo, {}, dest)
775 n = other.lookup(q)
775 n = other.lookup(q)
776 if n in repo:
776 if n in repo:
777 r = repo[n].rev()
777 r = repo[n].rev()
778 if r in subset:
778 if r in subset:
779 return [r]
779 return [r]
780 return []
780 return []
781
781
782 def removes(repo, subset, x):
782 def removes(repo, subset, x):
783 """``removes(pattern)``
783 """``removes(pattern)``
784 Changesets which remove files matching pattern.
784 Changesets which remove files matching pattern.
785 """
785 """
786 # i18n: "removes" is a keyword
786 # i18n: "removes" is a keyword
787 pat = getstring(x, _("removes requires a pattern"))
787 pat = getstring(x, _("removes requires a pattern"))
788 return checkstatus(repo, subset, pat, 2)
788 return checkstatus(repo, subset, pat, 2)
789
789
790 def rev(repo, subset, x):
790 def rev(repo, subset, x):
791 """``rev(number)``
791 """``rev(number)``
792 Revision with the given numeric identifier.
792 Revision with the given numeric identifier.
793 """
793 """
794 # i18n: "rev" is a keyword
794 # i18n: "rev" is a keyword
795 l = getargs(x, 1, 1, _("rev requires one argument"))
795 l = getargs(x, 1, 1, _("rev requires one argument"))
796 try:
796 try:
797 # i18n: "rev" is a keyword
797 # i18n: "rev" is a keyword
798 l = int(getstring(l[0], _("rev requires a number")))
798 l = int(getstring(l[0], _("rev requires a number")))
799 except (TypeError, ValueError):
799 except (TypeError, ValueError):
800 # i18n: "rev" is a keyword
800 # i18n: "rev" is a keyword
801 raise error.ParseError(_("rev expects a number"))
801 raise error.ParseError(_("rev expects a number"))
802 return [r for r in subset if r == l]
802 return [r for r in subset if r == l]
803
803
804 def reverse(repo, subset, x):
804 def reverse(repo, subset, x):
805 """``reverse(set)``
805 """``reverse(set)``
806 Reverse order of set.
806 Reverse order of set.
807 """
807 """
808 l = getset(repo, subset, x)
808 l = getset(repo, subset, x)
809 l.reverse()
809 l.reverse()
810 return l
810 return l
811
811
812 def roots(repo, subset, x):
812 def roots(repo, subset, x):
813 """``roots(set)``
813 """``roots(set)``
814 Changesets with no parent changeset in set.
814 Changesets with no parent changeset in set.
815 """
815 """
816 s = getset(repo, xrange(len(repo)), x)
816 s = getset(repo, xrange(len(repo)), x)
817 cs = _children(repo, s, s)
817 cs = _children(repo, s, s)
818 return [r for r in s if r not in cs]
818 return [r for r in s if r not in cs]
819
819
820 def secret(repo, subset, x):
820 def secret(repo, subset, x):
821 """``secret()``
821 """``secret()``
822 Changeset in secret phase."""
822 Changeset in secret phase."""
823 getargs(x, 0, 0, _("secret takes no arguments"))
823 getargs(x, 0, 0, _("secret takes no arguments"))
824 return [r for r in subset if repo._phaserev[r] == phases.secret]
824 return [r for r in subset if repo._phaserev[r] == phases.secret]
825
825
826 def sort(repo, subset, x):
826 def sort(repo, subset, x):
827 """``sort(set[, [-]key...])``
827 """``sort(set[, [-]key...])``
828 Sort set by keys. The default sort order is ascending, specify a key
828 Sort set by keys. The default sort order is ascending, specify a key
829 as ``-key`` to sort in descending order.
829 as ``-key`` to sort in descending order.
830
830
831 The keys can be:
831 The keys can be:
832
832
833 - ``rev`` for the revision number,
833 - ``rev`` for the revision number,
834 - ``branch`` for the branch name,
834 - ``branch`` for the branch name,
835 - ``desc`` for the commit message (description),
835 - ``desc`` for the commit message (description),
836 - ``user`` for user name (``author`` can be used as an alias),
836 - ``user`` for user name (``author`` can be used as an alias),
837 - ``date`` for the commit date
837 - ``date`` for the commit date
838 """
838 """
839 # i18n: "sort" is a keyword
839 # i18n: "sort" is a keyword
840 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
840 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
841 keys = "rev"
841 keys = "rev"
842 if len(l) == 2:
842 if len(l) == 2:
843 keys = getstring(l[1], _("sort spec must be a string"))
843 keys = getstring(l[1], _("sort spec must be a string"))
844
844
845 s = l[0]
845 s = l[0]
846 keys = keys.split()
846 keys = keys.split()
847 l = []
847 l = []
848 def invert(s):
848 def invert(s):
849 return "".join(chr(255 - ord(c)) for c in s)
849 return "".join(chr(255 - ord(c)) for c in s)
850 for r in getset(repo, subset, s):
850 for r in getset(repo, subset, s):
851 c = repo[r]
851 c = repo[r]
852 e = []
852 e = []
853 for k in keys:
853 for k in keys:
854 if k == 'rev':
854 if k == 'rev':
855 e.append(r)
855 e.append(r)
856 elif k == '-rev':
856 elif k == '-rev':
857 e.append(-r)
857 e.append(-r)
858 elif k == 'branch':
858 elif k == 'branch':
859 e.append(c.branch())
859 e.append(c.branch())
860 elif k == '-branch':
860 elif k == '-branch':
861 e.append(invert(c.branch()))
861 e.append(invert(c.branch()))
862 elif k == 'desc':
862 elif k == 'desc':
863 e.append(c.description())
863 e.append(c.description())
864 elif k == '-desc':
864 elif k == '-desc':
865 e.append(invert(c.description()))
865 e.append(invert(c.description()))
866 elif k in 'user author':
866 elif k in 'user author':
867 e.append(c.user())
867 e.append(c.user())
868 elif k in '-user -author':
868 elif k in '-user -author':
869 e.append(invert(c.user()))
869 e.append(invert(c.user()))
870 elif k == 'date':
870 elif k == 'date':
871 e.append(c.date()[0])
871 e.append(c.date()[0])
872 elif k == '-date':
872 elif k == '-date':
873 e.append(-c.date()[0])
873 e.append(-c.date()[0])
874 else:
874 else:
875 raise error.ParseError(_("unknown sort key %r") % k)
875 raise error.ParseError(_("unknown sort key %r") % k)
876 e.append(r)
876 e.append(r)
877 l.append(e)
877 l.append(e)
878 l.sort()
878 l.sort()
879 return [e[-1] for e in l]
879 return [e[-1] for e in l]
880
880
881 def tag(repo, subset, x):
881 def tag(repo, subset, x):
882 """``tag([name])``
882 """``tag([name])``
883 The specified tag by name, or all tagged revisions if no name is given.
883 The specified tag by name, or all tagged revisions if no name is given.
884 """
884 """
885 # i18n: "tag" is a keyword
885 # i18n: "tag" is a keyword
886 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
886 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
887 cl = repo.changelog
887 cl = repo.changelog
888 if args:
888 if args:
889 tn = getstring(args[0],
889 tn = getstring(args[0],
890 # i18n: "tag" is a keyword
890 # i18n: "tag" is a keyword
891 _('the argument to tag must be a string'))
891 _('the argument to tag must be a string'))
892 if not repo.tags().get(tn, None):
892 if not repo.tags().get(tn, None):
893 raise util.Abort(_("tag '%s' does not exist") % tn)
893 raise util.Abort(_("tag '%s' does not exist") % tn)
894 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
894 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
895 else:
895 else:
896 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
896 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
897 return [r for r in subset if r in s]
897 return [r for r in subset if r in s]
898
898
899 def tagged(repo, subset, x):
899 def tagged(repo, subset, x):
900 return tag(repo, subset, x)
900 return tag(repo, subset, x)
901
901
902 def user(repo, subset, x):
902 def user(repo, subset, x):
903 """``user(string)``
903 """``user(string)``
904 User name contains string. The match is case-insensitive.
904 User name contains string. The match is case-insensitive.
905 """
905 """
906 return author(repo, subset, x)
906 return author(repo, subset, x)
907
907
908 # for internal use
908 # for internal use
909 def _list(repo, subset, x):
909 def _list(repo, subset, x):
910 s = getstring(x, "internal error")
910 s = getstring(x, "internal error")
911 if not s:
911 if not s:
912 return []
912 return []
913 if not isinstance(subset, set):
913 if not isinstance(subset, set):
914 subset = set(subset)
914 subset = set(subset)
915 ls = [repo[r].rev() for r in s.split('\0')]
915 ls = [repo[r].rev() for r in s.split('\0')]
916 return [r for r in ls if r in subset]
916 return [r for r in ls if r in subset]
917
917
918 symbols = {
918 symbols = {
919 "adds": adds,
919 "adds": adds,
920 "all": getall,
920 "all": getall,
921 "ancestor": ancestor,
921 "ancestor": ancestor,
922 "ancestors": ancestors,
922 "ancestors": ancestors,
923 "author": author,
923 "author": author,
924 "bisect": bisect,
924 "bisect": bisect,
925 "bisected": bisected,
925 "bisected": bisected,
926 "bookmark": bookmark,
926 "bookmark": bookmark,
927 "branch": branch,
927 "branch": branch,
928 "children": children,
928 "children": children,
929 "closed": closed,
929 "closed": closed,
930 "contains": contains,
930 "contains": contains,
931 "date": date,
931 "date": date,
932 "desc": desc,
932 "desc": desc,
933 "descendants": descendants,
933 "descendants": descendants,
934 "draft": draft,
934 "draft": draft,
935 "file": hasfile,
935 "file": hasfile,
936 "filelog": filelog,
936 "filelog": filelog,
937 "first": first,
937 "first": first,
938 "follow": follow,
938 "follow": follow,
939 "grep": grep,
939 "grep": grep,
940 "head": head,
940 "head": head,
941 "heads": heads,
941 "heads": heads,
942 "id": node,
942 "id": node,
943 "keyword": keyword,
943 "keyword": keyword,
944 "last": last,
944 "last": last,
945 "limit": limit,
945 "limit": limit,
946 "max": maxrev,
946 "max": maxrev,
947 "merge": merge,
947 "merge": merge,
948 "min": minrev,
948 "min": minrev,
949 "modifies": modifies,
949 "modifies": modifies,
950 "outgoing": outgoing,
950 "outgoing": outgoing,
951 "p1": p1,
951 "p1": p1,
952 "p2": p2,
952 "p2": p2,
953 "parents": parents,
953 "parents": parents,
954 "present": present,
954 "present": present,
955 "public": public,
955 "public": public,
956 "remote": remote,
956 "remote": remote,
957 "removes": removes,
957 "removes": removes,
958 "rev": rev,
958 "rev": rev,
959 "reverse": reverse,
959 "reverse": reverse,
960 "roots": roots,
960 "roots": roots,
961 "sort": sort,
961 "sort": sort,
962 "secret": secret,
962 "secret": secret,
963 "tag": tag,
963 "tag": tag,
964 "tagged": tagged,
964 "tagged": tagged,
965 "user": user,
965 "user": user,
966 "_list": _list,
966 "_list": _list,
967 }
967 }
968
968
969 methods = {
969 methods = {
970 "range": rangeset,
970 "range": rangeset,
971 "string": stringset,
971 "string": stringset,
972 "symbol": symbolset,
972 "symbol": symbolset,
973 "and": andset,
973 "and": andset,
974 "or": orset,
974 "or": orset,
975 "not": notset,
975 "not": notset,
976 "list": listset,
976 "list": listset,
977 "func": func,
977 "func": func,
978 "ancestor": ancestorspec,
978 "ancestor": ancestorspec,
979 "parent": parentspec,
979 "parent": parentspec,
980 "parentpost": p1,
980 "parentpost": p1,
981 }
981 }
982
982
983 def optimize(x, small):
983 def optimize(x, small):
984 if x is None:
984 if x is None:
985 return 0, x
985 return 0, x
986
986
987 smallbonus = 1
987 smallbonus = 1
988 if small:
988 if small:
989 smallbonus = .5
989 smallbonus = .5
990
990
991 op = x[0]
991 op = x[0]
992 if op == 'minus':
992 if op == 'minus':
993 return optimize(('and', x[1], ('not', x[2])), small)
993 return optimize(('and', x[1], ('not', x[2])), small)
994 elif op == 'dagrange':
994 elif op == 'dagrange':
995 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
995 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
996 ('func', ('symbol', 'ancestors'), x[2])), small)
996 ('func', ('symbol', 'ancestors'), x[2])), small)
997 elif op == 'dagrangepre':
997 elif op == 'dagrangepre':
998 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
998 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
999 elif op == 'dagrangepost':
999 elif op == 'dagrangepost':
1000 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1000 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1001 elif op == 'rangepre':
1001 elif op == 'rangepre':
1002 return optimize(('range', ('string', '0'), x[1]), small)
1002 return optimize(('range', ('string', '0'), x[1]), small)
1003 elif op == 'rangepost':
1003 elif op == 'rangepost':
1004 return optimize(('range', x[1], ('string', 'tip')), small)
1004 return optimize(('range', x[1], ('string', 'tip')), small)
1005 elif op == 'negate':
1005 elif op == 'negate':
1006 return optimize(('string',
1006 return optimize(('string',
1007 '-' + getstring(x[1], _("can't negate that"))), small)
1007 '-' + getstring(x[1], _("can't negate that"))), small)
1008 elif op in 'string symbol negate':
1008 elif op in 'string symbol negate':
1009 return smallbonus, x # single revisions are small
1009 return smallbonus, x # single revisions are small
1010 elif op == 'and' or op == 'dagrange':
1010 elif op == 'and' or op == 'dagrange':
1011 wa, ta = optimize(x[1], True)
1011 wa, ta = optimize(x[1], True)
1012 wb, tb = optimize(x[2], True)
1012 wb, tb = optimize(x[2], True)
1013 w = min(wa, wb)
1013 w = min(wa, wb)
1014 if wa > wb:
1014 if wa > wb:
1015 return w, (op, tb, ta)
1015 return w, (op, tb, ta)
1016 return w, (op, ta, tb)
1016 return w, (op, ta, tb)
1017 elif op == 'or':
1017 elif op == 'or':
1018 wa, ta = optimize(x[1], False)
1018 wa, ta = optimize(x[1], False)
1019 wb, tb = optimize(x[2], False)
1019 wb, tb = optimize(x[2], False)
1020 if wb < wa:
1020 if wb < wa:
1021 wb, wa = wa, wb
1021 wb, wa = wa, wb
1022 return max(wa, wb), (op, ta, tb)
1022 return max(wa, wb), (op, ta, tb)
1023 elif op == 'not':
1023 elif op == 'not':
1024 o = optimize(x[1], not small)
1024 o = optimize(x[1], not small)
1025 return o[0], (op, o[1])
1025 return o[0], (op, o[1])
1026 elif op == 'parentpost':
1026 elif op == 'parentpost':
1027 o = optimize(x[1], small)
1027 o = optimize(x[1], small)
1028 return o[0], (op, o[1])
1028 return o[0], (op, o[1])
1029 elif op == 'group':
1029 elif op == 'group':
1030 return optimize(x[1], small)
1030 return optimize(x[1], small)
1031 elif op in 'range list parent ancestorspec':
1031 elif op in 'range list parent ancestorspec':
1032 if op == 'parent':
1032 if op == 'parent':
1033 # x^:y means (x^) : y, not x ^ (:y)
1033 # x^:y means (x^) : y, not x ^ (:y)
1034 post = ('parentpost', x[1])
1034 post = ('parentpost', x[1])
1035 if x[2][0] == 'dagrangepre':
1035 if x[2][0] == 'dagrangepre':
1036 return optimize(('dagrange', post, x[2][1]), small)
1036 return optimize(('dagrange', post, x[2][1]), small)
1037 elif x[2][0] == 'rangepre':
1037 elif x[2][0] == 'rangepre':
1038 return optimize(('range', post, x[2][1]), small)
1038 return optimize(('range', post, x[2][1]), small)
1039
1039
1040 wa, ta = optimize(x[1], small)
1040 wa, ta = optimize(x[1], small)
1041 wb, tb = optimize(x[2], small)
1041 wb, tb = optimize(x[2], small)
1042 return wa + wb, (op, ta, tb)
1042 return wa + wb, (op, ta, tb)
1043 elif op == 'func':
1043 elif op == 'func':
1044 f = getstring(x[1], _("not a symbol"))
1044 f = getstring(x[1], _("not a symbol"))
1045 wa, ta = optimize(x[2], small)
1045 wa, ta = optimize(x[2], small)
1046 if f in ("author branch closed date desc file grep keyword "
1046 if f in ("author branch closed date desc file grep keyword "
1047 "outgoing user"):
1047 "outgoing user"):
1048 w = 10 # slow
1048 w = 10 # slow
1049 elif f in "modifies adds removes":
1049 elif f in "modifies adds removes":
1050 w = 30 # slower
1050 w = 30 # slower
1051 elif f == "contains":
1051 elif f == "contains":
1052 w = 100 # very slow
1052 w = 100 # very slow
1053 elif f == "ancestor":
1053 elif f == "ancestor":
1054 w = 1 * smallbonus
1054 w = 1 * smallbonus
1055 elif f in "reverse limit first":
1055 elif f in "reverse limit first":
1056 w = 0
1056 w = 0
1057 elif f in "sort":
1057 elif f in "sort":
1058 w = 10 # assume most sorts look at changelog
1058 w = 10 # assume most sorts look at changelog
1059 else:
1059 else:
1060 w = 1
1060 w = 1
1061 return w + wa, (op, x[1], ta)
1061 return w + wa, (op, x[1], ta)
1062 return 1, x
1062 return 1, x
1063
1063
1064 class revsetalias(object):
1064 class revsetalias(object):
1065 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1065 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1066 args = None
1066 args = None
1067
1067
1068 def __init__(self, name, value):
1068 def __init__(self, name, value):
1069 '''Aliases like:
1069 '''Aliases like:
1070
1070
1071 h = heads(default)
1071 h = heads(default)
1072 b($1) = ancestors($1) - ancestors(default)
1072 b($1) = ancestors($1) - ancestors(default)
1073 '''
1073 '''
1074 if isinstance(name, tuple): # parameter substitution
1075 self.tree = name
1076 self.replacement = value
1077 else: # alias definition
1078 m = self.funcre.search(name)
1074 m = self.funcre.search(name)
1079 if m:
1075 if m:
1076 self.name = m.group(1)
1080 self.tree = ('func', ('symbol', m.group(1)))
1077 self.tree = ('func', ('symbol', m.group(1)))
1081 self.args = [x.strip() for x in m.group(2).split(',')]
1078 self.args = [x.strip() for x in m.group(2).split(',')]
1082 for arg in self.args:
1079 for arg in self.args:
1083 value = value.replace(arg, repr(arg))
1080 value = value.replace(arg, repr(arg))
1084 else:
1081 else:
1082 self.name = name
1085 self.tree = ('symbol', name)
1083 self.tree = ('symbol', name)
1086
1084
1087 self.replacement, pos = parse(value)
1085 self.replacement, pos = parse(value)
1088 if pos != len(value):
1086 if pos != len(value):
1089 raise error.ParseError(_('invalid token'), pos)
1087 raise error.ParseError(_('invalid token'), pos)
1090
1088
1091 def process(self, tree):
1089 def _getalias(aliases, tree):
1092 if isinstance(tree, tuple):
1090 """If tree looks like an unexpanded alias, return it. Return None
1093 if self.args is None:
1091 otherwise.
1094 if tree == self.tree:
1092 """
1095 return self.replacement
1093 if isinstance(tree, tuple) and tree:
1096 elif tree[:2] == self.tree:
1094 if tree[0] == 'symbol' and len(tree) == 2:
1095 name = tree[1]
1096 alias = aliases.get(name)
1097 if alias and alias.args is None and alias.tree == tree:
1098 return alias
1099 if tree[0] == 'func' and len(tree) > 1:
1100 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
1101 name = tree[1][1]
1102 alias = aliases.get(name)
1103 if alias and alias.args is not None and alias.tree == tree[:2]:
1104 return alias
1105 return None
1106
1107 def _expandargs(tree, args):
1108 """Replace all occurences of ('string', name) with the
1109 substitution value of the same name in args, recursively.
1110 """
1111 if not isinstance(tree, tuple):
1112 return tree
1113 if len(tree) == 2 and tree[0] == 'string':
1114 return args.get(tree[1], tree)
1115 return tuple(_expandargs(t, args) for t in tree)
1116
1117 def _expandaliases(aliases, tree, expanding):
1118 """Expand aliases in tree, recursively.
1119
1120 'aliases' is a dictionary mapping user defined aliases to
1121 revsetalias objects.
1122 """
1123 if not isinstance(tree, tuple):
1124 # Do not expand raw strings
1125 return tree
1126 alias = _getalias(aliases, tree)
1127 if alias is not None:
1128 if alias in expanding:
1129 raise error.ParseError(_('infinite expansion of revset alias "%s" '
1130 'detected') % alias.name)
1131 expanding.append(alias)
1132 result = alias.replacement
1133 if alias.args is not None:
1097 l = getlist(tree[2])
1134 l = getlist(tree[2])
1098 if len(l) != len(self.args):
1135 if len(l) != len(alias.args):
1099 raise error.ParseError(
1136 raise error.ParseError(
1100 _('invalid number of arguments: %s') % len(l))
1137 _('invalid number of arguments: %s') % len(l))
1101 result = self.replacement
1138 result = _expandargs(result, dict(zip(alias.args, l)))
1102 for a, v in zip(self.args, l):
1139 # Recurse in place, the base expression may have been rewritten
1103 valalias = revsetalias(('string', a), v)
1140 result = _expandaliases(aliases, result, expanding)
1104 result = valalias.process(result)
1141 expanding.pop()
1142 else:
1143 result = tuple(_expandaliases(aliases, t, expanding)
1144 for t in tree)
1105 return result
1145 return result
1106 return tuple(map(self.process, tree))
1107 return tree
1108
1146
1109 def findaliases(ui, tree):
1147 def findaliases(ui, tree):
1148 aliases = {}
1110 for k, v in ui.configitems('revsetalias'):
1149 for k, v in ui.configitems('revsetalias'):
1111 alias = revsetalias(k, v)
1150 alias = revsetalias(k, v)
1112 tree = alias.process(tree)
1151 aliases[alias.name] = alias
1113 return tree
1152 return _expandaliases(aliases, tree, [])
1114
1153
1115 parse = parser.parser(tokenize, elements).parse
1154 parse = parser.parser(tokenize, elements).parse
1116
1155
1117 def match(ui, spec):
1156 def match(ui, spec):
1118 if not spec:
1157 if not spec:
1119 raise error.ParseError(_("empty query"))
1158 raise error.ParseError(_("empty query"))
1120 tree, pos = parse(spec)
1159 tree, pos = parse(spec)
1121 if (pos != len(spec)):
1160 if (pos != len(spec)):
1122 raise error.ParseError(_("invalid token"), pos)
1161 raise error.ParseError(_("invalid token"), pos)
1123 if ui:
1162 if ui:
1124 tree = findaliases(ui, tree)
1163 tree = findaliases(ui, tree)
1125 weight, tree = optimize(tree, True)
1164 weight, tree = optimize(tree, True)
1126 def mfunc(repo, subset):
1165 def mfunc(repo, subset):
1127 return getset(repo, subset, tree)
1166 return getset(repo, subset, tree)
1128 return mfunc
1167 return mfunc
1129
1168
1130 def formatspec(expr, *args):
1169 def formatspec(expr, *args):
1131 '''
1170 '''
1132 This is a convenience function for using revsets internally, and
1171 This is a convenience function for using revsets internally, and
1133 escapes arguments appropriately. Aliases are intentionally ignored
1172 escapes arguments appropriately. Aliases are intentionally ignored
1134 so that intended expression behavior isn't accidentally subverted.
1173 so that intended expression behavior isn't accidentally subverted.
1135
1174
1136 Supported arguments:
1175 Supported arguments:
1137
1176
1138 %r = revset expression, parenthesized
1177 %r = revset expression, parenthesized
1139 %d = int(arg), no quoting
1178 %d = int(arg), no quoting
1140 %s = string(arg), escaped and single-quoted
1179 %s = string(arg), escaped and single-quoted
1141 %b = arg.branch(), escaped and single-quoted
1180 %b = arg.branch(), escaped and single-quoted
1142 %n = hex(arg), single-quoted
1181 %n = hex(arg), single-quoted
1143 %% = a literal '%'
1182 %% = a literal '%'
1144
1183
1145 Prefixing the type with 'l' specifies a parenthesized list of that type.
1184 Prefixing the type with 'l' specifies a parenthesized list of that type.
1146
1185
1147 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1186 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1148 '(10 or 11):: and ((this()) or (that()))'
1187 '(10 or 11):: and ((this()) or (that()))'
1149 >>> formatspec('%d:: and not %d::', 10, 20)
1188 >>> formatspec('%d:: and not %d::', 10, 20)
1150 '10:: and not 20::'
1189 '10:: and not 20::'
1151 >>> formatspec('%ld or %ld', [], [1])
1190 >>> formatspec('%ld or %ld', [], [1])
1152 "_list('') or 1"
1191 "_list('') or 1"
1153 >>> formatspec('keyword(%s)', 'foo\\xe9')
1192 >>> formatspec('keyword(%s)', 'foo\\xe9')
1154 "keyword('foo\\\\xe9')"
1193 "keyword('foo\\\\xe9')"
1155 >>> b = lambda: 'default'
1194 >>> b = lambda: 'default'
1156 >>> b.branch = b
1195 >>> b.branch = b
1157 >>> formatspec('branch(%b)', b)
1196 >>> formatspec('branch(%b)', b)
1158 "branch('default')"
1197 "branch('default')"
1159 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1198 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1160 "root(_list('a\\x00b\\x00c\\x00d'))"
1199 "root(_list('a\\x00b\\x00c\\x00d'))"
1161 '''
1200 '''
1162
1201
1163 def quote(s):
1202 def quote(s):
1164 return repr(str(s))
1203 return repr(str(s))
1165
1204
1166 def argtype(c, arg):
1205 def argtype(c, arg):
1167 if c == 'd':
1206 if c == 'd':
1168 return str(int(arg))
1207 return str(int(arg))
1169 elif c == 's':
1208 elif c == 's':
1170 return quote(arg)
1209 return quote(arg)
1171 elif c == 'r':
1210 elif c == 'r':
1172 parse(arg) # make sure syntax errors are confined
1211 parse(arg) # make sure syntax errors are confined
1173 return '(%s)' % arg
1212 return '(%s)' % arg
1174 elif c == 'n':
1213 elif c == 'n':
1175 return quote(nodemod.hex(arg))
1214 return quote(nodemod.hex(arg))
1176 elif c == 'b':
1215 elif c == 'b':
1177 return quote(arg.branch())
1216 return quote(arg.branch())
1178
1217
1179 def listexp(s, t):
1218 def listexp(s, t):
1180 l = len(s)
1219 l = len(s)
1181 if l == 0:
1220 if l == 0:
1182 return "_list('')"
1221 return "_list('')"
1183 elif l == 1:
1222 elif l == 1:
1184 return argtype(t, s[0])
1223 return argtype(t, s[0])
1185 elif t == 'd':
1224 elif t == 'd':
1186 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1225 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1187 elif t == 's':
1226 elif t == 's':
1188 return "_list('%s')" % "\0".join(s)
1227 return "_list('%s')" % "\0".join(s)
1189 elif t == 'n':
1228 elif t == 'n':
1190 return "_list('%s')" % "\0".join(nodemod.hex(a) for a in s)
1229 return "_list('%s')" % "\0".join(nodemod.hex(a) for a in s)
1191 elif t == 'b':
1230 elif t == 'b':
1192 return "_list('%s')" % "\0".join(a.branch() for a in s)
1231 return "_list('%s')" % "\0".join(a.branch() for a in s)
1193
1232
1194 m = l // 2
1233 m = l // 2
1195 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1234 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1196
1235
1197 ret = ''
1236 ret = ''
1198 pos = 0
1237 pos = 0
1199 arg = 0
1238 arg = 0
1200 while pos < len(expr):
1239 while pos < len(expr):
1201 c = expr[pos]
1240 c = expr[pos]
1202 if c == '%':
1241 if c == '%':
1203 pos += 1
1242 pos += 1
1204 d = expr[pos]
1243 d = expr[pos]
1205 if d == '%':
1244 if d == '%':
1206 ret += d
1245 ret += d
1207 elif d in 'dsnbr':
1246 elif d in 'dsnbr':
1208 ret += argtype(d, args[arg])
1247 ret += argtype(d, args[arg])
1209 arg += 1
1248 arg += 1
1210 elif d == 'l':
1249 elif d == 'l':
1211 # a list of some type
1250 # a list of some type
1212 pos += 1
1251 pos += 1
1213 d = expr[pos]
1252 d = expr[pos]
1214 ret += listexp(list(args[arg]), d)
1253 ret += listexp(list(args[arg]), d)
1215 arg += 1
1254 arg += 1
1216 else:
1255 else:
1217 raise util.Abort('unexpected revspec format character %s' % d)
1256 raise util.Abort('unexpected revspec format character %s' % d)
1218 else:
1257 else:
1219 ret += c
1258 ret += c
1220 pos += 1
1259 pos += 1
1221
1260
1222 return ret
1261 return ret
1223
1262
1224 # tell hggettext to extract docstrings from these functions:
1263 # tell hggettext to extract docstrings from these functions:
1225 i18nfunctions = symbols.values()
1264 i18nfunctions = symbols.values()
@@ -1,553 +1,594 b''
1 $ "$TESTDIR/hghave" no-msys || exit 80 # MSYS will translate /a/b/c/ as if it was a real file path
1 $ "$TESTDIR/hghave" no-msys || exit 80 # MSYS will translate /a/b/c/ as if it was a real file path
2
2
3 $ HGENCODING=utf-8
3 $ HGENCODING=utf-8
4 $ export HGENCODING
4 $ export HGENCODING
5
5
6 $ try() {
6 $ try() {
7 > hg debugrevspec --debug "$@"
7 > hg debugrevspec --debug "$@"
8 > }
8 > }
9
9
10 $ log() {
10 $ log() {
11 > hg log --template '{rev}\n' -r "$1"
11 > hg log --template '{rev}\n' -r "$1"
12 > }
12 > }
13
13
14 $ hg init repo
14 $ hg init repo
15 $ cd repo
15 $ cd repo
16
16
17 $ echo a > a
17 $ echo a > a
18 $ hg branch a
18 $ hg branch a
19 marked working directory as branch a
19 marked working directory as branch a
20 (branches are permanent and global, did you want a bookmark?)
20 (branches are permanent and global, did you want a bookmark?)
21 $ hg ci -Aqm0
21 $ hg ci -Aqm0
22
22
23 $ echo b > b
23 $ echo b > b
24 $ hg branch b
24 $ hg branch b
25 marked working directory as branch b
25 marked working directory as branch b
26 (branches are permanent and global, did you want a bookmark?)
26 (branches are permanent and global, did you want a bookmark?)
27 $ hg ci -Aqm1
27 $ hg ci -Aqm1
28
28
29 $ rm a
29 $ rm a
30 $ hg branch a-b-c-
30 $ hg branch a-b-c-
31 marked working directory as branch a-b-c-
31 marked working directory as branch a-b-c-
32 (branches are permanent and global, did you want a bookmark?)
32 (branches are permanent and global, did you want a bookmark?)
33 $ hg ci -Aqm2 -u Bob
33 $ hg ci -Aqm2 -u Bob
34
34
35 $ hg co 1
35 $ hg co 1
36 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
36 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
37 $ hg branch +a+b+c+
37 $ hg branch +a+b+c+
38 marked working directory as branch +a+b+c+
38 marked working directory as branch +a+b+c+
39 (branches are permanent and global, did you want a bookmark?)
39 (branches are permanent and global, did you want a bookmark?)
40 $ hg ci -Aqm3
40 $ hg ci -Aqm3
41
41
42 $ hg co 2 # interleave
42 $ hg co 2 # interleave
43 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
43 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
44 $ echo bb > b
44 $ echo bb > b
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 (branches are permanent and global, did you want a bookmark?)
47 (branches are permanent and global, did you want a bookmark?)
48 $ hg ci -Aqm4 -d "May 12 2005"
48 $ hg ci -Aqm4 -d "May 12 2005"
49
49
50 $ hg co 3
50 $ hg co 3
51 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
51 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
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 (branches are permanent and global, did you want a bookmark?)
54 (branches are permanent and global, did you want a bookmark?)
55 $ hg ci -Aqm"5 bug"
55 $ hg ci -Aqm"5 bug"
56
56
57 $ hg merge 4
57 $ hg merge 4
58 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
58 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
59 (branch merge, don't forget to commit)
59 (branch merge, don't forget to commit)
60 $ hg branch _a_b_c_
60 $ hg branch _a_b_c_
61 marked working directory as branch _a_b_c_
61 marked working directory as branch _a_b_c_
62 (branches are permanent and global, did you want a bookmark?)
62 (branches are permanent and global, did you want a bookmark?)
63 $ hg ci -Aqm"6 issue619"
63 $ hg ci -Aqm"6 issue619"
64
64
65 $ hg branch .a.b.c.
65 $ hg branch .a.b.c.
66 marked working directory as branch .a.b.c.
66 marked working directory as branch .a.b.c.
67 (branches are permanent and global, did you want a bookmark?)
67 (branches are permanent and global, did you want a bookmark?)
68 $ hg ci -Aqm7
68 $ hg ci -Aqm7
69
69
70 $ hg branch all
70 $ hg branch all
71 marked working directory as branch all
71 marked working directory as branch all
72 (branches are permanent and global, did you want a bookmark?)
72 (branches are permanent and global, did you want a bookmark?)
73 $ hg ci --close-branch -Aqm8
73 $ hg ci --close-branch -Aqm8
74 abort: can only close branch heads
74 abort: can only close branch heads
75 [255]
75 [255]
76
76
77 $ hg co 4
77 $ hg co 4
78 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
78 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
79 $ hg branch Γ©
79 $ hg branch Γ©
80 marked working directory as branch \xc3\xa9 (esc)
80 marked working directory as branch \xc3\xa9 (esc)
81 (branches are permanent and global, did you want a bookmark?)
81 (branches are permanent and global, did you want a bookmark?)
82 $ hg ci -Aqm9
82 $ hg ci -Aqm9
83
83
84 $ hg tag -r6 1.0
84 $ hg tag -r6 1.0
85
85
86 $ hg clone --quiet -U -r 7 . ../remote1
86 $ hg clone --quiet -U -r 7 . ../remote1
87 $ hg clone --quiet -U -r 8 . ../remote2
87 $ hg clone --quiet -U -r 8 . ../remote2
88 $ echo "[paths]" >> .hg/hgrc
88 $ echo "[paths]" >> .hg/hgrc
89 $ echo "default = ../remote1" >> .hg/hgrc
89 $ echo "default = ../remote1" >> .hg/hgrc
90
90
91 names that should work without quoting
91 names that should work without quoting
92
92
93 $ try a
93 $ try a
94 ('symbol', 'a')
94 ('symbol', 'a')
95 0
95 0
96 $ try b-a
96 $ try b-a
97 ('minus', ('symbol', 'b'), ('symbol', 'a'))
97 ('minus', ('symbol', 'b'), ('symbol', 'a'))
98 1
98 1
99 $ try _a_b_c_
99 $ try _a_b_c_
100 ('symbol', '_a_b_c_')
100 ('symbol', '_a_b_c_')
101 6
101 6
102 $ try _a_b_c_-a
102 $ try _a_b_c_-a
103 ('minus', ('symbol', '_a_b_c_'), ('symbol', 'a'))
103 ('minus', ('symbol', '_a_b_c_'), ('symbol', 'a'))
104 6
104 6
105 $ try .a.b.c.
105 $ try .a.b.c.
106 ('symbol', '.a.b.c.')
106 ('symbol', '.a.b.c.')
107 7
107 7
108 $ try .a.b.c.-a
108 $ try .a.b.c.-a
109 ('minus', ('symbol', '.a.b.c.'), ('symbol', 'a'))
109 ('minus', ('symbol', '.a.b.c.'), ('symbol', 'a'))
110 7
110 7
111 $ try -- '-a-b-c-' # complains
111 $ try -- '-a-b-c-' # complains
112 hg: parse error at 7: not a prefix: end
112 hg: parse error at 7: not a prefix: end
113 [255]
113 [255]
114 $ log -a-b-c- # succeeds with fallback
114 $ log -a-b-c- # succeeds with fallback
115 4
115 4
116 $ try -- -a-b-c--a # complains
116 $ try -- -a-b-c--a # complains
117 ('minus', ('minus', ('minus', ('negate', ('symbol', 'a')), ('symbol', 'b')), ('symbol', 'c')), ('negate', ('symbol', 'a')))
117 ('minus', ('minus', ('minus', ('negate', ('symbol', 'a')), ('symbol', 'b')), ('symbol', 'c')), ('negate', ('symbol', 'a')))
118 abort: unknown revision '-a'!
118 abort: unknown revision '-a'!
119 [255]
119 [255]
120 $ try Γ©
120 $ try Γ©
121 ('symbol', '\xc3\xa9')
121 ('symbol', '\xc3\xa9')
122 9
122 9
123
123
124 quoting needed
124 quoting needed
125
125
126 $ try '"-a-b-c-"-a'
126 $ try '"-a-b-c-"-a'
127 ('minus', ('string', '-a-b-c-'), ('symbol', 'a'))
127 ('minus', ('string', '-a-b-c-'), ('symbol', 'a'))
128 4
128 4
129
129
130 $ log '1 or 2'
130 $ log '1 or 2'
131 1
131 1
132 2
132 2
133 $ log '1|2'
133 $ log '1|2'
134 1
134 1
135 2
135 2
136 $ log '1 and 2'
136 $ log '1 and 2'
137 $ log '1&2'
137 $ log '1&2'
138 $ try '1&2|3' # precedence - and is higher
138 $ try '1&2|3' # precedence - and is higher
139 ('or', ('and', ('symbol', '1'), ('symbol', '2')), ('symbol', '3'))
139 ('or', ('and', ('symbol', '1'), ('symbol', '2')), ('symbol', '3'))
140 3
140 3
141 $ try '1|2&3'
141 $ try '1|2&3'
142 ('or', ('symbol', '1'), ('and', ('symbol', '2'), ('symbol', '3')))
142 ('or', ('symbol', '1'), ('and', ('symbol', '2'), ('symbol', '3')))
143 1
143 1
144 $ try '1&2&3' # associativity
144 $ try '1&2&3' # associativity
145 ('and', ('and', ('symbol', '1'), ('symbol', '2')), ('symbol', '3'))
145 ('and', ('and', ('symbol', '1'), ('symbol', '2')), ('symbol', '3'))
146 $ try '1|(2|3)'
146 $ try '1|(2|3)'
147 ('or', ('symbol', '1'), ('group', ('or', ('symbol', '2'), ('symbol', '3'))))
147 ('or', ('symbol', '1'), ('group', ('or', ('symbol', '2'), ('symbol', '3'))))
148 1
148 1
149 2
149 2
150 3
150 3
151 $ log '1.0' # tag
151 $ log '1.0' # tag
152 6
152 6
153 $ log 'a' # branch
153 $ log 'a' # branch
154 0
154 0
155 $ log '2785f51ee'
155 $ log '2785f51ee'
156 0
156 0
157 $ log 'date(2005)'
157 $ log 'date(2005)'
158 4
158 4
159 $ log 'date(this is a test)'
159 $ log 'date(this is a test)'
160 hg: parse error at 10: unexpected token: symbol
160 hg: parse error at 10: unexpected token: symbol
161 [255]
161 [255]
162 $ log 'date()'
162 $ log 'date()'
163 hg: parse error: date requires a string
163 hg: parse error: date requires a string
164 [255]
164 [255]
165 $ log 'date'
165 $ log 'date'
166 hg: parse error: can't use date here
166 hg: parse error: can't use date here
167 [255]
167 [255]
168 $ log 'date('
168 $ log 'date('
169 hg: parse error at 5: not a prefix: end
169 hg: parse error at 5: not a prefix: end
170 [255]
170 [255]
171 $ log 'date(tip)'
171 $ log 'date(tip)'
172 abort: invalid date: 'tip'
172 abort: invalid date: 'tip'
173 [255]
173 [255]
174 $ log '"date"'
174 $ log '"date"'
175 abort: unknown revision 'date'!
175 abort: unknown revision 'date'!
176 [255]
176 [255]
177 $ log 'date(2005) and 1::'
177 $ log 'date(2005) and 1::'
178 4
178 4
179
179
180 $ log 'ancestor(1)'
180 $ log 'ancestor(1)'
181 hg: parse error: ancestor requires two arguments
181 hg: parse error: ancestor requires two arguments
182 [255]
182 [255]
183 $ log 'ancestor(4,5)'
183 $ log 'ancestor(4,5)'
184 1
184 1
185 $ log 'ancestor(4,5) and 4'
185 $ log 'ancestor(4,5) and 4'
186 $ log 'ancestors(5)'
186 $ log 'ancestors(5)'
187 0
187 0
188 1
188 1
189 3
189 3
190 5
190 5
191 $ log 'author(bob)'
191 $ log 'author(bob)'
192 2
192 2
193 $ log 'branch(Γ©)'
193 $ log 'branch(Γ©)'
194 8
194 8
195 9
195 9
196 $ log 'children(ancestor(4,5))'
196 $ log 'children(ancestor(4,5))'
197 2
197 2
198 3
198 3
199 $ log 'closed()'
199 $ log 'closed()'
200 $ log 'contains(a)'
200 $ log 'contains(a)'
201 0
201 0
202 1
202 1
203 3
203 3
204 5
204 5
205 $ log 'desc(B)'
205 $ log 'desc(B)'
206 5
206 5
207 $ log 'descendants(2 or 3)'
207 $ log 'descendants(2 or 3)'
208 2
208 2
209 3
209 3
210 4
210 4
211 5
211 5
212 6
212 6
213 7
213 7
214 8
214 8
215 9
215 9
216 $ log 'file(b)'
216 $ log 'file(b)'
217 1
217 1
218 4
218 4
219 $ log 'follow()'
219 $ log 'follow()'
220 0
220 0
221 1
221 1
222 2
222 2
223 4
223 4
224 8
224 8
225 9
225 9
226 $ log 'grep("issue\d+")'
226 $ log 'grep("issue\d+")'
227 6
227 6
228 $ try 'grep("(")' # invalid regular expression
228 $ try 'grep("(")' # invalid regular expression
229 ('func', ('symbol', 'grep'), ('string', '('))
229 ('func', ('symbol', 'grep'), ('string', '('))
230 hg: parse error: invalid match pattern: unbalanced parenthesis
230 hg: parse error: invalid match pattern: unbalanced parenthesis
231 [255]
231 [255]
232 $ try 'grep("\bissue\d+")'
232 $ try 'grep("\bissue\d+")'
233 ('func', ('symbol', 'grep'), ('string', '\x08issue\\d+'))
233 ('func', ('symbol', 'grep'), ('string', '\x08issue\\d+'))
234 $ try 'grep(r"\bissue\d+")'
234 $ try 'grep(r"\bissue\d+")'
235 ('func', ('symbol', 'grep'), ('string', '\\bissue\\d+'))
235 ('func', ('symbol', 'grep'), ('string', '\\bissue\\d+'))
236 6
236 6
237 $ try 'grep(r"\")'
237 $ try 'grep(r"\")'
238 hg: parse error at 7: unterminated string
238 hg: parse error at 7: unterminated string
239 [255]
239 [255]
240 $ log 'head()'
240 $ log 'head()'
241 0
241 0
242 1
242 1
243 2
243 2
244 3
244 3
245 4
245 4
246 5
246 5
247 6
247 6
248 7
248 7
249 9
249 9
250 $ log 'heads(6::)'
250 $ log 'heads(6::)'
251 7
251 7
252 $ log 'keyword(issue)'
252 $ log 'keyword(issue)'
253 6
253 6
254 $ log 'limit(head(), 1)'
254 $ log 'limit(head(), 1)'
255 0
255 0
256 $ log 'max(contains(a))'
256 $ log 'max(contains(a))'
257 5
257 5
258 $ log 'min(contains(a))'
258 $ log 'min(contains(a))'
259 0
259 0
260 $ log 'merge()'
260 $ log 'merge()'
261 6
261 6
262 $ log 'modifies(b)'
262 $ log 'modifies(b)'
263 4
263 4
264 $ log 'id(5)'
264 $ log 'id(5)'
265 2
265 2
266 $ log 'outgoing()'
266 $ log 'outgoing()'
267 8
267 8
268 9
268 9
269 $ log 'outgoing("../remote1")'
269 $ log 'outgoing("../remote1")'
270 8
270 8
271 9
271 9
272 $ log 'outgoing("../remote2")'
272 $ log 'outgoing("../remote2")'
273 3
273 3
274 5
274 5
275 6
275 6
276 7
276 7
277 9
277 9
278 $ log 'p1(merge())'
278 $ log 'p1(merge())'
279 5
279 5
280 $ log 'p2(merge())'
280 $ log 'p2(merge())'
281 4
281 4
282 $ log 'parents(merge())'
282 $ log 'parents(merge())'
283 4
283 4
284 5
284 5
285 $ log 'removes(a)'
285 $ log 'removes(a)'
286 2
286 2
287 6
287 6
288 $ log 'roots(all())'
288 $ log 'roots(all())'
289 0
289 0
290 $ log 'reverse(2 or 3 or 4 or 5)'
290 $ log 'reverse(2 or 3 or 4 or 5)'
291 5
291 5
292 4
292 4
293 3
293 3
294 2
294 2
295 $ log 'rev(5)'
295 $ log 'rev(5)'
296 5
296 5
297 $ log 'sort(limit(reverse(all()), 3))'
297 $ log 'sort(limit(reverse(all()), 3))'
298 7
298 7
299 8
299 8
300 9
300 9
301 $ log 'sort(2 or 3 or 4 or 5, date)'
301 $ log 'sort(2 or 3 or 4 or 5, date)'
302 2
302 2
303 3
303 3
304 5
304 5
305 4
305 4
306 $ log 'tagged()'
306 $ log 'tagged()'
307 6
307 6
308 $ log 'tag()'
308 $ log 'tag()'
309 6
309 6
310 $ log 'tag(1.0)'
310 $ log 'tag(1.0)'
311 6
311 6
312 $ log 'tag(tip)'
312 $ log 'tag(tip)'
313 9
313 9
314 $ log 'tag(unknown)'
314 $ log 'tag(unknown)'
315 abort: tag 'unknown' does not exist
315 abort: tag 'unknown' does not exist
316 [255]
316 [255]
317 $ log 'branch(unknown)'
317 $ log 'branch(unknown)'
318 abort: unknown revision 'unknown'!
318 abort: unknown revision 'unknown'!
319 [255]
319 [255]
320 $ log 'user(bob)'
320 $ log 'user(bob)'
321 2
321 2
322
322
323 $ log '4::8'
323 $ log '4::8'
324 4
324 4
325 8
325 8
326 $ log '4:8'
326 $ log '4:8'
327 4
327 4
328 5
328 5
329 6
329 6
330 7
330 7
331 8
331 8
332
332
333 $ log 'sort(!merge() & (modifies(b) | user(bob) | keyword(bug) | keyword(issue) & 1::9), "-date")'
333 $ log 'sort(!merge() & (modifies(b) | user(bob) | keyword(bug) | keyword(issue) & 1::9), "-date")'
334 4
334 4
335 2
335 2
336 5
336 5
337
337
338 $ log 'not 0 and 0:2'
338 $ log 'not 0 and 0:2'
339 1
339 1
340 2
340 2
341 $ log 'not 1 and 0:2'
341 $ log 'not 1 and 0:2'
342 0
342 0
343 2
343 2
344 $ log 'not 2 and 0:2'
344 $ log 'not 2 and 0:2'
345 0
345 0
346 1
346 1
347 $ log '(1 and 2)::'
347 $ log '(1 and 2)::'
348 $ log '(1 and 2):'
348 $ log '(1 and 2):'
349 $ log '(1 and 2):3'
349 $ log '(1 and 2):3'
350 $ log 'sort(head(), -rev)'
350 $ log 'sort(head(), -rev)'
351 9
351 9
352 7
352 7
353 6
353 6
354 5
354 5
355 4
355 4
356 3
356 3
357 2
357 2
358 1
358 1
359 0
359 0
360 $ log '4::8 - 8'
360 $ log '4::8 - 8'
361 4
361 4
362
362
363 issue2437
363 issue2437
364
364
365 $ log '3 and p1(5)'
365 $ log '3 and p1(5)'
366 3
366 3
367 $ log '4 and p2(6)'
367 $ log '4 and p2(6)'
368 4
368 4
369 $ log '1 and parents(:2)'
369 $ log '1 and parents(:2)'
370 1
370 1
371 $ log '2 and children(1:)'
371 $ log '2 and children(1:)'
372 2
372 2
373 $ log 'roots(all()) or roots(all())'
373 $ log 'roots(all()) or roots(all())'
374 0
374 0
375 $ log 'heads(branch(Γ©)) or heads(branch(Γ©))'
375 $ log 'heads(branch(Γ©)) or heads(branch(Γ©))'
376 9
376 9
377 $ log 'ancestors(8) and (heads(branch("-a-b-c-")) or heads(branch(Γ©)))'
377 $ log 'ancestors(8) and (heads(branch("-a-b-c-")) or heads(branch(Γ©)))'
378 4
378 4
379
379
380 issue2654: report a parse error if the revset was not completely parsed
380 issue2654: report a parse error if the revset was not completely parsed
381
381
382 $ log '1 OR 2'
382 $ log '1 OR 2'
383 hg: parse error at 2: invalid token
383 hg: parse error at 2: invalid token
384 [255]
384 [255]
385
385
386 or operator should preserve ordering:
386 or operator should preserve ordering:
387 $ log 'reverse(2::4) or tip'
387 $ log 'reverse(2::4) or tip'
388 4
388 4
389 2
389 2
390 9
390 9
391
391
392 parentrevspec
392 parentrevspec
393
393
394 $ log 'merge()^0'
394 $ log 'merge()^0'
395 6
395 6
396 $ log 'merge()^'
396 $ log 'merge()^'
397 5
397 5
398 $ log 'merge()^1'
398 $ log 'merge()^1'
399 5
399 5
400 $ log 'merge()^2'
400 $ log 'merge()^2'
401 4
401 4
402 $ log 'merge()^^'
402 $ log 'merge()^^'
403 3
403 3
404 $ log 'merge()^1^'
404 $ log 'merge()^1^'
405 3
405 3
406 $ log 'merge()^^^'
406 $ log 'merge()^^^'
407 1
407 1
408
408
409 $ log 'merge()~0'
409 $ log 'merge()~0'
410 6
410 6
411 $ log 'merge()~1'
411 $ log 'merge()~1'
412 5
412 5
413 $ log 'merge()~2'
413 $ log 'merge()~2'
414 3
414 3
415 $ log 'merge()~2^1'
415 $ log 'merge()~2^1'
416 1
416 1
417 $ log 'merge()~3'
417 $ log 'merge()~3'
418 1
418 1
419
419
420 $ log '(-3:tip)^'
420 $ log '(-3:tip)^'
421 4
421 4
422 6
422 6
423 8
423 8
424
424
425 $ log 'tip^foo'
425 $ log 'tip^foo'
426 hg: parse error: ^ expects a number 0, 1, or 2
426 hg: parse error: ^ expects a number 0, 1, or 2
427 [255]
427 [255]
428
428
429 aliases:
429 aliases:
430
430
431 $ echo '[revsetalias]' >> .hg/hgrc
431 $ echo '[revsetalias]' >> .hg/hgrc
432 $ echo 'm = merge()' >> .hg/hgrc
432 $ echo 'm = merge()' >> .hg/hgrc
433 $ echo 'sincem = descendants(m)' >> .hg/hgrc
433 $ echo 'd($1) = reverse(sort($1, date))' >> .hg/hgrc
434 $ echo 'd($1) = reverse(sort($1, date))' >> .hg/hgrc
434 $ echo 'rs(ARG1, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
435 $ echo 'rs(ARG1, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
435 $ echo 'rs4(ARG1, ARGA, ARGB, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
436 $ echo 'rs4(ARG1, ARGA, ARGB, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
436
437
437 $ try m
438 $ try m
438 ('symbol', 'm')
439 ('symbol', 'm')
439 ('func', ('symbol', 'merge'), None)
440 ('func', ('symbol', 'merge'), None)
440 6
441 6
442
443 test alias recursion
444
445 $ try sincem
446 ('symbol', 'sincem')
447 ('func', ('symbol', 'descendants'), ('func', ('symbol', 'merge'), None))
448 6
449 7
450
451 test infinite recursion
452
453 $ echo 'recurse1 = recurse2' >> .hg/hgrc
454 $ echo 'recurse2 = recurse1' >> .hg/hgrc
455 $ try recurse1
456 ('symbol', 'recurse1')
457 hg: parse error: infinite expansion of revset alias "recurse1" detected
458 [255]
459
460 test nesting and variable passing
461
462 $ echo 'nested($1) = nested2($1)' >> .hg/hgrc
463 $ echo 'nested2($1) = nested3($1)' >> .hg/hgrc
464 $ echo 'nested3($1) = max($1)' >> .hg/hgrc
465 $ try 'nested(2:5)'
466 ('func', ('symbol', 'nested'), ('range', ('symbol', '2'), ('symbol', '5')))
467 ('func', ('symbol', 'max'), ('range', ('symbol', '2'), ('symbol', '5')))
468 5
469
470 test variable isolation, variable placeholders are rewritten as string
471 then parsed and matched again as string. Check they do not leak too
472 far away.
473
474 $ echo 'injectparamasstring = max("$1")' >> .hg/hgrc
475 $ echo 'callinjection($1) = descendants(injectparamasstring)' >> .hg/hgrc
476 $ try 'callinjection(2:5)'
477 ('func', ('symbol', 'callinjection'), ('range', ('symbol', '2'), ('symbol', '5')))
478 ('func', ('symbol', 'descendants'), ('func', ('symbol', 'max'), ('string', '$1')))
479 abort: unknown revision '$1'!
480 [255]
481
441 $ try 'd(2:5)'
482 $ try 'd(2:5)'
442 ('func', ('symbol', 'd'), ('range', ('symbol', '2'), ('symbol', '5')))
483 ('func', ('symbol', 'd'), ('range', ('symbol', '2'), ('symbol', '5')))
443 ('func', ('symbol', 'reverse'), ('func', ('symbol', 'sort'), ('list', ('range', ('symbol', '2'), ('symbol', '5')), ('symbol', 'date'))))
484 ('func', ('symbol', 'reverse'), ('func', ('symbol', 'sort'), ('list', ('range', ('symbol', '2'), ('symbol', '5')), ('symbol', 'date'))))
444 4
485 4
445 5
486 5
446 3
487 3
447 2
488 2
448 $ try 'rs(2 or 3, date)'
489 $ try 'rs(2 or 3, date)'
449 ('func', ('symbol', 'rs'), ('list', ('or', ('symbol', '2'), ('symbol', '3')), ('symbol', 'date')))
490 ('func', ('symbol', 'rs'), ('list', ('or', ('symbol', '2'), ('symbol', '3')), ('symbol', 'date')))
450 ('func', ('symbol', 'reverse'), ('func', ('symbol', 'sort'), ('list', ('or', ('symbol', '2'), ('symbol', '3')), ('symbol', 'date'))))
491 ('func', ('symbol', 'reverse'), ('func', ('symbol', 'sort'), ('list', ('or', ('symbol', '2'), ('symbol', '3')), ('symbol', 'date'))))
451 3
492 3
452 2
493 2
453 $ try 'rs()'
494 $ try 'rs()'
454 ('func', ('symbol', 'rs'), None)
495 ('func', ('symbol', 'rs'), None)
455 hg: parse error: invalid number of arguments: 0
496 hg: parse error: invalid number of arguments: 0
456 [255]
497 [255]
457 $ try 'rs(2)'
498 $ try 'rs(2)'
458 ('func', ('symbol', 'rs'), ('symbol', '2'))
499 ('func', ('symbol', 'rs'), ('symbol', '2'))
459 hg: parse error: invalid number of arguments: 1
500 hg: parse error: invalid number of arguments: 1
460 [255]
501 [255]
461 $ try 'rs(2, data, 7)'
502 $ try 'rs(2, data, 7)'
462 ('func', ('symbol', 'rs'), ('list', ('list', ('symbol', '2'), ('symbol', 'data')), ('symbol', '7')))
503 ('func', ('symbol', 'rs'), ('list', ('list', ('symbol', '2'), ('symbol', 'data')), ('symbol', '7')))
463 hg: parse error: invalid number of arguments: 3
504 hg: parse error: invalid number of arguments: 3
464 [255]
505 [255]
465 $ try 'rs4(2 or 3, x, x, date)'
506 $ try 'rs4(2 or 3, x, x, date)'
466 ('func', ('symbol', 'rs4'), ('list', ('list', ('list', ('or', ('symbol', '2'), ('symbol', '3')), ('symbol', 'x')), ('symbol', 'x')), ('symbol', 'date')))
507 ('func', ('symbol', 'rs4'), ('list', ('list', ('list', ('or', ('symbol', '2'), ('symbol', '3')), ('symbol', 'x')), ('symbol', 'x')), ('symbol', 'date')))
467 ('func', ('symbol', 'reverse'), ('func', ('symbol', 'sort'), ('list', ('or', ('symbol', '2'), ('symbol', '3')), ('symbol', 'date'))))
508 ('func', ('symbol', 'reverse'), ('func', ('symbol', 'sort'), ('list', ('or', ('symbol', '2'), ('symbol', '3')), ('symbol', 'date'))))
468 3
509 3
469 2
510 2
470
511
471 issue2549 - correct optimizations
512 issue2549 - correct optimizations
472
513
473 $ log 'limit(1 or 2 or 3, 2) and not 2'
514 $ log 'limit(1 or 2 or 3, 2) and not 2'
474 1
515 1
475 $ log 'max(1 or 2) and not 2'
516 $ log 'max(1 or 2) and not 2'
476 $ log 'min(1 or 2) and not 1'
517 $ log 'min(1 or 2) and not 1'
477 $ log 'last(1 or 2, 1) and not 2'
518 $ log 'last(1 or 2, 1) and not 2'
478
519
479 tests for 'remote()' predicate:
520 tests for 'remote()' predicate:
480 #. (csets in remote) (id) (remote)
521 #. (csets in remote) (id) (remote)
481 1. less than local current branch "default"
522 1. less than local current branch "default"
482 2. same with local specified "default"
523 2. same with local specified "default"
483 3. more than local specified specified
524 3. more than local specified specified
484
525
485 $ hg clone --quiet -U . ../remote3
526 $ hg clone --quiet -U . ../remote3
486 $ cd ../remote3
527 $ cd ../remote3
487 $ hg update -q 7
528 $ hg update -q 7
488 $ echo r > r
529 $ echo r > r
489 $ hg ci -Aqm 10
530 $ hg ci -Aqm 10
490 $ log 'remote()'
531 $ log 'remote()'
491 7
532 7
492 $ log 'remote("a-b-c-")'
533 $ log 'remote("a-b-c-")'
493 2
534 2
494 $ cd ../repo
535 $ cd ../repo
495 $ log 'remote(".a.b.c.", "../remote3")'
536 $ log 'remote(".a.b.c.", "../remote3")'
496
537
497 $ cd ..
538 $ cd ..
498
539
499 test author/desc/keyword in problematic encoding
540 test author/desc/keyword in problematic encoding
500 # unicode: cp932:
541 # unicode: cp932:
501 # u30A2 0x83 0x41(= 'A')
542 # u30A2 0x83 0x41(= 'A')
502 # u30C2 0x83 0x61(= 'a')
543 # u30C2 0x83 0x61(= 'a')
503
544
504 $ hg init problematicencoding
545 $ hg init problematicencoding
505 $ cd problematicencoding
546 $ cd problematicencoding
506
547
507 $ python > setup.sh <<EOF
548 $ python > setup.sh <<EOF
508 > print u'''
549 > print u'''
509 > echo a > text
550 > echo a > text
510 > hg add text
551 > hg add text
511 > hg --encoding utf-8 commit -u '\u30A2' -m none
552 > hg --encoding utf-8 commit -u '\u30A2' -m none
512 > echo b > text
553 > echo b > text
513 > hg --encoding utf-8 commit -u '\u30C2' -m none
554 > hg --encoding utf-8 commit -u '\u30C2' -m none
514 > echo c > text
555 > echo c > text
515 > hg --encoding utf-8 commit -u none -m '\u30A2'
556 > hg --encoding utf-8 commit -u none -m '\u30A2'
516 > echo d > text
557 > echo d > text
517 > hg --encoding utf-8 commit -u none -m '\u30C2'
558 > hg --encoding utf-8 commit -u none -m '\u30C2'
518 > '''.encode('utf-8')
559 > '''.encode('utf-8')
519 > EOF
560 > EOF
520 $ sh < setup.sh
561 $ sh < setup.sh
521
562
522 test in problematic encoding
563 test in problematic encoding
523 $ python > test.sh <<EOF
564 $ python > test.sh <<EOF
524 > print u'''
565 > print u'''
525 > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30A2)'
566 > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30A2)'
526 > echo ====
567 > echo ====
527 > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30C2)'
568 > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30C2)'
528 > echo ====
569 > echo ====
529 > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30A2)'
570 > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30A2)'
530 > echo ====
571 > echo ====
531 > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30C2)'
572 > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30C2)'
532 > echo ====
573 > echo ====
533 > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30A2)'
574 > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30A2)'
534 > echo ====
575 > echo ====
535 > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30C2)'
576 > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30C2)'
536 > '''.encode('cp932')
577 > '''.encode('cp932')
537 > EOF
578 > EOF
538 $ sh < test.sh
579 $ sh < test.sh
539 0
580 0
540 ====
581 ====
541 1
582 1
542 ====
583 ====
543 2
584 2
544 ====
585 ====
545 3
586 3
546 ====
587 ====
547 0
588 0
548 2
589 2
549 ====
590 ====
550 1
591 1
551 3
592 3
552
593
553 $ cd ..
594 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now