Skip to content

Commit bbccf28

Browse files
committed
feat: run command with context
Pass a context down to *exec.Command and make timing out optional when o.Timeout < 0. Using `context.Background()` _all the time_ is not practical as sometimes we need to stop the execution when _a_ parent context stops. Signed-off-by: Ayman Bagabas <[email protected]>
1 parent 2085e8a commit bbccf28

File tree

1 file changed

+41
-10
lines changed

1 file changed

+41
-10
lines changed

command.go

+41-10
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,18 @@ type Command struct {
2020
name string
2121
args []string
2222
envs []string
23+
ctx context.Context
2324
}
2425

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

3137
// String returns the string representation of the command.
@@ -38,9 +44,16 @@ func (c *Command) String() string {
3844

3945
// NewCommand creates and returns a new Command with given arguments for "git".
4046
func NewCommand(args ...string) *Command {
47+
return NewCommandWithContext(context.Background(), args...)
48+
}
49+
50+
// NewCommandWithContext creates and returns a new Command with given arguments
51+
// and context for "git".
52+
func NewCommandWithContext(ctx context.Context, args ...string) *Command {
4153
return &Command{
4254
name: "git",
4355
args: args,
56+
ctx: ctx,
4457
}
4558
}
4659

@@ -56,9 +69,17 @@ func (c *Command) AddEnvs(envs ...string) *Command {
5669
return c
5770
}
5871

72+
// WithContext sets the context for the command.
73+
func (c *Command) WithContext(ctx context.Context) *Command {
74+
c.ctx = ctx
75+
return c
76+
}
77+
5978
// AddOptions adds options to the command.
79+
// Note: only the last option will take effect if there are duplicated options.
6080
func (c *Command) AddOptions(opts ...CommandOptions) *Command {
6181
for _, opt := range opts {
82+
c = c.WithContext(opt.Context)
6283
c.AddArgs(opt.Args...)
6384
c.AddEnvs(opt.Envs...)
6485
}
@@ -124,7 +145,9 @@ func (c *Command) RunInDirWithOptions(dir string, opts ...RunInDirOptions) (err
124145
if len(opts) > 0 {
125146
opt = opts[0]
126147
}
127-
if opt.Timeout < time.Nanosecond {
148+
if opt.Timeout < 0 {
149+
opt.Timeout = -1
150+
} else if opt.Timeout == 0 {
128151
opt.Timeout = DefaultTimeout
129152
}
130153

@@ -147,13 +170,21 @@ func (c *Command) RunInDirWithOptions(dir string, opts ...RunInDirOptions) (err
147170
}
148171
}()
149172

150-
ctx, cancel := context.WithTimeout(context.Background(), opt.Timeout)
151-
defer func() {
152-
cancel()
153-
if err == context.DeadlineExceeded {
154-
err = ErrExecTimeout
155-
}
156-
}()
173+
ctx := context.Background()
174+
if c.ctx != nil {
175+
ctx = c.ctx
176+
}
177+
178+
if opt.Timeout > 0 {
179+
var cancel context.CancelFunc
180+
ctx, cancel = context.WithTimeout(ctx, opt.Timeout)
181+
defer func() {
182+
cancel()
183+
if err == context.DeadlineExceeded {
184+
err = ErrExecTimeout
185+
}
186+
}()
187+
}
157188

158189
cmd := exec.CommandContext(ctx, c.name, c.args...)
159190
if len(c.envs) > 0 {

0 commit comments

Comments
 (0)