Why there are two ways to unstage a file in Git?
Asked Answered
T

15

1472

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

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
#
Treble answered 2/8, 2011 at 21:50 Comment(7)
Why? I'd say it's because git's commandline interface evolved organically and has never been subject to a major restructuring to make things consistent. (If you disagree, note how git rm can both stage a deletion and also unstage an addition)Carnarvon
@romkyns: I agree that Git's interface has several oddities because it evolved organically, but a removal is surely an inverse function of an addition, so isn't it logical for rm to undo add? How do you think rm should behave?Rager
The only actual answer to your question is that right after a git init there is no HEAD to reset to.Pirog
Best docs for this: help.github.com/articles/changing-a-remote-s-urlPutman
@Zaz, I'll give my opinion. rm implies deletion in a unix context. It is not the opposite of adding to the index. A function to remove files shouldn't be overloaded with functions to change the staging-state. If there are implementation details that make those convenient to combine, that simply points to the lack of a thoughtful layer of abstraction in git, which would make usability clear.Piranha
Unstaging, removing or cleaning working tree could be achieved with git stash technique. See it in action: https://mcmap.net/q/12971/-git-stash-uncached-how-to-put-away-all-unstaged-changes, full docs: git-scm.com/docs/git-stash. Lastly git stash drop will poof away all your magic like nothing happened :)Cholera
These were very useful. I wanted to reset a file I had changed. This required the additional step of git checkout <file>. I know it is oblivious, but ...Closet
T
2338

git rm --cached <filePath> does not unstage a file, it actually stages the removal of the file(s) from the repo (assuming it was already committed before) but leaves the file in your working tree (leaving you with an untracked file).

git reset -- <filePath> will unstage any staged changes for the given file(s).

That said, if you used git rm --cached on a new file that is staged, it would basically look like you had just unstaged it since it had never been committed before.

Update git 2.24
In this newer version of git you can use git restore --staged instead of git reset. See git docs.

Tumble answered 2/8, 2011 at 22:3 Comment(16)
I would say git rm --cached unstages the file but doesn't remove it from the working directory.Clansman
This is a bit like cvs rm to locally undo cvs add all over again.Lighthouse
To remove a file staged for addition so that it is no longer staged can surely be called "unstaging a file staged for addition", right? The end result is not a staged deletion, that's for sure, hence I think the misunderstanding is totally understandable.Carnarvon
So typically, one would use git rm --cached <filePath> to remove some file(s) from the repo after realizing it should have never been in the repo: so most likely running this command & then adding the relevant files to gitignore. Am I correct?Housewarming
git reset HEAD {filename}Fretwell
thanks so much, but some people may want extra clarification that git rm does not remove the file with --cached as in @waldyrious's answerLientery
@Ryan Stewart, is the -- in git reset -- <filePath> really necessary? This answer just uses git reset <file>: https://mcmap.net/q/12972/-how-do-i-undo-39-git-add-39-before-commit.Memory
git rm --cached tells git to stop tracking a file... that's not what I wanted. I wanted to unstage a file just for now, not stop tracking it all together. :(Cleome
git reset -- <filePath> is soft reset, unstages the file!Kubetz
With this many votes on both question and answer, I would say that apparently we want to have an unstage command in git.Vindicate
"git status" advises now: use "git restore --staged <file>..." to unstageArlenarlena
Just a sidenote: after "git stash" and then "git stash pop" also all files that where staged are unstaged now.Scream
It is still annoying that the "index" is here called "cache" (--cached option) and in git restore --staged it is called yet something else. The original name "index" is of course the most misleading one, but after that, couldn't they settle on "staging area" immediately?Discoloration
I wish I could upvote this every time I refer back to it.Shoa
Why can't we have git stage and git unstage instead of adding ever new variations of obscure commands?Ado
@tobi_s, exactly. reset and revert were pretty unnecessarily confusing already alone, so there, now we have restore to "fix" that... (Which means we'll first have to try and carefully decipher from its doc page whether it's eagerly waiting to be destructive if we forget to add some modifier, and then giving that up we're just gonna google for it dammit, as usual for this hopeless mess of what the git UI is, ending up at this page here again and again from now on, as seen from the counters... :) )Boucicault
R
349

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 :)

