How can I squash my last X commits together into one commit using Git?
If you are on a remote branch(called feature-branch
) cloned from a Golden Repository(golden_repo_name
), then here's the technique to squash your commits into one:
Checkout the golden repo
git checkout golden_repo_name
Create a new branch from it(golden repo) as follows
git checkout -b dev-branch
Squash merge with your local branch that you have already
git merge --squash feature-branch
Commit your changes (this will be the only commit that goes in dev-branch)
git commit -m "My feature complete"
Push the branch to your local repository
git push origin dev-branch
This is super-duper kludgy, but in a kind of cool way, so I'll just toss it into the ring:
GIT_EDITOR='f() { if [ "$(basename $1)" = "git-rebase-todo" ]; then sed -i "2,\$s/pick/squash/" $1; else vim $1; fi }; f' git rebase -i foo~5 foo
Translation: provide a new "editor" for git which, if the filename to be edited is git-rebase-todo
(the interactive rebase prompt) changes all but the first "pick" to "squash", and otherwise spawns vim - so that when you're prompted to edit the squashed commit message, you get vim. (And obviously I was squashing the last five commits on branch foo, but you could change that however you like.)
I'd probably do what Mark Longair suggested, though.
You can do this fairly easily without git rebase
or git merge --squash
. In this example, we'll squash the last 3 commits.
If you want to write the new commit message from scratch, this suffices:
git reset --soft HEAD~3 &&
git commit
If you want to start editing the new commit message with a concatenation of the existing commit messages (i.e. similar to what a pick/squash/squash/…/squash git rebase -i
instruction list would start you with), then you need to extract those messages and pass them to git commit
:
git reset --soft HEAD~3 &&
git commit --edit -m"$(git log --format=%B --reverse HEAD..HEAD@{1})"
Both of those methods squash the last three commits into a single new commit in the same way. The soft reset just re-points HEAD to the last commit that you do not want to squash. Neither the index nor the working tree are touched by the soft reset, leaving the index in the desired state for your new commit (i.e. it already has all the changes from the commits that you are about to “throw away”).
Thanks to this handy blog post I found that you can use this command to squash the last 3 commits:
git rebase -i HEAD~3
This is handy as it works even when you are on a local branch with no tracking information/remote repo.
The command will open the interactive rebase editor which then allows you to reorder, squash, reword, etc as per normal.
Using the interactive rebase editor:
The interactive rebase editor shows the last three commits. This constraint was determined by HEAD~3
when running the command git rebase -i HEAD~3
.
The most recent commit, HEAD
, is displayed first on line 1. The lines starting with a #
are comments/documentation.
The documentation displayed is pretty clear. On any given line you can change the command from pick
to a command of your choice.
I prefer to use the command fixup
as this "squashes" the commit's changes into the commit on the line above and discards the commit's message.
As the commit on line 1 is HEAD
, in most cases you would leave this as pick
.
You cannot use squash
or fixup
as there is no other commit to squash the commit into.
You may also change the order of the commits. This allows you to squash or fixup commits that are not adjacent chronologically.
A practical everyday example
I've recently committed a new feature. Since then, I have committed two bug fixes. But now I have discovered a bug (or maybe just a spelling error) in the new feature I committed. How annoying! I don't want a new commit polluting my commit history!
The first thing I do is fix the mistake and make a new commit with the comment squash this into my new feature!
.
I then run git log
or gitk
and get the commit SHA of the new feature (in this case 1ff9460
).
Next, I bring up the interactive rebase editor with git rebase -i 1ff9460~
. The ~
after the commit SHA tells the editor to include that commit in the editor.
Next, I move the commit containing the fix (fe7f1e0
) to underneath the feature commit, and change pick
to fixup
.
When closing the editor, the fix will get squashed into the feature commit and my commit history will look nice and clean!
This works well when all the commits are local, but if you try to change any commits already pushed to the remote you can really cause problems for other devs that have checked out the same branch!
I think the easiest way to do this is by making a new branch based on master and doing a merge --squash
of the feature branch.
git checkout master
git checkout -b feature_branch_squashed
git merge --squash feature_branch
Then you have all of the changes ready to commit.
If you're working with GitLab, you can just click the Squash option in the Merge Request as shown below. The commit message will be the title of the Merge Request.