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.
--- 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')))