#!/usr/bin/perl use strict; use warnings; use POSIX qw(strftime setlocale LC_ALL); $ENV{'TZ'} = "CET"; setlocale(LC_ALL, "C"); { my ($last_ts, $last_email, $last_commit, $last_message) = (0, ""); sub print_commit { my ($commit, $email, $ts, @message) = @_; return unless $commit; # display series by the same author and with the same author date # as a single changelog entry (see scripts/log2) if ($last_ts != $ts || $last_email ne $email) { if ($last_commit) { print "- commit " . substr($last_commit, 0, 7) . "\n\n"; } return unless @message; print "-" x 67 . "\n"; print strftime("%a %b %e %H:%M:%S %Z %Y - $email\n\n", localtime($ts)); $last_commit = $commit; $last_message = ""; } $last_ts = $ts; $last_email = $email; my $first = 1; for my $line (@message) { if ($line !~ /^[- ] /) { if ($first) { $line = "- $line"; } else { $line = " $line"; } } $first = 0; } my $msg = join("\n", @message); if ($msg eq $last_message) { # avoid printing cherry-picked commits twice # FIXME: Handle the case where a whole patch series is # cherry-picked one by one. At the same time, we do not want # to filter commits, that have the same changelog within a # series, but are different. See for example # git grep 'e1000e: update driver version number' SLE11-SP3 # and many others return; } $last_message = $msg; print $msg, "\n"; } } sub parse_gitlog { my $fh = shift; my @res; my $cur = { message => [] }; my @states = qw(commit tree parent author committer blank message); my $st = 0; my $gpgsig = 0; while (my $line = <$fh>) { next if $line =~ /^#/; chomp($line); my $expect = $states[$st]; if ($expect eq "blank") { if ($gpgsig > 0) { if ($line =~ /-----END PGP SIGNATURE-----/) { $gpgsig = 0; } next; } if ($line =~ /^gpgsig/) { $gpgsig = 1; next; } if ($line ne "") { die "Malformed git rev-parse output ($cur->{commit}): expected blank line, got \"$line\"\n"; } $st++; next; } if ($expect eq "message") { if ($line eq "") { push(@res, $cur); $cur = { message => [] }; $st = 0; next; } if ($line !~ s/^ {4}//) { die "Malformed git rev-parse output ($cur->{commit}): expected log message, got \"$line\"\n"; } next unless $line; # delete Signed-off-by: et al next if $line =~ /^[A-Z][-a-zA-Z]+-by: /; push(@{$cur->{message}}, $line) if $line; next; } # parsing commit headers next if $expect eq "commit" && $line eq ""; (my $got = $line) =~ s/ .*//; # Root commit has no "parent" header. Multiple "parent" headers are # not possible, since we use --no-merges if ($expect eq "parent" && $got eq "author") { $expect = $states[++$st]; } if ($got ne $expect) { $cur->{commit} ||= "commit unknown"; die "Malformed git rev-parse output ($cur->{commit}): expected \"$expect\", got \"$got\"\n"; } if ($got eq "commit") { ($cur->{commit} = $line) =~ s/^commit //; } elsif ($got eq "author") { ($cur->{email} = $line) =~ s/.*<(.+)>.*/$1/; ($cur->{ts} = $line) =~ s/.*> (\d+) [-+]\d{4}$/$1/; if (!$cur->{email} || !$cur->{ts}) { die "Malformed author header ($cur->{commit}): $line\n"; } } $st++; } return @res; } my $excludes_file; if ($ARGV[0] eq "--excludes") { shift(@ARGV); $excludes_file = shift(@ARGV); } my @fixups; if ($ARGV[0] eq "--fixups") { shift(@ARGV); my $fixups_file = shift(@ARGV); open(my $fh, '<', $fixups_file) or die "$fixups_file: $!\n"; @fixups = parse_gitlog($fh); close($fh); } open(my $pipe, '-|', "git", "rev-list", "--no-merges", "--pretty=raw", @ARGV) or die "Error running git rev-list: $!\n"; my @commits = parse_gitlog($pipe); close($pipe) or die "Error running git rev-list: $!\n"; # apply any fixups my %commits_h = map { $_->{commit} => $_ } @commits; for my $fix (@fixups) { my $orig = $commits_h{$fix->{commit}}; if (!$fix->{message}) { # delete the original commit $orig->{commit} = undef; } else { $orig->{email} = $fix->{email}; $orig->{ts} = $fix->{ts}; $orig->{message} = $fix->{message}; } } if ($excludes_file) { open(my $fh, '<', $excludes_file) or die "$excludes_file: $!\n"; while (my $id = <$fh>) { next if $id =~ /^#/; chomp($id); # delete the original commit $commits_h{$id}->{commit} = undef; } close($fh); } for my $c (sort { $b->{ts} - $a->{ts} } @commits) { print_commit($c->{commit}, $c->{email}, $c->{ts}, @{$c->{message}}); } # print "- commit 1234567" for the last commit print_commit($commits[$#commits]->{commit}, "", 0);