{"thread":{"id":"42415","subject":"Re: Git reset --hard with staged changes","startedAt":"2016-05-23T00:55:55Z","lastAt":"2016-05-31T06:41:34Z","messageCount":8,"participants":["Yotam Gingold","Junio C Hamano","Christian Couder"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"287217","messageId":"loom.20160523T023140-975@post.gmane.org","threadId":"42415","inReplyTo":null,"subject":"Re: Git reset --hard with staged changes","fromName":"Yotam Gingold","fromEmail":"yotam@yotamgingold.com","sentAt":"2016-05-23T00:55:55Z","receivedAt":"2016-05-23T00:55:55Z","isPatch":false,"sender":{"key":"yotam@yotamgingold.com","avatar":null},"body":"Pierre-François CLEMENT <likeyn <at> gmail.com> writes:\n> 2014-06-10 17:27 GMT+02:00 David Kastrup <dak <at> gnu.org>:\n>> Pierre-François CLEMENT <likeyn <at> gmail.com> writes:\n>>\n>>> ...\n>>>\n>>> Hm I see. Even though the documentation doesn't make it very clear\n>>> about what happens to such files, it turns out the scenario we\n>>> stumbled upon seems to be the special use case after all. Thanks for\n>>> shedding some light on this :) I wonder why does git-reset's hard mode\n>>> not always remove untracked files then?\n>>\n>> Because it never removes them?  Git only removes files once it tracks\n>> them.  This includes the operation of removing _and_ untracking them,\n>> like with git reset --hard.\n>>\n>> The only command which explicitly messes with untracked files is\n>> git-clean.\n>>\n>> --\n>> David Kastrup\n> \n> ... I couldn't find a definition that backs this in the man\n> pages (maybe the git-glossary would be a good place for it?), and the\n> one from the Git-Scm book only confused me in thinking the opposite.\n> Thanks for the clarification\n> \n> --\n> Pierre-François CLEMENT\n> Application developer at Upcast Social\n\nJumping into this conversation two years later*. There's confusion about what\nconstitutes a tracked file for git reset --hard, and good reasons for git reset\n--hard's behavior. Nevertheless, I think we can all agree that the man page\nentry for git reset --hard is woefully deficient:\n\n--hard Resets the index and working tree. Any changes to tracked files in the\nworking tree since <commit> are discarded.\n\nThis should be clarified to define what a tracked file is. I propose appending:\n\n    A file is considered tracked if it exists in a prior commit or in the\n    staging area. Note that a newly added file not in any prior commit will be\n    removed.\n\nI would also like to propose that the staging area's tree object be saved,\nperhaps in the reflog or perhaps just as a dangling object. This would allow\ngraceful recovery from git reset --hard. Witness the many questions and answers\non recovery:\n    http://stackoverflow.com/questions/7374069/undo-git-reset-hard-with-uncommitted-files-in-the-\nstaging-area\n    http://stackoverflow.com/questions/5788037/recover-from-git-reset-hard\n    http://stackoverflow.com/questions/5473/how-can-i-undo-git-reset-hard-head1\n    http://gitready.com/advanced/2009/01/17/restoring-lost-commits.html\n    https://bani.com.br/2014/10/recovering-lost-files-after-a-git-reset-hard/\n    https://medium.com/@CarrieGuss/how-to-recover-from-a-git-hard-reset-b830b5e3f60c\n\nAll of these solutions recover the contents of files, but not their names or the\ndirectory structure. Saving the tree object somewhere (anywhere!) would solve\nthis problem.\n\nI was bitten by this in a vicious way. I was setting up a new repository for a\nbunch of code and data (git init; git add .), changed my mind about adding the\ndata (git reset --hard), and nearly lost everything. The only tree object that\ncould be found was an empty one, so I got file contents without names or\ndirectories (not good, because experimental conditions for the data were encoded\nin the directory structure).\n\nCheers,\nYotam"},{"id":"287259","messageId":"xmqqr3csaenj.fsf@gitster.mtv.corp.google.com","threadId":"42415","inReplyTo":"loom.20160523T023140-975@post.gmane.org","subject":"Re: Git reset --hard with staged changes","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-05-23T17:05:20Z","receivedAt":"2016-05-23T17:05:20Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Yotam Gingold <yotam@yotamgingold.com> writes:\n\n> --hard Resets the index and working tree. Any changes to tracked files in the\n> working tree since <commit> are discarded.\n>\n> This should be clarified to define what a tracked file is.\n\nA \"tracked file\" in that sentence is a file that is not untracked, I\nthink.\n\nThere are only four cases, so let's enumerate:\n\n * A path that is in HEAD but not in the index.  \"reset --hard\"\n   wants to make the resulting index match what is in HEAD, so\n   the path is added back to the index.  \"reset --hard\" also wants\n   to make the resulting working tree match what is in the index, so\n   the path in the working tree will be overwritten (or created, if\n   you removed it earlier) with the contents taken from HEAD.\n\n * A path that is in HEAD and also in the index.  \"reset --hard\"\n   wants to make the resulting index match what is in HEAD, and it\n   also wants to make the resulting working tree match what is in\n   the index, so any changes to the index and the working tree will\n   be overwritten by the contents taken from HEAD.\n\n * A path that is not in HEAD but in the index.  The path is removed\n   from the index and from the working tree, due to the same logic\n   as the previous two.\n\n * A path that is neither in HEAD nor in the index.  Nothing\n   happens.  This is the \"untracked files\" case.\n\nThe third case may smell that Git is discarding more than it needs\nto, and if we were designing Git without any existing users from\nscratch today, we might have chosen to remove it from the index and\nmake the working tree copy simply \"untracked\", but we already have\nmore than a dozen users that rely on the current behaviour, so such\na change is not likely to happen.\n\nThe biggest use of the third case is to abort a failed merge and to\nrestart it from scratch.  A path that is not in your branch that the\nother branch created since the two branches forked will be added to\nthe index and to the working tree.  You see conflicts in other paths\n(where both branches made changes in different and incompatible\nways), and after looking at it trying to resolve them, realize that\nyou made a mess and you want to start from scratch, i.e. you run\n\"reset --hard\" followed by the same \"git merge\".\n\nIf \"reset --hard\" does not remove the \"new\" file from the index and\nthe working tree, the next \"git merge\" will think that the untracked\nfile is something you may have created, and would refuse to run, so\nthat it won't lose information.\n\nAs \"reset --hard\" is an operation that is run by the user to discard\nthe changes, it is understood to deliberately lose information, so\nmaking the third one to remove the path from the working tree\n(instead of making it untracked) is a better solution than forcing\nthe user in the \"reset --hard && merge\" scenario to see a merge that\ndoes not even start (as opposed to \"an attempted merge that resulted\nin conflicts) and to manually remove these untracked files that the\nuser did not even create herself.\n"},{"id":"287299","messageId":"CAP8UFD0dQGmfhPuHjEGRZjEZHwUHR_XzAASwq+87Obf26yi+BQ@mail.gmail.com","threadId":"42415","inReplyTo":"loom.20160523T023140-975@post.gmane.org","subject":"Re: Git reset --hard with staged changes","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2016-05-23T19:31:16Z","receivedAt":"2016-05-23T19:31:16Z","isPatch":false,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Mon, May 23, 2016 at 2:55 AM, Yotam Gingold <yotam@yotamgingold.com> wrote:\n> Pierre-François CLEMENT <likeyn <at> gmail.com> writes:\n>> 2014-06-10 17:27 GMT+02:00 David Kastrup <dak <at> gnu.org>:\n>>> Pierre-François CLEMENT <likeyn <at> gmail.com> writes:\n>>>\n>>>> ...\n>>>>\n>>>> Hm I see. Even though the documentation doesn't make it very clear\n>>>> about what happens to such files, it turns out the scenario we\n>>>> stumbled upon seems to be the special use case after all. Thanks for\n>>>> shedding some light on this :) I wonder why does git-reset's hard mode\n>>>> not always remove untracked files then?\n>>>\n>>> Because it never removes them?  Git only removes files once it tracks\n>>> them.  This includes the operation of removing _and_ untracking them,\n>>> like with git reset --hard.\n>>>\n>>> The only command which explicitly messes with untracked files is\n>>> git-clean.\n>>>\n>>> --\n>>> David Kastrup\n>>\n>> ... I couldn't find a definition that backs this in the man\n>> pages (maybe the git-glossary would be a good place for it?), and the\n>> one from the Git-Scm book only confused me in thinking the opposite.\n>> Thanks for the clarification\n>>\n>> --\n>> Pierre-François CLEMENT\n>> Application developer at Upcast Social\n>\n> Jumping into this conversation two years later*. There's confusion about what\n> constitutes a tracked file for git reset --hard, and good reasons for git reset\n> --hard's behavior. Nevertheless, I think we can all agree that the man page\n> entry for git reset --hard is woefully deficient:\n>\n> --hard Resets the index and working tree. Any changes to tracked files in the\n> working tree since <commit> are discarded.\n>\n> This should be clarified to define what a tracked file is. I propose appending:\n>\n>     A file is considered tracked if it exists in a prior commit or in the\n>     staging area. Note that a newly added file not in any prior commit will be\n>     removed.\n\nWould you like to send a patch with something like the above?\n\nI don't know if something about why it is like this, or why it is the\nright thing to do, at least for recovering from merges, should be\nadded though.\n\n> I would also like to propose that the staging area's tree object be saved,\n> perhaps in the reflog or perhaps just as a dangling object. This would allow\n> graceful recovery from git reset --hard. Witness the many questions and answers\n> on recovery:\n>     http://stackoverflow.com/questions/7374069/undo-git-reset-hard-with-uncommitted-files-in-the-\n> staging-area\n>     http://stackoverflow.com/questions/5788037/recover-from-git-reset-hard\n>     http://stackoverflow.com/questions/5473/how-can-i-undo-git-reset-hard-head1\n>     http://gitready.com/advanced/2009/01/17/restoring-lost-commits.html\n>     https://bani.com.br/2014/10/recovering-lost-files-after-a-git-reset-hard/\n>     https://medium.com/@CarrieGuss/how-to-recover-from-a-git-hard-reset-b830b5e3f60c\n>\n> All of these solutions recover the contents of files, but not their names or the\n> directory structure. Saving the tree object somewhere (anywhere!) would solve\n> this problem.\n\nYeah, it might be a good idea.\n\n> I was bitten by this in a vicious way. I was setting up a new repository for a\n> bunch of code and data (git init; git add .), changed my mind about adding the\n> data (git reset --hard), and nearly lost everything.\n\nI think we could also perhaps have a special case when the current\nbranch doesn't really exist yet.\n\nAt least if you had used \"git reset --keep\", it would have failed with:\n\n$ git reset --keep\nerror: You do not have a valid HEAD.\nfatal: Could not reset index file to revision 'HEAD'.\n\nAlso if there had already been a commit, with --keep the new files\nwould not have been deleted.\n\n> The only tree object that\n> could be found was an empty one, so I got file contents without names or\n> directories (not good, because experimental conditions for the data were encoded\n> in the directory structure).\n\nBest,\nChristian.\n"},{"id":"287312","messageId":"xmqqeg8s8og8.fsf@gitster.mtv.corp.google.com","threadId":"42415","inReplyTo":"CAP8UFD0dQGmfhPuHjEGRZjEZHwUHR_XzAASwq+87Obf26yi+BQ@mail.gmail.com","subject":"Re: Git reset --hard with staged changes","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-05-23T21:16:39Z","receivedAt":"2016-05-23T21:16:39Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n>> This should be clarified to define what a tracked file is. I propose appending:\n>>\n>>     A file is considered tracked if it exists in a prior commit or in the\n>>     staging area. Note that a newly added file not in any prior commit will be\n>>     removed.\n>\n> Would you like to send a patch with something like the above?\n\nI am not sure if that is a good addition, though.\n\n> I don't know if something about why it is like this, or why it is the\n> right thing to do, at least for recovering from merges, should be\n> added though.\n\nI excuse you as it seems that you haven't read my response ;-)\n\n>> I would also like to propose that the staging area's tree object be saved,\n>> ..\n> Yeah, it might be a good idea.\n\nTwo issues with that \"proposal\" is that\n\n 1. the index may not be writable as a tree (think: during a\n    conflict resolution); and\n\n 2. the sole point of \"reset --hard\" is to \"discard the changes\".\n    If you want to instead save them away, there is another command\n    that was designed to do just that.\n\nIt wasn't all that surprising that those on stackoverflow would\nthink such a proposal is a good idea, but I somehow was hoping you\nhave been around here long enough to know \"git stash\" ;-)\n"},{"id":"287339","messageId":"CAP8UFD0yB8XjUi0f2OTUrW9W1UPC_ekY3+8--CC5rk_5RciYAA@mail.gmail.com","threadId":"42415","inReplyTo":"xmqqeg8s8og8.fsf@gitster.mtv.corp.google.com","subject":"Re: Git reset --hard with staged changes","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2016-05-24T06:20:18Z","receivedAt":"2016-05-24T06:20:18Z","isPatch":false,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Mon, May 23, 2016 at 11:16 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Christian Couder <christian.couder@gmail.com> writes:\n>\n>>> This should be clarified to define what a tracked file is. I propose appending:\n>>>\n>>>     A file is considered tracked if it exists in a prior commit or in the\n>>>     staging area. Note that a newly added file not in any prior commit will be\n>>>     removed.\n>>\n>> Would you like to send a patch with something like the above?\n>\n> I am not sure if that is a good addition, though.\n\nI am not sure either, but at least if something like that is added,\npeople may complain less.\n\n>> I don't know if something about why it is like this, or why it is the\n>> right thing to do, at least for recovering from merges, should be\n>> added though.\n>\n> I excuse you as it seems that you haven't read my response ;-)\n>\n>>> I would also like to propose that the staging area's tree object be saved,\n>>> ..\n>> Yeah, it might be a good idea.\n>\n> Two issues with that \"proposal\" is that\n>\n>  1. the index may not be writable as a tree (think: during a\n>     conflict resolution); and\n>\n>  2. the sole point of \"reset --hard\" is to \"discard the changes\".\n>     If you want to instead save them away, there is another command\n>     that was designed to do just that.\n>\n> It wasn't all that surprising that those on stackoverflow would\n> think such a proposal is a good idea, but I somehow was hoping you\n> have been around here long enough to know \"git stash\" ;-)\n\nYeah, we can try to teach people about git stash and git reset --keep\ninstead, but I doubt that it will be very effective.\n"},{"id":"287773","messageId":"4067AC3B-D369-4E86-9EB9-ED19FD362E2D@yotamgingold.com","threadId":"42415","inReplyTo":"CAP8UFD0yB8XjUi0f2OTUrW9W1UPC_ekY3+8--CC5rk_5RciYAA@mail.gmail.com","subject":"Re: Git reset --hard with staged changes","fromName":"Yotam Gingold","fromEmail":"yotam@yotamgingold.com","sentAt":"2016-05-30T05:07:49Z","receivedAt":"2016-05-30T05:07:49Z","isPatch":false,"sender":{"key":"yotam@yotamgingold.com","avatar":null},"body":"> On May 24, 2016, at 2:20 AM, Christian Couder <christian.couder@gmail.com> wrote:\n> \n> On Mon, May 23, 2016 at 11:16 PM, Junio C Hamano <gitster@pobox.com> wrote:\n>> \n>> \n>> I am not sure if that is a good addition, though.\n> \n> I am not sure either, but at least if something like that is added,\n> people may complain less.\n\nThe present documentation is at best wrong and at worst misleading.\nI read Junio's enumerations of the situations, and I appreciate that the\ncurrent behavior of git reset --hard cannot be changed because of the\nmany tools that rely on the current behavior. After reading it, I have\nmodified my proposed amendment to the git reset --hard documentation:\n\n    A file is considered tracked if it exists in a prior commit or in the\n    staging area. Note that a newly added file not in any prior commit will be\n    removed. This is useful for aborting a failed merge.\n\nShall I submit a patch?\n\n\n>>>> I would also like to propose that the staging area's tree object be saved,\n>>>> ..\n>>> Yeah, it might be a good idea.\n>> \n>> Two issues with that \"proposal\" is that\n>> \n>> 1. the index may not be writable as a tree (think: during a\n>>    conflict resolution); and\n\nSo why not do it only when possible? But in this case (and every case),\nwouldn't a solution be to create a tree that is not the index just for the reflog?\n\n\n>> 2. the sole point of \"reset --hard\" is to \"discard the changes\".\n>>    If you want to instead save them away, there is another command\n>>    that was designed to do just that.\n\ngit stash isn't relevant here. git reset --hard deletes data from disk that has\nonly ever been in the staging area. Many commands in git can be \"undone\"\ndue to the fact that it is a version control system and there is a reflog.\nThis is not one of those. git reset --hard can cause a catastrophic loss of\ndata, with no way to undo it.\n\n\n>> It wasn't all that surprising that those on stackoverflow would\n>> think such a proposal is a good idea, but I somehow was hoping you\n>> have been around here long enough to know \"git stash\" ;-)\n> \n> Yeah, we can try to teach people about git stash and git reset --keep\n> instead, but I doubt that it will be very effective.\n\ngit stash doesn't make sense in this context. The documentation for\ngit reset --hard is misleading. The error is unrecoverable. How could anyone\nbut a git developer know this?\n\nYotam\n"},{"id":"287874","messageId":"xmqqvb1u7okk.fsf@gitster.mtv.corp.google.com","threadId":"42415","inReplyTo":"4067AC3B-D369-4E86-9EB9-ED19FD362E2D@yotamgingold.com","subject":"Re: Git reset --hard with staged changes","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-05-31T06:02:03Z","receivedAt":"2016-05-31T06:02:03Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Yotam Gingold <yotam@yotamgingold.com> writes:\n\n> I read Junio's enumerations of the situations, and I appreciate that the\n> current behavior of git reset --hard cannot be changed because of the\n> many tools that rely on the current behavior. After reading it, I have\n> modified my proposed amendment to the git reset --hard documentation:\n>\n>     A file is considered tracked if it exists in a prior commit or in the\n>     staging area. Note that a newly added file not in any prior commit will be\n>     removed. This is useful for aborting a failed merge.\n>\n> Shall I submit a patch?\n\nI think the root cause of the confusion is that the description of\n\"reset --hard\" uses an undefined word \"tracked\", which does not\nappear anywhere else in \"git reset --help\".\n\nA better rewrite is probably a one that does not to use such a fuzzy\nword that is not even in official Git lingo (there is no glossary\nentry for it).\n\n Documentation/git-reset.txt | 10 ++++++++--\n t/perf/perf-lib.sh          |  4 +++-\n 2 files changed, 11 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt\nindex 25432d9..c4cc035 100644\n--- a/Documentation/git-reset.txt\n+++ b/Documentation/git-reset.txt\n@@ -65,8 +65,14 @@ If `-N` is specified, removed paths are marked as intent-to-add (see\n linkgit:git-add[1]).\n \n --hard::\n-\tResets the index and working tree. Any changes to tracked files in the\n-\tworking tree since <commit> are discarded.\n+\tReset the index and the working tree to be identical to the\n+\ttree of the given <commit> (defaults to HEAD) by discarding\n+\tthe changes made to them relative to <commit>.  Paths\n+\tmodified in the index and in the working tree are reset to\n+\twhat is recorded in <commit>.  Paths removed in the working\n+\ttree and in the index are resurrected from <commit>.  Paths\n+\tadded to the index and the working tree since <commit> are\n+\tremoved.\n \n --merge::\n \tResets the index and updates the files in the working tree that are\n"},{"id":"287880","messageId":"CAP8UFD18mbT12+9P0vqNWAnNirCD0oRz1O-ajFmm5y1BNTdVFA@mail.gmail.com","threadId":"42415","inReplyTo":"xmqqvb1u7okk.fsf@gitster.mtv.corp.google.com","subject":"Re: Git reset --hard with staged changes","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2016-05-31T06:41:34Z","receivedAt":"2016-05-31T06:41:34Z","isPatch":false,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Tue, May 31, 2016 at 8:02 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>\n> diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt\n> index 25432d9..c4cc035 100644\n> --- a/Documentation/git-reset.txt\n> +++ b/Documentation/git-reset.txt\n> @@ -65,8 +65,14 @@ If `-N` is specified, removed paths are marked as intent-to-add (see\n>  linkgit:git-add[1]).\n>\n>  --hard::\n> -       Resets the index and working tree. Any changes to tracked files in the\n> -       working tree since <commit> are discarded.\n> +       Reset the index and the working tree to be identical to the\n> +       tree of the given <commit> (defaults to HEAD) by discarding\n> +       the changes made to them relative to <commit>.  Paths\n> +       modified in the index and in the working tree are reset to\n> +       what is recorded in <commit>.  Paths removed in the working\n> +       tree and in the index are resurrected from <commit>.  Paths\n> +       added to the index and the working tree since <commit> are\n> +       removed.\n\nThis is a great improvement.\n\nI am not sure that it will be enough to make people use `git stash`\nand `git reset --keep` more though. But maybe that can wait until the\nnext time someone complain.\n\nThanks,\nChristian.\n"}]}