In the previous lesson we took the mystery apart: a branch is a 41-byte file with a hash inside, and HEAD is another file that records which branch you are on. Now we are going to put that machinery to work. Ana will finally open feature/task-counter, Bruno will do the same with feature/pending-filter, and the repository will stop having a single line of work.

Creating branches in Git is trivial — you will see that in a second — but moving between them has subtleties worth mastering from the start: what happens to the work you have half finished, why Git sometimes lets you switch and sometimes refuses, and what that detached HEAD state is, the one that turns up when you least expect it and frightens everybody the first time.

We will also settle a question that follows anyone who learned Git before 2019: the difference between git checkout and git switch, and why Git decided to split one command into two.

Contents

  1. git branch: creating without moving
  2. git switch: moving
  3. git switch -c: create and move in one step
  4. git switch versus git checkout
  5. Creating a branch from a specific point in history
  6. git switch -: toggling between two branches
  7. Uncommitted changes when you switch branches
  8. Detached HEAD
  9. The team's repository, now with branches

  1. git branch: creating without moving

Ana is on main, with the repository exactly as we left it:

git log --oneline
c5d9b1e (HEAD -> main) Document installation in the README
4e7f2a9 Add task deletion to the list
8b6d3c2 Add base styles for the list
1a4c8d6 Add initial task manager structure

She wants a branch for the task counter:

git branch feature/task-counter
(no output)

Complete silence. In Git, silence usually means success. Let us see exactly what happened:

ls .git/refs/heads/
feature  main
cat .git/refs/heads/feature/task-counter
c5d9b1e7a3f2d8b4e6c1a9f5d3b7e2c8a4f6d1b9

A new file, holding the hash of the commit Ana was sitting on. (An interesting aside: because the branch name contains a slash, Git has created a directory called feature/ inside refs/heads/. Branch names with slashes are simply file paths; we will come back to naming conventions in lesson 03-06.)

Now the important part:

cat .git/HEAD
ref: refs/heads/main

HEAD has not moved. Ana is still on main. She has created a new pointer but has not placed herself on it:

git status
On branch main
nothing to commit, working tree clean
git branch
  feature/task-counter
* main

The asterisk marks the current branch. There are two branches and both point at the same commit.

gitGraph
   commit id: "1a4c8d6"
   commit id: "8b6d3c2"
   commit id: "4e7f2a9"
   commit id: "c5d9b1e"
   branch task-counter

Rule: git branch <name> creates the pointer and touches nothing else. Not HEAD, not the working tree, not the staging area.

If Ana committed now, the commit would land on main, because that is still where HEAD points. It is a classic mistake: create the branch and forget to switch to it. That is why, in practice, plain git branch is almost never used to start work; the variant in section 3 is.

  1. git switch: moving

To place herself on the branch she has just created:

git switch feature/task-counter
Switched to branch 'feature/task-counter'

And now:

cat .git/HEAD
ref: refs/heads/feature/task-counter

That is all Git did in this case: rewrite the file .git/HEAD. Because both branches pointed at the same commit, the working tree did not change by a single byte.

git status
On branch feature/task-counter
nothing to commit, working tree clean

When the branches point at different commits, git switch does more: as well as rewriting HEAD, it updates the working tree and the staging area so they match the tree of the target commit. In other words, it changes the files on your disk. That is what happens in the three internal operations of a branch switch:

Step What Git does
1 Checks that the switch is safe (see section 7)
2 Updates the working tree and the index to the tree of the target commit
3 Rewrites .git/HEAD with the new branch

Ana writes the task counter in app.js and commits:

git add app.js
git commit -m "Add the pending task counter"
[feature/task-counter 6f2b9d4] Add the pending task counter
 1 file changed, 12 insertions(+)

Look at the name in square brackets: [feature/task-counter 6f2b9d4]. Git is telling you which branch the commit landed on. It is a free check, and one worth reading every time.

