# HG changeset patch # User Patrick Mezard # Date 1330204296 -3600 # Node ID 0a73c4bd9f477c1422bde5978ffa733b4ec50283 # Parent 9178d284b880efde10eca117cbdeeffd2c6d055c 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. diff -r 9178d284b880 -r 0a73c4bd9f47 hgext/graphlog.py --- 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) diff -r 9178d284b880 -r 0a73c4bd9f47 mercurial/revset.py --- 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, diff -r 9178d284b880 -r 0a73c4bd9f47 tests/test-glog.t --- 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 + | | +