Merge branch 'trunk' into eugene/attestation/fetch-oci-bundle
This commit is contained in:
commit
7539f2aea4
15 changed files with 455 additions and 183 deletions
|
|
@ -8,6 +8,8 @@ triage role. The initial expectation is that the person in the role for the week
|
|||
|
||||
Review and label [open issues missing either the `enhancement`, `bug`, or `docs` label](https://github.com/cli/cli/issues?q=is%3Aopen+is%3Aissue+-label%3Abug%2Cenhancement%2Cdocs+).
|
||||
|
||||
Any issue that is accepted to be done as either an `enhancement`, `bug`, or `docs` requires explicit Acceptance Criteria in a comment on the issue before `needs-triage` label is removed.
|
||||
|
||||
To be considered triaged, `enhancement` issues require at least one of the following additional labels:
|
||||
|
||||
- `core`: reserved for the core CLI team
|
||||
|
|
|
|||
5
go.mod
5
go.mod
|
|
@ -20,7 +20,7 @@ require (
|
|||
github.com/gabriel-vasile/mimetype v1.4.5
|
||||
github.com/gdamore/tcell/v2 v2.5.4
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/google/go-containerregistry v0.20.1
|
||||
github.com/google/go-containerregistry v0.20.2
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
|
|
@ -70,9 +70,8 @@ require (
|
|||
github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 // indirect
|
||||
github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 // indirect
|
||||
github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||
github.com/docker/cli v24.0.0+incompatible // indirect
|
||||
github.com/docker/cli v27.1.1+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.2+incompatible // indirect
|
||||
github.com/docker/docker v24.0.9+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.7.0 // indirect
|
||||
github.com/fatih/color v1.16.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
|
|
|
|||
10
go.sum
10
go.sum
|
|
@ -131,12 +131,10 @@ github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK
|
|||
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/docker/cli v24.0.0+incompatible h1:0+1VshNwBQzQAx9lOl+OYCTCEAD8fKs/qeXMx3O0wqM=
|
||||
github.com/docker/cli v24.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli v27.1.1+incompatible h1:goaZxOqs4QKxznZjjBWKONQci/MywhtRv2oNn0GkeZE=
|
||||
github.com/docker/cli v27.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
|
||||
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0=
|
||||
github.com/docker/docker v24.0.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A=
|
||||
github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
|
|
@ -201,8 +199,8 @@ github.com/google/certificate-transparency-go v1.2.1 h1:4iW/NwzqOqYEEoCBEFP+jPbB
|
|||
github.com/google/certificate-transparency-go v1.2.1/go.mod h1:bvn/ytAccv+I6+DGkqpvSsEdiVGramgaSC6RD3tEmeE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-containerregistry v0.20.1 h1:eTgx9QNYugV4DN5mz4U8hiAGTi1ybXn0TPi4Smd8du0=
|
||||
github.com/google/go-containerregistry v0.20.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI=
|
||||
github.com/google/go-containerregistry v0.20.2 h1:B1wPJ1SN/S7pB+ZAimcciVD+r+yV/l/DSArMxlbwseo=
|
||||
github.com/google/go-containerregistry v0.20.2/go.mod h1:z38EKdKh4h7IP2gSfUUqEvalZBqs6AoLeWfUy34nQC8=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
|
||||
|
|
|
|||
|
|
@ -81,19 +81,11 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co
|
|||
opts.BaseRepo = f.BaseRepo
|
||||
opts.HasRepoOverride = cmd.Flags().Changed("repo")
|
||||
|
||||
if err := cmdutil.MutuallyExclusive(
|
||||
"specify only one of `--editor` or `--web`",
|
||||
opts.EditorMode,
|
||||
opts.WebMode,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config, err := f.Config()
|
||||
var err error
|
||||
opts.EditorMode, err = prShared.InitEditorMode(f, opts.EditorMode, opts.WebMode, opts.IO.CanPrompt())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts.EditorMode = !opts.WebMode && (opts.EditorMode || config.PreferEditorPrompt("").Value == "enabled")
|
||||
|
||||
titleProvided := cmd.Flags().Changed("title")
|
||||
bodyProvided := cmd.Flags().Changed("body")
|
||||
|
|
@ -119,9 +111,6 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co
|
|||
if opts.Interactive && !opts.IO.CanPrompt() {
|
||||
return cmdutil.FlagErrorf("must provide `--title` and `--body` when not running interactively")
|
||||
}
|
||||
if opts.EditorMode && !opts.IO.CanPrompt() {
|
||||
return errors.New("--editor or enabled prefer_editor_prompt configuration are not supported in non-tty mode")
|
||||
}
|
||||
|
||||
if runF != nil {
|
||||
return runF(opts)
|
||||
|
|
@ -133,11 +122,11 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co
|
|||
cmd.Flags().StringVarP(&opts.Title, "title", "t", "", "Supply a title. Will prompt for one otherwise.")
|
||||
cmd.Flags().StringVarP(&opts.Body, "body", "b", "", "Supply a body. Will prompt for one otherwise.")
|
||||
cmd.Flags().StringVarP(&bodyFile, "body-file", "F", "", "Read body text from `file` (use \"-\" to read from standard input)")
|
||||
cmd.Flags().BoolVarP(&opts.EditorMode, "editor", "e", false, "Skip prompts and open the text editor to write the title and body in. The first line is the title and the rest text is the body.")
|
||||
cmd.Flags().BoolVarP(&opts.EditorMode, "editor", "e", false, "Skip prompts and open the text editor to write the title and body in. The first line is the title and the remaining text is the body.")
|
||||
cmd.Flags().BoolVarP(&opts.WebMode, "web", "w", false, "Open the browser to create an issue")
|
||||
cmd.Flags().StringSliceVarP(&opts.Assignees, "assignee", "a", nil, "Assign people by their `login`. Use \"@me\" to self-assign.")
|
||||
cmd.Flags().StringSliceVarP(&opts.Labels, "label", "l", nil, "Add labels by `name`")
|
||||
cmd.Flags().StringSliceVarP(&opts.Projects, "project", "p", nil, "Add the issue to projects by `name`")
|
||||
cmd.Flags().StringSliceVarP(&opts.Projects, "project", "p", nil, "Add the issue to projects by `title`")
|
||||
cmd.Flags().StringVarP(&opts.Milestone, "milestone", "m", "", "Add the issue to a milestone by `name`")
|
||||
cmd.Flags().StringVar(&opts.RecoverFile, "recover", "", "Recover input from a failed run of create")
|
||||
cmd.Flags().StringVarP(&opts.Template, "template", "T", "", "Template `file` to use as starting body text")
|
||||
|
|
|
|||
|
|
@ -153,8 +153,8 @@ func NewCmdEdit(f *cmdutil.Factory, runF func(*EditOptions) error) *cobra.Comman
|
|||
cmd.Flags().StringSliceVar(&opts.Editable.Assignees.Remove, "remove-assignee", nil, "Remove assigned users by their `login`. Use \"@me\" to unassign yourself.")
|
||||
cmd.Flags().StringSliceVar(&opts.Editable.Labels.Add, "add-label", nil, "Add labels by `name`")
|
||||
cmd.Flags().StringSliceVar(&opts.Editable.Labels.Remove, "remove-label", nil, "Remove labels by `name`")
|
||||
cmd.Flags().StringSliceVar(&opts.Editable.Projects.Add, "add-project", nil, "Add the issue to projects by `name`")
|
||||
cmd.Flags().StringSliceVar(&opts.Editable.Projects.Remove, "remove-project", nil, "Remove the issue from projects by `name`")
|
||||
cmd.Flags().StringSliceVar(&opts.Editable.Projects.Add, "add-project", nil, "Add the issue to projects by `title`")
|
||||
cmd.Flags().StringSliceVar(&opts.Editable.Projects.Remove, "remove-project", nil, "Remove the issue from projects by `title`")
|
||||
cmd.Flags().StringVarP(&opts.Editable.Milestone.Value, "milestone", "m", "", "Edit the milestone the issue belongs to by `name`")
|
||||
cmd.Flags().BoolVar(&removeMilestone, "remove-milestone", false, "Remove the milestone association from the issue")
|
||||
|
||||
|
|
|
|||
|
|
@ -30,15 +30,16 @@ import (
|
|||
|
||||
type CreateOptions struct {
|
||||
// This struct stores user input and factory functions
|
||||
HttpClient func() (*http.Client, error)
|
||||
GitClient *git.Client
|
||||
Config func() (gh.Config, error)
|
||||
IO *iostreams.IOStreams
|
||||
Remotes func() (ghContext.Remotes, error)
|
||||
Branch func() (string, error)
|
||||
Browser browser.Browser
|
||||
Prompter shared.Prompt
|
||||
Finder shared.PRFinder
|
||||
HttpClient func() (*http.Client, error)
|
||||
GitClient *git.Client
|
||||
Config func() (gh.Config, error)
|
||||
IO *iostreams.IOStreams
|
||||
Remotes func() (ghContext.Remotes, error)
|
||||
Branch func() (string, error)
|
||||
Browser browser.Browser
|
||||
Prompter shared.Prompt
|
||||
Finder shared.PRFinder
|
||||
TitledEditSurvey func(string, string) (string, string, error)
|
||||
|
||||
TitleProvided bool
|
||||
BodyProvided bool
|
||||
|
|
@ -49,6 +50,7 @@ type CreateOptions struct {
|
|||
Autofill bool
|
||||
FillVerbose bool
|
||||
FillFirst bool
|
||||
EditorMode bool
|
||||
WebMode bool
|
||||
RecoverFile string
|
||||
|
||||
|
|
@ -88,14 +90,15 @@ type CreateContext struct {
|
|||
|
||||
func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Command {
|
||||
opts := &CreateOptions{
|
||||
IO: f.IOStreams,
|
||||
HttpClient: f.HttpClient,
|
||||
GitClient: f.GitClient,
|
||||
Config: f.Config,
|
||||
Remotes: f.Remotes,
|
||||
Branch: f.Branch,
|
||||
Browser: f.Browser,
|
||||
Prompter: f.Prompter,
|
||||
IO: f.IOStreams,
|
||||
HttpClient: f.HttpClient,
|
||||
GitClient: f.GitClient,
|
||||
Config: f.Config,
|
||||
Remotes: f.Remotes,
|
||||
Branch: f.Branch,
|
||||
Browser: f.Browser,
|
||||
Prompter: f.Prompter,
|
||||
TitledEditSurvey: shared.TitledEditSurvey(&shared.UserEditor{Config: f.Config, IO: f.IOStreams}),
|
||||
}
|
||||
|
||||
var bodyFile string
|
||||
|
|
@ -177,6 +180,20 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co
|
|||
return cmdutil.FlagErrorf("`--fill-verbose` is not supported with `--fill`")
|
||||
}
|
||||
|
||||
if err := cmdutil.MutuallyExclusive(
|
||||
"specify only one of `--editor` or `--web`",
|
||||
opts.EditorMode,
|
||||
opts.WebMode,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var err error
|
||||
opts.EditorMode, err = shared.InitEditorMode(f, opts.EditorMode, opts.WebMode, opts.IO.CanPrompt())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
opts.BodyProvided = cmd.Flags().Changed("body")
|
||||
if bodyFile != "" {
|
||||
b, err := cmdutil.ReadFile(bodyFile, opts.IO.In)
|
||||
|
|
@ -213,6 +230,7 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co
|
|||
fl.StringVarP(&bodyFile, "body-file", "F", "", "Read body text from `file` (use \"-\" to read from standard input)")
|
||||
fl.StringVarP(&opts.BaseBranch, "base", "B", "", "The `branch` into which you want your code merged")
|
||||
fl.StringVarP(&opts.HeadBranch, "head", "H", "", "The `branch` that contains commits for your pull request (default [current branch])")
|
||||
fl.BoolVarP(&opts.EditorMode, "editor", "e", false, "Skip prompts and open the text editor to write the title and body in. The first line is the title and the remaining text is the body.")
|
||||
fl.BoolVarP(&opts.WebMode, "web", "w", false, "Open the web browser to create a pull request")
|
||||
fl.BoolVarP(&opts.FillVerbose, "fill-verbose", "", false, "Use commits msg+body for description")
|
||||
fl.BoolVarP(&opts.Autofill, "fill", "f", false, "Use commit info for title and body")
|
||||
|
|
@ -220,7 +238,7 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co
|
|||
fl.StringSliceVarP(&opts.Reviewers, "reviewer", "r", nil, "Request reviews from people or teams by their `handle`")
|
||||
fl.StringSliceVarP(&opts.Assignees, "assignee", "a", nil, "Assign people by their `login`. Use \"@me\" to self-assign.")
|
||||
fl.StringSliceVarP(&opts.Labels, "label", "l", nil, "Add labels by `name`")
|
||||
fl.StringSliceVarP(&opts.Projects, "project", "p", nil, "Add the pull request to projects by `name`")
|
||||
fl.StringSliceVarP(&opts.Projects, "project", "p", nil, "Add the pull request to projects by `title`")
|
||||
fl.StringVarP(&opts.Milestone, "milestone", "m", "", "Add the pull request to a milestone by `name`")
|
||||
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")
|
||||
|
|
@ -315,7 +333,7 @@ func createRun(opts *CreateOptions) (err error) {
|
|||
ghrepo.FullName(ctx.BaseRepo))
|
||||
}
|
||||
|
||||
if opts.FillVerbose || opts.Autofill || opts.FillFirst || (opts.TitleProvided && opts.BodyProvided) {
|
||||
if !opts.EditorMode && (opts.FillVerbose || opts.Autofill || opts.FillFirst || (opts.TitleProvided && opts.BodyProvided)) {
|
||||
err = handlePush(*opts, *ctx)
|
||||
if err != nil {
|
||||
return
|
||||
|
|
@ -330,71 +348,101 @@ func createRun(opts *CreateOptions) (err error) {
|
|||
}
|
||||
}
|
||||
|
||||
if !opts.TitleProvided {
|
||||
err = shared.TitleSurvey(opts.Prompter, state)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
action := shared.SubmitAction
|
||||
if opts.IsDraft {
|
||||
action = shared.SubmitDraftAction
|
||||
}
|
||||
|
||||
defer shared.PreserveInput(opts.IO, state, &err)()
|
||||
tpl := shared.NewTemplateManager(client.HTTP(), ctx.BaseRepo, opts.Prompter, opts.RootDirOverride, opts.RepoOverride == "", true)
|
||||
|
||||
if !opts.BodyProvided {
|
||||
templateContent := ""
|
||||
if opts.RecoverFile == "" {
|
||||
tpl := shared.NewTemplateManager(client.HTTP(), ctx.BaseRepo, opts.Prompter, opts.RootDirOverride, opts.RepoOverride == "", true)
|
||||
if opts.EditorMode {
|
||||
if opts.Template != "" {
|
||||
var template shared.Template
|
||||
|
||||
if opts.Template != "" {
|
||||
template, err = tpl.Select(opts.Template)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
template, err = tpl.Choose()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
template, err = tpl.Select(opts.Template)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if state.Title == "" {
|
||||
state.Title = template.Title()
|
||||
}
|
||||
state.Body = string(template.Body())
|
||||
}
|
||||
|
||||
if template != nil {
|
||||
templateContent = string(template.Body())
|
||||
state.Title, state.Body, err = opts.TitledEditSurvey(state.Title, state.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if state.Title == "" {
|
||||
err = fmt.Errorf("title can't be blank")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
|
||||
if !opts.TitleProvided {
|
||||
err = shared.TitleSurvey(opts.Prompter, state)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = shared.BodySurvey(opts.Prompter, state, templateContent)
|
||||
if err != nil {
|
||||
return
|
||||
defer shared.PreserveInput(opts.IO, state, &err)()
|
||||
|
||||
if !opts.BodyProvided {
|
||||
templateContent := ""
|
||||
if opts.RecoverFile == "" {
|
||||
var template shared.Template
|
||||
|
||||
if opts.Template != "" {
|
||||
template, err = tpl.Select(opts.Template)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
template, err = tpl.Choose()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if template != nil {
|
||||
templateContent = string(template.Body())
|
||||
}
|
||||
}
|
||||
|
||||
err = shared.BodySurvey(opts.Prompter, state, templateContent)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
openURL, err = generateCompareURL(*ctx, *state)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
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 {
|
||||
return fmt.Errorf("unable to confirm: %w", err)
|
||||
}
|
||||
|
||||
if action == shared.MetadataAction {
|
||||
fetcher := &shared.MetadataFetcher{
|
||||
IO: opts.IO,
|
||||
APIClient: client,
|
||||
Repo: ctx.BaseRepo,
|
||||
State: state,
|
||||
}
|
||||
err = shared.MetadataSurvey(opts.Prompter, opts.IO, ctx.BaseRepo, fetcher, state)
|
||||
openURL, err = generateCompareURL(*ctx, *state)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
action, err = shared.ConfirmPRSubmission(opts.Prompter, !state.HasMetadata() && !opts.DryRun, false, state.Draft)
|
||||
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 {
|
||||
return
|
||||
return fmt.Errorf("unable to confirm: %w", err)
|
||||
}
|
||||
|
||||
if action == shared.MetadataAction {
|
||||
fetcher := &shared.MetadataFetcher{
|
||||
IO: opts.IO,
|
||||
APIClient: client,
|
||||
Repo: ctx.BaseRepo,
|
||||
State: state,
|
||||
}
|
||||
err = shared.MetadataSurvey(opts.Prompter, opts.IO, ctx.BaseRepo, fetcher, state)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
action, err = shared.ConfirmPRSubmission(opts.Prompter, !state.HasMetadata() && !opts.DryRun, false, state.Draft)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ func TestNewCmdCreate(t *testing.T) {
|
|||
tty bool
|
||||
stdin string
|
||||
cli string
|
||||
config string
|
||||
wantsErr bool
|
||||
wantsOpts CreateOptions
|
||||
}{
|
||||
|
|
@ -202,6 +203,64 @@ func TestNewCmdCreate(t *testing.T) {
|
|||
cli: "--web --dry-run",
|
||||
wantsErr: true,
|
||||
},
|
||||
{
|
||||
name: "editor by cli",
|
||||
tty: true,
|
||||
cli: "--editor",
|
||||
wantsErr: false,
|
||||
wantsOpts: CreateOptions{
|
||||
Title: "",
|
||||
Body: "",
|
||||
RecoverFile: "",
|
||||
WebMode: false,
|
||||
EditorMode: true,
|
||||
MaintainerCanModify: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "editor by config",
|
||||
tty: true,
|
||||
cli: "",
|
||||
config: "prefer_editor_prompt: enabled",
|
||||
wantsErr: false,
|
||||
wantsOpts: CreateOptions{
|
||||
Title: "",
|
||||
Body: "",
|
||||
RecoverFile: "",
|
||||
WebMode: false,
|
||||
EditorMode: true,
|
||||
MaintainerCanModify: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "editor and web",
|
||||
tty: true,
|
||||
cli: "--editor --web",
|
||||
wantsErr: true,
|
||||
},
|
||||
{
|
||||
name: "can use web even though editor is enabled by config",
|
||||
tty: true,
|
||||
cli: `--web --title mytitle --body "issue body"`,
|
||||
config: "prefer_editor_prompt: enabled",
|
||||
wantsErr: false,
|
||||
wantsOpts: CreateOptions{
|
||||
Title: "mytitle",
|
||||
Body: "issue body",
|
||||
TitleProvided: true,
|
||||
BodyProvided: true,
|
||||
RecoverFile: "",
|
||||
WebMode: true,
|
||||
EditorMode: false,
|
||||
MaintainerCanModify: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "editor with non-tty",
|
||||
tty: false,
|
||||
cli: "--editor",
|
||||
wantsErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
@ -215,6 +274,12 @@ func TestNewCmdCreate(t *testing.T) {
|
|||
|
||||
f := &cmdutil.Factory{
|
||||
IOStreams: ios,
|
||||
Config: func() (gh.Config, error) {
|
||||
if tt.config != "" {
|
||||
return config.NewFromString(tt.config), nil
|
||||
}
|
||||
return config.NewBlankConfig(), nil
|
||||
},
|
||||
}
|
||||
|
||||
var opts *CreateOptions
|
||||
|
|
@ -1372,6 +1437,33 @@ func Test_createRun(t *testing.T) {
|
|||
expectedOut: "https://github.com/OWNER/REPO/pull/12\n",
|
||||
expectedErrOut: "\nCreating pull request for feature into master in OWNER/REPO\n\n",
|
||||
},
|
||||
{
|
||||
name: "editor",
|
||||
httpStubs: func(r *httpmock.Registry, t *testing.T) {
|
||||
r.Register(
|
||||
httpmock.GraphQL(`mutation PullRequestCreate\b`),
|
||||
httpmock.GraphQLMutation(`
|
||||
{
|
||||
"data": { "createPullRequest": { "pullRequest": {
|
||||
"URL": "https://github.com/OWNER/REPO/pull/12"
|
||||
} } }
|
||||
}
|
||||
`, func(inputs map[string]interface{}) {
|
||||
assert.Equal(t, "title", inputs["title"])
|
||||
assert.Equal(t, "body", inputs["body"])
|
||||
}))
|
||||
},
|
||||
setup: func(opts *CreateOptions, t *testing.T) func() {
|
||||
opts.EditorMode = true
|
||||
opts.HeadBranch = "feature"
|
||||
opts.TitledEditSurvey = func(string, string) (string, string, error) { return "title", "body", nil }
|
||||
return func() {}
|
||||
},
|
||||
cmdStubs: func(cs *run.CommandStubber) {
|
||||
cs.Register("git -c log.ShowSignature=false log --pretty=format:%H%x00%s%x00%b%x00 --cherry origin/master...feature", 0, "")
|
||||
},
|
||||
expectedOut: "https://github.com/OWNER/REPO/pull/12\n",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -161,8 +161,8 @@ func NewCmdEdit(f *cmdutil.Factory, runF func(*EditOptions) error) *cobra.Comman
|
|||
cmd.Flags().StringSliceVar(&opts.Editable.Assignees.Remove, "remove-assignee", nil, "Remove assigned users by their `login`. Use \"@me\" to unassign yourself.")
|
||||
cmd.Flags().StringSliceVar(&opts.Editable.Labels.Add, "add-label", nil, "Add labels by `name`")
|
||||
cmd.Flags().StringSliceVar(&opts.Editable.Labels.Remove, "remove-label", nil, "Remove labels by `name`")
|
||||
cmd.Flags().StringSliceVar(&opts.Editable.Projects.Add, "add-project", nil, "Add the pull request to projects by `name`")
|
||||
cmd.Flags().StringSliceVar(&opts.Editable.Projects.Remove, "remove-project", nil, "Remove the pull request from projects by `name`")
|
||||
cmd.Flags().StringSliceVar(&opts.Editable.Projects.Add, "add-project", nil, "Add the pull request to projects by `title`")
|
||||
cmd.Flags().StringSliceVar(&opts.Editable.Projects.Remove, "remove-project", nil, "Remove the pull request from projects by `title`")
|
||||
cmd.Flags().StringVarP(&opts.Editable.Milestone.Value, "milestone", "m", "", "Edit the milestone the pull request belongs to by `name`")
|
||||
cmd.Flags().BoolVar(&removeMilestone, "remove-milestone", false, "Remove the milestone association from the pull request")
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package shared
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
|
|
@ -357,3 +358,26 @@ func TitledEditSurvey(editor Editor) func(string, string) (string, string, error
|
|||
return title, strings.TrimSuffix(body, "\n"), nil
|
||||
}
|
||||
}
|
||||
|
||||
func InitEditorMode(f *cmdutil.Factory, editorMode bool, webMode bool, canPrompt bool) (bool, error) {
|
||||
if err := cmdutil.MutuallyExclusive(
|
||||
"specify only one of `--editor` or `--web`",
|
||||
editorMode,
|
||||
webMode,
|
||||
); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
config, err := f.Config()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
editorMode = !webMode && (editorMode || config.PreferEditorPrompt("").Value == "enabled")
|
||||
|
||||
if editorMode && !canPrompt {
|
||||
return false, errors.New("--editor or enabled prefer_editor_prompt configuration are not supported in non-tty mode")
|
||||
}
|
||||
|
||||
return editorMode, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,6 +116,9 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co
|
|||
Use release notes from a file
|
||||
$ gh release create v1.2.3 -F changelog.md
|
||||
|
||||
Use annotated tag notes
|
||||
$ gh release create v1.2.3 --notes-from-tag
|
||||
|
||||
Don't mark the release as latest
|
||||
$ gh release create v1.2.3 --latest=false
|
||||
|
||||
|
|
@ -516,12 +519,38 @@ func createRun(opts *CreateOptions) error {
|
|||
}
|
||||
|
||||
func gitTagInfo(client *git.Client, tagName string) (string, error) {
|
||||
cmd, err := client.Command(context.Background(), "tag", "--list", tagName, "--format=%(contents:subject)%0a%0a%(contents:body)")
|
||||
contentCmd, err := client.Command(context.Background(), "tag", "--list", tagName, "--format=%(contents)")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
b, err := cmd.Output()
|
||||
return string(b), err
|
||||
content, err := contentCmd.Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// If there is a signature, we should strip it from the end of the content.
|
||||
// Note that, we can achieve this by looking for markers like "-----BEGIN PGP
|
||||
// SIGNATURE-----" and cut the remaining text from the content, but this is
|
||||
// not a safe approach, because, although unlikely, the content can contain
|
||||
// a signature-like section which we shouldn't leave it as is. So, we need
|
||||
// to get the tag signature as a whole, if any, and remote it from the content.
|
||||
signatureCmd, err := client.Command(context.Background(), "tag", "--list", tagName, "--format=%(contents:signature)")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
signature, err := signatureCmd.Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(signature) == 0 {
|
||||
// The tag annotation content has no trailing signature to strip out,
|
||||
// so we return the entire content.
|
||||
return string(content), nil
|
||||
}
|
||||
|
||||
body, _ := strings.CutSuffix(string(content), "\n"+string(signature))
|
||||
return body, nil
|
||||
}
|
||||
|
||||
func detectPreviousTag(client *git.Client, headRef string) (string, error) {
|
||||
|
|
|
|||
|
|
@ -412,6 +412,14 @@ func Test_NewCmdCreate(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_createRun(t *testing.T) {
|
||||
const contentCmd = `git tag --list .* --format=%\(contents\)`
|
||||
const signatureCmd = `git tag --list .* --format=%\(contents:signature\)`
|
||||
|
||||
defaultRunStubs := func(rs *run.CommandStubber) {
|
||||
rs.Register(contentCmd, 0, "")
|
||||
rs.Register(signatureCmd, 0, "")
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
isTTY bool
|
||||
|
|
@ -432,9 +440,7 @@ func Test_createRun(t *testing.T) {
|
|||
BodyProvided: true,
|
||||
Target: "",
|
||||
},
|
||||
runStubs: func(rs *run.CommandStubber) {
|
||||
rs.Register(`git tag --list`, 0, "")
|
||||
},
|
||||
runStubs: defaultRunStubs,
|
||||
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("POST", "repos/OWNER/REPO/releases"), httpmock.RESTPayload(201, `{
|
||||
"url": "https://api.github.com/releases/123",
|
||||
|
|
@ -464,9 +470,7 @@ func Test_createRun(t *testing.T) {
|
|||
Target: "",
|
||||
DiscussionCategory: "General",
|
||||
},
|
||||
runStubs: func(rs *run.CommandStubber) {
|
||||
rs.Register(`git tag --list`, 0, "")
|
||||
},
|
||||
runStubs: defaultRunStubs,
|
||||
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("POST", "repos/OWNER/REPO/releases"), httpmock.RESTPayload(201, `{
|
||||
"url": "https://api.github.com/releases/123",
|
||||
|
|
@ -496,9 +500,7 @@ func Test_createRun(t *testing.T) {
|
|||
BodyProvided: true,
|
||||
Target: "main",
|
||||
},
|
||||
runStubs: func(rs *run.CommandStubber) {
|
||||
rs.Register(`git tag --list`, 0, "")
|
||||
},
|
||||
runStubs: defaultRunStubs,
|
||||
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("POST", "repos/OWNER/REPO/releases"), httpmock.RESTPayload(201, `{
|
||||
"url": "https://api.github.com/releases/123",
|
||||
|
|
@ -527,9 +529,7 @@ func Test_createRun(t *testing.T) {
|
|||
Draft: true,
|
||||
Target: "",
|
||||
},
|
||||
runStubs: func(rs *run.CommandStubber) {
|
||||
rs.Register(`git tag --list`, 0, "")
|
||||
},
|
||||
runStubs: defaultRunStubs,
|
||||
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("POST", "repos/OWNER/REPO/releases"), httpmock.RESTPayload(201, `{
|
||||
"url": "https://api.github.com/releases/123",
|
||||
|
|
@ -558,9 +558,7 @@ func Test_createRun(t *testing.T) {
|
|||
BodyProvided: true,
|
||||
GenerateNotes: false,
|
||||
},
|
||||
runStubs: func(rs *run.CommandStubber) {
|
||||
rs.Register(`git tag --list`, 0, "")
|
||||
},
|
||||
runStubs: defaultRunStubs,
|
||||
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("POST", "repos/OWNER/REPO/releases"), httpmock.RESTPayload(201, `{
|
||||
"url": "https://api.github.com/releases/123",
|
||||
|
|
@ -589,9 +587,7 @@ func Test_createRun(t *testing.T) {
|
|||
BodyProvided: true,
|
||||
GenerateNotes: true,
|
||||
},
|
||||
runStubs: func(rs *run.CommandStubber) {
|
||||
rs.Register(`git tag --list`, 0, "")
|
||||
},
|
||||
runStubs: defaultRunStubs,
|
||||
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("POST", "repos/OWNER/REPO/releases"), httpmock.RESTPayload(201, `{
|
||||
"url": "https://api.github.com/releases/123",
|
||||
|
|
@ -621,9 +617,7 @@ func Test_createRun(t *testing.T) {
|
|||
GenerateNotes: true,
|
||||
NotesStartTag: "v1.1.0",
|
||||
},
|
||||
runStubs: func(rs *run.CommandStubber) {
|
||||
rs.Register(`git tag --list`, 0, "")
|
||||
},
|
||||
runStubs: defaultRunStubs,
|
||||
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("POST", "repos/OWNER/REPO/releases/generate-notes"),
|
||||
httpmock.RESTPayload(200, `{
|
||||
|
|
@ -664,9 +658,7 @@ func Test_createRun(t *testing.T) {
|
|||
GenerateNotes: true,
|
||||
NotesStartTag: "v1.1.0",
|
||||
},
|
||||
runStubs: func(rs *run.CommandStubber) {
|
||||
rs.Register(`git tag --list`, 0, "")
|
||||
},
|
||||
runStubs: defaultRunStubs,
|
||||
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("POST", "repos/OWNER/REPO/releases/generate-notes"),
|
||||
httpmock.RESTPayload(200, `{
|
||||
|
|
@ -715,9 +707,7 @@ func Test_createRun(t *testing.T) {
|
|||
},
|
||||
Concurrency: 1,
|
||||
},
|
||||
runStubs: func(rs *run.CommandStubber) {
|
||||
rs.Register(`git tag --list`, 0, "")
|
||||
},
|
||||
runStubs: defaultRunStubs,
|
||||
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("HEAD", "repos/OWNER/REPO/releases/tags/v1.2.3"), httpmock.StatusStringResponse(404, ``))
|
||||
reg.Register(httpmock.REST("POST", "repos/OWNER/REPO/releases"), httpmock.RESTPayload(201, `{
|
||||
|
|
@ -776,9 +766,7 @@ func Test_createRun(t *testing.T) {
|
|||
},
|
||||
Concurrency: 1,
|
||||
},
|
||||
runStubs: func(rs *run.CommandStubber) {
|
||||
rs.Register(`git tag --list`, 0, "")
|
||||
},
|
||||
runStubs: defaultRunStubs,
|
||||
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("HEAD", "repos/OWNER/REPO/releases/tags/v1.2.3"), httpmock.StatusStringResponse(404, ``))
|
||||
reg.Register(httpmock.REST("POST", "repos/OWNER/REPO/releases"), httpmock.RESTPayload(201, `{
|
||||
|
|
@ -838,9 +826,7 @@ func Test_createRun(t *testing.T) {
|
|||
},
|
||||
Concurrency: 1,
|
||||
},
|
||||
runStubs: func(rs *run.CommandStubber) {
|
||||
rs.Register(`git tag --list`, 0, "")
|
||||
},
|
||||
runStubs: defaultRunStubs,
|
||||
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("HEAD", "repos/OWNER/REPO/releases/tags/v1.2.3"), httpmock.StatusStringResponse(200, ``))
|
||||
},
|
||||
|
|
@ -868,9 +854,7 @@ func Test_createRun(t *testing.T) {
|
|||
},
|
||||
Concurrency: 1,
|
||||
},
|
||||
runStubs: func(rs *run.CommandStubber) {
|
||||
rs.Register(`git tag --list`, 0, "")
|
||||
},
|
||||
runStubs: defaultRunStubs,
|
||||
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("HEAD", "repos/OWNER/REPO/releases/tags/v1.2.3"), httpmock.StatusStringResponse(404, ``))
|
||||
reg.Register(httpmock.REST("POST", "repos/OWNER/REPO/releases"), httpmock.StatusStringResponse(201, `{
|
||||
|
|
@ -905,9 +889,7 @@ func Test_createRun(t *testing.T) {
|
|||
},
|
||||
Concurrency: 1,
|
||||
},
|
||||
runStubs: func(rs *run.CommandStubber) {
|
||||
rs.Register(`git tag --list`, 0, "")
|
||||
},
|
||||
runStubs: defaultRunStubs,
|
||||
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("HEAD", "repos/OWNER/REPO/releases/tags/v1.2.3"), httpmock.StatusStringResponse(404, ``))
|
||||
reg.Register(httpmock.REST("POST", "repos/OWNER/REPO/releases"), httpmock.StatusStringResponse(201, `{
|
||||
|
|
@ -943,9 +925,7 @@ func Test_createRun(t *testing.T) {
|
|||
},
|
||||
Concurrency: 1,
|
||||
},
|
||||
runStubs: func(rs *run.CommandStubber) {
|
||||
rs.Register(`git tag --list`, 0, "")
|
||||
},
|
||||
runStubs: defaultRunStubs,
|
||||
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("HEAD", "repos/OWNER/REPO/releases/tags/v1.2.3"), httpmock.StatusStringResponse(200, ``))
|
||||
},
|
||||
|
|
@ -974,9 +954,7 @@ func Test_createRun(t *testing.T) {
|
|||
DiscussionCategory: "general",
|
||||
Concurrency: 1,
|
||||
},
|
||||
runStubs: func(rs *run.CommandStubber) {
|
||||
rs.Register(`git tag --list`, 0, "")
|
||||
},
|
||||
runStubs: defaultRunStubs,
|
||||
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("HEAD", "repos/OWNER/REPO/releases/tags/v1.2.3"), httpmock.StatusStringResponse(404, ``))
|
||||
reg.Register(httpmock.REST("POST", "repos/OWNER/REPO/releases"), httpmock.RESTPayload(201, `{
|
||||
|
|
@ -1027,7 +1005,8 @@ func Test_createRun(t *testing.T) {
|
|||
NotesFromTag: true,
|
||||
},
|
||||
runStubs: func(rs *run.CommandStubber) {
|
||||
rs.Register(`git tag --list`, 0, "some tag message")
|
||||
rs.Register(contentCmd, 0, "some tag message")
|
||||
rs.Register(signatureCmd, 0, "")
|
||||
},
|
||||
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
|
|
@ -1064,7 +1043,8 @@ func Test_createRun(t *testing.T) {
|
|||
NotesFromTag: true,
|
||||
},
|
||||
runStubs: func(rs *run.CommandStubber) {
|
||||
rs.Register(`git tag --list`, 0, "some tag message")
|
||||
rs.Register(contentCmd, 0, "some tag message")
|
||||
rs.Register(signatureCmd, 0, "")
|
||||
},
|
||||
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
|
|
@ -1099,10 +1079,8 @@ func Test_createRun(t *testing.T) {
|
|||
Assets: []*shared.AssetForUpload(nil),
|
||||
NotesFromTag: true,
|
||||
},
|
||||
runStubs: func(rs *run.CommandStubber) {
|
||||
rs.Register(`git tag --list`, 0, "")
|
||||
},
|
||||
wantErr: "cannot generate release notes from tag v1.2.3 as it does not exist locally",
|
||||
runStubs: defaultRunStubs,
|
||||
wantErr: "cannot generate release notes from tag v1.2.3 as it does not exist locally",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
|
@ -1149,6 +1127,13 @@ func Test_createRun(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_createRun_interactive(t *testing.T) {
|
||||
const contentCmd = `git tag --list .* --format=%\(contents\)`
|
||||
const signatureCmd = `git tag --list .* --format=%\(contents:signature\)`
|
||||
|
||||
defaultRunStubs := func(rs *run.CommandStubber) {
|
||||
rs.Register(contentCmd, 1, "")
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
httpStubs func(*httpmock.Registry)
|
||||
|
|
@ -1185,9 +1170,7 @@ func Test_createRun_interactive(t *testing.T) {
|
|||
return false, nil
|
||||
})
|
||||
},
|
||||
runStubs: func(rs *run.CommandStubber) {
|
||||
rs.Register(`git tag --list`, 1, "")
|
||||
},
|
||||
runStubs: defaultRunStubs,
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", "repos/OWNER/REPO/tags"), httpmock.StatusStringResponse(200, `[
|
||||
{ "name": "v1.2.3" }, { "name": "v1.2.2" }, { "name": "v1.0.0" }, { "name": "v0.1.2" }
|
||||
|
|
@ -1234,9 +1217,7 @@ func Test_createRun_interactive(t *testing.T) {
|
|||
return false, nil
|
||||
})
|
||||
},
|
||||
runStubs: func(rs *run.CommandStubber) {
|
||||
rs.Register(`git tag --list`, 1, "")
|
||||
},
|
||||
runStubs: defaultRunStubs,
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", "repos/OWNER/REPO/tags"), httpmock.StatusStringResponse(200, `[
|
||||
{ "name": "v1.2.2" }, { "name": "v1.0.0" }, { "name": "v0.1.2" }
|
||||
|
|
@ -1283,9 +1264,7 @@ func Test_createRun_interactive(t *testing.T) {
|
|||
return false, nil
|
||||
})
|
||||
},
|
||||
runStubs: func(rs *run.CommandStubber) {
|
||||
rs.Register(`git tag --list`, 1, "")
|
||||
},
|
||||
runStubs: defaultRunStubs,
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", "repos/OWNER/REPO/tags"), httpmock.StatusStringResponse(200, `[
|
||||
{ "name": "v1.2.2" }, { "name": "v1.0.0" }, { "name": "v0.1.2" }
|
||||
|
|
@ -1332,9 +1311,7 @@ func Test_createRun_interactive(t *testing.T) {
|
|||
return false, nil
|
||||
})
|
||||
},
|
||||
runStubs: func(rs *run.CommandStubber) {
|
||||
rs.Register(`git tag --list`, 1, "")
|
||||
},
|
||||
runStubs: defaultRunStubs,
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("POST", "repos/OWNER/REPO/releases/generate-notes"),
|
||||
httpmock.StatusStringResponse(200, `{
|
||||
|
|
@ -1381,7 +1358,7 @@ func Test_createRun_interactive(t *testing.T) {
|
|||
})
|
||||
},
|
||||
runStubs: func(rs *run.CommandStubber) {
|
||||
rs.Register(`git tag --list`, 1, "")
|
||||
defaultRunStubs(rs)
|
||||
rs.Register(`git describe --tags --abbrev=0 HEAD\^`, 0, "v1.2.2\n")
|
||||
rs.Register(`git .+log .+v1\.2\.2\.\.HEAD$`, 0, "commit subject\n\ncommit body\n")
|
||||
},
|
||||
|
|
@ -1427,7 +1404,8 @@ func Test_createRun_interactive(t *testing.T) {
|
|||
})
|
||||
},
|
||||
runStubs: func(rs *run.CommandStubber) {
|
||||
rs.Register(`git tag --list`, 0, "hello from annotated tag")
|
||||
rs.Register(contentCmd, 0, "hello from annotated tag")
|
||||
rs.Register(signatureCmd, 0, "")
|
||||
rs.Register(`git describe --tags --abbrev=0 v1\.2\.3\^`, 1, "")
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
|
|
@ -1456,7 +1434,8 @@ func Test_createRun_interactive(t *testing.T) {
|
|||
TagName: "v1.2.3",
|
||||
},
|
||||
runStubs: func(rs *run.CommandStubber) {
|
||||
rs.Register(`git tag --list`, 0, "tag exists")
|
||||
rs.Register(contentCmd, 0, "tag exists")
|
||||
rs.Register(signatureCmd, 0, "")
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.GraphQL("RepositoryFindRef"),
|
||||
|
|
@ -1489,7 +1468,8 @@ func Test_createRun_interactive(t *testing.T) {
|
|||
})
|
||||
},
|
||||
runStubs: func(rs *run.CommandStubber) {
|
||||
rs.Register(`git tag --list`, 0, "tag exists")
|
||||
rs.Register(contentCmd, 0, "tag exists")
|
||||
rs.Register(signatureCmd, 0, "")
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("POST", "repos/OWNER/REPO/releases/generate-notes"),
|
||||
|
|
@ -1536,9 +1516,7 @@ func Test_createRun_interactive(t *testing.T) {
|
|||
return false, nil
|
||||
})
|
||||
},
|
||||
runStubs: func(rs *run.CommandStubber) {
|
||||
rs.Register(`git tag --list`, 1, "")
|
||||
},
|
||||
runStubs: defaultRunStubs,
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("POST", "repos/OWNER/REPO/releases/generate-notes"),
|
||||
httpmock.RESTPayload(200, `{
|
||||
|
|
@ -1591,7 +1569,7 @@ func Test_createRun_interactive(t *testing.T) {
|
|||
})
|
||||
},
|
||||
runStubs: func(rs *run.CommandStubber) {
|
||||
rs.Register(`git tag --list`, 1, "")
|
||||
defaultRunStubs(rs)
|
||||
rs.Register(`git .+log .+v1\.1\.0\.\.HEAD$`, 0, "commit subject\n\ncommit body\n")
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
|
|
@ -1639,7 +1617,8 @@ func Test_createRun_interactive(t *testing.T) {
|
|||
})
|
||||
},
|
||||
runStubs: func(rs *run.CommandStubber) {
|
||||
rs.Register(`git tag --list`, 0, "tag exists")
|
||||
rs.Register(contentCmd, 0, "tag exists")
|
||||
rs.Register(signatureCmd, 0, "")
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.GraphQL("RepositoryFindRef"),
|
||||
|
|
@ -1751,6 +1730,106 @@ func Test_createRun_interactive(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func Test_gitTagInfo(t *testing.T) {
|
||||
const tagName = "foo"
|
||||
const contentCmd = `git tag --list foo --format=%\(contents\)`
|
||||
const signatureCmd = `git tag --list foo --format=%\(contents:signature\)`
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
runStubs func(*run.CommandStubber)
|
||||
wantErr string
|
||||
wantResult string
|
||||
}{
|
||||
{
|
||||
name: "no signature",
|
||||
runStubs: func(cs *run.CommandStubber) {
|
||||
cs.Register(contentCmd, 0, "some\nmultiline\ncontent")
|
||||
cs.Register(signatureCmd, 0, "")
|
||||
},
|
||||
wantResult: "some\nmultiline\ncontent",
|
||||
},
|
||||
{
|
||||
name: "with signature (PGP)",
|
||||
runStubs: func(cs *run.CommandStubber) {
|
||||
cs.Register(contentCmd, 0, "some\nmultiline\ncontent\n-----BEGIN PGP SIGNATURE-----\n\nfoo\n-----END PGP SIGNATURE-----")
|
||||
cs.Register(signatureCmd, 0, "-----BEGIN PGP SIGNATURE-----\n\nfoo\n-----END PGP SIGNATURE-----")
|
||||
},
|
||||
wantResult: "some\nmultiline\ncontent",
|
||||
},
|
||||
{
|
||||
name: "with signature (PGP, RFC1991)",
|
||||
runStubs: func(cs *run.CommandStubber) {
|
||||
cs.Register(contentCmd, 0, "some\nmultiline\ncontent\n-----BEGIN PGP MESSAGE-----\n\nfoo\n-----END PGP MESSAGE-----")
|
||||
cs.Register(signatureCmd, 0, "-----BEGIN PGP MESSAGE-----\n\nfoo\n-----END PGP MESSAGE-----")
|
||||
},
|
||||
wantResult: "some\nmultiline\ncontent",
|
||||
},
|
||||
{
|
||||
name: "with signature (SSH)",
|
||||
runStubs: func(cs *run.CommandStubber) {
|
||||
cs.Register(contentCmd, 0, "some\nmultiline\ncontent\n-----BEGIN SSH SIGNATURE-----\nfoo\n-----END SSH SIGNATURE-----")
|
||||
cs.Register(signatureCmd, 0, "-----BEGIN SSH SIGNATURE-----\nfoo\n-----END SSH SIGNATURE-----")
|
||||
},
|
||||
wantResult: "some\nmultiline\ncontent",
|
||||
},
|
||||
{
|
||||
name: "with signature (X.509)",
|
||||
runStubs: func(cs *run.CommandStubber) {
|
||||
cs.Register(contentCmd, 0, "some\nmultiline\ncontent\n-----BEGIN SIGNED MESSAGE-----\nfoo\n-----END SIGNED MESSAGE-----")
|
||||
cs.Register(signatureCmd, 0, "-----BEGIN SIGNED MESSAGE-----\nfoo\n-----END SIGNED MESSAGE-----")
|
||||
},
|
||||
wantResult: "some\nmultiline\ncontent",
|
||||
},
|
||||
{
|
||||
name: "with signature in content but not as true signature",
|
||||
runStubs: func(cs *run.CommandStubber) {
|
||||
cs.Register(contentCmd, 0, "some\nmultiline\ncontent\n-----BEGIN PGP SIGNATURE-----\n\nfoo\n-----END PGP SIGNATURE-----")
|
||||
cs.Register(signatureCmd, 0, "")
|
||||
},
|
||||
wantResult: "some\nmultiline\ncontent\n-----BEGIN PGP SIGNATURE-----\n\nfoo\n-----END PGP SIGNATURE-----",
|
||||
},
|
||||
{
|
||||
name: "error getting content",
|
||||
runStubs: func(cs *run.CommandStubber) {
|
||||
cs.Register(contentCmd, 1, "some error")
|
||||
},
|
||||
wantErr: fmt.Sprintf("failed to run git: %s exited with status 1", contentCmd),
|
||||
},
|
||||
{
|
||||
name: "error getting signature",
|
||||
runStubs: func(cs *run.CommandStubber) {
|
||||
cs.Register(contentCmd, 0, "whatever")
|
||||
cs.Register(signatureCmd, 1, "some error")
|
||||
},
|
||||
wantErr: fmt.Sprintf("failed to run git: %s exited with status 1", signatureCmd),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gitClient := &git.Client{GitPath: "some/path/git"}
|
||||
|
||||
rs, teardown := run.Stub()
|
||||
defer teardown(t)
|
||||
if tt.runStubs != nil {
|
||||
tt.runStubs(rs)
|
||||
}
|
||||
|
||||
result, err := gitTagInfo(gitClient, tagName)
|
||||
|
||||
if tt.wantErr != "" {
|
||||
require.EqualError(t, err, tt.wantErr)
|
||||
return
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
assert.Equal(t, tt.wantResult, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func boolPtr(b bool) *bool {
|
||||
return &b
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,12 +121,11 @@ func setDefaultRun(opts *SetDefaultOptions) error {
|
|||
if opts.ViewMode {
|
||||
if currentDefaultRepo != nil {
|
||||
fmt.Fprintln(opts.IO.Out, displayRemoteRepoName(currentDefaultRepo))
|
||||
} else if opts.IO.IsStdoutTTY() {
|
||||
fmt.Fprintln(opts.IO.Out, "no default repository has been set; use `gh repo set-default` to select one")
|
||||
} else {
|
||||
fmt.Fprintln(opts.IO.ErrOut, "no default repository has been set; use `gh repo set-default` to select one")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
cs := opts.IO.ColorScheme()
|
||||
|
||||
if opts.UnsetMode {
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ func TestDefaultRun(t *testing.T) {
|
|||
gitStubs func(*run.CommandStubber)
|
||||
prompterStubs func(*prompter.PrompterMock)
|
||||
wantStdout string
|
||||
wantStderr string
|
||||
wantErr bool
|
||||
errMsg string
|
||||
}{
|
||||
|
|
@ -175,10 +176,11 @@ func TestDefaultRun(t *testing.T) {
|
|||
Repo: repo1,
|
||||
},
|
||||
},
|
||||
wantStdout: "no default repository has been set; use `gh repo set-default` to select one\n",
|
||||
wantStderr: "no default repository has been set; use `gh repo set-default` to select one\n",
|
||||
},
|
||||
{
|
||||
name: "view mode no current default",
|
||||
tty: false,
|
||||
opts: SetDefaultOptions{ViewMode: true},
|
||||
remotes: []*context.Remote{
|
||||
{
|
||||
|
|
@ -186,6 +188,7 @@ func TestDefaultRun(t *testing.T) {
|
|||
Repo: repo1,
|
||||
},
|
||||
},
|
||||
wantStderr: "no default repository has been set; use `gh repo set-default` to select one\n",
|
||||
},
|
||||
{
|
||||
name: "view mode with base resolved current default",
|
||||
|
|
@ -466,7 +469,7 @@ func TestDefaultRun(t *testing.T) {
|
|||
return &http.Client{Transport: reg}, nil
|
||||
}
|
||||
|
||||
io, _, stdout, _ := iostreams.Test()
|
||||
io, _, stdout, stderr := iostreams.Test()
|
||||
io.SetStdinTTY(tt.tty)
|
||||
io.SetStdoutTTY(tt.tty)
|
||||
io.SetStderrTTY(tt.tty)
|
||||
|
|
@ -498,7 +501,11 @@ func TestDefaultRun(t *testing.T) {
|
|||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.wantStdout, stdout.String())
|
||||
if tt.wantStdout != "" {
|
||||
assert.Equal(t, tt.wantStdout, stdout.String())
|
||||
} else {
|
||||
assert.Equal(t, tt.wantStderr, stderr.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,13 +45,15 @@ func NewCmdView(f *cmdutil.Factory, runF func(*ViewOptions) error) *cobra.Comman
|
|||
cmd := &cobra.Command{
|
||||
Use: "view [<repository>]",
|
||||
Short: "View a repository",
|
||||
Long: `Display the description and the README of a GitHub repository.
|
||||
Long: heredoc.Docf(`
|
||||
Display the description and the README of a GitHub repository.
|
||||
|
||||
With no argument, the repository for the current directory is displayed.
|
||||
With no argument, the repository for the current directory is displayed.
|
||||
|
||||
With '--web', open the repository in a web browser instead.
|
||||
With %[1]s--web%[1]s, open the repository in a web browser instead.
|
||||
|
||||
With '--branch', view a specific branch of the repository.`,
|
||||
With %[1]s--branch%[1]s, view a specific branch of the repository.
|
||||
`, "`"),
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
RunE: func(c *cobra.Command, args []string) error {
|
||||
if len(args) > 0 {
|
||||
|
|
|
|||
|
|
@ -42,13 +42,17 @@ func NewCmdDownload(f *cmdutil.Factory, runF func(*DownloadOptions) error) *cobr
|
|||
cmd := &cobra.Command{
|
||||
Use: "download [<run-id>]",
|
||||
Short: "Download artifacts generated by a workflow run",
|
||||
Long: heredoc.Doc(`
|
||||
Long: heredoc.Docf(`
|
||||
Download artifacts generated by a GitHub Actions workflow run.
|
||||
|
||||
The contents of each artifact will be extracted under separate directories based on
|
||||
the artifact name. If only a single artifact is specified, it will be extracted into
|
||||
the current directory.
|
||||
`),
|
||||
|
||||
By default, this command downloads the latest artifact created and uploaded through
|
||||
GitHub Actions. Because workflows can delete or overwrite artifacts, %[1]s<run-id>%[1]s
|
||||
must be used to select an artifact from a specific workflow run.
|
||||
`, "`"),
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
Example: heredoc.Doc(`
|
||||
# Download all artifacts generated by a workflow run
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue