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