Changing branches does not discard local changes

Viewed 605

So, I'm a bit confused.

I'm not very good on git, but I remember that if you're in a branch with uncommited changes and you try to checkout another branch, git will either don't let you, or it will discard your changes.

I've also checked this behaviour to be true in the Apress' "Pro Git" book by Chacon and Straub.

enter image description here

And also

enter image description here

I confess my English isn't very good, but I remember this interpretation to be true from the old days were I used to use git more often. Or did I'm wrong about git discarding uncommited changes when changing branch? Am I understanding what the book is saying wrongly?

If I'm right, why do my repository is moving changed files from one branch to another? You can check the behaviour here:

enter image description here

Check that I'm in main branch and that it's clean from changes using git status.

Create a new branch and move to it.

Do some random alterations like deleting a couple of lines using ed.

Change back to main branch.

Surprise, a git diff or a git status shows that the file is changed on main as well.

Should not git prevent me changing branch or discard my changes instead? This is very confusing and took me a while to realize why my main branch was having unexpected behaviour if I didn't changed anything on it.

git version 2.30.1 (Apple Git-130)


Edit: Some answers said about git switch being different than git checkout. I'm still not sure that's the case since different people (and docs) says different things.

But, I've also tried the same using git checkout and it will also move the files instead of preventing me to switch or warning me about differences in the working directory.

You can see git checkout doing the same as follow:

enter image description here

3 Answers

Uncommitted changes will be left untouched, when you switch branches. Otherwise, doing innocuous like switching branches would silently delete any local work you have done. That's not good (and users would complain; rightfully).

Remember: local changes in your working tree are not linked to any branch. They only live in your working tree.

Your first quote/screenshot talks about committed changes only: When you switch branches, your working directory will reflect the content of switched-to branch. It also mentions "if Git cannot do it cleanly" (emphasis mine) – meaning Git will only abort if the same files have been changed locally (uncommitted) and in the other branch.

The second quote explicitly mentions "uncommitted changes that conflict". In that case, Git will refuse to switch branches. You can either commit the changes, remove them, or stash them – after that, you can switch to the other branch.

A conflict only occurs if the (committed) files are different between branches. Local uncommitted changes are not considered in the "conflict detection" when switching branches.

This behavior of git checkout has always been the same. git switch (a newer alternative to checkout) behaves in the same way.

The crux of the issue is that the behavior you described applies to the git checkout command, but you're using the relatively new git switch command to change branches. This is further complicated by the docs you referenced saying things like "when you switch branches", but they are likely also referring to git checkout.

If you use git switch, and you have local changes that do not conflict with the switched-to branch, it will just switch and move your changes over, essentially.

From the docs:

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

...

Switching branches does not require a clean index and working tree (i.e. no differences compared to HEAD). The operation is aborted however if the operation leads to loss of local changes, unless told otherwise with --discard-changes or --merge

Things to know

First thing to know: every commit contains all your files.

Second thing to know: to checkout or switch to a commit means to completely replace the visible files (the work tree) with copies of everything in that commit.

Switch to a new branch

A common use case is that you are on branch A and you edit and/or add some files and you are about to commit, and you suddenly think: wait, no, I should do this on a new branch. So you create a new branch and switch to it, and everything is fine; all your work just follows you to the new branch. Why?

Because checking out the new branch has no effect. The branch is just a new name for the same commit you are already on.

Switch to an old branch

Okay, now consider a different case.

On the new branch you edit add and commit, and you start editing a file. Suddenly you decide to switch back to the first branch.

This means you are asking Git to replace all your files with what's in the commit represented by the first branch. That is potentially a very radical change. Still, normally Git is happy to do it.

But you are in the middle of editing a file. If that file is already tracked by git, replacing or removing it would destroy uncommitted work. So Git refuses.

Git will never overwrite your uncommitted changes when you check out a branch.

Smash all the things

BUT...

If you ask to checkout or restore a specific file, then Git assumes you know what you're doing and will let you smash your own uncommitted work. Similarly with a hard reset. Those things are inherently destructive and Git will just stand back and let you be destructive.

Related