github.com/emmahsax/go-git-helper@v0.0.8-0.20240519163017-907b9de0fa52/cmd/codeRequest/codeRequest.go (about)

     1  package codeRequest
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"regexp"
     7  	"strings"
     8  
     9  	"github.com/emmahsax/go-git-helper/internal/commandline"
    10  	"github.com/emmahsax/go-git-helper/internal/executor"
    11  	"github.com/emmahsax/go-git-helper/internal/git"
    12  	"github.com/emmahsax/go-git-helper/internal/githubPullRequest"
    13  	"github.com/emmahsax/go-git-helper/internal/gitlabMergeRequest"
    14  	"github.com/emmahsax/go-git-helper/internal/utils"
    15  	"github.com/spf13/cobra"
    16  )
    17  
    18  type CodeRequest struct {
    19  	Debug    bool
    20  	Executor executor.ExecutorInterface
    21  }
    22  
    23  func NewCommand() *cobra.Command {
    24  	var (
    25  		debug bool
    26  	)
    27  
    28  	cmd := &cobra.Command{
    29  		Use:                   "code-request",
    30  		Short:                 "Create either a GitHub pull request or a GitLab merge request",
    31  		Args:                  cobra.ExactArgs(0),
    32  		DisableFlagsInUseLine: true,
    33  		RunE: func(cmd *cobra.Command, args []string) error {
    34  			newCodeRequest(debug, executor.NewExecutor(debug)).execute()
    35  			return nil
    36  		},
    37  	}
    38  
    39  	cmd.Flags().BoolVar(&debug, "debug", false, "enables debug mode")
    40  
    41  	return cmd
    42  }
    43  
    44  func newCodeRequest(debug bool, executor executor.ExecutorInterface) *CodeRequest {
    45  	return &CodeRequest{
    46  		Debug:    debug,
    47  		Executor: executor,
    48  	}
    49  }
    50  
    51  func (cr *CodeRequest) execute() {
    52  	if cr.isGitHub() && cr.isGitLab() {
    53  		cr.askForClarification()
    54  	} else if cr.isGitHub() {
    55  		cr.createGitHub()
    56  	} else if cr.isGitLab() {
    57  		cr.createGitLab()
    58  	} else {
    59  		err := errors.New("could not locate GitHub or GitLab remote URLs")
    60  		utils.HandleError(err, cr.Debug, nil)
    61  		return
    62  	}
    63  }
    64  
    65  func (cr *CodeRequest) askForClarification() {
    66  	answer := commandline.AskMultipleChoice("Found git remotes for both GitHub and GitLab. Choose one to proceed with", []string{"GitHub", "GitLab"})
    67  	if answer == "GitHub" {
    68  		cr.createGitHub()
    69  	} else {
    70  		cr.createGitLab()
    71  	}
    72  }
    73  
    74  func (cr *CodeRequest) createGitHub() {
    75  	options := make(map[string]string)
    76  	options["baseBranch"] = cr.baseBranch()
    77  	options["draft"] = cr.draft()
    78  	options["newPrTitle"] = cr.newPrTitle()
    79  	g := git.NewGit(cr.Debug, cr.Executor)
    80  	options["gitRootDir"] = g.GetGitRootDir()
    81  	options["localBranch"] = g.CurrentBranch()
    82  	options["localRepo"] = g.RepoName()
    83  	githubPullRequest.NewGitHubPullRequest(options, cr.Debug).Create()
    84  }
    85  
    86  func (cr *CodeRequest) createGitLab() {
    87  	options := make(map[string]string)
    88  	options["baseBranch"] = cr.baseBranch()
    89  	options["draft"] = cr.draft()
    90  	options["newMrTitle"] = cr.newMrTitle()
    91  	g := git.NewGit(cr.Debug, cr.Executor)
    92  	options["gitRootDir"] = g.GetGitRootDir()
    93  	options["localBranch"] = g.CurrentBranch()
    94  	options["localProject"] = g.RepoName()
    95  	gitlabMergeRequest.NewGitLabMergeRequest(options, cr.Debug).Create()
    96  }
    97  
    98  func (cr *CodeRequest) baseBranch() string {
    99  	g := git.NewGit(cr.Debug, cr.Executor)
   100  	answer := commandline.AskYesNoQuestion("Is '" + g.DefaultBranch() + "' the correct base branch for your new code request?")
   101  
   102  	if answer {
   103  		return g.DefaultBranch()
   104  	} else {
   105  		return commandline.AskOpenEndedQuestion("Base branch", false)
   106  	}
   107  }
   108  
   109  func (cr *CodeRequest) draft() string {
   110  	answer := commandline.AskYesNoQuestion("Create a draft code request?")
   111  
   112  	if answer {
   113  		return "true"
   114  	} else {
   115  		return "false"
   116  	}
   117  }
   118  
   119  func (cr *CodeRequest) newMrTitle() string {
   120  	return cr.newPrTitle()
   121  }
   122  
   123  func (cr *CodeRequest) newPrTitle() string {
   124  	answer := commandline.AskYesNoQuestion("Accept the autogenerated code request title '" + cr.autogeneratedTitle() + "'?")
   125  
   126  	if answer {
   127  		return cr.autogeneratedTitle()
   128  	} else {
   129  		return commandline.AskOpenEndedQuestion("Title", false)
   130  	}
   131  }
   132  
   133  func (cr *CodeRequest) autogeneratedTitle() string {
   134  	g := git.NewGit(cr.Debug, cr.Executor)
   135  	branchArr := strings.FieldsFunc(g.CurrentBranch(), func(r rune) bool {
   136  		return r == '-' || r == '_'
   137  	})
   138  
   139  	if len(branchArr) == 0 {
   140  		return ""
   141  	}
   142  
   143  	var result string
   144  
   145  	if len(branchArr) == 1 {
   146  		result = cr.titleize(branchArr[0])
   147  	} else if cr.checkAllLetters(branchArr[0]) && cr.checkAllNumbers(branchArr[1]) { // Branch includes jira_123 at beginning
   148  		issue := fmt.Sprintf("%s-%s", strings.ToUpper(branchArr[0]), branchArr[1])
   149  		description := strings.Join(branchArr[2:], " ")
   150  		result = fmt.Sprintf("%s %s", issue, cr.titleize(description))
   151  	} else if cr.matchesFullJiraPattern(branchArr[0]) { // Branch includes jira-123 at beginning
   152  		issueSplit := strings.Split(branchArr[0], "-")
   153  		issue := fmt.Sprintf("%s-%s", strings.ToUpper(issueSplit[0]), issueSplit[1])
   154  		description := strings.Join(branchArr[2:], " ")
   155  		result = fmt.Sprintf("%s %s", issue, cr.titleize(description))
   156  	} else {
   157  		result = cr.titleize(strings.Join(branchArr, " "))
   158  	}
   159  
   160  	return result
   161  }
   162  
   163  func (cr *CodeRequest) checkAllLetters(s string) bool {
   164  	match, _ := regexp.MatchString("^[a-zA-Z]+$", s)
   165  	return match
   166  }
   167  
   168  func (cr *CodeRequest) checkAllNumbers(s string) bool {
   169  	match, _ := regexp.MatchString("^[0-9]+$", s)
   170  	return match
   171  }
   172  
   173  func (cr *CodeRequest) matchesFullJiraPattern(str string) bool {
   174  	match, _ := regexp.MatchString(`^\w+-\d+$`, str)
   175  	return match
   176  }
   177  
   178  func (cr *CodeRequest) titleize(s string) string {
   179  	if len(s) == 0 {
   180  		return s
   181  	}
   182  
   183  	firstChar := strings.ToUpper(string(s[0]))
   184  	return firstChar + s[1:]
   185  }
   186  
   187  func (cr *CodeRequest) isGitHub() bool {
   188  	return cr.containsSubstring(git.NewGit(cr.Debug, cr.Executor).Remotes(), "github.com")
   189  }
   190  
   191  func (cr *CodeRequest) isGitLab() bool {
   192  	return cr.containsSubstring(git.NewGit(cr.Debug, cr.Executor).Remotes(), "gitlab.com")
   193  }
   194  
   195  func (cr *CodeRequest) containsSubstring(strs []string, substring string) bool {
   196  	for _, str := range strs {
   197  		if strings.Contains(str, substring) {
   198  			return true
   199  		}
   200  	}
   201  	return false
   202  }