Techniques for rewriting Git history
It’s been a couple months since my last article on squashing commits, so I figured I’d take a few minutes and dive into some of the other ways Git allows us to rewrite history. Below are the techniques I’m going to cover so either feel free to keep scrolling, or click any of the links below to go directly to any of the sections.
- Amending commits
- Rewording commit messages
- Deleting commits
- Reordering commits
- Squashing commits
- Splitting commits
Amending commits
Amending refers to either adding files to, or removing files from a commit. Amend can also be used to change a commit’s log message, but today I want to use it to add files to a commit.
As an example: if I run git status you can see that I have 2 files that would really like to belong in my latest commit.
To get started, I’ll first need to stage my files.
Then I’ll use amend to add those staged changes to my most recent commit
The --no-edit flag tells Git that you’d like to leave the commit message of your latest commit unchanged. If you want to update the commit message, remove this flag.
That’s all there is to it.
Rewording commit messages
What if you want to reword a commit message? Enter interactive rebase and its plethora of options. I’ll start by telling Git I want to enter interactive rebase and operate upon the last 2 commits back from HEAD.
Git will then open up my default terminal text editor (most likely vim) and present me with a file that I’ll need to edit.
As you can see, at the top, there is a list of commits, and a big comment section explaining all the different options. Right now I just want to fix that spelling mistake in the first commit. To start, I’ll change the prefix of that commit from pick to reword (If you’re using vim, type i to enter insert mode).
Then I’ll save and close the file. (Again, in vim press ESC then type :wq to save and exit the file). At this point Git will pop up another file where I can rename the commit message. I’ll simply edit this file with the reworded commit message.
After I’m done making my changes to the commit message, I’ll simply save and close the file to finalize the spelling changes.
Deleting commits
As you saw from the rewording example, interactive rebase has many more options, so let’s dive into some. Say I’d like to remove a commit from my history.
I’ll start by opening interactive rebase again, but this time I’ll operate on the last three commits.
To remove a commit, I simply find the commit I’d like gone and change it’s prefix from pick to drop.
Again, I’ll save and quit the file and the commit is gone.
Reordering commits
Reordering commits is just as easy. We’ll enter interactive rebase again
To reorder, I’ll simply change the order of commits by reordering their lines at the top of the file.
Be careful here, if you delete a commit line and save this file without putting it back, Git will destroy the commit entirely.
I’ll once again save and quit to finalize the reordering.
Squashing commits
Now let’s get into some fun stuff. Say I want to meld my previous 2 commits into one. We’ll once again use interactive rebase…
As you can see (above) Git provides two options for combining commits: squash and fixup. Both options do pretty much the same thing, except squash will kick you to an extra screen which allows you to edit the commit messages. I have a whole other article that goes into greater detail on the squash workflow, so for now I don’t care about preserving those other log messages.
I’ll change pick to fixup which indicates to Git that I want to meld this commit into the previous one then discard it’s log message.
Now I’ll save and quit this file. As you can see, I’ve ended up with a new squashed commit which contains the contents of all 3 previous commits.
Splitting commits
Lastly, let’s talk about splitting commits. Down the line you may decide certain changes would be better off as separate commits and that’s what splitting is all about.
As you can see above, I’d really prefer if the second commit was split up. To make this happen, I’ll enter interactive rebase once again.
As you might guess, the splitting process is a bit more complicated than our previous operations, but don’t worry, I’ll break it down. First, we need to tell Git which commit we want to change. In this case I want to split the second commit so I’ll change it’s keyword from pick to edit
Next, I’ll save and close the file. When I do this, Git will start executing any rebase operations I specified. Since I left the other commits alone, Git will do nothing to those; however, when it comes to the commit for which I specified edit, Git will pause the rebase operation and allow me to make changes.
As you can see above, Git dropped me back to the terminal on a special rebasing branch. I can now type commands that will modify our commit. To split this commit, I’ll first want to undo the commit by unstaging all the files.
If I run git status you can see I have 3 files that are now unstaged and ready to be re-commited
Now I can re-commit the files using the normal add and commit workflow as below
After I’m done re-commiting all my files, I’ll just tell Git to continue
That’s it. Now if I take a look at my history, you can see I’ve ended up with 4 commits after splitting