Skip to main content
RunBook Academy

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

Intermediate⏱ ~18 mingit

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

Not yet marked complete on this device.

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 switch and git restore are added that provide an experimental alternative to the incumbent git checkout command, to clarify the diverging use-cases.”

The split divides the four operations above by what they touch:

  • git switch handles operations that move HEAD: switching to a branch, creating a new branch, detaching at a commit, and toggling to the previous branch.
  • git restore handles 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:

LegacyNewOperation
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> and git checkout -b <name> still work; they emit a hint recommending the new spellings.
  • git checkout <commit> still detaches HEAD.
  • git checkout -- <path> and git 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

  1. Use git switch for branch work and git restore for file work in new scripts. The split makes the script readable to the next engineer without requiring them to parse the argument.
  2. Keep git checkout in old scripts. It is not deprecated; rewriting a working git checkout to git switch or git restore for its own sake is a no-op that costs reviewer time.
  3. 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 switch and git restore as 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 switch programmatically 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 restore to discard scratch files created by terraform plan before committing; the explicit name makes the pre-commit hook auditable.

Quiz

Knowledge check · 4 questions

  1. Q1. Before Git 2.23, which of these operations did the single command `git checkout` perform, selected by the shape of its arguments?

  2. Q2. Git 2.23 deprecated `git checkout` and planned to remove it in a later release.

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

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