Topic · Branching & Integration

Switching & Checkout

Moving between branches and commits is the most common Git operation after committing, and the one most polluted by history. Modern Git splits the job in two: git switch moves your HEAD between branches, and git restore pulls files out of history. The older git checkout still does both, and you will keep seeing it everywhere.

The historical checkout problem

For most of Git's life, git checkout was the Swiss Army knife for moving things around. It switched branches, created branches, detached HEAD, restored individual files from history, threw away uncommitted edits, and a few other things besides. The argument that followed git checkout changed the meaning of the command entirely, and beginners learned the hard way that git checkout main and git checkout main.py were doing different jobs.

In August 2019, Git 2.23 introduced two new commands designed to split that responsibility. The release notes describe the goal directly: to clearly separate the responsibilities of git checkout into two narrower categories: operations which change branches and operations which change files.

  • git switch handles branches and HEAD movement.
  • git restore handles file contents in the working tree and index.

Both were marked experimental at introduction and have been stable for years now. git checkout still exists and is not going anywhere. You should write new commands with switch and restore, but you should also be able to read checkout in tutorials, scripts, and your own muscle memory without flinching.

Heuristic

If the argument names a branch or a commit, you want git switch. If the argument names a file, you want git restore. git checkout tries to guess which you meant, and the guess is occasionally wrong.

Switching branches with git switch

The base form does exactly what its name says.

switch to an existing branchshell
git switch feature-login

HEAD moves to point at the named branch. The working tree and index are updated so the files on disk match the snapshot at the tip of that branch. Files that are identical in both branches are left alone, which is why switching is usually fast and unobtrusive.

git switch <branch>

Move HEAD to <branch> and update the working tree and index to match. Refuses to run if uncommitted changes would be silently overwritten.

There is a small but useful shortcut: - means "the previous branch," in the same spirit as cd - in a shell.

jump back to the previous branchshell
git switch -

Use it when you are bouncing between a feature branch and the main line to compare behavior or rerun tests.

Refusing to clobber your work

If switching would overwrite local changes you have not committed, Git refuses up front rather than losing them. The message looks like this:

guarded switchshell
$ git switch main
error: Your local changes to the following files would be overwritten by checkout:
        src/login.js
Please commit your changes or stash them before you switch branches.
Aborting

You have three reasonable responses, in order of preference:

  • Commit the changes, even as a scratch commit on the current branch you can clean up later.
  • Stash them with git stash if they are not ready to commit but you want to come back to them.
  • Discard them with git switch --discard-changes <branch> if you genuinely do not want them.

If the changes touch files that do not differ between the two branches, Git happily carries them across with you. That is the case nine times out of ten: you are working on a single feature, you want to peek at another branch for a moment, and your work-in-progress travels along.

Creating branches as you switch

The -c flag (for create) starts a new branch from the current commit and switches to it in one step. This is the everyday "start a new feature" command.

create and switchshell
git switch -c add-search-bar

You can give a starting point explicitly, in which case the new branch begins at that commit, branch, or tag instead of the current HEAD.

branch from somewhere elseshell
git switch -c hotfix-1.4.1 v1.4.0
git switch -c review-pr-273 origin/feature/payments

The first creates a hotfix branch starting at the v1.4.0 tag. The second creates a local branch tracking a colleague's remote branch, which is how you typically review someone else's pull request locally.

-c versus -C

Lowercase -c refuses if the branch name already exists. Uppercase -C force-creates: if the branch exists, it gets reset to the new starting point. -C rewrites a label and is destructive to anyone who was relying on the old position of that branch. Prefer -c until you specifically need to reset a branch.

What happens to the working tree

It helps to be exact about what changes when you switch. Git compares the snapshot at the tip of the target branch with the snapshot at HEAD and updates only the files that differ. Unchanged files are not touched on disk at all, which matters for build caches, editor state, and large repositories.

