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  }