# HG changeset patch # User Patrick Mezard # Date 2012-07-11 15:13:39 # Node ID 0849d725e2f9d877e7e3fb5152da95c217f5f976 # Parent 8308f6284640f0d8df07bd270e81c9f127e60494 graphlog: extract ascii drawing code into graphmod diff --git a/hgext/graphlog.py b/hgext/graphlog.py --- a/hgext/graphlog.py +++ b/hgext/graphlog.py @@ -21,209 +21,6 @@ cmdtable = {} command = cmdutil.command(cmdtable) testedwith = 'internal' -def asciiedges(type, char, lines, seen, rev, parents): - """adds edge info to changelog DAG walk suitable for ascii()""" - if rev not in seen: - seen.append(rev) - nodeidx = seen.index(rev) - - knownparents = [] - newparents = [] - for parent in parents: - if parent in seen: - knownparents.append(parent) - else: - newparents.append(parent) - - ncols = len(seen) - nextseen = seen[:] - nextseen[nodeidx:nodeidx + 1] = newparents - edges = [(nodeidx, nextseen.index(p)) for p in knownparents] - - 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 - yield (type, char, lines, (nodeidx, edges, ncols, nmorecols)) - char = '\\' - lines = [] - 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 - seen[:] = nextseen - yield (type, char, lines, (nodeidx, edges, ncols, nmorecols)) - -def _fixlongrightedges(edges): - for (i, (start, end)) in enumerate(edges): - if end > start: - edges[i] = (start, end + 1) - -def _getnodelineedgestail( - node_index, p_node_index, n_columns, n_columns_diff, p_diff, fix_tail): - if fix_tail and n_columns_diff == p_diff and n_columns_diff != 0: - # Still going in the same non-vertical direction. - if n_columns_diff == -1: - start = max(node_index + 1, p_node_index) - tail = ["|", " "] * (start - node_index - 1) - tail.extend(["/", " "] * (n_columns - start)) - return tail - else: - return ["\\", " "] * (n_columns - node_index - 1) - else: - return ["|", " "] * (n_columns - node_index - 1) - -def _drawedges(edges, nodeline, interline): - for (start, end) in edges: - if start == end + 1: - interline[2 * end + 1] = "/" - elif start == end - 1: - interline[2 * start + 1] = "\\" - elif start == end: - interline[2 * start] = "|" - else: - if 2 * end >= len(nodeline): - continue - nodeline[2 * end] = "+" - if start > end: - (start, end) = (end, start) - for i in range(2 * start + 1, 2 * end): - if nodeline[i] != "+": - nodeline[i] = "-" - -def _getpaddingline(ni, n_columns, edges): - line = [] - line.extend(["|", " "] * ni) - if (ni, ni - 1) in edges or (ni, ni) in edges: - # (ni, ni - 1) (ni, ni) - # | | | | | | | | - # +---o | | o---+ - # | | c | | c | | - # | |/ / | |/ / - # | | | | | | - c = "|" - else: - c = " " - line.extend([c, " "]) - line.extend(["|", " "] * (n_columns - ni - 1)) - return line - -def asciistate(): - """returns the initial value for the "state" argument to ascii()""" - return [0, 0] - -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 - if coldiff == -1: - # Transform - # - # | | | | | | - # o | | into o---+ - # |X / |/ / - # | | | | - _fixlongrightedges(edges) - - # add_padding_line says whether to rewrite - # - # | | | | | | | | - # | o---+ into | o---+ - # | / / | | | # <--- padding line - # o | | | / / - # o | | - add_padding_line = (len(text) > 2 and coldiff == -1 and - [x for (x, y) in edges if x + 1 < y]) - - # 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) - nodeline = ["|", " "] * idx - nodeline.extend([char, " "]) - - nodeline.extend( - _getnodelineedgestail(idx, state[1], ncols, coldiff, - state[0], fix_nodeline_tail)) - - # shift_interline is the line containing the non-vertical - # edges between this entry and the next - shift_interline = ["|", " "] * idx - if coldiff == -1: - n_spaces = 1 - edge_ch = "/" - elif coldiff == 0: - n_spaces = 2 - edge_ch = "|" - else: - n_spaces = 3 - edge_ch = "\\" - shift_interline.extend(n_spaces * [" "]) - shift_interline.extend([edge_ch, " "] * (ncols - idx - 1)) - - # draw edges from the current node to its parents - _drawedges(edges, nodeline, shift_interline) - - # lines is the list of all graph lines to print - lines = [nodeline] - if add_padding_line: - lines.append(_getpaddingline(idx, ncols, edges)) - lines.append(shift_interline) - - # make sure that there are as many graph lines as there are - # log strings - while len(text) < len(lines): - text.append("") - if len(lines) < len(text): - extra_interline = ["|", " "] * (ncols + coldiff) - while len(lines) < len(text): - lines.append(extra_interline) - - # print lines - indentation_level = max(ncols, ncols + coldiff) - for (line, logstr) in zip(lines, text): - ln = "%-*s %s" % (2 * indentation_level, "".join(line), logstr) - ui.write(ln.rstrip() + '\n') - - # ... and start over - state[0] = coldiff - state[1] = idx - def _checkunsupportedflags(pats, opts): for op in ["newest_first"]: if op in opts and opts[op]: @@ -441,7 +238,7 @@ def getlogrevs(repo, pats, opts): def generate(ui, dag, displayer, showparents, edgefn, getrenamed=None, filematcher=None): - seen, state = [], asciistate() + seen, state = [], graphmod.asciistate() for rev, type, ctx, parents in dag: char = 'o' if ctx.node() in showparents: @@ -465,7 +262,7 @@ def generate(ui, dag, displayer, showpar displayer.flush(rev) edges = edgefn(type, char, lines, seen, rev, parents) for type, char, lines, coldata in edges: - ascii(ui, state, type, char, lines, coldata) + graphmod.ascii(ui, state, type, char, lines, coldata) displayer.close() @command('glog', @@ -516,8 +313,8 @@ def graphlog(ui, repo, *pats, **opts): getrenamed = templatekw.getrenamedfn(repo, endrev=endrev) displayer = show_changeset(ui, repo, opts, buffered=True) showparents = [ctx.node() for ctx in repo[None].parents()] - generate(ui, revdag, displayer, showparents, asciiedges, getrenamed, - filematcher) + generate(ui, revdag, displayer, showparents, graphmod.asciiedges, + getrenamed, filematcher) def graphrevs(repo, nodes, opts): limit = cmdutil.loglimit(opts) @@ -544,7 +341,7 @@ def goutgoing(ui, repo, dest=None, **opt revdag = graphrevs(repo, o, opts) displayer = show_changeset(ui, repo, opts, buffered=True) showparents = [ctx.node() for ctx in repo[None].parents()] - generate(ui, revdag, displayer, showparents, asciiedges) + generate(ui, revdag, displayer, showparents, graphmod.asciiedges) def gincoming(ui, repo, source="default", **opts): """show the incoming changesets alongside an ASCII revision graph @@ -562,7 +359,7 @@ def gincoming(ui, repo, source="default" def display(other, chlist, displayer): revdag = graphrevs(other, chlist, opts) showparents = [ctx.node() for ctx in repo[None].parents()] - generate(ui, revdag, displayer, showparents, asciiedges) + generate(ui, revdag, displayer, showparents, graphmod.asciiedges) hg._incoming(display, subreporecurse, ui, repo, source, opts, buffered=True) diff --git a/mercurial/graphmod.py b/mercurial/graphmod.py --- a/mercurial/graphmod.py +++ b/mercurial/graphmod.py @@ -163,3 +163,206 @@ def grandparent(cl, lowestrev, roots, he pending.update([p for p in cl.parentrevs(r)]) seen.add(r) return sorted(kept) + +def asciiedges(type, char, lines, seen, rev, parents): + """adds edge info to changelog DAG walk suitable for ascii()""" + if rev not in seen: + seen.append(rev) + nodeidx = seen.index(rev) + + knownparents = [] + newparents = [] + for parent in parents: + if parent in seen: + knownparents.append(parent) + else: + newparents.append(parent) + + ncols = len(seen) + nextseen = seen[:] + nextseen[nodeidx:nodeidx + 1] = newparents + edges = [(nodeidx, nextseen.index(p)) for p in knownparents] + + 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 + yield (type, char, lines, (nodeidx, edges, ncols, nmorecols)) + char = '\\' + lines = [] + 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 + seen[:] = nextseen + yield (type, char, lines, (nodeidx, edges, ncols, nmorecols)) + +def _fixlongrightedges(edges): + for (i, (start, end)) in enumerate(edges): + if end > start: + edges[i] = (start, end + 1) + +def _getnodelineedgestail( + node_index, p_node_index, n_columns, n_columns_diff, p_diff, fix_tail): + if fix_tail and n_columns_diff == p_diff and n_columns_diff != 0: + # Still going in the same non-vertical direction. + if n_columns_diff == -1: + start = max(node_index + 1, p_node_index) + tail = ["|", " "] * (start - node_index - 1) + tail.extend(["/", " "] * (n_columns - start)) + return tail + else: + return ["\\", " "] * (n_columns - node_index - 1) + else: + return ["|", " "] * (n_columns - node_index - 1) + +def _drawedges(edges, nodeline, interline): + for (start, end) in edges: + if start == end + 1: + interline[2 * end + 1] = "/" + elif start == end - 1: + interline[2 * start + 1] = "\\" + elif start == end: + interline[2 * start] = "|" + else: + if 2 * end >= len(nodeline): + continue + nodeline[2 * end] = "+" + if start > end: + (start, end) = (end, start) + for i in range(2 * start + 1, 2 * end): + if nodeline[i] != "+": + nodeline[i] = "-" + +def _getpaddingline(ni, n_columns, edges): + line = [] + line.extend(["|", " "] * ni) + if (ni, ni - 1) in edges or (ni, ni) in edges: + # (ni, ni - 1) (ni, ni) + # | | | | | | | | + # +---o | | o---+ + # | | c | | c | | + # | |/ / | |/ / + # | | | | | | + c = "|" + else: + c = " " + line.extend([c, " "]) + line.extend(["|", " "] * (n_columns - ni - 1)) + return line + +def asciistate(): + """returns the initial value for the "state" argument to ascii()""" + return [0, 0] + +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 + if coldiff == -1: + # Transform + # + # | | | | | | + # o | | into o---+ + # |X / |/ / + # | | | | + _fixlongrightedges(edges) + + # add_padding_line says whether to rewrite + # + # | | | | | | | | + # | o---+ into | o---+ + # | / / | | | # <--- padding line + # o | | | / / + # o | | + add_padding_line = (len(text) > 2 and coldiff == -1 and + [x for (x, y) in edges if x + 1 < y]) + + # 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) + nodeline = ["|", " "] * idx + nodeline.extend([char, " "]) + + nodeline.extend( + _getnodelineedgestail(idx, state[1], ncols, coldiff, + state[0], fix_nodeline_tail)) + + # shift_interline is the line containing the non-vertical + # edges between this entry and the next + shift_interline = ["|", " "] * idx + if coldiff == -1: + n_spaces = 1 + edge_ch = "/" + elif coldiff == 0: + n_spaces = 2 + edge_ch = "|" + else: + n_spaces = 3 + edge_ch = "\\" + shift_interline.extend(n_spaces * [" "]) + shift_interline.extend([edge_ch, " "] * (ncols - idx - 1)) + + # draw edges from the current node to its parents + _drawedges(edges, nodeline, shift_interline) + + # lines is the list of all graph lines to print + lines = [nodeline] + if add_padding_line: + lines.append(_getpaddingline(idx, ncols, edges)) + lines.append(shift_interline) + + # make sure that there are as many graph lines as there are + # log strings + while len(text) < len(lines): + text.append("") + if len(lines) < len(text): + extra_interline = ["|", " "] * (ncols + coldiff) + while len(lines) < len(text): + lines.append(extra_interline) + + # print lines + indentation_level = max(ncols, ncols + coldiff) + for (line, logstr) in zip(lines, text): + ln = "%-*s %s" % (2 * indentation_level, "".join(line), logstr) + ui.write(ln.rstrip() + '\n') + + # ... and start over + state[0] = coldiff + state[1] = idx