Last active
November 9, 2022 08:44
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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