/mercurial/revset.py
Python | 2856 lines | 2652 code | 53 blank | 151 comment | 120 complexity | a7c93e7212c923b73893474afbf61d1b MD5 | raw file
Possible License(s): GPL-2.0
Large files files are truncated, but you can click here to view the full file
- # 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
- import parser, util, error, discovery, hbisect, phases
- import node
- import heapq
- import match as matchmod
- import ancestor as ancestormod
- from i18n import _
- import encoding
- import obsolete as obsmod
- import pathutil
- import repoview
- def _revancestors(repo, revs, followfirst):
- """Like revlog.ancestors(), but supports followfirst."""
- cut = followfirst and 1 or None
- cl = repo.changelog
- def iterate():
- revqueue, revsnode = None, None
- h = []
- revs.descending()
- revqueue = util.deque(revs)
- if revqueue:
- revsnode = revqueue.popleft()
- heapq.heappush(h, -revsnode)
- seen = set([node.nullrev])
- while h:
- current = -heapq.heappop(h)
- if current not in seen:
- if revsnode and current == revsnode:
- if revqueue:
- revsnode = revqueue.popleft()
- heapq.heappush(h, -revsnode)
- seen.add(current)
- yield current
- for parent in cl.parentrevs(current)[:cut]:
- if parent != node.nullrev:
- heapq.heappush(h, -parent)
- return _descgeneratorset(iterate())
- def _revdescendants(repo, revs, followfirst):
- """Like revlog.descendants() but supports followfirst."""
- cut = followfirst and 1 or None
- def iterate():
- cl = repo.changelog
- first = min(revs)
- nullrev = node.nullrev
- if first == nullrev:
- # Are there nodes with a null first parent and a non-null
- # second one? Maybe. Do we care? Probably not.
- for i in cl:
- yield i
- else:
- seen = set(revs)
- for i in cl.revs(first + 1):
- for x in cl.parentrevs(i)[:cut]:
- if x != nullrev and x in seen:
- seen.add(i)
- yield i
- break
- return _ascgeneratorset(iterate())
- def _revsbetween(repo, roots, heads):
- """Return all paths between roots and heads, inclusive of both endpoint
- sets."""
- if not roots:
- return baseset([])
- parentrevs = repo.changelog.parentrevs
- visit = baseset(heads)
- reachable = set()
- seen = {}
- minroot = min(roots)
- roots = set(roots)
- # open-code the post-order traversal due to the tiny size of
- # sys.getrecursionlimit()
- while visit:
- rev = visit.pop()
- if rev in roots:
- reachable.add(rev)
- parents = parentrevs(rev)
- seen[rev] = parents
- for parent in parents:
- if parent >= minroot and parent not in seen:
- visit.append(parent)
- if not reachable:
- return baseset([])
- for rev in sorted(seen):
- for parent in seen[rev]:
- if parent in reachable:
- reachable.add(rev)
- return baseset(sorted(reachable))
- elements = {
- "(": (20, ("group", 1, ")"), ("func", 1, ")")),
- "~": (18, None, ("ancestor", 18)),
- "^": (18, None, ("parent", 18), ("parentpost", 18)),
- "-": (5, ("negate", 19), ("minus", 5)),
- "::": (17, ("dagrangepre", 17), ("dagrange", 17),
- ("dagrangepost", 17)),
- "..": (17, ("dagrangepre", 17), ("dagrange", 17),
- ("dagrangepost", 17)),
- ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
- "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, lookup=None):
- '''
- Parse a revset statement into a stream of tokens
- Check that @ is a valid unquoted token character (issue3686):
- >>> list(tokenize("@::"))
- [('symbol', '@', 0), ('::', None, 1), ('end', None, 3)]
- '''
- pos, l = 0, len(program)
- while pos < l:
- c = program[pos]
- if c.isspace(): # skip inter-token whitespace
- pass
- elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
- yield ('::', None, pos)
- pos += 1 # skip ahead
- elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
- yield ('..', None, pos)
- pos += 1 # skip ahead
- elif c in "():,-|&+!~^": # handle simple operators
- yield (c, None, pos)
- 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')
- 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:
- yield ('string', decode(program[s:pos]), s)
- break
- pos += 1
- else:
- raise error.ParseError(_("unterminated string"), s)
- # gather up a symbol/keyword
- elif c.isalnum() or c in '._@' or ord(c) > 127:
- s = pos
- pos += 1
- while pos < l: # find end of symbol
- d = program[pos]
- if not (d.isalnum() or d in "-._/@" or ord(d) > 127):
- 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
- yield (sym, None, s)
- elif '-' in sym:
- # some jerk gave us foo-bar-baz, try to check if it's a symbol
- if lookup and lookup(sym):
- # looks like a real symbol
- yield ('symbol', sym, s)
- else:
- # looks like an expression
- parts = sym.split('-')
- for p in parts[:-1]:
- if p: # possible consecutive -
- yield ('symbol', p, s)
- s += len(p)
- yield ('-', None, pos)
- s += 1
- if parts[-1]: # possible trailing -
- yield ('symbol', parts[-1], s)
- else:
- yield ('symbol', sym, s)
- pos -= 1
- else:
- raise error.ParseError(_("syntax error"), pos)
- pos += 1
- yield ('end', None, pos)
- # helpers
- def getstring(x, err):
- if x and (x[0] == 'string' or x[0] == 'symbol'):
- return x[1]
- raise error.ParseError(err)
- def getlist(x):
- if not x:
- return []
- if x[0] == 'list':
- return getlist(x[1]) + [x[2]]
- return [x]
- def getargs(x, min, max, err):
- l = getlist(x)
- if len(l) < min or (max >= 0 and len(l) > max):
- raise error.ParseError(err)
- return l
- def getset(repo, subset, x):
- if not x:
- raise error.ParseError(_("missing argument"))
- s = methods[x[0]](repo, subset, *x[1:])
- if util.safehasattr(s, 'set'):
- return s
- return baseset(s)
- def _getrevsource(repo, r):
- extra = repo[r].extra()
- for label in ('source', 'transplant_source', 'rebase_source'):
- if label in extra:
- try:
- return repo[extra[label]].rev()
- except error.RepoLookupError:
- pass
- return None
- # operator methods
- def stringset(repo, subset, x):
- x = repo[x].rev()
- if x == -1 and len(subset) == len(repo):
- return baseset([-1])
- if len(subset) == len(repo) or x in subset:
- return baseset([x])
- return baseset([])
- def symbolset(repo, subset, x):
- if x in symbols:
- raise error.ParseError(_("can't use %s here") % x)
- return stringset(repo, subset, x)
- def rangeset(repo, subset, x, y):
- cl = baseset(repo.changelog)
- m = getset(repo, cl, x)
- n = getset(repo, cl, y)
- if not m or not n:
- return baseset([])
- m, n = m[0], n[-1]
- if m < n:
- r = spanset(repo, m, n + 1)
- else:
- r = spanset(repo, m, n - 1)
- return r & subset
- def dagrange(repo, subset, x, y):
- r = spanset(repo)
- xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y))
- s = subset.set()
- return xs.filter(s.__contains__)
- def andset(repo, subset, x, y):
- return getset(repo, getset(repo, subset, x), y)
- def orset(repo, subset, x, y):
- xl = getset(repo, subset, x)
- yl = getset(repo, subset - xl, y)
- return xl + yl
- def notset(repo, subset, x):
- return subset - getset(repo, subset, x)
- def listset(repo, subset, a, b):
- raise error.ParseError(_("can't use a list in this context"))
- def func(repo, subset, a, b):
- if a[0] == 'symbol' and a[1] in symbols:
- return symbols[a[1]](repo, subset, b)
- raise error.ParseError(_("not a function: %s") % a[1])
- # functions
- def adds(repo, subset, x):
- """``adds(pattern)``
- Changesets that add a file matching pattern.
- The pattern without explicit kind like ``glob:`` is expected to be
- relative to the current directory and match against a file or a
- directory.
- """
- # i18n: "adds" is a keyword
- pat = getstring(x, _("adds requires a pattern"))
- return checkstatus(repo, subset, pat, 1)
- def ancestor(repo, subset, x):
- """``ancestor(*changeset)``
- A greatest common ancestor of the changesets.
- Accepts 0 or more changesets.
- Will return empty list when passed no args.
- Greatest common ancestor of a single changeset is that changeset.
- """
- # i18n: "ancestor" is a keyword
- l = getlist(x)
- rl = spanset(repo)
- anc = None
- # (getset(repo, rl, i) for i in l) generates a list of lists
- for revs in (getset(repo, rl, i) for i in l):
- for r in revs:
- if anc is None:
- anc = repo[r]
- else:
- anc = anc.ancestor(repo[r])
- if anc is not None and anc.rev() in subset:
- return baseset([anc.rev()])
- return baseset([])
- def _ancestors(repo, subset, x, followfirst=False):
- args = getset(repo, spanset(repo), x)
- if not args:
- return baseset([])
- s = _revancestors(repo, args, followfirst)
- return subset.filter(s.__contains__)
- def ancestors(repo, subset, x):
- """``ancestors(set)``
- Changesets that are ancestors of a changeset in set.
- """
- return _ancestors(repo, subset, x)
- def _firstancestors(repo, subset, x):
- # ``_firstancestors(set)``
- # Like ``ancestors(set)`` but follows only the first parents.
- return _ancestors(repo, subset, x, followfirst=True)
- def ancestorspec(repo, subset, x, n):
- """``set~n``
- Changesets that are the Nth ancestor (first parents only) of a changeset
- in set.
- """
- try:
- n = int(n[1])
- except (TypeError, ValueError):
- raise error.ParseError(_("~ expects a number"))
- ps = set()
- cl = repo.changelog
- for r in getset(repo, baseset(cl), x):
- for i in range(n):
- r = cl.parentrevs(r)[0]
- ps.add(r)
- return subset.filter(ps.__contains__)
- def author(repo, subset, x):
- """``author(string)``
- Alias for ``user(string)``.
- """
- # i18n: "author" is a keyword
- n = encoding.lower(getstring(x, _("author requires a string")))
- kind, pattern, matcher = _substringmatcher(n)
- return subset.filter(lambda x: matcher(encoding.lower(repo[x].user())))
- def only(repo, subset, x):
- """``only(set, [set])``
- Changesets that are ancestors of the first set that are not ancestors
- of any other head in the repo. If a second set is specified, the result
- is ancestors of the first set that are not ancestors of the second set
- (i.e. ::<set1> - ::<set2>).
- """
- cl = repo.changelog
- # i18n: "only" is a keyword
- args = getargs(x, 1, 2, _('only takes one or two arguments'))
- include = getset(repo, spanset(repo), args[0]).set()
- if len(args) == 1:
- descendants = set(_revdescendants(repo, include, False))
- exclude = [rev for rev in cl.headrevs()
- if not rev in descendants and not rev in include]
- else:
- exclude = getset(repo, spanset(repo), args[1])
- results = set(ancestormod.missingancestors(include, exclude, cl.parentrevs))
- return lazyset(subset, results.__contains__)
- def bisect(repo, subset, x):
- """``bisect(string)``
- Changesets marked in the specified bisect status:
- - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
- - ``goods``, ``bads`` : csets topologically good/bad
- - ``range`` : csets taking part in the bisection
- - ``pruned`` : csets that are goods, bads or skipped
- - ``untested`` : csets whose fate is yet unknown
- - ``ignored`` : csets ignored due to DAG topology
- - ``current`` : the cset currently being bisected
- """
- # i18n: "bisect" is a keyword
- status = getstring(x, _("bisect requires a string")).lower()
- state = set(hbisect.get(repo, status))
- return subset.filter(state.__contains__)
- # Backward-compatibility
- # - no help entry so that we do not advertise it any more
- def bisected(repo, subset, x):
- return bisect(repo, subset, x)
- def bookmark(repo, subset, x):
- """``bookmark([name])``
- The named bookmark or all bookmarks.
- If `name` starts with `re:`, the remainder of the name is treated as
- a regular expression. To match a bookmark that actually starts with `re:`,
- use the prefix `literal:`.
- """
- # i18n: "bookmark" is a keyword
- args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
- if args:
- bm = getstring(args[0],
- # i18n: "bookmark" is a keyword
- _('the argument to bookmark must be a string'))
- kind, pattern, matcher = _stringmatcher(bm)
- if kind == 'literal':
- bmrev = repo._bookmarks.get(bm, None)
- if not bmrev:
- raise util.Abort(_("bookmark '%s' does not exist") % bm)
- bmrev = repo[bmrev].rev()
- return subset.filter(lambda r: r == bmrev)
- else:
- matchrevs = set()
- for name, bmrev in repo._bookmarks.iteritems():
- if matcher(name):
- matchrevs.add(bmrev)
- if not matchrevs:
- raise util.Abort(_("no bookmarks exist that match '%s'")
- % pattern)
- bmrevs = set()
- for bmrev in matchrevs:
- bmrevs.add(repo[bmrev].rev())
- return subset & bmrevs
- bms = set([repo[r].rev()
- for r in repo._bookmarks.values()])
- return subset.filter(bms.__contains__)
- def branch(repo, subset, x):
- """``branch(string or set)``
- All changesets belonging to the given branch or the branches of the given
- changesets.
- If `string` starts with `re:`, the remainder of the name is treated as
- a regular expression. To match a branch that actually starts with `re:`,
- use the prefix `literal:`.
- """
- try:
- b = getstring(x, '')
- except error.ParseError:
- # not a string, but another revspec, e.g. tip()
- pass
- else:
- kind, pattern, matcher = _stringmatcher(b)
- if kind == 'literal':
- # note: falls through to the revspec case if no branch with
- # this name exists
- if pattern in repo.branchmap():
- return subset.filter(lambda r: matcher(repo[r].branch()))
- else:
- return subset.filter(lambda r: matcher(repo[r].branch()))
- s = getset(repo, spanset(repo), x)
- b = set()
- for r in s:
- b.add(repo[r].branch())
- s = s.set()
- return subset.filter(lambda r: r in s or repo[r].branch() in b)
- def bumped(repo, subset, x):
- """``bumped()``
- Mutable changesets marked as successors of public changesets.
- Only non-public and non-obsolete changesets can be `bumped`.
- """
- # i18n: "bumped" is a keyword
- getargs(x, 0, 0, _("bumped takes no arguments"))
- bumped = obsmod.getrevs(repo, 'bumped')
- return subset & bumped
- def bundle(repo, subset, x):
- """``bundle()``
- Changesets in the bundle.
- Bundle must be specified by the -R option."""
- try:
- bundlerevs = repo.changelog.bundlerevs
- except AttributeError:
- raise util.Abort(_("no bundle provided - specify with -R"))
- return subset & bundlerevs
- def checkstatus(repo, subset, pat, field):
- hasset = matchmod.patkind(pat) == 'set'
- def matches(x):
- m = None
- fname = None
- c = repo[x]
- if not m or hasset:
- m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
- if not m.anypats() and len(m.files()) == 1:
- fname = m.files()[0]
- if fname is not None:
- if fname not in c.files():
- return False
- else:
- for f in c.files():
- if m(f):
- break
- else:
- return False
- files = repo.status(c.p1().node(), c.node())[field]
- if fname is not None:
- if fname in files:
- return True
- else:
- for f in files:
- if m(f):
- return True
- return subset.filter(matches)
- def _children(repo, narrow, parentset):
- cs = set()
- if not parentset:
- return baseset(cs)
- pr = repo.changelog.parentrevs
- minrev = min(parentset)
- for r in narrow:
- if r <= minrev:
- continue
- for p in pr(r):
- if p in parentset:
- cs.add(r)
- return baseset(cs)
- def children(repo, subset, x):
- """``children(set)``
- Child changesets of changesets in set.
- """
- s = getset(repo, baseset(repo), x).set()
- cs = _children(repo, subset, s)
- return subset & cs
- def closed(repo, subset, x):
- """``closed()``
- Changeset is closed.
- """
- # i18n: "closed" is a keyword
- getargs(x, 0, 0, _("closed takes no arguments"))
- return subset.filter(lambda r: repo[r].closesbranch())
- def contains(repo, subset, x):
- """``contains(pattern)``
- The revision's manifest contains a file matching pattern (but might not
- modify it). See :hg:`help patterns` for information about file patterns.
- The pattern without explicit kind like ``glob:`` is expected to be
- relative to the current directory and match against a file exactly
- for efficiency.
- """
- # i18n: "contains" is a keyword
- pat = getstring(x, _("contains requires a pattern"))
- def matches(x):
- if not matchmod.patkind(pat):
- pats = pathutil.canonpath(repo.root, repo.getcwd(), pat)
- if pats in repo[x]:
- return True
- else:
- c = repo[x]
- m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
- for f in c.manifest():
- if m(f):
- return True
- return False
- return subset.filter(matches)
- def converted(repo, subset, x):
- """``converted([id])``
- Changesets converted from the given identifier in the old repository if
- present, or all converted changesets if no identifier is specified.
- """
- # There is exactly no chance of resolving the revision, so do a simple
- # string compare and hope for the best
- rev = None
- # i18n: "converted" is a keyword
- l = getargs(x, 0, 1, _('converted takes one or no arguments'))
- if l:
- # i18n: "converted" is a keyword
- rev = getstring(l[0], _('converted requires a revision'))
- def _matchvalue(r):
- source = repo[r].extra().get('convert_revision', None)
- return source is not None and (rev is None or source.startswith(rev))
- return subset.filter(lambda r: _matchvalue(r))
- def date(repo, subset, x):
- """``date(interval)``
- Changesets within the interval, see :hg:`help dates`.
- """
- # i18n: "date" is a keyword
- ds = getstring(x, _("date requires a string"))
- dm = util.matchdate(ds)
- return subset.filter(lambda x: dm(repo[x].date()[0]))
- def desc(repo, subset, x):
- """``desc(string)``
- Search commit message for string. The match is case-insensitive.
- """
- # i18n: "desc" is a keyword
- ds = encoding.lower(getstring(x, _("desc requires a string")))
- def matches(x):
- c = repo[x]
- return ds in encoding.lower(c.description())
- return subset.filter(matches)
- def _descendants(repo, subset, x, followfirst=False):
- args = getset(repo, spanset(repo), x)
- if not args:
- return baseset([])
- s = _revdescendants(repo, args, followfirst)
- # Both sets need to be ascending in order to lazily return the union
- # in the correct order.
- args.ascending()
- subsetset = subset.set()
- result = (orderedlazyset(s, subsetset.__contains__, ascending=True) +
- orderedlazyset(args, subsetset.__contains__, ascending=True))
- # Wrap result in a lazyset since it's an _addset, which doesn't implement
- # all the necessary functions to be consumed by callers.
- return orderedlazyset(result, lambda r: True, ascending=True)
- def descendants(repo, subset, x):
- """``descendants(set)``
- Changesets which are descendants of changesets in set.
- """
- return _descendants(repo, subset, x)
- def _firstdescendants(repo, subset, x):
- # ``_firstdescendants(set)``
- # Like ``descendants(set)`` but follows only the first parents.
- return _descendants(repo, subset, x, followfirst=True)
- def destination(repo, subset, x):
- """``destination([set])``
- Changesets that were created by a graft, transplant or rebase operation,
- with the given revisions specified as the source. Omitting the optional set
- is the same as passing all().
- """
- if x is not None:
- args = getset(repo, spanset(repo), x).set()
- else:
- args = getall(repo, spanset(repo), x).set()
- dests = set()
- # subset contains all of the possible destinations that can be returned, so
- # iterate over them and see if their source(s) were provided in the args.
- # Even if the immediate src of r is not in the args, src's source (or
- # further back) may be. Scanning back further than the immediate src allows
- # transitive transplants and rebases to yield the same results as transitive
- # grafts.
- for r in subset:
- src = _getrevsource(repo, r)
- lineage = None
- while src is not None:
- if lineage is None:
- lineage = list()
- lineage.append(r)
- # The visited lineage is a match if the current source is in the arg
- # set. Since every candidate dest is visited by way of iterating
- # subset, any dests further back in the lineage will be tested by a
- # different iteration over subset. Likewise, if the src was already
- # selected, the current lineage can be selected without going back
- # further.
- if src in args or src in dests:
- dests.update(lineage)
- break
- r = src
- src = _getrevsource(repo, r)
- return subset.filter(dests.__contains__)
- def divergent(repo, subset, x):
- """``divergent()``
- Final successors of changesets with an alternative set of final successors.
- """
- # i18n: "divergent" is a keyword
- getargs(x, 0, 0, _("divergent takes no arguments"))
- divergent = obsmod.getrevs(repo, 'divergent')
- return subset.filter(divergent.__contains__)
- def draft(repo, subset, x):
- """``draft()``
- Changeset in draft phase."""
- # i18n: "draft" is a keyword
- getargs(x, 0, 0, _("draft takes no arguments"))
- pc = repo._phasecache
- return subset.filter(lambda r: pc.phase(repo, r) == phases.draft)
- def extinct(repo, subset, x):
- """``extinct()``
- Obsolete changesets with obsolete descendants only.
- """
- # i18n: "extinct" is a keyword
- getargs(x, 0, 0, _("extinct takes no arguments"))
- extincts = obsmod.getrevs(repo, 'extinct')
- return subset & extincts
- def extra(repo, subset, x):
- """``extra(label, [value])``
- Changesets with the given label in the extra metadata, with the given
- optional value.
- If `value` starts with `re:`, the remainder of the value is treated as
- a regular expression. To match a value that actually starts with `re:`,
- use the prefix `literal:`.
- """
- # i18n: "extra" is a keyword
- l = getargs(x, 1, 2, _('extra takes at least 1 and at most 2 arguments'))
- # i18n: "extra" is a keyword
- label = getstring(l[0], _('first argument to extra must be a string'))
- value = None
- if len(l) > 1:
- # i18n: "extra" is a keyword
- value = getstring(l[1], _('second argument to extra must be a string'))
- kind, value, matcher = _stringmatcher(value)
- def _matchvalue(r):
- extra = repo[r].extra()
- return label in extra and (value is None or matcher(extra[label]))
- return subset.filter(lambda r: _matchvalue(r))
- def filelog(repo, subset, x):
- """``filelog(pattern)``
- Changesets connected to the specified filelog.
- For performance reasons, visits only revisions mentioned in the file-level
- filelog, rather than filtering through all changesets (much faster, but
- doesn't include deletes or duplicate changes). For a slower, more accurate
- result, use ``file()``.
- The pattern without explicit kind like ``glob:`` is expected to be
- relative to the current directory and match against a file exactly
- for efficiency.
- """
- # i18n: "filelog" is a keyword
- pat = getstring(x, _("filelog requires a pattern"))
- s = set()
- if not matchmod.patkind(pat):
- f = pathutil.canonpath(repo.root, repo.getcwd(), pat)
- fl = repo.file(f)
- for fr in fl:
- s.add(fl.linkrev(fr))
- else:
- m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None])
- for f in repo[None]:
- if m(f):
- fl = repo.file(f)
- for fr in fl:
- s.add(fl.linkrev(fr))
- return subset.filter(s.__contains__)
- def first(repo, subset, x):
- """``first(set, [n])``
- An alias for limit().
- """
- return limit(repo, subset, x)
- def _follow(repo, subset, x, name, followfirst=False):
- l = getargs(x, 0, 1, _("%s takes no arguments or a filename") % name)
- c = repo['.']
- if l:
- x = getstring(l[0], _("%s expected a filename") % name)
- if x in c:
- cx = c[x]
- s = set(ctx.rev() for ctx in cx.ancestors(followfirst=followfirst))
- # include the revision responsible for the most recent version
- s.add(cx.linkrev())
- else:
- return baseset([])
- else:
- s = _revancestors(repo, baseset([c.rev()]), followfirst)
- return subset.filter(s.__contains__)
- def follow(repo, subset, x):
- """``follow([file])``
- An alias for ``::.`` (ancestors of the working copy's first parent).
- If a filename is specified, the history of the given file is followed,
- including copies.
- """
- return _follow(repo, subset, x, 'follow')
- def _followfirst(repo, subset, x):
- # ``followfirst([file])``
- # Like ``follow([file])`` but follows only the first parent of
- # every revision or file revision.
- return _follow(repo, subset, x, '_followfirst', followfirst=True)
- def getall(repo, subset, x):
- """``all()``
- All changesets, the same as ``0:tip``.
- """
- # i18n: "all" is a keyword
- getargs(x, 0, 0, _("all takes no arguments"))
- return subset
- def grep(repo, subset, x):
- """``grep(regex)``
- Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
- to ensure special escape characters are handled correctly. Unlike
- ``keyword(string)``, the match is case-sensitive.
- """
- try:
- # i18n: "grep" is a keyword
- gr = re.compile(getstring(x, _("grep requires a string")))
- except re.error, e:
- raise error.ParseError(_('invalid match pattern: %s') % e)
- def matches(x):
- c = repo[x]
- for e in c.files() + [c.user(), c.description()]:
- if gr.search(e):
- return True
- return False
- return subset.filter(matches)
- def _matchfiles(repo, subset, x):
- # _matchfiles takes a revset list of prefixed arguments:
- #
- # [p:foo, i:bar, x:baz]
- #
- # builds a match object from them and filters subset. Allowed
- # prefixes are 'p:' for regular patterns, 'i:' for include
- # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
- # a revision identifier, or the empty string to reference the
- # working directory, from which the match object is
- # initialized. Use 'd:' to set the default matching mode, default
- # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
- # i18n: "_matchfiles" is a keyword
- l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
- pats, inc, exc = [], [], []
- hasset = False
- rev, default = None, None
- for arg in l:
- # i18n: "_matchfiles" is a keyword
- s = getstring(arg, _("_matchfiles requires string arguments"))
- prefix, value = s[:2], s[2:]
- if prefix == 'p:':
- pats.append(value)
- elif prefix == 'i:':
- inc.append(value)
- elif prefix == 'x:':
- exc.append(value)
- elif prefix == 'r:':
- if rev is not None:
- # i18n: "_matchfiles" is a keyword
- raise error.ParseError(_('_matchfiles expected at most one '
- 'revision'))
- rev = value
- elif prefix == 'd:':
- if default is not None:
- # i18n: "_matchfiles" is a keyword
- raise error.ParseError(_('_matchfiles expected at most one '
- 'default mode'))
- default = value
- else:
- # i18n: "_matchfiles" is a keyword
- raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
- if not hasset and matchmod.patkind(value) == 'set':
- hasset = True
- if not default:
- default = 'glob'
- def matches(x):
- m = None
- c = repo[x]
- if not m or (hasset and rev is None):
- ctx = c
- if rev is not None:
- ctx = repo[rev or None]
- m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
- exclude=exc, ctx=ctx, default=default)
- for f in c.files():
- if m(f):
- return True
- return False
- return subset.filter(matches)
- def hasfile(repo, subset, x):
- """``file(pattern)``
- Changesets affecting files matched by pattern.
- For a faster but less accurate result, consider using ``filelog()``
- instead.
- This predicate uses ``glob:`` as the default kind of pattern.
- """
- # i18n: "file" is a keyword
- pat = getstring(x, _("file requires a pattern"))
- return _matchfiles(repo, subset, ('string', 'p:' + pat))
- def head(repo, subset, x):
- """``head()``
- Changeset is a named branch head.
- """
- # i18n: "head" is a keyword
- getargs(x, 0, 0, _("head takes no arguments"))
- hs = set()
- for b, ls in repo.branchmap().iteritems():
- hs.update(repo[h].rev() for h in ls)
- return baseset(hs).filter(subset.__contains__)
- def heads(repo, subset, x):
- """``heads(set)``
- Members of set with no children in set.
- """
- s = getset(repo, subset, x)
- ps = parents(repo, subset, x)
- return s - ps
- def hidden(repo, subset, x):
- """``hidden()``
- Hidden changesets.
- """
- # i18n: "hidden" is a keyword
- getargs(x, 0, 0, _("hidden takes no arguments"))
- hiddenrevs = repoview.filterrevs(repo, 'visible')
- return subset & hiddenrevs
- def keyword(repo, subset, x):
- """``keyword(string)``
- Search commit message, user name, and names of changed files for
- string. The match is case-insensitive.
- """
- # i18n: "keyword" is a keyword
- kw = encoding.lower(getstring(x, _("keyword requires a string")))
- def matches(r):
- c = repo[r]
- return util.any(kw in encoding.lower(t) for t in c.files() + [c.user(),
- c.description()])
- return subset.filter(matches)
- def limit(repo, subset, x):
- """``limit(set, [n])``
- First n members of set, defaulting to 1.
- """
- # i18n: "limit" is a keyword
- l = getargs(x, 1, 2, _("limit requires one or two arguments"))
- try:
- lim = 1
- if len(l) == 2:
- # i18n: "limit" is a keyword
- lim = int(getstring(l[1], _("limit requires a number")))
- except (TypeError, ValueError):
- # i18n: "limit" is a keyword
- raise error.ParseError(_("limit expects a number"))
- ss = subset.set()
- os = getset(repo, spanset(repo), l[0])
- bs = baseset([])
- it = iter(os)
- for x in xrange(lim):
- try:
- y = it.next()
- if y in ss:
- bs.append(y)
- except (StopIteration):
- break
- return bs
- def last(repo, subset, x):
- """``last(set, [n])``
- Last n members of set, defaulting to 1.
- """
- # i18n: "last" is a keyword
- l = getargs(x, 1, 2, _("last requires one or two arguments"))
- try:
- lim = 1
- if len(l) == 2:
- # i18n: "last" is a keyword
- lim = int(getstring(l[1], _("last requires a number")))
- except (TypeError, ValueError):
- # i18n: "last" is a keyword
- raise error.ParseError(_("last expects a number"))
- ss = subset.set()
- os = getset(repo, spanset(repo), l[0])
- os.reverse()
- bs = baseset([])
- it = iter(os)
- for x in xrange(lim):
- try:
- y = it.next()
- if y in ss:
- bs.append(y)
- except (StopIteration):
- break
- return bs
- def maxrev(repo, subset, x):
- """``max(set)``
- Changeset with highest revision number in set.
- """
- os = getset(repo, spanset(repo), x)
- if os:
- m = os.max()
- if m in subset:
- return baseset([m])
- return baseset([])
- def merge(repo, subset, x):
- """``merge()``
- Changeset is a merge changeset.
- """
- # i18n: "merge" is a keyword
- getargs(x, 0, 0, _("merge takes no arguments"))
- cl = repo.changelog
- return subset.filter(lambda r: cl.parentrevs(r)[1] != -1)
- def branchpoint(repo, subset, x):
- """``branchpoint()``
- Changesets with more than one child.
- """
- # i18n: "branchpoint" is a keyword
- getargs(x, 0, 0, _("branchpoint takes no arguments"))
- cl = repo.changelog
- if not subset:
- return baseset([])
- baserev = min(subset)
- parentscount = [0]*(len(repo) - baserev)
- for r in cl.revs(start=baserev + 1):
- for p in cl.parentrevs(r):
- if p >= baserev:
- parentscount[p - baserev] += 1
- return subset.filter(lambda r: parentscount[r - baserev] > 1)
- def minrev(repo, subset, x):
- """``min(set)``
- Changeset with lowest revision number in set.
- """
- os = getset(repo, spanset(repo), x)
- if os:
- m = os.min()
- if m in subset:
- return baseset([m])
- return baseset([])
- def _missingancestors(repo, subset, x):
- # i18n: "_missingancestors" is a keyword
- revs, bases = getargs(x, 2, 2,
- _("_missingancestors requires two arguments"))
- rs = baseset(repo)
- revs = getset(repo, rs, revs)
- bases = getset(repo, rs, bases)
- missing = set(repo.changelog.findmissingrevs(bases, revs))
- return baseset([r for r in subset if r in missing])
- def modifies(repo, subset, x):
- """``modifies(pattern)``
- Changesets modifying files matched by pattern.
- The pattern without explicit kind like ``glob:`` is expected to be
- relative to the current directory and match against a file or a
- directory.
- """
- # i18n: "modifies" is a keyword
- pat = getstring(x, _("modifies requires a pattern"))
- return checkstatus(repo, subset, pat, 0)
- def node_(repo, subset, x):
- """``id(string)``
- Revision non-ambiguously specified by the given hex string prefix.
- """
- # i18n: "id" is a keyword
- l = getargs(x, 1, 1, _("id requires one argument"))
- # i18n: "id" is a keyword
- n = getstring(l[0], _("id requires a string"))
- if len(n) == 40:
- rn = repo[n].rev()
- else:
- rn = None
- pm = repo.changelog._partialmatch(n)
- if pm is not None:
- rn = repo.changelog.rev(pm)
- return subset.filter(lambda r: r == rn)
- def obsolete(repo, subset, x):
- """``obsolete()``
- Mutable changeset with a newer version."""
- # i18n: "obsolete" is a keyword
- getargs(x, 0, 0, _("obsolete takes no arguments"))
- obsoletes = obsmod.getrevs(repo, 'obsolete')
- return subset & obsoletes
- def origin(repo, subset, x):
- """``origin([set])``
- Changesets that were specified as a source for the grafts, transplants or
- rebases that created the given revisions. Omitting the optional set is the
- same as passing all(). If a changeset created by these operations is itself
- specified as a source for one of these operations, only the source changeset
- for the first operation is selected.
- """
- if x is not None:
- args = getset(repo, spanset(repo), x).set()
- else:
- args = getall(repo, spanset(repo), x).set()
- def _firstsrc(rev):
- src = _getrevsource(repo, rev)
- if src is None:
- return None
- while True:
- prev = _getrevsource(repo, src)
- if prev is None:
- return src
- src = prev
- o = set([_firstsrc(r) for r in args])
- return subset.filter(o.__contains__)
- def outgoing(repo, subset, x):
- """``outgoing([path])``
- Changesets not found in the specified destination repository, or the
- default push location.
- """
- import hg # avoid start-up nasties
- # i18n: "outgoing" is a keyword
- l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
- # i18n: "outgoing" is a keyword
- dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
- dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
- dest, branches = hg.parseurl(dest)
- revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
- if revs:
- revs = [repo.lookup(rev) for rev in revs]
- other = hg.peer(repo, {}, dest)
- repo.ui.pushbuffer()
- outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
- repo.ui.popbuffer()
- cl = repo.changelog
- o = set([cl.rev(r) for r in outgoing.missing])
- return subset.filter(o.__contains__)
- def p1(repo, subset, x):
- """``p1([set])``
- First parent of changesets in set, or the working directory.
- """
- if x is None:
- p = repo[x].p1().rev()
- return subset.filter(lambda r: r == p)
- ps = set()
- cl = repo.changelog
- for r in getset(repo, spanset(repo), x):
- ps.add(cl.parentrevs(r)[0])
- return subset & ps
- def p2(repo, subset, x):
- """``p2([set])``
- Second parent of changesets in set, or the working directory.
- """
- if x is None:
- ps = repo[x].parents()
- try:
- p = ps[1].rev()
- return subset.filter(lambda r: r == p)
- except IndexError:
- return baseset([])
- ps = set()
- cl = repo.changelog
- for r in getset(repo, spanset(repo), x):
- ps.add(cl.parentrevs(r)[1])
- return subset & ps
- def parents(repo, subset, x):
- """``parents([set])``
- The set of all parents for all changesets in set, or the working directory.
- """
- if x is None:
- ps = tuple(p.rev() for p in repo[x].parents())
- return subset & ps
- ps = set()
- cl = repo.changelog
- for r in getset(repo, spanset(repo), x):
- ps.update(cl.parentrevs(r))
- return subset & ps
- def parentspec(repo, subset, x, n):
- """``set^0``
- The set.
- ``set^1`` (or ``set^``), ``set^2``
- First or second parent, respectively, of all changesets in set.
- """
- try:
- n = int(n[1])
- if n not in (0, 1, 2):
- raise ValueError
- except (TypeError, ValueError):
- raise error.ParseError(_("^ expects a number 0, 1, or 2"))
- ps = set()
- cl = repo.changelog
- for r in getset(repo, baseset(cl), x):
- if n == 0:
- ps.add(r)
- elif n == 1:
- ps.add(cl.parentrevs(r)[0])
- elif n == 2:
- parents = cl.parentrevs(r)
- if len(parents) > 1:
- ps.add(parents[1])
- return subset & ps
- def present(repo, subset, x):
- """``present(set)``
- An empty set, if any revision in set isn't found; otherwise,
- all revisions in set.
- If any of specified revisions is not present in the local repository,
- the query is normally aborted. But this predicate allows the query
- to continue even in such cases.
- """
- try:
- return getset(repo, subset, x)
- except error.RepoLookupError:
- return baseset([])
- def public(repo, subset, x):
- """``public()``
- Changeset in public phase."""
- # i18n: "public" is a keyword
- getargs(x, 0, 0, _("public takes no arguments"))
- pc = repo._phasecache
- return subset.filter(lambda r: pc.phase(repo, r) == phases.public)
- def remote(repo, subset, x):
- """``remote([id [,path]])``
- Local revision that corresponds to the given identifier in a
- remote repository, if present. Here, the '.' identifier is a
- synonym for the current local branch.
- """
- import hg # avoid start-up nasties
- # i18n: "remote" is a keyword
- l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
- q = '.'
- if len(l) > 0:
- # i18n: "remote" is a keyword
- q = getstring(l[0], _("remote requires a string id"))
- if q == '.':
- q = repo['.'].branch()
- dest = ''
- if len(l) > 1:
- # i18n: "remote" is a keyword
- dest = getstring(l[1], _("remote requires a repository path"))
- dest = repo.ui.expandpath(dest or 'default')
- dest, branches = hg.parseurl(dest)
- revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
- if revs:
- revs = [repo.lookup(rev) for rev in revs]
- other = hg.peer(repo, {}, dest)
- n = other.lookup(q)
- if n in repo:
- r = repo[n].rev()
- if r in subset:
- return baseset([r])
- return baseset([])
- def removes(repo, subset, x):
- """``removes(pattern)``
- Changesets which remove files matching pattern.
- The pattern without explicit kind like ``glob:`` is expected to be
- relative to the current directory and match against a file or a
- directory.
- """
- # i18n: "removes" is a keyword
- pat = getstring(x, _("removes requires a pattern"))
- return checkstatus(repo, subset, pat, 2)
- def rev(repo, subset, x):
- """``rev(number)``
- Revision with the given numeric identifier.
- """
- # i18n: "rev" is a keyword
- l = getargs(x, 1, 1, _("rev requires one argument"))
- try:
- # i18n: "rev" is a keyword
- l = int(getstring(l[0], _("rev requires a number")))
- except (TypeError, ValueError):
- # i18n: "rev" is a keyword
- raise error.ParseError(_("rev expects a number"))
- return subset.filter(lambda r: r == l)
- def matching(repo, subset, x):
- """``matching(revision [, field])``
- Changesets in which a given set of fields match the set of fields in the
- selected revision or set.
- To match more than one field pass the list of fields to match separated
- by spaces (e.g. ``author description``).
- Valid fields are most regular revision fields and some special fields.
- Regular revision fields are ``description``, ``author``, ``branch``,
- ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
- and ``diff``.
- Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
- contents of the revision. Two revisions matching their ``diff`` will
- also match their ``files``.
- Special fields are ``summary`` and ``metadata``:
- ``summary`` matches the first line of the description.
- ``metadata`` is equivalent to matching ``description user date``
- (i.e. it matches the main metadata fields).
- ``metadata`` is the default field which is used when no fields are
- specified. You can match more than one field at a time.
- """
- # i18n: "matching" is a keyword
- l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
- revs = getset(repo, baseset(repo.changelog), l[0])
- fieldlist = ['metadata']
- if len(l) > 1:
- fieldlist = getstring(l[1],
- # i18n: "matching" is a keyword
- _("matching requires a string "
- "as its second argument")).split()
- # Make sure that there are no repeated fields,
- # expand the 'special' 'metadata' field type
- # and check the 'files' whenever we check the 'diff'
- fields = []
- for field in fieldlist:
- if field == 'metadata':
- fields += ['user', 'description', 'date']
- elif field == 'diff':
- # a revision matching the diff must also match the files
- # since matching the diff is very costly, make sure to
- # also match the files first
- fields += ['files', 'diff']
- else:
- if field == 'author':
- field = 'user'
- fields.append(field)
- fields = set(fields)
- if 'summary' in fields and 'description' in fields:
- # If a revision matches its description it also matches its summary
- fields.discard('summary')
- # We may want to match more than one field
- # Not all fields take the same amount of time to be matched
- # Sort the selected fields in order of increasing matching cost
- fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
- 'files', 'description', 'substate', 'diff']
- def fieldkeyfunc(f):
- try:
- return fieldorder.index(f)
- except ValueError:
- # assume an unknown field is very costly
- return len(fieldorder)
- fields = list(fields)
- fields.sort(key=fieldkeyfunc)
- # Each field will be matched with its own "getfield" function
- # which will be added to the getfieldfuncs array of functions
- getfieldfuncs = []
- _funcs = {
- 'user': lambda r: repo[r].user(),
- 'branch': lambda r: repo[r].branch(),
- 'date': lambda r: repo[r].date(),
- 'description': lambda r: repo[r].description(),
- 'files': lambda r: repo[r].files(),
- 'parents': lambda r: repo[r].parents(),
- 'phase': lambda r: repo[r].phase(),
- 'substate': lambda r: repo[r].substate,
- 'summary': lambda r: repo[r].description().splitlines()[0],
- 'diff': lambda r: list(repo[r].diff(git=True),)
- }
- for info in fields:
- getfield = _funcs.get(info, None)
- if getfield is None:
- raise error.ParseError(
- # i18n: "matching" is a keyword
- _("unexpected field name passed to matching: %s") % info)
- getfieldfuncs.append(getfield)
- # convert the getfield array of functions into a "getinfo" function
- # which returns an array of field values (or a single value if there
- # is only one field to match)
- getinfo = lambda r: [f(r) for f in getfieldfuncs]
- def matches(x):
- for rev in revs:
- target = getinfo(rev)
- match = True
- for n, f in enumerate(getfieldfuncs):
- if target[n] != f(x):
- match = False
- if match:
- return True
- return False
- return subset.filter(matches)
- def reverse(repo, subset, x):
- """``reverse(set)``
- Reverse order of set.
- """
- l = getset(repo, subset, x)
- l.reverse()
- return l
- def roots(repo, subset, x):
- """``roots(set)``
- Changesets in set with no parent changeset in set.
- """
- s = getset(repo, spanset(repo), x).set()
- subset = baseset([r for r in s if r in subset.set()])
- cs = _children(repo, subset, s)
- return subset - cs
- def secret(repo, subset, x):
- """``secret()``
- Changeset in secret phase."""
- # i18n: "secret" is a keyword
- getargs(x, 0, 0, _("secret takes no arguments"))
- pc = repo._phasecache
- return subset.filter(lambda x: pc.phase(repo, x) == phases.secret)
- def sort(repo, subset, x):
- """``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
- """
- # i18n: "sort" is a keyword
- l = getargs(x, 1, 2, _("sort requires one or two arguments"))
- keys = "rev"
- if len(l) == 2:
- # i18n: "sort" is a keyword
- keys = getstring(l[1], _("sort spec must be a string"))
- s = l[0]
- keys = keys.split()
- l = []
- def invert(s):
- return "".join(chr(255 - ord(c)) for c in s)
- revs = getset(repo, subset, s)
- if keys == ["rev"]:
- …
Large files files are truncated, but you can click here to view the full file