From 4f7c88ea4eaf27603be3a5d58889bbb45decead5 Mon Sep 17 00:00:00 2001 From: Kiran Adhikari Date: Wed, 13 Apr 2022 16:13:05 +0545 Subject: [PATCH] pr merge: print instructions for merge conflicts (#5330) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sam Coe Co-authored-by: Mislav Marohnić --- pkg/cmd/pr/merge/merge.go | 34 ++++++++++++++++++++++++++++++++-- pkg/cmd/pr/merge/merge_test.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/pkg/cmd/pr/merge/merge.go b/pkg/cmd/pr/merge/merge.go index f6d5fb37b..7b5770f8b 100644 --- a/pkg/cmd/pr/merge/merge.go +++ b/pkg/cmd/pr/merge/merge.go @@ -11,6 +11,7 @@ import ( "github.com/cli/cli/v2/context" "github.com/cli/cli/v2/git" "github.com/cli/cli/v2/internal/config" + "github.com/cli/cli/v2/internal/ghrepo" "github.com/cli/cli/v2/pkg/cmd/pr/shared" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/iostreams" @@ -72,7 +73,7 @@ func NewCmdMerge(f *cmdutil.Factory, runF func(*MergeOptions) error) *cobra.Comm Merge a pull request on GitHub. Without an argument, the pull request that belongs to the current branch - is selected. + is selected. `), Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { @@ -130,6 +131,7 @@ func NewCmdMerge(f *cmdutil.Factory, runF func(*MergeOptions) error) *cobra.Comm ); err != nil { return err } + if bodyProvided || bodyFileProvided { opts.BodySet = true if bodyFileProvided { @@ -139,7 +141,6 @@ func NewCmdMerge(f *cmdutil.Factory, runF func(*MergeOptions) error) *cobra.Comm } opts.Body = string(b) } - } opts.Editor = &userEditor{ @@ -211,6 +212,16 @@ func mergeRun(opts *MergeOptions) error { if reason := blockedReason(pr.MergeStateStatus, opts.UseAdmin); !opts.AutoMergeEnable && !isPRAlreadyMerged && reason != "" { fmt.Fprintf(opts.IO.ErrOut, "%s Pull request #%d is not mergeable: %s.\n", cs.FailureIcon(), pr.Number, reason) fmt.Fprintf(opts.IO.ErrOut, "To have the pull request merged after all the requirements have been met, add the `--auto` flag.\n") + if remote := remoteForMergeConflictResolution(baseRepo, pr, opts); remote != nil { + mergeOrRebase := "merge" + if opts.MergeMethod == PullRequestMergeMethodRebase { + mergeOrRebase = "rebase" + } + fetchBranch := fmt.Sprintf("%s %s", remote.Name, pr.BaseRefName) + mergeBranch := fmt.Sprintf("%s %s/%s", mergeOrRebase, remote.Name, pr.BaseRefName) + cmd := fmt.Sprintf("gh pr checkout %d && git fetch %s && git %s", pr.Number, fetchBranch, mergeBranch) + fmt.Fprintf(opts.IO.ErrOut, "Run the following to resolve the merge conflicts locally:\n %s\n", cs.Bold(cmd)) + } if !opts.UseAdmin && allowsAdminOverride(pr.MergeStateStatus) { // TODO: show this flag only to repo admins fmt.Fprintf(opts.IO.ErrOut, "To use administrator privileges to immediately merge the pull request, add the `--admin` flag.\n") @@ -562,6 +573,25 @@ func allowsAdminOverride(status string) bool { } } +func remoteForMergeConflictResolution(baseRepo ghrepo.Interface, pr *api.PullRequest, opts *MergeOptions) *context.Remote { + if !mergeConflictStatus(pr.MergeStateStatus) || !opts.CanDeleteLocalBranch { + return nil + } + remotes, err := opts.Remotes() + if err != nil { + return nil + } + remote, err := remotes.FindByRepo(baseRepo.RepoOwner(), baseRepo.RepoName()) + if err != nil { + return nil + } + return remote +} + +func mergeConflictStatus(status string) bool { + return status == "DIRTY" +} + func isImmediatelyMergeable(status string) bool { switch status { case "CLEAN", "HAS_HOOKS", "UNSTABLE": diff --git a/pkg/cmd/pr/merge/merge_test.go b/pkg/cmd/pr/merge/merge_test.go index c7f30f7c4..742cfd184 100644 --- a/pkg/cmd/pr/merge/merge_test.go +++ b/pkg/cmd/pr/merge/merge_test.go @@ -333,6 +333,39 @@ func TestPrMerge_blocked(t *testing.T) { `, "`"), output.Stderr()) } +func TestPrMerge_dirty(t *testing.T) { + http := initFakeHTTP() + defer http.Verify(t) + + shared.RunCommandFinder( + "1", + &api.PullRequest{ + ID: "THE-ID", + Number: 123, + State: "OPEN", + Title: "The title of the PR", + MergeStateStatus: "DIRTY", + BaseRefName: "trunk", + HeadRefName: "feature", + }, + baseRepo("OWNER", "REPO", "master"), + ) + + _, cmdTeardown := run.Stub() + defer cmdTeardown(t) + + output, err := runCommand(http, "master", true, "pr merge 1 --merge") + assert.EqualError(t, err, "SilentError") + + assert.Equal(t, "", output.String()) + assert.Equal(t, heredoc.Docf(` + X Pull request #123 is not mergeable: the merge commit cannot be cleanly created. + To have the pull request merged after all the requirements have been met, add the %[1]s--auto%[1]s flag. + Run the following to resolve the merge conflicts locally: + gh pr checkout 123 && git fetch origin trunk && git merge origin/trunk + `, "`"), output.Stderr()) +} + func TestPrMerge_nontty(t *testing.T) { http := initFakeHTTP() defer http.Verify(t)