Centralize bootstrap capability checks · Entire
Centralize bootstrap capability checks
Sessions
Changes
5
docs
Mrewrite-issue-list.md+3/-1
internal
gitproto
- Mfetch.go+16/-8
Mfetch_test.go+37
strategy/bootstrap
- Mbootstrap.go+42/-28
Mbootstrap_test.go+44/-3
426 unmodified lines
---
426 unmodified lines
- Batched lightweight-tag creation without an extra pack is now covered directly.
- Basic context cancellation coverage now exists.
- Batched bootstrap resume mismatch and final-tip cutover paths now have direct integration coverage.
- Some harder batch-failure injection and partial cutover failure paths still remain.
- Batched bootstrap reruns now also cover the "target ref already created, temp ref cleanup still pending" recovery path.
- Some harder injected batch-failure paths still remain.
### 22. No benchmark coverage for the expensive paths
---
- All mapping validation happens before network activity. Status: done
- Capability negotiation is centralized and enforced consistently. Status: partial
Source-side fetch capability checks now live behind `gitproto.RefService` methods, but some target-side relay gating still relies on orchestration wiring rather than a fully typed capability boundary.
- Relay strategies are separate packages with explicit inputs and outputs. Status: done
- Tag creation is correct whether or not a pack transfer is needed. Status: done
- Stats are concurrency-safe. Status: done
func (s *RefService) ProtocolName() string {
if s == nil {
return ""
}
return s.Protocol
}
// FetchFeatures summarizes negotiated source fetch features used by strategies.
type FetchFeatures struct {
Filter bool
IncludeTag bool
}
func (s *RefService) SupportsFetchFeature(feature string) bool {
return s != nil && s.Protocol == "v2" && s.V2Caps.FetchSupports(feature)
}
func (s *RefService) FetchFeatures() FetchFeatures {
if s == nil || s.Protocol != "v2" || s.V2Caps == nil {
return FetchFeatures{}
}
return s.V2Caps.FetchSupports(feature)
}
func TestFetchFeatures(t *testing.T) {
v2Caps := &V2Capabilities{
Caps: map[string]string{
"fetch": "shallow filter include-tag",
},
}
rs := &RefService{Protocol: "v2", V2Caps: v2Caps}
features := rs.FetchFeatures()
if !features.Filter || !features.IncludeTag {
t.Fatalf("FetchFeatures() = %+v, want filter and include-tag enabled", features)
}
}
// Execute runs the bootstrap strategy (one-shot or batched).
func Execute(ctx context.Context, p Params, relayReason string) (Result, error) {
plans, err := planner.BuildBootstrapPlans(p.DesiredRefs, p.TargetRefs)
if err != nil {
return Result{}, err
}
result := Result{
Plans: plans, Relay: true, RelayMode: "bootstrap", RelayReason: relayReason,
}
if p.TargetPusher == nil {
return result, fmt.Errorf("bootstrap strategy requires TargetPusher")
}
}
func TestExecuteRequiresTargetPusherBeforeFetch(t *testing.T) {
mainRef := plumbing.NewBranchReferenceName("main")
mainHash := plumbing.NewHash("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
tests := []struct {
name string
batchMaxPack int64
}{
{name: "one-shot bootstrap", batchMaxPack: 0},
{name: "batched bootstrap", batchMaxPack: 1},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
calledFetch := false
_, err := Execute(context.Background(), Params{
SourceService: fakeBootstrapSource{
fetchPack: func(context.Context, *gitproto.Conn, map[plumbing.ReferenceName]gitproto.DesiredRef, map[plumbing.ReferenceName]plumbing.Hash) (io.ReadCloser, error) {
calledFetch = true
return io.NopCloser(bytes.NewReader(nil)), nil
},
},
DesiredRefs: map[plumbing.ReferenceName]planner.DesiredRef{
mainRef: {
SourceRef: mainRef,
TargetRef: mainRef,
SourceHash: mainHash,
Kind: planner.RefKindBranch,
},
},
TargetRefs: map[plumbing.ReferenceName]plumbing.Hash{},
BatchMaxPack: tt.batchMaxPack,
}, "missing pusher")
if err == nil || err.Error() != "bootstrap strategy requires TargetPusher" {
t.Fatalf("Execute() error = %v, want missing TargetPusher", err)
}
if calledFetch {
t.Fatal("expected bootstrap to fail before fetching source pack")
}
})
}
}