Introduction
A merge combines two lines of development. The most common case is bringing a feature branch back into main. This page walks through a clean merge, a fast-forward, and a conflicted merge.
Setting up
git switch -c feature/greeting
echo "hello world" > greeting.txt
git add greeting.txt
git commit -m "Add greeting"
git switch main
git merge feature/greeting
If main has not moved since the branch was created, Git fast-forwards: it simply moves main's pointer to feature/greeting's tip. No merge commit is created.
True three-way merges
If both branches have new commits, Git creates a merge commit with two parents:
git switch main
echo "main change" >> notes.txt
git commit -am "Update notes on main"
git merge feature/greeting
# Merge made by the 'ort' strategy.
The merge commit's first parent is the previous HEAD of main; its second parent is the tip of the merged branch.
Forcing a merge commit
To preserve history visibly even when fast-forward is possible:
git merge --no-ff feature/greeting -m "Merge feature/greeting"
Conflicts
If both sides edited the same lines, Git stops and writes conflict markers:
<<<<<<< HEAD
line from main
=======
line from feature
>>>>>>> feature/greeting
Resolve by editing the file, then:
git add notes.txt
git status
git commit # uses the prepared merge message
Aborting
If you change your mind mid-merge:
git merge --abort
This restores the working tree to the pre-merge state.
Inspecting the result
git log --oneline --graph --decorate -n 10
The graph shows the diamond shape created by the merge.
Inspecting a merge
Merge commits have two parents, so diffs need explicit handling. git show on a merge defaults to a "combined diff" that highlights only changes that conflict with both parents:
git show HEAD # combined diff
git show -m HEAD # diff against each parent separately
git show --first-parent HEAD # against the first parent only
git log --merges --oneline # only merge commits
git log --no-merges --oneline # excluding merge commits
For code archaeology, --first-parent on a long-running branch reveals the integration story: when each feature landed, in chronological trunk order.
Common mistakes
Editing files during a merge but forgetting to git add them, so git commit still complains about unmerged paths. Always run git status after editing. Resolving by deleting the conflict markers but leaving stale text from one side; build and run tests before committing the merge. Using --no-ff on every merge in a fast-moving repo, polluting history with empty merge commits. Finally, panicking at the prompt and running git reset --hard mid-merge; git merge --abort is safer because it knows about ORIG_HEAD.