3 # git-log-compact.pl -- compact git log --oneline alternative with dates, times and initials
4 # Copyright (C) 2015,2016 Kyle J. McKay <mackyle@gmail.com>. All rights reserved.
13 use File
::Basename
qw(basename);
14 use POSIX
qw(strftime _exit);
17 my $USAGE = <<'USAGE';
18 usage: git%slog-compact [<options>] [<revision-range>] [[--] <path>...]
21 --seconds Use HH:MM:SS instead of just the default HH:MM
22 --minutes Use just HH:MM (default) for times not HH:MM:SS
23 --no-times Omit the time field entirely
24 --two-initials Use maximum of two initials instead of default three
25 --three-initials Use maximum of three initials (default)
26 --no-initials Omit the initials field entirely
27 --commit-message Show the commit message when using --walk-reflogs
28 --author-date Use author dates and times
29 --committer-date Use committer dates and times (default)
30 --initials=author Use author initials (default)
31 --initials=committer Use committer initials
32 --intiials=author,committer
33 Use author/committer initials and --two-initials
34 --intiials=committer,author
35 Use committer/author initials and --two-initials
36 --time-zone=<zone> Set TZ environment variable to <zone>
37 --weekday Show the weekday with the date
38 --no-weekday Do not show the weekday with the date (default)
40 other log options See `git help log` for more information
42 Default colors for dates, times and initials may be changed by setting
43 `color.log-compact.date`, `color.log-compact.time` and/or
44 `color.log-compact.initials` config values. Dates and times are shown in the
45 local time zone if TZ is not set in the environment and the `--time-zone`
46 option has not been used. Default options may be set in the
47 `log-compact.defaults` config value and they will be treated as though they were
48 listed first in the command line options list (e.g.
49 `git config log-compact.defaults "--abbrev=8 --seconds"`)
52 my $timeformat = "%H:%M";
54 $SIG{PIPE
} = sub {_exit
1};
57 my $msg = join(" ", @_);
59 die basename
($0).": fatal: ".$msg."\n";
62 my ($setusedecorate, $usedecorate);
64 return $usedecorate if $setusedecorate;
65 my $do = qx(git config
--get
log.decorate
2>/dev/null
) || "0";
67 return 0 if $do eq "0" || $do eq "false" || $do eq "off";
68 return 0 if $do eq "auto" && ! -t STDOUT
;
75 my $wasutf8 = utf8
::decode
($initials);
76 $initials = lc($initials)." ";
77 $initials =~ s/[.]/ /g;
78 $initials =~ s/ iii? / /g;
79 $initials =~ s/ iv / /g;
80 $initials =~ s/ [js]r / /g;
81 $initials =~ s/[,;:'\042+_-]//g;
82 $initials =~ s/\([^(]*?\)/ /g;
83 $initials =~ s/\[[^[]*?\]/ /g;
84 $initials =~ s/\s+/ /g;
86 return "jc" if $iw == 2 && $initials eq "junio c hamano ";
87 $initials =~ s/([^ ])[^ ]* /$1/g;
89 $initials =~ s/^(.).+(.)$/$1$2/;
91 $initials =~ s/^(..).+(.)$/$1$2/;
93 utf8
::encode
($initials) if $wasutf8;
97 sub get_nocolor_indent
{
99 $indent =~ s/\033[^m]*m//g;
101 $indent =~ s/-+\.$//;
105 sub get_blank_graph_indent
{
108 $indent =~ s/\033[^m]*m//g;
109 $indent =~ s/^[\s|]+//;
113 sub get_first_indent
{
115 $indent =~ s/\033[^m]*m//g;
125 my ($prefix, $index) = @_;
126 my $c = (split(m{[-=^<>*+o /|\\_]}, $prefix))[$index];
127 $c =~ s/\Q$resetcolor\E//g if $resetcolor;
134 $indent =~ tr/\-=^<>*+o./ /;
136 $indent =~ s/[-=^<>*+o]/$barcolor ? $barcolor."|".$resetcolor : "|"/e;
137 $indent =~ tr/\-./ /;
144 $indent =~ tr
'\/'||';
149 # defaults are cumulative, but an empty setting resets
151 my $opts = qx(git config --get-all log-compact.defaults 2>/dev/null);
153 foreach (split(/\r\n|\r|\n/, $opts, -1)) {
161 return split(" ", join(" ", @defaults));
164 system("git rev-parse --git-dir >/dev/null") == 0 or exit(1);
165 my ($usemark, $usegraph, $usereflog, $useboundary, $useleftright, $usecherry, $setusecolor, $usecolor, $usecad);
170 my $reflogsubj = "%gs";
174 my ($committer, $author, $ivar, $ivar2);
176 foreach my $arg (get_defaults(), @ARGV) {
178 if ($sawdashdash || $lastwasgrep) {
180 $lastwasgrep = $nextisgrep;
185 my $exec_path = qx(git --exec-path 2>/dev/null);
187 $dash = " " if $ENV{PATH} =~ /^\Q$exec_path\E:/;
188 printf "$USAGE\n", $dash;
190 } elsif ($arg eq "--oneline") {
191 # silently ignore --oneline as we are always in a one line format
193 } elsif ($arg eq "--seconds") {
195 $timeformat = "%H:%M:%S";
197 } elsif ($arg eq "--minutes") {
199 $timeformat = "%H:%M";
201 } elsif ($arg eq "--no-times") {
205 } elsif ($arg eq "--two-initials") {
209 } elsif ($arg eq "--three-initials") {
213 } elsif ($arg eq "--no-initials") {
217 } elsif ($arg eq "--two-initials") {
221 } elsif ($arg eq "--commit-message") {
225 } elsif ($arg eq "--author-date") {
230 } elsif ($arg eq "--committer-date") {
235 } elsif ($arg eq "--weekday") {
239 } elsif ($arg eq "--no-weekday") {
243 } elsif ($arg =~ /^--initials=/) {
245 $arg =~ s/^--initials=//;
246 if ($arg eq "author") {
249 } elsif ($arg eq "committer") {
252 } elsif ($arg eq "committer,author" || $arg eq "committer/author") {
255 } elsif ($arg eq "author,committer" || $arg eq "author/committer") {
257 $ivar2 = \$committer;
259 dodie "--initials= requires 'author
', 'committer
' or 'committer
,author
'";
262 } elsif ($arg =~ /^--time-zone=/) {
264 $arg =~ s/^--time-zone=//;
267 } elsif ($arg eq "--date-order" || $arg eq "--topo-order") {
268 $dateopt = "%ct" unless $usecad;
269 } elsif ($arg eq "--author-date-order") {
271 } elsif ($arg =~ /^--(pretty|pretty=.*|format=.*|notes|show-notes|show-notes=.*|standard-notes)$/) {
272 dodie "formatting/notes option not allowed: $arg";
273 } elsif ($arg eq "--no-decorate" || $arg eq "--decorate=no") {
275 $usedecorate = undef;
276 } elsif ($arg eq "--decorate=auto") {
278 $usedecorate = -t STDOUT ? 1 : undef;
279 } elsif ($arg eq "--decorate" || $arg =~ /^--decorate=/) {
282 } elsif ($arg eq "--color" || $arg eq "--color=always") {
285 } elsif ($arg eq "--no-color" || $arg eq "--color=never") {
288 } elsif ($arg eq "--color=auto") {
290 $usecolor = -t STDOUT ? 1 : undef;
291 } elsif ($arg eq "-g" || $arg eq "--walk-reflogs") {
293 } elsif ($arg eq "--boundary") {
296 } elsif ($arg eq "--cherry-mark" || $arg eq "--cherry") {
299 } elsif ($arg eq "--left-right") {
302 } elsif ($arg eq "--graph") {
304 } elsif ($arg =~ /^(--grep|--grep-reflog|-S|-G)$/) {
306 } elsif ($arg eq "--") {
310 $lastwasgrep = $nextisgrep;
312 $iw = defined($ivar2) ? 2 : 3 unless defined($iw);
314 $iw2 = $iw if defined($ivar2);
315 my ($mark, $fixmark) = ("");
316 $mark = "%m " unless $usegraph || !$usemark;
317 if ($mark && !$useleftright) {
319 $fixmark = "+" if $usecherry;
323 my ($hashcolor, $datecolor, $timecolor, $initialscolor, $autocolor) = ("", "", "", "", "");
324 $usecolor = 1 if !$setusecolor && system("git", "config", "--get-colorbool", "color.diff") == 0;
327 $autocolor = "%C(auto)";
328 $hashcolor= qx(git config --get-color color.diff.commit "yellow");
329 $datecolor= qx(git config --get-color color.log-compact.date "bold blue");
330 $timecolor= qx(git config --get-color color.log-compact.time "green") if $timeformat;
331 $initialscolor = qx(git config --get-color color.log-compact.initials "red") if $iw;
332 $resetcolor = qx(git config --get-color "" "reset");
335 $decopt = "$autocolor%d" if use_decorate;
336 my $pager = qx(git var GIT_PAGER);
337 defined($pager) and chomp $pager;
338 $ENV{LESS} = "-FRX" unless exists $ENV{LESS};
339 $ENV{LV} = "-c" unless exists $ENV{LV};
341 my ($lastdate, $lastprefix, $lastplainprefix) = ("");
343 $msgopt = "%gd: $reflogsubj" if $usereflog;
345 open(LOG, '-|', "git", "log", "--color=$color",
346 "--format=tformat:$mark%x1fCOMMIT %H %h $dateopt%x1f%cn%x1f%an%x1f%P%x1f$decopt $msgopt%x1f",
348 if (defined($pager) && $pager ne "cat") {
349 open OUT, "|$pager" or dodie "could not run pager \"$pager\": $!\n";
351 open OUT, '>&STDOUT
' or die "could not dupe STDOUT: $!";
353 select((select(OUT),$|=1)[0]);
355 my @lastparents = ();
357 my ($prefix, $data, $parentlist, $subject);
358 while (my $logline = <LOG>) {
359 ($prefix, $data, $committer, $author, $parentlist, $subject) = split(/\x1f/, $logline, -1);
360 $subject =~ s/ // if $subject;
361 my ($flag, $fullhash, $hash, $timestamp) = split(" ", $data, 4) if defined($data);
362 if (!defined($flag) || $flag ne "COMMIT") {
364 $delblank = 0, next if $delblank && !$usegraph && $prefix =~ /^\s*$/;
365 $delblank = 0, next if $delblank && $usegraph && !get_blank_graph_indent($prefix);
366 print OUT "$prefix\n";
367 $lastprefix = $prefix;
368 $lastplainprefix = undef;
370 $lastwascommit = undef;
373 my $isroot = !$parentlist;
374 my @parents = split(' ', $parentlist) if $usegraph;
375 my $initials = $iw ? get_initials($$ivar) : "";
376 my $initials2 = $iw2 ? get_initials($$ivar2) : "";
377 my ($newdate, $newday, $newtime) = split(" ", strftime("%Y-%m-%d %a $timeformat", localtime($timestamp)));
378 $newdate .= " " . $newday if $usewkday;
379 my $mightneedbreak = $lastwascommit && !$lastwasroot && $usegraph && !grep($_ eq $fullhash, @lastparents);
380 if ($lastdate ne $newdate || $mightneedbreak) {
382 if (!$lastdate || $mark) {
383 $indent = get_first_indent($prefix);
384 $lastprefix = $prefix;
385 $lastplainprefix = undef;
386 } elsif ($prefix ne "") {
387 my $newplainprefix = get_nocolor_indent($prefix);
388 defined($lastplainprefix) or $lastplainprefix = get_nocolor_indent($lastprefix);
391 if ($newplainprefix =~ /^(.*?[-=^<>*+o])/) {{
392 my $marklen = length($1);
393 my $difflen = length($lastplainprefix) - length($1);
396 my $lastmark = substr($lastplainprefix, $marklen-1, 1);
397 $lastmark =~ /[-=^<>*+o]/ and $nobar = $lastwasroot || $mightneedbreak, last;
398 $lastmark eq "|" && $lastdate ne $newdate and
400 $barcolor = get_bar_color($lastprefix, $marklen - 1),
403 if ($lastdate eq $newdate) {
404 $lastprefix = $prefix;
405 $lastplainprefix = $newplainprefix;
408 $difflen >= -1 or last;
409 substr($lastplainprefix, $marklen-2, 1) eq "\\" and
411 $barcolor = get_bar_color($lastprefix, $marklen - 2),
414 substr($lastplainprefix, $marklen, 1) eq "/" and
416 $barcolor = get_bar_color($lastprefix, $marklen);
418 $indent = get_indent($prefix);
419 $lastprefix = $prefix;
420 $prefix = get_prefix($prefix);
421 $lastplainprefix = $newplainprefix;
423 if ($lastdate ne $newdate) {
424 printf OUT "%s%s=== %s ===%s\n", $indent,
425 $datecolor, $newdate, $resetcolor;
426 $lastdate = $newdate;
428 printf OUT "%s%s %s%s%-${iw}s%s%-${iw2}s %s\n", $indent,
429 ' ' x length($hash), ' ' x length($newtime),
430 ($iw ? " " : ""), "", ($iw2 ? " " : ""), "",
434 $lastprefix = $prefix;
435 $lastplainprefix = undef;
438 $lastwasroot = $isroot;
439 @lastparents = @parents;
444 $prefix = substr($prefix, 0, length($prefix) - 1) . "_"
446 if ($prefix =~ /^(.*?[-=^<>*+o])(.+)$/) {
447 my ($initial, $trail) = ($1, $2);
449 $prefix = $initial . $trail;
453 $prefix = $fixmark . substr($prefix, 1)
454 if $prefix =~ /^[<>]/;
456 printf OUT "%s%s%s%s%s%-${iw}s%s%-${iw2}s%s%s\n", $prefix,
457 "$hashcolor$hash$resetcolor",
458 $rootflag, ($timeformat ? "$timecolor$newtime$resetcolor " : ""),
459 $initialscolor, $initials, ($iw2 ? "/" : ""), $initials2,
460 ($iw ? "$resetcolor " : ""), $subject;