Thursday, September 24, 2009

Git undo, reset or revert?

If you have found this page you probably came here since you wanted to clear your working directory from all the changes that you have made.

The simple answer is:

# Clear working directory tree from all changes
$ git checkout -f HEAD

This is, however, not the best way to do it. A better way is:

# Clears the working directory tree, and stashes all the changes.
$ git stash

git stash allows you to get your changes back any time you need them in case you change your mind. It is also possible to inspect and manipulate the stashes.

# List all the stashes
$ git stash list
stash@{0}: WIP on admin_ui: 0c1a80a Removed annotation from JdbcAdminService, it is now explicity initialized in the applicationContext.
stash@{1}: WIP on admin_ui: 14e12e6 Added foreign keys for UserRole
stash@{2}: WIP on master: d188ecd Merge branch 'master' of semc-git:customercare
stash@{3}: WIP on master: 3763795 More work on user_details.

# Apply the latest stash, and remove it from the stack
$ git stash pop

# Apply a named patch, but leave it on the stack
$ git stash apply stash@{2} 

# Drop a stash
$ git stash drop stash@{3} 

# Clear the entire stash stack (almost never needed)
$ git stash clear

# A better way to purge the stash
$ git reflog expire --expire=30.days refs/stash

What about git reset then, it sounds like it should do about the same as git co -f HEAD. It doesn't. git reset is used for setting the current reference pointer, HEAD.

# Reset the latest commit, and leave the changes in the index.
$ git reset --soft HEAD^

# Reset the latest commit, and leave the changes in the working directory
$ git reset HEAD^

# Undo add, move the changes from the index to the working directory
$ git reset

# Reset the latest successful pull or merge
$ git reset --hard ORIG_HEAD

# Reset the latest failed pull or merge
$ git reset --hard

# Reset the latest pull or merge, into a dirty working tree
$ git reset --merge ORIG_HEAD

You can do more things with reset, but the above covers the typical cases. And now to the last thing, git revert. What does it do? git revert creates a new commit that is the opposite of the commit it names.

# Show the commits
$ git log --oneline
4717a5c new line
7e38e95 added tapir file

# Revert the commit named, 4717a5c, and commit it.
$ git revert 4717a5c

# Revert the HEAD commit, but don't commit it
$ git revert -n HEAD

Git is incredibly flexible and lets you control everything if you want to.


Torkel Ă–degaard said...

Interesting, did not know about the git stash feature.

Anders Janmyr said...

It's a very, very good feature.

Anonymous said...

I came here to learn the diff between "undo, reset or revert".

I learned nothing. :|

Anders Janmyr said...

@Anonymous, I'm sorry to hear that, here is the short gist of it.

git revert COMMIT-SHA creates and commits a new commit from an old commit reversing the changes.

git reset (--mixed) changes what HEAD (in current branch) points to without changing the files. This will make git status show the current working directory as full of modified files. This is relative to the new HEAD.

git reset --soft does the same as git reset --mixed but it also adds the files to the index, as in git reset COMMIT-SHA; git add .

git reset --hard changes what head points to and also resets all the files in the current working directory to match that of the current HEAD. This is similar to git reset COMMIT-SHA; git co -f .

I hope this clarifies it for you.

dare to win said...

ahh .. learnt something new that is git stash.
thanks for posting this.

Ethan Furman said...

Exactly the information I was looking for. Thank you.

Anonymous said...

:( I just want to roll back to several versions ago, but get a merge conflict and then end up trawling through long-winded explanations.

I only want to roll back - why would I need to resolve a merge?!

Anders Janmyr said...

@Unknown, if all you want to do is to roll back three versions, you can do this with:

git reset --hard HEAD~3

if you have the SHA of the commit you want to roll back to from "git log" (for example A23EF78) do

git reset --hard A23EF78

This will reset the current branch to the one you specify.