Skip to content

Commit c6ff875

Browse files
committed
fix
1 parent fafd1db commit c6ff875

70 files changed

Lines changed: 350 additions & 629 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cmd/hook.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ func runHookPostReceive(ctx context.Context, c *cli.Command) error {
318318
setup(ctx, c.Bool("debug"))
319319

320320
// First of all run update-server-info no matter what
321-
if _, _, err := gitcmd.NewCommand("update-server-info").RunStdString(ctx); err != nil {
321+
if err := gitcmd.NewCommand("update-server-info").RunWithStderr(ctx); err != nil {
322322
return fmt.Errorf("failed to call 'git update-server-info': %w", err)
323323
}
324324

modules/git/attribute/batch.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,11 @@ func NewBatchChecker(repo *git.Repository, treeish string, attributes []string)
7676
_ = stdinReader.Close()
7777
_ = lw.Close()
7878
}()
79-
stdErr := new(bytes.Buffer)
8079
err := cmd.WithEnv(envs).
8180
WithDir(repo.Path).
8281
WithStdin(stdinReader).
8382
WithStdout(lw).
84-
WithStderr(stdErr).
85-
Run(ctx)
83+
RunWithStderr(ctx)
8684

8785
if err != nil && !git.IsErrCanceledOrKilled(err) {
8886
log.Error("Attribute checker for commit %s exits with error: %v", treeish, err)

modules/git/attribute/checker.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,11 @@ func CheckAttributes(ctx context.Context, gitRepo *git.Repository, treeish strin
6969
defer cancel()
7070

7171
stdOut := new(bytes.Buffer)
72-
stdErr := new(bytes.Buffer)
73-
7472
if err := cmd.WithEnv(append(os.Environ(), envs...)).
7573
WithDir(gitRepo.Path).
7674
WithStdout(stdOut).
77-
WithStderr(stdErr).
78-
Run(ctx); err != nil {
79-
return nil, fmt.Errorf("failed to run check-attr: %w\n%s\n%s", err, stdOut.String(), stdErr.String())
75+
RunWithStderr(ctx); err != nil && !errors.Is(err, context.Canceled) {
76+
return nil, fmt.Errorf("failed to run check-attr: %w", err)
8077
}
8178

8279
fields := bytes.Split(stdOut.Bytes(), []byte{'\000'})

modules/git/catfile_batch_reader.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414

1515
"code.gitea.io/gitea/modules/git/gitcmd"
1616
"code.gitea.io/gitea/modules/log"
17+
"github.com/go-ap/errors"
1718
)
1819

1920
type catFileBatchCommunicator struct {
@@ -40,15 +41,13 @@ func newCatFileBatch(ctx context.Context, repoPath string, cmdCatFile *gitcmd.Co
4041

4142
var batchStdinWriter io.WriteCloser
4243
var batchStdoutReader io.ReadCloser
43-
stderr := strings.Builder{}
4444
cmdCatFile = cmdCatFile.
4545
WithDir(repoPath).
4646
WithStdinWriter(&batchStdinWriter).
4747
WithStdoutReader(&batchStdoutReader).
48-
WithStderr(&stderr).
4948
WithUseContextTimeout(true)
5049

51-
err := cmdCatFile.Start(ctx)
50+
err := cmdCatFile.StartWithStderr(ctx)
5251
if err != nil {
5352
log.Error("Unable to start git command %v: %v", cmdCatFile.LogString(), err)
5453
// ideally here it should return the error, but it would require refactoring all callers
@@ -63,9 +62,9 @@ func newCatFileBatch(ctx context.Context, repoPath string, cmdCatFile *gitcmd.Co
6362
}
6463

6564
go func() {
66-
err := cmdCatFile.Wait()
67-
if err != nil {
68-
log.Error("cat-file --batch command failed in repo %s: %v - stderr: %s", repoPath, err, stderr.String())
65+
err := cmdCatFile.WaitWithStderr()
66+
if err != nil && !errors.Is(err, context.Canceled) {
67+
log.Error("cat-file --batch command failed in repo %s, error: %v", repoPath, err)
6968
}
7069
ctxCancel(err)
7170
}()

modules/git/commit.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ func CommitChanges(ctx context.Context, repoPath string, opts CommitChangesOptio
120120

121121
_, _, err := cmd.WithDir(repoPath).RunStdString(ctx)
122122
// No stderr but exit status 1 means nothing to commit.
123-
if err != nil && err.Error() == "exit status 1" {
123+
if gitcmd.IsErrorExitCode(err, 1) {
124124
return nil
125125
}
126126
return err
@@ -315,7 +315,7 @@ func GetFullCommitID(ctx context.Context, repoPath, shortID string) (string, err
315315
WithDir(repoPath).
316316
RunStdString(ctx)
317317
if err != nil {
318-
if strings.Contains(err.Error(), "exit status 128") {
318+
if gitcmd.IsErrorExitCode(err, 128) {
319319
return "", ErrNotExist{shortID, ""}
320320
}
321321
return "", err

modules/git/diff.go

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

66
import (
77
"bufio"
8-
"bytes"
98
"context"
109
"fmt"
1110
"io"
@@ -80,14 +79,9 @@ func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diff
8079
return fmt.Errorf("invalid diffType: %s", diffType)
8180
}
8281

83-
stderr := new(bytes.Buffer)
84-
if err = cmd.WithDir(repo.Path).
82+
return cmd.WithDir(repo.Path).
8583
WithStdout(writer).
86-
WithStderr(stderr).
87-
Run(repo.Ctx); err != nil {
88-
return fmt.Errorf("Run: %w - %s", err, stderr)
89-
}
90-
return nil
84+
RunWithStderr(repo.Ctx)
9185
}
9286

9387
// ParseDiffHunkString parse the diff hunk content and return

modules/git/gitcmd/command.go

Lines changed: 62 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ const DefaultLocale = "C"
4040

4141
// Command represents a command with its subcommands or arguments.
4242
type Command struct {
43+
callerInfo string
4344
prog string
4445
args []string
4546
preErrors []error
@@ -52,9 +53,10 @@ type Command struct {
5253
cmdFinished context.CancelFunc
5354
cmdStartTime time.Time
5455

55-
cmdStdinWriter *io.WriteCloser
56-
cmdStdoutReader *io.ReadCloser
57-
cmdStderrReader *io.ReadCloser
56+
cmdStdinWriter *io.WriteCloser
57+
cmdStdoutReader *io.ReadCloser
58+
cmdStderrReader *io.ReadCloser
59+
cmdManagedStderr *bytes.Buffer
5860
}
5961

6062
func logArgSanitize(arg string) string {
@@ -221,7 +223,7 @@ type runOpts struct {
221223
// The correct approach is to use `--git-dir" global argument
222224
Dir string
223225

224-
Stdout, Stderr io.Writer
226+
Stdout io.Writer
225227

226228
// Stdin is used for passing input to the command
227229
// The caller must make sure the Stdin writer is closed properly to finish the Run function.
@@ -235,8 +237,6 @@ type runOpts struct {
235237
Stdin io.Reader
236238

237239
PipelineFunc func(context.Context, context.CancelFunc) error
238-
239-
callerInfo string
240240
}
241241

242242
func commonBaseEnvs() []string {
@@ -310,12 +310,6 @@ func (c *Command) WithStderrReader(r *io.ReadCloser) *Command {
310310
return c
311311
}
312312

313-
// WithStderr is deprecated, use WithStderrReader instead
314-
func (c *Command) WithStderr(stderr io.Writer) *Command {
315-
c.opts.Stderr = stderr
316-
return c
317-
}
318-
319313
func (c *Command) WithStdinWriter(w *io.WriteCloser) *Command {
320314
c.cmdStdinWriter = w
321315
return c
@@ -343,11 +337,11 @@ func (c *Command) WithUseContextTimeout(useContextTimeout bool) *Command {
343337
// then you can to call this function in GeneralWrapperFunc to set the caller info of FeatureFunc.
344338
// The caller info can only be set once.
345339
func (c *Command) WithParentCallerInfo(optInfo ...string) *Command {
346-
if c.opts.callerInfo != "" {
340+
if c.callerInfo != "" {
347341
return c
348342
}
349343
if len(optInfo) > 0 {
350-
c.opts.callerInfo = optInfo[0]
344+
c.callerInfo = optInfo[0]
351345
return c
352346
}
353347
skip := 1 /*parent "wrap/run" functions*/ + 1 /*this function*/
@@ -356,7 +350,7 @@ func (c *Command) WithParentCallerInfo(optInfo ...string) *Command {
356350
if pos := strings.LastIndex(callerInfo, "/"); pos >= 0 {
357351
callerInfo = callerInfo[pos+1:]
358352
}
359-
c.opts.callerInfo = callerInfo
353+
c.callerInfo = callerInfo
360354
return c
361355
}
362356

@@ -372,7 +366,7 @@ func (c *Command) Start(ctx context.Context) (retErr error) {
372366
safeClosePtrCloser(c.cmdStdoutReader)
373367
safeClosePtrCloser(c.cmdStderrReader)
374368
safeClosePtrCloser(c.cmdStdinWriter)
375-
// if no error, cmdFinished will be called in "Wait" function
369+
// if error occurs, we must also finish the task, other, cmdFinished will be called in "Wait" function
376370
if c.cmdFinished != nil {
377371
c.cmdFinished()
378372
}
@@ -393,16 +387,16 @@ func (c *Command) Start(ctx context.Context) (retErr error) {
393387
}
394388

395389
cmdLogString := c.LogString()
396-
if c.opts.callerInfo == "" {
390+
if c.callerInfo == "" {
397391
c.WithParentCallerInfo()
398392
}
399393
// these logs are for debugging purposes only, so no guarantee of correctness or stability
400-
desc := fmt.Sprintf("git.Run(by:%s, repo:%s): %s", c.opts.callerInfo, logArgSanitize(c.opts.Dir), cmdLogString)
394+
desc := fmt.Sprintf("git.Run(by:%s, repo:%s): %s", c.callerInfo, logArgSanitize(c.opts.Dir), cmdLogString)
401395
log.Debug("git.Command: %s", desc)
402396

403397
_, span := gtprof.GetTracer().Start(ctx, gtprof.TraceSpanGitRun)
404398
defer span.End()
405-
span.SetAttributeString(gtprof.TraceAttrFuncCaller, c.opts.callerInfo)
399+
span.SetAttributeString(gtprof.TraceAttrFuncCaller, c.callerInfo)
406400
span.SetAttributeString(gtprof.TraceAttrGitCommand, cmdLogString)
407401

408402
if c.opts.UseContextTimeout {
@@ -425,7 +419,6 @@ func (c *Command) Start(ctx context.Context) (retErr error) {
425419
cmd.Env = append(cmd.Env, CommonGitCmdEnvs()...)
426420
cmd.Dir = c.opts.Dir
427421
cmd.Stdout = c.opts.Stdout
428-
cmd.Stderr = c.opts.Stderr
429422
cmd.Stdin = c.opts.Stdin
430423

431424
if _, err := safeAssignPipe(c.cmdStdinWriter, cmd.StdinPipe); err != nil {
@@ -437,19 +430,32 @@ func (c *Command) Start(ctx context.Context) (retErr error) {
437430
if _, err := safeAssignPipe(c.cmdStderrReader, cmd.StderrPipe); err != nil {
438431
return err
439432
}
433+
434+
if c.cmdManagedStderr != nil {
435+
if cmd.Stderr != nil {
436+
panic("CombineStderr needs managed (but not caller-provided) stderr pipe")
437+
}
438+
cmd.Stderr = c.cmdManagedStderr
439+
}
440440
return cmd.Start()
441441
}
442442

443443
func (c *Command) Wait() error {
444-
defer c.cmdFinished()
444+
defer func() {
445+
safeClosePtrCloser(c.cmdStdoutReader)
446+
safeClosePtrCloser(c.cmdStderrReader)
447+
safeClosePtrCloser(c.cmdStdinWriter)
448+
c.cmdFinished()
449+
}()
450+
445451
cmd, ctx, cancel := c.cmd, c.cmdCtx, c.cmdCancel
446452

447453
if c.opts.PipelineFunc != nil {
448454
err := c.opts.PipelineFunc(ctx, cancel)
449455
if err != nil {
450456
cancel()
451-
_ = cmd.Wait()
452-
return err
457+
errWait := cmd.Wait()
458+
return errors.Join(err, errWait)
453459
}
454460
}
455461

@@ -472,6 +478,34 @@ func (c *Command) Wait() error {
472478
return errCause
473479
}
474480

481+
func (c *Command) StartWithStderr(ctx context.Context) RunStdError {
482+
c.cmdManagedStderr = &bytes.Buffer{}
483+
err := c.Start(ctx)
484+
if err != nil {
485+
return &runStdError{err: err}
486+
}
487+
return nil
488+
}
489+
490+
func (c *Command) WaitWithStderr() RunStdError {
491+
if c.cmdManagedStderr == nil {
492+
panic("CombineStderr needs managed (but not caller-provided) stderr pipe")
493+
}
494+
errWait := c.Wait()
495+
if errWait == nil {
496+
// if no exec error but only stderr output, the error is still saved in "c.cmdManagedStderr" and can be read later
497+
return nil
498+
}
499+
return &runStdError{err: errWait, stderr: util.UnsafeBytesToString(c.cmdManagedStderr.Bytes())}
500+
}
501+
502+
func (c *Command) RunWithStderr(ctx context.Context) RunStdError {
503+
if err := c.StartWithStderr(ctx); err != nil {
504+
return &runStdError{err: err}
505+
}
506+
return c.WaitWithStderr()
507+
}
508+
475509
func (c *Command) Run(ctx context.Context) (err error) {
476510
if err = c.Start(ctx); err != nil {
477511
return err
@@ -495,7 +529,7 @@ func (r *runStdError) Error() string {
495529
// FIXME: GIT-CMD-STDERR: it is a bad design, the stderr should not be put in the error message
496530
// But a lof of code only checks `strings.Contains(err.Error(), "git error")`
497531
if r.errMsg == "" {
498-
r.errMsg = ConcatenateError(r.err, r.stderr).Error()
532+
r.errMsg = fmt.Sprintf("%s - %s", r.err.Error(), strings.TrimSpace(r.stderr))
499533
}
500534
return r.errMsg
501535
}
@@ -543,24 +577,16 @@ func (c *Command) RunStdBytes(ctx context.Context) (stdout, stderr []byte, runEr
543577
return c.WithParentCallerInfo().runStdBytes(ctx)
544578
}
545579

546-
func (c *Command) runStdBytes(ctx context.Context) ( /*stdout*/ []byte /*stderr*/, []byte /*runErr*/, RunStdError) {
547-
if c.opts.Stdout != nil || c.opts.Stderr != nil {
580+
func (c *Command) runStdBytes(ctx context.Context) ([]byte, []byte, RunStdError) {
581+
if c.opts.Stdout != nil || c.cmdStdoutReader != nil || c.cmdStderrReader != nil {
548582
// we must panic here, otherwise there would be bugs if developers set Stdin/Stderr by mistake, and it would be very difficult to debug
549583
panic("stdout and stderr field must be nil when using RunStdBytes")
550584
}
551585
stdoutBuf := &bytes.Buffer{}
552-
stderrBuf := &bytes.Buffer{}
553586
err := c.WithParentCallerInfo().
554587
WithStdout(stdoutBuf).
555-
WithStderr(stderrBuf).
556-
Run(ctx)
557-
if err != nil {
558-
// FIXME: GIT-CMD-STDERR: it is a bad design, the stderr should not be put in the error message
559-
// But a lot of code depends on it, so we have to keep this behavior
560-
return nil, stderrBuf.Bytes(), &runStdError{err: err, stderr: util.UnsafeBytesToString(stderrBuf.Bytes())}
561-
}
562-
// even if there is no err, there could still be some stderr output
563-
return stdoutBuf.Bytes(), stderrBuf.Bytes(), nil
588+
RunWithStderr(ctx)
589+
return stdoutBuf.Bytes(), c.cmdManagedStderr.Bytes(), err
564590
}
565591

566592
func (c *Command) DebugKill() {

modules/git/gitcmd/command_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func TestRunWithContextStd(t *testing.T) {
4545
assert.Equal(t, stderr, err.Stderr())
4646
assert.Equal(t, "fatal: Not a valid object name no-such\n", err.Stderr())
4747
// FIXME: GIT-CMD-STDERR: it is a bad design, the stderr should not be put in the error message
48-
assert.Equal(t, "exit status 128 - fatal: Not a valid object name no-such\n", err.Error())
48+
assert.Equal(t, "exit status 128 - fatal: Not a valid object name no-such", err.Error())
4949
assert.Empty(t, stdout)
5050
}
5151
}
@@ -57,7 +57,7 @@ func TestRunWithContextStd(t *testing.T) {
5757
assert.Equal(t, string(stderr), err.Stderr())
5858
assert.Equal(t, "fatal: Not a valid object name no-such\n", err.Stderr())
5959
// FIXME: GIT-CMD-STDERR: it is a bad design, the stderr should not be put in the error message
60-
assert.Equal(t, "exit status 128 - fatal: Not a valid object name no-such\n", err.Error())
60+
assert.Equal(t, "exit status 128 - fatal: Not a valid object name no-such", err.Error())
6161
assert.Empty(t, stdout)
6262
}
6363
}

modules/git/gitcmd/utils.go

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,9 @@
44
package gitcmd
55

66
import (
7-
"fmt"
87
"io"
9-
10-
"code.gitea.io/gitea/modules/util"
118
)
129

13-
// ConcatenateError concatenates an error with stderr string
14-
// FIXME: use RunStdError instead
15-
func ConcatenateError(err error, stderr string) error {
16-
if len(stderr) == 0 {
17-
return err
18-
}
19-
errMsg := fmt.Sprintf("%s - %s", err.Error(), stderr)
20-
return util.ErrorWrap(&runStdError{err: err, stderr: stderr, errMsg: errMsg}, "%s", errMsg)
21-
}
22-
2310
func safeClosePtrCloser[T *io.ReadCloser | *io.WriteCloser](c T) {
2411
switch v := any(c).(type) {
2512
case *io.ReadCloser:

0 commit comments

Comments
 (0)