Skip to main content

git merge — Combining Branches

What It Does

git merge integrates the history of one branch into another. It finds the common ancestor of the two branches and combines their changes, creating a new merge commit (or fast-forwarding the pointer if possible).


Merge Strategies

Fast-Forward Merge

When the target branch has not diverged from the source — it is simply behind — Git moves the pointer forward instead of creating a new commit.

Before:
main: A -- B
\
feature: C -- D

After: git merge feature (fast-forward)
main: A -- B -- C -- D (no merge commit, pointer just moved)
git switch main
git merge feature/JIRA-123

Forces a merge commit even when fast-forward is possible. This preserves the fact that a feature branch existed, making history easier to read.

Before:
main: A -- B
\
feature: C -- D

After: git merge --no-ff feature/JIRA-123
main: A -- B ----------- M (M = merge commit)
\ /
feature: C -- D --
git switch main
git merge --no-ff feature/JIRA-123 -m "feat(transactions): merge export feature (#PR-42)"

This is the recommended strategy for integrating feature branches. The merge commit creates a clear, navigable record of the feature's integration point.

Squash Merge

Collapses all commits from the feature branch into a single staged change and lets you commit it cleanly. The feature branch commits do not appear in main's history.

git switch main
git merge --squash feature/JIRA-123
git commit -m "feat(transactions): add CSV export (#PR-42)"

Use this when the feature branch has messy WIP commits that you do not want in main's history. See also Squash.


Common Merge Scenarios

Merge a feature branch into develop

git switch develop
git pull --rebase origin develop # catch up first
git merge --no-ff feature/JIRA-123 \
-m "feat(transactions): add CSV export (JIRA-113)"
git push origin develop

Merge a release branch into main and develop

# Merge to main
git switch main
git merge --no-ff release/1.2.0 -m "chore: release v1.2.0"
git tag -a v1.2.0 -m "Release 1.2.0"
git push origin main --tags

# Back-merge to develop
git switch develop
git merge --no-ff release/1.2.0 -m "chore: back-merge release v1.2.0 to develop"
git push origin develop

Merge a hotfix

git switch main
git merge --no-ff hotfix/JIRA-999 -m "fix: resolve NPE on null description (JIRA-999)"
git tag -a v1.2.1 -m "Hotfix v1.2.1"
git push origin main --tags

# Also merge into develop
git switch develop
git merge --no-ff hotfix/JIRA-999
git push origin develop

Handling Merge Conflicts

When both branches have changed the same lines, Git cannot auto-resolve and leaves conflict markers:

<<<<<<< HEAD (current branch — develop)
public Page<TransactionDto> findTransactions(UUID userId, Pageable pageable) {
=======
public Page<TransactionDto> findTransactions(UUID userId,
LocalDate fromDate, LocalDate toDate, Pageable pageable) {
>>>>>>> feature/JIRA-123 (incoming branch)

Resolve by editing the file to the correct final state (removing all <<<<, ====, >>>> markers), then:

git add src/main/java/com/example/TransactionService.java
git merge --continue # or: git commit

To abort the merge and return to the pre-merge state:

git merge --abort

See Conflict Resolution for full guidance.


Useful Flags Summary

FlagMeaning
--no-ffAlways create a merge commit
--ff-onlyOnly merge if fast-forward is possible, else abort
--squashCollapse all branch commits into staged changes
--abortAbandon an in-progress merge
--continueContinue after resolving conflicts
--no-commitPerform merge but stop before committing
-m "msg"Set the merge commit message inline
--strategy-option=theirsResolve all conflicts by preferring the incoming branch
--strategy-option=oursResolve all conflicts by preferring the current branch

Merge vs Rebase

Use merge (--no-ff) when integrating completed branches into shared branches (develop, main). Use rebase to update a feature branch with the latest changes from main before opening a PR. This gives you the best of both: linear feature branch history and clear merge points in main.