From 907ff5e57fba0d499b8a80fe3f940b68dea34ce4 Mon Sep 17 00:00:00 2001 From: Runxi Yu Date: Fri, 12 Jun 2026 04:47:04 +0000 Subject: internal/testgit: Seed history --- internal/testgit/seed.go | 206 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 internal/testgit/seed.go (limited to 'internal/testgit') diff --git a/internal/testgit/seed.go b/internal/testgit/seed.go new file mode 100644 index 00000000..9f60b23e --- /dev/null +++ b/internal/testgit/seed.go @@ -0,0 +1,206 @@ +package testgit + +import ( + "fmt" + "strings" + "testing" + + "lindenii.org/go/furgit/object/id" + "lindenii.org/go/furgit/object/typ" +) + +// Seeded lists the objects created by one [Repo.SeedHistory], by type. +// Each object ID appears exactly once. +type Seeded struct { + Blobs []id.ObjectID + Trees []id.ObjectID + Commits []id.ObjectID + Tags []id.ObjectID +} + +// All returns all seeded object IDs. +func (seeded *Seeded) All() []id.ObjectID { + all := make([]id.ObjectID, 0, len(seeded.Blobs)+len(seeded.Trees)+len(seeded.Commits)+len(seeded.Tags)) + + all = append(all, seeded.Blobs...) + all = append(all, seeded.Trees...) + all = append(all, seeded.Commits...) + all = append(all, seeded.Tags...) + + return all +} + +// SeedHistory creates one deterministic synthetic history +// of 64 commits on refs/heads/main, +// covering all four object types and various situations, +// such as stable, growing, unique, and empty blobs, +// nested subtrees shared across several commits, +// executable and symlink tree entries, +// and an annotated tag every sixteenth commit. +func (repo *Repo) SeedHistory(tb testing.TB) (Seeded, error) { + tb.Helper() + + seeder := &historySeeder{ + repo: repo, + tb: tb, + seen: make(map[id.ObjectID]struct{}), + seeded: Seeded{ + Blobs: nil, + Trees: nil, + Commits: nil, + Tags: nil, + }, + } + + err := seeder.run() + if err != nil { + return Seeded{}, err + } + + return seeder.seeded, nil +} + +type historySeeder struct { + repo *Repo + tb testing.TB + seen map[id.ObjectID]struct{} + seeded Seeded +} + +func (seeder *historySeeder) run() error { + readme, err := seeder.blob("stable readme\n") + if err != nil { + return err + } + + empty, err := seeder.blob("") + if err != nil { + return err + } + + link, err := seeder.blob("README") + if err != nil { + return err + } + + var parents []id.ObjectID + + for i := range 64 { + commitID, err := seeder.commit(i, readme, empty, link, parents) + if err != nil { + return fmt.Errorf("seed commit %d: %w", i, err) + } + + seeder.seeded.Commits = append(seeder.seeded.Commits, commitID) + parents = []id.ObjectID{commitID} + + if (i+1)%16 == 0 { + err = seeder.tag(fmt.Sprintf("v%d", (i+1)/16), commitID) + if err != nil { + return fmt.Errorf("seed tag at commit %d: %w", i, err) + } + } + } + + return seeder.repo.UpdateRef(seeder.tb, "refs/heads/main", parents[0]) +} + +func (seeder *historySeeder) commit(i int, readme, empty, link id.ObjectID, parents []id.ObjectID) (id.ObjectID, error) { + var growingContent strings.Builder + for j := range i + 1 { + fmt.Fprintf(&growingContent, "entry %d\n", j) + } + + growing, err := seeder.blob(growingContent.String()) + if err != nil { + return id.ObjectID{}, err + } + + tool, err := seeder.blob(fmt.Sprintf("tool %d\n", i)) + if err != nil { + return id.ObjectID{}, err + } + + inner, err := seeder.blob(fmt.Sprintf("inner %d\n", i/4)) + if err != nil { + return id.ObjectID{}, err + } + + subtree, err := seeder.tree([]TreeEntry{ + {Mode: "100644", Type: typ.Blob, OID: inner, Name: "inner"}, + {Mode: "120000", Type: typ.Blob, OID: link, Name: "link"}, + }) + if err != nil { + return id.ObjectID{}, err + } + + root, err := seeder.tree([]TreeEntry{ + {Mode: "100644", Type: typ.Blob, OID: readme, Name: "README"}, + {Mode: "100644", Type: typ.Blob, OID: empty, Name: "empty"}, + {Mode: "100644", Type: typ.Blob, OID: growing, Name: "log"}, + {Mode: "040000", Type: typ.Tree, OID: subtree, Name: "sub"}, + {Mode: "100755", Type: typ.Blob, OID: tool, Name: "tool"}, + }) + if err != nil { + return id.ObjectID{}, err + } + + return seeder.repo.CommitTree( + seeder.tb, + root, + CommitTreeOptions{Message: fmt.Sprintf("commit %d", i)}, //nolint:exhaustruct + parents..., + ) +} + +func (seeder *historySeeder) blob(content string) (id.ObjectID, error) { + oid, err := seeder.repo.HashObject(seeder.tb, typ.Blob, strings.NewReader(content)) + if err != nil { + return id.ObjectID{}, err + } + + if seeder.record(oid) { + seeder.seeded.Blobs = append(seeder.seeded.Blobs, oid) + } + + return oid, nil +} + +func (seeder *historySeeder) tree(entries []TreeEntry) (id.ObjectID, error) { + oid, err := seeder.repo.MkTree(seeder.tb, entries) + if err != nil { + return id.ObjectID{}, err + } + + if seeder.record(oid) { + seeder.seeded.Trees = append(seeder.seeded.Trees, oid) + } + + return oid, nil +} + +func (seeder *historySeeder) tag(name string, target id.ObjectID) error { + oid, err := seeder.repo.TagAnnotated( + seeder.tb, + name, + target, + TagAnnotatedOptions{Message: "tag " + name}, //nolint:exhaustruct + ) + if err != nil { + return err + } + + seeder.seeded.Tags = append(seeder.seeded.Tags, oid) + + return nil +} + +func (seeder *historySeeder) record(oid id.ObjectID) bool { + if _, ok := seeder.seen[oid]; ok { + return false + } + + seeder.seen[oid] = struct{}{} + + return true +} -- cgit v1.3.1-10-gc9f91