Git, CI/CD & GitOpsXVI · Restore and SwitchRestoreSwitch
Switch versus checkout — when each is appropriate, behaviour differences, and the migration story
What you'll learn
- Identify behaviour differences between git switch and git checkout that go beyond a rename
- Recognise the cases where git checkout is still the right command
- Choose between git switch and git checkout for a given team context
- Describe the migration path from checkout to switch and restore
- Recognise the advisory hints git emits when an old spelling is used
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
The 2.23 split is presented as git switch replacing the
branch side of git checkout, but the replacement is not a
pure rename. There are behaviour differences that matter for
scripts, CI workflows, and onboarding documentation. This
lesson maps the differences side by side and explains when
each command is the right choice.
The advertised equivalence
For most operations, git switch and git checkout produce
identical three-tree states:
# Switch to an existing branch
git switch main
git checkout main
# Create a branch and switch to it
git switch -c feature/x
git checkout -b feature/x
# Detach at a commit
git switch --detach 8a3f9d2
git checkout 8a3f9d2
# Toggle to the previous branch
git switch -
git checkout -
These pairs are exact synonyms. The new command produces the same tree states, the same reflog entries, and the same exit codes as the old command.
flowchart LR
A["git switch <branch>"] --> X["identical"]
B["git checkout <branch>"] --> X
C["git switch -c feature/x"] --> Y["identical"]
D["git checkout -b feature/x"] --> Y
E["git switch --detach 8a3f9d2"] --> Z["identical"]
F["git checkout 8a3f9d2"] --> Z
Behaviour differences that go beyond the rename
Three differences are worth knowing for CI scripts and onboarding documentation:
1. The orphan-branch syntax
git switch --orphan <name> is the canonical way to start a
branchless history. The legacy equivalent is git checkout --orphan <name>, which still works. The two commands produce
the same tree state; the difference is only in the spelling.
2. The disjoint-checks flag
git switch --disjoint-checks tightens the dirty-tree check
to also refuse the switch when an untracked file would be
overwritten. git checkout does not have an equivalent flag;
the only way to get the strict behaviour from git checkout
is to use the --no-overlay or --force flags, neither of
which means the same thing.
3. The advisory hints
Starting with Git 2.24, git checkout emits a one-line hint
when the new spelling would be more appropriate:
git checkout main
# Switched to branch 'main'
# hint: using 'git switch' to switch branches is more intuitive
# hint: 'git switch main' would do the same thing
The hint is informational only - the command succeeds and
the tree state is correct. CI scripts that match on the
exact stdout of git checkout may break on the hint lines
and should adjust their output handling to ignore lines
that begin with hint:.
When checkout is still the right command
git checkout is still the right command in three cases:
1. Legacy scripts that work
A CI pipeline that has run git checkout main in production
for five years is doing the right thing. Rewriting it to
git switch main is a no-op change that costs reviewer
time. The script should be modernised only when it is being
modified for another reason.
2. Symmetric read of a file from a ref
git checkout <ref> -- <path> is the legacy spelling for
pulling a file from a named tree into the working tree. The
new spelling is git restore --source=<ref> <path>. The
legacy spelling still works and is one flag shorter, which
some engineers find ergonomic. The new spelling is the
recommended path.
3. Detached HEAD at a tag with no flag
git checkout v3.2.7 detaches HEAD at the tag without
requiring --detach. git switch v3.2.7 would attempt to
switch to a branch named v3.2.7 and fail with an error
message; the new spelling requires the explicit
--detach. The legacy spelling is shorter for this case.
flowchart TD
A["use case"] --> C{"command shape"}
C -- "branch switching, new script" --> D["git switch\n(recommended)"]
C -- "branch switching, old script" --> E["git checkout\n(no rewrite for its own sake)"]
C -- "file from ref" --> F["git restore --source\n(recommended)"]
C -- "file from ref, old script" --> G["git checkout --\n(still works)"]
C -- "detach at tag with no flag" --> H["git checkout <tag>\n(shorter)"]
C -- "detach at commit, new code" --> I["git switch --detach\n(explicit)"]
The migration story
The migration from checkout to switch and restore is
not a forced rewrite. The official documentation describes
the new commands as “experimental alternatives to clarify
the diverging use-cases”, which is the language of a
recommendation, not a deprecation. The migration story
has three parts:
1. New work uses the new spellings
Code committed after the team’s adoption date uses git switch for branch operations and git restore for file
operations. The transition is enforced by code review, not
by tooling - the team agrees on the rule and applies it.
2. Old scripts are modernised when touched
A CI script that is being modified for another reason
(unrelated bug fix, dependency upgrade, behaviour change)
has its git checkout lines reviewed during the change. If
they can be replaced with the new spellings without
behaviour change, they are replaced. The review checklist
item is “while you are here, are any of the git checkout
lines now better expressed as git switch or git restore?”
3. Legacy scripts that work stay
A CI script that has not been touched in three years and
still works is not modified. The cost of rewriting a
working script exceeds the cost of leaving it alone. The
team accepts that some git checkout lines will exist in
the codebase for years.
flowchart LR
A["new code"] -->|adoption| B["git switch / git restore"]
C["modified code"] -->|"while you are here"| D["review for switch / restore"]
E["stable code"] -->|"do not touch"| F["git checkout remains"]
B --> G["codebase mixes old and new"]
D --> G
F --> G
G -->|"as old code is touched"| B
Comparing the legacy and new spellings
The full mapping from the legacy git checkout spellings to
the new commands:
| Legacy | New | Behaviour diff? |
|---|---|---|
git checkout <branch> | git switch <branch> | None |
git checkout -b <name> | git switch -c <name> | None (default to HEAD; legacy defaults to current HEAD) |
git checkout -B <name> [<start>] | git switch -C <name> [<start>] | None |
git checkout <commit> | git switch --detach <commit> | None |
git checkout <tag> | git switch --detach <tag> | None (but legacy is shorter) |
git checkout - | git switch - | None |
git checkout --orphan <name> | git switch --orphan <name> | None |
git checkout -- <path> | git restore <path> | None |
git checkout HEAD -- <path> | git restore --staged --worktree <path> | None |
git checkout <ref> -- <path> | git restore --source=<ref> <path> | None |
git checkout --overlay <ref> -- <path> | (no direct equivalent) | Yes: --overlay keeps untracked files at the path |
The last row is the rare case where the legacy command has
a flag with no new equivalent. --overlay controls how
untracked files at a target path interact with the restore;
the default is to leave them alone, and --no-overlay
deletes them. The new git restore does not have an
explicit overlay flag - the behaviour matches the legacy
default of “leave untracked files alone”.
Production discipline
- Adopt the new spellings in new code. Review
checklists should explicitly mention
git switchandgit restorefor the corresponding operations. - Modernise
git checkoutwhen the surrounding code is being changed for another reason. A pure-rename commit is a low-risk cleanup; a flag-change commit needs explicit justification. - Test CI scripts against Git 2.24+ output. The
advisory hints change the stdout of
git checkoutbut not the exit code. - Document the team’s choice. A line in
CONTRIBUTING that says “new code uses
git switchandgit restore; legacy code is modernised when touched” sets the expectation for new contributors.
Cross-course references
- Ansible for Production Sysadmins - Part XXXVII (RepoArch) documents the migration rule in its CONTRIBUTING file, with the explicit phrasing above.
- GitOps with Argo CD - Part II (Repos) uses
git switchin the controller’s workspace management and parses stdout by exit code only, so the advisory hints do not affect reconciliation. - Terraform for Production Sysadmins - Part XV (CIHooks) tests its hook scripts against the hints and asserts the exit code rather than the stdout shape.
Quiz
Knowledge check · 4 questions
Q1. Which scenario is a case where `git checkout` is still the right command over `git switch`?
Q2. Starting with Git 2.24, `git checkout` emits advisory `hint:` lines recommending `git switch` or `git restore`, but the hints do not affect the exit code of the command.
Q3. What is the official migration rule from `git checkout` to `git switch` and `git restore`, and why is the rule not 'rewrite all `git checkout` lines'?
Q4. Audit a CI pipeline that uses `git checkout` heavily, classify each line as 'modernise now', 'modernise when modified', or 'keep as-is', and recommend the migration plan.
A team's Terraform CI pipeline contains six `git checkout` invocations: (1) `git checkout main` at the start; (2) `git checkout -b ci/$BUILD_ID` after fetching; (3) `git checkout terraform/.terraform.lock.hcl` to discard the lock file before commit; (4) `git checkout -- terraform/.terraform.lock.hcl` in a post-build cleanup; (5) `git checkout HEAD -- terraform/main.tf` to reset after a failed test; (6) `git checkout origin/main -- README.md` to refresh a generated README before commit. The team is starting a one-quarter migration to the new spellings.
Passing score: 75%. Answers are checked in this browser.