The Git Undo Toolkit – Part 2 – How to Undo Staged Changes in Git?

In Part 1 of our Git undo series, we showed how to fix mistakes that are not yet tracked by git, also known as your working directory. But what happens when you have already executed the git add command and git is now tracking your changes and then you realized that you have made a mistake. In this blog post, we will cover how to undo staged changes in git.

In particular, we will cover the following most common undo scenarios:

  • You ran git add . to quickly stage everything, only to realize that you have included a temporary debug file, build artifacts, or a sensitive credential.
  • You staged changes for a feature branch, but then remembered that you wanted to split a large change into two smaller, more focused commits.
  • You accidentally added a file that should be ignored by Git altogether, and now it is sitting in your staging area, waiting to be committed.

Do not worry! Just because changes are staged and getting tracked by git, it does not mean they cannot be undone. Git provides straightforward ways to “unstage” these changes, moving them back to your working directory where you can modify them further or discard them entirely.

Let us explore how to undo changes from the Git Staging Area. But before that, please complete the initial setup as this is a continuation from the previous blog.

Scenario 1: How to Unstage Changes from a Single File?

This is perhaps the most frequently needed solution when working with the staging area. You have added a file, or specific changes within a file, but now you want to remove it from staging before committing.

Question: How do I unstage changes from a single specific file, moving them back to the working directory?

Answer: Use git restore --staged <file> (recommended for modern Git) or git reset HEAD <file> (for older Git versions).

Code:

# ----------------------------------
# Unstage Changes from a Single File
# ----------------------------------

# Step 1: Make some changes and stage them
echo "line 1" > file1.txt &&
echo "line 2" > file2.txt

# Step 2: Stage the changes using git add command
git add file1.txt file2.txt

# Step 3: Check the status 
git status 

# Output:
# On branch main
# Changes to be committed:
#   (use "git restore --staged <file>..." to unstage)
#         new file:   file1.txt
#         new file:   file2.txt

# Observe: Both files are staged

# Step 4: Realize you want to unstage only file1.txt
git restore --staged file1.txt

# Or, for older Git versions
# git reset HEAD file1.txt

# Step 5: Check status again
git status

# Output:
# On branch main
# Changes to be committed:
#   (use "git restore --staged <file>..." to unstage)
#         new file:   file2.txt
# 
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#         file1.txt

Explanation:

  • git restore --staged: This command is specifically designed for unstaging. The --staged option tells Git to operate on the staging area. It takes the content from the last commit (HEAD) and puts it into the staging area, effectively “unstaging” your current changes for that file. The modified content of the file remains untouched in your working directory.
  • git reset HEAD <file>: In older Git versions, git reset was used for this purpose. HEAD refers to your current commit. By specifying HEAD and a file path, you are telling Git to reset the staging area for that file to match the state of HEAD. Your local changes are preserved in the working directory.

Notes/Warnings:

  • Preserves Working Directory: Both commands do not discard your actual file modifications in the working directory. They only remove the changes from the staging area. The file will appear as “modified” (unstaged) in git status.
  • Selective Unstaging: This is perfect for when you have git added too much, and you want to selectively decide what goes into your next commit. Another alternative would be to use .gitignore.

Scenario 2: How to Unstage ALL Changes?

If you have staged everything using the git add . or git add * command and then noticed that none of it should be committed yet, or you want to rethink what should go to your commit, you can unstage all changes.

Question: How do I unstage all changes that are currently in the staging area, moving them all back to the working directory?

Answer: Use git restore --staged . (recommended) or git reset HEAD.

Code:

# -------------------
# Unstage ALL Changes
# -------------------

# Step 1: Make some changes and stage them
echo "change A" > file1.txt &&
echo "change B" > file2.txt &&
mkdir new_folder && echo "new content" > new_folder/new_file.txt

# Step 2: Stage the changes using git add command
git add .

# Step 3: Check the status 
git status 

# Output:
# On branch main
# Changes to be committed:
#   (use "git restore --staged <file>..." to unstage)
#         new file:   file1.txt
#         new file:   file2.txt
#         new file:   new_folder/new_file.txt
 
# Observe: See all new/modified files are staged

# Step 4: Unstage everything
git restore --staged .

# Or, for older Git versions
# git reset HEAD

# Step 5: Check status again
git status

# Output:
# On branch main
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#         file1.txt
#         file2.txt
#         new_folder/
# 
# nothing added to commit but untracked files present (use "git add" to track)

# Observe: All changes are now 'untracked' (moved to working directory)

# Bonus: 
# Step 6: Delete the unstaged files from working directory
git clean -fd

# Output:
# Removing file1.txt
# Removing file2.txt
# Removing new_folder/

# Step 7: Check status one last time
git status

# Output:
# On branch main
# nothing to commit, working tree clean

Explanation:

  • git restore --staged .: Similar to the single-file version, the . (dot) indicates the current directory, applying the un-staging operation to all tracked files.
  • git reset HEAD: When git reset HEAD is used without a file path, it defaults to resetting the entire staging area to match the HEAD commit.

