skip to content

Why did Git split git checkout into the git switch and git restore commands?

level: middleimportance: should knowfreq 52%

answer

  1. One command, two unrelated jobs
  2. Branch name or file name — which did you mean?
  3. The dash-dash separator existed for a reason
  4. One half moves a pointer, the other overwrites bytes
  5. Old command still works, nothing removed

basics

~20 s

git checkout did two unrelated jobs: moving HEAD to another branch or commit, and overwriting files from a tree. Git 2.23 split them so switch handles refs and restore handles file content, removing an ambiguous and silently destructive interface.

solid answer

~40 s

`git checkout` was overloaded. `git checkout main` moves HEAD to a branch, `git checkout -- main` overwrites a *file* named `main` from the index, and `git checkout <commit> -- <path>` pulls one path out of history. Three very different operations, distinguished only by argument shape and a `--` separator, and one of them silently destroys uncommitted edits. Git 2.23 introduced two focused commands: `git switch` for refs (`-c` to create, `--detach` for a detached HEAD, `-` for the previous branch) and `git restore` for content (`--staged`, `--worktree`, `--source=<tree>`). Each rejects the other's arguments, so a typo cannot turn a branch change into a file wipe. `git checkout` is not deprecated and still works everywhere; the split is about a safer, clearer interface, not new capability.

code

bash · 10 lines
bash
# ref operations -> switch
git switch main
git switch -c feature/login
git switch --detach v2.1.0
git switch -            # back to the previous branch

# content operations -> restore
git restore --staged app/config.yml
git restore app/config.yml
git restore --source=HEAD~2 --staged --worktree app/config.yml

go deeper

for a junior

Know that switch changes branches and restore changes file content, and that both are newer, clearer spellings of things git checkout already did.

for a middle

Explain the ambiguity concretely: a branch and a file sharing a name, the -- separator, and the fact that the path form silently destroys uncommitted edits.

for a senior

Talk about interface risk in tooling — commands whose destructiveness depends on argument shape cause real data loss, and the fix was separation plus explicit target flags, not a warning prompt.

for a principal

Own the migration tradeoff: recommending switch/restore in team docs and reviews while keeping checkout in scripts for portability, and how you retire a habit without breaking existing automation.

## The problem: one command, two jobs From the beginning `git checkout` did two conceptually unrelated things. **Job one — move HEAD.** `git checkout main` repoints HEAD at a branch and updates the index and working tree to that commit. `git checkout -b feature` creates a branch and moves onto it. `git checkout <sha>` leaves you on a detached HEAD. **Job two — overwrite file content.** `git checkout -- src/app.js` copies that path out of the index into the working tree, destroying local edits. `git checkout HEAD~2 -- src/app.js` copies it out of an old commit into both the index and the working tree. The two jobs are distinguished only by the shape of the arguments. That produced real problems: - **Ambiguity.** If a file and a branch share a name, `git checkout main` is genuinely ambiguous. Git resolves it as the branch, which is why you had to remember the `--` separator to mean "this is a path". Beginners rarely did. - **Silent destruction.** The path form has no confirmation and no undo. Working-tree edits were never stored as Git objects, so once overwritten they cannot be recovered from the reflog or from `git fsck`. - **Unlearnable flags.** `-b`, `-B`, `--orphan`, `--detach`, `--track`, `--patch`, `--ours`, `--theirs`, `--merge` all lived on one command, most of them meaningful for only one of the two jobs. ## The split, in Git 2.23 **`git switch`** owns refs and nothing else: - `git switch <branch>` — move to an existing branch. - `git switch -c <new>` — create and move (`-C` to force-reset an existing branch). - `git switch --detach <commit>` — detach HEAD deliberately, so you cannot land there by accident. - `git switch -` — go back to the previously checked-out branch. Because `switch` accepts no pathspec, it can never touch a single file, and it refuses to detach HEAD onto a raw commit unless you asked with `--detach`. **`git restore`** owns file content and never moves a ref: - `git restore <path>` — restore the working tree from the index (the old `git checkout -- <path>`). - `git restore --staged <path>` — restore the index from HEAD, i.e. unstage. - `git restore --staged --worktree <path>` — both, defaulting to HEAD. - `git restore --source=<tree> <path>` — read from any commit or tree instead of the default. - `git restore -p <path>` — choose hunks interactively. Because `restore` requires a pathspec and takes explicit target flags, what it will overwrite is visible in the command line itself. ## What did not change `git checkout` still works and is not deprecated; countless scripts, tutorials and muscle memories depend on it, and Git's compatibility discipline keeps it. The split adds clarity, not capability — anything `switch` and `restore` do, `checkout` could already do. Recent `git status` output nudges you toward the new spellings by printing `git restore --staged <file>` and `git switch <branch>` in its hints. If an interviewer pushes on "should we ban checkout in our docs", the honest answer is that the new commands are better for teaching and for scripts a human reads, while `checkout` remains the safe assumption for portability across older tooling. ## The one-line summary to give One command that both moved HEAD and clobbered files was ambiguous and dangerous; Git separated the ref operation (`switch`) from the content operation (`restore`) so each has an unambiguous argument shape and its destructive behaviour is spelled out by flags.

  • Does git switch make it harder to end up on a detached HEAD?
    Yes. `git switch <commit>` refuses to land on a raw commit; you must ask explicitly with `git switch --detach <commit>`. `git checkout <commit>` silently detaches and prints a long warning that people learned to ignore. Making the unusual state opt-in is a large part of why the split happened.
  • Is git checkout deprecated now?
    No. It remains fully supported and is what you will find in older scripts, tutorials and CI configurations. The new commands are recommended for clarity, and `git status` hints now suggest them, but nothing was removed. Prefer `switch` and `restore` in documentation and reviews; keep `checkout` where portability across old tooling matters.

saying these in an interview costs you the question

  • Saying git checkout is deprecated or removed
  • Claiming switch and restore added capabilities checkout lacked
  • Believing git switch can restore a single file
  • Thinking git restore can move HEAD to another branch

context