{"thread":{"id":"64719","subject":"[RFC] builtin/stash: data loss from reset --hard","startedAt":"2026-01-04T11:05:05Z","lastAt":"2026-01-05T01:47:53Z","messageCount":2,"participants":["Tsahi Elkayam","Junio C Hamano"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"532987","messageId":"-98ze4v1cX5P2d_tlWY6nBZuQhY3J7OJLJX51VS53bVhirt-Gm9zA6E_Y-pNMMYhtcLN2MM_miuPfR_Nrq5JCUWDgI_BwG9rUxtuBoqf8h0=@protonmail.com","threadId":"64719","inReplyTo":null,"subject":"[RFC] builtin/stash: data loss from reset --hard","fromName":"Tsahi Elkayam","fromEmail":"tsahi.elkayam@protonmail.com","sentAt":"2026-01-04T11:05:01Z","receivedAt":"2026-01-04T11:05:05Z","isPatch":false,"sender":{"key":"tsahi.elkayam@protonmail.com","avatar":null},"body":"Hi,\n\nI am a beginner C developer exploring the Git codebase and came across\nsomething I would like to understand better.\n\nIn builtin/stash.c line 1747, there is a comment:\n\n    /* BUG: this nukes untracked files in the way */\n    strvec_pushl(&cp.args, \"reset\", \"--hard\", \"-q\",\n                 \"--no-recurse-submodules\", NULL);\n\nSteps to reproduce:\n\n    $ git init test && cd test\n    $ echo \"tracked\" > foo && git add foo && git commit -m \"init\"\n    $ git rm foo\n    $ mkdir foo && echo \"precious\" > foo/file\n    $ git stash\n    $ cat foo/file\n    cat: foo/file: Not a directory   # precious data is lost\n\nThe reset --hard restores the original tracked file \"foo\" from HEAD,\ndestroying the untracked directory \"foo/\" and its contents.\n\nThere is also a test_expect_failure test in t/t2500-untracked-overwriting.sh\nthat documents this behavior.\n\nI am not sure if this is considered a bug to be fixed, or intentional\nbehavior that is simply documented.\n\nIf it is a bug, would this fix be reasonable:\n\n-       /* BUG: this nukes untracked files in the way */\n-       strvec_pushl(&cp.args, \"reset\", \"--hard\", \"-q\",\n+       strvec_pushl(&cp.args, \"reset\", \"--merge\", \"-q\",\n                     \"--no-recurse-submodules\", NULL);\n\nI understand --merge would fail instead of silently overwriting,\nwhich seems safer.\n\nI would appreciate any feedback or guidance.\n\nThanks,\nTsahi\n\n\n\nSent with Proton Mail secure email.\n"},{"id":"533005","messageId":"xmqqms2sn83d.fsf@gitster.g","threadId":"64719","inReplyTo":"-98ze4v1cX5P2d_tlWY6nBZuQhY3J7OJLJX51VS53bVhirt-Gm9zA6E_Y-pNMMYhtcLN2MM_miuPfR_Nrq5JCUWDgI_BwG9rUxtuBoqf8h0=@protonmail.com","subject":"Re: [RFC] builtin/stash: data loss from reset --hard","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-01-05T01:47:50Z","receivedAt":"2026-01-05T01:47:53Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Tsahi Elkayam <Tsahi.Elkayam@protonmail.com> writes:\n\n> Hi,\n>\n> I am a beginner C developer exploring the Git codebase and came across\n> something I would like to understand better.\n>\n> In builtin/stash.c line 1747, there is a comment:\n>\n>     /* BUG: this nukes untracked files in the way */\n>     strvec_pushl(&cp.args, \"reset\", \"--hard\", \"-q\",\n>                  \"--no-recurse-submodules\", NULL);\n>\n> Steps to reproduce:\n>\n>     $ git init test && cd test\n>     $ echo \"tracked\" > foo && git add foo && git commit -m \"init\"\n>     $ git rm foo\n>     $ mkdir foo && echo \"precious\" > foo/file\n>     $ git stash\n>     $ cat foo/file\n>     cat: foo/file: Not a directory   # precious data is lost\n\nAs far as I can see, everything I see in the above is working as\nintended.  \"stash\" is a way to get rid of your work in progress and\ntake you back to the pristine state quickly (after all, it is\n\"panic!  the boss is here and tells me to work on something else,\nclear the desk real quick now\" option), so after \"git stash\",\nwhatever change you made relative to the pristine state (that is, a\nregular file 'foo' exists and has \"tracked\" in it) should go away,\nif it gets in a way in order to get us back to the pristine state.\n\nAfter the above sequence, if you \"git stash pop\", it should get you\nback to the state immediately before you did \"git stash [push]\", but\ndepending on what you did between push and pop, it is possible that\nthe changes conflict and \"stash pop\" may fail without popping the\nstash entry.  This is to allow you to attempt to pop it again later\nafter cleaning up the mess (like, perhaps going back to the state\nbefore you did \"stash push\" on a new branch).  The point to note\nhere is that the \"push\" operation cannot afford to fail at the\n\"panic!  the boss is here\" moment, but at the \"stash pop\" stage, aka\n\"the crisis is over, now let's get back to where we were\", the user\ncan afford to see a failure and spend time on conflict resolution.\n\nAnother thing to note is that the new foo/file in the above example\nis not tracked, and IIUC, \"git stash\" by default will not save\nrandom untracked cruft found in the working tree.  I wasn't heavily\ninvolved in the design of this optional feature of saving away the\nuntracked cruft (\"git stash push -u\"), so I do not offhand know if\nthe contents of foo/file is saved away correctly in the above\nsequence of yours if you changed your \"git stash\" to \"git stash\n[push] -u\", or if it is recovered when you later say \"git stash pop\"\n(and if it doesn't, then you have found a bug there).  But it does\nnot change the fact that the \"cat\" in the above sequence immediately\nafter \"git stash [push]\" with or without \"-u\" should notice that\nfoo/file is now gone, once you got back to the pristine state.\n\nI do not know what \"BUG:\" comment refers to in the above.  Without\n\"-u\", getting rid of untracked files that get in the way of going\nback to the pristine state is absolutely the right thing to do, so\nthere is no bug there.  It is possible that it is done way too early\neven when \"-u\" is given, making it impossible to save away such an\nuntracked files that are in the way, but I didn't check.\n\n"}]}