Add narrow incremental relay path · Entire

Add narrow incremental relay path

520e91f→main·

Soph·3mo ago·4 files·+78 added/-10 removed

Sessions

894a895034bbView transcript

[?
Can you take a look at the go code (wasm) in /Users/soph/Work/entire/devenv/entire-io-worktree1 based a bit on that I wonder if something like this can be build:Codex·GPT-5.4·1 step](/content/gh/entireio/git-sync/session/019d6d29-8cf7-7fe3-adc9-8c3e4d9d5603#timeline-894a895034bb/index.html)

Changes

4

90 unmodified lines

91
92
93
94
95
96
97
98

90 unmodified lines

When `sync` sees that all managed target refs are absent and the run is compatible with bootstrap semantics, it automatically uses the bootstrap relay path instead of the normal decode-and-repack sync path.

`sync` also uses a narrow incremental relay path for a single fast-forward branch update when the target already has the old tip and the target does not advertise `no-thin`. More complex updates still fall back to the normal local decode-and-repack path.

Sync specific branches:

```bash

MREADME.md+2

159 unmodified lines

160
161
162
163
164
165
166
167
168

159 unmodified lines

- consider a more advanced incremental relay mode for non-empty targets
- only pursue this if large migration workflows become important enough to justify the added protocol complexity

Progress:

- there is now a narrow incremental relay path in `sync`
- it is limited to a single fast-forward branch update
- tags, deletes, force, prune, mapped refs, and multi-ref updates still use the normal path

Mdocs/bootstrap.md+6

79 unmodified lines

80
81
82
83
84
83
84
85
86
87

79 unmodified lines

if result.Pushed != 1 || result.Blocked != 0 {
        t.Fatalf("unexpected incremental result: %+v", result)
    }
    if result.Relay || result.RelayMode != "" {
        t.Fatalf("expected incremental sync to stay on normal path for now, got %+v", result)
    }
    if !result.Relay || result.RelayMode != "incremental" {
        t.Fatalf("expected incremental sync to use incremental relay, got %+v", result)
    }

assertGitRefEqual(t, sourceBare, targetBare, plumbing.NewBranchReferenceName(testBranch))

Minternal/syncer/git_http_backend_test.go+2/-2

472 unmodified lines

473
474
475
476
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
264 unmodified lines

758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
476 unmodified lines

1283
1284
1285
1229
1230
1231
1232
1286
1287
1234
1235
1236
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299

472 unmodified lines

return result, fmt.Errorf("blocked %d ref update(s); rerun with --force where appropriate", result.Blocked)
    }

if !cfg.DryRun && len(pushPlans) > 0 {
        if !cfg.DryRun && canIncrementalRelay(cfg, pushPlans, targetAdv) {
            relayPlans := append([]BranchPlan(nil), pushPlans...)
            desiredRelay := desiredSubsetForPlans(desiredRefs, relayPlans)
            packReader, err := sourceService.FetchPack(ctx, sourceConn, desiredRelay, targetRefMap)
            if err != nil {
                return result, fmt.Errorf("fetch source pack: %w", err)
            }
            defer packReader.Close()
            packReader = limitPackReadCloser(packReader, cfg.MaxPackBytes)
            if err := pushPackToTarget(ctx, targetConn, targetAdv, relayPlans, packReader, cfg.Verbose); err != nil {
                return result, fmt.Errorf("push target refs: %w", err)
            }
            result.Relay = true
            result.RelayMode = "incremental"
        } else if !cfg.DryRun && len(pushPlans) > 0 {
            if err := pushToTarget(ctx, repo, targetConn, targetAdv, pushPlans, targetRefMap, cfg.Verbose); err != nil {
                return result, fmt.Errorf("push target refs: %w", err)
            }
        }
264 unmodified lines

return true
}

func canIncrementalRelay(cfg Config, plans []BranchPlan, targetAdv *packp.AdvRefs) bool {
    if cfg.Force || cfg.Prune || cfg.DryRun || cfg.IncludeTags {
        return false
    }
    if len(cfg.Mappings) > 0 {
        return false
    }
    if len(plans) != 1 {
        return false
    }
    if targetAdv == nil || targetAdv.Capabilities == nil {
        return false
    }
    if targetAdv.Capabilities.Supports(capability.Capability("no-thin")) {
        return false
    }

plan := plans[0]
    if plan.Kind != RefKindBranch {
        return false
    }
    if plan.Action != ActionUpdate {
        return false
    }
    if plan.TargetHash.IsZero() {
        return false
    }
    return true
}

func desiredSubsetForPlans(
    desired map[plumbing.ReferenceName]desiredRef,
    plans []BranchPlan,
) map[plumbing.ReferenceName]desiredRef {
    out := make(map[plumbing.ReferenceName]desiredRef, len(plans))
    for _, plan := range plans {
        if ref, ok := desired[plan.TargetRef]; ok {
            out[plan.TargetRef] = ref
        }
    }
    return out
}

func bootstrapWithInputs(
    ctx context.Context,
    cfg Config,
476 unmodified lines

commands := make([]*packp.Command, 0, len(plans))
    for _, plan := range plans {
        if plan.Action != ActionCreate {
            return fmt.Errorf("bootstrap only supports create actions")
        }
        commands = append(commands, &packp.Command{
        cmd := &packp.Command{
            Name: plan.TargetRef,
            Old:  plumbing.ZeroHash,
            New:  plan.SourceHash,
        }
            Old:  plan.TargetHash,
        }
        switch plan.Action {
        case ActionCreate, ActionUpdate:
            cmd.New = plan.SourceHash
        default:
            return fmt.Errorf("streamed pack push only supports create and update actions")
        }
        commands = append(commands, cmd)
    }
    req.Commands = commands
    conn.stats.addCommands("target receive-pack", len(commands))