The question “merge or rebase?” is one of the most common in Git-using teams. Both strategies achieve the same result – integrating changes – but produce completely different history. There is no single right answer, there is context. I show when to use each, how conflicts work, and why fast-forward is not the same as “no merge commit”.
Three merge strategies
Fast-forward
Possible when the target branch has not diverged since the feature branch was created. Git simply moves the pointer – no merge commit is created.
# Fast-forward is the default when possible git merge feature/new-endpoint # Force no fast-forward (always create a merge commit) git merge --no-ff feature/new-endpoint # Check if FF is possible before merge git merge-base --is-ancestor main feature/new-endpoint echo $? # 0 = yes, 1 = no
Merge commit
Creates a new commit with two parents. Preserves full history – you can see when the branch was created and when it returned. Preferred in Git Flow for merges to main/develop.
git checkout main git merge --no-ff feature/order-export # Merge commit message # Merge branch 'feature/order-export' # # * feat(export): add CSV export for orders # * feat(export): add PDF export for orders # * test(export): add unit tests for ExportService
Rebase before merge
The feature branch is rebased onto the current main, then fast-forward merged. History looks linear – as if the feature was developed on the current version of main.
git checkout feature/order-export git rebase main # move commits to the tip of main git checkout main git merge feature/order-export # fast-forward
Conflicts – how to resolve them
# During merge/rebase Git stops on conflict: # CONFLICT (content): Merge conflict in src/Model/OrderService.php # Check what is in conflict git status git diff --check # The conflicted file contains markers: # <<<<<<< HEAD # $this->calculateTax($order); # ======= # $this->calculateVat($order, $storeId); # >>>>>>> feature/tax-refactor # Resolve manually, remove markers, then: git add src/Model/OrderService.php git merge --continue # or git rebase --continue # Abort merge/rebase git merge --abort git rebase --abort
Conflict resolution tools
# Built-in tool (opens 3-panel view) git mergetool # Configure tool git config --global merge.tool vimdiff git config --global merge.tool phpstorm # PhpStorm resolves conflicts conveniently via File | Git | Resolve Conflicts
When merge, when rebase
| Situation | Strategy | Reason |
|---|---|---|
| Feature branch to main (Git Flow) | –no-ff merge | Visible feature history |
| Update feature with main | rebase | Linear history, simpler conflicts |
| Hotfix on main | fast-forward | Simple change, no noise |
| Squash before PR merge | squash + merge | One commit per PR |
| Public branch (shared) | merge NEVER rebase | Rebase rewrites SHAs |
Octopus merge – merging multiple branches
# Merge multiple branches at once (octopus merge) git merge feature-a feature-b feature-c # Useful when merging independent feature branches # Git requires no conflicts between them
Summary
Merge commit preserves branching history but “pollutes” the log with merge commits. Rebase creates linear history but rewrites SHAs – never use it on public branches. In practice: rebase to update a feature branch, –no-ff merge to integrate to the main branch. Next post: remote work – remote, fetch vs pull, force-with-lease.