Ana then adds the "mark as done" behaviour:

git commit -am "Mark tasks as done on click"
[feature/task-counter 9d1e4b7] Mark tasks as done on click
 2 files changed, 18 insertions(+), 2 deletions(-)

Current state:

git log --oneline --graph --all
* 9d1e4b7 (HEAD -> feature/task-counter) Mark tasks as done on click
* 6f2b9d4 Add the pending task counter
* c5d9b1e (main) Document installation in the README
* 4e7f2a9 Add task deletion to the list
* 8b6d3c2 Add base styles for the list
* 1a4c8d6 Add initial task manager structure

main has stayed put at c5d9b1e, exactly where it was. Ana's work is isolated: she can commit as much as she likes without touching the project's main line.

  1. git switch -c: create and move in one step

Creating a branch and not switching to it is unusual. Hence the shortcut:

git switch -c feature/pending-filter
Switched to a new branch 'feature/pending-filter'

The -c stands for create. It is exactly equivalent to:

git branch feature/pending-filter
git switch feature/pending-filter

This is the form you will use 95% of the time. Plain git branch <name> is reserved for the cases where you want to mark a point in history without abandoning what you are doing (for instance, leaving a bookmark before a risky operation).

One important detail that often gets overlooked: the new branch is created from wherever you are right now. If Ana ran that command while on feature/task-counter, the new branch would start at 9d1e4b7 and drag the task counter along with it, which is not what she wants. For Bruno's work, the correct starting point is main:

git switch main
git switch -c feature/pending-filter

Or, better still, by stating it explicitly in a single command — the form we will see in section 5.

There is a capital-letter variant, -C, which creates the branch or resets it if it already exists:

git switch -C feature/pending-filter

Use it carefully: if the branch already existed pointing somewhere else, -C moves the pointer without asking and you can leave commits orphaned. -c, by contrast, fails with fatal: a branch named '…' already exists, which is almost always what you want.

  1. git switch versus git checkout

If you have seen Git in any tutorial written before 2019, or in most of the ones on the internet today, you will have seen this:

git checkout feature/task-counter      # same as git switch
git checkout -b feature/pending-filter # same as git switch -c

It works. git checkout is not deprecated and is not going away. But since Git 2.23 (August 2019) we have git switch and git restore, and for new work they are the recommended option. The reason is simple: git checkout did far too many different things.

Need Old command Modern command
Switch branch git checkout <branch> git switch <branch>
Create a branch and switch git checkout -b <branch> git switch -c <branch>
Create a branch from a commit git checkout -b <branch> <commit> git switch -c <branch> <commit>
Go to a loose commit git checkout <commit> git switch --detach <commit>
Return to the previous branch git checkout - git switch -
Discard a file's changes git checkout -- <file> git restore <file>
Remove from the staging area git reset HEAD <file> git restore --staged <file>
Recover a file from another commit git checkout <commit> -- <file> git restore --source=<commit> <file>

Look at the last three rows, which are the important ones. git checkout served both to navigate between branches (a harmless, reversible operation) and to destroy working-tree changes (an irreversible one). The same verb for two things of opposite risk.

The potential disaster lay in the ambiguity. Imagine there is a file called report and also a branch called report:

git checkout report

Does it switch branch or discard the file's changes? Git resolved in favour of the branch, and you had to write git checkout -- report (with the double-dash separator) to force the file interpretation. Forget the -- and you could lose work with no warning.

The split introduced in Git 2.23 removes the ambiguity at the root:

  • git switch only works with branches. It cannot touch files.
  • git restore only works with files. It cannot switch branches.

You already used git restore in lesson 02-04, so you know half of the reform already. This lesson gives you the other half.

What to do in practice:

  • Type git switch and git restore in your everyday work.
  • Learn to read git checkout, because you will see it everywhere: old documentation, Stack Overflow answers, continuous integration scripts and colleagues with ingrained habits.
  • Do not convert existing scripts that work purely for the sake of fashion; checkout remains perfectly valid.

  1. Creating a branch from a specific point in history

