github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/pkg/cmd/pr/close/close.go (about) 1 package close 2 3 import ( 4 "context" 5 "fmt" 6 "net/http" 7 8 "github.com/ungtb10d/cli/v2/api" 9 "github.com/ungtb10d/cli/v2/git" 10 "github.com/ungtb10d/cli/v2/internal/ghrepo" 11 "github.com/ungtb10d/cli/v2/pkg/cmd/pr/shared" 12 "github.com/ungtb10d/cli/v2/pkg/cmdutil" 13 "github.com/ungtb10d/cli/v2/pkg/iostreams" 14 "github.com/spf13/cobra" 15 ) 16 17 type CloseOptions struct { 18 HttpClient func() (*http.Client, error) 19 GitClient *git.Client 20 IO *iostreams.IOStreams 21 Branch func() (string, error) 22 23 Finder shared.PRFinder 24 25 SelectorArg string 26 Comment string 27 DeleteBranch bool 28 DeleteLocalBranch bool 29 } 30 31 func NewCmdClose(f *cmdutil.Factory, runF func(*CloseOptions) error) *cobra.Command { 32 opts := &CloseOptions{ 33 IO: f.IOStreams, 34 HttpClient: f.HttpClient, 35 GitClient: f.GitClient, 36 Branch: f.Branch, 37 } 38 39 cmd := &cobra.Command{ 40 Use: "close {<number> | <url> | <branch>}", 41 Short: "Close a pull request", 42 Args: cmdutil.ExactArgs(1, "cannot close pull request: number, url, or branch required"), 43 RunE: func(cmd *cobra.Command, args []string) error { 44 opts.Finder = shared.NewFinder(f) 45 46 if len(args) > 0 { 47 opts.SelectorArg = args[0] 48 } 49 50 opts.DeleteLocalBranch = !cmd.Flags().Changed("repo") 51 52 if runF != nil { 53 return runF(opts) 54 } 55 return closeRun(opts) 56 }, 57 } 58 59 cmd.Flags().StringVarP(&opts.Comment, "comment", "c", "", "Leave a closing comment") 60 cmd.Flags().BoolVarP(&opts.DeleteBranch, "delete-branch", "d", false, "Delete the local and remote branch after close") 61 62 return cmd 63 } 64 65 func closeRun(opts *CloseOptions) error { 66 cs := opts.IO.ColorScheme() 67 68 findOptions := shared.FindOptions{ 69 Selector: opts.SelectorArg, 70 Fields: []string{"state", "number", "title", "isCrossRepository", "headRefName"}, 71 } 72 pr, baseRepo, err := opts.Finder.Find(findOptions) 73 if err != nil { 74 return err 75 } 76 77 if pr.State == "MERGED" { 78 fmt.Fprintf(opts.IO.ErrOut, "%s Pull request #%d (%s) can't be closed because it was already merged\n", cs.FailureIcon(), pr.Number, pr.Title) 79 return cmdutil.SilentError 80 } else if !pr.IsOpen() { 81 fmt.Fprintf(opts.IO.ErrOut, "%s Pull request #%d (%s) is already closed\n", cs.WarningIcon(), pr.Number, pr.Title) 82 return nil 83 } 84 85 httpClient, err := opts.HttpClient() 86 if err != nil { 87 return err 88 } 89 90 if opts.Comment != "" { 91 commentOpts := &shared.CommentableOptions{ 92 Body: opts.Comment, 93 HttpClient: opts.HttpClient, 94 InputType: shared.InputTypeInline, 95 Quiet: true, 96 RetrieveCommentable: func() (shared.Commentable, ghrepo.Interface, error) { 97 return pr, baseRepo, nil 98 }, 99 } 100 err := shared.CommentableRun(commentOpts) 101 if err != nil { 102 return err 103 } 104 } 105 106 err = api.PullRequestClose(httpClient, baseRepo, pr.ID) 107 if err != nil { 108 return fmt.Errorf("API call failed: %w", err) 109 } 110 111 fmt.Fprintf(opts.IO.ErrOut, "%s Closed pull request #%d (%s)\n", cs.SuccessIconWithColor(cs.Red), pr.Number, pr.Title) 112 113 if opts.DeleteBranch { 114 ctx := context.Background() 115 branchSwitchString := "" 116 apiClient := api.NewClientFromHTTP(httpClient) 117 localBranchExists := opts.GitClient.HasLocalBranch(ctx, pr.HeadRefName) 118 119 if opts.DeleteLocalBranch { 120 if localBranchExists { 121 currentBranch, err := opts.Branch() 122 if err != nil { 123 return err 124 } 125 126 var branchToSwitchTo string 127 if currentBranch == pr.HeadRefName { 128 branchToSwitchTo, err = api.RepoDefaultBranch(apiClient, baseRepo) 129 if err != nil { 130 return err 131 } 132 err = opts.GitClient.CheckoutBranch(ctx, branchToSwitchTo) 133 if err != nil { 134 return err 135 } 136 } 137 138 if err := opts.GitClient.DeleteLocalBranch(ctx, pr.HeadRefName); err != nil { 139 return fmt.Errorf("failed to delete local branch %s: %w", cs.Cyan(pr.HeadRefName), err) 140 } 141 142 if branchToSwitchTo != "" { 143 branchSwitchString = fmt.Sprintf(" and switched to branch %s", cs.Cyan(branchToSwitchTo)) 144 } 145 } else { 146 fmt.Fprintf(opts.IO.ErrOut, "%s Skipped deleting the local branch since current directory is not a git repository \n", cs.WarningIcon()) 147 } 148 } 149 150 if pr.IsCrossRepository { 151 fmt.Fprintf(opts.IO.ErrOut, "%s Skipped deleting the remote branch of a pull request from fork\n", cs.WarningIcon()) 152 if !opts.DeleteLocalBranch { 153 return nil 154 } 155 } else { 156 if err := api.BranchDeleteRemote(apiClient, baseRepo, pr.HeadRefName); err != nil { 157 return fmt.Errorf("failed to delete remote branch %s: %w", cs.Cyan(pr.HeadRefName), err) 158 } 159 } 160 fmt.Fprintf(opts.IO.ErrOut, "%s Deleted branch %s%s\n", cs.SuccessIconWithColor(cs.Red), cs.Cyan(pr.HeadRefName), branchSwitchString) 161 } 162 163 return nil 164 }