changeset 16409:2cbd7dd0cc1f

graphlog: fix --follow-first --rev combinations This solves a similar problem than the previous --follow/--rev patch. This time we need changelog.ancestors()/descendants() filtering on first parent. Duplicating the code looked better than introducing keyword arguments. Besides, the ancestors() version was already implemented in follow() revset.
author Patrick Mezard <patrick@mezard.eu>
date Wed, 11 Apr 2012 11:25:34 +0200
parents d74099ac2ac1
children 80b3d574881f
files hgext/graphlog.py mercurial/revset.py tests/test-glog.t
diffstat 3 files changed, 95 insertions(+), 26 deletions(-) [+]
line wrap: on
line diff
--- a/hgext/graphlog.py	Wed Apr 11 11:22:40 2012 +0200
+++ b/hgext/graphlog.py	Wed Apr 11 11:25:34 2012 +0200
@@ -279,11 +279,12 @@
     the files to be detailed when displaying the revision.
     """
     opt2revset = {
-        'follow_first':     ('_followfirst()', None),
         'no_merges':        ('not merge()', None),
         'only_merges':      ('merge()', None),
         '_ancestors':       ('ancestors(%(val)s)', None),
+        '_fancestors':      ('_firstancestors(%(val)s)', None),
         '_descendants':     ('descendants(%(val)s)', None),
+        '_fdescendants':    ('_firstdescendants(%(val)s)', None),
         '_matchfiles':      ('_matchfiles(%(val)s)', None),
         'date':             ('date(%(val)r)', None),
         'branch':           ('branch(%(val)r)', ' or '),
@@ -299,8 +300,6 @@
     # follow or not follow?
     follow = opts.get('follow') or opts.get('follow_first')
     followfirst = opts.get('follow_first')
-    if 'follow_first' in opts:
-        del opts['follow_first']
     # --follow with FILE behaviour depends on revs...
     startrev = revs[0]
     followdescendants = len(revs) > 1 and revs[0] < revs[1]
@@ -356,7 +355,10 @@
                 if pats:
                     opts['_patsfollowfirst'] = list(pats)
                 else:
-                    opts['follow_first'] = True
+                    if followdescendants:
+                        opts['_fdescendants'] = str(startrev)
+                    else:
+                        opts['_fancestors'] = str(startrev)
             else:
                 if pats:
                     opts['_patsfollow'] = list(pats)
--- a/mercurial/revset.py	Wed Apr 11 11:22:40 2012 +0200
+++ b/mercurial/revset.py	Wed Apr 11 11:25:34 2012 +0200
@@ -13,6 +13,39 @@
 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 = list(revs)
+    seen = set([nodemod.nullrev])
+    while visit:
+        for parent in cl.parentrevs(visit.pop(0))[: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)
+    if first == nodemod.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 != nodemod.nullrev and x in seen:
+                seen.add(i)
+                yield i
+                break
+
 elements = {
     "(": (20, ("group", 1, ")"), ("func", 1, ")")),
     "~": (18, None, ("ancestor", 18)),
@@ -203,15 +236,23 @@
 
     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.
     """
-    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]
+    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``
@@ -395,15 +436,23 @@
             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.
     """
-    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]
+    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 draft(repo, subset, x):
     """``draft()``
@@ -454,16 +503,7 @@
         else:
             return []
     else:
-        cut = followfirst and 1 or None
-        cl = repo.changelog
-        s = set()
-        visit = [c.rev()]
-        while visit:
-            for prev in cl.parentrevs(visit.pop(0))[:cut]:
-                if prev not in s and prev != nodemod.nullrev:
-                    visit.append(prev)
-                    s.add(prev)
-        s.add(c.rev())
+        s = set(_revancestors(repo, [c.rev()], followfirst)) | set([c.rev()])
 
     return [r for r in subset if r in s]
 
@@ -1055,6 +1095,7 @@
     "all": getall,
     "ancestor": ancestor,
     "ancestors": ancestors,
+    "_firstancestors": _firstancestors,
     "author": author,
     "bisect": bisect,
     "bisected": bisected,
@@ -1066,6 +1107,7 @@
     "date": date,
     "desc": desc,
     "descendants": descendants,
+    "_firstdescendants": _firstdescendants,
     "draft": draft,
     "file": hasfile,
     "filelog": filelog,
--- a/tests/test-glog.t	Wed Apr 11 11:22:40 2012 +0200
+++ b/tests/test-glog.t	Wed Apr 11 11:25:34 2012 +0200
@@ -1746,8 +1746,8 @@
   []
   (group
     (func
-      ('symbol', '_followfirst')
-      None))
+      ('symbol', '_firstancestors')
+      ('symbol', '6')))
 
 Cannot compare with log --follow-first FILE as it never worked
 
@@ -1967,6 +1967,23 @@
   +nodetag 6
   [1]
 
+Test --follow-first and forward --rev
+
+  $ testlog --follow-first -r6 -r8 -r5 -r7 -r4
+  ['6', '8', '5', '7', '4']
+  (group
+    (func
+      ('symbol', '_firstdescendants')
+      ('symbol', '6')))
+  --- log.nodes	* (glob)
+  +++ glog.nodes	* (glob)
+  @@ -1,3 +1,3 @@
+  -nodetag 6
+   nodetag 8
+   nodetag 7
+  +nodetag 6
+  [1]
+
 Test --follow and backward --rev
 
   $ testlog --follow -r6 -r5 -r7 -r8 -r4
@@ -1976,3 +1993,11 @@
       ('symbol', 'ancestors')
       ('symbol', '6')))
 
+Test --follow-first and backward --rev
+
+  $ testlog --follow-first -r6 -r5 -r7 -r8 -r4
+  ['6', '5', '7', '8', '4']
+  (group
+    (func
+      ('symbol', '_firstancestors')
+      ('symbol', '6')))