Git Checkout Explained: Switch Branches, Restore Files, and the Detached HEAD

`git checkout` is the Swiss Army knife of Git commands — and that is precisely the problem. In one command you can move `HEAD` to a different branch, create a new branch, jump to an arbitrary commit, or wipe out the uncommitted changes in a file. Four jobs, one verb. Understanding which job you are invoking — and which ones are reversible — is the difference between fluid branch navigation and accidentally deleting an hour of work.

This guide walks through every common use of `git checkout` with runnable examples, explains the dreaded detached `HEAD` state, and shows why modern Git split this overloaded command into the safer `git switch` and `git restore`.

Key Takeaways
• `git checkout` does three unrelated jobs: switching branches, creating branches, and discarding file changes.
• `git checkout ` moves you to another branch; `git checkout -b ` creates and switches in one step.
• `git checkout — file.txt` permanently discards uncommitted changes — there is no undo.
• Checking out a commit or tag directly puts you in detached HEAD; new commits there are orphaned unless you branch.
• Modern Git replaces the ambiguity: use `git switch` to change branches and `git restore` to change files.

What does git checkout actually do?

At its core, `git checkout` updates your working tree to match a target — a branch, a commit, a tag, or even a single file. It manipulates two things depending on the arguments you give it:

  1. `HEAD` — the pointer to “where you are.” Switching branches or commits moves `HEAD`.
  2. The working tree — the files on disk. Restoring a file from a commit overwrites what is currently on disk.

The confusion comes from the fact that the *same command* either safely repositions you (navigation) or destructively overwrites files (restoration). The arguments decide which. Let’s go through each use.

If you want the bigger picture of running Git on infrastructure you actually control, this article is part of our complete guide to hosting for developers.

How do you switch branches with git checkout?

The most common use. To move from your current branch to an existing one:

“`bash

git checkout main

“`

Git updates `HEAD` to point at `main` and rewrites your working tree to match that branch’s latest commit. If you have uncommitted changes that would be overwritten, Git refuses and warns you:

“`bash error: Your local changes to the following files would be overwritten by checkout: src/app.js Please commit your changes or stash them before you switch branches. “`

That guardrail is important: switching branches will *not* silently destroy committed-but-unstaged work. Commit or `git stash` first.

For more on managing the branches you switch between, see .

How do you create and switch to a new branch?

Use the `-b` flag to create a branch and move onto it in a single step:

“`bash

git checkout -b feature/login

“`

You can also branch from a specific starting point rather than your current `HEAD`:

“`bash

git checkout -b hotfix/patch origin/main “`

This is equivalent to running `git branch hotfix/patch origin/main` followed by `git checkout hotfix/patch`, but in one line. Once you have created the branch, renaming it later is a separate operation — see .

How do you check out a specific commit (and what is detached HEAD)?

You can point `HEAD` directly at any commit by its hash:

“`bash

git checkout a1b9f4c “`

Git will print a wall of text that scares everyone the first time they see it:

“`bash Note: switching to ‘a1b9f4c’.

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. “`

Detached HEAD means `HEAD` points at a commit directly instead of at a branch. Nothing is broken — you can read files, run the code, even commit. The danger is that any commits you make here belong to *no branch*. If you switch away, those commits become unreachable and eventually get garbage-collected. They are orphans.

To get back to safety, simply switch to a real branch:

“`bash

git checkout main “`

If you made commits in detached HEAD that you want to keep, create a branch *before* leaving:

“`bash

git checkout -b experiment-from-old-commit “`

Now those commits live on `experiment-from-old-commit` and will not be lost. This is the single most common way developers “lose” commits — and the single easiest to avoid.

How do you restore or discard changes to a file?

Here is where `git checkout` switches from navigation to destruction. To throw away uncommitted changes in a file and restore it to the last committed version:

“`bash

git checkout — src/config.js “`

The `–` separates the command’s options from file paths, telling Git “everything after this is a filename, not a branch.” After this runs, your edits to `src/config.js` are gone. There is no confirmation prompt, no trash bin, and no reflog entry for working-tree changes that were never committed. If you spent an hour editing that file and never staged or committed it, that hour is unrecoverable.

This is the operation people fear, and rightly so. The fix that modern Git provides — `git restore` — does exactly the same thing but with a name that screams “I am touching files, not branches.”

`git checkout` earned its reputation as confusing precisely because it does three *unrelated* jobs under one name — switching branches, creating branches, and destroying uncommitted file changes. The same verb can navigate safely or irreversibly delete your work depending entirely on its arguments. That overload is exactly why Git introduced `git switch` and `git restore`: to split the safe navigation from the dangerous file operations. The practical lesson is simple — when you mean “change branch,” reach for `switch`; when you mean “throw away changes,” reach for `restore`. Relying on `checkout`’s ambiguity is how people accidentally `git checkout — file` away an afternoon of work, because the command that navigates and the command that destroys are spelled identically.

