can you look at https://github.com/entireio/git-sync/issues/46 and then chat · Entire
Summary of Issue #46 and Related Discussions
I've read through issue #46. Quick summary of how I'm reading it:
- The core ask is to use receive-pack's
atomiccapability so a single push is all-or-nothing. - The honest finding in the issue itself:
atomiconly helps within one push. The real non-atomicity comes from the multi-phase shapes — batched bootstrap (branches → tail) and replicate (relay push → separate deletePushCommands). - The two phase-shape issues each have a different answer: replicate can fold deletes into the same push, but batched bootstrap is multi-transaction by design.
- And
atomic+BestEffortcancel each other out — they need an explicit interaction rule.
A few things I'd want to chat about before touching code:
- Scope. Is the goal here just "negotiate and set
atomicwhere pushes are already one-shot" (cheap, partial win), or also "restructure replicate to one push" (more invasive, actually closes the gap)? They're separable. - BestEffort policy. Your sketch suggests "skip atomic when BestEffort is set." That seems right to me — silent skip with a debug log, error only if a user explicitly asks for both. Agree?
- Batched bootstrap. Are you okay leaving that as documented-multi-transaction, or is there appetite to make the tail push optional / fold it into the last batch when small?
What's your priority on this — quick win first, or do you want to design the replicate change too?
Further Clarifications
Got it — that reframes #46 nicely. Reading the two together:
- #47 is the safety net. Every
PushCommandalready carriesOld: TargetHashcaptured at session start, so receive-pack rejects any update where the target moved underneath us. That's the lease. - #46 atomic is for "this single push is naturally all-or-nothing", not "make the whole sync run atomic."
Scope Reiteration
So the scope you're describing is:
- Negotiate
atomicand set it on the pushes that are already one transaction: one-shot bootstrap, sync relay, materialized fallback, sync+prune. - For batched bootstrap: set atomic on each batch individually. We assume no concurrent target changes for the initial flow; if that assumption breaks, the next batch's lease check (#47) catches it because its
Oldwon't match anymore. - Don't restructure replicate's two-push shape. Atomic still applies to the relay push; the delete push remains its own transaction.
BestEffort+ atomic: skip atomic when BestEffort is set; error only if a user explicitly passes both.
Two things to nail down before I touch code:
- Default on or opt-in? My read: on by default when the server advertises the capability — it's strictly safer and matches
git push --mirrorparity. Any reason to gate it behind a flag? - Per-batch atomic in batched bootstrap — confirming you want this. It adds nothing for the cross-batch story, but it does mean a single bad ref in batch N doesn't half-land that batch.
Operation Dynamics
How would that work with something like GitHub pull refs that should be warn skipped now if fail to create?
Pull refs today are handled one of two ways:
--exclude-ref-prefix refs/pull/— they never enter the push at all.--all-refswhich bundlesBestEffort— they hit the push, server returnsng, BestEffort downgrades each one to a warning.
Atomic Conflicts Resolution
Atomic conflicts only with (2). If BestEffort is on, even one ng from a pull ref under atomic fails the whole transaction → no refs land → the user gets nothing instead of "most things plus warnings." That defeats the BestEffort UX.
Resolution: atomic and BestEffort are mutually exclusive. Concretely:
- BestEffort on → never set atomic, regardless of server capability. Per-ref
ngcontinues to come back individually, warn-skip works. - BestEffort off → atomic safe to negotiate. If a pull ref (or anything else) gets
ng, the user already wants the whole sync to fail; atomic just makes it fail cleanly with nothing partially landed.
Future Work Consideration
Want me to write that scope clarification into the issue, or leave it and just implement?
Final Thoughts: I also tried to run go test ./internal/gitproto ./internal/syncer ./cmd/git-sync, but the sandbox blocks httptest from binding a local port, so I couldn’t use test execution to validate these paths here.
Proposed New Body for Issue:
Status
git-sync doesn't negotiate or set the receive-pack atomic capability today. Logging this so we don't lose track of the option.
Background
The atomic capability ( gitprotocol-pack.adoc) lets a client signal that all ref commands in one update-request apply or none do. It's what git push --atomic sets — not what git push --mirror sets. Default --mirror is per-ref: each command gets its own ok/ng, partial application is normal.
Current Behavior Comparison
Where git-sync stands relative to git push:
- Default
git push --mirror→ per-ref outcomes, partial apply. git-sync today matches this. git push --mirror --atomic→ all-or-nothing within one push. git-sync has no equivalent.
Foundational Reasons for Not Building it
- No demand yet. No one has asked for transactional pushes; the per-ref model has been fine.
- Conflicts with
BestEffort.--all-refson sync/plan bundles BestEffort, which downgrades per-refngto warnings — atomic would defeat that, so atomic would have to be off for the most common GitHub-mirror config anyway. - Phase shape, not capability, is the real gap. Where git-sync is non-atomic across phases (batched bootstrap tail, replicate's separate delete push), the
atomiccapability alone doesn't help — those are multi-transaction by design.
If We Revisit
- Add an explicit
--atomicflag (opt-in, matching git's UX). - Negotiate via
gitproto.TargetFeaturesFromAdvRefs, set onpackp.UpdateRequestsinbuildUpdateRequest. - Mutually exclusive with
BestEffort: error if both are set explicitly. - Per-batch only in batched bootstrap; the cross-batch story stays the lease check from #47.