##// END OF EJS Templates
subrepo: backout bcc6ed0f6c3b
subrepo: backout bcc6ed0f6c3b

File last commit:

r14043:1c1e1232 default
r14049:92db9667 default
Show More
graphmod.py
206 lines | 6.6 KiB | text/x-python | PythonLexer
Dirkjan Ochtman
add graph page to hgweb
r6691 # Revision graph generator for Mercurial
#
# Copyright 2008 Dirkjan Ochtman <dirkjan@ochtman.nl>
# Copyright 2007 Joel Rosdahl <joel@rosdahl.net>
#
Martin Geisler
updated license to be explicit about GPL version 2
r8225 # This software may be used and distributed according to the terms of the
Matt Mackall
Update license to GPLv2+
r10263 # GNU General Public License version 2 or any later version.
Dirkjan Ochtman
add graph page to hgweb
r6691
Peter Arrenbrecht
graphmod/graphlog: make dag walks carry data as type, payload
r8840 """supports walking the history as DAGs suitable for graphical output
The most basic format we use is that of::
(id, type, data, [parentids])
The node and parent ids are arbitrary integers which identify a node in the
context of the graph returned. Type is a constant specifying the node type.
Data depends on type.
"""
from mercurial.node import nullrev
Alexander Solovyov
graphmod: use revsets internally...
r14042 from mercurial.cmdutil import revrange
Peter Arrenbrecht
graphmod/graphlog: make dag walks carry data as type, payload
r8840
CHANGESET = 'C'
Dirkjan Ochtman
add graph page to hgweb
r6691
Alexander Solovyov
graphmod: use revsets internally...
r14042 def dagwalker(repo, revs):
"""cset DAG generator yielding (id, CHANGESET, ctx, [parentids]) tuples
This generator function walks through revisions (which should be ordered
from bigger to lower). It returns a tuple for each node. The node and parent
ids are arbitrary integers which identify a node in the context of the graph
returned.
Peter Arrenbrecht
graphmod/graphlog: move log walks to graphmod
r8836 """
Alexander Solovyov
graphmod: use revsets internally...
r14042 if not revs:
return []
ns = [repo[r].node() for r in revs]
revdag = list(nodes(repo, ns))
cl = repo.changelog
lowestrev = min(revs)
gpcache = {}
leafs = {}
for i, (id, c, ctx, parents) in enumerate(revdag):
mpars = [p.rev() for p in ctx.parents() if
p.rev() != nullrev and p.rev() not in parents]
grandparents = []
for mpar in mpars:
gp = gpcache.get(mpar) or grandparent(cl, lowestrev, revs, mpar)
gpcache[mpar] = gp
if gp is None:
leafs.setdefault(mpar, []).append((i, ctx))
else:
grandparents.append(gp)
if grandparents:
for gp in grandparents:
if gp not in revdag[i][3]:
revdag[i][3].append(gp)
for parent, leafs in leafs.iteritems():
for i, ctx in leafs:
revdag[i][3].append(parent)
return revdag
Peter Arrenbrecht
graphmod/graphlog: move log walks to graphmod
r8836
Peter Arrenbrecht
graphmod/graphlog: extract nodelistwalk
r8837 def nodes(repo, nodes):
Peter Arrenbrecht
graphmod/graphlog: make dag walks carry data as type, payload
r8840 """cset DAG generator yielding (id, CHANGESET, ctx, [parentids]) tuples
This generator function walks the given nodes. It only returns parents
that are in nodes, too.
"""
Peter Arrenbrecht
graphmod/graphlog: extract nodelistwalk
r8837 include = set(nodes)
for node in nodes:
ctx = repo[node]
Nicolas Dumazet
graphmod: safer code when a changeset has two identical parents...
r12951 parents = set([p.rev() for p in ctx.parents() if p.node() in include])
Peter Arrenbrecht
graphmod/graphlog: make dag walks carry data as type, payload
r8840 yield (ctx.rev(), CHANGESET, ctx, sorted(parents))
Peter Arrenbrecht
graphmod/graphlog: extract nodelistwalk
r8837
Peter Arrenbrecht
graphmod/webcommands: use generic DAG walks...
r8842 def colored(dag):
"""annotates a DAG with colored edge information
For each DAG node this function emits tuples::
Dirkjan Ochtman
add graph page to hgweb
r6691
Peter Arrenbrecht
graphmod/webcommands: use generic DAG walks...
r8842 (id, type, data, (col, color), [(col, nextcol, color)])
Dirkjan Ochtman
add graph page to hgweb
r6691
Peter Arrenbrecht
graphmod/webcommands: use generic DAG walks...
r8842 with the following new elements:
Peter Arrenbrecht
graphmod: code cleanup and doc fix
r8835 - Tuple (col, color) with column and color index for the current node
Peter Arrenbrecht
graphmod/webcommands: use generic DAG walks...
r8842 - A list of tuples indicating the edges between the current node and its
parents.
Dirkjan Ochtman
add graph page to hgweb
r6691 """
Peter Arrenbrecht
graphmod: rename a bunch of vars in graph()
r8841 seen = []
Dirkjan Ochtman
add graph page to hgweb
r6691 colors = {}
Peter Arrenbrecht
graphmod: rename a bunch of vars in graph()
r8841 newcolor = 1
Peter Arrenbrecht
graphmod/webcommands: use generic DAG walks...
r8842 for (cur, type, data, parents) in dag:
Dirkjan Ochtman
add graph page to hgweb
r6691
Peter Arrenbrecht
graphmod: rename a bunch of vars in graph()
r8841 # Compute seen and next
if cur not in seen:
seen.append(cur) # new head
colors[cur] = newcolor
newcolor += 1
Dirkjan Ochtman
add graph page to hgweb
r6691
Peter Arrenbrecht
graphmod: rename a bunch of vars in graph()
r8841 col = seen.index(cur)
color = colors.pop(cur)
next = seen[:]
Dirkjan Ochtman
add graph page to hgweb
r6691
Peter Arrenbrecht
graphmod/webcommands: use generic DAG walks...
r8842 # Add parents to next
Dirkjan Ochtman
add graph page to hgweb
r6691 addparents = [p for p in parents if p not in next]
Peter Arrenbrecht
graphmod: rename a bunch of vars in graph()
r8841 next[col:col + 1] = addparents
Dirkjan Ochtman
add graph page to hgweb
r6691
# Set colors for the parents
for i, p in enumerate(addparents):
if not i:
colors[p] = color
else:
Peter Arrenbrecht
graphmod: rename a bunch of vars in graph()
r8841 colors[p] = newcolor
newcolor += 1
Dirkjan Ochtman
add graph page to hgweb
r6691
# Add edges to the graph
edges = []
Peter Arrenbrecht
graphmod: rename a bunch of vars in graph()
r8841 for ecol, eid in enumerate(seen):
if eid in next:
edges.append((ecol, next.index(eid), colors[eid]))
Peter Arrenbrecht
graphmod/webcommands: use generic DAG walks...
r8842 elif eid == cur:
Dirkjan Ochtman
add graph page to hgweb
r6691 for p in parents:
Benoit Boissinot
hgweb/graph: edge should be same color as the destination
r10602 edges.append((ecol, next.index(p), color))
Dirkjan Ochtman
add graph page to hgweb
r6691
# Yield and move on
Peter Arrenbrecht
graphmod/webcommands: use generic DAG walks...
r8842 yield (cur, type, data, (col, color), edges)
Peter Arrenbrecht
graphmod: rename a bunch of vars in graph()
r8841 seen = next
Alexander Solovyov
graphmod: use revsets internally...
r14042
def grandparent(cl, lowestrev, roots, head):
"""Return closest 'root' rev in topological path from 'roots' to 'head'.
Derived from revlog.revlog.nodesbetween, but only returns next rev
of topologically sorted list of all nodes N that satisfy of these
constraints:
1. N is a descendant of some node in 'roots'
2. N is an ancestor of 'head'
3. N is some node in 'roots' or nullrev
Every node is considered to be both a descendant and an ancestor
of itself, so every reachable node in 'roots' and 'head' will be
included in 'nodes'.
"""
ancestors = set()
# Start at the top and keep marking parents until we're done.
revstotag = set([head])
revstotag.discard(nullrev)
llowestrev = max(nullrev, lowestrev)
while revstotag:
r = revstotag.pop()
# A node's revision number represents its place in a
# topologically sorted list of nodes.
if r >= llowestrev:
if r not in ancestors:
# If we are possibly a descendent of one of the roots
# and we haven't already been marked as an ancestor
ancestors.add(r) # Mark as ancestor
# Add non-nullrev parents to list of nodes to tag.
revstotag.update([p for p in cl.parentrevs(r)])
if not ancestors:
return
# Now that we have our set of ancestors, we want to remove any
# roots that are not ancestors.
# If one of the roots was nullrev, everything is included anyway.
if lowestrev > nullrev:
# But, since we weren't, let's recompute the lowest rev to not
# include roots that aren't ancestors.
# Filter out roots that aren't ancestors of heads
_roots = ancestors.intersection(roots)
if not _roots:
return
# Recompute the lowest revision
lowestrev = min(roots)
else:
# We are descending from nullrev, and don't need to care about
# any other roots.
lowestrev = nullrev
_roots = [nullrev]
# The roots are just the descendants.
# Don't start at nullrev since we don't want nullrev in our output list,
# and if nullrev shows up in descedents, empty parents will look like
# they're descendents.
lowestrevisnullrev = (lowestrev == nullrev)
for r in xrange(head - 1, max(lowestrev, -1) - 1, -1):
if lowestrevisnullrev or r in _roots:
pass
elif _roots.issuperset(cl.parentrevs(r)):
# A node is a descendent if either of its parents are
# descendents. (We seeded the dependents list with the roots
# up there, remember?)
_roots.add(r)
else:
continue
if r in ancestors:
# Only include nodes that are both descendents and ancestors.
return r