##// END OF EJS Templates
revsets: add a last function...
Matt Mackall -
r14061:611d2f8a default
parent child Browse files
Show More
@@ -1,845 +1,860 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, help, hbisect
9 import parser, util, error, discovery, help, hbisect
10 import bookmarks as bookmarksmod
10 import bookmarks as bookmarksmod
11 import match as matchmod
11 import match as matchmod
12 from i18n import _
12 from i18n import _
13
13
14 elements = {
14 elements = {
15 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
15 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
16 "-": (5, ("negate", 19), ("minus", 5)),
16 "-": (5, ("negate", 19), ("minus", 5)),
17 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
17 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
18 ("dagrangepost", 17)),
18 ("dagrangepost", 17)),
19 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
19 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
20 ("dagrangepost", 17)),
20 ("dagrangepost", 17)),
21 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
21 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
22 "not": (10, ("not", 10)),
22 "not": (10, ("not", 10)),
23 "!": (10, ("not", 10)),
23 "!": (10, ("not", 10)),
24 "and": (5, None, ("and", 5)),
24 "and": (5, None, ("and", 5)),
25 "&": (5, None, ("and", 5)),
25 "&": (5, None, ("and", 5)),
26 "or": (4, None, ("or", 4)),
26 "or": (4, None, ("or", 4)),
27 "|": (4, None, ("or", 4)),
27 "|": (4, None, ("or", 4)),
28 "+": (4, None, ("or", 4)),
28 "+": (4, None, ("or", 4)),
29 ",": (2, None, ("list", 2)),
29 ",": (2, None, ("list", 2)),
30 ")": (0, None, None),
30 ")": (0, None, None),
31 "symbol": (0, ("symbol",), None),
31 "symbol": (0, ("symbol",), None),
32 "string": (0, ("string",), None),
32 "string": (0, ("string",), None),
33 "end": (0, None, None),
33 "end": (0, None, None),
34 }
34 }
35
35
36 keywords = set(['and', 'or', 'not'])
36 keywords = set(['and', 'or', 'not'])
37
37
38 def tokenize(program):
38 def tokenize(program):
39 pos, l = 0, len(program)
39 pos, l = 0, len(program)
40 while pos < l:
40 while pos < l:
41 c = program[pos]
41 c = program[pos]
42 if c.isspace(): # skip inter-token whitespace
42 if c.isspace(): # skip inter-token whitespace
43 pass
43 pass
44 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
44 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
45 yield ('::', None, pos)
45 yield ('::', None, pos)
46 pos += 1 # skip ahead
46 pos += 1 # skip ahead
47 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
47 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
48 yield ('..', None, pos)
48 yield ('..', None, pos)
49 pos += 1 # skip ahead
49 pos += 1 # skip ahead
50 elif c in "():,-|&+!": # handle simple operators
50 elif c in "():,-|&+!": # handle simple operators
51 yield (c, None, pos)
51 yield (c, None, pos)
52 elif (c in '"\'' or c == 'r' and
52 elif (c in '"\'' or c == 'r' and
53 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
53 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
54 if c == 'r':
54 if c == 'r':
55 pos += 1
55 pos += 1
56 c = program[pos]
56 c = program[pos]
57 decode = lambda x: x
57 decode = lambda x: x
58 else:
58 else:
59 decode = lambda x: x.decode('string-escape')
59 decode = lambda x: x.decode('string-escape')
60 pos += 1
60 pos += 1
61 s = pos
61 s = pos
62 while pos < l: # find closing quote
62 while pos < l: # find closing quote
63 d = program[pos]
63 d = program[pos]
64 if d == '\\': # skip over escaped characters
64 if d == '\\': # skip over escaped characters
65 pos += 2
65 pos += 2
66 continue
66 continue
67 if d == c:
67 if d == c:
68 yield ('string', decode(program[s:pos]), s)
68 yield ('string', decode(program[s:pos]), s)
69 break
69 break
70 pos += 1
70 pos += 1
71 else:
71 else:
72 raise error.ParseError(_("unterminated string"), s)
72 raise error.ParseError(_("unterminated string"), s)
73 elif c.isalnum() or c in '._' or ord(c) > 127: # gather up a symbol/keyword
73 elif c.isalnum() or c in '._' or ord(c) > 127: # gather up a symbol/keyword
74 s = pos
74 s = pos
75 pos += 1
75 pos += 1
76 while pos < l: # find end of symbol
76 while pos < l: # find end of symbol
77 d = program[pos]
77 d = program[pos]
78 if not (d.isalnum() or d in "._" or ord(d) > 127):
78 if not (d.isalnum() or d in "._" or ord(d) > 127):
79 break
79 break
80 if d == '.' and program[pos - 1] == '.': # special case for ..
80 if d == '.' and program[pos - 1] == '.': # special case for ..
81 pos -= 1
81 pos -= 1
82 break
82 break
83 pos += 1
83 pos += 1
84 sym = program[s:pos]
84 sym = program[s:pos]
85 if sym in keywords: # operator keywords
85 if sym in keywords: # operator keywords
86 yield (sym, None, s)
86 yield (sym, None, s)
87 else:
87 else:
88 yield ('symbol', sym, s)
88 yield ('symbol', sym, s)
89 pos -= 1
89 pos -= 1
90 else:
90 else:
91 raise error.ParseError(_("syntax error"), pos)
91 raise error.ParseError(_("syntax error"), pos)
92 pos += 1
92 pos += 1
93 yield ('end', None, pos)
93 yield ('end', None, pos)
94
94
95 # helpers
95 # helpers
96
96
97 def getstring(x, err):
97 def getstring(x, err):
98 if x and (x[0] == 'string' or x[0] == 'symbol'):
98 if x and (x[0] == 'string' or x[0] == 'symbol'):
99 return x[1]
99 return x[1]
100 raise error.ParseError(err)
100 raise error.ParseError(err)
101
101
102 def getlist(x):
102 def getlist(x):
103 if not x:
103 if not x:
104 return []
104 return []
105 if x[0] == 'list':
105 if x[0] == 'list':
106 return getlist(x[1]) + [x[2]]
106 return getlist(x[1]) + [x[2]]
107 return [x]
107 return [x]
108
108
109 def getargs(x, min, max, err):
109 def getargs(x, min, max, err):
110 l = getlist(x)
110 l = getlist(x)
111 if len(l) < min or len(l) > max:
111 if len(l) < min or len(l) > max:
112 raise error.ParseError(err)
112 raise error.ParseError(err)
113 return l
113 return l
114
114
115 def getset(repo, subset, x):
115 def getset(repo, subset, x):
116 if not x:
116 if not x:
117 raise error.ParseError(_("missing argument"))
117 raise error.ParseError(_("missing argument"))
118 return methods[x[0]](repo, subset, *x[1:])
118 return methods[x[0]](repo, subset, *x[1:])
119
119
120 # operator methods
120 # operator methods
121
121
122 def stringset(repo, subset, x):
122 def stringset(repo, subset, x):
123 x = repo[x].rev()
123 x = repo[x].rev()
124 if x == -1 and len(subset) == len(repo):
124 if x == -1 and len(subset) == len(repo):
125 return [-1]
125 return [-1]
126 if len(subset) == len(repo) or x in subset:
126 if len(subset) == len(repo) or x in subset:
127 return [x]
127 return [x]
128 return []
128 return []
129
129
130 def symbolset(repo, subset, x):
130 def symbolset(repo, subset, x):
131 if x in symbols:
131 if x in symbols:
132 raise error.ParseError(_("can't use %s here") % x)
132 raise error.ParseError(_("can't use %s here") % x)
133 return stringset(repo, subset, x)
133 return stringset(repo, subset, x)
134
134
135 def rangeset(repo, subset, x, y):
135 def rangeset(repo, subset, x, y):
136 m = getset(repo, subset, x)
136 m = getset(repo, subset, x)
137 if not m:
137 if not m:
138 m = getset(repo, range(len(repo)), x)
138 m = getset(repo, range(len(repo)), x)
139
139
140 n = getset(repo, subset, y)
140 n = getset(repo, subset, y)
141 if not n:
141 if not n:
142 n = getset(repo, range(len(repo)), y)
142 n = getset(repo, range(len(repo)), y)
143
143
144 if not m or not n:
144 if not m or not n:
145 return []
145 return []
146 m, n = m[0], n[-1]
146 m, n = m[0], n[-1]
147
147
148 if m < n:
148 if m < n:
149 r = range(m, n + 1)
149 r = range(m, n + 1)
150 else:
150 else:
151 r = range(m, n - 1, -1)
151 r = range(m, n - 1, -1)
152 s = set(subset)
152 s = set(subset)
153 return [x for x in r if x in s]
153 return [x for x in r if x in s]
154
154
155 def andset(repo, subset, x, y):
155 def andset(repo, subset, x, y):
156 return getset(repo, getset(repo, subset, x), y)
156 return getset(repo, getset(repo, subset, x), y)
157
157
158 def orset(repo, subset, x, y):
158 def orset(repo, subset, x, y):
159 xl = getset(repo, subset, x)
159 xl = getset(repo, subset, x)
160 s = set(xl)
160 s = set(xl)
161 yl = getset(repo, [r for r in subset if r not in s], y)
161 yl = getset(repo, [r for r in subset if r not in s], y)
162 return xl + yl
162 return xl + yl
163
163
164 def notset(repo, subset, x):
164 def notset(repo, subset, x):
165 s = set(getset(repo, subset, x))
165 s = set(getset(repo, subset, x))
166 return [r for r in subset if r not in s]
166 return [r for r in subset if r not in s]
167
167
168 def listset(repo, subset, a, b):
168 def listset(repo, subset, a, b):
169 raise error.ParseError(_("can't use a list in this context"))
169 raise error.ParseError(_("can't use a list in this context"))
170
170
171 def func(repo, subset, a, b):
171 def func(repo, subset, a, b):
172 if a[0] == 'symbol' and a[1] in symbols:
172 if a[0] == 'symbol' and a[1] in symbols:
173 return symbols[a[1]](repo, subset, b)
173 return symbols[a[1]](repo, subset, b)
174 raise error.ParseError(_("not a function: %s") % a[1])
174 raise error.ParseError(_("not a function: %s") % a[1])
175
175
176 # functions
176 # functions
177
177
178 def adds(repo, subset, x):
178 def adds(repo, subset, x):
179 """``adds(pattern)``
179 """``adds(pattern)``
180 Changesets that add a file matching pattern.
180 Changesets that add a file matching pattern.
181 """
181 """
182 # i18n: "adds" is a keyword
182 # i18n: "adds" is a keyword
183 pat = getstring(x, _("adds requires a pattern"))
183 pat = getstring(x, _("adds requires a pattern"))
184 return checkstatus(repo, subset, pat, 1)
184 return checkstatus(repo, subset, pat, 1)
185
185
186 def ancestor(repo, subset, x):
186 def ancestor(repo, subset, x):
187 """``ancestor(single, single)``
187 """``ancestor(single, single)``
188 Greatest common ancestor of the two changesets.
188 Greatest common ancestor of the two changesets.
189 """
189 """
190 # i18n: "ancestor" is a keyword
190 # i18n: "ancestor" is a keyword
191 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
191 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
192 r = range(len(repo))
192 r = range(len(repo))
193 a = getset(repo, r, l[0])
193 a = getset(repo, r, l[0])
194 b = getset(repo, r, l[1])
194 b = getset(repo, r, l[1])
195 if len(a) != 1 or len(b) != 1:
195 if len(a) != 1 or len(b) != 1:
196 # i18n: "ancestor" is a keyword
196 # i18n: "ancestor" is a keyword
197 raise error.ParseError(_("ancestor arguments must be single revisions"))
197 raise error.ParseError(_("ancestor arguments must be single revisions"))
198 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
198 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
199
199
200 return [r for r in an if r in subset]
200 return [r for r in an if r in subset]
201
201
202 def ancestors(repo, subset, x):
202 def ancestors(repo, subset, x):
203 """``ancestors(set)``
203 """``ancestors(set)``
204 Changesets that are ancestors of a changeset in set.
204 Changesets that are ancestors of a changeset in set.
205 """
205 """
206 args = getset(repo, range(len(repo)), x)
206 args = getset(repo, range(len(repo)), x)
207 if not args:
207 if not args:
208 return []
208 return []
209 s = set(repo.changelog.ancestors(*args)) | set(args)
209 s = set(repo.changelog.ancestors(*args)) | set(args)
210 return [r for r in subset if r in s]
210 return [r for r in subset if r in s]
211
211
212 def author(repo, subset, x):
212 def author(repo, subset, x):
213 """``author(string)``
213 """``author(string)``
214 Alias for ``user(string)``.
214 Alias for ``user(string)``.
215 """
215 """
216 # i18n: "author" is a keyword
216 # i18n: "author" is a keyword
217 n = getstring(x, _("author requires a string")).lower()
217 n = getstring(x, _("author requires a string")).lower()
218 return [r for r in subset if n in repo[r].user().lower()]
218 return [r for r in subset if n in repo[r].user().lower()]
219
219
220 def bisected(repo, subset, x):
220 def bisected(repo, subset, x):
221 """``bisected(string)``
221 """``bisected(string)``
222 Changesets marked in the specified bisect state (good, bad, skip).
222 Changesets marked in the specified bisect state (good, bad, skip).
223 """
223 """
224 state = getstring(x, _("bisect requires a string")).lower()
224 state = getstring(x, _("bisect requires a string")).lower()
225 if state not in ('good', 'bad', 'skip', 'unknown'):
225 if state not in ('good', 'bad', 'skip', 'unknown'):
226 raise error.ParseError(_('invalid bisect state'))
226 raise error.ParseError(_('invalid bisect state'))
227 marked = set(repo.changelog.rev(n) for n in hbisect.load_state(repo)[state])
227 marked = set(repo.changelog.rev(n) for n in hbisect.load_state(repo)[state])
228 return [r for r in subset if r in marked]
228 return [r for r in subset if r in marked]
229
229
230 def bookmark(repo, subset, x):
230 def bookmark(repo, subset, x):
231 """``bookmark([name])``
231 """``bookmark([name])``
232 The named bookmark or all bookmarks.
232 The named bookmark or all bookmarks.
233 """
233 """
234 # i18n: "bookmark" is a keyword
234 # i18n: "bookmark" is a keyword
235 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
235 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
236 if args:
236 if args:
237 bm = getstring(args[0],
237 bm = getstring(args[0],
238 # i18n: "bookmark" is a keyword
238 # i18n: "bookmark" is a keyword
239 _('the argument to bookmark must be a string'))
239 _('the argument to bookmark must be a string'))
240 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
240 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
241 if not bmrev:
241 if not bmrev:
242 raise util.Abort(_("bookmark '%s' does not exist") % bm)
242 raise util.Abort(_("bookmark '%s' does not exist") % bm)
243 bmrev = repo[bmrev].rev()
243 bmrev = repo[bmrev].rev()
244 return [r for r in subset if r == bmrev]
244 return [r for r in subset if r == bmrev]
245 bms = set([repo[r].rev()
245 bms = set([repo[r].rev()
246 for r in bookmarksmod.listbookmarks(repo).values()])
246 for r in bookmarksmod.listbookmarks(repo).values()])
247 return [r for r in subset if r in bms]
247 return [r for r in subset if r in bms]
248
248
249 def branch(repo, subset, x):
249 def branch(repo, subset, x):
250 """``branch(string or set)``
250 """``branch(string or set)``
251 All changesets belonging to the given branch or the branches of the given
251 All changesets belonging to the given branch or the branches of the given
252 changesets.
252 changesets.
253 """
253 """
254 try:
254 try:
255 b = getstring(x, '')
255 b = getstring(x, '')
256 if b in repo.branchmap():
256 if b in repo.branchmap():
257 return [r for r in subset if repo[r].branch() == b]
257 return [r for r in subset if repo[r].branch() == b]
258 except error.ParseError:
258 except error.ParseError:
259 # not a string, but another revspec, e.g. tip()
259 # not a string, but another revspec, e.g. tip()
260 pass
260 pass
261
261
262 s = getset(repo, range(len(repo)), x)
262 s = getset(repo, range(len(repo)), x)
263 b = set()
263 b = set()
264 for r in s:
264 for r in s:
265 b.add(repo[r].branch())
265 b.add(repo[r].branch())
266 s = set(s)
266 s = set(s)
267 return [r for r in subset if r in s or repo[r].branch() in b]
267 return [r for r in subset if r in s or repo[r].branch() in b]
268
268
269 def checkstatus(repo, subset, pat, field):
269 def checkstatus(repo, subset, pat, field):
270 m = matchmod.match(repo.root, repo.getcwd(), [pat])
270 m = matchmod.match(repo.root, repo.getcwd(), [pat])
271 s = []
271 s = []
272 fast = (m.files() == [pat])
272 fast = (m.files() == [pat])
273 for r in subset:
273 for r in subset:
274 c = repo[r]
274 c = repo[r]
275 if fast:
275 if fast:
276 if pat not in c.files():
276 if pat not in c.files():
277 continue
277 continue
278 else:
278 else:
279 for f in c.files():
279 for f in c.files():
280 if m(f):
280 if m(f):
281 break
281 break
282 else:
282 else:
283 continue
283 continue
284 files = repo.status(c.p1().node(), c.node())[field]
284 files = repo.status(c.p1().node(), c.node())[field]
285 if fast:
285 if fast:
286 if pat in files:
286 if pat in files:
287 s.append(r)
287 s.append(r)
288 else:
288 else:
289 for f in files:
289 for f in files:
290 if m(f):
290 if m(f):
291 s.append(r)
291 s.append(r)
292 break
292 break
293 return s
293 return s
294
294
295 def children(repo, subset, x):
295 def children(repo, subset, x):
296 """``children(set)``
296 """``children(set)``
297 Child changesets of changesets in set.
297 Child changesets of changesets in set.
298 """
298 """
299 cs = set()
299 cs = set()
300 cl = repo.changelog
300 cl = repo.changelog
301 s = set(getset(repo, range(len(repo)), x))
301 s = set(getset(repo, range(len(repo)), x))
302 for r in xrange(0, len(repo)):
302 for r in xrange(0, len(repo)):
303 for p in cl.parentrevs(r):
303 for p in cl.parentrevs(r):
304 if p in s:
304 if p in s:
305 cs.add(r)
305 cs.add(r)
306 return [r for r in subset if r in cs]
306 return [r for r in subset if r in cs]
307
307
308 def closed(repo, subset, x):
308 def closed(repo, subset, x):
309 """``closed()``
309 """``closed()``
310 Changeset is closed.
310 Changeset is closed.
311 """
311 """
312 # i18n: "closed" is a keyword
312 # i18n: "closed" is a keyword
313 getargs(x, 0, 0, _("closed takes no arguments"))
313 getargs(x, 0, 0, _("closed takes no arguments"))
314 return [r for r in subset if repo[r].extra().get('close')]
314 return [r for r in subset if repo[r].extra().get('close')]
315
315
316 def contains(repo, subset, x):
316 def contains(repo, subset, x):
317 """``contains(pattern)``
317 """``contains(pattern)``
318 Revision contains pattern.
318 Revision contains pattern.
319 """
319 """
320 # i18n: "contains" is a keyword
320 # i18n: "contains" is a keyword
321 pat = getstring(x, _("contains requires a pattern"))
321 pat = getstring(x, _("contains requires a pattern"))
322 m = matchmod.match(repo.root, repo.getcwd(), [pat])
322 m = matchmod.match(repo.root, repo.getcwd(), [pat])
323 s = []
323 s = []
324 if m.files() == [pat]:
324 if m.files() == [pat]:
325 for r in subset:
325 for r in subset:
326 if pat in repo[r]:
326 if pat in repo[r]:
327 s.append(r)
327 s.append(r)
328 else:
328 else:
329 for r in subset:
329 for r in subset:
330 for f in repo[r].manifest():
330 for f in repo[r].manifest():
331 if m(f):
331 if m(f):
332 s.append(r)
332 s.append(r)
333 break
333 break
334 return s
334 return s
335
335
336 def date(repo, subset, x):
336 def date(repo, subset, x):
337 """``date(interval)``
337 """``date(interval)``
338 Changesets within the interval, see :hg:`help dates`.
338 Changesets within the interval, see :hg:`help dates`.
339 """
339 """
340 # i18n: "date" is a keyword
340 # i18n: "date" is a keyword
341 ds = getstring(x, _("date requires a string"))
341 ds = getstring(x, _("date requires a string"))
342 dm = util.matchdate(ds)
342 dm = util.matchdate(ds)
343 return [r for r in subset if dm(repo[r].date()[0])]
343 return [r for r in subset if dm(repo[r].date()[0])]
344
344
345 def descendants(repo, subset, x):
345 def descendants(repo, subset, x):
346 """``descendants(set)``
346 """``descendants(set)``
347 Changesets which are descendants of changesets in set.
347 Changesets which are descendants of changesets in set.
348 """
348 """
349 args = getset(repo, range(len(repo)), x)
349 args = getset(repo, range(len(repo)), x)
350 if not args:
350 if not args:
351 return []
351 return []
352 s = set(repo.changelog.descendants(*args)) | set(args)
352 s = set(repo.changelog.descendants(*args)) | set(args)
353 return [r for r in subset if r in s]
353 return [r for r in subset if r in s]
354
354
355 def follow(repo, subset, x):
355 def follow(repo, subset, x):
356 """``follow()``
356 """``follow()``
357 An alias for ``::.`` (ancestors of the working copy's first parent).
357 An alias for ``::.`` (ancestors of the working copy's first parent).
358 """
358 """
359 # i18n: "follow" is a keyword
359 # i18n: "follow" is a keyword
360 getargs(x, 0, 0, _("follow takes no arguments"))
360 getargs(x, 0, 0, _("follow takes no arguments"))
361 p = repo['.'].rev()
361 p = repo['.'].rev()
362 s = set(repo.changelog.ancestors(p)) | set([p])
362 s = set(repo.changelog.ancestors(p)) | set([p])
363 return [r for r in subset if r in s]
363 return [r for r in subset if r in s]
364
364
365 def getall(repo, subset, x):
365 def getall(repo, subset, x):
366 """``all()``
366 """``all()``
367 All changesets, the same as ``0:tip``.
367 All changesets, the same as ``0:tip``.
368 """
368 """
369 # i18n: "all" is a keyword
369 # i18n: "all" is a keyword
370 getargs(x, 0, 0, _("all takes no arguments"))
370 getargs(x, 0, 0, _("all takes no arguments"))
371 return subset
371 return subset
372
372
373 def grep(repo, subset, x):
373 def grep(repo, subset, x):
374 """``grep(regex)``
374 """``grep(regex)``
375 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
375 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
376 to ensure special escape characters are handled correctly.
376 to ensure special escape characters are handled correctly.
377 """
377 """
378 try:
378 try:
379 # i18n: "grep" is a keyword
379 # i18n: "grep" is a keyword
380 gr = re.compile(getstring(x, _("grep requires a string")))
380 gr = re.compile(getstring(x, _("grep requires a string")))
381 except re.error, e:
381 except re.error, e:
382 raise error.ParseError(_('invalid match pattern: %s') % e)
382 raise error.ParseError(_('invalid match pattern: %s') % e)
383 l = []
383 l = []
384 for r in subset:
384 for r in subset:
385 c = repo[r]
385 c = repo[r]
386 for e in c.files() + [c.user(), c.description()]:
386 for e in c.files() + [c.user(), c.description()]:
387 if gr.search(e):
387 if gr.search(e):
388 l.append(r)
388 l.append(r)
389 break
389 break
390 return l
390 return l
391
391
392 def hasfile(repo, subset, x):
392 def hasfile(repo, subset, x):
393 """``file(pattern)``
393 """``file(pattern)``
394 Changesets affecting files matched by pattern.
394 Changesets affecting files matched by pattern.
395 """
395 """
396 # i18n: "file" is a keyword
396 # i18n: "file" is a keyword
397 pat = getstring(x, _("file requires a pattern"))
397 pat = getstring(x, _("file requires a pattern"))
398 m = matchmod.match(repo.root, repo.getcwd(), [pat])
398 m = matchmod.match(repo.root, repo.getcwd(), [pat])
399 s = []
399 s = []
400 for r in subset:
400 for r in subset:
401 for f in repo[r].files():
401 for f in repo[r].files():
402 if m(f):
402 if m(f):
403 s.append(r)
403 s.append(r)
404 break
404 break
405 return s
405 return s
406
406
407 def head(repo, subset, x):
407 def head(repo, subset, x):
408 """``head()``
408 """``head()``
409 Changeset is a named branch head.
409 Changeset is a named branch head.
410 """
410 """
411 # i18n: "head" is a keyword
411 # i18n: "head" is a keyword
412 getargs(x, 0, 0, _("head takes no arguments"))
412 getargs(x, 0, 0, _("head takes no arguments"))
413 hs = set()
413 hs = set()
414 for b, ls in repo.branchmap().iteritems():
414 for b, ls in repo.branchmap().iteritems():
415 hs.update(repo[h].rev() for h in ls)
415 hs.update(repo[h].rev() for h in ls)
416 return [r for r in subset if r in hs]
416 return [r for r in subset if r in hs]
417
417
418 def heads(repo, subset, x):
418 def heads(repo, subset, x):
419 """``heads(set)``
419 """``heads(set)``
420 Members of set with no children in set.
420 Members of set with no children in set.
421 """
421 """
422 s = getset(repo, subset, x)
422 s = getset(repo, subset, x)
423 ps = set(parents(repo, subset, x))
423 ps = set(parents(repo, subset, x))
424 return [r for r in s if r not in ps]
424 return [r for r in s if r not in ps]
425
425
426 def keyword(repo, subset, x):
426 def keyword(repo, subset, x):
427 """``keyword(string)``
427 """``keyword(string)``
428 Search commit message, user name, and names of changed files for
428 Search commit message, user name, and names of changed files for
429 string.
429 string.
430 """
430 """
431 # i18n: "keyword" is a keyword
431 # i18n: "keyword" is a keyword
432 kw = getstring(x, _("keyword requires a string")).lower()
432 kw = getstring(x, _("keyword requires a string")).lower()
433 l = []
433 l = []
434 for r in subset:
434 for r in subset:
435 c = repo[r]
435 c = repo[r]
436 t = " ".join(c.files() + [c.user(), c.description()])
436 t = " ".join(c.files() + [c.user(), c.description()])
437 if kw in t.lower():
437 if kw in t.lower():
438 l.append(r)
438 l.append(r)
439 return l
439 return l
440
440
441 def limit(repo, subset, x):
441 def limit(repo, subset, x):
442 """``limit(set, n)``
442 """``limit(set, n)``
443 First n members of set.
443 First n members of set.
444 """
444 """
445 # i18n: "limit" is a keyword
445 # i18n: "limit" is a keyword
446 l = getargs(x, 2, 2, _("limit requires two arguments"))
446 l = getargs(x, 2, 2, _("limit requires two arguments"))
447 try:
447 try:
448 # i18n: "limit" is a keyword
448 # i18n: "limit" is a keyword
449 lim = int(getstring(l[1], _("limit requires a number")))
449 lim = int(getstring(l[1], _("limit requires a number")))
450 except ValueError:
450 except ValueError:
451 # i18n: "limit" is a keyword
451 # i18n: "limit" is a keyword
452 raise error.ParseError(_("limit expects a number"))
452 raise error.ParseError(_("limit expects a number"))
453 return getset(repo, subset, l[0])[:lim]
453 return getset(repo, subset, l[0])[:lim]
454
454
455 def last(repo, subset, x):
456 """``last(set, n)``
457 Last n members of set.
458 """
459 # i18n: "last" is a keyword
460 l = getargs(x, 2, 2, _("last requires two arguments"))
461 try:
462 # i18n: "last" is a keyword
463 lim = int(getstring(l[1], _("last requires a number")))
464 except ValueError:
465 # i18n: "last" is a keyword
466 raise error.ParseError(_("last expects a number"))
467 return getset(repo, subset, l[0])[-lim:]
468
455 def maxrev(repo, subset, x):
469 def maxrev(repo, subset, x):
456 """``max(set)``
470 """``max(set)``
457 Changeset with highest revision number in set.
471 Changeset with highest revision number in set.
458 """
472 """
459 s = getset(repo, subset, x)
473 s = getset(repo, subset, x)
460 if s:
474 if s:
461 m = max(s)
475 m = max(s)
462 if m in subset:
476 if m in subset:
463 return [m]
477 return [m]
464 return []
478 return []
465
479
466 def merge(repo, subset, x):
480 def merge(repo, subset, x):
467 """``merge()``
481 """``merge()``
468 Changeset is a merge changeset.
482 Changeset is a merge changeset.
469 """
483 """
470 # i18n: "merge" is a keyword
484 # i18n: "merge" is a keyword
471 getargs(x, 0, 0, _("merge takes no arguments"))
485 getargs(x, 0, 0, _("merge takes no arguments"))
472 cl = repo.changelog
486 cl = repo.changelog
473 return [r for r in subset if cl.parentrevs(r)[1] != -1]
487 return [r for r in subset if cl.parentrevs(r)[1] != -1]
474
488
475 def minrev(repo, subset, x):
489 def minrev(repo, subset, x):
476 """``min(set)``
490 """``min(set)``
477 Changeset with lowest revision number in set.
491 Changeset with lowest revision number in set.
478 """
492 """
479 s = getset(repo, subset, x)
493 s = getset(repo, subset, x)
480 if s:
494 if s:
481 m = min(s)
495 m = min(s)
482 if m in subset:
496 if m in subset:
483 return [m]
497 return [m]
484 return []
498 return []
485
499
486 def modifies(repo, subset, x):
500 def modifies(repo, subset, x):
487 """``modifies(pattern)``
501 """``modifies(pattern)``
488 Changesets modifying files matched by pattern.
502 Changesets modifying files matched by pattern.
489 """
503 """
490 # i18n: "modifies" is a keyword
504 # i18n: "modifies" is a keyword
491 pat = getstring(x, _("modifies requires a pattern"))
505 pat = getstring(x, _("modifies requires a pattern"))
492 return checkstatus(repo, subset, pat, 0)
506 return checkstatus(repo, subset, pat, 0)
493
507
494 def node(repo, subset, x):
508 def node(repo, subset, x):
495 """``id(string)``
509 """``id(string)``
496 Revision non-ambiguously specified by the given hex string prefix.
510 Revision non-ambiguously specified by the given hex string prefix.
497 """
511 """
498 # i18n: "id" is a keyword
512 # i18n: "id" is a keyword
499 l = getargs(x, 1, 1, _("id requires one argument"))
513 l = getargs(x, 1, 1, _("id requires one argument"))
500 # i18n: "id" is a keyword
514 # i18n: "id" is a keyword
501 n = getstring(l[0], _("id requires a string"))
515 n = getstring(l[0], _("id requires a string"))
502 if len(n) == 40:
516 if len(n) == 40:
503 rn = repo[n].rev()
517 rn = repo[n].rev()
504 else:
518 else:
505 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
519 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
506 return [r for r in subset if r == rn]
520 return [r for r in subset if r == rn]
507
521
508 def outgoing(repo, subset, x):
522 def outgoing(repo, subset, x):
509 """``outgoing([path])``
523 """``outgoing([path])``
510 Changesets not found in the specified destination repository, or the
524 Changesets not found in the specified destination repository, or the
511 default push location.
525 default push location.
512 """
526 """
513 import hg # avoid start-up nasties
527 import hg # avoid start-up nasties
514 # i18n: "outgoing" is a keyword
528 # i18n: "outgoing" is a keyword
515 l = getargs(x, 0, 1, _("outgoing requires a repository path"))
529 l = getargs(x, 0, 1, _("outgoing requires a repository path"))
516 # i18n: "outgoing" is a keyword
530 # i18n: "outgoing" is a keyword
517 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
531 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
518 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
532 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
519 dest, branches = hg.parseurl(dest)
533 dest, branches = hg.parseurl(dest)
520 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
534 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
521 if revs:
535 if revs:
522 revs = [repo.lookup(rev) for rev in revs]
536 revs = [repo.lookup(rev) for rev in revs]
523 other = hg.repository(hg.remoteui(repo, {}), dest)
537 other = hg.repository(hg.remoteui(repo, {}), dest)
524 repo.ui.pushbuffer()
538 repo.ui.pushbuffer()
525 o = discovery.findoutgoing(repo, other)
539 o = discovery.findoutgoing(repo, other)
526 repo.ui.popbuffer()
540 repo.ui.popbuffer()
527 cl = repo.changelog
541 cl = repo.changelog
528 o = set([cl.rev(r) for r in repo.changelog.nodesbetween(o, revs)[0]])
542 o = set([cl.rev(r) for r in repo.changelog.nodesbetween(o, revs)[0]])
529 return [r for r in subset if r in o]
543 return [r for r in subset if r in o]
530
544
531 def p1(repo, subset, x):
545 def p1(repo, subset, x):
532 """``p1([set])``
546 """``p1([set])``
533 First parent of changesets in set, or the working directory.
547 First parent of changesets in set, or the working directory.
534 """
548 """
535 if x is None:
549 if x is None:
536 p = repo[x].p1().rev()
550 p = repo[x].p1().rev()
537 return [r for r in subset if r == p]
551 return [r for r in subset if r == p]
538
552
539 ps = set()
553 ps = set()
540 cl = repo.changelog
554 cl = repo.changelog
541 for r in getset(repo, range(len(repo)), x):
555 for r in getset(repo, range(len(repo)), x):
542 ps.add(cl.parentrevs(r)[0])
556 ps.add(cl.parentrevs(r)[0])
543 return [r for r in subset if r in ps]
557 return [r for r in subset if r in ps]
544
558
545 def p2(repo, subset, x):
559 def p2(repo, subset, x):
546 """``p2([set])``
560 """``p2([set])``
547 Second parent of changesets in set, or the working directory.
561 Second parent of changesets in set, or the working directory.
548 """
562 """
549 if x is None:
563 if x is None:
550 ps = repo[x].parents()
564 ps = repo[x].parents()
551 try:
565 try:
552 p = ps[1].rev()
566 p = ps[1].rev()
553 return [r for r in subset if r == p]
567 return [r for r in subset if r == p]
554 except IndexError:
568 except IndexError:
555 return []
569 return []
556
570
557 ps = set()
571 ps = set()
558 cl = repo.changelog
572 cl = repo.changelog
559 for r in getset(repo, range(len(repo)), x):
573 for r in getset(repo, range(len(repo)), x):
560 ps.add(cl.parentrevs(r)[1])
574 ps.add(cl.parentrevs(r)[1])
561 return [r for r in subset if r in ps]
575 return [r for r in subset if r in ps]
562
576
563 def parents(repo, subset, x):
577 def parents(repo, subset, x):
564 """``parents([set])``
578 """``parents([set])``
565 The set of all parents for all changesets in set, or the working directory.
579 The set of all parents for all changesets in set, or the working directory.
566 """
580 """
567 if x is None:
581 if x is None:
568 ps = tuple(p.rev() for p in repo[x].parents())
582 ps = tuple(p.rev() for p in repo[x].parents())
569 return [r for r in subset if r in ps]
583 return [r for r in subset if r in ps]
570
584
571 ps = set()
585 ps = set()
572 cl = repo.changelog
586 cl = repo.changelog
573 for r in getset(repo, range(len(repo)), x):
587 for r in getset(repo, range(len(repo)), x):
574 ps.update(cl.parentrevs(r))
588 ps.update(cl.parentrevs(r))
575 return [r for r in subset if r in ps]
589 return [r for r in subset if r in ps]
576
590
577 def present(repo, subset, x):
591 def present(repo, subset, x):
578 """``present(set)``
592 """``present(set)``
579 An empty set, if any revision in set isn't found; otherwise,
593 An empty set, if any revision in set isn't found; otherwise,
580 all revisions in set.
594 all revisions in set.
581 """
595 """
582 try:
596 try:
583 return getset(repo, subset, x)
597 return getset(repo, subset, x)
584 except error.RepoLookupError:
598 except error.RepoLookupError:
585 return []
599 return []
586
600
587 def removes(repo, subset, x):
601 def removes(repo, subset, x):
588 """``removes(pattern)``
602 """``removes(pattern)``
589 Changesets which remove files matching pattern.
603 Changesets which remove files matching pattern.
590 """
604 """
591 # i18n: "removes" is a keyword
605 # i18n: "removes" is a keyword
592 pat = getstring(x, _("removes requires a pattern"))
606 pat = getstring(x, _("removes requires a pattern"))
593 return checkstatus(repo, subset, pat, 2)
607 return checkstatus(repo, subset, pat, 2)
594
608
595 def rev(repo, subset, x):
609 def rev(repo, subset, x):
596 """``rev(number)``
610 """``rev(number)``
597 Revision with the given numeric identifier.
611 Revision with the given numeric identifier.
598 """
612 """
599 # i18n: "rev" is a keyword
613 # i18n: "rev" is a keyword
600 l = getargs(x, 1, 1, _("rev requires one argument"))
614 l = getargs(x, 1, 1, _("rev requires one argument"))
601 try:
615 try:
602 # i18n: "rev" is a keyword
616 # i18n: "rev" is a keyword
603 l = int(getstring(l[0], _("rev requires a number")))
617 l = int(getstring(l[0], _("rev requires a number")))
604 except ValueError:
618 except ValueError:
605 # i18n: "rev" is a keyword
619 # i18n: "rev" is a keyword
606 raise error.ParseError(_("rev expects a number"))
620 raise error.ParseError(_("rev expects a number"))
607 return [r for r in subset if r == l]
621 return [r for r in subset if r == l]
608
622
609 def reverse(repo, subset, x):
623 def reverse(repo, subset, x):
610 """``reverse(set)``
624 """``reverse(set)``
611 Reverse order of set.
625 Reverse order of set.
612 """
626 """
613 l = getset(repo, subset, x)
627 l = getset(repo, subset, x)
614 l.reverse()
628 l.reverse()
615 return l
629 return l
616
630
617 def roots(repo, subset, x):
631 def roots(repo, subset, x):
618 """``roots(set)``
632 """``roots(set)``
619 Changesets with no parent changeset in set.
633 Changesets with no parent changeset in set.
620 """
634 """
621 s = getset(repo, subset, x)
635 s = getset(repo, subset, x)
622 cs = set(children(repo, subset, x))
636 cs = set(children(repo, subset, x))
623 return [r for r in s if r not in cs]
637 return [r for r in s if r not in cs]
624
638
625 def sort(repo, subset, x):
639 def sort(repo, subset, x):
626 """``sort(set[, [-]key...])``
640 """``sort(set[, [-]key...])``
627 Sort set by keys. The default sort order is ascending, specify a key
641 Sort set by keys. The default sort order is ascending, specify a key
628 as ``-key`` to sort in descending order.
642 as ``-key`` to sort in descending order.
629
643
630 The keys can be:
644 The keys can be:
631
645
632 - ``rev`` for the revision number,
646 - ``rev`` for the revision number,
633 - ``branch`` for the branch name,
647 - ``branch`` for the branch name,
634 - ``desc`` for the commit message (description),
648 - ``desc`` for the commit message (description),
635 - ``user`` for user name (``author`` can be used as an alias),
649 - ``user`` for user name (``author`` can be used as an alias),
636 - ``date`` for the commit date
650 - ``date`` for the commit date
637 """
651 """
638 # i18n: "sort" is a keyword
652 # i18n: "sort" is a keyword
639 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
653 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
640 keys = "rev"
654 keys = "rev"
641 if len(l) == 2:
655 if len(l) == 2:
642 keys = getstring(l[1], _("sort spec must be a string"))
656 keys = getstring(l[1], _("sort spec must be a string"))
643
657
644 s = l[0]
658 s = l[0]
645 keys = keys.split()
659 keys = keys.split()
646 l = []
660 l = []
647 def invert(s):
661 def invert(s):
648 return "".join(chr(255 - ord(c)) for c in s)
662 return "".join(chr(255 - ord(c)) for c in s)
649 for r in getset(repo, subset, s):
663 for r in getset(repo, subset, s):
650 c = repo[r]
664 c = repo[r]
651 e = []
665 e = []
652 for k in keys:
666 for k in keys:
653 if k == 'rev':
667 if k == 'rev':
654 e.append(r)
668 e.append(r)
655 elif k == '-rev':
669 elif k == '-rev':
656 e.append(-r)
670 e.append(-r)
657 elif k == 'branch':
671 elif k == 'branch':
658 e.append(c.branch())
672 e.append(c.branch())
659 elif k == '-branch':
673 elif k == '-branch':
660 e.append(invert(c.branch()))
674 e.append(invert(c.branch()))
661 elif k == 'desc':
675 elif k == 'desc':
662 e.append(c.description())
676 e.append(c.description())
663 elif k == '-desc':
677 elif k == '-desc':
664 e.append(invert(c.description()))
678 e.append(invert(c.description()))
665 elif k in 'user author':
679 elif k in 'user author':
666 e.append(c.user())
680 e.append(c.user())
667 elif k in '-user -author':
681 elif k in '-user -author':
668 e.append(invert(c.user()))
682 e.append(invert(c.user()))
669 elif k == 'date':
683 elif k == 'date':
670 e.append(c.date()[0])
684 e.append(c.date()[0])
671 elif k == '-date':
685 elif k == '-date':
672 e.append(-c.date()[0])
686 e.append(-c.date()[0])
673 else:
687 else:
674 raise error.ParseError(_("unknown sort key %r") % k)
688 raise error.ParseError(_("unknown sort key %r") % k)
675 e.append(r)
689 e.append(r)
676 l.append(e)
690 l.append(e)
677 l.sort()
691 l.sort()
678 return [e[-1] for e in l]
692 return [e[-1] for e in l]
679
693
680 def tag(repo, subset, x):
694 def tag(repo, subset, x):
681 """``tag(name)``
695 """``tag(name)``
682 The specified tag by name, or all tagged revisions if no name is given.
696 The specified tag by name, or all tagged revisions if no name is given.
683 """
697 """
684 # i18n: "tag" is a keyword
698 # i18n: "tag" is a keyword
685 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
699 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
686 cl = repo.changelog
700 cl = repo.changelog
687 if args:
701 if args:
688 tn = getstring(args[0],
702 tn = getstring(args[0],
689 # i18n: "tag" is a keyword
703 # i18n: "tag" is a keyword
690 _('the argument to tag must be a string'))
704 _('the argument to tag must be a string'))
691 if not repo.tags().get(tn, None):
705 if not repo.tags().get(tn, None):
692 raise util.Abort(_("tag '%s' does not exist") % tn)
706 raise util.Abort(_("tag '%s' does not exist") % tn)
693 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
707 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
694 else:
708 else:
695 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
709 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
696 return [r for r in subset if r in s]
710 return [r for r in subset if r in s]
697
711
698 def tagged(repo, subset, x):
712 def tagged(repo, subset, x):
699 return tag(repo, subset, x)
713 return tag(repo, subset, x)
700
714
701 def user(repo, subset, x):
715 def user(repo, subset, x):
702 """``user(string)``
716 """``user(string)``
703 User name is string.
717 User name is string.
704 """
718 """
705 return author(repo, subset, x)
719 return author(repo, subset, x)
706
720
707 symbols = {
721 symbols = {
708 "adds": adds,
722 "adds": adds,
709 "all": getall,
723 "all": getall,
710 "ancestor": ancestor,
724 "ancestor": ancestor,
711 "ancestors": ancestors,
725 "ancestors": ancestors,
712 "author": author,
726 "author": author,
713 "bisected": bisected,
727 "bisected": bisected,
714 "bookmark": bookmark,
728 "bookmark": bookmark,
715 "branch": branch,
729 "branch": branch,
716 "children": children,
730 "children": children,
717 "closed": closed,
731 "closed": closed,
718 "contains": contains,
732 "contains": contains,
719 "date": date,
733 "date": date,
720 "descendants": descendants,
734 "descendants": descendants,
721 "file": hasfile,
735 "file": hasfile,
722 "follow": follow,
736 "follow": follow,
723 "grep": grep,
737 "grep": grep,
724 "head": head,
738 "head": head,
725 "heads": heads,
739 "heads": heads,
726 "keyword": keyword,
740 "keyword": keyword,
741 "last": last,
727 "limit": limit,
742 "limit": limit,
728 "max": maxrev,
743 "max": maxrev,
729 "min": minrev,
744 "min": minrev,
730 "merge": merge,
745 "merge": merge,
731 "modifies": modifies,
746 "modifies": modifies,
732 "id": node,
747 "id": node,
733 "outgoing": outgoing,
748 "outgoing": outgoing,
734 "p1": p1,
749 "p1": p1,
735 "p2": p2,
750 "p2": p2,
736 "parents": parents,
751 "parents": parents,
737 "present": present,
752 "present": present,
738 "removes": removes,
753 "removes": removes,
739 "reverse": reverse,
754 "reverse": reverse,
740 "rev": rev,
755 "rev": rev,
741 "roots": roots,
756 "roots": roots,
742 "sort": sort,
757 "sort": sort,
743 "tag": tag,
758 "tag": tag,
744 "tagged": tagged,
759 "tagged": tagged,
745 "user": user,
760 "user": user,
746 }
761 }
747
762
748 methods = {
763 methods = {
749 "range": rangeset,
764 "range": rangeset,
750 "string": stringset,
765 "string": stringset,
751 "symbol": symbolset,
766 "symbol": symbolset,
752 "and": andset,
767 "and": andset,
753 "or": orset,
768 "or": orset,
754 "not": notset,
769 "not": notset,
755 "list": listset,
770 "list": listset,
756 "func": func,
771 "func": func,
757 }
772 }
758
773
759 def optimize(x, small):
774 def optimize(x, small):
760 if x is None:
775 if x is None:
761 return 0, x
776 return 0, x
762
777
763 smallbonus = 1
778 smallbonus = 1
764 if small:
779 if small:
765 smallbonus = .5
780 smallbonus = .5
766
781
767 op = x[0]
782 op = x[0]
768 if op == 'minus':
783 if op == 'minus':
769 return optimize(('and', x[1], ('not', x[2])), small)
784 return optimize(('and', x[1], ('not', x[2])), small)
770 elif op == 'dagrange':
785 elif op == 'dagrange':
771 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
786 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
772 ('func', ('symbol', 'ancestors'), x[2])), small)
787 ('func', ('symbol', 'ancestors'), x[2])), small)
773 elif op == 'dagrangepre':
788 elif op == 'dagrangepre':
774 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
789 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
775 elif op == 'dagrangepost':
790 elif op == 'dagrangepost':
776 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
791 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
777 elif op == 'rangepre':
792 elif op == 'rangepre':
778 return optimize(('range', ('string', '0'), x[1]), small)
793 return optimize(('range', ('string', '0'), x[1]), small)
779 elif op == 'rangepost':
794 elif op == 'rangepost':
780 return optimize(('range', x[1], ('string', 'tip')), small)
795 return optimize(('range', x[1], ('string', 'tip')), small)
781 elif op == 'negate':
796 elif op == 'negate':
782 return optimize(('string',
797 return optimize(('string',
783 '-' + getstring(x[1], _("can't negate that"))), small)
798 '-' + getstring(x[1], _("can't negate that"))), small)
784 elif op in 'string symbol negate':
799 elif op in 'string symbol negate':
785 return smallbonus, x # single revisions are small
800 return smallbonus, x # single revisions are small
786 elif op == 'and' or op == 'dagrange':
801 elif op == 'and' or op == 'dagrange':
787 wa, ta = optimize(x[1], True)
802 wa, ta = optimize(x[1], True)
788 wb, tb = optimize(x[2], True)
803 wb, tb = optimize(x[2], True)
789 w = min(wa, wb)
804 w = min(wa, wb)
790 if wa > wb:
805 if wa > wb:
791 return w, (op, tb, ta)
806 return w, (op, tb, ta)
792 return w, (op, ta, tb)
807 return w, (op, ta, tb)
793 elif op == 'or':
808 elif op == 'or':
794 wa, ta = optimize(x[1], False)
809 wa, ta = optimize(x[1], False)
795 wb, tb = optimize(x[2], False)
810 wb, tb = optimize(x[2], False)
796 if wb < wa:
811 if wb < wa:
797 wb, wa = wa, wb
812 wb, wa = wa, wb
798 return max(wa, wb), (op, ta, tb)
813 return max(wa, wb), (op, ta, tb)
799 elif op == 'not':
814 elif op == 'not':
800 o = optimize(x[1], not small)
815 o = optimize(x[1], not small)
801 return o[0], (op, o[1])
816 return o[0], (op, o[1])
802 elif op == 'group':
817 elif op == 'group':
803 return optimize(x[1], small)
818 return optimize(x[1], small)
804 elif op in 'range list':
819 elif op in 'range list':
805 wa, ta = optimize(x[1], small)
820 wa, ta = optimize(x[1], small)
806 wb, tb = optimize(x[2], small)
821 wb, tb = optimize(x[2], small)
807 return wa + wb, (op, ta, tb)
822 return wa + wb, (op, ta, tb)
808 elif op == 'func':
823 elif op == 'func':
809 f = getstring(x[1], _("not a symbol"))
824 f = getstring(x[1], _("not a symbol"))
810 wa, ta = optimize(x[2], small)
825 wa, ta = optimize(x[2], small)
811 if f in "grep date user author keyword branch file outgoing closed":
826 if f in "grep date user author keyword branch file outgoing closed":
812 w = 10 # slow
827 w = 10 # slow
813 elif f in "modifies adds removes":
828 elif f in "modifies adds removes":
814 w = 30 # slower
829 w = 30 # slower
815 elif f == "contains":
830 elif f == "contains":
816 w = 100 # very slow
831 w = 100 # very slow
817 elif f == "ancestor":
832 elif f == "ancestor":
818 w = 1 * smallbonus
833 w = 1 * smallbonus
819 elif f in "reverse limit":
834 elif f in "reverse limit":
820 w = 0
835 w = 0
821 elif f in "sort":
836 elif f in "sort":
822 w = 10 # assume most sorts look at changelog
837 w = 10 # assume most sorts look at changelog
823 else:
838 else:
824 w = 1
839 w = 1
825 return w + wa, (op, x[1], ta)
840 return w + wa, (op, x[1], ta)
826 return 1, x
841 return 1, x
827
842
828 parse = parser.parser(tokenize, elements).parse
843 parse = parser.parser(tokenize, elements).parse
829
844
830 def match(spec):
845 def match(spec):
831 if not spec:
846 if not spec:
832 raise error.ParseError(_("empty query"))
847 raise error.ParseError(_("empty query"))
833 tree, pos = parse(spec)
848 tree, pos = parse(spec)
834 if (pos != len(spec)):
849 if (pos != len(spec)):
835 raise error.ParseError("invalid token", pos)
850 raise error.ParseError("invalid token", pos)
836 weight, tree = optimize(tree, True)
851 weight, tree = optimize(tree, True)
837 def mfunc(repo, subset):
852 def mfunc(repo, subset):
838 return getset(repo, subset, tree)
853 return getset(repo, subset, tree)
839 return mfunc
854 return mfunc
840
855
841 def makedoc(topic, doc):
856 def makedoc(topic, doc):
842 return help.makeitemsdoc(topic, doc, '.. predicatesmarker', symbols)
857 return help.makeitemsdoc(topic, doc, '.. predicatesmarker', symbols)
843
858
844 # tell hggettext to extract docstrings from these functions:
859 # tell hggettext to extract docstrings from these functions:
845 i18nfunctions = symbols.values()
860 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now