github.com/nkprince007/lab@v0.6.2-0.20171218071646-19d68b56f403/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().StringSliceVarP(&msgs, "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 branch, err := git.CurrentBranch() 43 if err != nil { 44 log.Fatal(err) 45 } 46 47 sourceRemote := determineSourceRemote(branch) 48 sourceProjectName, err := git.PathWithNameSpace(sourceRemote) 49 if err != nil { 50 log.Fatal(err) 51 } 52 53 if !lab.BranchPushed(sourceProjectName, branch) { 54 log.Fatal("aborting MR, branch not present on remote: ", sourceRemote) 55 } 56 57 targetRemote := forkedFromRemote 58 if len(args) > 0 { 59 ok, err := git.IsRemote(args[0]) 60 if err != nil { 61 log.Fatal(err) 62 } 63 if ok { 64 targetRemote = args[0] 65 } 66 } 67 targetProjectName, err := git.PathWithNameSpace(targetRemote) 68 if err != nil { 69 log.Fatal(err) 70 } 71 targetProject, err := lab.FindProject(targetProjectName) 72 if err != nil { 73 log.Fatal(err) 74 } 75 76 var title, body string 77 78 if len(msgs) > 0 { 79 title, body = msgs[0], strings.Join(msgs[1:], "\n\n") 80 } else { 81 msg, err := mrText(targetBranch, branch, sourceRemote, forkedFromRemote) 82 if err != nil { 83 log.Fatal(err) 84 } 85 86 title, body, err = git.Edit("MERGEREQ", msg) 87 if err != nil { 88 _, f, l, _ := runtime.Caller(0) 89 log.Fatal(f+":"+strconv.Itoa(l)+" ", err) 90 } 91 } 92 93 if title == "" { 94 log.Fatal("aborting MR due to empty MR msg") 95 } 96 97 mrURL, err := lab.MergeRequest(sourceProjectName, &gitlab.CreateMergeRequestOptions{ 98 SourceBranch: &branch, 99 TargetBranch: gitlab.String(targetBranch), 100 TargetProjectID: &targetProject.ID, 101 Title: &title, 102 Description: &body, 103 }) 104 if err != nil { 105 // FIXME: not exiting fatal here to allow code coverage to 106 // generate during Test_mrCreate. In the meantime API failures 107 // will exit 0 108 fmt.Fprintln(os.Stderr, err) 109 } 110 fmt.Println(mrURL + "/diffs") 111 } 112 113 func determineSourceRemote(branch string) string { 114 // Check if the branch is being tracked 115 r, err := gitconfig.Local("branch." + branch + ".remote") 116 if err == nil { 117 return r 118 } 119 120 // If not, check if the fork is named after the user 121 _, err = gitconfig.Local("remote." + lab.User() + ".url") 122 if err == nil { 123 return lab.User() 124 } 125 126 // If not, default to origin 127 return "origin" 128 } 129 130 func mrText(base, head, sourceRemote, forkedFromRemote string) (string, error) { 131 lastCommitMsg, err := git.LastCommitMessage() 132 if err != nil { 133 return "", err 134 } 135 const tmpl = `{{if .InitMsg}}{{.InitMsg}}{{end}} 136 137 {{if .Tmpl}}{{.Tmpl}}{{end}} 138 {{.CommentChar}} Requesting a merge into {{.Base}} from {{.Head}} 139 {{.CommentChar}} 140 {{.CommentChar}} Write a message for this merge request. The first block 141 {{.CommentChar}} of text is the title and the rest is the description.{{if .CommitLogs}} 142 {{.CommentChar}} 143 {{.CommentChar}} Changes: 144 {{.CommentChar}} 145 {{.CommitLogs}}{{end}}` 146 147 mrTmpl := lab.LoadGitLabTmpl(lab.TmplMR) 148 149 remoteBase := fmt.Sprintf("%s/%s", forkedFromRemote, base) 150 commitLogs, err := git.Log(remoteBase, head) 151 if err != nil { 152 return "", err 153 } 154 startRegexp := regexp.MustCompilePOSIX("^") 155 commentChar := git.CommentChar() 156 commitLogs = strings.TrimSpace(commitLogs) 157 commitLogs = startRegexp.ReplaceAllString(commitLogs, fmt.Sprintf("%s ", commentChar)) 158 159 t, err := template.New("tmpl").Parse(tmpl) 160 if err != nil { 161 return "", err 162 } 163 164 msg := &struct { 165 InitMsg string 166 Tmpl string 167 CommentChar string 168 Base string 169 Head string 170 CommitLogs string 171 }{ 172 InitMsg: lastCommitMsg, 173 Tmpl: mrTmpl, 174 CommentChar: commentChar, 175 Base: forkedFromRemote + ":" + base, 176 Head: sourceRemote + ":" + head, 177 CommitLogs: commitLogs, 178 } 179 180 var b bytes.Buffer 181 err = t.Execute(&b, msg) 182 if err != nil { 183 return "", err 184 } 185 186 return b.String(), nil 187 }