Skip to content
masterclass Buddy

Git · Command

git merge

git merge brings the changes from one or more named commits, usually branch tips, into the current branch. Depending on the history, it either moves the branch pointer forward (a fast-forward) or records a new merge commit with several parents.

Last updated

How it works

Git finds the merge base, which is the point where the two histories split. If your branch tip is an ancestor of the target, Git simply moves HEAD forward to it. If not, the strategy (ort by default) runs a 3-way merge using the base and both tips, then commits the result with every merged tip as a parent. If some changes cannot be reconciled automatically, HEAD stays where it was, MERGE_HEAD records the other tip, and conflict markers are written into the files. You fix them, git add the files, and finish with git merge --continue or git commit. ORIG_HEAD is saved before the merge starts, so you can find your way back.

Syntax

git merge [-n] [--stat] [--compact-summary] [--no-commit] [--squash] [--[no-]edit] [--no-verify] [-s <strategy>] [-X <strategy-option>] [-S[<keyid>]] [--[no-]allow-unrelated-histories] [--[no-]rerere-autoupdate] [-m <msg>] [-F <file>] [--into-name <branch>] [<commit>...]
git merge (--continue | --abort | --quit)

Parameters

NameTypeDescription
<commit>...commit-ishThe commits to merge in, usually other branch heads. If you give more than one, you get an octopus merge. If you give none, Git merges the upstream that the current branch is configured to track.
--ff / --no-ff / --ff-onlyflagControls what happens when a fast-forward is possible: fast-forward (the default), always create a merge commit, or allow only a fast-forward and fail otherwise.
--no-commitflagDoes the merge but stops just before creating the merge commit, so you can inspect or adjust the result first.
--squashflagPuts the merged result into the index and working tree but does not commit, move HEAD or record MERGE_HEAD.
-s, --strategy=<strategy>stringThe merge strategy to use: ort, resolve, octopus, ours or subtree (recursive is now an alias for ort).
-X, --strategy-option=<option>stringAn option passed to the strategy, e.g. ours, theirs, ignore-space-change, renormalize, find-renames or diff-algorithm.
-m <msg> / -F <file>stringThe message for the merge commit, given inline or read from a file (only used if a merge commit is created).
--allow-unrelated-historiesflagLets Git merge histories that have no common ancestor.
--autostashflagStashes local changes before the merge and reapplies them afterwards.
--abort / --continue / --quitflagFor a merge that has stopped: undo it and restore the pre-merge state, finish it after resolving conflicts, or forget it while leaving the index and working tree as they are.

Returns

Exit status 0 on success. A non-zero status when a conflict stops the merge, or when --ff-only cannot fast-forward. If all named commits are already in HEAD's history, it prints "Already up to date."

Key points

  • --ff-only is a safe way to update a branch: Git moves the pointer forward only when it can, and fails instead of creating a merge commit.
  • --no-ff always creates a merge commit, so the history shows that the feature was developed on its own branch.
  • --squash lets you land a whole branch as one ordinary commit, which you then create yourself with git commit.
  • If you name several branches at once, Git creates an octopus merge. The octopus strategy handles this and refuses merges that would need manual conflict resolution.
  • When merging a single branch, the default strategy is ort. It handles renames and builds a virtual base when the branches have more than one common ancestor.
  • While a conflict is open, the index holds the base, ours and theirs versions of each conflicted file. You can view them with git show :1:file, :2:file and :3:file.
  • Git refuses to start a merge if your staged changes differ from HEAD, so unrelated work cannot slip into the merge commit.
  • Joining two projects that were started separately requires --allow-unrelated-histories. There is deliberately no config setting to make this the default.

Common mistakes

  • If you start a merge with uncommitted changes, --abort may not be able to restore them, so commit or stash first.
  • --no-commit cannot pause a fast-forward, because a fast-forward never creates a commit. Use --no-ff together with --no-commit.
  • A 3-way merge only looks at the two tips and the base. A change made on both branches and later reverted on only one of them will still appear in the merged result.
  • The ours strategy (-s ours) throws away everything the other branch did. The -X ours option only picks our side for conflicting hunks.

