package gitx import ( "os" "os/exec" "path/filepath" "strings" "testing" ) // The fixture builds a bare repository at /~alice/demo with a history rich // enough to exercise every command: // // root ── c2 ── rename ── binary ─┐ // \ merge (branch: main, HEAD) // feature ─────────────┘ (branch: feature/with-slash) // // root : add a.txt (root commit) // c2 : add b.txt, edit a.txt // rename : git mv a.txt -> a-renamed.txt (tag: v0.9.0) // binary : add bin.dat (4 NUL-ish bytes) (main^1) // feature : add feature.txt (from c2) (branch feature/with-slash, main^2) // merge : merge feature into main (main, HEAD; tag: v1.0.0) // // The commit dates increase monotonically so tag creator-date ordering is // deterministic (v1.0.0 newer than v0.9.0). const ( fxOwner = "alice" fxName = "demo" ) // gitTest runs git in dir with a hardened, deterministic environment, failing // the test on error. It takes a testing.TB rather than a *testing.T because // bench_test.go builds its own fixture repository with the same helper. func gitTest(t testing.TB, dir string, date string, args ...string) string { t.Helper() cmd := exec.Command("git", args...) cmd.Dir = dir cmd.Env = append(os.Environ(), "GIT_CONFIG_GLOBAL=/dev/null", "GIT_CONFIG_SYSTEM=/dev/null", "GIT_TERMINAL_PROMPT=0", "LC_ALL=C", "GIT_AUTHOR_NAME=Alice Example", "GIT_AUTHOR_EMAIL=alice@example.com", "GIT_COMMITTER_NAME=Alice Example", "GIT_COMMITTER_EMAIL=alice@example.com", "GIT_AUTHOR_DATE="+date, "GIT_COMMITTER_DATE="+date, ) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("git %s: %v\n%s", strings.Join(args, " "), err, out) } return string(out) } func writeFile(t testing.TB, dir, name string, data []byte) { t.Helper() p := filepath.Join(dir, name) if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(p, data, 0o644); err != nil { t.Fatal(err) } } // newFixtureRepo builds the bare fixture repository and returns reposRoot (the // directory that Open takes). The bare repo lives at reposRoot/~alice/demo. func newFixtureRepo(t *testing.T) string { t.Helper() if _, err := exec.LookPath("git"); err != nil { t.Skipf("git not available: %v", err) } reposRoot := t.TempDir() work := t.TempDir() d1 := "2024-01-01T00:00:00Z" d2 := "2024-01-02T00:00:00Z" d3 := "2024-01-03T00:00:00Z" d4 := "2024-01-04T00:00:00Z" d5 := "2024-01-05T00:00:00Z" d6 := "2024-01-06T00:00:00Z" gitTest(t, work, d1, "init", "-b", "main") // root: add a.txt writeFile(t, work, "a.txt", []byte("hello\nworld\n")) gitTest(t, work, d1, "add", "a.txt") gitTest(t, work, d1, "commit", "-m", "root: add a.txt") // c2: add b.txt, edit a.txt writeFile(t, work, "a.txt", []byte("hello\nworld\nmore\n")) writeFile(t, work, "b.txt", []byte("bee\n")) gitTest(t, work, d2, "add", "a.txt", "b.txt") gitTest(t, work, d2, "commit", "-m", "c2: add b, edit a") // feature branch from c2 (before rename/binary): add feature.txt gitTest(t, work, d5, "branch", "feature/with-slash") // rename: pure git mv a.txt -> a-renamed.txt gitTest(t, work, d3, "mv", "a.txt", "a-renamed.txt") gitTest(t, work, d3, "commit", "-m", "rename: a.txt -> a-renamed.txt") gitTest(t, work, d3, "tag", "-a", "-m", "release 0.9.0", "v0.9.0") // binary: add bin.dat (4 bytes, includes NUL) writeFile(t, work, "bin.dat", []byte{0x00, 0x01, 0x02, 0x03}) gitTest(t, work, d4, "add", "bin.dat") gitTest(t, work, d4, "commit", "-m", "binary: add bin.dat") // build feature tip gitTest(t, work, d5, "checkout", "feature/with-slash") writeFile(t, work, "feature.txt", []byte("feature\n")) gitTest(t, work, d5, "add", "feature.txt") gitTest(t, work, d5, "commit", "-m", "feature: add feature.txt") // merge feature into main (no-ff so a merge commit is created) gitTest(t, work, d6, "checkout", "main") gitTest(t, work, d6, "merge", "--no-ff", "-m", "merge feature/with-slash", "feature/with-slash") gitTest(t, work, d6, "tag", "-a", "-m", "release 1.0.0", "v1.0.0") // bare clone into reposRoot/~alice/demo if err := os.MkdirAll(filepath.Join(reposRoot, "~"+fxOwner), 0o755); err != nil { t.Fatal(err) } bare := filepath.Join(reposRoot, "~"+fxOwner, fxName) gitTest(t, work, d6, "clone", "--bare", work, bare) return reposRoot } // fxRev resolves a revision inside the bare fixture repo to its full SHA. It is // a test helper for landmark navigation (e.g. "main^1~1" for the rename // commit); it is independent of the code under test. func fxRev(t *testing.T, reposRoot, rev string) string { t.Helper() return fxRevIn(t, reposRoot, fxName, rev) } // openFixture opens the fixture repo through the code under test. func openFixture(t *testing.T, reposRoot string) *Repo { t.Helper() repo, err := Open(reposRoot, fxOwner, fxName) if err != nil { t.Fatalf("Open: %v", err) } return repo }