Git Workflow: Merge vs Rebase for Feature Branches
When your feature branch falls behind main, you have two options to sync it up: merge or rebase. Both achieve the same goal, your branch includes the latest changes from main, but they work differently and have different consequences for your PR, code review, and git history.
Let’s break down the trade-offs.
TL;DR
If your team squash-merges PRs into main, rebasing feature branches onto main before opening a PR produces cleaner diffs and better code reviews, with no downsides for solo feature branches. Using merge syncs the branch but adds unrelated CICD commits to your PR, creating messier diffs and requiring reviewers to filter out noise.
The Two Flows
Merge flow
main: A---B---C---D(CICD)---E
\ \
feature: F---G---M(merge)---H---I
Running git merge main when the branch falls behind creates merge commits inside the feature branch. CICD commits from main appear as part of the PR’s proposed changes.
Rebase flow
main: A---B---C---D---E
\
feature: F'---G'---H'
Running git rebase main instead replays feature commits on top of latest main. SHAs change (F→F’), requires force-push, and is safe for solo feature branches.
The “squash anyway” argument
A common reasoning goes: “We squash-merge everything so main stays clean. The spaghetti only lives in the PR branch temporarily.”
The key consideration: code review happens before the squash.
| Problem | Merge flow | Rebase flow |
|---|---|---|
| PR diff includes main commits | Yes — reviewers see CICD/infra noise | No — only feature changes |
git log on branch | Tangled with merge commits | Linear, only feature work |
| GitHub “Files changed” tab | Can show unrelated files | Clean |
| Conflict resolution | All at once in one merge commit | One commit at a time, in context |
| CICD commits in PR | Appear as part of proposed changes | Invisible (they’re the base) |
The squash cleans main’s history, but review quality degrades before that happens.
The CICD commit problem specifically
Most concrete symptom of the merge flow:
git merge main # pulls in CICD commit locally
git push # PR now shows CICD commit as "your changes"
Reviewers must mentally filter: “Is this intentional or noise from main?” With rebase, CICD commits are part of the base — completely invisible in the diff.
Real impacts in this specific workflow
Things genuinely affected by using merge:
- Review throughput — reviewers spend time parsing noise; bugs hide more easily in messy PRs
- Conflict isolation — one big merge commit with conflicts vs. isolated conflicts per-commit during rebase
- CI runs on the PR — merge commits can trigger CI re-runs that don’t represent real changes
git bisectduring incidents — merge commits pollute traversal when tracing a bug in a branch pre-merge
Things genuinely not affected (squash handles these):
- Main branch history — stays clean either way
- Long-term
git blameon main git bisecton main after merge
Strongest argument for keeping the merge flow
Simplicity. Rebase requires devs to understand:
- SHAs change after rebase → force-push is required
- Force-pushing a branch someone else has checked out destroys their work
- If you have commits on top of an in-flight rebase, they can be lost
For solo feature branches (one dev per branch, which is the standard PR model), these risks are absent. The danger only applies to shared branches.
Verdict
The rebase approach has meaningful advantages in this workflow because:
- Solo feature branches — the main risk of rebase (force-push on a shared branch) doesn’t exist
- Squash+merge final strategy — no “rewritten history in main” concern
- Review quality is real — the only window to catch bugs is the PR review
- The CICD noise problem is a direct, preventable consequence of the merge flow
Practical adoption path
Lowest friction first:
git config --global pull.rebase true
This alone prevents the accidental merge commits that come from git pull (the most common cause of the problem, even for devs who try to rebase).
Branch protection (GitHub/GitLab): Require branches to be up-to-date before merge. This forces the question of how to sync — good moment to standardize on rebase.
Reduce branch lifetime: The longer a branch lives, the more painful any sync strategy becomes. Smaller PRs reduce the need to sync with main at all — the root cause fix.