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 }