From e4b9f7cb8c3935d22aea08e55aa8a3fcf2e9e472 Mon Sep 17 00:00:00 2001 From: zamasu Date: Thu, 22 Oct 2020 21:15:10 +0900 Subject: [PATCH 1/2] Alert unpushed commits when merging a pull request --- git/git.go | 23 ++ pkg/cmd/pr/merge/merge.go | 11 + pkg/cmd/pr/merge/merge_test.go | 401 +++++++++++++-------------------- 3 files changed, 185 insertions(+), 250 deletions(-) diff --git a/git/git.go b/git/git.go index 9a9c4b76d..5f942d0d8 100644 --- a/git/git.go +++ b/git/git.go @@ -180,6 +180,29 @@ func Commits(baseRef, headRef string) ([]*Commit, error) { return commits, nil } +func LastCommit() (*Commit, error) { + logCmd := GitCommand("-c", "log.ShowSignature=false", "log", "--pretty=format:%H,%s", "-1") + output, err := run.PrepareCmd(logCmd).Output() + if err != nil { + return nil, err + } + + lines := outputLines(output) + if len(lines) != 1 { + return nil, ErrNotOnAnyBranch + } + + split := strings.SplitN(lines[0], ",", 2) + if len(split) != 2 { + return nil, ErrNotOnAnyBranch + } + + return &Commit{ + Sha: split[0], + Title: split[1], + }, nil +} + func CommitBody(sha string) (string, error) { showCmd, err := GitCommand("-c", "log.ShowSignature=false", "show", "-s", "--pretty=format:%b", sha) if err != nil { diff --git a/pkg/cmd/pr/merge/merge.go b/pkg/cmd/pr/merge/merge.go index 6626c3bc1..35ee71b27 100644 --- a/pkg/cmd/pr/merge/merge.go +++ b/pkg/cmd/pr/merge/merge.go @@ -131,6 +131,17 @@ func mergeRun(opts *MergeOptions) error { return err } + localBranchName, err := git.CurrentBranch() + if err == nil { + localBranchLastCommit, err := git.LastCommit() + if err == nil { + if localBranchName == pr.HeadRefName && localBranchLastCommit.Sha != pr.Commits.Nodes[0].Commit.Oid { + fmt.Fprintf(opts.IO.ErrOut, + "%s Pull request #%d (%s) may have a last commit that is different from local one\n", utils.Yellow("!"), pr.Number, pr.Title) + } + } + } + if pr.Mergeable == "CONFLICTING" { fmt.Fprintf(opts.IO.ErrOut, "%s Pull request #%d (%s) has conflicts and isn't mergeable\n", cs.Red("!"), pr.Number, pr.Title) return cmdutil.SilentError diff --git a/pkg/cmd/pr/merge/merge_test.go b/pkg/cmd/pr/merge/merge_test.go index 0f97a945e..89eb05c2f 100644 --- a/pkg/cmd/pr/merge/merge_test.go +++ b/pkg/cmd/pr/merge/merge_test.go @@ -2,7 +2,6 @@ package merge import ( "bytes" - "errors" "io/ioutil" "net/http" "regexp" @@ -14,7 +13,6 @@ import ( "github.com/cli/cli/git" "github.com/cli/cli/internal/config" "github.com/cli/cli/internal/ghrepo" - "github.com/cli/cli/internal/run" "github.com/cli/cli/pkg/cmdutil" "github.com/cli/cli/pkg/httpmock" "github.com/cli/cli/pkg/iostreams" @@ -27,53 +25,24 @@ import ( func Test_NewCmdMerge(t *testing.T) { tests := []struct { - name string - args string - isTTY bool - want MergeOptions - wantBody string - wantErr string + name string + args string + isTTY bool + want MergeOptions + wantErr string }{ { name: "number argument", args: "123", isTTY: true, want: MergeOptions{ - SelectorArg: "123", - DeleteBranch: false, - IsDeleteBranchIndicated: false, - CanDeleteLocalBranch: true, - MergeMethod: api.PullRequestMergeMethodMerge, - InteractiveMode: true, + SelectorArg: "123", + DeleteBranch: true, + DeleteLocalBranch: true, + MergeMethod: api.PullRequestMergeMethodMerge, + InteractiveMode: true, }, }, - { - name: "delete-branch specified", - args: "--delete-branch=false", - isTTY: true, - want: MergeOptions{ - SelectorArg: "", - DeleteBranch: false, - IsDeleteBranchIndicated: true, - CanDeleteLocalBranch: true, - MergeMethod: api.PullRequestMergeMethodMerge, - InteractiveMode: true, - }, - }, - { - name: "body", - args: "123 -bcool", - isTTY: true, - want: MergeOptions{ - SelectorArg: "123", - DeleteBranch: false, - IsDeleteBranchIndicated: false, - CanDeleteLocalBranch: true, - MergeMethod: api.PullRequestMergeMethodMerge, - InteractiveMode: true, - }, - wantBody: "cool", - }, { name: "no argument with --repo override", args: "-R owner/repo", @@ -135,15 +104,9 @@ func Test_NewCmdMerge(t *testing.T) { assert.Equal(t, tt.want.SelectorArg, opts.SelectorArg) assert.Equal(t, tt.want.DeleteBranch, opts.DeleteBranch) - assert.Equal(t, tt.want.CanDeleteLocalBranch, opts.CanDeleteLocalBranch) + assert.Equal(t, tt.want.DeleteLocalBranch, opts.DeleteLocalBranch) assert.Equal(t, tt.want.MergeMethod, opts.MergeMethod) assert.Equal(t, tt.want.InteractiveMode, opts.InteractiveMode) - - if tt.wantBody == "" { - assert.Nil(t, opts.Body) - } else { - assert.Equal(t, tt.wantBody, *opts.Body) - } }) } } @@ -228,9 +191,20 @@ func TestPrMerge(t *testing.T) { assert.Equal(t, "MERGE", input["mergeMethod"].(string)) assert.NotContains(t, input, "commitHeadline") })) + http.Register( + httpmock.REST("DELETE", "repos/OWNER/REPO/git/refs/heads/blueberries"), + httpmock.StringResponse(`{}`)) - _, cmdTeardown := run.Stub() - defer cmdTeardown(t) + cs, cmdTeardown := test.InitCmdStubber() + defer cmdTeardown() + + prompt.StubConfirm(true) + + cs.Stub("branch.blueberries.remote origin\nbranch.blueberries.merge refs/heads/blueberries") // git config --get-regexp ^branch\.master\.(remote|merge) + cs.Stub("") // git config --get-regexp ^branch\.blueberries\.(remote|merge)$ + cs.Stub("") // git symbolic-ref --quiet --short HEAD + cs.Stub("") // git checkout master + cs.Stub("") output, err := runCommand(http, "master", true, "pr merge 1 --merge") if err != nil { @@ -265,9 +239,20 @@ func TestPrMerge_nontty(t *testing.T) { assert.Equal(t, "MERGE", input["mergeMethod"].(string)) assert.NotContains(t, input, "commitHeadline") })) + http.Register( + httpmock.REST("DELETE", "repos/OWNER/REPO/git/refs/heads/blueberries"), + httpmock.StringResponse(`{}`)) - _, cmdTeardown := run.Stub() - defer cmdTeardown(t) + cs, cmdTeardown := test.InitCmdStubber() + defer cmdTeardown() + + prompt.StubConfirm(true) + + cs.Stub("branch.blueberries.remote origin\nbranch.blueberries.merge refs/heads/blueberries") // git config --get-regexp ^branch\.master\.(remote|merge) + cs.Stub("") // git config --get-regexp ^branch\.blueberries\.(remote|merge)$ + cs.Stub("") // git symbolic-ref --quiet --short HEAD + cs.Stub("") // git checkout master + cs.Stub("") output, err := runCommand(http, "master", false, "pr merge 1 --merge") if err != nil { @@ -299,15 +284,25 @@ func TestPrMerge_withRepoFlag(t *testing.T) { assert.Equal(t, "MERGE", input["mergeMethod"].(string)) assert.NotContains(t, input, "commitHeadline") })) + http.Register( + httpmock.REST("DELETE", "repos/OWNER/REPO/git/refs/heads/blueberries"), + httpmock.StringResponse(`{}`)) - _, cmdTeardown := run.Stub() - defer cmdTeardown(t) + cs, cmdTeardown := test.InitCmdStubber() + defer cmdTeardown() + + prompt.StubConfirm(true) + + cs.Stub("") // git symbolic-ref --quiet HEAD + cs.Stub("") // git -c log.ShowSignature=false log --pretty=format:%H,%s -1 output, err := runCommand(http, "master", true, "pr merge 1 --merge -R OWNER/REPO") if err != nil { t.Fatalf("error running command `pr merge`: %v", err) } + assert.Equal(t, 2, len(cs.Calls)) + r := regexp.MustCompile(`Merged pull request #1 \(The title of the PR\)`) if !r.MatchString(output.Stderr()) { @@ -333,20 +328,24 @@ func TestPrMerge_deleteBranch(t *testing.T) { httpmock.REST("DELETE", "repos/OWNER/REPO/git/refs/heads/blueberries"), httpmock.StringResponse(`{}`)) - cs, cmdTeardown := run.Stub() - defer cmdTeardown(t) + cs, cmdTeardown := test.InitCmdStubber() + defer cmdTeardown() - cs.Register(`git config --get-regexp.+branch\\\.blueberries\\\.`, 0, "") - cs.Register(`git checkout master`, 0, "") - cs.Register(`git rev-parse --verify refs/heads/blueberries`, 0, "") - cs.Register(`git branch -D blueberries`, 0, "") + prompt.StubConfirm(true) + + cs.Stub("") // git symbolic-ref --quiet HEAD + cs.Stub("") // git -c log.ShowSignature=false log --pretty=format:%H,%s -1 + cs.Stub("") // git config --get-regexp ^branch\.blueberries\.(remote|merge)$ + cs.Stub("") // git checkout master + cs.Stub("") // git rev-parse --verify blueberries` + cs.Stub("") // git branch -d + cs.Stub("") // git push origin --delete blueberries output, err := runCommand(http, "blueberries", true, `pr merge --merge --delete-branch`) if err != nil { t.Fatalf("Got unexpected error running `pr merge` %s", err) } - //nolint:staticcheck // prefer exact matchers over ExpectLines test.ExpectLines(t, output.Stderr(), `Merged pull request #10 \(Blueberries are a good fruit\)`, `Deleted branch.*blueberries`) } @@ -368,18 +367,24 @@ func TestPrMerge_deleteNonCurrentBranch(t *testing.T) { httpmock.REST("DELETE", "repos/OWNER/REPO/git/refs/heads/blueberries"), httpmock.StringResponse(`{}`)) - cs, cmdTeardown := run.Stub() - defer cmdTeardown(t) + cs, cmdTeardown := test.InitCmdStubber() + defer cmdTeardown() - cs.Register(`git rev-parse --verify refs/heads/blueberries`, 0, "") - cs.Register(`git branch -D blueberries`, 0, "") + prompt.StubConfirm(true) + + cs.Stub("") // git symbolic-ref --quiet HEAD + cs.Stub("") // git -c log.ShowSignature=false log --pretty=format:%H,%s -1 + + // We don't expect the default branch to be checked out, just that blueberries is deleted + cs.Stub("") // git rev-parse --verify blueberries + cs.Stub("") // git branch -d blueberries + cs.Stub("") // git push origin --delete blueberries output, err := runCommand(http, "master", true, `pr merge --merge --delete-branch blueberries`) if err != nil { t.Fatalf("Got unexpected error running `pr merge` %s", err) } - //nolint:staticcheck // prefer exact matchers over ExpectLines test.ExpectLines(t, output.Stderr(), `Merged pull request #10 \(Blueberries are a good fruit\)`, `Deleted branch.*blueberries`) } @@ -397,11 +402,22 @@ func TestPrMerge_noPrNumberGiven(t *testing.T) { assert.Equal(t, "MERGE", input["mergeMethod"].(string)) assert.NotContains(t, input, "commitHeadline") })) + http.Register( + httpmock.REST("DELETE", "repos/OWNER/REPO/git/refs/heads/blueberries"), + httpmock.StringResponse(`{}`)) - cs, cmdTeardown := run.Stub() - defer cmdTeardown(t) + cs, cmdTeardown := test.InitCmdStubber() + defer cmdTeardown() - cs.Register(`git config --get-regexp.+branch\\\.blueberries\\\.`, 0, "") + prompt.StubConfirm(true) + + cs.Stub("") // git symbolic-ref --quiet HEAD + cs.Stub("") // git -c log.ShowSignature=false log --pretty=format:%H,%s -1 + cs.Stub("branch.blueberries.remote origin\nbranch.blueberries.merge refs/heads/blueberries") // git config --get-regexp ^branch\.master\.(remote|merge) + cs.Stub("") // git config --get-regexp ^branch\.blueberries\.(remote|merge)$ + cs.Stub("") // git symbolic-ref --quiet --short HEAD + cs.Stub("") // git checkout master + cs.Stub("") // git branch -d output, err := runCommand(http, "blueberries", true, "pr merge --merge") if err != nil { @@ -436,9 +452,21 @@ func TestPrMerge_rebase(t *testing.T) { assert.Equal(t, "REBASE", input["mergeMethod"].(string)) assert.NotContains(t, input, "commitHeadline") })) + http.Register( + httpmock.REST("DELETE", "repos/OWNER/REPO/git/refs/heads/blueberries"), + httpmock.StringResponse(`{}`)) - _, cmdTeardown := run.Stub() - defer cmdTeardown(t) + cs, cmdTeardown := test.InitCmdStubber() + defer cmdTeardown() + + prompt.StubConfirm(true) + + cs.Stub("") // git symbolic-ref --quiet HEAD + cs.Stub("") // git -c log.ShowSignature=false log --pretty=format:%H,%s -1 + cs.Stub("") // git config --get-regexp ^branch\.blueberries\.(remote|merge)$ + cs.Stub("") // git symbolic-ref --quiet --short HEAD + cs.Stub("") // git checkout master + cs.Stub("") // git branch -d output, err := runCommand(http, "master", true, "pr merge 2 --rebase") if err != nil { @@ -473,60 +501,29 @@ func TestPrMerge_squash(t *testing.T) { assert.Equal(t, "SQUASH", input["mergeMethod"].(string)) assert.Equal(t, "The title of the PR (#3)", input["commitHeadline"].(string)) })) + http.Register( + httpmock.REST("DELETE", "repos/OWNER/REPO/git/refs/heads/blueberries"), + httpmock.StringResponse(`{}`)) - _, cmdTeardown := run.Stub() - defer cmdTeardown(t) + cs, cmdTeardown := test.InitCmdStubber() + defer cmdTeardown() + + prompt.StubConfirm(true) + + cs.Stub("") // git config --get-regexp ^branch\.blueberries\.(remote|merge)$ + cs.Stub("") // git symbolic-ref --quiet --short HEAD + cs.Stub("") // git checkout master + cs.Stub("") // git branch -d output, err := runCommand(http, "master", true, "pr merge 3 --squash") if err != nil { t.Fatalf("error running command `pr merge`: %v", err) } - //nolint:staticcheck // prefer exact matchers over ExpectLines - test.ExpectLines(t, output.Stderr(), "Squashed and merged pull request #3") + test.ExpectLines(t, output.Stderr(), "Squashed and merged pull request #3", `Deleted branch.*blueberries`) } func TestPrMerge_alreadyMerged(t *testing.T) { - http := initFakeHTTP() - defer http.Verify(t) - http.Register( - httpmock.GraphQL(`query PullRequestByNumber\b`), - httpmock.StringResponse(` - { "data": { "repository": { - "pullRequest": { - "number": 4, - "title": "The title of the PR", - "state": "MERGED", - "baseRefName": "master", - "headRefName": "blueberries", - "headRepositoryOwner": { - "login": "OWNER" - }, - "isCrossRepository": false - } - } } }`)) - - cs, cmdTeardown := run.Stub() - defer cmdTeardown(t) - - cs.Register(`git checkout master`, 0, "") - cs.Register(`git rev-parse --verify refs/heads/blueberries`, 0, "") - cs.Register(`git branch -D blueberries`, 0, "") - - as, surveyTeardown := prompt.InitAskStubber() - defer surveyTeardown() - as.StubOne(true) - - output, err := runCommand(http, "blueberries", true, "pr merge 4") - if err != nil { - t.Fatalf("Got unexpected error running `pr merge` %s", err) - } - - //nolint:staticcheck // prefer exact matchers over ExpectLines - test.ExpectLines(t, output.Stderr(), "✓ Deleted branch blueberries and switched to branch master") -} - -func TestPrMerge_alreadyMerged_nonInteractive(t *testing.T) { http := initFakeHTTP() defer http.Verify(t) http.Register( @@ -536,16 +533,26 @@ func TestPrMerge_alreadyMerged_nonInteractive(t *testing.T) { "pullRequest": { "number": 4, "title": "The title of the PR", "state": "MERGED"} } } }`)) - _, cmdTeardown := run.Stub() - defer cmdTeardown(t) + cs, cmdTeardown := test.InitCmdStubber() + defer cmdTeardown() - output, err := runCommand(http, "blueberries", true, "pr merge 4 --merge") - if err != nil { - t.Fatalf("Got unexpected error running `pr merge` %s", err) + prompt.StubConfirm(true) + + cs.Stub("") // git config --get-regexp ^branch\.blueberries\.(remote|merge)$ + cs.Stub("") // git symbolic-ref --quiet --short HEAD + cs.Stub("") // git checkout master + cs.Stub("") // git branch -d + + output, err := runCommand(http, "master", true, "pr merge 4") + if err == nil { + t.Fatalf("expected an error running command `pr merge`: %v", err) } - assert.Equal(t, "", output.String()) - assert.Equal(t, "! Pull request #4 was already merged\n", output.Stderr()) + r := regexp.MustCompile(`Pull request #4 \(The title of the PR\) was already merged`) + + if !r.MatchString(err.Error()) { + t.Fatalf("output did not match regexp /%s/\n> output\n%q\n", r, output.Stderr()) + } } func TestPRMerge_interactive(t *testing.T) { @@ -560,14 +567,6 @@ func TestPRMerge_interactive(t *testing.T) { "id": "THE-ID", "number": 3 }] } } } }`)) - http.Register( - httpmock.GraphQL(`query RepositoryInfo\b`), - httpmock.StringResponse(` - { "data": { "repository": { - "mergeCommitAllowed": true, - "rebaseMergeAllowed": true, - "squashMergeAllowed": true - } } }`)) http.Register( httpmock.GraphQL(`mutation PullRequestMerge\b`), httpmock.GraphQLMutation(`{}`, func(input map[string]interface{}) { @@ -579,135 +578,37 @@ func TestPRMerge_interactive(t *testing.T) { httpmock.REST("DELETE", "repos/OWNER/REPO/git/refs/heads/blueberries"), httpmock.StringResponse(`{}`)) - cs, cmdTeardown := run.Stub() - defer cmdTeardown(t) + cs, cmdTeardown := test.InitCmdStubber() + defer cmdTeardown() - cs.Register(`git config --get-regexp.+branch\\\.blueberries\\\.`, 0, "") - cs.Register(`git checkout master`, 0, "") - cs.Register(`git rev-parse --verify refs/heads/blueberries`, 0, "") - cs.Register(`git branch -D blueberries`, 0, "") + cs.Stub("") // git symbolic-ref --quiet HEAD + cs.Stub("") // git -c log.ShowSignature=false log --pretty=format:%H,%s -1 + cs.Stub("") // git config --get-regexp ^branch\.blueberries\.(remote|merge)$ + cs.Stub("") // git symbolic-ref --quiet --short HEAD + cs.Stub("") // git checkout master + cs.Stub("") // git push origin --delete blueberries + cs.Stub("") // git branch -d as, surveyTeardown := prompt.InitAskStubber() defer surveyTeardown() - as.StubOne(0) // Merge method survey - as.StubOne(true) // Delete branch survey - as.StubOne(true) // Confirm submit survey + as.Stub([]*prompt.QuestionStub{ + { + Name: "mergeMethod", + Value: 0, + }, + { + Name: "deleteBranch", + Value: true, + }, + }) + + prompt.StubConfirm(true) output, err := runCommand(http, "blueberries", true, "") if err != nil { t.Fatalf("Got unexpected error running `pr merge` %s", err) } - //nolint:staticcheck // prefer exact matchers over ExpectLines - test.ExpectLines(t, output.Stderr(), "Merged pull request #3") -} - -func TestPRMerge_interactiveWithDeleteBranch(t *testing.T) { - http := initFakeHTTP() - defer http.Verify(t) - http.Register( - httpmock.GraphQL(`query PullRequestForBranch\b`), - httpmock.StringResponse(` - { "data": { "repository": { "pullRequests": { "nodes": [{ - "headRefName": "blueberries", - "headRepositoryOwner": {"login": "OWNER"}, - "id": "THE-ID", - "number": 3 - }] } } } }`)) - http.Register( - httpmock.GraphQL(`query RepositoryInfo\b`), - httpmock.StringResponse(` - { "data": { "repository": { - "mergeCommitAllowed": true, - "rebaseMergeAllowed": true, - "squashMergeAllowed": true - } } }`)) - http.Register( - httpmock.GraphQL(`mutation PullRequestMerge\b`), - httpmock.GraphQLMutation(`{}`, func(input map[string]interface{}) { - assert.Equal(t, "THE-ID", input["pullRequestId"].(string)) - assert.Equal(t, "MERGE", input["mergeMethod"].(string)) - assert.NotContains(t, input, "commitHeadline") - })) - http.Register( - httpmock.REST("DELETE", "repos/OWNER/REPO/git/refs/heads/blueberries"), - httpmock.StringResponse(`{}`)) - - cs, cmdTeardown := run.Stub() - defer cmdTeardown(t) - - cs.Register(`git config --get-regexp.+branch\\\.blueberries\\\.`, 0, "") - cs.Register(`git checkout master`, 0, "") - cs.Register(`git rev-parse --verify refs/heads/blueberries`, 0, "") - cs.Register(`git branch -D blueberries`, 0, "") - - as, surveyTeardown := prompt.InitAskStubber() - defer surveyTeardown() - - as.StubOne(0) // Merge method survey - as.StubOne(true) // Confirm submit survey - - output, err := runCommand(http, "blueberries", true, "-d") - if err != nil { - t.Fatalf("Got unexpected error running `pr merge` %s", err) - } - - //nolint:staticcheck // prefer exact matchers over ExpectLines - test.ExpectLines(t, output.Stderr(), "Merged pull request #3", "Deleted branch blueberries and switched to branch master") -} - -func TestPRMerge_interactiveCancelled(t *testing.T) { - http := initFakeHTTP() - defer http.Verify(t) - http.Register( - httpmock.GraphQL(`query PullRequestForBranch\b`), - httpmock.StringResponse(` - { "data": { "repository": { "pullRequests": { "nodes": [{ - "headRefName": "blueberries", - "headRepositoryOwner": {"login": "OWNER"}, - "id": "THE-ID", - "number": 3 - }] } } } }`)) - http.Register( - httpmock.GraphQL(`query RepositoryInfo\b`), - httpmock.StringResponse(` - { "data": { "repository": { - "mergeCommitAllowed": true, - "rebaseMergeAllowed": true, - "squashMergeAllowed": true - } } }`)) - - cs, cmdTeardown := run.Stub() - defer cmdTeardown(t) - - cs.Register(`git config --get-regexp.+branch\\\.blueberries\\\.`, 0, "") - - as, surveyTeardown := prompt.InitAskStubber() - defer surveyTeardown() - - as.StubOne(0) // Merge method survey - as.StubOne(true) // Delete branch survey - as.StubOne(false) // Confirm submit survey - - output, err := runCommand(http, "blueberries", true, "") - if !errors.Is(err, cmdutil.SilentError) { - t.Fatalf("got error %v", err) - } - - assert.Equal(t, "Cancelled.\n", output.Stderr()) -} - -func Test_mergeMethodSurvey(t *testing.T) { - repo := &api.Repository{ - MergeCommitAllowed: false, - RebaseMergeAllowed: true, - SquashMergeAllowed: true, - } - as, surveyTeardown := prompt.InitAskStubber() - defer surveyTeardown() - as.StubOne(0) // Select first option which is rebase merge - method, err := mergeMethodSurvey(repo) - assert.Nil(t, err) - assert.Equal(t, api.PullRequestMergeMethodRebase, method) + test.ExpectLines(t, output.Stderr(), "Merged pull request #3", `Deleted branch.*blueberries`) } From 3f172ad9912b67eb3ddab4959c9c20169fd387f6 Mon Sep 17 00:00:00 2001 From: Sam Coe Date: Mon, 25 Jan 2021 12:03:14 -0800 Subject: [PATCH 2/2] Add contional and tests --- git/git.go | 6 +- pkg/cmd/pr/merge/merge.go | 7 +- pkg/cmd/pr/merge/merge_test.go | 439 ++++++++++++------ .../prViewPreviewWithMetadataByBranch.json | 9 +- 4 files changed, 304 insertions(+), 157 deletions(-) diff --git a/git/git.go b/git/git.go index 5f942d0d8..684b00c10 100644 --- a/git/git.go +++ b/git/git.go @@ -181,7 +181,11 @@ func Commits(baseRef, headRef string) ([]*Commit, error) { } func LastCommit() (*Commit, error) { - logCmd := GitCommand("-c", "log.ShowSignature=false", "log", "--pretty=format:%H,%s", "-1") + logCmd, err := GitCommand("-c", "log.ShowSignature=false", "log", "--pretty=format:%H,%s", "-1") + if err != nil { + return nil, err + } + output, err := run.PrepareCmd(logCmd).Output() if err != nil { return nil, err diff --git a/pkg/cmd/pr/merge/merge.go b/pkg/cmd/pr/merge/merge.go index 35ee71b27..4c8c946c0 100644 --- a/pkg/cmd/pr/merge/merge.go +++ b/pkg/cmd/pr/merge/merge.go @@ -131,13 +131,12 @@ func mergeRun(opts *MergeOptions) error { return err } - localBranchName, err := git.CurrentBranch() - if err == nil { + if opts.SelectorArg == "" { localBranchLastCommit, err := git.LastCommit() if err == nil { - if localBranchName == pr.HeadRefName && localBranchLastCommit.Sha != pr.Commits.Nodes[0].Commit.Oid { + if localBranchLastCommit.Sha != pr.Commits.Nodes[0].Commit.Oid { fmt.Fprintf(opts.IO.ErrOut, - "%s Pull request #%d (%s) may have a last commit that is different from local one\n", utils.Yellow("!"), pr.Number, pr.Title) + "%s Pull request #%d (%s) has diverged from local branch\n", cs.Yellow("!"), pr.Number, pr.Title) } } } diff --git a/pkg/cmd/pr/merge/merge_test.go b/pkg/cmd/pr/merge/merge_test.go index 89eb05c2f..ce772f1e6 100644 --- a/pkg/cmd/pr/merge/merge_test.go +++ b/pkg/cmd/pr/merge/merge_test.go @@ -2,6 +2,7 @@ package merge import ( "bytes" + "errors" "io/ioutil" "net/http" "regexp" @@ -13,6 +14,7 @@ import ( "github.com/cli/cli/git" "github.com/cli/cli/internal/config" "github.com/cli/cli/internal/ghrepo" + "github.com/cli/cli/internal/run" "github.com/cli/cli/pkg/cmdutil" "github.com/cli/cli/pkg/httpmock" "github.com/cli/cli/pkg/iostreams" @@ -25,24 +27,53 @@ import ( func Test_NewCmdMerge(t *testing.T) { tests := []struct { - name string - args string - isTTY bool - want MergeOptions - wantErr string + name string + args string + isTTY bool + want MergeOptions + wantBody string + wantErr string }{ { name: "number argument", args: "123", isTTY: true, want: MergeOptions{ - SelectorArg: "123", - DeleteBranch: true, - DeleteLocalBranch: true, - MergeMethod: api.PullRequestMergeMethodMerge, - InteractiveMode: true, + SelectorArg: "123", + DeleteBranch: false, + IsDeleteBranchIndicated: false, + CanDeleteLocalBranch: true, + MergeMethod: api.PullRequestMergeMethodMerge, + InteractiveMode: true, }, }, + { + name: "delete-branch specified", + args: "--delete-branch=false", + isTTY: true, + want: MergeOptions{ + SelectorArg: "", + DeleteBranch: false, + IsDeleteBranchIndicated: true, + CanDeleteLocalBranch: true, + MergeMethod: api.PullRequestMergeMethodMerge, + InteractiveMode: true, + }, + }, + { + name: "body", + args: "123 -bcool", + isTTY: true, + want: MergeOptions{ + SelectorArg: "123", + DeleteBranch: false, + IsDeleteBranchIndicated: false, + CanDeleteLocalBranch: true, + MergeMethod: api.PullRequestMergeMethodMerge, + InteractiveMode: true, + }, + wantBody: "cool", + }, { name: "no argument with --repo override", args: "-R owner/repo", @@ -104,9 +135,15 @@ func Test_NewCmdMerge(t *testing.T) { assert.Equal(t, tt.want.SelectorArg, opts.SelectorArg) assert.Equal(t, tt.want.DeleteBranch, opts.DeleteBranch) - assert.Equal(t, tt.want.DeleteLocalBranch, opts.DeleteLocalBranch) + assert.Equal(t, tt.want.CanDeleteLocalBranch, opts.CanDeleteLocalBranch) assert.Equal(t, tt.want.MergeMethod, opts.MergeMethod) assert.Equal(t, tt.want.InteractiveMode, opts.InteractiveMode) + + if tt.wantBody == "" { + assert.Nil(t, opts.Body) + } else { + assert.Equal(t, tt.wantBody, *opts.Body) + } }) } } @@ -191,20 +228,9 @@ func TestPrMerge(t *testing.T) { assert.Equal(t, "MERGE", input["mergeMethod"].(string)) assert.NotContains(t, input, "commitHeadline") })) - http.Register( - httpmock.REST("DELETE", "repos/OWNER/REPO/git/refs/heads/blueberries"), - httpmock.StringResponse(`{}`)) - cs, cmdTeardown := test.InitCmdStubber() - defer cmdTeardown() - - prompt.StubConfirm(true) - - cs.Stub("branch.blueberries.remote origin\nbranch.blueberries.merge refs/heads/blueberries") // git config --get-regexp ^branch\.master\.(remote|merge) - cs.Stub("") // git config --get-regexp ^branch\.blueberries\.(remote|merge)$ - cs.Stub("") // git symbolic-ref --quiet --short HEAD - cs.Stub("") // git checkout master - cs.Stub("") + _, cmdTeardown := run.Stub() + defer cmdTeardown(t) output, err := runCommand(http, "master", true, "pr merge 1 --merge") if err != nil { @@ -239,20 +265,9 @@ func TestPrMerge_nontty(t *testing.T) { assert.Equal(t, "MERGE", input["mergeMethod"].(string)) assert.NotContains(t, input, "commitHeadline") })) - http.Register( - httpmock.REST("DELETE", "repos/OWNER/REPO/git/refs/heads/blueberries"), - httpmock.StringResponse(`{}`)) - cs, cmdTeardown := test.InitCmdStubber() - defer cmdTeardown() - - prompt.StubConfirm(true) - - cs.Stub("branch.blueberries.remote origin\nbranch.blueberries.merge refs/heads/blueberries") // git config --get-regexp ^branch\.master\.(remote|merge) - cs.Stub("") // git config --get-regexp ^branch\.blueberries\.(remote|merge)$ - cs.Stub("") // git symbolic-ref --quiet --short HEAD - cs.Stub("") // git checkout master - cs.Stub("") + _, cmdTeardown := run.Stub() + defer cmdTeardown(t) output, err := runCommand(http, "master", false, "pr merge 1 --merge") if err != nil { @@ -284,25 +299,15 @@ func TestPrMerge_withRepoFlag(t *testing.T) { assert.Equal(t, "MERGE", input["mergeMethod"].(string)) assert.NotContains(t, input, "commitHeadline") })) - http.Register( - httpmock.REST("DELETE", "repos/OWNER/REPO/git/refs/heads/blueberries"), - httpmock.StringResponse(`{}`)) - cs, cmdTeardown := test.InitCmdStubber() - defer cmdTeardown() - - prompt.StubConfirm(true) - - cs.Stub("") // git symbolic-ref --quiet HEAD - cs.Stub("") // git -c log.ShowSignature=false log --pretty=format:%H,%s -1 + _, cmdTeardown := run.Stub() + defer cmdTeardown(t) output, err := runCommand(http, "master", true, "pr merge 1 --merge -R OWNER/REPO") if err != nil { t.Fatalf("error running command `pr merge`: %v", err) } - assert.Equal(t, 2, len(cs.Calls)) - r := regexp.MustCompile(`Merged pull request #1 \(The title of the PR\)`) if !r.MatchString(output.Stderr()) { @@ -328,24 +333,21 @@ func TestPrMerge_deleteBranch(t *testing.T) { httpmock.REST("DELETE", "repos/OWNER/REPO/git/refs/heads/blueberries"), httpmock.StringResponse(`{}`)) - cs, cmdTeardown := test.InitCmdStubber() - defer cmdTeardown() + cs, cmdTeardown := run.Stub() + defer cmdTeardown(t) - prompt.StubConfirm(true) - - cs.Stub("") // git symbolic-ref --quiet HEAD - cs.Stub("") // git -c log.ShowSignature=false log --pretty=format:%H,%s -1 - cs.Stub("") // git config --get-regexp ^branch\.blueberries\.(remote|merge)$ - cs.Stub("") // git checkout master - cs.Stub("") // git rev-parse --verify blueberries` - cs.Stub("") // git branch -d - cs.Stub("") // git push origin --delete blueberries + cs.Register("git -c log.ShowSignature=false log --pretty=format:%H,%s -1", 0, "") + cs.Register(`git config --get-regexp.+branch\\\.blueberries\\\.`, 0, "") + cs.Register(`git checkout master`, 0, "") + cs.Register(`git rev-parse --verify refs/heads/blueberries`, 0, "") + cs.Register(`git branch -D blueberries`, 0, "") output, err := runCommand(http, "blueberries", true, `pr merge --merge --delete-branch`) if err != nil { t.Fatalf("Got unexpected error running `pr merge` %s", err) } + //nolint:staticcheck // prefer exact matchers over ExpectLines test.ExpectLines(t, output.Stderr(), `Merged pull request #10 \(Blueberries are a good fruit\)`, `Deleted branch.*blueberries`) } @@ -367,24 +369,18 @@ func TestPrMerge_deleteNonCurrentBranch(t *testing.T) { httpmock.REST("DELETE", "repos/OWNER/REPO/git/refs/heads/blueberries"), httpmock.StringResponse(`{}`)) - cs, cmdTeardown := test.InitCmdStubber() - defer cmdTeardown() + cs, cmdTeardown := run.Stub() + defer cmdTeardown(t) - prompt.StubConfirm(true) - - cs.Stub("") // git symbolic-ref --quiet HEAD - cs.Stub("") // git -c log.ShowSignature=false log --pretty=format:%H,%s -1 - - // We don't expect the default branch to be checked out, just that blueberries is deleted - cs.Stub("") // git rev-parse --verify blueberries - cs.Stub("") // git branch -d blueberries - cs.Stub("") // git push origin --delete blueberries + cs.Register(`git rev-parse --verify refs/heads/blueberries`, 0, "") + cs.Register(`git branch -D blueberries`, 0, "") output, err := runCommand(http, "master", true, `pr merge --merge --delete-branch blueberries`) if err != nil { t.Fatalf("Got unexpected error running `pr merge` %s", err) } + //nolint:staticcheck // prefer exact matchers over ExpectLines test.ExpectLines(t, output.Stderr(), `Merged pull request #10 \(Blueberries are a good fruit\)`, `Deleted branch.*blueberries`) } @@ -402,22 +398,12 @@ func TestPrMerge_noPrNumberGiven(t *testing.T) { assert.Equal(t, "MERGE", input["mergeMethod"].(string)) assert.NotContains(t, input, "commitHeadline") })) - http.Register( - httpmock.REST("DELETE", "repos/OWNER/REPO/git/refs/heads/blueberries"), - httpmock.StringResponse(`{}`)) - cs, cmdTeardown := test.InitCmdStubber() - defer cmdTeardown() + cs, cmdTeardown := run.Stub() + defer cmdTeardown(t) - prompt.StubConfirm(true) - - cs.Stub("") // git symbolic-ref --quiet HEAD - cs.Stub("") // git -c log.ShowSignature=false log --pretty=format:%H,%s -1 - cs.Stub("branch.blueberries.remote origin\nbranch.blueberries.merge refs/heads/blueberries") // git config --get-regexp ^branch\.master\.(remote|merge) - cs.Stub("") // git config --get-regexp ^branch\.blueberries\.(remote|merge)$ - cs.Stub("") // git symbolic-ref --quiet --short HEAD - cs.Stub("") // git checkout master - cs.Stub("") // git branch -d + cs.Register("git -c log.ShowSignature=false log --pretty=format:%H,%s -1", 0, "") + cs.Register(`git config --get-regexp.+branch\\\.blueberries\\\.`, 0, "") output, err := runCommand(http, "blueberries", true, "pr merge --merge") if err != nil { @@ -431,6 +417,39 @@ func TestPrMerge_noPrNumberGiven(t *testing.T) { } } +func Test_divergingPullRequestWarning(t *testing.T) { + http := initFakeHTTP() + defer http.Verify(t) + http.Register( + httpmock.GraphQL(`query PullRequestForBranch\b`), + // FIXME: references fixture from another package + httpmock.FileResponse("../view/fixtures/prViewPreviewWithMetadataByBranch.json")) + http.Register( + httpmock.GraphQL(`mutation PullRequestMerge\b`), + httpmock.GraphQLMutation(`{}`, func(input map[string]interface{}) { + assert.Equal(t, "PR_10", input["pullRequestId"].(string)) + assert.Equal(t, "MERGE", input["mergeMethod"].(string)) + assert.NotContains(t, input, "commitHeadline") + })) + + cs, cmdTeardown := run.Stub() + defer cmdTeardown(t) + + cs.Register("git -c log.ShowSignature=false log --pretty=format:%H,%s -1", 0, "deadbeef,title") + cs.Register(`git config --get-regexp.+branch\\\.blueberries\\\.`, 0, "") + + output, err := runCommand(http, "blueberries", true, "pr merge --merge") + if err != nil { + t.Fatalf("error running command `pr merge`: %v", err) + } + + r := regexp.MustCompile(`. Pull request #10 \(Blueberries are a good fruit\) has diverged from local branch\n. Merged pull request #10 \(Blueberries are a good fruit\)\n`) + + if !r.MatchString(output.Stderr()) { + t.Fatalf("output did not match regexp /%s/\n> output\n%q\n", r, output.Stderr()) + } +} + func TestPrMerge_rebase(t *testing.T) { http := initFakeHTTP() defer http.Verify(t) @@ -452,21 +471,9 @@ func TestPrMerge_rebase(t *testing.T) { assert.Equal(t, "REBASE", input["mergeMethod"].(string)) assert.NotContains(t, input, "commitHeadline") })) - http.Register( - httpmock.REST("DELETE", "repos/OWNER/REPO/git/refs/heads/blueberries"), - httpmock.StringResponse(`{}`)) - cs, cmdTeardown := test.InitCmdStubber() - defer cmdTeardown() - - prompt.StubConfirm(true) - - cs.Stub("") // git symbolic-ref --quiet HEAD - cs.Stub("") // git -c log.ShowSignature=false log --pretty=format:%H,%s -1 - cs.Stub("") // git config --get-regexp ^branch\.blueberries\.(remote|merge)$ - cs.Stub("") // git symbolic-ref --quiet --short HEAD - cs.Stub("") // git checkout master - cs.Stub("") // git branch -d + _, cmdTeardown := run.Stub() + defer cmdTeardown(t) output, err := runCommand(http, "master", true, "pr merge 2 --rebase") if err != nil { @@ -501,29 +508,60 @@ func TestPrMerge_squash(t *testing.T) { assert.Equal(t, "SQUASH", input["mergeMethod"].(string)) assert.Equal(t, "The title of the PR (#3)", input["commitHeadline"].(string)) })) - http.Register( - httpmock.REST("DELETE", "repos/OWNER/REPO/git/refs/heads/blueberries"), - httpmock.StringResponse(`{}`)) - cs, cmdTeardown := test.InitCmdStubber() - defer cmdTeardown() - - prompt.StubConfirm(true) - - cs.Stub("") // git config --get-regexp ^branch\.blueberries\.(remote|merge)$ - cs.Stub("") // git symbolic-ref --quiet --short HEAD - cs.Stub("") // git checkout master - cs.Stub("") // git branch -d + _, cmdTeardown := run.Stub() + defer cmdTeardown(t) output, err := runCommand(http, "master", true, "pr merge 3 --squash") if err != nil { t.Fatalf("error running command `pr merge`: %v", err) } - test.ExpectLines(t, output.Stderr(), "Squashed and merged pull request #3", `Deleted branch.*blueberries`) + //nolint:staticcheck // prefer exact matchers over ExpectLines + test.ExpectLines(t, output.Stderr(), "Squashed and merged pull request #3") } func TestPrMerge_alreadyMerged(t *testing.T) { + http := initFakeHTTP() + defer http.Verify(t) + http.Register( + httpmock.GraphQL(`query PullRequestByNumber\b`), + httpmock.StringResponse(` + { "data": { "repository": { + "pullRequest": { + "number": 4, + "title": "The title of the PR", + "state": "MERGED", + "baseRefName": "master", + "headRefName": "blueberries", + "headRepositoryOwner": { + "login": "OWNER" + }, + "isCrossRepository": false + } + } } }`)) + + cs, cmdTeardown := run.Stub() + defer cmdTeardown(t) + + cs.Register(`git checkout master`, 0, "") + cs.Register(`git rev-parse --verify refs/heads/blueberries`, 0, "") + cs.Register(`git branch -D blueberries`, 0, "") + + as, surveyTeardown := prompt.InitAskStubber() + defer surveyTeardown() + as.StubOne(true) + + output, err := runCommand(http, "blueberries", true, "pr merge 4") + if err != nil { + t.Fatalf("Got unexpected error running `pr merge` %s", err) + } + + //nolint:staticcheck // prefer exact matchers over ExpectLines + test.ExpectLines(t, output.Stderr(), "✓ Deleted branch blueberries and switched to branch master") +} + +func TestPrMerge_alreadyMerged_nonInteractive(t *testing.T) { http := initFakeHTTP() defer http.Verify(t) http.Register( @@ -533,26 +571,16 @@ func TestPrMerge_alreadyMerged(t *testing.T) { "pullRequest": { "number": 4, "title": "The title of the PR", "state": "MERGED"} } } }`)) - cs, cmdTeardown := test.InitCmdStubber() - defer cmdTeardown() + _, cmdTeardown := run.Stub() + defer cmdTeardown(t) - prompt.StubConfirm(true) - - cs.Stub("") // git config --get-regexp ^branch\.blueberries\.(remote|merge)$ - cs.Stub("") // git symbolic-ref --quiet --short HEAD - cs.Stub("") // git checkout master - cs.Stub("") // git branch -d - - output, err := runCommand(http, "master", true, "pr merge 4") - if err == nil { - t.Fatalf("expected an error running command `pr merge`: %v", err) + output, err := runCommand(http, "blueberries", true, "pr merge 4 --merge") + if err != nil { + t.Fatalf("Got unexpected error running `pr merge` %s", err) } - r := regexp.MustCompile(`Pull request #4 \(The title of the PR\) was already merged`) - - if !r.MatchString(err.Error()) { - t.Fatalf("output did not match regexp /%s/\n> output\n%q\n", r, output.Stderr()) - } + assert.Equal(t, "", output.String()) + assert.Equal(t, "! Pull request #4 was already merged\n", output.Stderr()) } func TestPRMerge_interactive(t *testing.T) { @@ -567,6 +595,14 @@ func TestPRMerge_interactive(t *testing.T) { "id": "THE-ID", "number": 3 }] } } } }`)) + http.Register( + httpmock.GraphQL(`query RepositoryInfo\b`), + httpmock.StringResponse(` + { "data": { "repository": { + "mergeCommitAllowed": true, + "rebaseMergeAllowed": true, + "squashMergeAllowed": true + } } }`)) http.Register( httpmock.GraphQL(`mutation PullRequestMerge\b`), httpmock.GraphQLMutation(`{}`, func(input map[string]interface{}) { @@ -578,37 +614,138 @@ func TestPRMerge_interactive(t *testing.T) { httpmock.REST("DELETE", "repos/OWNER/REPO/git/refs/heads/blueberries"), httpmock.StringResponse(`{}`)) - cs, cmdTeardown := test.InitCmdStubber() - defer cmdTeardown() + cs, cmdTeardown := run.Stub() + defer cmdTeardown(t) - cs.Stub("") // git symbolic-ref --quiet HEAD - cs.Stub("") // git -c log.ShowSignature=false log --pretty=format:%H,%s -1 - cs.Stub("") // git config --get-regexp ^branch\.blueberries\.(remote|merge)$ - cs.Stub("") // git symbolic-ref --quiet --short HEAD - cs.Stub("") // git checkout master - cs.Stub("") // git push origin --delete blueberries - cs.Stub("") // git branch -d + cs.Register("git -c log.ShowSignature=false log --pretty=format:%H,%s -1", 0, "") + cs.Register(`git config --get-regexp.+branch\\\.blueberries\\\.`, 0, "") + cs.Register(`git checkout master`, 0, "") + cs.Register(`git rev-parse --verify refs/heads/blueberries`, 0, "") + cs.Register(`git branch -D blueberries`, 0, "") as, surveyTeardown := prompt.InitAskStubber() defer surveyTeardown() - as.Stub([]*prompt.QuestionStub{ - { - Name: "mergeMethod", - Value: 0, - }, - { - Name: "deleteBranch", - Value: true, - }, - }) - - prompt.StubConfirm(true) + as.StubOne(0) // Merge method survey + as.StubOne(true) // Delete branch survey + as.StubOne(true) // Confirm submit survey output, err := runCommand(http, "blueberries", true, "") if err != nil { t.Fatalf("Got unexpected error running `pr merge` %s", err) } - test.ExpectLines(t, output.Stderr(), "Merged pull request #3", `Deleted branch.*blueberries`) + //nolint:staticcheck // prefer exact matchers over ExpectLines + test.ExpectLines(t, output.Stderr(), "Merged pull request #3") +} + +func TestPRMerge_interactiveWithDeleteBranch(t *testing.T) { + http := initFakeHTTP() + defer http.Verify(t) + http.Register( + httpmock.GraphQL(`query PullRequestForBranch\b`), + httpmock.StringResponse(` + { "data": { "repository": { "pullRequests": { "nodes": [{ + "headRefName": "blueberries", + "headRepositoryOwner": {"login": "OWNER"}, + "id": "THE-ID", + "number": 3 + }] } } } }`)) + http.Register( + httpmock.GraphQL(`query RepositoryInfo\b`), + httpmock.StringResponse(` + { "data": { "repository": { + "mergeCommitAllowed": true, + "rebaseMergeAllowed": true, + "squashMergeAllowed": true + } } }`)) + http.Register( + httpmock.GraphQL(`mutation PullRequestMerge\b`), + httpmock.GraphQLMutation(`{}`, func(input map[string]interface{}) { + assert.Equal(t, "THE-ID", input["pullRequestId"].(string)) + assert.Equal(t, "MERGE", input["mergeMethod"].(string)) + assert.NotContains(t, input, "commitHeadline") + })) + http.Register( + httpmock.REST("DELETE", "repos/OWNER/REPO/git/refs/heads/blueberries"), + httpmock.StringResponse(`{}`)) + + cs, cmdTeardown := run.Stub() + defer cmdTeardown(t) + + cs.Register("git -c log.ShowSignature=false log --pretty=format:%H,%s -1", 0, "") + cs.Register(`git config --get-regexp.+branch\\\.blueberries\\\.`, 0, "") + cs.Register(`git checkout master`, 0, "") + cs.Register(`git rev-parse --verify refs/heads/blueberries`, 0, "") + cs.Register(`git branch -D blueberries`, 0, "") + + as, surveyTeardown := prompt.InitAskStubber() + defer surveyTeardown() + + as.StubOne(0) // Merge method survey + as.StubOne(true) // Confirm submit survey + + output, err := runCommand(http, "blueberries", true, "-d") + if err != nil { + t.Fatalf("Got unexpected error running `pr merge` %s", err) + } + + //nolint:staticcheck // prefer exact matchers over ExpectLines + test.ExpectLines(t, output.Stderr(), "Merged pull request #3", "Deleted branch blueberries and switched to branch master") +} + +func TestPRMerge_interactiveCancelled(t *testing.T) { + http := initFakeHTTP() + defer http.Verify(t) + http.Register( + httpmock.GraphQL(`query PullRequestForBranch\b`), + httpmock.StringResponse(` + { "data": { "repository": { "pullRequests": { "nodes": [{ + "headRefName": "blueberries", + "headRepositoryOwner": {"login": "OWNER"}, + "id": "THE-ID", + "number": 3 + }] } } } }`)) + http.Register( + httpmock.GraphQL(`query RepositoryInfo\b`), + httpmock.StringResponse(` + { "data": { "repository": { + "mergeCommitAllowed": true, + "rebaseMergeAllowed": true, + "squashMergeAllowed": true + } } }`)) + + cs, cmdTeardown := run.Stub() + defer cmdTeardown(t) + + cs.Register("git -c log.ShowSignature=false log --pretty=format:%H,%s -1", 0, "") + cs.Register(`git config --get-regexp.+branch\\\.blueberries\\\.`, 0, "") + + as, surveyTeardown := prompt.InitAskStubber() + defer surveyTeardown() + + as.StubOne(0) // Merge method survey + as.StubOne(true) // Delete branch survey + as.StubOne(false) // Confirm submit survey + + output, err := runCommand(http, "blueberries", true, "") + if !errors.Is(err, cmdutil.SilentError) { + t.Fatalf("got error %v", err) + } + + assert.Equal(t, "Cancelled.\n", output.Stderr()) +} + +func Test_mergeMethodSurvey(t *testing.T) { + repo := &api.Repository{ + MergeCommitAllowed: false, + RebaseMergeAllowed: true, + SquashMergeAllowed: true, + } + as, surveyTeardown := prompt.InitAskStubber() + defer surveyTeardown() + as.StubOne(0) // Select first option which is rebase merge + method, err := mergeMethodSurvey(repo) + assert.Nil(t, err) + assert.Equal(t, api.PullRequestMergeMethodRebase, method) } diff --git a/pkg/cmd/pr/view/fixtures/prViewPreviewWithMetadataByBranch.json b/pkg/cmd/pr/view/fixtures/prViewPreviewWithMetadataByBranch.json index 73a64ca1c..15842a7b9 100644 --- a/pkg/cmd/pr/view/fixtures/prViewPreviewWithMetadataByBranch.json +++ b/pkg/cmd/pr/view/fixtures/prViewPreviewWithMetadataByBranch.json @@ -116,6 +116,13 @@ "login": "OWNER" }, "commits": { + "nodes": [ + { + "commit": { + "oid": "123456789" + } + } + ], "totalCount": 8 }, "isCrossRepository": false, @@ -125,4 +132,4 @@ } } } -} \ No newline at end of file +}