Merge Branches with git merge and Survive the Conflicts

The feature branch is ready, the target branch has moved on, and git merge is what brings the two together, cleanly or with a conflict to resolve. By the end of this guide you will be able to merge one branch into another, tell whether Git made a fast-forward or a merge commit, and recover cleanly when it cannot combine the two automatically. The examples use Git 2.43.0, installed here as part of the Git suite. Allow 10 to 20 minutes for a routine merge, longer if you need to review conflicting changes properly.

Before you start

You need a Git repository with a target branch and a branch containing changes to bring into it. Swap target-branch and feature-branch below for real names. Merging changes the current branch, its index, and often its working tree, so do not start with valuable uncommitted work sitting around: commit it, stash it, or use a clean worktree first.

Check the version and current state before you change anything:

git --version
git status --short --branch
git branch --show-current

A safe checkpoint is a clean status report, apart from the branch line. Modified or untracked files listed? Stop and decide what to do with them first. Git normally refuses an overlapping merge, but git merge --abort cannot always reconstruct uncommitted changes that were already sitting there when the merge began.

1. Select the branch that will receive the changes

Switch to the target branch: the one whose history and files are about to change.

git switch target-branch
git status --short --branch

Expected output includes ## target-branch and nothing else. If the branch only exists on a remote, check it first with git branch --all, then create a local branch the way your repository normally does that. Never guess a remote name.

2. Inspect the incoming branch

Review what the merge would bring in before you run it. The first command shows commits reachable from the incoming branch but not the target; the second and third show the file changes:

git log --oneline --decorate target-branch..feature-branch
git diff --stat target-branch...feature-branch
git diff target-branch...feature-branch

The three-dot diff compares each branch against their common ancestor, which is usually the review you actually want for a proposed merge. An empty log can mean the incoming branch is already included, and a later merge would just report Already up to date.

3. Choose the merge shape

For an ordinary merge, ask Git to bring the branch in:

git merge feature-branch

Git 2.43.0 fast-forwards when the target branch is an ancestor of the incoming branch: that just moves the pointer, no merge commit involved. Diverged histories get a commit with both tips as parents instead, using this version's default single-head strategy, ort.

Reach for an explicit policy when the history shape matters:

# Refuse to create a merge commit; useful for a linear-only branch.
git merge --ff-only feature-branch

# Always record a merge commit, even when fast-forwarding is possible.
git merge --no-ff feature-branch

--ff-only exits non-zero and leaves the merge undone the moment branches have diverged. --no-ff does rewrite history, so use it only when your project's history policy calls for an explicit branch boundary. Verify the result with:

git status --short --branch
git log --graph --oneline --decorate -8

4. Pause before committing when required

Want to inspect or edit a non-fast-forward result before the commit lands? Start with --no-commit:

git merge --no-commit feature-branch
git diff --cached
git status

There is a subtle boundary here: --no-commit cannot pause a fast-forward, because there is no merge commit to hold open. Pair it with --no-ff when you need a genuine inspection point:

git merge --no-ff --no-commit feature-branch

5. Resolve a conflict without guessing

When Git cannot combine both sides automatically, it leaves HEAD where it was, updates the clean paths, and marks the conflicted files. Get the exact list:

git status
git diff --name-only --diff-filter=U

Open each unmerged file and make a deliberate call between the sections marked by <<<<<<<, ======= and >>>>>>>, then remove every marker. The upper section is normally the target branch, the lower the incoming branch. Original context unclear? Compare the three index stages:

git show :1:path/to/file
git show :2:path/to/file
git show :3:path/to/file

Stage a file only after checking its contents:

git diff -- path/to/file
git add path/to/file
git diff --cached -- path/to/file

Repeat until git diff --name-only --diff-filter=U prints nothing, then finish it off:

git merge --continue

If an editor opens, keep or revise the merge message and save it. Hooks or the message causing a failure? Fix the reported problem and run the command again. Do not reach for --no-verify casually: it bypasses the pre-merge and commit-message hooks that are usually there for a reason.

6. Abandon or park the merge

Warning: these commands alter the in-progress operation. Use them only once you have decided whether the current work should be discarded or kept.

To return to the pre-merge state:

git merge --abort
git status --short --branch

This tries to reconstruct the state from before the merge, but it is not a guarantee for dirty worktrees, especially if files changed after the merge started. Need to leave the index and working tree exactly as they are while forgetting the merge metadata instead? Use git merge --quit. Any autostash goes to the stash list under --quit, but gets applied automatically under --abort.

Common traps

Done means