Configuring rebase-merge as the default in GitHub Jump to heading

A linear main — no merge bubbles, every commit reachable in a single first-parent walk — is the payoff of picking rebase over merge for integration. But the choice is not self-enforcing: GitHub offers three merge buttons on every pull request, and a single contributor clicking “Create a merge commit” reintroduces the topology you were trying to eliminate. The fix is to make rebase-merge the only method the platform will accept, so the policy holds without relying on discipline. This page is a focused recipe within the Merge vs Rebase Decision Matrix, which weighs the trade-offs of each integration strategy; here we assume the decision is made and cover the mechanics of locking it in.

When to use this approach Jump to heading

Apply this recipe when:

  • Your team has standardized on a linear history and you want git log --first-parent main to read as a clean sequence of integrated changes.
  • You practice trunk-based development, where short-lived branches rebase onto main and merge bubbles add noise without value.
  • Contributors clean up their branch history locally — via interactive rebase workflows — so each PR commit is already meaningful and worth preserving individually, rather than squashing.
  • You need the policy to be identical across dozens of repositories and want it declared as code, not clicked into each repo by hand.

This recipe is not the right fit if your team relies on merge commits to record integration points for release tooling, or if you prefer one-commit-per-PR history — in that case squash-merge is the better single method to standardize on. The mechanics below are the same; only which flag you leave enabled changes.

The diagram shows the three enforcement layers that together make rebase-merge the effective default: the PR button restriction, the branch protection rule, and the merge queue method.

Enforcement layers for rebase-merge as the defaultDiagram showing three stacked controls: repository allowed merge methods restricting the PR button to rebase, branch protection requiring linear history, and the merge queue method set to rebase, all converging on a linear main branch.Repo merge methodsallow_rebase_merge = truemerge / squash = falseBranch protectionrequired_linear_historyrejects non-linear pushesMerge queuemerge_method = REBASErebases before checksLinear mainno merge commitsfirst-parent = full history

Step-by-step recipe Jump to heading

Step 1 — Audit the current merge-method configuration Jump to heading

Before changing anything, record what the repository allows today. The three booleans that control the PR merge button are allow_merge_commit, allow_squash_merge, and allow_rebase_merge:

# Read the three merge-method flags for one repository
gh api repos/OWNER/REPO \
  --jq '{merge: .allow_merge_commit, squash: .allow_squash_merge, rebase: .allow_rebase_merge}'

Verify you get a clean baseline snapshot before proceeding:

# Expected shape: {"merge":true,"squash":true,"rebase":true} on a default repo
gh api repos/OWNER/REPO --jq '.allow_rebase_merge'

At least one method must remain enabled at all times — GitHub rejects an update that would disable all three. Enable rebase first, then disable the others, so you never pass through an all-false state.

Step 2 — Disable merge commits and squash in repository settings Jump to heading

For a single repository, the click path is Settings → General → Pull Requests. Uncheck Allow merge commits and Allow squash merging, leave Allow rebase merging checked, and save. The PR page will then show only the “Rebase and merge” button, with no dropdown to switch methods.

While you are on that screen, also enable Automatically delete head branches so merged branches do not accumulate — rebase-merge leaves the source branch behind just like the other methods.

Verify from the terminal that only rebase remains:

gh api repos/OWNER/REPO \
  --jq 'if .allow_rebase_merge and (.allow_merge_commit or .allow_squash_merge | not) then "OK: rebase-only" else "FAIL" end'

Step 3 — Apply the same policy with gh api for reproducibility Jump to heading

Clicking works for one repo; a scripted PATCH is auditable and repeatable. Enable rebase first, then turn off the other two in the same call so the update is atomic:

# PATCH all three flags in one request — never leaves an all-false state
gh api --method PATCH repos/OWNER/REPO \
  -F allow_rebase_merge=true \
  -F allow_merge_commit=false \
  -F allow_squash_merge=false \
  -F delete_branch_on_merge=true

To roll this out across every repository in an org, iterate the repo list:

# Apply rebase-only policy to all repos in an org
gh repo list ORG --limit 1000 --json nameWithOwner --jq '.[].nameWithOwner' \
  | while read -r repo; do
      echo "→ $repo"
      gh api --method PATCH "repos/$repo" \
        -F allow_rebase_merge=true \
        -F allow_merge_commit=false \
        -F allow_squash_merge=false >/dev/null
    done

Verify the batch took effect by re-reading a sample:

# Should print false false true for merge, squash, rebase
gh api repos/OWNER/REPO --jq '[.allow_merge_commit, .allow_squash_merge, .allow_rebase_merge] | @tsv'

Step 4 — Codify the policy in Terraform for the whole org Jump to heading

Imperative scripts drift as repos are created and settings are toggled in the UI. Declaring the policy in Terraform makes it self-healing — every apply reasserts the intended state. The github_repository resource exposes the three merge-method arguments directly:

# main.tf — rebase-only policy as declarative infrastructure
resource "github_repository" "service" {
  name                   = "payments-service"
  visibility             = "private"

  # Restrict the PR merge button to rebase only
  allow_merge_commit     = false
  allow_squash_merge     = false
  allow_rebase_merge     = true
  allow_auto_merge       = true
  delete_branch_on_merge = true
}

