DEV Community

Wilson
Wilson

Posted on

Merge Strategies in GitLab: Merge Commits vs. Fast-Forward Merges

Merge Strategies in GitLab: Merge Commits vs. Fast-Forward Merges

When working with GitLab merge requests, one of the most important decisions your team needs to make is how to handle merge history. GitLab offers several merge methods, but two of the most common are:

  • Merge Commit (always create a merge commit)
  • Fast-Forward Merge (no merge commits, linear history only)

Each approach has trade-offs, and the right choice depends on your workflow. Let’s take a closer look.


1. Merge Commit

With this option, every merge request creates a merge commit that ties the feature branch into the target branch.

Pros

  • Preserves branch history — it’s clear when and how a branch was merged.
  • Works even if the branch is outdated compared to the target.
  • Easier for beginners who don’t need to rebase frequently.

Cons

  • History gets noisy with many merge commits.
  • CI may pass on an old branch, but the merge result could still break the target.
  • Developers can skip updating their branch before merging, hiding integration issues.

2. Fast-Forward Merge

With this option, GitLab merges only if the feature branch is up-to-date with the target branch. No merge commit is created; instead, history remains linear.

Pros

  • Produces a clean, linear history that’s easier to read and debug.
  • Guarantees CI runs on the exact code that lands in the target branch.
  • Forces developers to stay in sync with the latest branch.

Cons

  • Requires developers to rebase often.
  • Can be painful for long-lived feature branches with many conflicts.
  • Removes the explicit “merge event” from history.

Real-World Example

Our team currently uses this workflow:

  • Squash commits: Every feature branch is squashed into a single commit when merged into the dev branch.
  • Merge commit method: GitLab then generates a merge commit to record the merge.
  • Branching strategy: Feature branches → devmaster.
  • Conflict handling: If conflicts exist, the developer must fix them locally. If not, the MR can be merged without updating against the latest dev.
  • Branch lifetime: Some feature branches live for weeks and are maintained by multiple developers.

This setup is forgiving and works well for long-lived branches, but it has weaknesses:

  • Old branches can be merged without rebasing, which sometimes causes CI to fail for the next merge.
  • Integration issues may stay hidden until late in the process.

Should You Switch to Fast-Forward Merges?

If we switched to fast-forward merges, developers would need to rebase their branches before every merge. This would solve the “stale branch” problem, but it would also create headaches for large, long-lived branches, especially those with multiple contributors.

Verdict: For our case, sticking with merge commits + squash is still the most practical choice. But we should enforce stricter CI rules:

  • Require CI to run on the merge result with the target branch.
  • Encourage smaller, shorter-lived feature branches to reduce drift and conflicts.

Takeaway

  • Merge commits = forgiving, but messy. Best for long-lived feature branches.
  • Fast-forward merges = strict, but clean. Best for trunk-based development and short-lived branches.

The right choice depends less on “which is better” and more on how your team actually works.

Top comments (0)