Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
JohnFlux's nifty git graphing tool
#!/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] [--nofiles] <git log options>.
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
#show all the commits back to branch foo and don't show the files in seperate boxes:
$0 --nofiles HEAD...foo && firefox plot.svg
EOT
exit;
}
my $onlyPrintResult = 0;
my $noFiles = 0;
if ($ARGV[0] eq "--stdout") {
shift;
$onlyPrintResult = 1;
}
if ($ARGV[0] eq "--nofiles") {
shift;
$noFiles = 1;
}
my $git = Git::Repository->new( work_tree => '.' );
my @lines = $git->run( log=> '--name-only', '--pretty=format:%h %s', @ARGV);
my %filesToSha = ();
my %filesToColorIndex = ();
my $nextColorIndex = 1;
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/&#10;/g;
$fullinfo =~ s/ +/ /g; #spaces eat up a lot space since they get encoded to &nbsp; = 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 {
# $line is a filename that the current $sha commit modified
# See http://www.graphviz.org/doc/info/colors.html for a list of color schemes
my $colorIndex;
if( defined( my $parentSha = $filesToSha{ $line } ) ) {
$colorIndex = $filesToColorIndex{ $line };
$output .= "\"$parentSha\" -> { \"$sha\" } ";# no newline - we append the edge properties next
$output .= " [edgetooltip=\"$line\", colorscheme=\"paired12\", color=\"$colorIndex\"]\n";
} else {
$filesToColorIndex{ $line } = $colorIndex = $nextColorIndex;
$nextColorIndex = ($nextColorIndex % 12) +1; # mod 12 because we're using the paired12 colorscheme, which has 12 colors
if ($noFiles == 0) {
$output .= "\"$line\" [shape=box]\n";
$output .= "\"$line\" -> { \"$sha\" } "; # no newline - we append the edge properties next
$output .= " [edgetooltip=\"$line\", colorscheme=\"paired12\", color=\"$colorIndex\"]\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 ];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.