From: Tzu-Jung Lee Date: Thu, 01 Sep 2011 17:50:53 GMT Subject: Re: git-checkout silently throws away the dirty status of index without a warning? Message-ID: In-Reply-To: <20110901165638.GE15018@sigill.intra.peff.net> On Fri, Sep 2, 2011 at 12:56 AM, Jeff King wrote: > On Thu, Sep 01, 2011 at 11:47:59PM +0800, Tzu-Jung Lee wrote: > >> Correct me if I'm wrong: >> >>     git-checkout saves the changes to index and working-tree, and >> tries to apply them to the destined commit. >>     If the changes are applicable, then git-checkout the destined >> commit and apply the changes. >>     Otherwise, git-checkout fails with warnings and leaves the current >> status untouched. > > Not exactly. "git checkout " will switch your HEAD to , > and then try to make your index and working tree match the contents of > , with two exceptions: > >  1. If you have local changes in a file, but the contents of the file >     in do not differ from what's in the current HEAD, then the >     file will be left alone (i.e., your local changes will be >     preserved). > >  2. If you have local changes in a file, and the contents of the file >     in differ both from what's in your working tree and from >     what's in your current HEAD, git will print an error and refuse to >     overwrite your changes (though you can ask it to merge them with >     "git checkout -m"). > > So it is not about "do these changes apply", but rather that we will > give up any time file-level merging is required (unless "-m" is > specified). > Ah!!! I think it was the "file-level merging" that surprised me so much. I used to think it's an atomic "commit-level merging" -- cleanly apply all the changes or touch nothing at all. Having using git for years, I never notice this difference and neither did it cause any trouble to me. Until the corner case came to me today... Thanks for the quick and precise explanation. > The other form, "git checkout [--] ", is not about > switching branches at all, but about putting content from into > the current index and working tree, overwriting what's there. > >> If the above correct. Please help me clarify if the following corner >> case an intended or unexpected behavior. >> [...] >>     $ git checkout -b br1 >>     $ git reset HEAD^ >>     Unstaged changes after reset: >>     M       aaa.txt >>     M       bbb.txt > > So you have changes in two commits... > >>     $ git checkout HEAD aaa.txt > > And here you explicitly overwrite the changes in aaa.txt. > >>     $ git status --short >>     M bbb.txt > > ...leaving only the changes in bbb.txt. > >>     $ git add bbb.txt >>     $ git status >> >>     # On branch br1 >>     # Changes to be committed: >>     #   (use "git reset HEAD ..." to unstage) >>     # >>     #       modified:   bbb.txt >>     # > > OK, now it's staged. > >>     $ git checkout master >>     Switched to branch 'master' >> >> git silently switch to master without warning against the index are >> "RESTORE/RESET" to clean. > > Yes, because the changes in your index were identical to what was in the > destination branch. So we didn't drop any changes; they're still in the > index and in the working tree. It's simply that when compared to your > new HEAD, they are uninteresting. > >>     $ git checkout br1 >>     $ git status >>     # On branch br1 >>     nothing to commit (working directory clean) > > And now when we switch to br1, you have no changes against master in > your working tree or index, so there is no dirty state to block > switching branches. > > I think git is working as intended here.  I agree it is a somewhat > surprising corner case, but only because your changes happened to > exactly match the difference between the two branches you are switching > between. But it makes sense when you think about what "dirty state" > means: it is differences between HEAD and your index and working tree. > So we usually think of creating or removing dirty state by changing the > working tree. But you could equally well do it by changing the HEAD > without changing the working tree, which is what you did here. > > -Peff >