Git, CI/CD & GitOpsXVI · Restore and SwitchRestoreSwitch
git switch in detail — branch switching, creation, detachment, and orphan branches
What you'll learn
- Use git switch <branch> to switch to an existing branch
- Use git switch -c <name> and git switch -C <name> to create and create-or-reset a branch
- Use git switch -d <commit> and git switch --detach <commit> to detach HEAD
- Use git switch --orphan <name> to start a branchless history
- Explain what --disjoint-checks does and when to use it
- Use git switch - to toggle to the previous branch
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 switch is one of the two commands introduced in Git 2.23
to replace the overloaded git checkout. Its job is narrow:
operations that move HEAD. Its flag set is correspondingly
narrow: every flag describes a HEAD-movement variant, not a
file operation. Lesson VIII-01 covered the most common case
(-c to create, - to toggle); this lesson covers the full
flag set so an engineer can pick the right variant for any
branch-movement task.
Switching to an existing branch
The bare form, with no flags, switches to a branch that already exists:
git switch main
# Switched to branch 'main'
git switch feature/iam-rotation
# Switched to branch 'feature/iam-rotation'
The operation is the three-tree switch from lesson VIII-01: HEAD moves, the index is rewritten from the branch’s tree object, and the working tree files whose blob OIDs changed are rewritten. The command refuses if the working tree or index is dirty in a way that would be clobbered. There is no flag to force an overwrite - the safety is structural.
flowchart LR
A["git switch <branch>"] --> B["branch exists?"]
B -- "yes" --> C["three-tree switch"]
B -- "no" --> D["error: unknown switch target"]
C --> E{"working tree clean?"}
E -- "yes" --> F["HEAD, index, WT updated"]
E -- "no" --> G["refused with dirty-tree error"]
Creating a new branch: -c versus -C
The two creation flags differ in one byte and one behaviour:
git switch -c <new-branch> [<start>]creates the branch and switches to it. Fails if<new-branch>already exists. The optional<start>argument names the commit the new branch should point at; if omitted, the new branch points at the current HEAD.git switch -C <branch> [<start>]creates the branch and switches to it, or resets the branch if it already exists. The lowercase-cis “create”; the uppercase-Cis “create-or-reset”.
# Create a new feature branch off the current HEAD
git switch -c feature/billing-export
# Create a new branch off main, then switch to it
git switch -c feature/billing-export main
# Create or reset feature/billing-export to point at HEAD
git switch -C feature/billing-export
# Create or reset feature/billing-export to point at origin/main
git switch -C feature/billing-export origin/main
Detaching HEAD: -d and —detach
git switch -d <commit> (or the long form git switch --detach <commit>) detaches HEAD at the named commit. HEAD
becomes a direct reference to the commit OID; the index and
working tree are rewritten to that commit’s tree. No branch
moves.
# Detach at a specific commit by SHA
git switch -d 8a3f9d2
# Detach at a tag
git switch -d v3.2.7
# Detach at the tip of a branch (same as `git switch <branch>` minus the branch)
git switch -d feature/iam-rotation
The -d form implies --detach; the two are interchangeable
when followed by a commit-ish. The --detach flag also accepts
no argument - git switch --detach alone detaches at HEAD,
which is a no-op for the tree but moves HEAD out of the
branch-tracking machinery. The use case is rarely worth the
confusion.
stateDiagram-v2
[*] --> Attached: HEAD -> refs/heads/<name>
Attached --> Detached: git switch -d <commit>
Detached --> Attached: git switch <branch>
Detached --> Detached: git switch -d <other>
A detached HEAD is the right state for inspecting an old commit (bisecting, reviewing a tag, recovering a lost branch tip) and the wrong state for doing work - the lesson on detached-HEAD state covers why commits made on a detached HEAD become orphaned when the reflog expires.
Starting an orphan branch: —orphan
git switch --orphan <new-branch> creates a new branch with
no history. The first commit made on the new branch will have
no parent; the branch is a fresh root.
git switch --orphan gh-pages
# Switched to a new branch 'gh-pages'
The interesting property is what the command does to the
index and working tree: it stages every file currently in the
working tree as the content of the new branch’s first commit.
This is intentional. The canonical use case is publishing a
website branch (the gh-pages pattern) where the published
content has no shared history with the source code. The
command stages whatever is in the working tree so the
engineer can curate it before the first git commit.
Toggling to the previous branch: -
git switch - toggles to wherever HEAD was before the last
switch. The previous branch is recorded in the reflog as
@{-1}; the - argument is the readable spelling.
git switch feature/iam-rotation
# Switched to branch 'feature/iam-rotation'
git switch main
# Switched to branch 'main'
git switch -
# Switched to branch 'feature/iam-rotation'
The toggle is safe in a way that an explicit branch name is
not: there is no branch name to typo. CI scripts that bounce
between two branches (the common hot-fix-and-back pattern)
should prefer - over a hard-coded name because the branch
name might rotate across releases while the toggle never
changes.
Tightening the safety check: —disjoint-checks
git switch --disjoint-checks is an undocumented experimental
flag added in 2.24 that strengthens the dirty-tree check by
also refusing the switch when the working tree contains files
that are untracked but would be overwritten. Without the flag,
a switch refuses only on tracked-and-dirty paths; with the
flag, an untracked file at a location the new branch contains
also refuses the switch.
git switch --disjoint-checks main
# error: The following untracked working tree files would be overwritten by checkout:
# scripts/scratch.sh
# Please move or remove them before you switch branches.
# Aborting
The flag is opt-in and rarely used. Its production use is in CI scripts that need the strictest possible safety - for example, a script that materialises many branches in sequence and cannot tolerate an untracked scratch file being silently clobbered.
Production discipline
- Use
-cfor new work; reserve-Cfor explicit resets. The lowercase form fails loudly if the branch exists, which is the right behaviour for accidental reuse. - Use
--detachfor read-only inspection only. Any commit made on a detached HEAD is recoverable only through the reflog, and the reflog expires. - Audit
--orphanbranches carefully. The first commit on an orphan branch is the entire history. Make sure the stage contains exactly what should be in the first commit. - Prefer
git switch -over hard-coded branch names in CI scripts. The toggle does not break when branch names rotate.
Cross-course references
- Ansible for Production Sysadmins - Part XXXVIII
(Review) standardises on
git switch -c feature/<name>as the opening of every change branch andgit switch -as the closing toggles in a hot-fix workflow. - GitOps with Argo CD - Part II (Repos) uses
git switch --detachin the controller’s workspace management to materialise a specific commit for reconciliation without touching any branch. - Terraform for Production Sysadmins - Part XV (CIHooks)
uses
git switch -Cin the post-merge hook to reset the CI branch to the latestmaintip, deliberately choosing the forceful form because the CI branch is local-only and its tip is meaningless after every merge.
Quiz
Knowledge check · 4 questions
Q1. What is the difference between `git switch -c feature/x` and `git switch -C feature/x`?
Q2. `git switch --orphan <name>` starts a new branch that shares no history with the current branch; the first commit on the orphan branch has no parent.
Q3. What does `git switch -` do, what reflog entry records the destination, and why is it preferred over a hard-coded branch name in CI scripts?
Q4. Choose the right switch flag for each of three operations in a Terraform release pipeline, and explain why.
A release pipeline performs three branch operations in sequence. (a) Create a release branch `release/v3.2.7` off `main` and switch to it; the branch must not exist yet, and the script should fail loudly if it does. (b) Detach HEAD at the tag `v3.2.6` to build the previous release's documentation from the historical source. (c) Start a fresh `docs/v3.2.7` branch with no history from the source tree, populating it with only the rendered documentation.
Passing score: 75%. Answers are checked in this browser.