##// END OF EJS Templates
filesets: introduce basic fileset expression parser
Matt Mackall -
r14511:30506b89 default
parent child Browse files
Show More
@@ -13,7 +13,7 import hg, scmutil, util, revlog, extens
13 13 import patch, help, url, encoding, templatekw, discovery
14 14 import archival, changegroup, cmdutil, sshserver, hbisect, hgweb, hgweb.server
15 15 import merge as mergemod
16 import minirst, revset
16 import minirst, revset, fileset
17 17 import dagparser, context, simplemerge
18 18 import random, setdiscovery, treediscovery, dagutil
19 19
@@ -1597,6 +1597,13 def debugdiscovery(ui, repo, remoteurl="
1597 1597 localrevs = opts.get('local_head')
1598 1598 doit(localrevs, remoterevs)
1599 1599
1600 @command('debugfileset', [], ('REVSPEC'))
1601 def debugfileset(ui, repo, expr):
1602 '''parse and apply a fileset specification'''
1603 if ui.verbose:
1604 tree = fileset.parse(expr)[0]
1605 ui.note(tree, "\n")
1606
1600 1607 @command('debugfsinfo', [], _('[PATH]'))
1601 1608 def debugfsinfo(ui, path = "."):
1602 1609 """show information detected about current filesystem"""
This diff has been collapsed as it changes many lines, (960 lines changed) Show them Hide them
@@ -1,26 +1,16
1 # revset.py - revision set queries for mercurial
1 # fileset.py - file set queries for mercurial
2 2 #
3 3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 import re
9 import parser, util, error, discovery, hbisect
10 import bookmarks as bookmarksmod
11 import match as matchmod
8 import parser, error
12 9 from i18n import _
13 10
14 11 elements = {
15 12 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
16 "~": (18, None, ("ancestor", 18)),
17 "^": (18, None, ("parent", 18), ("parentpost", 18)),
18 13 "-": (5, ("negate", 19), ("minus", 5)),
19 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
20 ("dagrangepost", 17)),
21 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
22 ("dagrangepost", 17)),
23 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
24 14 "not": (10, ("not", 10)),
25 15 "!": (10, ("not", 10)),
26 16 "and": (5, None, ("and", 5)),
@@ -43,13 +33,7 def tokenize(program):
43 33 c = program[pos]
44 34 if c.isspace(): # skip inter-token whitespace
45 35 pass
46 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
47 yield ('::', None, pos)
48 pos += 1 # skip ahead
49 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
50 yield ('..', None, pos)
51 pos += 1 # skip ahead
52 elif c in "():,-|&+!~^": # handle simple operators
36 elif c in "(),-|&+!": # handle simple operators
53 37 yield (c, None, pos)
54 38 elif (c in '"\'' or c == 'r' and
55 39 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
@@ -72,15 +56,12 def tokenize(program):
72 56 pos += 1
73 57 else:
74 58 raise error.ParseError(_("unterminated string"), s)
75 elif c.isalnum() or c in '._' or ord(c) > 127: # gather up a symbol/keyword
59 elif c.isalnum() or c in '.*{}[]?' or ord(c) > 127: # gather up a symbol/keyword
76 60 s = pos
77 61 pos += 1
78 62 while pos < l: # find end of symbol
79 63 d = program[pos]
80 if not (d.isalnum() or d in "._" or ord(d) > 127):
81 break
82 if d == '.' and program[pos - 1] == '.': # special case for ..
83 pos -= 1
64 if not (d.isalnum() or d in ".*{}[]?," or ord(d) > 127):
84 65 break
85 66 pos += 1
86 67 sym = program[s:pos]
@@ -94,936 +75,5 def tokenize(program):
94 75 pos += 1
95 76 yield ('end', None, pos)
96 77
97 # helpers
98
99 def getstring(x, err):
100 if x and (x[0] == 'string' or x[0] == 'symbol'):
101 return x[1]
102 raise error.ParseError(err)
103
104 def getlist(x):
105 if not x:
106 return []
107 if x[0] == 'list':
108 return getlist(x[1]) + [x[2]]
109 return [x]
110
111 def getargs(x, min, max, err):
112 l = getlist(x)
113 if len(l) < min or len(l) > max:
114 raise error.ParseError(err)
115 return l
116
117 def getset(repo, subset, x):
118 if not x:
119 raise error.ParseError(_("missing argument"))
120 return methods[x[0]](repo, subset, *x[1:])
121
122 # operator methods
123
124 def stringset(repo, subset, x):
125 x = repo[x].rev()
126 if x == -1 and len(subset) == len(repo):
127 return [-1]
128 if len(subset) == len(repo) or x in subset:
129 return [x]
130 return []
131
132 def symbolset(repo, subset, x):
133 if x in symbols:
134 raise error.ParseError(_("can't use %s here") % x)
135 return stringset(repo, subset, x)
136
137 def rangeset(repo, subset, x, y):
138 m = getset(repo, subset, x)
139 if not m:
140 m = getset(repo, range(len(repo)), x)
141
142 n = getset(repo, subset, y)
143 if not n:
144 n = getset(repo, range(len(repo)), y)
145
146 if not m or not n:
147 return []
148 m, n = m[0], n[-1]
149
150 if m < n:
151 r = range(m, n + 1)
152 else:
153 r = range(m, n - 1, -1)
154 s = set(subset)
155 return [x for x in r if x in s]
156
157 def andset(repo, subset, x, y):
158 return getset(repo, getset(repo, subset, x), y)
159
160 def orset(repo, subset, x, y):
161 xl = getset(repo, subset, x)
162 s = set(xl)
163 yl = getset(repo, [r for r in subset if r not in s], y)
164 return xl + yl
165
166 def notset(repo, subset, x):
167 s = set(getset(repo, subset, x))
168 return [r for r in subset if r not in s]
169
170 def listset(repo, subset, a, b):
171 raise error.ParseError(_("can't use a list in this context"))
172
173 def func(repo, subset, a, b):
174 if a[0] == 'symbol' and a[1] in symbols:
175 return symbols[a[1]](repo, subset, b)
176 raise error.ParseError(_("not a function: %s") % a[1])
177
178 # functions
179
180 def adds(repo, subset, x):
181 """``adds(pattern)``
182 Changesets that add a file matching pattern.
183 """
184 # i18n: "adds" is a keyword
185 pat = getstring(x, _("adds requires a pattern"))
186 return checkstatus(repo, subset, pat, 1)
187
188 def ancestor(repo, subset, x):
189 """``ancestor(single, single)``
190 Greatest common ancestor of the two changesets.
191 """
192 # i18n: "ancestor" is a keyword
193 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
194 r = range(len(repo))
195 a = getset(repo, r, l[0])
196 b = getset(repo, r, l[1])
197 if len(a) != 1 or len(b) != 1:
198 # i18n: "ancestor" is a keyword
199 raise error.ParseError(_("ancestor arguments must be single revisions"))
200 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
201
202 return [r for r in an if r in subset]
203
204 def ancestors(repo, subset, x):
205 """``ancestors(set)``
206 Changesets that are ancestors of a changeset in set.
207 """
208 args = getset(repo, range(len(repo)), x)
209 if not args:
210 return []
211 s = set(repo.changelog.ancestors(*args)) | set(args)
212 return [r for r in subset if r in s]
213
214 def ancestorspec(repo, subset, x, n):
215 """``set~n``
216 Changesets that are the Nth ancestor (first parents only) of a changeset in set.
217 """
218 try:
219 n = int(n[1])
220 except ValueError:
221 raise error.ParseError(_("~ expects a number"))
222 ps = set()
223 cl = repo.changelog
224 for r in getset(repo, subset, x):
225 for i in range(n):
226 r = cl.parentrevs(r)[0]
227 ps.add(r)
228 return [r for r in subset if r in ps]
229
230 def author(repo, subset, x):
231 """``author(string)``
232 Alias for ``user(string)``.
233 """
234 # i18n: "author" is a keyword
235 n = getstring(x, _("author requires a string")).lower()
236 return [r for r in subset if n in repo[r].user().lower()]
237
238 def bisected(repo, subset, x):
239 """``bisected(string)``
240 Changesets marked in the specified bisect state (good, bad, skip).
241 """
242 state = getstring(x, _("bisect requires a string")).lower()
243 if state not in ('good', 'bad', 'skip', 'unknown'):
244 raise error.ParseError(_('invalid bisect state'))
245 marked = set(repo.changelog.rev(n) for n in hbisect.load_state(repo)[state])
246 return [r for r in subset if r in marked]
247
248 def bookmark(repo, subset, x):
249 """``bookmark([name])``
250 The named bookmark or all bookmarks.
251 """
252 # i18n: "bookmark" is a keyword
253 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
254 if args:
255 bm = getstring(args[0],
256 # i18n: "bookmark" is a keyword
257 _('the argument to bookmark must be a string'))
258 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
259 if not bmrev:
260 raise util.Abort(_("bookmark '%s' does not exist") % bm)
261 bmrev = repo[bmrev].rev()
262 return [r for r in subset if r == bmrev]
263 bms = set([repo[r].rev()
264 for r in bookmarksmod.listbookmarks(repo).values()])
265 return [r for r in subset if r in bms]
266
267 def branch(repo, subset, x):
268 """``branch(string or set)``
269 All changesets belonging to the given branch or the branches of the given
270 changesets.
271 """
272 try:
273 b = getstring(x, '')
274 if b in repo.branchmap():
275 return [r for r in subset if repo[r].branch() == b]
276 except error.ParseError:
277 # not a string, but another revspec, e.g. tip()
278 pass
279
280 s = getset(repo, range(len(repo)), x)
281 b = set()
282 for r in s:
283 b.add(repo[r].branch())
284 s = set(s)
285 return [r for r in subset if r in s or repo[r].branch() in b]
286
287 def checkstatus(repo, subset, pat, field):
288 m = matchmod.match(repo.root, repo.getcwd(), [pat])
289 s = []
290 fast = (m.files() == [pat])
291 for r in subset:
292 c = repo[r]
293 if fast:
294 if pat not in c.files():
295 continue
296 else:
297 for f in c.files():
298 if m(f):
299 break
300 else:
301 continue
302 files = repo.status(c.p1().node(), c.node())[field]
303 if fast:
304 if pat in files:
305 s.append(r)
306 else:
307 for f in files:
308 if m(f):
309 s.append(r)
310 break
311 return s
312
313 def children(repo, subset, x):
314 """``children(set)``
315 Child changesets of changesets in set.
316 """
317 cs = set()
318 cl = repo.changelog
319 s = set(getset(repo, range(len(repo)), x))
320 for r in xrange(0, len(repo)):
321 for p in cl.parentrevs(r):
322 if p in s:
323 cs.add(r)
324 return [r for r in subset if r in cs]
325
326 def closed(repo, subset, x):
327 """``closed()``
328 Changeset is closed.
329 """
330 # i18n: "closed" is a keyword
331 getargs(x, 0, 0, _("closed takes no arguments"))
332 return [r for r in subset if repo[r].extra().get('close')]
333
334 def contains(repo, subset, x):
335 """``contains(pattern)``
336 Revision contains a file matching pattern. See :hg:`help patterns`
337 for information about file patterns.
338 """
339 # i18n: "contains" is a keyword
340 pat = getstring(x, _("contains requires a pattern"))
341 m = matchmod.match(repo.root, repo.getcwd(), [pat])
342 s = []
343 if m.files() == [pat]:
344 for r in subset:
345 if pat in repo[r]:
346 s.append(r)
347 else:
348 for r in subset:
349 for f in repo[r].manifest():
350 if m(f):
351 s.append(r)
352 break
353 return s
354
355 def date(repo, subset, x):
356 """``date(interval)``
357 Changesets within the interval, see :hg:`help dates`.
358 """
359 # i18n: "date" is a keyword
360 ds = getstring(x, _("date requires a string"))
361 dm = util.matchdate(ds)
362 return [r for r in subset if dm(repo[r].date()[0])]
363
364 def descendants(repo, subset, x):
365 """``descendants(set)``
366 Changesets which are descendants of changesets in set.
367 """
368 args = getset(repo, range(len(repo)), x)
369 if not args:
370 return []
371 s = set(repo.changelog.descendants(*args)) | set(args)
372 return [r for r in subset if r in s]
373
374 def filelog(repo, subset, x):
375 """``filelog(pattern)``
376 Changesets connected to the specified filelog.
377 """
378
379 pat = getstring(x, _("filelog requires a pattern"))
380 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath')
381 s = set()
382
383 if not m.anypats():
384 for f in m.files():
385 fl = repo.file(f)
386 for fr in fl:
387 s.add(fl.linkrev(fr))
388 else:
389 for f in repo[None]:
390 if m(f):
391 fl = repo.file(f)
392 for fr in fl:
393 s.add(fl.linkrev(fr))
394
395 return [r for r in subset if r in s]
396
397 def follow(repo, subset, x):
398 """``follow([file])``
399 An alias for ``::.`` (ancestors of the working copy's first parent).
400 If a filename is specified, the history of the given file is followed,
401 including copies.
402 """
403 # i18n: "follow" is a keyword
404 l = getargs(x, 0, 1, _("follow takes no arguments or a filename"))
405 p = repo['.'].rev()
406 if l:
407 x = getstring(l[0], "follow expected a filename")
408 s = set(ctx.rev() for ctx in repo['.'][x].ancestors())
409 else:
410 s = set(repo.changelog.ancestors(p))
411
412 s |= set([p])
413 return [r for r in subset if r in s]
414
415 def followfile(repo, subset, f):
416 """``follow()``
417 An alias for ``::.`` (ancestors of the working copy's first parent).
418 """
419 # i18n: "follow" is a keyword
420 getargs(x, 0, 0, _("follow takes no arguments"))
421 p = repo['.'].rev()
422 s = set(repo.changelog.ancestors(p)) | set([p])
423 return [r for r in subset if r in s]
424
425 def getall(repo, subset, x):
426 """``all()``
427 All changesets, the same as ``0:tip``.
428 """
429 # i18n: "all" is a keyword
430 getargs(x, 0, 0, _("all takes no arguments"))
431 return subset
432
433 def grep(repo, subset, x):
434 """``grep(regex)``
435 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
436 to ensure special escape characters are handled correctly. Unlike
437 ``keyword(string)``, the match is case-sensitive.
438 """
439 try:
440 # i18n: "grep" is a keyword
441 gr = re.compile(getstring(x, _("grep requires a string")))
442 except re.error, e:
443 raise error.ParseError(_('invalid match pattern: %s') % e)
444 l = []
445 for r in subset:
446 c = repo[r]
447 for e in c.files() + [c.user(), c.description()]:
448 if gr.search(e):
449 l.append(r)
450 break
451 return l
452
453 def hasfile(repo, subset, x):
454 """``file(pattern)``
455 Changesets affecting files matched by pattern.
456 """
457 # i18n: "file" is a keyword
458 pat = getstring(x, _("file requires a pattern"))
459 m = matchmod.match(repo.root, repo.getcwd(), [pat])
460 s = []
461 for r in subset:
462 for f in repo[r].files():
463 if m(f):
464 s.append(r)
465 break
466 return s
467
468 def head(repo, subset, x):
469 """``head()``
470 Changeset is a named branch head.
471 """
472 # i18n: "head" is a keyword
473 getargs(x, 0, 0, _("head takes no arguments"))
474 hs = set()
475 for b, ls in repo.branchmap().iteritems():
476 hs.update(repo[h].rev() for h in ls)
477 return [r for r in subset if r in hs]
478
479 def heads(repo, subset, x):
480 """``heads(set)``
481 Members of set with no children in set.
482 """
483 s = getset(repo, subset, x)
484 ps = set(parents(repo, subset, x))
485 return [r for r in s if r not in ps]
486
487 def keyword(repo, subset, x):
488 """``keyword(string)``
489 Search commit message, user name, and names of changed files for
490 string. The match is case-insensitive.
491 """
492 # i18n: "keyword" is a keyword
493 kw = getstring(x, _("keyword requires a string")).lower()
494 l = []
495 for r in subset:
496 c = repo[r]
497 t = " ".join(c.files() + [c.user(), c.description()])
498 if kw in t.lower():
499 l.append(r)
500 return l
501
502 def limit(repo, subset, x):
503 """``limit(set, n)``
504 First n members of set.
505 """
506 # i18n: "limit" is a keyword
507 l = getargs(x, 2, 2, _("limit requires two arguments"))
508 try:
509 # i18n: "limit" is a keyword
510 lim = int(getstring(l[1], _("limit requires a number")))
511 except ValueError:
512 # i18n: "limit" is a keyword
513 raise error.ParseError(_("limit expects a number"))
514 ss = set(subset)
515 os = getset(repo, range(len(repo)), l[0])[:lim]
516 return [r for r in os if r in ss]
517
518 def last(repo, subset, x):
519 """``last(set, n)``
520 Last n members of set.
521 """
522 # i18n: "last" is a keyword
523 l = getargs(x, 2, 2, _("last requires two arguments"))
524 try:
525 # i18n: "last" is a keyword
526 lim = int(getstring(l[1], _("last requires a number")))
527 except ValueError:
528 # i18n: "last" is a keyword
529 raise error.ParseError(_("last expects a number"))
530 ss = set(subset)
531 os = getset(repo, range(len(repo)), l[0])[-lim:]
532 return [r for r in os if r in ss]
533
534 def maxrev(repo, subset, x):
535 """``max(set)``
536 Changeset with highest revision number in set.
537 """
538 os = getset(repo, range(len(repo)), x)
539 if os:
540 m = max(os)
541 if m in subset:
542 return [m]
543 return []
544
545 def merge(repo, subset, x):
546 """``merge()``
547 Changeset is a merge changeset.
548 """
549 # i18n: "merge" is a keyword
550 getargs(x, 0, 0, _("merge takes no arguments"))
551 cl = repo.changelog
552 return [r for r in subset if cl.parentrevs(r)[1] != -1]
553
554 def minrev(repo, subset, x):
555 """``min(set)``
556 Changeset with lowest revision number in set.
557 """
558 os = getset(repo, range(len(repo)), x)
559 if os:
560 m = min(os)
561 if m in subset:
562 return [m]
563 return []
564
565 def modifies(repo, subset, x):
566 """``modifies(pattern)``
567 Changesets modifying files matched by pattern.
568 """
569 # i18n: "modifies" is a keyword
570 pat = getstring(x, _("modifies requires a pattern"))
571 return checkstatus(repo, subset, pat, 0)
572
573 def node(repo, subset, x):
574 """``id(string)``
575 Revision non-ambiguously specified by the given hex string prefix.
576 """
577 # i18n: "id" is a keyword
578 l = getargs(x, 1, 1, _("id requires one argument"))
579 # i18n: "id" is a keyword
580 n = getstring(l[0], _("id requires a string"))
581 if len(n) == 40:
582 rn = repo[n].rev()
583 else:
584 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
585 return [r for r in subset if r == rn]
586
587 def outgoing(repo, subset, x):
588 """``outgoing([path])``
589 Changesets not found in the specified destination repository, or the
590 default push location.
591 """
592 import hg # avoid start-up nasties
593 # i18n: "outgoing" is a keyword
594 l = getargs(x, 0, 1, _("outgoing requires a repository path"))
595 # i18n: "outgoing" is a keyword
596 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
597 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
598 dest, branches = hg.parseurl(dest)
599 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
600 if revs:
601 revs = [repo.lookup(rev) for rev in revs]
602 other = hg.repository(hg.remoteui(repo, {}), dest)
603 repo.ui.pushbuffer()
604 common, outheads = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
605 repo.ui.popbuffer()
606 cl = repo.changelog
607 o = set([cl.rev(r) for r in repo.changelog.findmissing(common, outheads)])
608 return [r for r in subset if r in o]
609
610 def p1(repo, subset, x):
611 """``p1([set])``
612 First parent of changesets in set, or the working directory.
613 """
614 if x is None:
615 p = repo[x].p1().rev()
616 return [r for r in subset if r == p]
617
618 ps = set()
619 cl = repo.changelog
620 for r in getset(repo, range(len(repo)), x):
621 ps.add(cl.parentrevs(r)[0])
622 return [r for r in subset if r in ps]
623
624 def p2(repo, subset, x):
625 """``p2([set])``
626 Second parent of changesets in set, or the working directory.
627 """
628 if x is None:
629 ps = repo[x].parents()
630 try:
631 p = ps[1].rev()
632 return [r for r in subset if r == p]
633 except IndexError:
634 return []
635
636 ps = set()
637 cl = repo.changelog
638 for r in getset(repo, range(len(repo)), x):
639 ps.add(cl.parentrevs(r)[1])
640 return [r for r in subset if r in ps]
641
642 def parents(repo, subset, x):
643 """``parents([set])``
644 The set of all parents for all changesets in set, or the working directory.
645 """
646 if x is None:
647 ps = tuple(p.rev() for p in repo[x].parents())
648 return [r for r in subset if r in ps]
649
650 ps = set()
651 cl = repo.changelog
652 for r in getset(repo, range(len(repo)), x):
653 ps.update(cl.parentrevs(r))
654 return [r for r in subset if r in ps]
655
656 def parentspec(repo, subset, x, n):
657 """``set^0``
658 The set.
659 ``set^1`` (or ``set^``), ``set^2``
660 First or second parent, respectively, of all changesets in set.
661 """
662 try:
663 n = int(n[1])
664 if n not in (0, 1, 2):
665 raise ValueError
666 except ValueError:
667 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
668 ps = set()
669 cl = repo.changelog
670 for r in getset(repo, subset, x):
671 if n == 0:
672 ps.add(r)
673 elif n == 1:
674 ps.add(cl.parentrevs(r)[0])
675 elif n == 2:
676 parents = cl.parentrevs(r)
677 if len(parents) > 1:
678 ps.add(parents[1])
679 return [r for r in subset if r in ps]
680
681 def present(repo, subset, x):
682 """``present(set)``
683 An empty set, if any revision in set isn't found; otherwise,
684 all revisions in set.
685 """
686 try:
687 return getset(repo, subset, x)
688 except error.RepoLookupError:
689 return []
690
691 def removes(repo, subset, x):
692 """``removes(pattern)``
693 Changesets which remove files matching pattern.
694 """
695 # i18n: "removes" is a keyword
696 pat = getstring(x, _("removes requires a pattern"))
697 return checkstatus(repo, subset, pat, 2)
698
699 def rev(repo, subset, x):
700 """``rev(number)``
701 Revision with the given numeric identifier.
702 """
703 # i18n: "rev" is a keyword
704 l = getargs(x, 1, 1, _("rev requires one argument"))
705 try:
706 # i18n: "rev" is a keyword
707 l = int(getstring(l[0], _("rev requires a number")))
708 except ValueError:
709 # i18n: "rev" is a keyword
710 raise error.ParseError(_("rev expects a number"))
711 return [r for r in subset if r == l]
712
713 def reverse(repo, subset, x):
714 """``reverse(set)``
715 Reverse order of set.
716 """
717 l = getset(repo, subset, x)
718 l.reverse()
719 return l
720
721 def roots(repo, subset, x):
722 """``roots(set)``
723 Changesets with no parent changeset in set.
724 """
725 s = getset(repo, subset, x)
726 cs = set(children(repo, subset, x))
727 return [r for r in s if r not in cs]
728
729 def sort(repo, subset, x):
730 """``sort(set[, [-]key...])``
731 Sort set by keys. The default sort order is ascending, specify a key
732 as ``-key`` to sort in descending order.
733
734 The keys can be:
735
736 - ``rev`` for the revision number,
737 - ``branch`` for the branch name,
738 - ``desc`` for the commit message (description),
739 - ``user`` for user name (``author`` can be used as an alias),
740 - ``date`` for the commit date
741 """
742 # i18n: "sort" is a keyword
743 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
744 keys = "rev"
745 if len(l) == 2:
746 keys = getstring(l[1], _("sort spec must be a string"))
747
748 s = l[0]
749 keys = keys.split()
750 l = []
751 def invert(s):
752 return "".join(chr(255 - ord(c)) for c in s)
753 for r in getset(repo, subset, s):
754 c = repo[r]
755 e = []
756 for k in keys:
757 if k == 'rev':
758 e.append(r)
759 elif k == '-rev':
760 e.append(-r)
761 elif k == 'branch':
762 e.append(c.branch())
763 elif k == '-branch':
764 e.append(invert(c.branch()))
765 elif k == 'desc':
766 e.append(c.description())
767 elif k == '-desc':
768 e.append(invert(c.description()))
769 elif k in 'user author':
770 e.append(c.user())
771 elif k in '-user -author':
772 e.append(invert(c.user()))
773 elif k == 'date':
774 e.append(c.date()[0])
775 elif k == '-date':
776 e.append(-c.date()[0])
777 else:
778 raise error.ParseError(_("unknown sort key %r") % k)
779 e.append(r)
780 l.append(e)
781 l.sort()
782 return [e[-1] for e in l]
783
784 def tag(repo, subset, x):
785 """``tag([name])``
786 The specified tag by name, or all tagged revisions if no name is given.
787 """
788 # i18n: "tag" is a keyword
789 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
790 cl = repo.changelog
791 if args:
792 tn = getstring(args[0],
793 # i18n: "tag" is a keyword
794 _('the argument to tag must be a string'))
795 if not repo.tags().get(tn, None):
796 raise util.Abort(_("tag '%s' does not exist") % tn)
797 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
798 else:
799 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
800 return [r for r in subset if r in s]
801
802 def tagged(repo, subset, x):
803 return tag(repo, subset, x)
804
805 def user(repo, subset, x):
806 """``user(string)``
807 User name contains string. The match is case-insensitive.
808 """
809 return author(repo, subset, x)
810
811 symbols = {
812 "adds": adds,
813 "all": getall,
814 "ancestor": ancestor,
815 "ancestors": ancestors,
816 "author": author,
817 "bisected": bisected,
818 "bookmark": bookmark,
819 "branch": branch,
820 "children": children,
821 "closed": closed,
822 "contains": contains,
823 "date": date,
824 "descendants": descendants,
825 "file": hasfile,
826 "filelog": filelog,
827 "follow": follow,
828 "grep": grep,
829 "head": head,
830 "heads": heads,
831 "keyword": keyword,
832 "last": last,
833 "limit": limit,
834 "max": maxrev,
835 "min": minrev,
836 "merge": merge,
837 "modifies": modifies,
838 "id": node,
839 "outgoing": outgoing,
840 "p1": p1,
841 "p2": p2,
842 "parents": parents,
843 "present": present,
844 "removes": removes,
845 "reverse": reverse,
846 "rev": rev,
847 "roots": roots,
848 "sort": sort,
849 "tag": tag,
850 "tagged": tagged,
851 "user": user,
852 }
853
854 methods = {
855 "range": rangeset,
856 "string": stringset,
857 "symbol": symbolset,
858 "and": andset,
859 "or": orset,
860 "not": notset,
861 "list": listset,
862 "func": func,
863 "ancestor": ancestorspec,
864 "parent": parentspec,
865 "parentpost": p1,
866 }
867
868 def optimize(x, small):
869 if x is None:
870 return 0, x
871
872 smallbonus = 1
873 if small:
874 smallbonus = .5
875
876 op = x[0]
877 if op == 'minus':
878 return optimize(('and', x[1], ('not', x[2])), small)
879 elif op == 'dagrange':
880 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
881 ('func', ('symbol', 'ancestors'), x[2])), small)
882 elif op == 'dagrangepre':
883 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
884 elif op == 'dagrangepost':
885 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
886 elif op == 'rangepre':
887 return optimize(('range', ('string', '0'), x[1]), small)
888 elif op == 'rangepost':
889 return optimize(('range', x[1], ('string', 'tip')), small)
890 elif op == 'negate':
891 return optimize(('string',
892 '-' + getstring(x[1], _("can't negate that"))), small)
893 elif op in 'string symbol negate':
894 return smallbonus, x # single revisions are small
895 elif op == 'and' or op == 'dagrange':
896 wa, ta = optimize(x[1], True)
897 wb, tb = optimize(x[2], True)
898 w = min(wa, wb)
899 if wa > wb:
900 return w, (op, tb, ta)
901 return w, (op, ta, tb)
902 elif op == 'or':
903 wa, ta = optimize(x[1], False)
904 wb, tb = optimize(x[2], False)
905 if wb < wa:
906 wb, wa = wa, wb
907 return max(wa, wb), (op, ta, tb)
908 elif op == 'not':
909 o = optimize(x[1], not small)
910 return o[0], (op, o[1])
911 elif op == 'parentpost':
912 o = optimize(x[1], small)
913 return o[0], (op, o[1])
914 elif op == 'group':
915 return optimize(x[1], small)
916 elif op in 'range list parent ancestorspec':
917 wa, ta = optimize(x[1], small)
918 wb, tb = optimize(x[2], small)
919 return wa + wb, (op, ta, tb)
920 elif op == 'func':
921 f = getstring(x[1], _("not a symbol"))
922 wa, ta = optimize(x[2], small)
923 if f in "grep date user author keyword branch file outgoing closed":
924 w = 10 # slow
925 elif f in "modifies adds removes":
926 w = 30 # slower
927 elif f == "contains":
928 w = 100 # very slow
929 elif f == "ancestor":
930 w = 1 * smallbonus
931 elif f in "reverse limit":
932 w = 0
933 elif f in "sort":
934 w = 10 # assume most sorts look at changelog
935 else:
936 w = 1
937 return w + wa, (op, x[1], ta)
938 return 1, x
939
940 class revsetalias(object):
941 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
942 args = ()
943
944 def __init__(self, token, value):
945 '''Aliases like:
946
947 h = heads(default)
948 b($1) = ancestors($1) - ancestors(default)
949 '''
950 if isinstance(token, tuple):
951 self.type, self.name = token
952 else:
953 m = self.funcre.search(token)
954 if m:
955 self.type = 'func'
956 self.name = m.group(1)
957 self.args = [x.strip() for x in m.group(2).split(',')]
958 else:
959 self.type = 'symbol'
960 self.name = token
961
962 if isinstance(value, str):
963 for arg in self.args:
964 value = value.replace(arg, repr(arg))
965 self.replacement, pos = parse(value)
966 if pos != len(value):
967 raise error.ParseError('invalid token', pos)
968 else:
969 self.replacement = value
970
971 def match(self, tree):
972 if not tree:
973 return False
974 if tree == (self.type, self.name):
975 return True
976 if tree[0] != self.type:
977 return False
978 if len(tree) > 1 and tree[1] != ('symbol', self.name):
979 return False
980 # 'func' + funcname + args
981 if ((self.args and len(tree) != 3) or
982 (len(self.args) == 1 and tree[2][0] == 'list') or
983 (len(self.args) > 1 and (tree[2][0] != 'list' or
984 len(tree[2]) - 1 != len(self.args)))):
985 raise error.ParseError('invalid amount of arguments', len(tree) - 2)
986 return True
987
988 def replace(self, tree):
989 if tree == (self.type, self.name):
990 return self.replacement
991 result = self.replacement
992 def getsubtree(i):
993 if tree[2][0] == 'list':
994 return tree[2][i + 1]
995 return tree[i + 2]
996 for i, v in enumerate(self.args):
997 valalias = revsetalias(('string', v), getsubtree(i))
998 result = valalias.process(result)
999 return result
1000
1001 def process(self, tree):
1002 if self.match(tree):
1003 return self.replace(tree)
1004 if isinstance(tree, tuple):
1005 return tuple(map(self.process, tree))
1006 return tree
1007
1008 def findaliases(ui, tree):
1009 for k, v in ui.configitems('revsetalias'):
1010 alias = revsetalias(k, v)
1011 tree = alias.process(tree)
1012 return tree
1013
1014 78 parse = parser.parser(tokenize, elements).parse
1015 79
1016 def match(ui, spec):
1017 if not spec:
1018 raise error.ParseError(_("empty query"))
1019 tree, pos = parse(spec)
1020 if (pos != len(spec)):
1021 raise error.ParseError("invalid token", pos)
1022 tree = findaliases(ui, tree)
1023 weight, tree = optimize(tree, True)
1024 def mfunc(repo, subset):
1025 return getset(repo, subset, tree)
1026 return mfunc
1027
1028 # tell hggettext to extract docstrings from these functions:
1029 i18nfunctions = symbols.values()
@@ -76,6 +76,7 Show debug commands if there are no othe
76 76 debugdata
77 77 debugdate
78 78 debugdiscovery
79 debugfileset
79 80 debugfsinfo
80 81 debuggetbundle
81 82 debugignore
@@ -222,6 +223,7 Show all commands + options
222 223 debugdata: changelog, manifest
223 224 debugdate: extended
224 225 debugdiscovery: old, nonheads, ssh, remotecmd, insecure
226 debugfileset:
225 227 debugfsinfo:
226 228 debuggetbundle: head, common, type
227 229 debugignore:
General Comments 0
You need to be logged in to leave comments. Login now