Cover batched + AllRefs + BestEffort, polish bootstrap polish-items · Entire

Cover batched + AllRefs + BestEffort, polish bootstrap polish-items

b2a248a→main·

Soph·2mo ago·2 files·+98 added/-2 removed

The most complex --all-refs path had no end-to-end coverage: large source pack forces TargetMaxPackBytes batching, the tail phase pushes other-kind refs after checkpointed branch batches, and the target ng's the notes ref. The OnRejection callback has to flow through *Pusher into bootstrap.Params.TargetPusher's interface boundary and downgrade the rejected ref to a warning. New TestBootstrap_IntegrationAllRefsBatched- BestEffortDowngradesNg pins this — the receivePackHook only fires on the tail-phase push (so branch batches go through the real handler) and the test asserts both Warned=1 and the kept-on-target branch tip.

Plus two polish items:

Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

Sessions

93ec937211e5View transcript

Changes

2

100 unmodified lines

101
102
103
104
104
105
106
107
108
109
110
562 unmodified lines

673
674
675
673
676
677
678
679

100 unmodified lines

}
}

// Result holds the outcome of the bootstrap strategy.
// Result holds the outcome of the bootstrap strategy. Pushed is the count
// of attempted ref creates; under BestEffort, callers that want a count
// excluding rejected refs need to consult Pusher.OnRejection or apply the
// same downgrade pass the syncer wrapper does.
type Result struct {
    Plans             []planner.BranchPlan
    Pushed            int
562 unmodified lines

}
    switch {
    case hasTag && hasOther:
        return "pushing tags and other refs"
    return "pushing tail refs"
    case hasOther:
        return "pushing other refs"
    default:

Minternal/strategy/bootstrap/bootstrap.go+5/-2

3049 unmodified lines

3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148

3049 unmodified lines

assertHeadsMatch(t, sourceRepo, targetRepo, testBranch)
}

// Batched bootstrap + AllRefs + BestEffort is the most complex
// --all-refs path: large source pack forces TargetMaxPackBytes batching,
// the tail phase pushes other-kind refs after checkpointed branch
// batches, and the target ng's the notes ref. The OnRejection callback
// must flow through *Pusher into bootstrap.Params.TargetPusher's
// interface boundary and downgrade the rejected ref to a warning.
func TestBootstrap_IntegrationAllRefsBatchedBestEffortDowngradesNg(t *testing.T) {
    sourceRepo, sourceFS := newSourceRepo(t)
    makeLargeCommits(t, sourceRepo, sourceFS, 5, 200_000)

head, err := sourceRepo.Reference(plumbing.NewBranchReferenceName(testBranch), true)
    if err != nil {
        t.Fatalf("resolve source head: %v", err)
    }
    notesRef := plumbing.ReferenceName("refs/notes/commits")
    if err := sourceRepo.Storer.SetReference(plumbing.NewHashReference(notesRef, head.Hash())); err != nil {
        t.Fatalf("set source notes ref: %v", err)
    }

targetRepo, err := git.Init(memory.NewStorage())
    if err != nil {
        t.Fatalf("init target repo: %v", err)
    }

sourceServer := newSmartHTTPRepoServerV2(t, sourceRepo)
    targetServer := newSmartHTTPRepoServer(t, targetRepo)
    defer sourceServer.Close()
    defer targetServer.Close()

// Hook only fires on the tail-phase push (the request that contains
    // the notes ref); branch-batch pushes pass through the real
    // receive-pack handler so the target actually receives them.
targetServer.receivePackHook = func(req *packp.UpdateRequests, _ bool) *packp.ReportStatus {
        hasNotes := false
        for _, cmd := range req.Commands {
            if cmd.Name == notesRef {
                hasNotes = true
                break
            }
        }
        if !hasNotes {
            return nil
        }
    treport := packp.NewReportStatus()
    report.UnpackStatus = "ok"
    for _, cmd := range req.Commands {
        status := "ok"
        if cmd.Name == notesRef {
            status = "deny updating a hidden ref"
        }
        report.CommandStatuses = append(report.CommandStatuses, &packp.CommandStatus{
            ReferenceName: cmd.Name,
            Status:        status,
        })
    }
    return report
}

result, err := Run(context.Background(), Config{
        Source:             Endpoint{URL: sourceServer.RepoURL()},
        Target:             Endpoint{URL: targetServer.RepoURL()},
        ProtocolMode:       protocolModeAuto,
        AllRefs:            true,
        BestEffort:         true,
        TargetMaxPackBytes: 350_000,
    })
    if err != nil {
        t.Fatalf("batched all-refs best-effort sync failed: %v", err)
    }
    if !result.Batching {
        t.Errorf("expected batched mode, got %+v", result)
    }
    if result.Warned != 1 {
        t.Fatalf("expected Warned=1 (notes rejected), got %+v", result)
    }
    var foundWarn bool
    for _, plan := range result.Plans {
        if plan.TargetRef == notesRef {
            if plan.Action != ActionWarn {
                t.Errorf("expected notes Action=warn, got %s", plan.Action)
            }
            if !strings.Contains(plan.Reason, "deny updating a hidden ref") {
                t.Errorf("expected ng reason in plan.Reason, got %q", plan.Reason)
            }
            foundWarn = true
        }
    }
    if !foundWarn {
        t.Fatal("notes ref missing from result.Plans")
    }
    assertHeadsMatch(t, sourceRepo, targetRepo, testBranch)
}

// Other-kind refs don't have FF semantics (a notes append is rarely an
// ancestor of the previous notes tip), so PlanRef requires --force to
// retarget them — same as tags. This pins the block reason and the