changeset 16174:0a73c4bd9f47

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.
author Patrick Mezard <patrick@mezard.eu>
date Sat, 25 Feb 2012 22:11:36 +0100
parents 9178d284b880
children 0bb0b9f22cd7
files hgext/graphlog.py mercurial/revset.py tests/test-glog.t
diffstat 3 files changed, 87 insertions(+), 17 deletions(-) [+]
line wrap: on
line diff
--- 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
+  | |
+