From: Stefano Lattarini Date: Sun, 15 Jan 2012 18:43:18 +0000 (+0100) Subject: maint: generate ChangeLog from git log X-Git-Tag: v2.68b~26 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6ed5195ee1b1d46b225d7e97d897b773e9aedf97;p=thirdparty%2Fautoconf.git maint: generate ChangeLog from git log Following the practice set by various other GNU projects, we start to automatically generate the ChangeLog file from the git commit messages. This will avoid duplication (as the ChangeLog entries were always inserted both in the git commit message and in the version-controlled ChangeLog file), and potential problems with spurious merge conflicts (which, although greatly mitigated by Bruno Haible's `git-merge-changelog' helper program, have never been completely solved). * ChangeLog: Moved ... * ChangeLog.3: ... to this. * build-aux/gitlog-to-changelog: New script, synced from gnulib. * cfg.mk (gnulib-update): Also sync gitlog-to-changelog. * Makefile.am (gen-ChangeLog): New .PHONY rule, generate the ChangeLog for distribution. (dist-hook): Depend on it. ($(srcdir)/ChangeLog): New dummy rule, to pacify automake "gnu" strictness. Creates a dummy ChangeLog, that will be overridden by the proper one at distribution time. (gen_start_date): New variable, the date starting from which the git log entries are to be copied in the generated ChangeLog. (EXTRA_DIST): Add ChangeLog.3 and gitlog-to-changelog. * configure.ac (AC_CONFIG_SRCDIR): Use 'lib/autoconf/autoconf.m4' instead of 'ChangeLog' as the sentinel file. * .gitignore: Add ChangeLog. --- diff --git a/.gitignore b/.gitignore index 119a0ce8..b40b90ac 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ .#* .version CVS +/ChangeLog /Fetchdir /INSTALL Makefile diff --git a/ChangeLog b/ChangeLog.3 similarity index 100% rename from ChangeLog rename to ChangeLog.3 diff --git a/Makefile.am b/Makefile.am index 6799c8e5..5bf4cb5e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -22,12 +22,13 @@ SUBDIRS = bin . lib doc tests man ACLOCAL_AMFLAGS = -I m4 -EXTRA_DIST = ChangeLog.0 ChangeLog.1 ChangeLog.2 \ +EXTRA_DIST = ChangeLog.0 ChangeLog.1 ChangeLog.2 ChangeLog.3 \ BUGS COPYINGv3 COPYING.EXCEPTION \ GNUmakefile maint.mk cfg.mk \ build-aux/gendocs.sh \ build-aux/git-version-gen \ build-aux/announce-gen build-aux/gnupload \ + build-aux/gitlog-to-changelog \ .prev-version .version ## --------- ## @@ -48,6 +49,23 @@ $(srcdir)/INSTALL: $(top_srcdir)/doc/install.texi rm -f tmp.texi endif +# Generate the ChangeLog from git history. +gen_start_date = 2012-01-15 18:00:00 UTC +.PHONY: gen-ChangeLog +gen-ChangeLog: + if test -d $(top_srcdir)/.git; then \ + $(top_srcdir)/build-aux/gitlog-to-changelog \ + --since='$(gen_start_date)' > $(distdir)/cl-t \ + && rm -f $(distdir)/ChangeLog \ + && mv $(distdir)/cl-t $(distdir)/ChangeLog; \ + fi + +# Dummy rule, to pacify automake "gnu" strictness and allow a clean +# bootstrap. Creates a dummy ChangeLog (unless one is already present, +# as might be the case for users of vc-dwim) that will be overridden +# with a proper one at distribution time. +$(srcdir)/ChangeLog: + @echo dummy > $@ # Version string management. There are two files to be aware of: # .tarball-version - present only in a distribution tarball, and not in @@ -69,7 +87,7 @@ $(top_srcdir)/.version: # Arrange so that .tarball-version appears only in distribution tarballs, # never in a checked-out repository. -dist-hook: +dist-hook: gen-ChangeLog echo $(VERSION) > $(distdir)/.tarball-version # Arrange to remove the symlink to GNUmakefile in VPATH builds. diff --git a/build-aux/gitlog-to-changelog b/build-aux/gitlog-to-changelog new file mode 100755 index 00000000..0efedb04 --- /dev/null +++ b/build-aux/gitlog-to-changelog @@ -0,0 +1,377 @@ +eval '(exit $?0)' && eval 'exec perl -wS "$0" ${1+"$@"}' + & eval 'exec perl -wS "$0" $argv:q' + if 0; +# Convert git log output to ChangeLog format. + +my $VERSION = '2012-01-06 07:14'; # 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-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 +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Written by Jim Meyering + +use strict; +use warnings; +use Getopt::Long; +use POSIX qw(strftime); + +(my $ME = $0) =~ s|.*/||; + +# use File::Coda; # http://meyering.net/code/Coda/ +END { + defined fileno STDOUT or return; + close STDOUT and return; + warn "$ME: failed to close standard output: $!\n"; + $? ||= 1; +} + +sub usage ($) +{ + my ($exit_code) = @_; + my $STREAM = ($exit_code == 0 ? *STDOUT : *STDERR); + if ($exit_code != 0) + { + print $STREAM "Try '$ME --help' for more information.\n"; + } + else + { + print $STREAM < 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 + List the specified name and email address on a second + ChangeLog header, denoting a co-author. + Signed-off-by: Joe User + These lines are simply elided. + +In a FILE specified via --amend, comment lines (starting with "#") are ignored. +FILE must consist of 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 , + +EOF + } + exit $exit_code; +} + +# If the string $S is a well-behaved file name, simply return it. +# If it contains white space, quotes, etc., quote it, and return the new string. +sub shell_quote($) +{ + my ($s) = @_; + if ($s =~ m![^\w+/.,-]!) + { + # Convert each single quote to '\'' + $s =~ s/\'/\'\\\'\'/g; + # Then single quote the string. + $s = "'$s'"; + } + return $s; +} + +sub quoted_cmd(@) +{ + return join (' ', map {shell_quote $_} @_); +} + +# Parse file F. +# Comment lines (starting with "#") are ignored. +# F must consist of 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 = )) + { + $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; + my $format_string = '%s%n%b%n'; + my $amend_file; + my $append_dot = 0; + 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, + ) or usage 1; + + + 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" + . "(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 = ) + or last; + $in =~ /^log size (\d+)$/ + or die "$ME:$.: Invalid line (expected log size):\n$in"; + my $log_nbytes = $1; + + my $log; + my $n_read = read PIPE, $log, $log_nbytes; + $n_read == $log_nbytes + or die "$ME:$.: unexpected EOF\n"; + + # 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"; + $author_line =~ /^(\d+) (.*>)$/ + or die "$ME:$.: Invalid line " + . "(expected date/author/email):\n$author_line\n"; + + # 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 ' 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 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 ($date_line ne $prev_date_line + or "@coauthors" ne "@prev_coauthors" + or $multi_paragraph + or $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; + @prev_coauthors = @coauthors; + $prev_multi_paragraph = $multi_paragraph; + + # If there were any lines + if (@line == 0) + { + warn "$ME: warning: empty commit message:\n $date_line\n"; + } + else + { + 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; + + print "\n", join ("\n", @line), "\n"; + } + + defined ($in = ) + or last; + $in ne "\n" + and die "$ME:$.: unexpected line:\n$in"; + } + + 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: +# mode: perl +# indent-tabs-mode: nil +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "my $VERSION = '" +# time-stamp-format: "%:y-%02m-%02d %02H:%02M" +# time-stamp-time-zone: "UTC" +# time-stamp-end: "'; # UTC" +# End: diff --git a/cfg.mk b/cfg.mk index e727d0f7..f14737ec 100644 --- a/cfg.mk +++ b/cfg.mk @@ -55,6 +55,7 @@ gnulib-update: cp $(gnulib_dir)/build-aux/elisp-comp $(srcdir)/build-aux cp $(gnulib_dir)/build-aux/gendocs.sh $(srcdir)/build-aux cp $(gnulib_dir)/build-aux/git-version-gen $(srcdir)/build-aux + cp $(gnulib_dir)/build-aux/gitlog-to-changelog $(srcdir)/build-aux cp $(gnulib_dir)/build-aux/gnupload $(srcdir)/build-aux cp $(gnulib_dir)/build-aux/install-sh $(srcdir)/build-aux cp $(gnulib_dir)/build-aux/mdate-sh $(srcdir)/build-aux diff --git a/configure.ac b/configure.ac index 92efb250..5bf7b9d0 100644 --- a/configure.ac +++ b/configure.ac @@ -22,7 +22,7 @@ AC_PREREQ([2.60]) AC_INIT([GNU Autoconf], m4_esyscmd([build-aux/git-version-gen .tarball-version]), [bug-autoconf@gnu.org]) -AC_CONFIG_SRCDIR([ChangeLog]) +AC_CONFIG_SRCDIR([lib/autoconf/autoconf.m4]) AC_CONFIG_AUX_DIR([build-aux])