Artifact 2d511c81ee58a673c25bf87b9542b0d03e458fdb04d1061ef619eed50f3b1f74:
- File
internal/annotate/annotate_test.go
— part of check-in
[6b66ea6b2d]
at
2026-05-12 20:01:20
on branch trunk
— feat: CreateOpts.ProjectCode — accept explicit project-code on Create (#31)
* feat: CreateOpts.ProjectCode — accept explicit project-code on Create
Downstream EdgeSync coordinates a shared project-code across sibling Fossil repos created on different machines so its NATS sync subject ("<prefix>.<project-code>.sync") matches across leaves. Today every Create generates a fresh project-code, so commits never propagate.
Add CreateOpts.ProjectCode: empty preserves current behavior; non-empty must be 40-char lowercase hex (^[0-9a-f]{40}$) — the same format generated project-codes use. Validation runs at the top of libfossil.Create before any disk I/O, so invalid input cannot leave a half-written file behind. Server-code stays auto-generated.
Threaded through repo.Create / repo.CreateWithEnv / db.SeedConfig. SeedConfig validates as defense-in-depth (public package, multiple callers possible).
Clone accepting ProjectCode is out of scope; will follow separately if EdgeSync needs it.
* test: round-trip explicit ProjectCode through fossil rebuild
Asserts that vanilla `fossil` (2.28) accepts a Go-side repo created with an explicit ProjectCode, and that `fossil sql` reads back the same value we set. Closes the gap where the existing TestCreate_FossilValidation only covered the generated-project-code path. (user: [email protected], size: 6366) [annotate] [blame] [check-ins using]
package annotate import ( "path/filepath" "testing" "time" libfossil "github.com/danmestas/libfossil/internal/fsltype" "github.com/danmestas/libfossil/internal/manifest" "github.com/danmestas/libfossil/internal/repo" "github.com/danmestas/libfossil/simio" _ "github.com/danmestas/libfossil/internal/testdriver" ) func setupTestRepo(t *testing.T) *repo.Repo { t.Helper() path := filepath.Join(t.TempDir(), "test.fossil") r, err := repo.Create(path, "testuser", simio.CryptoRand{}, "") if err != nil { t.Fatalf("repo.Create: %v", err) } t.Cleanup(func() { r.Close() }) return r } // setupThreeCommits creates a repo with 3 commits: // // commit 1 (alice): "line A\nline B\n" // commit 2 (bob): "line A\nline C\n" (B changed to C) // commit 3 (charlie): "line A\nline C\nline D\n" (added D) // // Returns the repo and the three checkin RIDs. func setupThreeCommits(t *testing.T) (*repo.Repo, libfossil.FslID, libfossil.FslID, libfossil.FslID) { t.Helper() r := setupTestRepo(t) rid1, _, err := manifest.Checkin(r, manifest.CheckinOpts{ Files: []manifest.File{{Name: "file.txt", Content: []byte("line A\nline B\n")}}, Comment: "commit 1", User: "alice", Time: time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC), }) if err != nil { t.Fatalf("checkin 1: %v", err) } rid2, _, err := manifest.Checkin(r, manifest.CheckinOpts{ Files: []manifest.File{{Name: "file.txt", Content: []byte("line A\nline C\n")}}, Comment: "commit 2", User: "bob", Parent: rid1, Time: time.Date(2024, 1, 15, 11, 0, 0, 0, time.UTC), }) if err != nil { t.Fatalf("checkin 2: %v", err) } rid3, _, err := manifest.Checkin(r, manifest.CheckinOpts{ Files: []manifest.File{{Name: "file.txt", Content: []byte("line A\nline C\nline D\n")}}, Comment: "commit 3", User: "charlie", Parent: rid2, Time: time.Date(2024, 1, 15, 12, 0, 0, 0, time.UTC), }) if err != nil { t.Fatalf("checkin 3: %v", err) } return r, rid1, rid2, rid3 } func TestAnnotateBasic(t *testing.T) { r, _, _, rid3 := setupThreeCommits(t) lines, err := Annotate(r, Options{ FilePath: "file.txt", StartRID: rid3, }) if err != nil { t.Fatalf("Annotate: %v", err) } if len(lines) != 3 { t.Fatalf("expected 3 lines, got %d", len(lines)) } // line A should be attributed to alice (commit 1) if lines[0].Text != "line A" { t.Errorf("line 0 text = %q, want %q", lines[0].Text, "line A") } if lines[0].Version.User != "alice" { t.Errorf("line 0 user = %q, want %q", lines[0].Version.User, "alice") } // line C should be attributed to bob (commit 2) if lines[1].Text != "line C" { t.Errorf("line 1 text = %q, want %q", lines[1].Text, "line C") } if lines[1].Version.User != "bob" { t.Errorf("line 1 user = %q, want %q", lines[1].Version.User, "bob") } // line D should be attributed to charlie (commit 3) if lines[2].Text != "line D" { t.Errorf("line 2 text = %q, want %q", lines[2].Text, "line D") } if lines[2].Version.User != "charlie" { t.Errorf("line 2 user = %q, want %q", lines[2].Version.User, "charlie") } } func TestAnnotateWithLimit(t *testing.T) { r, _, _, rid3 := setupThreeCommits(t) // Limit=1 means we can only walk 1 ancestor (commit 2), not commit 1. // But we start at commit 3 — limit restricts parent walks. // With limit=0 steps allowed, all lines stay at charlie. lines, err := Annotate(r, Options{ FilePath: "file.txt", StartRID: rid3, Limit: 0, // unlimited — test limit=1 below }) if err != nil { t.Fatalf("Annotate unlimited: %v", err) } if lines[0].Version.User != "alice" { t.Errorf("unlimited: line 0 user = %q, want alice", lines[0].Version.User) } // Now with limit=1: can walk only to commit 2 (one step from commit 3). // All lines start attributed to charlie, then one walk to bob. // line A appears in bob's version -> attributed to bob // line C appears in bob's version -> attributed to bob // line D is new in charlie -> stays charlie // But we can't walk to alice because limit is exhausted. lines, err = Annotate(r, Options{ FilePath: "file.txt", StartRID: rid3, Limit: 1, }) if err != nil { t.Fatalf("Annotate limit=1: %v", err) } if len(lines) != 3 { t.Fatalf("expected 3 lines, got %d", len(lines)) } // line A -> bob (walked one step) if lines[0].Version.User != "bob" { t.Errorf("limit=1: line 0 user = %q, want bob", lines[0].Version.User) } // line C -> bob if lines[1].Version.User != "bob" { t.Errorf("limit=1: line 1 user = %q, want bob", lines[1].Version.User) } // line D -> charlie (new in commit 3) if lines[2].Version.User != "charlie" { t.Errorf("limit=1: line 2 user = %q, want charlie", lines[2].Version.User) } } func TestAnnotateWithOrigin(t *testing.T) { r, _, rid2, rid3 := setupThreeCommits(t) // Origin=commit 2: stop before walking past commit 2. // Walk from commit 3 to commit 2 (one step), but don't go to commit 1. // line A -> bob (from commit 2, can't go further) // line C -> bob // line D -> charlie lines, err := Annotate(r, Options{ FilePath: "file.txt", StartRID: rid3, OriginRID: rid2, }) if err != nil { t.Fatalf("Annotate: %v", err) } if len(lines) != 3 { t.Fatalf("expected 3 lines, got %d", len(lines)) } if lines[0].Version.User != "bob" { t.Errorf("origin: line 0 user = %q, want bob", lines[0].Version.User) } if lines[1].Version.User != "bob" { t.Errorf("origin: line 1 user = %q, want bob", lines[1].Version.User) } if lines[2].Version.User != "charlie" { t.Errorf("origin: line 2 user = %q, want charlie", lines[2].Version.User) } } func TestAnnotateSingleCommit(t *testing.T) { r, rid1, _, _ := setupThreeCommits(t) lines, err := Annotate(r, Options{ FilePath: "file.txt", StartRID: rid1, }) if err != nil { t.Fatalf("Annotate: %v", err) } if len(lines) != 2 { t.Fatalf("expected 2 lines, got %d", len(lines)) } // Both lines attributed to alice (single commit, no parent). if lines[0].Version.User != "alice" { t.Errorf("line 0 user = %q, want alice", lines[0].Version.User) } if lines[0].Text != "line A" { t.Errorf("line 0 text = %q, want %q", lines[0].Text, "line A") } if lines[1].Version.User != "alice" { t.Errorf("line 1 user = %q, want alice", lines[1].Version.User) } if lines[1].Text != "line B" { t.Errorf("line 1 text = %q, want %q", lines[1].Text, "line B") } }