coreutils
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH] scripts: rewrite git commit-msg hook in Perl


From: Jim Meyering
Subject: [PATCH] scripts: rewrite git commit-msg hook in Perl
Date: Fri, 18 Nov 2011 15:41:12 +0100

FYI, I've rewritten the commit-msg script in perl.
This one is more robust, with a few other improvements:
  - suggested diagnostics include precise replacement URLs
  - the script now dies when its parent dies

>From 1c5ca5c05bac29aec079df61dae2b7f843cb1f0a Mon Sep 17 00:00:00 2001
From: Jim Meyering <address@hidden>
Date: Fri, 18 Nov 2011 15:33:56 +0100
Subject: [PATCH] scripts: rewrite git commit-msg hook in Perl

* scripts/git-hooks/commit-msg: Rewrite in perl.
This is still a work in progress in that it hard-codes coreutils-
specific program names and policies that should be easy to selectively
enable or disable without modifying the script.
---
 scripts/git-hooks/commit-msg |  198 +++++++++++++++++++++++++++---------------
 1 files changed, 129 insertions(+), 69 deletions(-)

diff --git a/scripts/git-hooks/commit-msg b/scripts/git-hooks/commit-msg
index 76b829d..7d0891e 100755
--- a/scripts/git-hooks/commit-msg
+++ b/scripts/git-hooks/commit-msg
@@ -1,80 +1,140 @@
-#!/bin/sh
-# A hook script to check the commit log message.
+eval '(exit $?0)' && eval 'exec perl -w "$0" ${1+"$@"}'
+  & eval 'exec perl -w "$0" $argv:q'
+    if 0;

-log_file=$1
-export log_file
+use strict;
+use warnings;
+(my $ME = $0) =~ s|.*/||;

-re_edit()
+my $editor = $ENV{EDITOR} || 'vi';
+$ENV{PATH} = '/bin:/usr/bin';
+
+# Keywords allowed before the colon on the first line of a commit message:
+# program names and a few general category names.
+my @valid = qw(
+    arch base64 basename cat chcon chgrp chmod chown chroot cksum comm
+    cp csplit cut date dd df dir dircolors dirname du echo env expand
+    expr factor false fmt fold groups head hostid hostname id install
+    join kill link ln logname ls md5sum mkdir mkfifo mknod mktemp
+    mv nice nl nohup nproc od paste pathchk pinky pr printenv printf
+    ptx pwd readlink rm rmdir runcon seq sha1sum sha224sum sha256sum
+    sha384sum sha512sum shred shuf sleep sort split stat stdbuf stty
+    su sum sync tac tail tee test timeout touch tr true truncate tsort
+    tty uname unexpand uniq unlink uptime users vdir wc who whoami yes
+
+    copy 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($$$)
 {
-  read -p "Hit return to edit. Ctrl-C to abort..." v 1>&2
-  ${EDITOR:-vi} "$log_file"
+  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";
 }

-get_msg()
+sub re_edit($)
 {
-  sed '/^#/d' "$log_file" #filter comments
+  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";
 }

-check_msg()
+# Given a $LOG_FILE name and a address@hidden 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($$)
 {
-  # First line must contain a colon, e.g., "keyword: ...".
-  line_1=$(get_msg | sed 1q)
-  case $line_1 in
-    *:*) ;;
-    [Vv]ersion' '[0-9]*) return 0;;
-    *) echo "missing colon on first line of log message"; return 1;;
-  esac
-
-  # The token(s) before the colon on the first line must be one of
-  # the following.  Tokens may be space- or comma-separated.
-  fail=0
-  for w in $(echo "$line_1"|sed 's/:.*//'|tr -s ' ,' '  '); do
-    case $w in
-      # program names
-      \[|arch|base64|basename|cat|chcon|chgrp|chmod|chown|chroot) ;;
-      cksum|comm|cp|csplit|cut|date|dd|df|dir|dircolors|dirname|du) ;;
-      echo|env|expand|expr|factor|false|fmt|fold|groups|head|hostid) ;;
-      hostname|id|install|join|kill|link|ln|logname|ls|md5sum|mkdir) ;;
-      mkfifo|mknod|mktemp|mv|nice|nl|nohup|nproc|od|paste|pathchk) ;;
-      pinky|pr|printenv|printf|ptx|pwd|readlink|rm|rmdir|runcon) ;;
-      seq|sha1sum|sha224sum|sha256sum|sha384sum|sha512sum|shred|shuf) ;;
-      sleep|sort|split|stat|stdbuf|stty|su|sum|sync|tac|tail|tee) ;;
-      test|timeout|touch|tr|true|truncate|tsort|tty|uname|unexpand) ;;
-      uniq|unlink|uptime|users|vdir|wc|who|whoami|yes) ;;
-      # other tags
-      copy|gnulib|tests|maint|doc|build|scripts) ;;
-      *) echo "invalid first word of summary line: $w"; fail=1;;
-    esac
-  done
-  test $fail = 1 && return 1
-
-  # Limit line length to allow for tab in changelog
-  test $(get_msg | wc -L | cut -f1 -d' ') -gt 72 \
-    && { echo "line > 72 chars"; return 1; }
-
-  # Second line should be blank or not present
-  test "$(get_msg | sed -n 2p)" \
-    && { echo "second line should be blank"; return 1; }
-
-  get_msg | grep -E 'https?://bugzilla\.redhat\.com/show_bug\.cgi' >&2 \
-    && { echo 'use shorter http://bugzilla.redhat.com/NNNNNN'; return 1; }
-
-  get_msg | grep -E 'https?://debbugs\.gnu\.org/cgi/bugreport\.cgi?bug=' >&2 \
-    && { echo 'use shorter http://bugs.gnu.org/NNNNN'; return 1; }
-
-  # Flag redundant use of "issue"
-  get_msg | grep -Fi "issue reported by" >&2 \
-    && { echo "just say: Reported by ..."; return 1; }
-
-  return 0;
+  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';
+
+  # The first line must have a colon or must give a version number.
+  $line[0] =~ /(?::|^[Vv]ersion [0-9])/
+    or return 'missing colon on first line of log message';
+
+  # 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[0]) =~ s/:.*//;
+  my @word = split (/[ ,]/, $pre_colon);
+  my @bad = grep !/$valid_regex/, @word;
+  @bad
+    and return 'invalid first word(s) of summary line: ' . join (', ', @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
+        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";;
+
+  return '';
 }

-while :; do
-  err=$(check_msg) && break
-  ME=${0##*/}
-  err="$ME: $err"
-  # Insert the diagnostic as a comment on the first line of $log_file.
-  perl -ni -e '$. == 1 and print "# '"$err"'\n"; print' $log_file
-  printf '%s\n' "$err" 1>&2
-  re_edit
-done
+{
+  @ARGV == 1
+    or die;
+
+  my $log_file = $ARGV[0];
+
+  while (1)
+    {
+      my @line;
+      my $err = check_msg $log_file, address@hidden;
+      $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, address@hidden;
+      re_edit $log_file;
+
+      # Stop if our parent is killed.
+      getppid() == 1
+        and last;
+    }
+}
--
1.7.8.rc2.3.g0911



reply via email to

[Prev in Thread] Current Thread [Next in Thread]