By default, a new branch starts from HEAD. But you can give it another starting point as a second argument, and that works for both forms of the command:

git branch <name> <start-point>
git switch -c <name> <start-point>

The starting point can be anything Git knows how to resolve to a commit, with all the syntax you learned in lesson 02-06:

# From another branch, without having to switch first
git switch -c feature/pending-filter main

# From a specific commit, by its hash
git switch -c experiment/redesign 8b6d3c2

# From a relative position
git switch -c fix/regression HEAD~2

# From the parent of a specific commit
git switch -c test 4e7f2a9^

The first is Bruno's case and it is the right way to do it: it creates the branch from main without needing to switch to main first. One command instead of two, and no risk of forgetting.

A real case where this saves your afternoon: you have been coding on main for half an hour by mistake, you have made three commits, and you realise they should have gone on a branch. The fix:

# 1. Create the branch right here, with the three commits inside
git branch feature/whatever

# 2. Put main back where it belonged
git switch main
git reset --hard HEAD~3

# 3. Go to the branch, where all the work is intact
git switch feature/whatever

Notice that step 1 uses git branch without switching: it creates the bookmark before moving anything. (git reset --hard is powerful and dangerous; we will study it thoroughly in lesson 09-02. It is safe here because the commits are still reachable from the new branch.)

  1. git switch -: toggling between two branches

When you are reviewing a colleague's work and hopping constantly between two branches, typing the full name gets tiring. A lone dash means "the previous branch":

git switch feature/task-counter
Switched to branch 'feature/task-counter'
git switch -
Switched to branch 'feature/pending-filter'
git switch -
Switched to branch 'feature/task-counter'

It is the same idea as cd - in the shell. It works with branch names as long as feature/export-list-to-csv and saves an enormous number of keystrokes.

Under the bonnet, the dash is an alias for @{-1}, meaning "the branch I was on one switch ago". And there is more:

git switch @{-2}    # the branch I was on two switches ago

This information comes from the reflog, the record Git keeps of every movement of HEAD. It is the same tool that lets you recover apparently lost work, and we will study it in lesson 09-04.

  1. Uncommitted changes when you switch branches

Here is the subtlety that causes the most confusion. What happens to your half-finished work when you switch branches?

The short answer: Git tries to bring it with you, and if it cannot do so without losing anything, it refuses.

The long answer depends on whether the changes affect files that differ between the two branches.

Case A: Git lets you through and brings the changes along

Ana is on feature/task-counter and has started reworking README.md, a file that is identical on both branches:

git status --short
 M README.md
git switch main
M	README.md
Switched to branch 'main'

It worked, and that M README.md line is Git's way of saying that the file arrived modified and is still modified. The change has travelled with Ana to the other branch. It is still uncommitted, in her working tree:

git status --short
 M README.md

This is deliberately useful: it lets you start writing something, realise you are on the wrong branch and put it right without losing anything.

Case B: Git refuses

Now Ana is on main and modifies app.js, which does differ between main and the counter branch (that is where the counter changed it):

git status --short
 M app.js
git switch feature/task-counter
error: Your local changes to the following files would be overwritten by checkout:
	app.js
Please commit your changes or stash them before you switch branches.
Aborting

Git has refused, and rightly so. To place itself on the other branch it would have to overwrite app.js with the version from there, and that would destroy Ana's changes beyond recovery, because they have never been in the object database.

The important thing is that the operation was aborted entirely. You are still where you were and your changes are untouched.

The rule, summarised

Situation Behaviour
The modified file is the same on both branches Switches branch and brings the modifications along
The modified file differs between the two branches Refuses the switch and aborts
There are staged changes to files that do not differ Switches branch and keeps the staging area
There are new untracked files that do not exist on the target Switches branch and leaves them where they are
There are untracked files that do exist on the target Refuses, so as not to overwrite them

