@@ -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
+ err = & ge
91
+ }
92
+ return out , err
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
@@ -278,15 +342,14 @@ func (c *Client) Config(ctx context.Context, name string) (string, error) {
278
342
if err != nil {
279
343
return "" , err
280
344
}
281
- errBuf := bytes.Buffer {}
282
- cmd .Stderr = & errBuf
283
345
out , err := cmd .Output ()
284
346
if err != nil {
285
- var exitError * exec.ExitError
286
- if ok := errors .As (err , & exitError ); ok && exitError .Error () == "1" {
287
- return "" , & GitError {err : err , stderr : fmt .Sprintf ("unknown config key %s" , name )}
347
+ var gitErr * GitError
348
+ if ok := errors .As (err , & gitErr ); ok && gitErr .ExitCode == 1 {
349
+ gitErr .Stderr = fmt .Sprintf ("unknown config key %s" , name )
350
+ return "" , gitErr
288
351
}
289
- return "" , & GitError { err : err , stderr : errBuf . String ()}
352
+ return "" , err
290
353
}
291
354
return firstLine (out ), nil
292
355
}
@@ -299,7 +362,7 @@ func (c *Client) UncommittedChangeCount(ctx context.Context) (int, error) {
299
362
}
300
363
out , err := cmd .Output ()
301
364
if err != nil {
302
- return 0 , & GitError { err : err }
365
+ return 0 , err
303
366
}
304
367
lines := strings .Split (string (out ), "\n " )
305
368
count := 0
@@ -319,7 +382,7 @@ func (c *Client) Commits(ctx context.Context, baseRef, headRef string) ([]*Commi
319
382
}
320
383
out , err := cmd .Output ()
321
384
if err != nil {
322
- return nil , & GitError { err : err }
385
+ return nil , err
323
386
}
324
387
commits := []* Commit {}
325
388
sha := 0
@@ -348,7 +411,7 @@ func (c *Client) lookupCommit(ctx context.Context, sha, format string) ([]byte,
348
411
}
349
412
out , err := cmd .Output ()
350
413
if err != nil {
351
- return nil , & GitError { err : err }
414
+ return nil , err
352
415
}
353
416
return out , nil
354
417
}
@@ -371,13 +434,16 @@ func (c *Client) CommitBody(ctx context.Context, sha string) (string, error) {
371
434
}
372
435
373
436
// Push publishes a git ref to a remote and sets up upstream configuration.
374
- func (c * Client ) Push (ctx context.Context , remote string , ref string ) error {
437
+ func (c * Client ) Push (ctx context.Context , remote string , ref string , mods ... CommandModifier ) error {
375
438
args := []string {"push" , "--set-upstream" , remote , ref }
376
439
//TODO: Use AuthenticatedCommand
377
440
cmd , err := c .Command (ctx , args ... )
378
441
if err != nil {
379
442
return err
380
443
}
444
+ for _ , mod := range mods {
445
+ mod (cmd )
446
+ }
381
447
return cmd .Run ()
382
448
}
383
449
@@ -423,7 +489,11 @@ func (c *Client) DeleteLocalBranch(ctx context.Context, branch string) error {
423
489
if err != nil {
424
490
return err
425
491
}
426
- return cmd .Run ()
492
+ _ , err = cmd .Output ()
493
+ if err != nil {
494
+ return err
495
+ }
496
+ return nil
427
497
}
428
498
429
499
func (c * Client ) HasLocalBranch (ctx context.Context , branch string ) bool {
@@ -432,7 +502,7 @@ func (c *Client) HasLocalBranch(ctx context.Context, branch string) bool {
432
502
if err != nil {
433
503
return false
434
504
}
435
- err = cmd .Run ()
505
+ _ , err = cmd .Output ()
436
506
return err == nil
437
507
}
438
508
@@ -442,7 +512,11 @@ func (c *Client) CheckoutBranch(ctx context.Context, branch string) error {
442
512
if err != nil {
443
513
return err
444
514
}
445
- return cmd .Run ()
515
+ _ , err = cmd .Output ()
516
+ if err != nil {
517
+ return err
518
+ }
519
+ return nil
446
520
}
447
521
448
522
func (c * Client ) CheckoutNewBranch (ctx context.Context , remoteName , branch string ) error {
@@ -452,7 +526,11 @@ func (c *Client) CheckoutNewBranch(ctx context.Context, remoteName, branch strin
452
526
if err != nil {
453
527
return err
454
528
}
455
- return cmd .Run ()
529
+ _ , err = cmd .Output ()
530
+ if err != nil {
531
+ return err
532
+ }
533
+ return nil
456
534
}
457
535
458
536
func (c * Client ) Pull (ctx context.Context , remote , branch string ) error {
@@ -465,7 +543,7 @@ func (c *Client) Pull(ctx context.Context, remote, branch string) error {
465
543
return cmd .Run ()
466
544
}
467
545
468
- func (c * Client ) Clone (ctx context.Context , cloneURL string , args []string ) (target string , err error ) {
546
+ func (c * Client ) Clone (ctx context.Context , cloneURL string , args []string ) (string , error ) {
469
547
cloneArgs , target := parseCloneArgs (args )
470
548
cloneArgs = append (cloneArgs , cloneURL )
471
549
// If the args contain an explicit target, pass it to clone
@@ -482,7 +560,10 @@ func (c *Client) Clone(ctx context.Context, cloneURL string, args []string) (tar
482
560
return "" , err
483
561
}
484
562
err = cmd .Run ()
485
- return
563
+ if err != nil {
564
+ return "" , err
565
+ }
566
+ return target , nil
486
567
}
487
568
488
569
// ToplevelDir returns the top-level directory path of the current repository.
@@ -494,7 +575,7 @@ func (c *Client) ToplevelDir(ctx context.Context) (string, error) {
494
575
}
495
576
out , err := cmd .Output ()
496
577
if err != nil {
497
- return "" , & GitError { err : err }
578
+ return "" , err
498
579
}
499
580
return firstLine (out ), nil
500
581
}
@@ -507,7 +588,7 @@ func (c *Client) GitDir(ctx context.Context) (string, error) {
507
588
}
508
589
out , err := cmd .Output ()
509
590
if err != nil {
510
- return "" , & GitError { err : err }
591
+ return "" , err
511
592
}
512
593
return firstLine (out ), nil
513
594
}
0 commit comments