Artifact bd87a59ea91cfdd1b0815d82195790eff5a8ecb3946fd1ef13a44ef4cb247148:
- File
internal/undo/fossilbin_test.go
— part of check-in
[2dfae63b63]
at
2026-10-19 16:26:21
on branch feat/submanifest-reader
— fix(stash,undo): scan before stashing; return undo changes, print nothing
Stash save now scans the checkout first, as fossil's stash_create does (vfile_check_signature), so an edit no scan had recorded is stashed too. Save takes the scanner, a *checkout.Checkout, as its first argument.
Undo and redo no longer print to standard output: they return the files they changed, as []undo.Change, in the undo table's row order, the order fossil undoes them in.
Tracked in #448 (user: [email protected], size: 3688) [annotate] [blame] [check-ins using] [more...]
//go:build !js package undo_test import ( "database/sql" "os" "os/exec" "path/filepath" "strings" "testing" "time" libdb "github.com/danmestas/go-libfossil/db" "github.com/danmestas/go-libfossil/internal/undo" "github.com/danmestas/go-libfossil/simio" "github.com/danmestas/go-libfossil/testutil" ) // recordedMTime fails unless name's vfile.mtime is its file's mtime. func recordedMTime(t *testing.T, ckout *sql.DB, ck, name string) { t.Helper() var mtime int64 if err := ckout.QueryRow( "SELECT CAST(coalesce(mtime, 0) AS INTEGER) FROM vfile WHERE pathname=?", name, ).Scan(&mtime); err != nil { t.Fatal(err) } info, err := os.Stat(filepath.Join(ck, name)) if err != nil { t.Fatal(err) } if mtime != info.ModTime().Unix() { t.Fatalf("%s: vfile.mtime %d, file mtime %d", name, mtime, info.ModTime().Unix()) } } func fossilIn(t *testing.T, bin, dir string, args ...string) string { t.Helper() cmd := exec.Command(bin, args...) cmd.Dir = dir out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("fossil %s: %v\n%s", strings.Join(args, " "), err, out) } return strings.TrimSpace(string(out)) } // withCheckoutDB opens the checkout's database for fn and closes it before // fossil runs again. func withCheckoutDB(t *testing.T, ck string, fn func(ckout *sql.DB)) { t.Helper() // OpenSQL turns foreign keys off, as fossil runs and as checkouts open // this database: fossil's vfile references a blob table it does not have. ckout, err := libdb.OpenSQL(filepath.Join(ck, ".fslckout"), libdb.OpenConfig{}, nil) if err != nil { t.Fatal(err) } defer ckout.Close() fn(ckout) } // TestUndoVFileReadByFossil: on a checkout fossil scanned, undo here // restores the file and the row fossil recorded for it, now with the written // file's mtime, and redo the edit; fossil's own scan then reports what is on // disk each time. func TestUndoVFileReadByFossil(t *testing.T) { bin := testutil.RequireFossilBin(t) // A clock an hour ahead makes each file written here recordable at once. env := &simio.Env{ Clock: simio.NewSimClockAt(time.Now().Add(time.Hour)), Rand: simio.CryptoRand{}, Storage: simio.OSStorage{}, } dir := t.TempDir() repoPath := filepath.Join(dir, "r.fossil") ck := filepath.Join(dir, "ck") if err := os.Mkdir(ck, 0o755); err != nil { t.Fatal(err) } fossilIn(t, bin, dir, "init", repoPath) fossilIn(t, bin, ck, "open", repoPath) a := filepath.Join(ck, "a.txt") if err := os.WriteFile(a, []byte("hello world\n"), 0o644); err != nil { t.Fatal(err) } fossilIn(t, bin, ck, "add", "a.txt") fossilIn(t, bin, ck, "commit", "-m", "v1") if got := fossilIn(t, bin, ck, "changes"); got != "" { t.Fatalf("fossil changes after commit = %q", got) } withCheckoutDB(t, ck, func(ckout *sql.DB) { if err := undo.Save(ckout, env, ck, nil); err != nil { t.Fatal(err) } }) if err := os.WriteFile(a, []byte("hello world, edited\n"), 0o644); err != nil { t.Fatal(err) } if got := fossilIn(t, bin, ck, "changes"); got != "EDITED a.txt" { t.Fatalf("fossil changes after edit = %q", got) } withCheckoutDB(t, ck, func(ckout *sql.DB) { if _, err := undo.Undo(ckout, env, ck); err != nil { t.Fatal(err) } recordedMTime(t, ckout, ck, "a.txt") }) if got := fossilIn(t, bin, ck, "changes"); got != "" { t.Fatalf("fossil changes after undo = %q", got) } withCheckoutDB(t, ck, func(ckout *sql.DB) { if _, err := undo.Redo(ckout, env, ck); err != nil { t.Fatal(err) } recordedMTime(t, ckout, ck, "a.txt") }) if got := fossilIn(t, bin, ck, "changes"); got != "EDITED a.txt" { t.Fatalf("fossil changes after redo = %q", got) } fossilIn(t, bin, ck, "close", "--force") }