Split --force into --force-with-lease and --force-blind · Entire

Split --force into --force-with-lease and --force-blind

d02d15a→main·

Soph·2mo ago·19 files·+197 added/-68 removed

The previous --force was always lease-protected: PlansToPushCommands sent the captured session-start target hash as the push command's expected-old, and receive-pack rejected updates where the target had moved during the run. The name oversold the danger — it never matched git push --force's raw clobber semantics.

Replace with two explicit flags matching git push's surface:

- --force-with-lease — previous behavior (allow non-FF, send captured target tip as expected-old; server rejects on lease miss). - --force-blind — new path; zero the expected-old for non-delete commands so receive-pack overwrites regardless of current target value. Matches git push --force.

The two are mutually exclusive. Legacy --force errors out with a migration hint pointing at both replacements (pre-0.5, no installed script base to preserve). bootstrap and replicate continue to reject force flags entirely.

SyncPolicy.Force splits into ForceWithLease + ForceBlind on the public API; syncer.Config grows a ForceAny() helper for the internal "allow non-FF" sense (planner permissiveness). convert.PlansToPushCommands takes a forceBlind bool; incremental/materialized strategies plumb it from cfg, others pass false since bootstrap/replicate reject force.

Closes the rename portion of #47.

Sessions

a733497c4d7fView transcript

[?
can you rebase soph/progress-indicators onto soph/smart-subdivisionClaude Code·1 step](/content/gh/entireio/git-sync/session/3ee1ca7a-a436-44c1-906a-a912c6d33f96#timeline-a733497c4d7f/index.html)

Changes

19

227 unmodified lines

```go
func bridgePolicy(policy SyncPolicy) internalbridge.SyncPolicy {
    return internalbridge.SyncPolicy{
        Mode:        internalbridge.OperationMode(policy.Mode),
        IncludeTags: policy.IncludeTags,
        Force:       policy.Force,
        Prune:       policy.Prune,
        BestEffort:  policy.BestEffort,
        Protocol:    internalbridge.ProtocolMode(policy.Protocol),
        Mode:           internalbridge.OperationMode(policy.Mode),
        IncludeTags:    policy.IncludeTags,
        ForceWithLease: policy.ForceWithLease,
        ForceBlind:     policy.ForceBlind,
        Prune:          policy.Prune,
        BestEffort:     policy.BestEffort,
        Protocol:       internalbridge.ProtocolMode(policy.Protocol),
    }
}

Mclient.go+7/-6

105 unmodified lines

...

Force Updates and the Per-Run Lease

Non-fast-forward updates and tag retargets are opt-in. git-sync exposes two flags that mirror git push's force semantics:

The two flags are mutually exclusive. Without either, divergent or non-ancestor refs are reported as blocked and the sync exits non-zero before any push, so the lease check is a second line of defense against races for users who opt into non-fast-forward updates.