---
title: "How to merge main into a branch in Git (and when to rebase instead)"
description: "Your feature branch is behind main and you need to catch up before opening a PR. Here are the two commands that solve it, the trade-off between merge and rebase, and the conflict cases that bite people every time."
author: julien-danjou
date: 2026-06-01
category: merge-queue-ci
readTimeMinutes: 6
featured: false
product: merge-queue
---

It is Wednesday afternoon. Your feature branch is two weeks old. `main` has shipped seventeen commits since you forked. The PR review is finally green, but the merge button is grey, with a "this branch is out of date" warning underneath it. You need to bring `main` into your branch and you would rather not turn the rest of the day into a rebase forensics session.

The short version of what to do:

```bash
git fetch origin
git merge origin/main
```

That is it. Two commands. Your branch now contains every commit on `main`, your branch's own commits, and a merge commit that joins the two histories together. Push and the PR is up to date.

The longer version is about when this is the right move and when `git rebase` is. Both bring `main` into your branch. Both resolve the "out of date" warning. They produce different histories, and the choice has consequences for the people who read your PR and the people who run `git log` six months from now.

## Merge or rebase: the actual difference

`git merge origin/main` keeps your branch's history intact and adds a merge commit at the tip that has two parents: your previous tip and the current `main`. The history becomes a graph. Anyone reading `git log` sees that your branch was synced with `main` at a specific moment in time.

`git rebase origin/main` takes your branch's commits, rewinds them, applies `main`'s new commits on top of the base, and then reapplies your commits one by one. The history becomes a straight line. The merge commit is gone because the commit graph no longer has a fork.

Two questions decide which one to reach for.

**Has anyone else pulled your branch?** If yes, rebase rewrites history they already have. They will see "your branch and origin/branch have diverged" the next time they fetch, and the recovery is awkward enough that most teams treat shared branches as merge-only by policy.

**Does your team care about a linear history on `main`?** Some teams enforce a no-merge-commits-on-main policy through GitHub branch protection. In that world, rebasing your feature branch onto `main` before merging is the only way to keep `main` linear. Other teams accept merge commits as a useful signal of when integration happened. Both work. Pick one and write it down so nobody has to guess.

If you are alone on the branch and your team prefers linear history, rebase. Otherwise, merge.

## What "merge main into branch" actually does step by step

```bash
git fetch origin
```

This updates your local view of the remote without touching your working tree. After this, `origin/main` is the most recent commit on the remote `main`. Your local `main` branch (if you have one) is untouched until you `git pull` or `git merge` it explicitly.

People skip `git fetch` and use `git pull origin main` instead. That works, but it merges into whatever branch you are currently on, which is sometimes what you want and sometimes not. Splitting fetch from merge makes the intent visible.

```bash
git merge origin/main
```

This brings the new commits from `origin/main` into your current branch. Git computes the three-way merge, identifies conflicts, and either creates a merge commit or fast-forwards if your branch had no commits ahead.

If a conflict appears, Git stops and tells you which files are in conflict. Resolve them in your editor, run `git add` on the resolved files, then run `git merge --continue`. A plain `git commit` works too because Git knows it is in a merge state (it has `.git/MERGE_HEAD`), but `--continue` is the more explicit form and makes the intent visible in your shell history.

If you want to abort and start over: `git merge --abort` returns the working tree to the state before the merge.

## The rebase version, with the part nobody mentions

```bash
git fetch origin
git rebase origin/main
```

Git rewinds your branch's commits, applies the new `main` commits to the base, and then reapplies your commits one by one. If a commit conflicts, Git stops at that commit. You resolve, `git add`, then `git rebase --continue`. Repeat for every conflicted commit.

The part nobody mentions: if you have ten commits on your branch and the first one creates a function that the tenth one renames, you may resolve the same conflict ten times, once per commit. The remedy is to squash your branch first (`git rebase -i origin/main` with `squash` actions, or `git reset --soft origin/main && git commit`) and then rebase a single commit onto `main`. The conflict resolution happens once.

After a rebase, the next `git push` will be rejected because your local branch has rewritten history compared to the remote. The fix is `git push --force-with-lease` (not plain `--force`, which can clobber work someone else pushed since you fetched). `--force-with-lease` checks that the remote tip is what you expected before overwriting.

## Conflicts: the three that actually bite

The Git documentation enumerates conflict types abstractly. In practice, three patterns produce most of the pain.

**Both sides edited the same line.** Git marks the conflict, you pick one (or write a third version), `git add`, continue. Mechanical, irritating, fine.

**You edited a file that the other side deleted.** Git tells you the file is in conflict, but the actual question is: do you want the file back, or do you want the deletion? `git rm <file>` to accept the deletion, or `git add <file>` to keep your edited version. Picking the wrong one quietly is how files reappear in a codebase months after a deletion.

**Logical conflicts that Git does not see.** Your branch added a caller to `getUser(id)`. `main` renamed it to `getUserById(id)`. The two changes do not touch the same lines, so Git merges cleanly. The result does not compile. The only fix is to run your test suite after every merge or rebase, before pushing. The PR review missed it because the PR diff was clean before the merge. CI catches it. If CI is slow or flaky, this is the bug that ends up in `main`.

## When "merge main into branch" is a smell

If you merge `main` into your branch once a week, that is normal. If you merge it once a day because every other PR landing breaks yours, your team has outgrown the "everyone merges manually" workflow.

The structural fix is a [merge queue](/product/merge-queue). The queue tests each PR against the latest `main` before merging, in the order they will land, so the "your branch is behind" loop disappears. The PR you reviewed is the PR that ships. No more pre-merge re-syncs. No more "I had to rebase and now CI is red on an unrelated test."

[Mergify's merge queue](/product/merge-queue) was built around this exact problem and handles squash, rebase, and merge-commit modes per branch protection rule. The [TLA+ verification of the core algorithm](/blog/how-we-formally-verified-our-merge-queue-with-tla-plus) is worth reading if you want the proof that "the commit you tested is the commit you land."

## Cheat sheet

| Situation | Command |
| --- | --- |
| Update your local view of the remote | `git fetch origin` |
| Bring `main` into your branch, keep history graph | `git merge origin/main` |
| Bring `main` into your branch, keep linear history | `git rebase origin/main` |
| Conflict resolved, continue the merge | `git add <files>` then `git merge --continue` |
| Conflict resolved, continue the rebase | `git add <files>` then `git rebase --continue` |
| Abort and start over | `git merge --abort` or `git rebase --abort` |
| Push after a rebase | `git push --force-with-lease` |
| Squash branch to one commit before rebasing | `git reset --soft origin/main && git commit` |

The choice between merge and rebase matters less than picking one and being consistent. Pick the one your team's branch-protection rules prefer. Write it in CONTRIBUTING.md. Move on.