Pair it with a branch protection resource that enforces linear history at the ref level (see Step 6 for why this matters):

resource "github_branch_protection" "main" {
  repository_id  = github_repository.service.node_id
  pattern        = "main"
  required_linear_history = true
  # ... required_status_checks, required_pull_request_reviews, etc.
}

Verify the plan converges with no drift after an apply:

# A second plan immediately after apply must report no changes
terraform plan -detailed-exitcode   # exit code 0 == no drift

Step 5 — Align local pull.rebase so contributors stay linear Jump to heading

Server-side policy governs the merge button, but contributors still integrate main into their feature branches locally. If they use the default merge-based git pull, they create merge commits on their own branch — which rebase-merge then has to replay awkwardly. Standardize local pulls to rebase:

# Rebase local work on top of the fetched upstream instead of merging
git config --global pull.rebase true

# Keep a merged-in branch's commits linear when rebasing over already-merged work
git config --global rebase.autoStash true

Ship this as a repository-scoped default so every clone inherits it, rather than depending on each developer’s global config:

# Committed helper: scripts/setup-git.sh, run once after clone
git config pull.rebase true
git config branch.autoSetupRebase always

Verify the effective setting resolves to rebase:

# Should print "true"
git config --get pull.rebase

Step 6 — Reconcile with required linear history and the merge queue Jump to heading

Disabling the merge-commit button does not stop a user with push access from creating a merge commit locally and pushing it straight to main. The button restriction only governs the PR UI. The enforcement layer is the branch protection rule’s Require linear history, which rejects any push that would add a commit with more than one parent:

# Enable required linear history on main via the branch protection API
gh api --method PUT repos/OWNER/REPO/branches/main/protection \
  --input - <<'JSON'
{
  "required_linear_history": true,
  "required_status_checks": null,
  "enforce_admins": true,
  "required_pull_request_reviews": null,
  "restrictions": null
}
JSON

If the repository uses GitHub’s merge queue, note that the queue carries its own merge_method, independent of the allowed PR methods. A queue left at the default MERGE will produce merge commits even after you have restricted the PR button to rebase. Set the queue method to REBASE so queued PRs are rebased onto the latest base tip before the required checks run:

# Set the merge queue method to rebase (GraphQL — queue config is not in the REST branch API)
gh api graphql -f query='
  mutation {
    updateMergeQueue(input: {
      repositoryId: "REPO_NODE_ID",
      branch: "main",
      mergeMethod: REBASE
    }) { mergeQueue { mergeMethod } }
  }'

Verify the required-linear-history flag is active:

# Expected: true
gh api repos/OWNER/REPO/branches/main/protection --jq '.required_linear_history.enabled'

SAFETY WARNING: Rebase-merge rewrites every PR commit onto the base tip, producing new SHAs — the original PR commits become orphaned objects. Any tag, deployment record, or external system pinned to a pre-merge commit SHA will dangle after the merge. Before enforcing this, audit for SHA references; if a downstream consumer relies on the merged SHA, publish the post-merge SHA from the base branch (git rev-parse main) rather than the branch commit, and recover an orphaned commit with git reflog or git fsck --lost-found within the reflog expiry window.

Validation checklist Jump to heading

Before declaring the policy enforced, confirm each item:

Frequently asked questions Jump to heading

Does rebase-merge preserve the exact commit SHAs from the pull request? Jump to heading

No. GitHub replays each PR commit onto the current tip of the base branch, which produces new commit objects with new SHAs — the parent pointer and commit timestamp change, so the hash changes. The tree contents, authored diff, and author/date metadata are preserved, but the original PR commit SHAs become orphaned. Anything that pinned those SHAs (tags, release manifests, deployment logs) must be updated to reference the post-merge commits on the base branch.

Can I require rebase-merge without also enabling required linear history? Jump to heading

You can, but it leaves a hole. Disabling the merge-commit method only removes the option from the pull request button; a user with direct push access to the base branch can still push a locally created merge commit and bypass the UI entirely. Turning on Require linear history in the branch protection rule is the actual enforcement — it rejects any ref update that introduces a multi-parent commit. Treat the two settings as a pair, not alternatives.

How does rebase-merge interact with GitHub’s merge queue? Jump to heading

The merge queue has its own merge_method setting that is independent of the repository’s allowed PR merge methods. If you restrict the PR button to rebase but leave the queue at its default MERGE, the queue will still create merge commits when it integrates entries. Set the queue’s method to REBASE so each queued PR is rebased onto the latest base tip before the required status checks run, keeping the queue’s output consistent with your linear-history policy.


  • Merge vs Rebase Decision Matrix — the parent page weighing when to prefer rebase, merge, or squash for branch integration, and the topology consequences of each.
  • Trunk-Based Development Setup — the branching model where short-lived branches rebase onto a linear main, the workflow this rebase-only policy is built to support.
  • Interactive Rebase Workflows — clean up branch history locally so each preserved PR commit is meaningful before rebase-merge replays it onto the base branch.