Repository navigation
Security
-
Bumped the Go toolchain to
1.27.3(go.modtoolchainand the mise pin), clearing GO-2026-6809:crypto/x509now distrusts compromised intermediate certificates. It is reachable through TLS verification on every HTTPS remote. (#126) -
Bumped the Go toolchain to
1.27.2(go.modtoolchainand the mise pin), clearing ten standard-library advisories reachable from the HTTP transport and theconvert-sha256filesystem paths — among them HTTP/1 client desynchronization after aCONNECTrejection (GO-2026-6605), a memory-limit bypass parsing MIME headers innet/textproto(GO-2026-6608), malformed ECH outer extension references accepted bycrypto/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. Thegodirective stays at1.26, so the minimum Go version for library consumers is unchanged. golangci-lint moves tov2.15.0, since releases beforev2.13.0refuse to lint a module targeting Go 1.27. (#125) -
Bumped
golang.org/x/cryptotov0.58.0andgolang.org/x/nettov0.61.0(raised as indirect dependencies;golang.org/x/sysfollows tov0.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 ingolang.org/x/net. None was reachable from git-sync's call graph. The remaining module-level finding, GO-2026-5932 (golang.org/x/crypto/openpgpis unmaintained), has no fix and git-sync does not import that package. (#125)
Added
-
RefScope.IncludeRefPrefixesnarrows 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.Managesand the prune set all ask, so push scope and prune scope cannot disagree. Exclusions still apply within an included prefix, and explicitMappingsbypass both as before. Every entry must start withrefs/: a blank orrefs/-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 ownrefs/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 excluderefs/gitsync/explicitly; the markers of branches a run manages stay prunable. The source ref listing is not narrowed:ref-prefixstaysrefs/underAllRefs, so an empty in-scope set stays distinguishable from a source that advertised nothing. (#121) -
SyncPolicy.AllowEmptyScopelets 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 withErrSourceEmptyUnverified, 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 requiresPrune(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) andIncludeRefPrefixes. Mutually exclusive withAllowEmptySource, 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-bytesandunstable.AdvancedOptions.BootstrapFallbackMaxPackBytesenable the fallback for bootstrap, sync, replicate, and plan.unstable.Options.BootstrapLoggersupplies structured diagnostics to bootstrap and sync paths. (#120) -
Options.BootstrapFallbackMaxPackBytesprovides 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, notErrCheckpointExceedsTargetLimit. Diagnostics identify theconfigured_fallbackbudget separately from a target-announced limit. (#120) -
Options.BootstrapLoggeremits 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) -
ErrCheckpointExceedsTargetLimitidentifies a permanent, target-verified bootstrap size failure.SyncandReplicate, plusunstable.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 witherrors.Iswhile continuing to retry transport failures and deadline expiry (#118)
Changed
-
A failed bootstrap now returns its route alongside the error.
Plan,Sync, andReplicate, plus their unstable counterparts andBootstrap, 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 torefs/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 throughOptions.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 asobserver_failed, not expectedobserver_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-refngreaches a callback and the push still returnsnil, 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)