[git] How to unstage large number of files without deleting the content

I accidentally added a lot of temporary files using git add -A

I managed to unstage the files using the following commands and managed to remove the dirty index.

git ls-files -z | xargs -0 rm -f
git diff --name-only --diff-filter=D -z | xargs -0 git rm --cached

The above commands are listed in the git help rm. But sadly, my files were also deleted on execution, even though I had given cache option. How can I clear the index without losing the content?

Also it would be helpful if someone can explain the way this pipe operation works.

This question is related to git version-control

The answer is


If you have a pristine repo (or HEAD isn't set)[1] you could simply

rm .git/index

Of course, this will require you to re-add the files that you did want to be added.


[1] Note (as explained in the comments) this would usually only happen when the repo is brand-new ("pristine") or if no commits have been made. More technically, whenever there is no checkout or work-tree.

Just making it more clear :)


If you want to unstage all the changes use below command,

git reset --soft HEAD

In the case you want to unstage changes and revert them from the working directory,

git reset --hard HEAD

2019 update

As pointed out by others in related questions (see here, here, here, here, here, here, and here), you can now unstage a file with git restore --staged <file>.

To unstage all the files in your project, run the following from the root of the repository (the command is recursive):

git restore --staged .

If you only want to unstage the files in a directory, navigate to it before running the above or run:

git restore --staged <directory-path>

Notes

  • git restore was introduced in July 2019 and released in version 2.23.
    With the --staged flag, it restores the content of the working tree from HEAD (so it does the opposite of git add and does not delete any change).

  • This is a new command, but the behaviour of the old commands remains unchanged. So the older answers with git reset or git reset HEAD are still perfectly valid.

  • When running git status with staged uncommitted file(s), this is now what Git suggests to use to unstage file(s) (instead of git reset HEAD <file> as it used to prior to v2.23).


I'm afraid that the first of those command lines unconditionally deleted from the working copy all the files that are in git's staging area. The second one unstaged all the files that were tracked but have now been deleted. Unfortunately this means that you will have lost any uncommitted modifications to those files.

If you want to get your working copy and index back to how they were at the last commit, you can (carefully) use the following command:

git reset --hard

I say "carefully" since git reset --hard will obliterate uncommitted changes in your working copy and index. However, in this situation it sounds as if you just want to go back to the state at your last commit, and the uncommitted changes have been lost anyway.

Update: it sounds from your comments on Amber's answer that you haven't yet created any commits (since HEAD cannot be resolved), so this won't help, I'm afraid.

As for how those pipes work: git ls-files -z and git diff --name-only --diff-filter=D -z both output a list of file names separated with the byte 0. (This is useful, since, unlike newlines, 0 bytes are guaranteed not to occur in filenames on Unix-like systems.) The program xargs essentially builds command lines from its standard input, by default by taking lines from standard input and adding them to the end of the command line. The -0 option says to expect standard input to by separated by 0 bytes. xargs may invoke the command several times to use up all the parameters from standard input, making sure that the command line never becomes too long.

As a simple example, if you have a file called test.txt, with the following contents:

hello
goodbye
hello again

... then the command xargs echo whatever < test.txt will invoke the command:

echo whatever hello goodbye hello again

Use git reset HEAD to reset the index without removing files. (If you only want to reset a particular file in the index, you can use git reset HEAD -- /path/to/file to do so.)

The pipe operator, in a shell, takes the stdout of the process on the left and passes it as stdin to the process on the right. It's essentially the equivalent of:

$ proc1 > proc1.out
$ proc2 < proc1.out
$ rm proc1.out

but instead it's $ proc1 | proc2, the second process can start getting data before the first is done outputting it, and there's no actual file involved.


Warning: do not use the following command unless you want to lose uncommitted work!

Using git reset has been explained, but you asked for an explanation of the piped commands as well, so here goes:

git ls-files -z | xargs -0 rm -f
git diff --name-only --diff-filter=D -z | xargs -0 git rm --cached

The command git ls-files lists all files git knows about. The option -z imposes a specific format on them, the format expected by xargs -0, which then invokes rm -f on them, which means to remove them without checking for your approval.

In other words, "list all files git knows about and remove your local copy".

Then we get to git diff, which shows changes between different versions of items git knows about. Those can be changes between different trees, differences between local copies and remote copies, and so on.
As used here, it shows the unstaged changes; the files you have changed but haven't committed yet. The option --name-only means you want the (full) file names only and --diff-filter=D means you're interested in deleted files only. (Hey, didn't we just delete a bunch of stuff?) This then gets piped into the xargs -0 we saw before, which invokes git rm --cached on them, meaning that they get removed from the cache, while the working tree should be left alone — except that you've just removed all files from your working tree. Now they're removed from your index as well.

In other words, all changes, staged or unstaged, are gone, and your working tree is empty. Have a cry, checkout your files fresh from origin or remote, and redo your work. Curse the sadist who wrote these infernal lines; I have no clue whatsoever why anybody would want to do this.


TL;DR: you just hosed everything; start over and use git reset from now on.


If HEAD isn't set (i.e., you have no commits yet, but you don't want to just blow away .git because you've already set up other repo config you want to keep), you can also do

git rm -rf --cached .

to unstage everything. This is effectively the same as sehe's solution, but avoids mucking with Git internals.


git stash && git stash pop