Semi linear merge

Semi-linear merge
This strategy is the most exotic – it’s a mix of rebase and a merge. First, the commits in the pull request are rebased on top of the master branch. Then those rebased pull requests are merged into master branch. It emulates running git rebase master on the pull request branch, followed by git merge pr --no-ff on the master branch.

enter image description here

Some people think of this as the best of both worlds: individual commits are retained, so that you can see how the work evolved, but instead of just being rebased, a “merge bubble” is shown so that you can immediately see the work in each individual pull request.

Taken from Pull Requests with Rebase


Semi-linear merge just adds a rebase to get your branch up to date before completing the merge. If you are PR'ing my-branch into target-branch, it is identical to the following commands:

git fetch
git checkout my-branch
git rebase origin/target-branch
git branch -D target-branch # just in case you have an old version of it locally
git checkout target-branch
git merge --no-ff my-branch

Some Pros and Cons are as follows:

Pros:

  1. The rebase first makes each merge cleaner and easier to trace visually. It also puts all of the changes into the commits themselves, rather than having to track down undesirable side effects in a merge commit, which is usually difficult. Note it's still recommended that developers do a rebase themselves prior to creating their PR, so they can witness any changes that are occurring at that time, and also run their unit tests against the latest version to make sure nothing funky happened. (This is akin to testing a merge commit and running tests before you create a PR, just to make sure the regular merge is still going to work as intended.)
  2. The merge (with --no-ff) forces a merge commit, and this is helpful because each PR contains the list of commits associated with just that PR, enabling you to view the first-parent history which shows all merges into the branch, and easily compare them. Another benefit of forcing the merge commit is that it's easy to revert an entire PR by simply reverting the merge commit, rather than individually reverting every commit that was in the original PR.

Cons:

  • There are some scenarios where you just don't want to use semi-linear merge. A good example is if your source branch has new merge commits in it that you want to retain. In Git Flow, when you merge release into master, or master back into develop you need to retain any of the merge commits from PRs on those branches. The rebase portion of semi-linear merge will rewrite those merge commits, and not only will the "bubbles" be popped, but worse, the commit IDs will be rewritten. This means you won't know for sure if everything in master is currently in develop. There could be IDs that are missing, despite the code being identical. As a rule of thumb, simply don't do a semi-linear merge if the source branch contains new merge commits.
  • After a semi-linear merge, when deleting your local branches you might have to use git branch -D my-branch (instead of lowercase -d) because the commit IDs may have changed. This is barely an inconvenience unless you generally don't remove your local branches right away; if you wait you'll need to confirm that you can really delete it.
  • If your workflow is for developers to rebase onto the target branch before creating their PR, having this option available might make them lazy, since they know it's going to do it for them. Most of the time it isn't a problem, but every once in a while both an automatic merge or rebase will break something, and it's better that they get used to rebasing first to detect it prior to PR completion. Otherwise, in this (admittedly rare) scenario the conflict is automatically resolved and something breaks in your integration branch.
  • If you use signed commits, if a rebase is needed during semi-linear merge the signatures would not be retained (because the commits are re-written). Depending on why you are signing your commits, this may not be an acceptable choice.

Side Note: other tools provide this feature as well:

  • Bitbucket offers the identical merge strategy, and calls it, appropriately, "Rebase, merge".
  • GitLab has used to have a setting for forcing a "merge commit with semi-linear history" however, at the time of this writing AFAIK it is was just a gate on the MR. (Note GitLab calls Pull Requests "Merge Requests". They are the same thing.) With this setting on you have had to rebase it yourself first (which you can do in the UI) and then you complete the MR. Basically it's 2 button clicks instead of 1. Update: This may no longer be possible. Now GitLab has an option for fast-forward only, but I don't know that you can require the check that the branch is up to date and still do a --no-ff merge.
  • GitHub does not support this yet, though people have been requesting it since 2017.

Additional Note: I can't find this documented anywhere, but I have tested and confirmed, that when completing a PR in Azure DevOps, if you choose either "Rebase and fast-forward" or "Semi-linear merge", your PR source branch is re-written before the PR is completed. Normally you would tick the box to delete your source branch after merging and wouldn't care about this, but if you elect to leave that setting unchecked, then it is important to realize your remote branch will appear as "(forced update)" at your next local fetch, if a rebase was required. This could be a Pro or a Con depending on your use case; I would lean towards preferring this most of the time.

Regarding the other tools, with this strategy GitLab forces you to update your branch with a button click, and Bitbucket specifically does not modify the PR branch in its equivalent "Rebase, merge" strategy.