# Merging on GitHub: merge commit, squash and merge, rebase

> What merging does, the three options behind GitHub's merge button (merge commit, squash and merge, rebase and merge) and what each leaves in the history, a fast-forward merge in plain words, and how to check whether a change is really in main.

- Course: [Git and GitHub for Product People](https://productovi.com/courses/git-for-product.md)
- Module: Branches and merges
- Lesson 5/15, ~32 min
- Source: https://productovi.com/courses/git-for-product/merging

## The green button

**Alex Tutor:** News for you and the rest of CatChow's product team on the finance request. Daniel's pull request #133, "Raise free-delivery threshold to 1000 UAH", has been approved in review. One thing is left: merging it.

**Daniel:** That title is your commit message from module 1, by the way. I liked the summary line, so I reused it for the pull request.

**Sofia:** My words in the history of the site. I could get used to that.

**Alex Tutor:** **Merging** means bringing the changes of one branch into another. Here: from `feature/free-delivery-1000` into `main`. Until that happens, the site keeps saying 800.

```text
Commit graph, oldest first: Pull request #133 before the merge
1. commit 8be2f70 on main "Add cat treats bundle"
2. branch feature/free-delivery-1000 starts from commit 8be2f70
3. commit 5c1d7e2 on feature/free-delivery-1000 "Set freeDeliveryFrom to 1000"
4. commit a8f40b9 on feature/free-delivery-1000 "Update delivery page text"
5. commit d36b9c5 on feature/free-delivery-1000 "Update cart hint text"
```

*Three commits on the branch, none of them in main yet.*

**Sofia:** Three commits on the branch and none of them in `main`. Three ahead, zero behind: `main` hasn't moved since Daniel started.

**Alex Tutor:** After the merge `main` will contain all three changes: the new number in the config, the delivery page text and the hint in the cart. The branch itself stays as it was.

> **Exercise:** Daniel merges `feature/free-delivery-1000` into `main`. What changes?

## One button, three options

**Alex Tutor:** At the bottom of the pull request there is a green button, `Merge pull request`, with a small arrow next to it. The arrow opens three options.

**Sofia:** `Create a merge commit`, `Squash and merge`, `Rebase and merge`. Which one gives the right result?

**Alex Tutor:** All of them. After any of the three, the files in `main` are identical: the threshold is 1000 either way. What differs is the history you'll read afterwards.

![Three differently shaped paths wind down a hill, one with a junction, one straight, one across stepping stones, and all reach the same front door.](https://productovi.com/courses/git-for-product/inline/merging-illustration-2.webp)

*The files end up identical; what differs is how the history reads.*

**Sofia:** Then how does anyone choose?

**Daniel:** Which options you see depends on the repository settings. An admin can switch any of them off, so some repositories offer only one.

**Alex Tutor:** Let's take them one by one, on the same branch, and draw what `main` looks like afterwards.

> **Exercise:** The three options in GitHub's merge button leave different text in the files of `main`.

## Create a merge commit

**Alex Tutor:** Option one, `Create a merge commit`. GitHub adds the branch's commits to `main` as they are and ties the two lines together with one extra commit, a **merge commit**.

```text
Commit graph, oldest first: Option 1: a merge commit
1. commit 8be2f70 on main "Add cat treats bundle"
2. branch feature/free-delivery-1000 starts from commit 8be2f70
3. commit 5c1d7e2 on feature/free-delivery-1000 "Set freeDeliveryFrom to 1000"
4. commit a8f40b9 on feature/free-delivery-1000 "Update delivery page text"
5. commit d36b9c5 on feature/free-delivery-1000 "Update cart hint text"
6. merge commit e4b7a90: feature/free-delivery-1000 merged into main "Merge pull request #133" (highlighted)
```

*The two lanes join in e4b7a90, which has two parents. The history of main gains four entries.*

**Sofia:** The two lanes join in `e4b7a90`. So that commit has two parents: the previous commit on `main` and the last one on the branch.

**Alex Tutor:** Right. Its message is written by GitHub: "Merge pull request #133 from catchow/feature/free-delivery-1000". The history of `main` grows by four entries: Daniel's three commits and the merge commit.

**Daniel:** In the terminal engineers do the same with `git merge`. Nothing is rewritten: my commits keep their hashes, and you can always see where a branch started and where it came back.

**Sofia:** And the price is a busier history: every small step of every branch ends up in `main`.

> **Exercise:** Pull request #140 was merged with `Create a merge commit`. Which statements are true? Select all that apply.

## What is squash and merge?

**Alex Tutor:** Option two, `Squash and merge`. **Squash and merge** packs all the commits of a pull request into one new commit on `main`: GitHub takes every change from the branch, and they arrive together as a single step in the history.

```text
Commit graph, oldest first: Option 2: squash and merge
1. commit 8be2f70 on main "Add cat treats bundle"
2. branch feature/free-delivery-1000 starts from commit 8be2f70
3. commit 5c1d7e2 on feature/free-delivery-1000 "Set freeDeliveryFrom to 1000"
4. commit a8f40b9 on feature/free-delivery-1000 "Update delivery page text"
5. commit d36b9c5 on feature/free-delivery-1000 "Update cart hint text"
6. commit 6f2c8a1: feature/free-delivery-1000 squash-merged into main "Raise free-delivery threshold to 1000 UAH (#133)" (highlighted)
```

*One new commit on main, 6f2c8a1, with a dashed link to the branch. The three original commits are not in the history of main.*

**Sofia:** One commit, `6f2c8a1`, and a dashed line instead of a solid one. And its title ends with `(#133)`.

**Alex Tutor:** Dashed, because Daniel's three commits did not become part of `main`'s history. Their changes did, packed into one commit. GitHub adds the pull request number to its title by itself.

**Daniel:** Before confirming, GitHub lets me edit that commit's message. By default it takes the pull request title and lists my three commit titles underneath, so nothing is really lost.

**Daniel:** This is what our team uses. That's why `main` in module 1 looked like a straight line: one pull request, one commit. My "oops, typo" commits never get there.

![Three small parcels are packed into one bigger box with a single blank address sticker, which rides a conveyor into a warehouse.](https://productovi.com/courses/git-for-product/inline/merging-illustration-3.webp)

*A squash merge turns a branch's steps into one commit on main.*

**Sofia:** And if I want to see the three original steps?

**Alex Tutor:** Open the pull request. Its `Commits` tab keeps them, even after the branch has been deleted.

> **Exercise:** Pull request #140 was merged with `Squash and merge`. How many new commits did `main` get, and where can t1 and t2 still be seen?

## Rebase and merge, and a fast-forward merge

**Alex Tutor:** Option three, `Rebase and merge`. GitHub replays the branch's commits on top of `main`, one by one. No merge commit and no squashing.

```text
Commit graph, oldest first: Option 3: rebase and merge
1. commit 8be2f70 on main "Add cat treats bundle"
2. branch feature/free-delivery-1000 starts from commit 8be2f70
3. commit 5c1d7e2 on feature/free-delivery-1000 "Set freeDeliveryFrom to 1000"
4. commit a8f40b9 on feature/free-delivery-1000 "Update delivery page text"
5. commit d36b9c5 on feature/free-delivery-1000 "Update cart hint text"
6. commit 1f6a3d8 on main "Set freeDeliveryFrom to 1000"
7. commit 2b9e5c4 on main "Update delivery page text"
8. commit 7d0c8f1 on main "Update cart hint text" (highlighted)
```

*Three copies of the commits, with new hashes, continue the straight line of main. The originals stay on the branch until it is deleted.*

**Sofia:** Three commits on `main` with the same messages as on the branch, but different hashes.

**Alex Tutor:** Because they are copies. **Rebase** re-creates each commit in a new place, and a re-created commit gets a new hash. The history stays a straight line and keeps every step.

**Daniel:** You'll also hear "fast-forward". If `main` hasn't changed since the branch started, there is nothing to combine. Git can simply move the name `main` forward to the last commit of the branch.

**Sofia:** Like our case: `main` stood still while you worked. No new commit at all, just the name catching up.

**Daniel:** Yes, but that's a terminal thing. As of 2026 GitHub's button doesn't do a plain fast-forward: the first option always adds a merge commit, and the third always re-creates the commits.

**Alex Tutor:** One more check, on the terminal-only case Daniel mentioned.

> **Exercise:** `main` hasn't changed since Daniel started his branch. In a terminal he runs `git merge feature/free-delivery-1000` while on `main`. What happens by default?

> **Exercise:** Match each way of merging with what the graph looks like afterwards.

## Merge vs squash vs rebase: why a team picks one

**Sofia:** If the files end up the same, why do teams argue about this?

**Daniel:** Because we read history every day. With squash, each line in `main` is one finished piece of work with a link to its pull request. Undoing it means reverting a single commit.

**Daniel:** Teams that choose merge commits want the full record: every commit with its original hash and the exact points where branches joined. Rebase fans want every step too, but in a straight line.

**Alex Tutor:** There is no winner. What matters is that the team agrees on one way, so the history reads the same from week to week.

**Alex Tutor:** For you it means: look at the graph before you count. In a squash repository one commit can hide a week of work. In a merge-commit repository ten commits can be one small feature.

**Sofia:** So "how many commits went into this release?" is a trick question until I know how the team merges.

> **Exercise:** Which merge option produced this history of `main`?

## Is my fix in main?

**Alex Tutor:** Daniel pressed `Squash and merge`, then `Confirm squash and merge`. Now the question you'll hear most often as a PM: is the change in?

**Alex Tutor:** Look at the badge at the top of the pull request. Green `Open` has turned into purple `Merged`, and the line next to it says the commit went into `main`.

```text
Pull requests in catchow/website
- [Merged] Raise free-delivery threshold to 1000 UAH #133: Merged into main today: the changes are in main
- [Open] Rewrite delivery page copy #132: Open: Emma's changes are still on her branch
- [Draft] Fix checkout button after cart edits #134: Draft: Daniel is still working on it
```

**Sofia:** So #133 is in `main`. Emma's #132 is still open, and the checkout fix, #134, is only a draft. Neither of those has reached `main`.

**Alex Tutor:** Two more checks. First, read which branch it was merged into: a pull request can target a branch other than `main`. Second, merged doesn't mean released.

**Daniel:** At CatChow a merge into `main` is deployed automatically within minutes. Other teams collect merges and ship them as a release. We'll get there in module 4.

**Daniel:** And remember my button. Pull request #97 still says `Merged`, although #98 reverted it ten minutes later. Merged is a fact about the past. When in doubt, open the file in `main`.

![A parcel with a delivered stamp sits on a doorstep while a courier walks away carrying an identical parcel back to the van.](https://productovi.com/courses/git-for-product/inline/merging-illustration-4.webp)

*Merged into what, when, and is it still there?*

**Sofia:** So three questions every time: merged into what, when, and is it still there.

> **Exercise:** Which of these pull requests have their changes in `main` right now? Select all that apply.

**Alex Tutor:** And Daniel's warning about #97 deserves its own check.

> **Exercise:** If a pull request shows the purple `Merged` badge, its changes are guaranteed to be on the live site right now.

## The case of the missing commits

**Alex Tutor:** Last one. A colleague from analytics opened pull request #133 and is confused: the pull request lists three commits, but the history of `main` shows only one.

**Sofia:** And they're sure two commits got lost on the way.

**Alex Tutor:** Explain in two or three sentences what happened and where the three commits can still be found.

**Daniel:** Bonus point if you mention the number in the commit title. It's the fastest way back to the pull request.

> **Exercise:** A colleague opened pull request #133 and sees three commits in its `Commits` tab, but the history of `main` shows only one: "Raise free-delivery threshold to 1000 UAH (#133)". They think two commits were lost. Explain in 2–3 sentences what happened and where the three commits can still be found.

**Alex Tutor:** Next lesson: Emma's pull request. She and Daniel changed the same line on the delivery page, and GitHub refuses to merge it on its own.

**Sofia:** A conflict. The word alone sounds like trouble.

- Previous lesson: [Git branches: parallel versions of the project](https://productovi.com/courses/git-for-product/branches.md)
- Next lesson: [Merge conflicts on GitHub: why they happen and what to do](https://productovi.com/courses/git-for-product/merge-conflicts.md)

---
Productovi Learning. Free, no sign-up needed to read.
