Rebasing Merges With Git Git

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.

Default Rebase

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.

Rebase Merges

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:

  • we start by picking A and B,
  • because feature-a branches on B, a label branch-point is created,
  • then 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),
  • then the change reset to branch-point which was on B in order to apply E and G where we make another label branch-point-2,
  • then apply H, label the result L-Merge-branch-feature-b- and reset back to branch-point-2,
  • then apply 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!

External Sources

Designed, built and maintained by Kimserey Lam.