@@ -38,22 +38,16 @@ func (e *NotInstalled) Unwrap() error {
38
38
}
39
39
40
40
type GitError struct {
41
- stderr string
42
- err error
41
+ ExitCode int
42
+ Stderr string
43
+ err error
43
44
}
44
45
45
46
func (ge * GitError ) Error () string {
46
- stderr := ge .stderr
47
- if stderr == "" {
48
- var exitError * exec.ExitError
49
- if errors .As (ge .err , & exitError ) {
50
- stderr = string (exitError .Stderr )
51
- }
52
- }
53
- if stderr == "" {
47
+ if ge .Stderr == "" {
54
48
return fmt .Sprintf ("failed to run git: %v" , ge .err )
55
49
}
56
- return fmt .Sprintf ("failed to run git: %s" , stderr )
50
+ return fmt .Sprintf ("failed to run git: %s" , ge . Stderr )
57
51
}
58
52
59
53
func (ge * GitError ) Unwrap () error {
@@ -64,16 +58,77 @@ type gitCommand struct {
64
58
* exec.Cmd
65
59
}
66
60
67
- // This is a hack in order to not break the hundreds of
68
- // existing tests that rely on `run.PrepareCmd` to be invoked.
69
61
func (gc * gitCommand ) Run () error {
70
- return run .PrepareCmd (gc .Cmd ).Run ()
62
+ // This is a hack in order to not break the hundreds of
63
+ // existing tests that rely on `run.PrepareCmd` to be invoked.
64
+ err := run .PrepareCmd (gc .Cmd ).Run ()
65
+ if err != nil {
66
+ ge := GitError {err : err }
67
+ var exitError * exec.ExitError
68
+ if errors .As (err , & exitError ) {
69
+ ge .Stderr = string (exitError .Stderr )
70
+ ge .ExitCode = exitError .ExitCode ()
71
+ }
72
+ return & ge
73
+ }
74
+ return nil
71
75
}
72
76
73
- // This is a hack in order to not break the hundreds of
74
- // existing tests that rely on `run.PrepareCmd` to be invoked.
75
77
func (gc * gitCommand ) Output () ([]byte , error ) {
76
- return run .PrepareCmd (gc .Cmd ).Output ()
78
+ gc .Stdout = nil
79
+ gc .Stderr = nil
80
+ // This is a hack in order to not break the hundreds of
81
+ // existing tests that rely on `run.PrepareCmd` to be invoked.
82
+ out , err := run .PrepareCmd (gc .Cmd ).Output ()
83
+ if err != nil {
84
+ ge := GitError {err : err }
85
+ var exitError * exec.ExitError
86
+ if errors .As (err , & exitError ) {
87
+ ge .Stderr = string (exitError .Stderr )
88
+ ge .ExitCode = exitError .ExitCode ()
89
+ }
90
+ return []byte {}, & ge
91
+ }
92
+ return out , nil
93
+ }
94
+
95
+ func (gc * gitCommand ) setRepoDir (repoDir string ) {
96
+ for i , arg := range gc .Args {
97
+ if arg == "-C" {
98
+ gc .Args [i + 1 ] = repoDir
99
+ return
100
+ }
101
+ }
102
+ gc .Args = append (gc .Args [:3 ], gc .Args [1 :]... )
103
+ gc .Args [1 ] = "-C"
104
+ gc .Args [2 ] = repoDir
105
+ }
106
+
107
+ // Allow individual commands to be modified from the default client options.
108
+ type CommandModifier func (* gitCommand )
109
+
110
+ func WithStderr (stderr io.Writer ) CommandModifier {
111
+ return func (gc * gitCommand ) {
112
+ gc .Stderr = stderr
113
+ }
114
+ }
115
+
116
+ func WithStdout (stdout io.Writer ) CommandModifier {
117
+ return func (gc * gitCommand ) {
118
+ gc .Stdout = stdout
119
+ }
120
+ }
121
+
122
+ func WithStdin (stdin io.Reader ) CommandModifier {
123
+ return func (gc * gitCommand ) {
124
+ gc .Stdin = stdin
125
+ }
126
+ }
127
+
128
+ func WithRepoDir (repoDir string ) CommandModifier {
129
+ return func (gc * gitCommand ) {
130
+ gc .setRepoDir (repoDir )
131
+ }
77
132
}
78
133
79
134
type Client struct {
@@ -133,8 +188,7 @@ func resolveGitPath() (string, error) {
133
188
// AuthenticatedCommand is a wrapper around Command that included configuration to use gh
134
189
// as the credential helper for git.
135
190
func (c * Client ) AuthenticatedCommand (ctx context.Context , args ... string ) (* gitCommand , error ) {
136
- preArgs := []string {}
137
- preArgs = append (preArgs , "-c" , "credential.helper=" )
191
+ preArgs := []string {"-c" , "credential.helper=" }
138
192
if c .GhPath == "" {
139
193
// Assumes that gh is in PATH.
140
194
c .GhPath = "gh"
@@ -153,7 +207,7 @@ func (c *Client) Remotes(ctx context.Context) (RemoteSet, error) {
153
207
}
154
208
remoteOut , remoteErr := remoteCmd .Output ()
155
209
if remoteErr != nil {
156
- return nil , & GitError { err : remoteErr }
210
+ return nil , remoteErr
157
211
}
158
212
159
213
configArgs := []string {"config" , "--get-regexp" , `^remote\..*\.gh-resolved$` }
@@ -164,9 +218,9 @@ func (c *Client) Remotes(ctx context.Context) (RemoteSet, error) {
164
218
configOut , configErr := configCmd .Output ()
165
219
if configErr != nil {
166
220
// Ignore exit code 1 as it means there are no resolved remotes.
167
- var exitErr * exec. ExitError
168
- if errors .As (configErr , & exitErr ) && exitErr .ExitCode () != 1 {
169
- return nil , & GitError { err : configErr }
221
+ var gitErr * GitError
222
+ if ok := errors .As (configErr , & gitErr ); ok && gitErr .ExitCode != 1 {
223
+ return nil , gitErr
170
224
}
171
225
}
172
226
@@ -176,18 +230,20 @@ func (c *Client) Remotes(ctx context.Context) (RemoteSet, error) {
176
230
return remotes , nil
177
231
}
178
232
179
- func (c * Client ) AddRemote (ctx context.Context , name , urlStr string , trackingBranches []string ) (* Remote , error ) {
233
+ func (c * Client ) AddRemote (ctx context.Context , name , urlStr string , trackingBranches []string , mods ... CommandModifier ) (* Remote , error ) {
180
234
args := []string {"remote" , "add" }
181
235
for _ , branch := range trackingBranches {
182
236
args = append (args , "-t" , branch )
183
237
}
184
238
args = append (args , "-f" , name , urlStr )
185
- //TODO: Use AuthenticatedCommand
186
239
cmd , err := c .Command (ctx , args ... )
187
240
if err != nil {
188
241
return nil , err
189
242
}
190
- if err := cmd .Run (); err != nil {
243
+ for _ , mod := range mods {
244
+ mod (cmd )
245
+ }
246
+ if _ , err := cmd .Output (); err != nil {
191
247
return nil , err
192
248
}
193
249
var urlParsed * url.URL
@@ -216,7 +272,11 @@ func (c *Client) UpdateRemoteURL(ctx context.Context, name, url string) error {
216
272
if err != nil {
217
273
return err
218
274
}
219
- return cmd .Run ()
275
+ _ , err = cmd .Output ()
276
+ if err != nil {
277
+ return err
278
+ }
279
+ return nil
220
280
}
221
281
222
282
func (c * Client ) SetRemoteResolution (ctx context.Context , name , resolution string ) error {
@@ -225,7 +285,11 @@ func (c *Client) SetRemoteResolution(ctx context.Context, name, resolution strin
225
285
if err != nil {
226
286
return err
227
287
}
228
- return cmd .Run ()
288
+ _ , err = cmd .Output ()
289
+ if err != nil {
290
+ return err
291
+ }
292
+ return nil
229
293
}
230
294
231
295
// CurrentBranch reads the checked-out branch for the git repository.
@@ -235,14 +299,14 @@ func (c *Client) CurrentBranch(ctx context.Context) (string, error) {
235
299
if err != nil {
236
300
return "" , err
237
301
}
238
- errBuf := bytes.Buffer {}
239
- cmd .Stderr = & errBuf
240
302
out , err := cmd .Output ()
241
303
if err != nil {
242
- if errBuf .Len () == 0 {
243
- return "" , & GitError {err : err , stderr : "not on any branch" }
304
+ var gitErr * GitError
305
+ if ok := errors .As (err , & gitErr ); ok && len (gitErr .Stderr ) == 0 {
306
+ gitErr .Stderr = "not on any branch"
307
+ return "" , gitErr
244
308
}
245
- return "" , & GitError { err : err , stderr : errBuf . String ()}
309
+ return "" , err
246
310
}
247
311
branch := firstLine (out )
248
312
return strings .TrimPrefix (branch , "refs/heads/" ), nil
@@ -257,7 +321,7 @@ func (c *Client) ShowRefs(ctx context.Context, ref ...string) ([]Ref, error) {
257
321
}
258
322
out , err := cmd .Output ()
259
323
if err != nil {
260
- return nil , & GitError { err : err }
324
+ return nil , err
261
325
}
262
326
var refs []Ref
263
327
for _ , line := range outputLines (out ) {
@@ -279,15 +343,14 @@ func (c *Client) Config(ctx context.Context, name string) (string, error) {
279
343
if err != nil {
280
344
return "" , err
281
345
}
282
- errBuf := bytes.Buffer {}
283
- cmd .Stderr = & errBuf
284
346
out , err := cmd .Output ()
285
347
if err != nil {
286
- var exitError * exec.ExitError
287
- if ok := errors .As (err , & exitError ); ok && exitError .Error () == "1" {
288
- return "" , & GitError {err : err , stderr : fmt .Sprintf ("unknown config key %s" , name )}
348
+ var gitErr * GitError
349
+ if ok := errors .As (err , & gitErr ); ok && gitErr .ExitCode == 1 {
350
+ gitErr .Stderr = fmt .Sprintf ("unknown config key %s" , name )
351
+ return "" , gitErr
289
352
}
290
- return "" , & GitError { err : err , stderr : errBuf . String ()}
353
+ return "" , err
291
354
}
292
355
return firstLine (out ), nil
293
356
}
@@ -300,7 +363,7 @@ func (c *Client) UncommittedChangeCount(ctx context.Context) (int, error) {
300
363
}
301
364
out , err := cmd .Output ()
302
365
if err != nil {
303
- return 0 , & GitError { err : err }
366
+ return 0 , err
304
367
}
305
368
lines := strings .Split (string (out ), "\n " )
306
369
count := 0
@@ -320,7 +383,7 @@ func (c *Client) Commits(ctx context.Context, baseRef, headRef string) ([]*Commi
320
383
}
321
384
out , err := cmd .Output ()
322
385
if err != nil {
323
- return nil , & GitError { err : err }
386
+ return nil , err
324
387
}
325
388
commits := []* Commit {}
326
389
sha := 0
@@ -349,7 +412,7 @@ func (c *Client) lookupCommit(ctx context.Context, sha, format string) ([]byte,
349
412
}
350
413
out , err := cmd .Output ()
351
414
if err != nil {
352
- return nil , & GitError { err : err }
415
+ return nil , err
353
416
}
354
417
return out , nil
355
418
}
@@ -372,13 +435,16 @@ func (c *Client) CommitBody(ctx context.Context, sha string) (string, error) {
372
435
}
373
436
374
437
// Push publishes a git ref to a remote and sets up upstream configuration.
375
- func (c * Client ) Push (ctx context.Context , remote string , ref string ) error {
438
+ func (c * Client ) Push (ctx context.Context , remote string , ref string , mods ... CommandModifier ) error {
376
439
args := []string {"push" , "--set-upstream" , remote , ref }
377
440
//TODO: Use AuthenticatedCommand
378
441
cmd , err := c .Command (ctx , args ... )
379
442
if err != nil {
380
443
return err
381
444
}
445
+ for _ , mod := range mods {
446
+ mod (cmd )
447
+ }
382
448
return cmd .Run ()
383
449
}
384
450
@@ -424,7 +490,11 @@ func (c *Client) DeleteLocalBranch(ctx context.Context, branch string) error {
424
490
if err != nil {
425
491
return err
426
492
}
427
- return cmd .Run ()
493
+ _ , err = cmd .Output ()
494
+ if err != nil {
495
+ return err
496
+ }
497
+ return nil
428
498
}
429
499
430
500
func (c * Client ) HasLocalBranch (ctx context.Context , branch string ) bool {
@@ -433,7 +503,7 @@ func (c *Client) HasLocalBranch(ctx context.Context, branch string) bool {
433
503
if err != nil {
434
504
return false
435
505
}
436
- err = cmd .Run ()
506
+ _ , err = cmd .Output ()
437
507
return err == nil
438
508
}
439
509
@@ -443,7 +513,11 @@ func (c *Client) CheckoutBranch(ctx context.Context, branch string) error {
443
513
if err != nil {
444
514
return err
445
515
}
446
- return cmd .Run ()
516
+ _ , err = cmd .Output ()
517
+ if err != nil {
518
+ return err
519
+ }
520
+ return nil
447
521
}
448
522
449
523
func (c * Client ) CheckoutNewBranch (ctx context.Context , remoteName , branch string ) error {
@@ -453,7 +527,11 @@ func (c *Client) CheckoutNewBranch(ctx context.Context, remoteName, branch strin
453
527
if err != nil {
454
528
return err
455
529
}
456
- return cmd .Run ()
530
+ _ , err = cmd .Output ()
531
+ if err != nil {
532
+ return err
533
+ }
534
+ return nil
457
535
}
458
536
459
537
func (c * Client ) Pull (ctx context.Context , remote , branch string ) error {
@@ -466,7 +544,7 @@ func (c *Client) Pull(ctx context.Context, remote, branch string) error {
466
544
return cmd .Run ()
467
545
}
468
546
469
- func (c * Client ) Clone (ctx context.Context , cloneURL string , args []string ) (target string , err error ) {
547
+ func (c * Client ) Clone (ctx context.Context , cloneURL string , args []string ) (string , error ) {
470
548
cloneArgs , target := parseCloneArgs (args )
471
549
cloneArgs = append (cloneArgs , cloneURL )
472
550
// If the args contain an explicit target, pass it to clone
@@ -483,7 +561,10 @@ func (c *Client) Clone(ctx context.Context, cloneURL string, args []string) (tar
483
561
return "" , err
484
562
}
485
563
err = cmd .Run ()
486
- return
564
+ if err != nil {
565
+ return "" , err
566
+ }
567
+ return target , nil
487
568
}
488
569
489
570
// ToplevelDir returns the top-level directory path of the current repository.
@@ -495,7 +576,7 @@ func (c *Client) ToplevelDir(ctx context.Context) (string, error) {
495
576
}
496
577
out , err := cmd .Output ()
497
578
if err != nil {
498
- return "" , & GitError { err : err }
579
+ return "" , err
499
580
}
500
581
return firstLine (out ), nil
501
582
}
@@ -508,7 +589,7 @@ func (c *Client) GitDir(ctx context.Context) (string, error) {
508
589
}
509
590
out , err := cmd .Output ()
510
591
if err != nil {
511
- return "" , & GitError { err : err }
592
+ return "" , err
512
593
}
513
594
return firstLine (out ), nil
514
595
}
0 commit comments