[PATCH] graphmod: allow for different styles for different edge types

Martijn Pieters mj at zopatista.com
Sat Mar 19 18:51:59 UTC 2016


# HG changeset patch
# User Martijn Pieters <mjpieters at fb.com>
# Date 1458413415 25200
#      Sat Mar 19 11:50:15 2016 -0700
# Node ID dcbdad4f3c3f518a68946e3b0ba831f78691ff87
# Parent  a3f3fdac84332e743f9fb930e4bc9ef3bb24e9f8
graphmod: allow for different styles for different edge types

Rather than draw all edges as solid lines, allow for using different styles for
different edge types. For example you could use dotted lines for edges that
do not connect to a parent, and dashed lines when connecting to a grandparent
(implying missing nodes in between).

For example, setting the following configuration:

    [ui]
    graphstyle.grandparent = :
    graphstyle.missing = .

would result in a graph like this:

  o    changeset:   32:d06dffa21a31
  |\   parent:      27:886ed638191b
  | :  parent:      31:621d83e11f67
  | :
  o :  changeset:   31:621d83e11f67
  |\:  parent:      21:d42a756af44d
  | :  parent:      30:6e11cd4b648f
  | :
  o :    changeset:   30:6e11cd4b648f
  |\ \   parent:      28:44ecd0b9ae99
  | . :  parent:      29:cd9bb2be7593
  | . :
  o . :    changeset:   28:44ecd0b9ae99
  |\ \ \   parent:      1:6db2ef61d156
  | . . :  parent:      26:7f25b6c2f0b9
  | . . :
  o . . :    changeset:   26:7f25b6c2f0b9
  |\ \ \ \   parent:      18:1aa84d96232a
  | | . . :  parent:      25:91da8ed57247
  | | . . :
  | o-----+  changeset:   25:91da8ed57247
  | | . . :  parent:      21:d42a756af44d
  | | . . :  parent:      24:a9c19a3d96b7
  | | . . :
  | o . . :    changeset:   24:a9c19a3d96b7
  | |\ \ \ \   parent:      0:e6eb3150255d
  | | . . . :  parent:      23:a01cddf0766d
  | | . . . :
  | o---+ . :  changeset:   23:a01cddf0766d
  | | . . . :  parent:      1:6db2ef61d156
  | | . . . :  parent:      22:e0d9cccacb5d
  | | . . . :
  | o-------+  changeset:   22:e0d9cccacb5d
  | . . . . :  parent:      18:1aa84d96232a
  |/ / / / /   parent:      21:d42a756af44d
  | . . . :
  | . . . o    changeset:   21:d42a756af44d
  | . . . |\   parent:      19:31ddc2c1573b
  | . . . | |  parent:      20:d30ed6450e32
  | . . . | |
  +-+-------o  changeset:   20:d30ed6450e32
  | . . . |    parent:      0:e6eb3150255d
  | . . . |    parent:      18:1aa84d96232a
  | . . . |
  | . . . o    changeset:   19:31ddc2c1573b
  | . . . .\   parent:      15:1dda3f72782d
  | . . . . |  parent:      17:44765d7c06e0
  | . . . . |
  o---+---+ |  changeset:   18:1aa84d96232a
    . . . . |  parent:      1:6db2ef61d156
   / / / / /   parent:      15:1dda3f72782d
  . . . . .


Edge styles can be altered by setting the following one-character config options::

    [ui]
    graphstyle.parent = |
    graphstyle.grandparent = :
    graphstyle.missing = .

The default configuration leaves all 3 types set to |, leaving graph styles
unaffected.

This is part of the work towards moving smartlog upstream; currently smartlog
injects extra nodes into the graph to indicate grandparent relationships (nodes
elided).

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -2218,6 +2218,15 @@
                  filematcher=None):
     formatnode = _graphnodeformatter(ui, displayer)
     state = graphmod.asciistate()
+    styles = state['styles']
+    edgetypes = {
+        'parent': graphmod.PARENT,
+        'grandparent': graphmod.GRANDPARENT,
+        'missing': graphmod.MISSINGPARENT
+    }
+    for name, key in edgetypes.items():
+        # experimental config: ui.graphstyle.*
+        styles[key] = ui.config('ui', 'graphstyle.%s' % name, styles[key])
     for rev, type, ctx, parents in dag:
         char = formatnode(repo, ctx)
         copies = None
