All of lore.kernel.org
 help / color / mirror / Atom feed
* git-checkout silently throws away the dirty status of index without a warning?
@ 2011-09-01 15:47 Tzu-Jung Lee
  2011-09-01 16:56 ` Jeff King
  2011-09-01 17:11 ` Junio C Hamano
  0 siblings, 2 replies; 6+ messages in thread
From: Tzu-Jung Lee @ 2011-09-01 15:47 UTC (permalink / raw)
  To: git

Hi guys,

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.

If the above correct. Please help me clarify if the following corner
case an intended or unexpected behavior.

Setup and git repo with two commits to illustrate the scenario:

    $ git init
    $ echo aaa >> aaa.txt
    $ echo bbb >> bbb.txt
    $ git add .
    $ git commit -a -m commit1

    $ echo bbb >> bbb.txt
    $ echo aaa >> aaa.txt
    $ git commit -a -m commit2

Forge a unclean index with changes that are subset of the destined
commit we are about to switching to.

    $ git checkout -b br1
    $ git reset HEAD^
    Unstaged changes after reset:
    M       aaa.txt
    M       bbb.txt

    $ git checkout HEAD aaa.txt
    $ git status --short
    M bbb.txt
    $ git add bbb.txt
    $ git status

    # On branch br1
    # Changes to be committed:
    #   (use "git reset HEAD <file>..." to unstage)
    #
    #       modified:   bbb.txt
    #

    $ git checkout master
    Switched to branch 'master'

git silently switch to master without warning against the index are
"RESTORE/RESET" to clean.

    $ git checkout br1
    $ git status
    # On branch br1
    nothing to commit (working directory clean)

Is this an intended behavior?
Though the status and changes can be safely restore from the database
with some manipulation.
But it may become difficult and tedious if the number of involved
files are large.


Regards,
Roy

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: git-checkout silently throws away the dirty status of index without a warning?
  2011-09-01 15:47 git-checkout silently throws away the dirty status of index without a warning? Tzu-Jung Lee
@ 2011-09-01 16:56 ` Jeff King
  2011-09-01 17:50   ` Tzu-Jung Lee
  2011-09-01 17:11 ` Junio C Hamano
  1 sibling, 1 reply; 6+ messages in thread
From: Jeff King @ 2011-09-01 16:56 UTC (permalink / raw)
  To: Tzu-Jung Lee; +Cc: git

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 <branch>" will switch your HEAD to <branch>,
and then try to make your index and working tree match the contents of
<branch>, with two exceptions:

  1. If you have local changes in a file, but the contents of the file
     in <branch> 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 <branch> 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).

The other form, "git checkout <branch> [--] <file>", is not about
switching branches at all, but about putting content from <branch> 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 <file>..." 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

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: git-checkout silently throws away the dirty status of index without a warning?
  2011-09-01 15:47 git-checkout silently throws away the dirty status of index without a warning? Tzu-Jung Lee
  2011-09-01 16:56 ` Jeff King
@ 2011-09-01 17:11 ` Junio C Hamano
  2011-09-01 18:28   ` Tzu-Jung Lee
  1 sibling, 1 reply; 6+ messages in thread
From: Junio C Hamano @ 2011-09-01 17:11 UTC (permalink / raw)
  To: Tzu-Jung Lee; +Cc: git

Tzu-Jung Lee <roylee17@gmail.com> writes:

> Is this an intended behavior?

Yes, I think you are talking about the case where "the current index does
not match the current HEAD, but it does match the tree we are switching
to" case. In that case we take the contents of switched-to branch.

It is the last case in the table in this old design document:

    http://thread.gmane.org/gmane.comp.version-control.git/4641

