feat(gitflow): remove the origin copy of a branch once its merge is verified

`gitflow_delete` now ends with `_gitflow_delete_remote`: after the local
copy is gone, the remote tip is read with `ls-remote --exit-code`, checked
against develop/main with the same ancestor test, and only then removed
with `push origin --delete`. Same contract as the pushes (BDR-095): best
effort, warn never fail. No origin, `GITFLOW_NO_PUSH=1` or
`gitflow.autopush false` skip it; an unreachable origin or a remote tip
holding commits the bases lack keeps the remote branch, loudly. A base is
never targeted, by construction and by an explicit guard.

The static deny on hand `git push --delete` stays: it matches the Bash
tool's command string, the lib is the sanctioned path. Prose (hard_deny,
environment), doctrine, gitflow SKILL (table, op, warning row),
SETTINGS.md and CHANGELOG updated. T24: 9 checks (finish removes the
copy, bases untouched, unmerged remote tip kept, never pushed silent,
unreachable origin loud, autopush opt-out). 161/163, the 2 failures are
the pre-existing T16a (gitleaks absent on this host).
This commit is contained in:
bastien
2026-09-24 11:50:16 +02:00
parent abd1254d66
commit 68c9df354b
7 changed files with 104 additions and 18 deletions
+8 -5
View File
@@ -35,7 +35,7 @@ develop [+ any open release/*]).
bash ~/.claude/lib/gitflow.sh init [msg] # main+develop; root-commit (fresh) or ensure (existing); reconcile .gitignore; install hook
bash ~/.claude/lib/gitflow.sh start <type> <name> # branch from the correct base
bash ~/.claude/lib/gitflow.sh finish # directed merge of the CURRENT branch — HUMAN-GATED (below)
bash ~/.claude/lib/gitflow.sh delete <branch> # delete a branch merged elsewhere (Gitea PR, hand merge) — refuses main/develop + anything unmerged
bash ~/.claude/lib/gitflow.sh delete <branch> # delete a merged branch, local + origin copy — refuses main/develop + anything unmerged
bash ~/.claude/lib/gitflow.sh protected-base [br] # rc 0 on main/develop — the shared predicate
```
@@ -43,13 +43,15 @@ bash ~/.claude/lib/gitflow.sh protected-base [br] # rc 0 on main/develop — the
| Current branch | Merges into | then |
|---|---|---|
| `feature/*` · `bugfix/*` · `chore/*` | develop | delete |
| `release/*` | main + develop | delete |
| `hotfix/*` | main + develop + any open `release/*` | delete |
| `feature/*` · `bugfix/*` · `chore/*` | develop | delete local + `origin/` copy |
| `release/*` | main + develop | delete local + `origin/` copy |
| `hotfix/*` | main + develop + any open `release/*` | delete local + `origin/` copy |
`delete` is `gitflow_delete`, the only path that removes a branch: it refuses
`main`/`develop` (rc 6) and any branch not merged into develop or main (rc 5),
and keeps the branch. Hand `git branch -d` is denied — with an auto-pushed
and keeps the branch. The `origin/` copy is removed right after, once ITS
tip passes the same check; a remote tip holding commits the bases lack is
kept, loudly (T24). Hand `git branch -d` is denied — with an auto-pushed
upstream it checks the wrong thing (T22a). A `reference-transaction` hook
vetoes any deletion or rename of `main`/`develop` at the ref layer, in every
repo.
@@ -98,6 +100,7 @@ call `start <type>` to branch first; on a working branch they commit in place. S
| `init` rc=1 — socle commit failed | Recoverable: aborted BEFORE hook activation by design; fix the cause (hooks, perms), re-run `init` |
| `delete`/`finish` rc=5 — branch not merged into develop or main | The branch still holds unmerged work: KEEP it, report it, never fall back to `git branch -d`/`-D`. Merge first (human gate), then re-run |
| `delete` rc=6 — protected base | `main`/`develop` are never deleted. Stop; the request itself is the defect to report |
| `delete`/`finish` warning "remote copy KEPT" or "NOT removed" | Non-fatal BY CONTRACT (remote cleanup is best-effort). KEPT = origin/<br> has a tip the bases lack: fetch, look, merge or leave it — never `git push --delete` by hand. NOT removed = origin unreachable or refused: report the printed command to the user |
## Common Mistakes