Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save schakko/4f33f942d6007f92bcde83eeacb5507a to your computer and use it in GitHub Desktop.
Save schakko/4f33f942d6007f92bcde83eeacb5507a to your computer and use it in GitHub Desktop.
git pre-receive hook for e.g. GitLab or GitHub Enterprise to enforce the one-change-per-directory pattern for GitOps (Argo CD etc) repos.
#!/bin/env bash
#
# git pre-receive hook for e.g. GitLab or GitHub Enterprise to enforce the one-change-per-directory pattern for GitOps (Argo CD etc) repos.
# It checks each of the commits in the received packs for a given path specification ($PATH_SPECIFICATION).
# If a file in another matching path has been modified, the commit is denied.
#
# This script makes heavy use of bash-only features.
#
# Author: Christopher Klein <ckl[dot]dreitier[dot]com>
#
# Per one commit, only file paths of the same capture groups are allowed.
# For the following path speci, only changes to `environments/asia/staging/a.yaml` and `environments/asia/staging/b/b.yaml` are allowed but `environments/asia/prod` would fail.
#PATH_SPECIFICATION="environments/(.*)/(.*)/*."
PATH_SPECIFICATION="(templates)/.*"
# ignore changes outside the path specification, like changes to `./README.md`
ALLOW_CHANGES_OUTSIDE=0
# For testing purpuses, you can try it with `echo "6ad95 08f135 main` | ./pre-receive-hook.enforce-one-change-per-directory.sh`
while read OLDREV NEWREV REFNAME ; do
for COMMIT in `git rev-list $OLDREV..$NEWREV`;
do
echo "Checking commit $COMMIT ..."
# find files in commit
FILES=`git diff-tree --no-commit-id --name-only -r $COMMIT`
# already matched paths in this commit
MATCHED_PATHS=()
# changed files outside the path specification
CHANGED_FILES_OUTSIDE_PATH_SPECIFICATION=()
for FILE in $FILES;
do
echo " $FILE"
# check against regex
if [[ $FILE =~ $PATH_SPECIFICATION ]]
then
echo " file matches path template, checking ..."
total_groups=${#BASH_REMATCH[@]}
total_already_matched_paths=${#MATCHED_PATHS[@]}
for ((idx=1; idx<$total_groups; idx++))
do
PART=${BASH_REMATCH[$idx]}
echo " group $idx => $PART"
idx_in_already_matched_path=$(expr $idx - 1)
if [[ $idx -le $total_already_matched_paths ]]
then
previous_matched_path=${MATCHED_PATHS[$idx_in_already_matched_path]}
if [[ "$previous_matched_path" != "$PART" ]]
then
echo " - Commit $COMMIT has already changes in protected path specification '$previous_matched_path'. You have to remove file '$FILE' from this commit."
exit 1
fi
fi
MATCHED_PATHS[$idx_in_already_matched_path]=$PART
done
else
CHANGED_FILES_OUTSIDE_PATH_SPECIFICATION+=($FILE)
fi
done
if [[ $ALLOW_CHANGES_OUTSIDE -eq 0 && ${#CHANGED_FILES_OUTSIDE_PATH_SPECIFICATION[@]} -gt 0 && ${#MATCHED_PATHS[@]} -gt 0 ]]
then
echo "There have been made changes to ($CHANGED_FILES_OUTSIDE_PATH_SPECIFICATION) outside the path specification '$PATH_SPECIFICATION'. This is not allowed. You have to split up commit $COMMIT"
exit 1
fi
done
done
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment