9 my $SECS_PER_DAY = 24*3600;
13 daily => $SECS_PER_DAY,
14 weekly => 7*$SECS_PER_DAY,
15 monthly => 30*$SECS_PER_DAY,
19 print "Usage: git chart\n";
26 print "Gathering data ...\n";
31 my $step=$options->{step};
33 open my $fd, '-|', qw(git log --date=raw), '--pretty=%ad %s', @{$options->{cmdline}};
34 die "failed to get revs" unless $fd;
38 my ($date, $tz, $sub) = split ' ', $_, 3;
41 my $key = $date - ($date % $step);
42 if (exists $dataset{$key}) {
51 print "...done, $commits commits in $groups groups.\n";
53 # fill missing steps and find max
54 my @keys = sort keys %dataset;
57 while (my $key = shift @keys) {
58 $max = $dataset{$key} if $max < $dataset{$key};
59 my $next = $key + $step;
60 while (! exists $dataset{$next}) {
63 last if $next >= $last;
67 $options->{max} = $max;
71 # functions to plot the datasets.
72 # each function can be called with either one or two parameters.
73 # when called with two parameters, the first is assumed to be the dataset, and the second the options
74 # (array and hash ref respectively).
75 # when called with a single parameter, it is assumed to be an options hash ref, and the dataset is
76 # created by calling gather_data with the passed options.
79 # TODO needs a lot of customization
80 sub google_chart($;$) {
83 if (! defined $options) {
85 $dataset = gather_data($options);
88 my $height=$options->{chart_height};
89 my $max = $options->{max};
91 my @keys = sort keys %$dataset;
93 my $to = $keys[@keys-1];
96 while (my $key = shift @keys) {
97 push @data, $dataset->{$key};
100 my $width=ceil(4*$height/3);
102 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";
104 my $launch = sprintf $url, join(",",@data);
106 # `git web--browse "$launch"`
110 sub gnuplot_chart($;$) {
113 if (! defined $options) {
115 $dataset = gather_data($options);
118 my @keys = sort keys %$dataset;
119 my $step=$options->{step};
121 my $to = $keys[@keys-1];
123 while (my $key = shift @keys) {
124 $data .= "$key $dataset->{$key}\n";
126 my $max = $options->{max};
128 # TODO allow customization
129 # in particular, detect (lack of) display and set term to dumb accordingly
130 my $termcmd = $options->{gnuplot_term};
132 my $plotsetup = $options->{gnuplot_setup};
133 $plotsetup .= "\nset yrange [0:$max]\n";
134 $plotsetup .= "set xrange ['$from':'$to']\n";
135 my ($formatx, $ticks);
136 if ($to - $from > $steps{monthly}) {
137 $formatx = "%b\\n%Y";
138 $ticks = $steps{monthly};
139 } elsif ($to - $from > 2*$steps{daily}) {
140 $formatx = "%d\\n%b";
141 $ticks = $steps{daily};
144 $ticks = $steps{hourly};
146 $plotsetup .= "set format x \"$formatx\"\n";
147 $plotsetup .= "set xtics $ticks\n";
148 my $plotstyle = $options->{gnuplot_style};
149 my $plotoptions = $options->{gnuplot_plotwith};
151 open my $gp, "|gnuplot -persist";
153 my $gp_script = <<GPCMD
159 plot "-" using 1:2 $plotoptions
164 print STDOUT $gp_script;
165 print $gp $gp_script;
171 step => $SECS_PER_DAY,
173 # charting/plotting options
174 plotter => \&gnuplot_chart,
177 gnuplot_setup => "set nokey",
178 gnuplot_style => 'set style data histeps',
179 gnuplot_plotwith => '',
185 if (exists $steps{$key}) {
186 $options{step} = $steps{$key};
189 die "this can't happen ($key)" unless $key eq 'step';
191 if ($step =~/^\d+$/) {
192 $options{step} = 0 + $step;
195 if (exists $steps{$step}) {
196 $options{step} = $steps{$step};
199 die "unknown step $step";
203 # read our options first
204 Getopt::Long::Configure('pass_through');
206 'hourly' => \&parse_step,
207 'daily' => \&parse_step,
208 'weekly' => \&parse_step,
209 'monthly' => \&parse_step,
210 'step=s' => sub { parse_step(@_) },
211 'chart-height=i' => sub { $options{chart_height} = $_[1]},
212 google => sub { $options{plotter} = \&google_chart },
213 gnuplot => sub { $options{plotter} = \&gnuplot_chart },
216 # if anything was left, check for log options
218 $options{cmdline} = \@ARGV;
221 die "step must be strictly positive!" unless $options{step} > 0;
223 $options{plotter}->(\%options);