diff options
Diffstat (limited to 'etc/gitlog-to-changelog')
-rwxr-xr-x | etc/gitlog-to-changelog | 230 |
1 files changed, 212 insertions, 18 deletions
diff --git a/etc/gitlog-to-changelog b/etc/gitlog-to-changelog index 7660af5..38c6f3a 100755 --- a/etc/gitlog-to-changelog +++ b/etc/gitlog-to-changelog @@ -3,13 +3,13 @@ eval '(exit $?0)' && eval 'exec perl -wS "$0" ${1+"$@"}' if 0; # Convert git log output to ChangeLog format. -my $VERSION = '2009-10-30 13:46'; # UTC +my $VERSION = '2012-01-18 07:50'; # UTC # The definition above must lie within the first 8 lines in order # for the Emacs time-stamp write hook (at end) to update it. # If you change this file with Emacs, please let the write hook # do its job. Otherwise, update this string manually. -# Copyright (C) 2008-2010 Free Software Foundation, Inc. +# Copyright (C) 2008-2012 Free Software Foundation, Inc. # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -47,7 +47,7 @@ sub usage ($) my $STREAM = ($exit_code == 0 ? *STDOUT : *STDERR); if ($exit_code != 0) { - print $STREAM "Try `$ME --help' for more information.\n"; + print $STREAM "Try '$ME --help' for more information.\n"; } else { @@ -60,6 +60,14 @@ $ME, they may be preceded by '--'. OPTIONS: + --amend=FILE FILE maps from an SHA1 to perl code (i.e., s/old/new/) that + makes a change to SHA1's commit log text or metadata. + --append-dot append a dot to the first line of each commit message if + there is no other punctuation or blank at the end. + --no-cluster never cluster commit messages under the same date/author + header; the default is to cluster adjacent commit messages + if their headers are the same and neither commit message + contains multiple paragraphs. --since=DATE convert only the logs since DATE; the default is to convert all log entries. --format=FMT set format string for commit subject and body; @@ -74,6 +82,38 @@ EXAMPLE: $ME --since=2008-01-01 > ChangeLog $ME -- -n 5 foo > last-5-commits-to-branch-foo +SPECIAL SYNTAX: + +The following types of strings are interpreted specially when they appear +at the beginning of a log message line. They are not copied to the output. + + Copyright-paperwork-exempt: Yes + Append the "(tiny change)" notation to the usual "date name email" + ChangeLog header to mark a change that does not require a copyright + assignment. + Co-authored-by: Joe User <user\@example.com> + List the specified name and email address on a second + ChangeLog header, denoting a co-author. + Signed-off-by: Joe User <user\@example.com> + These lines are simply elided. + +In a FILE specified via --amend, comment lines (starting with "#") are ignored. +FILE must consist of <SHA,CODE+> pairs where SHA is a 40-byte SHA1 (alone on +a line) referring to a commit in the current project, and CODE refers to one +or more consecutive lines of Perl code. Pairs must be separated by one or +more blank line. + +Here is sample input for use with --amend=FILE, from coreutils: + +3a169f4c5d9159283548178668d2fae6fced3030 +# fix typo in title: +s/all tile types/all file types/ + +1379ed974f1fa39b12e2ffab18b3f7a607082202 +# Due to a bug in vc-dwim, I mis-attributed a patch by Paul to myself. +# Change the author to be Paul. Note the escaped "@": +s,Jim .*>,Paul Eggert <eggert\\\@cs.ucla.edu>, + EOF } exit $exit_code; @@ -99,24 +139,90 @@ sub quoted_cmd(@) return join (' ', map {shell_quote $_} @_); } +# Parse file F. +# Comment lines (starting with "#") are ignored. +# F must consist of <SHA,CODE+> pairs where SHA is a 40-byte SHA1 +# (alone on a line) referring to a commit in the current project, and +# CODE refers to one or more consecutive lines of Perl code. +# Pairs must be separated by one or more blank line. +sub parse_amend_file($) +{ + my ($f) = @_; + + open F, '<', $f + or die "$ME: $f: failed to open for reading: $!\n"; + + my $fail; + my $h = {}; + my $in_code = 0; + my $sha; + while (defined (my $line = <F>)) + { + $line =~ /^\#/ + and next; + chomp $line; + $line eq '' + and $in_code = 0, next; + + if (!$in_code) + { + $line =~ /^([0-9a-fA-F]{40})$/ + or (warn "$ME: $f:$.: invalid line; expected an SHA1\n"), + $fail = 1, next; + $sha = lc $1; + $in_code = 1; + exists $h->{$sha} + and (warn "$ME: $f:$.: duplicate SHA1\n"), + $fail = 1, next; + } + else + { + $h->{$sha} ||= ''; + $h->{$sha} .= "$line\n"; + } + } + close F; + + $fail + and exit 1; + + return $h; +} + { - my $since_date = '1970-01-01 UTC'; + my $since_date; my $format_string = '%s%n%b%n'; + my $amend_file; + my $append_dot = 0; + my $cluster = 1; GetOptions ( help => sub { usage 0 }, version => sub { print "$ME version $VERSION\n"; exit }, 'since=s' => \$since_date, 'format=s' => \$format_string, + 'amend=s' => \$amend_file, + 'append-dot' => \$append_dot, + 'cluster!' => \$cluster, ) or usage 1; - my @cmd = (qw (git log --log-size), "--since=$since_date", - '--pretty=format:%ct %an <%ae>%n%n'.$format_string, @ARGV); + + defined $since_date + and unshift @ARGV, "--since=$since_date"; + + # This is a hash that maps an SHA1 to perl code (i.e., s/old/new/) + # that makes a correction in the log or attribution of that commit. + my $amend_code = defined $amend_file ? parse_amend_file $amend_file : {}; + + my @cmd = (qw (git log --log-size), + '--pretty=format:%H:%ct %an <%ae>%n%n'.$format_string, @ARGV); open PIPE, '-|', @cmd - or die ("$ME: failed to run `". quoted_cmd (@cmd) ."': $!\n" + or die ("$ME: failed to run '". quoted_cmd (@cmd) ."': $!\n" . "(Is your Git too old? Version 1.5.1 or later is required.)\n"); + my $prev_multi_paragraph; my $prev_date_line = ''; + my @prev_coauthors = (); while (1) { defined (my $in = <PIPE>) @@ -130,7 +236,34 @@ sub quoted_cmd(@) $n_read == $log_nbytes or die "$ME:$.: unexpected EOF\n"; - my @line = split "\n", $log; + # Extract leading hash. + my ($sha, $rest) = split ':', $log, 2; + defined $sha + or die "$ME:$.: malformed log entry\n"; + $sha =~ /^[0-9a-fA-F]{40}$/ + or die "$ME:$.: invalid SHA1: $sha\n"; + + # If this commit's log requires any transformation, do it now. + my $code = $amend_code->{$sha}; + if (defined $code) + { + eval 'use Safe'; + my $s = new Safe; + # Put the unpreprocessed entry into "$_". + $_ = $rest; + + # Let $code operate on it, safely. + my $r = $s->reval("$code") + or die "$ME:$.:$sha: failed to eval \"$code\":\n$@\n"; + + # Note that we've used this entry. + delete $amend_code->{$sha}; + + # Update $rest upon success. + $rest = $_; + } + + my @line = split "\n", $rest; my $author_line = shift @line; defined $author_line or die "$ME:$.: unexpected EOF\n"; @@ -138,19 +271,62 @@ sub quoted_cmd(@) or die "$ME:$.: Invalid line " . "(expected date/author/email):\n$author_line\n"; - my $date_line = sprintf "%s $2\n", strftime ("%F", localtime ($1)); - # If this line would be the same as the previous date/name/email - # line, then arrange not to print it. - if ($date_line ne $prev_date_line) + # Format 'Copyright-paperwork-exempt: Yes' as a standard ChangeLog + # `(tiny change)' annotation. + my $tiny = (grep (/^Copyright-paperwork-exempt:\s+[Yy]es$/, @line) + ? ' (tiny change)' : ''); + + my $date_line = sprintf "%s %s$tiny\n", + strftime ("%F", localtime ($1)), $2; + + my @coauthors = grep /^Co-authored-by:.*$/, @line; + # Omit meta-data lines we've already interpreted. + @line = grep !/^(?:Signed-off-by:[ ].*>$ + |Co-authored-by:[ ] + |Copyright-paperwork-exempt:[ ] + )/x, @line; + + # Remove leading and trailing blank lines. + if (@line) + { + while ($line[0] =~ /^\s*$/) { shift @line; } + while ($line[$#line] =~ /^\s*$/) { pop @line; } + } + + # Record whether there are two or more paragraphs. + my $multi_paragraph = grep /^\s*$/, @line; + + # Format 'Co-authored-by: A U Thor <email@example.com>' lines in + # standard multi-author ChangeLog format. + for (@coauthors) + { + s/^Co-authored-by:\s*/\t /; + s/\s*</ </; + + /<.*?@.*\..*>/ + or warn "$ME: warning: missing email address for " + . substr ($_, 5) . "\n"; + } + + # If clustering of commit messages has been disabled, if this header + # would be different from the previous date/name/email/coauthors header, + # or if this or the previous entry consists of two or more paragraphs, + # then print the header. + if ( ! $cluster + || $date_line ne $prev_date_line + || "@coauthors" ne "@prev_coauthors" + || $multi_paragraph + || $prev_multi_paragraph) { $prev_date_line eq '' or print "\n"; print $date_line; + @coauthors + and print join ("\n", @coauthors), "\n"; } $prev_date_line = $date_line; - - # Omit "Signed-off-by..." lines. - @line = grep !/^Signed-off-by: .*>$/, @line; + @prev_coauthors = @coauthors; + $prev_multi_paragraph = $multi_paragraph; # If there were any lines if (@line == 0) @@ -159,9 +335,17 @@ sub quoted_cmd(@) } else { - # Remove leading and trailing blank lines. - while ($line[0] =~ /^\s*$/) { shift @line; } - while ($line[$#line] =~ /^\s*$/) { pop @line; } + if ($append_dot) + { + # If the first line of the message has enough room, then + if (length $line[0] < 72) + { + # append a dot if there is no other punctuation or blank + # at the end. + $line[0] =~ /[[:punct:]\s]$/ + or $line[0] .= '.'; + } + } # Prefix each non-empty line with a TAB. @line = map { length $_ ? "\t$_" : '' } @line; @@ -178,6 +362,16 @@ sub quoted_cmd(@) close PIPE or die "$ME: error closing pipe from " . quoted_cmd (@cmd) . "\n"; # FIXME-someday: include $PROCESS_STATUS in the diagnostic + + # Complain about any unused entry in the --amend=F specified file. + my $fail = 0; + foreach my $sha (keys %$amend_code) + { + warn "$ME:$amend_file: unused entry: $sha\n"; + $fail = 1; + } + + exit $fail; } # Local Variables: |