diff --git a/internal/app/pull_merge_form_test.go b/internal/app/pull_merge_form_test.go index b28d8d85..e9454c41 100644 --- a/internal/app/pull_merge_form_test.go +++ b/internal/app/pull_merge_form_test.go @@ -121,6 +121,36 @@ func TestPullMergeAsksForTheMethodAndBranchDeletion(t *testing.T) { require.Equal(t, "āœ“ Squashed and merged pull request alice/project#3 (Add login)\nāœ“ Deleted remote branch feature\n", diagnostics.String()) } +// Gitea keeps the head branch after a merge when another open pull request uses it, since +// deleting it would close that one. tea must not delete it either: mergeServer fails on a DELETE. +func TestPullMergeKeepsAHeadBranchThatGiteaKept(t *testing.T) { + for _, tc := range []struct { + name string + flags []string + }{ + {"repository default", nil}, + {"flag", []string{"--delete-branch"}}, + } { + t.Run(tc.name, func(t *testing.T) { + var payload map[string]any + pulls := mergeServer(t, openPull, &payload) + server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodGet && r.URL.Path == "/api/v1/repos/alice/project/pulls/3" && payload != nil { + _, _ = w.Write([]byte(`{"number":3,"title":"Add login","state":"closed","merged":true,"base":{"ref":"main"},"head":{"ref":"feature","sha":"abc123","repo":{"full_name":"alice/project","permissions":{"push":true}}}}`)) + return + } + pulls.ServeHTTP(w, r) + })) + defer server.Close() + deps, _, diagnostics := serverDeps(t, server.URL) + deps.Git = &fakeGit{results: map[string]string{"symbolic-ref --short HEAD": "main"}} + require.NoError(t, Execute(context.Background(), deps, append([]string{"pr", "merge", "3", "-R", "alice/project", "--yes"}, tc.flags...))) + require.Equal(t, true, payload["delete_branch_after_merge"]) + require.Equal(t, "! Kept remote branch feature: Gitea did not delete it (it is protected, the default branch, or used by another open pull request)\n", diagnostics.String()) + }) + } +} + func TestPullMergeRefusals(t *testing.T) { for _, tc := range []struct { name, pull string diff --git a/internal/app/pull_mutations.go b/internal/app/pull_mutations.go index 79be53c0..b9dfb60a 100644 --- a/internal/app/pull_mutations.go +++ b/internal/app/pull_mutations.go @@ -123,8 +123,9 @@ $ tea pr close 13 -c "Superseded by #15" --delete-branch` } // deleteHead deletes the head branch of a merged or closed pull request on Gitea, unless it is -// gone, and then locally when local is set, reporting each step. merging tells that Gitea deleted -// it as part of the merge. +// gone, and then locally when local is set, reporting each step. merging tells that Gitea was +// asked to delete it as part of the merge; a branch Gitea kept then stays, since Gitea keeps the +// head branch of another open pull request, which deleting it would close. func (a *application) deleteHead(cmd *cobra.Command, service services.Pulls, item services.Pull, merging, local bool) { switch { case item.HeadRemoteDeleted && merging: @@ -133,6 +134,8 @@ func (a *application) deleteHead(cmd *cobra.Command, service services.Pulls, ite a.warn(cmd, "Remote branch %s was already deleted", item.Head) case !item.HeadCanPush: a.warn(cmd, "Kept remote branch %s: you cannot push to %s", item.Head, item.HeadRepo) + case merging: + a.warn(cmd, "Kept remote branch %s: Gitea did not delete it (it is protected, the default branch, or used by another open pull request)", item.Head) default: if err := service.DeleteHead(cmd.Context(), item); err != nil { a.warn(cmd, "Kept remote branch %s: %v", item.Head, err)