eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/mercurial/revset.py
changeset 69 c6bca38c1cbf
equal deleted inserted replaced
68:5ff1fc726848 69:c6bca38c1cbf
       
     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()