use strict;
use Getopt::Long qw{:config gnu_getopt posix_default};
+use POSIX;
use Term::ReadKey;
$0 =~ s{^.*/([^/]+)$}{$1};
-sub stop { exit 1; } # because 'die' uses a weird exit status
-sub wail { warn "$0: @_\n"; }
-sub fail { wail @_; stop; }
-sub fale { fail "@_: $!"; }
-
my %opt;
GetOptions(\%opt, qw{
- cancel
- overlay
- stack
-}) or stop;
+ cancel|c
+ height|h=i
+ overlay|o
+ stack|s
+ width|w=i
+}) or do {
+ print STDERR <<END;
+usage: git-graph [options]
+ -c | --cancel +/- counts cancel out
+ -o | --overlay +/- counts are overlaid as # (default)
+ -s | --cancel +/- counts are stacked
+ --height [n] Set height of graph
+ -h [n] (height of terminal is default)
+ --width [n] Set horizontal scale
+ -w [n] (fit to width of terminal by default)
+END
+ exit 1;
+};
-$opt{stack} = 1 unless $opt{cancel} or $opt{overlay};
+$opt{overlay} = 1 unless $opt{cancel} or $opt{stack};
-my @log = qx{git log --pretty=format:%ai --shortstat};
+my @terminal = GetTerminalSize;
+my $width = $terminal[0] - 11;
+my $height = $opt{height} // $terminal[1] - 2;
my %add;
my %del;
my $delmax = 0;
my $canmax = 0;
-while (@log) {
- my $date = shift @log;
- my $stat = shift @log;
- my $blank = shift @log;
- fail "bad blank line $blank"
- unless not defined $blank or $blank eq "\n";
- fail "truncated git log output"
- unless defined $date and defined $stat;
- chomp ($date, $stat);
- $date =~ s{^([0-9-]{10}) [0-9:]{8} [+-][0-9]{4}$}{$1}
- or fail "bad date $date";
- $stat =~ m{^\ \d+\ files?\ changed
- (?:,\ (\d+)\ insertion[s()+]+)?
- (?:,\ (\d+)\ deletion[s()-]+)?$}x
- or fail "bad stats $stat";
+my $date;
+
+for (qx{git log --pretty=format:%ct --shortstat --since='$height days ago'}) {
+ next if m{^\s*$};
+ if (m{^(\d{10})\n$}) {
+ $date = int ($1 / 86400);
+ next;
+ }
+ m{^\ \d+\ files?\ changed
+ (?:,\ (\d+)\ insertion[s()+]+)?
+ (?:,\ (\d+)\ deletion[s()-]+)?$}x
+ or die "$0: bad stats $_";
my ($add,$del) = ($1 || 0, $2 || 0);
$add{$date} += $add;
$del{$date} += $del;
$addmax = $add{$date} if $addmax < $add{$date};
$delmax = $del{$date} if $delmax < $del{$date};
- my $can = $add{$date} > $del{$date} ?
- $add{$date} - $del{$date} :
- $del{$date} - $add{$date};
+ my $can = abs $add{$date} - $del{$date};
$canmax = $can if $canmax < $can;
}
-my $max = $opt{cancel} ? $canmax :
+my $max = $opt{width} ? $opt{width} :
+ $opt{cancel} ? $canmax :
$opt{stack} ? $addmax + $delmax :
$addmax > $delmax ? $addmax : $delmax;
-my $width = [GetTerminalSize]->[0] - 12;
my $scale = $width / $max;
-for my $date (sort keys %add) {
- my $add;
- my $del;
- my $mod;
- if ($opt{cancel}) {
+my $today = int (time / 86400);
+
+for my $date ($today - $height .. $today) {
+ my $add = 0;
+ my $del = 0;
+ my $mod = 0;
+ $add{$date} //= 0;
+ $del{$date} //= 0;
+ if ($opt{cancel} or $opt{overlay}) {
if ($add{$date} > $del{$date}) {
$add = $add{$date} - $del{$date};
- $del = $mod = 0;
} else {
$del = $del{$date} - $add{$date};
- $add = $mod = 0;
}
}
if ($opt{overlay}) {
- if ($add{$date} > $del{$date}) {
- $add = $add{$date} - $del{$date};
- $del = 0;
- $mod = $del{$date};
- } else {
- $del = $del{$date} - $add{$date};
- $add = 0;
- $mod = $add{$date};
- }
+ $mod = $add{$date} < $del{$date} ?
+ $add{$date} : $del{$date};
}
if ($opt{stack}) {
$add = $add{$date};
$del = $del{$date};
- $mod = 0;
}
- printf "$date \e[33m%s\e[32m%s\e[31m%s\e[0m\n",
- "#" x ($mod * $scale),
- "+" x ($add * $scale),
- "-" x ($del * $scale);
+ my $greg = strftime "%b %d %w", localtime $date * 86400;
+ $greg =~ s{(\d)$}{substr "sMTWTFs", $1, 1}e;
+ my $line = sprintf "$greg \e[33m%s\e[32m%s\e[31m%s\e[0m\n",
+ "#" x ($mod && 1 + $mod * $scale),
+ "+" x ($add && 1 + $add * $scale),
+ "-" x ($del && 1 + $del * $scale);
+ if (length $line > $width + 30) {
+ $line = sprintf "## %d ##\e[32m++ %d ++\e[31m-- %d --\e[0m\n",
+ $mod, $add, $del;
+ $line = "$greg \e[33m" . "#"x($width + 16 - length $line) . $line;
+ }
+ print $line;
}
+printf " \e[33m#\e[0m <- %-8.1f %s %8d ->\n",
+ 1/$scale, " " x ($width - 25), $max;