When user chooses "Edit commit message", open the editor immediately instead of showing an additional prompt to open the editor.
900 lines
27 KiB
Go
900 lines
27 KiB
Go
package merge
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/cli/cli/api"
|
|
"github.com/cli/cli/context"
|
|
"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"
|
|
"github.com/cli/cli/pkg/prompt"
|
|
"github.com/cli/cli/test"
|
|
"github.com/google/shlex"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func Test_NewCmdMerge(t *testing.T) {
|
|
tests := []struct {
|
|
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: PullRequestMergeMethodMerge,
|
|
InteractiveMode: true,
|
|
Body: "",
|
|
BodySet: false,
|
|
},
|
|
},
|
|
{
|
|
name: "delete-branch specified",
|
|
args: "--delete-branch=false",
|
|
isTTY: true,
|
|
want: MergeOptions{
|
|
SelectorArg: "",
|
|
DeleteBranch: false,
|
|
IsDeleteBranchIndicated: true,
|
|
CanDeleteLocalBranch: true,
|
|
MergeMethod: PullRequestMergeMethodMerge,
|
|
InteractiveMode: true,
|
|
Body: "",
|
|
BodySet: false,
|
|
},
|
|
},
|
|
{
|
|
name: "body",
|
|
args: "123 -bcool",
|
|
isTTY: true,
|
|
want: MergeOptions{
|
|
SelectorArg: "123",
|
|
DeleteBranch: false,
|
|
IsDeleteBranchIndicated: false,
|
|
CanDeleteLocalBranch: true,
|
|
MergeMethod: PullRequestMergeMethodMerge,
|
|
InteractiveMode: true,
|
|
Body: "cool",
|
|
BodySet: true,
|
|
},
|
|
},
|
|
{
|
|
name: "no argument with --repo override",
|
|
args: "-R owner/repo",
|
|
isTTY: true,
|
|
wantErr: "argument required when using the --repo flag",
|
|
},
|
|
{
|
|
name: "insufficient flags in non-interactive mode",
|
|
args: "123",
|
|
isTTY: false,
|
|
wantErr: "--merge, --rebase, or --squash required when not running interactively",
|
|
},
|
|
{
|
|
name: "multiple merge methods",
|
|
args: "123 --merge --rebase",
|
|
isTTY: true,
|
|
wantErr: "only one of --merge, --rebase, or --squash can be enabled",
|
|
},
|
|
{
|
|
name: "multiple merge methods, non-tty",
|
|
args: "123 --merge --rebase",
|
|
isTTY: false,
|
|
wantErr: "only one of --merge, --rebase, or --squash can be enabled",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
io, _, _, _ := iostreams.Test()
|
|
io.SetStdoutTTY(tt.isTTY)
|
|
io.SetStdinTTY(tt.isTTY)
|
|
io.SetStderrTTY(tt.isTTY)
|
|
|
|
f := &cmdutil.Factory{
|
|
IOStreams: io,
|
|
}
|
|
|
|
var opts *MergeOptions
|
|
cmd := NewCmdMerge(f, func(o *MergeOptions) error {
|
|
opts = o
|
|
return nil
|
|
})
|
|
cmd.PersistentFlags().StringP("repo", "R", "", "")
|
|
|
|
argv, err := shlex.Split(tt.args)
|
|
require.NoError(t, err)
|
|
cmd.SetArgs(argv)
|
|
|
|
cmd.SetIn(&bytes.Buffer{})
|
|
cmd.SetOut(ioutil.Discard)
|
|
cmd.SetErr(ioutil.Discard)
|
|
|
|
_, err = cmd.ExecuteC()
|
|
if tt.wantErr != "" {
|
|
require.EqualError(t, err, tt.wantErr)
|
|
return
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
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.MergeMethod, opts.MergeMethod)
|
|
assert.Equal(t, tt.want.InteractiveMode, opts.InteractiveMode)
|
|
assert.Equal(t, tt.want.Body, opts.Body)
|
|
assert.Equal(t, tt.want.BodySet, opts.BodySet)
|
|
})
|
|
}
|
|
}
|
|
|
|
func runCommand(rt http.RoundTripper, branch string, isTTY bool, cli string) (*test.CmdOut, error) {
|
|
io, _, stdout, stderr := iostreams.Test()
|
|
io.SetStdoutTTY(isTTY)
|
|
io.SetStdinTTY(isTTY)
|
|
io.SetStderrTTY(isTTY)
|
|
|
|
factory := &cmdutil.Factory{
|
|
IOStreams: io,
|
|
HttpClient: func() (*http.Client, error) {
|
|
return &http.Client{Transport: rt}, nil
|
|
},
|
|
Config: func() (config.Config, error) {
|
|
return config.NewBlankConfig(), nil
|
|
},
|
|
BaseRepo: func() (ghrepo.Interface, error) {
|
|
return api.InitRepoHostname(&api.Repository{
|
|
Name: "REPO",
|
|
Owner: api.RepositoryOwner{Login: "OWNER"},
|
|
DefaultBranchRef: api.BranchRef{Name: "master"},
|
|
}, "github.com"), nil
|
|
},
|
|
Remotes: func() (context.Remotes, error) {
|
|
return context.Remotes{
|
|
{
|
|
Remote: &git.Remote{Name: "origin"},
|
|
Repo: ghrepo.New("OWNER", "REPO"),
|
|
},
|
|
}, nil
|
|
},
|
|
Branch: func() (string, error) {
|
|
return branch, nil
|
|
},
|
|
}
|
|
|
|
cmd := NewCmdMerge(factory, nil)
|
|
cmd.PersistentFlags().StringP("repo", "R", "", "")
|
|
|
|
cli = strings.TrimPrefix(cli, "pr merge")
|
|
argv, err := shlex.Split(cli)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cmd.SetArgs(argv)
|
|
|
|
cmd.SetIn(&bytes.Buffer{})
|
|
cmd.SetOut(ioutil.Discard)
|
|
cmd.SetErr(ioutil.Discard)
|
|
|
|
_, err = cmd.ExecuteC()
|
|
return &test.CmdOut{
|
|
OutBuf: stdout,
|
|
ErrBuf: stderr,
|
|
}, err
|
|
}
|
|
|
|
func initFakeHTTP() *httpmock.Registry {
|
|
return &httpmock.Registry{}
|
|
}
|
|
|
|
func TestPrMerge(t *testing.T) {
|
|
http := initFakeHTTP()
|
|
defer http.Verify(t)
|
|
http.Register(
|
|
httpmock.GraphQL(`query PullRequestByNumber\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": { "repository": { "pullRequest": {
|
|
"id": "THE-ID",
|
|
"number": 1,
|
|
"title": "The title of the PR",
|
|
"state": "OPEN",
|
|
"headRefName": "blueberries",
|
|
"headRepositoryOwner": {"login": "OWNER"}
|
|
} } } }`))
|
|
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")
|
|
}))
|
|
|
|
_, cmdTeardown := run.Stub()
|
|
defer cmdTeardown(t)
|
|
|
|
output, err := runCommand(http, "master", true, "pr merge 1 --merge")
|
|
if err != nil {
|
|
t.Fatalf("error running command `pr merge`: %v", err)
|
|
}
|
|
|
|
r := regexp.MustCompile(`Merged pull request #1 \(The title of the PR\)`)
|
|
|
|
if !r.MatchString(output.Stderr()) {
|
|
t.Fatalf("output did not match regexp /%s/\n> output\n%q\n", r, output.Stderr())
|
|
}
|
|
}
|
|
|
|
func TestPrMerge_nontty(t *testing.T) {
|
|
http := initFakeHTTP()
|
|
defer http.Verify(t)
|
|
http.Register(
|
|
httpmock.GraphQL(`query PullRequestByNumber\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": { "repository": { "pullRequest": {
|
|
"id": "THE-ID",
|
|
"number": 1,
|
|
"title": "The title of the PR",
|
|
"state": "OPEN",
|
|
"headRefName": "blueberries",
|
|
"headRepositoryOwner": {"login": "OWNER"}
|
|
} } } }`))
|
|
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")
|
|
}))
|
|
|
|
_, cmdTeardown := run.Stub()
|
|
defer cmdTeardown(t)
|
|
|
|
output, err := runCommand(http, "master", false, "pr merge 1 --merge")
|
|
if err != nil {
|
|
t.Fatalf("error running command `pr merge`: %v", err)
|
|
}
|
|
|
|
assert.Equal(t, "", output.String())
|
|
assert.Equal(t, "", output.Stderr())
|
|
}
|
|
|
|
func TestPrMerge_withRepoFlag(t *testing.T) {
|
|
http := initFakeHTTP()
|
|
defer http.Verify(t)
|
|
http.Register(
|
|
httpmock.GraphQL(`query PullRequestByNumber\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": { "repository": { "pullRequest": {
|
|
"id": "THE-ID",
|
|
"number": 1,
|
|
"title": "The title of the PR",
|
|
"state": "OPEN",
|
|
"headRefName": "blueberries",
|
|
"headRepositoryOwner": {"login": "OWNER"}
|
|
} } } }`))
|
|
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")
|
|
}))
|
|
|
|
_, 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)
|
|
}
|
|
|
|
r := regexp.MustCompile(`Merged pull request #1 \(The title of the PR\)`)
|
|
|
|
if !r.MatchString(output.Stderr()) {
|
|
t.Fatalf("output did not match regexp /%s/\n> output\n%q\n", r, output.Stderr())
|
|
}
|
|
}
|
|
|
|
func TestPrMerge_deleteBranch(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")
|
|
}))
|
|
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, "")
|
|
|
|
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`)
|
|
}
|
|
|
|
func TestPrMerge_deleteNonCurrentBranch(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")
|
|
}))
|
|
http.Register(
|
|
httpmock.REST("DELETE", "repos/OWNER/REPO/git/refs/heads/blueberries"),
|
|
httpmock.StringResponse(`{}`))
|
|
|
|
cs, cmdTeardown := run.Stub()
|
|
defer cmdTeardown(t)
|
|
|
|
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`)
|
|
}
|
|
|
|
func TestPrMerge_noPrNumberGiven(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, "")
|
|
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(`Merged pull request #10 \(Blueberries are a good fruit\)`)
|
|
|
|
if !r.MatchString(output.Stderr()) {
|
|
t.Fatalf("output did not match regexp /%s/\n> output\n%q\n", r, output.Stderr())
|
|
}
|
|
}
|
|
|
|
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)
|
|
http.Register(
|
|
httpmock.GraphQL(`query PullRequestByNumber\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": { "repository": { "pullRequest": {
|
|
"id": "THE-ID",
|
|
"number": 2,
|
|
"title": "The title of the PR",
|
|
"state": "OPEN",
|
|
"headRefName": "blueberries",
|
|
"headRepositoryOwner": {"login": "OWNER"}
|
|
} } } }`))
|
|
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, "REBASE", input["mergeMethod"].(string))
|
|
assert.NotContains(t, input, "commitHeadline")
|
|
}))
|
|
|
|
_, cmdTeardown := run.Stub()
|
|
defer cmdTeardown(t)
|
|
|
|
output, err := runCommand(http, "master", true, "pr merge 2 --rebase")
|
|
if err != nil {
|
|
t.Fatalf("error running command `pr merge`: %v", err)
|
|
}
|
|
|
|
r := regexp.MustCompile(`Rebased and merged pull request #2 \(The title of the PR\)`)
|
|
|
|
if !r.MatchString(output.Stderr()) {
|
|
t.Fatalf("output did not match regexp /%s/\n> output\n%q\n", r, output.Stderr())
|
|
}
|
|
}
|
|
|
|
func TestPrMerge_squash(t *testing.T) {
|
|
http := initFakeHTTP()
|
|
defer http.Verify(t)
|
|
http.Register(
|
|
httpmock.GraphQL(`query PullRequestByNumber\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": { "repository": { "pullRequest": {
|
|
"id": "THE-ID",
|
|
"number": 3,
|
|
"title": "The title of the PR",
|
|
"state": "OPEN",
|
|
"headRefName": "blueberries",
|
|
"headRepositoryOwner": {"login": "OWNER"}
|
|
} } } }`))
|
|
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, "SQUASH", input["mergeMethod"].(string))
|
|
assert.NotContains(t, input, "commitHeadline")
|
|
}))
|
|
|
|
_, 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)
|
|
}
|
|
|
|
//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(
|
|
httpmock.GraphQL(`query PullRequestByNumber\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": { "repository": {
|
|
"pullRequest": { "number": 4, "title": "The title of the PR", "state": "MERGED"}
|
|
} } }`))
|
|
|
|
_, cmdTeardown := run.Stub()
|
|
defer cmdTeardown(t)
|
|
|
|
output, err := runCommand(http, "blueberries", true, "pr merge 4 --merge")
|
|
if err != nil {
|
|
t.Fatalf("Got unexpected error running `pr merge` %s", err)
|
|
}
|
|
|
|
assert.Equal(t, "", output.String())
|
|
assert.Equal(t, "! Pull request #4 was already merged\n", output.Stderr())
|
|
}
|
|
|
|
func TestPRMerge_interactive(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")
|
|
}))
|
|
|
|
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(false) // Delete branch survey
|
|
as.StubOne("Submit") // Confirm submit survey
|
|
|
|
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 -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("Submit") // 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_interactiveSquashEditCommitMsg(t *testing.T) {
|
|
io, _, stdout, stderr := iostreams.Test()
|
|
io.SetStdoutTTY(true)
|
|
io.SetStderrTTY(true)
|
|
|
|
tr := initFakeHTTP()
|
|
defer tr.Verify(t)
|
|
|
|
tr.Register(
|
|
httpmock.GraphQL(`query PullRequestByNumber\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": { "repository": { "pullRequest": {
|
|
"headRepositoryOwner": {"login": "OWNER"},
|
|
"id": "THE-ID",
|
|
"number": 3,
|
|
"title": "title"
|
|
} } } }`))
|
|
tr.Register(
|
|
httpmock.GraphQL(`query RepositoryInfo\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": { "repository": {
|
|
"mergeCommitAllowed": true,
|
|
"rebaseMergeAllowed": true,
|
|
"squashMergeAllowed": true
|
|
} } }`))
|
|
tr.Register(
|
|
httpmock.GraphQL(`query PullRequestMergeText\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": { "node": {
|
|
"viewerMergeBodyText": "default body text"
|
|
} } }`))
|
|
tr.Register(
|
|
httpmock.GraphQL(`mutation PullRequestMerge\b`),
|
|
httpmock.GraphQLMutation(`{}`, func(input map[string]interface{}) {
|
|
assert.Equal(t, "THE-ID", input["pullRequestId"].(string))
|
|
assert.Equal(t, "SQUASH", input["mergeMethod"].(string))
|
|
assert.Equal(t, "DEFAULT BODY TEXT", input["commitBody"].(string))
|
|
}))
|
|
|
|
_, cmdTeardown := run.Stub()
|
|
defer cmdTeardown(t)
|
|
|
|
as, surveyTeardown := prompt.InitAskStubber()
|
|
defer surveyTeardown()
|
|
|
|
as.StubOne(2) // Merge method survey
|
|
as.StubOne(false) // Delete branch survey
|
|
as.StubOne("Edit commit message") // Confirm submit survey
|
|
as.StubOne("Submit") // Confirm submit survey
|
|
|
|
err := mergeRun(&MergeOptions{
|
|
IO: io,
|
|
Editor: testEditor{},
|
|
HttpClient: func() (*http.Client, error) {
|
|
return &http.Client{Transport: tr}, nil
|
|
},
|
|
SelectorArg: "https://github.com/OWNER/REPO/pull/123",
|
|
InteractiveMode: true,
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, "", stdout.String())
|
|
assert.Equal(t, "✓ Squashed and merged pull request #3 (title)\n", stderr.String())
|
|
}
|
|
|
|
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("Cancel") // 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, PullRequestMergeMethodRebase, method)
|
|
}
|
|
|
|
func TestMergeRun_autoMerge(t *testing.T) {
|
|
io, _, stdout, stderr := iostreams.Test()
|
|
io.SetStdoutTTY(true)
|
|
io.SetStderrTTY(true)
|
|
|
|
tr := initFakeHTTP()
|
|
defer tr.Verify(t)
|
|
|
|
tr.Register(
|
|
httpmock.GraphQL(`query PullRequestByNumber\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": { "repository": { "pullRequest": {
|
|
"id": "THE-ID",
|
|
"number": 123,
|
|
"title": "The title of the PR",
|
|
"state": "OPEN",
|
|
"headRefName": "blueberries",
|
|
"headRepositoryOwner": {"login": "OWNER"}
|
|
} } } }`))
|
|
tr.Register(
|
|
httpmock.GraphQL(`mutation PullRequestAutoMerge\b`),
|
|
httpmock.GraphQLMutation(`{}`, func(input map[string]interface{}) {
|
|
assert.Equal(t, "THE-ID", input["pullRequestId"].(string))
|
|
assert.Equal(t, "SQUASH", input["mergeMethod"].(string))
|
|
}))
|
|
|
|
_, cmdTeardown := run.Stub()
|
|
defer cmdTeardown(t)
|
|
|
|
err := mergeRun(&MergeOptions{
|
|
IO: io,
|
|
HttpClient: func() (*http.Client, error) {
|
|
return &http.Client{Transport: tr}, nil
|
|
},
|
|
SelectorArg: "https://github.com/OWNER/REPO/pull/123",
|
|
AutoMergeEnable: true,
|
|
MergeMethod: PullRequestMergeMethodSquash,
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, "", stdout.String())
|
|
assert.Equal(t, "✓ Pull request #123 will be automatically merged via squash when all requirements are met\n", stderr.String())
|
|
}
|
|
|
|
func TestMergeRun_disableAutoMerge(t *testing.T) {
|
|
io, _, stdout, stderr := iostreams.Test()
|
|
io.SetStdoutTTY(true)
|
|
io.SetStderrTTY(true)
|
|
|
|
tr := initFakeHTTP()
|
|
defer tr.Verify(t)
|
|
|
|
tr.Register(
|
|
httpmock.GraphQL(`query PullRequestByNumber\b`),
|
|
httpmock.StringResponse(`
|
|
{ "data": { "repository": { "pullRequest": {
|
|
"id": "THE-ID",
|
|
"number": 123,
|
|
"title": "The title of the PR",
|
|
"state": "OPEN",
|
|
"headRefName": "blueberries",
|
|
"headRepositoryOwner": {"login": "OWNER"}
|
|
} } } }`))
|
|
tr.Register(
|
|
httpmock.GraphQL(`mutation PullRequestAutoMergeDisable\b`),
|
|
httpmock.StringResponse(`{}`))
|
|
|
|
_, cmdTeardown := run.Stub()
|
|
defer cmdTeardown(t)
|
|
|
|
err := mergeRun(&MergeOptions{
|
|
IO: io,
|
|
HttpClient: func() (*http.Client, error) {
|
|
return &http.Client{Transport: tr}, nil
|
|
},
|
|
SelectorArg: "https://github.com/OWNER/REPO/pull/123",
|
|
AutoMergeDisable: true,
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, "", stdout.String())
|
|
assert.Equal(t, "✓ Auto-merge disabled for pull request #123\n", stderr.String())
|
|
}
|
|
|
|
type testEditor struct{}
|
|
|
|
func (e testEditor) Edit(filename, text string) (string, error) {
|
|
return strings.ToUpper(text), nil
|
|
}
|