Artifact 91dc26ef368469fa937f923ddd6ca1c3144cc56ee95b4a7062846b6c54febcb3:
- File
repo_history_test.go
— part of check-in
[a3fbae135e]
at
2026-04-21 02:36:41
on branch trunk
— feat: implement Repo.Diff (#1)
Fills in the TODO stub in repo_history.go so Repo.Diff returns a real unified diff for a file between two checkins instead of a "not yet implemented" error.
The implementation piggybacks on internal/diff.Unified, which already produces Myers-algorithm output. A small blobAt helper walks the manifest at each checkin, resolves the file's UUID to a blob RID, and expands the content. An absent file on a side is treated as empty bytes so pure additions and deletions render as clean insert/delete hunks. Identical content on both sides returns an empty slice rather than a DiffEntry with an empty Unified string, which keeps callers from having to distinguish "no diff" from "missing output".
Adds repo_history_test.go covering modification, identical content, addition, deletion, absent-both-sides, and the empty-filePath error. (user: [email protected], size: 4670) [annotate] [blame] [check-ins using]
package libfossil import ( "path/filepath" "strings" "testing" _ "github.com/danmestas/libfossil/internal/testdriver" ) func newTestRepo(t *testing.T) *Repo { t.Helper() dir := t.TempDir() r, err := Create(filepath.Join(dir, "test.fossil"), CreateOpts{User: "test"}) if err != nil { t.Fatalf("Create: %v", err) } t.Cleanup(func() { _ = r.Close() }) return r } func commit(t *testing.T, r *Repo, parent int64, comment string, files []FileToCommit) int64 { t.Helper() rid, _, err := r.Commit(CommitOpts{ ParentID: parent, Files: files, Comment: comment, User: "test", }) if err != nil { t.Fatalf("Commit %q: %v", comment, err) } return rid } func TestDiff_Modification(t *testing.T) { r := newTestRepo(t) a := commit(t, r, 0, "v1", []FileToCommit{ {Name: "hello.txt", Content: []byte("hello\nworld\n")}, }) b := commit(t, r, a, "v2", []FileToCommit{ {Name: "hello.txt", Content: []byte("hello\nbrave new world\n")}, }) entries, err := r.Diff(a, b, "hello.txt") if err != nil { t.Fatalf("Diff: %v", err) } if len(entries) != 1 { t.Fatalf("got %d entries, want 1", len(entries)) } e := entries[0] if e.Name != "hello.txt" { t.Errorf("Name = %q, want %q", e.Name, "hello.txt") } if !strings.Contains(e.Unified, "a/hello.txt") || !strings.Contains(e.Unified, "b/hello.txt") { t.Errorf("missing headers in unified output:\n%s", e.Unified) } if !strings.Contains(e.Unified, "-world") || !strings.Contains(e.Unified, "+brave new world") { t.Errorf("missing hunk lines in unified output:\n%s", e.Unified) } } func TestDiff_Identical(t *testing.T) { r := newTestRepo(t) a := commit(t, r, 0, "v1", []FileToCommit{ {Name: "hello.txt", Content: []byte("hello\n")}, {Name: "other.txt", Content: []byte("first\n")}, }) b := commit(t, r, a, "touch other", []FileToCommit{ {Name: "hello.txt", Content: []byte("hello\n")}, {Name: "other.txt", Content: []byte("second\n")}, }) entries, err := r.Diff(a, b, "hello.txt") if err != nil { t.Fatalf("Diff: %v", err) } if len(entries) != 0 { t.Errorf("want empty slice for identical content, got %d entries:\n%+v", len(entries), entries) } } func TestDiff_Addition(t *testing.T) { r := newTestRepo(t) a := commit(t, r, 0, "v1", []FileToCommit{ {Name: "keep.txt", Content: []byte("anchor\n")}, }) b := commit(t, r, a, "add new", []FileToCommit{ {Name: "new.txt", Content: []byte("line one\nline two\n")}, }) entries, err := r.Diff(a, b, "new.txt") if err != nil { t.Fatalf("Diff: %v", err) } if len(entries) != 1 { t.Fatalf("got %d entries, want 1", len(entries)) } out := entries[0].Unified if !strings.Contains(out, "+line one") || !strings.Contains(out, "+line two") { t.Errorf("expected insertion lines, got:\n%s", out) } if strings.Contains(out, "-line one") { t.Errorf("unexpected deletion marker for pure addition:\n%s", out) } } func TestDiff_Deletion(t *testing.T) { r := newTestRepo(t) a := commit(t, r, 0, "v1", []FileToCommit{ {Name: "doomed.txt", Content: []byte("goodbye\ncruel world\n")}, {Name: "keep.txt", Content: []byte("anchor\n")}, }) b := commit(t, r, a, "drop doomed", []FileToCommit{ {Name: "keep.txt", Content: []byte("anchor\n")}, }) entries, err := r.Diff(a, b, "doomed.txt") if err != nil { t.Fatalf("Diff: %v", err) } if len(entries) != 1 { t.Fatalf("got %d entries, want 1", len(entries)) } out := entries[0].Unified if !strings.Contains(out, "-goodbye") || !strings.Contains(out, "-cruel world") { t.Errorf("expected deletion lines, got:\n%s", out) } if strings.Contains(out, "+goodbye") { t.Errorf("unexpected insertion marker for pure deletion:\n%s", out) } } func TestDiff_AbsentBothSides(t *testing.T) { r := newTestRepo(t) a := commit(t, r, 0, "v1", []FileToCommit{ {Name: "only.txt", Content: []byte("x\n")}, }) b := commit(t, r, a, "v2", []FileToCommit{ {Name: "only.txt", Content: []byte("y\n")}, }) entries, err := r.Diff(a, b, "never-existed.txt") if err != nil { t.Fatalf("Diff: %v", err) } if len(entries) != 0 { t.Errorf("want empty slice for file absent in both checkins, got %d entries:\n%+v", len(entries), entries) } } func TestDiff_EmptyFilePath(t *testing.T) { r := newTestRepo(t) a := commit(t, r, 0, "v1", []FileToCommit{ {Name: "hello.txt", Content: []byte("hello\n")}, }) b := commit(t, r, a, "v2", []FileToCommit{ {Name: "hello.txt", Content: []byte("world\n")}, }) _, err := r.Diff(a, b, "") if err == nil { t.Fatal("expected error for empty filePath, got nil") } if !strings.Contains(err.Error(), "filePath is required") { t.Errorf("error = %q, want it to mention filePath", err.Error()) } }