164 lines
4.6 KiB
Go
164 lines
4.6 KiB
Go
package close
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"github.com/cli/cli/v2/api"
|
|
"github.com/cli/cli/v2/git"
|
|
"github.com/cli/cli/v2/internal/ghrepo"
|
|
"github.com/cli/cli/v2/pkg/cmd/pr/shared"
|
|
"github.com/cli/cli/v2/pkg/cmdutil"
|
|
"github.com/cli/cli/v2/pkg/iostreams"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
type CloseOptions struct {
|
|
HttpClient func() (*http.Client, error)
|
|
GitClient *git.Client
|
|
IO *iostreams.IOStreams
|
|
Branch func() (string, error)
|
|
|
|
Finder shared.PRFinder
|
|
|
|
SelectorArg string
|
|
Comment string
|
|
DeleteBranch bool
|
|
DeleteLocalBranch bool
|
|
}
|
|
|
|
func NewCmdClose(f *cmdutil.Factory, runF func(*CloseOptions) error) *cobra.Command {
|
|
opts := &CloseOptions{
|
|
IO: f.IOStreams,
|
|
HttpClient: f.HttpClient,
|
|
GitClient: f.GitClient,
|
|
Branch: f.Branch,
|
|
}
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "close {<number> | <url> | <branch>}",
|
|
Short: "Close a pull request",
|
|
Args: cmdutil.ExactArgs(1, "cannot close pull request: number, url, or branch required"),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
opts.Finder = shared.NewFinder(f)
|
|
|
|
if len(args) > 0 {
|
|
opts.SelectorArg = args[0]
|
|
}
|
|
|
|
opts.DeleteLocalBranch = !cmd.Flags().Changed("repo")
|
|
|
|
if runF != nil {
|
|
return runF(opts)
|
|
}
|
|
return closeRun(opts)
|
|
},
|
|
}
|
|
|
|
cmd.Flags().StringVarP(&opts.Comment, "comment", "c", "", "Leave a closing comment")
|
|
cmd.Flags().BoolVarP(&opts.DeleteBranch, "delete-branch", "d", false, "Delete the local and remote branch after close")
|
|
|
|
return cmd
|
|
}
|
|
|
|
func closeRun(opts *CloseOptions) error {
|
|
cs := opts.IO.ColorScheme()
|
|
|
|
findOptions := shared.FindOptions{
|
|
Selector: opts.SelectorArg,
|
|
Fields: []string{"state", "number", "title", "isCrossRepository", "headRefName"},
|
|
}
|
|
pr, baseRepo, err := opts.Finder.Find(findOptions)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if pr.State == "MERGED" {
|
|
fmt.Fprintf(opts.IO.ErrOut, "%s Pull request %s#%d (%s) can't be closed because it was already merged\n", cs.FailureIcon(), ghrepo.FullName(baseRepo), pr.Number, pr.Title)
|
|
return cmdutil.SilentError
|
|
} else if !pr.IsOpen() {
|
|
fmt.Fprintf(opts.IO.ErrOut, "%s Pull request %s#%d (%s) is already closed\n", cs.WarningIcon(), ghrepo.FullName(baseRepo), pr.Number, pr.Title)
|
|
return nil
|
|
}
|
|
|
|
httpClient, err := opts.HttpClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if opts.Comment != "" {
|
|
commentOpts := &shared.CommentableOptions{
|
|
Body: opts.Comment,
|
|
HttpClient: opts.HttpClient,
|
|
InputType: shared.InputTypeInline,
|
|
Quiet: true,
|
|
RetrieveCommentable: func() (shared.Commentable, ghrepo.Interface, error) {
|
|
return pr, baseRepo, nil
|
|
},
|
|
}
|
|
err := shared.CommentableRun(commentOpts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
err = api.PullRequestClose(httpClient, baseRepo, pr.ID)
|
|
if err != nil {
|
|
return fmt.Errorf("API call failed: %w", err)
|
|
}
|
|
|
|
fmt.Fprintf(opts.IO.ErrOut, "%s Closed pull request %s#%d (%s)\n", cs.SuccessIconWithColor(cs.Red), ghrepo.FullName(baseRepo), pr.Number, pr.Title)
|
|
|
|
if opts.DeleteBranch {
|
|
ctx := context.Background()
|
|
branchSwitchString := ""
|
|
apiClient := api.NewClientFromHTTP(httpClient)
|
|
localBranchExists := opts.GitClient.HasLocalBranch(ctx, pr.HeadRefName)
|
|
|
|
if opts.DeleteLocalBranch {
|
|
if localBranchExists {
|
|
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 = opts.GitClient.CheckoutBranch(ctx, branchToSwitchTo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := opts.GitClient.DeleteLocalBranch(ctx, pr.HeadRefName); err != nil {
|
|
return fmt.Errorf("failed to delete local branch %s: %w", cs.Cyan(pr.HeadRefName), err)
|
|
}
|
|
|
|
if branchToSwitchTo != "" {
|
|
branchSwitchString = fmt.Sprintf(" and switched to branch %s", cs.Cyan(branchToSwitchTo))
|
|
}
|
|
} else {
|
|
fmt.Fprintf(opts.IO.ErrOut, "%s Skipped deleting the local branch since current directory is not a git repository\n", cs.WarningIcon())
|
|
}
|
|
}
|
|
|
|
if pr.IsCrossRepository {
|
|
fmt.Fprintf(opts.IO.ErrOut, "%s Skipped deleting the remote branch of a pull request from fork\n", cs.WarningIcon())
|
|
if !opts.DeleteLocalBranch {
|
|
return nil
|
|
}
|
|
} else {
|
|
if err := api.BranchDeleteRemote(apiClient, baseRepo, pr.HeadRefName); err != nil {
|
|
return fmt.Errorf("failed to delete remote branch %s: %w", cs.Cyan(pr.HeadRefName), err)
|
|
}
|
|
}
|
|
fmt.Fprintf(opts.IO.ErrOut, "%s Deleted branch %s%s\n", cs.SuccessIconWithColor(cs.Red), cs.Cyan(pr.HeadRefName), branchSwitchString)
|
|
}
|
|
|
|
return nil
|
|
}
|