Isolate all alias commands
This commit is contained in:
parent
3c55b29446
commit
172ea2b078
14 changed files with 786 additions and 579 deletions
233
command/alias.go
233
command/alias.go
|
|
@ -1,233 +0,0 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/utils"
|
||||
"github.com/google/shlex"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
aliasCmd.AddCommand(aliasSetCmd)
|
||||
aliasCmd.AddCommand(aliasListCmd)
|
||||
aliasCmd.AddCommand(aliasDeleteCmd)
|
||||
|
||||
aliasSetCmd.Flags().BoolP("shell", "s", false, "Declare an alias to be passed through a shell interpreter")
|
||||
}
|
||||
|
||||
var aliasCmd = &cobra.Command{
|
||||
Use: "alias",
|
||||
Short: "Create command shortcuts",
|
||||
Long: heredoc.Doc(`
|
||||
Aliases can be used to make shortcuts for gh commands or to compose multiple commands.
|
||||
|
||||
Run "gh help alias set" to learn more.
|
||||
`),
|
||||
}
|
||||
|
||||
var aliasSetCmd = &cobra.Command{
|
||||
Use: "set <alias> <expansion>",
|
||||
Short: "Create a shortcut for a gh command",
|
||||
Long: heredoc.Doc(`
|
||||
Declare a word as a command alias that will expand to the specified command(s).
|
||||
|
||||
The expansion may specify additional arguments and flags. If the expansion
|
||||
includes positional placeholders such as '$1', '$2', etc., any extra arguments
|
||||
that follow the invocation of an alias will be inserted appropriately.
|
||||
|
||||
If '--shell' is specified, the alias will be run through a shell interpreter (sh). This allows you
|
||||
to compose commands with "|" or redirect with ">". Note that extra arguments following the alias
|
||||
will not be automatically passed to the expanded expression. To have a shell alias receive
|
||||
arguments, you must explicitly accept them using "$1", "$2", etc., or "$@" to accept all of them.
|
||||
|
||||
Platform note: on Windows, shell aliases are executed via "sh" as installed by Git For Windows. If
|
||||
you have installed git on Windows in some other way, shell aliases may not work for you.
|
||||
|
||||
Quotes must always be used when defining a command as in the examples.`),
|
||||
Example: heredoc.Doc(`
|
||||
$ gh alias set pv 'pr view'
|
||||
$ gh pv -w 123
|
||||
#=> gh pr view -w 123
|
||||
|
||||
$ gh alias set bugs 'issue list --label="bugs"'
|
||||
|
||||
$ gh alias set epicsBy 'issue list --author="$1" --label="epic"'
|
||||
$ gh epicsBy vilmibm
|
||||
#=> gh issue list --author="vilmibm" --label="epic"
|
||||
|
||||
$ gh alias set --shell igrep 'gh issue list --label="$1" | grep $2'
|
||||
$ gh igrep epic foo
|
||||
#=> gh issue list --label="epic" | grep "foo"`),
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: aliasSet,
|
||||
}
|
||||
|
||||
func aliasSet(cmd *cobra.Command, args []string) error {
|
||||
ctx := contextForCommand(cmd)
|
||||
cfg, err := ctx.Config()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aliasCfg, err := cfg.Aliases()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
alias := args[0]
|
||||
expansion := args[1]
|
||||
|
||||
stderr := colorableErr(cmd)
|
||||
if connectedToTerminal(cmd) {
|
||||
fmt.Fprintf(stderr, "- Adding alias for %s: %s\n", utils.Bold(alias), utils.Bold(expansion))
|
||||
}
|
||||
|
||||
shell, err := cmd.Flags().GetBool("shell")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if shell && !strings.HasPrefix(expansion, "!") {
|
||||
expansion = "!" + expansion
|
||||
}
|
||||
isExternal := strings.HasPrefix(expansion, "!")
|
||||
|
||||
if validCommand(alias) {
|
||||
return fmt.Errorf("could not create alias: %q is already a gh command", alias)
|
||||
}
|
||||
|
||||
if !isExternal && !validCommand(expansion) {
|
||||
return fmt.Errorf("could not create alias: %s does not correspond to a gh command", expansion)
|
||||
}
|
||||
|
||||
successMsg := fmt.Sprintf("%s Added alias.", utils.Green("✓"))
|
||||
|
||||
oldExpansion, ok := aliasCfg.Get(alias)
|
||||
if ok {
|
||||
successMsg = fmt.Sprintf("%s Changed alias %s from %s to %s",
|
||||
utils.Green("✓"),
|
||||
utils.Bold(alias),
|
||||
utils.Bold(oldExpansion),
|
||||
utils.Bold(expansion),
|
||||
)
|
||||
}
|
||||
|
||||
err = aliasCfg.Add(alias, expansion)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create alias: %s", err)
|
||||
}
|
||||
|
||||
if connectedToTerminal(cmd) {
|
||||
fmt.Fprintln(stderr, successMsg)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validCommand(expansion string) bool {
|
||||
split, err := shlex.Split(expansion)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
cmd, _, err := RootCmd.Traverse(split)
|
||||
return err == nil && cmd != RootCmd
|
||||
}
|
||||
|
||||
var aliasListCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List your aliases",
|
||||
Long: `This command prints out all of the aliases gh is configured to use.`,
|
||||
Args: cobra.ExactArgs(0),
|
||||
RunE: aliasList,
|
||||
}
|
||||
|
||||
func aliasList(cmd *cobra.Command, args []string) error {
|
||||
ctx := contextForCommand(cmd)
|
||||
cfg, err := ctx.Config()
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't read config: %w", err)
|
||||
}
|
||||
|
||||
aliasCfg, err := cfg.Aliases()
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't read aliases config: %w", err)
|
||||
}
|
||||
|
||||
stderr := colorableErr(cmd)
|
||||
|
||||
if aliasCfg.Empty() {
|
||||
if connectedToTerminal(cmd) {
|
||||
fmt.Fprintf(stderr, "no aliases configured\n")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
tp := utils.NewTablePrinter(&iostreams.IOStreams{
|
||||
Out: cmd.OutOrStdout(),
|
||||
})
|
||||
|
||||
aliasMap := aliasCfg.All()
|
||||
keys := []string{}
|
||||
for alias := range aliasMap {
|
||||
keys = append(keys, alias)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, alias := range keys {
|
||||
if tp.IsTTY() {
|
||||
// ensure that screen readers pause
|
||||
tp.AddField(alias+":", nil, nil)
|
||||
} else {
|
||||
tp.AddField(alias, nil, nil)
|
||||
}
|
||||
tp.AddField(aliasMap[alias], nil, nil)
|
||||
tp.EndRow()
|
||||
}
|
||||
|
||||
return tp.Render()
|
||||
}
|
||||
|
||||
var aliasDeleteCmd = &cobra.Command{
|
||||
Use: "delete <alias>",
|
||||
Short: "Delete an alias.",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: aliasDelete,
|
||||
}
|
||||
|
||||
func aliasDelete(cmd *cobra.Command, args []string) error {
|
||||
alias := args[0]
|
||||
|
||||
ctx := contextForCommand(cmd)
|
||||
cfg, err := ctx.Config()
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't read config: %w", err)
|
||||
}
|
||||
|
||||
aliasCfg, err := cfg.Aliases()
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't read aliases config: %w", err)
|
||||
}
|
||||
|
||||
expansion, ok := aliasCfg.Get(alias)
|
||||
if !ok {
|
||||
return fmt.Errorf("no such alias %s", alias)
|
||||
|
||||
}
|
||||
|
||||
err = aliasCfg.Delete(alias)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete alias %s: %w", alias, err)
|
||||
}
|
||||
|
||||
if connectedToTerminal(cmd) {
|
||||
stderr := colorableErr(cmd)
|
||||
redCheck := utils.Red("✓")
|
||||
fmt.Fprintf(stderr, "%s Deleted alias %s; was %s\n", redCheck, alias, expansion)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,12 +1,9 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
|
@ -20,180 +17,6 @@ func stubSh(value string) func() {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAliasSet_gh_command(t *testing.T) {
|
||||
initBlankContext("", "OWNER/REPO", "trunk")
|
||||
|
||||
mainBuf := bytes.Buffer{}
|
||||
hostsBuf := bytes.Buffer{}
|
||||
defer config.StubWriteConfig(&mainBuf, &hostsBuf)()
|
||||
|
||||
_, err := RunCommand("alias set pr 'pr status'")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
|
||||
eq(t, err.Error(), `could not create alias: "pr" is already a gh command`)
|
||||
}
|
||||
|
||||
func TestAliasSet_empty_aliases(t *testing.T) {
|
||||
cfg := `---
|
||||
aliases:
|
||||
editor: vim
|
||||
`
|
||||
initBlankContext(cfg, "OWNER/REPO", "trunk")
|
||||
|
||||
defer stubTerminal(true)()
|
||||
|
||||
mainBuf := bytes.Buffer{}
|
||||
hostsBuf := bytes.Buffer{}
|
||||
defer config.StubWriteConfig(&mainBuf, &hostsBuf)()
|
||||
|
||||
output, err := RunCommand("alias set co 'pr checkout'")
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
test.ExpectLines(t, output.Stderr(), "Added alias")
|
||||
test.ExpectLines(t, output.String(), "")
|
||||
|
||||
expected := `aliases:
|
||||
co: pr checkout
|
||||
editor: vim
|
||||
`
|
||||
eq(t, mainBuf.String(), expected)
|
||||
}
|
||||
|
||||
func TestAliasSet_existing_alias(t *testing.T) {
|
||||
cfg := `---
|
||||
hosts:
|
||||
github.com:
|
||||
user: OWNER
|
||||
oauth_token: token123
|
||||
aliases:
|
||||
co: pr checkout
|
||||
`
|
||||
initBlankContext(cfg, "OWNER/REPO", "trunk")
|
||||
defer stubTerminal(true)()
|
||||
|
||||
mainBuf := bytes.Buffer{}
|
||||
hostsBuf := bytes.Buffer{}
|
||||
defer config.StubWriteConfig(&mainBuf, &hostsBuf)()
|
||||
|
||||
output, err := RunCommand("alias set co 'pr checkout -Rcool/repo'")
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
test.ExpectLines(t, output.Stderr(), "Changed alias.*co.*from.*pr checkout.*to.*pr checkout -Rcool/repo")
|
||||
}
|
||||
|
||||
func TestAliasSet_space_args(t *testing.T) {
|
||||
initBlankContext("", "OWNER/REPO", "trunk")
|
||||
defer stubTerminal(true)()
|
||||
|
||||
mainBuf := bytes.Buffer{}
|
||||
hostsBuf := bytes.Buffer{}
|
||||
defer config.StubWriteConfig(&mainBuf, &hostsBuf)()
|
||||
|
||||
output, err := RunCommand(`alias set il 'issue list -l "cool story"'`)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
test.ExpectLines(t, output.Stderr(), `Adding alias for.*il.*issue list -l "cool story"`)
|
||||
|
||||
test.ExpectLines(t, mainBuf.String(), `il: issue list -l "cool story"`)
|
||||
}
|
||||
|
||||
func TestAliasSet_arg_processing(t *testing.T) {
|
||||
initBlankContext("", "OWNER/REPO", "trunk")
|
||||
defer stubTerminal(true)()
|
||||
cases := []struct {
|
||||
Cmd string
|
||||
ExpectedOutputLine string
|
||||
ExpectedConfigLine string
|
||||
}{
|
||||
{`alias set il "issue list"`, "- Adding alias for.*il.*issue list", "il: issue list"},
|
||||
|
||||
{`alias set iz 'issue list'`, "- Adding alias for.*iz.*issue list", "iz: issue list"},
|
||||
|
||||
{`alias set ii 'issue list --author="$1" --label="$2"'`,
|
||||
`- Adding alias for.*ii.*issue list --author="\$1" --label="\$2"`,
|
||||
`ii: issue list --author="\$1" --label="\$2"`},
|
||||
|
||||
{`alias set ix "issue list --author='\$1' --label='\$2'"`,
|
||||
`- Adding alias for.*ix.*issue list --author='\$1' --label='\$2'`,
|
||||
`ix: issue list --author='\$1' --label='\$2'`},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
mainBuf := bytes.Buffer{}
|
||||
hostsBuf := bytes.Buffer{}
|
||||
defer config.StubWriteConfig(&mainBuf, &hostsBuf)()
|
||||
|
||||
output, err := RunCommand(c.Cmd)
|
||||
if err != nil {
|
||||
t.Fatalf("got unexpected error running %s: %s", c.Cmd, err)
|
||||
}
|
||||
|
||||
test.ExpectLines(t, output.Stderr(), c.ExpectedOutputLine)
|
||||
test.ExpectLines(t, mainBuf.String(), c.ExpectedConfigLine)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAliasSet_init_alias_cfg(t *testing.T) {
|
||||
cfg := `---
|
||||
editor: vim
|
||||
`
|
||||
initBlankContext(cfg, "OWNER/REPO", "trunk")
|
||||
defer stubTerminal(true)()
|
||||
|
||||
mainBuf := bytes.Buffer{}
|
||||
hostsBuf := bytes.Buffer{}
|
||||
defer config.StubWriteConfig(&mainBuf, &hostsBuf)()
|
||||
|
||||
output, err := RunCommand("alias set diff 'pr diff'")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
expected := `editor: vim
|
||||
aliases:
|
||||
diff: pr diff
|
||||
`
|
||||
|
||||
test.ExpectLines(t, output.Stderr(), "Adding alias for.*diff.*pr diff", "Added alias.")
|
||||
eq(t, mainBuf.String(), expected)
|
||||
}
|
||||
|
||||
func TestAliasSet_existing_aliases(t *testing.T) {
|
||||
cfg := `---
|
||||
aliases:
|
||||
foo: bar
|
||||
`
|
||||
initBlankContext(cfg, "OWNER/REPO", "trunk")
|
||||
defer stubTerminal(true)()
|
||||
|
||||
mainBuf := bytes.Buffer{}
|
||||
hostsBuf := bytes.Buffer{}
|
||||
defer config.StubWriteConfig(&mainBuf, &hostsBuf)()
|
||||
|
||||
output, err := RunCommand("alias set view 'pr view'")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
expected := `aliases:
|
||||
foo: bar
|
||||
view: pr view
|
||||
`
|
||||
|
||||
test.ExpectLines(t, output.Stderr(), "Adding alias for.*view.*pr view", "Added alias.")
|
||||
eq(t, mainBuf.String(), expected)
|
||||
|
||||
}
|
||||
|
||||
func TestExpandAlias_shell(t *testing.T) {
|
||||
defer stubSh("sh")()
|
||||
cfg := `---
|
||||
|
|
@ -284,140 +107,3 @@ aliases:
|
|||
assert.Equal(t, c.ExpectedArgs, expanded)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAliasSet_invalid_command(t *testing.T) {
|
||||
initBlankContext("", "OWNER/REPO", "trunk")
|
||||
_, err := RunCommand("alias set co 'pe checkout'")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
|
||||
eq(t, err.Error(), "could not create alias: pe checkout does not correspond to a gh command")
|
||||
}
|
||||
|
||||
func TestAliasList_empty(t *testing.T) {
|
||||
initBlankContext("", "OWNER/REPO", "trunk")
|
||||
|
||||
output, err := RunCommand("alias list")
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
eq(t, output.String(), "")
|
||||
}
|
||||
|
||||
func TestAliasList(t *testing.T) {
|
||||
cfg := `---
|
||||
aliases:
|
||||
co: pr checkout
|
||||
il: issue list --author=$1 --label=$2
|
||||
clone: repo clone
|
||||
prs: pr status
|
||||
cs: config set editor 'quoted path'
|
||||
`
|
||||
initBlankContext(cfg, "OWNER/REPO", "trunk")
|
||||
|
||||
output, err := RunCommand("alias list")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
expected := `clone repo clone
|
||||
co pr checkout
|
||||
cs config set editor 'quoted path'
|
||||
il issue list --author=$1 --label=$2
|
||||
prs pr status
|
||||
`
|
||||
|
||||
eq(t, output.String(), expected)
|
||||
}
|
||||
|
||||
func TestAliasDelete_nonexistent_command(t *testing.T) {
|
||||
cfg := `---
|
||||
aliases:
|
||||
co: pr checkout
|
||||
il: issue list --author="$1" --label="$2"
|
||||
ia: issue list --author="$1" --assignee="$1"
|
||||
`
|
||||
initBlankContext(cfg, "OWNER/REPO", "trunk")
|
||||
|
||||
_, err := RunCommand("alias delete cool")
|
||||
if err == nil {
|
||||
t.Fatalf("expected error")
|
||||
}
|
||||
|
||||
eq(t, err.Error(), "no such alias cool")
|
||||
}
|
||||
|
||||
func TestAliasDelete(t *testing.T) {
|
||||
cfg := `---
|
||||
aliases:
|
||||
co: pr checkout
|
||||
il: issue list --author="$1" --label="$2"
|
||||
ia: issue list --author="$1" --assignee="$1"
|
||||
`
|
||||
initBlankContext(cfg, "OWNER/REPO", "trunk")
|
||||
defer stubTerminal(true)()
|
||||
|
||||
mainBuf := bytes.Buffer{}
|
||||
hostsBuf := bytes.Buffer{}
|
||||
defer config.StubWriteConfig(&mainBuf, &hostsBuf)()
|
||||
|
||||
output, err := RunCommand("alias delete co")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
test.ExpectLines(t, output.Stderr(), "Deleted alias co; was pr checkout")
|
||||
|
||||
expected := `aliases:
|
||||
il: issue list --author="$1" --label="$2"
|
||||
ia: issue list --author="$1" --assignee="$1"
|
||||
`
|
||||
|
||||
eq(t, mainBuf.String(), expected)
|
||||
}
|
||||
|
||||
func TestShellAlias_flag(t *testing.T) {
|
||||
initBlankContext("", "OWNER/REPO", "trunk")
|
||||
|
||||
defer stubTerminal(true)()
|
||||
|
||||
mainBuf := bytes.Buffer{}
|
||||
hostsBuf := bytes.Buffer{}
|
||||
defer config.StubWriteConfig(&mainBuf, &hostsBuf)()
|
||||
|
||||
output, err := RunCommand("alias set --shell igrep 'gh issue list | grep'")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
test.ExpectLines(t, output.Stderr(), "Adding alias for.*igrep")
|
||||
expected := `aliases:
|
||||
igrep: '!gh issue list | grep'
|
||||
`
|
||||
|
||||
eq(t, mainBuf.String(), expected)
|
||||
}
|
||||
|
||||
func TestShellAlias_bang(t *testing.T) {
|
||||
initBlankContext("", "OWNER/REPO", "trunk")
|
||||
|
||||
defer stubTerminal(true)()
|
||||
|
||||
mainBuf := bytes.Buffer{}
|
||||
hostsBuf := bytes.Buffer{}
|
||||
defer config.StubWriteConfig(&mainBuf, &hostsBuf)()
|
||||
|
||||
output, err := RunCommand("alias set igrep '!gh issue list | grep'")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
test.ExpectLines(t, output.Stderr(), "Adding alias for.*igrep")
|
||||
expected := `aliases:
|
||||
igrep: '!gh issue list | grep'
|
||||
`
|
||||
|
||||
eq(t, mainBuf.String(), expected)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package command
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
|
@ -17,12 +16,10 @@ import (
|
|||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/internal/ghinstance"
|
||||
"github.com/cli/cli/internal/run"
|
||||
configCmd "github.com/cli/cli/pkg/cmd/config"
|
||||
"github.com/cli/cli/pkg/cmd/factory"
|
||||
"github.com/cli/cli/pkg/cmd/root"
|
||||
"github.com/cli/cli/utils"
|
||||
"github.com/google/shlex"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
@ -43,9 +40,6 @@ func init() {
|
|||
|
||||
cmdFactory := factory.New(Version)
|
||||
RootCmd = root.NewCmdRoot(cmdFactory, Version, BuildDate)
|
||||
RootCmd.AddCommand(aliasCmd)
|
||||
RootCmd.AddCommand(root.NewCmdCompletion(cmdFactory.IOStreams))
|
||||
RootCmd.AddCommand(configCmd.NewCmdConfig(cmdFactory))
|
||||
}
|
||||
|
||||
// overridden in tests
|
||||
|
|
@ -74,24 +68,12 @@ func BasicClient() (*api.Client, error) {
|
|||
return api.NewClient(opts...), nil
|
||||
}
|
||||
|
||||
func contextForCommand(cmd *cobra.Command) context.Context {
|
||||
return initContext()
|
||||
}
|
||||
|
||||
func apiVerboseLog() api.ClientOption {
|
||||
logTraffic := strings.Contains(os.Getenv("DEBUG"), "api")
|
||||
colorize := utils.IsTerminal(os.Stderr)
|
||||
return api.VerboseLog(utils.NewColorable(os.Stderr), logTraffic, colorize)
|
||||
}
|
||||
|
||||
func colorableErr(cmd *cobra.Command) io.Writer {
|
||||
err := cmd.ErrOrStderr()
|
||||
if outFile, isFile := err.(*os.File); isFile {
|
||||
return utils.NewColorable(outFile)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func ExecuteShellAlias(args []string) error {
|
||||
externalCmd := exec.Command(args[0], args[1:]...)
|
||||
externalCmd.Stderr = os.Stderr
|
||||
|
|
@ -198,7 +180,3 @@ func ExpandAlias(args []string) (expanded []string, isShell bool, err error) {
|
|||
expanded = args[1:]
|
||||
return
|
||||
}
|
||||
|
||||
func connectedToTerminal(cmd *cobra.Command) bool {
|
||||
return utils.IsTerminal(cmd.InOrStdin()) && utils.IsTerminal(cmd.OutOrStdout())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,22 +75,30 @@ func parseConfigFile(filename string) ([]byte, *yaml.Node, error) {
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
var root yaml.Node
|
||||
err = yaml.Unmarshal(data, &root)
|
||||
root, err := parseConfigData(data)
|
||||
if err != nil {
|
||||
return data, nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
return data, root, err
|
||||
}
|
||||
|
||||
func parseConfigData(data []byte) (*yaml.Node, error) {
|
||||
var root yaml.Node
|
||||
err := yaml.Unmarshal(data, &root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(root.Content) == 0 {
|
||||
return data, &yaml.Node{
|
||||
return &yaml.Node{
|
||||
Kind: yaml.DocumentNode,
|
||||
Content: []*yaml.Node{{Kind: yaml.MappingNode}},
|
||||
}, nil
|
||||
}
|
||||
if root.Content[0].Kind != yaml.MappingNode {
|
||||
return data, &root, fmt.Errorf("expected a top level map")
|
||||
return &root, fmt.Errorf("expected a top level map")
|
||||
}
|
||||
|
||||
return data, &root, nil
|
||||
return &root, nil
|
||||
}
|
||||
|
||||
func isLegacy(root *yaml.Node) bool {
|
||||
|
|
|
|||
|
|
@ -122,6 +122,16 @@ func NewConfig(root *yaml.Node) Config {
|
|||
}
|
||||
}
|
||||
|
||||
// NewFromString initializes a Config from a yaml string
|
||||
func NewFromString(str string) Config {
|
||||
root, err := parseConfigData([]byte(str))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return NewConfig(root)
|
||||
}
|
||||
|
||||
// NewBlankConfig initializes a config file pre-populated with comments and default values
|
||||
func NewBlankConfig() Config {
|
||||
return NewConfig(NewBlankRoot())
|
||||
}
|
||||
|
|
|
|||
28
pkg/cmd/alias/alias.go
Normal file
28
pkg/cmd/alias/alias.go
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
package alias
|
||||
|
||||
import (
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
deleteCmd "github.com/cli/cli/pkg/cmd/alias/delete"
|
||||
listCmd "github.com/cli/cli/pkg/cmd/alias/list"
|
||||
setCmd "github.com/cli/cli/pkg/cmd/alias/set"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewCmdAlias(f *cmdutil.Factory) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "alias",
|
||||
Short: "Create command shortcuts",
|
||||
Long: heredoc.Doc(`
|
||||
Aliases can be used to make shortcuts for gh commands or to compose multiple commands.
|
||||
|
||||
Run "gh help alias set" to learn more.
|
||||
`),
|
||||
}
|
||||
|
||||
cmd.AddCommand(deleteCmd.NewCmdDelete(f, nil))
|
||||
cmd.AddCommand(listCmd.NewCmdList(f, nil))
|
||||
cmd.AddCommand(setCmd.NewCmdSet(f, nil))
|
||||
|
||||
return cmd
|
||||
}
|
||||
71
pkg/cmd/alias/delete/delete.go
Normal file
71
pkg/cmd/alias/delete/delete.go
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
package delete
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type DeleteOptions struct {
|
||||
Config func() (config.Config, error)
|
||||
IO *iostreams.IOStreams
|
||||
|
||||
Name string
|
||||
}
|
||||
|
||||
func NewCmdDelete(f *cmdutil.Factory, runF func(*DeleteOptions) error) *cobra.Command {
|
||||
opts := &DeleteOptions{
|
||||
IO: f.IOStreams,
|
||||
Config: f.Config,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "delete <alias>",
|
||||
Short: "Delete an alias",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.Name = args[0]
|
||||
|
||||
if runF != nil {
|
||||
return runF(opts)
|
||||
}
|
||||
return deleteRun(opts)
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func deleteRun(opts *DeleteOptions) error {
|
||||
cfg, err := opts.Config()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aliasCfg, err := cfg.Aliases()
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't read aliases config: %w", err)
|
||||
}
|
||||
|
||||
expansion, ok := aliasCfg.Get(opts.Name)
|
||||
if !ok {
|
||||
return fmt.Errorf("no such alias %s", opts.Name)
|
||||
|
||||
}
|
||||
|
||||
err = aliasCfg.Delete(opts.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete alias %s: %w", opts.Name, err)
|
||||
}
|
||||
|
||||
if opts.IO.IsStdoutTTY() {
|
||||
redCheck := utils.Red("✓")
|
||||
fmt.Fprintf(opts.IO.ErrOut, "%s Deleted alias %s; was %s\n", redCheck, opts.Name, expansion)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
90
pkg/cmd/alias/delete/delete_test.go
Normal file
90
pkg/cmd/alias/delete/delete_test.go
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
package delete
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/google/shlex"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAliasDelete(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config string
|
||||
cli string
|
||||
isTTY bool
|
||||
wantStdout string
|
||||
wantStderr string
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "no aliases",
|
||||
config: "",
|
||||
cli: "co",
|
||||
isTTY: true,
|
||||
wantStdout: "",
|
||||
wantStderr: "",
|
||||
wantErr: "no such alias co",
|
||||
},
|
||||
{
|
||||
name: "delete one",
|
||||
config: heredoc.Doc(`
|
||||
aliases:
|
||||
il: issue list
|
||||
co: pr checkout
|
||||
`),
|
||||
cli: "co",
|
||||
isTTY: true,
|
||||
wantStdout: "",
|
||||
wantStderr: "✓ Deleted alias co; was pr checkout\n",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
defer config.StubWriteConfig(ioutil.Discard, ioutil.Discard)()
|
||||
|
||||
cfg := config.NewFromString(tt.config)
|
||||
|
||||
io, _, stdout, stderr := iostreams.Test()
|
||||
io.SetStdoutTTY(tt.isTTY)
|
||||
io.SetStdinTTY(tt.isTTY)
|
||||
io.SetStderrTTY(tt.isTTY)
|
||||
|
||||
factory := &cmdutil.Factory{
|
||||
IOStreams: io,
|
||||
Config: func() (config.Config, error) {
|
||||
return cfg, nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd := NewCmdDelete(factory, nil)
|
||||
|
||||
argv, err := shlex.Split(tt.cli)
|
||||
require.NoError(t, err)
|
||||
cmd.SetArgs(argv)
|
||||
|
||||
cmd.SetIn(&bytes.Buffer{})
|
||||
cmd.SetOut(ioutil.Discard)
|
||||
cmd.SetErr(ioutil.Discard)
|
||||
|
||||
_, err = cmd.ExecuteC()
|
||||
if tt.wantErr != "" {
|
||||
if assert.Error(t, err) {
|
||||
assert.Equal(t, tt.wantErr, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tt.wantStdout, stdout.String())
|
||||
assert.Equal(t, tt.wantStderr, stderr.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
83
pkg/cmd/alias/list/list.go
Normal file
83
pkg/cmd/alias/list/list.go
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
package list
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type ListOptions struct {
|
||||
Config func() (config.Config, error)
|
||||
IO *iostreams.IOStreams
|
||||
}
|
||||
|
||||
func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Command {
|
||||
opts := &ListOptions{
|
||||
IO: f.IOStreams,
|
||||
Config: f.Config,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List your aliases",
|
||||
Long: heredoc.Doc(`
|
||||
This command prints out all of the aliases gh is configured to use.
|
||||
`),
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if runF != nil {
|
||||
return runF(opts)
|
||||
}
|
||||
return listRun(opts)
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func listRun(opts *ListOptions) error {
|
||||
cfg, err := opts.Config()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aliasCfg, err := cfg.Aliases()
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't read aliases config: %w", err)
|
||||
}
|
||||
|
||||
if aliasCfg.Empty() {
|
||||
if opts.IO.IsStdoutTTY() {
|
||||
fmt.Fprintf(opts.IO.ErrOut, "no aliases configured\n")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
tp := utils.NewTablePrinter(opts.IO)
|
||||
|
||||
aliasMap := aliasCfg.All()
|
||||
keys := []string{}
|
||||
for alias := range aliasMap {
|
||||
keys = append(keys, alias)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, alias := range keys {
|
||||
if tp.IsTTY() {
|
||||
// ensure that screen readers pause
|
||||
tp.AddField(alias+":", nil, nil)
|
||||
} else {
|
||||
tp.AddField(alias, nil, nil)
|
||||
}
|
||||
tp.AddField(aliasMap[alias], nil, nil)
|
||||
tp.EndRow()
|
||||
}
|
||||
|
||||
return tp.Render()
|
||||
}
|
||||
77
pkg/cmd/alias/list/list_test.go
Normal file
77
pkg/cmd/alias/list/list_test.go
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
package list
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAliasList(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config string
|
||||
isTTY bool
|
||||
wantStdout string
|
||||
wantStderr string
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
config: "",
|
||||
isTTY: true,
|
||||
wantStdout: "",
|
||||
wantStderr: "no aliases configured\n",
|
||||
},
|
||||
{
|
||||
name: "some",
|
||||
config: heredoc.Doc(`
|
||||
aliases:
|
||||
co: pr checkout
|
||||
gc: "!gh gist create \"$@\" | pbcopy"
|
||||
`),
|
||||
isTTY: true,
|
||||
wantStdout: "co: pr checkout\ngc: !gh gist create \"$@\" | pbcopy\n",
|
||||
wantStderr: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// TODO: change underlying config implementation so Write is not
|
||||
// automatically called when editing aliases in-memory
|
||||
defer config.StubWriteConfig(ioutil.Discard, ioutil.Discard)()
|
||||
|
||||
cfg := config.NewFromString(tt.config)
|
||||
|
||||
io, _, stdout, stderr := iostreams.Test()
|
||||
io.SetStdoutTTY(tt.isTTY)
|
||||
io.SetStdinTTY(tt.isTTY)
|
||||
io.SetStderrTTY(tt.isTTY)
|
||||
|
||||
factory := &cmdutil.Factory{
|
||||
IOStreams: io,
|
||||
Config: func() (config.Config, error) {
|
||||
return cfg, nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd := NewCmdList(factory, nil)
|
||||
cmd.SetArgs([]string{})
|
||||
|
||||
cmd.SetIn(&bytes.Buffer{})
|
||||
cmd.SetOut(ioutil.Discard)
|
||||
cmd.SetErr(ioutil.Discard)
|
||||
|
||||
_, err := cmd.ExecuteC()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tt.wantStdout, stdout.String())
|
||||
assert.Equal(t, tt.wantStderr, stderr.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
147
pkg/cmd/alias/set/set.go
Normal file
147
pkg/cmd/alias/set/set.go
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
package set
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/utils"
|
||||
"github.com/google/shlex"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type SetOptions struct {
|
||||
Config func() (config.Config, error)
|
||||
IO *iostreams.IOStreams
|
||||
|
||||
Name string
|
||||
Expansion string
|
||||
IsShell bool
|
||||
RootCmd *cobra.Command
|
||||
}
|
||||
|
||||
func NewCmdSet(f *cmdutil.Factory, runF func(*SetOptions) error) *cobra.Command {
|
||||
opts := &SetOptions{
|
||||
IO: f.IOStreams,
|
||||
Config: f.Config,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "set <alias> <expansion>",
|
||||
Short: "Create a shortcut for a gh command",
|
||||
Long: heredoc.Doc(`
|
||||
Declare a word as a command alias that will expand to the specified command(s).
|
||||
|
||||
The expansion may specify additional arguments and flags. If the expansion
|
||||
includes positional placeholders such as '$1', '$2', etc., any extra arguments
|
||||
that follow the invocation of an alias will be inserted appropriately.
|
||||
|
||||
If '--shell' is specified, the alias will be run through a shell interpreter (sh). This allows you
|
||||
to compose commands with "|" or redirect with ">". Note that extra arguments following the alias
|
||||
will not be automatically passed to the expanded expression. To have a shell alias receive
|
||||
arguments, you must explicitly accept them using "$1", "$2", etc., or "$@" to accept all of them.
|
||||
|
||||
Platform note: on Windows, shell aliases are executed via "sh" as installed by Git For Windows. If
|
||||
you have installed git on Windows in some other way, shell aliases may not work for you.
|
||||
|
||||
Quotes must always be used when defining a command as in the examples.
|
||||
`),
|
||||
Example: heredoc.Doc(`
|
||||
$ gh alias set pv 'pr view'
|
||||
$ gh pv -w 123
|
||||
#=> gh pr view -w 123
|
||||
|
||||
$ gh alias set bugs 'issue list --label="bugs"'
|
||||
|
||||
$ gh alias set epicsBy 'issue list --author="$1" --label="epic"'
|
||||
$ gh epicsBy vilmibm
|
||||
#=> gh issue list --author="vilmibm" --label="epic"
|
||||
|
||||
$ gh alias set --shell igrep 'gh issue list --label="$1" | grep $2'
|
||||
$ gh igrep epic foo
|
||||
#=> gh issue list --label="epic" | grep "foo"
|
||||
`),
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.RootCmd = cmd.Root()
|
||||
|
||||
opts.Name = args[0]
|
||||
opts.Expansion = args[1]
|
||||
|
||||
if runF != nil {
|
||||
return runF(opts)
|
||||
}
|
||||
return setRun(opts)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVarP(&opts.IsShell, "shell", "s", false, "Declare an alias to be passed through a shell interpreter")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func setRun(opts *SetOptions) error {
|
||||
cfg, err := opts.Config()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aliasCfg, err := cfg.Aliases()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isTerminal := opts.IO.IsStdoutTTY()
|
||||
if isTerminal {
|
||||
fmt.Fprintf(opts.IO.ErrOut, "- Adding alias for %s: %s\n", utils.Bold(opts.Name), utils.Bold(opts.Expansion))
|
||||
}
|
||||
|
||||
expansion := opts.Expansion
|
||||
isShell := opts.IsShell
|
||||
if isShell && !strings.HasPrefix(expansion, "!") {
|
||||
expansion = "!" + expansion
|
||||
}
|
||||
isShell = strings.HasPrefix(expansion, "!")
|
||||
|
||||
if validCommand(opts.RootCmd, opts.Name) {
|
||||
return fmt.Errorf("could not create alias: %q is already a gh command", opts.Name)
|
||||
}
|
||||
|
||||
if !isShell && !validCommand(opts.RootCmd, expansion) {
|
||||
return fmt.Errorf("could not create alias: %s does not correspond to a gh command", expansion)
|
||||
}
|
||||
|
||||
successMsg := fmt.Sprintf("%s Added alias.", utils.Green("✓"))
|
||||
if oldExpansion, ok := aliasCfg.Get(opts.Name); ok {
|
||||
successMsg = fmt.Sprintf("%s Changed alias %s from %s to %s",
|
||||
utils.Green("✓"),
|
||||
utils.Bold(opts.Name),
|
||||
utils.Bold(oldExpansion),
|
||||
utils.Bold(expansion),
|
||||
)
|
||||
}
|
||||
|
||||
err = aliasCfg.Add(opts.Name, expansion)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create alias: %s", err)
|
||||
}
|
||||
|
||||
if isTerminal {
|
||||
fmt.Fprintln(opts.IO.ErrOut, successMsg)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validCommand(rootCmd *cobra.Command, expansion string) bool {
|
||||
split, err := shlex.Split(expansion)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
cmd, _, err := rootCmd.Traverse(split)
|
||||
return err == nil && cmd != rootCmd
|
||||
}
|
||||
252
pkg/cmd/alias/set/set_test.go
Normal file
252
pkg/cmd/alias/set/set_test.go
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
package set
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/test"
|
||||
"github.com/google/shlex"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func runCommand(cfg config.Config, isTTY bool, cli string) (*test.CmdOut, error) {
|
||||
io, _, stdout, stderr := iostreams.Test()
|
||||
io.SetStdoutTTY(isTTY)
|
||||
io.SetStdinTTY(isTTY)
|
||||
io.SetStderrTTY(isTTY)
|
||||
|
||||
factory := &cmdutil.Factory{
|
||||
IOStreams: io,
|
||||
Config: func() (config.Config, error) {
|
||||
return cfg, nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd := NewCmdSet(factory, nil)
|
||||
|
||||
// fake command nesting structure needed for validCommand
|
||||
rootCmd := &cobra.Command{}
|
||||
rootCmd.AddCommand(cmd)
|
||||
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)
|
||||
|
||||
argv, err := shlex.Split("set " + cli)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rootCmd.SetArgs(argv)
|
||||
|
||||
rootCmd.SetIn(&bytes.Buffer{})
|
||||
rootCmd.SetOut(ioutil.Discard)
|
||||
rootCmd.SetErr(ioutil.Discard)
|
||||
|
||||
_, err = rootCmd.ExecuteC()
|
||||
return &test.CmdOut{
|
||||
OutBuf: stdout,
|
||||
ErrBuf: stderr,
|
||||
}, err
|
||||
}
|
||||
|
||||
func TestAliasSet_gh_command(t *testing.T) {
|
||||
defer config.StubWriteConfig(ioutil.Discard, ioutil.Discard)()
|
||||
|
||||
cfg := config.NewFromString(``)
|
||||
|
||||
_, err := runCommand(cfg, true, "pr 'pr status'")
|
||||
|
||||
if assert.Error(t, err) {
|
||||
assert.Equal(t, `could not create alias: "pr" is already a gh command`, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAliasSet_empty_aliases(t *testing.T) {
|
||||
mainBuf := bytes.Buffer{}
|
||||
defer config.StubWriteConfig(&mainBuf, ioutil.Discard)()
|
||||
|
||||
cfg := config.NewFromString(heredoc.Doc(`
|
||||
aliases:
|
||||
editor: vim
|
||||
`))
|
||||
|
||||
output, err := runCommand(cfg, true, "co 'pr checkout'")
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
test.ExpectLines(t, output.Stderr(), "Added alias")
|
||||
test.ExpectLines(t, output.String(), "")
|
||||
|
||||
expected := `aliases:
|
||||
co: pr checkout
|
||||
editor: vim
|
||||
`
|
||||
assert.Equal(t, expected, mainBuf.String())
|
||||
}
|
||||
|
||||
func TestAliasSet_existing_alias(t *testing.T) {
|
||||
mainBuf := bytes.Buffer{}
|
||||
defer config.StubWriteConfig(&mainBuf, ioutil.Discard)()
|
||||
|
||||
cfg := config.NewFromString(heredoc.Doc(`
|
||||
aliases:
|
||||
co: pr checkout
|
||||
`))
|
||||
|
||||
output, err := runCommand(cfg, true, "co 'pr checkout -Rcool/repo'")
|
||||
require.NoError(t, err)
|
||||
|
||||
test.ExpectLines(t, output.Stderr(), "Changed alias.*co.*from.*pr checkout.*to.*pr checkout -Rcool/repo")
|
||||
}
|
||||
|
||||
func TestAliasSet_space_args(t *testing.T) {
|
||||
mainBuf := bytes.Buffer{}
|
||||
defer config.StubWriteConfig(&mainBuf, ioutil.Discard)()
|
||||
|
||||
cfg := config.NewFromString(``)
|
||||
|
||||
output, err := runCommand(cfg, true, `il 'issue list -l "cool story"'`)
|
||||
require.NoError(t, err)
|
||||
|
||||
test.ExpectLines(t, output.Stderr(), `Adding alias for.*il.*issue list -l "cool story"`)
|
||||
|
||||
test.ExpectLines(t, mainBuf.String(), `il: issue list -l "cool story"`)
|
||||
}
|
||||
|
||||
func TestAliasSet_arg_processing(t *testing.T) {
|
||||
cases := []struct {
|
||||
Cmd string
|
||||
ExpectedOutputLine string
|
||||
ExpectedConfigLine string
|
||||
}{
|
||||
{`il "issue list"`, "- Adding alias for.*il.*issue list", "il: issue list"},
|
||||
|
||||
{`iz 'issue list'`, "- Adding alias for.*iz.*issue list", "iz: issue list"},
|
||||
|
||||
{`ii 'issue list --author="$1" --label="$2"'`,
|
||||
`- Adding alias for.*ii.*issue list --author="\$1" --label="\$2"`,
|
||||
`ii: issue list --author="\$1" --label="\$2"`},
|
||||
|
||||
{`ix "issue list --author='\$1' --label='\$2'"`,
|
||||
`- Adding alias for.*ix.*issue list --author='\$1' --label='\$2'`,
|
||||
`ix: issue list --author='\$1' --label='\$2'`},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.Cmd, func(t *testing.T) {
|
||||
mainBuf := bytes.Buffer{}
|
||||
defer config.StubWriteConfig(&mainBuf, ioutil.Discard)()
|
||||
|
||||
cfg := config.NewFromString(``)
|
||||
|
||||
output, err := runCommand(cfg, true, c.Cmd)
|
||||
if err != nil {
|
||||
t.Fatalf("got unexpected error running %s: %s", c.Cmd, err)
|
||||
}
|
||||
|
||||
test.ExpectLines(t, output.Stderr(), c.ExpectedOutputLine)
|
||||
test.ExpectLines(t, mainBuf.String(), c.ExpectedConfigLine)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAliasSet_init_alias_cfg(t *testing.T) {
|
||||
mainBuf := bytes.Buffer{}
|
||||
defer config.StubWriteConfig(&mainBuf, ioutil.Discard)()
|
||||
|
||||
cfg := config.NewFromString(heredoc.Doc(`
|
||||
editor: vim
|
||||
`))
|
||||
|
||||
output, err := runCommand(cfg, true, "diff 'pr diff'")
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := `editor: vim
|
||||
aliases:
|
||||
diff: pr diff
|
||||
`
|
||||
|
||||
test.ExpectLines(t, output.Stderr(), "Adding alias for.*diff.*pr diff", "Added alias.")
|
||||
assert.Equal(t, expected, mainBuf.String())
|
||||
}
|
||||
|
||||
func TestAliasSet_existing_aliases(t *testing.T) {
|
||||
mainBuf := bytes.Buffer{}
|
||||
defer config.StubWriteConfig(&mainBuf, ioutil.Discard)()
|
||||
|
||||
cfg := config.NewFromString(heredoc.Doc(`
|
||||
aliases:
|
||||
foo: bar
|
||||
`))
|
||||
|
||||
output, err := runCommand(cfg, true, "view 'pr view'")
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := `aliases:
|
||||
foo: bar
|
||||
view: pr view
|
||||
`
|
||||
|
||||
test.ExpectLines(t, output.Stderr(), "Adding alias for.*view.*pr view", "Added alias.")
|
||||
assert.Equal(t, expected, mainBuf.String())
|
||||
|
||||
}
|
||||
|
||||
func TestAliasSet_invalid_command(t *testing.T) {
|
||||
defer config.StubWriteConfig(ioutil.Discard, ioutil.Discard)()
|
||||
|
||||
cfg := config.NewFromString(``)
|
||||
|
||||
_, err := runCommand(cfg, true, "co 'pe checkout'")
|
||||
if assert.Error(t, err) {
|
||||
assert.Equal(t, "could not create alias: pe checkout does not correspond to a gh command", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestShellAlias_flag(t *testing.T) {
|
||||
mainBuf := bytes.Buffer{}
|
||||
defer config.StubWriteConfig(&mainBuf, ioutil.Discard)()
|
||||
|
||||
cfg := config.NewFromString(``)
|
||||
|
||||
output, err := runCommand(cfg, true, "--shell igrep 'gh issue list | grep'")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
test.ExpectLines(t, output.Stderr(), "Adding alias for.*igrep")
|
||||
|
||||
expected := `aliases:
|
||||
igrep: '!gh issue list | grep'
|
||||
`
|
||||
assert.Equal(t, expected, mainBuf.String())
|
||||
}
|
||||
|
||||
func TestShellAlias_bang(t *testing.T) {
|
||||
mainBuf := bytes.Buffer{}
|
||||
defer config.StubWriteConfig(&mainBuf, ioutil.Discard)()
|
||||
|
||||
cfg := config.NewFromString(``)
|
||||
|
||||
output, err := runCommand(cfg, true, "igrep '!gh issue list | grep'")
|
||||
require.NoError(t, err)
|
||||
|
||||
test.ExpectLines(t, output.Stderr(), "Adding alias for.*igrep")
|
||||
|
||||
expected := `aliases:
|
||||
igrep: '!gh issue list | grep'
|
||||
`
|
||||
assert.Equal(t, expected, mainBuf.String())
|
||||
}
|
||||
|
|
@ -9,7 +9,9 @@ import (
|
|||
"github.com/cli/cli/api"
|
||||
"github.com/cli/cli/context"
|
||||
"github.com/cli/cli/internal/ghrepo"
|
||||
aliasCmd "github.com/cli/cli/pkg/cmd/alias"
|
||||
apiCmd "github.com/cli/cli/pkg/cmd/api"
|
||||
configCmd "github.com/cli/cli/pkg/cmd/config"
|
||||
gistCmd "github.com/cli/cli/pkg/cmd/gist"
|
||||
issueCmd "github.com/cli/cli/pkg/cmd/issue"
|
||||
prCmd "github.com/cli/cli/pkg/cmd/pr"
|
||||
|
|
@ -94,9 +96,12 @@ func NewCmdRoot(f *cmdutil.Factory, version, buildDate string) *cobra.Command {
|
|||
|
||||
// CHILD COMMANDS
|
||||
|
||||
cmd.AddCommand(aliasCmd.NewCmdAlias(f))
|
||||
cmd.AddCommand(apiCmd.NewCmdApi(f, nil))
|
||||
cmd.AddCommand(gistCmd.NewCmdGist(f))
|
||||
cmd.AddCommand(configCmd.NewCmdConfig(f))
|
||||
cmd.AddCommand(creditsCmd.NewCmdCredits(f, nil))
|
||||
cmd.AddCommand(gistCmd.NewCmdGist(f))
|
||||
cmd.AddCommand(NewCmdCompletion(f.IOStreams))
|
||||
|
||||
// below here at the commands that require the "intelligent" BaseRepo resolver
|
||||
repoResolvingCmdFactory := *f
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import (
|
|||
"fmt"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/internal/run"
|
||||
)
|
||||
|
|
@ -86,7 +85,13 @@ func createStubbedPrepareCmd(cs *CmdStubber) func(*exec.Cmd) run.Runnable {
|
|||
}
|
||||
}
|
||||
|
||||
func ExpectLines(t *testing.T, output string, lines ...string) {
|
||||
type T interface {
|
||||
Helper()
|
||||
Errorf(string, ...interface{})
|
||||
}
|
||||
|
||||
func ExpectLines(t T, output string, lines ...string) {
|
||||
t.Helper()
|
||||
var r *regexp.Regexp
|
||||
for _, l := range lines {
|
||||
r = regexp.MustCompile(l)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue