git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* On the behavior of checkout <branch> with uncommitted local changes
@ 2013-09-19  9:23 r.ductor
  2013-09-19 17:43 ` Junio C Hamano
  0 siblings, 1 reply; 5+ messages in thread
From: r.ductor @ 2013-09-19  9:23 UTC (permalink / raw)
  To: git

Dear all

I'm not a power git user but I profit of git every day and I like to fully understand what I do.

The man section for git checkout is too vague for my taste. In particular it is not clearly (unambiguously) stated what happens to index and worktree whenever local uncommitted changes are around. I've already rised a similar problem in this mail list [1], but I understand that a man page must be concise.

On the other hand, I couldn't find any complete information on this behavior: tutorials and books seem to avoid the problem, user posts seems confused ... 

To grasp some more information,  I've spent some hours in trials (sorry I'm unable to grasp information browsing the code repository). That resulted in the algorithm below presented.

Could anybody authoritative on that subject confirm/correct/discharge my statement? That could be of help for me and may others.

Nonetheless to say having this kind of pseudocodes available somewhere (e.g. for stash [2] and other tools modifing index and working tree) would make my git experience  (and that of many more people) happier.

Thanks to all developers for their efforts.

Regards
ric


Notations: let us fix a file and denote
C0  = its version in the initial commit
I0   = its version in the initial index
W0 = its version in the working tree
C1 = its version in the target commit
W1= its version in working tree after checkout completed
I1  = its version in index after checkout completed


git checkout Branch

if C0=W0=I0,          then: W1=I1=C1;
if C1=I0,                 then: W1=W0 and I1=C1=I0;
if C1=C0,                then: W1=W0 and I1=I0;
otherwise: abort


Note: in particular, if W0=I0 !=C0 then (in general) abort

Note: in particular, if C0=I0 and C1=W0 then abort  (...actually why that? no information is lost)


REFS
[1]http://thread.gmane.org/gmane.linux.debian.devel.bugs.general/782914/focus=164647
[2]http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=717088

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

* Re: On the behavior of checkout <branch> with uncommitted local changes
  2013-09-19  9:23 On the behavior of checkout <branch> with uncommitted local changes r.ductor
@ 2013-09-19 17:43 ` Junio C Hamano
  2013-09-20 13:33   ` r.ductor
  0 siblings, 1 reply; 5+ messages in thread
From: Junio C Hamano @ 2013-09-19 17:43 UTC (permalink / raw)
  To: r.ductor; +Cc: git

r.ductor@gmail.com writes:

> The man section for git checkout ... In
> particular it is not clearly (unambiguously) stated what happens
> to index and worktree whenever local uncommitted changes are
> around.

In the current text, the key information is in two places:

    'git checkout' <branch>::
            To prepare for working on <branch>, switch to it by updating
            the index and the files in the working tree, and by pointing
            HEAD at the branch. Local modifications to the files in the
            working tree are kept, so that they can be committed to the
            <branch>.

    -m::
    --merge::
            When switching branches,
            if you have local modifications to one or more files that
            are different between the current branch and the branch to
            which you are switching, the command refuses to switch
            branches in order to preserve your modifications in context.

Let's see how we can improve the text.  Points to notice are:

 * "by updating the index and the files" does not say "how" they are
   updated and can be clarified:

    - The index is made to match the state the commit at the tip of
      <branch> records.

    - The working tree files without local modifications are updated
      the same way.

    - The working tree files with local modifications are not
      touched.

 * Because "the command refuses to checkout another branch under
   this and that condition" does not have its own section, the
   description of "-m" needs to say it as a background information
   to explain in what situation the option may be useful.  It can be
   moved to the main "'git checkout' <branch>" part and it may make
   the result read easier.

 * "in order to preserve your modifications in context" is correct
   and sufficient description, but it requires some thinking in
   readers' part to understand why it is a good thing.  It can be
   clarified.  The thinking goes like this.

   Suppose your current branch has file X whose contents is X0 (that
   is, the commit at the tip of this branch records file X with
   content X0).  You have local changes to this file and its
   contents is X1.  The index at path X is unchanged and still
   records X0.

   The branch you are checking out has contents X2 at the path.

   If we allowed "git checkout <the other branch>" and simply kept
   the local changes, you will end up in a funny state in which:

    - The tip commit, that will become the parent commit when you
      make the next commit, has X2 at path X.

    - The index has X2 at path X to match the tip commit.  You could
      change this to keep X0 but it does not matter in the larger
      picture, because you will be editing the working tree version
      and updating the index from there to prepare for the next
      commit.

    - The working tree has contents X1 at path X.  But realize that
      the change "you" made is the difference between X0 and X1, not
      X2 and X1.

   If we allowed such a checkout and then you did "git commit -a",
   you will end up reverting the state between X0 (contents in your
   original branch) and X2 (contents in the new branch), even though
   the change you wanted to make was only the difference between X0
   and X1.

Also, if you did "git add X" and then "checkout <branch>", unless
the version in the index at path X match either your original branch
or the branch you are checking out, the command will stop you, and
the "-m" option does not resolve this with a 4-way merge (it will be
too complex for users to understand if we did so).

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

* Re: On the behavior of checkout <branch> with uncommitted local changes
  2013-09-19 17:43 ` Junio C Hamano
