##// END OF EJS Templates
push: indent the some part of the command...
push: indent the some part of the command That code will be put in a loop in the next changeset, pre-indenting make the next change clearer. Differential Revision: https://phab.mercurial-scm.org/D10160

File last commit:

r44937:9d2b2df2 default
r47535:25850879 default
Show More
graphmod.py
517 lines | 16.8 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.
"""
Gregory Szorc
graphmod: use absolute_import
r25951 from __future__ import absolute_import
Peter Arrenbrecht
graphmod/graphlog: make dag walks carry data as type, payload
r8840
Gregory Szorc
graphmod: use absolute_import
r25951 from .node import nullrev
Yuya Nishihara
graphlog: change state dict to attr struct...
r44215 from .thirdparty import attr
Laurent Charignon
revset: remove grandparent by using reachableroots...
r26003 from . import (
Yuya Nishihara
dagop: split module hosting DAG-related algorithms from revset...
r32903 dagop,
Gregory Szorc
global: use pycompat.xrange()...
r38806 pycompat,
Yuya Nishihara
revset: import set classes directly from smartset module...
r31023 smartset,
Laurent Charignon
revset: remove grandparent by using reachableroots...
r26003 util,
)
Gregory Szorc
graphmod: use absolute_import
r25951
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 CHANGESET = b'C'
PARENT = b'P'
GRANDPARENT = b'G'
MISSINGPARENT = b'M'
Martijn Pieters
graphmod: allow edges to end early...
r28601 # Style of line to draw. None signals a line that ends and is removed at this
Martijn Pieters
graphmod: partial edge styling...
r29134 # point. A number prefix means only the last N characters of the current block
# will use that style, the rest will use the PARENT style. Add a - sign
# (so making N negative) and all but the first N characters use that style.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 EDGES = {PARENT: b'|', GRANDPARENT: b':', MISSINGPARENT: None}
Dirkjan Ochtman
add graph page to hgweb
r6691
Augie Fackler
formatting: blacken the codebase...
r43346
Alexander Solovyov
graphmod: use revsets internally...
r14042 def dagwalker(repo, revs):
Martijn Pieters
graphmod: augment the graph to include more information about the edges...
r28376 """cset DAG generator yielding (id, CHANGESET, ctx, [parentinfo]) tuples
Alexander Solovyov
graphmod: use revsets internally...
r14042
This generator function walks through revisions (which should be ordered
Martijn Pieters
graphmod: augment the graph to include more information about the edges...
r28376 from bigger to lower). It returns a tuple for each node.
Each parentinfo entry is a tuple with (edgetype, parentid), where edgetype
is one of PARENT, GRANDPARENT or MISSINGPARENT. The node and parent ids
are arbitrary integers which identify a node in the context of the graph
Alexander Solovyov
graphmod: use revsets internally...
r14042 returned.
Martijn Pieters
graphmod: augment the graph to include more information about the edges...
r28376
Peter Arrenbrecht
graphmod/graphlog: move log walks to graphmod
r8836 """
Alexander Solovyov
graphmod: use revsets internally...
r14042 gpcache = {}
Idan Kamara
graphmod: restore generator nature of dagwalker...
r14087 for rev in revs:
ctx = repo[rev]
Martijn Pieters
graphmod: augment the graph to include more information about the edges...
r28376 # partition into parents in the rev set and missing parents, then
# augment the lists with markers, to inform graph drawing code about
# what kind of edge to draw between nodes.
Augie Fackler
cleanup: run pyupgrade on our source tree to clean up varying things...
r44937 pset = {p.rev() for p in ctx.parents() if p.rev() in revs}
Augie Fackler
formatting: blacken the codebase...
r43346 mpars = [
p.rev()
for p in ctx.parents()
if p.rev() != nullrev and p.rev() not in pset
]
Martijn Pieters
graphmod: augment the graph to include more information about the edges...
r28376 parents = [(PARENT, p) for p in sorted(pset)]
Alexander Solovyov
graphmod: use revsets internally...
r14042
for mpar in mpars:
Patrick Mezard
graphmod: correctly emit nodes with more than 2 predecessors...
r14131 gp = gpcache.get(mpar)
Alexander Solovyov
graphmod: use revsets internally...
r14042 if gp is None:
Yuya Nishihara
graphmod: compute slow revset query once prior to reachableroots (issue4782)...
r26187 # precompute slow query as we know reachableroots() goes
# through all revs (issue4782)
Yuya Nishihara
revset: import set classes directly from smartset module...
r31023 if not isinstance(revs, smartset.baseset):
revs = smartset.baseset(revs)
Augie Fackler
formatting: blacken the codebase...
r43346 gp = gpcache[mpar] = sorted(
set(dagop.reachableroots(repo, revs, [mpar]))
)
Patrick Mezard
graphmod: correctly emit nodes with more than 2 predecessors...
r14131 if not gp:
Martijn Pieters
graphmod: augment the graph to include more information about the edges...
r28376 parents.append((MISSINGPARENT, mpar))
pset.add(mpar)
Patrick Mezard
graphmod: correctly emit nodes with more than 2 predecessors...
r14131 else:
Martijn Pieters
graphmod: augment the graph to include more information about the edges...
r28376 parents.extend((GRANDPARENT, g) for g in gp if g not in pset)
pset.update(gp)
Alexander Solovyov
graphmod: use revsets internally...
r14042
Idan Kamara
graphmod: restore generator nature of dagwalker...
r14087 yield (ctx.rev(), CHANGESET, ctx, parents)
Peter Arrenbrecht
graphmod/graphlog: move log walks to graphmod
r8836
Augie Fackler
formatting: blacken the codebase...
r43346
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]
Augie Fackler
cleanup: run pyupgrade on our source tree to clean up varying things...
r44937 parents = {
Augie Fackler
formatting: blacken the codebase...
r43346 (PARENT, p.rev()) for p in ctx.parents() if p.node() in include
Augie Fackler
cleanup: run pyupgrade on our source tree to clean up varying things...
r44937 }
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
Augie Fackler
formatting: blacken the codebase...
r43346
Constantine Linnick
graph: in hgrc specify line width for main branch...
r16129 def colored(dag, repo):
Peter Arrenbrecht
graphmod/webcommands: use generic DAG walks...
r8842 """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
Constantine Linnick
graph: in hgrc specify line width for main branch...
r16129 config = {}
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 for key, val in repo.ui.configitems(b'graph'):
if b'.' in key:
branch, setting = key.rsplit(b'.', 1)
Matt Mackall
graphmod: rewrite graph config validation...
r16131 # Validation
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if setting == b"width" and val.isdigit():
Patrick Mezard
hgweb: refactor graph customization javascript...
r16138 config.setdefault(branch, {})[setting] = int(val)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif setting == b"color" and val.isalnum():
Matt Mackall
graphmod: rewrite graph config validation...
r16131 config.setdefault(branch, {})[setting] = val
Constantine Linnick
graph: in hgrc specify line width for main branch...
r16129
Matt Mackall
graphmod: add config cache...
r16132 if config:
Patrick Mezard
hgweb: refactor graph customization javascript...
r16138 getconf = util.lrucachefunc(
Augie Fackler
formatting: blacken the codebase...
r43346 lambda rev: config.get(repo[rev].branch(), {})
)
Matt Mackall
graphmod: add config cache...
r16132 else:
Patrick Mezard
hgweb: refactor graph customization javascript...
r16138 getconf = lambda rev: {}
Constantine Linnick
graph: in hgrc specify line width for main branch...
r16129
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:
Augie Fackler
formatting: blacken the codebase...
r43346 seen.append(cur) # new head
Peter Arrenbrecht
graphmod: rename a bunch of vars in graph()
r8841 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
Martijn Pieters
graphmod: augment the graph to include more information about the edges...
r28376 addparents = [p for pt, p in parents if p not in next]
Augie Fackler
formatting: blacken the codebase...
r43346 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:
Patrick Mezard
hgweb: refactor graph customization javascript...
r16138 bconf = getconf(eid)
Augie Fackler
formatting: blacken the codebase...
r43346 edges.append(
(
ecol,
next.index(eid),
colors[eid],
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 bconf.get(b'width', -1),
bconf.get(b'color', b''),
Augie Fackler
formatting: blacken the codebase...
r43346 )
)
Peter Arrenbrecht
graphmod/webcommands: use generic DAG walks...
r8842 elif eid == cur:
Martijn Pieters
graphmod: augment the graph to include more information about the edges...
r28376 for ptype, p in parents:
Patrick Mezard
hgweb: refactor graph customization javascript...
r16138 bconf = getconf(p)
Augie Fackler
formatting: blacken the codebase...
r43346 edges.append(
(
ecol,
next.index(p),
color,
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 bconf.get(b'width', -1),
bconf.get(b'color', b''),
Augie Fackler
formatting: blacken the codebase...
r43346 )
)
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
Augie Fackler
formatting: blacken the codebase...
r43346
Danny Hooper
log: add a "graphwidth" template variable...
r33860 def asciiedges(type, char, state, rev, parents):
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179 """adds edge info to changelog DAG walk suitable for ascii()"""
Yuya Nishihara
graphlog: change state dict to attr struct...
r44215 seen = state.seen
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179 if rev not in seen:
seen.append(rev)
nodeidx = seen.index(rev)
knownparents = []
newparents = []
Martijn Pieters
graphmod: augment the graph to include more information about the edges...
r28376 for ptype, parent in parents:
Yuya Nishihara
graphlog: draw multiple edges towards null node (issue5440)...
r31552 if parent == rev:
# self reference (should only be seen in null rev)
continue
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179 if parent in seen:
knownparents.append(parent)
else:
newparents.append(parent)
Yuya Nishihara
graphlog: change state dict to attr struct...
r44215 state.edges[parent] = state.styles.get(ptype, b'|')
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179
ncols = len(seen)
Danny Hooper
log: add a "graphwidth" template variable...
r33860 width = 1 + ncols * 2
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179 nextseen = seen[:]
Augie Fackler
formatting: blacken the codebase...
r43346 nextseen[nodeidx : nodeidx + 1] = newparents
Yuya Nishihara
graphlog: draw multiple edges towards null node (issue5440)...
r31552 edges = [(nodeidx, nextseen.index(p)) for p in knownparents]
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179
Martijn Pieters
graphmod: fix seen state handling for > 2 parents (issue5174)...
r28998 seen[:] = nextseen
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179 while len(newparents) > 2:
# ascii() only knows how to add or remove a single column between two
# calls. Nodes with more than two parents break this constraint so we
# introduce intermediate expansion lines to grow the active node list
# slowly.
edges.append((nodeidx, nodeidx))
edges.append((nodeidx, nodeidx + 1))
nmorecols = 1
Danny Hooper
log: add a "graphwidth" template variable...
r33860 width += 2
yield (type, char, width, (nodeidx, edges, ncols, nmorecols))
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 char = b'\\'
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179 nodeidx += 1
ncols += 1
edges = []
del newparents[0]
if len(newparents) > 0:
edges.append((nodeidx, nodeidx))
if len(newparents) > 1:
edges.append((nodeidx, nodeidx + 1))
nmorecols = len(nextseen) - ncols
Danny Hooper
log: add a "graphwidth" template variable...
r33860 if nmorecols > 0:
width += 2
Martijn Pieters
graphmod: allow for different styles for different edge types...
r28600 # remove current node from edge characters, no longer needed
Yuya Nishihara
graphlog: change state dict to attr struct...
r44215 state.edges.pop(rev, None)
Danny Hooper
log: add a "graphwidth" template variable...
r33860 yield (type, char, width, (nodeidx, edges, ncols, nmorecols))
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179
Augie Fackler
formatting: blacken the codebase...
r43346
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179 def _fixlongrightedges(edges):
for (i, (start, end)) in enumerate(edges):
if end > start:
edges[i] = (start, end + 1)
Augie Fackler
formatting: blacken the codebase...
r43346
def _getnodelineedgestail(echars, idx, pidx, ncols, coldiff, pdiff, fix_tail):
Martijn Pieters
graphmod: allow for different styles for different edge types...
r28600 if fix_tail and coldiff == pdiff and coldiff != 0:
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179 # Still going in the same non-vertical direction.
Martijn Pieters
graphmod: allow for different styles for different edge types...
r28600 if coldiff == -1:
start = max(idx + 1, pidx)
Augie Fackler
formatting: blacken the codebase...
r43346 tail = echars[idx * 2 : (start - 1) * 2]
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 tail.extend([b"/", b" "] * (ncols - start))
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179 return tail
else:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return [b"\\", b" "] * (ncols - idx - 1)
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179 else:
Augie Fackler
formatting: blacken the codebase...
r43346 remainder = ncols - idx - 1
return echars[-(remainder * 2) :] if remainder > 0 else []
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179
Martijn Pieters
graphmod: allow for different styles for different edge types...
r28600 def _drawedges(echars, edges, nodeline, interline):
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179 for (start, end) in edges:
if start == end + 1:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 interline[2 * end + 1] = b"/"
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179 elif start == end - 1:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 interline[2 * start + 1] = b"\\"
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179 elif start == end:
Martijn Pieters
graphmod: allow for different styles for different edge types...
r28600 interline[2 * start] = echars[2 * start]
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179 else:
if 2 * end >= len(nodeline):
continue
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 nodeline[2 * end] = b"+"
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179 if start > end:
(start, end) = (end, start)
for i in range(2 * start + 1, 2 * end):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if nodeline[i] != b"+":
nodeline[i] = b"-"
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179
Augie Fackler
formatting: blacken the codebase...
r43346
Martijn Pieters
graphmod: allow for different styles for different edge types...
r28600 def _getpaddingline(echars, idx, ncols, edges):
# all edges up to the current node
Augie Fackler
formatting: blacken the codebase...
r43346 line = echars[: idx * 2]
Martijn Pieters
graphmod: allow for different styles for different edge types...
r28600 # an edge for the current node, if there is one
if (idx, idx - 1) in edges or (idx, idx) in edges:
# (idx, idx - 1) (idx, idx)
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179 # | | | | | | | |
# +---o | | o---+
Martijn Pieters
graphmod: allow for different styles for different edge types...
r28600 # | | X | | X | |
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179 # | |/ / | |/ /
# | | | | | |
Augie Fackler
formatting: blacken the codebase...
r43346 line.extend(echars[idx * 2 : (idx + 1) * 2])
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179 else:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 line.extend([b' ', b' '])
Martijn Pieters
graphmod: allow for different styles for different edge types...
r28600 # all edges to the right of the current node
remainder = ncols - idx - 1
if remainder > 0:
Augie Fackler
formatting: blacken the codebase...
r43346 line.extend(echars[-(remainder * 2) :])
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179 return line
Augie Fackler
formatting: blacken the codebase...
r43346
Kyle Lippincott
log: respect graphshorten on terminal nodes (collapsing o-~ to just o~)...
r39322 def _drawendinglines(lines, extra, edgemap, seen, state):
Martijn Pieters
graphmod: allow edges to end early...
r28601 """Draw ending lines for missing parent edges
None indicates an edge that ends at between this node and the next
Replace with a short line ending in ~ and add / lines to any edges to
the right.
"""
if None not in edgemap.values():
return
# Check for more edges to the right of our ending edges.
# We need enough space to draw adjustment lines for these.
edgechars = extra[::2]
while edgechars and edgechars[-1] is None:
edgechars.pop()
shift_size = max((edgechars.count(None) * 2) - 1, 0)
Yuya Nishihara
graphlog: change state dict to attr struct...
r44215 minlines = 3 if not state.graphshorten else 2
Kyle Lippincott
log: respect graphshorten on terminal nodes (collapsing o-~ to just o~)...
r39322 while len(lines) < minlines + shift_size:
Martijn Pieters
graphmod: allow edges to end early...
r28601 lines.append(extra[:])
if shift_size:
empties = []
toshift = []
first_empty = extra.index(None)
for i, c in enumerate(extra[first_empty::2], first_empty // 2):
if c is None:
empties.append(i * 2)
else:
toshift.append(i * 2)
targets = list(range(first_empty, first_empty + len(toshift) * 2, 2))
positions = toshift[:]
for line in lines[-shift_size:]:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 line[first_empty:] = [b' '] * (len(line) - first_empty)
Martijn Pieters
graphmod: allow edges to end early...
r28601 for i in range(len(positions)):
pos = positions[i] - 1
positions[i] = max(pos, targets[i])
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 line[pos] = b'/' if pos > targets[i] else extra[toshift[i]]
Martijn Pieters
graphmod: allow edges to end early...
r28601
Yuya Nishihara
graphlog: change state dict to attr struct...
r44215 map = {1: b'|', 2: b'~'} if not state.graphshorten else {1: b'~'}
Martijn Pieters
graphmod: allow edges to end early...
r28601 for i, line in enumerate(lines):
if None not in line:
continue
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 line[:] = [c or map.get(i, b' ') for c in line]
Martijn Pieters
graphmod: allow edges to end early...
r28601
# remove edges that ended
remove = [p for p, c in edgemap.items() if c is None]
for parent in remove:
del edgemap[parent]
seen.remove(parent)
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
graphlog: change state dict to attr struct...
r44215 @attr.s
class asciistate(object):
"""State of ascii() graph rendering"""
seen = attr.ib(init=False, default=attr.Factory(list))
edges = attr.ib(init=False, default=attr.Factory(dict))
lastcoldiff = attr.ib(init=False, default=0)
lastindex = attr.ib(init=False, default=0)
styles = attr.ib(init=False, default=attr.Factory(EDGES.copy))
graphshorten = attr.ib(init=False, default=False)
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179
Augie Fackler
formatting: blacken the codebase...
r43346
John Stiles
graph: add outputgraph() function, called by ascii() to print...
r38167 def outputgraph(ui, graph):
"""outputs an ASCII graph of a DAG
this is a helper function for 'ascii' below.
takes the following arguments:
- ui to write to
- graph data: list of { graph nodes/edges, text }
this function can be monkey-patched by extensions to alter graph display
without needing to mimic all of the edge-fixup logic in ascii()
"""
for (ln, logstr) in graph:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui.write((ln + logstr).rstrip() + b"\n")
John Stiles
graph: add outputgraph() function, called by ascii() to print...
r38167
Augie Fackler
formatting: blacken the codebase...
r43346
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179 def ascii(ui, state, type, char, text, coldata):
"""prints an ASCII graph of the DAG
takes the following arguments (one call per node in the graph):
- ui to write to
- Somewhere to keep the needed state in (init to asciistate())
- Column of the current node in the set of ongoing edges.
- Type indicator of node data, usually 'C' for changesets.
- Payload: (char, lines):
- Character to use as node's symbol.
- List of lines to display as the node's text.
- Edges; a list of (col, next_col) indicating the edges between
the current node and its parents.
- Number of columns (ongoing edges) in the current revision.
- The difference between the number of columns (ongoing edges)
in the next revision and the number of columns (ongoing edges)
in the current revision. That is: -1 means one column removed;
0 means no columns added or removed; 1 means one column added.
"""
idx, edges, ncols, coldiff = coldata
assert -2 < coldiff < 2
Martijn Pieters
graphmod: allow for different styles for different edge types...
r28600
Yuya Nishihara
graphlog: change state dict to attr struct...
r44215 edgemap, seen = state.edges, state.seen
Martijn Pieters
graphmod: allow for different styles for different edge types...
r28600 # Be tolerant of history issues; make sure we have at least ncols + coldiff
# elements to work with. See test-glog.t for broken history test cases.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 echars = [c for p in seen for c in (edgemap.get(p, b'|'), b' ')]
echars.extend((b'|', b' ') * max(ncols + coldiff - len(seen), 0))
Martijn Pieters
graphmod: allow for different styles for different edge types...
r28600
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179 if coldiff == -1:
# Transform
#
# | | | | | |
# o | | into o---+
# |X / |/ /
# | | | |
_fixlongrightedges(edges)
# add_padding_line says whether to rewrite
#
# | | | | | | | |
# | o---+ into | o---+
# | / / | | | # <--- padding line
# o | | | / /
# o | |
Augie Fackler
formatting: blacken the codebase...
r43346 add_padding_line = (
len(text) > 2 and coldiff == -1 and [x for (x, y) in edges if x + 1 < y]
)
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179
# fix_nodeline_tail says whether to rewrite
#
# | | o | | | | o | |
# | | |/ / | | |/ /
# | o | | into | o / / # <--- fixed nodeline tail
# | |/ / | |/ /
# o | | o | |
fix_nodeline_tail = len(text) <= 2 and not add_padding_line
# nodeline is the line containing the node character (typically o)
Augie Fackler
formatting: blacken the codebase...
r43346 nodeline = echars[: idx * 2]
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 nodeline.extend([char, b" "])
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179
nodeline.extend(
Martijn Pieters
graphmod: allow for different styles for different edge types...
r28600 _getnodelineedgestail(
Augie Fackler
formatting: blacken the codebase...
r43346 echars,
idx,
Yuya Nishihara
graphlog: change state dict to attr struct...
r44215 state.lastindex,
Augie Fackler
formatting: blacken the codebase...
r43346 ncols,
coldiff,
Yuya Nishihara
graphlog: change state dict to attr struct...
r44215 state.lastcoldiff,
Augie Fackler
formatting: blacken the codebase...
r43346 fix_nodeline_tail,
)
)
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179
# shift_interline is the line containing the non-vertical
# edges between this entry and the next
Augie Fackler
formatting: blacken the codebase...
r43346 shift_interline = echars[: idx * 2]
Gregory Szorc
global: use pycompat.xrange()...
r38806 for i in pycompat.xrange(2 + coldiff):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 shift_interline.append(b' ')
Martijn Pieters
graphmod: allow for different styles for different edge types...
r28600 count = ncols - idx - 1
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179 if coldiff == -1:
Gregory Szorc
global: use pycompat.xrange()...
r38806 for i in pycompat.xrange(count):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 shift_interline.extend([b'/', b' '])
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179 elif coldiff == 0:
Augie Fackler
formatting: blacken the codebase...
r43346 shift_interline.extend(echars[(idx + 1) * 2 : ncols * 2])
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179 else:
Gregory Szorc
global: use pycompat.xrange()...
r38806 for i in pycompat.xrange(count):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 shift_interline.extend([b'\\', b' '])
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179
# draw edges from the current node to its parents
Martijn Pieters
graphmod: allow for different styles for different edge types...
r28600 _drawedges(echars, edges, nodeline, shift_interline)
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179
# lines is the list of all graph lines to print
lines = [nodeline]
if add_padding_line:
Martijn Pieters
graphmod: allow for different styles for different edge types...
r28600 lines.append(_getpaddingline(echars, idx, ncols, edges))
santiagopim
graphmod: shorten graph...
r28891
# If 'graphshorten' config, only draw shift_interline
# when there is any non vertical flow in graph.
Yuya Nishihara
graphlog: change state dict to attr struct...
r44215 if state.graphshorten:
Gregory Szorc
graphmod: use raw string...
r41677 if any(c in br'\/' for c in shift_interline if c):
santiagopim
graphmod: shorten graph...
r28891 lines.append(shift_interline)
# Else, no 'graphshorten' config so draw shift_interline.
else:
lines.append(shift_interline)
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179
# make sure that there are as many graph lines as there are
# log strings
Augie Fackler
formatting: blacken the codebase...
r43346 extra_interline = echars[: (ncols + coldiff) * 2]
Martijn Pieters
graphmod: allow edges to end early...
r28601 if len(lines) < len(text):
while len(lines) < len(text):
lines.append(extra_interline[:])
Kyle Lippincott
log: respect graphshorten on terminal nodes (collapsing o-~ to just o~)...
r39322 _drawendinglines(lines, extra_interline, edgemap, seen, state)
Martijn Pieters
graphmod: allow edges to end early...
r28601
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179 while len(text) < len(lines):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 text.append(b"")
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179
# print lines
indentation_level = max(ncols, ncols + coldiff)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 lines = [
b"%-*s " % (2 * indentation_level, b"".join(line)) for line in lines
]
John Stiles
graph: add outputgraph() function, called by ascii() to print...
r38167 outputgraph(ui, zip(lines, text))
Patrick Mezard
graphlog: extract ascii drawing code into graphmod
r17179
# ... and start over
Yuya Nishihara
graphlog: change state dict to attr struct...
r44215 state.lastcoldiff = coldiff
state.lastindex = idx