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.git

This 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-receive

hooks/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 0

3. Create a Client Repository

cd /tmp/git-proxy-demo
mkdir client
cd client
git init

Add the “server” as the remote:

git remote add origin /tmp/git-proxy-demo/server.git

4. Test a Valid Commit (Should Pass)

echo "hello world" > ok.txt
git add ok.txt
git commit -m "valid commit"
git push origin main

Expected 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 main

Expected 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 --all

You 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.