Merge branch 'trunk' into gh-attestation-cmd
This commit is contained in:
commit
af7f6996b9
4 changed files with 345 additions and 16 deletions
2
go.mod
2
go.mod
|
|
@ -46,7 +46,7 @@ require (
|
|||
golang.org/x/term v0.17.0
|
||||
golang.org/x/text v0.14.0
|
||||
google.golang.org/grpc v1.61.0
|
||||
google.golang.org/protobuf v1.32.0
|
||||
google.golang.org/protobuf v1.33.0
|
||||
gopkg.in/h2non/gock.v1 v1.1.2
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -562,8 +562,8 @@ google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0=
|
|||
google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
||||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
|
|
@ -23,6 +24,7 @@ import (
|
|||
"github.com/cli/cli/v2/pkg/cmd/pr/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/markdown"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
@ -64,6 +66,8 @@ type CreateOptions struct {
|
|||
|
||||
MaintainerCanModify bool
|
||||
Template string
|
||||
|
||||
DryRun bool
|
||||
}
|
||||
|
||||
type CreateContext struct {
|
||||
|
|
@ -190,6 +194,10 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co
|
|||
return cmdutil.FlagErrorf("must provide `--title` and `--body` (or `--fill` or `fill-first` or `--fillverbose`) when not running interactively")
|
||||
}
|
||||
|
||||
if opts.DryRun && opts.WebMode {
|
||||
return cmdutil.FlagErrorf("`--dry-run` is not supported when using `--web`")
|
||||
}
|
||||
|
||||
if runF != nil {
|
||||
return runF(opts)
|
||||
}
|
||||
|
|
@ -216,6 +224,7 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co
|
|||
fl.Bool("no-maintainer-edit", false, "Disable maintainer's ability to modify pull request")
|
||||
fl.StringVar(&opts.RecoverFile, "recover", "", "Recover input from a failed run of create")
|
||||
fl.StringVarP(&opts.Template, "template", "T", "", "Template `file` to use as starting body text")
|
||||
fl.BoolVar(&opts.DryRun, "dry-run", false, "Print details instead of creating the PR. May still push git changes.")
|
||||
|
||||
_ = cmdutil.RegisterBranchCompletionFlags(f.GitClient, cmd, "base", "head")
|
||||
|
||||
|
|
@ -292,6 +301,9 @@ func createRun(opts *CreateOptions) (err error) {
|
|||
if state.Draft {
|
||||
message = "\nCreating draft pull request for %s into %s in %s\n\n"
|
||||
}
|
||||
if opts.DryRun {
|
||||
message = "\nDry Running pull request for %s into %s in %s\n\n"
|
||||
}
|
||||
|
||||
cs := opts.IO.ColorScheme()
|
||||
|
||||
|
|
@ -360,7 +372,7 @@ func createRun(opts *CreateOptions) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
allowPreview := !state.HasMetadata() && shared.ValidURL(openURL)
|
||||
allowPreview := !state.HasMetadata() && shared.ValidURL(openURL) && !opts.DryRun
|
||||
allowMetadata := ctx.BaseRepo.ViewerCanTriage()
|
||||
action, err := shared.ConfirmPRSubmission(opts.Prompter, allowPreview, allowMetadata, state.Draft)
|
||||
if err != nil {
|
||||
|
|
@ -379,7 +391,7 @@ func createRun(opts *CreateOptions) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
action, err = shared.ConfirmPRSubmission(opts.Prompter, !state.HasMetadata(), false, state.Draft)
|
||||
action, err = shared.ConfirmPRSubmission(opts.Prompter, !state.HasMetadata() && !opts.DryRun, false, state.Draft)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
@ -712,6 +724,14 @@ func submitPR(opts CreateOptions, ctx CreateContext, state shared.IssueMetadataS
|
|||
return err
|
||||
}
|
||||
|
||||
if opts.DryRun {
|
||||
if opts.IO.IsStdoutTTY() {
|
||||
return renderPullRequestTTY(opts.IO, params, &state)
|
||||
} else {
|
||||
return renderPullRequestPlain(opts.IO.Out, params, &state)
|
||||
}
|
||||
}
|
||||
|
||||
opts.IO.StartProgressIndicator()
|
||||
pr, err := api.CreatePullRequest(client, ctx.BaseRepo, params)
|
||||
opts.IO.StopProgressIndicator()
|
||||
|
|
@ -727,6 +747,80 @@ func submitPR(opts CreateOptions, ctx CreateContext, state shared.IssueMetadataS
|
|||
return nil
|
||||
}
|
||||
|
||||
func renderPullRequestPlain(w io.Writer, params map[string]interface{}, state *shared.IssueMetadataState) error {
|
||||
fmt.Fprint(w, "Would have created a Pull Request with:\n")
|
||||
fmt.Fprintf(w, "title:\t%s\n", params["title"])
|
||||
fmt.Fprintf(w, "draft:\t%t\n", params["draft"])
|
||||
fmt.Fprintf(w, "base:\t%s\n", params["baseRefName"])
|
||||
fmt.Fprintf(w, "head:\t%s\n", params["headRefName"])
|
||||
if len(state.Labels) != 0 {
|
||||
fmt.Fprintf(w, "labels:\t%v\n", strings.Join(state.Labels, ", "))
|
||||
}
|
||||
if len(state.Reviewers) != 0 {
|
||||
fmt.Fprintf(w, "reviewers:\t%v\n", strings.Join(state.Reviewers, ", "))
|
||||
}
|
||||
if len(state.Assignees) != 0 {
|
||||
fmt.Fprintf(w, "assignees:\t%v\n", strings.Join(state.Assignees, ", "))
|
||||
}
|
||||
if len(state.Milestones) != 0 {
|
||||
fmt.Fprintf(w, "milestones:\t%v\n", strings.Join(state.Milestones, ", "))
|
||||
}
|
||||
if len(state.Projects) != 0 {
|
||||
fmt.Fprintf(w, "projects:\t%v\n", strings.Join(state.Projects, ", "))
|
||||
}
|
||||
fmt.Fprintf(w, "maintainerCanModify:\t%t\n", params["maintainerCanModify"])
|
||||
fmt.Fprint(w, "body:\n")
|
||||
if len(params["body"].(string)) != 0 {
|
||||
fmt.Fprintln(w, params["body"])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func renderPullRequestTTY(io *iostreams.IOStreams, params map[string]interface{}, state *shared.IssueMetadataState) error {
|
||||
iofmt := io.ColorScheme()
|
||||
out := io.Out
|
||||
|
||||
fmt.Fprint(out, "Would have created a Pull Request with:\n")
|
||||
fmt.Fprintf(out, "%s: %s\n", iofmt.Bold("Title"), params["title"].(string))
|
||||
fmt.Fprintf(out, "%s: %t\n", iofmt.Bold("Draft"), params["draft"])
|
||||
fmt.Fprintf(out, "%s: %s\n", iofmt.Bold("Base"), params["baseRefName"])
|
||||
fmt.Fprintf(out, "%s: %s\n", iofmt.Bold("Head"), params["headRefName"])
|
||||
if len(state.Labels) != 0 {
|
||||
fmt.Fprintf(out, "%s: %s\n", iofmt.Bold("Labels"), strings.Join(state.Labels, ", "))
|
||||
}
|
||||
if len(state.Reviewers) != 0 {
|
||||
fmt.Fprintf(out, "%s: %s\n", iofmt.Bold("Reviewers"), strings.Join(state.Reviewers, ", "))
|
||||
}
|
||||
if len(state.Assignees) != 0 {
|
||||
fmt.Fprintf(out, "%s: %s\n", iofmt.Bold("Assignees"), strings.Join(state.Assignees, ", "))
|
||||
}
|
||||
if len(state.Milestones) != 0 {
|
||||
fmt.Fprintf(out, "%s: %s\n", iofmt.Bold("Milestones"), strings.Join(state.Milestones, ", "))
|
||||
}
|
||||
if len(state.Projects) != 0 {
|
||||
fmt.Fprintf(out, "%s: %s\n", iofmt.Bold("Projects"), strings.Join(state.Projects, ", "))
|
||||
}
|
||||
fmt.Fprintf(out, "%s: %t\n", iofmt.Bold("MaintainerCanModify"), params["maintainerCanModify"])
|
||||
|
||||
fmt.Fprintf(out, "%s\n", iofmt.Bold("Body:"))
|
||||
// Body
|
||||
var md string
|
||||
var err error
|
||||
if len(params["body"].(string)) == 0 {
|
||||
md = fmt.Sprintf("%s\n", iofmt.Gray("No description provided"))
|
||||
} else {
|
||||
md, err = markdown.Render(params["body"].(string),
|
||||
markdown.WithTheme(io.TerminalTheme()),
|
||||
markdown.WithWrap(io.TerminalWidth()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(out, "%s", md)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func previewPR(opts CreateOptions, openURL string) error {
|
||||
if opts.IO.IsStdinTTY() && opts.IO.IsStdoutTTY() {
|
||||
fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", text.DisplayURL(openURL))
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
|
|
@ -194,6 +195,12 @@ func TestNewCmdCreate(t *testing.T) {
|
|||
cli: "--fill --fill-first",
|
||||
wantsErr: true,
|
||||
},
|
||||
{
|
||||
name: "dry-run and web",
|
||||
tty: false,
|
||||
cli: "--web --dry-run",
|
||||
wantsErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
@ -250,16 +257,17 @@ func TestNewCmdCreate(t *testing.T) {
|
|||
|
||||
func Test_createRun(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setup func(*CreateOptions, *testing.T) func()
|
||||
cmdStubs func(*run.CommandStubber)
|
||||
promptStubs func(*prompter.PrompterMock)
|
||||
httpStubs func(*httpmock.Registry, *testing.T)
|
||||
expectedOut string
|
||||
expectedErrOut string
|
||||
expectedBrowse string
|
||||
wantErr string
|
||||
tty bool
|
||||
name string
|
||||
setup func(*CreateOptions, *testing.T) func()
|
||||
cmdStubs func(*run.CommandStubber)
|
||||
promptStubs func(*prompter.PrompterMock)
|
||||
httpStubs func(*httpmock.Registry, *testing.T)
|
||||
expectedOutputs []string
|
||||
expectedOut string
|
||||
expectedErrOut string
|
||||
expectedBrowse string
|
||||
wantErr string
|
||||
tty bool
|
||||
}{
|
||||
{
|
||||
name: "nontty web",
|
||||
|
|
@ -300,6 +308,228 @@ func Test_createRun(t *testing.T) {
|
|||
},
|
||||
expectedOut: "https://github.com/OWNER/REPO/pull/12\n",
|
||||
},
|
||||
{
|
||||
name: "dry-run-nontty-with-default-base",
|
||||
tty: false,
|
||||
setup: func(opts *CreateOptions, t *testing.T) func() {
|
||||
opts.TitleProvided = true
|
||||
opts.BodyProvided = true
|
||||
opts.Title = "my title"
|
||||
opts.Body = "my body"
|
||||
opts.HeadBranch = "feature"
|
||||
opts.DryRun = true
|
||||
return func() {}
|
||||
},
|
||||
expectedOutputs: []string{
|
||||
"Would have created a Pull Request with:",
|
||||
`title: my title`,
|
||||
`draft: false`,
|
||||
`base: master`,
|
||||
`head: feature`,
|
||||
`maintainerCanModify: false`,
|
||||
`body:`,
|
||||
`my body`,
|
||||
``,
|
||||
},
|
||||
expectedErrOut: "",
|
||||
},
|
||||
{
|
||||
name: "dry-run-nontty-with-all-opts",
|
||||
tty: false,
|
||||
setup: func(opts *CreateOptions, t *testing.T) func() {
|
||||
opts.TitleProvided = true
|
||||
opts.BodyProvided = true
|
||||
opts.Title = "TITLE"
|
||||
opts.Body = "BODY"
|
||||
opts.BaseBranch = "trunk"
|
||||
opts.HeadBranch = "feature"
|
||||
opts.Assignees = []string{"monalisa"}
|
||||
opts.Labels = []string{"bug", "todo"}
|
||||
opts.Projects = []string{"roadmap"}
|
||||
opts.Reviewers = []string{"hubot", "monalisa", "/core", "/robots"}
|
||||
opts.Milestone = "big one.oh"
|
||||
opts.DryRun = true
|
||||
return func() {}
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry, t *testing.T) {
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query RepositoryResolveMetadataIDs\b`),
|
||||
httpmock.StringResponse(`
|
||||
{ "data": {
|
||||
"u000": { "login": "MonaLisa", "id": "MONAID" },
|
||||
"u001": { "login": "hubot", "id": "HUBOTID" },
|
||||
"repository": {
|
||||
"l000": { "name": "bug", "id": "BUGID" },
|
||||
"l001": { "name": "TODO", "id": "TODOID" }
|
||||
},
|
||||
"organization": {
|
||||
"t000": { "slug": "core", "id": "COREID" },
|
||||
"t001": { "slug": "robots", "id": "ROBOTID" }
|
||||
}
|
||||
} }
|
||||
`))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query RepositoryMilestoneList\b`),
|
||||
httpmock.StringResponse(`
|
||||
{ "data": { "repository": { "milestones": {
|
||||
"nodes": [
|
||||
{ "title": "GA", "id": "GAID" },
|
||||
{ "title": "Big One.oh", "id": "BIGONEID" }
|
||||
],
|
||||
"pageInfo": { "hasNextPage": false }
|
||||
} } } }
|
||||
`))
|
||||
mockRetrieveProjects(t, reg)
|
||||
},
|
||||
expectedOutputs: []string{
|
||||
"Would have created a Pull Request with:",
|
||||
`title: TITLE`,
|
||||
`draft: false`,
|
||||
`base: trunk`,
|
||||
`head: feature`,
|
||||
`labels: bug, todo`,
|
||||
`reviewers: hubot, monalisa, /core, /robots`,
|
||||
`assignees: monalisa`,
|
||||
`milestones: big one.oh`,
|
||||
`projects: roadmap`,
|
||||
`maintainerCanModify: false`,
|
||||
`body:`,
|
||||
`BODY`,
|
||||
``,
|
||||
},
|
||||
expectedErrOut: "",
|
||||
},
|
||||
{
|
||||
name: "dry-run-tty-with-default-base",
|
||||
tty: true,
|
||||
setup: func(opts *CreateOptions, t *testing.T) func() {
|
||||
opts.TitleProvided = true
|
||||
opts.BodyProvided = true
|
||||
opts.Title = "my title"
|
||||
opts.Body = "my body"
|
||||
opts.HeadBranch = "feature"
|
||||
opts.DryRun = true
|
||||
return func() {}
|
||||
},
|
||||
expectedOutputs: []string{
|
||||
`Would have created a Pull Request with:`,
|
||||
`Title: my title`,
|
||||
`Draft: false`,
|
||||
`Base: master`,
|
||||
`Head: feature`,
|
||||
`MaintainerCanModify: false`,
|
||||
`Body:`,
|
||||
``,
|
||||
` my body `,
|
||||
``,
|
||||
``,
|
||||
},
|
||||
expectedErrOut: heredoc.Doc(`
|
||||
|
||||
Dry Running pull request for feature into master in OWNER/REPO
|
||||
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "dry-run-tty-with-all-opts",
|
||||
tty: true,
|
||||
setup: func(opts *CreateOptions, t *testing.T) func() {
|
||||
opts.TitleProvided = true
|
||||
opts.BodyProvided = true
|
||||
opts.Title = "TITLE"
|
||||
opts.Body = "BODY"
|
||||
opts.BaseBranch = "trunk"
|
||||
opts.HeadBranch = "feature"
|
||||
opts.Assignees = []string{"monalisa"}
|
||||
opts.Labels = []string{"bug", "todo"}
|
||||
opts.Projects = []string{"roadmap"}
|
||||
opts.Reviewers = []string{"hubot", "monalisa", "/core", "/robots"}
|
||||
opts.Milestone = "big one.oh"
|
||||
opts.DryRun = true
|
||||
return func() {}
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry, t *testing.T) {
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query RepositoryResolveMetadataIDs\b`),
|
||||
httpmock.StringResponse(`
|
||||
{ "data": {
|
||||
"u000": { "login": "MonaLisa", "id": "MONAID" },
|
||||
"u001": { "login": "hubot", "id": "HUBOTID" },
|
||||
"repository": {
|
||||
"l000": { "name": "bug", "id": "BUGID" },
|
||||
"l001": { "name": "TODO", "id": "TODOID" }
|
||||
},
|
||||
"organization": {
|
||||
"t000": { "slug": "core", "id": "COREID" },
|
||||
"t001": { "slug": "robots", "id": "ROBOTID" }
|
||||
}
|
||||
} }
|
||||
`))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query RepositoryMilestoneList\b`),
|
||||
httpmock.StringResponse(`
|
||||
{ "data": { "repository": { "milestones": {
|
||||
"nodes": [
|
||||
{ "title": "GA", "id": "GAID" },
|
||||
{ "title": "Big One.oh", "id": "BIGONEID" }
|
||||
],
|
||||
"pageInfo": { "hasNextPage": false }
|
||||
} } } }
|
||||
`))
|
||||
mockRetrieveProjects(t, reg)
|
||||
},
|
||||
expectedOutputs: []string{
|
||||
`Would have created a Pull Request with:`,
|
||||
`Title: TITLE`,
|
||||
`Draft: false`,
|
||||
`Base: trunk`,
|
||||
`Head: feature`,
|
||||
`Labels: bug, todo`,
|
||||
`Reviewers: hubot, monalisa, /core, /robots`,
|
||||
`Assignees: monalisa`,
|
||||
`Milestones: big one.oh`,
|
||||
`Projects: roadmap`,
|
||||
`MaintainerCanModify: false`,
|
||||
`Body:`,
|
||||
``,
|
||||
` BODY `,
|
||||
``,
|
||||
``,
|
||||
},
|
||||
expectedErrOut: heredoc.Doc(`
|
||||
|
||||
Dry Running pull request for feature into trunk in OWNER/REPO
|
||||
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "dry-run-tty-with-empty-body",
|
||||
tty: true,
|
||||
setup: func(opts *CreateOptions, t *testing.T) func() {
|
||||
opts.TitleProvided = true
|
||||
opts.BodyProvided = true
|
||||
opts.Title = "TITLE"
|
||||
opts.Body = ""
|
||||
opts.HeadBranch = "feature"
|
||||
opts.DryRun = true
|
||||
return func() {}
|
||||
},
|
||||
expectedOut: heredoc.Doc(`
|
||||
Would have created a Pull Request with:
|
||||
Title: TITLE
|
||||
Draft: false
|
||||
Base: master
|
||||
Head: feature
|
||||
MaintainerCanModify: false
|
||||
Body:
|
||||
No description provided
|
||||
`),
|
||||
expectedErrOut: heredoc.Doc(`
|
||||
|
||||
Dry Running pull request for feature into master in OWNER/REPO
|
||||
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "survey",
|
||||
tty: true,
|
||||
|
|
@ -1219,7 +1449,12 @@ func Test_createRun(t *testing.T) {
|
|||
assert.EqualError(t, err, tt.wantErr)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedOut, output.String())
|
||||
if tt.expectedOut != "" {
|
||||
assert.Equal(t, tt.expectedOut, output.String())
|
||||
}
|
||||
if len(tt.expectedOutputs) > 0 {
|
||||
assert.Equal(t, tt.expectedOutputs, strings.Split(output.String(), "\n"))
|
||||
}
|
||||
assert.Equal(t, tt.expectedErrOut, output.Stderr())
|
||||
assert.Equal(t, tt.expectedBrowse, output.BrowsedURL)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue