git: Reflect external stash renames

Zed reads `%s` (commit subject) from `git stash list`, but external
rename tools (lazygit, or `git stash store -m` directly) update only
the reflog message and never rewrite the underlying commit. The stash
picker therefore keeps showing the original message forever after an
external rename, even across restarts — no amount of cache invalidation
or fs-watching can fix that, because the data we ask git for genuinely
never changes.

Switch to `%gs` (reflog subject), which is what `git stash list`
displays by default and what `git stash store -m` actually updates.
Also correct an adjacent error message that claimed `git status failed`
when the failing command was `git stash list`.

Closes #55234
This commit is contained in:
Kunall Banerjee 2026-04-30 03:30:57 -04:00
parent 24f62484e9
commit b56d35f838
No known key found for this signature in database

View file

@ -1759,8 +1759,40 @@ impl GitRepository for RealGitRepository {
self.executor
.spawn(async move {
let git = git_binary?;
// Use `%gs` (reflog subject), not `%s` (commit subject). See
// https://github.com/zed-industries/zed/issues/55234 for further
// context.
//
// A stash is a regular commit object whose tree records the working
// directory state. The list of stashes is not an array — it's the
// reflog of `refs/stash`, where each reflog entry points at a stash
// commit. From git-stash(1):
//
// "The latest stash you created is stored in `refs/stash`; older
// stashes are found in the reflog of this reference and can be
// named using the usual reflog syntax."
// -- https://git-scm.com/docs/git-stash (DESCRIPTION)
//
// `git stash store` is documented as: "Store a given stash created via
// `git stash create` ... in the stash ref, updating the stash reflog."
// Crucially, it does not rewrite the underlying commit object — it
// only adds a reflog entry pointing at the existing commit.
//
// From git's pretty-format docs:
//
// %s subject
// %gs reflog subject
//
// -- https://git-scm.com/docs/pretty-formats
//
// So `%s` would return the ORIGINAL message forever, regardless of how
// many times the stash gets renamed externally — no amount of cache
// invalidation or fs-watching can fix that, because the data we asked
// git for genuinely never changes. `%gs` reads the reflog message that
// `store -m` actually updates, and matches what `git stash list` shows
// by default.
let output = git
.build_command(&["stash", "list", "--pretty=format:%gd%x00%H%x00%ct%x00%s"])
.build_command(&["stash", "list", "--pretty=format:%gd%x00%H%x00%ct%x00%gs"])
.output()
.await?;
if output.status.success() {
@ -1768,7 +1800,7 @@ impl GitRepository for RealGitRepository {
stdout.parse()
} else {
let stderr = String::from_utf8_lossy(&output.stderr);
anyhow::bail!("git status failed: {stderr}");
anyhow::bail!("git stash list failed: {stderr}");
}
})
.boxed()