1. Introduction
The Git versioning system has three main trees:
- working tree
- staging tree
- commit tree
The usual workflow between these structures is to first make changes to the working tree, then stage some or all of these changes, and finally create a commit from the staging tree. Yet, there are often times when might want to unstage changes instead of committing them.
In this tutorial, we talk about ways to remove a file from staging and the difference between several related options. First, we explore the standard way for unstaging. After that, we go over another fairly common method, which got superseded. Finally, we turn to a relatively dangerous command with additional side effects.
We tested the code in this tutorial on Debian 12 (Bookworm) with GNU Bash 5.2.15. Unless otherwise specified, it should work in most POSIX-compliant environments.
2. Standard restore –staged
The correct and currently standard way to unstage a file is part of the output that the status subcommand of git returns about staged files:
$ echo $(date) >> file
$ git add file
$ git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: file
Effectively, the separate restore subcommand can return a working tree path to another state via a source. Notably, the argument is mandatory and it can consist of more than one path.
In practice, using –staged moves the changes from the staging tree to the working directory tree:
$ git restore --staged file
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: file
no changes added to commit (use "git add" and/or "git commit -a")
Thus, the modifications are preserved, but not as part of the stage. This is by far the safest method that only unstages files without unexpected side effects.
Notably, we can see the hint above also indicates that we can use restore to discard the working tree changes as well.
3. Revert With reset
Similar to restore, the reset subcommand enables reverting the state of files based on a source. In this case, the source is most often a commit.
Notably, changes are the difference between the combination of the current working and staging trees on one hand, and the HEAD – on the other. So, we can essentially revert any part of the stage to HEAD, thereby negating the difference:
$ git reset HEAD
Unstaged changes after reset:
M file
$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: file
no changes added to commit (use "git add" and/or "git commit -a")
While this works in simple cases, we might experience issues if we perform an incorrect reset type or point to the wrong commit.
On the other hand, a reset doesn’t need specific paths to operate, so we can unstage all files at once.
4. Removal With rm –cached
Although not specifically directed at unstaging, the rm subcommand enables file removal related to Git.
Let’s see its basic use:
$ git rm <FILE1> <FILE2> <FILE3> ... <FILEn>
This way, all file paths we pass get deleted from both the working and the staging tree.
However, unstaging usually only entails removal from staging. To avoid the side effect of removing the file from the filesystem, we can add the –cached flag:
$ git rm --cached <FILE1> <FILE2> <FILE3> ... <FILEn>
Now, we preserve the file object. However, the file also becomes untracked as a side effect.
Let’s see an example:
$ echo $(date) >> file
$ git add file
At this point, file is a tracked modified file.
Now, we can perform the cached removal:
$ git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
deleted: file
Untracked files:
(use "git add <file>..." to include in what will be committed)
file
There are two differences between this result and the one from restore:
- file has one staged change (deletion) pending, but that doesn’t relate to its content
- file is untracked
Depending on the circumstances, this may be a problem or a way to preserve the current working tree file version permanently.
5. Summary
In this article, we talked about ways to unstage a file in Git and the consequences of doing so with some commands.
In conclusion, while there are several methods for removing files from staging, only some of them perform that task exclusively.