by Peter Bell
One of the great things about git is that you can change your version control history. Messed up a commit message or forgot to commit a file? Amend your commit. Made three commits where one would do? Reset the commits and create one new one with all of the changes. However it can be confusing to keep track of all the different commands and options available for cleaning up your history, so in this article we’ll look at how to solve some common problems around rewriting history by using git. But first we need to know when not to change history…
Local vs. Shared History
There are two types of history that you need to treat very differently in git: local history that is only currently stored on your machine and shared history that you have pushed to a server or shared with other developers. Generally you should not change shared history. If you do, you’ll cause problems for anyone else working with you on the project. If you have a commit that you wish to undo but that has been shared with someone, use the
revert command. It doesn’t actually change your history. It just introduces a new commit that is the opposite of the one you are reverting – getting the working directory back to the condition it would have been in if the initial commit had never been made.
So, the rest of the commands we’re going to discuss are strictly for local history – unless you really know what you’re doing!
Fixing the Last Commit
Have you ever made a typo in a commit message? Or maybe you’ve used the
git commit -a and forgotten that it only adds modified – not untracked – files? Either way, you often recognize immediately that you’ve made a mistake. Luckily if you notice this before you push, there is a really easy fix.
To fix a commit message, just type:
git commit --amend -m "The fixed commit message"
This will recreate your last commit, changing the commit message to the new one you created.
If you forgot to add one or more files to your last commit, just add them using
git add and then type:
git commit --amend
It’ll throw you into your default commit message editor (if it’s vi, “:wq” will get you out). Then run a
git status and you’ll see that the files you added are now committed and if you do a
git log you’ll see they were just added to the last commit as if they’d always been a part of it.
git commit --amend is great if you only want to change the most recent commit, but what if you’ve created two or three local commits that you want to change? Simple. We can use the
git reset command to undo them. There are three options for
git reset – soft, mixed (the default) and hard.
Turning a Few Commits into One
Let’s say you want to merge your last two commits into a single commit. Easy. Just type:
git reset --soft HEAD~2
This gets rid of the last two commits in history (returning you to the state as of two commits before HEAD – the most recent commit on this branch). However, it keeps all of your changes and even keeps them all staged. If you check your
git status you’ll see all your changes from the last two commits are in the staging area. So to create the new commit to replace the old two we just have to type:
git commit -m "Put your new commit message here"
And it’ll create the new commit you want with the new message and all of the changes from the two commits you removed.
Changing the Grouping of Your Commits
Imagine that you created three html files and a css file for each one and then did two commits. You typed:
git add *.html git commit -m "Added html files" git add *.css git commit -m "Added styling for new html files"
So you decided to commit all of the html files together and then all of the styling for them. That might be the right strategy, but let’s say you decided you would have been better off making three commits – each with it’s own html and css file. Again, not a problem. This time you just type:
git reset HEAD~2
This time we’re using the default “mixed” mode instead of
--soft. (We could have typed
git reset --mixed HEAD~2, but didn’t need to as the mode defaults to
--mixed). Now run a
git status and you’ll see that you have got all of the files from the last two commits, but instead of being in the staging area ready for committing, they are just in the working directory. That is perfect for this use case. Now you can just add the files you want for each of the three commits that you wish to make and then make your three commits.
Pretending This Afternoon Never Happened
Ever had one of those afternoons? You’re working hard, make a number of commits and at the end of the afternoon you realize that you’ve been going in the wrong direction and completely wasted your time and those commits are of no value at all? If you want to completely get rid of (say) the last 5 commits you made, type:
git reset --hard HEAD~5
This will delete the last five commits and also remove all of those changes from the working directory, so it’s as if you didn’t make any of the changes at all. Be really sure before you do this, but sometimes it’s the best possible choice.
There are even more ways you can play with history in git. If you’re interested in another approach, search “interactive rebase git” for another really cool way to squash, reorder and even completely eliminate local commits. And if you get into trouble, search for information on the “git reflog” which allows you to get back commits that you have reset or amended. I hope the hints and tips above have been of some use. If you’d like to get regular hints and tips on better ways to use git and GitHub, you can sign up here for regular recipes to keep improving your usage of git!
Brian has published in a variety of technical publications over the years, has presented at numerous conferences and events and has served as a technical editor on a number of books.
You can read Brian’s blog archive with 9+ years of content at remotesynthesis.com (he still posts, infrequently). You can find a full list of Brian’s past publications and presentations. Follow Brian on Twitter @remotesynth.