Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/graphmod.py @ 28601:cd10171d6c71
graphmod: allow edges to end early
Rather than draw an edge all the way to the bottom of the graph, make it
possible to end an edge to parents that are not part of the graph early on.
This results in a far cleaner graph.
Any edge type can be set to end early; set the ui.graphstyle.<edgetype>
parameter to the empty string to enable this.
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
The default configuration leaves all 3 types set to |.
This is part of the work towards moving smartlog upstream; currently smartlog
injects extra nodes into the graph to indicate grandparent relationships (nodes
elided).
author | Martijn Pieters <mjpieters@fb.com> |
---|---|
date | Sat, 19 Mar 2016 16:37:47 -0700 |
parents | 0d6137891114 |
children | d7af9b4ae7dd |
comparison
equal
deleted
inserted
replaced
28600:0d6137891114 | 28601:cd10171d6c71 |
---|---|
29 | 29 |
30 CHANGESET = 'C' | 30 CHANGESET = 'C' |
31 PARENT = 'P' | 31 PARENT = 'P' |
32 GRANDPARENT = 'G' | 32 GRANDPARENT = 'G' |
33 MISSINGPARENT = 'M' | 33 MISSINGPARENT = 'M' |
34 # Style of line to draw. None signals a line that ends and is removed at this | |
35 # point. | |
34 EDGES = {PARENT: '|', GRANDPARENT: '|', MISSINGPARENT: '|'} | 36 EDGES = {PARENT: '|', GRANDPARENT: '|', MISSINGPARENT: '|'} |
35 | 37 |
36 def groupbranchiter(revs, parentsfunc, firstbranch=()): | 38 def groupbranchiter(revs, parentsfunc, firstbranch=()): |
37 """Yield revisions from heads to roots one (topo) branch at a time. | 39 """Yield revisions from heads to roots one (topo) branch at a time. |
38 | 40 |
481 remainder = ncols - idx - 1 | 483 remainder = ncols - idx - 1 |
482 if remainder > 0: | 484 if remainder > 0: |
483 line.extend(echars[-(remainder * 2):]) | 485 line.extend(echars[-(remainder * 2):]) |
484 return line | 486 return line |
485 | 487 |
488 def _drawendinglines(lines, extra, edgemap, seen): | |
489 """Draw ending lines for missing parent edges | |
490 | |
491 None indicates an edge that ends at between this node and the next | |
492 Replace with a short line ending in ~ and add / lines to any edges to | |
493 the right. | |
494 | |
495 """ | |
496 if None not in edgemap.values(): | |
497 return | |
498 | |
499 # Check for more edges to the right of our ending edges. | |
500 # We need enough space to draw adjustment lines for these. | |
501 edgechars = extra[::2] | |
502 while edgechars and edgechars[-1] is None: | |
503 edgechars.pop() | |
504 shift_size = max((edgechars.count(None) * 2) - 1, 0) | |
505 while len(lines) < 3 + shift_size: | |
506 lines.append(extra[:]) | |
507 | |
508 if shift_size: | |
509 empties = [] | |
510 toshift = [] | |
511 first_empty = extra.index(None) | |
512 for i, c in enumerate(extra[first_empty::2], first_empty // 2): | |
513 if c is None: | |
514 empties.append(i * 2) | |
515 else: | |
516 toshift.append(i * 2) | |
517 targets = list(range(first_empty, first_empty + len(toshift) * 2, 2)) | |
518 positions = toshift[:] | |
519 for line in lines[-shift_size:]: | |
520 line[first_empty:] = [' '] * (len(line) - first_empty) | |
521 for i in range(len(positions)): | |
522 pos = positions[i] - 1 | |
523 positions[i] = max(pos, targets[i]) | |
524 line[pos] = '/' if pos > targets[i] else extra[toshift[i]] | |
525 | |
526 map = {1: '|', 2: '~'} | |
527 for i, line in enumerate(lines): | |
528 if None not in line: | |
529 continue | |
530 line[:] = [c or map.get(i, ' ') for c in line] | |
531 | |
532 # remove edges that ended | |
533 remove = [p for p, c in edgemap.items() if c is None] | |
534 for parent in remove: | |
535 del edgemap[parent] | |
536 seen.remove(parent) | |
537 | |
486 def asciistate(): | 538 def asciistate(): |
487 """returns the initial value for the "state" argument to ascii()""" | 539 """returns the initial value for the "state" argument to ascii()""" |
488 return { | 540 return { |
489 'seen': [], | 541 'seen': [], |
490 'edges': {}, | 542 'edges': {}, |
580 lines.append(_getpaddingline(echars, idx, ncols, edges)) | 632 lines.append(_getpaddingline(echars, idx, ncols, edges)) |
581 lines.append(shift_interline) | 633 lines.append(shift_interline) |
582 | 634 |
583 # make sure that there are as many graph lines as there are | 635 # make sure that there are as many graph lines as there are |
584 # log strings | 636 # log strings |
637 extra_interline = echars[:(ncols + coldiff) * 2] | |
638 if len(lines) < len(text): | |
639 while len(lines) < len(text): | |
640 lines.append(extra_interline[:]) | |
641 | |
642 _drawendinglines(lines, extra_interline, edgemap, seen) | |
643 | |
585 while len(text) < len(lines): | 644 while len(text) < len(lines): |
586 text.append("") | 645 text.append("") |
587 if len(lines) < len(text): | |
588 extra_interline = echars[:(ncols + coldiff) * 2] | |
589 while len(lines) < len(text): | |
590 lines.append(extra_interline) | |
591 | 646 |
592 # print lines | 647 # print lines |
593 indentation_level = max(ncols, ncols + coldiff) | 648 indentation_level = max(ncols, ncols + coldiff) |
594 for (line, logstr) in zip(lines, text): | 649 for (line, logstr) in zip(lines, text): |
595 ln = "%-*s %s" % (2 * indentation_level, "".join(line), logstr) | 650 ln = "%-*s %s" % (2 * indentation_level, "".join(line), logstr) |