diff --git a/mercurial/graphmod.py b/mercurial/graphmod.py
--- a/mercurial/graphmod.py
+++ b/mercurial/graphmod.py
@@ -31,6 +31,7 @@
 PARENT = 'P'
 GRANDPARENT = 'G'
 MISSINGPARENT = 'M'
+EDGES = {PARENT: '|', GRANDPARENT: '|', MISSINGPARENT: '|'}
 
 def groupbranchiter(revs, parentsfunc, firstbranch=()):
     """Yield revisions from heads to roots one (topo) branch at a time.
@@ -390,11 +391,13 @@
             knownparents.append(parent)
         else:
             newparents.append(parent)
+            state['edges'][parent] = state['styles'].get(ptype, '|')
 
     ncols = len(seen)
     nextseen = seen[:]
     nextseen[nodeidx:nodeidx + 1] = newparents
-    edges = [(nodeidx, nextseen.index(p)) for p in knownparents if p != nullrev]
+    edges = [(nodeidx, nextseen.index(p))
+             for p in knownparents if p != nullrev]
 
     while len(newparents) > 2:
         # ascii() only knows how to add or remove a single column between two
@@ -418,6 +421,8 @@
         edges.append((nodeidx, nodeidx + 1))
     nmorecols = len(nextseen) - ncols
     seen[:] = nextseen
+    # remove current node from edge characters, no longer needed
+    state['edges'].pop(rev, None)
     yield (type, char, lines, (nodeidx, edges, ncols, nmorecols))
 
 def _fixlongrightedges(edges):
@@ -426,27 +431,28 @@
             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:
+        echars, idx, pidx, ncols, coldiff, pdiff, fix_tail):
+    if fix_tail and coldiff == pdiff and coldiff != 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))
+        if coldiff == -1:
+            start = max(idx + 1, pidx)
+            tail = echars[idx * 2:(start - 1) * 2]
+            tail.extend(["/", " "] * (ncols - start))
             return tail
         else:
-            return ["\\", " "] * (n_columns - node_index - 1)
+            return ["\\", " "] * (ncols - idx - 1)
     else:
-        return ["|", " "] * (n_columns - node_index - 1)
+        remainder = (ncols - idx - 1)
+        return echars[-(remainder * 2):] if remainder > 0 else []
 
-def _drawedges(edges, nodeline, interline):
+def _drawedges(echars, 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] = "|"
+            interline[2 * start] = echars[2 * start]
         else:
             if 2 * end >= len(nodeline):
                 continue
@@ -457,26 +463,35 @@
                 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)
+def _getpaddingline(echars, idx, ncols, edges):
+    # all edges up to the current node
+    line = echars[:idx * 2]
+    # 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)
         # | | | |           | | | |
         # +---o |           | o---+
-        # | | c |           | c | |
+        # | | X |           | X | |
         # | |/ /            | |/ /
         # | | |             | | |
-        c = "|"
+        line.extend(echars[idx * 2:(idx + 1) * 2])
     else:
-        c = " "
-    line.extend([c, " "])
-    line.extend(["|", " "] * (n_columns - ni - 1))
+        line.extend('  ')
+    # all edges to the right of the current node
+    remainder = ncols - idx - 1
+    if remainder > 0:
+        line.extend(echars[-(remainder * 2):])
     return line
 
 def asciistate():
     """returns the initial value for the "state" argument to ascii()"""
-    return {'seen': [], 'lastcoldiff': 0, 'lastindex': 0}
+    return {
+        'seen': [],
+        'edges': {},
+        'lastcoldiff': 0,
+        'lastindex': 0,
+        'styles': EDGES.copy(),
+    }
 
 def ascii(ui, state, type, char, text, coldata):
     """prints an ASCII graph of the DAG
@@ -498,9 +513,15 @@
         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
+
+    edgemap, seen = state['edges'], state['seen']
+    # 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.
+    echars = [c for p in seen for c in (edgemap.get(p, '|'), ' ')]
+    echars.extend(('|', ' ') * max(ncols + coldiff - len(seen), 0))
+
     if coldiff == -1:
         # Transform
         #
@@ -530,35 +551,33 @@
     fix_nodeline_tail = len(text) <= 2 and not add_padding_line
 
     # nodeline is the line containing the node character (typically o)
