362

I have executed the following command

git add <foo.java>
git commit -m "add the foo.java file"

How can I delete my local commit now and unstage foo.java?

If I type git reset --hard, I found that it reverts my modified foo.java to the original one.

Jens
  • 5,767
  • 5
  • 54
  • 69
TheOneTeam
  • 25,806
  • 45
  • 116
  • 158

7 Answers7

609

git reset --soft HEAD~1 should do what you want. After this, you'll have the first changes in the index (visible with git diff --cached), and your newest changes not staged. git status will then look like this:

# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   foo.java
#
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       modified:   foo.java
#

You can then do git add foo.java and commit both changes at once.

MrValdez
  • 8,515
  • 10
  • 56
  • 79
Antti
  • 11,944
  • 2
  • 24
  • 29
  • 4
    What is described in this answer is actually what `git commit --amend` does; but with a much more complicated workflow. This does not answer the question OP asked, in spite of giving a good direction (`git reset`). – 7heo.tk May 12 '15 at 11:26
  • 2
    Had to replace the '^' with a '~' to make it work, so it looks like: `git reset --soft HEAD~` – Shahar Mar 24 '16 at 14:02
  • 4
    do git reset --soft HEAD~1 then git reset HEAD – Joko Wandiro May 04 '16 at 11:40
  • For those using zsh and getting a `zsh: no match found` errors, write `noglob git reset --soft HEAD^` – solidak Dec 17 '17 at 06:14
123

Use:

git reset HEAD^

That does a "mixed" reset by default, which will do what you asked; put foo.java in unstaged, removing the most recent commit.

Ryan Stewart
  • 126,015
  • 21
  • 180
  • 199
80

To me, the following is more readable (thus preferable) way to do it:

git reset HEAD~1

Instead of 1, there could be any number of commits you want to unstage.

Andrey Deineko
  • 51,333
  • 10
  • 112
  • 145
  • @AlexandreDaubricourt you can create a command (alias) or a function for your own usage with any name. It can be, for example, `git_unstage(num_of_commits)`. It might take a bit of effort as it might require passing an argument (number of commits), but it's possible. – Andrey Deineko Sep 21 '20 at 07:46
  • I have no doubts about it but this should be a builtin feature. – Alexandre Daubricourt Sep 22 '20 at 09:59
41

git reset --soft is just for that: it is like git reset --hard, but doesn't touch the files.

wRAR
  • 25,009
  • 4
  • 84
  • 97
  • 4
    That was the most easily understandable explanation I've heard yet (in only 11 words)! Thanks! – phreakhead Feb 06 '14 at 09:30
  • 3
    That answer is wrong. `git reset` "is like `git reset --hard` but doesn't touch the files.". Not `git reset --soft`. `git reset --soft` will stage the changes, so you will not have to add them to staging in case you want to commit them, but you will have to `git reset` them (yes, a second time, and without the `--soft`) in case you don't. So that answer is short, but incorrect. – 7heo.tk May 12 '15 at 11:21
19

For unstaging all the files in your last commit -

git reset HEAD~

Lavika
  • 501
  • 3
  • 8
15

"Reset" is the way to undo changes locally. When committing, you first select changes to include with "git add"--that's called "staging." And once the changes are staged, then you "git commit" them.

To back out from either the staging or the commit, you "reset" the HEAD. On a branch, HEAD is a git variable that points to the most recent commit. So if you've staged but haven't committed, you "git reset HEAD." That backs up to the current HEAD by taking changes off the stage. It's shorthand for "git reset --mixed HEAD~0."

If you've already committed, then the HEAD has already advanced, so you need to back up to the previous commit. Here you "reset HEAD~1" or "reset HEAD^1" or "reset HEAD~" or "reset HEAD^"-- all reference HEAD minus one.

Which is the better symbol, ~ or ^? Think of the ~ tilde as a single stream -- when each commit has a single parent and it's just a series of changes in sequence, then you can reference back up the stream using the tilde, as HEAD~1, HEAD~2, HEAD~3, for parent, grandparent, great-grandparent, etc. (technically it's finding the first parent in earlier generations).

When there's a merge, then commits have more than one parent. That's when the ^ caret comes into play--you can remember because it shows the branches coming together. Using the caret, HEAD^1 would be the first parent and HEAD^2 would be the second parent of a single commit--mother and father, for example.

So if you're just going back one hop on a single-parent commit, then HEAD~ and HEAD^ are equivalent--you can use either one.

Also, the reset can be --soft, --mixed, or --hard. A soft reset just backs out the commit--it resets the HEAD, but it doesn't check out the files from the earlier commit, so all changes in the working directory are preserved. And --soft reset doesn't even clear the stage (also known as the index), so all the files that were staged will still be on stage.

A --mixed reset (the default) also does not check out the files from the earlier commit, so all changes are preserved, but the stage is cleared. That's why a simple "git reset HEAD" will clear off the stage.

A --hard reset resets the HEAD, and it clears the stage, but it also checks out all the files from the earlier commit and so it overwrites any changes.

If you've pushed the commit to a remote repository, then reset doesn't work so well. You can reset locally, but when you try to push to the remote, git will see that your local HEAD is behind the HEAD in the remote branch and will refuse to push. You may be able to force the push, but git really does not like doing that.

Alternatively, you can stash your changes if you want to keep them, check out the earlier commit, un-stash the changes, stage them, create a new commit, and then push that.

HieroB
  • 3,917
  • 4
  • 17
  • 22
  • 2
    +1 for the detailed explanation of the operation. IMO this should be the accepted answer! – ISAE Sep 13 '19 at 17:57
6

Lets say you want to unstage changes upto n commits,

Where commit hashes are as follows:

  • h1
  • h2 ...
  • hn
  • hn+1

Then run the following command:
git reset hn

Now the HEAD will be at hn+1. Changes from h1 to hn will be unstaged.

Vasantha Ganesh
  • 4,570
  • 3
  • 25
  • 33