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