{"thread":{"id":"28277","subject":"git-checkout silently throws away the dirty status of index without a warning?","startedAt":"2011-09-01T15:47:59Z","lastAt":"2011-09-02T01:52:56Z","messageCount":6,"participants":["Tzu-Jung Lee","Jeff King","Junio C Hamano","Andrew Ardill"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"174669","messageId":"CAEvN+1h+mY+f3dzK7LFOwkqokOZSS-LosCzBqtYGbyjz=Dg7Zw@mail.gmail.com","threadId":"28277","inReplyTo":null,"subject":"git-checkout silently throws away the dirty status of index without a warning?","fromName":"Tzu-Jung Lee","fromEmail":"roylee17@gmail.com","sentAt":"2011-09-01T15:47:59Z","receivedAt":"2011-09-01T15:47:59Z","isPatch":false,"sender":{"key":"roylee17@gmail.com","avatar":null},"body":"Hi guys,\n\nCorrect me if I'm wrong:\n\n    git-checkout saves the changes to index and working-tree, and\ntries to apply them to the destined commit.\n    If the changes are applicable, then git-checkout the destined\ncommit and apply the changes.\n    Otherwise, git-checkout fails with warnings and leaves the current\nstatus untouched.\n\nIf the above correct. Please help me clarify if the following corner\ncase an intended or unexpected behavior.\n\nSetup and git repo with two commits to illustrate the scenario:\n\n    $ git init\n    $ echo aaa >> aaa.txt\n    $ echo bbb >> bbb.txt\n    $ git add .\n    $ git commit -a -m commit1\n\n    $ echo bbb >> bbb.txt\n    $ echo aaa >> aaa.txt\n    $ git commit -a -m commit2\n\nForge a unclean index with changes that are subset of the destined\ncommit we are about to switching to.\n\n    $ git checkout -b br1\n    $ git reset HEAD^\n    Unstaged changes after reset:\n    M       aaa.txt\n    M       bbb.txt\n\n    $ git checkout HEAD aaa.txt\n    $ git status --short\n    M bbb.txt\n    $ git add bbb.txt\n    $ git status\n\n    # On branch br1\n    # Changes to be committed:\n    #   (use \"git reset HEAD <file>...\" to unstage)\n    #\n    #       modified:   bbb.txt\n    #\n\n    $ git checkout master\n    Switched to branch 'master'\n\ngit silently switch to master without warning against the index are\n\"RESTORE/RESET\" to clean.\n\n    $ git checkout br1\n    $ git status\n    # On branch br1\n    nothing to commit (working directory clean)\n\nIs this an intended behavior?\nThough the status and changes can be safely restore from the database\nwith some manipulation.\nBut it may become difficult and tedious if the number of involved\nfiles are large.\n\n\nRegards,\nRoy\n"},{"id":"174684","messageId":"20110901165638.GE15018@sigill.intra.peff.net","threadId":"28277","inReplyTo":"CAEvN+1h+mY+f3dzK7LFOwkqokOZSS-LosCzBqtYGbyjz=Dg7Zw@mail.gmail.com","subject":"Re: git-checkout silently throws away the dirty status of index without a warning?","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-09-01T16:56:38Z","receivedAt":"2011-09-01T16:56:38Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Sep 01, 2011 at 11:47:59PM +0800, Tzu-Jung Lee wrote:\n\n> Correct me if I'm wrong:\n> \n>     git-checkout saves the changes to index and working-tree, and\n> tries to apply them to the destined commit.\n>     If the changes are applicable, then git-checkout the destined\n> commit and apply the changes.\n>     Otherwise, git-checkout fails with warnings and leaves the current\n> status untouched.\n\nNot exactly. \"git checkout <branch>\" will switch your HEAD to <branch>,\nand then try to make your index and working tree match the contents of\n<branch>, with two exceptions:\n\n  1. If you have local changes in a file, but the contents of the file\n     in <branch> do not differ from what's in the current HEAD, then the\n     file will be left alone (i.e., your local changes will be\n     preserved).\n\n  2. If you have local changes in a file, and the contents of the file\n     in <branch> differ both from what's in your working tree and from\n     what's in your current HEAD, git will print an error and refuse to\n     overwrite your changes (though you can ask it to merge them with\n     \"git checkout -m\").\n\nSo it is not about \"do these changes apply\", but rather that we will\ngive up any time file-level merging is required (unless \"-m\" is\nspecified).\n\nThe other form, \"git checkout <branch> [--] <file>\", is not about\nswitching branches at all, but about putting content from <branch> into\nthe current index and working tree, overwriting what's there.\n\n> If the above correct. Please help me clarify if the following corner\n> case an intended or unexpected behavior.\n> [...]\n>     $ git checkout -b br1\n>     $ git reset HEAD^\n>     Unstaged changes after reset:\n>     M       aaa.txt\n>     M       bbb.txt\n\nSo you have changes in two commits...\n\n>     $ git checkout HEAD aaa.txt\n\nAnd here you explicitly overwrite the changes in aaa.txt.\n\n>     $ git status --short\n>     M bbb.txt\n\n...leaving only the changes in bbb.txt.\n\n>     $ git add bbb.txt\n>     $ git status\n> \n>     # On branch br1\n>     # Changes to be committed:\n>     #   (use \"git reset HEAD <file>...\" to unstage)\n>     #\n>     #       modified:   bbb.txt\n>     #\n\nOK, now it's staged.\n\n>     $ git checkout master\n>     Switched to branch 'master'\n> \n> git silently switch to master without warning against the index are\n> \"RESTORE/RESET\" to clean.\n\nYes, because the changes in your index were identical to what was in the\ndestination branch. So we didn't drop any changes; they're still in the\nindex and in the working tree. It's simply that when compared to your\nnew HEAD, they are uninteresting.\n\n>     $ git checkout br1\n>     $ git status\n>     # On branch br1\n>     nothing to commit (working directory clean)\n\nAnd now when we switch to br1, you have no changes against master in\nyour working tree or index, so there is no dirty state to block\nswitching branches.\n\nI think git is working as intended here.  I agree it is a somewhat\nsurprising corner case, but only because your changes happened to\nexactly match the difference between the two branches you are switching\nbetween. But it makes sense when you think about what \"dirty state\"\nmeans: it is differences between HEAD and your index and working tree.\nSo we usually think of creating or removing dirty state by changing the\nworking tree. But you could equally well do it by changing the HEAD\nwithout changing the working tree, which is what you did here.\n\n-Peff\n"},{"id":"174688","messageId":"7v1uw0xk57.fsf@alter.siamese.dyndns.org","threadId":"28277","inReplyTo":"CAEvN+1h+mY+f3dzK7LFOwkqokOZSS-LosCzBqtYGbyjz=Dg7Zw@mail.gmail.com","subject":"Re: git-checkout silently throws away the dirty status of index without a warning?","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-09-01T17:11:32Z","receivedAt":"2011-09-01T17:11:32Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Tzu-Jung Lee <roylee17@gmail.com> writes:\n\n> Is this an intended behavior?\n\nYes, I think you are talking about the case where \"the current index does\nnot match the current HEAD, but it does match the tree we are switching\nto\" case. In that case we take the contents of switched-to branch.\n\nIt is the last case in the table in this old design document:\n\n    http://thread.gmane.org/gmane.comp.version-control.git/4641\n\nbug ignore the terminology (stage#). Read only the body of the table, with\nthe understanding that the three entries in each row talk about the state\nfor the same path in the index entry, the tree entry in the current HEAD,\nand the tree entry in the switched-to branch. Also the table does not talk\nabout the checking performed on the working tree file, but assume that we\ndo not overwrite it when the resulting entry in the index does not match\nwhat you have there.\n\nThe reason we allow branch switching in this case, instead of failing, is\nso that you can be in a state where you applied the same change (relative\nto the current branch to the branch you are switching to) lying around\nalready in your workspace and safely switch to the new branch without\nlosing any work (after all, the content matches).\n\nBy the way, the first six lines of your original message that describes\n\"saving and applying\" is not correct.\n"},{"id":"174692","messageId":"CAEvN+1hH+9XUcuqrT8DaRYu+BW10HJrVk=H96M+ZsdrMy4d8ug@mail.gmail.com","threadId":"28277","inReplyTo":"20110901165638.GE15018@sigill.intra.peff.net","subject":"Re: git-checkout silently throws away the dirty status of index without a warning?","fromName":"Tzu-Jung Lee","fromEmail":"roylee17@gmail.com","sentAt":"2011-09-01T17:50:53Z","receivedAt":"2011-09-01T17:50:53Z","isPatch":false,"sender":{"key":"roylee17@gmail.com","avatar":null},"body":"On Fri, Sep 2, 2011 at 12:56 AM, Jeff King <peff@peff.net> wrote:\n> On Thu, Sep 01, 2011 at 11:47:59PM +0800, Tzu-Jung Lee wrote:\n>\n>> Correct me if I'm wrong:\n>>\n>>     git-checkout saves the changes to index and working-tree, and\n>> tries to apply them to the destined commit.\n>>     If the changes are applicable, then git-checkout the destined\n>> commit and apply the changes.\n>>     Otherwise, git-checkout fails with warnings and leaves the current\n>> status untouched.\n>\n> Not exactly. \"git checkout <branch>\" will switch your HEAD to <branch>,\n> and then try to make your index and working tree match the contents of\n> <branch>, with two exceptions:\n>\n>  1. If you have local changes in a file, but the contents of the file\n>     in <branch> do not differ from what's in the current HEAD, then the\n>     file will be left alone (i.e., your local changes will be\n>     preserved).\n>\n>  2. If you have local changes in a file, and the contents of the file\n>     in <branch> differ both from what's in your working tree and from\n>     what's in your current HEAD, git will print an error and refuse to\n>     overwrite your changes (though you can ask it to merge them with\n>     \"git checkout -m\").\n>\n> So it is not about \"do these changes apply\", but rather that we will\n> give up any time file-level merging is required (unless \"-m\" is\n> specified).\n>\n\nAh!!! I think it was the \"file-level merging\" that surprised me so much.\nI used to think it's an atomic \"commit-level merging\" -- cleanly apply\nall the changes or touch nothing at all.\nHaving using git for years, I never notice this difference and neither\ndid it cause any trouble to me.\nUntil the corner case came to me today...\n\nThanks for the quick and precise explanation.\n\n> The other form, \"git checkout <branch> [--] <file>\", is not about\n> switching branches at all, but about putting content from <branch> into\n> the current index and working tree, overwriting what's there.\n>\n>> If the above correct. Please help me clarify if the following corner\n>> case an intended or unexpected behavior.\n>> [...]\n>>     $ git checkout -b br1\n>>     $ git reset HEAD^\n>>     Unstaged changes after reset:\n>>     M       aaa.txt\n>>     M       bbb.txt\n>\n> So you have changes in two commits...\n>\n>>     $ git checkout HEAD aaa.txt\n>\n> And here you explicitly overwrite the changes in aaa.txt.\n>\n>>     $ git status --short\n>>     M bbb.txt\n>\n> ...leaving only the changes in bbb.txt.\n>\n>>     $ git add bbb.txt\n>>     $ git status\n>>\n>>     # On branch br1\n>>     # Changes to be committed:\n>>     #   (use \"git reset HEAD <file>...\" to unstage)\n>>     #\n>>     #       modified:   bbb.txt\n>>     #\n>\n> OK, now it's staged.\n>\n>>     $ git checkout master\n>>     Switched to branch 'master'\n>>\n>> git silently switch to master without warning against the index are\n>> \"RESTORE/RESET\" to clean.\n>\n> Yes, because the changes in your index were identical to what was in the\n> destination branch. So we didn't drop any changes; they're still in the\n> index and in the working tree. It's simply that when compared to your\n> new HEAD, they are uninteresting.\n>\n>>     $ git checkout br1\n>>     $ git status\n>>     # On branch br1\n>>     nothing to commit (working directory clean)\n>\n> And now when we switch to br1, you have no changes against master in\n> your working tree or index, so there is no dirty state to block\n> switching branches.\n>\n> I think git is working as intended here.  I agree it is a somewhat\n> surprising corner case, but only because your changes happened to\n> exactly match the difference between the two branches you are switching\n> between. But it makes sense when you think about what \"dirty state\"\n> means: it is differences between HEAD and your index and working tree.\n> So we usually think of creating or removing dirty state by changing the\n> working tree. But you could equally well do it by changing the HEAD\n> without changing the working tree, which is what you did here.\n>\n> -Peff\n>\n"},{"id":"174696","messageId":"CAEvN+1hA1mUjDc0cQidEBOf5LhJ9LrVbTjyV5qPkUKNM6f7urQ@mail.gmail.com","threadId":"28277","inReplyTo":"7v1uw0xk57.fsf@alter.siamese.dyndns.org","subject":"Re: git-checkout silently throws away the dirty status of index without a warning?","fromName":"Tzu-Jung Lee","fromEmail":"roylee17@gmail.com","sentAt":"2011-09-01T18:28:54Z","receivedAt":"2011-09-01T18:28:54Z","isPatch":false,"sender":{"key":"roylee17@gmail.com","avatar":null},"body":"On Fri, Sep 2, 2011 at 1:11 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Tzu-Jung Lee <roylee17@gmail.com> writes:\n>\n>> Is this an intended behavior?\n>\n> Yes, I think you are talking about the case where \"the current index does\n> not match the current HEAD, but it does match the tree we are switching\n> to\" case. In that case we take the contents of switched-to branch.\n>\n> It is the last case in the table in this old design document:\n>\n>    http://thread.gmane.org/gmane.comp.version-control.git/4641\n>\n\nCool! It's really valuable resource about the internals. Do all the\nentries of the matrix still hold true today?\nMaybe we can have an updated version put in the manual or other\nofficial documentation.\n\n> bug ignore the terminology (stage#). Read only the body of the table, with\n> the understanding that the three entries in each row talk about the state\n> for the same path in the index entry, the tree entry in the current HEAD,\n> and the tree entry in the switched-to branch. Also the table does not talk\n> about the checking performed on the working tree file, but assume that we\n> do not overwrite it when the resulting entry in the index does not match\n> what you have there.\n>\n> The reason we allow branch switching in this case, instead of failing, is\n> so that you can be in a state where you applied the same change (relative\n> to the current branch to the branch you are switching to) lying around\n> already in your workspace and safely switch to the new branch without\n> losing any work (after all, the content matches).\n\nI understand it's a safe behavior in the sense that all the 'content'\nare preserved. But the 'work' may still lost in another sense.\nConsider the extended scenario. I was in the middle of doing... kind\nof 'in-commit-bisection' (btw, how would you call this?).\nFinding out which are the guilty changes in a giant patch that has\nlarge number of changes including binary updates.\n\n(good)   (good)    (bad)\nindex    HEAD    switchd-to\n\nA1         A1        A2\nB2         B1        B2\nC2         C1        C2\nD2         D1        D2\nE1         E1        E2\n...\n\nI accidentally switch to the switched-to branch without committing the\nintermediate status.\nThe 'work' was gone, and I had no idea how to bring it back.\n\n> By the way, the first six lines of your original message that describes\n> \"saving and applying\" is not correct.\n"},{"id":"174726","messageId":"CAH5451mU1DOb3T27U4kYCFzhSVRzmP6Z94h+5oq-K5CU71gTBg@mail.gmail.com","threadId":"28277","inReplyTo":"CAEvN+1hA1mUjDc0cQidEBOf5LhJ9LrVbTjyV5qPkUKNM6f7urQ@mail.gmail.com","subject":"Re: git-checkout silently throws away the dirty status of index without a warning?","fromName":"Andrew Ardill","fromEmail":"andrew.ardill@gmail.com","sentAt":"2011-09-02T01:52:56Z","receivedAt":"2011-09-02T01:52:56Z","isPatch":false,"sender":{"key":"andrew.ardill@gmail.com","avatar":"https://gravatar.com/avatar/da14cb7c091dd44dc6c63a4d3361b149acaf25226dc78eb4131a17b93d9b0993?d=mp&s=160"},"body":"> I accidentally switch to the switched-to branch without committing the\n> intermediate status.\n> The 'work' was gone, and I had no idea how to bring it back.\n\nForgive me if I am missing something here, but can't you use reflog to\ngo back to the point just before you checked the switched-to branch\nout?\n\nI guess the question is in general, how do you revert to a previous\ninstance of the working index AND previous checked out branch? I don't\nknow the 'switch branch with modified working index' mapping well\nenough to be able to determine if there is a bijection possible, a\nconcise description of it would probably show us. If not, the only way\nto achieve this is to keep the history available.\n\nRegards,\n\nAndrew Ardill\n"}]}