Rebasing Onto A Squashed Commit
| Ellen ShapiroWhen you're using Github's nice "Squash and Merge" button (or the GitLab
equivalent, or git merge --squash
) to squash all the commits in a pull request
rather than simply merging them, your commit history can get really screwy if
you try to rebase off of the merged commit.
Let's say I have master
branch, that I make branch fix
. off of to fix some
small bugs. I make commits m
, n
, and o
on that branch. Then from o
, I
create branch feature
, and make commits p
, q
, and r
, that implement some
new feature that depends on the previous fix. Visually, it'd look something like
this:
| (branch feature)
| commit r
| commit q
| commit p
/
/
/
| (branch fix)
| commit o
| commit n
| commit m
/
/
/
-/
|
(master)
Next, I'd make a PR from branch fix
into master
:
| (branch feature)
| commit r
| commit q
PR | commit p
\ /
\ /
\ /
| (branch fix)
| commit o
| commit n
| commit m
/
/
/
-/
|
(master)
Once that PR is merged, you wind up with something which looks something like this:
| (branch feature)
| commit r
| commit q
| commit p
| commit o
| commit n
| commit m
\
\ (master)
\ / including the squashed commits of `fix` branch: m, n, and o
\-/
|
|
(master~1)
If I merge branch feature
into master
right now (without squashing), while
the file changes will be correct, suddenly the previously squashed commits of
the fix
branch will show up again in the master
branch history. This is
unfortunate, as I wanted to get rid of those.
Alternatively, if I merge branch feature
with squashing, the commit messages
of the squashed commits of the fix
branch will be included in the commit
message of the squashed commit in master
. While I can remove that manually,
it's a hassle and error-prone.
If I try to do a vanilla rebase
before the merge, rebase
will attempt to
apply the changes from every single commit - including commits m
, n
, and
o
, which have already been squashed and merged.
This is because the metadata about the individual commits that made up the squashed merge are gone. In fact, this is the only difference between a squashed merge and a normal merge: Both put the merged commits on top of the destination branch, but while a normal merge does this in a special merge commit that includes metadata about the commit hashes of the branches that have been merged, a squash merge omits that metadata, and "pretends" the merge commit is a normal commit.
Now, if you've got a branch which you created off of the commits which were
squashed and merged, using a plain vanilla rebase
command will attempt to
apply every one of those commits again, sequentially.
This gets annoying very quickly. I asked in our Slack if anyone knew a good way
around this. A few people banged their heads together, and we wound up with an
answer that takes the commits at the end of branch feature
, which haven't been
merged yet, and make them do something like this:
| PR
|
| (branch feature)
| commit r
| commit q
| commit p
\
\
\
\-
|
|
(master) including squashed commits of `fix` branch
There's a slightly obscure git rebase
sub-command for this. git checkout
the
branch you want to pick a bunch of commits off of and plop them onto master.
Then, you can pass in the magic commands:
git rebase --onto master [hash for commit o] [hash for commit r]
What this does is tell Git that it should rebase a range of commits on to
master
. Note that you actually need to start with the commit before those
you wish to pick up and move over onto master, so that all the commits are
picked up.
One thing to note is that this exact command will cause the result to appear as
a detached HEAD
rather than as HEAD
of the branch you have checked out.
You'll need to create an updated branch from there, as your original feature
branch will still be as it was (which is slightly annoying, but VERY helpful if
you mess this up). Alternatively, you can checkout feature
again, and
reset --hard
that branch to the hash of the detached head.
Now, you're able to make a PR that looks like the most recent ASCII art above, and have a clean commit history while still taking advantage of branching, squashing, and merging.