@@ -14,6 +14,7 @@ import (
14
14
"os/exec"
15
15
"strings"
16
16
"time"
17
+ "unsafe"
17
18
18
19
"code.gitea.io/gitea/modules/log"
19
20
"code.gitea.io/gitea/modules/process"
@@ -93,32 +94,6 @@ func (c *Command) AddArguments(args ...string) *Command {
93
94
return c
94
95
}
95
96
96
- // RunInDirTimeoutEnvPipeline executes the command in given directory with given timeout,
97
- // it pipes stdout and stderr to given io.Writer.
98
- func (c * Command ) RunInDirTimeoutEnvPipeline (env []string , timeout time.Duration , dir string , stdout , stderr io.Writer ) error {
99
- return c .RunInDirTimeoutEnvFullPipeline (env , timeout , dir , stdout , stderr , nil )
100
- }
101
-
102
- // RunInDirTimeoutEnvFullPipeline executes the command in given directory with given timeout,
103
- // it pipes stdout and stderr to given io.Writer and passes in an io.Reader as stdin.
104
- func (c * Command ) RunInDirTimeoutEnvFullPipeline (env []string , timeout time.Duration , dir string , stdout , stderr io.Writer , stdin io.Reader ) error {
105
- return c .RunInDirTimeoutEnvFullPipelineFunc (env , timeout , dir , stdout , stderr , stdin , nil )
106
- }
107
-
108
- // RunInDirTimeoutEnvFullPipelineFunc executes the command in given directory with given timeout,
109
- // it pipes stdout and stderr to given io.Writer and passes in an io.Reader as stdin. Between cmd.Start and cmd.Wait the passed in function is run.
110
- func (c * Command ) RunInDirTimeoutEnvFullPipelineFunc (env []string , timeout time.Duration , dir string , stdout , stderr io.Writer , stdin io.Reader , fn func (context.Context , context.CancelFunc ) error ) error {
111
- return c .RunWithContext (& RunContext {
112
- Env : env ,
113
- Timeout : timeout ,
114
- Dir : dir ,
115
- Stdout : stdout ,
116
- Stderr : stderr ,
117
- Stdin : stdin ,
118
- PipelineFunc : fn ,
119
- })
120
- }
121
-
122
97
// RunContext represents parameters to run the command
123
98
type RunContext struct {
124
99
Env []string
@@ -131,7 +106,7 @@ type RunContext struct {
131
106
132
107
// RunWithContext run the command with context
133
108
func (c * Command ) RunWithContext (rc * RunContext ) error {
134
- if rc .Timeout == - 1 {
109
+ if rc .Timeout <= 0 {
135
110
rc .Timeout = defaultCommandExecutionTimeout
136
111
}
137
112
@@ -203,58 +178,73 @@ func (c *Command) RunWithContext(rc *RunContext) error {
203
178
return ctx .Err ()
204
179
}
205
180
206
- // RunInDirTimeoutPipeline executes the command in given directory with given timeout,
207
- // it pipes stdout and stderr to given io.Writer.
208
- func (c * Command ) RunInDirTimeoutPipeline (timeout time.Duration , dir string , stdout , stderr io.Writer ) error {
209
- return c .RunInDirTimeoutEnvPipeline (nil , timeout , dir , stdout , stderr )
181
+ type RunError interface {
182
+ error
183
+ Stderr () string
210
184
}
211
185
212
- // RunInDirTimeoutFullPipeline executes the command in given directory with given timeout,
213
- // it pipes stdout and stderr to given io.Writer, and stdin from the given io.Reader
214
- func ( c * Command ) RunInDirTimeoutFullPipeline ( timeout time. Duration , dir string , stdout , stderr io. Writer , stdin io. Reader ) error {
215
- return c . RunInDirTimeoutEnvFullPipeline ( nil , timeout , dir , stdout , stderr , stdin )
186
+ type runError struct {
187
+ err error
188
+ stderr string
189
+ errMsg string
216
190
}
217
191
218
- // RunInDirTimeout executes the command in given directory with given timeout,
219
- // and returns stdout in []byte and error (combined with stderr).
220
- func (c * Command ) RunInDirTimeout (timeout time.Duration , dir string ) ([]byte , error ) {
221
- return c .RunInDirTimeoutEnv (nil , timeout , dir )
192
+ func (r * runError ) Error () string {
193
+ // the stderr must be in the returned error text, some code only checks `strings.Contains(err.Error(), "git error")`
194
+ if r .errMsg == "" {
195
+ r .errMsg = ConcatenateError (r .err , r .stderr ).Error ()
196
+ }
197
+ return r .errMsg
222
198
}
223
199
224
- // RunInDirTimeoutEnv executes the command in given directory with given timeout,
225
- // and returns stdout in []byte and error (combined with stderr).
226
- func (c * Command ) RunInDirTimeoutEnv (env []string , timeout time.Duration , dir string ) ([]byte , error ) {
227
- stdout := new (bytes.Buffer )
228
- stderr := new (bytes.Buffer )
229
- if err := c .RunInDirTimeoutEnvPipeline (env , timeout , dir , stdout , stderr ); err != nil {
230
- return nil , ConcatenateError (err , stderr .String ())
231
- }
232
- if stdout .Len () > 0 && log .IsTrace () {
233
- tracelen := stdout .Len ()
234
- if tracelen > 1024 {
235
- tracelen = 1024
236
- }
237
- log .Trace ("Stdout:\n %s" , stdout .Bytes ()[:tracelen ])
238
- }
239
- return stdout .Bytes (), nil
200
+ func (r * runError ) Unwrap () error {
201
+ return r .err
202
+ }
203
+
204
+ func (r * runError ) Stderr () string {
205
+ return r .stderr
240
206
}
241
207
242
- // RunInDirPipeline executes the command in given directory,
243
- // it pipes stdout and stderr to given io.Writer.
244
- func (c * Command ) RunInDirPipeline (dir string , stdout , stderr io.Writer ) error {
245
- return c .RunInDirFullPipeline (dir , stdout , stderr , nil )
208
+ func bytesToString (b []byte ) string {
209
+ return * (* string )(unsafe .Pointer (& b )) // that's what Golang's strings.Builder.String() does (go/src/strings/builder.go)
246
210
}
247
211
248
- // RunInDirFullPipeline executes the command in given directory,
249
- // it pipes stdout and stderr to given io.Writer.
250
- func (c * Command ) RunInDirFullPipeline (dir string , stdout , stderr io.Writer , stdin io.Reader ) error {
251
- return c .RunInDirTimeoutFullPipeline (- 1 , dir , stdout , stderr , stdin )
212
+ // RunWithContextString run the command with context and returns stdout/stderr as string. and store stderr to returned error (err combined with stderr).
213
+ func (c * Command ) RunWithContextString (rc * RunContext ) (stdout , stderr string , runErr RunError ) {
214
+ stdoutBytes , stderrBytes , err := c .RunWithContextBytes (rc )
215
+ stdout = bytesToString (stdoutBytes )
216
+ stderr = bytesToString (stderrBytes )
217
+ if err != nil {
218
+ return stdout , stderr , & runError {err : err , stderr : stderr }
219
+ }
220
+ // even if there is no err, there could still be some stderr output, so we just return stdout/stderr as they are
221
+ return stdout , stderr , nil
222
+ }
223
+
224
+ // RunWithContextBytes run the command with context and returns stdout/stderr as bytes. and store stderr to returned error (err combined with stderr).
225
+ func (c * Command ) RunWithContextBytes (rc * RunContext ) (stdout , stderr []byte , runErr RunError ) {
226
+ if rc .Stdout != nil || rc .Stderr != nil {
227
+ // we must panic here, otherwise there would be bugs if developers set Stdin/Stderr by mistake, and it would be very difficult to debug
228
+ panic ("stdout and stderr field must be nil when using RunWithContextBytes" )
229
+ }
230
+ stdoutBuf := & bytes.Buffer {}
231
+ stderrBuf := & bytes.Buffer {}
232
+ rc .Stdout = stdoutBuf
233
+ rc .Stderr = stderrBuf
234
+ err := c .RunWithContext (rc )
235
+ stderr = stderrBuf .Bytes ()
236
+ if err != nil {
237
+ return nil , stderr , & runError {err : err , stderr : string (stderr )}
238
+ }
239
+ // even if there is no err, there could still be some stderr output
240
+ return stdoutBuf .Bytes (), stderr , nil
252
241
}
253
242
254
243
// RunInDirBytes executes the command in given directory
255
244
// and returns stdout in []byte and error (combined with stderr).
256
245
func (c * Command ) RunInDirBytes (dir string ) ([]byte , error ) {
257
- return c .RunInDirTimeout (- 1 , dir )
246
+ stdout , _ , err := c .RunWithContextBytes (& RunContext {Dir : dir })
247
+ return stdout , err
258
248
}
259
249
260
250
// RunInDir executes the command in given directory
@@ -266,27 +256,15 @@ func (c *Command) RunInDir(dir string) (string, error) {
266
256
// RunInDirWithEnv executes the command in given directory
267
257
// and returns stdout in string and error (combined with stderr).
268
258
func (c * Command ) RunInDirWithEnv (dir string , env []string ) (string , error ) {
269
- stdout , err := c .RunInDirTimeoutEnv (env , - 1 , dir )
270
- if err != nil {
271
- return "" , err
272
- }
273
- return string (stdout ), nil
274
- }
275
-
276
- // RunTimeout executes the command in default working directory with given timeout,
277
- // and returns stdout in string and error (combined with stderr).
278
- func (c * Command ) RunTimeout (timeout time.Duration ) (string , error ) {
279
- stdout , err := c .RunInDirTimeout (timeout , "" )
280
- if err != nil {
281
- return "" , err
282
- }
283
- return string (stdout ), nil
259
+ stdout , _ , err := c .RunWithContextString (& RunContext {Env : env , Dir : dir })
260
+ return stdout , err
284
261
}
285
262
286
263
// Run executes the command in default working directory
287
264
// and returns stdout in string and error (combined with stderr).
288
265
func (c * Command ) Run () (string , error ) {
289
- return c .RunTimeout (- 1 )
266
+ stdout , _ , err := c .RunWithContextString (& RunContext {})
267
+ return stdout , err
290
268
}
291
269
292
270
// AllowLFSFiltersArgs return globalCommandArgs with lfs filter, it should only be used for tests
0 commit comments