Artifact f105e9536bb7988c2bcbe782de1348da9493be070d39636a2923afdf151d11b1:
- File
checkout_update_undo_interop_test.go
— part of check-in
[294a537880]
at
2026-10-19 16:10:06
on branch feat/submanifest-reader
— fix(checkout): save undo state in Update, as fossil does
Update saved no undo state, so nothing could reverse it. It now reads the file of every step that writes or removes one before writing, as fossil's update calls undo_save, and saves those files with copies of vfile and vmerge and the checked-out version in the same transaction that moves the checkout to the target. If a write fails partway the files are put back, as fossil's undo_rollback does.
Checkout.Undo and Checkout.Redo (internal and public) swap that state back and forth and keep checkout-hash naming the restored version. The undo package gains ReadFile, SaveTx and RestoreFiles, and its undo table now has fossil's schema (isExe, unique pathname, rowid order, the executable bit read from disk), so fossil undo reverses a go-libfossil update and Checkout.Undo reverses fossil update; fossil 2.28 interop tests check both. Under js/wasm, which has no undo package, Update saves nothing.
Tracked in #446 (user: [email protected], size: 2803) [annotate] [blame] [check-ins using] [more...]
//go:build !js package libfossil_test // Interop tests for undoing an update (#446): the undo state go-libfossil's // Update saves is fossil's, so `fossil undo` reverses it, and Checkout.Undo // reverses `fossil update`. import ( "os" "path/filepath" "sort" "strings" "testing" libfossil "github.com/danmestas/go-libfossil" ) // undoState is what an undo must restore: the files on disk, what fossil's // changes reports, and the checked-out version. func undoState(t *testing.T, bin, dir string) string { t.Helper() var files []string for name, body := range treeFiles(t, dir) { files = append(files, name+"="+body) } sort.Strings(files) return strings.Join(files, "\n") + "\n" + fossilRun(t, bin, dir, "changes") + fossilRun(t, bin, dir, "sql", "SELECT value FROM vvar WHERE name IN ('checkout','checkout-hash') ORDER BY name") } // TestFossilUndoReversesLibfossilUpdate: go-libfossil updates a checkout // with local edits, a pending add and removal; fossil's undo puts it back. func TestFossilUndoReversesLibfossilUpdate(t *testing.T) { bin, repoPath, a, b := updateEditsRepo(t) ck, goDir, ridOf := goCheckoutAt(t, bin, repoPath, a) localEdits(t, goDir) if _, err := ck.Add([]string{"new.txt"}); err != nil { t.Fatal(err) } if err := ck.Remove([]string{"same.txt"}); err != nil { t.Fatal(err) } if _, err := ck.Status(); err != nil { t.Fatal(err) } before := undoState(t, bin, goDir) if _, err := ck.Update(libfossil.UpdateOpts{TargetRID: ridOf(b)}); err != nil { t.Fatal(err) } // The binary conflict's backup is not undo state, in fossil either. if err := os.Remove(filepath.Join(goDir, "bin.dat-original")); err != nil { t.Fatal(err) } fossilRun(t, bin, goDir, "undo") if got := undoState(t, bin, goDir); got != before { t.Fatalf("after fossil undo:\n%s\nwant, as before the update:\n%s", got, before) } } // TestLibfossilUndoReversesFossilUpdate: fossil updates the same checkout // state; go-libfossil's Undo puts it back. func TestLibfossilUndoReversesFossilUpdate(t *testing.T) { bin, repoPath, a, b := updateEditsRepo(t) dir := t.TempDir() fossilRun(t, bin, dir, "open", repoPath, a) localEdits(t, dir) fossilRun(t, bin, dir, "add", "new.txt") fossilRun(t, bin, dir, "rm", "same.txt") fossilRun(t, bin, dir, "changes") before := undoState(t, bin, dir) fossilRun(t, bin, dir, "update", b) // The binary conflict's backup is not undo state. if err := os.Remove(filepath.Join(dir, "bin.dat-original")); err != nil { t.Fatal(err) } ck := openLibfossilCheckout(t, repoPath, dir) if err := ck.Undo(); err != nil { t.Fatal(err) } if err := ck.Close(); err != nil { t.Fatal(err) } if got := undoState(t, bin, dir); got != before { t.Fatalf("after Undo:\n%s\nwant, as before fossil's update:\n%s", got, before) } }