{"thread":{"id":"34874","subject":"git stash doesn't always save work dir as-is: bug?","startedAt":"2013-09-08T00:40:25Z","lastAt":"2014-08-28T02:54:26Z","messageCount":2,"participants":["Chris Torek","Dan Lenski"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"227040","messageId":"201309080040.r880ePlr094459@elf.torek.net","threadId":"34874","inReplyTo":null,"subject":"git stash doesn't always save work dir as-is: bug?","fromName":"Chris Torek","fromEmail":"chris.torek@gmail.com","sentAt":"2013-09-08T00:40:25Z","receivedAt":"2013-09-08T00:40:25Z","isPatch":false,"sender":{"key":"chris.torek@gmail.com","avatar":"https://avatars.githubusercontent.com/u/16826774?v=4"},"body":"When \"git stash\" does its work, if the index and the work\ndirectory are out of sync, but the work directory is in sync with\nthe HEAD commit, the work directory commit does not contain the\nfile in its work-directory state, but rather in its index-state.\n\nThis seems wrong.  For instance, \"git stash branch recover\"\ngets you the wrong work-directory state in the new branch.\n\nHere's a simple test-case, its output (before and after ?fix?),\nand a possible fix, although I'm not at all sure this doesn't\nbreak other cases; I don't properly understand the code here.\n(I did not touch the \"$patch_mode\" logic either as it's entirely\ndifferent, and looks correct at first blush.)\n\n(Also, not that I have a patch for this, the man page DISCUSSION\nsection needs to add that there's a third parent holding unstaged\nfiles when doing \"git stash save -u\".)\n\nChris\n\n----snip---- test-stash.sh\n#! /bin/sh\n\nfatal()\n{\n\techo \"$@\" >&2\n\texit 1\n}\n\ncd /tmp || fatal \"can't cd to /tmp\"\nrm -rf test_stash || fatal \"can't remove old test_stash\"\nmkdir test_stash &&\ncd test_stash &&\ngit init -q || fatal \"can't make test_stash\"\n\necho base > basefile &&\ngit add basefile &&\ngit commit -q -m initial || fatal \"can't create initial commit\"\n\necho add to basefile >> basefile &&\ngit add basefile &&\nsed -i .bak -e '2d' basefile || fatal \"can't set up the problem\"\nrm -f basefile.bak\n\necho \"status before stash:\"\ngit status --short\n\ngit stash save || fatal \"stash failed\"\n\necho \"stash created\"\necho \"in the index, basefile contains:\"\ngit show stash^2:basefile | cat -n\necho\necho \"in the WIP, basefile contains:\"\ngit show stash:basefile | cat -n\necho\necho \"in the actual basefile:\"\ncat -n basefile\n----snip---- ./test-stash.sh (with original stash script)\nstatus before stash:\nMM basefile\nSaved working directory and index state WIP on master: c334d9e initial\nHEAD is now at c334d9e initial\nstash created\nin the index, basefile contains:\n     1\tbase\n     2\tadd to basefile\n\nin the WIP, basefile contains:\n     1\tbase\n     2\tadd to basefile\n\nin the actual basefile:\n     1\tbase\n----snip---- ./test-stash.sh (after ?fix?)\nstatus before stash:\nMM basefile\nSaved working directory and index state WIP on master: 3097b8b initial\nHEAD is now at 3097b8b initial\nstash created\nin the index, basefile contains:\n     1\tbase\n     2\tadd to basefile\n\nin the WIP, basefile contains:\n     1\tbase\n\nin the actual basefile:\n     1\tbase\n----snip---- possible fix\ncommit 5288ca30b9425a8c3fd1eb179706275cda3eb717\nAuthor: Chris Torek <chris.torek@gmail.com>\nDate:   Sat Sep 7 18:13:31 2013 -0600\n\n    stash: diff work dir against index\n    \n    When saving a full stash, compare the work directory against the\n    already-saved index tree, rather than the HEAD commit, in case\n    the work tree reverts things back to the HEAD.\n\ndiff --git a/git-stash.sh b/git-stash.sh\nindex 1e541a2..02818ae 100755\n--- a/git-stash.sh\n+++ b/git-stash.sh\n@@ -115,7 +115,7 @@ create_stash () {\n \t\t\tgit read-tree --index-output=\"$TMPindex\" -m $i_tree &&\n \t\t\tGIT_INDEX_FILE=\"$TMPindex\" &&\n \t\t\texport GIT_INDEX_FILE &&\n-\t\t\tgit diff --name-only -z HEAD -- >\"$TMP-stagenames\" &&\n+\t\t\tgit diff --name-only -z $i_tree -- >\"$TMP-stagenames\" &&\n \t\t\tgit update-index -z --add --remove --stdin <\"$TMP-stagenames\" &&\n \t\t\tgit write-tree &&\n \t\t\trm -f \"$TMPindex\"\n"},{"id":"248470","messageId":"loom.20140828T044432-503@post.gmane.org","threadId":"34874","inReplyTo":"201309080040.r880ePlr094459@elf.torek.net","subject":"Re: git stash doesn't always save work dir as-is: bug?","fromName":"Dan Lenski","fromEmail":"dlenski@gmail.com","sentAt":"2014-08-28T02:54:26Z","receivedAt":"2014-08-28T02:54:26Z","isPatch":false,"sender":{"key":"dlenski@gmail.com","avatar":null},"body":"Chris Torek <chris.torek <at> gmail.com> writes:\n\n> \n> When \"git stash\" does its work, if the index and the work\n> directory are out of sync, but the work directory is in sync with\n> the HEAD commit, the work directory commit does not contain the\n> file in its work-directory state, but rather in its index-state.\n\nI too encountered this bug while trying to design a custom pre-commit hook \nthat would speculatively change the working tree and then \"stash pop\" its \nchanges. Chris Torek alerted me to it on StackOverflow: \nhttp://stackoverflow.com/questions/25536034/modifying-working-directory-and-\nstaging-area-temporarily-in-git-pre-commit-hook\n\nAnother small issue I had in solving the same problem: there's no way to \ntell if \"git stash save\" has created a new stash or not, so it's hard to \nfigure out whether one should be popped later.\n\nIt would be helpful if there were a specific non-zero return code assigned \nwhen no stash was generated due to no changes requiring it. Here's a simple \npatch for that:\n\n--- a/git-stash\n+++ b/git-stash\n@@ -220,8 +220,7 @@ save_stash () {\n        git update-index -q --refresh\n        if no_changes\n        then\n-               say \"$(gettext \"No local changes to save\")\"\n-               exit 0\n+               die_with_status 9 \"$(gettext \"No local changes to save\")\"\n        fi\n        test -f \"$GIT_DIR/logs/$ref_stash\" ||\n                clear_stash || die \"$(gettext \"Cannot initialize stash\")\"\n\nThanks,\nDan\n"}]}