diff options
author | Lorry Tar Creator <lorry-tar-importer@baserock.org> | 2011-10-01 20:49:36 +0000 |
---|---|---|
committer | Lorry <lorry@roadtrain.codethink.co.uk> | 2012-09-27 13:27:51 +0000 |
commit | 921ced43c48c1d170452a7b251b94cc96ec8dd44 (patch) | |
tree | 3c4a89176ea67fe4c7bf7b375488361a823c95fa /mercurial/revset.py | |
parent | 9039c805b0a7e36220101323f82735f08a104b37 (diff) | |
download | mercurial-tarball-master.tar.gz |
Imported from /srv/lorry/lorry-area/mercurial-tarball/mercurial-1.9.3.tar.gz.HEADmercurial-1.9.3master
Diffstat (limited to 'mercurial/revset.py')
-rw-r--r-- | mercurial/revset.py | 1123 |
1 files changed, 148 insertions, 975 deletions
diff --git a/mercurial/revset.py b/mercurial/revset.py index a7e9d07..cb089d7 100644 --- a/mercurial/revset.py +++ b/mercurial/revset.py @@ -6,76 +6,10 @@ # GNU General Public License version 2 or any later version. import re -import parser, util, error, discovery, hbisect, phases -import node +import parser, util, error, discovery, hbisect import bookmarks as bookmarksmod import match as matchmod from i18n import _ -import encoding - -def _revancestors(repo, revs, followfirst): - """Like revlog.ancestors(), but supports followfirst.""" - cut = followfirst and 1 or None - cl = repo.changelog - visit = util.deque(revs) - seen = set([node.nullrev]) - while visit: - for parent in cl.parentrevs(visit.popleft())[:cut]: - if parent not in seen: - visit.append(parent) - seen.add(parent) - yield parent - -def _revdescendants(repo, revs, followfirst): - """Like revlog.descendants() but supports followfirst.""" - cut = followfirst and 1 or None - 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 - return - - seen = set(revs) - for i in xrange(first + 1, len(cl)): - for x in cl.parentrevs(i)[:cut]: - if x != nullrev and x in seen: - seen.add(i) - yield i - break - -def _revsbetween(repo, roots, heads): - """Return all paths between roots and heads, inclusive of both endpoint - sets.""" - if not roots: - return [] - parentrevs = repo.changelog.parentrevs - visit = 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 [] - for rev in sorted(seen): - for parent in seen[rev]: - if parent in reachable: - reachable.add(rev) - return sorted(reachable) elements = { "(": (20, ("group", 1, ")"), ("func", 1, ")")), @@ -138,13 +72,12 @@ def tokenize(program): pos += 1 else: raise error.ParseError(_("unterminated string"), s) - # gather up a symbol/keyword - elif c.isalnum() or c in '._' or ord(c) > 127: + elif c.isalnum() or c in '._' or ord(c) > 127: # gather up a symbol/keyword 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): + if not (d.isalnum() or d in "._" or ord(d) > 127): break if d == '.' and program[pos - 1] == '.': # special case for .. pos -= 1 @@ -177,7 +110,7 @@ def getlist(x): def getargs(x, min, max, err): l = getlist(x) - if len(l) < min or (max >= 0 and len(l) > max): + if len(l) < min or len(l) > max: raise error.ParseError(err) return l @@ -186,16 +119,6 @@ def getset(repo, subset, x): raise error.ParseError(_("missing argument")) return methods[x[0]](repo, subset, *x[1:]) -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): @@ -231,14 +154,6 @@ def rangeset(repo, subset, x, y): s = set(subset) return [x for x in r if x in s] -def dagrange(repo, subset, x, y): - if subset: - r = range(len(repo)) - xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y)) - s = set(subset) - return [r for r in xs if r in s] - return [] - def andset(repo, subset, x, y): return getset(repo, getset(repo, subset, x), y) @@ -286,28 +201,19 @@ def ancestor(repo, subset, x): return [r for r in an if r in subset] -def _ancestors(repo, subset, x, followfirst=False): - args = getset(repo, range(len(repo)), x) - if not args: - return [] - s = set(_revancestors(repo, args, followfirst)) | set(args) - return [r for r in subset if r in s] - 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) + args = getset(repo, range(len(repo)), x) + if not args: + return [] + s = set(repo.changelog.ancestors(*args)) | set(args) + return [r for r in subset if r in s] def ancestorspec(repo, subset, x, n): """``set~n`` - Changesets that are the Nth ancestor (first parents only) of a changeset - in set. + Changesets that are the Nth ancestor (first parents only) of a changeset in set. """ try: n = int(n[1]) @@ -326,39 +232,22 @@ def author(repo, subset, x): Alias for ``user(string)``. """ # i18n: "author" is a keyword - n = encoding.lower(getstring(x, _("author requires a string"))) - kind, pattern, matcher = _substringmatcher(n) - return [r for r in subset if matcher(encoding.lower(repo[r].user()))] - -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 topologicaly 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 [r for r in subset if r in state] + n = getstring(x, _("author requires a string")).lower() + return [r for r in subset if n in repo[r].user().lower()] -# Backward-compatibility -# - no help entry so that we do not advertise it any more def bisected(repo, subset, x): - return bisect(repo, subset, x) + """``bisected(string)`` + Changesets marked in the specified bisect state (good, bad, skip). + """ + state = getstring(x, _("bisect requires a string")).lower() + if state not in ('good', 'bad', 'skip', 'unknown'): + raise error.ParseError(_('invalid bisect state')) + marked = set(repo.changelog.rev(n) for n in hbisect.load_state(repo)[state]) + return [r for r in subset if r in marked] 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')) @@ -366,26 +255,11 @@ def bookmark(repo, subset, x): 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 = bookmarksmod.listbookmarks(repo).get(bm, None) - if not bmrev: - raise util.Abort(_("bookmark '%s' does not exist") % bm) - bmrev = repo[bmrev].rev() - return [r for r in subset if r == bmrev] - else: - matchrevs = set() - for name, bmrev in bookmarksmod.listbookmarks(repo).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 [r for r in subset if r in bmrevs] - + bmrev = bookmarksmod.listbookmarks(repo).get(bm, None) + if not bmrev: + raise util.Abort(_("bookmark '%s' does not exist") % bm) + bmrev = repo[bmrev].rev() + return [r for r in subset if r == bmrev] bms = set([repo[r].rev() for r in bookmarksmod.listbookmarks(repo).values()]) return [r for r in subset if r in bms] @@ -394,25 +268,14 @@ 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, '') + if b in repo.branchmap(): + return [r for r in subset if repo[r].branch() == b] 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 [r for r in subset if matcher(repo[r].branch())] - else: - return [r for r in subset if matcher(repo[r].branch())] s = getset(repo, range(len(repo)), x) b = set() @@ -422,18 +285,13 @@ def branch(repo, subset, x): return [r for r in subset if r in s or repo[r].branch() in b] def checkstatus(repo, subset, pat, field): - m = None + m = matchmod.match(repo.root, repo.getcwd(), [pat]) s = [] - hasset = matchmod.patkind(pat) == 'set' - fname = None + fast = (m.files() == [pat]) for r in subset: c = repo[r] - 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(): + if fast: + if pat not in c.files(): continue else: for f in c.files(): @@ -442,8 +300,8 @@ def checkstatus(repo, subset, pat, field): else: continue files = repo.status(c.p1().node(), c.node())[field] - if fname is not None: - if fname in files: + if fast: + if pat in files: s.append(r) else: for f in files: @@ -452,21 +310,17 @@ def checkstatus(repo, subset, pat, field): break return s -def _children(repo, narrow, parentset): - cs = set() - pr = repo.changelog.parentrevs - for r in narrow: - for p in pr(r): - if p in parentset: - cs.add(r) - return cs - def children(repo, subset, x): """``children(set)`` Child changesets of changesets in set. """ + cs = set() + cl = repo.changelog s = set(getset(repo, range(len(repo)), x)) - cs = _children(repo, subset, s) + 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 closed(repo, subset, x): @@ -475,7 +329,7 @@ def closed(repo, subset, x): """ # i18n: "closed" is a keyword getargs(x, 0, 0, _("closed takes no arguments")) - return [r for r in subset if repo[r].closesbranch()] + return [r for r in subset if repo[r].extra().get('close')] def contains(repo, subset, x): """``contains(pattern)`` @@ -484,45 +338,20 @@ def contains(repo, subset, x): """ # i18n: "contains" is a keyword pat = getstring(x, _("contains requires a pattern")) - m = None + m = matchmod.match(repo.root, repo.getcwd(), [pat]) s = [] - if not matchmod.patkind(pat): + if m.files() == [pat]: for r in subset: if pat in repo[r]: s.append(r) else: for r in subset: - c = repo[r] - if not m or matchmod.patkind(pat) == 'set': - m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c) - for f in c.manifest(): + for f in repo[r].manifest(): if m(f): s.append(r) break return s -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 [r for r in subset if _matchvalue(r)] - def date(repo, subset, x): """``date(interval)`` Changesets within the interval, see :hg:`help dates`. @@ -537,136 +366,34 @@ def desc(repo, subset, x): Search commit message for string. The match is case-insensitive. """ # i18n: "desc" is a keyword - ds = encoding.lower(getstring(x, _("desc requires a string"))) + ds = getstring(x, _("desc requires a string")).lower() l = [] for r in subset: c = repo[r] - if ds in encoding.lower(c.description()): + if ds in c.description().lower(): l.append(r) return l -def _descendants(repo, subset, x, followfirst=False): - args = getset(repo, range(len(repo)), x) - if not args: - return [] - s = set(_revdescendants(repo, args, followfirst)) | set(args) - return [r for r in subset if r in s] - 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 = set(getset(repo, range(len(repo)), x)) - else: - args = set(getall(repo, range(len(repo)), x)) - - 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 futher 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 [r for r in subset if r in dests] - -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 [r for r in subset if 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")) - extinctset = set(repo.revs('(obsolete()::) - (::(not obsolete()))')) - return [r for r in subset if r in extinctset] - -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 [r for r in subset if _matchvalue(r)] + args = getset(repo, range(len(repo)), x) + if not args: + return [] + s = set(repo.changelog.descendants(*args)) | set(args) + return [r for r in subset if r in s] def filelog(repo, subset, x): """``filelog(pattern)`` Changesets connected to the specified filelog. - - For performance reasons, ``filelog()`` does not show every changeset - that affects the requested file(s). See :hg:`help log` for details. For - a slower, more accurate result, use ``file()``. """ - # i18n: "filelog" is a keyword pat = getstring(x, _("filelog requires a pattern")) - m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath', - ctx=repo[None]) + m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath') s = set() - if not matchmod.patkind(pat): + if not m.anypats(): for f in m.files(): fl = repo.file(f) for fr in fl: @@ -680,42 +407,33 @@ def filelog(repo, subset, x): return [r for r in subset if r in s] -def first(repo, subset, x): - """``first(set, [n])`` - An alias for limit(). +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 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['.'] + # i18n: "follow" is a keyword + l = getargs(x, 0, 1, _("follow takes no arguments or a filename")) + p = repo['.'].rev() 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 [] + x = getstring(l[0], _("follow expected a filename")) + s = set(ctx.rev() for ctx in repo['.'][x].ancestors()) else: - s = set(_revancestors(repo, [c.rev()], followfirst)) | set([c.rev()]) + s = set(repo.changelog.ancestors(p)) + s |= set([p]) return [r for r in subset if r in s] -def follow(repo, subset, x): - """``follow([file])`` +def followfile(repo, subset, x): + """``follow()`` 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) + # i18n: "follow" is a keyword + getargs(x, 0, 0, _("follow takes no arguments")) + p = repo['.'].rev() + s = set(repo.changelog.ancestors(p)) | set([p]) + return [r for r in subset if r in s] def getall(repo, subset, x): """``all()`` @@ -745,79 +463,20 @@ def grep(repo, subset, x): break return l -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' - m = None - s = [] - for r in subset: - c = repo[r] - 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): - s.append(r) - break - return s - def hasfile(repo, subset, x): """``file(pattern)`` Changesets affecting files matched by pattern. - - For a faster but less accurate result, consider using ``filelog()`` - instead. """ # i18n: "file" is a keyword pat = getstring(x, _("file requires a pattern")) - return _matchfiles(repo, subset, ('string', 'p:' + pat)) + m = matchmod.match(repo.root, repo.getcwd(), [pat]) + s = [] + for r in subset: + for f in repo[r].files(): + if m(f): + s.append(r) + break + return s def head(repo, subset, x): """``head()`` @@ -844,26 +503,24 @@ def keyword(repo, subset, x): string. The match is case-insensitive. """ # i18n: "keyword" is a keyword - kw = encoding.lower(getstring(x, _("keyword requires a string"))) + kw = getstring(x, _("keyword requires a string")).lower() l = [] for r in subset: c = repo[r] t = " ".join(c.files() + [c.user(), c.description()]) - if kw in encoding.lower(t): + if kw in t.lower(): l.append(r) return l def limit(repo, subset, x): - """``limit(set, [n])`` - First n members of set, defaulting to 1. + """``limit(set, n)`` + First n members of set. """ # i18n: "limit" is a keyword - l = getargs(x, 1, 2, _("limit requires one or two arguments")) + l = getargs(x, 2, 2, _("limit requires two arguments")) try: - lim = 1 - if len(l) == 2: - # i18n: "limit" is a keyword - lim = int(getstring(l[1], _("limit requires a number"))) + # 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")) @@ -872,16 +529,14 @@ def limit(repo, subset, x): return [r for r in os if r in ss] def last(repo, subset, x): - """``last(set, [n])`` - Last n members of set, defaulting to 1. + """``last(set, n)`` + Last n members of set. """ # i18n: "last" is a keyword - l = getargs(x, 1, 2, _("last requires one or two arguments")) + l = getargs(x, 2, 2, _("last requires two arguments")) try: - lim = 1 - if len(l) == 2: - # i18n: "last" is a keyword - lim = int(getstring(l[1], _("last requires a number"))) + # 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")) @@ -928,7 +583,7 @@ def modifies(repo, subset, x): pat = getstring(x, _("modifies requires a pattern")) return checkstatus(repo, subset, pat, 0) -def node_(repo, subset, x): +def node(repo, subset, x): """``id(string)`` Revision non-ambiguously specified by the given hex string prefix. """ @@ -939,48 +594,9 @@ def node_(repo, subset, x): 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) - + rn = repo.changelog.rev(repo.changelog._partialmatch(n)) return [r for r in subset if 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")) - return [r for r in subset if repo[r].obsolete()] - -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 = set(getset(repo, range(len(repo)), x)) - else: - args = set(getall(repo, range(len(repo)), x)) - - 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 [r for r in subset if r in o] - def outgoing(repo, subset, x): """``outgoing([path])`` Changesets not found in the specified destination repository, or the @@ -998,10 +614,10 @@ def outgoing(repo, subset, x): revs = [repo.lookup(rev) for rev in revs] other = hg.peer(repo, {}, dest) repo.ui.pushbuffer() - outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs) + common, outheads = discovery.findcommonoutgoing(repo, other, onlyheads=revs) repo.ui.popbuffer() cl = repo.changelog - o = set([cl.rev(r) for r in outgoing.missing]) + o = set([cl.rev(r) for r in repo.changelog.findmissing(common, outheads)]) return [r for r in subset if r in o] def p1(repo, subset, x): @@ -1079,59 +695,12 @@ 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 [] -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 [r for r in subset if 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 [r] - return [] - def removes(repo, subset, x): """``removes(pattern)`` Changesets which remove files matching pattern. @@ -1154,144 +723,21 @@ def rev(repo, subset, x): raise error.ParseError(_("rev expects a number")) return [r for r in subset if 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, xrange(len(repo)), 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] - - matches = set() - for rev in revs: - target = getinfo(rev) - for r in subset: - match = True - for n, f in enumerate(getfieldfuncs): - if target[n] != f(r): - match = False - break - if match: - matches.add(r) - return [r for r in subset if r in matches] - def reverse(repo, subset, x): """``reverse(set)`` Reverse order of set. """ l = getset(repo, subset, x) - if not isinstance(l, list): - l = list(l) l.reverse() return l def roots(repo, subset, x): """``roots(set)`` - Changesets in set with no parent changeset in set. + Changesets with no parent changeset in set. """ - s = set(getset(repo, xrange(len(repo)), x)) - subset = [r for r in subset if r in s] - cs = _children(repo, subset, s) - return [r for r in subset if r not in 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 [r for r in subset if pc.phase(repo, r) == phases.secret] + s = getset(repo, subset, x) + cs = set(children(repo, subset, x)) + return [r for r in s if r not in cs] def sort(repo, subset, x): """``sort(set[, [-]key...])`` @@ -1310,7 +756,6 @@ def sort(repo, subset, x): 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] @@ -1349,51 +794,6 @@ def sort(repo, subset, x): l.sort() return [e[-1] for e in l] -def _stringmatcher(pattern): - """ - accepts a string, possibly starting with 're:' or 'literal:' prefix. - returns the matcher name, pattern, and matcher function. - missing or unknown prefixes are treated as literal matches. - - helper for tests: - >>> def test(pattern, *tests): - ... kind, pattern, matcher = _stringmatcher(pattern) - ... return (kind, pattern, [bool(matcher(t)) for t in tests]) - - exact matching (no prefix): - >>> test('abcdefg', 'abc', 'def', 'abcdefg') - ('literal', 'abcdefg', [False, False, True]) - - regex matching ('re:' prefix) - >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar') - ('re', 'a.+b', [False, False, True]) - - force exact matches ('literal:' prefix) - >>> test('literal:re:foobar', 'foobar', 're:foobar') - ('literal', 're:foobar', [False, True]) - - unknown prefixes are ignored and treated as literals - >>> test('foo:bar', 'foo', 'bar', 'foo:bar') - ('literal', 'foo:bar', [False, False, True]) - """ - if pattern.startswith('re:'): - pattern = pattern[3:] - try: - regex = re.compile(pattern) - except re.error, e: - raise error.ParseError(_('invalid regular expression: %s') - % e) - return 're', pattern, regex.search - elif pattern.startswith('literal:'): - pattern = pattern[8:] - return 'literal', pattern, pattern.__eq__ - -def _substringmatcher(pattern): - kind, pattern, matcher = _stringmatcher(pattern) - if kind == 'literal': - matcher = lambda s: pattern in s - return kind, pattern, matcher - def tag(repo, subset, x): """``tag([name])`` The specified tag by name, or all tagged revisions if no name is given. @@ -1402,20 +802,12 @@ def tag(repo, subset, x): args = getargs(x, 0, 1, _("tag takes one or no arguments")) cl = repo.changelog if args: - pattern = getstring(args[0], - # i18n: "tag" is a keyword - _('the argument to tag must be a string')) - kind, pattern, matcher = _stringmatcher(pattern) - if kind == 'literal': - # avoid resolving all tags - tn = repo._tagscache.tags.get(pattern, None) - if tn is None: - raise util.Abort(_("tag '%s' does not exist") % pattern) - s = set([repo[tn].rev()]) - else: - s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)]) - if not s: - raise util.Abort(_("no tags exist that match '%s'") % pattern) + tn = getstring(args[0], + # i18n: "tag" is a keyword + _('the argument to tag must be a string')) + if not repo.tags().get(tn, None): + raise util.Abort(_("tag '%s' does not exist") % tn) + 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']) return [r for r in subset if r in s] @@ -1423,102 +815,58 @@ def tag(repo, subset, x): def tagged(repo, subset, x): return tag(repo, subset, x) -def unstable(repo, subset, x): - """``unstable()`` - Non-obsolete changesets with obsolete ancestors. - """ - # i18n: "unstable" is a keyword - getargs(x, 0, 0, _("unstable takes no arguments")) - unstableset = set(repo.revs('(obsolete()::) - obsolete()')) - return [r for r in subset if r in unstableset] - - def user(repo, subset, x): """``user(string)`` User name contains string. The match is case-insensitive. - - If `string` starts with `re:`, the remainder of the string is treated as - a regular expression. To match a user that actually contains `re:`, use - the prefix `literal:`. """ return author(repo, subset, x) -# for internal use -def _list(repo, subset, x): - s = getstring(x, "internal error") - if not s: - return [] - if not isinstance(subset, set): - subset = set(subset) - ls = [repo[r].rev() for r in s.split('\0')] - return [r for r in ls if r in subset] - symbols = { "adds": adds, "all": getall, "ancestor": ancestor, "ancestors": ancestors, - "_firstancestors": _firstancestors, "author": author, - "bisect": bisect, "bisected": bisected, "bookmark": bookmark, "branch": branch, "children": children, "closed": closed, "contains": contains, - "converted": converted, "date": date, "desc": desc, "descendants": descendants, - "_firstdescendants": _firstdescendants, - "destination": destination, - "draft": draft, - "extinct": extinct, - "extra": extra, "file": hasfile, "filelog": filelog, - "first": first, "follow": follow, - "_followfirst": _followfirst, "grep": grep, "head": head, "heads": heads, - "id": node_, + "id": node, "keyword": keyword, "last": last, "limit": limit, - "_matchfiles": _matchfiles, "max": maxrev, "merge": merge, "min": minrev, "modifies": modifies, - "obsolete": obsolete, - "origin": origin, "outgoing": outgoing, "p1": p1, "p2": p2, "parents": parents, "present": present, - "public": public, - "remote": remote, "removes": removes, "rev": rev, "reverse": reverse, "roots": roots, "sort": sort, - "secret": secret, - "matching": matching, "tag": tag, "tagged": tagged, "user": user, - "unstable": unstable, - "_list": _list, } methods = { "range": rangeset, - "dagrange": dagrange, "string": stringset, "symbol": symbolset, "and": andset, @@ -1542,6 +890,9 @@ def optimize(x, small): op = x[0] if op == 'minus': 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': @@ -1555,7 +906,7 @@ def optimize(x, small): '-' + getstring(x[1], _("can't negate that"))), small) elif op in 'string symbol negate': return smallbonus, x # single revisions are small - elif op == 'and': + elif op == 'and' or op == 'dagrange': wa, ta = optimize(x[1], True) wb, tb = optimize(x[2], True) w = min(wa, wb) @@ -1576,7 +927,7 @@ def optimize(x, small): return o[0], (op, o[1]) elif op == 'group': return optimize(x[1], small) - elif op in 'dagrange range list parent ancestorspec': + elif op in 'range list parent ancestorspec': if op == 'parent': # x^:y means (x^) : y, not x ^ (:y) post = ('parentpost', x[1]) @@ -1600,7 +951,7 @@ def optimize(x, small): w = 100 # very slow elif f == "ancestor": w = 1 * smallbonus - elif f in "reverse limit first": + elif f in "reverse limit": w = 0 elif f in "sort": w = 10 # assume most sorts look at changelog @@ -1609,27 +960,6 @@ def optimize(x, small): return w + wa, (op, x[1], ta) return 1, x -_aliasarg = ('func', ('symbol', '_aliasarg')) -def _getaliasarg(tree): - """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X)) - return X, None otherwise. - """ - if (len(tree) == 3 and tree[:2] == _aliasarg - and tree[2][0] == 'string'): - return tree[2][1] - return None - -def _checkaliasarg(tree, known=None): - """Check tree contains no _aliasarg construct or only ones which - value is in known. Used to avoid alias placeholders injection. - """ - if isinstance(tree, tuple): - arg = _getaliasarg(tree) - if arg is not None and (not known or arg not in known): - raise error.ParseError(_("not a function: %s") % '_aliasarg') - for t in tree: - _checkaliasarg(t, known) - class revsetalias(object): funcre = re.compile('^([^(]+)\(([^)]+)\)$') args = None @@ -1640,93 +970,46 @@ class revsetalias(object): h = heads(default) b($1) = ancestors($1) - ancestors(default) ''' - m = self.funcre.search(name) - if m: - self.name = m.group(1) - self.tree = ('func', ('symbol', m.group(1))) - self.args = [x.strip() for x in m.group(2).split(',')] - for arg in self.args: - # _aliasarg() is an unknown symbol only used separate - # alias argument placeholders from regular strings. - value = value.replace(arg, '_aliasarg(%r)' % (arg,)) - else: - self.name = name - self.tree = ('symbol', name) - - self.replacement, pos = parse(value) - if pos != len(value): - raise error.ParseError(_('invalid token'), pos) - # Check for placeholder injection - _checkaliasarg(self.replacement, self.args) - -def _getalias(aliases, tree): - """If tree looks like an unexpanded alias, return it. Return None - otherwise. - """ - if isinstance(tree, tuple) and tree: - if tree[0] == 'symbol' and len(tree) == 2: - name = tree[1] - alias = aliases.get(name) - if alias and alias.args is None and alias.tree == tree: - return alias - if tree[0] == 'func' and len(tree) > 1: - if tree[1][0] == 'symbol' and len(tree[1]) == 2: - name = tree[1][1] - alias = aliases.get(name) - if alias and alias.args is not None and alias.tree == tree[:2]: - return alias - return None - -def _expandargs(tree, args): - """Replace _aliasarg instances with the substitution value of the - same name in args, recursively. - """ - if not tree or not isinstance(tree, tuple): - return tree - arg = _getaliasarg(tree) - if arg is not None: - return args[arg] - return tuple(_expandargs(t, args) for t in tree) - -def _expandaliases(aliases, tree, expanding, cache): - """Expand aliases in tree, recursively. - - 'aliases' is a dictionary mapping user defined aliases to - revsetalias objects. - """ - if not isinstance(tree, tuple): - # Do not expand raw strings + if isinstance(name, tuple): # parameter substitution + self.tree = name + self.replacement = value + else: # alias definition + m = self.funcre.search(name) + if m: + self.tree = ('func', ('symbol', m.group(1))) + self.args = [x.strip() for x in m.group(2).split(',')] + for arg in self.args: + value = value.replace(arg, repr(arg)) + else: + self.tree = ('symbol', name) + + self.replacement, pos = parse(value) + if pos != len(value): + raise error.ParseError(_('invalid token'), pos) + + def process(self, tree): + if isinstance(tree, tuple): + if self.args is None: + if tree == self.tree: + return self.replacement + elif tree[:2] == self.tree: + l = getlist(tree[2]) + if len(l) != len(self.args): + raise error.ParseError( + _('invalid number of arguments: %s') % len(l)) + result = self.replacement + for a, v in zip(self.args, l): + valalias = revsetalias(('string', a), v) + result = valalias.process(result) + return result + return tuple(map(self.process, tree)) return tree - alias = _getalias(aliases, tree) - if alias is not None: - if alias in expanding: - raise error.ParseError(_('infinite expansion of revset alias "%s" ' - 'detected') % alias.name) - expanding.append(alias) - if alias.name not in cache: - cache[alias.name] = _expandaliases(aliases, alias.replacement, - expanding, cache) - result = cache[alias.name] - expanding.pop() - if alias.args is not None: - l = getlist(tree[2]) - if len(l) != len(alias.args): - raise error.ParseError( - _('invalid number of arguments: %s') % len(l)) - l = [_expandaliases(aliases, a, [], cache) for a in l] - result = _expandargs(result, dict(zip(alias.args, l))) - else: - result = tuple(_expandaliases(aliases, t, expanding, cache) - for t in tree) - return result def findaliases(ui, tree): - _checkaliasarg(tree) - aliases = {} for k, v in ui.configitems('revsetalias'): alias = revsetalias(k, v) - aliases[alias.name] = alias - return _expandaliases(aliases, tree, [], {}) + tree = alias.process(tree) + return tree parse = parser.parser(tokenize, elements).parse @@ -1736,121 +1019,11 @@ def match(ui, spec): tree, pos = parse(spec) if (pos != len(spec)): raise error.ParseError(_("invalid token"), pos) - if ui: - tree = findaliases(ui, tree) + tree = findaliases(ui, tree) weight, tree = optimize(tree, True) def mfunc(repo, subset): return getset(repo, subset, tree) return mfunc -def formatspec(expr, *args): - ''' - This is a convenience function for using revsets internally, and - escapes arguments appropriately. Aliases are intentionally ignored - so that intended expression behavior isn't accidentally subverted. - - Supported arguments: - - %r = revset expression, parenthesized - %d = int(arg), no quoting - %s = string(arg), escaped and single-quoted - %b = arg.branch(), escaped and single-quoted - %n = hex(arg), single-quoted - %% = a literal '%' - - Prefixing the type with 'l' specifies a parenthesized list of that type. - - >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()")) - '(10 or 11):: and ((this()) or (that()))' - >>> formatspec('%d:: and not %d::', 10, 20) - '10:: and not 20::' - >>> formatspec('%ld or %ld', [], [1]) - "_list('') or 1" - >>> formatspec('keyword(%s)', 'foo\\xe9') - "keyword('foo\\\\xe9')" - >>> b = lambda: 'default' - >>> b.branch = b - >>> formatspec('branch(%b)', b) - "branch('default')" - >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd']) - "root(_list('a\\x00b\\x00c\\x00d'))" - ''' - - def quote(s): - return repr(str(s)) - - def argtype(c, arg): - if c == 'd': - return str(int(arg)) - elif c == 's': - return quote(arg) - elif c == 'r': - parse(arg) # make sure syntax errors are confined - return '(%s)' % arg - elif c == 'n': - return quote(node.hex(arg)) - elif c == 'b': - return quote(arg.branch()) - - def listexp(s, t): - l = len(s) - if l == 0: - return "_list('')" - elif l == 1: - return argtype(t, s[0]) - elif t == 'd': - return "_list('%s')" % "\0".join(str(int(a)) for a in s) - elif t == 's': - return "_list('%s')" % "\0".join(s) - elif t == 'n': - return "_list('%s')" % "\0".join(node.hex(a) for a in s) - elif t == 'b': - return "_list('%s')" % "\0".join(a.branch() for a in s) - - m = l // 2 - return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t)) - - ret = '' - pos = 0 - arg = 0 - while pos < len(expr): - c = expr[pos] - if c == '%': - pos += 1 - d = expr[pos] - if d == '%': - ret += d - elif d in 'dsnbr': - ret += argtype(d, args[arg]) - arg += 1 - elif d == 'l': - # a list of some type - pos += 1 - d = expr[pos] - ret += listexp(list(args[arg]), d) - arg += 1 - else: - raise util.Abort('unexpected revspec format character %s' % d) - else: - ret += c - pos += 1 - - return ret - -def prettyformat(tree): - def _prettyformat(tree, level, lines): - if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'): - lines.append((level, str(tree))) - else: - lines.append((level, '(%s' % tree[0])) - for s in tree[1:]: - _prettyformat(s, level + 1, lines) - lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')] - - lines = [] - _prettyformat(tree, 0, lines) - output = '\n'.join((' '*l + s) for l, s in lines) - return output - # tell hggettext to extract docstrings from these functions: i18nfunctions = symbols.values() |