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.
This commit is contained in:
Jim Meyering 2016-08-12 11:17:26 -07:00
parent 1a0df4396e
commit b3def738f3
6 changed files with 270 additions and 0 deletions

View File

@ -42,3 +42,17 @@ gen-ChangeLog:
ALL_RECURSIVE_TARGETS += distcheck-hook
distcheck-hook:
$(MAKE) my-distcheck
# Some of our git hook scripts are supposed to be identical to git's samples.
# See if they are still in sync.
.PHONY: check-git-hook-script-sync
check-git-hook-script-sync:
@fail=0; \
t=$$(mktemp -d) \
&& cd $$t && git init -q && cd .git/hooks \
&& for i in pre-commit pre-applypatch applypatch-msg; do \
diff -u $(abs_top_srcdir)/scripts/git-hooks/$$i $$i.sample \
|| fail=1; \
done; \
rm -rf $$t; \
test $$fail = 0

View File

@ -129,4 +129,29 @@ bootstrap_post_import_hook()
bootstrap_epilogue()
{
perl -pi -e "s/\@PACKAGE\@/$package/g" README-release
# Since this is a "GNU" package, replace this line
# if LC_ALL=C grep 'GNU @PACKAGE@' $(top_srcdir)/* 2>/dev/null \
# | grep -v 'libtool:' >/dev/null; then
# with this:
# if true; then
# Why? That pipeline searches all files in $(top_srcdir), and if you
# happen to have large files (or apparently large sparse files), the
# first grep may well run out of memory.
perl -pi -e 's/if LC_ALL=C grep .GNU .PACKAGE.*; then/if true; then/' \
po/Makefile.in.in
# Install our git hooks, as long as "cp" accepts the --backup option,
# so that we can back up any existing files.
case $(cp --help) in *--backup*) backup=1;; *) backup=0;; esac
if test $backup = 1; then
hooks=$(cd scripts/git-hooks && git ls-files)
for f in $hooks; do
# If it is identical, skip it.
cmp scripts/git-hooks/$f .git/hooks/$f > /dev/null \
&& continue
cp --backup=numbered scripts/git-hooks/$f .git/hooks
chmod a-w .git/hooks/$f
done
fi
}

View File

@ -0,0 +1,15 @@
#!/bin/sh
#
# An example hook script to check the commit log message taken by
# applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit. The hook is
# allowed to edit the commit message file.
#
# To enable this hook, rename this file to "applypatch-msg".
. git-sh-setup
commitmsg="$(git rev-parse --git-path hooks/commit-msg)"
test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
:

153
scripts/git-hooks/commit-msg Executable file
View File

@ -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;
}
}

View File

@ -0,0 +1,14 @@
#!/bin/sh
#
# An example hook script to verify what is about to be committed
# by applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-applypatch".
. git-sh-setup
precommit="$(git rev-parse --git-path hooks/pre-commit)"
test -x "$precommit" && exec "$precommit" ${1+"$@"}
:

49
scripts/git-hooks/pre-commit Executable file
View File

@ -0,0 +1,49 @@
#!/bin/sh
#
# An example hook script to verify what is about to be committed.
# Called by "git commit" with no arguments. The hook should
# exit with non-zero status after issuing an appropriate message if
# it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-commit".
if git rev-parse --verify HEAD >/dev/null 2>&1
then
against=HEAD
else
# Initial commit: diff against an empty tree object
against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi
# If you want to allow non-ASCII filenames set this variable to true.
allownonascii=$(git config --bool hooks.allownonascii)
# Redirect output to stderr.
exec 1>&2
# Cross platform projects tend to avoid non-ASCII filenames; prevent
# them from being added to the repository. We exploit the fact that the
# printable range starts at the space character and ends with tilde.
if [ "$allownonascii" != "true" ] &&
# Note that the use of brackets around a tr range is ok here, (it's
# even required, for portability to Solaris 10's /usr/bin/tr), since
# the square bracket bytes happen to fall in the designated range.
test $(git diff --cached --name-only --diff-filter=A -z $against |
LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
then
cat <<\EOF
Error: Attempt to add a non-ASCII file name.
This can cause problems if you want to work with people on other platforms.
To be portable it is advisable to rename the file.
If you know what you are doing you can disable this check using:
git config hooks.allownonascii true
EOF
exit 1
fi
# If there are whitespace errors, print the offending file names and fail.
exec git diff-index --check --cached $against --