Sync to targets with overlapping reachability · Entire

Sync to targets with overlapping reachability

9c95280→main·

Soph·2mo ago·5 files·+359 added/-20 removed

Three related fixes uncovered while syncing a repo into a target that already had main:

Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

Sessions

0ceb3c36dd52View transcript

[?
git-sync sync --tags https://github.com/entireio/cli.git $ARTIFACTS_REMOTEClaude Code·Opus 4.7[1m]·16 steps

Changes

5

15 unmodified lines

const maxHTTPErrorBody = 64 * 1024

// diagnosticHeaders carry trace/correlation IDs that operators of upstream
// services use to look up the failing request server-side. Surfaced in
// httpError so a 500 with an opaque body (e.g. "Internal Server Error") still
// gives the user something actionable to share when reporting the failure.
var diagnosticHeaders = []string{
    "Cf-Ray",
    "X-Request-Id",
    "Request-Id",
    "X-Trace-Id",
    "X-Amz-Request-Id",
    "X-Github-Request-Id",
    "Server",
    "Content-Type",
};

// httpError checks an HTTP response status and returns an error for non-2xx responses.
func httpError(res *http.Response) error {
    if res.StatusCode >= http.StatusOK && res.StatusCode < http.StatusMultipleChoices {

// ... (truncated for brevity)

}
}

Minternal/gitproto/smarthttp.go+25/-1

406 unmodified lines

}

// ObjectsToPush computes the set of objects that need to be sent to the target.

// A fetch with target refs as haves prunes the source pack server-side, so
// objects reachable from a have are intentionally absent from the local
// store. Both top-level wants and transitive references encountered during
// the walk may be missing for this reason — they're treated as implicitly
// have'd by the target and excluded from the push pack. The target's
// receive-pack accepts ref updates referencing such objects because it
// already has them under one of its existing refs.
func ObjectsToPush(store storer.EncodedObjectStorer, wants []plumbing.Hash, targetRefs map[plumbing.ReferenceName]plumbing.Hash) ([]plumbing.Hash, error) {
    haveSet := make(map[plumbing.Hash]struct{})
    for _, h := range targetRefs {

// ... (further truncation for brevity)
}
}

Minternal/planner/planner.go+14

1037 unmodified lines

}

// CanIncrementalRelay checks whether all plans are eligible for the incremental
// relay fast-path (fast-forward branch updates + new tag creates).
// relay fast-path. Eligible plan shapes:
//   - Branch fast-forward update (TargetHash is parent of SourceHash).
//   - Branch create (target has no such ref yet). The relay still passes
//     all target refs as haves, so the source pack covers only objects
//     target doesn't already have via some existing ref. The receive-pack
//     accepts the create command because any pruned objects are reachable
//     from those existing refs.
//   - New tag create.

// Incremental tolerates targets that advertise "no-thin" for the same reason
// as replicate (see SupportsReplicateRelay): gitproto.FetchPack never requests
// the "thin-pack" capability, so the relayed pack is always self-contained.
// If gitproto.FetchPack ever begins requesting thin-pack, this function must
// gain a matching fallback when target.NoThin is set.
func CanIncrementalRelay(force, prune, dryRun bool, plans []BranchPlan, target RelayTargetPolicy) (bool, string) {
    if force || prune || dryRun {
        return false, "incremental-disabled-by-force-prune-or-dry-run"
    }

// ... (further truncation for brevity)
}

Minternal/planner/planner_test.go+187/-10

52 unmodified lines

// A "Create" plan with a non-zero TargetHash is incoherent — surface it
// rather than silently relay against the wrong have.
plans := []BranchPlan{{
    Branch:     "feature",
    SourceRef:  "refs/heads/feature",
    TargetRef:  "refs/heads/feature",
    SourceHash: plumbing.NewHash("1111111111111111111111111111111111111111"),
    TargetHash: plumbing.NewHash("2222222222222222222222222222222222222222"),
    Kind:       RefKindBranch,
    Action:     ActionCreate,
}}

// ... (further truncation for brevity)
}

Minternal/syncer/integration_test.go+108

157 unmodified lines

}