2

I have a git repo with key files that have to be all in the format

#possible comment
key "<key with 64 chars>"; # possible comment
vpn .... #optional line

I would like to add a hook into our git repository, that when you try to commit a new file, this regular expression is checked on all files in the repository beforehand:

cat *|grep -v -E "^\s*key\s+\"[0-9a-f]{64}\";\s*(#.*)*$"|grep -v -E "(^#|vpn|^$)"

I created a .git/hooks/pre-commit file:

#!/bin/sh

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

# Redirect output to stderr.
exec 1>&2

diffstr=$(git diff --cached $against | sed 's/^+//g' | grep -E "^\+[^\+]" \
| grep -v -E "^key \"\w{64}\";\s*(#.*)*$" | grep -v -E "(^#|vpn|^$)")
if [ "$diffstr" != "" ] ; then
    echo "You have a malformed key in your changes, you can't commit until it has been corrected:"
    echo $diffstr
    exit 1
fi

I want to be stopped from committing changed/new key-files that are not in the correct format. Any of the following:

  • don't start with key
  • dont use "-quotes
  • dont end with ; (optionally followed by a comment)
  • where the key is not a 64 hex characters.
  • any other lines, that don't start with comment #

But my solution still doesn't stop me from committing wrong key files. What am I doing wrong?

rubo77
  • 19,527
  • 31
  • 134
  • 226
  • Have you checked what `git diff --cached` gives you? At least in my case, the only lines starting with at least two `+` characters are the file names of the differing files. But they all start with three `+` signs thus `git diff --cached $against | sed 's/^+//g' | grep -E "^\+[^\+]"` would already be empty. Can you explain what you want to achieve with all those `grep` commands? – jotasi Aug 11 '16 at 04:48
  • The current grep commands filter the output, so no valid lines are left. This works fine on the console. I forgot to mention that there are also lines starting with "vpn" – rubo77 Aug 11 '16 at 06:21

1 Answers1

2

Another approach is to grep files, instead of diff'ing content directly.
The list of files to check is:

  git diff --cached --name-only --diff-filter=ACM $against --

So

#!/bin/bash
exec 1>&2
git diff --cached --name-status --diff-filter=ACM | cut -d$'\t' -f2 \
| while read st file; do
  diffstr=$(cat "$st" | grep -v -E "^key \"\w{64}\";\s*(#.*)*$" \
  | grep -v -E "(^#|vpn|^$)")
  if [ "$diffstr" != "" ] ; then
    echo "$st: you have a malformed key in your changes, you can't commit until it has been corrected:"
    echo $diffstr
    exit 1
  fi
done

The drawback is that it greps the full file instead of focussing on added lines (which was what the grep -E "^\+[^\+]" was for, for grepping those lines starting with ++)

That means if somehow a key was incorrect in an old commit, you would still detect it even the only change you did was, for instance, modifying a comment.


After testing and fixes, the OP rubo77 settled to freifunk-kiel/fastd-git-hook/git/hooks/pre-commit:

#!/bin/bash
#
# A 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.
#
# This script checks if all files to be committed fit the needs for 
# a valid fastd-keyfile so it can be pulled to all Freifu9nk gateways
# and used as public key

# Redirect output to stderr.
exec 1>&2

while read keyfile file; do
  # sanitize last linebreak:
  tail -c1 "$keyfile" | read -r _ || echo >> "$keyfile" 
  diffstr=$(cat "$keyfile" | grep -v -E "^\s*key\s+\"\w{64}\";\s*(#.*)*$" \
  | grep -v -E "(^\s*#|^\s*remote|^$)")
  if [ "$diffstr" != "" ] ; then
    echo "$keyfile: you have a malformed key in your changes , you can't commit until it has been corrected:"
    echo "$diffstr"
    exit 1
  fi
done < <(git diff --cached --name-status --diff-filter=ACM | cut -f2)

# checks
...
Community
  • 1
  • 1
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • 1
    Probably worth using process substitution: `while IFS= read -r st file; do ... done < <(git diff --cached ...)`. Also, whenever I see three piped `grep`s I start wondering how to do this in `awk` :) – fedorqui Aug 11 '16 at 10:56
  • 1
    @rubo77 Yes, I didn't analyses fully your regexes: the point is to deal with file content, not diff content (which has all those leading '+' and '-') – VonC Aug 11 '16 at 11:12
  • @rubo77 Great! I have included your version in the answer for more visibility. – VonC Aug 11 '16 at 12:34
  • strangely, if I continue in the script and add one more line after `done` like `echo test`, then the exit code seems to be changed, and the commit is not interrupted. why??? is the exit in a subshell? how do I change that? – rubo77 Aug 11 '16 at 19:34
  • 1
    I found out: the while starts asubshell. you can exit the script with `exitcode=$?; if [ $exitcode != 0 ]; then exit $exitcode; fi` – rubo77 Aug 11 '16 at 19:59
  • @rubo77 yes indeed: http://stackoverflow.com/q/13726764/6309, http://unix.stackexchange.com/q/204597/7490 – VonC Aug 11 '16 at 20:06