ADR 0029 omission is bypassed by a hook-silent git checkout -- <path> #554
Labels
No labels
breaking
bug
documentation
enhancement
epic
good first issue
help wanted
refactoring
resolution/duplicate
resolution/invalid
resolution/wontfix
security
severity/critical
severity/high
severity/low
severity/medium
status/abandoned
status/blocked
status/needs-decision
status/needs-info
status/needs-verification
testing
upstream
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
sixfold-space/madtea#554
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The hole
ADR 0029 omits
worktree remove --forcefrom every surface, so madtea cannot discard a dirty worktree. Its Context section rests that on two layers: the omitted flag, plus the plugin hooks that steer callers off raw git. It states the two layers are independent, neither depending on the other behaving.Both layers missed the shortest path to the same loss:
Step one destroys every uncommitted change in the named files.
check-git-local.shclassifiesgit checkout -- <path>file restore as fully SILENT: no deny, not even an advisory. Step two then succeeds, because the omission's protection is a dirty-tree refusal, and the tree is no longer dirty. The work is gone, and no layer ever fired.I did exactly this today, to four files, in one command. Nothing warned me.
Why the omission does not cover it
The per-operation table justifies dropping the force flag with "a clean worktree already removes without force, so dropping the flag loses nothing recoverable-affecting". That holds only while MAKING a worktree clean is itself guarded. It is not. Cleaning is one silent command away, so
removewithoutforce=isremove --forcein two steps.Clause 3 also assumes the escape is mine to run: "the refusal names the tool the user already holds, raw git in their own shell". Here no refusal was involved and no terminal of mine was used. The destruction ran through the plugin's own hooked Bash path, in a form the plugin deliberately stays silent on.
The inverted priority
The hook suite guards the RECOVERABLE destructive forms and ignores the unrecoverable one:
git branch -Ddestroys a ref only. The commits survive in the reflog.madt_branch_deleteomits force, and ADR 0029 treats this as a headline protection.git checkout -- <path>andgit restore <path>destroy uncommitted content. There is no reflog for a working-tree file. Both are silent.The second is the only one of the two that loses work which never existed anywhere else.
What needs deciding
This is a question about ADR 0029's scope, not only a matcher gap, so I do not want it closed by quietly adding a pattern.
git checkout -- <path>,git restore <path>, andgit stash drop/git stash cleareach need a classification. ADR 0021 makes these a hard case: they are not reads, so its advisory rule does not reach them, and a deny has nomadt_*replacement to point at, because madtea deliberately offers no discard.My lean is that the guard belongs on the act of making a tree clean rather than on the removal that follows, and that it must not be satisfiable by running the destructive form first.
Acceptance criteria
git checkout -- <path>,git restore <path>, and the stash discard forms each carry a recorded classification with its reasoning, whichever way it goes.hooks/tests/check-git-local.test.sh.