Merge branch 'trunk' into gh-search-code
This commit is contained in:
commit
99cff8f8eb
24 changed files with 810 additions and 75 deletions
|
|
@ -39,6 +39,8 @@ type PullRequest struct {
|
|||
ClosedAt *time.Time
|
||||
MergedAt *time.Time
|
||||
|
||||
AutoMergeRequest *AutoMergeRequest
|
||||
|
||||
MergeCommit *Commit
|
||||
PotentialMergeCommit *Commit
|
||||
|
||||
|
|
@ -136,6 +138,16 @@ type PRRepository struct {
|
|||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type AutoMergeRequest struct {
|
||||
AuthorEmail *string `json:"authorEmail"`
|
||||
CommitBody *string `json:"commitBody"`
|
||||
CommitHeadline *string `json:"commitHeadline"`
|
||||
// MERGE, REBASE, SQUASH
|
||||
MergeMethod string `json:"mergeMethod"`
|
||||
EnabledAt time.Time `json:"enabledAt"`
|
||||
EnabledBy Author `json:"enabledBy"`
|
||||
}
|
||||
|
||||
// Commit loads just the commit SHA and nothing else
|
||||
type Commit struct {
|
||||
OID string `json:"oid"`
|
||||
|
|
|
|||
|
|
@ -132,6 +132,17 @@ var prCommits = shortenQuery(`
|
|||
}
|
||||
`)
|
||||
|
||||
var autoMergeRequest = shortenQuery(`
|
||||
autoMergeRequest {
|
||||
authorEmail,
|
||||
commitBody,
|
||||
commitHeadline,
|
||||
mergeMethod,
|
||||
enabledAt,
|
||||
enabledBy{login,...on User{id,name}}
|
||||
}
|
||||
`)
|
||||
|
||||
func StatusCheckRollupGraphQL(after string) string {
|
||||
var afterClause string
|
||||
if after != "" {
|
||||
|
|
@ -231,6 +242,7 @@ var IssueFields = []string{
|
|||
|
||||
var PullRequestFields = append(IssueFields,
|
||||
"additions",
|
||||
"autoMergeRequest",
|
||||
"baseRefName",
|
||||
"changedFiles",
|
||||
"commits",
|
||||
|
|
@ -285,6 +297,8 @@ func IssueGraphQL(fields []string) string {
|
|||
q = append(q, `mergeCommit{oid}`)
|
||||
case "potentialMergeCommit":
|
||||
q = append(q, `potentialMergeCommit{oid}`)
|
||||
case "autoMergeRequest":
|
||||
q = append(q, autoMergeRequest)
|
||||
case "comments":
|
||||
q = append(q, issueComments)
|
||||
case "lastComment": // pseudo-field
|
||||
|
|
|
|||
|
|
@ -128,11 +128,18 @@ func (a *API) GetUser(ctx context.Context) (*User, error) {
|
|||
return &response, nil
|
||||
}
|
||||
|
||||
// RepositoryOwner represents owner of a repository
|
||||
type RepositoryOwner struct {
|
||||
Type string `json:"type"`
|
||||
Login string `json:"login"`
|
||||
}
|
||||
|
||||
// Repository represents a GitHub repository.
|
||||
type Repository struct {
|
||||
ID int `json:"id"`
|
||||
FullName string `json:"full_name"`
|
||||
DefaultBranch string `json:"default_branch"`
|
||||
ID int `json:"id"`
|
||||
FullName string `json:"full_name"`
|
||||
DefaultBranch string `json:"default_branch"`
|
||||
Owner RepositoryOwner `json:"owner"`
|
||||
}
|
||||
|
||||
// GetRepository returns the repository associated with the given owner and name.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.28.1
|
||||
// protoc v3.21.12
|
||||
// protoc v3.12.4
|
||||
// source: codespace/codespace_host_service.v1.proto
|
||||
|
||||
package codespace
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.2.0
|
||||
// - protoc v3.21.12
|
||||
// - protoc v3.12.4
|
||||
// source: codespace/codespace_host_service.v1.proto
|
||||
|
||||
package codespace
|
||||
|
|
|
|||
|
|
@ -20,11 +20,11 @@ function generate {
|
|||
|
||||
local contract="$dir/$proto"
|
||||
|
||||
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative "$contract"
|
||||
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative "$contract" --experimental_allow_proto3_optional
|
||||
echo "Generated protocol buffers for $contract"
|
||||
|
||||
services=$(cat "$contract" | grep -Eo "service .+ {" | awk '{print $2 "Server"}')
|
||||
moq -out $contract.mock.go $dir $services
|
||||
services=$(grep -Eo "service .+ {" <$contract | awk '{print $2 "Server"}')
|
||||
moq -out "$contract.mock.go" "$dir" "$services"
|
||||
echo "Generated mock protocols for $contract"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.28.1
|
||||
// protoc v3.21.12
|
||||
// protoc v3.12.4
|
||||
// source: jupyter/jupyter_server_host_service.v1.proto
|
||||
|
||||
package jupyter
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.2.0
|
||||
// - protoc v3.21.12
|
||||
// - protoc v3.12.4
|
||||
// source: jupyter/jupyter_server_host_service.v1.proto
|
||||
|
||||
package jupyter
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.28.1
|
||||
// protoc v3.21.12
|
||||
// protoc v3.12.4
|
||||
// source: ssh/ssh_server_host_service.v1.proto
|
||||
|
||||
package ssh
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.2.0
|
||||
// - protoc v3.21.12
|
||||
// - protoc v3.12.4
|
||||
// source: ssh/ssh_server_host_service.v1.proto
|
||||
|
||||
package ssh
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package alias
|
|||
import (
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
deleteCmd "github.com/cli/cli/v2/pkg/cmd/alias/delete"
|
||||
importCmd "github.com/cli/cli/v2/pkg/cmd/alias/imports"
|
||||
listCmd "github.com/cli/cli/v2/pkg/cmd/alias/list"
|
||||
setCmd "github.com/cli/cli/v2/pkg/cmd/alias/set"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
|
|
@ -23,6 +24,7 @@ func NewCmdAlias(f *cmdutil.Factory) *cobra.Command {
|
|||
cmdutil.DisableAuthCheck(cmd)
|
||||
|
||||
cmd.AddCommand(deleteCmd.NewCmdDelete(f, nil))
|
||||
cmd.AddCommand(importCmd.NewCmdImport(f, nil))
|
||||
cmd.AddCommand(listCmd.NewCmdList(f, nil))
|
||||
cmd.AddCommand(setCmd.NewCmdSet(f, nil))
|
||||
|
||||
|
|
|
|||
195
pkg/cmd/alias/imports/import.go
Normal file
195
pkg/cmd/alias/imports/import.go
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
package imports
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/pkg/cmd/alias/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/spf13/cobra"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type ImportOptions struct {
|
||||
Config func() (config.Config, error)
|
||||
IO *iostreams.IOStreams
|
||||
|
||||
Filename string
|
||||
OverwriteExisting bool
|
||||
|
||||
existingCommand func(string) bool
|
||||
}
|
||||
|
||||
func NewCmdImport(f *cmdutil.Factory, runF func(*ImportOptions) error) *cobra.Command {
|
||||
opts := &ImportOptions{
|
||||
IO: f.IOStreams,
|
||||
Config: f.Config,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "import [<filename> | -]",
|
||||
Short: "Import aliases from a YAML file",
|
||||
Long: heredoc.Doc(`
|
||||
Import aliases from the contents of a YAML file.
|
||||
|
||||
Aliases should be defined as a map in YAML, where the keys represent aliases and
|
||||
the values represent the corresponding expansions. An example file should look like
|
||||
the following:
|
||||
|
||||
bugs: issue list --label=bug
|
||||
igrep: '!gh issue list --label="$1" | grep "$2"'
|
||||
features: |-
|
||||
issue list
|
||||
--label=enhancement
|
||||
|
||||
Use "-" to read aliases (in YAML format) from standard input.
|
||||
|
||||
The output from the gh command "alias list" can be used to produce a YAML file
|
||||
containing your aliases, which you can use to import them from one machine to
|
||||
another. Run "gh help alias list" to learn more.
|
||||
`),
|
||||
Example: heredoc.Doc(`
|
||||
# Import aliases from a file
|
||||
$ gh alias import aliases.yml
|
||||
|
||||
# Import aliases from standard input
|
||||
$ gh alias import -
|
||||
`),
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) > 1 {
|
||||
return cmdutil.FlagErrorf("too many arguments")
|
||||
}
|
||||
if len(args) == 0 && opts.IO.IsStdinTTY() {
|
||||
return cmdutil.FlagErrorf("no filename passed and nothing on STDIN")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.Filename = "-"
|
||||
if len(args) > 0 {
|
||||
opts.Filename = args[0]
|
||||
}
|
||||
|
||||
opts.existingCommand = shared.ExistingCommandFunc(f, cmd)
|
||||
|
||||
if runF != nil {
|
||||
return runF(opts)
|
||||
}
|
||||
|
||||
return importRun(opts)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVar(&opts.OverwriteExisting, "clobber", false, "Overwrite existing aliases of the same name")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func importRun(opts *ImportOptions) error {
|
||||
cs := opts.IO.ColorScheme()
|
||||
cfg, err := opts.Config()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aliasCfg := cfg.Aliases()
|
||||
|
||||
b, err := cmdutil.ReadFile(opts.Filename, opts.IO.In)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aliasMap := map[string]string{}
|
||||
if err = yaml.Unmarshal(b, &aliasMap); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isTerminal := opts.IO.IsStdoutTTY()
|
||||
if isTerminal {
|
||||
if opts.Filename == "-" {
|
||||
fmt.Fprintf(opts.IO.ErrOut, "- Importing aliases from standard input\n")
|
||||
} else {
|
||||
fmt.Fprintf(opts.IO.ErrOut, "- Importing aliases from file %q\n", opts.Filename)
|
||||
}
|
||||
}
|
||||
|
||||
var msg strings.Builder
|
||||
|
||||
for _, alias := range getSortedKeys(aliasMap) {
|
||||
if opts.existingCommand(alias) {
|
||||
msg.WriteString(
|
||||
fmt.Sprintf("%s Could not import alias %s: already a gh command\n",
|
||||
cs.FailureIcon(),
|
||||
cs.Bold(alias),
|
||||
),
|
||||
)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
expansion := aliasMap[alias]
|
||||
|
||||
if !(strings.HasPrefix(expansion, "!") || opts.existingCommand(expansion)) {
|
||||
msg.WriteString(
|
||||
fmt.Sprintf("%s Could not import alias %s: expansion does not correspond to a gh command\n",
|
||||
cs.FailureIcon(),
|
||||
cs.Bold(alias),
|
||||
),
|
||||
)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err := aliasCfg.Get(alias); err == nil {
|
||||
if opts.OverwriteExisting {
|
||||
aliasCfg.Add(alias, expansion)
|
||||
|
||||
msg.WriteString(
|
||||
fmt.Sprintf("%s Changed alias %s\n",
|
||||
cs.WarningIcon(),
|
||||
cs.Bold(alias),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
msg.WriteString(
|
||||
fmt.Sprintf("%s Could not import alias %s: name already taken\n",
|
||||
cs.FailureIcon(),
|
||||
cs.Bold(alias),
|
||||
),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
aliasCfg.Add(alias, expansion)
|
||||
|
||||
msg.WriteString(
|
||||
fmt.Sprintf("%s Added alias %s\n",
|
||||
cs.SuccessIcon(),
|
||||
cs.Bold(alias),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if err := cfg.Write(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isTerminal {
|
||||
fmt.Fprintln(opts.IO.ErrOut, msg.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getSortedKeys(m map[string]string) []string {
|
||||
keys := []string{}
|
||||
for key := range m {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
||||
346
pkg/cmd/alias/imports/import_test.go
Normal file
346
pkg/cmd/alias/imports/import_test.go
Normal file
|
|
@ -0,0 +1,346 @@
|
|||
package imports
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/pkg/cmd/alias/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/extensions"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/google/shlex"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewCmdImport(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cli string
|
||||
tty bool
|
||||
wants ImportOptions
|
||||
wantsError string
|
||||
}{
|
||||
{
|
||||
name: "no filename and stdin tty",
|
||||
cli: "",
|
||||
tty: true,
|
||||
wants: ImportOptions{
|
||||
Filename: "",
|
||||
OverwriteExisting: false,
|
||||
},
|
||||
wantsError: "no filename passed and nothing on STDIN",
|
||||
},
|
||||
{
|
||||
name: "no filename and stdin is not tty",
|
||||
cli: "",
|
||||
tty: false,
|
||||
wants: ImportOptions{
|
||||
Filename: "-",
|
||||
OverwriteExisting: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "stdin arg",
|
||||
cli: "-",
|
||||
wants: ImportOptions{
|
||||
Filename: "-",
|
||||
OverwriteExisting: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple filenames",
|
||||
cli: "aliases1 aliases2",
|
||||
wants: ImportOptions{
|
||||
Filename: "aliases1 aliases2",
|
||||
OverwriteExisting: false,
|
||||
},
|
||||
wantsError: "too many arguments",
|
||||
},
|
||||
{
|
||||
name: "clobber flag",
|
||||
cli: "aliases --clobber",
|
||||
wants: ImportOptions{
|
||||
Filename: "aliases",
|
||||
OverwriteExisting: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ios, _, _, _ := iostreams.Test()
|
||||
ios.SetStdinTTY(tt.tty)
|
||||
f := &cmdutil.Factory{IOStreams: ios}
|
||||
|
||||
argv, err := shlex.Split(tt.cli)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var gotOpts *ImportOptions
|
||||
cmd := NewCmdImport(f, func(opts *ImportOptions) error {
|
||||
gotOpts = opts
|
||||
return nil
|
||||
})
|
||||
cmd.SetArgs(argv)
|
||||
cmd.SetIn(&bytes.Buffer{})
|
||||
cmd.SetOut(io.Discard)
|
||||
cmd.SetErr(io.Discard)
|
||||
|
||||
_, err = cmd.ExecuteC()
|
||||
if tt.wantsError != "" {
|
||||
assert.EqualError(t, err, tt.wantsError)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tt.wants.Filename, gotOpts.Filename)
|
||||
assert.Equal(t, tt.wants.OverwriteExisting, gotOpts.OverwriteExisting)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportRun(t *testing.T) {
|
||||
tmpFile := filepath.Join(t.TempDir(), "aliases.yml")
|
||||
importFileMsg := fmt.Sprintf(`- Importing aliases from file %q`, tmpFile)
|
||||
importStdinMsg := "- Importing aliases from standard input"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
opts *ImportOptions
|
||||
stdin string
|
||||
fileContents string
|
||||
initConfig string
|
||||
wantConfig string
|
||||
wantStderr string
|
||||
}{
|
||||
{
|
||||
name: "with no existing aliases",
|
||||
opts: &ImportOptions{
|
||||
Filename: tmpFile,
|
||||
OverwriteExisting: false,
|
||||
},
|
||||
fileContents: heredoc.Doc(`
|
||||
co: pr checkout
|
||||
igrep: '!gh issue list --label="$1" | grep "$2"'
|
||||
`),
|
||||
wantConfig: heredoc.Doc(`
|
||||
aliases:
|
||||
co: pr checkout
|
||||
igrep: '!gh issue list --label="$1" | grep "$2"'
|
||||
`),
|
||||
wantStderr: importFileMsg + "\n✓ Added alias co\n✓ Added alias igrep\n\n",
|
||||
},
|
||||
{
|
||||
name: "with existing aliases",
|
||||
opts: &ImportOptions{
|
||||
Filename: tmpFile,
|
||||
OverwriteExisting: false,
|
||||
},
|
||||
fileContents: heredoc.Doc(`
|
||||
users: |-
|
||||
api graphql -F name="$1" -f query='
|
||||
query ($name: String!) {
|
||||
user(login: $name) {
|
||||
name
|
||||
}
|
||||
}'
|
||||
co: pr checkout
|
||||
`),
|
||||
initConfig: heredoc.Doc(`
|
||||
aliases:
|
||||
igrep: '!gh issue list --label="$1" | grep "$2"'
|
||||
editor: vim
|
||||
`),
|
||||
wantConfig: heredoc.Doc(`
|
||||
aliases:
|
||||
igrep: '!gh issue list --label="$1" | grep "$2"'
|
||||
co: pr checkout
|
||||
users: |-
|
||||
api graphql -F name="$1" -f query='
|
||||
query ($name: String!) {
|
||||
user(login: $name) {
|
||||
name
|
||||
}
|
||||
}'
|
||||
editor: vim
|
||||
`),
|
||||
wantStderr: importFileMsg + "\n✓ Added alias co\n✓ Added alias users\n\n",
|
||||
},
|
||||
{
|
||||
name: "from stdin",
|
||||
opts: &ImportOptions{
|
||||
Filename: "-",
|
||||
OverwriteExisting: false,
|
||||
},
|
||||
stdin: heredoc.Doc(`
|
||||
co: pr checkout
|
||||
features: |-
|
||||
issue list
|
||||
--label=enhancement
|
||||
igrep: '!gh issue list --label="$1" | grep "$2"'
|
||||
`),
|
||||
wantConfig: heredoc.Doc(`
|
||||
aliases:
|
||||
co: pr checkout
|
||||
features: |-
|
||||
issue list
|
||||
--label=enhancement
|
||||
igrep: '!gh issue list --label="$1" | grep "$2"'
|
||||
`),
|
||||
wantStderr: importStdinMsg + "\n✓ Added alias co\n✓ Added alias features\n✓ Added alias igrep\n\n",
|
||||
},
|
||||
{
|
||||
name: "already taken aliases",
|
||||
opts: &ImportOptions{
|
||||
Filename: tmpFile,
|
||||
OverwriteExisting: false,
|
||||
},
|
||||
fileContents: heredoc.Doc(`
|
||||
co: pr checkout -R cool/repo
|
||||
igrep: '!gh issue list --label="$1" | grep "$2"'
|
||||
`),
|
||||
initConfig: heredoc.Doc(`
|
||||
aliases:
|
||||
co: pr checkout
|
||||
editor: vim
|
||||
`),
|
||||
wantConfig: heredoc.Doc(`
|
||||
aliases:
|
||||
co: pr checkout
|
||||
igrep: '!gh issue list --label="$1" | grep "$2"'
|
||||
editor: vim
|
||||
`),
|
||||
wantStderr: importFileMsg + "\nX Could not import alias co: name already taken\n✓ Added alias igrep\n\n",
|
||||
},
|
||||
{
|
||||
name: "override aliases",
|
||||
opts: &ImportOptions{
|
||||
Filename: tmpFile,
|
||||
OverwriteExisting: true,
|
||||
},
|
||||
fileContents: heredoc.Doc(`
|
||||
co: pr checkout -R cool/repo
|
||||
igrep: '!gh issue list --label="$1" | grep "$2"'
|
||||
`),
|
||||
initConfig: heredoc.Doc(`
|
||||
aliases:
|
||||
co: pr checkout
|
||||
editor: vim
|
||||
`),
|
||||
wantConfig: heredoc.Doc(`
|
||||
aliases:
|
||||
co: pr checkout -R cool/repo
|
||||
igrep: '!gh issue list --label="$1" | grep "$2"'
|
||||
editor: vim
|
||||
`),
|
||||
wantStderr: importFileMsg + "\n! Changed alias co\n✓ Added alias igrep\n\n",
|
||||
},
|
||||
{
|
||||
name: "alias is a gh command",
|
||||
opts: &ImportOptions{
|
||||
Filename: tmpFile,
|
||||
OverwriteExisting: false,
|
||||
},
|
||||
fileContents: heredoc.Doc(`
|
||||
pr: pr checkout
|
||||
issue: issue list
|
||||
api: api graphql
|
||||
`),
|
||||
wantStderr: strings.Join(
|
||||
[]string{
|
||||
importFileMsg,
|
||||
"X Could not import alias api: already a gh command",
|
||||
"X Could not import alias issue: already a gh command",
|
||||
"X Could not import alias pr: already a gh command\n\n",
|
||||
},
|
||||
"\n",
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "invalid expansion",
|
||||
opts: &ImportOptions{
|
||||
Filename: tmpFile,
|
||||
OverwriteExisting: false,
|
||||
},
|
||||
fileContents: heredoc.Doc(`
|
||||
alias1:
|
||||
alias2: ps checkout
|
||||
`),
|
||||
wantStderr: strings.Join(
|
||||
[]string{
|
||||
importFileMsg,
|
||||
"X Could not import alias alias1: expansion does not correspond to a gh command",
|
||||
"X Could not import alias alias2: expansion does not correspond to a gh command\n\n",
|
||||
},
|
||||
"\n",
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.fileContents != "" {
|
||||
err := os.WriteFile(tmpFile, []byte(tt.fileContents), 0600)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
ios, stdin, _, stderr := iostreams.Test()
|
||||
ios.SetStdinTTY(true)
|
||||
ios.SetStdoutTTY(true)
|
||||
ios.SetStderrTTY(true)
|
||||
tt.opts.IO = ios
|
||||
|
||||
readConfigs := config.StubWriteConfig(t)
|
||||
cfg := config.NewFromString(tt.initConfig)
|
||||
tt.opts.Config = func() (config.Config, error) {
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// Create fake command factory for testing.
|
||||
f := &cmdutil.Factory{
|
||||
IOStreams: ios,
|
||||
ExtensionManager: &extensions.ExtensionManagerMock{
|
||||
ListFunc: func() []extensions.Extension {
|
||||
return []extensions.Extension{}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Create fake command structure for testing.
|
||||
rootCmd := &cobra.Command{}
|
||||
prCmd := &cobra.Command{Use: "pr"}
|
||||
prCmd.AddCommand(&cobra.Command{Use: "checkout"})
|
||||
prCmd.AddCommand(&cobra.Command{Use: "status"})
|
||||
rootCmd.AddCommand(prCmd)
|
||||
issueCmd := &cobra.Command{Use: "issue"}
|
||||
issueCmd.AddCommand(&cobra.Command{Use: "list"})
|
||||
rootCmd.AddCommand(issueCmd)
|
||||
apiCmd := &cobra.Command{Use: "api"}
|
||||
apiCmd.AddCommand(&cobra.Command{Use: "graphql"})
|
||||
rootCmd.AddCommand(apiCmd)
|
||||
|
||||
tt.opts.existingCommand = shared.ExistingCommandFunc(f, rootCmd)
|
||||
|
||||
if tt.stdin != "" {
|
||||
stdin.WriteString(tt.stdin)
|
||||
}
|
||||
|
||||
err := importRun(tt.opts)
|
||||
require.NoError(t, err)
|
||||
|
||||
configOut := bytes.Buffer{}
|
||||
readConfigs(&configOut, io.Discard)
|
||||
|
||||
assert.Equal(t, tt.wantStderr, stderr.String())
|
||||
assert.Equal(t, tt.wantConfig, configOut.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -7,9 +7,9 @@ import (
|
|||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/pkg/cmd/alias/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/google/shlex"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ type SetOptions struct {
|
|||
Expansion string
|
||||
IsShell bool
|
||||
|
||||
validCommand func(string) bool
|
||||
existingCommand func(string) bool
|
||||
}
|
||||
|
||||
func NewCmdSet(f *cmdutil.Factory, runF func(*SetOptions) error) *cobra.Command {
|
||||
|
|
@ -70,29 +70,12 @@ func NewCmdSet(f *cmdutil.Factory, runF func(*SetOptions) error) *cobra.Command
|
|||
opts.Name = args[0]
|
||||
opts.Expansion = args[1]
|
||||
|
||||
opts.validCommand = func(args string) bool {
|
||||
split, err := shlex.Split(args)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
rootCmd := cmd.Root()
|
||||
cmd, _, err := rootCmd.Traverse(split)
|
||||
if err == nil && cmd != rootCmd {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, ext := range f.ExtensionManager.List() {
|
||||
if ext.Name() == split[0] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
opts.existingCommand = shared.ExistingCommandFunc(f, cmd)
|
||||
|
||||
if runF != nil {
|
||||
return runF(opts)
|
||||
}
|
||||
|
||||
return setRun(opts)
|
||||
},
|
||||
}
|
||||
|
|
@ -127,11 +110,11 @@ func setRun(opts *SetOptions) error {
|
|||
}
|
||||
isShell = strings.HasPrefix(expansion, "!")
|
||||
|
||||
if opts.validCommand(opts.Name) {
|
||||
if opts.existingCommand(opts.Name) {
|
||||
return fmt.Errorf("could not create alias: %q is already a gh command", opts.Name)
|
||||
}
|
||||
|
||||
if !isShell && !opts.validCommand(expansion) {
|
||||
if !isShell && !opts.existingCommand(expansion) {
|
||||
return fmt.Errorf("could not create alias: %s does not correspond to a gh command", expansion)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ func runCommand(cfg config.Config, isTTY bool, cli string, in string) (*test.Cmd
|
|||
|
||||
cmd := NewCmdSet(factory, nil)
|
||||
|
||||
// fake command nesting structure needed for validCommand
|
||||
// Create fake command structure for testing.
|
||||
rootCmd := &cobra.Command{}
|
||||
rootCmd.AddCommand(cmd)
|
||||
prCmd := &cobra.Command{Use: "pr"}
|
||||
|
|
|
|||
32
pkg/cmd/alias/shared/validations.go
Normal file
32
pkg/cmd/alias/shared/validations.go
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
package shared
|
||||
|
||||
import (
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/google/shlex"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// ExistingCommandFunc returns a function that will check if the given string
|
||||
// corresponds to an existing command.
|
||||
func ExistingCommandFunc(f *cmdutil.Factory, cmd *cobra.Command) func(string) bool {
|
||||
return func(args string) bool {
|
||||
split, err := shlex.Split(args)
|
||||
if err != nil || len(split) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
rootCmd := cmd.Root()
|
||||
cmd, _, err = rootCmd.Traverse(split)
|
||||
if err == nil && cmd != rootCmd {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, ext := range f.ExtensionManager.List() {
|
||||
if ext.Name() == split[0] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
40
pkg/cmd/alias/shared/validations_test.go
Normal file
40
pkg/cmd/alias/shared/validations_test.go
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
package shared
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/extensions"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestExistingCommandFunc(t *testing.T) {
|
||||
// Create fake command factory for testing.
|
||||
factory := &cmdutil.Factory{
|
||||
ExtensionManager: &extensions.ExtensionManagerMock{
|
||||
ListFunc: func() []extensions.Extension {
|
||||
return []extensions.Extension{}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Create fake command structure for testing.
|
||||
issueCmd := &cobra.Command{Use: "issue"}
|
||||
prCmd := &cobra.Command{Use: "pr"}
|
||||
prCmd.AddCommand(&cobra.Command{Use: "checkout"})
|
||||
|
||||
cmd := &cobra.Command{}
|
||||
cmd.AddCommand(prCmd)
|
||||
cmd.AddCommand(issueCmd)
|
||||
|
||||
f := ExistingCommandFunc(factory, cmd)
|
||||
|
||||
assert.True(t, f("pr"))
|
||||
assert.True(t, f("pr checkout"))
|
||||
assert.True(t, f("issue"))
|
||||
|
||||
assert.False(t, f("ps"))
|
||||
assert.False(t, f("checkout"))
|
||||
assert.False(t, f("repo list"))
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ type CodespaceSelector struct {
|
|||
|
||||
repoName string
|
||||
codespaceName string
|
||||
repoOwner string
|
||||
}
|
||||
|
||||
var errNoFilteredCodespaces = errors.New("you have no codespaces meeting the filter criteria")
|
||||
|
|
@ -25,8 +26,10 @@ func AddCodespaceSelector(cmd *cobra.Command, api apiClient) *CodespaceSelector
|
|||
|
||||
cmd.PersistentFlags().StringVarP(&cs.codespaceName, "codespace", "c", "", "Name of the codespace")
|
||||
cmd.PersistentFlags().StringVarP(&cs.repoName, "repo", "R", "", "Filter codespace selection by repository name (user/repo)")
|
||||
cmd.PersistentFlags().StringVar(&cs.repoOwner, "repo-owner", "", "Filter codespace selection by repository owner (username or org)")
|
||||
|
||||
cmd.MarkFlagsMutuallyExclusive("codespace", "repo")
|
||||
cmd.MarkFlagsMutuallyExclusive("codespace", "repo-owner")
|
||||
|
||||
return cs
|
||||
}
|
||||
|
|
@ -102,6 +105,10 @@ func (cs *CodespaceSelector) fetchCodespaces(ctx context.Context) (codespaces []
|
|||
codespaces = filteredCodespaces
|
||||
}
|
||||
|
||||
if cs.repoOwner != "" {
|
||||
codespaces = filterCodespacesByRepoOwner(codespaces, cs.repoOwner)
|
||||
}
|
||||
|
||||
if len(codespaces) == 0 {
|
||||
return nil, errNoFilteredCodespaces
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,68 +48,101 @@ func TestSelectNameWithCodespaceName(t *testing.T) {
|
|||
|
||||
func TestFetchCodespaces(t *testing.T) {
|
||||
var (
|
||||
repoA1 = &api.Codespace{Name: "1", Repository: api.Repository{FullName: "mock/A"}}
|
||||
repoA2 = &api.Codespace{Name: "2", Repository: api.Repository{FullName: "mock/A"}}
|
||||
octocatOwner = api.RepositoryOwner{Login: "octocat"}
|
||||
cliOwner = api.RepositoryOwner{Login: "cli"}
|
||||
octocatA = &api.Codespace{
|
||||
Name: "1",
|
||||
Repository: api.Repository{FullName: "octocat/A", Owner: octocatOwner},
|
||||
}
|
||||
|
||||
repoB1 = &api.Codespace{Name: "1", Repository: api.Repository{FullName: "mock/B"}}
|
||||
octocatA2 = &api.Codespace{
|
||||
Name: "2",
|
||||
Repository: api.Repository{FullName: "octocat/A", Owner: octocatOwner},
|
||||
}
|
||||
|
||||
cliA = &api.Codespace{
|
||||
Name: "3",
|
||||
Repository: api.Repository{FullName: "cli/A", Owner: cliOwner},
|
||||
}
|
||||
|
||||
octocatB = &api.Codespace{
|
||||
Name: "4",
|
||||
Repository: api.Repository{FullName: "octocat/B", Owner: octocatOwner},
|
||||
}
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
tName string
|
||||
apiCodespaces []*api.Codespace
|
||||
codespaceName string
|
||||
repoName string
|
||||
repoOwner string
|
||||
wantCodespaces []*api.Codespace
|
||||
wantErr error
|
||||
}{
|
||||
// Empty case
|
||||
{
|
||||
"empty", nil, "", nil, errNoCodespaces,
|
||||
tName: "empty",
|
||||
apiCodespaces: nil,
|
||||
wantCodespaces: nil,
|
||||
wantErr: errNoCodespaces,
|
||||
},
|
||||
|
||||
// Tests with no filtering
|
||||
{
|
||||
"no filtering, single codespace",
|
||||
[]*api.Codespace{repoA1},
|
||||
"",
|
||||
[]*api.Codespace{repoA1},
|
||||
nil,
|
||||
tName: "no filtering, single codespaces",
|
||||
apiCodespaces: []*api.Codespace{octocatA},
|
||||
wantCodespaces: []*api.Codespace{octocatA},
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
"no filtering, multiple codespaces",
|
||||
[]*api.Codespace{repoA1, repoA2, repoB1},
|
||||
"",
|
||||
[]*api.Codespace{repoA1, repoA2, repoB1},
|
||||
nil,
|
||||
tName: "no filtering, multiple codespace",
|
||||
apiCodespaces: []*api.Codespace{octocatA, cliA, octocatB},
|
||||
wantCodespaces: []*api.Codespace{octocatA, cliA, octocatB},
|
||||
},
|
||||
|
||||
// Test repo filtering
|
||||
{
|
||||
"repo filtering, single codespace",
|
||||
[]*api.Codespace{repoA1},
|
||||
"mock/A",
|
||||
[]*api.Codespace{repoA1},
|
||||
nil,
|
||||
tName: "repo name filtering, single codespace",
|
||||
apiCodespaces: []*api.Codespace{octocatA},
|
||||
repoName: "octocat/A",
|
||||
wantCodespaces: []*api.Codespace{octocatA},
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
"repo filtering, multiple codespaces",
|
||||
[]*api.Codespace{repoA1, repoA2, repoB1},
|
||||
"mock/A",
|
||||
[]*api.Codespace{repoA1, repoA2},
|
||||
nil,
|
||||
tName: "repo name filtering, multiple codespace",
|
||||
apiCodespaces: []*api.Codespace{octocatA, octocatA2, cliA, octocatB},
|
||||
repoName: "octocat/A",
|
||||
wantCodespaces: []*api.Codespace{octocatA, octocatA2},
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
"repo filtering, multiple codespaces 2",
|
||||
[]*api.Codespace{repoA1, repoA2, repoB1},
|
||||
"mock/B",
|
||||
[]*api.Codespace{repoB1},
|
||||
nil,
|
||||
tName: "repo name filtering, multiple codespace 2",
|
||||
apiCodespaces: []*api.Codespace{octocatA, cliA, octocatB},
|
||||
repoName: "octocat/B",
|
||||
wantCodespaces: []*api.Codespace{octocatB},
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
"repo filtering, no matches",
|
||||
[]*api.Codespace{repoA1, repoA2, repoB1},
|
||||
"mock/C",
|
||||
nil,
|
||||
errNoFilteredCodespaces,
|
||||
tName: "repo name filtering, no matches",
|
||||
apiCodespaces: []*api.Codespace{octocatA, cliA, octocatB},
|
||||
repoName: "Unknown/unknown",
|
||||
wantCodespaces: nil,
|
||||
wantErr: errNoFilteredCodespaces,
|
||||
},
|
||||
{
|
||||
tName: "repo filtering, match with repo owner",
|
||||
apiCodespaces: []*api.Codespace{octocatA, octocatA2, cliA, octocatB},
|
||||
repoOwner: "octocat",
|
||||
wantCodespaces: []*api.Codespace{octocatA, octocatA2, octocatB},
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
tName: "repo filtering, no match with repo owner",
|
||||
apiCodespaces: []*api.Codespace{octocatA, cliA, octocatB},
|
||||
repoOwner: "unknown",
|
||||
wantCodespaces: []*api.Codespace{},
|
||||
wantErr: errNoFilteredCodespaces,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -121,7 +154,11 @@ func TestFetchCodespaces(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
cs := &CodespaceSelector{api: api, repoName: tt.repoName}
|
||||
cs := &CodespaceSelector{
|
||||
api: api,
|
||||
repoName: tt.repoName,
|
||||
repoOwner: tt.repoOwner,
|
||||
}
|
||||
|
||||
codespaces, err := cs.fetchCodespaces(context.Background())
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"log"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/AlecAivazis/survey/v2/terminal"
|
||||
|
|
@ -266,3 +267,14 @@ func addDeprecatedRepoShorthand(cmd *cobra.Command, target *string) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// filterCodespacesByRepoOwner filters a list of codespaces by the owner of the repository.
|
||||
func filterCodespacesByRepoOwner(codespaces []*api.Codespace, repoOwner string) []*api.Codespace {
|
||||
filtered := make([]*api.Codespace, 0, len(codespaces))
|
||||
for _, c := range codespaces {
|
||||
if strings.EqualFold(c.Repository.Owner.Login, repoOwner) {
|
||||
filtered = append(filtered, c)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ type deleteOptions struct {
|
|||
keepDays uint16
|
||||
orgName string
|
||||
userName string
|
||||
repoOwner string
|
||||
|
||||
isInteractive bool
|
||||
now func() time.Time
|
||||
|
|
@ -60,10 +61,12 @@ func newDeleteCmd(app *App) *cobra.Command {
|
|||
// After the admin subcommand is added (see https://github.com/cli/cli/pull/6944#issuecomment-1419553639) we can revisit this.
|
||||
opts.codespaceName = selector.codespaceName
|
||||
opts.repoFilter = selector.repoName
|
||||
opts.repoOwner = selector.repoOwner
|
||||
|
||||
if opts.deleteAll && opts.repoFilter != "" {
|
||||
return cmdutil.FlagErrorf("both `--all` and `--repo` is not supported")
|
||||
}
|
||||
|
||||
if opts.orgName != "" && opts.codespaceName != "" && opts.userName == "" {
|
||||
return cmdutil.FlagErrorf("using `--org` with `--codespace` requires `--user`")
|
||||
}
|
||||
|
|
@ -99,6 +102,9 @@ func (a *App) Delete(ctx context.Context, opts deleteOptions) (err error) {
|
|||
userName = currentUser.Login
|
||||
}
|
||||
codespaces, fetchErr = a.apiClient.ListCodespaces(ctx, api.ListCodespacesOptions{OrgName: opts.orgName, UserName: userName})
|
||||
if opts.repoOwner != "" {
|
||||
codespaces = filterCodespacesByRepoOwner(codespaces, opts.repoOwner)
|
||||
}
|
||||
return
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -139,6 +145,7 @@ func (a *App) Delete(ctx context.Context, opts deleteOptions) (err error) {
|
|||
if opts.repoFilter != "" && !strings.EqualFold(c.Repository.FullName, opts.repoFilter) {
|
||||
continue
|
||||
}
|
||||
|
||||
if opts.keepDays > 0 {
|
||||
t, err := time.Parse(time.RFC3339, c.LastUsedAt)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -217,6 +217,44 @@ func TestDelete(t *testing.T) {
|
|||
wantDeleted: []string{"monalisa-spoonknife-123"},
|
||||
wantStdout: "",
|
||||
},
|
||||
{
|
||||
name: "by repo owner",
|
||||
opts: deleteOptions{
|
||||
deleteAll: true,
|
||||
repoOwner: "octocat",
|
||||
},
|
||||
codespaces: []*api.Codespace{
|
||||
{
|
||||
Name: "octocat-spoonknife-123",
|
||||
Repository: api.Repository{
|
||||
FullName: "octocat/Spoon-Knife",
|
||||
Owner: api.RepositoryOwner{
|
||||
Login: "octocat",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "cli-robawt-abc",
|
||||
Repository: api.Repository{
|
||||
FullName: "cli/ROBAWT",
|
||||
Owner: api.RepositoryOwner{
|
||||
Login: "cli",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "octocat-spoonknife-c4f3",
|
||||
Repository: api.Repository{
|
||||
FullName: "octocat/Spoon-Knife",
|
||||
Owner: api.RepositoryOwner{
|
||||
Login: "octocat",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantDeleted: []string{"octocat-spoonknife-123", "octocat-spoonknife-c4f3"},
|
||||
wantStdout: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ func downloadRun(opts *DownloadOptions) error {
|
|||
return err
|
||||
}
|
||||
|
||||
opts.IO.StartProgressIndicator()
|
||||
opts.IO.StartProgressIndicatorWithLabel("Finding assets to download")
|
||||
defer opts.IO.StopProgressIndicator()
|
||||
|
||||
ctx := context.Background()
|
||||
|
|
@ -196,7 +196,7 @@ func downloadRun(opts *DownloadOptions) error {
|
|||
stdout: opts.IO.Out,
|
||||
}
|
||||
|
||||
return downloadAssets(&dest, httpClient, toDownload, opts.Concurrency, isArchive)
|
||||
return downloadAssets(&dest, httpClient, toDownload, opts.Concurrency, isArchive, opts.IO)
|
||||
}
|
||||
|
||||
func matchAny(patterns []string, name string) bool {
|
||||
|
|
@ -208,7 +208,7 @@ func matchAny(patterns []string, name string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func downloadAssets(dest *destinationWriter, httpClient *http.Client, toDownload []shared.ReleaseAsset, numWorkers int, isArchive bool) error {
|
||||
func downloadAssets(dest *destinationWriter, httpClient *http.Client, toDownload []shared.ReleaseAsset, numWorkers int, isArchive bool, io *iostreams.IOStreams) error {
|
||||
if numWorkers == 0 {
|
||||
return errors.New("the number of concurrent workers needs to be greater than 0")
|
||||
}
|
||||
|
|
@ -223,6 +223,7 @@ func downloadAssets(dest *destinationWriter, httpClient *http.Client, toDownload
|
|||
for w := 1; w <= numWorkers; w++ {
|
||||
go func() {
|
||||
for a := range jobs {
|
||||
io.StartProgressIndicatorWithLabel(fmt.Sprintf("Downloading %s", a.Name))
|
||||
results <- downloadAsset(dest, httpClient, a.APIURL, a.Name, isArchive)
|
||||
}
|
||||
}()
|
||||
|
|
@ -240,6 +241,8 @@ func downloadAssets(dest *destinationWriter, httpClient *http.Client, toDownload
|
|||
}
|
||||
}
|
||||
|
||||
io.StopProgressIndicator()
|
||||
|
||||
return downloadError
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -730,7 +730,7 @@ func interactiveRepoInfo(client *http.Client, hostname string, prompter iprompte
|
|||
name = fmt.Sprintf("%s/%s", owner, name)
|
||||
}
|
||||
|
||||
description, err := prompter.Input("Description", defaultName)
|
||||
description, err := prompter.Input("Description", "")
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue