"When I see a door with a push sign, I pull first to avoid conflicts" - anonymous
For those that work with git for some time, it is not often that you get to discover new things about it. That is if you exclude the plumbing commands which probably most of us don't know by heart and most likely that's for the better. To my surprise, I recently found out about 2 new additions to the list of high-level commands:
To understand why they came to be, let's first visit our old friend
git checkout is one of the many reasons why newcomers find git confusing. And that is because its effect is context-dependent.
The way most people use it is to switch the active branch in their local repo. More exactly, to switch the branch to which HEAD points. For example, you can switch to the
develop branch if you are on the
git checkout develop
You can also make your HEAD pointer reference a specific commit instead of a branch(reaching the so-called detached HEAD state):
git checkout f8c540805b7e16753c65619ca3d7514178353f39
Where things get tricky is that if you provide a file as an argument instead of a branch or commit, it will discard your local changes to that file and restore it to the branch state.
For example, if you checked out the
develop branch and you made some changes to the
test.txt file, then you can restore the file as it is in the latest commit of your branch with:
git checkout -- test.txt
A method to the madness
If you first look at these two behaviors you might think that it doesn't make any sense, why have one command do 2 different actions? Well, things are a little more subtle than that. If we look at the git documentation, we can see that the command has an extra argument that is usually omitted:
git checkout <tree-ish> -- <pathspec>
<tree-ish> ? It can mean a lot of different things, but most commonly it means a commit hash or a branch name. By default that is taken to be the current branch, but it can be any other branch or commit. So for example if you are in the
develop branch and want to change the
test.txt file to be the version from the
main branch, you can do it like this:
git checkout main -- test.txt
With this in mind, maybe things start to make sense. When you provide just a branch or commit as an argument for
git checkout, then it will change all your files to their state in the corresponding revision, but if you also specify a filename, it will only change the state of that file to match the specified revision.
New kids on the block
So even if things may start to make sense after reading the previous paragraphs, we must admit that it is still confusing especially for newcomers. That's why in version 2.23 of git, two new commands have been introduced to replace the old
git checkout(it is still available, but people new to git should start with these ones preferably). As you would expect, they basically each implement one of the two behaviors described previously, splitting
git checkout in two.
This one implements the behavior of
git checkout when running it only against a branch name. So you can use it to switch between branches or commits.
git switch develop
git checkout you can switch to a commit and transition into a detached HEAD state, by default git switch does not allow that. You need to provide the -d flag:
git switch -d f8c540805b7e16753c65619ca3d7514178353f39
Another difference is that with git checkout you can create and switch to the new branch in one command using the -b flag:
git checkout -b new_branch
You can do the same with the new one, but the flag is -c:
git switch -c new_branch
This one implements the behavior of
git checkout when running it against a file. You can restore, as the name suggests, the state of a file to a specified git revision(the current branch by default)
git restore -- test.txt
These methods are still marked experimental, but for all intents and purposes they are here to stay so by all means I encourage everyone to start using them since they will probably make a lot more sense in your head and also it will make git just a little bit less confusing to new users.
More details about the two commands can be found in their git documentation: