# gitproto: tighten SpooledBody encapsulation and trim docs

`756eda3`→[main](/content/gh/entireio/git-sync/commits/main/index.html)·

Soph·1mo ago·2 files·+21 added/-36 removed

Three small refinements:

- Encapsulate SpooledBody's request wiring as (*SpooledBody).applyTo,  
so PostRPCStreamBody doesn't reach into unexported size/path fields  
to build req.ContentLength + req.GetBody.

- Trim the multi-paragraph Cloudflare/idle-write narrative on  
PushObjects and PostRPCStreamBody. The story now lives once on  
SpooledBody (the type that actually captures the workaround);  
the other call sites point there.

- Fix the countingWriter comment to say *why* the counter is atomic  
(concurrent read from the progress ticker) rather than restating  
what the type does.

## Sessions

0b0206eed178View transcript

## Changes

2

- internal/gitproto

- Mpush.go+6/-22

- Msmarthttp.go+15/-14

```
216 unmodified lines

// PushObjects pushes locally-materialized objects to the target.
//
// The receive-pack body (update-request header + pack) is written to a
// temp file before the POST so the upload goes out in one continuous
// burst. go-git's encoder runs delta selection synchronously before
// writing any pack bytes, which on big repos stalls the request body
// for tens of seconds — long enough for CDN edges like Cloudflare's to
// hit their idle-write timeout and close the connection mid-upload.
// Spooling collapses encoding and writing into one phase from the
// network's point of view, so the body bytes stream out without gaps.
//
// As a side benefit the spooled body carries a known length, so the
// POST sends Content-Length instead of Transfer-Encoding: chunked
// (matching upstream git's smart-HTTP transport), and req.GetBody lets
// Go's transport retry transient connection failures.
//
// The materialized strategy already requires the full source object
// closure to be local before encoding begins, so a temp file on upload
// doesn't change its fundamental shape. Relay paths (PushPack) keep
// streaming source bytes through to target with chunked encoding —
// source pack data flows steadily, there's no stall to engineer
// around, and the "streaming proxy" property git-sync is built around
// is preserved.
// The encoded body (update-request + pack) is spooled to a temp file
// before the POST so the upload streams without a mid-stream stall.
// See SpooledBody for why this matters. Relay paths (PushPack) keep
// streaming source bytes directly and don't need this.
func PushObjects(
    ctx context.Context,
    conn Conn,
) {
    // Implementation omitted for brevity
}

// countingWriter wraps an io.Writer and tracks total bytes written.
// Reads of the count are safe to call concurrently with Write.
// The count is read by the progress ticker concurrently with the
// encoder's writes, so the counter is atomic.
type countingWriter struct {
    w io.Writer
    n atomic.Int64
}

// PostRPCStreamBody sends a POST to the given service using a streaming request body.
// Caller must close the returned ReadCloser.
//
// The body is sent as-is. Streaming bodies (io.MultiReader, io.PipeReader)
// produce a chunked request — that's the right shape for relay paths,
// where source pack bytes flow steadily from source through to target.
// Callers whose body would otherwise stall mid-stream (e.g. the
// materialized push path, where the encoder's delta-selection phase
// produces no bytes for tens of seconds) spool the full payload first
// and pass a *SpooledBody; PostRPCStreamBody sets req.ContentLength and
// req.GetBody from its fields so the upload goes out in one continuous
// burst and Go's transport can auto-retry transient connection failures.
// The body is sent as-is — streaming bodies produce a chunked request,
// which is the right shape for relay paths. A *SpooledBody triggers
// fixed-length encoding and replayable retries; see SpooledBody.
func (c *HTTPConn) PostRPCStreamBody(
    ctx context.Context,
    service string,
    body io.Reader,
    v2 bool,
    phase string,
) (io.ReadCloser, error) {
    // Implementation omitted for brevity
}

// applyTo wires the spooled body's known length and a fresh-reader
// factory into req. GetBody lets Go's transport replay the request
// body on transient connection failures by reopening the temp file.
func (s *SpooledBody) applyTo(req *http.Request) {
    // Implementation omitted for brevity
}

// ApplyAuth applies the given auth method to an HTTP request. Errors from
// the Authorizer (e.g. transient signing failures) are surfaced as request
// failures by leaving the Authorization header unset; the upstream server
// Implementation omitted for brevity

```
