skip to content

questions

5

In Git, how do you unstage a file without losing the edits in your working tree?

level: juniorimportance: must knowfreq 76%

answer

  1. Two trees involved, only one should change
  2. The flag names which tree you are rewriting
  3. Without that flag it rewrites your file instead
  4. git status prints the exact command
  5. The old spelling used reset with a path

basics

~10 s

Run git restore --staged <file>. It rewrites that path in the index from HEAD, leaving the file on disk exactly as you edited it. The older equivalent is git reset -- <file>.

solid answer

~40 s

`git restore --staged <file>` is the modern answer: it restores the index entry for that path from HEAD, so the change stops being staged while your edited file on disk is untouched. `git status` prints exactly this command under "Changes to be committed". The historical spelling is `git reset HEAD <file>` (or `git reset -- <file>`), which does the same thing — a path-limited reset rewrites index entries and never moves the branch. The dangerous neighbour is `git restore <file>` with no `--staged`: that targets the working tree and overwrites your edits from the index, with no undo. Remember the default targets: without `--staged`, restore writes the working tree; without `--source`, `--staged` reads from HEAD.

code

console · 6 lines
console
$ git add report.txt
$ git status --short
M  report.txt
$ git restore --staged report.txt
$ git status --short
 M report.txt

go deeper

for a junior

Know the exact command and the one-flag trap: with --staged you unstage, without it you overwrite your own edits. Reading what git status suggests is a legitimate part of the answer.

for a middle

Explain the defaults behind it — restore's default target is the working tree, and --staged's default source is HEAD — plus the legacy git reset -- <file> equivalent.

for a senior

Show you know which recoveries exist: an unstaged edit lost to a plain restore was never an object, so no reflog helps. Reach for patch mode or a quick stash before touching a dirty tree.

for a principal

Own the ergonomics angle: destructive file-level commands deserve team aliases and onboarding notes, because the cost of the checkout/restore confusion is unrecoverable local work.

## The situation You ran `git add report.txt`, then realised the change belongs in a different commit. You want the file to stop being staged, but you absolutely do not want to lose the edits you made to it. Two of the three snapshots Git tracks are involved: the **index** (what the next commit will contain) and the **working tree** (the file on disk). Unstaging means rewriting the index entry only. ## The modern command `git restore --staged report.txt` `git restore` was added in Git 2.23 as the file-content half of the old `git checkout`. Its two targets are chosen by flags: - `--worktree` (`-W`) — restore the file on disk. This is the **default** when neither flag is given. - `--staged` (`-S`) — restore the index entry. - Both flags together restore the index and the working tree. And the source is chosen by `--source=<tree>`: when it is omitted, `--staged` reads from **HEAD**, while a plain working-tree restore reads from the **index**. That default is exactly what unstaging needs — the index entry goes back to the committed content, so the path drops out of "Changes to be committed", and because `--worktree` was not requested your edited file is not touched. `git status` in recent Git prints the command for you: under "Changes to be committed" it says `(use "git restore --staged <file>..." to unstage)`. ## The historical command Before `restore` existed, the answer was `git reset HEAD report.txt`, usually shortened to `git reset -- report.txt`. When `git reset` is given a pathspec it never moves the branch pointer; it only copies those paths from the named commit (default HEAD) into the index. It is still perfectly valid and you will meet it in every older script and tutorial. The mode flags `--soft` and `--hard` cannot be combined with paths. ## The command that loses your work `git restore report.txt` — no `--staged` — is a different operation. It restores the *working tree* from the index, so it overwrites your on-disk edits with the staged content. The legacy spelling is `git checkout -- report.txt`. Either way, content that was only in the working tree was never stored as a Git object, so nothing in the reflog or `git fsck` can bring it back. The one-character difference between "unstage" and "discard my edits" is why interviewers ask this at the screening stage. ## Useful variations - `git restore --staged .` unstages everything under the current directory; a bare `git reset` unstages everything in the repository. - `git restore --source=HEAD~2 --staged --worktree config.yml` puts both the index and the file on disk back to their content two commits ago, without moving HEAD or creating a commit — handy for pulling one file out of history. - `git restore --staged --worktree <file>` with no `--source` resets that path to HEAD in both trees: unstage *and* discard the edits in one step. - `git restore -p <file>` (patch mode) walks you through hunks so you can discard only part of a change. - `git restore` requires a pathspec. Running it bare is an error; use `git restore .` or `git restore :/` if you really mean everything. ## Newly added files If the file was previously untracked and you just ran `git add newfile.txt`, HEAD has no entry for it. `git restore --staged newfile.txt` still works: it removes the entry from the index, and the file returns to being untracked rather than vanishing. That is the behaviour you want — nothing on disk is deleted.

  • What is the difference between git restore <file> and git restore --staged <file>?
    They target different trees. `--staged` rewrites the index entry from HEAD, so the change is unstaged but your file on disk is untouched. Without it, restore rewrites the *working tree* from the index, overwriting your edits — content that was never committed or staged is unrecoverable. Passing both flags resets the path to HEAD everywhere.
  • How do you restore a single file to its content from an earlier commit without moving HEAD?
    `git restore --source=HEAD~3 --staged --worktree app/config.yml`. The `--source` option names any tree-ish to read from, and because restore only rewrites paths it never moves the branch pointer or creates a commit. Drop `--staged` to change only the file on disk, then review with `git diff` before committing the result.

saying these in an interview costs you the question

  • Suggesting git restore <file> to unstage, which discards the edits
  • Claiming you must commit and then amend to unstage
  • Saying git reset --hard <file> unstages a single path
  • Thinking unstaging a newly added file deletes it from disk

context

open as a page

In Git, what is the difference between reset --soft, --mixed, and --hard?

level: middleimportance: must knowfreq 82%

basics

~20 s

All three move the current branch pointer to the target commit. --soft stops there, leaving the index and working tree untouched; --mixed also resets the index; --hard additionally overwrites the working tree, destroying uncommitted changes.

open as a page

In Git, what does git clean -fdx remove, and why preview it with -n first?

level: middleimportance: should knowfreq 48%

basics

~20 s

git clean -fdx deletes untracked files (-f), descends into untracked directories (-d), and also removes files your ignore rules exclude (-x) — build output, local env files, dependency folders. Nothing it deletes is in Git, so -n previews the list first.

open as a page

Why did Git split git checkout into the git switch and git restore commands?

level: middleimportance: should knowfreq 52%

basics

~20 s

git checkout did two unrelated jobs: moving HEAD to another branch or commit, and overwriting files from a tree. Git 2.23 split them so switch handles refs and restore handles file content, removing an ambiguous and silently destructive interface.

open as a page

In Git, what is ORIG_HEAD, and why can it be stale after two operations in a row?

level: seniorimportance: should knowfreq 36%

basics

~20 s

ORIG_HEAD is a single ref Git overwrites with the previous HEAD position whenever a command moves HEAD drastically — reset, merge, rebase, am. Because it holds only one value, a second such command overwrites it, so it points one step back, not to the original.

open as a page