Monday, April 16, 2012

Git tip : Using Graphviz to display branch graph

Works, but not great :)

echo 'digraph "git" {' > enb.dot
git log --pretty='format: %h [label="%s"]' HEAD^^..HEAD | perl -p -e 's/([0-9a-f]{7})/"\1"/' >> enb.dot
#git log --pretty='format: %h [label="Subject:Testing%h"]' HEAD^^..HEAD | sed 's/[0-9a-f]\+/\"&\"/' >> enb.dot
git log --pretty='format: %h -> { %p }' HEAD^^..HEAD | sed 's/[0-9a-f]\+/\"&\"/g' >> enb.dot
#git log --pretty='format: %p -> { %h }' HEAD^^..HEAD | sed 's/[0-9a-f]\+/\"&\"/g' >> enb.dot ##reverse arrow
echo '}' >> enb.dot
dot -Tsvg enb.dot -o enb.svg
The Result:


3 comments:

JohnFlux said...

I made a change to turn around what you did, and graph the commits so that a commit's parents are the last commit that touched the same file. This way we can see a more logical graph - seeing which commits are semantically children, and which are just children because they were committed later but touch completely different code.

Anyway, so here it is is:

#!/usr/bin/perl

use strict;
use warnings;
eval "require Git::Repository; require IPC::Run; 1" or die "Please do: sudo apt-get install libgit-repository-perl graphviz libipc-run-perl - Missing libraries";
use Git::Repository;
use IPC::Run qw( run );
my $svg_filename = "plot.svg";

if (!defined($ARGV[0]) || $ARGV[0] eq "-h" || $ARGV[0] eq "--help") {
print <<"EOT";
JohnFlux's nifty git graphing tool
==================================

This program produces a $svg_filename file in the current folder, for the
commits in the given git range in the current git repository.

In the SVG, a commit's parents are the commits that last touched the same files
that the commit touched. This graph is particularly useful when trying to squash.

Multiple lines between the same commits means that those commits both modified the same
multiple set of files.

Run as $0 [-h|--help]
$0 [--stdout] .

Examples:

#show the last 100 commits:
$0 -100 && firefox plot.svg

#show all the commits back to branch foo:
$0 HEAD...foo && firefox plot.svg

EOT
exit;
}

my $onlyPrintResult = 0;

if ($ARGV[0] eq "--stdout") {
shift;
$onlyPrintResult = 1;
}

my $git = Git::Repository->new( work_tree => '.' );
my @lines = $git->run( log=> '--name-only', '--pretty=format:%h %s', @ARGV);

my %filesToSha = ();
my $sha = "";
my $title = "";
my $nextLineIsSha = 1;
my $output ="";
$output .= 'digraph "git" {' . "\n";
for my $line ( @lines ) {
#Empty line moves us on to the next commit
if ($line =~ /^\s*$/) {
$nextLineIsSha = 1;
} elsif ($nextLineIsSha) {
$nextLineIsSha = 0;
($sha, $title) = split ' ', $line, 2;
my $fullinfo = $git->run( show => '--stat', $sha );
$title =~ s/"/'/g;
$fullinfo =~ s/"/'/g;
$fullinfo =~ s/\n/
/g;
$fullinfo =~ s/ +/ /g; #spaces eat up a lot space since they get encoded to   = 6 letters

# Firefox can't cope with it being longer than about 3000 characters
# but character encodings mean that a single character can expand to many letters. So we just have to guess at
# a maximum length here
$fullinfo = substr($fullinfo, 0, 1000);

$output .= "\"$sha\" [label=\"$sha $title\", tooltip=\"$fullinfo\"]\n";
} else {
if( defined( my $parentSha = $filesToSha{ $line } ) ) {
$output .= "\"$sha\" -> { \"$parentSha\" }\n";
}

$filesToSha{ $line } = $sha;
}
}
$output .= "}\n";

if ($onlyPrintResult) {
print $output;
# Example use of output:
# cat tmp.dot | dot -Tsvg -o plot.svg
#
# Or to make the result tall instead of wide:
# cat tmp.dot | ccomps -Cx | dot | gvpack -array_1 | neato -n2 -Tsvg -o plot.svg
} else {
run [ qw(ccomps -Cx) ], '<', \$output, "|", [ qw(dot) ], "|", [ qw(gvpack -array_1) ], "|", [ qw( neato -n2 -Tsvg -o ), $svg_filename ];
}

brwyatt said...

I went and turned this into a shell script and put it up as a Gist on GitHub for convenience: https://gist.github.com/brwyatt/ea54cd0310e24785e898

Martin d'Anjou said...

How do you put all the commits of a given branch in the same "column"? I want to get a display like in http://nvie.com/posts/a-successful-git-branching-model/