You merge in Git from the branch that should receive the changes. Check out the target branch, run git merge with the name of the source branch, and the job is done.
What comes out depends on the history. Git either slides the branch pointer forward (a fast-forward) or writes a merge commit with two parents, which happens when each branch has commits the other doesn’t.
Most developers run this several times a day, usually to land a feature branch in main. The strategy doing the work since Git 2.34.0, released on November 15, 2021, is ort, which took over from recursive (Git 2.34 release notes, 2021).
How to Merge Two Branches in Git

Git merges into whatever is checked out, so switch to the target branch before anything else. The source branch stays untouched, and only the target gains commits.
Check the repository state before merging
Commit or stash anything pending, and confirm which branch HEAD points to. If the source branch lives on a remote, fetch first so you are merging what your teammates actually pushed.
None of this takes long. Start with the git status output, which lists uncommitted changes and names the checked-out branch.
Git refuses to run when uncommitted changes overlap files the merge has to update (Git documentation, as of the Git 2.54 manual). It also aborts when the index differs from HEAD.
Remote work needs a refresh too. The fetch command updates remote-tracking branches without touching your working files, so origin/feature-login reflects whatever was pushed.
Run the merge from the target branch
- Switch to the target with
git switch main(orgit checkout mainon older Git) - Update it with
git pull - Merge the source with
git merge feature-login - Read the output. “Fast-forward” means Git moved the pointer, and “Merge made by the ‘ort’ strategy” means it created a merge commit
- Check the result with
git log --graph --oneline - Publish it with
git push origin main
If you see “Already up to date.”, every commit on the source branch is already in the target and nothing changes.
A conflict pauses the merge before any commit exists. Those steps come further down.
What Happens Inside Git When Two Branches Merge?
It depends on how the histories relate. When the target branch tip is an ancestor of the source branch tip, Git does a fast-forward. When both branches carry commits the other lacks, it falls back to a three-way merge.
| Merge type | Trigger | Result |
|---|---|---|
| Fast-forward | Target tip is an ancestor of source tip | Pointer moves, no new commit |
| Three-way | Both branches have new commits | New merge commit with two parents |
| Annotated tag | Source is an annotated tag | Merge commit, even if a fast-forward is possible |
If you want the mechanics of what git merge does, it compares snapshots and then stores the combined result as one new snapshot.
Fast-forward merge
A fast-forward moves a pointer and nothing else.
In the Pro Git example, the hotfix branch sits directly ahead of main, so Git advances the main pointer and prints Fast-forward. There’s no divergent work to combine.
The --no-ff flag forces a merge commit even when a fast-forward would work. Merging an annotated tag also always creates a merge commit, which is why --ff-only is the flag to use when you’re syncing to a release tag and have no work of your own.
Three-way merge and the merge commit
Git needs three snapshots for this. The merge base is the latest common ancestor of both branches, the target tip is HEAD (the branch you’re standing on), and the source tip is the branch named in the command, known as MERGE\_HEAD while a conflict is open.
It compares both tips against the merge base and writes the result as a new commit with two parents. That double parentage is what git log --graph draws as a join.
Before the merge starts, Git sets ORIG\_HEAD to the target tip, which later gives git reset a way back. When the branches share more than one common ancestor, the ort strategy first builds a merged tree of those ancestors and uses that as the base (Git documentation, as of the Git 2.54 manual).
Which Merge Option Fits: –no-ff, –ff-only, –squash or –no-commit?
Use --no-ff to keep the branch visible in history and --ff-only to refuse anything that isn’t a clean fast-forward. --squash collapses a branch into one commit, and --no-commit lets you inspect the result before it gets committed.
| Flag | Merge commit | History shape | Best fit |
|---|---|---|---|
| –no-ff | Always | Branch boundaries visible | Team feature branches |
| –ff-only | Never, fails if diverged | Linear | CI gates, protected main |
| –squash | None, manual commit | Linear, branch commits collapsed | Noisy work-in-progress branches |
| –no-commit | Held back | Depends on the final commit | Inspecting before committing |
Pros and cons by option
–no-ff
Every merge leaves a commit with two parents, so git log --graph shows where each branch started and ended. The cost is one extra merge commit per branch, and in a busy repository that adds up to a noisy log.
–ff-only
History stays linear and no surprise merge commits show up. It also fails loudly, which suits automated jobs.
The downside is that it refuses to merge and exits with a non-zero status the moment the branches diverge, so a rebase or a normal merge has to follow.
–squash
One commit lands on the target, which hides all the noisy work-in-progress commits. The combined changes arrive staged, so you write the commit message fresh.
Git records no MERGE\_HEAD here, so it never learns the branch was merged, and git branch -d treats the source as unmerged.
–no-commit
This stops just before the merge commit so you can look at the result. It can’t stop a fast-forward, though, so pair it with --no-ff when the branch must not move (Git documentation, as of the Git 2.54 manual). Keep any edits you make in that window small, like a version bump.
Which flag to pick
Match the flag to whoever reads the history later.
For solo work on a short branch, plain git merge is fine, and the fast-forward happens by itself. Team feature branches do better with --no-ff, so each feature stays one visible unit. A protected main or a CI gate calls for --ff-only after the branch has been rebased, and a messy contributor branch is where --squash earns its keep.
Squash gets overused, in my opinion. On a branch of deliberate, well-described commits it erases the granularity that git bisect and git blame depend on.
GitLab’s merge methods follow the same split. Its fast-forward method equals git merge --ff-only, and fast-forward merging with squashing enabled equals git merge --squash followed by git commit (GitLab documentation).
Setting a default with merge.ff
Left unset, Git fast-forwards whenever it can. You can change that through Git’s config command. Running git config --global merge.ff false makes every merge behave like --no-ff, while setting merge.ff to only behaves like --ff-only.
To change the behavior for one branch only, branch.main.mergeOptions takes the same flags (Git documentation, as of the Git 2.54 manual).
What Is the Default Merge Strategy in Git, and When Should It Change?
Git uses ort when you merge one branch and octopus when you merge several at once. Change it only when the merge has a particular shape, like more than two branches, a side branch whose work should be thrown away, or conflicting hunks that should always favor one side.
Ort became the default in Git 2.34.0 (released 2021-11-15) and replaced recursive. It first shipped as an opt-in strategy in Git 2.33.0 (released 2021-08-16). More recently, recursive was turned into an alias for ort in Git 2.50.0 (released 2025-06-16). The documentation lists 6 built-in strategies in all: ort, recursive, resolve, octopus, ours and subtree.
Those figures come from the Git merge-strategies documentation (as of the Git 2.54 manual), the Git 2.34 release notes and the git-scm.com release list.
Other built-in strategies
Octopus merges more than two heads, as in git merge fixes enhancements, and it refuses any merge that needs manual resolution.
Resolve is a two-head, three-way strategy that doesn’t handle renames.
With ours, the result is always the current branch’s tree, which supersedes old side-branch history, as in git merge -s ours obsolete.
Subtree is a modified ort that shifts one tree to match the other when it corresponds to a subdirectory.
The ours strategy versus the -X ours option
The -s ours strategy keeps the current branch’s tree and discards everything the other branch did. The merge commit still records both parents.
The -X ours option is narrower. It resolves only the conflicting hunks in favor of the current side, so non-conflicting changes from the other branch still merge, and a binary file is taken whole from the current side.
People mix the two up all the time, and the documentation warns about it. Git has no theirs strategy, only a -X theirs option.
Why ort replaced recursive
GitHub measured a speedup of more than 500x for a large merge with many renames (GitHub Blog, 2021).
Typical merges gain less than that. The bigger difference is consistency, since recursive swung widely in speed while ort holds a steady pace.
Ort also writes an AUTO\_MERGE ref, and git diff AUTO_MERGE shows what has been resolved so far during a conflict (Git documentation, as of the Git 2.54 manual).
Reverted changes that come back
Three-way strategies, ort included, bring back a change that was made on both branches and later reverted on one of them.
Git only compares the two tips and the merge base, never individual commits, so the revert looks like no change at all.
Say both branches add a debug line, then main removes it. After the merge the line is back, because main’s tip matches the merge base and only the feature branch differs.
How to Resolve Merge Conflicts in Git
Open each file listed under unmerged paths, remove the conflict markers, keep the lines that belong, stage the file with git add, and finish with git merge --continue. Until the last conflict is staged, the merge stays paused. HEAD doesn’t move and no merge commit exists.
- Run
git statusand note every file under unmerged paths - Open one file and find the conflict markers
- Keep one side or combine both, then delete the marker lines
- Stage the file with
git add index.html - Repeat for every conflicted file, then run the test suite
- Conclude the merge with
git merge --continue
If you’d like a longer, step-by-step version, follow this guide to resolving merge conflicts in Git.
Read the conflict markers
The line starting with <<<<<<< HEAD opens the target branch version. A row of ======= divides the two versions, and >>>>>>> feature-login closes the source branch version.
Setting merge.conflictStyle to diff3 or zdiff3 adds a ||||||| line that holds the merge base text. The default style produces smaller conflict regions, while diff3 shows what each side started from (Git documentation, as of Git 2.56).
Resolve in an editor or merge tool
A text editor is enough for most conflicts. git mergetool adds a side-by-side view when you want one, and it launches the tool named in merge.tool or picks one from a built-in list that includes vimdiff, meld, kdiff3 and vscode.
During a conflict, the git diff command prints a three-way diff of the HEAD and MERGE\_HEAD versions, which helps when you’re deciding which lines win.
The index stores up to 3 versions of each conflicted path. Stage 1 holds the merge base, stage 2 holds HEAD and stage 3 holds MERGE\_HEAD, so git show :1:index.html prints the first one.
Binary files are where line-by-line merging gives up, because they can’t be combined. Pick a side with git checkout --ours -- file or git checkout --theirs -- file, then stage it.
Finish the merge
git merge --continue checks that a merge is actually in progress before it commits. A plain git commit works as well.
Git status will then report that all conflicts are fixed but the merge is still open.
Use the commit message to explain any resolution that isn’t obvious. Future readers of git log never see the markers you deleted.
How to Undo a Git Merge
The right command depends on how far the merge got. Use git merge --abort while conflicts are open, git reset to ORIG\_HEAD after a local commit, and git revert -m 1 once the merge is pushed.
| Merge state | Command | Effect on history | Safe on shared branches |
|---|---|---|---|
| In progress, conflicts open | git merge –abort | No commit exists, pre-merge state restored | Yes |
| Committed, not pushed | git reset –hard ORIG\_HEAD | Branch tip moves back, merge commit dropped | Only before anyone pulls it |
| Committed, local edits present | git reset –merge ORIG\_HEAD | Same, local edits kept | Only before anyone pulls it |
| Pushed | git revert -m 1 merge-commit-hash | New commit reverses the merge, history grows | Yes |
Merge still in progress
The --abort flag rebuilds the pre-merge state, and it equals git reset --merge while MERGE\_HEAD exists. The --quit flag does something different. It forgets the merge but leaves the index and working tree exactly as they are.
Abort has a limit. If uncommitted changes existed when the merge began, it can’t always rebuild them (Git documentation, as of Git 2.56), so commit or stash first.
Merge committed but not pushed
Git saves the old target tip in ORIG\_HEAD before every merge.
The git reset command uses it to step back. With --hard ORIG_HEAD the working tree is overwritten too, while --merge ORIG_HEAD keeps your uncommitted edits.
ORIG\_HEAD doesn’t last. Another command that sets it, such as a later reset, replaces it, and branch@{1} in the reflog still points to the earlier tip.
Merge already pushed
Revert it. Don’t reset.
Resetting a shared branch rewrites history, and the git-reset manual warns against discarding commits that someone else already received.
The git revert command adds a new commit that reverses the merge, as in git revert -m 1 merge-commit-hash.
The -m flag names the mainline parent, counted from 1. Parent 1 is the target branch tip the merge was made on, so -m 1 keeps that side.
Re-merging later has a catch. Reverting a merge declares that its tree changes are never wanted, so a later merge of the same branch only brings commits that aren’t ancestors of the reverted merge (git-revert manual). You can revert the revert commit to restore the changes, or rebase the source branch with --force-rebase so it gets fresh commits and then merge again (git-rebase manual).
Merge, Rebase or Cherry-Pick: Which One Combines the Branches?
Merge a branch other people use, rebase a private branch before it joins the target, and cherry-pick when only some commits should move. They differ in one way that matters later. Merge keeps the original commits, while rebase and cherry-pick create new ones.
| Option | Pros | Cons |
|---|---|---|
| Merge | Original hashes stay, safe on shared branches | Extra merge commit, branchy log |
| Rebase | Linear history, no merge commit | New hashes, breaks work built on old commits |
| Cherry-pick | Moves only chosen commits | Copies changes under new hashes, no record of a join |
The git rebase command replays commits one by one onto the new base, which is why every hash changes.
How git pull chooses between merge and rebase
git pull fetches first, then integrates the upstream branch into the current one.
The git pull command offers 4 integration modes in the Git 2.55 manual. With --ff-only it only fast-forwards and fails if the local branch diverged, --rebase runs git rebase, --no-rebase runs git merge, and --squash runs git merge --squash.
The manual disagrees with itself on the default. Its overview names --ff-only, while the --rebase entry also carries a default label.
Run git config pull.rebase to see what a repository actually uses. pull.ff and pull.squash set the remaining modes.
What rebase changes that merge does not
Every replayed commit is new, and the old ones survive only in the reflog. Merge commits are dropped by default and only come back with --rebase-merges.
Conflicts behave differently as well. Rebase stops at the first problematic commit, so a long branch can need several resolutions where a merge needs one. And the sides swap, because ours is the series rebased so far and theirs is the working branch. That one trips people up constantly.
When rebase is the wrong choice
Rewriting a branch that others have based work on is a bad idea, according to the git-rebase manual.
Everyone downstream fixes their history by hand, and if old and new commits are later merged together, the duplicates stay in the log for good. git pull –rebase carries the same warning in the pull manual.
Cherry-pick for single commits
Cherry-pick copies the change from each named commit and records a new commit for it. It needs a clean working tree.
The -x flag appends the source commit hash to the message, which suits backports between public branches. If the named commit is a merge, -m picks the mainline parent.
A conflict sets CHERRY\_PICK\_HEAD and pauses the run. Resume with --continue or cancel with --abort.
When Does git merge Fail or Not Fit?
git merge stops in 3 common cases. A fast-forward is demanded but the branches diverged, the two histories share no ancestor, or local changes overlap the merge.
Fast-forward refused
When nobody else uses the source branch, rebase first and then fast-forward:
- On the source branch, run
git rebase main - On main, run
git merge --ff-only feature-login
Dropping --ff-only and accepting a merge commit is the other way out.
Unrelated histories
git merge refuses histories without a common ancestor. The --allow-unrelated-histories flag overrides that check for two projects that started independently.
git remote add other-repo repository-urlgit fetch other-repogit merge --allow-unrelated-histories other-repo/main
No configuration variable turns this on by default, and none will be added (Git documentation, as of Git 2.56).
Overlapping local changes
Commit the work, or park it with the git stash command and restore it after the merge. You can also pass --autostash, or set merge.autoStash, which defaults to false.
Autostash reapplies the stash after the merge, and that final step can raise conflicts of its own.
Rename detection limits
merge.renameLimit defaults to 7000 files when neither it nor diff.renameLimit is set (Git documentation, as of Git 2.56).
That value caps the exhaustive portion of rename detection. A repository with a mass move across thousands of files can exceed it, and git config merge.renameLimit raises the cap.
Common Questions About git merge
How do I merge a remote branch?
Fetch first, then merge the remote-tracking branch. Run git fetch origin followed by git merge origin/feature-login while the target branch is checked out.
git pull origin feature-login does both steps in one command, as the Git documentation shows.
Can I merge main into a feature branch?
Yes. Check out the feature branch and run git merge main.
The feature branch picks up everything new on main, and main stays untouched until the feature branch is merged back.
How do I merge two branches into a new third branch?
Create the new branch from one source with git switch -c combined feature-a, then run git merge feature-b on it.
Both feature branches stay unchanged, and combined holds the joined work.
Should I delete a branch after merging it?
Yes, once the merge is on the target branch. git branch -d feature-login removes the local copy and refuses when Git sees unmerged commits, while git push origin --delete feature-login removes the remote one.
The steps for deleting a branch in Git cover both copies.
What is the difference between git switch and git checkout?
git switch only changes branches. git checkout does that too, but it also restores files and can detach HEAD.
For picking the target branch before a merge, either command works.
Is a pull request the same as running git merge?
Not exactly. A pull request asks the hosting platform to run the merge on its servers, with options such as a merge commit, a squash or a rebase.
The guide to merging branches in GitHub walks through that route. Your local copy catches up with a git pull afterward.
Choosing a Merge Policy for a Team
A team using git merge should settle on one policy and stick to it. Use –no-ff when branch boundaries in the log matter and –ff-only when a linear history matters more. Mixing the two leaves a log where some features appear as units and others dissolve into main.
Agree on the history shape first, pick the flag from that, and only then worry about enforcement outside local config.
Enforcement comes last for a reason. merge.ff lives in each clone’s config and doesn’t travel with the repository, so only a server-side merge method setting or a CI job applies the rule to every contributor.
The trade-off is concrete. --ff-only forces contributors to rebase before each merge, which rewrites their commit hashes, while --no-ff accepts a busier log in exchange for none of that friction.
Verified against the Git 2.56 documentation in October 2026, the advice changes only if a later release alters a default such as the pull mode. A team that writes the policy down can place it inside a documented git workflow.
- How to Run a React App in VS Code Step by Step - October 23, 2026
- Transactional or marketing email: which messages your app should send itself - October 23, 2026
- How Software Teams Write SOPs for Releases and Deployments - October 22, 2026