@ 2013-09-20 13:33   ` r.ductor
  2013-09-20 22:58     ` Junio C Hamano
  0 siblings, 1 reply; 5+ messages in thread
From: r.ductor @ 2013-09-20 13:33 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

Dear Junio,

thanks for your answer and you availability to revise the man text. Below my (irreverent) comments

On Thursday 19 September 2013 10:43:16 Junio C Hamano wrote:
> Let's see how we can improve the text.  Points to notice are:
> 
>  * "by updating the index and the files" does not say "how" they are
>    updated and can be clarified:
> 
>     - The index is made to match the state the commit at the tip of
>       <branch> records.
> 
>     - The working tree files without local modifications are updated
>       the same way.
> 
>     - The working tree files with local modifications are not
>       touched.

mmm maybe I'm wrong, but it seems to me that the first statement on the index (above) is oversimplifing and not correct in presence of local changes. In the example below, when in the branch 'dev' the index content was '2 in index' and the workspace content was  '3 in work' and the commited contents dev:a=master:a='1'. In this case checkout completed and the index (and workspace) contents after checkout where preserved, hence the index was different from the head commit in both branches master and dev.

$ echo '1'>a; git init; git add a; git commit -a -m '1';git checkout -b dev;echo '2 in index'>a;git add a; echo '3 in work'>a; git checkout master; cat a; git diff; git diff HEAD 
Initialized empty Git repository in /home/xxxx/git-test12/.git/                                                                 
[master (root-commit) d0064f1] 1                                                                                                       
 1 file changed, 1 insertion(+)                                                                                                        
 create mode 100644 a                                                                                                                  
Switched to a new branch 'dev'                                                                                                         
M       a                                                                                                                              
Switched to branch 'master'                                                                                                            
3 in work                                                                                                                              
diff --git a/a b/a                                                                                                                     
index d35137c..ee9231a 100644                                                                                                          
--- a/a                                                                                                                                
+++ b/a                                                                                                                                
@@ -1 +1 @@                                                                                                                            
-2 in index                                                                                                                            
+3 in work                                                                                                                             
diff --git a/a b/a
index d00491f..ee9231a 100644                                                                                                          
--- a/a                                                                                                                                
+++ b/a                                                                                                                                
@@ -1 +1 @@
-1
+3 in work
$

This is what I ment by  the line "if C1=C0, then: W1=W0 and I1=I0;"

... am I missing something?


>  * Because "the command refuses to checkout another branch under
>    this and that condition" does not have its own section, the
>    description of "-m" needs to say it as a background information
>    to explain in what situation the option may be useful.  It can be
>    moved to the main "'git checkout' <branch>" part and it may make
>    the result read easier.

I 100% agree that the git checkout' <branch> should mention that the command might not complete in some cases, and hopefully describe when. A normal user (me) learning the command tries to first understand the simpler usage before going below and learning the action of the options.

> "in order to preserve your modifications in context" is correct
>   and sufficient description

