| |

GitHub 11 ๐ŸŒ Merging PRs: Merge, Squash, and Rebase Strategies

โฑ๏ธ Estimated read: 10 min | |

A pull request is the proposal. Merging is the execution. When a pull request is approved and its checks pass, GitHub offers three distinct buttons: “Merge commit”, “Squash and merge”, and “Rebase and merge”. These are not cosmetic variations of the same action. Each produces a fundamentally different shape of Git history, and the choice determines how easy it is to trace bugs, revert features, and read the commit log six months from now.

Key point: A merge commit preserves the branch’s topology. Squash compresses the branch into a single commit. Rebase replays the branch’s commits onto the base without a merge point. Choose the strategy based on how much of the branch’s internal history is worth keeping.


Why merge strategies exist

The topology problem. Git is a directed acyclic graph. When two branches diverge and later join, the graph reflects that fork. A merge commit records the exact point where the histories rejoined, preserving the context that “these changes were developed in isolation and then integrated.” Some teams consider this topology essential for archaeologyโ€”knowing that a set of commits shipped together as a unit .

The noise problem. The flip side of topology is noise. A feature branch with fifteen commits (“fix typo”, “actually fix typo”, “wip”, “forgot to save”) produces a main branch where those fifteen commits are permanently visible. Every git log and git bisect must traverse them. Squashing collapses the branch into a single commit with a clean message, sacrificing internal history for a readable main line .

The linearity problem. Some teams enforce a strictly linear history on the main branchโ€”every commit has exactly one parent. This makes git log read like a book, git bisect straightforward, and reverting features a matter of reverting one commit. Both rebase and squash produce linear history; merge commits do not .

The traceability problem. If a commit on main was introduced by a squashed pull request, the individual commits that formed it are gone from main’s history (though they may still exist on the pull request page and in the remote branch, if it was not deleted). For teams that need to trace every change to a specific author and discussion, preserving individual commits matters. Merge commits preserve them; squash does not .

The trade-off. Merge preserves everything but clutters the log. Squash cleans the log but discards granularity. Rebase preserves the commits but rewrites their hashes. None is universally correct. The choice is a policy decision that should be documented and enforced by branch protection rules .


a. Merge commit

The “Create a merge commit” strategy preserves the full history of both branches and adds a merge commit with two parents. If the feature branch has three commits, the main branch receives all three plus one merge commit. The graph visibly shows the fork and rejoining.

# On main before merge:
A --- B --- C

# On feature:
         D --- E --- F

# After merge commit:
A --- B --- C ------- M
           \         /
            D --- E - F

The merge commit M has two parents: C (main) and F (feature). The commit message defaults to “Merge pull request #42 from user/feature” and can be edited to explain the integration. The individual commits D, E, and F remain addressable by hash and are visible in git log --graph. This is the only strategy that preserves the exact sequence of development, including the parent-child relationships within the branch .

This is the default on GitHub when branch protection allows it. It is appropriate for long-lived feature branches where the individual commits are meaningfulโ€”each one a logical unitโ€”and for workflows where reverting an entire feature means reverting the merge commit (with git revert -m 1 <merge-commit>).


b. Squash and merge

“Squash and merge” combines all commits in the pull request into a single commit on the base branch. The commit message defaults to the pull request title, with the individual commit messages appended to the body. The feature branch’s commits are not merged; they are discarded from the base branch’s history.

# On main before squash:
A --- B --- C

# On feature:
         D --- E --- F

# After squash and merge:
A --- B --- C --- S

The commit S contains all changes from D, E, and F combined. The individual commits exist only on the feature branch (until it is deleted) and in the pull request’s “Commits” tab. They are not part of main’s history. This makes git log on main a list of features, not a list of every intermediate attempt .

The commit body records the original commit messages, so the reasoning behind each step is not lostโ€”but the commits themselves are not individually addressable. If a bug is introduced by one of the intermediate commits and fixed by another within the same pull request, the squash merges both the bug and the fix into a single commit. The bug is not visible as a separate point in history.

