Skip to content

Commit 02ff2f4

Browse files
authored
feat: run command with context (#95)
1 parent 2085e8a commit 02ff2f4

13 files changed

+197
-44
lines changed

command.go

+107-39
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,22 @@ import (
1717

1818
// Command contains the name, arguments and environment variables of a command.
1919
type Command struct {
20-
name string
21-
args []string
22-
envs []string
20+
name string
21+
args []string
22+
envs []string
23+
timeout time.Duration
24+
ctx context.Context
2325
}
2426

2527
// CommandOptions contains options for running a command.
28+
// If timeout is zero, DefaultTimeout will be used.
29+
// If timeout is less than zero, no timeout will be set.
30+
// If context is nil, context.Background() will be used.
2631
type CommandOptions struct {
27-
Args []string
28-
Envs []string
32+
Args []string
33+
Envs []string
34+
Timeout time.Duration
35+
Context context.Context
2936
}
3037

3138
// String returns the string representation of the command.
@@ -38,9 +45,16 @@ func (c *Command) String() string {
3845

3946
// NewCommand creates and returns a new Command with given arguments for "git".
4047
func NewCommand(args ...string) *Command {
48+
return NewCommandWithContext(context.Background(), args...)
49+
}
50+
51+
// NewCommandWithContext creates and returns a new Command with given arguments
52+
// and context for "git".
53+
func NewCommandWithContext(ctx context.Context, args ...string) *Command {
4154
return &Command{
4255
name: "git",
4356
args: args,
57+
ctx: ctx,
4458
}
4559
}
4660

@@ -56,9 +70,29 @@ func (c *Command) AddEnvs(envs ...string) *Command {
5670
return c
5771
}
5872

73+
// WithContext returns a new Command with the given context.
74+
func (c Command) WithContext(ctx context.Context) *Command {
75+
c.ctx = ctx
76+
return &c
77+
}
78+
79+
// WithTimeout returns a new Command with given timeout.
80+
func (c Command) WithTimeout(timeout time.Duration) *Command {
81+
c.timeout = timeout
82+
return &c
83+
}
84+
85+
// SetTimeout sets the timeout for the command.
86+
func (c *Command) SetTimeout(timeout time.Duration) {
87+
c.timeout = timeout
88+
}
89+
5990
// AddOptions adds options to the command.
91+
// Note: only the last option will take effect if there are duplicated options.
6092
func (c *Command) AddOptions(opts ...CommandOptions) *Command {
6193
for _, opt := range opts {
94+
c.timeout = opt.Timeout
95+
c.ctx = opt.Context
6296
c.AddArgs(opt.Args...)
6397
c.AddEnvs(opt.Envs...)
6498
}
@@ -111,6 +145,8 @@ type RunInDirOptions struct {
111145
// Stderr is the error output from the command.
112146
Stderr io.Writer
113147
// Timeout is the duration to wait before timing out.
148+
//
149+
// Deprecated: Use CommandOptions.Timeout or *Command.WithTimeout instead.
114150
Timeout time.Duration
115151
}
116152

@@ -124,8 +160,15 @@ func (c *Command) RunInDirWithOptions(dir string, opts ...RunInDirOptions) (err
124160
if len(opts) > 0 {
125161
opt = opts[0]
126162
}
127-
if opt.Timeout < time.Nanosecond {
128-
opt.Timeout = DefaultTimeout
163+
164+
timeout := c.timeout
165+
// TODO: remove this in newer version
166+
if opt.Timeout > 0 {
167+
timeout = opt.Timeout
168+
}
169+
170+
if timeout == 0 {
171+
timeout = DefaultTimeout
129172
}
130173

131174
buf := new(bytes.Buffer)
@@ -141,19 +184,27 @@ func (c *Command) RunInDirWithOptions(dir string, opts ...RunInDirOptions) (err
141184

142185
defer func() {
143186
if len(dir) == 0 {
144-
log("[timeout: %v] %s\n%s", opt.Timeout, c, buf.Bytes())
187+
log("[timeout: %v] %s\n%s", timeout, c, buf.Bytes())
145188
} else {
146-
log("[timeout: %v] %s: %s\n%s", opt.Timeout, dir, c, buf.Bytes())
189+
log("[timeout: %v] %s: %s\n%s", timeout, dir, c, buf.Bytes())
147190
}
148191
}()
149192

150-
ctx, cancel := context.WithTimeout(context.Background(), opt.Timeout)
151-
defer func() {
152-
cancel()
153-
if err == context.DeadlineExceeded {
154-
err = ErrExecTimeout
155-
}
156-
}()
193+
ctx := context.Background()
194+
if c.ctx != nil {
195+
ctx = c.ctx
196+
}
197+
198+
if timeout > 0 {
199+
var cancel context.CancelFunc
200+
ctx, cancel = context.WithTimeout(ctx, timeout)
201+
defer func() {
202+
cancel()
203+
if err == context.DeadlineExceeded {
204+
err = ErrExecTimeout
205+
}
206+
}()
207+
}
157208

158209
cmd := exec.CommandContext(ctx, c.name, c.args...)
159210
if len(c.envs) > 0 {
@@ -188,55 +239,72 @@ func (c *Command) RunInDirWithOptions(dir string, opts ...RunInDirOptions) (err
188239

189240
}
190241

242+
// RunInDirPipeline executes the command in given directory and default timeout
243+
// duration. It pipes stdout and stderr to supplied io.Writer.
244+
func (c *Command) RunInDirPipeline(stdout, stderr io.Writer, dir string) error {
245+
return c.RunInDirWithOptions(dir, RunInDirOptions{
246+
Stdin: nil,
247+
Stdout: stdout,
248+
Stderr: stderr,
249+
})
250+
}
251+
191252
// RunInDirPipelineWithTimeout executes the command in given directory and
192253
// timeout duration. It pipes stdout and stderr to supplied io.Writer.
193254
// DefaultTimeout will be used if the timeout duration is less than
194255
// time.Nanosecond (i.e. less than or equal to 0). It returns an ErrExecTimeout
195256
// if the execution was timed out.
257+
//
258+
// Deprecated: Use RunInDirPipeline and CommandOptions instead.
259+
// TODO: remove this in the next major version
196260
func (c *Command) RunInDirPipelineWithTimeout(timeout time.Duration, stdout, stderr io.Writer, dir string) (err error) {
197-
return c.RunInDirWithOptions(dir, RunInDirOptions{
198-
Stdin: nil,
199-
Stdout: stdout,
200-
Stderr: stderr,
201-
Timeout: timeout,
202-
})
203-
}
204-
205-
// RunInDirPipeline executes the command in given directory and default timeout
206-
// duration. It pipes stdout and stderr to supplied io.Writer.
207-
func (c *Command) RunInDirPipeline(stdout, stderr io.Writer, dir string) error {
208-
return c.RunInDirPipelineWithTimeout(DefaultTimeout, stdout, stderr, dir)
261+
if timeout != 0 {
262+
c = c.WithTimeout(timeout)
263+
}
264+
return c.RunInDirPipeline(stdout, stderr, dir)
209265
}
210266

211267
// RunInDirWithTimeout executes the command in given directory and timeout
212268
// duration. It returns stdout in []byte and error (combined with stderr).
269+
//
270+
// Deprecated: Use RunInDir and CommandOptions instead.
271+
// TODO: remove this in the next major version
213272
func (c *Command) RunInDirWithTimeout(timeout time.Duration, dir string) ([]byte, error) {
214-
stdout := new(bytes.Buffer)
215-
stderr := new(bytes.Buffer)
216-
if err := c.RunInDirPipelineWithTimeout(timeout, stdout, stderr, dir); err != nil {
217-
return nil, concatenateError(err, stderr.String())
273+
if timeout != 0 {
274+
c = c.WithTimeout(timeout)
218275
}
219-
return stdout.Bytes(), nil
276+
return c.RunInDir(dir)
220277
}
221278

222279
// RunInDir executes the command in given directory and default timeout
223280
// duration. It returns stdout and error (combined with stderr).
224281
func (c *Command) RunInDir(dir string) ([]byte, error) {
225-
return c.RunInDirWithTimeout(DefaultTimeout, dir)
282+
stdout := new(bytes.Buffer)
283+
stderr := new(bytes.Buffer)
284+
if err := c.RunInDirPipeline(stdout, stderr, dir); err != nil {
285+
return nil, concatenateError(err, stderr.String())
286+
}
287+
return stdout.Bytes(), nil
226288
}
227289

228290
// RunWithTimeout executes the command in working directory and given timeout
229291
// duration. It returns stdout in string and error (combined with stderr).
292+
//
293+
// Deprecated: Use RunInDir and CommandOptions instead.
294+
// TODO: remove this in the next major version
230295
func (c *Command) RunWithTimeout(timeout time.Duration) ([]byte, error) {
231-
stdout, err := c.RunInDirWithTimeout(timeout, "")
232-
if err != nil {
233-
return nil, err
296+
if timeout != 0 {
297+
c = c.WithTimeout(timeout)
234298
}
235-
return stdout, nil
299+
return c.Run()
236300
}
237301

238302
// Run executes the command in working directory and default timeout duration.
239303
// It returns stdout in string and error (combined with stderr).
240304
func (c *Command) Run() ([]byte, error) {
241-
return c.RunWithTimeout(DefaultTimeout)
305+
stdout, err := c.RunInDir("")
306+
if err != nil {
307+
return nil, err
308+
}
309+
return stdout, nil
242310
}

command_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,6 @@ func TestCommand_AddEnvs(t *testing.T) {
6060
}
6161

6262
func TestCommand_RunWithTimeout(t *testing.T) {
63-
_, err := NewCommand("version").RunWithTimeout(time.Nanosecond)
63+
_, err := NewCommand("version").WithTimeout(time.Nanosecond).Run()
6464
assert.Equal(t, ErrExecTimeout, err)
6565
}

repo.go

+28
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ type InitOptions struct {
6060
Bare bool
6161
// The timeout duration before giving up for each shell command execution. The
6262
// default timeout duration will be used when not supplied.
63+
//
64+
// Deprecated: Use CommandOptions.Timeout instead.
6365
Timeout time.Duration
6466
// The additional options to be passed to the underlying git.
6567
CommandOptions
@@ -119,6 +121,8 @@ type CloneOptions struct {
119121
Depth uint64
120122
// The timeout duration before giving up for each shell command execution. The
121123
// default timeout duration will be used when not supplied.
124+
//
125+
// Deprecated: Use CommandOptions.Timeout instead.
122126
Timeout time.Duration
123127
// The additional options to be passed to the underlying git.
124128
CommandOptions
@@ -165,6 +169,8 @@ type FetchOptions struct {
165169
Prune bool
166170
// The timeout duration before giving up for each shell command execution. The
167171
// default timeout duration will be used when not supplied.
172+
//
173+
// Deprecated: Use CommandOptions.Timeout instead.
168174
Timeout time.Duration
169175
// The additional options to be passed to the underlying git.
170176
CommandOptions
@@ -200,6 +206,8 @@ type PullOptions struct {
200206
Branch string
201207
// The timeout duration before giving up for each shell command execution. The
202208
// default timeout duration will be used when not supplied.
209+
//
210+
// Deprecated: Use CommandOptions.Timeout instead.
203211
Timeout time.Duration
204212
// The additional options to be passed to the underlying git.
205213
CommandOptions
@@ -236,6 +244,8 @@ func (r *Repository) Pull(opts ...PullOptions) error {
236244
type PushOptions struct {
237245
// The timeout duration before giving up for each shell command execution. The
238246
// default timeout duration will be used when not supplied.
247+
//
248+
// Deprecated: Use CommandOptions.Timeout instead.
239249
Timeout time.Duration
240250
// The additional options to be passed to the underlying git.
241251
CommandOptions
@@ -272,6 +282,8 @@ type CheckoutOptions struct {
272282
BaseBranch string
273283
// The timeout duration before giving up for each shell command execution. The
274284
// default timeout duration will be used when not supplied.
285+
//
286+
// Deprecated: Use CommandOptions.Timeout instead.
275287
Timeout time.Duration
276288
// The additional options to be passed to the underlying git.
277289
CommandOptions
@@ -315,6 +327,8 @@ type ResetOptions struct {
315327
Hard bool
316328
// The timeout duration before giving up for each shell command execution. The
317329
// default timeout duration will be used when not supplied.
330+
//
331+
// Deprecated: Use CommandOptions.Timeout instead.
318332
Timeout time.Duration
319333
// The additional options to be passed to the underlying git.
320334
CommandOptions
@@ -353,6 +367,8 @@ func (r *Repository) Reset(rev string, opts ...ResetOptions) error {
353367
type MoveOptions struct {
354368
// The timeout duration before giving up for each shell command execution. The
355369
// default timeout duration will be used when not supplied.
370+
//
371+
// Deprecated: Use CommandOptions.Timeout instead.
356372
Timeout time.Duration
357373
// The additional options to be passed to the underlying git.
358374
CommandOptions
@@ -391,6 +407,8 @@ type AddOptions struct {
391407
Pathspecs []string
392408
// The timeout duration before giving up for each shell command execution. The
393409
// default timeout duration will be used when not supplied.
410+
//
411+
// Deprecated: Use CommandOptions.Timeout instead.
394412
Timeout time.Duration
395413
// The additional options to be passed to the underlying git.
396414
CommandOptions
@@ -433,6 +451,8 @@ type CommitOptions struct {
433451
Author *Signature
434452
// The timeout duration before giving up for each shell command execution. The
435453
// default timeout duration will be used when not supplied.
454+
//
455+
// Deprecated: Use CommandOptions.Timeout instead.
436456
Timeout time.Duration
437457
// The additional options to be passed to the underlying git.
438458
CommandOptions
@@ -488,6 +508,8 @@ type NameStatus struct {
488508
type ShowNameStatusOptions struct {
489509
// The timeout duration before giving up for each shell command execution. The
490510
// default timeout duration will be used when not supplied.
511+
//
512+
// Deprecated: Use CommandOptions.Timeout instead.
491513
Timeout time.Duration
492514
// The additional options to be passed to the underlying git.
493515
CommandOptions
@@ -554,6 +576,8 @@ func (r *Repository) ShowNameStatus(rev string, opts ...ShowNameStatusOptions) (
554576
type RevParseOptions struct {
555577
// The timeout duration before giving up for each shell command execution. The
556578
// default timeout duration will be used when not supplied.
579+
//
580+
// Deprecated: Use CommandOptions.Timeout instead.
557581
Timeout time.Duration
558582
// The additional options to be passed to the underlying git.
559583
CommandOptions
@@ -598,6 +622,8 @@ type CountObject struct {
598622
type CountObjectsOptions struct {
599623
// The timeout duration before giving up for each shell command execution. The
600624
// default timeout duration will be used when not supplied.
625+
//
626+
// Deprecated: Use CommandOptions.Timeout instead.
601627
Timeout time.Duration
602628
// The additional options to be passed to the underlying git.
603629
CommandOptions
@@ -663,6 +689,8 @@ func (r *Repository) CountObjects(opts ...CountObjectsOptions) (*CountObject, er
663689
type FsckOptions struct {
664690
// The timeout duration before giving up for each shell command execution. The
665691
// default timeout duration will be used when not supplied.
692+
//
693+
// Deprecated: Use CommandOptions.Timeout instead.
666694
Timeout time.Duration
667695
// The additional options to be passed to the underlying git.
668696
CommandOptions

repo_blame.go

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import (
1414
type BlameOptions struct {
1515
// The timeout duration before giving up for each shell command execution. The
1616
// default timeout duration will be used when not supplied.
17+
//
18+
// Deprecated: Use CommandOptions.Timeout instead.
1719
Timeout time.Duration
1820
// The additional options to be passed to the underlying git.
1921
CommandOptions

repo_blob.go

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import "time"
1212
type CatFileBlobOptions struct {
1313
// The timeout duration before giving up for each shell command execution.
1414
// The default timeout duration will be used when not supplied.
15+
//
16+
// Deprecated: Use CommandOptions.Timeout instead.
1517
Timeout time.Duration
1618
// The additional options to be passed to the underlying git.
1719
CommandOptions

repo_commit.go

+2
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ loop:
7272
type CatFileCommitOptions struct {
7373
// The timeout duration before giving up for each shell command execution.
7474
// The default timeout duration will be used when not supplied.
75+
//
76+
// Deprecated: Use CommandOptions.Timeout instead.
7577
Timeout time.Duration
7678
// The additional options to be passed to the underlying git.
7779
CommandOptions

0 commit comments

Comments
 (0)