From 6e2a73dfba7c0b1a7b4000f9305b666911f4a171 Mon Sep 17 00:00:00 2001 From: Jim Porter Date: Wed, 12 Apr 2023 23:03:31 -0700 Subject: [PATCH] Add Git hooks to check filenames listed in the commit message * build-aux/git-hooks/commit-msg-files.awk: * build-aux/git-hooks/post-commit: * build-aux/git-hooks/pre-push: New files... * autogen.sh: ... add them. --- autogen.sh | 3 +- build-aux/git-hooks/commit-msg-files.awk | 96 ++++++++++++++++++++++++ build-aux/git-hooks/post-commit | 29 +++++++ build-aux/git-hooks/pre-push | 70 +++++++++++++++++ 4 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 build-aux/git-hooks/commit-msg-files.awk create mode 100755 build-aux/git-hooks/post-commit create mode 100755 build-aux/git-hooks/pre-push diff --git a/autogen.sh b/autogen.sh index af4c2ad14df..6127e7b24f4 100755 --- a/autogen.sh +++ b/autogen.sh @@ -340,7 +340,8 @@ hooks= tailored_hooks= sample_hooks= -for hook in commit-msg pre-commit prepare-commit-msg; do +for hook in commit-msg pre-commit prepare-commit-msg post-commit \ + pre-push commit-msg-files.awk; do cmp -- build-aux/git-hooks/$hook "$hooks/$hook" >/dev/null 2>&1 || tailored_hooks="$tailored_hooks $hook" done diff --git a/build-aux/git-hooks/commit-msg-files.awk b/build-aux/git-hooks/commit-msg-files.awk new file mode 100644 index 00000000000..da066b83bdd --- /dev/null +++ b/build-aux/git-hooks/commit-msg-files.awk @@ -0,0 +1,96 @@ +# Check the file list of GNU Emacs change log entries for each commit SHA. + +# Copyright 2023 Free Software Foundation, Inc. + +# This file is part of GNU Emacs. + +# GNU Emacs 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. + +# GNU Emacs 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 GNU Emacs. If not, see . + +function get_commit_changes(commit_sha, changes, cmd, i, j, len, \ + bits, filename) { + # Collect all the files touched in the specified commit. + cmd = ("git log -1 --name-status --format= " commit_sha) + while ((cmd | getline) > 0) { + for (i = 2; i <= NF; i++) { + len = split($i, bits, "/") + for (j = 1; j <= len; j++) { + if (j == 1) + filename = bits[j] + else + filename = filename "/" bits[j] + changes[filename] = 1 + } + } + } + close(cmd) +} + +function check_commit_msg_files(commit_sha, verbose, changes, good, \ + cmd, msg, filenames_str, filenames, i) { + get_commit_changes(commit_sha, changes) + good = 1 + + cmd = ("git log -1 --format=%B " commit_sha) + while ((cmd | getline) > 0) { + if (verbose && ! msg) + msg = $0 + + # Find lines that reference files. We look at any line starting + # with "*" (possibly prefixed by "; ") where the file part starts + # with an alphanumeric character. The file part ends if we + # encounter any of the following characters: [ ( < { : + if (/^(; )?\*[ \t]+[[:alnum:]]/ && match($0, "[[:alnum:]][^[(<{:]*")) { + # There might be multiple files listed on this line, separated + # by spaces (and possibly a comma). Iterate over each of them. + split(substr($0, RSTART, RLENGTH), filenames, ",?[[:blank:]]+") + for (i in filenames) { + if (length(filenames[i]) && ! (filenames[i] in changes)) { + if (good) { + # Print a header describing the error. + if (verbose) + printf("In commit %s \"%s\"...\n", substr(commit_sha, 1, 10), msg) + printf("Files listed in commit message, but not in diff:\n") + } + printf(" %s\n", filenames[i]) + good = 0 + } + } + } + } + close(cmd) + + return good +} + +BEGIN { + if (reason == "pre-push") + verbose = 1 +} + +/^[a-z0-9]{40}$/ { + if (! check_commit_msg_files($0, verbose)) { + status = 1 + } +} + +END { + if (status != 0) { + if (reason == "pre-push") + error_msg = "Push aborted" + else + error_msg = "Bad commit message" + printf("%s; please see the file 'CONTRIBUTE'\n", error_msg) + } + exit status +} diff --git a/build-aux/git-hooks/post-commit b/build-aux/git-hooks/post-commit new file mode 100755 index 00000000000..4c30ec76e02 --- /dev/null +++ b/build-aux/git-hooks/post-commit @@ -0,0 +1,29 @@ +#!/bin/sh +# Check the file list of GNU Emacs change log entries after committing. + +# Copyright 2023 Free Software Foundation, Inc. + +# This file is part of GNU Emacs. + +# GNU Emacs 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. + +# GNU Emacs 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 GNU Emacs. If not, see . + +# Prefer gawk if available, as it handles NUL bytes properly. +if type gawk >/dev/null 2>&1; then + awk="gawk" +else + awk="awk" +fi + +git rev-parse HEAD | $awk -v reason=post-commit \ + -f .git/hooks/commit-msg-files.awk diff --git a/build-aux/git-hooks/pre-push b/build-aux/git-hooks/pre-push new file mode 100755 index 00000000000..b0185a97b28 --- /dev/null +++ b/build-aux/git-hooks/pre-push @@ -0,0 +1,70 @@ +#!/bin/sh +# Check the file list of GNU Emacs change log entries before pushing. + +# Copyright 2023 Free Software Foundation, Inc. + +# This file is part of GNU Emacs. + +# GNU Emacs 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. + +# GNU Emacs 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 GNU Emacs. If not, see . + +# Prefer gawk if available, as it handles NUL bytes properly. +if type gawk >/dev/null 2>&1; then + awk="gawk" +else + awk="awk" +fi + +# Standard input receives lines of the form: +# SP SP SP LF +$awk -v origin_name="$1" ' + # If the local SHA is all zeroes, ignore it. + $2 ~ /^0{40}$/ { + next + } + + $2 ~ /^[a-z0-9]{40}$/ { + newref = $2 + # If the remote SHA is all zeroes, this is a new object to be + # pushed (likely a branch). Go backwards until we find a SHA on + # an origin branch. + if ($4 ~ /^0{40}$/) { + back = 0 + cmd = ("git branch -r -l '\''" origin_name "/*'\'' --contains " \ + newref "~" back) + while ((cmd | getline) == 0) { + + # Only look back at most 1000 commits, just in case... + if (back++ > 1000) + break; + } + close(cmd) + + cmd = ("git rev-parse " newref "~" back) + cmd | getline oldref + if (!(oldref ~ /^[a-z0-9]{40}$/)) { + # The SHA is misformatted! Skip this line. + next + } + close(cmd) + } else if ($4 ~ /^[a-z0-9]{40}$/) { + oldref = $4 + } else { + # The SHA is misformatted! Skip this line. + next + } + + # Print every SHA after oldref, up to (and including) newref. + system("git rev-list --reverse " oldref ".." newref) + } +' | $awk -v reason=pre-push -f .git/hooks/commit-msg-files.awk -- 2.25.1