Squash is the standard for open-source projects where pull requests often contain “address review feedback” and “fix CI” commits that have no value as standalone history. The main branch remains a clean list of features .


c. Rebase and merge

“Rebase and merge” takes the commits from the feature branch and replays them one-by-one on top of the base branch. Each commit is rewrittenโ€”its hash changes, but its message and content are preserved. No merge commit is created. The resulting history is linear.

# On main before rebase:
A --- B --- C

# On feature:
         D --- E --- F

# After rebase and merge:
A --- B --- C --- D' --- E' --- F'

The commits D', E', and F' are new commits with new hashes. Their parent is C, not the original point of divergence. The individual commit messages and authors are preserved. The history reads as if the developer had created the branch after C was committed and worked linearly.

Rebase produces the cleanest linear history while still preserving individual commits. It is the strategy for teams that want both traceability and linearity. The cost is that the original commit hashes are lostโ€”any reference to D in a commit message, issue tracker, or external log is now stale. Rebasing also risks conflicts during the replay, and if the branch has been reviewed as a unit, the rebase process can produce commits that were never individually reviewed (though the sum of them was).


Complete Example Session

# ============================================
# PART 1: A PULL REQUEST WITH THREE COMMITS
# ============================================

git log --oneline feature-branch
# f1e2d3c Fix lint
# b4a5968 Address review feedback
# 7c9b8a1 Add login button
# ============================================
# PART 2: MERGE COMMIT RESULT
# ============================================

# After "Merge pull request":
git log --oneline --graph main
# *   a1b2c3d Merge pull request #42 from user/feature-branch
# |\
# | * f1e2d3c Fix lint
# | * b4a5968 Address review feedback
# | * 7c9b8a1 Add login button
# |/
# * e4f5g6h Previous commit on main
# ============================================
# PART 3: SQUASH AND MERGE RESULT
# ============================================

# After "Squash and merge":
git log --oneline main
# d4e5f6a Add login button (#42)
# e4f5g6h Previous commit on main
# ============================================
# PART 4: SQUASH COMMIT BODY
# ============================================

git show d4e5f6a --format=full
# Add login button (#42)
#
# * Fix lint
# * Address review feedback
# * Add login button
# ============================================
# PART 5: REBASE AND MERGE RESULT
# ============================================

# After "Rebase and merge":
git log --oneline main
# 9z8y7x6 Fix lint
# 6w5v4u3 Address review feedback
# 3t2s1r0 Add login button
# e4f5g6h Previous commit on main
# ============================================
# PART 6: BRANCH PROTECTION ENFORCEMENT
# ============================================

# Settings โ†’ Branches โ†’ Branch protection rule
# "Allow merge commits"         (checkbox)
# "Allow squash merging"        (checkbox)
# "Allow rebase merging"        (checkbox)
# Only the enabled strategies appear in the merge dropdown.

The six parts demonstrate the visual and structural differences. The merge commit preserves the graph. The squash collapses to one commit. The rebase creates a new linear sequence of commits with new hashes.


Quick Reference

Strategy Comparison

StrategyCommits on MainMerge CommitOriginal Hashes PreservedHistory Shape
Merge commitAll + 1YesYesBranched
Squash1NoNoLinear
RebaseAll (rewritten)NoNoLinear

Reverting Features

StrategyHow to Revert
Merge commitgit revert -m 1 <merge-commit>
Squashgit revert <squash-commit>
Rebasegit revert <commit> for each commit, or use a revert PR

When to Use Each

ScenarioRecommended
Long-lived feature with meaningful commitsMerge commit
Open-source PR with “fix review” commitsSquash
Team enforcing linear history with traceabilityRebase

Best Practices

โœ… Do This:

Merge commit for a feature branch where each commit is a logical unit
<!-- Preserves the reasoning and topology -->          # โœ…
Squash for open-source PRs with review-fixup commits
<!-- Keeps main history clean -->                      # โœ…
Rebase when linear history is required and commits are meaningful
<!-- No merge noise, individual commits preserved -->  # โœ…

