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  }