github.com/ianfoo/lab@v0.9.5-0.20180123060006-5ed79f2ccfc7/cmd/mrCreate.go (about) 1 package cmd 2 3 import ( 4 "bytes" 5 "fmt" 6 "log" 7 "os" 8 "regexp" 9 "runtime" 10 "strconv" 11 "strings" 12 "text/template" 13 14 "github.com/spf13/cobra" 15 "github.com/tcnksm/go-gitconfig" 16 "github.com/xanzy/go-gitlab" 17 "github.com/zaquestion/lab/internal/git" 18 lab "github.com/zaquestion/lab/internal/gitlab" 19 ) 20 21 // Only supporting merges into master currently, using this constant to keep 22 // track of reference when setting your own base allowed 23 const ( 24 targetBranch = "master" 25 ) 26 27 // mrCmd represents the mr command 28 var mrCreateCmd = &cobra.Command{ 29 Use: "create [remote]", 30 Short: "Open a merge request on GitLab", 31 Long: `Currently only supports MRs into master`, 32 Args: cobra.MaximumNArgs(1), 33 Run: runMRCreate, 34 } 35 36 func init() { 37 mrCreateCmd.Flags().StringSliceP("message", "m", []string{}, "Use the given <msg>; multiple -m are concatenated as seperate paragraphs") 38 mrCmd.AddCommand(mrCreateCmd) 39 } 40 41 func runMRCreate(cmd *cobra.Command, args []string) { 42 msgs, err := cmd.Flags().GetStringSlice("message") 43 if err != nil { 44 log.Fatal(err) 45 } 46 branch, err := git.CurrentBranch() 47 if err != nil { 48 log.Fatal(err) 49 } 50 51 sourceRemote := determineSourceRemote(branch) 52 sourceProjectName, err := git.PathWithNameSpace(sourceRemote) 53 if err != nil { 54 log.Fatal(err) 55 } 56 57 p, err := lab.FindProject(sourceProjectName) 58 if err != nil { 59 log.Fatal(err) 60 } 61 if !lab.BranchPushed(p.ID, branch) { 62 log.Fatalf("aborting MR, branch %s not present on remote %s. did you forget to push?", branch, sourceRemote) 63 } 64 65 targetRemote := forkedFromRemote 66 if len(args) > 0 { 67 ok, err := git.IsRemote(args[0]) 68 if err != nil { 69 log.Fatal(err) 70 } 71 if ok { 72 targetRemote = args[0] 73 } 74 } 75 targetProjectName, err := git.PathWithNameSpace(targetRemote) 76 if err != nil { 77 log.Fatal(err) 78 } 79 targetProject, err := lab.FindProject(targetProjectName) 80 if err != nil { 81 log.Fatal(err) 82 } 83 84 var title, body string 85 86 if len(msgs) > 0 { 87 title, body = msgs[0], strings.Join(msgs[1:], "\n\n") 88 } else { 89 msg, err := mrText(targetBranch, branch, sourceRemote, forkedFromRemote) 90 if err != nil { 91 log.Fatal(err) 92 } 93 94 title, body, err = git.Edit("MERGEREQ", msg) 95 if err != nil { 96 _, f, l, _ := runtime.Caller(0) 97 log.Fatal(f+":"+strconv.Itoa(l)+" ", err) 98 } 99 } 100 101 if title == "" { 102 log.Fatal("aborting MR due to empty MR msg") 103 } 104 105 mrURL, err := lab.MRCreate(sourceProjectName, &gitlab.CreateMergeRequestOptions{ 106 SourceBranch: &branch, 107 TargetBranch: gitlab.String(targetBranch), 108 TargetProjectID: &targetProject.ID, 109 Title: &title, 110 Description: &body, 111 }) 112 if err != nil { 113 // FIXME: not exiting fatal here to allow code coverage to 114 // generate during Test_mrCreate. In the meantime API failures 115 // will exit 0 116 fmt.Fprintln(os.Stderr, err) 117 } 118 fmt.Println(mrURL + "/diffs") 119 } 120 121 func determineSourceRemote(branch string) string { 122 // Check if the branch is being tracked 123 r, err := gitconfig.Local("branch." + branch + ".remote") 124 if err == nil { 125 return r 126 } 127 128 // If not, check if the fork is named after the user 129 _, err = gitconfig.Local("remote." + lab.User() + ".url") 130 if err == nil { 131 return lab.User() 132 } 133 134 // If not, default to origin 135 return "origin" 136 } 137 138 func mrText(base, head, sourceRemote, forkedFromRemote string) (string, error) { 139 lastCommitMsg, err := git.LastCommitMessage() 140 if err != nil { 141 return "", err 142 } 143 const tmpl = `{{if .InitMsg}}{{.InitMsg}}{{end}} 144 145 {{if .Tmpl}}{{.Tmpl}}{{end}} 146 {{.CommentChar}} Requesting a merge into {{.Base}} from {{.Head}} 147 {{.CommentChar}} 148 {{.CommentChar}} Write a message for this merge request. The first block 149 {{.CommentChar}} of text is the title and the rest is the description.{{if .CommitLogs}} 150 {{.CommentChar}} 151 {{.CommentChar}} Changes: 152 {{.CommentChar}} 153 {{.CommitLogs}}{{end}}` 154 155 mrTmpl := lab.LoadGitLabTmpl(lab.TmplMR) 156 157 remoteBase := fmt.Sprintf("%s/%s", forkedFromRemote, base) 158 commitLogs, err := git.Log(remoteBase, head) 159 if err != nil { 160 return "", err 161 } 162 startRegexp := regexp.MustCompilePOSIX("^") 163 commentChar := git.CommentChar() 164 commitLogs = strings.TrimSpace(commitLogs) 165 commitLogs = startRegexp.ReplaceAllString(commitLogs, fmt.Sprintf("%s ", commentChar)) 166 167 t, err := template.New("tmpl").Parse(tmpl) 168 if err != nil { 169 return "", err 170 } 171 172 msg := &struct { 173 InitMsg string 174 Tmpl string 175 CommentChar string 176 Base string 177 Head string 178 CommitLogs string 179 }{ 180 InitMsg: lastCommitMsg, 181 Tmpl: mrTmpl, 182 CommentChar: commentChar, 183 Base: forkedFromRemote + ":" + base, 184 Head: sourceRemote + ":" + head, 185 CommitLogs: commitLogs, 186 } 187 188 var b bytes.Buffer 189 err = t.Execute(&b, msg) 190 if err != nil { 191 return "", err 192 } 193 194 return b.String(), nil 195 }