Git, CI/CD & GitOpsXVI · Restore and SwitchRestoreSwitch
git restore in detail — restoring files from the index, from HEAD, and from any ref
What you'll learn
- Use git restore <path> to restore the working tree from the index
- Use git restore --staged <path> to unstage without touching the working tree
- Use git restore --source=<ref> <path> to pull a file from a named tree
- Use git restore --staged --worktree <path> to restore both trees to HEAD
- Recognise the default source and target of each flag combination
- Distinguish destructive file restore from reversible commit-level undo
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 restore is the second of the two commands introduced in
Git 2.23 to replace the overloaded git checkout. Its job is
the complement of git switch: operations that move file
contents, not HEAD. Its flag set encodes the source (where the
bytes come from), the destination (which tree the bytes are
written to), and the paths (which files are touched). With
three concepts - source, destination, path - the entire flag
set is described.
The three axes: source, destination, path
Every git restore invocation has three implicit choices:
- Source: where do the bytes come from? Default is the
index.
--source=<ref>(or-s <ref>) overrides this. - Destination: which tree is written? Default is the
working tree.
--stagedredirects the write to the index.--worktreeredirects the write to the working tree. The two can be combined. - Path: which files are touched? A path argument is
required (or
.for the whole tree, or--stagedalone without a path resets the entire index).
flowchart LR
A["source\n(default: index)"] --> B["git restore"]
C["destination\n(default: worktree)"] --> B
P["path\n(required)"] --> B
A2["--source=<ref>"] -.-> A
C1["--staged"] -.-> C
C2["--worktree"] -.-> C
B --> D["bytes written to destination"]
The defaults are the safe defaults: bytes come from the
index (not from any other tree), and bytes are written to
the working tree (not to the index). The defaults make
“discard working tree edits” the bare command - the most
common case of git restore.
Restoring the working tree from the index
The bare form, with no flags, restores the working tree file at each named path with the version that the index has:
# Discard working tree edits for one file
git restore terraform/main.tf
# Discard working tree edits for every file
git restore .
The operation reads the blob OID for each named path from the index, reads the corresponding blob object from the object store, and writes the bytes to the working tree. The index is not touched. HEAD is not touched. The previous working tree bytes are overwritten with no backup.
flowchart LR
A["working tree path\n(edited bytes)"] --> B["git restore <path>"]
C["index\n(blob OID for path)"] --> B
B --> D["working tree\n(index bytes)"]
Restoring the index from HEAD
git restore --staged <path> moves the named paths in the
index back to match HEAD. The working tree is untouched.
# Unstage a single file
git restore --staged terraform/backend.tf
# Unstage every file (the entire index matches HEAD)
git restore --staged .
This is the operation that undoes a git add without losing
the working tree edits. The flow is the canonical fix for the
“added too much” mistake: the file goes back to “modified,
not staged” status, and the engineer can re-stage
deliberately or discard separately.
flowchart LR
A["index\n(staged blob OID)"] --> B["git restore --staged"]
C["HEAD\n(tree OID)"] --> B
B --> D["index\n(HEAD tree bytes)"]
E["working tree\n(untouched)"] --> D
The default source for --staged is HEAD. There is no
ambiguity: --staged without --source means “restore the
index entries from HEAD”. Engineers who want to restore the
index from a non-HEAD tree-ish must use --source.
Restoring both trees from HEAD
git restore --staged --worktree <path> writes the path
into both trees from the HEAD tree. The operation is
equivalent to:
git restore --staged $FILE_PATH
git restore $FILE_PATH
in one command. The shorthand is useful in two scenarios: the engineer wants to discard both the staged version and the working tree version in a single, atomic-feeling step.
# Discard both staged and working tree edits, restoring HEAD
git restore --staged --worktree terraform/main.tf
After this command, both the index and the working tree match HEAD for the named path. The previous bytes - both the staged version and the working tree version - are overwritten.
Restoring from a named source: —source
git restore --source=<ref> <path> (or -s <ref>) changes
the source from “index” (default) or “HEAD” (default for
—staged) to whatever tree-ish the flag names.
# Pull a single file from a specific commit into the working tree
git restore --source=8a3f9d2 terraform/main.tf
# Pull a file from a tag into the working tree
git restore --source=v3.2.7 terraform/main.tf
# Pull a file from another branch into the working tree
git restore --source=feature/iam-rotation terraform/main.tf
The destination is still the working tree by default.
Combining --source with --staged writes the source’s
version into the index; combining with --worktree writes
into the working tree (redundant with the default, but
explicit).
# Pull a file from feature/iam-rotation into the index
git restore --source=feature/iam-rotation --staged terraform/main.tf
After this command, the index has the version of
terraform/main.tf that exists on feature/iam-rotation,
but the working tree still has whatever was there before.
The next git commit will record that version.
flowchart LR
A["--source=<ref>"] --> B["source tree"]
B --> C["git restore"]
D["destination flag\n(--staged or default)"] --> C
P["path"] --> C
C --> E["destination tree\n(bytes from source)"]
Defaults and combinations summary
The flag combinations, their effect, and their legacy spelling:
| Command | Effect | Legacy equivalent |
|---|---|---|
git restore <path> | Working tree ← index for <path> | git checkout -- <path> |
git restore --staged <path> | Index ← HEAD for <path>; working tree untouched | git reset HEAD <path> |
git restore --staged --worktree <path> | Index ← HEAD and working tree ← HEAD for <path> | git checkout HEAD -- <path> |
git restore --source=<ref> <path> | Working tree ← <ref> tree for <path> | git checkout <ref> -- <path> |
git restore --source=<ref> --staged <path> | Index ← <ref> tree for <path>; working tree untouched | git checkout <ref> -- <path> (when index desired) |
Every row produces an identical three-tree state after it runs. The new commands do not add capability.
Production discipline
- Use
git restore --stagedto unstage,git restoreto discard. The two commands map to two distinct intents; reading the command line tells the reviewer which intent is in play. - Commit before discarding. A throwaway WIP commit is
the cheapest insurance against an unwanted restore. The
commit can be undone with
git reset --soft HEAD~1; the discarded working tree bytes cannot. - Prefer
--sourcefor cherry-picking one file. Agit restore --source=<ref> --staged <path>followed bygit commitis a single-file cherry-pick without the merge machinery ofgit cherry-pick.
Cross-course references
- Ansible for Production Sysadmins - Part XXXVII
(RepoArch) uses
git restore --stagedin pre-commit hooks to unstage any playbook file that fails a lint check without losing the working tree edits for the engineer to fix and re-stage. - GitOps with Argo CD - Part IV (AppSources) uses
git restore --sourcein reconciliation logic to pull a specific file from a known-good commit when the rendered manifest is suspect but the rest of the application is healthy. - Terraform for Production Sysadmins - Part XV (CIHooks)
uses
git restore terraform/.terraform.lock.hclin the pre-commit hook to discard the lock file thatterraform initregenerates on every run; the lock file should never enter the index.
Quiz
Knowledge check · 4 questions
Q1. An engineer has staged `terraform/backend.tf` with `git add` and now wants to unstage it without losing the working tree edits. Which command is correct?
Q2. `git restore <path>` creates a backup of the previous working tree bytes in `.git/`, allowing recovery with `git restore --source` for at least 30 days.
Q3. What three concepts does every `git restore` invocation encode, and what are the defaults for each?
Q4. Recover a single Terraform file from a sibling feature branch onto the current branch without merging the entire branch.
An engineer is on `main` and needs the contents of `terraform/iam/policy.json` from the `feature/iam-rotation` branch - a single file out of dozens on the feature branch. A full merge would drag in unrelated changes; a cherry-pick is overkill for one file. The engineer needs the bytes on `main`'s next commit without disturbing the rest of the working tree.
Passing score: 75%. Answers are checked in this browser.