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.
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.
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.
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.
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
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
git commit, or git merge --continue if the operation is mid conflict-resolution.git merge --abort while the merge is still in progress.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
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.
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.
--no-commit had nothing to pause. Repeat with --no-ff --no-commit only if an explicit merge commit is actually appropriate.--allow-unrelated-histories is a deliberate exception for independently started projects, not a routine conflict switch.git merge --squash feature-branch prepares the working tree and index without moving HEAD or recording MERGE_HEAD. You still make a separate commit yourself, and --squash cannot be combined with --commit.--ff-only when refusing that extra commit is the safety check you actually want.git status --short is empty, unless you intentionally kept unrelated work.git diff --name-only --diff-filter=U prints nothing.git log --graph --oneline --decorate shows the fast-forward or merge commit you intended.