How-to

git merge: How to Merge Branches and Resolve Conflicts

Updated: · Skillaria.Top

git merge combines another branch's history into the current one: if the current branch hasn't moved ahead, Git just moves the pointer (fast-forward); otherwise it creates a merge commit with two parents. Switch to the target branch and run git merge branch-name; on a conflict, fix the files, then git add and git commit, or back out entirely with git merge --abort.

What git merge does

The git merge command takes the changes from the branch you name and integrates them into the branch you're currently on. Remember the direction: you merge "into yourself". To merge feature/login into main, you first switch to main and then run the merge.

git switch main
git pull                 # get the latest main from the server
git merge feature/login

Git doesn't copy anything line by line "by hand". It finds the common ancestor of the two branches (the merge base), compares both versions against it, and combines the changes. If the two sides edited different places, the result is produced automatically. If they edited the same place, you get a conflict, and the decision is yours.

Two modes: fast-forward and three-way merge

Fast-forward — when no merge commit is needed

Say you branched feature off main, made three commits, and nobody added anything to main in the meantime. Then main's history is simply the beginning of feature's history. Git moves the main pointer forward to the latest commit on feature, and no new commit is created.

$ git merge feature
Updating 3f2a1c4..9b7e0d2
Fast-forward
 src/auth.js | 42 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 42 insertions(+)

The history stays linear, but the fact that "there was a feature branch here" is lost. Sometimes that's fine, sometimes it isn't — more on that below in the section on --no-ff.

Three-way merge and the merge commit

If both branches have moved ahead of their common ancestor, the pointer can't simply be moved. Git performs a three-way merge: the base, "our" side (the current branch) and "their" side (the branch being merged). The result is recorded as a separate merge commit with two parents.

$ git merge feature
Merge made by the 'ort' strategy.
 src/auth.js   | 42 ++++++++++++++++++++
 src/router.js |  3 +-
 2 files changed, 44 insertions(+), 1 deletion(-)

Git opens an editor with a message like Merge branch 'feature'. You can keep it or add why the merge was done. To see the resulting fork in history:

git log --oneline --graph --all

The ort strategy has been the default since Git 2.34; before that it was recursive. For everyday work there's no need to change the strategy.

Useful git merge flags

FlagWhat it doesWhen it's useful
--no-ffAlways creates a merge commit, even when a fast-forward is possibleYou want the history to show which commits came from one feature
--ff-onlyMerges only by fast-forward, otherwise failsUpdating a local branch without accidental merge commits
--squashStages all of the branch's changes but doesn't commit or link to the branchYou need to turn a feature into one clean commit
--no-commitPerforms the merge but stops before committingYou want to inspect the result or run tests before committing
-m "message"Sets the merge commit messageMerging in scripts or without an editor
--abortCancels a merge in progress and restores the previous stateToo many conflicts, you need to start over
--continueCompletes the merge after conflicts are resolvedAn alternative to git commit as the last step
-X ours / -X theirsAutomatically takes our or their version for conflicting hunksYou know for sure whose version is right (be careful!)

--no-ff: keeping feature boundaries

Many teams merge features only with --no-ff. That way every task shows up as a "bubble" in the graph and can be rolled back with a single git revert -m 1 <merge-commit>. With a fast-forward there's no such point, and you'd have to revert every commit individually.

--squash: one commit instead of ten

git switch main
git merge --squash feature/login
git commit -m "Email and password login"

After --squash, Git doesn't consider the branch merged: git branch -d feature/login will refuse to delete it with a "not fully merged" warning. Delete it with git branch -D once you've confirmed everything is in place. And don't keep working on the same branch after a squash — the next merge will bring in the already merged changes again and will almost certainly cause conflicts.

Merge conflicts: how to resolve them

A conflict is a normal part of the job, not a disaster. It means just one thing: two branches changed the same lines (or one deleted a file the other edited), and Git won't guess which version is correct.

$ git merge feature/login
Auto-merging src/config.js
CONFLICT (content): Merge conflict in src/config.js
Automatic merge failed; fix conflicts and then commit the result.

Step 1. See what's conflicting

$ git status
On branch main
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Unmerged paths:
  (use "git add <file>..." to mark resolution)
        both modified:   src/config.js

Step 2. Edit the file

Git adds markers to the conflicting file:

<<<<<<< HEAD
const TIMEOUT = 5000;
=======
const TIMEOUT = 10000;
>>>>>>> feature/login

The top part is your current branch (HEAD), the bottom part is the branch being merged. Keep the correct version (or combine both), delete all three marker lines, and save the file. A common mistake is leaving a marker somewhere in the middle of the file; search for them before committing — git diff --check will flag any leftover markers.

