Skip to content
This repository has been archived by the owner on Nov 22, 2022. It is now read-only.

feat: mr merge options #695

Merged
merged 1 commit into from
Apr 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions commands/cmdutils/cmdutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func GetEditor(cf func() (config.Config, error)) (string, error) {
return editorCommand, nil
}

func DescriptionPrompt(response *string, templateContent, editorCommand string) error {
func EditorPrompt(response *string, question, templateContent, editorCommand string) error {
defaultBody := *response
if templateContent != "" {
if defaultBody != "" {
Expand All @@ -112,7 +112,7 @@ func DescriptionPrompt(response *string, templateContent, editorCommand string)

qs := []*survey.Question{
{
Name: "Description",
Name: question,
Prompt: &surveyext.GLabEditor{
BlankAllowed: true,
EditorCommand: editorCommand,
Expand Down Expand Up @@ -265,6 +265,7 @@ const (
PreviewAction
AddMetadataAction
CancelAction
EditCommitMessageAction
)

func ConfirmSubmission(allowPreview bool, allowAddMetadata bool) (Action, error) {
Expand Down
2 changes: 1 addition & 1 deletion commands/issue/create/issue_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ func createRun(opts *CreateOpts) error {
if err != nil {
return err
}
err = cmdutils.DescriptionPrompt(&opts.Description, templateContents, editor)
err = cmdutils.EditorPrompt(&opts.Description, "Description", templateContents, editor)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion commands/mr/create/mr_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ func createRun(opts *CreateOpts) error {
if err != nil {
return err
}
err = cmdutils.DescriptionPrompt(&opts.Description, templateContents, editor)
err = cmdutils.EditorPrompt(&opts.Description, "Description", templateContents, editor)
if err != nil {
return err
}
Expand Down
178 changes: 167 additions & 11 deletions commands/mr/merge/mr_merge.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package merge

import (
"errors"
"fmt"

"github.com/profclems/glab/pkg/surveyext"

"github.com/AlecAivazis/survey/v2"
"github.com/MakeNowJust/heredoc"
"github.com/profclems/glab/api"
"github.com/profclems/glab/commands/mr/mrutils"
Expand All @@ -13,18 +17,31 @@ import (
"github.com/xanzy/go-gitlab"
)

type MRMergeMethod int

const (
MRMergeMethodMerge MRMergeMethod = iota
MRMergeMethodSquash
MRMergeMethodRebase
)

type MergeOpts struct {
MergeWhenPipelineSucceeds bool
SquashBeforeMerge bool
RebaseBeforeMerge bool
RemoveSourceBranch bool

SquashMessage string
MergeCommitMessage string
SHA string

MergeMethod MRMergeMethod
}

func NewCmdMerge(f *cmdutils.Factory) *cobra.Command {
var opts = &MergeOpts{}
var opts = &MergeOpts{
MergeMethod: MRMergeMethodMerge,
}

var mrMergeCmd = &cobra.Command{
Use: "merge {<id> | <branch>}",
Expand All @@ -41,6 +58,14 @@ func NewCmdMerge(f *cmdutils.Factory) *cobra.Command {
var err error
c := f.IO.Color()

if opts.SquashBeforeMerge && opts.RebaseBeforeMerge {
return &cmdutils.FlagError{Err: errors.New("only one of --rebase, or --squash can be enabled")}
}

if !opts.SquashBeforeMerge && opts.SquashMessage != "" {
return &cmdutils.FlagError{Err: errors.New("--squash-message can only be used with --squash")}
}

apiClient, err := f.HttpClient()
if err != nil {
return err
Expand All @@ -66,6 +91,55 @@ func NewCmdMerge(f *cmdutils.Factory) *cobra.Command {
_ = prompt.Confirm(&opts.MergeWhenPipelineSucceeds, "Merge when pipeline succeeds?", true)
}

if f.IO.IsOutputTTY() {
if !opts.SquashBeforeMerge && !opts.RebaseBeforeMerge && opts.MergeCommitMessage == "" {
opts.MergeMethod, err = mergeMethodSurvey()
if err != nil {
return err
}
if opts.MergeMethod == MRMergeMethodSquash {
opts.SquashBeforeMerge = true
} else if opts.MergeMethod == MRMergeMethodRebase {
opts.RebaseBeforeMerge = true
}
}

if opts.MergeCommitMessage == "" && opts.SquashMessage == "" {
action, err := confirmSurvey(opts.MergeMethod != MRMergeMethodRebase)
if err != nil {
return fmt.Errorf("unable to prompt: %w", err)
}

if action == cmdutils.EditCommitMessageAction {
var mergeMessage string

editor, err := cmdutils.GetEditor(f.Config)
if err != nil {
return err
}
mergeMessage, err = surveyext.Edit(editor, "*.md", mr.Title, f.IO.In, f.IO.StdOut, f.IO.StdErr, nil)
if err != nil {
return err
}

if opts.SquashBeforeMerge {
opts.SquashMessage = mergeMessage
} else {
opts.MergeCommitMessage = mergeMessage
}

action, err = confirmSurvey(false)
if err != nil {
return fmt.Errorf("unable to confirm: %w", err)
}
}
if action == cmdutils.CancelAction {
fmt.Fprintln(f.IO.StdErr, "Cancelled.")
return cmdutils.SilentError
}
}
}

mergeOpts := &gitlab.AcceptMergeRequestOptions{}
if opts.MergeCommitMessage != "" {
mergeOpts.MergeCommitMessage = gitlab.String(opts.MergeCommitMessage)
Expand All @@ -80,37 +154,58 @@ func NewCmdMerge(f *cmdutils.Factory) *cobra.Command {
mergeOpts.ShouldRemoveSourceBranch = gitlab.Bool(true)
}
if opts.MergeWhenPipelineSucceeds && mr.Pipeline != nil {
if mr.Pipeline.Status == "canceled" || mr.Pipeline.Status == "failed" {
fmt.Fprintln(f.IO.StdOut, c.FailedIcon(), "Pipeline Status:", mr.Pipeline.Status)
fmt.Fprintln(f.IO.StdOut, c.FailedIcon(), "Cannot perform merge action")
return cmdutils.SilentError
}
mergeOpts.MergeWhenPipelineSucceeds = gitlab.Bool(true)
}
if opts.SHA != "" {
mergeOpts.SHA = gitlab.String(opts.SHA)
}

fmt.Fprintf(f.IO.StdOut, "- Merging merge request !%d\n", mr.IID)
if opts.RebaseBeforeMerge {
err := mrutils.RebaseMR(f.IO, apiClient, repo, mr)
if err != nil {
return err
}
}

f.IO.StartSpinner("Merging merge request !%d", mr.IID)
mr, err = api.MergeMR(apiClient, repo.FullName(), mr.IID, mergeOpts)

if err != nil {
return err
}

f.IO.StopSpinner("")
isMerged := true
if opts.MergeWhenPipelineSucceeds {
if mr.Pipeline == nil {
fmt.Fprintln(f.IO.StdOut, c.WarnIcon(), "No pipeline running on", mr.SourceBranch)
} else if mr.Pipeline.Status == "success" {
fmt.Fprintln(f.IO.StdOut, c.GreenCheck(), "Pipeline Succeeded")
} else {
fmt.Fprintln(f.IO.StdOut, c.WarnIcon(), "Pipeline Status:", mr.Pipeline.Status)
fmt.Fprintln(f.IO.StdOut, c.GreenCheck(), "Will merge when pipeline succeeds")
isMerged = false
switch mr.Pipeline.Status {
case "success":
fmt.Fprintln(f.IO.StdOut, c.GreenCheck(), "Pipeline Succeeded")
default:
fmt.Fprintln(f.IO.StdOut, c.WarnIcon(), "Pipeline Status:", mr.Pipeline.Status)
if mr.State != "merged" {
fmt.Fprintln(f.IO.StdOut, c.GreenCheck(), "Will merge when pipeline succeeds")
isMerged = false
}
}
}
}
if isMerged {
fmt.Fprintln(f.IO.StdOut, c.GreenCheck(), "Merged")
action := "Merged"
switch opts.MergeMethod {
case MRMergeMethodRebase:
action = "Rebased and merged"
case MRMergeMethodSquash:
action = "Squashed and merged"
}
fmt.Fprintln(f.IO.StdOut, c.GreenCheck(), action)
}
fmt.Fprintln(f.IO.StdOut, mrutils.DisplayMR(c, mr))

return nil
},
}
Expand All @@ -121,6 +216,67 @@ func NewCmdMerge(f *cmdutils.Factory) *cobra.Command {
mrMergeCmd.Flags().StringVarP(&opts.MergeCommitMessage, "message", "m", "", "Custom merge commit message")
mrMergeCmd.Flags().StringVarP(&opts.SquashMessage, "squash-message", "", "", "Custom Squash commit message")
mrMergeCmd.Flags().BoolVarP(&opts.SquashBeforeMerge, "squash", "s", false, "Squash commits on merge")
mrMergeCmd.Flags().BoolVarP(&opts.RebaseBeforeMerge, "rebase", "r", false, "Rebase the commits onto the base branch\n")

return mrMergeCmd
}

func mergeMethodSurvey() (MRMergeMethod, error) {
type mergeOption struct {
title string
method MRMergeMethod
}

var mergeOpts = []mergeOption{
{title: "Create a merge commit", method: MRMergeMethodMerge},
{title: "Rebase and merge", method: MRMergeMethodRebase},
{title: "Squash and merge", method: MRMergeMethodSquash},
}

var surveyOpts []string
for _, v := range mergeOpts {
surveyOpts = append(surveyOpts, v.title)
}

mergeQuestion := &survey.Select{
Message: "What merge method would you like to use?",
Options: surveyOpts,
}

var result int
err := prompt.AskOne(mergeQuestion, &result)
return mergeOpts[result].method, err
}

func confirmSurvey(allowEditMsg bool) (cmdutils.Action, error) {
const (
submitLabel = "Submit"
editCommitMsgLabel = "Edit commit message"
cancelLabel = "Cancel"
)

options := []string{submitLabel}
if allowEditMsg {
options = append(options, editCommitMsgLabel)
}
options = append(options, cancelLabel)

var result string
submit := &survey.Select{
Message: "What's next?",
Options: options,
}
err := prompt.AskOne(submit, &result)
if err != nil {
return cmdutils.CancelAction, fmt.Errorf("could not prompt: %w", err)
}

switch result {
case submitLabel:
return cmdutils.SubmitAction, nil
case editCommitMsgLabel:
return cmdutils.EditCommitMessageAction, nil
default:
return cmdutils.CancelAction, nil
}
}
40 changes: 40 additions & 0 deletions commands/mr/mrutils/mrutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package mrutils

import (
"context"
"errors"
"fmt"
"strconv"
"strings"
Expand Down Expand Up @@ -279,3 +280,42 @@ var getMRForBranch = func(apiClient *gitlab.Client, baseRepo glrepo.Interface, a
}
return mrMap[pickedMR], nil
}

func RebaseMR(ios *iostreams.IOStreams, apiClient *gitlab.Client, repo glrepo.Interface, mr *gitlab.MergeRequest) error {
ios.StartSpinner("Sending rebase request...")
err := api.RebaseMR(apiClient, repo.FullName(), mr.IID)
if err != nil {
return err
}
ios.StopSpinner("")

opts := &gitlab.GetMergeRequestsOptions{}
opts.IncludeRebaseInProgress = gitlab.Bool(true)
ios.StartSpinner("Checking rebase status...")
errorMSG := ""
i := 0
for {
mr, err := api.GetMR(apiClient, repo.FullName(), mr.IID, opts)
if err != nil {
errorMSG = err.Error()
break
}
if i == 0 {
ios.StopSpinner("")
ios.StartSpinner("Rebase in progress...")
}
if !mr.RebaseInProgress {
if mr.MergeError != "" && mr.MergeError != "null" {
errorMSG = mr.MergeError
}
break
}
i++
}
ios.StopSpinner("")
if errorMSG != "" {
return errors.New(errorMSG)
}
fmt.Fprintln(ios.StdOut, ios.Color().GreenCheck(), "Rebase successful")
return nil
}
Loading