I am pretty darn frustrated after having spent more than an hour trying to clean up my mess from a 5 second code change. At work we are now supposed to squash (rebase?) multiple commits in a single branch into a single commit. Before starting at this job, I had done exactly zero rebases in git. Ever. And much like any new action in git, it tends to make me overly paranoid that I might screw up the project.
An overwhelming amount of information I found online for using git rebase
involved multiple branches, which is not what I wanted to accomplish. I would like to think that there is an easier way than what I’m about to describe here–and I may very well update/edit this article if that happens–but for now, this is what works for me:
- Ensure that you are on the right branch.
- Start an interactive rebase.
- Squash the commit(s).
- Update the commit message if you want to.
- Push the changes to remote.
Check out the branch with the commits that you want to rebase
We want to checkout the most recent commit in the branch containing the commits you want to squash.
git checkout your_branch
Start interactive rebase
You need to know how many commits that you want to squash together. That number of commits you want to squash will be referenced at the end of your git rebase
command. So if we want to squash the last 2 commits together, we would use the following:
git rebase --interactive HEAD~2
This will open your text editor (most likely vim) with a list of commits & what ought to be some helpful information as to what you need to do.
Squash the commits
VERY, VERY IMPORTANT: git rebase
works top down. So the top most commit is the one you want to pick (aka “keep”), and the commits below it will be the one(s) you want to squash into that top-most commit.
So you are presented with a list of (at least 2) commits. Each of those lines start with the word “pick” followed by a (partial) hash of the commit & the first line of the commit message for that hash. Any lines here that start with ‘#’ are comments & can be ignored.
- DO NOT CHANGE OR REMOVE THE TOPMOST UNCOMMENTED LINE. Merely confirm that it is the (oldest) commit you wish to retain in this branch.
- Change
pick
tosquash
for every commit below the first one that you want to squash into that first commit. (Leave the hash & commit message alone!) - Save your changes & exit.
Upon saving your changes it will (re)open your text editor to allow you how you want to preserve the commit message.
Update the commit message
Again, any lines that start with ‘#’ are comments that will not be retained in the resultant commit message. By default (just like in the previous editor session), it will display commit messages from oldest at the top to newest at the bottom. You can remove the commented lines if you want, but they should be discarded automatically anyway. Odds are you probably won’t want/need to change your commit messages, but you can do so now if you wish.
Save your changes & exit the editor when you are satisfied with how your commit message looks. This will exit git rebase
.
If you happen to look at your project commit tree, it will probably look like you made a terrible mess. This is not what you were intending to do & it will probably look like you just wrecked your project, not just your branch! But I can assure you, we’re going to fix that now. And it’s (thankfully!) easy.
Push changes to remote
First, DON’T PANIC. git probably is showing that the rebased commits & remote commits are now on separate branches that also happen to have the same name. One more time: DON’T PANIC.
Just push your changes to remote with:
git push origin +HEAD
This will display some information about counting, compressing & writing objects then a series of lines starting with remote:
referencing create a pull request for your branch. But if you look at the project tree again, you will see that order has been restored & that what was once multiple commits is now just a single commit.
Mission accomplished & now carry on with your day…
Update as of July 30, 2021
I have since learned that–at least on GitLab–that much of the hassle here can largely be avoided by using Merge Requests–identical in all but name to GitHub’s Pull Requests. At the very bottom of the Merge Requests page, there are 2 very handy options that I would encourage checking before submitting:
- Squash all commits.
- Delete branch. (Doesn’t delete your work, just deletes the branch name. And while this sounds like a potentially Bad ThingTM because it makes it harder (but not impossible) to checkout that branch again, keep in mind that it doesn’t take effect on the remote until the Merge Request is approved…which implies the code has passed any tests & been QA’ed successfully.
Thank you, very helpful. An improvement would be a few images with pre-existing commits with comments like:
“this is someone else’s commit I don’t want to touch”
“this is the commit I want everything to squash into”
“I want to squash/fixup this commit 1”
“I want to squash/fixup this commit 2”
and maybe reference fixup instead of squash so you don’t have to mess with the extra comments
Hey 🙂
thanks for the article!
One question, what does the `+` operator do in `git push origin +HEAD`?
Thank you!
Somewhat but not exactly, the ‘+’ operator is shorthand for the ‘–force’ option.
The git docs provide a highly detailed explanation of its use. From your terminal, type
git help push
, then scroll down to theOPTIONS
section & find the
block.Glad you found my post useful!