Which Git merge strategy is appropriate for our team?

💡
Disclaimer
I'm not a Git pro. I wanted to learn more about the different merge strategies. This blog post is the result of that learning process.

Table of contents

A messy git history. Source: Stackoverflow

How do you bring your feature branches into the main branch?

Let us assume that you distinguish between the main branch and the feature branches in your team. For each new feature, the team creates a feature branch. The developers work on the specified feature branches for the new features, resulting in multiple commits.

How do you get your feature into the main branch?

From this scenario, the following questions arise for me:

How do you keep the Git history clean when there are a number of commits on the feature branch and you want to bring them into the main branch?

As a developer, you do not want to think about how to split commits. So on the way to a new feature, there may be a lot of commits.

How does the feature get into the main branch?

I have seen many development teams use an explicit merge strategy at this point, resulting in separate merge commits.

But what are the differences between an explicit merge strategy and an implicit merge strategy?

Let us look at the differences between explicit merge strategy using git merge --no-ff and implicit merge strategy using git rebase  and git merge👇

Explicit git merge strategy (git merge --no-ff)

The explicit Git merge strategy is the default merge strategy. It is explicit because it creates a new merge commit that extends the Git history to indicate that a merge has been invoked.

Suppose you are working on a feature branch.

git checkout -b feature/feature-1

After you have committed your work on the feature branch, you want to bring your feature onto the main branch.

git add .
git commit -m "tbd"
Merging a feature branch with git merge strategy 'recursive'

To merge your feature branch into the main branch with an explicit merge strategy, you must check out the main branch and merge it into the main branch without the Fast Forward option.

git checkout main
git merge feature/feature-1 --no-ff

This is the recursive merge strategy. With this merge strategy, Git recurses over the branch commits and creates a new merge commit.

Other types of explicit git merge strategies

There are other types of explicit git merge strategies.

Octopus (default strategy with more than two branches)

The Octopus merge strategy is used to bundle branches. It is the standard strategy for merging multiple branches at the same time.

git merge -s octopus feature/feature-1 feature/feature-2
Merging two branches with git merge strategy 'octopus'

Ours

This strategy discards all changes from the other branch. The contents of your branch will remain unchanged, and the next time you merge from the other branch, Git will only consider changes made from that point on. In this way, you can preserve the history of a branch without considering its effects.

git merge -s ours feature/feature-1-old
Merging two feature branches with git merge strategy 'ours'

Resolve

Taken from O'Reilly book Version Control with Git (Amazon):

Originally, "resolve" was the default strategy for Git merges. In criss-cross merge situations, where there is more than one possible merge basis, the resolve strategy works like this: pick one of the possible merge bases, and hope for the best. This is actually not as bad as it sounds. It often turns out that the users have been working on different parts of the code. In that case, Git detects that it's remerging some changes that are already in place and skips the duplicate changes, avoiding the conflict. Or, if these are slight changes that do cause conflict, at least the conflict should be easy for the developer to handle.

Squashing commits with git merge --squash

There is an option to squash your commits into one commit using git merge.

You can do this with the following commands:

git checkout main

git merge feature/feature-1 --squash

What happened here?

Prepared squashed git commit with git merge --squash

When you do a Git merge with --squash, Git prepares a squashed commit on your current branch.

The corresponding feature branch is preserved.

Characteristics of an explicit merge strategy

  • Your team will get a non linear commit history.
  • Every merge will produce a separate merge commit.

Pro's of an explicit merge strategy

  • Simple and familiar
  • Every action is traceable in the Git history, as it does not rewrite the Git history.
  • Maintains the context of the branch

Con's of an explicit merge strategy

  • Git history can become confusing and hard to read by lots of (merge) commits
  • It may be difficult to find and return a specific feature, bug fix, or whatever.

Implicit git merge strategy (git rebase, git merge)

In an implicit merge strategy, there is no separate merge commit. Only the commits from the branch are taken and placed at the top of the target branch. It can be triggered by rebase events and fast forward merges.

Suppose you are working on a new feature "feature-1" in a separate feature branch.

git checkout -b feature/feature-1

