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 l开发者_开发知识库osing the content?
Also it would be helpful if someone can explain the way this pipe operation works.
git reset
If all you want is to undo an overzealous "git add" run:
git reset
Your changes will be unstaged and ready for you to re-add as you please.
DO NOT RUN git reset --hard
.
It will not only unstage your added files, but will revert any changes you made in your working directory. If you created any new files in working directory, it will not delete them though.
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 :)
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.
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 ofgit 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
orgit 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 ofgit reset HEAD <file>
as it used to prior to v2.23).
git stash && git stash pop
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.
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.
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
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
精彩评论