The three ways out when Git refuses

  1. Commit. If the work makes sense on the current branch, commit it. If it is half finished, you can make a provisional commit and tidy it up later with git commit --amend (lesson 02-04) or with an interactive rebase (lesson 05-02).

  2. Discard. If the change is worthless, git restore app.js removes it and the branch switch becomes possible.

  3. Stash it. For the realistic case — half-finished work you want to keep but that does not yet deserve a commit — Git has a dedicated tool: git stash, which sets the changes aside in a temporary store, leaves the working tree clean and lets you get them back later with git stash pop:

git stash                          # sets the changes aside
git switch feature/task-counter    # now it can switch
# … whatever you came to do …
git switch main
git stash pop                      # brings the changes back

It is a command with a great deal more depth than this example suggests (several stacked entries, including untracked files, applying on another branch…). We will study it in full in lesson 05-04; for now, just remember it exists and that it is the standard answer to this problem.

Forcing the branch switch (carefully)

There is an option to discard the changes and switch branch anyway:

git switch --discard-changes feature/task-counter

(The old equivalent was git checkout -f.) It is destructive and irreversible: uncommitted changes are not in any Git object, so there is no reflog and no possible recovery. Use it only when you are absolutely certain you want to throw that work away.

  1. Detached HEAD

Remember the previous lesson: HEAD normally contains ref: refs/heads/<branch>. But it can contain a hash directly. That state is called a detached HEAD.

How you get there

The deliberate way is to ask for it:

git switch --detach 4e7f2a9
Note: switching to '4e7f2a9'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

  git switch -c <new-branch-name>

HEAD is now at 4e7f2a9 Add task deletion to the list

Git gives you a whole speech because the state is unusual, not because it is dangerous. In fact that text already explains almost everything you need to know.

The accidental ways of getting there are the frightening ones:

  • git checkout <hash> (without -b), the most common with the old command.
  • git checkout v1.2.0, when landing on a tag — tags point at commits, they are not branches.
  • During a git rebase or a git bisect, which work internally in this state (modules 5 and 6).
  • Landing on origin/main instead of main, a very common mistake we will see in module 4.

What you see

cat .git/HEAD
4e7f2a9c8b1d5e3f7a2c9d4b6e8f1a3c5d7b9e2f

A hash directly, with no ref:. HEAD points at the commit, not at a branch.

git status
HEAD detached at 4e7f2a9
nothing to commit, working tree clean
git branch
* (HEAD detached at 4e7f2a9)
  feature/pending-filter
  feature/task-counter
  main

The working tree holds the files exactly as they were at that commit. It is a perfectly legitimate state: you can build, run the tests, look at old code or even commit.

What it is genuinely for

It is not an accident to be avoided; it is a tool:

  • Reviewing how the project looked at a given moment. Ana wants to see the application as it was on the day of the first commit: git switch --detach 1a4c8d6, open index.html in the browser, done.
  • Testing whether a bug existed in an older version, by landing on different points in history.
  • Experimenting without committing yourself. You can commit in this state, and if the result is no good you go back to a branch and the commits are left orphaned.

The real danger

The danger is not being in this state. It is committing in it and then leaving without a trace:

# In detached HEAD
echo "// performance test" >> app.js
git commit -am "Try a rendering optimisation"
[detached HEAD 7d2f9a1] Try a rendering optimisation
 1 file changed, 1 insertion(+)

That commit 7d2f9a1 is real and is in the object database, but no branch reaches it. If Ana now runs git switch main:

Warning: you are leaving 1 commit behind, not connected to
any of your branches:

  7d2f9a1 Try a rendering optimisation

If you want to keep it by creating a new branch, this may be a good time
to do so with:

 git branch <new-branch-name> 7d2f9a1

Switched to branch 'main'

