Rewriting History with git Posted on February 22nd, 2018
While manipulating your git history can be intimidating, it can be very beneficial. Below are common strategies I use re-writing my git history.
Before digging in, it is important to not manipulate the history on a shared branch unless you’ve discussed the matter with people who are using or have copies of the branch.
The most common history manipulation I use is
git commit --amend. This command will take your currently staged changes and amends them to the previous commit. This can be really helpful when doing small changes or syntax fixes discovered in a code review. Make the code changes as normal, but rather than saying
git commit -m "Fixed white spacing" use
git commit --amend. This will open up a text editor where you can re-write your last commit message. Once you save and quit the text editor the previous commit will now contain the changes. Using amend can keep your git history cleaner and easier to read. When pushing this code to a remote that already has a copy of the branch, you will need to use
git push -f <remote> <branch>. The
f flag will force your changes onto the remote repository, you’re essentially saying my git history is right, so use it.
Occasionally, I’ll be working on a Pull Request (PR) with several different commits. During a review, I may realize that one of those commits introduced a bug or has a typo. Rather than creating a new commit to address the issue, I’ll use rebase to modify the original commit. Generally, I use
git rebase -i HEAD~<n>. In this case
<n> is the number of commits you wish to rebase. When rebasing, you cannot say I want to edit commit y, you have to say I want to rebase my branch starting n commits ago. Once you have run the command, a text editor will pop up asking you how you want to modify your commit history. In this situation, I want to fix a typo in the commit where it was created. I’ll identify the commit hash in the text editor and replace the word pick with edit, then save and exit the editor. The rebase will run until you’re at that commit, well, just after you had run
git commit -m "A commit with a typo". Here you can fix the typo and then run
git commit --amend to add the update to the previous commit (the one with the typo). Now that you’ve fixed the typo and committed it, you must run
git rebase --continue which will rebase the rest of the commits without a stop. If you ever get into an issue with a rebase you can generally run
git rebase --abort to return to your starting point. Now that we’ve fixed the typo in the correct commit, we push the changes back to the remote server, using -f again. This may seem like a lot of work, but now nobody will know that you misspell words all the time, plus your git log won’t be cluttered with commits reading fixed typo.
Partial / Cherry Pick
Recently, I found myself in an odd situation. A short while ago, I had been working on a new feature in a branch, I had pushed all my code changes in one commit to the remote. Then I needed some code from this large commit in another branch. But wasn’t sure how to get it there without copying pasting. I decided to break the large commit into several smaller commits. This would allow me to cherry-pick the commit that I wanted from this older branch into the branch I was currently working on. First, I needed to rebase like I did in the previous example. But once I had rebased, instead of using
git commit --amend I used
git reset HEAD^. This would undo the changes from the large commit. The file changes remained but were no longer committed or staged. From there, I broke the large commit into several small commits based on functionality and then ran
git rebase --continue. Now my commit history on my old branch was broken into smaller commits, I checked out my new branch and ran
git cherry-pick <x>, where
<x> is the commit hash of the commit in the old branch that I needed. Now I was able to continue working.
Rewriting your commit history is great skill to have. It will allow you to remove unnecessary commits from your change logs, highlighting important changes to code rather than filling your logs with “typo fix”, “documentation added”, “fixed thing”.