mercurial/graphmod.py
changeset 28600 0d6137891114
parent 28376 fa2cd0c9a567
child 28601 cd10171d6c71
--- a/mercurial/graphmod.py	Thu Mar 17 18:32:10 2016 +0000
+++ b/mercurial/graphmod.py	Sat Mar 19 16:46:15 2016 -0700
@@ -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)