Checkout, Switch and Restore
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production; see the section bibliography for the reference material consulted while preparing these pages. |
git checkout is one of Git’s oldest and most overloaded commands: depending on its arguments it switches the
branch HEAD points to, or it overwrites files in the working tree with a version from the index or another
commit. Git 2.23 (2019) introduced git switch and git restore as narrower, less error-prone replacements for
those two jobs, without removing checkout itself. This page covers all three commands, detached HEAD,
switching with uncommitted changes, and checking out branches, tags, commits and pull requests from the command
line, GitHub.com, IntelliJ IDEA and VS Code. It is written against Git 2.55.x, GitHub CLI 2.101.x, IntelliJ IDEA
2026.2, a current stable VS Code with the GitHub Pull Requests and Issues extension, and github.com as of September
2026, with a repository whose default branch is main.
Concept
git checkout was designed to do two unrelated things depending on how it is called:
-
git checkout <branch>pointsHEADat<branch>and rewrites the working tree and index to match it — a branch switch. -
git checkout — <file>overwrites<file>in the working tree with the version from the index (or, with a commit given, from that commit) — a file restore, and it discards uncommitted changes to that file without asking twice.
The same verb, checkout, covers both, and the only thing separating "switch to that branch" from "throw away my
changes to that file" is whether the argument happens to be a branch name or a path. That ambiguity is real: typing
a branch name that does not exist, with -- accidentally omitted or a typo in a path, can silently do the wrong
one of these two very different operations. git switch and git restore, added in Git 2.23, split checkout
into two single-purpose commands with clearer names and safer defaults, while git checkout itself is unchanged
and still works exactly as before for scripts and habits built around it.
| Job | Old form (checkout) |
New form (switch / restore) |
|---|---|---|
Switch to an existing branch |
|
|
Create a branch and switch to it |
|
|
Discard working-tree changes to a file |
|
|
Restore a file from another commit |
|
|
switch and restore are not aliases for pieces of checkout — they are separate commands with their own
option sets, and each refuses by default to do what the other one does: git switch will not touch an unqualified
file path, and git restore never moves HEAD. Both were marked experimental until Git 2.51 (2025) and are now
stable; they are the form the rest of this page, and the sibling pages in this section,
use in examples. See Commits for git restore --staged, which
unstages a file rather than switching branches or discarding a file, and
Branches for what a branch and HEAD actually are.
Detached HEAD
Checking out a tag or a specific commit — rather than a branch — puts HEAD in detached state: it points
directly at a commit instead of at a branch name that would move forward with new commits.
git switch --detach v1.2.0 # HEAD now points directly at the commit tagged v1.2.0
git log --oneline -1 # confirms which commit HEAD is on
Derived from git switch and
Pro Git 2nd ed., 2.6 Tagging. git checkout v1.2.0 (without --detach) does the same thing for a tag or commit, printing a warning
about detached HEAD; git switch requires --detach explicitly, which is exactly the kind of accidental
detachment the split was meant to prevent.
In the diagram, HEAD was detached at the commit tagged v1.2.0, one commit behind the tip of main. Any new
commit made while detached (c1 in the diagram, which grows off v1.2.0 rather than off main) is written
normally, but while HEAD stays detached those commits belong to no branch: nothing references them once
HEAD moves elsewhere, so Git’s garbage collector will eventually delete them (a grace period of about 30 days
by default, via the reflog). To get back to where you were:
git switch - # back to the branch (or commit) checked out immediately before
git switch main # or name the branch explicitly
Derived from git switch. If you made commits while detached and want to
keep them, create a branch at the current detached position before switching away — once you switch to another
branch first, the detached commits become unreachable and harder to find:
git switch -c hotfix-from-tag # creates "hotfix-from-tag" at the current (detached) commit and switches to it
Derived from Pro Git 2nd ed., 2.6 Tagging. See Tags and Releases for how
tags such as v1.2.0 are created and why checking one out lands in detached HEAD rather than on a branch.
Command line
Checking out a remote branch
git switch <name> (and, historically, git checkout <name>) recognizes when <name> does not exist locally but
matches exactly one remote-tracking branch such as origin/<name>, and auto-creates a local branch that tracks
it:
git fetch origin
git switch feature/reporting # no local "feature/reporting" yet, but origin/feature/reporting exists
git branch -vv # confirms it now tracks origin/feature/reporting
Derived from git switch (the --guess/--no-guess option controls this
behavior; it is on by default). See
Fetch, Pull and Push for what a remote-tracking
branch is and how git fetch populates it.
Switching with uncommitted changes
Git refuses a branch switch that would silently overwrite uncommitted changes to a file that differs between the current branch and the target branch, because the working tree cannot represent both a checked-out branch and leftover edits meant for a different one without losing information:
git switch other-branch
# error: Your local changes to the following files would be overwritten by checkout: ...
# Please commit your changes or stash them before you switch branches.
Derived from git switch. The usual fix is git stash, which saves the
working tree and index as a new stash entry and reverts them to match HEAD, so the switch can proceed on a clean
tree:
git stash push -u -m "wip: report filters" # -u also stashes untracked files
git switch other-branch
# ... do work on other-branch ...
git switch - # back to the original branch
git stash list # see saved stashes, most recent first
git stash pop # re-apply the top stash and remove it from the list
Derived from git stash and
Pro Git 2nd ed., 7.3 Stashing and Cleaning.
git stash pop fails, leaving the stash entry in place, if reapplying it would itself conflict — resolve the
conflict and then git stash drop the entry manually. git stash apply reapplies without removing the entry,
useful for testing the same stash against more than one branch.
An alternative to stashing is git switch -m (--merge), which carries the local changes across the switch by
performing a three-way merge between the two branches and the changes in the working tree, rather than saving
them aside first:
git switch -m other-branch # merges local changes into the target branch's version of each file
Derived from git switch. This can produce the same kind of conflict markers
as a real merge if the same lines were touched on both sides; git stash followed by an explicit git switch and
git stash pop makes that conflict resolution an separate, later step instead of happening inline with the
switch.
Checking out a pull request locally
gh pr checkout fetches a pull request’s branch and switches to it in one step, whether the PR comes from a
branch in the same repository or a fork:
gh pr checkout 42 # by pull request number
gh pr checkout feature/reporting # by branch name, if unambiguous
gh pr checkout 42 --branch pr-42-review # use a custom local branch name
Derived from gh pr checkout. See
Pull Requests for opening, reviewing and merging pull
requests; that page covers the rest of the gh pr subcommands.
On GitHub.com
-
Open the repository and use the branch/tag selector above the file listing (it shows the current branch name, for example
main) to switch which branch or tag’s files, commits and file tree the page displays. -
Type to filter the list, or switch to the Tags tab in the same dropdown to browse a tagged release instead of a branch.
-
To browse the repository exactly as it looked at one specific commit, open that commit (from the commit history, a pull request, or a permalink) and use the
<>Browse repository at this point in the history button, or navigate tohttps://github.com/<owner>/<repo>/tree/<sha>; this is the read-only web equivalent of a detachedHEADcheckout, since github.com never has an uncommitted working tree to switch away from.
Derived from GitHub
Docs — Navigating code on GitHub. There is no equivalent of git stash, git switch -m or the local-only
"uncommitted changes block a switch" error on github.com, because the web UI has no working tree to carry changes
in.
In IntelliJ IDEA
-
Open the branch-name (VCS) widget in the main window header (or Git > Branches…) to open the branches popup, which lists local branches, tags and remote branches side by side with a search field.
-
Select a branch and choose Checkout to switch the current working tree to it; IntelliJ performs a "smart checkout": if there are uncommitted changes it offers to shelve them first, checks out the branch, and then offers to unshelve them back on top, rather than blocking the switch outright the way plain
git switchdoes. -
Choose Checkout Tag or Revision… from the same popup (or Git > Checkout Tag or Revision…) and enter a tag name, branch name or commit SHA to detach
HEADat that point, equivalent togit switch --detach. -
IntelliJ’s own Shelve Changes (Git > Uncommitted Changes > Shelve Changes, or the Shelf tab of the Commit tool window) is a separate mechanism from
git stash: shelved changes are stored as patch files under.ideaoutside Git’s own object database and are visible only to the IDE, while a stash is a real Git ref (refs/stash) visible to the command line and any other Git tool on this machine. Neither is pushed or cloned, so both stay local to this copy of the repository. Both solve the same problem — setting changes aside to switch branches cleanly — and IntelliJ’s own Git > Uncommitted Changes > Stash Changes… action drives realgit stashwhen you want the Git-native form instead.
Derived from JetBrains — Manage Git branches and JetBrains — Shelve or stash your changes.
In VS Code
-
Open the Command Palette (Ctrl+Shift+P on Windows/Linux, Cmd+Shift+P on macOS) and run Git: Checkout to…, which opens a branch/tag picker equivalent to
git switch <name>; picking a tag or a commit SHA from the same picker detachesHEAD. -
The same picker is reachable from the branch name shown in the Status Bar at the bottom left of the window.
-
Run Git: Stash from the Command Palette to stash the current changes, and Git: Pop Stash… to reapply and remove a stash entry from the list; Git: Apply Stash… reapplies without removing it, matching
git stash apply. -
With the GitHub Pull Requests and Issues extension installed, open the GitHub Pull Requests view in the Activity Bar, find the pull request under Created By Me / Waiting For My Review or by searching, and use its Checkout action (or right-click a pull request and choose Checkout Pull Request) to fetch and switch to it locally, equivalent to
gh pr checkout <number>.
Derived from VS Code — Git branches and worktrees and the GitHub Pull Requests and Issues extension page.
Common mistakes / tips
-
Running
git checkout <name>out of habit when<name>is actually a typo for a branch that does not exist: Git silently treats it asgit checkout — <name>if a file with that name exists, discarding changes to it.git switchnever does this, because it only ever switches branches. -
Forgetting that a detached
HEADis not a branch: committing there and then runninggit switch main(or any other branch) without first creating a branch at the detached commit leaves those commits reachable only from the reflog, and they are pruned after the default grace period unless a branch or tag is created to keep them. -
Expecting
git stash popto always succeed: if the top of the working tree conflicts with the stash, resolve the conflict, then rungit stash dropexplicitly —poponly removes the entry on success. -
Confusing IntelliJ’s Shelf with
git stash: a shelved changelist never leaves the local.ideametadata and is invisible togit stash listandgit stash pop(and, like a stash, to any other clone of the repository). -
Using
git checkout <sha> — <file>and forgetting the result is staged automatically as a change against the currentHEAD(plaingit restore --source <sha> <file>only updates the working tree, leaving it unstaged; add--staged --worktreeto matchcheckout), not a checkout of that commit — the branch andHEADdo not move, only that one file’s content does. See Commits for staging and committing that change afterwards.