State of a file What git switch <branch> does
Same content in both branches. Leaves the file alone.
Differs between branches, clean working tree. Replaces the working file with the target branch's version.
You have uncommitted edits, file unchanged between branches. Carries your edits across the switch.
You have uncommitted edits, file differs between branches. Refuses to switch. You see the local changes would be overwritten error.

This is the model that justifies the previous section's three options. Commit, stash, or discard is the menu because those are the three ways to clear the conflict that triggered the refusal.

Detached HEAD

Normally HEAD points at a branch, and the branch points at a commit. Move HEAD by switching branches, and the branch label travels along when you commit. There is a second mode where HEAD points directly at a commit, with no branch in between. Git calls this a detached HEAD.

On a branch HEAD main commit a1b2c3 snapshot Committing here advances the branch label and HEAD together. Detached HEAD HEAD commit 9f4e21 no branch label new commits live in nobody's branch

You enter detached HEAD on purpose with --detach, or accidentally by passing a commit identifier to the older git checkout command:

inspecting an old commitshell
git switch --detach 9f4e21d
# legacy form, still common:
git checkout 9f4e21d

This is genuinely useful. You can check out a tagged release, an old commit, or someone else's pinned reference, build the project, and poke around without disturbing any branch. Reading old states is what detached HEAD is for.

Watch out

Commits you make while in detached HEAD belong to no branch. The moment you git switch back to a normal branch, those new commits become unreachable from any label. Git's reflog will remember them for a while, but they will eventually be garbage-collected. If you committed by accident here, recover them now with git switch -c rescue before moving away.

The escape route is one command:

rescue commits made in detached HEADshell
git switch -c rescue-experiment

That creates a real branch at the current commit and switches to it. Your detached commits now have a name attached. From there you can keep going, merge them back, or delete the branch if you decide the experiment was not worth keeping.

If you only wanted to look around and made no commits, leaving is easy: just git switch <branch> to any normal branch and HEAD reattaches.

Restoring files from another ref

Switching moves your whole HEAD. Sometimes you do not want that. You want one file the way it looked at a particular commit, or you want to undo your unstaged edits to one file. That is what git restore is for.

restore one file from another commitshell
git restore --source main --staged --worktree src/config.js

That command says: take src/config.js as it exists at main, put it in the index, and put it in the working tree. Your HEAD does not move. No branch is touched. You have just one file overwritten with another version.

The flags say which copy you want to change.

Flags Default source Result
git restore <path> The index Discard unstaged edits in the working tree, returning to the staged version.
git restore --staged <path> HEAD Unstage: replace the index entry with the HEAD version, leaving the working file alone.
git restore --source <ref> <path> <ref> Replace the working file with the version at that ref.
git restore --source <ref> --staged --worktree <path> <ref> Replace both the index and the working file with the version at that ref.

The default source for --staged is HEAD, and the default for a bare git restore with no --source is the index. That asymmetry exists because the two most common jobs are "throw away my unstaged edits to this file" and "unstage this file."

Pitfall

Both git restore <file> and the older git checkout -- <file> silently destroy your unstaged edits to that file. There is no confirmation, no stash, no reflog entry. If you ran one of these by mistake and have not saved that work somewhere else, it is gone. Check git status twice before running either.

Translating old checkout commands

You will read git checkout in old tutorials, blog posts, accepted Stack Overflow answers, and your own shell history forever. Here is the translation table so you can match what you see to what you would write today.

Older checkout form Modern equivalent Job
git checkout main git switch main Switch to an existing branch.
git checkout -b feature git switch -c feature Create a branch and switch to it.
git checkout -b feature origin/feature git switch -c feature origin/feature Create a local branch tracking a remote-tracking ref.
git checkout -B feature start git switch -C feature start Force-create or reset a branch and switch to it.
git checkout 9f4e21d git switch --detach 9f4e21d Detach HEAD at a specific commit.
git checkout - git switch - Switch to the previous branch.
git checkout -- src/config.js git restore src/config.js Discard unstaged edits to a file.
git checkout main -- src/config.js git restore --source main --staged --worktree src/config.js Replace one file with its version at another ref.
git reset HEAD src/config.js git restore --staged src/config.js Unstage a file.

