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)