@@ -10,9 +10,11 @@ import (
1010 "os/exec"
1111 "path"
1212 "regexp"
13+ "runtime"
1314 "strings"
1415
1516 "github.com/cli/cli/internal/run"
17+ "github.com/cli/safeexec"
1618)
1719
1820// ErrNotOnAnyBranch indicates that the user is in detached HEAD state
@@ -37,7 +39,10 @@ func (r TrackingRef) String() string {
3739// ShowRefs resolves fully-qualified refs to commit hashes
3840func ShowRefs (ref ... string ) ([]Ref , error ) {
3941 args := append ([]string {"show-ref" , "--verify" , "--" }, ref ... )
40- showRef := exec .Command ("git" , args ... )
42+ showRef , err := GitCommand (args ... )
43+ if err != nil {
44+ return nil , err
45+ }
4146 output , err := run .PrepareCmd (showRef ).Output ()
4247
4348 var refs []Ref
@@ -57,7 +62,10 @@ func ShowRefs(ref ...string) ([]Ref, error) {
5762
5863// CurrentBranch reads the checked-out branch for the git repository
5964func CurrentBranch () (string , error ) {
60- refCmd := GitCommand ("symbolic-ref" , "--quiet" , "HEAD" )
65+ refCmd , err := GitCommand ("symbolic-ref" , "--quiet" , "HEAD" )
66+ if err != nil {
67+ return "" , err
68+ }
6169
6270 output , err := run .PrepareCmd (refCmd ).Output ()
6371 if err == nil {
@@ -78,13 +86,19 @@ func CurrentBranch() (string, error) {
7886}
7987
8088func listRemotes () ([]string , error ) {
81- remoteCmd := exec .Command ("git" , "remote" , "-v" )
89+ remoteCmd , err := GitCommand ("remote" , "-v" )
90+ if err != nil {
91+ return nil , err
92+ }
8293 output , err := run .PrepareCmd (remoteCmd ).Output ()
8394 return outputLines (output ), err
8495}
8596
8697func Config (name string ) (string , error ) {
87- configCmd := exec .Command ("git" , "config" , name )
98+ configCmd , err := GitCommand ("config" , name )
99+ if err != nil {
100+ return "" , err
101+ }
88102 output , err := run .PrepareCmd (configCmd ).Output ()
89103 if err != nil {
90104 return "" , fmt .Errorf ("unknown config key: %s" , name )
@@ -94,12 +108,23 @@ func Config(name string) (string, error) {
94108
95109}
96110
97- var GitCommand = func (args ... string ) * exec.Cmd {
98- return exec .Command ("git" , args ... )
111+ var GitCommand = func (args ... string ) (* exec.Cmd , error ) {
112+ gitExe , err := safeexec .LookPath ("git" )
113+ if err != nil {
114+ programName := "git"
115+ if runtime .GOOS == "windows" {
116+ programName = "Git for Windows"
117+ }
118+ return nil , fmt .Errorf ("unable to find git executable in PATH; please install %s before retrying" , programName )
119+ }
120+ return exec .Command (gitExe , args ... ), nil
99121}
100122
101123func UncommittedChangeCount () (int , error ) {
102- statusCmd := GitCommand ("status" , "--porcelain" )
124+ statusCmd , err := GitCommand ("status" , "--porcelain" )
125+ if err != nil {
126+ return 0 , err
127+ }
103128 output , err := run .PrepareCmd (statusCmd ).Output ()
104129 if err != nil {
105130 return 0 , err
@@ -123,10 +148,13 @@ type Commit struct {
123148}
124149
125150func Commits (baseRef , headRef string ) ([]* Commit , error ) {
126- logCmd := GitCommand (
151+ logCmd , err := GitCommand (
127152 "-c" , "log.ShowSignature=false" ,
128153 "log" , "--pretty=format:%H,%s" ,
129154 "--cherry" , fmt .Sprintf ("%s...%s" , baseRef , headRef ))
155+ if err != nil {
156+ return nil , err
157+ }
130158 output , err := run .PrepareCmd (logCmd ).Output ()
131159 if err != nil {
132160 return []* Commit {}, err
@@ -154,7 +182,10 @@ func Commits(baseRef, headRef string) ([]*Commit, error) {
154182}
155183
156184func CommitBody (sha string ) (string , error ) {
157- showCmd := GitCommand ("-c" , "log.ShowSignature=false" , "show" , "-s" , "--pretty=format:%b" , sha )
185+ showCmd , err := GitCommand ("-c" , "log.ShowSignature=false" , "show" , "-s" , "--pretty=format:%b" , sha )
186+ if err != nil {
187+ return "" , err
188+ }
158189 output , err := run .PrepareCmd (showCmd ).Output ()
159190 if err != nil {
160191 return "" , err
@@ -164,7 +195,10 @@ func CommitBody(sha string) (string, error) {
164195
165196// Push publishes a git ref to a remote and sets up upstream configuration
166197func Push (remote string , ref string , cmdOut , cmdErr io.Writer ) error {
167- pushCmd := GitCommand ("push" , "--set-upstream" , remote , ref )
198+ pushCmd , err := GitCommand ("push" , "--set-upstream" , remote , ref )
199+ if err != nil {
200+ return err
201+ }
168202 pushCmd .Stdout = cmdOut
169203 pushCmd .Stderr = cmdErr
170204 return run .PrepareCmd (pushCmd ).Run ()
@@ -179,7 +213,10 @@ type BranchConfig struct {
179213// ReadBranchConfig parses the `branch.BRANCH.(remote|merge)` part of git config
180214func ReadBranchConfig (branch string ) (cfg BranchConfig ) {
181215 prefix := regexp .QuoteMeta (fmt .Sprintf ("branch.%s." , branch ))
182- configCmd := GitCommand ("config" , "--get-regexp" , fmt .Sprintf ("^%s(remote|merge)$" , prefix ))
216+ configCmd , err := GitCommand ("config" , "--get-regexp" , fmt .Sprintf ("^%s(remote|merge)$" , prefix ))
217+ if err != nil {
218+ return
219+ }
183220 output , err := run .PrepareCmd (configCmd ).Output ()
184221 if err != nil {
185222 return
@@ -209,21 +246,28 @@ func ReadBranchConfig(branch string) (cfg BranchConfig) {
209246}
210247
211248func DeleteLocalBranch (branch string ) error {
212- branchCmd := GitCommand ("branch" , "-D" , branch )
213- err := run .PrepareCmd (branchCmd ).Run ()
214- return err
249+ branchCmd , err := GitCommand ("branch" , "-D" , branch )
250+ if err != nil {
251+ return err
252+ }
253+ return run .PrepareCmd (branchCmd ).Run ()
215254}
216255
217256func HasLocalBranch (branch string ) bool {
218- configCmd := GitCommand ("rev-parse" , "--verify" , "refs/heads/" + branch )
219- _ , err := run .PrepareCmd (configCmd ).Output ()
257+ configCmd , err := GitCommand ("rev-parse" , "--verify" , "refs/heads/" + branch )
258+ if err != nil {
259+ return false
260+ }
261+ _ , err = run .PrepareCmd (configCmd ).Output ()
220262 return err == nil
221263}
222264
223265func CheckoutBranch (branch string ) error {
224- configCmd := GitCommand ("checkout" , branch )
225- err := run .PrepareCmd (configCmd ).Run ()
226- return err
266+ configCmd , err := GitCommand ("checkout" , branch )
267+ if err != nil {
268+ return err
269+ }
270+ return run .PrepareCmd (configCmd ).Run ()
227271}
228272
229273func parseCloneArgs (extraArgs []string ) (args []string , target string ) {
@@ -252,7 +296,10 @@ func RunClone(cloneURL string, args []string) (target string, err error) {
252296
253297 cloneArgs = append ([]string {"clone" }, cloneArgs ... )
254298
255- cloneCmd := GitCommand (cloneArgs ... )
299+ cloneCmd , err := GitCommand (cloneArgs ... )
300+ if err != nil {
301+ return "" , err
302+ }
256303 cloneCmd .Stdin = os .Stdin
257304 cloneCmd .Stdout = os .Stdout
258305 cloneCmd .Stderr = os .Stderr
@@ -262,7 +309,10 @@ func RunClone(cloneURL string, args []string) (target string, err error) {
262309}
263310
264311func AddUpstreamRemote (upstreamURL , cloneDir string ) error {
265- cloneCmd := GitCommand ("-C" , cloneDir , "remote" , "add" , "-f" , "upstream" , upstreamURL )
312+ cloneCmd , err := GitCommand ("-C" , cloneDir , "remote" , "add" , "-f" , "upstream" , upstreamURL )
313+ if err != nil {
314+ return err
315+ }
266316 cloneCmd .Stdout = os .Stdout
267317 cloneCmd .Stderr = os .Stderr
268318 return run .PrepareCmd (cloneCmd ).Run ()
@@ -274,7 +324,10 @@ func isFilesystemPath(p string) bool {
274324
275325// ToplevelDir returns the top-level directory path of the current repository
276326func ToplevelDir () (string , error ) {
277- showCmd := exec .Command ("git" , "rev-parse" , "--show-toplevel" )
327+ showCmd , err := GitCommand ("rev-parse" , "--show-toplevel" )
328+ if err != nil {
329+ return "" , err
330+ }
278331 output , err := run .PrepareCmd (showCmd ).Output ()
279332 return firstLine (output ), err
280333
0 commit comments