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 that the resulting tree looks like this:
* 26af837 N - (HEAD -> master) Hello, world
* c9b1299 N - Hello
The easiest way to achieve this is to use the git reset
command.
$ git reset --hard 26af837
It sets the current branch to the specified commit. The --hard
option discards all changes 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 26af837
commit 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 a ‘detached HEAD’ state, which means we are no longer on a branch, and further commits will not be kept. From there, 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 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 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 remove a past commit from the Git tree completely.
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 you to rewrite the history of a branch from a specific starting point. And, it will prompt the list of all commits since this starting point, letting you perform different actions on these commits, for example, rewording a commit message, squashing several commits into one, and reordering and deleting 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. Alternatively, we could also have used the specific hash of the commit (c9b1299
).
Case #5: Revert a Commit
During this workshop, an attendee mentioned the git revert
command. Unlike the other commands in this article, git revert
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 removing 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 helpful when several people work on the same branch to avoid a forced push on the remote repository. It also keeps an explicit trace of the action in the tree for a particular reason.
Last resort: Keep Calm and Use Git Reflog
The git reflog
command recovers commits that appear to be lost. To illustrate what it 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
.