Git warns you, and even hands you the exact command. But if you do not read that warning and close the terminal, the hash disappears from view and the commit is orphaned. It stays recoverable for a while through the reflog (module 9), but it is not a situation you want.

How to get out without losing anything

If you have committed nothing (you were only looking):

git switch -

Or git switch main, or any branch. There is nothing to save.

If you have committed and want to keep it, create a branch before you leave:

git switch -c experiment/render-optimisation
Switched to a new branch 'experiment/render-optimisation'

Now HEAD points at a branch again, that branch points at 7d2f9a1, and the commit is reachable. Nothing is orphaned any more.

If you have already left and the commit stayed behind, you can still create the branch after the fact, as long as you have the hash (it appears in the warning, and if you lost it, in git reflog):

git branch experiment/render-optimisation 7d2f9a1
graph TD
    A["On a branch<br/>HEAD → ref: refs/heads/main"] -->|"git switch --detach abc123"| B["Detached HEAD<br/>HEAD → abc123"]
    B -->|"git switch -<br/>(nothing committed)"| A
    B -->|"commit here"| C["Orphaned commits<br/>no branch reaches them"]
    C -->|"git switch -c my-branch"| D["Work saved<br/>on a new branch"]
    C -->|"leave without creating a branch"| E["Recoverable only<br/>via the reflog (module 9)"]

  1. The team's repository, now with branches

Let us recap where we are. Ana has her branch with two commits. Bruno has been working on his, with the three commits we saw at the end of module 2 — the pending filter, the styling of completed tasks and the focus fix — now on feature/pending-filter rather than directly on main.

For the rest of the module we will reason as though the three branches lived in one and the same local repository, Ana's. How the team gets work to travel between Ana's laptop and Bruno's MacBook is precisely the subject of module 4; here we concentrate on branch mechanics, which are identical wherever the work comes from.

git branch -v
  feature/pending-filter b2e6d3f Restore field focus after adding a task
  feature/task-counter   9d1e4b7 Mark tasks as done on click
* main                   c5d9b1e Document installation in the README
git log --oneline --graph --all
* b2e6d3f (feature/pending-filter) Restore field focus after adding a task
* 7c1f4a9 Apply style to completed tasks
* 3d5b8e1 Add a pending tasks filter
| * 9d1e4b7 (feature/task-counter) Mark tasks as done on click
| * 6f2b9d4 Add the pending task counter
|/
* c5d9b1e (HEAD -> main) Document installation in the README
* 4e7f2a9 Add task deletion to the list
* 8b6d3c2 Add base styles for the list
* 1a4c8d6 Add initial task manager structure
gitGraph
   commit id: "1a4c8d6"
   commit id: "8b6d3c2"
   commit id: "4e7f2a9"
   commit id: "c5d9b1e"
   branch task-counter
   checkout task-counter
   commit id: "6f2b9d4"
   commit id: "9d1e4b7"
   checkout main
   branch pending-filter
   checkout pending-filter
   commit id: "3d5b8e1"
   commit id: "7c1f4a9"
   commit id: "b2e6d3f"

Three lines of work, three pointers, 123 bytes of total cost. The four base commits sit once in the object database and all three branches share them.

And here is the problem we will solve in the next lesson: both features are finished and tested, but the real project — main — is still exactly where it was a week ago. Nothing Ana and Bruno have done is on the main line.

Common Mistakes and Tips

Mistake 1: git branch <name> and then getting to work. You create the branch, forget the git switch, make five commits and every one lands on main. Two-part prevention: use git switch -c instead of git branch, and read the line in square brackets that git commit prints, which tells you the target branch.

Mistake 2: creating the branch from the wrong place. Running git switch -c fix/urgent while on a half-finished feature branch drags all that unfinished work into the urgent fix. Before creating a branch, check where you are with git branch --show-current, or state the starting point explicitly: git switch -c fix/urgent main.