Requiem answered 2/8, 2011 at 23:0 Comment(3)
Both the accepted answer and this one are great, and explain why you would use one vs the other. But they don't directly answer the implicit question of why does git suggest two different methods. In the first case in the OP's example, a git init has just been done. In that case, git suggests "git rm --cached" because at that point there are no commits in the repository and so HEAD is not valid. "git reset HEAD -- a" produces: "fatal: Failed to resolve 'HEAD' as a valid ref."Disincentive
with 'git checkout', wouldn't you lose all changes that you made to the file?That's not the same thing as unstaging a file, unless I'm misunderstanding.Hyperbolize
there is never really only one way to do anything in git. that is the beauty of it - Hmm... why ? it is always great, when there is only one obvious way. this saves a lot of our time and memory in brain ))Chaffee
S
151

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.)

Slowwitted answered 17/10, 2014 at 21:16 Comment(6)
It's git rm <file> --cachedCordiecordier
I really don't think the edit of Aug 4 2015 to this answer was an overall improvement. It might have fixed technical correctness (I don't feel qualified to evaluate that), but I'm afraid it made the tone of the answer much less accessible, by introducing language like "unsets the imperative to begin tracking a currently-untracked file", and using jargon like "index" and "HEAD", precisely the kind of stuff that scares off beginners. If someone can, please edit to restore a more newcomer-friendly language.Slowwitted
Agree with @waldyrious. The original answer might not have been straight out of the git textbook, but it answered the question at a sufficient technical level. Technical details should have been clarified in comments, not as an edit that obscured the original intent.Lyra
I've reverted the edit. I believe there's been sufficient validation by the community (in the previous comments and the votes on them) that the edit was detrimental to the clarity of the answer.Slowwitted
Note @DrewT warns that if using rm --cached and pushing, anyone pulling the same branch will have the file(s) actually removed from their working tree.Gymnastics
@TomHale thanks for the heads-up. I added an additional note to the answer to point this out.Slowwitted
M
60

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

Mosely answered 16/4, 2013 at 19:0 Comment(3)
Thanks, wasn't entirely clear from the first two answers (probably just my ignorance on terminology) that git reset left the modifications in the file locally (as opposed to git checkout which would revert them).Traps
You should put a warning in the begining about the version, because old version deletes the files in the new versionsElfland
@DanielAlder sry, I just retested, it does not delete, my mistake.Elfland
B
38

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).

Bate answered 10/2, 2015 at 17:6 Comment(2)
This is a clean solution and much less worrisome than typing "git rm"Clingfish
git stash has other related benefits, because it creates entries in the reflog which are then available in the future. when in doubt, go ahead and do a git stash (e.g. git stash save -u "WIP notes to self" (the '-u' is to include any new/untracked files in the stash commit) ... then try git reflog show stash to see the list of stash commits and their sha's. I recommend a shell alias like alias grs="git reflog show stash"Ingaborg
C
28

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.

Cinchonine answered 12/2, 2021 at 12:53 Comment(1)
. if in project root.Estonian
L
16

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.

Lahnda answered 10/8, 2014 at 20:4 Comment(0)
I
13

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.

Instruct answered 28/8, 2015 at 16:8 Comment(1)
idk why everyone else likes so much complexity. This is a good answer.Putman
G
11

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'
Gruber answered 8/9, 2016 at 16:26 Comment(1)
This is actually the only answer that properly answers the question, IMO. It actually answers the question, which is not 'what are the differences between 'git rm --cached' and 'git reset HEAD' but 'why does git inconsistently give both as options?', the answer being that there's no HEAD to reset to when you git init for the first time.Pirog
B
10

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.

Batton answered 10/9, 2019 at 23:38 Comment(0)
I
9

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).

Impartible answered 29/10, 2019 at 15:20 Comment(1)
It works with both the options --stage as well as --staged.Runt
R
9

Unstaging files (undoing git add)

git restore --staged file.js # Copies the last version of file.js from repo to index

discarding local changes

git restore file.js # Copies file.js from index to working directory

git restore file1.js file2.js # Restores multiple files in working directory

git restore . # Discards all local changes (except untracked files)

git clean -fd # Removes all untracked files

Rational answered 17/1, 2022 at 8:23 Comment(1)
Worth mentioning that you'd need a git version > 2.23.0 to use git restoreLyrist
M
5

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.

Mathew answered 10/2, 2015 at 12:6 Comment(0)
R
4

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.

Riding answered 2/8, 2011 at 21:51 Comment(0)
C
0

I have gone through the above commands but the command which worked for is like when you are on the root of the project just adding a screenshot for more clarity

git rm --cached 'ios/AppName/Info.plist'

OR

git reset HEAD 'ios/AppName/Info.plist'

enter image description here

Cream answered 7/11, 2023 at 8:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.