Below is a minimal, fully local, copy-pasteable example you can run on one machine to understand the flow. No GitHub, no SSH, no external tools.
The example rejects any commit that contains the string TODO.
1. Create a Bare “Server” Repository
mkdir -p /tmp/git-proxy-demo
cd /tmp/git-proxy-demo
git init --bare server.gitThis simulates your Git server.
2. Add a pre-receive Hook (the “Proxy Logic”)
cd server.git
mkdir -p hooks
nano hooks/pre-receive
chmod +x hooks/pre-receivehooks/pre-receive
#!/bin/bash
set -e
TMP=$(mktemp -d)
trap "rm -rf $TMP" EXIT
while read oldrev newrev refname; do
# Handle first push
if [ "$oldrev" = "0000000000000000000000000000000000000000" ]; then
RANGE="$newrev"
else
RANGE="$oldrev..$newrev"
fi
for commit in $(git rev-list "$RANGE"); do
git --work-tree="$TMP" checkout -f "$commit" -- .
if grep -R "TODO" "$TMP" >/dev/null 2>&1; then
echo "❌ Push rejected: TODO found in commit $commit"
exit 1
fi
done
done
exit 03. Create a Client Repository
cd /tmp/git-proxy-demo
mkdir client
cd client
git initAdd the “server” as the remote:
git remote add origin /tmp/git-proxy-demo/server.git4. Test a Valid Commit (Should Pass)
echo "hello world" > ok.txt
git add ok.txt
git commit -m "valid commit"
git push origin mainExpected output:
Enumerating objects...
Writing objects...
To /tmp/git-proxy-demo/server.git
* [new branch] main -> main
5. Test an Invalid Commit (Should Be Rejected)
echo "# TODO: fix this" > bad.txt
git add bad.txt
git commit -m "introduce todo"
git push origin mainExpected output:
❌ Push rejected: TODO found in commit abc123
error: hook declined to update refs/heads/main
Nothing is pushed.
6. Verify Server State
cd /tmp/git-proxy-demo/server.git
git log --oneline --allYou will not see the rejected commit.
7. What This Demonstrates
✔ Git server enforcement ✔ Analysis happens before accepting commits ✔ No CI, no client hooks, no proxy hacks ✔ Identical mechanics to production setups
8. Common Variations to Try
Replace the grep check with:
- File size limits
- Forbidden imports
- Secrets regex
- Language-specific linters
semgrep/bandit/eslint
9. Key Mental Model
A Git “proxy” is almost always just a server-side hook.
You are intercepting refs updates, not network traffic.