revset.py
814 lines
| 23.5 KiB
| text/x-python
|
PythonLexer
/ mercurial / revset.py
Matt Mackall
|
r11275 | # revset.py - revision set queries for mercurial | ||
# | ||||
# Copyright 2010 Matt Mackall <mpm@selenic.com> | ||||
# | ||||
# This software may be used and distributed according to the terms of the | ||||
# GNU General Public License version 2 or any later version. | ||||
import re | ||||
Dirkjan Ochtman
|
r11301 | import parser, util, error, discovery | ||
Martin Geisler
|
r12085 | import match as matchmod | ||
Wagner Bruna
|
r12855 | from i18n import _, gettext | ||
Matt Mackall
|
r11275 | |||
elements = { | ||||
"(": (20, ("group", 1, ")"), ("func", 1, ")")), | ||||
Matt Mackall
|
r12616 | "-": (5, ("negate", 19), ("minus", 5)), | ||
Matt Mackall
|
r11278 | "::": (17, ("dagrangepre", 17), ("dagrange", 17), | ||
("dagrangepost", 17)), | ||||
"..": (17, ("dagrangepre", 17), ("dagrange", 17), | ||||
("dagrangepost", 17)), | ||||
":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)), | ||||
Matt Mackall
|
r11275 | "not": (10, ("not", 10)), | ||
"!": (10, ("not", 10)), | ||||
"and": (5, None, ("and", 5)), | ||||
"&": (5, None, ("and", 5)), | ||||
"or": (4, None, ("or", 4)), | ||||
"|": (4, None, ("or", 4)), | ||||
"+": (4, None, ("or", 4)), | ||||
",": (2, None, ("list", 2)), | ||||
")": (0, None, None), | ||||
"symbol": (0, ("symbol",), None), | ||||
"string": (0, ("string",), None), | ||||
"end": (0, None, None), | ||||
} | ||||
keywords = set(['and', 'or', 'not']) | ||||
def tokenize(program): | ||||
pos, l = 0, len(program) | ||||
while pos < l: | ||||
c = program[pos] | ||||
if c.isspace(): # skip inter-token whitespace | ||||
pass | ||||
Matt Mackall
|
r11278 | elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully | ||
Matt Mackall
|
r11289 | yield ('::', None, pos) | ||
Matt Mackall
|
r11278 | pos += 1 # skip ahead | ||
Matt Mackall
|
r11275 | elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully | ||
Matt Mackall
|
r11289 | yield ('..', None, pos) | ||
Matt Mackall
|
r11275 | pos += 1 # skip ahead | ||
Matt Mackall
|
r11278 | elif c in "():,-|&+!": # handle simple operators | ||
Matt Mackall
|
r11289 | yield (c, None, pos) | ||
Brodie Rao
|
r12408 | elif (c in '"\'' or c == 'r' and | ||
program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings | ||||
if c == 'r': | ||||
pos += 1 | ||||
c = program[pos] | ||||
decode = lambda x: x | ||||
else: | ||||
decode = lambda x: x.decode('string-escape') | ||||
Matt Mackall
|
r11275 | pos += 1 | ||
s = pos | ||||
while pos < l: # find closing quote | ||||
d = program[pos] | ||||
if d == '\\': # skip over escaped characters | ||||
pos += 2 | ||||
continue | ||||
if d == c: | ||||
Brodie Rao
|
r12408 | yield ('string', decode(program[s:pos]), s) | ||
Matt Mackall
|
r11275 | break | ||
pos += 1 | ||||
else: | ||||
Martin Geisler
|
r11383 | raise error.ParseError(_("unterminated string"), s) | ||
Matt Mackall
|
r11404 | elif c.isalnum() or c in '._' or ord(c) > 127: # gather up a symbol/keyword | ||
Matt Mackall
|
r11275 | s = pos | ||
pos += 1 | ||||
while pos < l: # find end of symbol | ||||
d = program[pos] | ||||
Matt Mackall
|
r11404 | if not (d.isalnum() or d in "._" or ord(d) > 127): | ||
Matt Mackall
|
r11275 | break | ||
if d == '.' and program[pos - 1] == '.': # special case for .. | ||||
pos -= 1 | ||||
break | ||||
pos += 1 | ||||
sym = program[s:pos] | ||||
if sym in keywords: # operator keywords | ||||
Matt Mackall
|
r11289 | yield (sym, None, s) | ||
Matt Mackall
|
r11275 | else: | ||
Matt Mackall
|
r11289 | yield ('symbol', sym, s) | ||
Matt Mackall
|
r11275 | pos -= 1 | ||
else: | ||||
Martin Geisler
|
r11383 | raise error.ParseError(_("syntax error"), pos) | ||
Matt Mackall
|
r11275 | pos += 1 | ||
Matt Mackall
|
r11289 | yield ('end', None, pos) | ||
Matt Mackall
|
r11275 | |||
# helpers | ||||
def getstring(x, err): | ||||
Matt Mackall
|
r11406 | if x and (x[0] == 'string' or x[0] == 'symbol'): | ||
Matt Mackall
|
r11275 | return x[1] | ||
Matt Mackall
|
r11289 | raise error.ParseError(err) | ||
Matt Mackall
|
r11275 | |||
def getlist(x): | ||||
if not x: | ||||
return [] | ||||
if x[0] == 'list': | ||||
return getlist(x[1]) + [x[2]] | ||||
return [x] | ||||
Matt Mackall
|
r11339 | def getargs(x, min, max, err): | ||
Matt Mackall
|
r11275 | l = getlist(x) | ||
Matt Mackall
|
r11339 | if len(l) < min or len(l) > max: | ||
Matt Mackall
|
r11289 | raise error.ParseError(err) | ||
Matt Mackall
|
r11275 | return l | ||
def getset(repo, subset, x): | ||||
if not x: | ||||
Martin Geisler
|
r11383 | raise error.ParseError(_("missing argument")) | ||
Matt Mackall
|
r11275 | return methods[x[0]](repo, subset, *x[1:]) | ||
# operator methods | ||||
def stringset(repo, subset, x): | ||||
x = repo[x].rev() | ||||
Matt Mackall
|
r11282 | if x == -1 and len(subset) == len(repo): | ||
return [-1] | ||||
Matt Mackall
|
r11275 | if x in subset: | ||
return [x] | ||||
return [] | ||||
def symbolset(repo, subset, x): | ||||
if x in symbols: | ||||
Martin Geisler
|
r11383 | raise error.ParseError(_("can't use %s here") % x) | ||
Matt Mackall
|
r11275 | return stringset(repo, subset, x) | ||
def rangeset(repo, subset, x, y): | ||||
Matt Mackall
|
r11456 | m = getset(repo, subset, x) | ||
if not m: | ||||
m = getset(repo, range(len(repo)), x) | ||||
n = getset(repo, subset, y) | ||||
if not n: | ||||
n = getset(repo, range(len(repo)), y) | ||||
if not m or not n: | ||||
return [] | ||||
m, n = m[0], n[-1] | ||||
Matt Mackall
|
r11275 | if m < n: | ||
Matt Mackall
|
r11456 | r = range(m, n + 1) | ||
else: | ||||
r = range(m, n - 1, -1) | ||||
s = set(subset) | ||||
return [x for x in r if x in s] | ||||
Matt Mackall
|
r11275 | |||
def andset(repo, subset, x, y): | ||||
return getset(repo, getset(repo, subset, x), y) | ||||
def orset(repo, subset, x, y): | ||||
s = set(getset(repo, subset, x)) | ||||
s |= set(getset(repo, [r for r in subset if r not in s], y)) | ||||
return [r for r in subset if r in s] | ||||
def notset(repo, subset, x): | ||||
s = set(getset(repo, subset, x)) | ||||
return [r for r in subset if r not in s] | ||||
def listset(repo, subset, a, b): | ||||
Martin Geisler
|
r11383 | raise error.ParseError(_("can't use a list in this context")) | ||
Matt Mackall
|
r11275 | |||
def func(repo, subset, a, b): | ||||
if a[0] == 'symbol' and a[1] in symbols: | ||||
return symbols[a[1]](repo, subset, b) | ||||
Martin Geisler
|
r11383 | raise error.ParseError(_("not a function: %s") % a[1]) | ||
Matt Mackall
|
r11275 | |||
# functions | ||||
Augie Fackler
|
r12716 | def node(repo, subset, x): | ||
Patrick Mezard
|
r12821 | """``id(string)`` | ||
Wagner Bruna
|
r12859 | Revision non-ambiguously specified by the given hex string prefix. | ||
Patrick Mezard
|
r12821 | """ | ||
Martin Geisler
|
r12815 | # i18n: "id" is a keyword | ||
Benoit Boissinot
|
r12736 | l = getargs(x, 1, 1, _("id requires one argument")) | ||
Martin Geisler
|
r12815 | # i18n: "id" is a keyword | ||
Benoit Boissinot
|
r12736 | n = getstring(l[0], _("id requires a string")) | ||
Augie Fackler
|
r12716 | if len(n) == 40: | ||
rn = repo[n].rev() | ||||
else: | ||||
rn = repo.changelog.rev(repo.changelog._partialmatch(n)) | ||||
return [r for r in subset if r == rn] | ||||
def rev(repo, subset, x): | ||||
Patrick Mezard
|
r12821 | """``rev(number)`` | ||
Revision with the given numeric identifier. | ||||
""" | ||||
Martin Geisler
|
r12815 | # i18n: "rev" is a keyword | ||
Benoit Boissinot
|
r12736 | l = getargs(x, 1, 1, _("rev requires one argument")) | ||
Augie Fackler
|
r12716 | try: | ||
Martin Geisler
|
r12815 | # i18n: "rev" is a keyword | ||
Benoit Boissinot
|
r12736 | l = int(getstring(l[0], _("rev requires a number"))) | ||
Augie Fackler
|
r12716 | except ValueError: | ||
Martin Geisler
|
r12815 | # i18n: "rev" is a keyword | ||
Augie Fackler
|
r12716 | raise error.ParseError(_("rev expects a number")) | ||
return [r for r in subset if r == l] | ||||
Matt Mackall
|
r11275 | def p1(repo, subset, x): | ||
Kevin Bullock
|
r12928 | """``p1([set])`` | ||
First parent of changesets in set, or the working directory. | ||||
Patrick Mezard
|
r12821 | """ | ||
Kevin Bullock
|
r12928 | if x is None: | ||
Patrick Mezard
|
r12935 | p = repo[x].parents()[0].rev() | ||
return [r for r in subset if r == p] | ||||
Kevin Bullock
|
r12928 | |||
Matt Mackall
|
r11275 | ps = set() | ||
cl = repo.changelog | ||||
Wagner Bruna
|
r12786 | for r in getset(repo, range(len(repo)), x): | ||
Matt Mackall
|
r11275 | ps.add(cl.parentrevs(r)[0]) | ||
return [r for r in subset if r in ps] | ||||
def p2(repo, subset, x): | ||||
Kevin Bullock
|
r12928 | """``p2([set])`` | ||
Second parent of changesets in set, or the working directory. | ||||
Patrick Mezard
|
r12821 | """ | ||
Kevin Bullock
|
r12928 | if x is None: | ||
ps = repo[x].parents() | ||||
try: | ||||
Patrick Mezard
|
r12935 | p = ps[1].rev() | ||
return [r for r in subset if r == p] | ||||
Kevin Bullock
|
r12928 | except IndexError: | ||
return [] | ||||
Matt Mackall
|
r11275 | ps = set() | ||
cl = repo.changelog | ||||
Wagner Bruna
|
r12786 | for r in getset(repo, range(len(repo)), x): | ||
Matt Mackall
|
r11275 | ps.add(cl.parentrevs(r)[1]) | ||
return [r for r in subset if r in ps] | ||||
def parents(repo, subset, x): | ||||
Kevin Bullock
|
r12929 | """``parents([set])`` | ||
The set of all parents for all changesets in set, or the working directory. | ||||
Patrick Mezard
|
r12821 | """ | ||
Kevin Bullock
|
r12929 | repo.ui.debug(repr(x), '\n') | ||
if x is None: | ||||
Patrick Mezard
|
r12935 | ps = tuple(p.rev() for p in repo[x].parents()) | ||
return [r for r in subset if r in ps] | ||||
Kevin Bullock
|
r12929 | |||
Matt Mackall
|
r11275 | ps = set() | ||
cl = repo.changelog | ||||
Wagner Bruna
|
r12786 | for r in getset(repo, range(len(repo)), x): | ||
Matt Mackall
|
r11275 | ps.update(cl.parentrevs(r)) | ||
return [r for r in subset if r in ps] | ||||
def maxrev(repo, subset, x): | ||||
Patrick Mezard
|
r12821 | """``max(set)`` | ||
Changeset with highest revision number in set. | ||||
""" | ||||
Matt Mackall
|
r11275 | s = getset(repo, subset, x) | ||
if s: | ||||
m = max(s) | ||||
if m in subset: | ||||
return [m] | ||||
return [] | ||||
Nicolas Dumazet
|
r11708 | def minrev(repo, subset, x): | ||
Patrick Mezard
|
r12821 | """``min(set)`` | ||
Changeset with lowest revision number in set. | ||||
""" | ||||
Nicolas Dumazet
|
r11708 | s = getset(repo, subset, x) | ||
if s: | ||||
m = min(s) | ||||
if m in subset: | ||||
return [m] | ||||
return [] | ||||
Matt Mackall
|
r11275 | def limit(repo, subset, x): | ||
Patrick Mezard
|
r12821 | """``limit(set, n)`` | ||
First n members of set. | ||||
""" | ||||
Martin Geisler
|
r12815 | # i18n: "limit" is a keyword | ||
Benoit Boissinot
|
r12736 | l = getargs(x, 2, 2, _("limit requires two arguments")) | ||
Matt Mackall
|
r11275 | try: | ||
Martin Geisler
|
r12815 | # i18n: "limit" is a keyword | ||
Benoit Boissinot
|
r12736 | lim = int(getstring(l[1], _("limit requires a number"))) | ||
Matt Mackall
|
r11275 | except ValueError: | ||
Martin Geisler
|
r12815 | # i18n: "limit" is a keyword | ||
Martin Geisler
|
r11383 | raise error.ParseError(_("limit expects a number")) | ||
Matt Mackall
|
r11275 | return getset(repo, subset, l[0])[:lim] | ||
def children(repo, subset, x): | ||||
Patrick Mezard
|
r12821 | """``children(set)`` | ||
Child changesets of changesets in set. | ||||
""" | ||||
Matt Mackall
|
r11275 | cs = set() | ||
cl = repo.changelog | ||||
Wagner Bruna
|
r12786 | s = set(getset(repo, range(len(repo)), x)) | ||
Matt Mackall
|
r11275 | for r in xrange(0, len(repo)): | ||
for p in cl.parentrevs(r): | ||||
if p in s: | ||||
cs.add(r) | ||||
return [r for r in subset if r in cs] | ||||
def branch(repo, subset, x): | ||||
Patrick Mezard
|
r12821 | """``branch(set)`` | ||
All changesets belonging to the branches of changesets in set. | ||||
""" | ||||
Matt Mackall
|
r11275 | s = getset(repo, range(len(repo)), x) | ||
b = set() | ||||
for r in s: | ||||
b.add(repo[r].branch()) | ||||
s = set(s) | ||||
return [r for r in subset if r in s or repo[r].branch() in b] | ||||
def ancestor(repo, subset, x): | ||||
Patrick Mezard
|
r12821 | """``ancestor(single, single)`` | ||
Greatest common ancestor of the two changesets. | ||||
""" | ||||
Martin Geisler
|
r12815 | # i18n: "ancestor" is a keyword | ||
Benoit Boissinot
|
r12736 | l = getargs(x, 2, 2, _("ancestor requires two arguments")) | ||
Matt Mackall
|
r11650 | r = range(len(repo)) | ||
a = getset(repo, r, l[0]) | ||||
b = getset(repo, r, l[1]) | ||||
if len(a) != 1 or len(b) != 1: | ||||
Martin Geisler
|
r12815 | # i18n: "ancestor" is a keyword | ||
Martin Geisler
|
r11383 | raise error.ParseError(_("ancestor arguments must be single revisions")) | ||
Matt Mackall
|
r11650 | an = [repo[a[0]].ancestor(repo[b[0]]).rev()] | ||
return [r for r in an if r in subset] | ||||
Matt Mackall
|
r11275 | |||
def ancestors(repo, subset, x): | ||||
Patrick Mezard
|
r12821 | """``ancestors(set)`` | ||
Changesets that are ancestors of a changeset in set. | ||||
""" | ||||
Matt Mackall
|
r11275 | args = getset(repo, range(len(repo)), x) | ||
Matt Mackall
|
r11456 | if not args: | ||
return [] | ||||
Matt Mackall
|
r11275 | s = set(repo.changelog.ancestors(*args)) | set(args) | ||
return [r for r in subset if r in s] | ||||
def descendants(repo, subset, x): | ||||
Patrick Mezard
|
r12821 | """``descendants(set)`` | ||
Changesets which are descendants of changesets in set. | ||||
""" | ||||
Matt Mackall
|
r11275 | args = getset(repo, range(len(repo)), x) | ||
Matt Mackall
|
r11456 | if not args: | ||
return [] | ||||
Matt Mackall
|
r11275 | s = set(repo.changelog.descendants(*args)) | set(args) | ||
return [r for r in subset if r in s] | ||||
def follow(repo, subset, x): | ||||
Patrick Mezard
|
r12821 | """``follow()`` | ||
An alias for ``::.`` (ancestors of the working copy's first parent). | ||||
""" | ||||
Martin Geisler
|
r12815 | # i18n: "follow" is a keyword | ||
Martin Geisler
|
r11383 | getargs(x, 0, 0, _("follow takes no arguments")) | ||
Matt Mackall
|
r11275 | p = repo['.'].rev() | ||
s = set(repo.changelog.ancestors(p)) | set([p]) | ||||
return [r for r in subset if r in s] | ||||
def date(repo, subset, x): | ||||
Patrick Mezard
|
r12821 | """``date(interval)`` | ||
Changesets within the interval, see :hg:`help dates`. | ||||
""" | ||||
Martin Geisler
|
r12815 | # i18n: "date" is a keyword | ||
Benoit Boissinot
|
r12736 | ds = getstring(x, _("date requires a string")) | ||
Matt Mackall
|
r11275 | dm = util.matchdate(ds) | ||
return [r for r in subset if dm(repo[r].date()[0])] | ||||
def keyword(repo, subset, x): | ||||
Patrick Mezard
|
r12821 | """``keyword(string)`` | ||
Search commit message, user name, and names of changed files for | ||||
string. | ||||
""" | ||||
Martin Geisler
|
r12815 | # i18n: "keyword" is a keyword | ||
Benoit Boissinot
|
r12736 | kw = getstring(x, _("keyword requires a string")).lower() | ||
Matt Mackall
|
r11275 | l = [] | ||
for r in subset: | ||||
c = repo[r] | ||||
t = " ".join(c.files() + [c.user(), c.description()]) | ||||
if kw in t.lower(): | ||||
l.append(r) | ||||
return l | ||||
def grep(repo, subset, x): | ||||
Patrick Mezard
|
r12821 | """``grep(regex)`` | ||
Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')`` | ||||
to ensure special escape characters are handled correctly. | ||||
""" | ||||
Brodie Rao
|
r12320 | try: | ||
Martin Geisler
|
r12815 | # i18n: "grep" is a keyword | ||
Benoit Boissinot
|
r12736 | gr = re.compile(getstring(x, _("grep requires a string"))) | ||
Brodie Rao
|
r12320 | except re.error, e: | ||
raise error.ParseError(_('invalid match pattern: %s') % e) | ||||
Matt Mackall
|
r11275 | l = [] | ||
for r in subset: | ||||
c = repo[r] | ||||
for e in c.files() + [c.user(), c.description()]: | ||||
if gr.search(e): | ||||
l.append(r) | ||||
continue | ||||
return l | ||||
def author(repo, subset, x): | ||||
Patrick Mezard
|
r12821 | """``author(string)`` | ||
Alias for ``user(string)``. | ||||
""" | ||||
Martin Geisler
|
r12815 | # i18n: "author" is a keyword | ||
Benoit Boissinot
|
r12736 | n = getstring(x, _("author requires a string")).lower() | ||
Matt Mackall
|
r11275 | return [r for r in subset if n in repo[r].user().lower()] | ||
Patrick Mezard
|
r12821 | def user(repo, subset, x): | ||
"""``user(string)`` | ||||
User name is string. | ||||
""" | ||||
return author(repo, subset, x) | ||||
Matt Mackall
|
r11275 | def hasfile(repo, subset, x): | ||
Patrick Mezard
|
r12821 | """``file(pattern)`` | ||
Changesets affecting files matched by pattern. | ||||
""" | ||||
Martin Geisler
|
r12815 | # i18n: "file" is a keyword | ||
Benoit Boissinot
|
r12736 | pat = getstring(x, _("file requires a pattern")) | ||
Martin Geisler
|
r12085 | m = matchmod.match(repo.root, repo.getcwd(), [pat]) | ||
Matt Mackall
|
r11275 | s = [] | ||
for r in subset: | ||||
for f in repo[r].files(): | ||||
if m(f): | ||||
s.append(r) | ||||
continue | ||||
return s | ||||
def contains(repo, subset, x): | ||||
Patrick Mezard
|
r12821 | """``contains(pattern)`` | ||
Revision contains pattern. | ||||
""" | ||||
Martin Geisler
|
r12815 | # i18n: "contains" is a keyword | ||
Benoit Boissinot
|
r12736 | pat = getstring(x, _("contains requires a pattern")) | ||
Martin Geisler
|
r12085 | m = matchmod.match(repo.root, repo.getcwd(), [pat]) | ||
Matt Mackall
|
r11275 | s = [] | ||
if m.files() == [pat]: | ||||
for r in subset: | ||||
if pat in repo[r]: | ||||
s.append(r) | ||||
continue | ||||
else: | ||||
for r in subset: | ||||
for f in repo[r].manifest(): | ||||
if m(f): | ||||
s.append(r) | ||||
continue | ||||
return s | ||||
def checkstatus(repo, subset, pat, field): | ||||
Martin Geisler
|
r12085 | m = matchmod.match(repo.root, repo.getcwd(), [pat]) | ||
Matt Mackall
|
r11275 | s = [] | ||
fast = (m.files() == [pat]) | ||||
for r in subset: | ||||
c = repo[r] | ||||
if fast: | ||||
if pat not in c.files(): | ||||
continue | ||||
else: | ||||
for f in c.files(): | ||||
if m(f): | ||||
break | ||||
else: | ||||
continue | ||||
files = repo.status(c.p1().node(), c.node())[field] | ||||
if fast: | ||||
if pat in files: | ||||
s.append(r) | ||||
continue | ||||
else: | ||||
for f in files: | ||||
if m(f): | ||||
s.append(r) | ||||
continue | ||||
return s | ||||
def modifies(repo, subset, x): | ||||
Patrick Mezard
|
r12821 | """``modifies(pattern)`` | ||
Changesets modifying files matched by pattern. | ||||
""" | ||||
Martin Geisler
|
r12815 | # i18n: "modifies" is a keyword | ||
Benoit Boissinot
|
r12736 | pat = getstring(x, _("modifies requires a pattern")) | ||
Matt Mackall
|
r11275 | return checkstatus(repo, subset, pat, 0) | ||
def adds(repo, subset, x): | ||||
Patrick Mezard
|
r12821 | """``adds(pattern)`` | ||
Changesets that add a file matching pattern. | ||||
""" | ||||
Martin Geisler
|
r12815 | # i18n: "adds" is a keyword | ||
Benoit Boissinot
|
r12736 | pat = getstring(x, _("adds requires a pattern")) | ||
Matt Mackall
|
r11275 | return checkstatus(repo, subset, pat, 1) | ||
def removes(repo, subset, x): | ||||
Patrick Mezard
|
r12821 | """``removes(pattern)`` | ||
Changesets which remove files matching pattern. | ||||
""" | ||||
Martin Geisler
|
r12815 | # i18n: "removes" is a keyword | ||
Benoit Boissinot
|
r12736 | pat = getstring(x, _("removes requires a pattern")) | ||
Matt Mackall
|
r11275 | return checkstatus(repo, subset, pat, 2) | ||
def merge(repo, subset, x): | ||||
Patrick Mezard
|
r12821 | """``merge()`` | ||
Changeset is a merge changeset. | ||||
""" | ||||
Martin Geisler
|
r12815 | # i18n: "merge" is a keyword | ||
Martin Geisler
|
r11383 | getargs(x, 0, 0, _("merge takes no arguments")) | ||
Matt Mackall
|
r11275 | cl = repo.changelog | ||
return [r for r in subset if cl.parentrevs(r)[1] != -1] | ||||
def closed(repo, subset, x): | ||||
Patrick Mezard
|
r12821 | """``closed()`` | ||
Changeset is closed. | ||||
""" | ||||
Martin Geisler
|
r12815 | # i18n: "closed" is a keyword | ||
Martin Geisler
|
r11383 | getargs(x, 0, 0, _("closed takes no arguments")) | ||
Georg Brandl
|
r11349 | return [r for r in subset if repo[r].extra().get('close')] | ||
Matt Mackall
|
r11275 | |||
def head(repo, subset, x): | ||||
Patrick Mezard
|
r12821 | """``head()`` | ||
Changeset is a named branch head. | ||||
""" | ||||
Martin Geisler
|
r12815 | # i18n: "head" is a keyword | ||
Martin Geisler
|
r11383 | getargs(x, 0, 0, _("head takes no arguments")) | ||
Matt Mackall
|
r11275 | hs = set() | ||
for b, ls in repo.branchmap().iteritems(): | ||||
hs.update(repo[h].rev() for h in ls) | ||||
return [r for r in subset if r in hs] | ||||
def reverse(repo, subset, x): | ||||
Patrick Mezard
|
r12821 | """``reverse(set)`` | ||
Reverse order of set. | ||||
""" | ||||
Matt Mackall
|
r11275 | l = getset(repo, subset, x) | ||
l.reverse() | ||||
return l | ||||
Wagner Bruna
|
r11944 | def present(repo, subset, x): | ||
Patrick Mezard
|
r12821 | """``present(set)`` | ||
An empty set, if any revision in set isn't found; otherwise, | ||||
all revisions in set. | ||||
""" | ||||
Wagner Bruna
|
r11944 | try: | ||
return getset(repo, subset, x) | ||||
except error.RepoLookupError: | ||||
return [] | ||||
Matt Mackall
|
r11275 | def sort(repo, subset, x): | ||
Patrick Mezard
|
r12821 | """``sort(set[, [-]key...])`` | ||
Sort set by keys. The default sort order is ascending, specify a key | ||||
as ``-key`` to sort in descending order. | ||||
The keys can be: | ||||
- ``rev`` for the revision number, | ||||
- ``branch`` for the branch name, | ||||
- ``desc`` for the commit message (description), | ||||
- ``user`` for user name (``author`` can be used as an alias), | ||||
- ``date`` for the commit date | ||||
""" | ||||
Martin Geisler
|
r12815 | # i18n: "sort" is a keyword | ||
Benoit Boissinot
|
r12736 | l = getargs(x, 1, 2, _("sort requires one or two arguments")) | ||
Matt Mackall
|
r11275 | keys = "rev" | ||
if len(l) == 2: | ||||
Martin Geisler
|
r11383 | keys = getstring(l[1], _("sort spec must be a string")) | ||
Matt Mackall
|
r11275 | |||
s = l[0] | ||||
keys = keys.split() | ||||
l = [] | ||||
def invert(s): | ||||
return "".join(chr(255 - ord(c)) for c in s) | ||||
for r in getset(repo, subset, s): | ||||
c = repo[r] | ||||
e = [] | ||||
for k in keys: | ||||
if k == 'rev': | ||||
e.append(r) | ||||
elif k == '-rev': | ||||
e.append(-r) | ||||
elif k == 'branch': | ||||
e.append(c.branch()) | ||||
elif k == '-branch': | ||||
e.append(invert(c.branch())) | ||||
elif k == 'desc': | ||||
e.append(c.description()) | ||||
elif k == '-desc': | ||||
e.append(invert(c.description())) | ||||
elif k in 'user author': | ||||
e.append(c.user()) | ||||
elif k in '-user -author': | ||||
e.append(invert(c.user())) | ||||
elif k == 'date': | ||||
e.append(c.date()[0]) | ||||
elif k == '-date': | ||||
e.append(-c.date()[0]) | ||||
else: | ||||
Martin Geisler
|
r11383 | raise error.ParseError(_("unknown sort key %r") % k) | ||
Matt Mackall
|
r11275 | e.append(r) | ||
l.append(e) | ||||
l.sort() | ||||
return [e[-1] for e in l] | ||||
def getall(repo, subset, x): | ||||
Patrick Mezard
|
r12821 | """``all()`` | ||
All changesets, the same as ``0:tip``. | ||||
""" | ||||
Martin Geisler
|
r12815 | # i18n: "all" is a keyword | ||
Martin Geisler
|
r11383 | getargs(x, 0, 0, _("all takes no arguments")) | ||
Matt Mackall
|
r11275 | return subset | ||
def heads(repo, subset, x): | ||||
Patrick Mezard
|
r12821 | """``heads(set)`` | ||
Members of set with no children in set. | ||||
""" | ||||
Matt Mackall
|
r11275 | s = getset(repo, subset, x) | ||
ps = set(parents(repo, subset, x)) | ||||
return [r for r in s if r not in ps] | ||||
def roots(repo, subset, x): | ||||
Patrick Mezard
|
r12821 | """``roots(set)`` | ||
Changesets with no parent changeset in set. | ||||
""" | ||||
Matt Mackall
|
r11275 | s = getset(repo, subset, x) | ||
cs = set(children(repo, subset, x)) | ||||
return [r for r in s if r not in cs] | ||||
def outgoing(repo, subset, x): | ||||
Patrick Mezard
|
r12821 | """``outgoing([path])`` | ||
Changesets not found in the specified destination repository, or the | ||||
default push location. | ||||
""" | ||||
Matt Mackall
|
r11293 | import hg # avoid start-up nasties | ||
Martin Geisler
|
r12815 | # i18n: "outgoing" is a keyword | ||
Benoit Boissinot
|
r12736 | l = getargs(x, 0, 1, _("outgoing requires a repository path")) | ||
Martin Geisler
|
r12815 | # i18n: "outgoing" is a keyword | ||
Benoit Boissinot
|
r12736 | dest = l and getstring(l[0], _("outgoing requires a repository path")) or '' | ||
Matt Mackall
|
r11275 | dest = repo.ui.expandpath(dest or 'default-push', dest or 'default') | ||
dest, branches = hg.parseurl(dest) | ||||
Adrian Buehlmann
|
r12614 | revs, checkout = hg.addbranchrevs(repo, repo, branches, []) | ||
if revs: | ||||
revs = [repo.lookup(rev) for rev in revs] | ||||
Matt Mackall
|
r11275 | other = hg.repository(hg.remoteui(repo, {}), dest) | ||
repo.ui.pushbuffer() | ||||
Dirkjan Ochtman
|
r11301 | o = discovery.findoutgoing(repo, other) | ||
Matt Mackall
|
r11275 | repo.ui.popbuffer() | ||
cl = repo.changelog | ||||
Adrian Buehlmann
|
r12614 | o = set([cl.rev(r) for r in repo.changelog.nodesbetween(o, revs)[0]]) | ||
Matt Mackall
|
r11275 | return [r for r in subset if r in o] | ||
Augie Fackler
|
r12715 | def tag(repo, subset, x): | ||
Patrick Mezard
|
r12821 | """``tag(name)`` | ||
The specified tag by name, or all tagged revisions if no name is given. | ||||
""" | ||||
Martin Geisler
|
r12815 | # i18n: "tag" is a keyword | ||
Augie Fackler
|
r12715 | args = getargs(x, 0, 1, _("tag takes one or no arguments")) | ||
Matt Mackall
|
r11280 | cl = repo.changelog | ||
Augie Fackler
|
r12715 | if args: | ||
tn = getstring(args[0], | ||||
Martin Geisler
|
r12815 | # i18n: "tag" is a keyword | ||
Augie Fackler
|
r12715 | _('the argument to tag must be a string')) | ||
s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn]) | ||||
else: | ||||
s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip']) | ||||
Matt Mackall
|
r11280 | return [r for r in subset if r in s] | ||
Patrick Mezard
|
r12821 | def tagged(repo, subset, x): | ||
return tag(repo, subset, x) | ||||
Matt Mackall
|
r11275 | symbols = { | ||
Matt Mackall
|
r11284 | "adds": adds, | ||
"all": getall, | ||||
Matt Mackall
|
r11275 | "ancestor": ancestor, | ||
"ancestors": ancestors, | ||||
Matt Mackall
|
r11284 | "author": author, | ||
Matt Mackall
|
r11275 | "branch": branch, | ||
Matt Mackall
|
r11284 | "children": children, | ||
"closed": closed, | ||||
"contains": contains, | ||||
"date": date, | ||||
"descendants": descendants, | ||||
"file": hasfile, | ||||
"follow": follow, | ||||
"grep": grep, | ||||
"head": head, | ||||
"heads": heads, | ||||
Matt Mackall
|
r11275 | "keyword": keyword, | ||
Matt Mackall
|
r11284 | "limit": limit, | ||
"max": maxrev, | ||||
Nicolas Dumazet
|
r11708 | "min": minrev, | ||
Matt Mackall
|
r11284 | "merge": merge, | ||
"modifies": modifies, | ||||
Augie Fackler
|
r12716 | "id": node, | ||
Matt Mackall
|
r11284 | "outgoing": outgoing, | ||
Matt Mackall
|
r11275 | "p1": p1, | ||
"p2": p2, | ||||
"parents": parents, | ||||
Wagner Bruna
|
r11944 | "present": present, | ||
Matt Mackall
|
r11284 | "removes": removes, | ||
"reverse": reverse, | ||||
Augie Fackler
|
r12716 | "rev": rev, | ||
Matt Mackall
|
r11275 | "roots": roots, | ||
Matt Mackall
|
r11284 | "sort": sort, | ||
Augie Fackler
|
r12715 | "tag": tag, | ||
Patrick Mezard
|
r12821 | "tagged": tagged, | ||
"user": user, | ||||
Matt Mackall
|
r11275 | } | ||
methods = { | ||||
"range": rangeset, | ||||
"string": stringset, | ||||
"symbol": symbolset, | ||||
"and": andset, | ||||
"or": orset, | ||||
"not": notset, | ||||
"list": listset, | ||||
"func": func, | ||||
} | ||||
Matt Mackall
|
r11279 | def optimize(x, small): | ||
if x == None: | ||||
return 0, x | ||||
Matt Mackall
|
r11275 | smallbonus = 1 | ||
if small: | ||||
smallbonus = .5 | ||||
op = x[0] | ||||
Matt Mackall
|
r11283 | if op == 'minus': | ||
Matt Mackall
|
r11279 | return optimize(('and', x[1], ('not', x[2])), small) | ||
elif op == 'dagrange': | ||||
return optimize(('and', ('func', ('symbol', 'descendants'), x[1]), | ||||
('func', ('symbol', 'ancestors'), x[2])), small) | ||||
elif op == 'dagrangepre': | ||||
return optimize(('func', ('symbol', 'ancestors'), x[1]), small) | ||||
elif op == 'dagrangepost': | ||||
return optimize(('func', ('symbol', 'descendants'), x[1]), small) | ||||
elif op == 'rangepre': | ||||
return optimize(('range', ('string', '0'), x[1]), small) | ||||
elif op == 'rangepost': | ||||
return optimize(('range', x[1], ('string', 'tip')), small) | ||||
Matt Mackall
|
r11467 | elif op == 'negate': | ||
return optimize(('string', | ||||
'-' + getstring(x[1], _("can't negate that"))), small) | ||||
Matt Mackall
|
r11279 | elif op in 'string symbol negate': | ||
return smallbonus, x # single revisions are small | ||||
Matt Mackall
|
r11275 | elif op == 'and' or op == 'dagrange': | ||
Matt Mackall
|
r11279 | wa, ta = optimize(x[1], True) | ||
wb, tb = optimize(x[2], True) | ||||
w = min(wa, wb) | ||||
if wa > wb: | ||||
return w, (op, tb, ta) | ||||
return w, (op, ta, tb) | ||||
elif op == 'or': | ||||
wa, ta = optimize(x[1], False) | ||||
wb, tb = optimize(x[2], False) | ||||
if wb < wa: | ||||
wb, wa = wa, wb | ||||
return max(wa, wb), (op, ta, tb) | ||||
Matt Mackall
|
r11275 | elif op == 'not': | ||
Matt Mackall
|
r11279 | o = optimize(x[1], not small) | ||
return o[0], (op, o[1]) | ||||
Matt Mackall
|
r11275 | elif op == 'group': | ||
Matt Mackall
|
r11279 | return optimize(x[1], small) | ||
elif op in 'range list': | ||||
wa, ta = optimize(x[1], small) | ||||
wb, tb = optimize(x[2], small) | ||||
return wa + wb, (op, ta, tb) | ||||
Matt Mackall
|
r11275 | elif op == 'func': | ||
Martin Geisler
|
r11383 | f = getstring(x[1], _("not a symbol")) | ||
Matt Mackall
|
r11279 | wa, ta = optimize(x[2], small) | ||
Matt Mackall
|
r12351 | if f in "grep date user author keyword branch file outgoing": | ||
Matt Mackall
|
r11279 | w = 10 # slow | ||
Matt Mackall
|
r12351 | elif f in "modifies adds removes": | ||
Matt Mackall
|
r11279 | w = 30 # slower | ||
Matt Mackall
|
r11275 | elif f == "contains": | ||
Matt Mackall
|
r11279 | w = 100 # very slow | ||
Matt Mackall
|
r11275 | elif f == "ancestor": | ||
Matt Mackall
|
r11279 | w = 1 * smallbonus | ||
Matt Mackall
|
r11275 | elif f == "reverse limit": | ||
Matt Mackall
|
r11279 | w = 0 | ||
Matt Mackall
|
r11275 | elif f in "sort": | ||
Matt Mackall
|
r11279 | w = 10 # assume most sorts look at changelog | ||
Matt Mackall
|
r11275 | else: | ||
Matt Mackall
|
r11279 | w = 1 | ||
return w + wa, (op, x[1], ta) | ||||
return 1, x | ||||
Matt Mackall
|
r11275 | |||
parse = parser.parser(tokenize, elements).parse | ||||
def match(spec): | ||||
Matt Mackall
|
r11385 | if not spec: | ||
raise error.ParseError(_("empty query")) | ||||
Matt Mackall
|
r11275 | tree = parse(spec) | ||
Matt Mackall
|
r11279 | weight, tree = optimize(tree, True) | ||
Matt Mackall
|
r11275 | def mfunc(repo, subset): | ||
return getset(repo, subset, tree) | ||||
return mfunc | ||||
Patrick Mezard
|
r12821 | |||
def makedoc(topic, doc): | ||||
"""Generate and include predicates help in revsets topic.""" | ||||
predicates = [] | ||||
for name in sorted(symbols): | ||||
text = symbols[name].__doc__ | ||||
if not text: | ||||
continue | ||||
Wagner Bruna
|
r12855 | text = gettext(text.rstrip()) | ||
Patrick Mezard
|
r12821 | lines = text.splitlines() | ||
lines[1:] = [(' ' + l.strip()) for l in lines[1:]] | ||||
predicates.append('\n'.join(lines)) | ||||
Wagner Bruna
|
r12855 | predicates = '\n\n'.join(predicates) | ||
Patrick Mezard
|
r12821 | doc = doc.replace('.. predicatesmarker', predicates) | ||
return doc | ||||
Patrick Mezard
|
r12823 | |||
# tell hggettext to extract docstrings from these functions: | ||||
i18nfunctions = symbols.values() | ||||