← Blog
Cloud & DevOps

Unstage Git changes without losing your working copy

Move the right files or hunks out of Git’s index, prove the working tree is intact, and recover safely when staging and content changes get confused.

By Emmanuel Corels

You ran git add too broadly, but nothing has been committed. The safe correction is to change the index—Git’s proposed snapshot—without replacing the files in your working tree. For a path, the clearest modern command is git restore --staged -- path. It makes the staged version match HEAD while leaving the file you are editing alone.

First identify which of Git’s three states is wrong

A file can exist in three relevant forms: the version in HEAD, the version in the index, and the version in the working tree. git add copies current content into the index. Editing the file again after adding it can therefore leave both a staged change and a different unstaged change for the same path.

git status --short
git diff --cached -- path/to/config.yml
git diff -- path/to/config.yml

The cached diff is what the next ordinary commit would record. The uncached diff compares the working file with the index. Read both before changing state. If a path appears twice in short status, that is not a duplicate: the left column describes index state and the right column describes working-tree state.

Unstage a path, not its contents

git restore --staged -- path/to/config.yml
git status --short
git diff --cached
git diff -- path/to/config.yml

The --staged option selects the index as the destination. Because --worktree is absent, Git does not overwrite the working file. The -- ends option parsing, protecting paths that begin with a dash and making the boundary obvious to a reviewer.

The accepted Stack Overflow answer uses git reset -- path. That remains supported and, in pathspec mode, changes the index rather than moving the branch or rewriting the working tree. Current Git documentation explicitly describes it as the opposite of git add and equivalent to git restore --staged. Prefer the restore spelling in runbooks because it names the destination; keep reset available for older Git installations and established team practice.

git reset -- path/to/config.yml

Do not generalize that command into git reset --hard. The word “reset” covers several modes. A hard reset can replace both index and working-tree content and is a materially different, destructive operation.

Correct only the hunks that were staged by mistake

When a file contains both wanted and unwanted staged edits, unstage interactively:

git restore --staged --patch -- path/to/service.conf
# Older/equivalent index workflow:
git reset --patch -- path/to/service.conf

Review each hunk in context. Splitting a hunk can help when unrelated edits are close together, but generated files and mechanical formatting can make interactive boundaries misleading. Afterward, inspect git diff --cached rather than assuming every response had the intended effect.

Handle whole repositories and the unborn branch case

To unstage everything below the repository root while preserving working files:

git restore --staged -- :/
# Or:
git reset

:/ is Git pathspec syntax for the repository root, so it is not sensitive to the current subdirectory. Be deliberate in monorepositories: a repository-wide operation may affect another component’s staged work. Capture git status and the cached diff before a bulk change.

Before the first commit, HEAD does not yet name a commit. Modern git reset without a tree handles this by resetting toward an empty index, which is why the historical accepted answer still works. git restore --staged normally takes HEAD as its source and may not be the portable choice on an unborn branch. Check:

git rev-parse --verify HEAD
# If this reports no commit and you need to unstage all:
git reset

New, deleted, renamed, conflicted, and ignored paths

  • New file: unstaging makes it untracked; it does not delete the file.
  • Staged deletion: unstaging restores the index entry. The working file remains deleted, so status then shows an unstaged deletion.
  • Rename: Git records snapshots and detects renames later. Unstaging can appear as a deletion plus an untracked destination until both paths are staged together again.
  • Merge conflict: the index can contain multiple stages. Do not use a routine unstage recipe as conflict resolution. Inspect git status, resolve the file, and add the resolved result intentionally.
  • Ignored file already staged: ignore rules do not remove tracked or indexed content. Unstage it, confirm whether it is still tracked in HEAD, and use git rm --cached only when the intended repository change is to stop tracking it.

Prove the next commit is the one you intend

git status --short
git diff --cached --check
git diff --cached --stat
git diff --cached
git diff

--check catches whitespace errors but does not validate behavior. Run the tests and policy checks that apply to the staged snapshot. If tests execute from the working tree while that tree contains additional unstaged edits, they may test code different from what you are about to commit. A temporary worktree or a stash-assisted validation process can isolate the exact staged tree, but do not hide someone else’s changes in shared automation.

Recovery and rollback

Unstaging itself is easy to reverse: inspect the working diff, then run git add -- path or git add --patch to stage the intended content again. If you accidentally used a command that changed the working tree, stop editing. Check your editor’s local history, filesystem snapshots, backups, and any existing stash. git reflog can recover commits and moved refs, but it is not a guaranteed backup for working content that was never stored as an object.

For automation, log the repository root, exact pathspec, pre-change status, and post-change cached diff. Never run a broad hard reset as cleanup in a workspace that can contain human work. Disposable CI checkouts should be recreated; long-lived deployment worktrees need explicit ownership and recovery controls.

Attribution and verification

This guide was prompted by oz10’s Stack Overflow question and genehack’s accepted answer, used under CC BY-SA. The accepted git reset solution remains valid, while this guide foregrounds the clearer modern restore spelling and its unborn-branch limitation. Behavior was independently verified in the current primary git restore documentation, git reset documentation, and git status documentation.

Primary source: Review the official reference ↗