bug ignore the terminology (stage#). Read only the body of the table, with
the understanding that the three entries in each row talk about the state
for the same path in the index entry, the tree entry in the current HEAD,
and the tree entry in the switched-to branch. Also the table does not talk
about the checking performed on the working tree file, but assume that we
do not overwrite it when the resulting entry in the index does not match
what you have there.

The reason we allow branch switching in this case, instead of failing, is
so that you can be in a state where you applied the same change (relative
to the current branch to the branch you are switching to) lying around
already in your workspace and safely switch to the new branch without
losing any work (after all, the content matches).

By the way, the first six lines of your original message that describes
"saving and applying" is not correct.

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: git-checkout silently throws away the dirty status of index without a warning?
  2011-09-01 16:56 ` Jeff King
@ 2011-09-01 17:50   ` Tzu-Jung Lee
  0 siblings, 0 replies; 6+ messages in thread
From: Tzu-Jung Lee @ 2011-09-01 17:50 UTC (permalink / raw)
  To: Jeff King; +Cc: git

On Fri, Sep 2, 2011 at 12:56 AM, Jeff King <peff@peff.net> 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 <branch>" will switch your HEAD to <branch>,
> and then try to make your index and working tree match the contents of
> <branch>, with two exceptions:
>
>  1. If you have local changes in a file, but the contents of the file
>     in <branch> 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 <branch> 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 <branch> [--] <file>", is not about
> switching branches at all, but about putting content from <branch> 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 <file>..." 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
>

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: git-checkout silently throws away the dirty status of index without a warning?
  2011-09-01 17:11 ` Junio C Hamano
@ 2011-09-01 18:28   ` Tzu-Jung Lee
  2011-09-02  1:52     ` Andrew Ardill
  0 siblings, 1 reply; 6+ messages in thread
From: Tzu-Jung Lee @ 2011-09-01 18:28 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

On Fri, Sep 2, 2011 at 1:11 AM, Junio C Hamano <gitster@pobox.com> wrote:
> Tzu-Jung Lee <roylee17@gmail.com> writes:
>
>> Is this an intended behavior?
>
> Yes, I think you are talking about the case where "the current index does
> not match the current HEAD, but it does match the tree we are switching
> to" case. In that case we take the contents of switched-to branch.
>
> It is the last case in the table in this old design document:
>
>    http://thread.gmane.org/gmane.comp.version-control.git/4641
>

Cool! It's really valuable resource about the internals. Do all the
entries of the matrix still hold true today?
Maybe we can have an updated version put in the manual or other
official documentation.

> bug ignore the terminology (stage#). Read only the body of the table, with
> the understanding that the three entries in each row talk about the state
> for the same path in the index entry, the tree entry in the current HEAD,
> and the tree entry in the switched-to branch. Also the table does not talk
> about the checking performed on the working tree file, but assume that we
> do not overwrite it when the resulting entry in the index does not match
> what you have there.
>
> The reason we allow branch switching in this case, instead of failing, is
> so that you can be in a state where you applied the same change (relative
> to the current branch to the branch you are switching to) lying around
> already in your workspace and safely switch to the new branch without
> losing any work (after all, the content matches).

I understand it's a safe behavior in the sense that all the 'content'
are preserved. But the 'work' may still lost in another sense.
Consider the extended scenario. I was in the middle of doing... kind
of 'in-commit-bisection' (btw, how would you call this?).
Finding out which are the guilty changes in a giant patch that has
large number of changes including binary updates.

(good)   (good)    (bad)
index    HEAD    switchd-to

A1         A1        A2
B2         B1        B2
C2         C1        C2
D2         D1        D2
E1         E1        E2
...

I accidentally switch to the switched-to branch without committing the
intermediate status.
The 'work' was gone, and I had no idea how to bring it back.

> By the way, the first six lines of your original message that describes
> "saving and applying" is not correct.

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: git-checkout silently throws away the dirty status of index without a warning?
  2011-09-01 18:28   ` Tzu-Jung Lee
@ 2011-09-02  1:52     ` Andrew Ardill
  0 siblings, 0 replies; 6+ messages in thread
From: Andrew Ardill @ 2011-09-02  1:52 UTC (permalink / raw)
  To: Tzu-Jung Lee; +Cc: Junio C Hamano, git

> I accidentally switch to the switched-to branch without committing the
> intermediate status.
> The 'work' was gone, and I had no idea how to bring it back.

Forgive me if I am missing something here, but can't you use reflog to
go back to the point just before you checked the switched-to branch
out?

I guess the question is in general, how do you revert to a previous
instance of the working index AND previous checked out branch? I don't
know the 'switch branch with modified working index' mapping well
enough to be able to determine if there is a bijection possible, a
concise description of it would probably show us. If not, the only way
to achieve this is to keep the history available.

Regards,

Andrew Ardill

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2011-09-02  1:53 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2011-09-01 15:47 git-checkout silently throws away the dirty status of index without a warning? Tzu-Jung Lee
2011-09-01 16:56 ` Jeff King
2011-09-01 17:50   ` Tzu-Jung Lee
2011-09-01 17:11 ` Junio C Hamano
2011-09-01 18:28   ` Tzu-Jung Lee
2011-09-02  1:52     ` Andrew Ardill

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.