git merge: How to Merge Branches and Resolve Conflicts
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
| Flag | What it does | When it's useful |
|---|---|---|
--no-ff | Always creates a merge commit, even when a fast-forward is possible | You want the history to show which commits came from one feature |
--ff-only | Merges only by fast-forward, otherwise fails | Updating a local branch without accidental merge commits |
--squash | Stages all of the branch's changes but doesn't commit or link to the branch | You need to turn a feature into one clean commit |
--no-commit | Performs the merge but stops before committing | You want to inspect the result or run tests before committing |
-m "message" | Sets the merge commit message | Merging in scripts or without an editor |
--abort | Cancels a merge in progress and restores the previous state | Too many conflicts, you need to start over |
--continue | Completes the merge after conflicts are resolved | An alternative to git commit as the last step |
-X ours / -X theirs | Automatically takes our or their version for conflicting hunks | You 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 mainwhile onmain, or merging a feature into another feature. Check your current branch withgit branch --show-currentbefore merging. - Merging with a stale branch. Merging a local
mainthat hasn't been updated in a week. Rungit pullfirst, 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 initwhile 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 withgit diff --checkand 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.
This topic is covered in depth with practice in the course “Git from Scratch to Confident Developer”.
- Branches: The Parallel Reality of Your Code
- Merge Conflicts: Don't Panic - Understanding and Resolving Them