From 1b20beccda0e56c4a5e2fb44e4744fcd46b8af6b Mon Sep 17 00:00:00 2001 From: bastien Date: Thu, 24 Sep 2026 20:25:39 +0200 Subject: [PATCH] fix(gitflow): init on an existing repo under the machine-wide hooks lands the socle via a chore/gitflow-adopt merge (T2c) --- lib/gitflow-test.sh | 18 +++++++++++++++++- lib/gitflow.sh | 32 +++++++++++++++++++++++++------- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/lib/gitflow-test.sh b/lib/gitflow-test.sh index 901b17d..a961a7e 100644 --- a/lib/gitflow-test.sh +++ b/lib/gitflow-test.sh @@ -50,7 +50,7 @@ chk "tree CLEAN after init" '[ -z "$(git status --porcelain)" ]' chk "hook TRACKED in commit" 'git ls-files --error-unmatch .githooks/pre-commit >/dev/null 2>&1' chk "socle IN root commit" 'git show HEAD:.gitignore | grep -qxF ".claude/deploy/PENDING.json"' -echo "T2b — init existing (master→main rename + adoption commit, hook inactive during it)" +echo "T2b — init existing (master→main rename + adoption via chore/gitflow-adopt merge)" newrepo existing git symbolic-ref HEAD refs/heads/master # force the repo onto 'master' echo a > a.txt; printf 'node_modules/\n' > .gitignore; git add -A @@ -64,6 +64,22 @@ chk "existing tree CLEAN" '[ -z "$(git status --porcelain)" ]' chk "existing hook tracked" 'git ls-files --error-unmatch .githooks/pre-commit >/dev/null 2>&1' chk "kept project rule" 'git show HEAD:.gitignore | grep -qxF "node_modules/"' +echo "T2c — init existing under a LIVE pre-commit (global hooks simulated): socle lands via merge" +newrepo live; git symbolic-ref HEAD refs/heads/master +echo a > a.txt; printf 'node_modules/\n' > .gitignore; git add -A +git -c core.hooksPath=/dev/null commit -q -m "pre-existing on master" +_gitflow_write_hook "$WORK/globalhooks" # the machine-wide hook set, as make link installs it +git config core.hooksPath "$WORK/globalhooks" # stands in for git's GLOBAL core.hooksPath during init +# shellcheck disable=SC2034 +live_rc=0; GITFLOW_NO_PUSH=1 gitflow_init >/dev/null 2>&1 || live_rc=$? +chk "T2c init succeeds under the live hook (rc 0)" "[ $live_rc -eq 0 ]" +chk "T2c socle reached main via a merge commit" 'git log main --oneline -1 | grep -q "Merge chore/gitflow-adopt"' +chk "T2c .gitignore socle on main" 'git show main:.gitignore | grep -qxF ".claude/deploy/PENDING.json"' +chk "T2c hooks tracked on main" 'git ls-tree -r main --name-only | grep -q "^.githooks/pre-commit$"' +chk "T2c adoption branch deleted" '! git rev-parse --verify -q refs/heads/chore/gitflow-adopt >/dev/null' +chk "T2c develop created from main" '[ "$(git rev-parse develop)" = "$(git rev-parse main)" ]' +chk "T2c repo hook active afterwards" '[ "$(git config core.hooksPath)" = .githooks ]' + echo "T3 — hook blocks/permits after init" cd "$WORK/fresh" || exit 1 git checkout -q main diff --git a/lib/gitflow.sh b/lib/gitflow.sh index 856b413..b885603 100644 --- a/lib/gitflow.sh +++ b/lib/gitflow.sh @@ -293,20 +293,38 @@ _gitflow_init_existing() { # has commits → ensure main (rename master) fi fi git checkout -q "$GITFLOW_MAIN" || return 1 - # commit the socle + versioned hook now, while hooksPath is NOT yet active - # (activation is the last step of gitflow_init) → never self-blocked. + # The socle (.gitignore + versioned hooks) reaches main through a MERGE: the + # pre-commit hook is global on the machine (BDR-095), so a direct commit on + # main is refused even during init, while a merge commit is hook-exempt. + # Any failure aborts BEFORE develop/hook activation so a partial run can't + # activate the hook and self-block every re-run. git add -- .gitignore .githooks 2>/dev/null || true - # socle commit failure is FATAL — abort BEFORE develop/hook-activation so a - # partial run can't activate the hook and self-block every re-run (was a bug: - # the `|| commit` form swallowed the failure, then init activated the hook). if ! git diff --cached --quiet -- .gitignore .githooks 2>/dev/null; then - git commit -q -m "chore: adopt gitflow socle + pre-commit hook" \ - || { echo "gitflow_init: socle commit failed — aborting before hook activation (recoverable)" >&2; return 1; } + _gitflow_adopt_socle || return 1 fi git rev-parse --verify -q "refs/heads/$GITFLOW_DEVELOP" >/dev/null \ || git branch "$GITFLOW_DEVELOP" "$GITFLOW_MAIN" } +# Commit the staged socle on chore/gitflow-adopt (a working branch, so the +# pre-commit allows it), merge it --no-ff into main (a merge commit runs no +# pre-commit), delete the branch. The branch forks off main because develop +# does not exist yet at init time. +_gitflow_adopt_socle() { + local br="chore/gitflow-adopt" + if git rev-parse --verify -q "refs/heads/$br" >/dev/null; then + echo "gitflow_init: '$br' already exists (earlier run) — merge or delete it, then re-run" >&2 + return 1 + fi + git checkout -q -b "$br" || return 1 + git commit -q -m "chore: adopt gitflow socle + versioned hooks" \ + || { echo "gitflow_init: socle commit failed — aborting before hook activation (recoverable)" >&2; return 1; } + git checkout -q "$GITFLOW_MAIN" || return 1 + git merge --no-ff -q -m "Merge $br into $GITFLOW_MAIN" "$br" \ + || { echo "gitflow_init: socle merge into $GITFLOW_MAIN failed — aborting before hook activation" >&2; return 1; } + git branch -q -d "$br" +} + # gitflow_init [msg] → idempotent. Order matters (full BLK-010 closure): # reconcile .gitignore + write the versioned hook FIRST, so the fresh root # commit / existing adoption commit EMBED them; activate the hook LAST so the