feat(stack): add a command to delete a stack
Why
glab stack sync removes a stack's layers as their merge requests are merged or
closed, but the stack's own directory under .git/stacked stays behind. glab stack switch builds its list by reading those directories, so a finished stack keeps
showing up in the selector with nothing but a BASE_BRANCH file left in it.
There is no command for this, so the way out is to know the storage layout:
rm -r .git/stacked/<stack-name>What
glab stack delete <stack-name> removes that directory. With no argument it offers
the same picker as glab stack switch.
$ glab stack delete
? Choose a stack to delete: cool-new-feature
Stack cool-new-feature still has 2 layers.
Deleting a stack removes its local metadata. Branches and merge requests are not affected.
? Delete stack cool-new-feature? Yes!
Deleted stack cool-new-feature.It asks first unless -y is passed, and says what is at stake before asking, since
"delete" could reasonably be read as deleting the branches too. --yes matches the
spelling used by securefile remove, mr merge and the other commands that guard a
destructive step.
Deleting the stack you are currently on also unsets glab.currentstack. That is kept
in Git config rather than in the stack directory, so it survives the delete and would
otherwise name a stack that is no longer there.
Two helpers in internal/git/stacked.go:
UnsetLocalConfig, next to the existingSetLocalConfig.RemoveStackRefDir, which rejects"",.,..and any name containing a path separator. The name is already checked againstGetStacks()before it gets there, but this is the one place that callsos.RemoveAll, andstacked/..resolves to the.gitdirectory itself.
The issue also suggests stack sync could delete a stack automatically after
removing its final layer. I left that out. It changes what an existing command does
to your metadata, and the explicit command covers abandoned stacks either way, which
sync cannot detect. Happy to add it here if you would rather have both.
Verification
| check | result |
|---|---|
go build ./... |
ok |
go test ./internal/commands/stack/... ./internal/git/... |
ok, all packages |
golangci-lint run on both trees |
0 issues |
make gen-docs then git status |
clean, generated pages committed |
Ten command tests and two in internal/git. Every one of them runs under a context
deadline set with cmd.SetContext, which is worth explaining: IOStreams.Confirm
waits on input that never arrives rather than reading end of input, so a prompt that
fires when it should not blocks the test binary until the suite is killed. Without
the deadline the report reads panic: test timed out and never names the prompt.
A non-TTY IOStreams does not help here.
Mutation-tested, each reverted afterwards:
| mutation | result |
|---|---|
confirmation runs regardless of --yes |
4 tests FAIL in 10s each, deleting stack failed: timeout |
drop the name check in RemoveStackRefDir |
FAIL in 0.3s, name "" should be rejected |
never unset glab.currentstack |
FAIL in 0.09s, glab.currentstack should have been unset |
Closes #8534 (closed)