7

I am trying to write a pre-receive hook to check the pattern of the commit messages using bash/shell.

I want to reject the entire push if any commit has issues. How to retrieve the commit messages?

l a s
  • 3,836
  • 10
  • 42
  • 61

3 Answers3

4

There is an entire example, with explanations, in the git docs, that covers this. Link to the example.

Roughly translating the ruby example, we have:

#!/bin/bash

set -eo pipefail

refname="$0"
oldrev="$1"
newrev="$2"

echo "Enforcing Policies..."

# Iterate over all the commits
for commit in $(git rev-list 538c33..d14fc7); do
  git cat-file commit "${commit}" | sed '1,/^$/d' | your-validator
done
ffledgling
  • 11,502
  • 8
  • 47
  • 69
  • The code example is not using `oldrev` and `newrev` properly. I think, you want to pass them to `git rev-list` – mihca Jun 17 '22 at 07:22
  • My script is not called with these parameters. I am using Python and `print(sys.argv)` only prints a single argument, namely the path to the script. – mihca Jun 17 '22 at 07:25
  • 1
    Ups my bad. I did not pass the parameters from the update hook to my other script. My update hook is a bash script. Example to call Python script and passing parameters: `/path/to/other/script.py "$@"`. The commit ids are then available as parameters. – mihca Jun 17 '22 at 08:39
1

After several attempts and fixing of issues and edge cases I ended with the next:

#!/bin/bash

# regexp sample to validate commit messages
COMMIT_MSG_PATTERN="^olala[0-9]{3}"

refname="$1"
oldrev="$2"
newrev="$3"

# list of commits to validate
if echo "$oldrev" | grep -Eq '^0+$'; then
    # list everything reachable from $newrev but not any heads
    #commits=$(git rev-list $(git for-each-ref --format='%(refname)' refs/heads/* | sed 's/^/\^/') "$newrev")
    
    # or shorter version that also get list of revisions reachable from $newrev but not from any branche
    commits=$(git rev-list $newrev --not --branches=*)
else
    commits=$(git rev-list ${oldrev}..${newrev})
fi

# Iterate over all the commits
for commit in $commits; do
  #echo "commit=$commit"
  MSG=$(git cat-file commit "${commit}" | sed '1,/^$/d')
  if echo "$MSG" | grep -qvE "$COMMIT_MSG_PATTERN" ;then
    echo "$MSG"
    echo "Your commit message must match the pattern '$COMMIT_MSG_PATTERN'"
    exit 1
  fi
done
1

I had a similar problem to solve and tried with the Answer from Petrov Andrey but this didn't work:

refname="$1"
oldrev="$2"
newrev="$3"

Maybe its just in newer versions but you have to read the variables from stdin

read oldrev newrev refname
echo "Old revision: $oldrev"
echo "New revision: $newrev"
echo "Reference name: $refname"

Also be carefull with:

if echo "$MSG" | grep -qvE "$COMMIT_MSG_PATTERN" ;then
    echo "$MSG"
    echo "Your commit message must match the pattern '$COMMIT_MSG_PATTERN'"
    exit 1
fi

in a commit with multiple lines this will fail as it checks every line of the commit for the regexp. Better:

# if the regex finds no match the validation fails
    if echo "$MSG" | grep -qE "$COMMIT_MSG_PATTERN" ;then
        echo "Validation correct!"
    else
        echo "Validation failed!"
        echo "Your commit message must match the pattern '$COMMIT_MSG_PATTERN'"
        exit 1
    fi
  • Does not work for me. I have nothing on stdin. I am hosting a git repo with Phabricator and there does not seem to be any info about the specific commits in `update` hook. Not as given arguments, not in env variables, not on stdin. Maybe it is an issue with Phabricator? – mihca Jun 17 '22 at 08:11
  • Ups my bad. I did not pass the parameters from the update hook to my other script. My update hook is a bash script. Example to call Python script and passing parameters: `/path/to/other/script.py "$@"`. The commit ids are then available as parameters. – mihca Jun 17 '22 at 08:40