How do you check out a file from another branch or commit?

You don’t have to restore a file from your *current* branch — you can pull a single file’s contents out of any other branch or commit while staying where you are:

“`bash

git checkout develop — src/utils.js

git checkout a1b9f4c — src/legacy.py “`

Your `HEAD` does not move. Only `src/utils.js` is overwritten with the version from `develop`, and the change lands in your working tree (and is staged) for you to commit. This is genuinely useful — recovering one file from a deleted feature, or copying a config from another branch without merging.

How do you check out a tag?

Tags work like commits — checking one out lands you in detached HEAD, because a tag points at a fixed commit, not a moving branch:

“`bash

git checkout v2.1.0

git checkout -b release-2.1-patch v2.1.0 “`

This is the standard pattern for hotfixing a past release: check out the tag, branch from it, fix, and release.

Old checkout vs. new switch and restore

Git 2.23 (released 2019) introduced `git switch` and `git restore` to break up `checkout`’s overloaded responsibilities. `checkout` still works exactly as before — nothing is deprecated — but the new commands make intent explicit.

Task Old (checkout) New (clearer)
Switch to a branch `git checkout main` `git switch main`
Create + switch to branch `git checkout -b feat` `git switch -c feat`
Switch to a commit (detached) `git checkout a1b9f4c` `git switch –detach a1b9f4c`
Discard file changes `git checkout — file.txt` `git restore file.txt`
Restore file from a branch `git checkout dev — file.txt` `git restore –source dev file.txt`
Unstage a file `git checkout` (n/a) `git restore –staged file.txt`

The split has a real safety benefit: `git restore` never touches branches, and `git switch` never destroys file contents. The command’s *name* now matches its *consequence*. For more on the branch-focused replacement, see .

Quick reference: task to command

What you want to do Command
Switch to an existing branch `git checkout `
Create and switch to a new branch `git checkout -b `
Branch from a specific start point `git checkout -b `
Inspect a past commit (detached) `git checkout `
Inspect a tag (detached) `git checkout `
Return to a branch from detached HEAD `git checkout `
Discard uncommitted changes to a file `git checkout — `
Restore a file from another branch `git checkout `
Restore a file from a commit `git checkout `

If you are still building the fundamentals, the broader workflow context lives in .

Run real Git workflows on infrastructure you control

Branching, checking out commits, and restoring files are easy on your laptop — the friction starts when you need the same Git workflow on a *server*. Shared hosting that gives you a cramped panel and no shell access cannot do it.

DarazHost VPS and dedicated servers give developers full SSH and Git on the server itself. You can clone repositories, switch branches, check out a tag for a hotfix, and run git-based deploy workflows directly in your real environment — with full root control, guaranteed CPU and memory resources rather than a noisy shared pool, and 24/7 support when a deploy hook misbehaves at 2 a.m. It is the difference between *describing* your environment and *owning* it. When your `git checkout` and `git pull` run on the same box that serves production, your deploy pipeline stops being a guessing game.

Frequently asked questions

Is `git checkout` deprecated now that `git switch` exists? No. `git checkout` is fully supported and will be for the foreseeable future. `git switch` and `git restore` were added as clearer alternatives for two of checkout’s jobs, but checkout itself retains all its functionality. Use whichever you prefer — many developers still default to `checkout`.

Can I recover changes after running `git checkout — file.txt`? Generally no. Discarding uncommitted, unstaged changes is irreversible — those edits were never recorded in Git’s object database. The exception: if you had previously *staged* the file with `git add`, the staged version may still be recoverable via `git fsck` and dangling blobs, but do not rely on it. Commit early and often.

What is the difference between `git checkout` and `git reset`? `git checkout` moves `HEAD` and updates the working tree without rewriting branch history. `git reset` moves the *branch pointer* itself, which can rewrite history and change what commits a branch includes. Checkout navigates; reset rewinds.

How do I get out of detached HEAD without losing my commits? Run `git checkout -b ` *before* switching away. This anchors your detached-HEAD commits to a real branch so they survive. If you have already switched away and lost them, `git reflog` can often help you find the orphaned commit hash and check it out again.

Why does Git refuse to switch branches sometimes? Git blocks a branch switch when uncommitted changes in your working tree would be overwritten by the target branch. Commit your changes, stash them with `git stash`, or discard them — then the switch will proceed.

About the Author

Leave a Reply