Artifact 32ad0004377734ebdb709ca4555cb78d2d03b12c0d055b085aaaf7eae5beed83:
- File
internal/sync/clone_dangling_test.go
— part of check-in
[67bc700498]
at
2026-10-24 02:58:56
on branch main
— fix(sync): clone an origin that holds a dangling delta
An origin that held a delta whose source it never received could not be cloned at all. walkCloneChain found the source unforwardable (a phantom) and asked buildCloneArtifact to send the delta expanded; expanding it failed, the handler errored, and every client stopped at clone sequence 1.
The origin now withholds a chain that ends at a phantom, as it withholds a row outside the clone's cone. Sending the stored delta naming its phantom source, as fossil's send_compressed_file does, is not enough: a fossil client then rejects the whole clone for its unresolved deltas. A later pull fetches the chain once the source arrives. (user: [email protected], size: 2388) [annotate] [blame] [check-ins using] [more...]
package sync import ( "context" "path/filepath" "testing" "time" "github.com/danmestas/go-libfossil/internal/blob" "github.com/danmestas/go-libfossil/internal/content" "github.com/danmestas/go-libfossil/internal/delta" "github.com/danmestas/go-libfossil/internal/hash" "github.com/danmestas/go-libfossil/internal/xfer" ) // TestCloneWithholdsDanglingDelta pins that an origin holding a delta whose // source is a phantom can still be cloned. It pushes an ordinary file and a // delta against a base the origin never receives, then clones. The origin // cannot expand the delta, so it withholds it, and the ordinary file arrives // whole. Before, the origin tried to expand the delta, failed, and the clone // stopped at clone sequence 1. func TestCloneWithholdsDanglingDelta(t *testing.T) { origin := setupPushableTestRepo(t) base := []byte("missing base\n") target := []byte("target content\n") d, err := delta.Create(base, target) if err != nil { t.Fatal(err) } ordinary := []byte("ordinary\n") if _, err := HandleSync(context.Background(), origin, &xfer.Message{Cards: []xfer.Card{ &xfer.PushCard{ServerCode: "s", ProjectCode: testPC}, &xfer.FileCard{UUID: hash.SHA1(ordinary), Content: ordinary}, &xfer.FileCard{UUID: hash.SHA1(target), DeltaSrc: hash.SHA1(base), Content: d}, }}); err != nil { t.Fatal(err) } ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() clone, _, err := Clone(ctx, filepath.Join(t.TempDir(), "c.fossil"), handlerTransport(t, origin), CloneOpts{}) if err != nil { t.Fatalf("clone of an origin holding a dangling delta: %v", err) } defer clone.Close() rid, ok := blob.Exists(clone.DB(), hash.SHA1(ordinary)) if !ok { t.Fatal("ordinary file not cloned") } got, err := content.Expand(clone.DB(), rid) if err != nil || string(got) != string(ordinary) { t.Fatalf("ordinary file: %q, %v", got, err) } if _, ok := blob.Exists(clone.DB(), hash.SHA1(target)); ok { t.Fatal("the dangling delta was cloned; it cannot be expanded and must be withheld") } // A later pull must not fail on it either. serverCode, err := clone.Config("server-code") if err != nil { t.Fatal(err) } if _, err := Sync(ctx, clone, handlerTransport(t, origin), SyncOpts{ Pull: true, ProjectCode: testPC, ServerCode: serverCode, }); err != nil { t.Fatalf("pull after the clone: %v", err) } }