1

Here is my situation:

  1. create a file 2.md in branch dev, and git add this file, then I can see this file is added into index through git ls-files
  2. switch to branch master by git checkout master, but ls and git ls-files still shows this file exists in working tree and index. Why git checkout doesn't update working tree and index according to master's last commit?
    Here is the description of git checkout in git manual:

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>.

  1. git commit this file in branch master, then git checkout dev, I find this file disappears from both working tree and index of branch dev, which also confuse me

Below is my operation and output:

(base) [root@zjmhost hello-github]# git checkout dev
Switched to branch 'dev'
(base) [root@zjmhost hello-github]# touch 2.md
(base) [root@zjmhost hello-github]# ls
2.md  copy.md  README.md  test.md
(base) [root@zjmhost hello-github]# git ls-files
.ipynb_checkpoints/README-checkpoint.md
README.md
copy.md
test.md
(base) [root@zjmhost hello-github]# git add 2.md 
(base) [root@zjmhost hello-github]# git ls-files
.ipynb_checkpoints/README-checkpoint.md
2.md
README.md
copy.md
test.md
(base) [root@zjmhost hello-github]# git checkout master
A       2.md
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 12 commits.
  (use "git push" to publish your local commits)
(base) [root@zjmhost hello-github]# ls
1.md  2.md  copy.md  README.md  test.md
(base) [root@zjmhost hello-github]# git ls-files
.ipynb_checkpoints/README-checkpoint.md
1.md
2.md
README.md
copy.md
test.md
(base) [root@zjmhost hello-github]# git commit -m "commit 2.md in master branch"
[master 005f0c2] 2.md do the same with 1.md
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 2.md
(base) [root@zjmhost hello-github]# git ls-files
.ipynb_checkpoints/README-checkpoint.md
1.md
2.md
README.md
copy.md
test.md
(base) [root@zjmhost hello-github]# git checkout dev
Switched to branch 'dev'
(base) [root@zjmhost hello-github]# ls
copy.md  README.md  test.md
(base) [root@zjmhost hello-github]# git ls-files
.ipynb_checkpoints/README-checkpoint.md
README.md
copy.md
test.md
matt
  • 447,615
  • 74
  • 748
  • 977
zhao
  • 19
  • 2
  • When you checkout while there are pending changes in index, if the files _do not_ change between HEAD and where you are going, git assumes there's no issue checking out and allows you to do it bringing over your changes. It happens quite frequetly: **oops! Working in the wrong branch.** – eftshift0 May 08 '21 at 17:07
  • After you commit, well, the file is in master. You go back to dev and sure enough, it's not there. I think you are assuming that just because you were on dev when you added the file to index, it should keep some relation to it? Well, that's not the case. The index will be used to create the new revision where you do (in your example, master) and nothing about dev is kept (after checking out master, git _forgets_ that you were in dev.... well, kind of... you still get to have the luxury of reflog). – eftshift0 May 08 '21 at 17:11
  • Doesn't look like you `git commit -m` after you added it. – astrochun May 09 '21 at 01:05
  • Related: [switching branches with uncommitted changes](https://stackoverflow.com/q/22053757/1256452) – torek May 09 '21 at 07:32

3 Answers3

3

You are, I think, right to be concerned and surprised.

Checking out a branch is sort of scary, because it does involve writing to the index and working tree. The safest way to switch branches is to commit first, either using add-and-commit or using stash (which is a form of commit). If git status says there is nothing to know, you are safe to switch branches.

In general, however, git checkout will not overwrite or remove uncommitted work. I say "in general" because one does hear horror stories where work is unaccountably lost. All the same, if git checkout would cause damage, Git is supposed to stop and warn you instead of doing the checkout.

Let's take three cases; I think these are representative of the most common scenarios.

Edit an existing file and create a branch

I start with the most commonly encountered base case, mentioned by eftshift0: you start editing and then realize you should have been on a branch. Let's posit this:

* (HEAD -> master) created c
* created b
* created a

Now I edit an existing file, and I even add the new state of that file to the index, ready to commit. But then I think I should have been on a branch instead:

% pico c
% git add c
% git switch -c br
% git commit -m'edited c'

No problem! The edited version of c is now in the br commit, and the previous version of c is in the master commit, as you can readily discover just by peeking:

% git show master:c
% git show br:c

Why was the branch switch okay? That's an easy one: it's because nothing happened. At the time of the switch, br and master pointed to the very same commit. Therefore there was nothing to do. The index and the working tree were left alone.

New file and switch to an existing branch

Now let's posit this initial situation:

* (br) created z
| * (HEAD -> master) created c
| * created b
|/  
* created a

We're on master and we create a new file and add it, and switch to br, which already exists and points to a different commit:

% pico d
% git add d
% git switch br
% git commit -m'created d'

Again, no problem! Although br is a different commit altogether, nothing about d is known in master or br; it isn't a commit in either of them. So Git permits this, and the new file d just comes across with the switch, and we can commit the index that we configured while we were on master.

Edit an existing file and switch to an existing branch

Return now to this initial situation:

* (br) created z
| * (HEAD -> master) created c
| * created b
|/  
* created a

We're on master and this time we edit and add the file c, which already exists in master, just like in the first scenario, and then we try to switch to the branch br which, unlike the first scenario, already exists and points to a different commit:

% pico c
% git add c
% git switch br
error: Your local changes to the following files would be overwritten by checkout:
    c
Please commit your changes or stash them before you switch branches.
Aborting

This time, Git refuses to make the switch. There is a file c in master, and the state of that file c in the working tree is different from that, and the state of file c in br is different yet again (namely, it doesn't exist). Therefore checking out br would mean changing (in this case, removing) the file c in the working tree. That's an overwrite, and Git balks.

matt
  • 447,615
  • 74
  • 748
  • 977
  • Good summary of the various cases, using `git switch` instead of `git checkout`! Upvoted. – VonC May 09 '21 at 01:18
0

Instead of the confusing old git checkout command, try instead using git switch:

git switch master
git restore -- .

From the man page:

The working tree and the index are updated to match the branch. All new commits will be added to the tip of this branch.

VonC
  • 1,042,979
  • 435
  • 3,649
  • 4,283
0

Why git checkout doesn't update working tree and index according to master's last commit?

I think the key part you are missing is:

Local modifications to the files in the working tree are kept

In other words, the uncommitted changes in the index or local files will still remain. Since you didn't commit the changes to dev, they just carry along as you checkout master.

git commit this file in branch master, then git checkout dev, I find this file disappears from both working tree and index of branch dev, which also confuse me

You just committed the new file to master but never committed it to dev. So you shouldn't expect the file to exist when you checkout dev.

Code-Apprentice
  • 69,701
  • 17
  • 115
  • 226