diff --git a/.gitattributes b/.gitattributes index db0630e..9e60f11 100644 --- a/.gitattributes +++ b/.gitattributes @@ -15,3 +15,7 @@ graphify-out/graph.html -diff *.css text eol=lf *.json text eol=lf *.md text eol=lf +*.sh text eol=lf + +# Git hooks must stay LF — a CRLF shebang breaks the interpreter. +scripts/git-hooks/* text eol=lf diff --git a/CLAUDE.md b/CLAUDE.md index f449b67..b06b361 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,3 +1,9 @@ +## Branching (MANDATORY) + +`main` is protected: direct pushes are blocked (GitHub branch protection, admins included) and merging requires a PR with the Claude BugBot check green. Never commit feature work straight to `main` — branch, push the branch, open a PR, let it merge. + +After a fresh clone, install the local hooks once: `sh scripts/install-git-hooks.sh`. It adds a `pre-push` guard (rejects direct pushes to main before the round-trip) alongside graphify's hooks. Emergency bypass is `git push --no-verify`; the server rule still catches it. + ## graphify This project has a graphify knowledge graph at graphify-out/. diff --git a/scripts/git-hooks/pre-push b/scripts/git-hooks/pre-push new file mode 100644 index 0000000..d1229a1 --- /dev/null +++ b/scripts/git-hooks/pre-push @@ -0,0 +1,27 @@ +#!/bin/sh +# Refuse direct pushes to the protected branch. Feature work goes via a PR. +# Server-side branch protection also enforces this; this hook is the fast, +# local first line of defence so you find out before the round-trip. +# +# Bypass for a genuine emergency (rare): git push --no-verify +# +# Installed into .git/hooks/pre-push by scripts/install-git-hooks.sh. + +protected="refs/heads/main" + +while read -r local_ref local_sha remote_ref remote_sha; do + if [ "$remote_ref" = "$protected" ]; then + echo "" >&2 + echo "BLOCKED: direct push to main is not allowed." >&2 + echo " Branch, push that, and open a PR:" >&2 + echo " git switch -c feat/your-change" >&2 + echo " git push -u origin feat/your-change" >&2 + echo " gh pr create" >&2 + echo "" >&2 + echo " (Real emergency only: git push --no-verify)" >&2 + echo "" >&2 + exit 1 + fi +done + +exit 0 diff --git a/scripts/install-git-hooks.sh b/scripts/install-git-hooks.sh new file mode 100644 index 0000000..75a45c4 --- /dev/null +++ b/scripts/install-git-hooks.sh @@ -0,0 +1,19 @@ +#!/bin/sh +# Install this repo's tracked git hooks into .git/hooks without disturbing +# hooks other tools manage there (e.g. graphify's post-commit/post-checkout). +# Run once per clone: sh scripts/install-git-hooks.sh + +set -e + +repo_root=$(git rev-parse --show-toplevel) +src="$repo_root/scripts/git-hooks" +dst="$repo_root/.git/hooks" + +for hook in "$src"/*; do + name=$(basename "$hook") + cp "$hook" "$dst/$name" + chmod +x "$dst/$name" + echo "installed: .git/hooks/$name" +done + +echo "Done. Direct pushes to main will now be blocked locally."