Skip to content

fix(update): handle NoLocalChanges on auto-stash false positive#68

Merged
mxaddict merged 2 commits into
kryptic-sh:mainfrom
junjitree:fix/stash-no-local-changes
Jun 22, 2026
Merged

fix(update): handle NoLocalChanges on auto-stash false positive#68
mxaddict merged 2 commits into
kryptic-sh:mainfrom
junjitree:fix/stash-no-local-changes

Conversation

@junjitree

@junjitree junjitree commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Problem

krypt update fails with:

error: auto-stash push failed: no local changes to save

when the working tree is actually clean.

Root cause: gix::repository::is_dirty() can return true due to
stat-cache staleness (file timestamps differ from what git recorded, but
file content is unchanged). When gix::stash::push then compares the
actual tree contents it finds nothing to stash and returns
PushError::NoLocalChanges. krypt propagates this as an error instead of
treating it as "nothing to do".

Reproduced by: pulling/merging manually, then running krypt update on an
already-clean repo.

Fix

In gix_ff_pull, match PushError::NoLocalChanges and treat it as a
clean-tree signal — skip the stash and continue normally.

A separate commit fixes a pre-existing sha2 = "0.11" API break in
manifest.rs where digest.finalize() output no longer implements
LowerHex. Formats the bytes manually instead. Without this fix the crate
does not compile, so the two commits are bundled in one PR.

Test plan

  • cargo test -p krypt-core passes (238 tests) — also verified cargo build + cargo clippy clean
  • krypt update on a clean repo no longer errors
  • krypt update on a dirty repo still auto-stashes correctly

Note: the two runtime boxes are covered by manual repro only. The
NoLocalChanges skip path can't be unit-tested without inducing a gix
stat-cache false positive, so it isn't exercised by the 238 automated
tests. Confirmed manually against the repo that originally hit the error.

@junjitree junjitree requested a review from mxaddict as a code owner June 22, 2026 06:51
@mxaddict

Copy link
Copy Markdown
Collaborator

Reviewed @junjitree — checked out the head, built, tested, and ran clippy: cargo build -p krypt-core ✅, cargo test -p krypt-core → 238 passed ✅, cargo clippy clean ✅. Both fixes look correct:

  • update.rs — the NoLocalChanges match arm is sound. StashPush(Box<PushError>)**e derefs to the enum, and on the false-positive path stashed/stash_oid stay unset so the later stash_pop correctly no-ops. The clean-tree invariant the checkout relies on still holds (either we stashed, or the tree truly had no changes). It narrowly swallows only this variant — all other errors still propagate. 👍
  • manifest.rs:235 is the only finalize()/{:x} site in the crate, so the scope is right, and the output is byte-identical to the old {:x} (verified by the passing hash_file_matches_known_vector).

A few non-blocking points to address before merge:

  1. No test for the new behavior. The 238 are all pre-existing — nothing exercises the NoLocalChanges skip path, which is the one thing this PR changes. It's genuinely hard to unit-test (needs a gix stash + a stat-cache false positive), so I wouldn't block on it, but could you either tick the test-plan boxes with what you actually ran manually, or add a note that the path is covered only by manual repro? Right now the checkboxes are unticked.
  2. Two unrelated fixes in one commit. The stash handling and the sha2 0.11 compile fix are orthogonal. Bundling is defensible (the crate won't compile without the sha2 fix, so you can't test the stash fix otherwise), but splitting into two commits would make the history cleaner.
  3. Commit author identity is Your Name <you@example.com>. Worth fixing (git commit --amend --reset-author with your configured identity) so it doesn't land in history.
  4. Micro-nit (ignore freely): the hex .map(|b| format!(...)).collect() allocates a String per byte; a write! into a pre-sized buffer avoids it. Completely irrelevant at 32 bytes.

Nice catch on the stat-cache false positive — that's a subtle one.

sha2 0.11 digest output no longer implements LowerHex, so format the hash bytes manually instead of using {:x}.
gix::repository::is_dirty() can return true due to stat-cache staleness
even when no files have actually changed. When gix::stash::push then
compares tree contents it finds nothing to stash and returns
PushError::NoLocalChanges, causing krypt update to bail with
"auto-stash push failed: no local changes to save".

Treat NoLocalChanges as a clean-tree signal and skip the stash instead
of propagating the error.
@junjitree junjitree force-pushed the fix/stash-no-local-changes branch from 738fe56 to 20c97f8 Compare June 22, 2026 07:40
@junjitree

Copy link
Copy Markdown
Contributor Author

Hark, good @mxaddict, and accept mine humble thanks for thy most thorough scrutiny. Thy counsel hath been heeded in full:

  1. Of the absent test — verily 'tis a vexing path to exercise, for it demandeth a gix stash conspiring with a stat-cache that speaketh falsely. I have ticked the box for cargo test -p krypt-core (238 passeth, and the build and clippy stand clean besides), and set down a plain note that the two runtime boxes are proven by manual repro alone against the very repo that first wept the error.

  2. Of two fixes bound as one — I have cloven the commit in twain: first fix(manifest): adapt to sha2 0.11 LowerHex removal, then fix(update): handle NoLocalChanges on auto-stash false positive. They remain wed within this single pull, for the crate compileth not without the former.

  3. Of the false author Your Name <you@example.com> — a pox upon that stray identity; 'twas reset to mine own true name, and both commits now bear it rightly.

  4. Of the per-byte allocation — I leave it undisturbed, as thou didst graciously permit.

The branch is freshly force-pushed (with lease). I remain in thy debt for the sharp eye upon the stat-cache deceiver.

@mxaddict

Copy link
Copy Markdown
Collaborator

Hark, @junjitree! Thy patch I have once more beheld, and lo — it standeth true. ✅

The commits thou hast cloven in twain, each unto its own purpose, and thy name now sitteth rightly upon the ledger of history. The branch I did fetch, build, and put to trial: eight-and-thirty above two hundred tests didst pass, and clippy spake not a word of complaint.

The arm that catcheth NoLocalChanges is wrought soundly — it swalloweth only that false alarm of the stat-cache and letteth all other woes pass through unto the caller, as is meet. The hash, hand-wrought in hex since sha2 did forsake its LowerHex, yieldeth bytes identical to the days of old.

Naught remaineth save the manual proving of krypt update upon clean and dirty trees alike. That trial done, this work is fit to be joined unto the trunk.

Right well done, good sir. Ship it when thou art ready. 🚢

@mxaddict mxaddict merged commit a24a17b into kryptic-sh:main Jun 22, 2026
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.

2 participants