From 5b4a08dcb9bc63f8da5f966b4e47d73edf87f3b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Tue, 10 Nov 2020 20:03:24 +0100 Subject: [PATCH 1/3] Ensure that only PATH is searched when shelling out to external commands Works around https://github.com/golang/go/issues/38736 for Windows. --- cmd/gh/main.go | 9 +++- git/git.go | 92 ++++++++++++++++++++++++-------- git/remote.go | 18 +++++-- go.mod | 1 + go.sum | 2 + internal/run/run.go | 8 ++- pkg/browser/browser.go | 12 +++-- pkg/browser/browser_test.go | 5 ++ pkg/cmd/alias/expand/expand.go | 6 +-- pkg/cmd/pr/checkout/checkout.go | 8 ++- pkg/cmd/release/create/create.go | 17 ++++-- pkg/cmd/repo/create/create.go | 10 +++- pkg/cmd/repo/fork/fork.go | 5 +- pkg/iostreams/iostreams.go | 13 ++++- pkg/surveyext/editor_manual.go | 8 ++- 15 files changed, 168 insertions(+), 46 deletions(-) diff --git a/cmd/gh/main.go b/cmd/gh/main.go index d4abd2f74..bf4a77da9 100644 --- a/cmd/gh/main.go +++ b/cmd/gh/main.go @@ -22,6 +22,7 @@ import ( "github.com/cli/cli/pkg/cmdutil" "github.com/cli/cli/update" "github.com/cli/cli/utils" + "github.com/cli/safeexec" "github.com/mattn/go-colorable" "github.com/mgutz/ansi" "github.com/spf13/cobra" @@ -107,7 +108,13 @@ func main() { } if isShell { - externalCmd := exec.Command(expandedArgs[0], expandedArgs[1:]...) + exe, err := safeexec.LookPath(expandedArgs[0]) + if err != nil { + fmt.Fprintf(stderr, "failed to run external command: %s", err) + os.Exit(3) + } + + externalCmd := exec.Command(exe, expandedArgs[1:]...) externalCmd.Stderr = os.Stderr externalCmd.Stdout = os.Stdout externalCmd.Stdin = os.Stdin diff --git a/git/git.go b/git/git.go index 2c2d32e0e..36a6573cb 100644 --- a/git/git.go +++ b/git/git.go @@ -13,6 +13,7 @@ import ( "strings" "github.com/cli/cli/internal/run" + "github.com/cli/safeexec" ) // ErrNotOnAnyBranch indicates that the user is in detached HEAD state @@ -37,7 +38,10 @@ func (r TrackingRef) String() string { // ShowRefs resolves fully-qualified refs to commit hashes func ShowRefs(ref ...string) ([]Ref, error) { args := append([]string{"show-ref", "--verify", "--"}, ref...) - showRef := exec.Command("git", args...) + showRef, err := GitCommand(args...) + if err != nil { + return nil, err + } output, err := run.PrepareCmd(showRef).Output() var refs []Ref @@ -57,7 +61,10 @@ func ShowRefs(ref ...string) ([]Ref, error) { // CurrentBranch reads the checked-out branch for the git repository func CurrentBranch() (string, error) { - refCmd := GitCommand("symbolic-ref", "--quiet", "HEAD") + refCmd, err := GitCommand("symbolic-ref", "--quiet", "HEAD") + if err != nil { + return "", err + } output, err := run.PrepareCmd(refCmd).Output() if err == nil { @@ -78,13 +85,19 @@ func CurrentBranch() (string, error) { } func listRemotes() ([]string, error) { - remoteCmd := exec.Command("git", "remote", "-v") + remoteCmd, err := GitCommand("remote", "-v") + if err != nil { + return nil, err + } output, err := run.PrepareCmd(remoteCmd).Output() return outputLines(output), err } func Config(name string) (string, error) { - configCmd := exec.Command("git", "config", name) + configCmd, err := GitCommand("config", name) + if err != nil { + return "", err + } output, err := run.PrepareCmd(configCmd).Output() if err != nil { return "", fmt.Errorf("unknown config key: %s", name) @@ -94,12 +107,19 @@ func Config(name string) (string, error) { } -var GitCommand = func(args ...string) *exec.Cmd { - return exec.Command("git", args...) +var GitCommand = func(args ...string) (*exec.Cmd, error) { + gitExe, err := safeexec.LookPath("git") + if err != nil { + return nil, err + } + return exec.Command(gitExe, args...), nil } func UncommittedChangeCount() (int, error) { - statusCmd := GitCommand("status", "--porcelain") + statusCmd, err := GitCommand("status", "--porcelain") + if err != nil { + return 0, err + } output, err := run.PrepareCmd(statusCmd).Output() if err != nil { return 0, err @@ -123,10 +143,13 @@ type Commit struct { } func Commits(baseRef, headRef string) ([]*Commit, error) { - logCmd := GitCommand( + logCmd, err := GitCommand( "-c", "log.ShowSignature=false", "log", "--pretty=format:%H,%s", "--cherry", fmt.Sprintf("%s...%s", baseRef, headRef)) + if err != nil { + return nil, err + } output, err := run.PrepareCmd(logCmd).Output() if err != nil { return []*Commit{}, err @@ -154,7 +177,10 @@ func Commits(baseRef, headRef string) ([]*Commit, error) { } func CommitBody(sha string) (string, error) { - showCmd := GitCommand("-c", "log.ShowSignature=false", "show", "-s", "--pretty=format:%b", sha) + showCmd, err := GitCommand("-c", "log.ShowSignature=false", "show", "-s", "--pretty=format:%b", sha) + if err != nil { + return "", err + } output, err := run.PrepareCmd(showCmd).Output() if err != nil { return "", err @@ -164,7 +190,10 @@ func CommitBody(sha string) (string, error) { // Push publishes a git ref to a remote and sets up upstream configuration func Push(remote string, ref string, cmdOut, cmdErr io.Writer) error { - pushCmd := GitCommand("push", "--set-upstream", remote, ref) + pushCmd, err := GitCommand("push", "--set-upstream", remote, ref) + if err != nil { + return err + } pushCmd.Stdout = cmdOut pushCmd.Stderr = cmdErr return run.PrepareCmd(pushCmd).Run() @@ -179,7 +208,10 @@ type BranchConfig struct { // ReadBranchConfig parses the `branch.BRANCH.(remote|merge)` part of git config func ReadBranchConfig(branch string) (cfg BranchConfig) { prefix := regexp.QuoteMeta(fmt.Sprintf("branch.%s.", branch)) - configCmd := GitCommand("config", "--get-regexp", fmt.Sprintf("^%s(remote|merge)$", prefix)) + configCmd, err := GitCommand("config", "--get-regexp", fmt.Sprintf("^%s(remote|merge)$", prefix)) + if err != nil { + return + } output, err := run.PrepareCmd(configCmd).Output() if err != nil { return @@ -209,21 +241,28 @@ func ReadBranchConfig(branch string) (cfg BranchConfig) { } func DeleteLocalBranch(branch string) error { - branchCmd := GitCommand("branch", "-D", branch) - err := run.PrepareCmd(branchCmd).Run() - return err + branchCmd, err := GitCommand("branch", "-D", branch) + if err != nil { + return err + } + return run.PrepareCmd(branchCmd).Run() } func HasLocalBranch(branch string) bool { - configCmd := GitCommand("rev-parse", "--verify", "refs/heads/"+branch) - _, err := run.PrepareCmd(configCmd).Output() + configCmd, err := GitCommand("rev-parse", "--verify", "refs/heads/"+branch) + if err != nil { + return false + } + _, err = run.PrepareCmd(configCmd).Output() return err == nil } func CheckoutBranch(branch string) error { - configCmd := GitCommand("checkout", branch) - err := run.PrepareCmd(configCmd).Run() - return err + configCmd, err := GitCommand("checkout", branch) + if err != nil { + return err + } + return run.PrepareCmd(configCmd).Run() } func parseCloneArgs(extraArgs []string) (args []string, target string) { @@ -252,7 +291,10 @@ func RunClone(cloneURL string, args []string) (target string, err error) { cloneArgs = append([]string{"clone"}, cloneArgs...) - cloneCmd := GitCommand(cloneArgs...) + cloneCmd, err := GitCommand(cloneArgs...) + if err != nil { + return "", err + } cloneCmd.Stdin = os.Stdin cloneCmd.Stdout = os.Stdout cloneCmd.Stderr = os.Stderr @@ -262,7 +304,10 @@ func RunClone(cloneURL string, args []string) (target string, err error) { } func AddUpstreamRemote(upstreamURL, cloneDir string) error { - cloneCmd := GitCommand("-C", cloneDir, "remote", "add", "-f", "upstream", upstreamURL) + cloneCmd, err := GitCommand("-C", cloneDir, "remote", "add", "-f", "upstream", upstreamURL) + if err != nil { + return err + } cloneCmd.Stdout = os.Stdout cloneCmd.Stderr = os.Stderr return run.PrepareCmd(cloneCmd).Run() @@ -274,7 +319,10 @@ func isFilesystemPath(p string) bool { // ToplevelDir returns the top-level directory path of the current repository func ToplevelDir() (string, error) { - showCmd := exec.Command("git", "rev-parse", "--show-toplevel") + showCmd, err := GitCommand("rev-parse", "--show-toplevel") + if err != nil { + return "", err + } output, err := run.PrepareCmd(showCmd).Output() return firstLine(output), err diff --git a/git/remote.go b/git/remote.go index 77e550c37..a4291b40f 100644 --- a/git/remote.go +++ b/git/remote.go @@ -3,7 +3,6 @@ package git import ( "fmt" "net/url" - "os/exec" "regexp" "strings" @@ -45,7 +44,10 @@ func Remotes() (RemoteSet, error) { remotes := parseRemotes(list) // this is affected by SetRemoteResolution - remoteCmd := exec.Command("git", "config", "--get-regexp", `^remote\..*\.gh-resolved$`) + remoteCmd, err := GitCommand("config", "--get-regexp", `^remote\..*\.gh-resolved$`) + if err != nil { + return nil, err + } output, _ := run.PrepareCmd(remoteCmd).Output() for _, l := range outputLines(output) { parts := strings.SplitN(l, " ", 2) @@ -107,8 +109,11 @@ func parseRemotes(gitRemotes []string) (remotes RemoteSet) { // AddRemote adds a new git remote and auto-fetches objects from it func AddRemote(name, u string) (*Remote, error) { - addCmd := exec.Command("git", "remote", "add", "-f", name, u) - err := run.PrepareCmd(addCmd).Run() + addCmd, err := GitCommand("remote", "add", "-f", name, u) + if err != nil { + return nil, err + } + err = run.PrepareCmd(addCmd).Run() if err != nil { return nil, err } @@ -136,6 +141,9 @@ func AddRemote(name, u string) (*Remote, error) { } func SetRemoteResolution(name, resolution string) error { - addCmd := exec.Command("git", "config", "--add", fmt.Sprintf("remote.%s.gh-resolved", name), resolution) + addCmd, err := GitCommand("config", "--add", fmt.Sprintf("remote.%s.gh-resolved", name), resolution) + if err != nil { + return err + } return run.PrepareCmd(addCmd).Run() } diff --git a/go.mod b/go.mod index 3e56a0170..c2de4f5f4 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/MakeNowJust/heredoc v1.0.0 github.com/briandowns/spinner v1.11.1 github.com/charmbracelet/glamour v0.2.1-0.20200724174618-1246d13c1684 + github.com/cli/safeexec v1.0.0 github.com/cpuguy83/go-md2man/v2 v2.0.0 github.com/enescakir/emoji v1.0.0 github.com/google/go-cmp v0.5.2 diff --git a/go.sum b/go.sum index b3659cb76..91c17e6b5 100644 --- a/go.sum +++ b/go.sum @@ -44,6 +44,8 @@ github.com/briandowns/spinner v1.11.1/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/charmbracelet/glamour v0.2.1-0.20200724174618-1246d13c1684 h1:YMyvXRstOQc7n6eneHfudVMbARSCmZ7EZGjtTkkeB3A= github.com/charmbracelet/glamour v0.2.1-0.20200724174618-1246d13c1684/go.mod h1:UA27Kwj3QHialP74iU6C+Gpc8Y7IOAKupeKMLLBURWM= +github.com/cli/safeexec v1.0.0 h1:0VngyaIyqACHdcMNWfo6+KdUYnqEr2Sg+bSP1pdF+dI= +github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q= github.com/cli/shurcooL-graphql v0.0.0-20200707151639-0f7232a2bf7e h1:aq/1jlmtZoS6nlSp3yLOTZQ50G+dzHdeRNENgE/iBew= github.com/cli/shurcooL-graphql v0.0.0-20200707151639-0f7232a2bf7e/go.mod h1:it23pLwxmz6OyM6I5O0ATIXQS1S190Nas26L5Kahp4U= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= diff --git a/internal/run/run.go b/internal/run/run.go index 67de76fa2..0c9cea271 100644 --- a/internal/run/run.go +++ b/internal/run/run.go @@ -23,7 +23,13 @@ var PrepareCmd = func(cmd *exec.Cmd) Runnable { // SetPrepareCmd overrides PrepareCmd and returns a func to revert it back func SetPrepareCmd(fn func(*exec.Cmd) Runnable) func() { origPrepare := PrepareCmd - PrepareCmd = fn + PrepareCmd = func(cmd *exec.Cmd) Runnable { + // normalize git executable name for consistency in tests + if baseName := filepath.Base(cmd.Args[0]); baseName == "git" || baseName == "git.exe" { + cmd.Args[0] = baseName + } + return fn(cmd) + } return func() { PrepareCmd = origPrepare } diff --git a/pkg/browser/browser.go b/pkg/browser/browser.go index 133b3dbb1..ec4c59e6c 100644 --- a/pkg/browser/browser.go +++ b/pkg/browser/browser.go @@ -6,6 +6,7 @@ import ( "runtime" "strings" + "github.com/cli/safeexec" "github.com/google/shlex" ) @@ -31,7 +32,7 @@ func ForOS(goos, url string) *exec.Cmd { case "darwin": args = append(args, url) case "windows": - exe = "cmd" + exe, _ = lookPath("cmd") r := strings.NewReplacer("&", "^&") args = append(args, "/c", "start", r.Replace(url)) default: @@ -51,8 +52,13 @@ func FromLauncher(launcher, url string) (*exec.Cmd, error) { return nil, err } + exe, err := lookPath(args[0]) + if err != nil { + return nil, err + } + args = append(args, url) - cmd := exec.Command(args[0], args[1:]...) + cmd := exec.Command(exe, args[1:]...) cmd.Stderr = os.Stderr return cmd, nil } @@ -71,4 +77,4 @@ func linuxExe() string { return exe } -var lookPath = exec.LookPath +var lookPath = safeexec.LookPath diff --git a/pkg/browser/browser_test.go b/pkg/browser/browser_test.go index 48b91f7c1..6a24327da 100644 --- a/pkg/browser/browser_test.go +++ b/pkg/browser/browser_test.go @@ -49,10 +49,12 @@ func TestForOS(t *testing.T) { goos: "windows", url: "https://example.com/path?a=1&b=2&c=3", }, + exe: "cmd", want: []string{"cmd", "/c", "start", "https://example.com/path?a=1^&b=2^&c=3"}, }, } for _, tt := range tests { + origLookPath := lookPath lookPath = func(file string) (string, error) { if file == tt.exe { return file, nil @@ -60,6 +62,9 @@ func TestForOS(t *testing.T) { return "", errors.New("not found") } } + defer func() { + lookPath = origLookPath + }() t.Run(tt.name, func(t *testing.T) { if cmd := ForOS(tt.args.goos, tt.args.url); !reflect.DeepEqual(cmd.Args, tt.want) { diff --git a/pkg/cmd/alias/expand/expand.go b/pkg/cmd/alias/expand/expand.go index b2117f198..ec791df52 100644 --- a/pkg/cmd/alias/expand/expand.go +++ b/pkg/cmd/alias/expand/expand.go @@ -4,13 +4,13 @@ import ( "errors" "fmt" "os" - "os/exec" "path/filepath" "regexp" "runtime" "strings" "github.com/cli/cli/internal/config" + "github.com/cli/safeexec" "github.com/google/shlex" ) @@ -80,7 +80,7 @@ func ExpandAlias(cfg config.Config, args []string, findShFunc func() (string, er } func findSh() (string, error) { - shPath, err := exec.LookPath("sh") + shPath, err := safeexec.LookPath("sh") if err == nil { return shPath, nil } @@ -88,7 +88,7 @@ func findSh() (string, error) { if runtime.GOOS == "windows" { winNotFoundErr := errors.New("unable to locate sh to execute the shell alias with. The sh.exe interpreter is typically distributed with Git for Windows.") // We can try and find a sh executable in a Git for Windows install - gitPath, err := exec.LookPath("git") + gitPath, err := safeexec.LookPath("git") if err != nil { return "", winNotFoundErr } diff --git a/pkg/cmd/pr/checkout/checkout.go b/pkg/cmd/pr/checkout/checkout.go index 096be87a9..34db96f87 100644 --- a/pkg/cmd/pr/checkout/checkout.go +++ b/pkg/cmd/pr/checkout/checkout.go @@ -16,6 +16,7 @@ import ( "github.com/cli/cli/pkg/cmd/pr/shared" "github.com/cli/cli/pkg/cmdutil" "github.com/cli/cli/pkg/iostreams" + "github.com/cli/safeexec" "github.com/spf13/cobra" ) @@ -165,7 +166,12 @@ func checkoutRun(opts *CheckoutOptions) error { } for _, args := range cmdQueue { - cmd := exec.Command(args[0], args[1:]...) + // TODO: reuse the result of this lookup across loop iteration + exe, err := safeexec.LookPath(args[0]) + if err != nil { + return err + } + cmd := exec.Command(exe, args[1:]...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := run.PrepareCmd(cmd).Run(); err != nil { diff --git a/pkg/cmd/release/create/create.go b/pkg/cmd/release/create/create.go index e8a1ce853..9b3eb0fb7 100644 --- a/pkg/cmd/release/create/create.go +++ b/pkg/cmd/release/create/create.go @@ -6,11 +6,11 @@ import ( "io/ioutil" "net/http" "os" - "os/exec" "strings" "github.com/AlecAivazis/survey/v2" "github.com/MakeNowJust/heredoc" + "github.com/cli/cli/git" "github.com/cli/cli/internal/config" "github.com/cli/cli/internal/ghrepo" "github.com/cli/cli/internal/run" @@ -307,13 +307,19 @@ func createRun(opts *CreateOptions) error { } func gitTagInfo(tagName string) (string, error) { - cmd := exec.Command("git", "tag", "--list", tagName, "--format=%(contents:subject)%0a%0a%(contents:body)") + cmd, err := git.GitCommand("tag", "--list", tagName, "--format=%(contents:subject)%0a%0a%(contents:body)") + if err != nil { + return "", err + } b, err := run.PrepareCmd(cmd).Output() return string(b), err } func detectPreviousTag(headRef string) (string, error) { - cmd := exec.Command("git", "describe", "--tags", "--abbrev=0", fmt.Sprintf("%s^", headRef)) + cmd, err := git.GitCommand("describe", "--tags", "--abbrev=0", fmt.Sprintf("%s^", headRef)) + if err != nil { + return "", err + } b, err := run.PrepareCmd(cmd).Output() return strings.TrimSpace(string(b)), err } @@ -324,7 +330,10 @@ type logEntry struct { } func changelogForRange(refRange string) ([]logEntry, error) { - cmd := exec.Command("git", "-c", "log.ShowSignature=false", "log", "--first-parent", "--reverse", "--pretty=format:%B%x00", refRange) + cmd, err := git.GitCommand("-c", "log.ShowSignature=false", "log", "--first-parent", "--reverse", "--pretty=format:%B%x00", refRange) + if err != nil { + return nil, err + } b, err := run.PrepareCmd(cmd).Output() if err != nil { return nil, err diff --git a/pkg/cmd/repo/create/create.go b/pkg/cmd/repo/create/create.go index 50c4a4a8f..bdd2e4df5 100644 --- a/pkg/cmd/repo/create/create.go +++ b/pkg/cmd/repo/create/create.go @@ -284,14 +284,20 @@ func createRun(opts *CreateOptions) error { if doSetup { path := repo.Name - gitInit := git.GitCommand("init", path) + gitInit, err := git.GitCommand("init", path) + if err != nil { + return err + } gitInit.Stdout = stdout gitInit.Stderr = stderr err = run.PrepareCmd(gitInit).Run() if err != nil { return err } - gitRemoteAdd := git.GitCommand("-C", path, "remote", "add", "origin", remoteURL) + gitRemoteAdd, err := git.GitCommand("-C", path, "remote", "add", "origin", remoteURL) + if err != nil { + return err + } gitRemoteAdd.Stdout = stdout gitRemoteAdd.Stderr = stderr err = run.PrepareCmd(gitRemoteAdd).Run() diff --git a/pkg/cmd/repo/fork/fork.go b/pkg/cmd/repo/fork/fork.go index ddb376a17..ebaf6c400 100644 --- a/pkg/cmd/repo/fork/fork.go +++ b/pkg/cmd/repo/fork/fork.go @@ -218,7 +218,10 @@ func forkRun(opts *ForkOptions) error { } if _, err := remotes.FindByName(remoteName); err == nil { renameTarget := "upstream" - renameCmd := git.GitCommand("remote", "rename", remoteName, renameTarget) + renameCmd, err := git.GitCommand("remote", "rename", remoteName, renameTarget) + if err != nil { + return err + } err = run.PrepareCmd(renameCmd).Run() if err != nil { return err diff --git a/pkg/iostreams/iostreams.go b/pkg/iostreams/iostreams.go index 19bdd546a..99ae0cfdc 100644 --- a/pkg/iostreams/iostreams.go +++ b/pkg/iostreams/iostreams.go @@ -12,6 +12,7 @@ import ( "time" "github.com/briandowns/spinner" + "github.com/cli/safeexec" "github.com/google/shlex" "github.com/mattn/go-colorable" "github.com/mattn/go-isatty" @@ -160,7 +161,11 @@ func (s *IOStreams) StartPager() error { pagerEnv = append(pagerEnv, "LV=-c") } - pagerCmd := exec.Command(pagerArgs[0], pagerArgs[1:]...) + pagerExe, err := safeexec.LookPath(pagerArgs[0]) + if err != nil { + return err + } + pagerCmd := exec.Command(pagerExe, pagerArgs[1:]...) pagerCmd.Env = pagerEnv pagerCmd.Stdout = s.Out pagerCmd.Stderr = s.ErrOut @@ -228,7 +233,11 @@ func (s *IOStreams) TerminalWidth() int { } if isCygwinTerminal(out) { - tputCmd := exec.Command("tput", "cols") + tputExe, err := safeexec.LookPath("tput") + if err != nil { + return defaultWidth + } + tputCmd := exec.Command(tputExe, "cols") tputCmd.Stdin = os.Stdin if out, err := tputCmd.Output(); err == nil { if w, err := strconv.Atoi(strings.TrimSpace(string(out))); err == nil { diff --git a/pkg/surveyext/editor_manual.go b/pkg/surveyext/editor_manual.go index faa2345ad..d5faeb063 100644 --- a/pkg/surveyext/editor_manual.go +++ b/pkg/surveyext/editor_manual.go @@ -7,6 +7,7 @@ import ( "os" "os/exec" + "github.com/cli/safeexec" shellquote "github.com/kballard/go-shellquote" ) @@ -55,7 +56,12 @@ func Edit(editorCommand, fn, initialValue string, stdin io.Reader, stdout io.Wri } args = append(args, f.Name()) - cmd := exec.Command(args[0], args[1:]...) + editorExe, err := safeexec.LookPath(args[0]) + if err != nil { + return "", err + } + + cmd := exec.Command(editorExe, args[1:]...) cmd.Stdin = stdin cmd.Stdout = stdout cmd.Stderr = stderr From c87dc00f385438c09162d8e0f511d0ff10c87fce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Wed, 11 Nov 2020 13:23:06 +0100 Subject: [PATCH 2/3] Omit the full path of a command in DEBUG mode --- internal/run/run.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/run/run.go b/internal/run/run.go index 0c9cea271..a84fe2698 100644 --- a/internal/run/run.go +++ b/internal/run/run.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "os/exec" + "path/filepath" "strings" ) @@ -42,7 +43,9 @@ type cmdWithStderr struct { func (c cmdWithStderr) Output() ([]byte, error) { if os.Getenv("DEBUG") != "" { - fmt.Fprintf(os.Stderr, "%v\n", c.Cmd.Args) + // print commands, but omit the full path to an executable + debugArgs := append([]string{filepath.Base(c.Cmd.Args[0])}, c.Cmd.Args[1:]...) + fmt.Fprintf(os.Stderr, "%v\n", debugArgs) } if c.Cmd.Stderr != nil { return c.Cmd.Output() From 38f68d849fbbf4c540398015719047672d0e3d9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Wed, 11 Nov 2020 13:25:35 +0100 Subject: [PATCH 3/3] Improve error message when git isn't found --- git/git.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/git/git.go b/git/git.go index 36a6573cb..3cda262f0 100644 --- a/git/git.go +++ b/git/git.go @@ -10,6 +10,7 @@ import ( "os/exec" "path" "regexp" + "runtime" "strings" "github.com/cli/cli/internal/run" @@ -110,7 +111,11 @@ func Config(name string) (string, error) { var GitCommand = func(args ...string) (*exec.Cmd, error) { gitExe, err := safeexec.LookPath("git") if err != nil { - return nil, err + programName := "git" + if runtime.GOOS == "windows" { + programName = "Git for Windows" + } + return nil, fmt.Errorf("unable to find git executable in PATH; please install %s before retrying", programName) } return exec.Command(gitExe, args...), nil }