โŒ Don’t Do This:

Squash a 50-commit branch with distinct logical steps
<!-- Destroys useful granularity -->                   # โŒ
Rebase a branch that others have based work on
<!-- Rewrites hashes; breaks their references -->      # โŒ
Mix strategies without a documented policy
<!-- Inconsistent history, hard to automate -->        # โŒ

Common Pitfalls

PitfallWhy It HappensFix
Cannot revert a squashed PR cleanlyMultiple features bundled into one commitUse merge commit or rebase for complex PRs
Rebase introduces conflictsBase branch moved since branch creationResolve conflicts in the PR before merging
Feature missing from main historySquash discarded intermediate commitsInspect PR’s “Commits” tab for original history
Inconsistent history shapeTeam has no documented policyEnforce via branch protection rules

Real-World Examples

1. Merge Commit

*   Merge pull request #42 from user/feature
|\
| * Fix lint
| * Add login button
|/
* Previous commit

2. Squash and Merge

* Add login button (#42)
* Previous commit

3. Rebase and Merge

* Fix lint
* Address review feedback
* Add login button
* Previous commit

4. Revert Merge Commit

git revert -m 1 <merge-commit-sha>

5. Revert Squash Commit

git revert <squash-commit-sha>

6. Enforce Strategy in Branch Protection

Settings โ†’ Branches โ†’ only "Allow squash merging"

7. Edit Squash Message

On the merge form, edit the "Squash commit message" field.

8. Preserve Commit Message Body

Squash merge keeps individual messages in the commit body.

9. Fast-Forward Merge

Rebase merge is a fast-forward if no conflicts occur.

10. View PR Commits After Squash

Pull request โ†’ Commits tab

Visual

Merge Commit

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  main:    A --- B --- C ------- M            โ”‚
โ”‚                                  /           โ”‚
โ”‚  feature:         D --- E --- F              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

M has two parents. All commits are visible.

Squash

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  main:    A --- B --- C --- S                โ”‚
โ”‚                                              โ”‚
โ”‚  feature:         D --- E --- F              โ”‚
โ”‚                    (discarded from main)     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

S contains all changes. D, E, F are gone from main.

Rebase

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  main:    A --- B --- C --- D' --- E' --- F' โ”‚
โ”‚                                              โ”‚
โ”‚  Original D, E, F are rewritten.             โ”‚
โ”‚  No merge commit. Linear history.            โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The three strategies produce three distinct histories from the same branch.


Summary

ItemValue
Merge commitPreserves all commits + merge commit
Merge commit parentsTwo (base and feature)
SquashCombines all commits into one
RebaseReplays commits on top of base, new hashes
History shape (merge)Branched
History shape (squash)Linear
History shape (rebase)Linear
Reverting featuregit revert -m 1 <merge> or git revert <squash>
Policy enforcementBranch protection settings

Key takeaways:

  • Merge commit preserves everything. The individual commits, their order, and their relationships are visible in the graph. The merge commit is the undo point .
  • Squash cleans the main history. The feature becomes a single commit with the PR title. The internal commit history is discarded from main but remains visible on the PR page.
  • Rebase produces linear history with individual commits. The commit messages are preserved, but the hashes are rewritten .
  • The choice should be documented. A team that mixes strategies without a policy has inconsistent history. Branch protection settings can enforce the choice by disabling the unwanted buttons.
  • Reverting is different for each. A merge commit is reverted with -m 1. A squash commit is reverted directly. A rebased feature requires reverting each commit, or a revert PR.

Remember: A pull request is the proposal; the merge strategy is the execution. A merge commit says “this feature was developed here and integrated here.” A squash says “this feature exists now, and its internal steps are forgotten.” A rebase says “this feature was always part of the main line.” Choose the story you want the history to tell.

Was this helpful?


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!