Require git proof before prune deletes a branch
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful

A merged or closed PR no longer authorises a delete on its own: HEAD must
be contained in the PR's head commit or in origin/<branch>, otherwise the
worktree goes and the branch stays. Branch deletion runs `git branch -d`
first and falls back to -D only for a proven branch.

Reword the cherry check to say patches reached the default branch's
history, print the verdict --keep-branches will actually perform, and warn
when a PR listing hits the pagination cap instead of reading it as "no PR".
This commit is contained in:
2026-09-10 00:00:22 +10:00
parent 4bbeaae8f0
commit c1c02c01cf
7 changed files with 249 additions and 47 deletions
+18 -4
View File
@@ -296,7 +296,8 @@ func newRmCmd() *cobra.Command {
if err != nil {
return err
}
return removeWorktree(cmd.OutOrStdout(), wt, deleteBranch)
// Naming one worktree to delete is explicit, so rm keeps the force fallback.
return removeWorktree(cmd.OutOrStdout(), wt, deleteBranch, true)
},
}
cmd.Flags().BoolVar(&deleteBranch, "delete-branch", false, "Also delete the local branch after removing the worktree")
@@ -318,13 +319,16 @@ func resolveWorktree(target string) (managedWt, error) {
return managedWt{}, fmt.Errorf("no managed worktree matching %q (try `agentws list`)", target)
}
func removeWorktree(out io.Writer, wt managedWt, deleteBranch bool) error {
// removeWorktree removes a managed worktree and, when asked, its local branch.
// forceBranch overrides git's unmerged-branch guard, so only a caller that
// proved the commits survive elsewhere may set it.
func removeWorktree(out io.Writer, wt managedWt, deleteBranch, forceBranch bool) error {
if err := agent.GitWorktreeRemove(wt.srcDir, wt.path, true); err != nil {
return err
}
_, _ = fmt.Fprintf(out, "removed worktree %s\n", wt.path)
if deleteBranch {
if err := agent.GitDeleteBranch(wt.srcDir, wt.branch, true); err != nil {
if err := deleteLocalBranch(wt, forceBranch); err != nil {
return err
}
_, _ = fmt.Fprintf(out, "deleted branch %s\n", wt.branch)
@@ -336,6 +340,16 @@ func removeWorktree(out io.Writer, wt managedWt, deleteBranch bool) error {
return agent.GitWorktreePrune(wt.srcDir)
}
// deleteLocalBranch tries the guarded delete first so git refuses to drop
// unmerged commits on its own; force is a fallback, never the first attempt.
func deleteLocalBranch(wt managedWt, force bool) error {
err := agent.GitDeleteBranch(wt.srcDir, wt.branch, false)
if err == nil || !force {
return err
}
return agent.GitDeleteBranch(wt.srcDir, wt.branch, true)
}
// --- clean ----------------------------------------------------------------
func newCleanCmd() *cobra.Command {
@@ -354,7 +368,7 @@ func newCleanCmd() *cobra.Command {
return nil
}
for _, w := range managed {
if err := removeWorktree(out, w, false); err != nil {
if err := removeWorktree(out, w, false, false); err != nil {
return err
}
}