Artifact a31a15931d69fd4ed8707ba02bb9c2a7ab3e2e3d37380f8bedda846aa29407aa:
- File
internal/sync/clone_shun_test.go
— part of check-in
[aea957939f]
at
2026-10-12 22:57:27
on branch main
— fix(sync): drop shunned artifacts and never ask for a shunned phantom (#302) (#355)
* fix(sync): drop shunned artifacts and never ask for a shunned phantom (#302)
fossil's xfer_accept_file and xfer_accept_compressed_file drop a received artifact whose hash is on the shun list, and its request_phantoms and content_new leave shunned names out of what a client asks for (sections 3.3 and 7.7). The server, the sync client and clone stored a shunned artifact, and the client asked for shunned phantoms.
storeReceivedFile, which every receive path shares, now drops a shunned artifact; the server checks first so a dropped file is not reported as missing after store. The client's phantom scan and its igot handling skip shunned names, so a delta whose basis is shunned is kept as a delta and the basis is never requested.
* refactor(sync): split the client's reply handling into per-card helpers
processResponse had grown to 232 lines: one switch over every reply card followed by the convergence check. It is now a loop over handleReplyCard, which hands each card to a focused helper, and converged, which keeps the termination rules apart from card handling. Behavior is unchanged. (user: [email protected], size: 2462) [annotate] [blame] [check-ins using]
package sync import ( "context" "testing" "github.com/danmestas/go-libfossil/internal/blob" "github.com/danmestas/go-libfossil/internal/delta" "github.com/danmestas/go-libfossil/internal/hash" "github.com/danmestas/go-libfossil/internal/manifest" "github.com/danmestas/go-libfossil/internal/shun" "github.com/danmestas/go-libfossil/internal/xfer" "github.com/danmestas/go-libfossil/simio" ) // TestCloneShunRules pins fossil's shun handling during a clone: a shunned // cfile is dropped (xfer_accept_compressed_file checks the shun list // always), a shunned file card is kept (xfer_accept_file skips the check for // a clone), and a shunned delta basis never becomes a phantom to ask for. func TestCloneShunRules(t *testing.T) { r := setupSyncTestRepo(t) linker, err := manifest.NewReceiveLinker(r) if err != nil { t.Fatalf("NewReceiveLinker: %v", err) } cs := &cloneSession{ repo: r, env: simio.RealEnv(), linker: linker, seqno: 1, phantoms: map[string]bool{}, availableDeltaSources: map[string]struct{}{}, } cs.obs = resolveObserver(nil) fileData := []byte("a shunned file card") cfileData := []byte("a shunned cfile card") base := []byte("a shunned basis, long enough for a delta to be worth it") target := []byte("a shunned basis, long enough for a delta to be worth it, v2") deltaBytes, err := delta.Create(base, target) if err != nil { t.Fatalf("delta.Create: %v", err) } fileUUID, cfileUUID, baseUUID := hash.SHA1(fileData), hash.SHA1(cfileData), hash.SHA1(base) for _, u := range []string{fileUUID, cfileUUID, baseUUID} { if err := shun.Add(r.DB(), u, ""); err != nil { t.Fatalf("shun.Add: %v", err) } } msg := &xfer.Message{Cards: []xfer.Card{ &xfer.FileCard{UUID: fileUUID, Content: fileData}, &xfer.CFileCard{UUID: cfileUUID, Content: cfileData}, &xfer.CFileCard{UUID: hash.SHA1(target), DeltaSrc: baseUUID, Content: deltaBytes}, }} if err := cs.processResponse(context.Background(), 1, msg); err != nil { t.Fatalf("processResponse: %v", err) } if _, ok := blob.Exists(r.DB(), fileUUID); !ok { t.Error("a shunned file card was dropped during a clone") } if _, ok := blob.Exists(r.DB(), cfileUUID); ok { t.Error("a shunned cfile was stored during a clone") } if cs.phantoms[baseUUID] { t.Error("a shunned delta basis became a phantom to ask for") } if cs.result.BlobsRecvd != 2 { t.Errorf("BlobsRecvd = %d, want 2 (the shunned cfile not counted)", cs.result.BlobsRecvd) } }