Skip to content

Crash-safety hardening, new history commands, and code-health overhaul#2

Merged
javanhut merged 3 commits into
mainfrom
ivaldi_bug_fix
Jun 10, 2026
Merged

Crash-safety hardening, new history commands, and code-health overhaul#2
javanhut merged 3 commits into
mainfrom
ivaldi_bug_fix

Conversation

@javanhut

Copy link
Copy Markdown
Owner

This branch hardens ivaldi's core engine against crashes and concurrent use, adds the missing day-to-day history commands under ivaldi's own vocabulary, and clears the codebase's lint/CI debt. 83 files changed; 694 tests passing (up from 622), clippy -D warnings clean, CI added.

Crash safety & data integrity

A VCS must not lose data. This branch closes every known window where a crash or a second ivaldi process could corrupt repository state:

  • Atomic commits — Repo::commit() now lands all store writes (leaf, timeline head, seal name, MMR size) in a single redb transaction instead of four. A crash mid-seal can no longer leave a leaf without its head pointer.
  • Atomic metadata writes — new atomic_io::atomic_write (temp file → fsync → rename) backs every metadata file under .ivaldi/: staging area, HEAD, shelves, merge state, reviews, dotfile allowlist, and config. Readers see old or new contents, never a truncated file.
  • CAS durability — FileCas tracks dirty shards and flush() fsyncs only those; flushes happen at the points where the CAS holds the only copy of user data (sealing, shelf capture on timeline switch, bulk imports).
  • Process-level repo lock — mutating commands take an exclusive flock(2) on .ivaldi/repo.lock. A second concurrent process gets a clear "another ivaldi process is operating on this repository" message instead of interleaved corruption; the kernel releases the lock even on crash. redb's "database already open" error is also mapped to a friendly message.
  • Crash-recoverable timeline switches — the multi-step switch sequence (shelve → HEAD → materialize → restore) is journaled to .ivaldi/SWITCH_IN_PROGRESS. An interrupted switch can be completed or rolled back by re-running timeline switch; mutating commands refuse to run over an unresolved transition.

New commands (ivaldi vocabulary, not git's)

  • ivaldi reseal [msg] — redo the most recent seal, folding in staged changes and/or a new message. Warns when the seal was already uploaded.
  • ivaldi undo — new seal that removes an earlier seal's changes (three-way fuse; refuses with the conflicting paths listed rather than guessing).
  • ivaldi pluck — new seal that applies another seal's changes (alias: cherry-pick); detects already-applied no-ops.
  • ivaldi rewind [--discard] — move the timeline head back; files stay untouched unless --discard is passed. Orphaned seals remain recoverable via travel --all.
  • ivaldi gather -p/--patch — interactive hunk staging (y/n/a/d/q per hunk); selected hunks become a synthetic blob in the CAS, the working tree is never touched.
  • ivaldi completions and man-page generation, installed via make install-extras.
  • --json output on status, timeline list, portal list, plus log --format short|medium|full|json for scripting and CI.
  • Progress feedback on gather (spinner during scan, progress bar while hashing).

Config UX

  • The interactive form (ivaldi config / new alias configure) gains a scope selector — toggle repo-local ↔ global in the form itself; it reloads from and saves to whichever file is selected.
  • config --help now documents every known key with descriptions and examples; --set validates per key (email shape, true/false toggles, repo specs), errors on dotless keys (which previously silently vanished on save), and warns on unknown keys while still saving them.

Bug fixes

  • weld range validation contained an always-false condition (x == y && x != y) that made one reachability check dead code.
  • resolve_seal silently fell through on ambiguous seal-name prefixes; it now errors listing the candidates.
  • Nonexistent pathspecs to gather are now reported instead of silently skipped.
  • Truncated pack/index data could panic or silently yield zero entries; it now returns proper Corrupt errors.

Code health

  • CI (.github/workflows/ci.yml): fmt, clippy -D warnings, and tests on ubuntu + macos.
  • Clippy to zero (~100 warnings cleared) and a cargo fmt baseline.
  • Typed errors in sync: SyncError gained #[from] variants; 55 stringly Other(e.to_string()) sites removed; swallowed ref-write failures now abort the sync instead of producing invisible timelines.
  • sync.rs (2,900 lines) split into sync/{mod,upload,import,timeline_sync}.rs with no public API change.
  • OAuth dedup: GitHub/GitLab device flows share oauth_device.rs (RFC 8628 slow-down handling; GitHub gains distinct expired/denied errors).
  • Panics on untrusted network/pack input replaced with error returns; consistent red error: prefix on Cts help properly.

Docs

README, docs/cli.md, and docs/rosetta.md updated for all new commands; new module docs for atomic_io, luth_device; new user guide docs/undo.md; key reference in docs/config.md; dotfile/security-block behavior documented in docs/ignore.md.

Testing

694 unit tests (+72), including crash-recovery scenarios (interrupted-switch resume/rollback), scripted interactive patch sessions, lock contention, truncated-file tolerance, and config validation. Manually smoke-tested end-to-end: seal → reseal → undo → pluck → rewind → gather --patch → JSON output → concurrent-process locking.

javanhut added 3 commits June 9, 2026 00:38
Bug 1 — TUI empty merge base (src/tui/views/fuse.rs)
do_fuse now resolves both head indices via get_timeline_head, computes the real LCA with repo.merge_base, and loads that tree as the base (empty only for genuinely unrelated histories) — mirroring the CLI. No more spurious conflicts on unrelated changes.

Bug 2A — union truly concatenates (src/fuse.rs, callers, docs/fuse.md)
Added a shared concat_blobs primitive and a MergeDecision::Concat variant. The two genuine-conflict arms of merge_file_union now combine ours-then-theirs bytes instead of silently dropping a side; fuse() takes a &FsStore (infallible, falls back to theirs only on CAS error). All five callers (CLI, TUI, review, sync, tests) updated; docs corrected.

Bug 2B — TUI resolver wired up (src/tui/resolver.rs, src/tui/views/fuse.rs)
Slimmed resolver.rs to shared types and converted the resolver into a view-owned modal (matching the rest of the TUI) rather than a nested terminal sub-loop. On conflict, do_fuse opens the modal; apply_resolutions builds the final tree with Ours/Theirs/Both(→concat)/Skip semantics. Skip leaves the file unresolved and does not commit — no silent side-picking.

Bug 3 — file-mode fidelity (src/fsmerkle.rs, src/git_remote.rs, src/git_export.rs, src/workspace.rs)
Added MODE_EXEC/MODE_SYMLINK, relaxed validation, fixed git import/export to map 100644/100755/120000 faithfully (restoring byte-exact tree SHA-1), and made materialize create real symlinks + set the exec bit (#[cfg(unix)]). No tree-hash format change — the format already encoded entry.mode.
@javanhut javanhut merged commit 1497da6 into main Jun 10, 2026
4 checks passed
@javanhut javanhut deleted the ivaldi_bug_fix branch June 16, 2026 15:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant