From a42e8f60244c7c0a16643da3437135e8d22494ad Mon Sep 17 00:00:00 2001 From: bastien Date: Tue, 22 Sep 2026 17:55:41 +0200 Subject: [PATCH] chore(githooks): refresh pre-commit/post-commit/post-merge from the lib (gitleaks backstop, autopush) --- .githooks/post-commit | 15 +++++++++++++++ .githooks/post-merge | 15 +++++++++++++++ .githooks/pre-commit | 24 +++++++++++++++++++++--- 3 files changed, 51 insertions(+), 3 deletions(-) create mode 100755 .githooks/post-commit create mode 100755 .githooks/post-merge diff --git a/.githooks/post-commit b/.githooks/post-commit new file mode 100755 index 0000000..ad605ea --- /dev/null +++ b/.githooks/post-commit @@ -0,0 +1,15 @@ +#!/bin/sh +# gitflow post-commit — generated by gitflow_init. Do not hand-edit. +# Pushes every commit as it lands (BDR-095): a remote only backs up what it +# holds. Never fails the commit: no origin / offline / refused → warning only. +# Opt out for one command with GITFLOW_NO_PUSH=1 (throwaway repos, tests). +[ "${GITFLOW_NO_PUSH:-0}" = 1 ] && exit 0 +# Per-repo opt-out (no push rights on a foreign clone): git config gitflow.autopush false +[ "$(git config --bool --default true gitflow.autopush)" = false ] && exit 0 +git remote get-url origin >/dev/null 2>&1 || exit 0 +br=$(git symbolic-ref --short -q HEAD 2>/dev/null) || exit 0 # detached HEAD — nothing to track +if command -v timeout >/dev/null 2>&1; then t="timeout ${GITFLOW_PUSH_TIMEOUT:-30}"; else t=""; fi +if $t git push -q -u --follow-tags origin "$br" >/dev/null 2>&1; then exit 0; fi +echo "gitflow post-commit: push of '$br' FAILED — this commit exists only on this disk." >&2 +echo " Push by hand: git push -u origin $br (rejected as non-fast-forward? never force-push; ask first)" >&2 +exit 0 diff --git a/.githooks/post-merge b/.githooks/post-merge new file mode 100755 index 0000000..0456217 --- /dev/null +++ b/.githooks/post-merge @@ -0,0 +1,15 @@ +#!/bin/sh +# gitflow post-merge — generated by gitflow_init. Do not hand-edit. +# Pushes every commit as it lands (BDR-095): a remote only backs up what it +# holds. Never fails the commit: no origin / offline / refused → warning only. +# Opt out for one command with GITFLOW_NO_PUSH=1 (throwaway repos, tests). +[ "${GITFLOW_NO_PUSH:-0}" = 1 ] && exit 0 +# Per-repo opt-out (no push rights on a foreign clone): git config gitflow.autopush false +[ "$(git config --bool --default true gitflow.autopush)" = false ] && exit 0 +git remote get-url origin >/dev/null 2>&1 || exit 0 +br=$(git symbolic-ref --short -q HEAD 2>/dev/null) || exit 0 # detached HEAD — nothing to track +if command -v timeout >/dev/null 2>&1; then t="timeout ${GITFLOW_PUSH_TIMEOUT:-30}"; else t=""; fi +if $t git push -q -u --follow-tags origin "$br" >/dev/null 2>&1; then exit 0; fi +echo "gitflow post-commit: push of '$br' FAILED — this commit exists only on this disk." >&2 +echo " Push by hand: git push -u origin $br (rejected as non-fast-forward? never force-push; ask first)" >&2 +exit 0 diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 0ddeefc..ef3abef 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -7,17 +7,35 @@ br=$(git symbolic-ref --short -q HEAD 2>/dev/null) git rev-parse --verify -q HEAD >/dev/null 2>&1 || exit 0 # root commit — allow [ -f "$gd/MERGE_HEAD" ] && exit 0 # merge in progress — allow +# Secret backstop (job7) — any branch, not just protected ones. Non-blocking +# if gitleaks isn't installed; auto-discovers ./.gitleaks.toml (repo root). +if command -v gitleaks >/dev/null 2>&1; then + if ! gitleaks git --staged --no-banner >/dev/null 2>&1; then + echo "gitflow pre-commit: BLOCKED — gitleaks found a secret in staged changes." >&2 + echo " Details: gitleaks git --staged --no-banner" >&2 + echo " Genuine false-positive? add an allowlist rule to .gitleaks.toml — never bypass with --no-verify." >&2 + exit 1 + fi +else + echo "gitflow pre-commit: gitleaks not installed — secret scan skipped (https://github.com/gitleaks/gitleaks)." >&2 +fi + +# Per-repo opt-out of the branch model (a clone of a foreign project): +# git config gitflow.protect false +[ "$(git config --bool --default true gitflow.protect)" = false ] && exit 0 + case "$br" in main|develop) ;; # protected — keep checking *) exit 0 ;; # working branch — allow esac -# whitelist: all-staged-under-.claude/ (memory/doc/deploy helpers) — allow -if [ -z "$(git diff --cached --name-only | grep -v '^\.claude/' | head -1)" ]; then +# whitelist: all-staged-under-.claude/ (memory/doc/deploy helpers) or +# .githooks/ (the hooks themselves, refreshed by the lib) — allow +if [ -z "$(git diff --cached --name-only | grep -vE '^\.(claude|githooks)/' | head -1)" ]; then exit 0 fi echo "gitflow pre-commit: BLOCKED — direct commit on '$br'." >&2 echo " Branch from the right base (feature/bugfix->develop, hotfix->main), or merge." >&2 -echo " (.claude/** memory commits are exempt; --no-verify bypasses locally.)" >&2 +echo " (.claude/** and .githooks/** commits are exempt; foreign clone? git config gitflow.protect false)" >&2 exit 1