:((( this is one of the points I considered "vague"... what is "context"? "parent commit" or "parent commit AND index" or "index"? the reader starts to asks theirself too much questions, whose aswers requires an educated guess that should ideally not be necessary when reading a man page....
Maybe I'm to much mathematically minded, but few clean lines of pseudocode explain everything without any doubt.


>    If we allowed such a checkout and then you did "git commit -a",
>    you will end up reverting the state between X0 (contents in your
>    original branch) and X2 (contents in the new branch), even though
>    the change you wanted to make was only the difference between X0
>    and X1.
>    ...

OK I understand your motivations, in your example the contents would have been preserved by checkout, but a shallow user could be then "overchange" by mistake. 

> 
> Also, if you did "git add X" and then "checkout <branch>", unless
> the version in the index at path X match either your original branch
> or the branch you are checking out, the command will stop you, 

... disagree (or misunderstand), if X0=X2 and index = X1 (work) then checkout does not fail, an example would be the example above with 
echo '2 in index'>a; replaced by echo '3 in work'>a.

$ echo '1'>a; git init; git add a; git commit -a -m '1';git checkout -b dev;echo '3 in work'>a;git add a; echo '3 in work'>a; git checkout master; cat a; git diff; git diff HEAD 
Initialized empty Git repository in /home/xxx/git-test13/.git/
[master (root-commit) f8d39d9] 1
 1 file changed, 1 insertion(+)
 create mode 100644 a
Switched to a new branch 'dev'
M       a
Switched to branch 'master'
3 in work
diff --git a/a b/a
index d00491f..ee9231a 100644
--- a/a
+++ b/a
@@ -1 +1 @@
-1
+3 in work
$


... again, sorry if I'm stupidly missing a point or I'm too much irreverent. Any suggestion of references on that would be very welcome.

ric 

PS, BTW, updated in your glossary my statement would be

For each path,  denote by
C0  = its content in the head commit before checkout
I0   = its content in the index before checkout
W0 = its content in the working tree before checkout
C1 = its content in the head commit of the target branch <branch>
W1= its content in working tree after checkout <branch> successfully completed
I1  = its content in index after checkout <branch> successfully completed
then:
if C0=W0=I0,          then: W1=I1=C1;
if C1=I0,                 then: W1=W0 and I1=C1=I0;
if C1=C0,                then: W1=W0 and I1=I0;
otherwise: abort

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

* Re: On the behavior of checkout <branch> with uncommitted local changes
  2013-09-20 13:33   ` r.ductor
@ 2013-09-20 22:58     ` Junio C Hamano
  2013-09-24  9:25       ` r.ductor
  0 siblings, 1 reply; 5+ messages in thread
From: Junio C Hamano @ 2013-09-20 22:58 UTC (permalink / raw)
  To: r.ductor; +Cc: git

r.ductor@gmail.com writes:

> mmm maybe I'm wrong, but it seems to me that the first statement
> on the index (above) is oversimplifing.

Yes, it was simplified to illustrate the principle, not even trying
to be exhaustive.

The principle is that we allow you to check out a different branch
when you have local changes to the working tree and/or to the index,
as long as we can make the index and the working tree pretend as if
you reached that locally modified state, starting from a clean state
of the branch you are checking out.

That is what "your modifications in context" in the description of
the "-m" option refers to.

It directly follows that a local change to the index at a path is
carried forward when you check out a different branch, if HEAD and
the branch you are checking out have the same contents registered at
the path.

The message you are responding to illustrated that principle by
talking about changes to the working tree but the same principle
applies to changes to the index, as changes to the working tree is
much easier to picture in your mind.

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

* Re: On the behavior of checkout <branch> with uncommitted local changes
  2013-09-20 22:58     ` Junio C Hamano
@ 2013-09-24  9:25       ` r.ductor
  0 siblings, 0 replies; 5+ messages in thread
From: r.ductor @ 2013-09-24  9:25 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

Dear Juno

Thanks for your answer.

My fair criticism in my previous emails (and below) is just to try to convince you that with a few short sentences you risk to transmit only vague ideas, while a serious user is interested to understand the behavior of git in any occurrence, with no ambiguity. Once more, in my view a short pseudo code -- or its equivalent in words -- would be a useful compromise among simplicity and accuracy.

I'm attacking git checkout but I guess that what I say is a general problem of the git official documentation (i.e git stash in my experience has problems).

Each undocumented feature (simulated by my dumb questions) enforces the loss of a significant percentage of the users.

Lacking details, a hurried user will not use the feature, or if they are brave enough, they will use it without understanding (i.e. risking data loss one day or the other)...

Lacking details, a serious user will have to waste their time in trials or in studing the sources.

Do not fear that giving details in a man page would scare newbyes: as a git newbye I can say that what scares me is the lack of information, not the complexity(=power) of a program (*)

Do not waste your precious time in explaining me/us general ideas, we can find them in the tens of pedagogic tutorials/books out there, please give us those details that nobody out there seems to know and that man (un)intentionally hides!

I would be happy to submit a patch for the man git-checkout but I'm not sure that my understanding of git checkout (as encoded in the pseudocode I submitted) is correct.

My friendly regards,

ric

(*) of course you're allowed to discard my suggestions pretending that I'm not a representative user ;)

On Friday 20 September 2013 15:58:27 Junio C Hamano wrote:
> The principle is that we allow you to check out a different branch
> when you have local changes to the working tree and/or to the index,
> as long as we can make the index and the working tree pretend as if
> you reached that locally modified state, starting from a clean state
> of the branch you are checking out.

Of course I understand the idea, but if I try to grasp the details I'm in trouble: the problem in this statement is the ambiguity of "change" and "pretend as".

In plain english I can start from any content and change it to any other content, so in this semantics your statement is empty, or worse.

If I assume that change means "differences with N lines of unchanged context" I must still guess differences with respect to what? head-index, index-work, head-work ???? ... and how much is N? 3, 4, 1028?

Then, in order to understand I (a user) make the trials below, and concludes that also the nice principle stated above is somewhat incomplete....

git-test15$ echo -e 'a\n1\n2\n3\n4\n5\nb\n6\n7\n8\n9\n10\n'>f; cat f;git init; git add f; git commit -a -m 'ab'; git checkout -b dev; echo -e 'A\n1\n2\n3\n4\n5\nb\n6\n7\n8\n9\n10\n'>f;cat f;git commit -a -m 'Ab'; echo -e 'A\n1\n2\n3\n4\n5\nB\n6\n7\n8\n9\n10\n'>f;cat f;git add f; git checkout master                                                                            
a                                                                                                                                      
1                                                                                                                                      
2                                                                                                                                      
3                                                                                                                                      
4                                                                                                                                      
5                                                                                                                                      
b                                                                                                                                      
6                                                                                                                                      
7                                                                                                                                      
8                                                                                                                                      
9                                                                                                                                      
10                                                                                                                                     
                                                                                                                                       
Initialized empty Git repository in /home/git-test15/.git/                                                                 
[master (root-commit) 46d19ab] ab                                                                                                      
 1 file changed, 13 insertions(+)                                                                                                      
 create mode 100644 f                                                                                                                  
Switched to a new branch 'dev'                                                                                                         
A                                                                                                                                      
1
2
3
4
5
b
6
7
8
9
10

[dev bb852db] Ab
 1 file changed, 1 insertion(+), 1 deletion(-)
A
1
2
3
4
5
B
6
7
8
9
10

error: Your local changes to the following files would be overwritten by checkout:
        f
Please, commit your changes or stash them before you can switch branches.
Aborting

#####################################################################################
#### why abort the change w.r.t. dev was just b -> B and that patch was admissible in master ... I start questioning the principle ####
#####################################################################################

git-test15$ mkdir ../gittest16
git-test15$ cd ../gittest16
gittest16$ echo -e 'a\n1\n2\n3\n4\n5\nb\n6\n7\n8\n9\n10\n'>f; cat f;git init; git add f; git commit -a -m 'ab'; git checkout -b dev; echo -e 'A\n1\n2\n3\n4\n5\nb\n6\n7\n8\n9\n10\n'>f;cat f;git commit -a -m 'Ab'; echo -e 'a\n1\n2\n3\n4\n5\nB\n6\n7\n8\n9\n10\n'>f;cat f;git add f; git checkout master
a
1
2
3
4
5
b
6
7
8
9
10

Initialized empty Git repository in /home/gittest16/.git/
[master (root-commit) 7d7febe] ab
 1 file changed, 13 insertions(+)
 create mode 100644 f
Switched to a new branch 'dev'
A
1
2
3
4
5
b
6
7
8
9
10

[dev 143db1d] Ab
 1 file changed, 1 insertion(+), 1 deletion(-)
a
1
2
3
4
5
B
6
7
8
9
10

error: Your local changes to the following files would be overwritten by checkout:
        f
Please, commit your changes or stash them before you can switch branches.
Aborting
gittest16$

#####################################################################################
#### why abort? the change w.r.t. master was just b -> B and that patch was admissible in master ... I start questioning the principle ####
#####################################################################################

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

end of thread, other threads:[~2013-09-24  9:22 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2013-09-19  9:23 On the behavior of checkout <branch> with uncommitted local changes r.ductor
2013-09-19 17:43 ` Junio C Hamano
2013-09-20 13:33   ` r.ductor
2013-09-20 22:58     ` Junio C Hamano
2013-09-24  9:25       ` r.ductor

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).