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> points HEAD at <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

git checkout <branch>

git switch <branch>

Create a branch and switch to it

git checkout -b <branch>

git switch -c <branch>

Discard working-tree changes to a file

git checkout — <file>

git restore <file>

Restore a file from another commit

git checkout <sha> — <file>

git restore --source <sha> --staged --worktree <file>

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.

gitGraph commit id: "a1" commit id: "a2" commit id: "b1" commit id: "b2" tag: "v1.2.0" branch detached-HEAD commit id: "c1" type: HIGHLIGHT checkout main commit id: "b3"

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

  1. 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.

  2. Type to filter the list, or switch to the Tags tab in the same dropdown to browse a tagged release instead of a branch.

  3. 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 to https://github.com/<owner>/<repo>/tree/<sha>; this is the read-only web equivalent of a detached HEAD checkout, 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

  1. 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.

  2. 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 switch does.

  3. 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 HEAD at that point, equivalent to git switch --detach.

  4. 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 .idea outside 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 real git stash when you want the Git-native form instead.

In VS Code

  1. 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 detaches HEAD.

  2. The same picker is reachable from the branch name shown in the Status Bar at the bottom left of the window.

  3. 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.

  4. 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>.

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 as git checkout — <name> if a file with that name exists, discarding changes to it. git switch never does this, because it only ever switches branches.

  • Forgetting that a detached HEAD is not a branch: committing there and then running git 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 pop to always succeed: if the top of the working tree conflicts with the stash, resolve the conflict, then run git stash drop explicitly — pop only removes the entry on success.

  • Confusing IntelliJ’s Shelf with git stash: a shelved changelist never leaves the local .idea metadata and is invisible to git stash list and git 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 current HEAD (plain git restore --source <sha> <file> only updates the working tree, leaving it unstaged; add --staged --worktree to match checkout), not a checkout of that commit — the branch and HEAD do not move, only that one file’s content does. See Commits for staging and committing that change afterwards.