graphlog: implement --follow-first
log --graph --follow-first FILE cannot be compared with the regular version
because it never worked: --follow-first is not taken in account in
walkchangerevs() fast path and is explicitely bypassed in FILE case in
walkchangerevs() nested iterate() function.
--- a/hgext/graphlog.py Sat Feb 25 22:11:36 2012 +0100
+++ b/hgext/graphlog.py Sat Feb 25 22:11:36 2012 +0100
@@ -237,7 +237,7 @@
return (len(repo) - 1, 0)
def check_unsupported_flags(pats, opts):
- for op in ["follow_first", "copies", "newest_first"]:
+ for op in ["copies", "newest_first"]:
if op in opts and opts[op]:
raise util.Abort(_("-G/--graph option is incompatible with --%s")
% op.replace("_", "-"))
@@ -246,18 +246,20 @@
"""Return revset str built of revisions, log options and file patterns.
"""
opt2revset = {
- 'follow': ('follow()', None),
- 'no_merges': ('not merge()', None),
- 'only_merges': ('merge()', None),
- 'removed': ('removes("*")', None),
- 'date': ('date(%(val)r)', None),
- 'branch': ('branch(%(val)r)', ' or '),
- '_patslog': ('filelog(%(val)r)', ' or '),
- '_patsfollow': ('follow(%(val)r)', ' or '),
- 'keyword': ('keyword(%(val)r)', ' or '),
- 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
- 'user': ('user(%(val)r)', ' or '),
- 'rev': ('%(val)s', ' or '),
+ 'follow': ('follow()', None),
+ 'follow_first': ('_followfirst()', None),
+ 'no_merges': ('not merge()', None),
+ 'only_merges': ('merge()', None),
+ 'removed': ('removes("*")', None),
+ 'date': ('date(%(val)r)', None),
+ 'branch': ('branch(%(val)r)', ' or '),
+ '_patslog': ('filelog(%(val)r)', ' or '),
+ '_patsfollow': ('follow(%(val)r)', ' or '),
+ '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
+ 'keyword': ('keyword(%(val)r)', ' or '),
+ 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
+ 'user': ('user(%(val)r)', ' or '),
+ 'rev': ('%(val)s', ' or '),
}
opts = dict(opts)
@@ -266,9 +268,12 @@
if 'branch' in opts and 'only_branch' in opts:
opts['branch'] = opts['branch'] + opts.pop('only_branch')
- follow = opts.get('follow')
+ follow = opts.get('follow') or opts.get('follow_first')
+ followfirst = opts.get('follow_first')
if 'follow' in opts:
del opts['follow']
+ if 'follow_first' in opts:
+ del opts['follow_first']
# pats/include/exclude are passed to match.match() directly in
# _matchfile() revset but walkchangerevs() builds its matcher with
# scmutil.match(). The difference is input pats are globbed on
@@ -310,10 +315,16 @@
opts['rev'] = opts.get('rev', []) + ['_matchfiles(%s)' % matchargs]
else:
if follow:
- if pats:
- opts['_patsfollow'] = list(pats)
+ if followfirst:
+ if pats:
+ opts['_patsfollowfirst'] = list(pats)
+ else:
+ opts['follow_first'] = True
else:
- opts['follow'] = True
+ if pats:
+ opts['_patsfollow'] = list(pats)
+ else:
+ opts['follow'] = True
else:
opts['_patslog'] = list(pats)
--- a/mercurial/revset.py Sat Feb 25 22:11:36 2012 +0100
+++ b/mercurial/revset.py Sat Feb 25 22:11:36 2012 +0100
@@ -465,6 +465,40 @@
return [r for r in subset if r in s]
+def _followfirst(repo, subset, x):
+ # ``followfirst([file])``
+ # Like ``follow([file])`` but follows only the first parent of
+ # every revision or file revision.
+ # i18n: "_followfirst" is a keyword
+ l = getargs(x, 0, 1, _("_followfirst takes no arguments or a filename"))
+ c = repo['.']
+ if l:
+ x = getstring(l[0], _("_followfirst expected a filename"))
+ if x not in c:
+ return []
+ cx = c[x]
+ visit = {}
+ s = set([cx.linkrev()])
+ while True:
+ for p in cx.parents()[:1]:
+ visit[(p.rev(), p.node())] = p
+ if not visit:
+ break
+ cx = visit.pop(max(visit))
+ s.add(cx.rev())
+ else:
+ cl = repo.changelog
+ s = set()
+ visit = [c.rev()]
+ while visit:
+ for prev in cl.parentrevs(visit.pop(0))[:1]:
+ if prev not in s and prev != nodemod.nullrev:
+ visit.append(prev)
+ s.add(prev)
+ s.add(c.rev())
+
+ return [r for r in subset if r in s]
+
def getall(repo, subset, x):
"""``all()``
All changesets, the same as ``0:tip``.
@@ -965,6 +999,7 @@
"filelog": filelog,
"first": first,
"follow": follow,
+ "_followfirst": _followfirst,
"grep": grep,
"head": head,
"heads": heads,
--- a/tests/test-glog.t Sat Feb 25 22:11:36 2012 +0100
+++ b/tests/test-glog.t Sat Feb 25 22:11:36 2012 +0100
@@ -1618,3 +1618,27 @@
nodetag 1
nodetag 0
+Test --follow-first
+
+ $ hg up -q 3
+ $ echo ee > e
+ $ hg ci -Am "add another e" e
+ created new head
+ $ hg merge --tool internal:other 4
+ 0 files updated, 1 files merged, 1 files removed, 0 files unresolved
+ (branch merge, don't forget to commit)
+ $ echo merge > e
+ $ hg ci -m "merge 5 and 4"
+ $ testlog --follow-first
+ ('group', ('func', ('symbol', '_followfirst'), None))
+
+Cannot compare with log --follow-first FILE as it never worked
+
+ $ hg log -G --print-revset --follow-first e
+ ('group', ('group', ('func', ('symbol', '_followfirst'), ('string', 'e'))))
+ $ hg log -G --follow-first e --template '{rev} {desc|firstline}\n'
+ @ 6 merge 5 and 4
+ |\
+ o | 5 add another e
+ | |
+