Subject: Re: [RFC] builtin/stash: data loss from reset --hard

From: Junio C Hamano <[email-masked]>
Date: Mon, 05 Jan 2026 10:47:50 +0900

Tsahi Elkayam <[email-masked]> writes:

> Hi,
>
> I am a beginner C developer exploring the Git codebase and came across
> something I would like to understand better.
>
> In builtin/stash.c line 1747, there is a comment:
>
>     /* BUG: this nukes untracked files in the way */
>     strvec_pushl(&cp.args, "reset", "--hard", "-q",
>                  "--no-recurse-submodules", NULL);
>
> Steps to reproduce:
>
>     $ git init test && cd test
>     $ echo "tracked" > foo && git add foo && git commit -m "init"
>     $ git rm foo
>     $ mkdir foo && echo "precious" > foo/file
>     $ git stash
>     $ cat foo/file
>     cat: foo/file: Not a directory   # precious data is lost

As far as I can see, everything I see in the above is working as
intended.  "stash" is a way to get rid of your work in progress and
take you back to the pristine state quickly (after all, it is
"panic!  the boss is here and tells me to work on something else,
clear the desk real quick now" option), so after "git stash",
whatever change you made relative to the pristine state (that is, a
regular file 'foo' exists and has "tracked" in it) should go away,
if it gets in a way in order to get us back to the pristine state.

After the above sequence, if you "git stash pop", it should get you
back to the state immediately before you did "git stash [push]", but
depending on what you did between push and pop, it is possible that
the changes conflict and "stash pop" may fail without popping the
stash entry.  This is to allow you to attempt to pop it again later
after cleaning up the mess (like, perhaps going back to the state
before you did "stash push" on a new branch).  The point to note
here is that the "push" operation cannot afford to fail at the
"panic!  the boss is here" moment, but at the "stash pop" stage, aka
"the crisis is over, now let's get back to where we were", the user
can afford to see a failure and spend time on conflict resolution.

Another thing to note is that the new foo/file in the above example
is not tracked, and IIUC, "git stash" by default will not save
random untracked cruft found in the working tree.  I wasn't heavily
involved in the design of this optional feature of saving away the
untracked cruft ("git stash push -u"), so I do not offhand know if
the contents of foo/file is saved away correctly in the above
sequence of yours if you changed your "git stash" to "git stash
[push] -u", or if it is recovered when you later say "git stash pop"
(and if it doesn't, then you have found a bug there).  But it does
not change the fact that the "cat" in the above sequence immediately
after "git stash [push]" with or without "-u" should notice that
foo/file is now gone, once you got back to the pristine state.

I do not know what "BUG:" comment refers to in the above.  Without
"-u", getting rid of untracked files that get in the way of going
back to the pristine state is absolutely the right thing to do, so
there is no bug there.  It is possible that it is done way too early
even when "-u" is given, making it impossible to save away such an
untracked files that are in the way, but I didn't check.