#!/usr/bin/perl use strict; use warnings; use POSIX qw(ceil); use Getopt::Long; use Date::Parse; sub usage() { print "Usage: git chart\n"; } sub gather_data($) { my $options = shift; my %perday; print "Gathering data ...\n"; my $groups = 0; my $commits = 0; open my $fd, '-|', qw(git log --date=short), '--pretty=%ad,%s', @{$options->{cmdline}}; die "failed to get revs" unless $fd; while (<$fd>) { chomp; $commits += 1; my ($date, $sub) = split ',', $_, 2; if (exists $perday{$date}) { $perday{$date} += 1; } else { $perday{$date} = 1; $groups += 1; } } close $fd; print "...done, $commits commits in $groups days. Grouping\n"; my $step=$options->{step}*24*3600; # days to seconds my @keys = sort keys %perday; my $from = $keys[0]; my $to = $keys[@keys-1]; my %dataset ; my $first; my $next; my $val = 0; my $max = 0; while (my $key = shift @keys) { my $day = str2time($key); unless ($first) { $first = $day; $next = $first + $step; } if ($day < $next) { $val += $perday{$key}; } else { # TODO what do we put as key? $dataset{$first} = $val; $max = $val if $max < $val; while ($day >= $next) { $first = $next; $next = $first + $step; $dataset{$first} = 0 unless $day < $next; } $val = $perday{$key}; } } $dataset{$first} = $val; $max = $val if $max < $val; $options->{max} = $max; return \%dataset; } # functions to plot the datasets. # each function can be called with either one or two parameters. # when called with two parameters, the first is assumed to be the dataset, and the second the options # (array and hash ref respectively). # when called with a single parameter, it is assumed to be an options hash ref, and the dataset is # created by calling gather_data with the passed options. # google chart API # TODO needs a lot of customization sub google_chart($;$) { my $dataset = shift; my $options = shift; if (! defined $options) { $options = $dataset; $dataset = gather_data($options); } my $height=$options->{chart_height}; my $max = $options->{max}; my @keys = sort keys %$dataset; my $from = $keys[0]; my $to = $keys[@keys-1]; my @data; while (my $key = shift @keys) { push @data, $dataset->{$key}; } my $width=ceil(4*$height/3); my $url="https://chart.googleapis.com/chart?chs=${width}x${height}&cht=bvg&chd=t:%s&chds=0,$max&chbh=a&chxt=y&chxr=0,0,$max"; my $launch = sprintf $url, join(",",@data); print $launch, "\n"; # `git web--browse "$launch"` } # gnuplot sub gnuplot_chart($;$) { my $dataset = shift; my $options = shift; if (! defined $options) { $options = $dataset; $dataset = gather_data($options); } my @keys = sort keys %$dataset; my $from = $keys[0]; my $to = $keys[@keys-1]; my $data = ''; while (my $key = shift @keys) { $data .= "$key $dataset->{$key}\n"; } my $max = $options->{max}; # TODO allow customization # in particular, detect (lack of) display and set term to dumb accordingly my $termcmd = $options->{gnuplot_term}; my $plotsetup = $options->{gnuplot_setup}; $plotsetup .= "\nset yrange [0:$max]\n"; $plotsetup .= "set xrange ['$from':'$to']\n"; my $plotstyle = $options->{gnuplot_style}; my $plotoptions = $options->{gnuplot_plotwith}; open my $gp, "|gnuplot -persist"; my $gp_script = < 1, cmdline => [], # charting/plotting options plotter => \&gnuplot_chart, chart_height => 144, gnuplot_term => '', gnuplot_setup => "set nokey", gnuplot_style => 'set style data histeps', gnuplot_plotwith => '', ); my %steps = ( daily => 1, weekly => 7, monthly => 30, ); sub parse_step(@) { my $key = shift; my $step = shift; if (exists $steps{$key}) { $options{step} = $steps{$key}; return; } die "this can't happen ($key)" unless $key eq 'step'; if ($step =~/^\d+$/) { $options{step} = 0 + $step; return } else { if (exists $steps{$step}) { $options{step} = $steps{$step}; return; } die "unknown step $step"; } } # read our options first Getopt::Long::Configure('pass_through'); GetOptions( 'daily' => \&parse_step, 'weekly' => \&parse_step, 'monthly' => \&parse_step, 'step=s' => sub { parse_step(@_) }, 'chart-height=i' => sub { $options{chart_height} = @_[1]}, google => sub { $options{plotter} = \&google_chart }, gnuplot => sub { $options{plotter} = \&gnuplot_chart }, ); # if anything was left, check for log options if (@ARGV) { $options{cmdline} = \@ARGV; } die "step must be strictly positive!" unless $options{step} > 0; $options{plotter}->(\%options);