3

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

jacksonbenete
  • 159
  • 1
  • 9
  • 1
    I've always observed this behaviour: either you cannot switch to another branch, or the changes are kept but they will never be discarded. That would mean loosing your changes.. – Gaël J Jul 09 '21 at 17:23
  • It will allow you to checkout if the files that you modified are exactly the same between HEAD and the place where you are trying to switch to. If that's not the case, git will raise a warning and will stop (unless you use -f, of course). – eftshift0 Jul 09 '21 at 17:42
  • That's correct @elshift0. That's how I understand it. And in my understanding, if I delete a line of a file, like I did in the screenshots, the modified file isn't exactly the same between HEAD and main, right? Or how would a file with less content be considered the same? What really qualifies a "conflict" then? In the case I've showed I think that git should not let me switch unless I explicitly ask to move those files... – jacksonbenete Jul 09 '21 at 19:33
  • While `git switch` and `git checkout` *are* somewhat different, the real key difference is that some sets of arguments to `git checkout` *will* destroy uncommitted work in ways that Git newbies, and sometimes even experienced Git users, did not expect. The introduction of the `switch` command (a) fixed the problem (even in `git checkout`: it now notes that your request was ambiguous) and (b) gives newbies a command that *can't* destroy uncommitted work that way: there's no request-syntax for it. – torek Jul 09 '21 at 21:57
  • 2
    For the gory details on *when* checkout/switch lets you switch, and when it doesn't, even though you have uncommitted work, see [Checkout another branch when there are uncommitted changes on the current branch](https://stackoverflow.com/q/22053757/1256452). – torek Jul 09 '21 at 21:59

3 Answers3

2

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.

knittl
  • 246,190
  • 53
  • 318
  • 364
  • Then the problem would be in my understanding about what is a “conflict” between files. If I have the file `expand` in my main branch, move to another branch and edit the file named `expand`, switching back to main would not qualify as “uncommited changes that conflict”? Files with the same name (the same file) with different contents. How that isn’t a conflict? Would you mind to elaborate? Maybe the understanding about what really qualifies a conflict will be the key to my understanding. Thanks! – jacksonbenete Jul 09 '21 at 19:28
  • @jacksonbenete it is only a conflict if the (committed) file is different between the branches. If the file is the same in both branches, the file does not need to be updated by Git and all local (uncommitted) changes can be left in the file. Only when the committed contents of the file are different, Git will refuse to switch. Why? Because it would need to re-apply your (uncommitted) changes to a different version of the file and that could go wrong, potentially destroying your work. – knittl Jul 09 '21 at 19:47
1

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

  • I've initially thought it would be the case, but using `git checkout` will also move the changes as well (I've uploaded another screenshot showing that). Maybe that's always have been the same behaviour like mentioned by @knittl, but if that's the case I'm the one that didn't understood what really is a "conflict" between files. – jacksonbenete Jul 09 '21 at 19:21
1

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.

matt
  • 515,959
  • 87
  • 875
  • 1,141