If you've read our general git rebase vs merge guide, you know the philosophical difference. Rebase rewrites history for a clean, linear commit graph. Merge preserves the full history with a merge commit. Both are valid, and the right choice depends on your team's preferences.
But here's what that general guide doesn't cover: Bitbucket and GitLab have their own specific implementations of these strategies in their web interfaces. They don't just let you pick "rebase" or "merge" and call it a day. They have merge strategies with specific names, sync options that appear when your branch falls behind, and in GitLab's case, an automatic rebase feature that changes the workflow entirely.
I've used both platforms on teams with different Git philosophies. The friction points are rarely about the underlying Git concepts. They're about the UI, the default settings, and the subtle differences between "rebase + merge," "rebase + fast-forward," and "squash." This guide walks through the platform-specific details so you can configure your repository correctly and stop guessing what the buttons do.
Why Platform-Specific Matters
Git is Git. The underlying commands work the same regardless of whether you're using GitHub, GitLab, or Bitbucket. But the web interfaces abstract away the commands, and the abstraction isn't always transparent. When Bitbucket offers you "Rebase, fast-forward" and "Rebase, merge" as two distinct merge strategies, you need to know what each one actually does to your commit history.
The stakes are higher than they might seem. Choosing the wrong merge strategy for your team creates the kind of history that's hard to read six months later. Merge commits everywhere. Or the opposite problem: a history so squashed that you can't trace which commit introduced a bug. The platform's defaults shape this, and many teams never change them.
Bitbucket Merge Strategies Explained
Bitbucket Cloud has expanded its merge options significantly. Historically, it supported merge commit, squash, and fast-forward. In 2024, Atlassian added three more strategies: rebase + merge, rebase + fast-forward, and squash (fast-forward only). Let me break down what each one does.
Merge Commit (--no-ff) — The Default
This is Bitbucket's default strategy. A new merge commit is always created, even if the source branch is already up to date with the target. The result is a commit graph with visible branch points and merge commits.
git merge --no-ff feature
What it produces: A merge commit with two parents. The branch history is preserved, and you can see exactly when the feature was merged.
When to use it: Teams that value complete history and want to see the branch structure. This is the safe default because it never rewrites history.
Fast-Forward (--ff)
If the source branch is up to date with the target, the target is simply moved forward to the latest commit on the source branch. No merge commit is created. If the source branch is behind, a merge commit is created instead.
git merge --ff feature
What it produces: A linear history when possible, with a merge commit only when necessary.
When to use it: Teams that want linear history but don't want to force it when the branch is behind.
Fast-Forward Only (--ff-only)
If the source branch is out of date with the target, the merge is rejected. The developer must sync (via rebase or merge) before merging.
git merge --ff-only feature
What it produces: A strictly linear history. No merge commits, ever.
When to use it: Teams that enforce linear history and want the platform to reject out-of-date branches.
Rebase + Merge (Rebase, Merge)
Commits from the source branch are rebased onto the target branch, then a merge commit is created. The PR branch itself isn't modified.
git rebase target
git merge --no-ff feature
What it produces: Rebased commits followed by a merge commit. This is a hybrid approach that gives you a linear sequence of feature commits plus a merge commit that marks when the merge happened.
When to use it: Teams that want a linear feature history but still want a merge commit for tracking purposes.
Rebase + Fast-Forward (Rebase, Fast-Forward)
Commits from the source branch are rebased onto the target branch, then the target is fast-forwarded. No merge commit is created.
git rebase target
git merge --ff-only feature
What it produces: A completely linear history. The rebased commits appear directly on the target branch as if they were always there.
When to use it: Teams that want the cleanest possible history—linear, no merge commits, no branch markers. This is the "rebase workflow" purists' choice.
Squash (--squash)
All commits from the source branch are combined into a single commit on the target branch. The original commits remain on the source branch but aren't part of the target's history.
git merge --squash feature
git commit
What it produces: A single commit on the target branch representing the entire feature.
When to use it: Teams that want a clean history at the feature level. Each PR becomes one commit. You lose granular history but gain readability.
Squash, Fast-Forward Only
This is a variant that combines squashing with the fast-forward-only requirement. If the source branch is out of date with the target, the merge is rejected.
When to use it: Teams that want squashed commits and a strictly linear history.
Bitbucket Sync Options: What to Do When Your PR Falls Behind
When your source branch is missing commits from the target branch, Bitbucket shows a "Sync now" message on the pull request. You can choose between two options: merge commit or rebase.
The merge commit sync creates a merge commit on your source branch, pulling in the target branch's changes. This is the traditional approach. It preserves your commit history but adds a merge commit to your branch.
The rebase sync rewrites your source branch's history, replaying your commits on top of the target branch. This produces a linear history but changes commit hashes. If anyone else has pulled your branch, they'll need to force-pull.
The rebase sync option is what most teams want when they're preparing a PR for merge. It keeps the branch up to date without adding merge commit noise.
GitLab Merge Methods Explained
GitLab organizes its merge options into three "merge methods" at the project level. You configure one method, and it applies to all merge requests in the project. Within that method, you can also configure squash behavior.
Merge Commit (--no-ff)
This is GitLab's default. A merge commit is always created when a branch is merged. It's equivalent to git merge --no-ff.
What it produces: A merge commit on the target branch. The feature branch's commits are preserved, and the merge commit marks the integration point.
Merge Commit with Semi-Linear History
This method requires the source branch to be up to date with the target branch before merging. If it's not up to date, GitLab offers a rebase button. Once rebased, a merge commit is created.
What it produces: Rebased feature commits followed by a merge commit. This is GitLab's equivalent of Bitbucket's "Rebase, merge" strategy.
Why semi-linear? The "semi" part refers to the fact that merge commits are still created, so the history isn't fully linear. But the feature branch itself is linear because it must be rebased before merging.
Fast-Forward Merge
This method requires the source branch to be up to date, and it fast-forwards the target branch without creating a merge commit.
What it produces: A linear history with no merge commits. The feature commits appear directly on the target branch.
If a fast-forward merge isn't possible (because the source branch is behind), GitLab gives you the option to rebase. You click "Rebase," the source branch is updated, and then you can merge.
GitLab's Automatic Rebase Before Merge
GitLab 18.0 introduced a feature that eliminates the two-step rebase-then-merge workflow. It's called "automatic rebase before merge," and it's available for projects using the semi-linear or fast-forward merge methods.
When you enable this setting in your project's merge request settings, GitLab rebases the source branch onto the target branch at merge time. You click a single button and both the rebase and the merge happen. The two-step handoff—click Rebase, wait, click Merge—is gone.
This is a small change that adds up to meaningful workflow improvement. If your team uses semi-linear or fast-forward merges, enable automatic rebase. It reduces friction on every single merge request.
One caveat: if preserving GPG signatures on individual commits matters to you, leave the setting off. Rebasing rewrites commits and invalidates their signatures.
Conflict Resolution: Platform Differences
Conflicts happen with any merge strategy. How each platform handles them differs.
Bitbucket leaves the repository as it was before the merge attempt and shows the conflict in the UI. You can resolve conflicts locally by checking out the target branch, applying the rebase or merge, resolving conflicts, and pushing. Then you can fast-forward the target branch manually or retry the merge in the web interface.
GitLab gives you a Rebase button on the merge request when the source branch is behind. Clicking it attempts the rebase in the UI. If the rebase succeeds without conflicts, the branch is updated. If there are conflicts, you need to resolve them locally. The GitLab UI tells you exactly which files conflict, and you follow the standard git rebase --continue workflow.
In practice, conflicts are usually easier to resolve locally with your editor and merge tool than in the web UI. The UI is fine for simple conflicts—a single line change, a whitespace difference—but for anything more complex, clone locally and work through it with proper tooling.
If you need a refresher on conflict resolution during rebase, our git rebase tutorial covers the git rebase --continue and --abort workflows in detail.
A Comparison Table: Bitbucket vs GitLab
| Strategy | Bitbucket Name | GitLab Equivalent | Commit History |
|---|---|---|---|
| Merge commit | Merge commit (default) | Merge commit (default) | Merge commits, branch history preserved |
| Fast-forward | Fast-forward | Fast-forward merge | Linear, no merge commit |
| Fast-forward only | Fast-forward only | — | Linear, rejects if behind |
| Rebase + merge | Rebase, merge | Merge commit with semi-linear history | Rebased commits + merge commit |
| Rebase + fast-forward | Rebase, fast-forward | Fast-forward merge (with rebase) | Rebased commits, linear |
| Squash | Squash | Squash (setting) | Single commit per PR |
| Squash + fast-forward | Squash, fast-forward only | Squash + fast-forward merge | Squashed, linear |
A Real Story: Switching a Team from Merge to Rebase
I once worked with a team that had been using Bitbucket's default merge commit strategy for years. Their main branch was a tangled graph of merge commits, feature branches, and rebase attempts. Finding the commit that introduced a bug required navigating through multiple branch paths.
We switched to the "Rebase, fast-forward" strategy. The change was immediate. New PRs merged as linear sequences of commits. The commit graph on main became a straight line. git log --oneline was suddenly readable. git bisect became practical again.
The migration wasn't without friction. Developers had to learn to rebase their feature branches before opening a PR. Bitbucket's "Sync now" message appeared more often, and they had to choose rebase rather than merge commit. But within a week, the team had adapted. The resulting history was worth the adjustment period.
The lesson: the merge strategy you configure in your platform shapes the history you'll be debugging for years. Choose intentionally, not by default.
Which Should You Choose?
For most teams, I recommend one of two approaches:
If you value history and traceability: Use merge commit (the default on both platforms). It's the safest option. You never rewrite shared history, and you can always see exactly when a feature was merged.
If you value a clean, linear history: Use rebase + fast-forward (Bitbucket) or fast-forward merge with rebase (GitLab). The history is linear, git bisect works predictably, and the commit graph is readable.
If you want clean history at the feature level: Use squash. Each PR becomes one commit. This is excellent for teams that treat each PR as a logical unit of work.
If you're unsure: Start with merge commit. It's non-destructive and reversible. You can always switch to a more aggressive strategy later, but it's harder to clean up a messy history after the fact.
Whichever you choose, document it in your team's contributing guide. Ambiguity about merge strategy is a common source of friction. Our git rebase vs merge guide covers the general philosophy; this post gives you the platform-specific configuration.
Wrapping Up
Bitbucket and GitLab both offer a range of merge strategies that go far beyond the simple rebase-or-merge dichotomy. The platform-specific names and behaviors matter because they shape your commit history. Take the time to understand what each strategy does, configure it intentionally, and make sure your team agrees on the approach.
If you're on Bitbucket, the "Rebase, fast-forward" strategy gives you the cleanest history. If you're on GitLab, the "Fast-forward merge" method with automatic rebase enabled gets you to the same place with less manual effort. Both platforms have made significant improvements in recent years, so even if you looked at these options before and found them lacking, it's worth revisiting.
For deeper dives into the underlying Git concepts, see our git rebase tutorial for hands-on practice, or our git rebase vs merge comparison for the philosophical differences. The platform is just the interface. Understanding what happens under the hood is what makes you effective with either one.
Need help standardizing your team's Git workflow across Bitbucket or GitLab? Red Surge Technology helps teams adopt version control practices that scale. Get in touch to discuss your workflow.