Step 3. Mark it resolved and finish the merge

git add src/config.js
git commit          # or git merge --continue

If the conflict is resolved entirely by picking one side, you don't need to edit the file by hand:

git checkout --ours   src/config.js   # keep the current branch's version
git checkout --theirs src/config.js   # take the merged branch's version
git add src/config.js

Easier in an editor

VS Code, JetBrains IDEs and other editors show conflicts in three panes with "accept current / incoming / both" buttons. You can also plug in an external tool via git mergetool. A useful setting is git config --global merge.conflictStyle zdiff3: the markers then also include the original version from the common ancestor, which makes it clear who changed what.

Changed your mind — abort the merge

git merge --abort

This returns the working tree and the index to their state before git merge. That's why it's best to start a merge with a clean working tree: commit or stash unfinished changes with git stash, or they'll be hard to separate from the merge results.

How to undo a merge you've already made

If the merge commit hasn't been pushed yet, the easiest option is to move the branch back:

git reset --hard ORIG_HEAD

Git records ORIG_HEAD before risky operations, including merges — it's where the branch was before the merge. --hard throws away uncommitted changes, so check git status first.

If the merge is already in a shared repository, you don't rewrite history. You create a reverse commit instead:

git revert -m 1 a1b2c3d

The -m 1 flag says the first parent (the branch you merged into) is the "mainline". Watch out for a catch: after such a revert, Git considers the feature's commits already merged. If you later want to merge the fixed feature again, you'll first have to revert the revert commit itself.

Merging in a team workflow

git pull is fetch plus merge

A plain git pull downloads changes and merges the remote branch into your local one. If you and a teammate committed in parallel, you get a "Merge branch 'main' of ..." commit. To avoid these technical merges, set git config --global pull.ff only (pull fails if a fast-forward isn't possible, and you decide what to do) or use git pull --rebase.

Pull requests and the Merge button

On GitHub and GitLab, merging usually happens through a pull/merge request. The "Create a merge commit" button is equivalent to git merge --no-ff, "Squash and merge" to a squash merge, and "Rebase and merge" to a rebase. Which mode to use is a team convention; the underlying mechanism is the same as on the command line.

Merging main into your branch

A long-lived branch drifts away from main, and conflicts pile up. Pull the main branch into yours regularly:

git switch feature/login
git fetch origin
git merge origin/main

That way conflicts get resolved in small chunks as they appear, and the final merge into main goes smoothly.

Common mistakes

  • Merging in the wrong direction. Running git merge main while on main, or merging a feature into another feature. Check your current branch with git branch --show-current before merging.
  • Merging with a stale branch. Merging a local main that hasn't been updated in a week. Run git pull first, then merge.
  • "Your local changes would be overwritten by merge". Your working tree has uncommitted changes in the same files. Commit them or stash them.
  • "refusing to merge unrelated histories". The branches have no common ancestor — for example, the local repository was created with git init while the remote already has a README. If you really need to combine them, add --allow-unrelated-histories.
  • Committing conflict markers. The build fails on <<<<<<<. Check with git diff --check and run tests before committing.

Summary

git merge is a safe way to combine branches: it doesn't rewrite existing commits, it only adds a new one (or fast-forwards the pointer). Merge into the current branch, keep your working tree clean, use --no-ff when feature boundaries matter and --squash when you want a single commit. Conflicts are resolved in three steps — edit the file, git add, git commit — and git merge --abort always takes you back to where you started. Merging branches and resolving conflicts are practiced in detail in the Git course.

FAQ

What's the difference between git merge and git pull?

git pull first downloads changes from the server (git fetch) and then merges the remote branch into the current one with git merge or, if configured, with rebase. git merge only works with what you already have locally.

Can I merge several branches at once?

Yes, git merge feature-a feature-b performs an octopus merge with multiple parents, but only if there are no conflicts. In practice, branches are usually merged one at a time.

How do I see which branches are already merged into the current one?

git branch --merged lists branches fully contained in the current one, and git branch --no-merged lists those not yet merged. Merged branches can be safely deleted with git branch -d.

How can I preview what a merge will bring in?

Run git log main..feature to see commits that aren't in main, and git diff main...feature to see the branch's changes relative to the common ancestor.

What is ORIG_HEAD after a merge?

It's a ref Git saves before the merge, pointing to where the branch was before it. git reset --hard ORIG_HEAD rolls back a local merge as long as it hasn't been pushed.

Learn this topic on video

This topic is covered in depth with practice in the course “Git from Scratch to Confident Developer”.

Go to course