4 # * autodetect optimal frequency depending on date range for commits
5 # * default to something else than "the whole history", probably
6 # * if no commits are specified and a commit grouping step is specified,
7 # limit commits proportionally (e.g. --daily => one week worth of commits)
8 # * find a better way to group commits, the current is a little too inaccurate
10 # Most of the mentioned TODOs require either some peeking at the commits
11 # or some other form of pre/post-processing. For both it's probably better
12 # to just slurp them at once and then think about the grouping
20 my $SECS_PER_DAY = 24*3600;
24 daily => $SECS_PER_DAY,
25 weekly => 7*$SECS_PER_DAY,
26 monthly => 30*$SECS_PER_DAY,
27 yearly => 12*30*$SECS_PER_DAY,
32 print "Usage: git chart\n";
39 print "Gathering data ...\n";
44 my $step=$options->{step};
46 open my $fd, '-|', qw(git log --date=raw), '--pretty=%ad %s', @{$options->{cmdline}};
47 die "failed to get revs" unless $fd;
51 my ($date, $tz, $sub) = split ' ', $_, 3;
54 my $key = $date - ($date % $step);
55 if (exists $dataset{$key}) {
64 print "...done, $commits commits in $groups groups.\n";
66 # fill missing steps and find max
67 my @keys = sort keys %dataset;
70 while (my $key = shift @keys) {
71 $max = $dataset{$key} if $max < $dataset{$key};
72 my $next = $key + $step;
73 while (! exists $dataset{$next}) {
76 last if $next >= $last;
80 $options->{max} = $max;
84 # functions to plot the datasets.
85 # each function can be called with either one or two parameters.
86 # when called with two parameters, the first is assumed to be the dataset, and the second the options
87 # (array and hash ref respectively).
88 # when called with a single parameter, it is assumed to be an options hash ref, and the dataset is
89 # created by calling gather_data with the passed options.
92 # TODO needs a lot of customization
93 sub google_chart($;$) {
96 if (! defined $options) {
98 $dataset = gather_data($options);
101 my $height=$options->{chart_height};
102 my $max = $options->{max};
104 my @keys = sort keys %$dataset;
106 my $to = $keys[@keys-1];
109 while (my $key = shift @keys) {
110 push @data, $dataset->{$key};
113 my $width=ceil(4*$height/3);
115 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";
117 my $launch = sprintf $url, join(",",@data);
119 # `git web--browse "$launch"`
123 sub gnuplot_chart($;$) {
126 if (! defined $options) {
128 $dataset = gather_data($options);
131 my @keys = sort keys %$dataset;
132 my $step=$options->{step};
134 my $to = $keys[@keys-1];
136 while (my $key = shift @keys) {
137 $data .= "$key $dataset->{$key}\n";
139 my $max = $options->{max};
141 # TODO allow customization
142 # in particular, detect (lack of) display and set term to dumb accordingly
143 my $termcmd = $options->{gnuplot_term};
145 my $plotsetup = $options->{gnuplot_setup};
146 $plotsetup .= "\nset yrange [0:$max]\n";
147 $plotsetup .= "set xrange ['$from':'$to']\n";
148 my ($formatx, $ticks);
149 if ($to - $from > $steps{yearly}) {
151 $ticks = $steps{yearly};
152 } elsif ($to - $from > $steps{monthly}) {
153 $formatx = "%b\\n%Y";
154 $ticks = $steps{monthly};
155 } elsif ($to - $from > 2*$steps{daily}) {
156 $formatx = "%d\\n%b";
157 $ticks = $steps{daily};
160 $ticks = $steps{hourly};
162 $plotsetup .= "set format x \"$formatx\"\n";
163 $plotsetup .= "set xtics $ticks\n";
164 my $plotstyle = $options->{gnuplot_style};
165 my $plotoptions = $options->{gnuplot_plotwith};
167 open my $gp, "|gnuplot -persist";
169 my $gp_script = <<GPCMD
175 plot "-" using 1:2 $plotoptions
180 print STDOUT $gp_script;
181 print $gp $gp_script;
187 step => $SECS_PER_DAY,
189 # charting/plotting options
190 plotter => \&gnuplot_chart,
193 gnuplot_setup => "set nokey",
194 gnuplot_style => 'set style data histeps',
195 gnuplot_plotwith => '',
201 if (exists $steps{$key}) {
202 $options{step} = $steps{$key};
205 die "this can't happen ($key)" unless $key eq 'step';
207 if ($step =~/^\d+$/) {
208 $options{step} = 0 + $step;
211 if (exists $steps{$step}) {
212 $options{step} = $steps{$step};
215 die "unknown step $step";
219 # read our options first
220 Getopt::Long::Configure('pass_through');
222 'hourly' => \&parse_step,
223 'daily' => \&parse_step,
224 'weekly' => \&parse_step,
225 'monthly' => \&parse_step,
226 'yearly' => \&parse_step,
227 'step=s' => sub { parse_step(@_) },
228 'chart-height=i' => sub { $options{chart_height} = $_[1]},
229 google => sub { $options{plotter} = \&google_chart },
230 gnuplot => sub { $options{plotter} = \&gnuplot_chart },
233 # if anything was left, check for log options
235 $options{cmdline} = \@ARGV;
238 die "step must be strictly positive!" unless $options{step} > 0;
240 $options{plotter}->(\%options);