Artifact a53d6c02ce937037ce92d751db4987c1c7667aa07355f214d4a3398a7276dc31:
- File
internal/stash/fossilbin_test.go
— part of check-in
[41951ffbc5]
at
2026-10-19 01:53:57
on branch feat/submanifest-reader
— fix(stash,undo): write working files through the checkout's storage
Stash and undo read, wrote and removed working files with the os package, so a MemStorage checkout and simulation tests never saw those writes, and no write recorded its mtime. They now take a *simio.Env and go through its Storage, recording each written file's mtime via vfile.WrittenMTime (moved with RecordableMTime from internal/checkout so these packages can share the same-second guard).
Stash save marks reverted rows unchanged with their written mtime, as fossil's revert does. Apply and pop add a stashed added file again, as fossil's stash_apply does, and give a restored edit's row its changed state and written mtime. Undo and redo record a written file's mtime only when the restored row's mtime matched the file when it was saved.
Tracked in #448 (user: [email protected], size: 3792) [annotate] [blame] [check-ins using] [more...]
//go:build !js package stash_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/stash" "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)) } // withDBs opens the checkout's and the repository's databases for fn and // closes them before fossil runs again. func withDBs(t *testing.T, ck, repoPath string, fn func(ckout, repoDB *sql.DB)) { t.Helper() // OpenSQL turns foreign keys off, as fossil runs and as checkouts open // these databases: fossil's vfile references a blob table the checkout // database does not have. ckout, err := libdb.OpenSQL(filepath.Join(ck, ".fslckout"), libdb.OpenConfig{}, nil) if err != nil { t.Fatal(err) } defer ckout.Close() repoDB, err := libdb.OpenSQL(repoPath, libdb.OpenConfig{}, nil) if err != nil { t.Fatal(err) } defer repoDB.Close() fn(ckout, repoDB) } // TestStashVFileReadByFossil: after a stash save and pop made here, fossil's // own scan of the checkout reports what is on disk: nothing after the save, // which records the reverted file's mtime, and the edit after the pop, which // records the row changed. func TestStashVFileReadByFossil(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") // A different size: fossil itself misses a same-size edit in the second // its commit recorded. 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 before save = %q", got) } withDBs(t, ck, repoPath, func(ckout, repoDB *sql.DB) { if err := stash.Save(ckout, repoDB, env, ck, "edit"); err != nil { t.Fatal(err) } recordedMTime(t, ckout, ck, "a.txt") }) if got := fossilIn(t, bin, ck, "changes"); got != "" { t.Fatalf("fossil changes after save = %q", got) } withDBs(t, ck, repoPath, func(ckout, repoDB *sql.DB) { if err := stash.Pop(ckout, repoDB, 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 pop = %q", got) } fossilIn(t, bin, ck, "close", "--force") }