You do not have to migrate. The old forms still work and will keep working. The benefit of the new forms is that the command name tells you, before you read the arguments, whether you are about to move HEAD or to overwrite files.

Common pitfalls

Pitfall 1

Forgetting that detached HEAD eats commits. If you check out a commit by hash, make a change, and switch away, the commit you made is orphaned. Always run git switch -c <name> to give detached work a real branch before leaving.

Pitfall 2

Confusing git checkout <name> when <name> matches both a branch and a file. Git tries to guess and usually picks the branch. If you needed the file, use git restore, or insert the -- separator so Git stops guessing: git checkout -- <name>.

Pitfall 3

Using git switch -C when you meant -c. Lowercase creates only if the branch is new. Uppercase resets an existing branch in place, throwing away the position of someone else's work in progress. Type carefully.

Pitfall 4

Running git checkout -- <file> or git restore <file> on edits you have not saved elsewhere. The command does not prompt. It does not stash. There is no undo for the working tree. If in doubt, git stash first.

Worked examples

Example 1: Start a feature branch from the current tip

You are on main at the latest commit and want to add a search bar without disturbing main.

start a featureshell
git switch main
git pull
git switch -c add-search-bar

You first make sure main is up to date, then create add-search-bar from its tip. HEAD now points at the new branch. Commits you make stay on this branch; main is untouched.

Example 2: Try to switch with uncommitted work and recover

You have edited src/login.js on feature-login and try to switch to main to compare. Git refuses because the file differs between branches.

guarded switch and stashshell
git switch main
# error: Your local changes to the following files would be overwritten by checkout:
#         src/login.js
git stash push -m "wip login button"
git switch main
# ... look around ...
git switch -
git stash pop

Stash, switch, do your inspection, switch back with -, and pop the stash to recover your work in progress on feature-login.

Example 3: Inspect a tagged release in detached HEAD, then escape safely

You want to reproduce a bug reported against version v1.4.0.

detached inspectionshell
git switch --detach v1.4.0
# build, run, observe...
git switch -c hotfix-1.4.1

The first command puts you at the exact snapshot tagged v1.4.0 with HEAD detached. If you commit anything here, the second command turns those commits into a real branch named hotfix-1.4.1 so they cannot be lost. If you made no commits, you could instead run git switch main to leave detached HEAD without creating a branch.

Example 4: Restore a single file from another branch

You are working on feature-rewrite and decide the rewritten config.js was a mistake. You want the version from main back, but you want to keep all your other edits on this branch.

restore one fileshell
git restore --source main --staged --worktree src/config.js
git status

The first command overwrites both the index and the working tree copy of src/config.js with the version at main. HEAD does not move; your other in-progress files are untouched. The status check shows config.js as modified relative to your branch's previous tip, which is exactly what you want before committing this revert.

Sources & further reading

  • git-switch documentation Reference Git project

    Official reference for git switch, including every flag, the rules for refusing to switch with local changes, and the merge-on-switch option.

  • git-restore documentation Reference Git project

    Official reference for git restore: source semantics, the meaning of --staged and --worktree, and the interactive patch mode.

  • git-checkout documentation Reference Git project

    Full reference for the historical git checkout command, with the disambiguation rules between branch, commit, and file arguments.

  • Highlights from Git 2.23 Article GitHub Blog

    The release announcement that introduced git switch and git restore as experimental commands to split git checkout's responsibilities.

  • Pro Git, 3.1: Branches in a Nutshell Textbook Scott Chacon and Ben Straub

    Foundational chapter on what HEAD is, what a branch label is, and how switching moves them. Reach here when the mental model feels shaky.

  • Git Checkout Tutorial Tutorial Atlassian

    Long-form tutorial covering branch checkout, detached HEAD, and file checkout with worked walkthroughs. Useful for a second pass framed around checkout.