3 # git-log-compact.pl -- compact git log --oneline alternative with dates, times and initials
4 # Copyright (C) 2015,2016,2020 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 --initials=author,committer
33 Use author/committer initials and --two-initials
34 --initials=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
;
73 sub get_initials
($$) {
76 my $wasutf8 = utf8
::decode
($initials);
77 $initials = lc($initials)." ";
78 $initials =~ s/[.]/ /g;
79 $initials =~ s/ iii? / /g;
80 $initials =~ s/ iv / /g;
81 $initials =~ s/ [js]r / /g;
82 $initials =~ s/[,;:'\042+_-]//g;
83 $initials =~ s/\([^(]*?\)/ /g;
84 $initials =~ s/\[[^[]*?\]/ /g;
85 $initials =~ s/\s+/ /g;
87 return "jc" if $iw == 2 && $initials eq "junio c hamano ";
88 $initials =~ s/([^ ])[^ ]* /$1/g;
90 $initials =~ s/^(.).+(.)$/$1$2/;
92 $initials =~ s/^(..).+(.)$/$1$2/;
94 $initials .= " " x
($width - length($initials))
95 if length($initials) < $width;
96 utf8
::encode
($initials) if $wasutf8;
100 sub get_nocolor_indent
{
102 $indent =~ s/\033[^m]*m//g;
104 $indent =~ s/-+\.$//;
108 sub get_blank_graph_indent
{
111 $indent =~ s/\033[^m]*m//g;
112 $indent =~ s/^[\s|]+//;
116 sub get_first_indent
{
118 $indent =~ s/\033[^m]*m//g;
128 my ($prefix, $index) = @_;
129 my $c = (split(m{[-=^<>*+o /|\\_]}, $prefix))[$index];
130 $c =~ s/\Q$resetcolor\E//g if $resetcolor;
137 $indent =~ tr/\-=^<>*+o./ /;
139 $indent =~ s/[-=^<>*+o]/$barcolor ? $barcolor."|".$resetcolor : "|"/e;
140 $indent =~ tr/\-./ /;
147 $indent =~ tr
'\/'||';
152 # defaults are cumulative, but an empty setting resets
154 my $opts = qx(git config --get-all log-compact.defaults 2>/dev/null);
156 foreach (split(/\r\n|\r|\n/, $opts, -1)) {
164 return split(" ", join(" ", @defaults));
167 system("git rev-parse --git-dir >/dev/null") == 0 or exit(1);
168 my ($usemark, $usegraph, $usereflog, $useboundary, $useleftright, $usecherry, $setusecolor, $usecolor, $usecad);
173 my $reflogsubj = "%gs";
177 my ($committer, $author, $ivar, $ivar2);
179 foreach my $arg (get_defaults(), @ARGV) {
181 if ($sawdashdash || $lastwasgrep) {
183 $lastwasgrep = $nextisgrep;
188 my $exec_path = qx(git --exec-path 2>/dev/null);
190 $dash = " " if $ENV{PATH} =~ /^\Q$exec_path\E:/;
191 printf "$USAGE\n", $dash;
193 } elsif ($arg eq "--oneline") {
194 # silently ignore --oneline as we are always in a one line format
196 } elsif ($arg eq "--seconds") {
198 $timeformat = "%H:%M:%S";
200 } elsif ($arg eq "--minutes") {
202 $timeformat = "%H:%M";
204 } elsif ($arg eq "--no-times") {
208 } elsif ($arg eq "--two-initials") {
212 } elsif ($arg eq "--three-initials") {
216 } elsif ($arg eq "--no-initials") {
220 } elsif ($arg eq "--two-initials") {
224 } elsif ($arg eq "--commit-message") {
228 } elsif ($arg eq "--author-date") {
233 } elsif ($arg eq "--committer-date") {
238 } elsif ($arg eq "--weekday") {
242 } elsif ($arg eq "--no-weekday") {
246 } elsif ($arg =~ /^--initials=/) {
248 $arg =~ s/^--initials=//;
249 if ($arg eq "author") {
252 } elsif ($arg eq "committer") {
255 } elsif ($arg eq "committer,author" || $arg eq "committer/author") {
258 } elsif ($arg eq "author,committer" || $arg eq "author/committer") {
260 $ivar2 = \$committer;
262 dodie "--initials= requires 'author
', 'committer
' or 'committer
,author
'";
265 } elsif ($arg =~ /^--time-zone=/) {
267 $arg =~ s/^--time-zone=//;
270 } elsif ($arg eq "--date-order" || $arg eq "--topo-order") {
271 $dateopt = "%ct" unless $usecad;
272 } elsif ($arg eq "--author-date-order") {
274 } elsif ($arg =~ /^--(pretty|pretty=.*|format=.*|notes|show-notes|show-notes=.*|standard-notes)$/) {
275 dodie "formatting/notes option not allowed: $arg";
276 } elsif ($arg eq "--no-decorate" || $arg eq "--decorate=no") {
278 $usedecorate = undef;
279 } elsif ($arg eq "--decorate=auto") {
281 $usedecorate = -t STDOUT ? 1 : undef;
282 } elsif ($arg eq "--decorate" || $arg =~ /^--decorate=/) {
285 } elsif ($arg eq "--color" || $arg eq "--color=always") {
288 } elsif ($arg eq "--no-color" || $arg eq "--color=never") {
291 } elsif ($arg eq "--color=auto") {
293 $usecolor = -t STDOUT ? 1 : undef;
294 } elsif ($arg eq "-g" || $arg eq "--walk-reflogs") {
296 } elsif ($arg eq "--boundary") {
299 } elsif ($arg eq "--cherry-mark" || $arg eq "--cherry") {
302 } elsif ($arg eq "--left-right") {
305 } elsif ($arg eq "--graph") {
307 } elsif ($arg =~ /^(--grep|--grep-reflog|-S|-G)$/) {
309 } elsif ($arg eq "--") {
313 $lastwasgrep = $nextisgrep;
315 $iw = defined($ivar2) ? 2 : 3 unless defined($iw);
317 $iw2 = $iw if defined($ivar2);
318 my ($mark, $fixmark) = ("");
319 $mark = "%m " unless $usegraph || !$usemark;
320 if ($mark && !$useleftright) {
322 $fixmark = "+" if $usecherry;
326 my ($hashcolor, $datecolor, $timecolor, $initialscolor, $autocolor) = ("", "", "", "", "");
327 $usecolor = 1 if !$setusecolor && system("git", "config", "--get-colorbool", "color.diff") == 0;
330 $autocolor = "%C(auto)";
331 $hashcolor= qx(git config --get-color color.diff.commit "yellow");
332 $datecolor= qx(git config --get-color color.log-compact.date "bold blue");
333 $timecolor= qx(git config --get-color color.log-compact.time "green") if $timeformat;
334 $initialscolor = qx(git config --get-color color.log-compact.initials "red") if $iw;
335 $resetcolor = qx(git config --get-color "" "reset");
338 $decopt = "$autocolor%d" if use_decorate;
339 my $pager = qx(git var GIT_PAGER);
340 defined($pager) and chomp $pager;
341 $ENV{LESS} = "-FRX" unless exists $ENV{LESS};
342 $ENV{LV} = "-c" unless exists $ENV{LV};
344 my ($lastdate, $lastprefix, $lastplainprefix) = ("");
346 $msgopt = "%gd: $reflogsubj" if $usereflog;
348 open(LOG, '-|', "git", "log", "--color=$color",
349 "--format=tformat:$mark%x1fCOMMIT %H %h $dateopt%x1f%cn%x1f%an%x1f%P%x1f$decopt $msgopt%x1f",
351 if (defined($pager) && $pager ne "cat") {
352 open OUT, "|$pager" or dodie "could not run pager \"$pager\": $!\n";
354 open OUT, '>&STDOUT
' or die "could not dupe STDOUT: $!";
356 select((select(OUT),$|=1)[0]);
358 my @lastparents = ();
360 my ($prefix, $data, $parentlist, $subject);
361 while (my $logline = <LOG>) {
362 ($prefix, $data, $committer, $author, $parentlist, $subject) = split(/\x1f/, $logline, -1);
363 $subject =~ s/ // if $subject;
364 my ($flag, $fullhash, $hash, $timestamp) = split(" ", $data, 4) if defined($data);
365 if (!defined($flag) || $flag ne "COMMIT") {
367 $delblank = 0, next if $delblank && !$usegraph && $prefix =~ /^\s*$/;
368 $delblank = 0, next if $delblank && $usegraph && !get_blank_graph_indent($prefix);
369 print OUT "$prefix\n";
370 $lastprefix = $prefix;
371 $lastplainprefix = undef;
373 $lastwascommit = undef;
376 my $isroot = !$parentlist;
377 my @parents = split(' ', $parentlist) if $usegraph;
378 my $initials = $iw ? get_initials($$ivar, $iw) : "";
379 my $initials2 = $iw2 ? get_initials($$ivar2, $iw2) : "";
380 my ($newdate, $newday, $newtime) = split(" ", strftime("%Y-%m-%d %a $timeformat", localtime($timestamp)));
381 $newdate .= " " . $newday if $usewkday;
382 my $mightneedbreak = $lastwascommit && !$lastwasroot && $usegraph && !grep($_ eq $fullhash, @lastparents);
383 if ($lastdate ne $newdate || $mightneedbreak) {
385 if (!$lastdate || $mark) {
386 $indent = get_first_indent($prefix);
387 $lastprefix = $prefix;
388 $lastplainprefix = undef;
389 } elsif ($prefix ne "") {
390 my $newplainprefix = get_nocolor_indent($prefix);
391 defined($lastplainprefix) or $lastplainprefix = get_nocolor_indent($lastprefix);
394 if ($newplainprefix =~ /^(.*?[-=^<>*+o])/) {{
395 my $marklen = length($1);
396 my $difflen = length($lastplainprefix) - length($1);
399 my $lastmark = substr($lastplainprefix, $marklen-1, 1);
400 $lastmark =~ /[-=^<>*+o]/ and $nobar = $lastwasroot || $mightneedbreak, last;
401 $lastmark eq "|" && $lastdate ne $newdate and
403 $barcolor = get_bar_color($lastprefix, $marklen - 1),
406 if ($lastdate eq $newdate) {
407 $lastprefix = $prefix;
408 $lastplainprefix = $newplainprefix;
411 $difflen >= -1 or last;
412 substr($lastplainprefix, $marklen-2, 1) eq "\\" and
414 $barcolor = get_bar_color($lastprefix, $marklen - 2),
417 substr($lastplainprefix, $marklen, 1) eq "/" and
419 $barcolor = get_bar_color($lastprefix, $marklen);
421 $indent = get_indent($prefix);
422 $lastprefix = $prefix;
423 $prefix = get_prefix($prefix);
424 $lastplainprefix = $newplainprefix;
426 if ($lastdate ne $newdate) {
427 printf OUT "%s%s=== %s ===%s\n", $indent,
428 $datecolor, $newdate, $resetcolor;
429 $lastdate = $newdate;
431 printf OUT "%s%s %s%s%-${iw}s%s%-${iw2}s %s\n", $indent,
432 ' ' x length($hash), ' ' x length($newtime),
433 ($iw ? " " : ""), "", ($iw2 ? " " : ""), "",
437 $lastprefix = $prefix;
438 $lastplainprefix = undef;
441 $lastwasroot = $isroot;
442 @lastparents = @parents;
447 $prefix = substr($prefix, 0, length($prefix) - 1) . "_"
449 if ($prefix =~ /^(.*?[-=^<>*+o])(.+)$/) {
450 my ($initial, $trail) = ($1, $2);
452 $prefix = $initial . $trail;
456 $prefix = $fixmark . substr($prefix, 1)
457 if $prefix =~ /^[<>]/;
459 printf OUT "%s%s%s%s%s%s%s%s%s%s\n", $prefix,
460 "$hashcolor$hash$resetcolor",
461 $rootflag, ($timeformat ? "$timecolor$newtime$resetcolor " : ""),
462 $initialscolor, $initials, ($iw2 ? "/" : ""), $initials2,
463 ($iw ? "$resetcolor " : ""), $subject;