Recently, I conducted a workshop about how to go back in time with Git alongside Renaud. Here are the main points that we raised during this session.
Case #1: Delete the Last Commit
The initial Git tree used to illustrate this case is:
* 7ec8248 N - (HEAD -> master) Hello, world!
* 26af837 N - Hello, world
* c9b1299 N - Hello
The goal here is to delete the last commit, so the resulting tree looks like this:
* 26af837 N - (HEAD -> master) Hello, world
* c9b1299 N - Hello
The easiest way to achieve this purpose is to use the git reset
command.
$ git reset --hard 26af837
It sets the current branch to the specified commit. The --hard
option will discard all changes that have been made after the specified commit.
Case #2: Create a Branch from a Previous Commit
In this case, the initial Git tree is the same as before.
* 7ec8248 N - (HEAD -> master) Hello, world!
* 26af837 N - Hello, world
* c9b1299 N - Hello
We want to create a branch from the commit 26af837
to fix a bug, for instance. The resulting tree should be the following:
* ae77cf0 N - (HEAD -> bug-fix) Fixed it!
| * 7ec8248 N - (master) Hello, world!
|/
* 26af837 N - Hello, world
* c9b1299 N - Hello
First, we need to position ourselves on the commit:
$ git checkout 26af837
Then, we are in the ‘detached HEAD’ state, which means we are no longer on a branch, and further commits will not be kept. Consequently, we can create the branch and commit the bug fix:
$ git checkout -b bug-fix
$ # Fix the bug ...
$ git commit -m "Fixed it!"
Case #3: Put the Last Commit on a New Branch
Here, we made a commit on the master
branch by mistake and we want to transfer it to another branch.
The Git tree should go from this:
* 7ec8248 N - (HEAD -> master) Hello, world!
* 26af837 N - Hello, world
* c9b1299 N - Hello
to this:
* 7ec8248 N - (HEAD -> feature) Hello, world!
* 26af837 N - (master) Hello, world
* c9b1299 N - Hello
To do so, we just have to create the feature
branch from the last commit and reset master
to the previous one.
$ git checkout -b feature
$ git checkout master
$ git reset --hard 26af837
$ git checkout feature
Case #4: Rewrite History
In this case, we want to completely remove a past commit from the Git tree.
The initial tree looks like this:
* b1b5f0a N - (HEAD -> master) Hello, world
* b7d38ac N - Add key.txt
* c9b1299 N - Hello
We want the resulting tree to show no sign of the commit b7d38ac
:
* efb44c9 N - (HEAD -> master) Hello, world
* c9b1299 N - Hello
The git rebase -i
command allows rewriting the history of a branch from a specific starting point. And, it will prompt the list of all the commits since this starting point. Then, you can perform different actions on these commits like reword a commit message, squash several commits into one, reorder and delete commits. After these modifications, lines will be executed from top to bottom. You can find more information about rewriting history here.
$ git rebase -i HEAD~2
$ # Delete the line corresponding to the commit to remove.
Since we wanted to go two commits back, we used the notation HEAD~2
to specify the starting point. Note that we could also have used the specific hash of the commit (c9b1299
).
Case #5: Revert a Commit
During this workshop, an attendee talked about the git revert
command. Unlike the other commands that we saw in this article, the git revert
command does not modify past commits. Instead, it creates a new commit that is the exact opposite of the reverted commit.
For instance, if we start from this Git tree:
* f999291 N - (HEAD -> master) Hello, world
* a9f8fb3 N - Add key.txt
* c9b1299 N - Hello
and revert the commit a9f8fb3, which contains only a new file, a new commit that only contains the removal of this file will be created.
$ git revert a9f8fb3
* 55bc161 N - (HEAD -> master) Revert "Add key.txt"
* f999291 N - Hello, world
* a9f8fb3 N - Add key.txt
* c9b1299 N - Hello
This command could be used when several people work on the same branch to avoid a forced push on the remote repository. Moreover, it is possible to use it to keep an explicit trace of this action in the tree for a particular reason.
Last resort: Keep Calm and Use Git Reflog
Renaud wrote an article on the git reflog
command to recover commits that appear to be lost. To illustrate what this command does here is its output when used in case #4:
$ git reflog
efb44c9 HEAD@{0}: rebase -i (finish): returning to refs/heads/master
efb44c9 HEAD@{1}: rebase -i (pick): Hello, world
c9b1299 HEAD@{2}: rebase -i (start): checkout HEAD~2
26af837 HEAD@{3}: commit: Hello, world
712d068 HEAD@{4}: commit: Add key.txt
c9b1299 HEAD@{5}: commit (initial): Hello
You can see that the deleted commit (712d068
) still appears in the reflog.
Conclusion
This article shows several ways to go back in time with Git. These commands could be used alone or combined to get you out of complicated situations or to rearrange your Git tree before a git push
, for instance.