diff options
| author | Jim Meyering <meyering@fb.com> | 2016-08-12 11:17:26 -0700 |
|---|---|---|
| committer | Jim Meyering <meyering@fb.com> | 2016-08-13 21:56:16 -0700 |
| commit | b3def738f3b435cbe6f2a8406bae5a71175a0b80 (patch) | |
| tree | a984782eeab1f99f25a5bf7950cc6f71fbbf67fd /scripts/git-hooks/commit-msg | |
| parent | 1a0df4396ebe3b9a58b882bb976cfce3f50d3cac (diff) | |
| download | diffutils-b3def738f3b435cbe6f2a8406bae5a71175a0b80.tar.gz | |
maint: require that commit messages be of a certain form
* bootstrap.conf (bootstrap_epilogue): Merge from coreutils, so that
a local commit hook will now help enforce consistent commit messages.
* Makefile.am (check-git-hook-script-sync): New rule, largely copied
from coreutils.
* scripts/git-hooks/commit-msg: New file, from coreutils, but
with adapted list of program names.
* scripts/git-hooks/applypatch-msg: New file, from git.
* scripts/git-hooks/pre-applypatch: Likewise.
* scripts/git-hooks/pre-commit: Likewise.
Diffstat (limited to 'scripts/git-hooks/commit-msg')
| -rwxr-xr-x | scripts/git-hooks/commit-msg | 153 |
1 files changed, 153 insertions, 0 deletions
diff --git a/scripts/git-hooks/commit-msg b/scripts/git-hooks/commit-msg new file mode 100755 index 0000000..888b83b --- /dev/null +++ b/scripts/git-hooks/commit-msg @@ -0,0 +1,153 @@ +eval '(exit $?0)' && eval 'exec perl -w "$0" ${1+"$@"}' + & eval 'exec perl -w "$0" $argv:q' + if 0; + +use strict; +use warnings; +(my $ME = $0) =~ s|.*/||; + +# Emulate Git's choice of the editor for the commit message. +chomp (my $editor = `git var GIT_EDITOR`); +# And have a sane, minimal fallback in case of weird failures. +$editor = "vi" if $? != 0 or $editor =~ /^\s*\z/; + +# Keywords allowed before the colon on the first line of a commit message: +# program names and a few general category names. +my @valid = qw( + diff diff3 cmp sdiff + + gnulib tests maint doc build scripts + ); +my $v_or = join '|', @valid; +my $valid_regex = qr/^(?:$v_or)$/; + +# Rewrite the $LOG_FILE (old contents in @$LINE_REF) with an additional +# a commented diagnostic "# $ERR" line at the top. +sub rewrite($$$) +{ + my ($log_file, $err, $line_ref) = @_; + local *LOG; + open LOG, '>', $log_file + or die "$ME: $log_file: failed to open for writing: $!"; + print LOG "# $err"; + print LOG @$line_ref; + close LOG + or die "$ME: $log_file: failed to rewrite: $!\n"; +} + +sub re_edit($) +{ + my ($log_file) = @_; + + warn "Interrupt (Ctrl-C) to abort...\n"; + + system 'sh', '-c', "$editor $log_file"; + ($? & 127) || ($? >> 8) + and die "$ME: $log_file: the editor ($editor) failed, aborting\n"; +} + +sub bad_first_line($) +{ + my ($line) = @_; + + $line =~ /^[Vv]ersion \d/ + and return ''; + + $line =~ /:/ + or return 'missing colon on first line of log message'; + + $line =~ /\.$/ + and return 'do not use a period "." at the end of the first line'; + + # The token(s) before the colon on the first line must be on our list + # Tokens may be space- or comma-separated. + (my $pre_colon = $line) =~ s/:.*//; + my @word = split (/[ ,]/, $pre_colon); + my @bad = grep !/$valid_regex/, @word; + @bad + and return 'invalid first word(s) of summary line: ' . join (', ', @bad); + + return ''; +} + +# Given a $LOG_FILE name and a \@LINE buffer, +# read the contents of the file into the buffer and analyze it. +# If the log message passes muster, return the empty string. +# If not, return a diagnostic. +sub check_msg($$) +{ + my ($log_file, $line_ref) = @_; + + local *LOG; + open LOG, '<', $log_file + or return "failed to open for reading: $!"; + @$line_ref = <LOG>; + close LOG; + + my @line = @$line_ref; + chomp @line; + + # Don't filter out blank or comment lines; git does that already, + # and if we were to ignore them here, it could lead to committing + # with lines that start with "#" in the log. + + # Filter out leading blank and comment lines. + # while (@line && $line[0] =~ /^(?:#.*|[ \t]*)$/) { shift @line; } + + # Filter out blank and comment lines at EOF. + # while (@line && $line[$#line] =~ /^(?:#.*|[ \t]*)$/) { pop @line; } + + @line == 0 + and return 'no log message'; + + my $bad = bad_first_line $line[0]; + $bad + and return $bad; + + # Second line should be blank or not present. + 2 <= @line && length $line[1] + and return 'second line must be empty'; + + # Limit line length to allow for the ChangeLog's leading TAB. + foreach my $line (@line) + { + 72 < length $line && $line =~ /^[^#]/ + and return 'line longer than 72'; + } + + my $buf = join ("\n", @line) . "\n"; + $buf =~ m!https?://bugzilla\.redhat\.com/show_bug\.cgi\?id=(\d+)!s + and return "use shorter http://bugzilla.redhat.com/$1"; + + $buf =~ m!https?://debbugs\.gnu\.org/(?:cgi/bugreport\.cgi\?bug=)?(\d+)!s + and return "use shorter http://bugs.gnu.org/$1"; + + $buf =~ /^ *Signed-off-by:/mi + and return q(do not use "Signed-off-by:"); + + return ''; +} + +{ + @ARGV == 1 + or die; + + my $log_file = $ARGV[0]; + + while (1) + { + my @line; + my $err = check_msg $log_file, \@line; + $err eq '' + and last; + $err = "$ME: $err\n"; + warn $err; + # Insert the diagnostic as a comment on the first line of $log_file. + rewrite $log_file, $err, \@line; + re_edit $log_file; + + # Stop if our parent is killed. + getppid() == 1 + and last; + } +} |
