Paul's Tutorials - logo2-2Revising History



Of course, one of the important reasons for having version control is being able to go back to an earlier version if need be.

Referencing from HEAD

If you remember from lesson 1-1, it is possible to reference a commit using the first 7 or more characters of a commit hash. However, even the first 7 characters of a commit hash can be difficult to remember quickly, and usually involves copying and pasting, which can be difficult to do in the terminal under certain circumstances. But, if you can remember how many commits away the commit you want is from the local HEAD (as in the last commit you have locally, including unpushed commits), then there is another way to reference it.

If you want to refer to the parent of the HEAD commit, then you would use the parent symbol, ^:

HEAD^

If you want to refer to commits beyond the parent, then you have to pass a number with the tilde ~:

HEAD~3

Resetting

As was mentioned in lesson 1-2, a reset is useful if you want to undo unpushed changes, or briefly go back to a point in your repository's history such that your repository is exactly the way it was at that commit.

For command-line git users, the syntax is pretty simple. You just tell it what commit you want to reset to:

git reset <commit>

where commit can either be the first 7 (or more) characters of the commit hash, or a reference from HEAD.

If you use a GUI, its Reset option should provide you with a list of commits (or a text box to enter the commit in) and a list of the different reset modes.

The tricky thing about git reset is that there are actually multiple different reset modes. The three main ones that you will use are:

soft
All this does is move the HEAD pointer back to the specified commit. It does not change git's index or affect the files in the working tree. In other words, HEAD will point at the commit you specified, but nothing else will change — the files in the working tree will remain the way they were before the reset.
mixed
This resets the index but not the working tree. By resetting the index, this clears the staging area. This is the default git reset mode.
hard
This resets the index and makes the working tree mirror the state of the repository at the specified commit. This will affect the files in the working directory. If you want to put your working directly exactly in the state it was when you made the commit, then this is the mode you want to use.

To see a full list of the possible modes and their explanations, see man git-reset.

Basically, what mode you use comes down to a few rules:

After you have done a hard reset, if you have pushed changes after the commit your HEAD points at, you can get them back simply by doing a pull.

Examples

To clear the staging area:

git reset HEAD

To undo all changes since the last commit:

git reset --hard HEAD

To reset the working tree to 7 commits back in history:

git reset --hard HEAD~7

To reset the working tree to commit 845da8b:

git reset --hard 845da8b

Reverting

A revert is more useful if you want to more permanently undo the changes done in a commit, if a commit you want to undo is buried in history, or it has already been pushed to the remote repository.

Unlike reset, you can only revert one commit in a time. If you want to revert across multiple commits, you will have to revert each commit individually.

To undo just one commit, the syntax is similar to git reset:

git revert <commit>

where, again, commit can be either the shortened hash or a reference from HEAD.

However, if you want to revert several commits, the syntax is different:

git revert <first-commit>..<last-commit>

where first-commit is the first (oldest) commit you want to undo, and last-commit is the last (newest) commit you want to undo.

When you execute the revert command, git will generate reverse diffs for each commit and then give you a commit message to sign off on. If you revert multiple commits, git will ask you to approve the commit message for each individual revert in order (however, it will iterate through every commit automatically).

Detached HEAD

Sometimes, you may find yourself in a situation where you want to get something from a previous commit, but you don't want to go through the hassle of pulling the changes back down after a hard reset. Or perhaps you have unpushed commits that you don't want to lose, but aren't ready to push yet.

There is another way to access your repository at a certain point in history, and this is called having a detached HEAD. However, this method is really only usable on the command line.

This method takes advantage of the git checkout command (which, as you will learn in the next lesson, is used to switch branches) to put the repository in the state it was when the commit was made, essentially by treating the commit as if it were its own branch.

You can do this by passing the commit reference to git checkout:

git checkout <commit>

Git will let you know you are entering the detached HEAD state when you do this:

$ git checkout HEAD^
Note: checking out 'HEAD^'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b new_branch_name

HEAD is now at 845da8b... <commit message title>

Basically, what this does is make git behave like the commit is its own branch. While you can make commits and use git normally, you should note that anything you do won't end up on a branch unless, like the message says, you create a new branch to keep your changes.

You can go back to where your repository was before detaching HEAD by checking out the branch you were on before:

git checkout master

← 2-1 Basic Operations 2-2 Revising History 2-3 Branching →