Skip to content

Commit c7ab9f2

Browse files
committed
fix: address PR review feedback on run migration
Fix timeout context cancel leakage, tighten exit-status matching, remove fragile stderr re-concatenation paths, and harden author/diff argument handling.
1 parent 3edb1de commit c7ab9f2

6 files changed

Lines changed: 40 additions & 49 deletions

File tree

blob.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,14 @@ type Blob struct {
1919
// can be very slow and memory consuming for huge content.
2020
func (b *Blob) Bytes(ctx context.Context) ([]byte, error) {
2121
stdout := new(bytes.Buffer)
22-
stderr := new(bytes.Buffer)
2322

2423
// Preallocate memory to save ~50% memory usage on big files.
2524
if size := b.Size(ctx); size > 0 && size < int64(^uint(0)>>1) {
2625
stdout.Grow(int(size))
2726
}
2827

29-
if err := b.Pipeline(ctx, stdout, stderr); err != nil {
30-
return nil, concatenateError(err, stderr.String())
28+
if err := b.Pipeline(ctx, stdout, nil); err != nil {
29+
return nil, err
3130
}
3231
return stdout.Bytes(), nil
3332
}

command.go

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ package git
77
import (
88
"bytes"
99
"context"
10+
"errors"
1011
"fmt"
1112
"io"
1213
"os"
14+
"os/exec"
1315
"strings"
1416
"time"
1517

@@ -29,16 +31,17 @@ const DefaultTimeout = time.Minute
2931
// gitCmd builds a *run.Command for "git" with the given arguments, environment
3032
// variables and working directory. If the context does not already have a
3133
// deadline, DefaultTimeout will be applied automatically.
32-
func gitCmd(ctx context.Context, dir string, args []string, envs []string) *run.Command {
34+
func gitCmd(ctx context.Context, dir string, args []string, envs []string) (*run.Command, context.CancelFunc) {
3335
if ctx == nil {
3436
ctx = context.Background()
3537
}
38+
cancel := func() {}
3639

3740
// Apply default timeout if the context doesn't already have a deadline.
3841
if _, ok := ctx.Deadline(); !ok {
39-
var cancel context.CancelFunc
40-
ctx, cancel = context.WithTimeout(ctx, DefaultTimeout)
41-
_ = cancel
42+
var timeoutCancel context.CancelFunc
43+
ctx, timeoutCancel = context.WithTimeout(ctx, DefaultTimeout)
44+
cancel = timeoutCancel
4245
}
4346

4447
// run.Cmd joins all parts into a single string and then shell-parses it.
@@ -57,25 +60,26 @@ func gitCmd(ctx context.Context, dir string, args []string, envs []string) *run.
5760
if len(envs) > 0 {
5861
cmd = cmd.Environ(append(os.Environ(), envs...))
5962
}
60-
return cmd
63+
return cmd, cancel
6164
}
6265

6366
// gitRun executes a git command in the given directory and returns stdout as
6467
// bytes. Stderr is included in the error message on failure. If the command's
6568
// context does not have a deadline, DefaultTimeout will be applied
6669
// automatically. It returns an ErrExecTimeout if the execution was timed out.
6770
func gitRun(ctx context.Context, dir string, args []string, envs []string) ([]byte, error) {
68-
cmd := gitCmd(ctx, dir, args, envs)
71+
cmd, cancel := gitCmd(ctx, dir, args, envs)
72+
defer cancel()
6973

70-
logBuf := new(bytes.Buffer)
74+
var logBuf *bytes.Buffer
7175
if logOutput != nil {
76+
logBuf = new(bytes.Buffer)
7277
logBuf.Grow(512)
78+
defer func() {
79+
logf(dir, args, logBuf.Bytes())
80+
}()
7381
}
7482

75-
defer func() {
76-
logf(dir, args, logBuf.Bytes())
77-
}()
78-
7983
// Use Stream to a buffer to preserve raw bytes (including NUL bytes from
8084
// commands like "ls-tree -z"). The String/Lines methods process output
8185
// line-by-line which corrupts binary-ish output.
@@ -104,25 +108,27 @@ func gitRun(ctx context.Context, dir string, args []string, envs []string) ([]by
104108
// to the given writer. If stderr writer is provided and the command fails,
105109
// stderr content extracted from the error is written to it. stdin is optional.
106110
func gitPipeline(ctx context.Context, dir string, args []string, envs []string, stdout, stderr io.Writer, stdin io.Reader) error {
107-
cmd := gitCmd(ctx, dir, args, envs)
111+
cmd, cancel := gitCmd(ctx, dir, args, envs)
112+
defer cancel()
108113
if stdin != nil {
109114
cmd = cmd.Input(stdin)
110115
}
111116

112-
buf := new(bytes.Buffer)
117+
var buf *bytes.Buffer
113118
w := stdout
114119
if logOutput != nil {
120+
buf = new(bytes.Buffer)
115121
buf.Grow(512)
116122
w = &limitDualWriter{
117123
W: buf,
118124
N: int64(buf.Cap()),
119125
w: stdout,
120126
}
121-
}
122127

123-
defer func() {
124-
logf(dir, args, buf.Bytes())
125-
}()
128+
defer func() {
129+
logf(dir, args, buf.Bytes())
130+
}()
131+
}
126132

127133
streamErr := cmd.StdOut().Run().Stream(w)
128134
if streamErr != nil {
@@ -195,12 +201,6 @@ func mapContextError(err error, ctx context.Context) error {
195201
}
196202
return ctxErr
197203
}
198-
// Also check if the error itself wraps a context error.
199-
if strings.Contains(err.Error(), "signal: killed") || strings.Contains(err.Error(), context.DeadlineExceeded.Error()) {
200-
if ctx.Err() == context.DeadlineExceeded {
201-
return ErrExecTimeout
202-
}
203-
}
204204
return err
205205
}
206206

@@ -210,6 +210,10 @@ func extractStderr(err error) string {
210210
if err == nil {
211211
return ""
212212
}
213+
var exitErr *exec.ExitError
214+
if errors.As(err, &exitErr) && len(exitErr.Stderr) > 0 {
215+
return string(exitErr.Stderr)
216+
}
213217
msg := err.Error()
214218
// sourcegraph/run error format: "exit status N: <stderr>"
215219
if idx := strings.Index(msg, ": "); idx >= 0 && strings.HasPrefix(msg, "exit status") {

repo.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -387,13 +387,13 @@ func (r *Repository) Commit(ctx context.Context, committer *Signature, message s
387387
}
388388

389389
args := []string{"commit"}
390-
args = append(args, fmt.Sprintf("--author='%s <%s>'", opt.Author.Name, opt.Author.Email))
390+
args = append(args, fmt.Sprintf("--author=%s <%s>", opt.Author.Name, opt.Author.Email))
391391
args = append(args, "-m", message)
392392
args = append(args, opt.Args...)
393393

394394
_, err := gitRun(ctx, r.path, args, envs)
395395
// No stderr but exit status 1 means nothing to commit.
396-
if err != nil && err.Error() == "exit status 1" {
396+
if err != nil && strings.HasPrefix(err.Error(), "exit status 1") {
397397
return nil
398398
}
399399
return err
@@ -448,11 +448,10 @@ func (r *Repository) ShowNameStatus(ctx context.Context, rev string, opts ...Sho
448448
args = append(args, opt.Args...)
449449
args = append(args, "--end-of-options", rev)
450450

451-
stderr := new(bytes.Buffer)
452-
err := gitPipeline(ctx, r.path, args, opt.Envs, w, stderr, nil)
451+
err := gitPipeline(ctx, r.path, args, opt.Envs, w, nil, nil)
453452
_ = w.Close() // Close writer to exit parsing goroutine
454453
if err != nil {
455-
return nil, concatenateError(err, stderr.String())
454+
return nil, err
456455
}
457456

458457
<-done
@@ -481,7 +480,7 @@ func (r *Repository) RevParse(ctx context.Context, rev string, opts ...RevParseO
481480

482481
commitID, err := gitRun(ctx, r.path, args, opt.Envs)
483482
if err != nil {
484-
if strings.Contains(err.Error(), "exit status 128") {
483+
if strings.HasPrefix(err.Error(), "exit status 128") {
485484
return "", ErrRevisionNotExist
486485
}
487486
return "", err

repo_diff.go

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
package git
66

77
import (
8-
"bytes"
98
"context"
109
"fmt"
1110
"io"
@@ -60,11 +59,10 @@ func (r *Repository) Diff(ctx context.Context, rev string, maxFiles, maxFileLine
6059
done := make(chan SteamParseDiffResult)
6160
go StreamParseDiff(stdout, done, maxFiles, maxFileLines, maxLineChars)
6261

63-
stderr := new(bytes.Buffer)
64-
err = gitPipeline(ctx, r.path, args, opt.Envs, w, stderr, nil)
62+
err = gitPipeline(ctx, r.path, args, opt.Envs, w, nil, nil)
6563
_ = w.Close() // Close writer to exit parsing goroutine
6664
if err != nil {
67-
return nil, concatenateError(err, stderr.String())
65+
return nil, err
6866
}
6967

7068
result := <-done
@@ -134,9 +132,8 @@ func (r *Repository) RawDiff(ctx context.Context, rev string, diffType RawDiffFo
134132
return fmt.Errorf("invalid diffType: %s", diffType)
135133
}
136134

137-
stderr := new(bytes.Buffer)
138-
if err = gitPipeline(ctx, r.path, args, opt.Envs, w, stderr, nil); err != nil {
139-
return concatenateError(err, stderr.String())
135+
if err = gitPipeline(ctx, r.path, args, opt.Envs, w, nil, nil); err != nil {
136+
return err
140137
}
141138
return nil
142139
}
@@ -157,7 +154,7 @@ func (r *Repository) DiffBinary(ctx context.Context, base, head string, opts ...
157154

158155
args := []string{"diff"}
159156
args = append(args, opt.Args...)
160-
args = append(args, "--full-index", "--binary", base, head)
157+
args = append(args, "--full-index", "--binary", "--end-of-options", base, head)
161158

162159
return gitRun(ctx, r.path, args, opt.Envs)
163160
}

repo_pull.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func (r *Repository) MergeBase(ctx context.Context, base, head string, opts ...M
3131

3232
stdout, err := gitRun(ctx, r.path, args, opt.Envs)
3333
if err != nil {
34-
if strings.Contains(err.Error(), "exit status 1") {
34+
if strings.HasPrefix(err.Error(), "exit status 1") {
3535
return "", ErrNoMergeBase
3636
}
3737
return "", err

utils.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
package git
66

77
import (
8-
"fmt"
98
"os"
109
"strings"
1110
"sync"
@@ -66,13 +65,6 @@ func isExist(path string) bool {
6665
return err == nil || os.IsExist(err)
6766
}
6867

69-
func concatenateError(err error, stderr string) error {
70-
if len(stderr) == 0 {
71-
return err
72-
}
73-
return fmt.Errorf("%v - %s", err, stderr)
74-
}
75-
7668
// bytesToStrings splits given bytes into strings by line separator ("\n"). It
7769
// returns empty slice if the given bytes only contains line separators.
7870
func bytesToStrings(in []byte) []string {

0 commit comments

Comments
 (0)