[git] Why are there two ways to unstage a file in Git?

Sometimes git suggests git rm --cached to unstage a file, sometimes git reset HEAD file. When should I use which?

EDIT:

D:\code\gt2>git init
Initialized empty Git repository in D:/code/gt2/.git/
D:\code\gt2>touch a

D:\code\gt2>git status
# On branch master
#
# Initial commit
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       a
nothing added to commit but untracked files present (use "git add" to track)

D:\code\gt2>git add a

D:\code\gt2>git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
#   (use "git rm --cached <file>..." to unstage)
#
#       new file:   a
#
D:\code\gt2>git commit -m a
[master (root-commit) c271e05] a
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 a

D:\code\gt2>touch b

D:\code\gt2>git status
# On branch master
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       b
nothing added to commit but untracked files present (use "git add" to track)

D:\code\gt2>git add b

D:\code\gt2>git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       new file:   b
#

This question is related to git git-reset git-rm

The answer is


In the newer version that is > 2.2 you can use git restore --staged <file_name>. Note here If you want to unstage (move to changes) your files one at a time you use above command with your file name. eg

git restore --staged abc.html

Now if you want unstage all your file at once, you can do something like this

git restore --staged .

Please note space and dot (.) which means consider staged all files.


Quite simply:

  • git rm --cached <file> makes git stop tracking the file completely (leaving it in the filesystem, unlike plain git rm*)
  • git reset HEAD <file> unstages any modifications made to the file since the last commit (but doesn't revert them in the filesystem, contrary to what the command name might suggest**). The file remains under revision control.

If the file wasn't in revision control before (i.e. you're unstaging a file that you had just git added for the first time), then the two commands have the same effect, hence the appearance of these being "two ways of doing something".

* Keep in mind the caveat @DrewT mentions in his answer, regarding git rm --cached of a file that was previously committed to the repository. In the context of this question, of a file just added and not committed yet, there's nothing to worry about.

** I was scared for an embarrassingly long time to use the git reset command because of its name -- and still today I often look up the syntax to make sure I don't screw up. (update: I finally took the time to summarize the usage of git reset in a tldr page, so now I have a better mental model of how it works, and a quick reference for when I forget some detail.)


git rm --cached is used to remove a file from the index. In the case where the file is already in the repo, git rm --cached will remove the file from the index, leaving it in the working directory and a commit will now remove it from the repo as well. Basically, after the commit, you would have unversioned the file and kept a local copy.

git reset HEAD file ( which by default is using the --mixed flag) is different in that in the case where the file is already in the repo, it replaces the index version of the file with the one from repo (HEAD), effectively unstaging the modifications to it.

In the case of unversioned file, it is going to unstage the entire file as the file was not there in the HEAD. In this aspect git reset HEAD file and git rm --cached are same, but they are not same ( as explained in the case of files already in the repo)

To the question of Why are there 2 ways to unstage a file in git? - there is never really only one way to do anything in git. that is the beauty of it :)


For versions 2.23 and above only,

Instead of these suggestions, you could use git restore --staged <file> in order to unstage the file(s).


1.

D:\code\gt2>git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
#   (use "git rm --cached <file>..." to unstage)
#
#       new file:   a

(use "git rm --cached ..." to unstage)

  • git is a system of pointers

  • you do not have a commit yet to change your pointer to

  • the only way to 'take files out of the bucket being pointed to' is to remove files you told git to watch for changes

2.

D:\code\gt2>git commit -m a
[master (root-commit) c271e05] a
0 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 a

git commit -m a

  • you commited, 'saved'

3.

D:\code\gt2>git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       new file:   b
#

(use "git reset HEAD ..." to unstage)

  • you made a commit in your code at this time
  • now you can reset your pointer to your commit 'revert back to last save'

It would seem to me that git rm --cached <file> removes the file from the index without removing it from the directory where a plain git rm <file> would do both, just as an OS rm <file> would remove the file from the directory without removing its versioning.


I'm surprised noone mentioned the git reflog (http://git-scm.com/docs/git-reflog):

# git reflog
<find the place before your staged anything>
# git reset HEAD@{1}

The reflog is a git history that not only tracks the changes to the repo, but also tracks the user actions (Eg. pull, checkout to different branch, etc) and allows to undo those actions. So instead of unstaging the file that was mistakingly staged, where you can revert to the point where you didn't stage the files.

This is similar to git reset HEAD <file> but in certain cases may be more granular.