Mistake 3: panicking about detached HEAD. It is neither an error nor corruption: it is a normal state. The message is long because it explains, not because it is warning you of a disaster. If you were only looking, git switch - and that is that. If you committed, git switch -c <name> before you leave.

Mistake 4: using git checkout -f or --discard-changes to "get unstuck". That command destroys uncommitted changes irreversibly: they are in no Git object and no reflog will help. When Git refuses to switch branch, the right answer is to commit, to discard deliberately with git restore, or to set aside with git stash.

Mistake 5: git switch -C instead of -c. The capital letter resets an existing branch without asking. If the branch already had work on it, the pointer moves and that work is left orphaned. -c fails with a clear error, which in 99% of cases is exactly what you want to happen.

Tip 1: one branch per task, and short-lived. Branches are free, so do not pile work up in them. A branch that lives three weeks accumulates divergence and guarantees conflicts; one that lives two days almost never has any.

Tip 2: turn on Git's autocompletion. With names like feature/pending-filter, typing git switch fea<TAB> is the difference between working comfortably and landing on the wrong branch. The git-completion.bash script ships with Git and many distributions enable it by default.

Tip 3: check where you are before committing. It costs one second:

git branch --show-current

Tip 4: do not reuse old branches for new tasks. It is tempting (I already have the tests branch, I will reuse it), but it ends in histories nobody can follow and merges that drag in changes nobody asked for. Create a new branch and delete the old one once it is no longer useful; tidying up is the subject of lesson 03-06.

Exercises

Exercise 1: creating branches without moving

In a test repository with at least three commits, create two branches — experiment/one and experiment/two — without switching to either of them, such that:

  • experiment/one starts from the current commit.
  • experiment/two starts from two commits back.

Then prove with commands that HEAD has not moved and that both branches point where they should.

Exercise 2: triggering and resolving a refused branch switch

Design the minimal sequence of commands that triggers the error Your local changes to the following files would be overwritten by checkout, then resolve it in three different ways, explaining when you would use each.

Exercise 3: getting out of a detached HEAD without losing work

Deliberately land in a detached HEAD, make a commit there and keep it on a new branch. Then repeat the process but abandon the state without creating the branch, and observe what Git tells you. Which piece of information in the message is essential to note down?

Solutions

Solution 1:

# Starting point: check where we are
git branch --show-current
main
git rev-parse HEAD
c5d9b1e7a3f2d8b4e6c1a9f5d3b7e2c8a4f6d1b9
# Both branches, with git branch (which does NOT move HEAD)
git branch experiment/one
git branch experiment/two HEAD~2

Checking:

git branch -v
  experiment/one  c5d9b1e Document installation in the README
  experiment/two  8b6d3c2 Add base styles for the list
* main            c5d9b1e Document installation in the README

The asterisk is still on main: HEAD has not moved. experiment/one matches main and experiment/two is two commits back, as the exercise asked.

Byte-by-byte verification:

cat .git/HEAD
ref: refs/heads/main
git rev-parse experiment/one experiment/two
c5d9b1e7a3f2d8b4e6c1a9f5d3b7e2c8a4f6d1b9
8b6d3c2e1f5a9c3d7b2e6f8a4c1d9b3e5f7a2c6e

Solution 2:

Two conditions are needed to trigger the error: a file that differs between the two branches, and an uncommitted modification to that same file.

# 1. A branch with a committed change to app.js
git switch -c branch-x
echo "// change from branch X" >> app.js
git commit -am "Change app.js on branch X"

# 2. Back to main, and touch the SAME file without committing
git switch main
echo "// uncommitted change on main" >> app.js

# 3. Try to go back
git switch branch-x
error: Your local changes to the following files would be overwritten by checkout:
	app.js
Please commit your changes or stash them before you switch branches.
Aborting

The three resolutions:

# A) Commit: the work belongs on main and is ready
git commit -am "Add a note in app.js"
git switch branch-x

When: when the change is valid and makes sense on the current branch.

# B) Discard: the change is worthless
git restore app.js
git switch branch-x

