Skip to content

v0.10.0

Latest

Choose a tag to compare

@github-actions github-actions released this 21 Oct 16:19
· 17 commits to main since this release
c22261b

Security

  • Bumped the Go toolchain to 1.27.3 (go.mod toolchain and the mise pin), clearing GO-2026-6809: crypto/x509 now distrusts compromised intermediate certificates. It is reachable through TLS verification on every HTTPS remote. (#126)

  • Bumped the Go toolchain to 1.27.2 (go.mod toolchain and the mise pin), clearing ten standard-library advisories reachable from the HTTP transport and the convert-sha256 filesystem paths — among them HTTP/1 client desynchronization after a CONNECT rejection (GO-2026-6605), a memory-limit bypass parsing MIME headers in net/textproto (GO-2026-6608), malformed ECH outer extension references accepted by crypto/tls (GO-2026-6607), and HTTP/2 transport accepting malformed framing headers (GO-2026-6610). Released binaries are built from the go.mod toolchain; govulncheck ./... goes from 10 called vulnerabilities to 0. The go directive stays at 1.26, so the minimum Go version for library consumers is unchanged. golangci-lint moves to v2.15.0, since releases before v2.13.0 refuse to lint a module targeting Go 1.27. (#125)

  • Bumped golang.org/x/crypto to v0.58.0 and golang.org/x/net to v0.61.0 (raised as indirect dependencies; golang.org/x/sys follows to v0.49.0), clearing three SSH advisories in packages git-sync imports (GO-2026-6303, GO-2026-6354, GO-2026-6355) and five HTTP/2 advisories in golang.org/x/net. None was reachable from git-sync's call graph. The remaining module-level finding, GO-2026-5932 (golang.org/x/crypto/openpgp is unmaintained), has no fix and git-sync does not import that package. (#125)

Added

  • RefScope.IncludeRefPrefixes narrows a request to one or more namespaces. With any prefix set, a ref outside all of them is neither pushed nor pruned, in every kind — the filter is the single predicate the desired set, TargetScope.Manages and the prune set all ask, so push scope and prune scope cannot disagree. Exclusions still apply within an included prefix, and explicit Mappings bypass both as before. Every entry must start with refs/: a blank or refs/-less prefix scopes a request to nothing, which under a prune policy is a zero-plan success that repeats forever, so it is rejected at the request edge instead. git-sync's own refs/gitsync/ scaffolding is the exception to the narrowing: a run manages only the bootstrap markers of branches it manages, minus any it excludes by name, so two syncs with different scopes sharing one target cannot delete each other's live resume state, and other scaffolding answers to the exclusions alone because prune is the only cleaner a stale marker has. Callers whose target refuses writes outside the included prefixes must exclude refs/gitsync/ explicitly; the markers of branches a run manages stay prunable. The source ref listing is not narrowed: ref-prefix stays refs/ under AllRefs, so an empty in-scope set stays distinguishable from a source that advertised nothing. (#121)

  • SyncPolicy.AllowEmptyScope lets an empty in-scope desired set prune instead of failing. Without it, a replicate whose scope matched no source ref fails with the historical "no source refs matched", so a caller replicating a namespace could never propagate the deletion of its last ref. With it, a source that advertised at least one ref proceeds with an empty desired set and the target's in-scope refs are pruned; a source that advertised nothing at all still fails, now with ErrSourceEmptyUnverified, because a withheld listing is indistinguishable from an empty one. Replicate-only (only replicate's empty-set branch consults the policy; elsewhere the run returns the historical error and keeps the refs the caller meant to prune), and requires Prune (without it the policy plans nothing and reports a green pass over the refs it was set to reap), AllRefs (a narrowed listing cannot tell an empty namespace from one it never asked about) and IncludeRefPrefixes. Mutually exclusive with AllowEmptySource, which needs the unscoped listing its own claim rests on. Every one of these is rejected at the request edge rather than discarded. (#121)

  • The CLI flag --bootstrap-fallback-max-pack-bytes and unstable.AdvancedOptions.BootstrapFallbackMaxPackBytes enable the fallback for bootstrap, sync, replicate, and plan. unstable.Options.BootstrapLogger supplies structured diagnostics to bootstrap and sync paths. (#120)

  • Options.BootstrapFallbackMaxPackBytes provides an opt-in local byte ceiling for indivisible bootstrap checkpoints when no target limit is known. These uploads bypass projection and the batching margin, while preserving source limits, deadlines, and observed server cutoffs. Exceeding the fallback remains retryable, not ErrCheckpointExceedsTargetLimit. Diagnostics identify the configured_fallback budget separately from a target-announced limit. (#120)

  • Options.BootstrapLogger emits one structured event per bootstrap checkpoint upload without verbose protocol logging. Events include the checkpoint, split eligibility, effective budget and its source, read/object counters, size projection, and local abort reason. Abort counters are captured at the decision; they do not assert target receipt or acceptance. Upload limits and retry behavior are unchanged. (#119)

  • ErrCheckpointExceedsTargetLimit identifies a permanent, target-verified bootstrap size failure. Sync and Replicate, plus unstable.Client.Bootstrap, wrap this sentinel only after the target refuses a one-commit checkpoint that cannot be subdivided further, so embedders can terminate futile redelivery with errors.Is while continuing to retry transport failures and deadline expiry (#118)

Changed

  • A failed bootstrap now returns its route alongside the error. Plan, Sync, and Replicate, plus their unstable counterparts and Bootstrap, preserve the selected operation and transfer modes, relay fallback reason, attempted ref plan, and completed batch progress when a bootstrap fails, so a failed or partially completed bootstrap can be told apart from a pre-execution failure without reconstructing the route from the error text. This covers the bootstrap route only: a failed incremental or materialized relay still reports an empty transfer mode, and most other error paths still return a zero result. Outcome counts are unpopulated on every one of these paths and must not be used to infer how much landed (#118)

  • A target that refuses the batched bootstrap temp ref now fails the run, including under --best-effort. Every other per-ref refusal stays a warning there, but this one is the batching state machine: the run previously advanced its checkpoint position against a ref the target had not moved, then failed later and more confusingly, or deleted a marker at a hash the target never accepted. A run against a target that blocks writes to refs/gitsync/* used to exit 0 with warnings and now exits non-zero — worth knowing if you alert on exit codes (#117)

Fixed

  • Interrupted bootstraps can resume when their markers remain on the target. Replicate recognizes a live bootstrap marker and selects bootstrap when all desired target refs are absent and the source supports batching. Sync and replicate pruning preserve live resume markers, and source discovery ignores refs/gitsync/* scaffolding. (#116)

  • A private GitHub repository over 1.5 GiB now bootstraps in batches. The GitHub size lookup that selects batched bootstrap sent no credentials, so every private repository got a 404 and was sent silently as one pack. The lookup now carries the source credentials, scoped to api.github.com, and retries once anonymously if the API refuses them. Each lookup's outcome is logged through Options.BootstrapLogger, and a failed lookup prints a notice. (#124)

  • Preserve source-read errors when closing bootstrap observation, including io.ErrUnexpectedEOF; a truncated source is reported as observer_failed, not expected observer_interrupted. Intentional partial closure is unchanged. (#120)

  • At source EOF, bootstrap upload guards enforce actual bytes without projection or the batching safety margin. Final bytes returned together with EOF remain subject to the byte limit, and diagnostic counters retain their observed values. (#120)

  • Bootstrap upload diagnostics distinguish expected scanner interruption on a partially consumed upload (observer_interrupted) from malformed input or scanner failure (observer_failed). Closing a partial valid pack no longer reports instrumentation failure; malformed data and source truncation remain visible. (#120)

  • An indivisible one-commit bootstrap checkpoint is now tested against the target's announced pack limit before the run gives up. The batching budget is intentionally smaller than that limit, so treating a pack that crossed the budget as permanently too large made every retry fail locally even when the target would have accepted it. Once subdivision bottoms out at one commit, git-sync now attempts that pack and only reports a permanent failure when the target itself refuses it. A deadline retains its original classification instead of being converted into a size verdict (#118)

  • A batched bootstrap no longer discards its resume position when the target refuses the branch create. The cutover push carries two commands — advance the refs/gitsync/bootstrap/heads/<branch> temp ref to the final checkpoint, and create the real branch there — and the temp ref was deleted on the strength of that push returning no error. Under --best-effort (implied by --all-refs) a per-ref ng reaches a callback and the push still returns nil, so a target that refuses the create — a protected branch, a pre-receive policy — ended the run with neither the branch nor the marker and reported success. With nothing on the target referencing the objects already delivered, the next run had no fetch have to negotiate against and re-transferred the entire history, on precisely the large repositories batching exists for. Whether the branch landed is now settled against the target's own refs, not inferred from the push, and the marker survives anything short of the branch being present at the hash the run pushed (#117)