Git, CI/CD & GitOpsXVI · Restore and SwitchRestoreSwitch
The Git 2.23 command split — why git checkout was overloaded and how switch and restore replaced it
What you'll learn
- Explain why git checkout was overloaded and what accidents that overload caused
- Identify the three operations git checkout performed before Git 2.23 and which new command each maps to
- State the design intent of the Git 2.23 split between git switch and git restore
- Recognise that git checkout still exists and is not deprecated
- List the equivalent legacy and new spellings for the common branch and file operations
Prerequisites
Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x
Git 2.23, released on 16 August 2019, split one of the oldest
commands in Git into two new commands that each do one thing
well. The release notes announce two new porcelains - git switch and git restore - and describe them as a way to give
the overloaded git checkout command “two experimental
alternatives to clarify the diverging use-cases”. The split was
not a deprecation: git checkout still exists, still works,
and is still the default in millions of muscle memories. The
split was an attempt to fix a usability problem that had been
biting Git users for fifteen years.
What git checkout actually did before 2.23
Read the pre-2.23 git checkout documentation and the same
man page covers three unrelated operations. The command takes a
branch name, a commit, or a file path - and the operation it
performs depends entirely on which kind of argument it
receives:
git checkout <branch>updates HEAD to the branch, rewrites the index from the branch’s tree object, and updates the working tree. The three-tree switch covered in lesson VIII-01.git checkout <commit>detaches HEAD at that commit and rewrites the index and working tree. The detached-HEAD state covered in lesson V-04.git checkout -- <path>writes the named file from the index into the working tree, discarding local edits. The index-and-working-tree restore covered in lesson VI-06.
flowchart TD
A["git checkout <argument>"] --> B{"argument kind"}
B -- "branch name" --> C["three-tree switch"]
B -- "commit OID or tag" --> D["detached HEAD"]
B -- "file path" --> E["restore file from index"]
B -- "branch + start" --> F["create and switch (legacy -b)"]
A single command, four operations, all selected by the shape of
its arguments. The mental cost was real. Engineers who learned
git checkout by reading examples could not predict what the
command would do without knowing what category the argument
fell into. Worse, a typo that crossed category lines - typing
a file path that happened to collide with a branch name, or a
branch name that happened to resolve to a remote-tracking ref
- silently performed the wrong operation.
The 2.23 split
The Git 2.23 release notes summarise the change:
“Two new commands
git switchandgit restoreare added that provide an experimental alternative to the incumbentgit checkoutcommand, to clarify the diverging use-cases.”
The split divides the four operations above by what they touch:
git switchhandles operations that move HEAD: switching to a branch, creating a new branch, detaching at a commit, and toggling to the previous branch.git restorehandles operations that move file contents: restoring working tree files from the index, restoring index entries from HEAD, and writing files from any tree-ish source into the working tree or index.
flowchart LR
A["git checkout"] --> B["git switch\n(HEAD moves)"]
A --> C["git restore\n(files move)"]
B --> B1["git switch <branch>"]
B --> B2["git switch -c <name>"]
B --> B3["git switch --detach <commit>"]
B --> B4["git switch -"]
C --> C1["git restore <path>"]
C --> C2["git restore --staged <path>"]
C --> C3["git restore --source=<ref> <path>"]
The mapping from the legacy spellings to the new ones is mechanical:
| Legacy | New | Operation |
|---|---|---|
git checkout <branch> | git switch <branch> | Switch to existing branch |
git checkout -b <name> | git switch -c <name> | Create branch and switch |
git checkout <commit> | git switch --detach <commit> | Detached HEAD |
git checkout - | git switch - | Toggle to previous branch |
git checkout -- <path> | git restore <path> | Restore working tree file from index |
git checkout HEAD -- <path> | git restore --staged --worktree <path> | Restore both index and working tree file |
git checkout <ref> -- <path> | git restore --source=<ref> <path> | Restore file from named ref |
Every row in the table produces an identical three-tree or file state after it runs. The new commands do not add capability - they sharpen names.
Design intent
The release notes use the word “experimental” deliberately. The split was published to gather feedback before any of the old spellings were removed. Ten years later, the old spellings are still present, still default in many shells, and still ship in the official documentation as fully-supported. The new commands are the recommended path; the old command is the compatibility path. The intent was not to break workflows but to make new workflows easier to teach and to review.
The deeper intent was operational. Engineers reading a script
that contains git switch feature/x know immediately that the
line moves HEAD. Engineers reading git restore terraform/main.tf
know immediately that the line moves file contents. Engineers
reading git checkout do not know which category the line falls
into without looking at the arguments - which means a
code-review tool cannot flag the line as wrong without parsing
the arguments too.
What stayed in git checkout
git checkout is not empty after 2.23. The compatibility
surface is preserved:
git checkout <branch>andgit checkout -b <name>still work; they emit a hint recommending the new spellings.git checkout <commit>still detaches HEAD.git checkout -- <path>andgit checkout <ref> -- <path>still restore files.
What changed is the recommended path. New scripts should
use git switch and git restore. Old scripts that use
git checkout continue to work and will continue to work for
the foreseeable future.
Production discipline
- Use
git switchfor branch work andgit restorefor file work in new scripts. The split makes the script readable to the next engineer without requiring them to parse the argument. - Keep
git checkoutin old scripts. It is not deprecated; rewriting a workinggit checkouttogit switchorgit restorefor its own sake is a no-op that costs reviewer time. - Add the new spellings to review checklists. The review question “is the right command being used for the right tree?” is now answerable at a glance.
Cross-course references
- Ansible for Production Sysadmins - Part XXXVII
(RepoArch) prescribes
git switchandgit restoreas the default spellings in team onboarding documentation, on the same grounds: each command does one thing. - GitOps with Argo CD - Part II (Repos) discusses how
GitOps controllers invoke
git switchprogrammatically to materialise a branch into the working tree; the narrowed command surface makes the controller’s shell wrappers easier to test. - Terraform for Production Sysadmins - Part XV (CIHooks)
uses
git restoreto discard scratch files created byterraform planbefore committing; the explicit name makes the pre-commit hook auditable.
Quiz
Knowledge check · 4 questions
Q1. Before Git 2.23, which of these operations did the single command `git checkout` perform, selected by the shape of its arguments?
Q2. Git 2.23 deprecated `git checkout` and planned to remove it in a later release.
Q3. What is the operational rule for choosing between `git switch` and `git restore` in a new script, and what does the rule simplify in a code review?
Q4. Refactor a legacy post-receive hook from `git checkout` to the new spellings and justify each change.
A team's post-receive hook contains three lines: `git checkout main`, `git checkout -- terraform/.terraform.lock.hcl`, and `git checkout -b hotfix/$1 origin/main`. A new platform engineer argues that the lines should be modernised. Another argues that working code should not be touched. The team needs a decision and a documented standard.
Passing score: 75%. Answers are checked in this browser.