In my post about my first feature contribution to Babel1 I talked about how important a working knowledge of Git is in order to contribute to open source. I also said that I want to get better and Git because I want to be able to take full advantage of this amazing tool. I have indeed started learning some things behind the scenes. Some of it has been helpful and some of it rather confusing. But I think it is a good time for me to take notes on a typical Git problem: Recovering from mistakes in the last local commit. These are some of the scenarios I have in mind:

  • I commited a change that I did not want to.
  • I made a typo in the commit message.
  • I forgot to include a change with the commit.
  • I did not want to commit this at all.
  • I would like to split the changes into two separate commits.

I would like to emphasize that it only makes sense to change Git history of your local repository. Once a commit is published to the remote repository (GitHub, GitLab, et cetera) your only sane choice is to publish new commits that revert the things you desire to undo. This is because technically other people could have cloned the repository and undoing commits would put the two local copies out of sync. So the scenario I am talking about occurs after adding and committing changes, but before pushing them to a remote. I have encountered all of the above, probably including all possible combinations. Fortunately Git let’s us rewrite local history. Here are three ways I have discovered and used so far. I still have to figure out which way is the best for a given situation, but I think all of them can serve almost all scenarios above.

Resetting branch pointer without changing files

This command will move the current branch pointer back by one commit:

git reset HEAD~1

You can check the result by running git show which will give you the details of the most recent commit on the branch. Running it before and after resetting the branch pointer should show how it moved to the previous commit. Note that the files stay in the state of your undone commit and will therefore be marked as modified (or added, deleted) again. I think of this command as moving changes from the last commit back to the staging area.

Resetting branch pointer and files

This is actually just a variant of the command above. It will also set back the current branch by one commit but also undo all changes from that commit:

git reset --hard HEAD~1

This will completely wipe out the most recent commit from the current branch. I am not sure if the actual commit data is also deleted, but it will definitely no longer be part of the current branch and your repository should look exactly like one commit ago.

Amending the most recent commit

There is a flag for the commit command itself that allows you to pack changes on top of the most recent one. Instead of creating a new commit, Git will replace the most recent one:

git commit --amend -m "New commit"

Adding changes to such a commit works exactly the same way as with a regular commit. The one scenario where this is most useful is the commit message typo I think: After realizing the mistake in the message you can simply run this command with the corrected message and voilà!