Example: Merge a feature branch with --no-ff and handle a conflict

#!/usr/bin/env bash
set -euo pipefail
cd "$(mktemp -d)" && git init -q -b main demo && cd demo
git config user.name dev && git config user.email dev@example.com

echo "greeting=hello" > app.conf
git add app.conf && git commit -qm "initial"

git switch -qc feature
echo "greeting=hi" > app.conf
git commit -qam "feature: change greeting"

git switch -q main
echo "greeting=hey" > app.conf
git commit -qam "main: change greeting"

# Try the merge; it stops on a conflict
if ! git merge --no-ff feature -m "Merge feature"; then
  git status --short            # shows: UU app.conf
  git show :1:app.conf          # common ancestor version
  echo "greeting=hi" > app.conf # resolve by hand
  git add app.conf
  GIT_EDITOR=true git merge --continue
fi

git log --oneline --graph --all

The script builds two branches that edit the same line, merges them with --no-ff, looks at the base version from index stage 1, resolves the conflict by hand, and finishes with git merge --continue.

When to use it

Integrating a finished feature

After review, run git merge --no-ff feature on main so the feature appears as a single merge commit with its own commits kept beneath it.

Syncing with upstream without accidental merges

After git fetch, run git merge --ff-only origin/main to update a local branch that has no commits of its own. If it has diverged, the command fails instead of creating a merge commit.

Superseding an obsolete branch

Run git merge -s ours old-experiment to record the old branch as merged while keeping your tree exactly as it is.

Combining two independent repositories

Fetch the other project and run git merge --allow-unrelated-histories other/main to join two histories that share no ancestor.

Interview questions

QWhat is the difference between a fast-forward merge and a true merge, and how do you control which one happens?

If the current branch tip is an ancestor of the commit being merged, Git can fast-forward: it moves HEAD and the index to that commit without creating a new commit. Otherwise it does a true merge, which records a new commit whose parents are both tips. By default Git fast-forwards when it can. --no-ff forces a merge commit, and --ff-only refuses anything that is not a fast-forward. The merge.ff config setting chooses the default.

Follow-up: Why can merging an annotated tag create a merge commit even when a fast-forward is possible? Git always creates a merge commit for annotated tags so the tag message (and its signature check) goes into the commit. To avoid that, merge tag^0 or use --ff-only.

QWhat happens during a conflict, and what are your options for getting out of it?

HEAD stays where it was, and MERGE_HEAD points to the other tip. Files that merged cleanly are updated. Conflicted files get <<<<<<< ======= >>>>>>> markers, and the index keeps stages 1, 2 and 3 for the base, ours and theirs. You can edit the files, git add them and finish with git merge --continue or git commit. You can run git merge --abort to restore the pre-merge state, or git merge --quit to stop tracking the merge while leaving the files as they are.

Follow-up: How can you see the original base text inside the conflict markers? Set merge.conflictStyle to diff3 or zdiff3; Git then adds a ||||||| section showing the common ancestor's text.

QHow does git merge --squash differ from a normal merge?

--squash prepares the same working tree and index a real merge would, but it does not commit, does not move HEAD and does not write MERGE_HEAD. When you commit afterwards, you get a normal single-parent commit. Git therefore does not record the branch as merged, and the branch's commits are not part of the new commit's ancestry. You cannot combine --squash with --commit.

Follow-up: Why might merging the same branch again after a squash cause conflicts? The squash commit is not linked to the branch's history, so the merge base is still the old split point and Git tries to apply those changes a second time.

  • git pull
  • git rebase
  • git merge-base
  • git mergetool
  • git fmt-merge-msg
  • git reset
  • git rerere
  • git cherry-pick

Source: Git - git-merge Documentation. Checked 2026-11-02.