@@ -17,22 +17,16 @@ import (
1717
1818// Command contains the name, arguments and environment variables of a command.
1919type Command struct {
20- name string
21- args []string
22- envs []string
23- timeout time.Duration
24- ctx context.Context
20+ name string
21+ args []string
22+ envs []string
23+ ctx context.Context
2524}
2625
2726// 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.
3127type CommandOptions struct {
32- Args []string
33- Envs []string
34- Timeout time.Duration
35- Context context.Context
28+ Args []string
29+ Envs []string
3630}
3731
3832// String returns the string representation of the command.
@@ -44,13 +38,7 @@ func (c *Command) String() string {
4438}
4539
4640// NewCommand creates and returns a new Command with given arguments for "git".
47- 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 {
41+ func NewCommand (ctx context.Context , args ... string ) * Command {
5442 return & Command {
5543 name : "git" ,
5644 args : args ,
@@ -76,23 +64,9 @@ func (c Command) WithContext(ctx context.Context) *Command {
7664 return & c
7765}
7866
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-
9067// AddOptions adds options to the command.
91- // Note: only the last option will take effect if there are duplicated options.
9268func (c * Command ) AddOptions (opts ... CommandOptions ) * Command {
9369 for _ , opt := range opts {
94- c .timeout = opt .Timeout
95- c .ctx = opt .Context
9670 c .AddArgs (opt .Args ... )
9771 c .AddEnvs (opt .Envs ... )
9872 }
@@ -105,7 +79,8 @@ func (c *Command) AddCommitter(committer *Signature) *Command {
10579 return c
10680}
10781
108- // DefaultTimeout is the default timeout duration for all commands.
82+ // DefaultTimeout is the default timeout duration for all commands. It is
83+ // applied when the context does not already have a deadline.
10984const DefaultTimeout = time .Minute
11085
11186// A limitDualWriter writes to W but limits the amount of data written to just N
@@ -144,33 +119,19 @@ type RunInDirOptions struct {
144119 Stdout io.Writer
145120 // Stderr is the error output from the command.
146121 Stderr io.Writer
147- // Timeout is the duration to wait before timing out.
148- //
149- // Deprecated: Use CommandOptions.Timeout or *Command.WithTimeout instead.
150- Timeout time.Duration
151122}
152123
153124// RunInDirWithOptions executes the command in given directory and options. It
154125// pipes stdin from supplied io.Reader, and pipes stdout and stderr to supplied
155- // io.Writer. DefaultTimeout will be used if the timeout duration is less than
156- // time.Nanosecond (i.e. less than or equal to 0) . It returns an ErrExecTimeout
157- // if the execution was timed out.
126+ // io.Writer. If the command's context does not have a deadline, DefaultTimeout
127+ // will be applied automatically . It returns an ErrExecTimeout if the execution
128+ // was timed out.
158129func (c * Command ) RunInDirWithOptions (dir string , opts ... RunInDirOptions ) (err error ) {
159130 var opt RunInDirOptions
160131 if len (opts ) > 0 {
161132 opt = opts [0 ]
162133 }
163134
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
172- }
173-
174135 buf := new (bytes.Buffer )
175136 w := opt .Stdout
176137 if logOutput != nil {
@@ -184,26 +145,22 @@ func (c *Command) RunInDirWithOptions(dir string, opts ...RunInDirOptions) (err
184145
185146 defer func () {
186147 if len (dir ) == 0 {
187- log ("[timeout: %v] % s\n %s" , timeout , c , buf .Bytes ())
148+ log ("% s\n %s" , c , buf .Bytes ())
188149 } else {
189- log ("[timeout: %v] % s: %s\n %s" , timeout , dir , c , buf .Bytes ())
150+ log ("% s: %s\n %s" , dir , c , buf .Bytes ())
190151 }
191152 }()
192153
193- ctx := context . Background ()
194- if c . ctx ! = nil {
195- ctx = c . ctx
154+ ctx := c . ctx
155+ if ctx = = nil {
156+ ctx = context . Background ()
196157 }
197158
198- if timeout > 0 {
159+ // Apply default timeout if the context doesn't already have a deadline.
160+ if _ , ok := ctx .Deadline (); ! ok {
199161 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- }()
162+ ctx , cancel = context .WithTimeout (ctx , DefaultTimeout )
163+ defer cancel ()
207164 }
208165
209166 cmd := exec .CommandContext (ctx , c .name , c .args ... )
@@ -215,6 +172,11 @@ func (c *Command) RunInDirWithOptions(dir string, opts ...RunInDirOptions) (err
215172 cmd .Stdout = w
216173 cmd .Stderr = opt .Stderr
217174 if err = cmd .Start (); err != nil {
175+ if ctx .Err () == context .DeadlineExceeded {
176+ return ErrExecTimeout
177+ } else if ctx .Err () != nil {
178+ return ctx .Err ()
179+ }
218180 return err
219181 }
220182
@@ -225,22 +187,33 @@ func (c *Command) RunInDirWithOptions(dir string, opts ...RunInDirOptions) (err
225187
226188 select {
227189 case <- ctx .Done ():
228- <- result
229- if cmd .Process != nil && cmd .ProcessState != nil && ! cmd .ProcessState .Exited () {
230- if err := cmd .Process .Kill (); err != nil {
231- return fmt .Errorf ("kill process: %v" , err )
232- }
190+ // Kill the process before waiting so cancellation is enforced promptly.
191+ if cmd .Process != nil {
192+ _ = cmd .Process .Kill ()
233193 }
194+ <- result
234195
235- return ErrExecTimeout
196+ if ctx .Err () == context .DeadlineExceeded {
197+ return ErrExecTimeout
198+ }
199+ return ctx .Err ()
236200 case err = <- result :
201+ // Normalize errors when the context may have expired around the same time.
202+ if err != nil {
203+ if ctxErr := ctx .Err (); ctxErr != nil {
204+ if ctxErr == context .DeadlineExceeded {
205+ return ErrExecTimeout
206+ }
207+ return ctxErr
208+ }
209+ }
237210 return err
238211 }
239212
240213}
241214
242- // RunInDirPipeline executes the command in given directory and default timeout
243- // duration. It pipes stdout and stderr to supplied io.Writer.
215+ // RunInDirPipeline executes the command in given directory. It pipes stdout and
216+ // stderr to supplied io.Writer.
244217func (c * Command ) RunInDirPipeline (stdout , stderr io.Writer , dir string ) error {
245218 return c .RunInDirWithOptions (dir , RunInDirOptions {
246219 Stdin : nil ,
@@ -249,35 +222,8 @@ func (c *Command) RunInDirPipeline(stdout, stderr io.Writer, dir string) error {
249222 })
250223}
251224
252- // RunInDirPipelineWithTimeout executes the command in given directory and
253- // timeout duration. It pipes stdout and stderr to supplied io.Writer.
254- // DefaultTimeout will be used if the timeout duration is less than
255- // time.Nanosecond (i.e. less than or equal to 0). It returns an ErrExecTimeout
256- // if the execution was timed out.
257- //
258- // Deprecated: Use RunInDirPipeline and CommandOptions instead.
259- // TODO: remove this in the next major version
260- func (c * Command ) RunInDirPipelineWithTimeout (timeout time.Duration , stdout , stderr io.Writer , dir string ) (err error ) {
261- if timeout != 0 {
262- c = c .WithTimeout (timeout )
263- }
264- return c .RunInDirPipeline (stdout , stderr , dir )
265- }
266-
267- // RunInDirWithTimeout executes the command in given directory and timeout
268- // 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
272- func (c * Command ) RunInDirWithTimeout (timeout time.Duration , dir string ) ([]byte , error ) {
273- if timeout != 0 {
274- c = c .WithTimeout (timeout )
275- }
276- return c .RunInDir (dir )
277- }
278-
279- // RunInDir executes the command in given directory and default timeout
280- // duration. It returns stdout and error (combined with stderr).
225+ // RunInDir executes the command in given directory. It returns stdout and error
226+ // (combined with stderr).
281227func (c * Command ) RunInDir (dir string ) ([]byte , error ) {
282228 stdout := new (bytes.Buffer )
283229 stderr := new (bytes.Buffer )
@@ -287,20 +233,8 @@ func (c *Command) RunInDir(dir string) ([]byte, error) {
287233 return stdout .Bytes (), nil
288234}
289235
290- // RunWithTimeout executes the command in working directory and given timeout
291- // 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
295- func (c * Command ) RunWithTimeout (timeout time.Duration ) ([]byte , error ) {
296- if timeout != 0 {
297- c = c .WithTimeout (timeout )
298- }
299- return c .Run ()
300- }
301-
302- // Run executes the command in working directory and default timeout duration.
303- // It returns stdout in string and error (combined with stderr).
236+ // Run executes the command in working directory. It returns stdout and
237+ // error (combined with stderr).
304238func (c * Command ) Run () ([]byte , error ) {
305239 stdout , err := c .RunInDir ("" )
306240 if err != nil {
0 commit comments