##// END OF EJS Templates
revset: roots needs to be computed on full set
Matt Mackall -
r15903:0329d3b1 default
parent child Browse files
Show More
@@ -1,1187 +1,1187
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 = matchmod.match(repo.root, repo.getcwd(), [pat])
299 m = matchmod.match(repo.root, repo.getcwd(), [pat])
300 s = []
300 s = []
301 fast = (m.files() == [pat])
301 fast = (m.files() == [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 for f in c.files():
308 for f in c.files():
309 if m(f):
309 if m(f):
310 break
310 break
311 else:
311 else:
312 continue
312 continue
313 files = repo.status(c.p1().node(), c.node())[field]
313 files = repo.status(c.p1().node(), c.node())[field]
314 if fast:
314 if fast:
315 if pat in files:
315 if pat in files:
316 s.append(r)
316 s.append(r)
317 else:
317 else:
318 for f in files:
318 for f in files:
319 if m(f):
319 if m(f):
320 s.append(r)
320 s.append(r)
321 break
321 break
322 return s
322 return s
323
323
324 def _children(repo, narrow, s):
324 def _children(repo, narrow, s):
325 cs = set()
325 cs = set()
326 pr = repo.changelog.parentrevs
326 pr = repo.changelog.parentrevs
327 s = set(s)
327 s = set(s)
328 for r in narrow:
328 for r in xrange(len(repo)):
329 for p in pr(r):
329 for p in pr(r):
330 if p in s:
330 if p in s:
331 cs.add(r)
331 cs.add(r)
332 return cs
332 return cs
333
333
334 def children(repo, subset, x):
334 def children(repo, subset, x):
335 """``children(set)``
335 """``children(set)``
336 Child changesets of changesets in set.
336 Child changesets of changesets in set.
337 """
337 """
338 s = getset(repo, range(len(repo)), x)
338 s = getset(repo, range(len(repo)), x)
339 cs = _children(repo, subset, s)
339 cs = _children(repo, subset, s)
340 return [r for r in subset if r in cs]
340 return [r for r in subset if r in cs]
341
341
342 def closed(repo, subset, x):
342 def closed(repo, subset, x):
343 """``closed()``
343 """``closed()``
344 Changeset is closed.
344 Changeset is closed.
345 """
345 """
346 # i18n: "closed" is a keyword
346 # i18n: "closed" is a keyword
347 getargs(x, 0, 0, _("closed takes no arguments"))
347 getargs(x, 0, 0, _("closed takes no arguments"))
348 return [r for r in subset if repo[r].extra().get('close')]
348 return [r for r in subset if repo[r].extra().get('close')]
349
349
350 def contains(repo, subset, x):
350 def contains(repo, subset, x):
351 """``contains(pattern)``
351 """``contains(pattern)``
352 Revision contains a file matching pattern. See :hg:`help patterns`
352 Revision contains a file matching pattern. See :hg:`help patterns`
353 for information about file patterns.
353 for information about file patterns.
354 """
354 """
355 # i18n: "contains" is a keyword
355 # i18n: "contains" is a keyword
356 pat = getstring(x, _("contains requires a pattern"))
356 pat = getstring(x, _("contains requires a pattern"))
357 m = matchmod.match(repo.root, repo.getcwd(), [pat])
357 m = matchmod.match(repo.root, repo.getcwd(), [pat])
358 s = []
358 s = []
359 if m.files() == [pat]:
359 if m.files() == [pat]:
360 for r in subset:
360 for r in subset:
361 if pat in repo[r]:
361 if pat in repo[r]:
362 s.append(r)
362 s.append(r)
363 else:
363 else:
364 for r in subset:
364 for r in subset:
365 for f in repo[r].manifest():
365 for f in repo[r].manifest():
366 if m(f):
366 if m(f):
367 s.append(r)
367 s.append(r)
368 break
368 break
369 return s
369 return s
370
370
371 def date(repo, subset, x):
371 def date(repo, subset, x):
372 """``date(interval)``
372 """``date(interval)``
373 Changesets within the interval, see :hg:`help dates`.
373 Changesets within the interval, see :hg:`help dates`.
374 """
374 """
375 # i18n: "date" is a keyword
375 # i18n: "date" is a keyword
376 ds = getstring(x, _("date requires a string"))
376 ds = getstring(x, _("date requires a string"))
377 dm = util.matchdate(ds)
377 dm = util.matchdate(ds)
378 return [r for r in subset if dm(repo[r].date()[0])]
378 return [r for r in subset if dm(repo[r].date()[0])]
379
379
380 def desc(repo, subset, x):
380 def desc(repo, subset, x):
381 """``desc(string)``
381 """``desc(string)``
382 Search commit message for string. The match is case-insensitive.
382 Search commit message for string. The match is case-insensitive.
383 """
383 """
384 # i18n: "desc" is a keyword
384 # i18n: "desc" is a keyword
385 ds = encoding.lower(getstring(x, _("desc requires a string")))
385 ds = encoding.lower(getstring(x, _("desc requires a string")))
386 l = []
386 l = []
387 for r in subset:
387 for r in subset:
388 c = repo[r]
388 c = repo[r]
389 if ds in encoding.lower(c.description()):
389 if ds in encoding.lower(c.description()):
390 l.append(r)
390 l.append(r)
391 return l
391 return l
392
392
393 def descendants(repo, subset, x):
393 def descendants(repo, subset, x):
394 """``descendants(set)``
394 """``descendants(set)``
395 Changesets which are descendants of changesets in set.
395 Changesets which are descendants of changesets in set.
396 """
396 """
397 args = getset(repo, range(len(repo)), x)
397 args = getset(repo, range(len(repo)), x)
398 if not args:
398 if not args:
399 return []
399 return []
400 s = set(repo.changelog.descendants(*args)) | set(args)
400 s = set(repo.changelog.descendants(*args)) | set(args)
401 return [r for r in subset if r in s]
401 return [r for r in subset if r in s]
402
402
403 def draft(repo, subset, x):
403 def draft(repo, subset, x):
404 """``draft()``
404 """``draft()``
405 Changeset in draft phase."""
405 Changeset in draft phase."""
406 getargs(x, 0, 0, _("draft takes no arguments"))
406 getargs(x, 0, 0, _("draft takes no arguments"))
407 return [r for r in subset if repo._phaserev[r] == phases.draft]
407 return [r for r in subset if repo._phaserev[r] == phases.draft]
408
408
409 def filelog(repo, subset, x):
409 def filelog(repo, subset, x):
410 """``filelog(pattern)``
410 """``filelog(pattern)``
411 Changesets connected to the specified filelog.
411 Changesets connected to the specified filelog.
412 """
412 """
413
413
414 pat = getstring(x, _("filelog requires a pattern"))
414 pat = getstring(x, _("filelog requires a pattern"))
415 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath')
415 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath')
416 s = set()
416 s = set()
417
417
418 if not m.anypats():
418 if not m.anypats():
419 for f in m.files():
419 for f in m.files():
420 fl = repo.file(f)
420 fl = repo.file(f)
421 for fr in fl:
421 for fr in fl:
422 s.add(fl.linkrev(fr))
422 s.add(fl.linkrev(fr))
423 else:
423 else:
424 for f in repo[None]:
424 for f in repo[None]:
425 if m(f):
425 if m(f):
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
429
430 return [r for r in subset if r in s]
430 return [r for r in subset if r in s]
431
431
432 def first(repo, subset, x):
432 def first(repo, subset, x):
433 """``first(set, [n])``
433 """``first(set, [n])``
434 An alias for limit().
434 An alias for limit().
435 """
435 """
436 return limit(repo, subset, x)
436 return limit(repo, subset, x)
437
437
438 def follow(repo, subset, x):
438 def follow(repo, subset, x):
439 """``follow([file])``
439 """``follow([file])``
440 An alias for ``::.`` (ancestors of the working copy's first parent).
440 An alias for ``::.`` (ancestors of the working copy's first parent).
441 If a filename is specified, the history of the given file is followed,
441 If a filename is specified, the history of the given file is followed,
442 including copies.
442 including copies.
443 """
443 """
444 # i18n: "follow" is a keyword
444 # i18n: "follow" is a keyword
445 l = getargs(x, 0, 1, _("follow takes no arguments or a filename"))
445 l = getargs(x, 0, 1, _("follow takes no arguments or a filename"))
446 p = repo['.'].rev()
446 p = repo['.'].rev()
447 if l:
447 if l:
448 x = getstring(l[0], _("follow expected a filename"))
448 x = getstring(l[0], _("follow expected a filename"))
449 if x in repo['.']:
449 if x in repo['.']:
450 s = set(ctx.rev() for ctx in repo['.'][x].ancestors())
450 s = set(ctx.rev() for ctx in repo['.'][x].ancestors())
451 else:
451 else:
452 return []
452 return []
453 else:
453 else:
454 s = set(repo.changelog.ancestors(p))
454 s = set(repo.changelog.ancestors(p))
455
455
456 s |= set([p])
456 s |= set([p])
457 return [r for r in subset if r in s]
457 return [r for r in subset if r in s]
458
458
459 def followfile(repo, subset, x):
459 def followfile(repo, subset, x):
460 """``follow()``
460 """``follow()``
461 An alias for ``::.`` (ancestors of the working copy's first parent).
461 An alias for ``::.`` (ancestors of the working copy's first parent).
462 """
462 """
463 # i18n: "follow" is a keyword
463 # i18n: "follow" is a keyword
464 getargs(x, 0, 0, _("follow takes no arguments"))
464 getargs(x, 0, 0, _("follow takes no arguments"))
465 p = repo['.'].rev()
465 p = repo['.'].rev()
466 s = set(repo.changelog.ancestors(p)) | set([p])
466 s = set(repo.changelog.ancestors(p)) | set([p])
467 return [r for r in subset if r in s]
467 return [r for r in subset if r in s]
468
468
469 def getall(repo, subset, x):
469 def getall(repo, subset, x):
470 """``all()``
470 """``all()``
471 All changesets, the same as ``0:tip``.
471 All changesets, the same as ``0:tip``.
472 """
472 """
473 # i18n: "all" is a keyword
473 # i18n: "all" is a keyword
474 getargs(x, 0, 0, _("all takes no arguments"))
474 getargs(x, 0, 0, _("all takes no arguments"))
475 return subset
475 return subset
476
476
477 def grep(repo, subset, x):
477 def grep(repo, subset, x):
478 """``grep(regex)``
478 """``grep(regex)``
479 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
479 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
480 to ensure special escape characters are handled correctly. Unlike
480 to ensure special escape characters are handled correctly. Unlike
481 ``keyword(string)``, the match is case-sensitive.
481 ``keyword(string)``, the match is case-sensitive.
482 """
482 """
483 try:
483 try:
484 # i18n: "grep" is a keyword
484 # i18n: "grep" is a keyword
485 gr = re.compile(getstring(x, _("grep requires a string")))
485 gr = re.compile(getstring(x, _("grep requires a string")))
486 except re.error, e:
486 except re.error, e:
487 raise error.ParseError(_('invalid match pattern: %s') % e)
487 raise error.ParseError(_('invalid match pattern: %s') % e)
488 l = []
488 l = []
489 for r in subset:
489 for r in subset:
490 c = repo[r]
490 c = repo[r]
491 for e in c.files() + [c.user(), c.description()]:
491 for e in c.files() + [c.user(), c.description()]:
492 if gr.search(e):
492 if gr.search(e):
493 l.append(r)
493 l.append(r)
494 break
494 break
495 return l
495 return l
496
496
497 def hasfile(repo, subset, x):
497 def hasfile(repo, subset, x):
498 """``file(pattern)``
498 """``file(pattern)``
499 Changesets affecting files matched by pattern.
499 Changesets affecting files matched by pattern.
500 """
500 """
501 # i18n: "file" is a keyword
501 # i18n: "file" is a keyword
502 pat = getstring(x, _("file requires a pattern"))
502 pat = getstring(x, _("file requires a pattern"))
503 m = matchmod.match(repo.root, repo.getcwd(), [pat])
503 m = matchmod.match(repo.root, repo.getcwd(), [pat])
504 s = []
504 s = []
505 for r in subset:
505 for r in subset:
506 for f in repo[r].files():
506 for f in repo[r].files():
507 if m(f):
507 if m(f):
508 s.append(r)
508 s.append(r)
509 break
509 break
510 return s
510 return s
511
511
512 def head(repo, subset, x):
512 def head(repo, subset, x):
513 """``head()``
513 """``head()``
514 Changeset is a named branch head.
514 Changeset is a named branch head.
515 """
515 """
516 # i18n: "head" is a keyword
516 # i18n: "head" is a keyword
517 getargs(x, 0, 0, _("head takes no arguments"))
517 getargs(x, 0, 0, _("head takes no arguments"))
518 hs = set()
518 hs = set()
519 for b, ls in repo.branchmap().iteritems():
519 for b, ls in repo.branchmap().iteritems():
520 hs.update(repo[h].rev() for h in ls)
520 hs.update(repo[h].rev() for h in ls)
521 return [r for r in subset if r in hs]
521 return [r for r in subset if r in hs]
522
522
523 def heads(repo, subset, x):
523 def heads(repo, subset, x):
524 """``heads(set)``
524 """``heads(set)``
525 Members of set with no children in set.
525 Members of set with no children in set.
526 """
526 """
527 s = getset(repo, subset, x)
527 s = getset(repo, subset, x)
528 ps = set(parents(repo, subset, x))
528 ps = set(parents(repo, subset, x))
529 return [r for r in s if r not in ps]
529 return [r for r in s if r not in ps]
530
530
531 def keyword(repo, subset, x):
531 def keyword(repo, subset, x):
532 """``keyword(string)``
532 """``keyword(string)``
533 Search commit message, user name, and names of changed files for
533 Search commit message, user name, and names of changed files for
534 string. The match is case-insensitive.
534 string. The match is case-insensitive.
535 """
535 """
536 # i18n: "keyword" is a keyword
536 # i18n: "keyword" is a keyword
537 kw = encoding.lower(getstring(x, _("keyword requires a string")))
537 kw = encoding.lower(getstring(x, _("keyword requires a string")))
538 l = []
538 l = []
539 for r in subset:
539 for r in subset:
540 c = repo[r]
540 c = repo[r]
541 t = " ".join(c.files() + [c.user(), c.description()])
541 t = " ".join(c.files() + [c.user(), c.description()])
542 if kw in encoding.lower(t):
542 if kw in encoding.lower(t):
543 l.append(r)
543 l.append(r)
544 return l
544 return l
545
545
546 def limit(repo, subset, x):
546 def limit(repo, subset, x):
547 """``limit(set, [n])``
547 """``limit(set, [n])``
548 First n members of set, defaulting to 1.
548 First n members of set, defaulting to 1.
549 """
549 """
550 # i18n: "limit" is a keyword
550 # i18n: "limit" is a keyword
551 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
551 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
552 try:
552 try:
553 lim = 1
553 lim = 1
554 if len(l) == 2:
554 if len(l) == 2:
555 # i18n: "limit" is a keyword
555 # i18n: "limit" is a keyword
556 lim = int(getstring(l[1], _("limit requires a number")))
556 lim = int(getstring(l[1], _("limit requires a number")))
557 except (TypeError, ValueError):
557 except (TypeError, ValueError):
558 # i18n: "limit" is a keyword
558 # i18n: "limit" is a keyword
559 raise error.ParseError(_("limit expects a number"))
559 raise error.ParseError(_("limit expects a number"))
560 ss = set(subset)
560 ss = set(subset)
561 os = getset(repo, range(len(repo)), l[0])[:lim]
561 os = getset(repo, range(len(repo)), l[0])[:lim]
562 return [r for r in os if r in ss]
562 return [r for r in os if r in ss]
563
563
564 def last(repo, subset, x):
564 def last(repo, subset, x):
565 """``last(set, [n])``
565 """``last(set, [n])``
566 Last n members of set, defaulting to 1.
566 Last n members of set, defaulting to 1.
567 """
567 """
568 # i18n: "last" is a keyword
568 # i18n: "last" is a keyword
569 l = getargs(x, 1, 2, _("last requires one or two arguments"))
569 l = getargs(x, 1, 2, _("last requires one or two arguments"))
570 try:
570 try:
571 lim = 1
571 lim = 1
572 if len(l) == 2:
572 if len(l) == 2:
573 # i18n: "last" is a keyword
573 # i18n: "last" is a keyword
574 lim = int(getstring(l[1], _("last requires a number")))
574 lim = int(getstring(l[1], _("last requires a number")))
575 except (TypeError, ValueError):
575 except (TypeError, ValueError):
576 # i18n: "last" is a keyword
576 # i18n: "last" is a keyword
577 raise error.ParseError(_("last expects a number"))
577 raise error.ParseError(_("last expects a number"))
578 ss = set(subset)
578 ss = set(subset)
579 os = getset(repo, range(len(repo)), l[0])[-lim:]
579 os = getset(repo, range(len(repo)), l[0])[-lim:]
580 return [r for r in os if r in ss]
580 return [r for r in os if r in ss]
581
581
582 def maxrev(repo, subset, x):
582 def maxrev(repo, subset, x):
583 """``max(set)``
583 """``max(set)``
584 Changeset with highest revision number in set.
584 Changeset with highest revision number in set.
585 """
585 """
586 os = getset(repo, range(len(repo)), x)
586 os = getset(repo, range(len(repo)), x)
587 if os:
587 if os:
588 m = max(os)
588 m = max(os)
589 if m in subset:
589 if m in subset:
590 return [m]
590 return [m]
591 return []
591 return []
592
592
593 def merge(repo, subset, x):
593 def merge(repo, subset, x):
594 """``merge()``
594 """``merge()``
595 Changeset is a merge changeset.
595 Changeset is a merge changeset.
596 """
596 """
597 # i18n: "merge" is a keyword
597 # i18n: "merge" is a keyword
598 getargs(x, 0, 0, _("merge takes no arguments"))
598 getargs(x, 0, 0, _("merge takes no arguments"))
599 cl = repo.changelog
599 cl = repo.changelog
600 return [r for r in subset if cl.parentrevs(r)[1] != -1]
600 return [r for r in subset if cl.parentrevs(r)[1] != -1]
601
601
602 def minrev(repo, subset, x):
602 def minrev(repo, subset, x):
603 """``min(set)``
603 """``min(set)``
604 Changeset with lowest revision number in set.
604 Changeset with lowest revision number in set.
605 """
605 """
606 os = getset(repo, range(len(repo)), x)
606 os = getset(repo, range(len(repo)), x)
607 if os:
607 if os:
608 m = min(os)
608 m = min(os)
609 if m in subset:
609 if m in subset:
610 return [m]
610 return [m]
611 return []
611 return []
612
612
613 def modifies(repo, subset, x):
613 def modifies(repo, subset, x):
614 """``modifies(pattern)``
614 """``modifies(pattern)``
615 Changesets modifying files matched by pattern.
615 Changesets modifying files matched by pattern.
616 """
616 """
617 # i18n: "modifies" is a keyword
617 # i18n: "modifies" is a keyword
618 pat = getstring(x, _("modifies requires a pattern"))
618 pat = getstring(x, _("modifies requires a pattern"))
619 return checkstatus(repo, subset, pat, 0)
619 return checkstatus(repo, subset, pat, 0)
620
620
621 def node(repo, subset, x):
621 def node(repo, subset, x):
622 """``id(string)``
622 """``id(string)``
623 Revision non-ambiguously specified by the given hex string prefix.
623 Revision non-ambiguously specified by the given hex string prefix.
624 """
624 """
625 # i18n: "id" is a keyword
625 # i18n: "id" is a keyword
626 l = getargs(x, 1, 1, _("id requires one argument"))
626 l = getargs(x, 1, 1, _("id requires one argument"))
627 # i18n: "id" is a keyword
627 # i18n: "id" is a keyword
628 n = getstring(l[0], _("id requires a string"))
628 n = getstring(l[0], _("id requires a string"))
629 if len(n) == 40:
629 if len(n) == 40:
630 rn = repo[n].rev()
630 rn = repo[n].rev()
631 else:
631 else:
632 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
632 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
633 return [r for r in subset if r == rn]
633 return [r for r in subset if r == rn]
634
634
635 def outgoing(repo, subset, x):
635 def outgoing(repo, subset, x):
636 """``outgoing([path])``
636 """``outgoing([path])``
637 Changesets not found in the specified destination repository, or the
637 Changesets not found in the specified destination repository, or the
638 default push location.
638 default push location.
639 """
639 """
640 import hg # avoid start-up nasties
640 import hg # avoid start-up nasties
641 # i18n: "outgoing" is a keyword
641 # i18n: "outgoing" is a keyword
642 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
642 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
643 # i18n: "outgoing" is a keyword
643 # i18n: "outgoing" is a keyword
644 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
644 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
645 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
645 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
646 dest, branches = hg.parseurl(dest)
646 dest, branches = hg.parseurl(dest)
647 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
647 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
648 if revs:
648 if revs:
649 revs = [repo.lookup(rev) for rev in revs]
649 revs = [repo.lookup(rev) for rev in revs]
650 other = hg.peer(repo, {}, dest)
650 other = hg.peer(repo, {}, dest)
651 repo.ui.pushbuffer()
651 repo.ui.pushbuffer()
652 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
652 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
653 repo.ui.popbuffer()
653 repo.ui.popbuffer()
654 cl = repo.changelog
654 cl = repo.changelog
655 o = set([cl.rev(r) for r in outgoing.missing])
655 o = set([cl.rev(r) for r in outgoing.missing])
656 return [r for r in subset if r in o]
656 return [r for r in subset if r in o]
657
657
658 def p1(repo, subset, x):
658 def p1(repo, subset, x):
659 """``p1([set])``
659 """``p1([set])``
660 First parent of changesets in set, or the working directory.
660 First parent of changesets in set, or the working directory.
661 """
661 """
662 if x is None:
662 if x is None:
663 p = repo[x].p1().rev()
663 p = repo[x].p1().rev()
664 return [r for r in subset if r == p]
664 return [r for r in subset if r == p]
665
665
666 ps = set()
666 ps = set()
667 cl = repo.changelog
667 cl = repo.changelog
668 for r in getset(repo, range(len(repo)), x):
668 for r in getset(repo, range(len(repo)), x):
669 ps.add(cl.parentrevs(r)[0])
669 ps.add(cl.parentrevs(r)[0])
670 return [r for r in subset if r in ps]
670 return [r for r in subset if r in ps]
671
671
672 def p2(repo, subset, x):
672 def p2(repo, subset, x):
673 """``p2([set])``
673 """``p2([set])``
674 Second parent of changesets in set, or the working directory.
674 Second parent of changesets in set, or the working directory.
675 """
675 """
676 if x is None:
676 if x is None:
677 ps = repo[x].parents()
677 ps = repo[x].parents()
678 try:
678 try:
679 p = ps[1].rev()
679 p = ps[1].rev()
680 return [r for r in subset if r == p]
680 return [r for r in subset if r == p]
681 except IndexError:
681 except IndexError:
682 return []
682 return []
683
683
684 ps = set()
684 ps = set()
685 cl = repo.changelog
685 cl = repo.changelog
686 for r in getset(repo, range(len(repo)), x):
686 for r in getset(repo, range(len(repo)), x):
687 ps.add(cl.parentrevs(r)[1])
687 ps.add(cl.parentrevs(r)[1])
688 return [r for r in subset if r in ps]
688 return [r for r in subset if r in ps]
689
689
690 def parents(repo, subset, x):
690 def parents(repo, subset, x):
691 """``parents([set])``
691 """``parents([set])``
692 The set of all parents for all changesets in set, or the working directory.
692 The set of all parents for all changesets in set, or the working directory.
693 """
693 """
694 if x is None:
694 if x is None:
695 ps = tuple(p.rev() for p in repo[x].parents())
695 ps = tuple(p.rev() for p in repo[x].parents())
696 return [r for r in subset if r in ps]
696 return [r for r in subset if r in ps]
697
697
698 ps = set()
698 ps = set()
699 cl = repo.changelog
699 cl = repo.changelog
700 for r in getset(repo, range(len(repo)), x):
700 for r in getset(repo, range(len(repo)), x):
701 ps.update(cl.parentrevs(r))
701 ps.update(cl.parentrevs(r))
702 return [r for r in subset if r in ps]
702 return [r for r in subset if r in ps]
703
703
704 def parentspec(repo, subset, x, n):
704 def parentspec(repo, subset, x, n):
705 """``set^0``
705 """``set^0``
706 The set.
706 The set.
707 ``set^1`` (or ``set^``), ``set^2``
707 ``set^1`` (or ``set^``), ``set^2``
708 First or second parent, respectively, of all changesets in set.
708 First or second parent, respectively, of all changesets in set.
709 """
709 """
710 try:
710 try:
711 n = int(n[1])
711 n = int(n[1])
712 if n not in (0, 1, 2):
712 if n not in (0, 1, 2):
713 raise ValueError
713 raise ValueError
714 except (TypeError, ValueError):
714 except (TypeError, ValueError):
715 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
715 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
716 ps = set()
716 ps = set()
717 cl = repo.changelog
717 cl = repo.changelog
718 for r in getset(repo, subset, x):
718 for r in getset(repo, subset, x):
719 if n == 0:
719 if n == 0:
720 ps.add(r)
720 ps.add(r)
721 elif n == 1:
721 elif n == 1:
722 ps.add(cl.parentrevs(r)[0])
722 ps.add(cl.parentrevs(r)[0])
723 elif n == 2:
723 elif n == 2:
724 parents = cl.parentrevs(r)
724 parents = cl.parentrevs(r)
725 if len(parents) > 1:
725 if len(parents) > 1:
726 ps.add(parents[1])
726 ps.add(parents[1])
727 return [r for r in subset if r in ps]
727 return [r for r in subset if r in ps]
728
728
729 def present(repo, subset, x):
729 def present(repo, subset, x):
730 """``present(set)``
730 """``present(set)``
731 An empty set, if any revision in set isn't found; otherwise,
731 An empty set, if any revision in set isn't found; otherwise,
732 all revisions in set.
732 all revisions in set.
733 """
733 """
734 try:
734 try:
735 return getset(repo, subset, x)
735 return getset(repo, subset, x)
736 except error.RepoLookupError:
736 except error.RepoLookupError:
737 return []
737 return []
738
738
739 def public(repo, subset, x):
739 def public(repo, subset, x):
740 """``public()``
740 """``public()``
741 Changeset in public phase."""
741 Changeset in public phase."""
742 getargs(x, 0, 0, _("public takes no arguments"))
742 getargs(x, 0, 0, _("public takes no arguments"))
743 return [r for r in subset if repo._phaserev[r] == phases.public]
743 return [r for r in subset if repo._phaserev[r] == phases.public]
744
744
745 def removes(repo, subset, x):
745 def removes(repo, subset, x):
746 """``removes(pattern)``
746 """``removes(pattern)``
747 Changesets which remove files matching pattern.
747 Changesets which remove files matching pattern.
748 """
748 """
749 # i18n: "removes" is a keyword
749 # i18n: "removes" is a keyword
750 pat = getstring(x, _("removes requires a pattern"))
750 pat = getstring(x, _("removes requires a pattern"))
751 return checkstatus(repo, subset, pat, 2)
751 return checkstatus(repo, subset, pat, 2)
752
752
753 def rev(repo, subset, x):
753 def rev(repo, subset, x):
754 """``rev(number)``
754 """``rev(number)``
755 Revision with the given numeric identifier.
755 Revision with the given numeric identifier.
756 """
756 """
757 # i18n: "rev" is a keyword
757 # i18n: "rev" is a keyword
758 l = getargs(x, 1, 1, _("rev requires one argument"))
758 l = getargs(x, 1, 1, _("rev requires one argument"))
759 try:
759 try:
760 # i18n: "rev" is a keyword
760 # i18n: "rev" is a keyword
761 l = int(getstring(l[0], _("rev requires a number")))
761 l = int(getstring(l[0], _("rev requires a number")))
762 except (TypeError, ValueError):
762 except (TypeError, ValueError):
763 # i18n: "rev" is a keyword
763 # i18n: "rev" is a keyword
764 raise error.ParseError(_("rev expects a number"))
764 raise error.ParseError(_("rev expects a number"))
765 return [r for r in subset if r == l]
765 return [r for r in subset if r == l]
766
766
767 def reverse(repo, subset, x):
767 def reverse(repo, subset, x):
768 """``reverse(set)``
768 """``reverse(set)``
769 Reverse order of set.
769 Reverse order of set.
770 """
770 """
771 l = getset(repo, subset, x)
771 l = getset(repo, subset, x)
772 l.reverse()
772 l.reverse()
773 return l
773 return l
774
774
775 def roots(repo, subset, x):
775 def roots(repo, subset, x):
776 """``roots(set)``
776 """``roots(set)``
777 Changesets with no parent changeset in set.
777 Changesets with no parent changeset in set.
778 """
778 """
779 s = getset(repo, subset, x)
779 s = getset(repo, xrange(len(repo)), x)
780 cs = _children(repo, s, s)
780 cs = _children(repo, s, s)
781 return [r for r in s if r not in cs]
781 return [r for r in s if r not in cs]
782
782
783 def secret(repo, subset, x):
783 def secret(repo, subset, x):
784 """``secret()``
784 """``secret()``
785 Changeset in secret phase."""
785 Changeset in secret phase."""
786 getargs(x, 0, 0, _("secret takes no arguments"))
786 getargs(x, 0, 0, _("secret takes no arguments"))
787 return [r for r in subset if repo._phaserev[r] == phases.secret]
787 return [r for r in subset if repo._phaserev[r] == phases.secret]
788
788
789 def sort(repo, subset, x):
789 def sort(repo, subset, x):
790 """``sort(set[, [-]key...])``
790 """``sort(set[, [-]key...])``
791 Sort set by keys. The default sort order is ascending, specify a key
791 Sort set by keys. The default sort order is ascending, specify a key
792 as ``-key`` to sort in descending order.
792 as ``-key`` to sort in descending order.
793
793
794 The keys can be:
794 The keys can be:
795
795
796 - ``rev`` for the revision number,
796 - ``rev`` for the revision number,
797 - ``branch`` for the branch name,
797 - ``branch`` for the branch name,
798 - ``desc`` for the commit message (description),
798 - ``desc`` for the commit message (description),
799 - ``user`` for user name (``author`` can be used as an alias),
799 - ``user`` for user name (``author`` can be used as an alias),
800 - ``date`` for the commit date
800 - ``date`` for the commit date
801 """
801 """
802 # i18n: "sort" is a keyword
802 # i18n: "sort" is a keyword
803 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
803 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
804 keys = "rev"
804 keys = "rev"
805 if len(l) == 2:
805 if len(l) == 2:
806 keys = getstring(l[1], _("sort spec must be a string"))
806 keys = getstring(l[1], _("sort spec must be a string"))
807
807
808 s = l[0]
808 s = l[0]
809 keys = keys.split()
809 keys = keys.split()
810 l = []
810 l = []
811 def invert(s):
811 def invert(s):
812 return "".join(chr(255 - ord(c)) for c in s)
812 return "".join(chr(255 - ord(c)) for c in s)
813 for r in getset(repo, subset, s):
813 for r in getset(repo, subset, s):
814 c = repo[r]
814 c = repo[r]
815 e = []
815 e = []
816 for k in keys:
816 for k in keys:
817 if k == 'rev':
817 if k == 'rev':
818 e.append(r)
818 e.append(r)
819 elif k == '-rev':
819 elif k == '-rev':
820 e.append(-r)
820 e.append(-r)
821 elif k == 'branch':
821 elif k == 'branch':
822 e.append(c.branch())
822 e.append(c.branch())
823 elif k == '-branch':
823 elif k == '-branch':
824 e.append(invert(c.branch()))
824 e.append(invert(c.branch()))
825 elif k == 'desc':
825 elif k == 'desc':
826 e.append(c.description())
826 e.append(c.description())
827 elif k == '-desc':
827 elif k == '-desc':
828 e.append(invert(c.description()))
828 e.append(invert(c.description()))
829 elif k in 'user author':
829 elif k in 'user author':
830 e.append(c.user())
830 e.append(c.user())
831 elif k in '-user -author':
831 elif k in '-user -author':
832 e.append(invert(c.user()))
832 e.append(invert(c.user()))
833 elif k == 'date':
833 elif k == 'date':
834 e.append(c.date()[0])
834 e.append(c.date()[0])
835 elif k == '-date':
835 elif k == '-date':
836 e.append(-c.date()[0])
836 e.append(-c.date()[0])
837 else:
837 else:
838 raise error.ParseError(_("unknown sort key %r") % k)
838 raise error.ParseError(_("unknown sort key %r") % k)
839 e.append(r)
839 e.append(r)
840 l.append(e)
840 l.append(e)
841 l.sort()
841 l.sort()
842 return [e[-1] for e in l]
842 return [e[-1] for e in l]
843
843
844 def tag(repo, subset, x):
844 def tag(repo, subset, x):
845 """``tag([name])``
845 """``tag([name])``
846 The specified tag by name, or all tagged revisions if no name is given.
846 The specified tag by name, or all tagged revisions if no name is given.
847 """
847 """
848 # i18n: "tag" is a keyword
848 # i18n: "tag" is a keyword
849 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
849 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
850 cl = repo.changelog
850 cl = repo.changelog
851 if args:
851 if args:
852 tn = getstring(args[0],
852 tn = getstring(args[0],
853 # i18n: "tag" is a keyword
853 # i18n: "tag" is a keyword
854 _('the argument to tag must be a string'))
854 _('the argument to tag must be a string'))
855 if not repo.tags().get(tn, None):
855 if not repo.tags().get(tn, None):
856 raise util.Abort(_("tag '%s' does not exist") % tn)
856 raise util.Abort(_("tag '%s' does not exist") % tn)
857 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
857 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
858 else:
858 else:
859 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
859 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
860 return [r for r in subset if r in s]
860 return [r for r in subset if r in s]
861
861
862 def tagged(repo, subset, x):
862 def tagged(repo, subset, x):
863 return tag(repo, subset, x)
863 return tag(repo, subset, x)
864
864
865 def user(repo, subset, x):
865 def user(repo, subset, x):
866 """``user(string)``
866 """``user(string)``
867 User name contains string. The match is case-insensitive.
867 User name contains string. The match is case-insensitive.
868 """
868 """
869 return author(repo, subset, x)
869 return author(repo, subset, x)
870
870
871 # for internal use
871 # for internal use
872 def _list(repo, subset, x):
872 def _list(repo, subset, x):
873 s = getstring(x, "internal error")
873 s = getstring(x, "internal error")
874 if not s:
874 if not s:
875 return []
875 return []
876 if not isinstance(subset, set):
876 if not isinstance(subset, set):
877 subset = set(subset)
877 subset = set(subset)
878 ls = [repo[r].rev() for r in s.split('\0')]
878 ls = [repo[r].rev() for r in s.split('\0')]
879 return [r for r in ls if r in subset]
879 return [r for r in ls if r in subset]
880
880
881 symbols = {
881 symbols = {
882 "adds": adds,
882 "adds": adds,
883 "all": getall,
883 "all": getall,
884 "ancestor": ancestor,
884 "ancestor": ancestor,
885 "ancestors": ancestors,
885 "ancestors": ancestors,
886 "author": author,
886 "author": author,
887 "bisect": bisect,
887 "bisect": bisect,
888 "bisected": bisected,
888 "bisected": bisected,
889 "bookmark": bookmark,
889 "bookmark": bookmark,
890 "branch": branch,
890 "branch": branch,
891 "children": children,
891 "children": children,
892 "closed": closed,
892 "closed": closed,
893 "contains": contains,
893 "contains": contains,
894 "date": date,
894 "date": date,
895 "desc": desc,
895 "desc": desc,
896 "descendants": descendants,
896 "descendants": descendants,
897 "draft": draft,
897 "draft": draft,
898 "file": hasfile,
898 "file": hasfile,
899 "filelog": filelog,
899 "filelog": filelog,
900 "first": first,
900 "first": first,
901 "follow": follow,
901 "follow": follow,
902 "grep": grep,
902 "grep": grep,
903 "head": head,
903 "head": head,
904 "heads": heads,
904 "heads": heads,
905 "id": node,
905 "id": node,
906 "keyword": keyword,
906 "keyword": keyword,
907 "last": last,
907 "last": last,
908 "limit": limit,
908 "limit": limit,
909 "max": maxrev,
909 "max": maxrev,
910 "merge": merge,
910 "merge": merge,
911 "min": minrev,
911 "min": minrev,
912 "modifies": modifies,
912 "modifies": modifies,
913 "outgoing": outgoing,
913 "outgoing": outgoing,
914 "p1": p1,
914 "p1": p1,
915 "p2": p2,
915 "p2": p2,
916 "parents": parents,
916 "parents": parents,
917 "present": present,
917 "present": present,
918 "public": public,
918 "public": public,
919 "removes": removes,
919 "removes": removes,
920 "rev": rev,
920 "rev": rev,
921 "reverse": reverse,
921 "reverse": reverse,
922 "roots": roots,
922 "roots": roots,
923 "sort": sort,
923 "sort": sort,
924 "secret": secret,
924 "secret": secret,
925 "tag": tag,
925 "tag": tag,
926 "tagged": tagged,
926 "tagged": tagged,
927 "user": user,
927 "user": user,
928 "_list": _list,
928 "_list": _list,
929 }
929 }
930
930
931 methods = {
931 methods = {
932 "range": rangeset,
932 "range": rangeset,
933 "string": stringset,
933 "string": stringset,
934 "symbol": symbolset,
934 "symbol": symbolset,
935 "and": andset,
935 "and": andset,
936 "or": orset,
936 "or": orset,
937 "not": notset,
937 "not": notset,
938 "list": listset,
938 "list": listset,
939 "func": func,
939 "func": func,
940 "ancestor": ancestorspec,
940 "ancestor": ancestorspec,
941 "parent": parentspec,
941 "parent": parentspec,
942 "parentpost": p1,
942 "parentpost": p1,
943 }
943 }
944
944
945 def optimize(x, small):
945 def optimize(x, small):
946 if x is None:
946 if x is None:
947 return 0, x
947 return 0, x
948
948
949 smallbonus = 1
949 smallbonus = 1
950 if small:
950 if small:
951 smallbonus = .5
951 smallbonus = .5
952
952
953 op = x[0]
953 op = x[0]
954 if op == 'minus':
954 if op == 'minus':
955 return optimize(('and', x[1], ('not', x[2])), small)
955 return optimize(('and', x[1], ('not', x[2])), small)
956 elif op == 'dagrange':
956 elif op == 'dagrange':
957 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
957 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
958 ('func', ('symbol', 'ancestors'), x[2])), small)
958 ('func', ('symbol', 'ancestors'), x[2])), small)
959 elif op == 'dagrangepre':
959 elif op == 'dagrangepre':
960 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
960 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
961 elif op == 'dagrangepost':
961 elif op == 'dagrangepost':
962 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
962 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
963 elif op == 'rangepre':
963 elif op == 'rangepre':
964 return optimize(('range', ('string', '0'), x[1]), small)
964 return optimize(('range', ('string', '0'), x[1]), small)
965 elif op == 'rangepost':
965 elif op == 'rangepost':
966 return optimize(('range', x[1], ('string', 'tip')), small)
966 return optimize(('range', x[1], ('string', 'tip')), small)
967 elif op == 'negate':
967 elif op == 'negate':
968 return optimize(('string',
968 return optimize(('string',
969 '-' + getstring(x[1], _("can't negate that"))), small)
969 '-' + getstring(x[1], _("can't negate that"))), small)
970 elif op in 'string symbol negate':
970 elif op in 'string symbol negate':
971 return smallbonus, x # single revisions are small
971 return smallbonus, x # single revisions are small
972 elif op == 'and' or op == 'dagrange':
972 elif op == 'and' or op == 'dagrange':
973 wa, ta = optimize(x[1], True)
973 wa, ta = optimize(x[1], True)
974 wb, tb = optimize(x[2], True)
974 wb, tb = optimize(x[2], True)
975 w = min(wa, wb)
975 w = min(wa, wb)
976 if wa > wb:
976 if wa > wb:
977 return w, (op, tb, ta)
977 return w, (op, tb, ta)
978 return w, (op, ta, tb)
978 return w, (op, ta, tb)
979 elif op == 'or':
979 elif op == 'or':
980 wa, ta = optimize(x[1], False)
980 wa, ta = optimize(x[1], False)
981 wb, tb = optimize(x[2], False)
981 wb, tb = optimize(x[2], False)
982 if wb < wa:
982 if wb < wa:
983 wb, wa = wa, wb
983 wb, wa = wa, wb
984 return max(wa, wb), (op, ta, tb)
984 return max(wa, wb), (op, ta, tb)
985 elif op == 'not':
985 elif op == 'not':
986 o = optimize(x[1], not small)
986 o = optimize(x[1], not small)
987 return o[0], (op, o[1])
987 return o[0], (op, o[1])
988 elif op == 'parentpost':
988 elif op == 'parentpost':
989 o = optimize(x[1], small)
989 o = optimize(x[1], small)
990 return o[0], (op, o[1])
990 return o[0], (op, o[1])
991 elif op == 'group':
991 elif op == 'group':
992 return optimize(x[1], small)
992 return optimize(x[1], small)
993 elif op in 'range list parent ancestorspec':
993 elif op in 'range list parent ancestorspec':
994 if op == 'parent':
994 if op == 'parent':
995 # x^:y means (x^) : y, not x ^ (:y)
995 # x^:y means (x^) : y, not x ^ (:y)
996 post = ('parentpost', x[1])
996 post = ('parentpost', x[1])
997 if x[2][0] == 'dagrangepre':
997 if x[2][0] == 'dagrangepre':
998 return optimize(('dagrange', post, x[2][1]), small)
998 return optimize(('dagrange', post, x[2][1]), small)
999 elif x[2][0] == 'rangepre':
999 elif x[2][0] == 'rangepre':
1000 return optimize(('range', post, x[2][1]), small)
1000 return optimize(('range', post, x[2][1]), small)
1001
1001
1002 wa, ta = optimize(x[1], small)
1002 wa, ta = optimize(x[1], small)
1003 wb, tb = optimize(x[2], small)
1003 wb, tb = optimize(x[2], small)
1004 return wa + wb, (op, ta, tb)
1004 return wa + wb, (op, ta, tb)
1005 elif op == 'func':
1005 elif op == 'func':
1006 f = getstring(x[1], _("not a symbol"))
1006 f = getstring(x[1], _("not a symbol"))
1007 wa, ta = optimize(x[2], small)
1007 wa, ta = optimize(x[2], small)
1008 if f in ("author branch closed date desc file grep keyword "
1008 if f in ("author branch closed date desc file grep keyword "
1009 "outgoing user"):
1009 "outgoing user"):
1010 w = 10 # slow
1010 w = 10 # slow
1011 elif f in "modifies adds removes":
1011 elif f in "modifies adds removes":
1012 w = 30 # slower
1012 w = 30 # slower
1013 elif f == "contains":
1013 elif f == "contains":
1014 w = 100 # very slow
1014 w = 100 # very slow
1015 elif f == "ancestor":
1015 elif f == "ancestor":
1016 w = 1 * smallbonus
1016 w = 1 * smallbonus
1017 elif f in "reverse limit first":
1017 elif f in "reverse limit first":
1018 w = 0
1018 w = 0
1019 elif f in "sort":
1019 elif f in "sort":
1020 w = 10 # assume most sorts look at changelog
1020 w = 10 # assume most sorts look at changelog
1021 else:
1021 else:
1022 w = 1
1022 w = 1
1023 return w + wa, (op, x[1], ta)
1023 return w + wa, (op, x[1], ta)
1024 return 1, x
1024 return 1, x
1025
1025
1026 class revsetalias(object):
1026 class revsetalias(object):
1027 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1027 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1028 args = None
1028 args = None
1029
1029
1030 def __init__(self, name, value):
1030 def __init__(self, name, value):
1031 '''Aliases like:
1031 '''Aliases like:
1032
1032
1033 h = heads(default)
1033 h = heads(default)
1034 b($1) = ancestors($1) - ancestors(default)
1034 b($1) = ancestors($1) - ancestors(default)
1035 '''
1035 '''
1036 if isinstance(name, tuple): # parameter substitution
1036 if isinstance(name, tuple): # parameter substitution
1037 self.tree = name
1037 self.tree = name
1038 self.replacement = value
1038 self.replacement = value
1039 else: # alias definition
1039 else: # alias definition
1040 m = self.funcre.search(name)
1040 m = self.funcre.search(name)
1041 if m:
1041 if m:
1042 self.tree = ('func', ('symbol', m.group(1)))
1042 self.tree = ('func', ('symbol', m.group(1)))
1043 self.args = [x.strip() for x in m.group(2).split(',')]
1043 self.args = [x.strip() for x in m.group(2).split(',')]
1044 for arg in self.args:
1044 for arg in self.args:
1045 value = value.replace(arg, repr(arg))
1045 value = value.replace(arg, repr(arg))
1046 else:
1046 else:
1047 self.tree = ('symbol', name)
1047 self.tree = ('symbol', name)
1048
1048
1049 self.replacement, pos = parse(value)
1049 self.replacement, pos = parse(value)
1050 if pos != len(value):
1050 if pos != len(value):
1051 raise error.ParseError(_('invalid token'), pos)
1051 raise error.ParseError(_('invalid token'), pos)
1052
1052
1053 def process(self, tree):
1053 def process(self, tree):
1054 if isinstance(tree, tuple):
1054 if isinstance(tree, tuple):
1055 if self.args is None:
1055 if self.args is None:
1056 if tree == self.tree:
1056 if tree == self.tree:
1057 return self.replacement
1057 return self.replacement
1058 elif tree[:2] == self.tree:
1058 elif tree[:2] == self.tree:
1059 l = getlist(tree[2])
1059 l = getlist(tree[2])
1060 if len(l) != len(self.args):
1060 if len(l) != len(self.args):
1061 raise error.ParseError(
1061 raise error.ParseError(
1062 _('invalid number of arguments: %s') % len(l))
1062 _('invalid number of arguments: %s') % len(l))
1063 result = self.replacement
1063 result = self.replacement
1064 for a, v in zip(self.args, l):
1064 for a, v in zip(self.args, l):
1065 valalias = revsetalias(('string', a), v)
1065 valalias = revsetalias(('string', a), v)
1066 result = valalias.process(result)
1066 result = valalias.process(result)
1067 return result
1067 return result
1068 return tuple(map(self.process, tree))
1068 return tuple(map(self.process, tree))
1069 return tree
1069 return tree
1070
1070
1071 def findaliases(ui, tree):
1071 def findaliases(ui, tree):
1072 for k, v in ui.configitems('revsetalias'):
1072 for k, v in ui.configitems('revsetalias'):
1073 alias = revsetalias(k, v)
1073 alias = revsetalias(k, v)
1074 tree = alias.process(tree)
1074 tree = alias.process(tree)
1075 return tree
1075 return tree
1076
1076
1077 parse = parser.parser(tokenize, elements).parse
1077 parse = parser.parser(tokenize, elements).parse
1078
1078
1079 def match(ui, spec):
1079 def match(ui, spec):
1080 if not spec:
1080 if not spec:
1081 raise error.ParseError(_("empty query"))
1081 raise error.ParseError(_("empty query"))
1082 tree, pos = parse(spec)
1082 tree, pos = parse(spec)
1083 if (pos != len(spec)):
1083 if (pos != len(spec)):
1084 raise error.ParseError(_("invalid token"), pos)
1084 raise error.ParseError(_("invalid token"), pos)
1085 if ui:
1085 if ui:
1086 tree = findaliases(ui, tree)
1086 tree = findaliases(ui, tree)
1087 weight, tree = optimize(tree, True)
1087 weight, tree = optimize(tree, True)
1088 def mfunc(repo, subset):
1088 def mfunc(repo, subset):
1089 return getset(repo, subset, tree)
1089 return getset(repo, subset, tree)
1090 return mfunc
1090 return mfunc
1091
1091
1092 def formatspec(expr, *args):
1092 def formatspec(expr, *args):
1093 '''
1093 '''
1094 This is a convenience function for using revsets internally, and
1094 This is a convenience function for using revsets internally, and
1095 escapes arguments appropriately. Aliases are intentionally ignored
1095 escapes arguments appropriately. Aliases are intentionally ignored
1096 so that intended expression behavior isn't accidentally subverted.
1096 so that intended expression behavior isn't accidentally subverted.
1097
1097
1098 Supported arguments:
1098 Supported arguments:
1099
1099
1100 %r = revset expression, parenthesized
1100 %r = revset expression, parenthesized
1101 %d = int(arg), no quoting
1101 %d = int(arg), no quoting
1102 %s = string(arg), escaped and single-quoted
1102 %s = string(arg), escaped and single-quoted
1103 %b = arg.branch(), escaped and single-quoted
1103 %b = arg.branch(), escaped and single-quoted
1104 %n = hex(arg), single-quoted
1104 %n = hex(arg), single-quoted
1105 %% = a literal '%'
1105 %% = a literal '%'
1106
1106
1107 Prefixing the type with 'l' specifies a parenthesized list of that type.
1107 Prefixing the type with 'l' specifies a parenthesized list of that type.
1108
1108
1109 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1109 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1110 '(10 or 11):: and ((this()) or (that()))'
1110 '(10 or 11):: and ((this()) or (that()))'
1111 >>> formatspec('%d:: and not %d::', 10, 20)
1111 >>> formatspec('%d:: and not %d::', 10, 20)
1112 '10:: and not 20::'
1112 '10:: and not 20::'
1113 >>> formatspec('%ld or %ld', [], [1])
1113 >>> formatspec('%ld or %ld', [], [1])
1114 "_list('') or 1"
1114 "_list('') or 1"
1115 >>> formatspec('keyword(%s)', 'foo\\xe9')
1115 >>> formatspec('keyword(%s)', 'foo\\xe9')
1116 "keyword('foo\\\\xe9')"
1116 "keyword('foo\\\\xe9')"
1117 >>> b = lambda: 'default'
1117 >>> b = lambda: 'default'
1118 >>> b.branch = b
1118 >>> b.branch = b
1119 >>> formatspec('branch(%b)', b)
1119 >>> formatspec('branch(%b)', b)
1120 "branch('default')"
1120 "branch('default')"
1121 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1121 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1122 "root(_list('a\\x00b\\x00c\\x00d'))"
1122 "root(_list('a\\x00b\\x00c\\x00d'))"
1123 '''
1123 '''
1124
1124
1125 def quote(s):
1125 def quote(s):
1126 return repr(str(s))
1126 return repr(str(s))
1127
1127
1128 def argtype(c, arg):
1128 def argtype(c, arg):
1129 if c == 'd':
1129 if c == 'd':
1130 return str(int(arg))
1130 return str(int(arg))
1131 elif c == 's':
1131 elif c == 's':
1132 return quote(arg)
1132 return quote(arg)
1133 elif c == 'r':
1133 elif c == 'r':
1134 parse(arg) # make sure syntax errors are confined
1134 parse(arg) # make sure syntax errors are confined
1135 return '(%s)' % arg
1135 return '(%s)' % arg
1136 elif c == 'n':
1136 elif c == 'n':
1137 return quote(nodemod.hex(arg))
1137 return quote(nodemod.hex(arg))
1138 elif c == 'b':
1138 elif c == 'b':
1139 return quote(arg.branch())
1139 return quote(arg.branch())
1140
1140
1141 def listexp(s, t):
1141 def listexp(s, t):
1142 l = len(s)
1142 l = len(s)
1143 if l == 0:
1143 if l == 0:
1144 return "_list('')"
1144 return "_list('')"
1145 elif l == 1:
1145 elif l == 1:
1146 return argtype(t, s[0])
1146 return argtype(t, s[0])
1147 elif t == 'd':
1147 elif t == 'd':
1148 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1148 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1149 elif t == 's':
1149 elif t == 's':
1150 return "_list('%s')" % "\0".join(s)
1150 return "_list('%s')" % "\0".join(s)
1151 elif t == 'n':
1151 elif t == 'n':
1152 return "_list('%s')" % "\0".join(nodemod.hex(a) for a in s)
1152 return "_list('%s')" % "\0".join(nodemod.hex(a) for a in s)
1153 elif t == 'b':
1153 elif t == 'b':
1154 return "_list('%s')" % "\0".join(a.branch() for a in s)
1154 return "_list('%s')" % "\0".join(a.branch() for a in s)
1155
1155
1156 m = l // 2
1156 m = l // 2
1157 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1157 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1158
1158
1159 ret = ''
1159 ret = ''
1160 pos = 0
1160 pos = 0
1161 arg = 0
1161 arg = 0
1162 while pos < len(expr):
1162 while pos < len(expr):
1163 c = expr[pos]
1163 c = expr[pos]
1164 if c == '%':
1164 if c == '%':
1165 pos += 1
1165 pos += 1
1166 d = expr[pos]
1166 d = expr[pos]
1167 if d == '%':
1167 if d == '%':
1168 ret += d
1168 ret += d
1169 elif d in 'dsnbr':
1169 elif d in 'dsnbr':
1170 ret += argtype(d, args[arg])
1170 ret += argtype(d, args[arg])
1171 arg += 1
1171 arg += 1
1172 elif d == 'l':
1172 elif d == 'l':
1173 # a list of some type
1173 # a list of some type
1174 pos += 1
1174 pos += 1
1175 d = expr[pos]
1175 d = expr[pos]
1176 ret += listexp(list(args[arg]), d)
1176 ret += listexp(list(args[arg]), d)
1177 arg += 1
1177 arg += 1
1178 else:
1178 else:
1179 raise util.Abort('unexpected revspec format character %s' % d)
1179 raise util.Abort('unexpected revspec format character %s' % d)
1180 else:
1180 else:
1181 ret += c
1181 ret += c
1182 pos += 1
1182 pos += 1
1183
1183
1184 return ret
1184 return ret
1185
1185
1186 # tell hggettext to extract docstrings from these functions:
1186 # tell hggettext to extract docstrings from these functions:
1187 i18nfunctions = symbols.values()
1187 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now