Sorry - not really answering your question, but just pointing yet another way to unstage files that I use quite often (I for one like answers by Ryan Stewart and waldyrious very much.) ;) I hope it helps.


This thread is a bit old, but I still want to add a little demonstration since it is still not an intuitive problem:

me$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   new file:   to-be-added
#   modified:   to-be-modified
#   deleted:    to-be-removed
#

me$ git reset -q HEAD to-be-added

    # ok

me$ git reset -q HEAD to-be-modified

    # ok

me$ git reset -q HEAD to-be-removed

    # ok

# or alternatively:

me$ git reset -q HEAD to-be-added to-be-removed to-be-modified

    # ok

me$ git status
# On branch master
# Changes not staged for commit:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   to-be-modified
#   deleted:    to-be-removed
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#   to-be-added
no changes added to commit (use "git add" and/or "git commit -a")

git reset HEAD (without -q) gives a warning about the modified file and its exit code is 1 which will be considered as an error in a script.

Edit: git checkout HEAD to-be-modified to-be-removed also works for unstaging, but removes the change completely from the workspace

Update git 2.23.0: From time to time, the commands change. Now, git status says:

  (use "git restore --staged <file>..." to unstage)

... which works for all three types of change


if you've accidentally staged files that you would not like to commit, and want to be certain you keep the changes, you can also use:

git stash
git stash pop

this performs a reset to HEAD and re-applies your changes, allowing you to re-stage individual files for commit. this is also helpful if you've forgotten to create a feature branch for pull requests (git stash ; git checkout -b <feature> ; git stash pop).


These 2 commands have several subtle differences if the file in question is already in the repo and under version control (previously committed etc.):

  • git reset HEAD <file> unstages the file in the current commit.
  • git rm --cached <file> will unstage the file for future commits also. It's unstaged untill it gets added again with git add <file>.

And there's one more important difference:

  • After running git rm --cached <file> and push your branch to the remote, anyone pulling your branch from the remote will get the file ACTUALLY deleted from their folder, even though in your local working set the file just becomes untracked (i.e. not physically deleted from the folder).

This last difference is important for projects which include a config file where each developer on the team has a different config (i.e. different base url, ip or port setting) so if you're using git rm --cached <file> anyone who pulls your branch will have to manually re-create the config, or you can send them yours and they can re-edit it back to their ip settings (etc.), because the delete only effects people pulling your branch from the remote.


Just use:

git reset HEAD <filename>

This unstages the file and keeps the changes you did to it, so you can, in turn, change branches if you wanted and git add those files to another branch instead. All changes are kept.


Let's say you stage a whole directory via git add <folder>, but you want to exclude a file from the staged list (i.e. the list that generates when running git status) and keep the modifications within the excluded file (you were working on something and it's not ready for commit, but you don't want to lose your work...). You could simply use:

git reset <file>

When you run git status, you will see that whatever file(s) you reset are unstaged and the rest of the files you added are still in the staged list.


Examples related to git

Does the target directory for a git clone have to match the repo name? Git fatal: protocol 'https' is not supported Git is not working after macOS Update (xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools) git clone: Authentication failed for <URL> destination path already exists and is not an empty directory SSL_connect: SSL_ERROR_SYSCALL in connection to github.com:443 GitLab remote: HTTP Basic: Access denied and fatal Authentication How can I switch to another branch in git? VS 2017 Git Local Commit DB.lock error on every commit How to remove an unpushed outgoing commit in Visual Studio?

Examples related to git-reset

How to undo the last commit in git How can I move HEAD back to a previous location? (Detached head) & Undo commits What is difference between 'git reset --hard HEAD~1' and 'git reset --soft HEAD~1'? Git, How to reset origin/master to a commit? Can I delete a git commit but keep the changes? What is the meaning of git reset --hard origin/master? How to git reset --hard a subdirectory? git undo all uncommitted or unsaved changes Unstaged changes left after git reset --hard How do I use 'git reset --hard HEAD' to revert to a previous commit?

Examples related to git-rm

Remove a folder from git tracking Staging Deleted files Why are there two ways to unstage a file in Git? How to remove multiple deleted files in Git repository "git rm --cached x" vs "git reset head --? x"? Git: list only "untracked" files (also, custom commands) Git add all files modified, deleted, and untracked? Git: How to remove file from index without deleting files from any repository How to revert a "git rm -r ."? How can I delete a file from a Git repository?