Merge vs Rebase Decision Matrix Jump to heading

Selecting the wrong integration strategy for a branch is one of the most disruptive mistakes in collaborative Git workflows. As part of Git Workflow Architecture & Branching Strategies, this page gives you a concrete decision framework — not a stylistic preference — grounded in branch characteristics, team size, CI/CD topology, and compliance requirements.

Prerequisites Jump to heading

Before applying this decision matrix, confirm:


Merge vs rebase decision flowchartFlowchart with three diamond decision gates. Gate 1: multi-author branch? Yes leads to Merge. No leads to Gate 2: already pushed and shared? Yes leads to Merge. No leads to Gate 3: compliance or audit required? Yes leads to Merge with signed commit. No leads to Rebase (single author, private branch, no compliance constraint).Branch readyto integrate?Multi-authorbranch?yesMerge(--no-ff)noAlready pushed& shared?yesMerge(preserve SHAs)noCompliance /audit required?yesMerge+ signedcommit (-S)noRebasesingle-author,private branch① attribution → ② SHA stability → ③ audit trail → outcome

Step 1 — Evaluate Branch Characteristics Jump to heading

Intent: Identify the structural facts about the branch before choosing a strategy. Opinions about “clean history” come after the facts.

# How old is the branch relative to main?
git log --oneline main..HEAD | wc -l          # commit count
git log --format="%ar" main..HEAD | tail -1   # age of the oldest commit

# How many distinct authors contributed?
git shortlog -sn main..HEAD

# Has the branch already been pushed to the remote?
git fetch origin
git log --oneline origin/$(git branch --show-current)..HEAD
# Empty output = remote is up to date; non-empty = local-only commits

Verification: You have three numbers — commit count, author count, and whether any commits exist only locally. These drive every decision below.

Step 2 — Apply the Decision Rules Jump to heading

Intent: Map the branch facts to a concrete strategy using the four rules below. The rules are ordered by severity; the first one that matches wins.

RuleConditionRequired strategyReason
1More than one author on the branchMerge (--no-ff)Rebase rewrites SHAs and destroys per-author commit attribution
2Branch already pushed and fetched by teammatesMerge (--no-ff)Rebase forces a re-sync on every engineer who fetched the old SHAs
3Compliance, audit, or signed-commit requirementMerge with -SMerge commits preserve a signed, immutable record of integration
4Single author, not yet shared, no compliance constraintRebaseLinear history, no merge noise, easy git bisect traversal

Decision logic you can encode in a pre-merge automation script:

#!/usr/bin/env bash
# Usage: ./pick-strategy.sh <branch>
# Requires Git v2.30+ and POSIX tools

BRANCH="${1:-$(git branch --show-current)}"
BASE="main"

author_count=$(git shortlog -sn "${BASE}..${BRANCH}" | wc -l)
local_only=$(git log --oneline "origin/${BRANCH}..${BRANCH}" 2>/dev/null | wc -l)
compliance="${REQUIRE_SIGNED_COMMITS:-false}"  # set externally

if [[ "$author_count" -gt 1 ]]; then
  echo "merge  # multi-author: preserve attribution"
elif [[ "$local_only" -eq 0 ]]; then
  echo "merge  # already shared: preserve SHAs for teammates"
elif [[ "$compliance" == "true" ]]; then
  echo "merge-signed  # compliance: signed merge commit required"
else
  echo "rebase  # single-author, local-only, no compliance constraint"
fi

Verification:

chmod +x pick-strategy.sh
./pick-strategy.sh feat/my-feature
# Expected: one of merge / merge-signed / rebase

Step 3 — Execute the Chosen Strategy Safely Jump to heading

Intent: Run the integration operation with the flags that match the decision above.

Merge (--no-ff) Jump to heading

The --no-ff flag forces an explicit merge commit even when a fast-forward is possible. This preserves the branch topology and creates an unambiguous integration record.

# Ensure the local mainline is current
git fetch origin
git switch main
git merge --ff-only origin/main   # fast-forward main to remote, fail if not possible

# Integrate the feature branch
git merge --no-ff feat/my-feature -m "Merge feat/my-feature: add payment gateway"

# Push the result
git push origin main

Verification:

git log --oneline --graph main | head -10
# You should see a merge commit with two parent pointers (the asterisk + backslash shape)

Merge with signed commit (-S) Jump to heading

# Configure signing key once (SSH key shown; GPG key also supported)
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub

# Signed merge
git merge --no-ff -S feat/my-feature -m "Merge feat/my-feature: add payment gateway"

# Verify the signature on the resulting merge commit
git log --show-signature -1

Verification:

git log --show-signature -1 | grep -E "Good|Valid"
# Expected: "Good signature" or "Valid signature" line

SAFETY WARNING: Never use git push --force on main or any protected branch. If the push is rejected, resolve the divergence with git pull --rebase on your personal branch — never force-push shared mainlines. Doing so rewrites shared history, invalidates other engineers’ local refs, and breaks artifact traceability in downstream pipelines.

Rebase (single-author, local-only branches) Jump to heading

Interactive rebase gives you fine-grained control over the commit sequence before integration.

# Update main, then rebase the feature branch onto it
git fetch origin
git switch feat/my-feature
git rebase origin/main

# Resolve any conflicts, then continue
# git rebase --continue   (after staging resolved files)

# Push the rewritten branch — use --force-with-lease, never bare --force
git push --force-with-lease origin feat/my-feature

Verification:

git log --oneline origin/main..feat/my-feature
# Confirms the branch contains only your commits, rebased onto the latest main

SAFETY WARNING: git push --force bypasses the remote-state check. Always use git push --force-with-lease instead: it rejects the push if another engineer pushed to the remote branch after your last fetch, preventing silent data loss.

Step 4 — Enforce the Strategy via Branch Protection Jump to heading

Intent: Make the decision automatic and auditable rather than relying on individual discipline.

GitHub — require merge strategy via repository settings Jump to heading

# GitHub CLI: enforce squash or rebase on pull requests
gh api repos/{owner}/{repo} \
  --method PATCH \
  --field allow_merge_commit=true \
  --field allow_squash_merge=true \
  --field allow_rebase_merge=false   # disable rebase-on-merge at platform level

CI policy gate (GitHub Actions) Jump to heading

The workflow below blocks any PR whose branch is not rebased onto the current main, enforcing a linear history policy before merge is permitted:

# .github/workflows/topology-gate.yml
name: History topology gate
on:
  pull_request:
    branches: [main, "release/*"]
jobs:
  check-linear:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Reject non-rebased branches
        run: |
          BASE_SHA=$(git merge-base HEAD origin/main)
          MAIN_SHA=$(git rev-parse origin/main)
          if [[ "$BASE_SHA" != "$MAIN_SHA" ]]; then
            echo "Branch is not rebased onto current main."
            echo "Run: git fetch origin && git rebase origin/main"
            exit 1
          fi
          echo "Branch is current with main — topology check passed."

Verification: Open a PR whose branch is one commit behind main. The check-linear job should fail with the rebase instruction.

Integration with Adjacent Workflows Jump to heading

Feature Branch Isolation Jump to heading

Feature branch isolation defines the lifecycle rules — naming conventions, TTL limits, and permission scoping — that govern a branch before it reaches this decision point. The merge vs rebase choice is the last step in that lifecycle. Branches under the feat/ namespace with a single author and short TTL are strong candidates for rebase; hotfix/ branches touching shared infrastructure almost always warrant a signed merge commit.

Trunk-Based Development Jump to heading

In trunk-based development setup, branches live less than 24 hours and belong to a single engineer. This matches the rebase conditions almost automatically. The merge vs rebase decision simplifies to: rebase short-lived branches before opening the PR; the merge queue handles the final merge commit onto main.

CI/CD Pipeline Trigger Mapping Jump to heading

Rebase operations change commit SHAs and invalidate pipeline caches pinned to the old SHAs. After a rebase and force-push, configure your CI/CD pipeline trigger mapping to invalidate artifact caches keyed on the branch’s previous SHA. Path-aware triggers help re-run only the affected jobs rather than the full pipeline.

Troubleshooting Jump to heading

SymptomLikely causeFix
git push --force-with-lease rejected even though you just fetchedAnother engineer pushed to the branch between your fetch and pushFetch again (git fetch origin), inspect their commits, rebase on top, then retry
Merge commit missing from audit loggit merge was run with --ff (fast-forward) and created no merge commitRe-merge with --no-ff to force a merge commit, or check platform squash-merge settings
GPG signature missing after rebaseRebase rewrites SHAs, invalidating old signaturesSet commit.gpgSign=true globally or pass --gpg-sign to git rebase
CI artifact lookup fails after rebasePipeline cached artifact by pre-rebase SHAClear the SHA-keyed cache and re-run the pipeline on the new tip SHA
Teammate reports diverged branch after your rebaseThey fetched before your force-push; their local branch still tracks old SHAsThey should run git fetch origin then git reset --hard origin/<branch> after confirming they have no local-only commits
git bisect skips a merge commit--no-ff merge commits have two parents; bisect can traverse bothRun git bisect start --first-parent to restrict traversal to the mainline path

Frequently Asked Questions Jump to heading

Does rebase or merge produce a cleaner Git log? Jump to heading

Rebase produces a linear log that is easier to read with git log --oneline. Merge preserves the parallel-development context with explicit join commits, which matters when you need to reconstruct exactly when parallel work converged and who was responsible for each side. “Cleaner” depends on what question you are trying to answer with the history.

Can I use --force-with-lease after rebasing a shared branch? Jump to heading

Yes — --force-with-lease is always safer than bare --force: it verifies the remote ref matches your last fetch before overwriting. But the underlying coordination issue remains: teammates who fetched the branch before your rebase must run git fetch and rebase their own work on the new SHAs. Communicate before force-pushing any branch that others have checked out.

How do merge queues affect the merge vs rebase choice? Jump to heading

Most merge queue implementations (GitHub Merge Queue, GitLab merge trains) rebase pull requests onto the latest mainline before running CI, then merge with --no-ff. This means you get linear speculative history during validation and an explicit merge commit on mainline — combining both strategies automatically. When using a merge queue, you typically rebase locally to reduce conflicts, and the queue handles the final merge commit.

When should I squash instead of merge or rebase? Jump to heading

Squash is appropriate when the individual commits on a feature branch are noisy WIP snapshots and the branch represents a single logical unit of work. The result is one clean commit on mainline, preserving readability without rewriting individual commit SHAs across the branch. For the full trade-off analysis, see the squash and fixup strategies guidance.

Does rebasing break GPG-signed commits? Jump to heading

Yes. Rebase rewrites commit SHAs, which invalidates any existing GPG or SSH signatures. Pass --gpg-sign to the git rebase command, or set commit.gpgSign=true globally, to re-sign each replayed commit. Verify the result with git log --show-signature.