Join us on Facebook!
— Written by Triangles on November 17, 2017 • updated on December 21, 2020 • ID 60 —
A nice way to group some changes together, especially before sharing them with others.
In Git you can merge several commits into one with the powerful interactive rebase. It's a handy tool I use quite often; I usually tidy up my working space by grouping together several small intermediate commits into a single lump to push upstream.
The first thing to do is to invoke git to start an interactive rebase session:
git rebase --interactive HEAD~N
Or, shorter:
git rebase -i HEAD~N
where N is the number of commits you want to join, starting from the most recent one. For example, this is a hypothetical list of commits taken from the git log
command, while I'm working on a generic feature Z:
871adf OK, feature Z is fully implemented --- newer commit
0c3317 Whoops, not yet...
87871a I'm ready!
643d0e Code cleanup
afb581 Fix this and that
4e9baa Cool implementation
d94e78 Prepare the workbench for feature Z
6394dc Feature Y --- older commit
And this is what I would like to do:
871adf OK, feature Z is fully implemented --- newer commit --┐
0c3317 Whoops, not yet... |
87871a I'm ready! |
643d0e Code cleanup |-- Join these into one
afb581 Fix this and that |
4e9baa Cool implementation |
d94e78 Prepare the workbench for feature Z -------------------┘
6394dc Feature Y --- older commit
Obtaining:
84d1f8 Feature Z --- newer commit (result of rebase)
6394dc Feature Y --- older commit
Notice how a rebase generates a new commit with a new hash (84d1f8
in the example above). So in this case the command would be:
git rebase --interactive HEAD~[7]
because I want to combine the last seven commits into one, and d94e78 Prepare the workbench for feature Z
is the seventh one.
A downside of the git rebase --interactive HEAD~[N]
command is that you have to guess the exact number of commits, by counting them one by one. Luckily, there is another way:
git rebase --interactive [commit-hash]
Where [commit-hash]
is the hash of the commit just before the first one you want to rewrite from. So in my example the command would be:
git rebase --interactive 6394dc
Where 6394dc
is Feature Y
. You can read the whole thing as:
Merge all my commits on top of commit
[commit-hash]
.
Way easier, isn't it?
At this point your editor of choice will pop up, showing the list of commits you want to merge. Note that it might be confusing at first, since they are displayed in a reverse order, where the older commit is on top. I've added --- older commit
and --- newer commit
to make it clear, you won't find those notes in the editor.
pick d94e78 Prepare the workbench for feature Z --- older commit
pick 4e9baa Cool implementation
pick afb581 Fix this and that
pick 643d0e Code cleanup
pick 87871a I'm ready!
pick 0c3317 Whoops, not yet...
pick 871adf OK, feature Z is fully implemented --- newer commit
[...]
Below the commit list there is a short comment (omitted in my example) which outlines all the operations available. You can do many smart tricks during an interactive rebase, let's stick with the basics for now though. Our task here is to mark all the commits as squashable, except the first/older one: it will be used as a starting point.
You mark a commit as squashable by changing the word pick
into squash
next to it (or s
for brevity, as stated in the comments). The result would be:
pick d94e78 Prepare the workbench for feature Z --- older commit
s 4e9baa Cool implementation
s afb581 Fix this and that
s 643d0e Code cleanup
s 87871a I'm ready!
s 0c3317 Whoops, not yet...
s 871adf OK, feature Z is fully implemented --- newer commit
[...]
Save the file and close the editor.
You have just told Git to combine all seven commits into the first commit in the list. It's now time to give it a name: your editor pops up again with a default message, made of the names of all the commits you have squashed.
You can leave it as it is and the commit message will result in a list of all the intermediate commits, as follows:
Prepare the workbench for feature Z
Cool implementation
Fix this and that
Code cleanup
I'm ready!
Whoops, not yet...
OK, feature Z is fully implemented
Usually I don't care to keep such information, so I wipe out the default message and use something more self-explanatory like Implemented feature Z
.
Stackoverflow - Git rebase interactive the last n commits (link)
Stackoverflow - How can I merge two commits into one? (link)
Gitready - Squashing commits with rebase (link)
git rebase -i --root
Also SmartGit performs very convenient UI Squash... command for commits.
Worked like a charm
I did it with your help
Respect.
press escape
and type
":wq"
Thanks so much!
General question about Git. If not already there, why is there no option to merge the latest commit from the feature branch to the mainline. The advantage I see is that all my N commits won't pollute the mainline; at the same time, I have access to the commits in my feature branch, which is lost in rebase/squash.
There is indeed a way to do that.
You can cherry-pick the latest commit from the feature branch and apply it to the mainline(master).
In case you are not authorized to push directly to master, you can create a new branch, cherry-pick the latest commit from your feature branch to the newly created branch, and then raise a pull request of your newly-created branch.
Command -
[newly-created-branch] git cherry-pick
You forgot to remove the brackets :)
Great step by step instructions though, it helped. Thanks.
Thanks a lot, you saved me a lot of time
I want to notice though that if you do it by hash, you should select the hash of the commit before the first one. When I used the hash of the first one, it was left out and everything after got squashed.
FYI, I notice a small typo:
"because I want to combine the last seven commits into one, and d94e78 Prepare the workbench for feature X is the seventh one"
-> "feature X" should be "feature Z"
But as others mentioned, this needs to be fixed, square brackets removed:
git rebase --interactive HEAD~[7]
>>
git rebase --interactive HEAD~7
Tiny typo : I think you meant "changing the _word_ pick into squash" (not "work")
`git reset --soft HEAD~N`
That will remove the last N commits and leaves all the changes of the commits as staged changes.
Then, simply commit again:
`git commit -m"New commit message"`
Note that in the last code block, the commit hash order is REVERSED. If you do not reverse your commits, all of your work will be DELETED FOREVER.
Thanks!
git push --force
git push --force
pick hash2 commitmessage2
pick hash3 commitmessage3
pick hash4 commitmessage4 (newest)
how do I squash hash4 into hash1 in rebase --interactive mode?
Is this even possible?
Thanks
Found a small typo in Step 3: "into
One thing I need to always take into account tho, is that I should not do rebases when I already pushed or shared my code since I will be changing history and that can lead to all kinds of inconsistenties, merge conflicts and other headaches.