When: experiments, debugging traces or anything you do not want to keep. It is irreversible.

# C) Stash: half-finished work I want to keep
git stash
git switch branch-x
# … work on branch-x …
git switch main
git stash pop

When: the most common case in daily work, and the reason git stash exists (lesson 05-04).

Solution 3:

# Detach deliberately
git switch --detach HEAD~2
HEAD is now at 8b6d3c2 Add base styles for the list
# Commit something here
echo "/* test from a detached HEAD */" >> styles.css
git commit -am "Try a style tweak"
[detached HEAD 5f7a9c1] Try a style tweak
 1 file changed, 1 insertion(+)
# Keep it BEFORE leaving
git switch -c experiment/style-tweak
Switched to a new branch 'experiment/style-tweak'
git log --oneline -1
5f7a9c1 (HEAD -> experiment/style-tweak) Try a style tweak

The commit is now reachable from a branch: nothing is orphaned.

Second part, leaving without creating a branch:

git switch --detach HEAD~2
echo "/* another test */" >> styles.css
git commit -am "Another test I am about to abandon"
git switch main
Warning: you are leaving 1 commit behind, not connected to
any of your branches:

  3c9d4a2 Another test I am about to abandon

If you want to keep it by creating a new branch, this may be a good time
to do so with:

 git branch <new-branch-name> 3c9d4a2

Switched to branch 'main'

The essential piece of information is the hash: 3c9d4a2. As long as you have it, recovering the work is a single command:

git branch rescue 3c9d4a2

And if you lose the hash as well, the situation is not hopeless: git reflog stores every movement of HEAD for 90 days by default and lets you find it. That is the subject of lesson 09-04.

Conclusion

You can now create branches and move between them with judgement:

  • git branch <name> creates the pointer and touches nothing else: not HEAD, not the working tree, not the index. Useful for marking a point without abandoning what you are doing.
  • git switch <branch> places you on a branch: it rewrites .git/HEAD and updates the working tree to the target tree.
  • git switch -c <name> [start-point] is the usual way to begin a task: it creates and switches in one step, and accepts any reference as its origin (another branch, a hash, HEAD~2…).
  • git switch and git restore replace git checkout as of Git 2.23, splitting up what used to be a single ambiguous command: navigating between branches and destroying changes no longer share a verb. checkout still works and you will keep seeing it in documentation and scripts.
  • git switch - toggles back to the previous branch, just like cd -.
  • When you switch branches, Git brings uncommitted changes with you if it can do so without losing anything, and aborts cleanly if the file differs between the two branches. The ways out are to commit, to discard with git restore or to set aside with git stash (lesson 05-04).
  • A detached HEAD is HEAD holding a hash instead of ref: refs/heads/…. It is not an error: it is there for inspecting and experimenting. The only precaution is to create a branch with git switch -c before leaving it if you have committed anything.

What comes next

The team now has three branches: main sitting where it was, feature/task-counter with two commits from Ana and feature/pending-filter with three from Bruno. Two finished features that are not in the project.

Isolating the work was half the problem; the other half is bringing it back together. In the next lesson, Merging Branches, Ana will finally integrate both features into main. We will see the two scenarios Git distinguishes — the fast-forward, where the pointer simply moves along, and the three-way merge, which creates a special commit with two parents — how Git finds the common ancestor we studied in the previous lesson, and why it is sometimes worth forcing a merge commit even when one is not needed.

Mastering Git: From Beginner to Advanced

Module 1: Introduction to Git

Module 2: Basic Git Operations

Module 3: Branching and Merging

Module 4: Working with Remote Repositories

Module 5: Advanced Git Operations

Module 6: Git Tools and Techniques

Module 7: Collaboration and Workflow Strategies

Module 8: Git Best Practices and Tips

Module 9: Troubleshooting and Debugging

Module 10: Git in the Real World

© Copyright 2026. All rights reserved