After committing your work ("git add && git commit -m" several times), you want to bring it into the main branch

git add .
git commit -m "tbd"

The implicit merge strategy requires you to relink your feature branch to the main branch.

git rebase main

After a successful rebase, you can merge it (fast forward) into the main branch (git checkout main && git merge feature/feature-1)

git checkout main
git merge feature/feature-1

Squashing commits with an interactive rebase

Furthermore, rebasing offers the possibility to switch to an interactive mode that gives us the chance to squash certain commits.

With the -i parameter you can start the interactive mode.

git rebase -i main

The command that interests me here is squash. With the squash command, I can use the commit, but I can merge it with the previous commit. After I set the specific commit to 'squash', I need to specify the name of the squashed commit.

This way I can combine all the small commits into one big feature commit, resulting in a clean history.

Rewrite and squashing two commits 

Characteristics of a implicit git merge strategy

  • There are no new commits on the main branch (aka merge commits).
  • The result is a linear history.

Pro's of implicit merge strategy

  • Streamlining a complex history
  • Avoiding the merge commit "noise"
  • Chance to clean intermediate commits: The developer does not have to think about splitting commits. He or she can squash all commits related to the feature into one feature commit when he or she rebase it.
  • There is no additional merge commit.

Con's of implicit merge strategy

  • Rebasing can be dangerous because there is a rewrite of the history
  • More effort and potentially complicated (that's why I am creating this blog post 😉).
  • Rebasing can be used as an aesthetic operation, with no direct benefit to the customer.
  • It needs a force push

Difference between an explicit merge strategy and the implicit merge strategy

With the explicit git merge strategy, the entire history is preserved, while in the implicit git merge strategy, the history is rewritten.

What Git merge strategy should we follow as a team?

IMHO, this question cannot be answered in an organisational or team-agnostic way 🤓.

There are several aspects that influence the decision between an explicit and an implicit Git merge strategy.

First, what is the developer community's response?

There are people in the developer community who have a clear, organisation-independent agnostic opinion.

Pro implicit merge strategy

Avoid messy git history, use linear history
Lost 😰 Have you already encountered the same git history ? Maybe you don’t if you work...

Pro explicit merge strategy

Why you should stop using Git rebase
You love rebasing, right? Rewriting history might be appealing, but there are good reasons not to.

Your source control (Git) requirements

As with software requirements you probably have requirements for your source control (Git).

Why are you using Git as your source control? What are your team's goals with Git in terms of Git history?

What's more important to your team?

A rewritten, clean, and linear Git history vs. a non-linear Git history that represents each step of the work process

Advanced Git know-how required

Using an implicit merge strategy or rebasing requires advanced Git knowledge.

Before deciding on an implicit merge strategy, check the Git knowledge of your teammates and show them the consequences.

The explicit Git merge strategy via git merge recursive is more common in the developer community. Virtually everyone has experience with the explicit git merge strategy.

Regardless of the "right" merge strategy, it is important to define this within your team

Set clear but lean guidelines in your team for working with Git. There are quite a few strategies for how you can work with Git as a development team.

I recommend you define a consistent use of Git across your team. Write a lean team guide on how to use it. At least for the following elements of Git:

Make sure every team member knows how to use this guide and is familiar with Git. A 1-hour introduction to team-specific use of Git will save you hours 😊.

Appendix:  Git commands

Create an checkout a new branch

git checkout -b feature/feature-1

Merge without fast forward option

git merge feature/feature-1 --no-ff

Merge with a specific explicit merge option like octopus

git merge -s octopus feature/feature-1 feature/feature-2

Rebase your branch with a specific branch

git rebase main

Rebase with the interactive mode (for squashing commits)

git rebase -i main

Abort rebase

git rebase --abort

Show commit logs

git log

Show commit logs from all branches

git log --all

Show commit logs with a pretty format

git log --pretty=format:"%h %s" --graph

Show updates of the local git history

git reflog

Push force with lease

force-with-lease is a safer option than force . It will not overwrite any work on the remote branch if more commits were added to the remote branch (by another team-member or coworker or what have you). It ensures you do not overwrite someone else's work by force pushing.