Subject: Re: On the behavior of checkout <branch> with uncommitted local changes

From: Junio C Hamano <[email-masked]>
Date: Thu, 19 Sep 2013 10:43:16 -0700

[email-masked] 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).