Artifact cd3a5669173b27ba77fa9bdb2e8c3a6022725269d16e601383786446705fafa9:
- File
internal/checkout/revert_test.go
— part of check-in
[6273a0750d]
at
2026-07-22 23:03:24
on branch trunk
— fix: correct go.mod module path to go-libfossil (#76) (#117)
* fix: correct go.mod module path to go-libfossil (#76)
The module directive still declared github.com/danmestas/libfossil after the repository was renamed to go-libfossil. pkg.go.dev/godoc scraping matches the declared module path against its hosting path, so the stale path broke SDK doc generation.
Rename the module path across all four in-repo modules (root, the two db/driver submodules, observer/otel, and docs/site) and every internal import, plus the release workflow, the doc-generation script, and the docs.
No tag has been published under the go-libfossil path yet, so the cross-module requires cannot resolve from the proxy. Add replace directives pointing the in-repo requires at their local directories so GOWORK=off builds and the CI matrix resolve without a published version. These replaces are ignored by downstream consumers and can be dropped once new-path tags are cut.
* Guard releases against replace directives; regenerate SDK docs
The go-libfossil rename bridges the in-repo modules with `replace` directives. Go refuses a main module that carries them, so a tag cut while they are present breaks `go install .../cmd/libfossil@<tag>` and every `@version` fetch of the driver submodules. Add a Release workflow step that fails the job before anything is published if any tracked go.mod still contains a replace directive, so the bridge is a CI-enforced TODO rather than a silent landmine. The step comment records what unblocks a release: re-tag the submodules with the corrected module paths, bump the root requires, then drop the replace blocks.
Regenerate the SDK reference docs. The previous pass only substituted URLs over the pre-existing generated files, leaving stale signatures (Repo.Timeline was still documented as taking LogOpts rather than TimelineOpts). Note that gomarkdoc must be run outside a git worktree — inside one it silently strips every source link.
Also reword the README rename note, which read as though the import path had not changed, and re-sort the ncruces require block. (user: [email protected], size: 10844) [annotate] [blame] [check-ins using]
package checkout import ( "testing" "github.com/danmestas/go-libfossil/simio" ) func TestRevertModified(t *testing.T) { r, cleanup := newTestRepoWithCheckin(t) defer cleanup() dir := t.TempDir() co, err := Create(r, dir, CreateOpts{}) if err != nil { t.Fatal(err) } defer co.Close() rid, _, _ := co.Version() mem := simio.NewMemStorage() co.env = &simio.Env{Storage: mem, Clock: simio.RealClock{}, Rand: simio.CryptoRand{}} co.dir = "/checkout" // Extract files if err := co.Extract(rid, ExtractOpts{}); err != nil { t.Fatal(err) } // Modify hello.txt in MemStorage if err := mem.WriteFile("/checkout/hello.txt", []byte("modified content\n"), 0644); err != nil { t.Fatal(err) } // Scan to detect the change (with hash flag to detect content changes) if err := co.ScanChanges(ScanHash); err != nil { t.Fatal(err) } // Verify it's marked as changed var chnged int err = co.db.QueryRow("SELECT chnged FROM vfile WHERE vid=? AND pathname='hello.txt'", int64(rid)).Scan(&chnged) if err != nil { t.Fatal("vfile row not found:", err) } if chnged == 0 { t.Fatal("expected chnged > 0 after modification") } // Revert the file err = co.Revert(RevertOpts{Paths: []string{"hello.txt"}}) if err != nil { t.Fatal(err) } // Verify content is restored data, err := mem.ReadFile("/checkout/hello.txt") if err != nil { t.Fatal("file not found after revert:", err) } if string(data) != "hello world\n" { t.Fatalf("content = %q, want %q", data, "hello world\n") } // Verify vfile.chnged reset to 0 err = co.db.QueryRow("SELECT chnged FROM vfile WHERE vid=? AND pathname='hello.txt'", int64(rid)).Scan(&chnged) if err != nil { t.Fatal(err) } if chnged != 0 { t.Fatalf("chnged = %d, want 0 after revert", chnged) } } func TestRevertDeleted(t *testing.T) { r, cleanup := newTestRepoWithCheckin(t) defer cleanup() dir := t.TempDir() co, err := Create(r, dir, CreateOpts{}) if err != nil { t.Fatal(err) } defer co.Close() rid, _, _ := co.Version() mem := simio.NewMemStorage() co.env = &simio.Env{Storage: mem, Clock: simio.RealClock{}, Rand: simio.CryptoRand{}} co.dir = "/checkout" // Extract files if err := co.Extract(rid, ExtractOpts{}); err != nil { t.Fatal(err) } // Unmanage hello.txt (marks deleted=1) err = co.Unmanage(UnmanageOpts{Paths: []string{"hello.txt"}}) if err != nil { t.Fatal(err) } // Verify deleted=1 var deleted int err = co.db.QueryRow("SELECT deleted FROM vfile WHERE vid=? AND pathname='hello.txt'", int64(rid)).Scan(&deleted) if err != nil { t.Fatal(err) } if deleted != 1 { t.Fatalf("deleted = %d, want 1 after unmanage", deleted) } // Revert the file err = co.Revert(RevertOpts{Paths: []string{"hello.txt"}}) if err != nil { t.Fatal(err) } // Verify deleted reset to 0 err = co.db.QueryRow("SELECT deleted FROM vfile WHERE vid=? AND pathname='hello.txt'", int64(rid)).Scan(&deleted) if err != nil { t.Fatal(err) } if deleted != 0 { t.Fatalf("deleted = %d, want 0 after revert", deleted) } // Verify content is restored data, err := mem.ReadFile("/checkout/hello.txt") if err != nil { t.Fatal("file not found after revert:", err) } if string(data) != "hello world\n" { t.Fatalf("content = %q, want %q", data, "hello world\n") } } func TestRevertNewlyAdded(t *testing.T) { r, cleanup := newTestRepoWithCheckin(t) defer cleanup() dir := t.TempDir() co, err := Create(r, dir, CreateOpts{}) if err != nil { t.Fatal(err) } defer co.Close() rid, _, _ := co.Version() mem := simio.NewMemStorage() co.env = &simio.Env{Storage: mem, Clock: simio.RealClock{}, Rand: simio.CryptoRand{}} co.dir = "/checkout" // Extract files if err := co.Extract(rid, ExtractOpts{}); err != nil { t.Fatal(err) } // Add a new file if err := mem.WriteFile("/checkout/new.txt", []byte("new file"), 0644); err != nil { t.Fatal(err) } if _, err := co.Manage(ManageOpts{Paths: []string{"new.txt"}}); err != nil { t.Fatal(err) } // Verify vfile row exists with rid=0 var vRid int err = co.db.QueryRow("SELECT rid FROM vfile WHERE vid=? AND pathname='new.txt'", int64(rid)).Scan(&vRid) if err != nil { t.Fatal("vfile row not found:", err) } if vRid != 0 { t.Fatalf("rid = %d, want 0 (newly added)", vRid) } // Revert the newly added file err = co.Revert(RevertOpts{Paths: []string{"new.txt"}}) if err != nil { t.Fatal(err) } // Verify vfile row is deleted var count int err = co.db.QueryRow("SELECT count(*) FROM vfile WHERE vid=? AND pathname='new.txt'", int64(rid)).Scan(&count) if err != nil { t.Fatal(err) } if count != 0 { t.Fatalf("expected 0 rows after revert, got %d", count) } // Verify file is removed from Storage (or at least not an error if it doesn't exist) _, err = mem.ReadFile("/checkout/new.txt") if err == nil { t.Fatal("file should be removed after revert") } } func TestRevertAll(t *testing.T) { r, cleanup := newTestRepoWithCheckin(t) defer cleanup() dir := t.TempDir() co, err := Create(r, dir, CreateOpts{}) if err != nil { t.Fatal(err) } defer co.Close() rid, _, _ := co.Version() mem := simio.NewMemStorage() co.env = &simio.Env{Storage: mem, Clock: simio.RealClock{}, Rand: simio.CryptoRand{}} co.dir = "/checkout" // Extract files if err := co.Extract(rid, ExtractOpts{}); err != nil { t.Fatal(err) } // Modify hello.txt if err := mem.WriteFile("/checkout/hello.txt", []byte("modified 1\n"), 0644); err != nil { t.Fatal(err) } // Modify src/main.go if err := mem.WriteFile("/checkout/src/main.go", []byte("modified 2\n"), 0644); err != nil { t.Fatal(err) } // Add a new file if err := mem.WriteFile("/checkout/new.txt", []byte("new"), 0644); err != nil { t.Fatal(err) } if _, err := co.Manage(ManageOpts{Paths: []string{"new.txt"}}); err != nil { t.Fatal(err) } // Scan to detect changes (with hash flag to detect content changes) if err := co.ScanChanges(ScanHash); err != nil { t.Fatal(err) } // Verify we have changes hasChanges, err := co.HasChanges() if err != nil { t.Fatal(err) } if !hasChanges { t.Fatal("expected changes before revert") } // Revert all (empty Paths) err = co.Revert(RevertOpts{}) if err != nil { t.Fatal(err) } // Verify all files are restored data1, err := mem.ReadFile("/checkout/hello.txt") if err != nil { t.Fatal("hello.txt not found after revert:", err) } if string(data1) != "hello world\n" { t.Fatalf("hello.txt = %q, want %q", data1, "hello world\n") } data2, err := mem.ReadFile("/checkout/src/main.go") if err != nil { t.Fatal("src/main.go not found after revert:", err) } if string(data2) != "package main\n" { t.Fatalf("src/main.go = %q, want %q", data2, "package main\n") } // Verify new.txt is removed _, err = mem.ReadFile("/checkout/new.txt") if err == nil { t.Fatal("new.txt should be removed after revert") } // Verify no changes remain hasChanges, err = co.HasChanges() if err != nil { t.Fatal(err) } if hasChanges { t.Fatal("expected no changes after revert all") } } func TestRevertCallback(t *testing.T) { r, cleanup := newTestRepoWithCheckin(t) defer cleanup() dir := t.TempDir() co, err := Create(r, dir, CreateOpts{}) if err != nil { t.Fatal(err) } defer co.Close() rid, _, _ := co.Version() mem := simio.NewMemStorage() co.env = &simio.Env{Storage: mem, Clock: simio.RealClock{}, Rand: simio.CryptoRand{}} co.dir = "/checkout" // Extract and modify a file if err := co.Extract(rid, ExtractOpts{}); err != nil { t.Fatal(err) } if err := mem.WriteFile("/checkout/hello.txt", []byte("modified"), 0644); err != nil { t.Fatal(err) } if err := co.ScanChanges(ScanHash); err != nil { t.Fatal(err) } // Revert with callback var callbackName string var callbackChange RevertChange err = co.Revert(RevertOpts{ Paths: []string{"hello.txt"}, Callback: func(name string, change RevertChange) error { callbackName = name callbackChange = change return nil }, }) if err != nil { t.Fatal(err) } if callbackName != "hello.txt" { t.Fatalf("callback name = %q, want %q", callbackName, "hello.txt") } if callbackChange != RevertContents { t.Fatalf("callback change = %v, want RevertContents", callbackChange) } } func TestRevertCallbackUnmanage(t *testing.T) { r, cleanup := newTestRepoWithCheckin(t) defer cleanup() dir := t.TempDir() co, err := Create(r, dir, CreateOpts{}) if err != nil { t.Fatal(err) } defer co.Close() rid, _, _ := co.Version() mem := simio.NewMemStorage() co.env = &simio.Env{Storage: mem, Clock: simio.RealClock{}, Rand: simio.CryptoRand{}} co.dir = "/checkout" // Extract, add a new file, then revert it if err := co.Extract(rid, ExtractOpts{}); err != nil { t.Fatal(err) } if err := mem.WriteFile("/checkout/new.txt", []byte("new"), 0644); err != nil { t.Fatal(err) } if _, err := co.Manage(ManageOpts{Paths: []string{"new.txt"}}); err != nil { t.Fatal(err) } // Revert with callback var callbackName string var callbackChange RevertChange err = co.Revert(RevertOpts{ Paths: []string{"new.txt"}, Callback: func(name string, change RevertChange) error { callbackName = name callbackChange = change return nil }, }) if err != nil { t.Fatal(err) } if callbackName != "new.txt" { t.Fatalf("callback name = %q, want %q", callbackName, "new.txt") } if callbackChange != RevertUnmanage { t.Fatalf("callback change = %v, want RevertUnmanage", callbackChange) } } func TestRevertNotFound(t *testing.T) { r, cleanup := newTestRepoWithCheckin(t) defer cleanup() dir := t.TempDir() co, err := Create(r, dir, CreateOpts{}) if err != nil { t.Fatal(err) } defer co.Close() rid, _, _ := co.Version() mem := simio.NewMemStorage() co.env = &simio.Env{Storage: mem, Clock: simio.RealClock{}, Rand: simio.CryptoRand{}} co.dir = "/checkout" // Extract files if err := co.Extract(rid, ExtractOpts{}); err != nil { t.Fatal(err) } // Revert a file that doesn't exist — should not error (no-op) err = co.Revert(RevertOpts{Paths: []string{"nonexistent.txt"}}) if err != nil { t.Fatal("expected no error for nonexistent file, got:", err) } } func TestRevertNoChanges(t *testing.T) { r, cleanup := newTestRepoWithCheckin(t) defer cleanup() dir := t.TempDir() co, err := Create(r, dir, CreateOpts{}) if err != nil { t.Fatal(err) } defer co.Close() rid, _, _ := co.Version() mem := simio.NewMemStorage() co.env = &simio.Env{Storage: mem, Clock: simio.RealClock{}, Rand: simio.CryptoRand{}} co.dir = "/checkout" // Extract files but don't modify them if err := co.Extract(rid, ExtractOpts{}); err != nil { t.Fatal(err) } // Revert a clean file — should not error (no-op) err = co.Revert(RevertOpts{Paths: []string{"hello.txt"}}) if err != nil { t.Fatal("expected no error for clean file, got:", err) } }