Jan 29th, 2021 - written by Kimserey with .
Few weeks ago we talked about Git Rebase. We saw how we could use it to manipulate the history of branches. By default, rebasing would flatten all merge commits making the history linear. In today’s post we will see how we can rebase while keeping merge commits.
For example if we have the following graph:
1
2
3
4
5
C---D---F (feature-a)
/ \
A--B--E--G--I--mJ--K--mL--M (master)
\ /
---------H (feature-b)
We have master
with two merge commits mJ
and mL
. Merge commits are identified by the number of parents they have, here mJ
and mL
have two parents. We can list the merge commits with:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
❯ git log --merges
commit 0302e5620cc8daf9f8c539b24ff01348d6844133
Merge: a231419 909cacb
Author: Kimserey Lam
Date: Sat Jan 16 10:08:31 2021 +0000
L - Merge branch 'feature-b'
commit cb38acd62671341b324ebeb51d780363cf48c66a
Merge: 1670262 7bbbac6
Author: Kimserey Lam
Date: Sat Jan 16 10:03:02 2021 +0000
J - Merge branch 'feature-a'
Now if we create a branch rebase-a
:
1
2
3
4
5
C---D---F (feature-a)
/ \
A--B--E--G--I--mJ--K--mL--M (master, rebase-a)
\ /
---------H (feature-b)
We can then do a rebase:
1
2
3
4
5
6
7
8
9
10
11
12
13
❯ git rebase -i HEAD~9
pick 51cab55 A
pick 667ea78 B
pick bf03665 E
pick 692d052 G
pick 1670262 I
pick 437015e C
pick 667d352 D
pick 7bbbac6 F
pick a231419 K
pick 909cacb H
pick 09d3b89 M
We see that the default rebasing omits the merge commits J
and L
, while aligning all commits from feature-a
and feature-b
branches.
If we reword every commits to A'
, B'
, C'
etc.. we will have the following tree:
1
A--B--E--G--I--C--D--F--K--H--M (rebase-a)
Rebase default behaviour would be to re-apply each commits from the branches at the point where the merge occurred hence instead of mJ
and mL
, we have each commits inline in history.
When we want to preserve the origin of changes, we need to preserve the merge commits showing the origin of the branching. Git rebase supports maintaining merge commits by using the option --rebase-merges
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
❯ git rebase -i --rebase-merges HEAD~9
label onto
# Branch J-Merge-branch-feature-a-
reset onto
pick 51cab55 A
pick 667ea78 B
label branch-point
pick 437015e C
pick 667d352 D
pick 7bbbac6 F
label J-Merge-branch-feature-a-
# Branch L-Merge-branch-feature-b-
reset branch-point # B
pick bf03665 E
pick 692d052 G
label branch-point-2
pick 909cacb H
label L-Merge-branch-feature-b-
reset branch-point-2 # G
pick 1670262 I
merge -C cb38acd J-Merge-branch-feature-a- # J - Merge branch 'feature-a'
pick a231419 K
merge -C 0302e56 L-Merge-branch-feature-b- # L - Merge branch 'feature-b'
pick 09d3b89 M
We can see that the rebase interactive is a bit more involved here than the default rebase. This is due to the fact that the history needs to be replayed in the correct order in order to apply the proper commits.
If we recall our initial graph:
1
2
3
4
5
C---D---F (feature-a)
/ \
A--B--E--G--I--mJ--K--mL--M (master)
\ /
---------H (feature-b)
We can easily make sense of the ccommits:
A
and B
,feature-a
branches on B
, a label branch-point
is created,C
, D
and F
are picked which are initially commits from feature-a
then the result is labeled J-Merge-branch-feature-a-
(the commit message),branch-point
which was on B
in order to apply E
and G
where we make another label branch-point-2
,H
, label the result L-Merge-branch-feature-b-
and reset back to branch-point-2
,I
, apply merge J-Merge-branch-feature-a-
, then K
, then L-Merge-branch-feature-b-
and lastly M
.The rebase essentially reproduce the whole tree while applying commits and merge commit in the same way. This provides us the possibility to rework the way commits are merged and change the commit messages of each commit including the merge commit itself.
We can change the merge commit message by changing -c
instead of -C
on the merge
line.
1
merge -c cb38acd J-Merge-branch-feature-a-
And that concludes today’s post! See you on the next one!