package gitx
import (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"sourcecraft.dev/bigbes/sr-ht-compare/core"
)
// newFidelityRepo builds a two-commit bare repo whose base..head diff exercises
// (a) a rename WITH an edit, (b) a binary file change, and (c) a plain text
// edit. It returns reposRoot plus the base and head SHAs.
func newFidelityRepo(t *testing.T) (reposRoot, base, head string) {
t.Helper()
reposRoot = t.TempDir()
work := t.TempDir()
d1 := "2024-02-01T00:00:00Z"
d2 := "2024-02-02T00:00:00Z"
gitTest(t, work, d1, "init", "-b", "main")
// Base commit.
writeFile(t, work, "a.txt", []byte("line1\nline2\nline3\nline4\n"))
writeFile(t, work, "old.txt", []byte("one\ntwo\nthree\nfour\nfive\nsix\n"))
writeFile(t, work, "img.bin", []byte{0x00, 0x01, 0x02, 0x03, 0x00, 0xff})
gitTest(t, work, d1, "add", "-A")
gitTest(t, work, d1, "commit", "-m", "base")
// Head commit: plain edit, rename+edit, binary change.
writeFile(t, work, "a.txt", []byte("line1\nline2-edited\nline3\nline4\n"))
gitTest(t, work, d2, "mv", "old.txt", "new.txt")
writeFile(t, work, "new.txt", []byte("one\ntwo\nthree-edited\nfour\nfive\nsix\n"))
writeFile(t, work, "img.bin", []byte{0xff, 0xfe, 0x00, 0x11, 0x22, 0x33})
gitTest(t, work, d2, "add", "-A")
gitTest(t, work, d2, "commit", "-m", "head: rename+edit, binary change, plain edit")
if err := os.MkdirAll(filepath.Join(reposRoot, "~"+fxOwner), 0o755); err != nil {
t.Fatal(err)
}
bare := filepath.Join(reposRoot, "~"+fxOwner, "fidelity")
gitTest(t, work, d2, "clone", "--bare", work, bare)
base = fxRevIn(t, reposRoot, "fidelity", "main~1")
head = fxRevIn(t, reposRoot, "fidelity", "main")
return reposRoot, base, head
}
// fxRevIn is fxRev for an arbitrary repo name under fxOwner.
func fxRevIn(t *testing.T, reposRoot, name, rev string) string {
t.Helper()
bare := filepath.Join(reposRoot, "~"+fxOwner, name)
cmd := exec.Command("git", "-C", bare, "rev-parse", rev)
cmd.Env = append(os.Environ(),
"GIT_CONFIG_GLOBAL=/dev/null", "GIT_CONFIG_SYSTEM=/dev/null", "LC_ALL=C")
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("fxRevIn %q: %v\n%s", rev, err, out)
}
return strings.TrimSpace(string(out))
}
// TestPatchFidelity is the compatibility gate for the browser-side
// parsePatchFiles(): go-git's generated patch must carry standard git headers
// for renames, binary files, and text hunks.
func TestPatchFidelity(t *testing.T) {
reposRoot, base, head := newFidelityRepo(t)
repo, err := Open(reposRoot, fxOwner, "fidelity")
if err != nil {
t.Fatalf("Open: %v", err)
}
spec := core.CompareSpec{Base: base, Head: head}
patch, err := repo.Diff(ctx(), spec)
if err != nil {
t.Fatalf("Diff: %v", err)
}
text := patch.Text
t.Logf("generated patch:\n%s", text)
mustContain := []string{
// plain edit
"diff --git a/a.txt b/a.txt",
"@@",
"-line2",
"+line2-edited",
// rename + edit
"diff --git a/old.txt b/new.txt",
"rename from old.txt",
"rename to new.txt",
// binary change
"img.bin",
"Binary files",
}
for _, want := range mustContain {
if !strings.Contains(text, want) {
t.Errorf("patch missing %q\n---\n%s", want, text)
}
}
// The rename must be classified as a single R change, not add+delete.
files, err := repo.DiffStat(ctx(), spec)
if err != nil {
t.Fatalf("DiffStat: %v", err)
}
byPath := map[string]FileChange{}
for _, f := range files {
byPath[f.Path] = f
}
if r, ok := byPath["new.txt"]; !ok || r.Status != "R" || r.OldPath != "old.txt" {
t.Errorf("rename entry = %+v (ok=%v), want R from old.txt", byPath["new.txt"], ok)
}
if r := byPath["new.txt"]; r.Additions == 0 && r.Deletions == 0 {
t.Errorf("rename+edit should report line changes, got %+v", r)
}
if b, ok := byPath["img.bin"]; !ok || !b.Binary {
t.Errorf("img.bin = %+v (ok=%v), want binary", byPath["img.bin"], ok)
}
if a, ok := byPath["a.txt"]; !ok || a.Status != "M" {
t.Errorf("a.txt = %+v (ok=%v), want M", byPath["a.txt"], ok)
}
}