--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/graphlog.py Mon Apr 09 10:39:28 2007 +0200
@@ -0,0 +1,266 @@
+# ASCII graph log extension for Mercurial
+#
+# Copyright 2007 Joel Rosdahl <joel@rosdahl.net>
+#
+# This software may be used and distributed according to the terms of
+# the GNU General Public License, incorporated herein by reference.
+
+import sys
+from mercurial.cmdutil import revrange, show_changeset
+from mercurial.i18n import _
+from mercurial.node import nullid, nullrev
+from mercurial.util import Abort
+
+def revision_grapher(repo, start_rev, stop_rev):
+ """incremental revision grapher
+
+ This generator function walks through the revision history from
+ revision start_rev to revision stop_rev (which must be less than
+ or equal to start_rev) and for each revision emits tuples with the
+ following elements:
+
+ - Current revision.
+ - Current node.
+ - Column of the current node in the set of ongoing edges.
+ - Edges; a list of (col, next_col) indicating the edges between
+ the current node and its parents.
+ - Number of columns (ongoing edges) in the current revision.
+ - The difference between the number of columns (ongoing edges)
+ in the next revision and the number of columns (ongoing edges)
+ in the current revision. That is: -1 means one column removed;
+ 0 means no columns added or removed; 1 means one column added.
+ """
+
+ assert start_rev >= stop_rev
+ curr_rev = start_rev
+ revs = []
+ while curr_rev >= stop_rev:
+ node = repo.changelog.node(curr_rev)
+
+ # Compute revs and next_revs.
+ if curr_rev not in revs:
+ # New head.
+ revs.append(curr_rev)
+ rev_index = revs.index(curr_rev)
+ next_revs = revs[:]
+
+ # Add parents to next_revs.
+ parents = get_rev_parents(repo, curr_rev)
+ parents_to_add = []
+ for parent in parents:
+ if parent not in next_revs:
+ parents_to_add.append(parent)
+ parents_to_add.sort()
+ next_revs[rev_index:rev_index + 1] = parents_to_add
+
+ edges = []
+ for parent in parents:
+ edges.append((rev_index, next_revs.index(parent)))
+
+ n_columns_diff = len(next_revs) - len(revs)
+ yield (curr_rev, node, rev_index, edges, len(revs), n_columns_diff)
+
+ revs = next_revs
+ curr_rev -= 1
+
+def get_rev_parents(repo, rev):
+ return [x for x in repo.changelog.parentrevs(rev) if x != nullrev]
+
+def fix_long_right_edges(edges):
+ for (i, (start, end)) in enumerate(edges):
+ if end > start:
+ edges[i] = (start, end + 1)
+
+def draw_edges(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] = "|"
+ else:
+ nodeline[2 * end] = "+"
+ if start > end:
+ (start, end) = (end,start)
+ for i in range(2 * start + 1, 2 * end):
+ if nodeline[i] != "+":
+ nodeline[i] = "-"
+
+def format_line(line, level, logstr):
+ text = "%-*s %s" % (2 * level, "".join(line), logstr)
+ return "%s\n" % text.rstrip()
+
+def get_nodeline_edges_tail(
+ 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:
+ # 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))
+ return tail
+ else:
+ return ["\\", " "] * (n_columns - node_index - 1)
+ else:
+ return ["|", " "] * (n_columns - node_index - 1)
+
+def get_padding_line(ni, n_columns, edges):
+ line = []
+ line.extend(["|", " "] * ni)
+ if (ni, ni - 1) in edges or (ni, ni) in edges:
+ # (ni, ni - 1) (ni, ni)
+ # | | | | | | | |
+ # +---o | | o---+
+ # | | c | | c | |
+ # | |/ / | |/ /
+ # | | | | | |
+ c = "|"
+ else:
+ c = " "
+ line.extend([c, " "])
+ line.extend(["|", " "] * (n_columns - ni - 1))
+ return line
+
+def get_limit(limit_opt):
+ if limit_opt:
+ try:
+ limit = int(limit_opt)
+ except ValueError:
+ raise Abort(_("limit must be a positive integer"))
+ if limit <= 0:
+ raise Abort(_("limit must be positive"))
+ else:
+ limit = sys.maxint
+ return limit
+
+def get_revs(repo, rev_opt):
+ if rev_opt:
+ revs = revrange(repo, rev_opt)
+ return (max(revs), min(revs))
+ else:
+ return (repo.changelog.count() - 1, 0)
+
+def graphlog(ui, repo, *args, **opts):
+ """show revision history alongside an ASCII revision graph
+
+ Print a revision history alongside a revision graph drawn with
+ ASCII characters.
+
+ Nodes printed as an @ character are parents of the working
+ directory.
+ """
+
+ limit = get_limit(opts["limit"])
+ (start_rev, stop_rev) = get_revs(repo, opts["rev"])
+ stop_rev = max(stop_rev, start_rev - limit + 1)
+ if start_rev == nullrev:
+ return
+ cs_printer = show_changeset(ui, repo, opts)
+ grapher = revision_grapher(repo, start_rev, stop_rev)
+ repo_parents = repo.dirstate.parents()
+ prev_n_columns_diff = 0
+ prev_node_index = 0
+
+ for (rev, node, node_index, edges, n_columns, n_columns_diff) in grapher:
+ # log_strings is the list of all log strings to draw alongside
+ # the graph.
+ ui.pushbuffer()
+ cs_printer.show(rev, node)
+ log_strings = ui.popbuffer().split("\n")[:-1]
+
+ if n_columns_diff == -1:
+ # Transform
+ #
+ # | | | | | |
+ # o | | into o---+
+ # |X / |/ /
+ # | | | |
+ fix_long_right_edges(edges)
+
+ # add_padding_line says whether to rewrite
+ #
+ # | | | | | | | |
+ # | o---+ into | o---+
+ # | / / | | | # <--- padding line
+ # o | | | / /
+ # o | |
+ add_padding_line = \
+ len(log_strings) > 2 and \
+ n_columns_diff == -1 and \
+ [x for (x, y) in edges if x + 1 < y]
+
+ # fix_nodeline_tail says whether to rewrite
+ #
+ # | | o | | | | o | |
+ # | | |/ / | | |/ /
+ # | o | | into | o / / # <--- fixed nodeline tail
+ # | |/ / | |/ /
+ # o | | o | |
+ fix_nodeline_tail = len(log_strings) <= 2 and not add_padding_line
+
+ # nodeline is the line containing the node character (@ or o).
+ nodeline = ["|", " "] * node_index
+ if node in repo_parents:
+ node_ch = "@"
+ else:
+ node_ch = "o"
+ nodeline.extend([node_ch, " "])
+
+ nodeline.extend(
+ get_nodeline_edges_tail(
+ node_index, prev_node_index, n_columns, n_columns_diff,
+ prev_n_columns_diff, fix_nodeline_tail))
+
+ # shift_interline is the line containing the non-vertical
+ # edges between this entry and the next.
+ shift_interline = ["|", " "] * node_index
+ if n_columns_diff == -1:
+ n_spaces = 1
+ edge_ch = "/"
+ elif n_columns_diff == 0:
+ n_spaces = 2
+ edge_ch = "|"
+ else:
+ n_spaces = 3
+ edge_ch = "\\"
+ shift_interline.extend(n_spaces * [" "])
+ shift_interline.extend([edge_ch, " "] * (n_columns - node_index - 1))
+
+ # Draw edges from the current node to its parents.
+ draw_edges(edges, nodeline, shift_interline)
+
+ # lines is the list of all graph lines to print.
+ lines = [nodeline]
+ if add_padding_line:
+ lines.append(get_padding_line(node_index, n_columns, edges))
+ lines.append(shift_interline)
+
+ # Make sure that there are as many graph lines as there are
+ # log strings.
+ while len(log_strings) < len(lines):
+ log_strings.append("")
+ if len(lines) < len(log_strings):
+ extra_interline = ["|", " "] * (n_columns + n_columns_diff)
+ while len(lines) < len(log_strings):
+ lines.append(extra_interline)
+
+ # Print lines.
+ indentation_level = max(n_columns, n_columns + n_columns_diff)
+ for (line, logstr) in zip(lines, log_strings):
+ ui.write(format_line(line, indentation_level, logstr))
+
+ # ...and start over.
+ prev_node_index = node_index
+ prev_n_columns_diff = n_columns_diff
+
+cmdtable = {
+ "glog":
+ (graphlog,
+ [("l", "limit", "", _("limit number of changes displayed")),
+ ("p", "patch", False, _("show patch")),
+ ("r", "rev", [], _("show the specified revision or range")),
+ ("", "style", "", _("display using template map file")),
+ ("", "template", "", _("display with template"))],
+ "hg glog [OPTIONS]"),
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-glog Mon Apr 09 10:39:28 2007 +0200
@@ -0,0 +1,140 @@
+#!/bin/sh
+
+# @ (34) head
+# |
+# | o (33) head
+# | |
+# o | (32) expand
+# |\ \
+# | o \ (31) expand
+# | |\ \
+# | | o \ (30) expand
+# | | |\ \
+# | | | o | (29) regular commit
+# | | | | |
+# | | o | | (28) merge zero known
+# | | |\ \ \
+# o | | | | | (27) collapse
+# |/ / / / /
+# | | o---+ (26) merge one known; far right
+# | | | | |
+# +---o | | (25) merge one known; far left
+# | | | | |
+# | | o | | (24) merge one known; immediate right
+# | | |\| |
+# | | o | | (23) merge one known; immediate left
+# | |/| | |
+# +---o---+ (22) merge two known; one far left, one far right
+# | | / /
+# o | | | (21) expand
+# |\ \ \ \
+# | o---+-+ (20) merge two known; two far right
+# | / / /
+# o | | | (19) expand
+# |\ \ \ \
+# +---+---o (18) merge two known; two far left
+# | | | |
+# | o | | (17) expand
+# | |\ \ \
+# | | o---+ (16) merge two known; one immediate right, one near right
+# | | |/ /
+# o | | | (15) expand
+# |\ \ \ \
+# | o-----+ (14) merge two known; one immediate right, one far right
+# | |/ / /
+# o | | | (13) expand
+# |\ \ \ \
+# +---o | | (12) merge two known; one immediate right, one far left
+# | | |/ /
+# | o | | (11) expand
+# | |\ \ \
+# | | o---+ (10) merge two known; one immediate left, one near right
+# | |/ / /
+# o | | | (9) expand
+# |\ \ \ \
+# | o-----+ (8) merge two known; one immediate left, one far right
+# |/ / / /
+# o | | | (7) expand
+# |\ \ \ \
+# +---o | | (6) merge two known; one immediate left, one far left
+# | |/ / /
+# | o | | (5) expand
+# | |\ \ \
+# | | o | | (4) merge two known; one immediate left, one immediate right
+# | |/|/ /
+# | o / / (3) collapse
+# |/ / /
+# o / / (2) collapse
+# |/ /
+# o / (1) collapse
+# |/
+# o (0) root
+
+set -e
+
+commit()
+{
+ rev=$1
+ msg=$2
+ shift 2
+ if [ "$#" -gt 0 ]; then
+ hg debugsetparents "$@"
+ fi
+ echo $rev > $rev
+ hg add $rev
+ hg ci -d "$rev 0" -m "($rev) $msg"
+}
+
+echo "[extensions]" >> $HGRCPATH
+echo "graphlog=" >> $HGRCPATH
+
+echo % init
+hg init repo
+
+cd repo
+
+echo % empty repo
+hg glog
+
+echo % building tree
+commit 0 "root"
+commit 1 "collapse" 0
+commit 2 "collapse" 1
+commit 3 "collapse" 2
+commit 4 "merge two known; one immediate left, one immediate right" 1 3
+commit 5 "expand" 3 4
+commit 6 "merge two known; one immediate left, one far left" 2 5
+commit 7 "expand" 2 5
+commit 8 "merge two known; one immediate left, one far right" 0 7
+commit 9 "expand" 7 8
+commit 10 "merge two known; one immediate left, one near right" 0 6
+commit 11 "expand" 6 10
+commit 12 "merge two known; one immediate right, one far left" 1 9
+commit 13 "expand" 9 11
+commit 14 "merge two known; one immediate right, one far right" 0 12
+commit 15 "expand" 13 14
+commit 16 "merge two known; one immediate right, one near right" 0 1
+commit 17 "expand" 12 16
+commit 18 "merge two known; two far left" 1 15
+commit 19 "expand" 15 17
+commit 20 "merge two known; two far right" 0 18
+commit 21 "expand" 19 20
+commit 22 "merge two known; one far left, one far right" 18 21
+commit 23 "merge one known; immediate left" 1 22
+commit 24 "merge one known; immediate right" 0 23
+commit 25 "merge one known; far left" 21 24
+commit 26 "merge one known; far right" 18 25
+commit 27 "collapse" 21
+commit 28 "merge zero known" 1 26
+commit 29 "regular commit" 0
+commit 30 "expand" 28 29
+commit 31 "expand" 21 30
+commit 32 "expand" 27 31
+commit 33 "head" 18
+commit 34 "head" 32
+
+echo % glog -q
+hg glog -q
+
+echo % glog
+hg glog