graphmod.py
523 lines
| 16.8 KiB
| text/x-python
|
PythonLexer
/ mercurial / graphmod.py
Dirkjan Ochtman
|
r6691 | # Revision graph generator for Mercurial | ||
# | ||||
# Copyright 2008 Dirkjan Ochtman <dirkjan@ochtman.nl> | ||||
# Copyright 2007 Joel Rosdahl <joel@rosdahl.net> | ||||
# | ||||
Martin Geisler
|
r8225 | # This software may be used and distributed according to the terms of the | ||
Matt Mackall
|
r10263 | # GNU General Public License version 2 or any later version. | ||
Dirkjan Ochtman
|
r6691 | |||
Peter Arrenbrecht
|
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. | ||||
""" | ||||
Matt Harbison
|
r52756 | from __future__ import annotations | ||
Peter Arrenbrecht
|
r8840 | |||
Matt Harbison
|
r52622 | import typing | ||
Gregory Szorc
|
r25951 | from .node import nullrev | ||
Yuya Nishihara
|
r44215 | from .thirdparty import attr | ||
Matt Harbison
|
r52622 | |||
# Force pytype to use the non-vendored package | ||||
if typing.TYPE_CHECKING: | ||||
# noinspection PyPackageRequirements | ||||
import attr | ||||
Laurent Charignon
|
r26003 | from . import ( | ||
Yuya Nishihara
|
r32903 | dagop, | ||
Yuya Nishihara
|
r31023 | smartset, | ||
Laurent Charignon
|
r26003 | util, | ||
) | ||||
Gregory Szorc
|
r25951 | |||
Augie Fackler
|
r43347 | CHANGESET = b'C' | ||
PARENT = b'P' | ||||
GRANDPARENT = b'G' | ||||
MISSINGPARENT = b'M' | ||||
Martijn Pieters
|
r28601 | # Style of line to draw. None signals a line that ends and is removed at this | ||
Martijn Pieters
|
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
|
r43347 | EDGES = {PARENT: b'|', GRANDPARENT: b':', MISSINGPARENT: None} | ||
Dirkjan Ochtman
|
r6691 | |||
Augie Fackler
|
r43346 | |||
Alexander Solovyov
|
r14042 | def dagwalker(repo, revs): | ||
Martijn Pieters
|
r28376 | """cset DAG generator yielding (id, CHANGESET, ctx, [parentinfo]) tuples | ||
Alexander Solovyov
|
r14042 | |||
This generator function walks through revisions (which should be ordered | ||||
Martijn Pieters
|
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
|
r14042 | returned. | ||
Martijn Pieters
|
r28376 | |||
Peter Arrenbrecht
|
r8836 | """ | ||
Alexander Solovyov
|
r14042 | gpcache = {} | ||
Idan Kamara
|
r14087 | for rev in revs: | ||
ctx = repo[rev] | ||||
Martijn Pieters
|
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
|
r44937 | pset = {p.rev() for p in ctx.parents() if p.rev() in revs} | ||
Augie Fackler
|
r43346 | mpars = [ | ||
p.rev() | ||||
for p in ctx.parents() | ||||
if p.rev() != nullrev and p.rev() not in pset | ||||
] | ||||
Martijn Pieters
|
r28376 | parents = [(PARENT, p) for p in sorted(pset)] | ||
Alexander Solovyov
|
r14042 | |||
for mpar in mpars: | ||||
Patrick Mezard
|
r14131 | gp = gpcache.get(mpar) | ||
Alexander Solovyov
|
r14042 | if gp is None: | ||
Yuya Nishihara
|
r26187 | # precompute slow query as we know reachableroots() goes | ||
# through all revs (issue4782) | ||||
Yuya Nishihara
|
r31023 | if not isinstance(revs, smartset.baseset): | ||
revs = smartset.baseset(revs) | ||||
Augie Fackler
|
r43346 | gp = gpcache[mpar] = sorted( | ||
set(dagop.reachableroots(repo, revs, [mpar])) | ||||
) | ||||
Patrick Mezard
|
r14131 | if not gp: | ||
Martijn Pieters
|
r28376 | parents.append((MISSINGPARENT, mpar)) | ||
pset.add(mpar) | ||||
Patrick Mezard
|
r14131 | else: | ||
Martijn Pieters
|
r28376 | parents.extend((GRANDPARENT, g) for g in gp if g not in pset) | ||
pset.update(gp) | ||||
Alexander Solovyov
|
r14042 | |||
Idan Kamara
|
r14087 | yield (ctx.rev(), CHANGESET, ctx, parents) | ||
Peter Arrenbrecht
|
r8836 | |||
Augie Fackler
|
r43346 | |||
Peter Arrenbrecht
|
r8837 | def nodes(repo, nodes): | ||
Peter Arrenbrecht
|
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
|
r8837 | include = set(nodes) | ||
for node in nodes: | ||||
ctx = repo[node] | ||||
Augie Fackler
|
r44937 | parents = { | ||
Augie Fackler
|
r43346 | (PARENT, p.rev()) for p in ctx.parents() if p.node() in include | ||
Augie Fackler
|
r44937 | } | ||
Peter Arrenbrecht
|
r8840 | yield (ctx.rev(), CHANGESET, ctx, sorted(parents)) | ||
Peter Arrenbrecht
|
r8837 | |||
Augie Fackler
|
r43346 | |||
Constantine Linnick
|
r16129 | def colored(dag, repo): | ||
Peter Arrenbrecht
|
r8842 | """annotates a DAG with colored edge information | ||
For each DAG node this function emits tuples:: | ||||
Dirkjan Ochtman
|
r6691 | |||
Peter Arrenbrecht
|
r8842 | (id, type, data, (col, color), [(col, nextcol, color)]) | ||
Dirkjan Ochtman
|
r6691 | |||
Peter Arrenbrecht
|
r8842 | with the following new elements: | ||
Peter Arrenbrecht
|
r8835 | - Tuple (col, color) with column and color index for the current node | ||
Peter Arrenbrecht
|
r8842 | - A list of tuples indicating the edges between the current node and its | ||
parents. | ||||
Dirkjan Ochtman
|
r6691 | """ | ||
Peter Arrenbrecht
|
r8841 | seen = [] | ||
Dirkjan Ochtman
|
r6691 | colors = {} | ||
Peter Arrenbrecht
|
r8841 | newcolor = 1 | ||
Constantine Linnick
|
r16129 | config = {} | ||
Augie Fackler
|
r43347 | for key, val in repo.ui.configitems(b'graph'): | ||
if b'.' in key: | ||||
branch, setting = key.rsplit(b'.', 1) | ||||
Matt Mackall
|
r16131 | # Validation | ||
Augie Fackler
|
r43347 | if setting == b"width" and val.isdigit(): | ||
Patrick Mezard
|
r16138 | config.setdefault(branch, {})[setting] = int(val) | ||
Augie Fackler
|
r43347 | elif setting == b"color" and val.isalnum(): | ||
Matt Mackall
|
r16131 | config.setdefault(branch, {})[setting] = val | ||
Constantine Linnick
|
r16129 | |||
Matt Mackall
|
r16132 | if config: | ||
Patrick Mezard
|
r16138 | getconf = util.lrucachefunc( | ||
Augie Fackler
|
r43346 | lambda rev: config.get(repo[rev].branch(), {}) | ||
) | ||||
Matt Mackall
|
r16132 | else: | ||
Patrick Mezard
|
r16138 | getconf = lambda rev: {} | ||
Constantine Linnick
|
r16129 | |||
Raphaël Gomès
|
r52596 | for cur, type, data, parents in dag: | ||
Peter Arrenbrecht
|
r8841 | # Compute seen and next | ||
if cur not in seen: | ||||
Augie Fackler
|
r43346 | seen.append(cur) # new head | ||
Peter Arrenbrecht
|
r8841 | colors[cur] = newcolor | ||
newcolor += 1 | ||||
Dirkjan Ochtman
|
r6691 | |||
Peter Arrenbrecht
|
r8841 | col = seen.index(cur) | ||
color = colors.pop(cur) | ||||
next = seen[:] | ||||
Dirkjan Ochtman
|
r6691 | |||
Peter Arrenbrecht
|
r8842 | # Add parents to next | ||
Martijn Pieters
|
r28376 | addparents = [p for pt, p in parents if p not in next] | ||
Augie Fackler
|
r43346 | next[col : col + 1] = addparents | ||
Dirkjan Ochtman
|
r6691 | |||
# Set colors for the parents | ||||
for i, p in enumerate(addparents): | ||||
if not i: | ||||
colors[p] = color | ||||
else: | ||||
Peter Arrenbrecht
|
r8841 | colors[p] = newcolor | ||
newcolor += 1 | ||||
Dirkjan Ochtman
|
r6691 | |||
# Add edges to the graph | ||||
edges = [] | ||||
Peter Arrenbrecht
|
r8841 | for ecol, eid in enumerate(seen): | ||
if eid in next: | ||||
Patrick Mezard
|
r16138 | bconf = getconf(eid) | ||
Augie Fackler
|
r43346 | edges.append( | ||
( | ||||
ecol, | ||||
next.index(eid), | ||||
colors[eid], | ||||
Augie Fackler
|
r43347 | bconf.get(b'width', -1), | ||
bconf.get(b'color', b''), | ||||
Augie Fackler
|
r43346 | ) | ||
) | ||||
Peter Arrenbrecht
|
r8842 | elif eid == cur: | ||
Martijn Pieters
|
r28376 | for ptype, p in parents: | ||
Patrick Mezard
|
r16138 | bconf = getconf(p) | ||
Augie Fackler
|
r43346 | edges.append( | ||
( | ||||
ecol, | ||||
next.index(p), | ||||
color, | ||||
Augie Fackler
|
r43347 | bconf.get(b'width', -1), | ||
bconf.get(b'color', b''), | ||||
Augie Fackler
|
r43346 | ) | ||
) | ||||
Dirkjan Ochtman
|
r6691 | |||
# Yield and move on | ||||
Peter Arrenbrecht
|
r8842 | yield (cur, type, data, (col, color), edges) | ||
Peter Arrenbrecht
|
r8841 | seen = next | ||
Alexander Solovyov
|
r14042 | |||
Augie Fackler
|
r43346 | |||
Danny Hooper
|
r33860 | def asciiedges(type, char, state, rev, parents): | ||
Patrick Mezard
|
r17179 | """adds edge info to changelog DAG walk suitable for ascii()""" | ||
Yuya Nishihara
|
r44215 | seen = state.seen | ||
Patrick Mezard
|
r17179 | if rev not in seen: | ||
seen.append(rev) | ||||
nodeidx = seen.index(rev) | ||||
knownparents = [] | ||||
newparents = [] | ||||
Martijn Pieters
|
r28376 | for ptype, parent in parents: | ||
Yuya Nishihara
|
r31552 | if parent == rev: | ||
# self reference (should only be seen in null rev) | ||||
continue | ||||
Patrick Mezard
|
r17179 | if parent in seen: | ||
knownparents.append(parent) | ||||
else: | ||||
newparents.append(parent) | ||||
Yuya Nishihara
|
r44215 | state.edges[parent] = state.styles.get(ptype, b'|') | ||
Patrick Mezard
|
r17179 | |||
ncols = len(seen) | ||||
Danny Hooper
|
r33860 | width = 1 + ncols * 2 | ||
Patrick Mezard
|
r17179 | nextseen = seen[:] | ||
Augie Fackler
|
r43346 | nextseen[nodeidx : nodeidx + 1] = newparents | ||
Yuya Nishihara
|
r31552 | edges = [(nodeidx, nextseen.index(p)) for p in knownparents] | ||
Patrick Mezard
|
r17179 | |||
Martijn Pieters
|
r28998 | seen[:] = nextseen | ||
Patrick Mezard
|
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
|
r33860 | width += 2 | ||
yield (type, char, width, (nodeidx, edges, ncols, nmorecols)) | ||||
Augie Fackler
|
r43347 | char = b'\\' | ||
Patrick Mezard
|
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
|
r33860 | if nmorecols > 0: | ||
width += 2 | ||||
Martijn Pieters
|
r28600 | # remove current node from edge characters, no longer needed | ||
Yuya Nishihara
|
r44215 | state.edges.pop(rev, None) | ||
Danny Hooper
|
r33860 | yield (type, char, width, (nodeidx, edges, ncols, nmorecols)) | ||
Patrick Mezard
|
r17179 | |||
Augie Fackler
|
r43346 | |||
Patrick Mezard
|
r17179 | def _fixlongrightedges(edges): | ||
Raphaël Gomès
|
r52596 | for i, (start, end) in enumerate(edges): | ||
Patrick Mezard
|
r17179 | if end > start: | ||
edges[i] = (start, end + 1) | ||||
Augie Fackler
|
r43346 | |||
def _getnodelineedgestail(echars, idx, pidx, ncols, coldiff, pdiff, fix_tail): | ||||
Martijn Pieters
|
r28600 | if fix_tail and coldiff == pdiff and coldiff != 0: | ||
Patrick Mezard
|
r17179 | # Still going in the same non-vertical direction. | ||
Martijn Pieters
|
r28600 | if coldiff == -1: | ||
start = max(idx + 1, pidx) | ||||
Augie Fackler
|
r43346 | tail = echars[idx * 2 : (start - 1) * 2] | ||
Augie Fackler
|
r43347 | tail.extend([b"/", b" "] * (ncols - start)) | ||
Patrick Mezard
|
r17179 | return tail | ||
else: | ||||
Augie Fackler
|
r43347 | return [b"\\", b" "] * (ncols - idx - 1) | ||
Patrick Mezard
|
r17179 | else: | ||
Augie Fackler
|
r43346 | remainder = ncols - idx - 1 | ||
return echars[-(remainder * 2) :] if remainder > 0 else [] | ||||
Patrick Mezard
|
r17179 | |||
Martijn Pieters
|
r28600 | def _drawedges(echars, edges, nodeline, interline): | ||
Raphaël Gomès
|
r52596 | for start, end in edges: | ||
Patrick Mezard
|
r17179 | if start == end + 1: | ||
Augie Fackler
|
r43347 | interline[2 * end + 1] = b"/" | ||
Patrick Mezard
|
r17179 | elif start == end - 1: | ||
Augie Fackler
|
r43347 | interline[2 * start + 1] = b"\\" | ||
Patrick Mezard
|
r17179 | elif start == end: | ||
Martijn Pieters
|
r28600 | interline[2 * start] = echars[2 * start] | ||
Patrick Mezard
|
r17179 | else: | ||
if 2 * end >= len(nodeline): | ||||
continue | ||||
Augie Fackler
|
r43347 | nodeline[2 * end] = b"+" | ||
Patrick Mezard
|
r17179 | if start > end: | ||
(start, end) = (end, start) | ||||
for i in range(2 * start + 1, 2 * end): | ||||
Augie Fackler
|
r43347 | if nodeline[i] != b"+": | ||
nodeline[i] = b"-" | ||||
Patrick Mezard
|
r17179 | |||
Augie Fackler
|
r43346 | |||
Martijn Pieters
|
r28600 | def _getpaddingline(echars, idx, ncols, edges): | ||
# all edges up to the current node | ||||
Augie Fackler
|
r43346 | line = echars[: idx * 2] | ||
Martijn Pieters
|
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
|
r17179 | # | | | | | | | | | ||
# +---o | | o---+ | ||||
Martijn Pieters
|
r28600 | # | | X | | X | | | ||
Patrick Mezard
|
r17179 | # | |/ / | |/ / | ||
# | | | | | | | ||||
Augie Fackler
|
r43346 | line.extend(echars[idx * 2 : (idx + 1) * 2]) | ||
Patrick Mezard
|
r17179 | else: | ||
Augie Fackler
|
r43347 | line.extend([b' ', b' ']) | ||
Martijn Pieters
|
r28600 | # all edges to the right of the current node | ||
remainder = ncols - idx - 1 | ||||
if remainder > 0: | ||||
Augie Fackler
|
r43346 | line.extend(echars[-(remainder * 2) :]) | ||
Patrick Mezard
|
r17179 | return line | ||
Augie Fackler
|
r43346 | |||
Kyle Lippincott
|
r39322 | def _drawendinglines(lines, extra, edgemap, seen, state): | ||
Martijn Pieters
|
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
|
r44215 | minlines = 3 if not state.graphshorten else 2 | ||
Kyle Lippincott
|
r39322 | while len(lines) < minlines + shift_size: | ||
Martijn Pieters
|
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
|
r43347 | line[first_empty:] = [b' '] * (len(line) - first_empty) | ||
Martijn Pieters
|
r28601 | for i in range(len(positions)): | ||
pos = positions[i] - 1 | ||||
positions[i] = max(pos, targets[i]) | ||||
Augie Fackler
|
r43347 | line[pos] = b'/' if pos > targets[i] else extra[toshift[i]] | ||
Martijn Pieters
|
r28601 | |||
Yuya Nishihara
|
r44215 | map = {1: b'|', 2: b'~'} if not state.graphshorten else {1: b'~'} | ||
Martijn Pieters
|
r28601 | for i, line in enumerate(lines): | ||
if None not in line: | ||||
continue | ||||
Augie Fackler
|
r43347 | line[:] = [c or map.get(i, b' ') for c in line] | ||
Martijn Pieters
|
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
|
r43346 | |||
Yuya Nishihara
|
r44215 | @attr.s | ||
Gregory Szorc
|
r49801 | class asciistate: | ||
Yuya Nishihara
|
r44215 | """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
|
r17179 | |||
Augie Fackler
|
r43346 | |||
John Stiles
|
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() | ||||
""" | ||||
Raphaël Gomès
|
r52596 | for ln, logstr in graph: | ||
Augie Fackler
|
r43347 | ui.write((ln + logstr).rstrip() + b"\n") | ||
John Stiles
|
r38167 | |||
Augie Fackler
|
r43346 | |||
Patrick Mezard
|
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
|
r28600 | |||
Yuya Nishihara
|
r44215 | edgemap, seen = state.edges, state.seen | ||
Martijn Pieters
|
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
|
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
|
r28600 | |||
Patrick Mezard
|
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
|
r43346 | add_padding_line = ( | ||
len(text) > 2 and coldiff == -1 and [x for (x, y) in edges if x + 1 < y] | ||||
) | ||||
Patrick Mezard
|
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
|
r43346 | nodeline = echars[: idx * 2] | ||
Augie Fackler
|
r43347 | nodeline.extend([char, b" "]) | ||
Patrick Mezard
|
r17179 | |||
nodeline.extend( | ||||
Martijn Pieters
|
r28600 | _getnodelineedgestail( | ||
Augie Fackler
|
r43346 | echars, | ||
idx, | ||||
Yuya Nishihara
|
r44215 | state.lastindex, | ||
Augie Fackler
|
r43346 | ncols, | ||
coldiff, | ||||
Yuya Nishihara
|
r44215 | state.lastcoldiff, | ||
Augie Fackler
|
r43346 | fix_nodeline_tail, | ||
) | ||||
) | ||||
Patrick Mezard
|
r17179 | |||
# shift_interline is the line containing the non-vertical | ||||
# edges between this entry and the next | ||||
Augie Fackler
|
r43346 | shift_interline = echars[: idx * 2] | ||
Manuel Jacob
|
r50179 | for i in range(2 + coldiff): | ||
Augie Fackler
|
r43347 | shift_interline.append(b' ') | ||
Martijn Pieters
|
r28600 | count = ncols - idx - 1 | ||
Patrick Mezard
|
r17179 | if coldiff == -1: | ||
Manuel Jacob
|
r50179 | for i in range(count): | ||
Augie Fackler
|
r43347 | shift_interline.extend([b'/', b' ']) | ||
Patrick Mezard
|
r17179 | elif coldiff == 0: | ||
Augie Fackler
|
r43346 | shift_interline.extend(echars[(idx + 1) * 2 : ncols * 2]) | ||
Patrick Mezard
|
r17179 | else: | ||
Manuel Jacob
|
r50179 | for i in range(count): | ||
Augie Fackler
|
r43347 | shift_interline.extend([b'\\', b' ']) | ||
Patrick Mezard
|
r17179 | |||
# draw edges from the current node to its parents | ||||
Martijn Pieters
|
r28600 | _drawedges(echars, edges, nodeline, shift_interline) | ||
Patrick Mezard
|
r17179 | |||
# lines is the list of all graph lines to print | ||||
lines = [nodeline] | ||||
if add_padding_line: | ||||
Martijn Pieters
|
r28600 | lines.append(_getpaddingline(echars, idx, ncols, edges)) | ||
santiagopim
|
r28891 | |||
# If 'graphshorten' config, only draw shift_interline | ||||
# when there is any non vertical flow in graph. | ||||
Yuya Nishihara
|
r44215 | if state.graphshorten: | ||
Gregory Szorc
|
r41677 | if any(c in br'\/' for c in shift_interline if c): | ||
santiagopim
|
r28891 | lines.append(shift_interline) | ||
# Else, no 'graphshorten' config so draw shift_interline. | ||||
else: | ||||
lines.append(shift_interline) | ||||
Patrick Mezard
|
r17179 | |||
# make sure that there are as many graph lines as there are | ||||
# log strings | ||||
Augie Fackler
|
r43346 | extra_interline = echars[: (ncols + coldiff) * 2] | ||
Martijn Pieters
|
r28601 | if len(lines) < len(text): | ||
while len(lines) < len(text): | ||||
lines.append(extra_interline[:]) | ||||
Kyle Lippincott
|
r39322 | _drawendinglines(lines, extra_interline, edgemap, seen, state) | ||
Martijn Pieters
|
r28601 | |||
Patrick Mezard
|
r17179 | while len(text) < len(lines): | ||
Augie Fackler
|
r43347 | text.append(b"") | ||
Patrick Mezard
|
r17179 | |||
# print lines | ||||
indentation_level = max(ncols, ncols + coldiff) | ||||
Augie Fackler
|
r43347 | lines = [ | ||
b"%-*s " % (2 * indentation_level, b"".join(line)) for line in lines | ||||
] | ||||
John Stiles
|
r38167 | outputgraph(ui, zip(lines, text)) | ||
Patrick Mezard
|
r17179 | |||
# ... and start over | ||||
Yuya Nishihara
|
r44215 | state.lastcoldiff = coldiff | ||
state.lastindex = idx | ||||