Stream acquires the semaphore, starts a git command, and passes its stdout as an io.Reader to consume. The semaphore is held for the full duration. consume MUST fully drain the stdout reader before returning nil; otherwise cmd.Wait() may block or return a broken-pipe error.
(ctx context.Context, repoDir string, consume func(stdout io.Reader) error, args ...string)
| 88 | // consume MUST fully drain the stdout reader before returning nil; |
| 89 | // otherwise cmd.Wait() may block or return a broken-pipe error. |
| 90 | func (r *Runner) Stream(ctx context.Context, repoDir string, consume func(stdout io.Reader) error, args ...string) error { |
| 91 | if err := r.acquire(ctx); err != nil { |
| 92 | return err |
| 93 | } |
| 94 | defer r.release() |
| 95 | |
| 96 | cmd := exec.CommandContext(ctx, "git", args...) |
| 97 | cmd.Dir = repoDir |
| 98 | |
| 99 | var stderrBuf bytes.Buffer |
| 100 | cmd.Stderr = &stderrBuf |
| 101 | |
| 102 | stdoutPipe, err := cmd.StdoutPipe() |
| 103 | if err != nil { |
| 104 | return err |
| 105 | } |
| 106 | |
| 107 | if err := cmd.Start(); err != nil { |
| 108 | return err |
| 109 | } |
| 110 | |
| 111 | consumeErr := consume(stdoutPipe) |
| 112 | if consumeErr != nil { |
| 113 | cmd.Process.Kill() |
| 114 | } |
| 115 | waitErr := cmd.Wait() |
| 116 | |
| 117 | if consumeErr != nil { |
| 118 | return consumeErr |
| 119 | } |
| 120 | if waitErr != nil { |
| 121 | if stderrBuf.Len() > 0 { |
| 122 | return fmt.Errorf("%w: %s", waitErr, stderrBuf.String()) |
| 123 | } |
| 124 | return waitErr |
| 125 | } |
| 126 | return nil |
| 127 | } |