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