diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py --- a/mercurial/cmdutil.py +++ b/mercurial/cmdutil.py @@ -2218,6 +2218,15 @@ def displaygraph(ui, repo, dag, displaye 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 @@ CHANGESET = 'C' 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 @@ def asciiedges(type, char, lines, state, 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 @@ def asciiedges(type, char, lines, state, 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 @@ def _fixlongrightedges(edges): 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 @@ def _drawedges(edges, nodeline, interlin 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 @@ def ascii(ui, state, type, char, text, c 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 @@ def ascii(ui, state, type, char, text, c 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 @@ def ascii(ui, state, type, char, text, c 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,214 @@ label() should just work in node templat | $ 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 ..