Going back in time with git

jdavidb on 2008-06-19T16:14:39

There are several ways to deal with your history in git. The differences between these have been confusing me. The command you want to use depends on your purpose: do you want to just go back and temporarily use or look at the tree from an earlier state? (or perhaps start a branch at that state and explore a new alternate development track without losing the one you already took?) Or do you want to completely eliminate the most recent changes from your branch because they were complete mistakes, and you'd like them to not even exist in the branch history? Or do you want to revert, leaving a record of the changes that were made but undoing them, creating a new commit that records your reversion of changes, and possibly leaving later changes intact? All of these use different commands.

git log
commit 1228c62eac1ec083abf56947d1e4581dd8a5dee5
Author: David 
Date:   Thu Jun 19 15:44:23 2008 +0000

    Export subsystem to legacy application.

commit 62bece1638e0a8cba863f6560490424562673df0
Merge: 3f0144d... 9934f57...
Author: David 
Date:   Thu Jun 19 15:36:08 2008 +0000

    Merge branch 'master' into local

commit 9934f57d37af0e924dac2e698b1395571e0d999f
Author: David 
Date:   Thu Jun 19 15:36:02 2008 +0000

    Pull from CVS

commit 3f0144d972b383afc73ea10984bd043d0873dedb
Merge: 9cdc362... 406bba3...
Author: David 
Date:   Thu Jun 19 14:30:43 2008 +0000

    Big changes here

To temporarily go back to a previous commit:

git checkout 62be

This will set the state of your tree to the specified commit. You will no longer be on a branch, even if there happens to be a branch whose head is equal to that commit. If you'd like to start a branch, you can do this:

git checkout -b new_branch 62be  # if you haven't run checkout, yet
git checkout -b new_branch  # if you have

To roll your current branch back to an earlier state, removing subsequent changes from the history of this branch (they may still exist on other branches, and they won't be removed from your repository unless you run git-gc, although if there is no other branch containing them and you don't keep a record of them, they'll basically be inaccessible):

git reset --hard 9934

In this example, commits 62be and 1228 go away. Along with any uncommitted changes I have, by the way.

To revert a specific change:

git revert 62be

This computes the diff from 62be and the previous change (9924), and then undoes that diff, basically by creating its inverse and applying it to your current HEAD, in this case, 1228. So whatever change happened in commit 1228 will still exist, as much as possible, barring possible conflicts and errors. I think you'll get a chance to fix those manually if that happens. After the revert, the command will commit for you, filling in a sample commit message that mentions which commit(s) were reverted, but giving you a chance to edit it. If you change your mind afterward (or realize when the editor pops up that you didn't mean to run git revert, as I've often done), then just check the log and eliminate the revert commit with git reset --hard, as described above.

In this case, commit 62be is a merge, and it has two parents. There's no way to tell which of the parents is the "previous commit." (You may be in the branch that you would like to use as the conceptual parent of the commit you're reverting, but all git knows about that branch is which commit is its head right now; it doesn't keep track of the fact that one of those parent commits "used to be" the HEAD of this branch.) In this case, you can specify which commit to use as the parent with the -m option.