-    nodeline = ["|", " "] * idx
+    nodeline = echars[:idx * 2]
     nodeline.extend([char, " "])
 
     nodeline.extend(
-        _getnodelineedgestail(idx, state['lastindex'], ncols, coldiff,
-                              state['lastcoldiff'], fix_nodeline_tail))
+        _getnodelineedgestail(
+            echars, idx, state['lastindex'], ncols, coldiff,
+            state['lastcoldiff'], fix_nodeline_tail))
 
     # shift_interline is the line containing the non-vertical
     # edges between this entry and the next
-    shift_interline = ["|", " "] * idx
+    shift_interline = echars[:idx * 2]
+    shift_interline.extend(' ' * (2 + coldiff))
+    count = ncols - idx - 1
     if coldiff == -1:
-        n_spaces = 1
-        edge_ch = "/"
+        shift_interline.extend('/ ' * count)
     elif coldiff == 0:
-        n_spaces = 2
-        edge_ch = "|"
+        shift_interline.extend(echars[(idx + 1) * 2:ncols * 2])
     else:
-        n_spaces = 3
-        edge_ch = "\\"
-    shift_interline.extend(n_spaces * [" "])
-    shift_interline.extend([edge_ch, " "] * (ncols - idx - 1))
+        shift_interline.extend(r'\ ' * count)
 
     # draw edges from the current node to its parents
