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  }