Git development
 help / color / mirror / Atom feed
* [BUG] stash.index=true leaves a redundant stash entry after an autostash fast-forward
@ 2026-09-07  4:44 Eli Barzilay
  2026-09-15 21:16 ` D. Ben Knoble
  0 siblings, 1 reply; 12+ messages in thread
From: Eli Barzilay @ 2026-09-07  4:44 UTC (permalink / raw)
  To: git

Disclaimer, the following is written by an agent, but the bug is a
real problem that I have.


With stash.index=true, an autostash that is applied successfully is
nevertheless stored as a stash entry, and deleting MERGE_AUTOSTASH
fails.  A staged change at the time of the merge is required to
trigger it.

Reproduction (independent of the reporter's configuration):

    #!/bin/sh
    set -e
    export GIT_AUTHOR_NAME=A GIT_AUTHOR_EMAIL=a@b \
           GIT_COMMITTER_NAME=A GIT_COMMITTER_EMAIL=a@b
    rm -rf /tmp/gitbug && mkdir /tmp/gitbug && cd /tmp/gitbug
    git init -q -b main up
    cd up && echo a >u && echo z >z && git add . &&
        git commit -qm base && cd ..
    git clone -q up dn
    cd up && echo more >>u && git commit -qam up2 && cd ../dn
    echo staged >>z && git add z          # a STAGED change is required
    git fetch -q origin
    git -c stash.index=true merge --ff-only --autostash origin/main
    echo "--- git stash list:"; git stash list

Actual output:

    Updating 34a5e40..84ccd9d
    Created autostash: 71d4617
    Fast-forward
     u | 1 +
     1 file changed, 1 insertion(+)
    Applied autostash.
    error: cannot lock ref 'MERGE_AUTOSTASH': unable to resolve
reference 'MERGE_AUTOSTASH'
    --- git stash list:
    stash@{0}: autostash

Expected: the same without the error and with an empty stash list, as
happens with stash.index=false (the only change to the script).

The autostash is applied correctly, so nothing is lost: the leftover
entry duplicates what is already in the working tree and index.  The
command also exits 0, so the error is easy to miss, and the entries
accumulate one per merge.

Reported via `git merge` above for brevity, but the common way to meet
this is `git pull` with rebase.autoStash and pull.rebase set: when the
pull can fast-forward, builtin/pull.c:1164 hands off to
`git merge --ff-only --autostash` rather than to rebase.  Every such
pull with something staged leaves an entry behind.

Analysis
--------

Merge keeps its autostash in the MERGE_AUTOSTASH ref
(builtin/merge.c:1675) and applies it from finish()
(builtin/merge.c:540).  apply_save_autostash_ref() resolves the ref,
applies it, and then deletes it (sequencer.c:4821-4848).

The apply is a child process, `git stash apply <oid>`
(sequencer.c:4737-4751).  stash.index turns that into an --index
apply, which takes the index-restoring branch of do_apply_stash() and
calls reset_head() (builtin/stash.c:684-691), i.e. a
`git reset --quiet --refresh` child (builtin/stash.c:455-467).

That reset has no pathspec, so it calls remove_branch_state()
(builtin/reset.c:543) -> remove_merge_branch_state()
(branch.c:829-838), whose last statement is

    save_autostash_ref(r, "MERGE_AUTOSTASH");

which stores the autostash into refs/stash and deletes the ref -- in
the middle of the very apply that was about to consume it.  Control
returns to apply_save_autostash_ref(), the apply reports success
("Applied autostash."), and its refs_delete_ref() then fails on a ref
that is already gone, producing the error line.

The child's own explanation, "Autostash exists; creating a new stash
entry." (sequencer.c:4775-4779), never reaches the user because the
parent mutes the apply child's output (sequencer.c:4736-4738).

A staged change is required because with a clean index the stash's
base and index trees are equal, has_index is cleared
(builtin/stash.c:665-668), and reset_head() is never reached.

The rebase backend is unaffected: it keeps its autostash in the file
.git/rebase-merge/autostash, which remove_merge_branch_state() does
not touch.  Only the merge (ref-based) autostash is exposed.

Possible directions, in case they are useful: remove_merge_branch_state()
is about ending a merge, and `git stash apply --index` is not ending
one -- having stash's reset_head() avoid the branch-state cleanup, or
teaching an in-flight autostash apply to shield MERGE_AUTOSTASH, would
both close it.  Making apply_save_autostash_ref() tolerate a missing
ref would silence the error but leave the duplicate entry.

Versions
--------

Seen with git 2.55.0 on Linux (WSL2).  The code paths above are
unchanged on master as of 2026-09-07.

-- 
                 ((x=>x(x))(x=>x(x)))                  Eli Barzilay:
                 http://barzilay.org/                  Maze is Life!

^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2026-09-19 21:32 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-07  4:44 [BUG] stash.index=true leaves a redundant stash entry after an autostash fast-forward Eli Barzilay
2026-09-15 21:16 ` D. Ben Knoble
2026-09-16 13:35   ` Phillip Wood
2026-09-16 14:30     ` Ben Knoble
2026-09-16 19:57       ` Eli Barzilay
2026-09-17 12:04         ` D. Ben Knoble
2026-09-17  9:24       ` Phillip Wood
2026-09-17 13:15         ` D. Ben Knoble
2026-09-17 15:55           ` Phillip Wood
2026-09-19 15:22             ` Phillip Wood
2026-09-19 20:02             ` D. Ben Knoble
2026-09-19 21:32           ` D. Ben Knoble

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox