Skip to content

cli: hint at recovered changes after workspace update-stale#9786

Open
joshka wants to merge 1 commit into
jj-vcs:mainfrom
joshka:update-stale-recovered-changes-hint
Open

cli: hint at recovered changes after workspace update-stale#9786
joshka wants to merge 1 commit into
jj-vcs:mainfrom
joshka:update-stale-recovered-changes-hint

Conversation

@joshka

@joshka joshka commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Problem

When jj workspace update-stale resets a stale working copy, it first snapshots any un-snapshotted on-disk changes into a commit, then checks out the fresh commit — overwriting those changes on disk. The only output names the fresh commit, so the update looks like data loss even though the changes were preserved and are recoverable:

$ jj workspace update-stale
...
Updated working copy to fresh commit 90f3d42e0bff

This has confused several users (e.g. #7229, and reports on Discord). jj undo doesn't help here either, because update-stale ends on a reconcile/merge operation (Error: Cannot undo a merge operation), so the command's own output is the only reasonable place to surface the recovery path.

Change

When the pre-update snapshot actually preserved un-snapshotted changes, print a hint naming the commit they were saved in and the jj restore to get them back:

Updated working copy to fresh commit 90f3d42e0bff
Hint: Working-copy changes were saved in commit 5f9e41510c33.
Restore them with `jj restore --from 5f9e41510c33`.
  • Only fires when the pre-update snapshot amended the working-copy commit (i.e. there were un-snapshotted changes that got overwritten) — no output in the clean case.
  • The lost-operation recovery path already prints a labeled RECOVERY COMMIT FROM ... explanation; this gives the far more common normal path the same legibility.

Tests

  • New test_workspaces_update_stale_recovers_changes: makes a workspace stale with an un-snapshotted edit, runs jj workspace update-stale, asserts the hint, then runs the exact jj restore --from <hinted commit> and asserts the edit is recovered — a full round-trip.
  • Updated the two existing snapshots that hit this path (explicit update-stale, and the auto-update-stale case). The clean-stale and "attempted recovery, but not stale" tests are unchanged, pinning the gating.
  • Full jj-cli suite passes; cargo fmt clean.

Addresses the confusion reported in #7229.

@joshka
joshka requested a review from a team as a code owner July 8, 2026 12:39

@PhilipMetzger PhilipMetzger left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LG with minor nits. I guess Joseph will also chime in with some wording fixes.

Comment thread cli/src/cli_util.rs Outdated
Comment on lines +663 to +664
"Uncommitted changes were saved in commit {} before the working \
copy was updated.\nRestore them with `jj restore --from {}`.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: indoc seems useful here instead of manually inserting newlines and stuff.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — now uses writedoc! with an inline {stale_commit} capture, so there are no manual \ns or \ line-continuations.

Comment thread CHANGELOG.md Outdated
Comment on lines +22 to +25
* When `jj workspace update-stale` resets a working copy that had un-snapshotted
changes, it now prints a hint naming the commit those changes were saved in and
the `jj restore` command to recover them, so the update no longer looks like
data loss. Disable with `hints.recovered-changes = false`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: This is extremely wordy for no good reason. Also this is missing a Issue link.

"When jj workspace update-stale resets a working copy with unsnapshotted changes it now provides a hint on how to recover these changes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trimmed to one line and added the #7229 link.

Comment thread cli/src/config/hints.toml Outdated
@@ -1,2 +1,3 @@
[hints]
resolving-conflicts = true
recovered-changes = true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I don't think we should add this option to this table until people complain.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed — reverted hints.toml.

Comment thread cli/src/cli_util.rs Outdated
// overwrote them on disk. Point the user at that commit so the
// update doesn't look like data loss.
if recovered_local_changes
&& self.settings().get_bool("hints.recovered-changes")?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto, its to early to provide an option for that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropped the config gate; the hint is unconditional now (shown only when the recovery actually preserved changes).

@PhilipMetzger

Copy link
Copy Markdown
Contributor

I also think this addresses the mentioned issue.

@joshka
joshka force-pushed the update-stale-recovered-changes-hint branch 4 times, most recently from fc94b49 to 6d3008a Compare July 13, 2026 00:13
@joshka

joshka commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review! Addressed all four nits in the latest push:

Also took a small pass on the hint wording and the code comment, and rebased onto current main (picking up the trailing-period logging change, which is why CI re-ran). Happy to adjust the wording further based on Joseph's suggestions.

@joshka

joshka commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

A broader thought this message surfaces — not all of which belongs in the hint itself, but it frames why the wording matters here.

This fires at a genuinely high-stress moment. update-stale rewrites files in the working directory, so from the user's side, source they were editing just changed or disappeared — which reads as data loss on its own. It's compounded by a second hazard: those on-disk rewrites can collide with unsaved editor state — a buffer with newer edits, or an editor showing a file that was rewritten underneath it. So there are several avenues for confusion and recovery, not just one.

The user is also pushed down this path fairly blind. The upstream stale error —

Error: The working copy is stale (not updated since operation 17d05d75464f).
Hint: Run `jj workspace update-stale` to update it.

— doesn't mention that running update-stale will modify files in the working copy, so it's run without knowing on-disk edits are about to be touched. And update-stale's own output —

Rebased 1 descendant commits onto commits rewritten by other operation
Working copy  (@) now at: ypwkwnqk/2 e881a488 (divergent) (empty) ...
Added 1 files, modified 0 files, removed 1 files
Updated working copy to fresh commit e881a488dad2

— is cryptic about what became of the user's actual source (the (divergent) (empty), the file counts). This PR's hint is one small step, but the whole flow (stale error → update-stale output) is really an opportunity to teach how this affects the user's working copy.

Given that, this feels like one of the few jj messages where a reassurance-first phrasing is worth considering, e.g.:

Hint: Your working-copy changes weren't lost — they were saved in commit e881a488.
Restore them with `jj restore --from e881a488`.

Happy to keep the terse version for house-style consistency — mostly flagging that the emotional context here is unusually high for a hint, and that the surrounding flow (especially the stale error not warning that files will change) might merit its own follow-up. Curious what you and @josephlou5 think.

@josephlou5

Copy link
Copy Markdown
Contributor

Haha funny to see that I was mentioned for wording :p Seems mostly fine to me, though I have never used jj workspace commands so the use cases and the desired information at such times are a little fuzzy to me.

Also the commit description (and PR description too) are outdated after recent changes I think.

`jj workspace update-stale` snapshots any un-snapshotted changes in the stale
working copy before it checks out the fresh commit and overwrites them on
disk. Since the only output named the fresh commit, the update looked like
data loss even though the changes were preserved and recoverable.

When the pre-update snapshot preserved such changes, print a hint naming the
commit they were saved in and the `jj restore --from <commit>` to recover
them. The lost-operation recovery path already prints a similar explanation;
this gives the far more common path the same legibility.

Addresses jj-vcs#7229.
@joshka
joshka force-pushed the update-stale-recovered-changes-hint branch from 6d3008a to d585f17 Compare July 13, 2026 01:49
@joshka

joshka commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

Good catch, thanks — updated both the commit and PR descriptions to drop the config-gate mention (removed in the earlier round) and match the current wording.

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.

3 participants