-    _drawedges(edges, nodeline, shift_interline)
+    _drawedges(echars, 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(_getpaddingline(echars, idx, ncols, edges))
     lines.append(shift_interline)
 
     # make sure that there are as many graph lines as there are
@@ -566,7 +585,7 @@
     while len(text) < len(lines):
         text.append("")
     if len(lines) < len(text):
-        extra_interline = ["|", " "] * (ncols + coldiff)
+        extra_interline = echars[:(ncols + coldiff) * 2]
         while len(lines) < len(text):
             lines.append(extra_interline)
 
diff --git a/tests/test-glog.t b/tests/test-glog.t
--- a/tests/test-glog.t
+++ b/tests/test-glog.t
@@ -2415,3 +2415,215 @@
   |
 
   $ cd ..
+
+
+change graph edge styling
+
+  $ cd repo
+  $ cat << EOF >> $HGRCPATH
+  > [ui]
+  > graphstyle.parent = |
+  > graphstyle.grandparent = :
+  > graphstyle.missing = .
+  > EOF
+  $ hg log -G -r 'file("a")' -m
+  @  changeset:   36:08a19a744424
+  :  branch:      branch
+  :  tag:         tip
+  :  parent:      35:9159c3644c5e
+  :  parent:      35:9159c3644c5e
+  :  user:        test
+  :  date:        Thu Jan 01 00:00:36 1970 +0000
+  :  summary:     (36) buggy merge: identical parents
+  :
+  o    changeset:   32:d06dffa21a31
+  |\   parent:      27:886ed638191b
+  | :  parent:      31:621d83e11f67
+  | :  user:        test
+  | :  date:        Thu Jan 01 00:00:32 1970 +0000
+  | :  summary:     (32) expand
+  | :
+  o :  changeset:   31:621d83e11f67
+  |\:  parent:      21:d42a756af44d
+  | :  parent:      30:6e11cd4b648f
+  | :  user:        test
+  | :  date:        Thu Jan 01 00:00:31 1970 +0000
+  | :  summary:     (31) expand
+  | :
+  o :    changeset:   30:6e11cd4b648f
+  |\ \   parent:      28:44ecd0b9ae99
+  | . :  parent:      29:cd9bb2be7593
+  | . :  user:        test
+  | . :  date:        Thu Jan 01 00:00:30 1970 +0000
+  | . :  summary:     (30) expand
+  | . :
+  o . :    changeset:   28:44ecd0b9ae99
+  |\ \ \   parent:      1:6db2ef61d156
+  | . . :  parent:      26:7f25b6c2f0b9
+  | . . :  user:        test
+  | . . :  date:        Thu Jan 01 00:00:28 1970 +0000
+  | . . :  summary:     (28) merge zero known
+  | . . :
+  o . . :    changeset:   26:7f25b6c2f0b9
+  |\ \ \ \   parent:      18:1aa84d96232a
+  | | . . :  parent:      25:91da8ed57247
+  | | . . :  user:        test
+  | | . . :  date:        Thu Jan 01 00:00:26 1970 +0000
+  | | . . :  summary:     (26) merge one known; far right
+  | | . . :
+  | o-----+  changeset:   25:91da8ed57247
+  | | . . :  parent:      21:d42a756af44d
+  | | . . :  parent:      24:a9c19a3d96b7
+  | | . . :  user:        test
+  | | . . :  date:        Thu Jan 01 00:00:25 1970 +0000
+  | | . . :  summary:     (25) merge one known; far left
+  | | . . :
+  | o . . :    changeset:   24:a9c19a3d96b7
+  | |\ \ \ \   parent:      0:e6eb3150255d
+  | | . . . :  parent:      23:a01cddf0766d
+  | | . . . :  user:        test
+  | | . . . :  date:        Thu Jan 01 00:00:24 1970 +0000
+  | | . . . :  summary:     (24) merge one known; immediate right
+  | | . . . :
+  | o---+ . :  changeset:   23:a01cddf0766d
+  | | . . . :  parent:      1:6db2ef61d156
+  | | . . . :  parent:      22:e0d9cccacb5d
+  | | . . . :  user:        test
+  | | . . . :  date:        Thu Jan 01 00:00:23 1970 +0000
+  | | . . . :  summary:     (23) merge one known; immediate left
+  | | . . . :
+  | o-------+  changeset:   22:e0d9cccacb5d
+  | . . . . :  parent:      18:1aa84d96232a
+  |/ / / / /   parent:      21:d42a756af44d
+  | . . . :    user:        test
+  | . . . :    date:        Thu Jan 01 00:00:22 1970 +0000
+  | . . . :    summary:     (22) merge two known; one far left, one far right
+  | . . . :
+  | . . . o    changeset:   21:d42a756af44d
+  | . . . |\   parent:      19:31ddc2c1573b
+  | . . . | |  parent:      20:d30ed6450e32
+  | . . . | |  user:        test
+  | . . . | |  date:        Thu Jan 01 00:00:21 1970 +0000
+  | . . . | |  summary:     (21) expand
+  | . . . | |
+  +-+-------o  changeset:   20:d30ed6450e32
+  | . . . |    parent:      0:e6eb3150255d
+  | . . . |    parent:      18:1aa84d96232a
+  | . . . |    user:        test
+  | . . . |    date:        Thu Jan 01 00:00:20 1970 +0000
+  | . . . |    summary:     (20) merge two known; two far right
+  | . . . |
+  | . . . o    changeset:   19:31ddc2c1573b
+  | . . . |\   parent:      15:1dda3f72782d
+  | . . . | |  parent:      17:44765d7c06e0
+  | . . . | |  user:        test
+  | . . . | |  date:        Thu Jan 01 00:00:19 1970 +0000
+  | . . . | |  summary:     (19) expand
+  | . . . | |
+  o---+---+ |  changeset:   18:1aa84d96232a
+    . . . | |  parent:      1:6db2ef61d156
+   / / / / /   parent:      15:1dda3f72782d
+  . . . | |    user:        test
+  . . . | |    date:        Thu Jan 01 00:00:18 1970 +0000
+  . . . | |    summary:     (18) merge two known; two far left
+  . . . | |
+  . . . | o    changeset:   17:44765d7c06e0
+  . . . | |\   parent:      12:86b91144a6e9
+  . . . | | |  parent:      16:3677d192927d
+  . . . | | |  user:        test
+  . . . | | |  date:        Thu Jan 01 00:00:17 1970 +0000
+  . . . | | |  summary:     (17) expand
+  . . . | | |
+  +-+-------o  changeset:   16:3677d192927d
+  . . . | |    parent:      0:e6eb3150255d
+  . . . | |    parent:      1:6db2ef61d156
+  . . . | |    user:        test
+  . . . | |    date:        Thu Jan 01 00:00:16 1970 +0000
+  . . . | |    summary:     (16) merge two known; one immediate right, one near right
+  . . . | |
+  . . . o |    changeset:   15:1dda3f72782d
+  . . . |\ \   parent:      13:22d8966a97e3
+  . . . | | |  parent:      14:8eac370358ef
+  . . . | | |  user:        test
+  . . . | | |  date:        Thu Jan 01 00:00:15 1970 +0000
+  . . . | | |  summary:     (15) expand
+  . . . | | |
+  +-------o |  changeset:   14:8eac370358ef
+  . . . | |/   parent:      0:e6eb3150255d
+  . . . | |    parent:      12:86b91144a6e9
+  . . . | |    user:        test
+  . . . | |    date:        Thu Jan 01 00:00:14 1970 +0000
+  . . . | |    summary:     (14) merge two known; one immediate right, one far right
+  . . . | |
+  . . . o |    changeset:   13:22d8966a97e3
+  . . . |\ \   parent:      9:7010c0af0a35
+  . . . | | |  parent:      11:832d76e6bdf2
+  . . . | | |  user:        test
+  . . . | | |  date:        Thu Jan 01 00:00:13 1970 +0000
+  . . . | | |  summary:     (13) expand
+  . . . | | |
+  . +---+---o  changeset:   12:86b91144a6e9
+  . . . | |    parent:      1:6db2ef61d156
+  . . . | |    parent:      9:7010c0af0a35
+  . . . | |    user:        test
+  . . . | |    date:        Thu Jan 01 00:00:12 1970 +0000
+  . . . | |    summary:     (12) merge two known; one immediate right, one far left
+  . . . | |
+  . . . | o    changeset:   11:832d76e6bdf2
+  . . . | |\   parent:      6:b105a072e251
+  . . . | | |  parent:      10:74c64d036d72
+  . . . | | |  user:        test
+  . . . | | |  date:        Thu Jan 01 00:00:11 1970 +0000
+  . . . | | |  summary:     (11) expand
+  . . . | | |
+  +---------o  changeset:   10:74c64d036d72
+  . . . | |/   parent:      0:e6eb3150255d
+  . . . | |    parent:      6:b105a072e251
+  . . . | |    user:        test
+  . . . | |    date:        Thu Jan 01 00:00:10 1970 +0000
+  . . . | |    summary:     (10) merge two known; one immediate left, one near right
+  . . . | |
+  . . . o |    changeset:   9:7010c0af0a35
+  . . . |\ \   parent:      7:b632bb1b1224
+  . . . | | |  parent:      8:7a0b11f71937
+  . . . | | |  user:        test
+  . . . | | |  date:        Thu Jan 01 00:00:09 1970 +0000
+  . . . | | |  summary:     (9) expand
+  . . . | | |
+  +-------o |  changeset:   8:7a0b11f71937
+  . . . |/ /   parent:      0:e6eb3150255d
+  . . . | |    parent:      7:b632bb1b1224
+  . . . | |    user:        test
+  . . . | |    date:        Thu Jan 01 00:00:08 1970 +0000
+  . . . | |    summary:     (8) merge two known; one immediate left, one far right
+  . . . | |
+  . . . o |    changeset:   7:b632bb1b1224
+  . . . |\ \   parent:      2:3d9a33b8d1e1
+  . . . | . |  parent:      5:4409d547b708
+  . . . | . |  user:        test
+  . . . | . |  date:        Thu Jan 01 00:00:07 1970 +0000
+  . . . | . |  summary:     (7) expand
+  . . . | . |
+  . . . +---o  changeset:   6:b105a072e251
+  . . . | ./   parent:      2:3d9a33b8d1e1
+  . . . | .    parent:      5:4409d547b708
+  . . . | .    user:        test
+  . . . | .    date:        Thu Jan 01 00:00:06 1970 +0000
+  . . . | .    summary:     (6) merge two known; one immediate left, one far left
+  . . . | .
+  . . . o .    changeset:   5:4409d547b708
+  . . . |\ \   parent:      3:27eef8ed80b4
+  . . . | . .  parent:      4:26a8bac39d9f
+  . . . | . .  user:        test
+  . . . | . .  date:        Thu Jan 01 00:00:05 1970 +0000
+  . . . | . .  summary:     (5) expand
+  . . . | . .
+  . +---o . .  changeset:   4:26a8bac39d9f
+  . . . ./ /   parent:      1:6db2ef61d156
+  . . . . .    parent:      3:27eef8ed80b4
+  . . . . .    user:        test
+  . . . . .    date:        Thu Jan 01 00:00:04 1970 +0000
+  . . . . .    summary:     (4) merge two known; one immediate left, one immediate right
+  . . . . .
+
+  $ cd ..


More information about the Mercurial-devel mailing list