Notes/Warnings:

  • Preserves Working Directory: Just like with a single file, this command only unstages the changes. Your modifications remain in your working directory as “modified” (for tracked files) or “untracked” (for new files).
  • New Files Remain Untracked: If you git added a new file, then git restore --staged . will unstage it, and it will revert to an “untracked” status in your working directory. You had then use git clean (as discussed in Part 1) if you want to permanently delete it.

Scenario 3: How to Remove a File from Staging Area But Keep it in the Working Directory?

You accidentally git added a file that should never be part of your repository (e.g., a .env file, an IDE configuration, or a large log file). You want to prevent it from being committed, but you do not want to delete the file from your local machine either. This is a common precursor to adding it to your .gitignore file.

Question: How do I remove a file from the staging area without deleting it from my working directory?

Answer: Use git restore --staged <file> (same as unstage) or git rm --cached <file>.

Code:

# ---------------------------------------------------------------
# Remove a File from Staging But Keep it in the Working Directory
# ---------------------------------------------------------------

# Step 1: Create and stage a file to simulate accidental add
echo "MY_API_KEY=123" > .env && git add .env

# Step 2: Check the status
git status 

# Output:
# On branch main
# Changes to be committed:
#   (use "git restore --staged <file>..." to unstage)
#         new file:   .env

# Step 3: Remove .env from staging, but keep it locally
git rm --cached .env

# Alternative command to do the same thing
# git restore --staged .env 

# Step 4: Check status again
git status

# Output:
# On branch main
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#         .env
# 
# nothing added to commit but untracked files present (use "git add" to track)

Explanation:

  • git rm --cached <file>: This command explicitly tells Git to “remove” the file from the index (staging area) while leaving the physical file untouched in your working directory. This is the ideal command when you want to add the file to .gitignore immediately afterward.
  • git restore --staged <file>: While this also achieves the same result (unstaging the file and keeping it locally), git rm --cached explicitly communicates the intent of removing it from tracking, which is often the goal when a file was accidentally added.

Notes/Warnings:

  • Follow up with .gitignore: If you perform this action because a file should be ignored, immediately add its entry to your .gitignore file to prevent it from being accidentally staged again in the future.
  • No Deletion: This command will not delete the file from your local file system.

Scenario 4: How to Remove a File from Staging AND Delete it from the Working Directory?

In some rare cases, you might have created a new file, added it to staging, and then decided you want to completely get rid of it – both from staging and from your local disk.

Question: How do I remove a file from the staging area AND delete it from my working directory?

Answer: Use git rm <file>.

Code:

# -------------------------------------------------------------------
# Remove a File from Staging AND Delete it from the Working Directory
# -------------------------------------------------------------------

# Step 1: Create and stage a file 
echo "hello world" > file1.txt && git add file1.txt

# Step 2: Check current status
git status

# Output:
# On branch main
# Changes to be committed:
#   (use "git restore --staged <file>..." to unstage)
#         new file:   file1.txt
# 
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#         .env

# Observe:
# .env is currently in working directory ready to staged 
# file1.txt is currently in staging area, ready to get commited 

# Step 3: Delete a file from staging area and working directory
git rm -f file1.txt

# Output:
# rm 'file1.txt'

# Step 4: Check the status once again
git status

# Output:
# On branch main
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#         .env
# 
# nothing added to commit but untracked files present (use "git add" to track)

# Bonus: 
# Step 5: Delete the unstaged files/dir from working directory
git clean -fd

# Output:
# Removing .env

# Step 6: Check status one last time
git status

# Output:
# On branch main
# nothing to commit, working tree clean

Explanation:

  • git rm -f <file>: This command stages the deletion of the specified file and also deletes it from your working directory. It is effectively performing rm <file> and then git add <file> (to stage the deletion). When you commit next, this file will be removed from the repository’s history.

Notes/Warnings:

  • Permanent Deletion: This command permanently deletes the file from your working directory.
  • Stages Deletion: The deletion itself is staged. You still need to commit to finalize the removal from your repository’s history. If you do not commit, and you accidentally run git restore temp_file.txt, it would bring the file back!

Summary:

Scenario / GoalCommandDescriptionNotes / Warnings
Unstage changes in a single filegit restore --staged <file>Moves specific file changes from staging to working dir.Working directory changes are preserved.
Unstage ALL changes in staging areagit restore --staged .Moves all staged changes back to working directory.Working directory changes are preserved. New untracked files remain untracked.
Remove file from staging (keep locally)git rm --cached <file>Removes file from staging, but keeps it on your disk.Ideal before adding to .gitignore. File becomes untracked locally.
Remove file from staging AND delete locallygit rm -f <file>Deletes file from disk and stages the deletion.DANGER! File is permanently deleted from your working directory. Committing finalizes removal from repo.

References (Official Git Documentation):

Author

Debjeet Bhowmik

Experienced Cloud & DevOps Engineer with hands-on experience in AWS, GCP, Terraform, Ansible, ELK, Docker, Git, GitLab, Python, PowerShell, Shell, and theoretical knowledge on Azure, Kubernetes & Jenkins.
In my free time, I write blogs on ckdbtech.com

Leave a Comment