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 }