diff --git a/pkg/cmd/pr/close/close.go b/pkg/cmd/pr/close/close.go index a7d175624..7ff257855 100644 --- a/pkg/cmd/pr/close/close.go +++ b/pkg/cmd/pr/close/close.go @@ -5,6 +5,7 @@ import ( "net/http" "github.com/cli/cli/api" + "github.com/cli/cli/git" "github.com/cli/cli/internal/config" "github.com/cli/cli/internal/ghrepo" "github.com/cli/cli/pkg/cmd/pr/shared" @@ -19,8 +20,11 @@ type CloseOptions struct { Config func() (config.Config, error) IO *iostreams.IOStreams BaseRepo func() (ghrepo.Interface, error) + Branch func() (string, error) - SelectorArg string + SelectorArg string + DeleteBranch bool + DeleteLocalBranch bool } func NewCmdClose(f *cmdutil.Factory, runF func(*CloseOptions) error) *cobra.Command { @@ -28,6 +32,7 @@ func NewCmdClose(f *cmdutil.Factory, runF func(*CloseOptions) error) *cobra.Comm IO: f.IOStreams, HttpClient: f.HttpClient, Config: f.Config, + Branch: f.Branch, } cmd := &cobra.Command{ @@ -42,12 +47,15 @@ func NewCmdClose(f *cmdutil.Factory, runF func(*CloseOptions) error) *cobra.Comm opts.SelectorArg = args[0] } + opts.DeleteLocalBranch = !cmd.Flags().Changed("repo") + if runF != nil { return runF(opts) } return closeRun(opts) }, } + cmd.Flags().BoolVarP(&opts.DeleteBranch, "delete-branch", "d", false, "Delete the local and remote branch after close") return cmd } @@ -79,5 +87,52 @@ func closeRun(opts *CloseOptions) error { fmt.Fprintf(opts.IO.ErrOut, "%s Closed pull request #%d (%s)\n", utils.Red("✔"), pr.Number, pr.Title) + crossRepoPR := pr.HeadRepositoryOwner.Login != baseRepo.RepoOwner() + + if opts.DeleteBranch { + branchSwitchString := "" + + if opts.DeleteLocalBranch && !crossRepoPR { + currentBranch, err := opts.Branch() + if err != nil { + return err + } + + var branchToSwitchTo string + if currentBranch == pr.HeadRefName { + branchToSwitchTo, err = api.RepoDefaultBranch(apiClient, baseRepo) + if err != nil { + return err + } + err = git.CheckoutBranch(branchToSwitchTo) + if err != nil { + return err + } + } + + localBranchExists := git.HasLocalBranch(pr.HeadRefName) + if localBranchExists { + err = git.DeleteLocalBranch(pr.HeadRefName) + if err != nil { + err = fmt.Errorf("failed to delete local branch %s: %w", utils.Cyan(pr.HeadRefName), err) + return err + } + } + + if branchToSwitchTo != "" { + branchSwitchString = fmt.Sprintf(" and switched to branch %s", utils.Cyan(branchToSwitchTo)) + } + } + + if !crossRepoPR { + err = api.BranchDeleteRemote(apiClient, baseRepo, pr.HeadRefName) + if err != nil { + err = fmt.Errorf("failed to delete remote branch %s: %w", utils.Cyan(pr.HeadRefName), err) + return err + } + } + fmt.Fprintf(opts.IO.ErrOut, "%s Deleted branch %s%s\n", utils.Red("✔"), utils.Cyan(pr.HeadRefName), branchSwitchString) + } + return nil } diff --git a/pkg/cmd/pr/close/close_test.go b/pkg/cmd/pr/close/close_test.go index 1bb530637..136554b4f 100644 --- a/pkg/cmd/pr/close/close_test.go +++ b/pkg/cmd/pr/close/close_test.go @@ -33,6 +33,9 @@ func runCommand(rt http.RoundTripper, isTTY bool, cli string) (*test.CmdOut, err BaseRepo: func() (ghrepo.Interface, error) { return ghrepo.New("OWNER", "REPO"), nil }, + Branch: func() (string, error) { + return "trunk", nil + }, } cmd := NewCmdClose(factory, nil) @@ -99,3 +102,32 @@ func TestPrClose_alreadyClosed(t *testing.T) { t.Fatalf("output did not match regexp /%s/\n> output\n%q\n", r, output.Stderr()) } } + +func TestPrClose_deleteBranch(t *testing.T) { + http := &httpmock.Registry{} + defer http.Verify(t) + http.StubResponse(200, bytes.NewBufferString(` + { "data": { "repository": { + "pullRequest": { "number": 96, "title": "The title of the PR", "headRefName":"blueberries", "headRepositoryOwner": {"login": "OWNER"}} + } } } + `)) + http.StubResponse(200, bytes.NewBufferString(`{"id": "THE-ID"}`)) + http.Register( + httpmock.REST("DELETE", "repos/OWNER/REPO/git/refs/heads/blueberries"), + httpmock.StringResponse(`{}`)) + + cs, cmdTeardown := test.InitCmdStubber() + defer cmdTeardown() + + cs.Stub("") // git config --get-regexp ^branch\.blueberries\.(remote|merge)$ + cs.Stub("") // git rev-parse --verify blueberries` + cs.Stub("") // git branch -d + cs.Stub("") // git push origin --delete blueberries + + output, err := runCommand(http, true, `96 --delete-branch`) + if err != nil { + t.Fatalf("Got unexpected error running `pr close` %s", err) + } + + test.ExpectLines(t, output.Stderr(), `Closed pull request #96 \(The title of the PR\)`, `Deleted branch blueberries`) +}