The first questions that keep coming up during the first months using Git probably look like these: How do I undo git add
? How do I undo git commit
? How do I undo xxx?
These days I realized that there is really no “undoing” of most commands in Git. Essentially, you are always moving forward.
Take git add
for example. The popular ways to undo git add
are:
git reset HEAD <filename>
(and its variations,git reset
,git reset .
etc.) Since from 1.8.2 Git decides to throw in an empty index forHEAD
instead of just erroring, this command is applicable even with a repository with no previous commit. You may need to rungit gc --prune=now
to remove all loose objects but it is fine to leave them there (they would not interfere with your work and the memories would eventually be reclaimed).git rm --cached <filename>
Adding--cached
will let yourgit rm
command remove paths only from the index while leaving the working tree files intact, whether modified previously or not.
However, both are not perfect.
git rm --cached <filename>
, as the documentation goes, simply removes a file path from the index. This is not ideal. For example, in the case where your file has a few correct git add
or/and git commit
before you modified the file and made an erroneous git add
(which you want to undo), the git rm --cached
command will remove and unstage this file entirely and discarding all previous commits. This is probably not what you want.
In other words, git add
have two use cases: track a new file, or update content of a previously tracked file. git rm --cached <filename>
works as a undo for the first case but not the second.
git reset HEAD <filename>
is generally more preferable as it seems to work for both cases. However, it is still not an undo, since all it does it simply resetting the repository to the HEAD
commit. If your file has a few correct git add
(for updating content) before a erroneous git add
, those previous git add
will not survive the git reset
either.
In a way, all consecutive git add
s are “combined”. You could only commit them all or reset them all.
Example:
mkdir test
cd test
git init
vim dummy # type in 0 and exit
git add dummy
git status # dummy has staged but uncommitted changes: 0
git commit -m "0"
git status # working tree clean
vim dummy # type in 1 and exit
git status # dummy has unstaged changes: 1
git add dummy
git status # dummy has staged but uncommitted changes: 1
vim dummy # type in 2 and exit
git status # dummy has unstaged changes: 2; staged but uncommitted changes: 1
git add dummy
git status # dummy has staged but uncommitted changes: 12
vim dummy # type in "wrong" and exit
git status # dummy has unstaged changes: 12; staged but uncommitted changes: wrong
git add dummy
git status # dummy has staged but uncommitted changes: 12wrong
git reset HEAD dummy # Or `git restore --staged dummy`; they behave the same here
git status # dummy has unstaged changes: 12wrong
cat dummy #prints 012wrong
# Notice the correct changes of 1 and 2 also gets unstaged
# Also, working directory changes are still there, just unstaged
# If want to remove those working directory changes:
git checkout -- dummy #Or `git restore dummy`; they behave the same here
git status # working tree clean. So all working directory changes are gone now
cat dummy #prints 0
Hence there is really no perfect “undo command” for git add
. That is probably why most command names in Git are not symmetric (add
and unadd
, for example), because the processes are indeed not perfectly symmetric.
Hence, be mindful when you try to invent alias for undo commands. When you follow an online tutorial and start to do something like these:
git config --global alias.unadd 'reset HEAD --'
git config --global alias.unstage 'reset HEAD --'
Always keep in mind what your command actually do.
Update: Might git restore --staged
(new in 2.35.1) work as a better option?
Git used to recommend git reset
in its bash terminal but now it decides to recommend git restore
instead.
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: xxxxx.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
xxxxx.txt
I have not looked into this command in detail. Some basic testings show that they behave the same.
- Post link: https://reimirno.github.io/2022/03/26/Staged-but-Uncommited-Files-and-Git-Add/
- Copyright Notice: All articles in this blog are licensed under unless otherwise stated.
GitHub Discussions