Chapter 09Lesson 05~210 minutes

Checkpoint Lab — Merge Methods, Auto-Merge, Merge Queue, Conflict Handling, and Branch Cleanup

Run a production-style integration checkpoint: compare two merge histories, install a required check, demonstrate blocked auto-merge and recovery, resolve a controlled conflict, inspect final graph evidence, and write a merge policy for a busy protected branch.

CheckpointProtected branchIntegration gatePolicy

Learning objectives

  • Create a disposable public repository and two PRs that deliberately demonstrate different merge policies, then prove final graph differences.
  • Install a least-privilege required GitHub Actions check on the default branch and inspect the protection via versioned API evidence.
  • Use auto-merge to demonstrate blocked integration, repair the exact failing condition, and verify the resulting merged SHA without bypass.
  • Engineer a controlled competing-line conflict, resolve it locally, and prove the new PR head/check state before integration.
  • Simulate merge-queue ordering and merge_group evidence for a busy branch, with an optional live organization extension.
  • Write a merge-policy handoff covering history, gates, queue, conflicts, branch cleanup, rollback/debugging, and deployment separation.
Availability: Mandatory checkpoint: GitHub.com, GitHub Free, one disposable public personal repository, Git, GitHub CLI, and a tiny GitHub Actions workflow. Protected branches and auto-merge are live. Merge queue is a documented simulation because the repository is personally owned; an organization-owned public disposable repository is an optional no-paid live extension if the learner already has one.
Shell portability: multi-line examples with trailing \, command substitution such as $(...), here-documents, printf, and rm -rf are Git Bash/Bash/zsh syntax. PowerShell users can put the same Git/gh arguments on one line, use the backtick for continuation, and use Set-Content/Add-Content/Remove-Item -Recurse -Force for file operations. GitHub merge and policy semantics are shell-independent.

1. Checkpoint mission and evidence package

Build c09-merge-checkpoint. Capture: repository merge settings; baseline/default-branch SHA; PR head/base OIDs; merge-method choice; merged commit parents; ancestor tests; required-check protection response; blocked and recovered auto-merge state; conflict before/after head OID; remote branch cleanup evidence; queue simulation; and a written merge policy. No admin bypass, force push, production repository, secret, runner registration, package deletion, transfer, or repository deletion is required.

2. Preflight and collision gate

gh auth status --active --hostname github.com
OWNER=$(gh api -H "X-GitHub-Api-Version: 2026-03-10" user --jq .login)
REPO="c09-merge-checkpoint"

gh repo view "$OWNER/$REPO" >/dev/null 2>&1 && {
  echo "STOP: repository already exists" >&2
  exit 2
}

3. Create repository, baseline, merge settings, and queue-aware check

gh repo create "$OWNER/$REPO" --public --add-readme --clone
cd "$REPO"
DEFAULT_BRANCH=$(git branch --show-current)
printf 'mode=baseline
' > app.conf
git add app.conf
git commit -m "chore: checkpoint baseline"
git push
BASE0=$(git rev-parse HEAD)

gh repo edit "$OWNER/$REPO"   --enable-merge-commit   --enable-squash-merge   --enable-rebase-merge   --enable-auto-merge   --delete-branch-on-merge=false

mkdir -p .github/workflows
cat > .github/workflows/integration-gate.yml <<'EOF'
name: integration-gate
on:
  pull_request:
    types: [opened, synchronize, reopened, edited]
  merge_group:
permissions:
  contents: read
jobs:
  integration-gate:
    runs-on: ubuntu-latest
    steps:
      - name: Controlled block
        if: github.event_name == 'pull_request' && contains(github.event.pull_request.title, '[block]')
        run: exit 1
      - name: Pass gate
        if: ${{ !(github.event_name == 'pull_request' && contains(github.event.pull_request.title, '[block]')) }}
        run: echo "integration gate passed"
EOF

git add .github/workflows/integration-gate.yml
git commit -m "ci: add checkpoint integration gate"
git push
Actions security: the workflow uses explicit contents: read, no checkout, no secrets, no context dump, and does not interpolate the untrusted PR title into a shell command. The merge_group trigger makes the required check queue-ready.

4. Verify merge configuration before protection

gh repo view "$OWNER/$REPO" --json   nameWithOwner,defaultBranchRef,mergeCommitAllowed,squashMergeAllowed,rebaseMergeAllowed,deleteBranchOnMerge

Prediction 1: enabling merge methods/auto-merge changes hosted repository policy only; BASE0 and existing Git objects do not move. Confirm git rev-parse HEAD is still the expected setup commit after the workflow commit, and distinguish that normal setup commit movement from policy toggles.

5. PR 1 — merge commit: preserve topic commits and add integration point

git switch -c checkpoint/merge "$DEFAULT_BRANCH"
printf 'merge-A
' >> app.conf
git add app.conf && git commit -m "feat: checkpoint merge A"
printf 'merge-B
' >> app.conf
git add app.conf && git commit -m "test: checkpoint merge B"
PR1_TIP=$(git rev-parse HEAD)
git push -u origin checkpoint/merge

PR1_URL=$(gh pr create --base "$DEFAULT_BRANCH" --head checkpoint/merge   --title "checkpoint: preserve commit topology" --body "Merge-commit checkpoint PR.")
PR1=${PR1_URL##*/}
gh pr checks "$PR1" --watch

gh pr view "$PR1" --json baseRefOid,headRefOid,commits,mergeable,statusCheckRollup
gh pr merge "$PR1" --merge --delete-branch

git switch "$DEFAULT_BRANCH" && git pull --ff-only
PR1_MERGE=$(git rev-parse HEAD)
git show --no-patch --pretty='format:%H parents=%P subject=%s' "$PR1_MERGE"
git merge-base --is-ancestor "$PR1_TIP" HEAD && echo "PR1 topic tip preserved in base ancestry"

6. PR 2 — squash: one logical base commit

git switch -c checkpoint/squash "$DEFAULT_BRANCH"
printf 'squash-A
' >> app.conf
git add app.conf && git commit -m "feat: checkpoint squash A"
printf 'squash-B
' >> app.conf
git add app.conf && git commit -m "fix: checkpoint squash B"
PR2_TIP=$(git rev-parse HEAD)
git push -u origin checkpoint/squash

PR2_URL=$(gh pr create --base "$DEFAULT_BRANCH" --head checkpoint/squash   --title "checkpoint: one logical change" --body "Squash checkpoint PR.")
PR2=${PR2_URL##*/}
gh pr checks "$PR2" --watch
gh pr merge "$PR2" --squash --delete-branch

git switch "$DEFAULT_BRANCH" && git pull --ff-only
PR2_SQUASH=$(git rev-parse HEAD)
git show --no-patch --pretty='format:%H parents=%P subject=%s' "$PR2_SQUASH"
if git merge-base --is-ancestor "$PR2_TIP" HEAD; then
  echo "unexpected: original squash tip reachable"
else
  echo "expected: original squash tip is not in base ancestry"
fi

Prediction 2: PR 1 adds a two-parent merge commit and keeps topic commits reachable; PR 2 adds one base commit and does not make the original topic tip an ancestor. The two predictions are verified with different evidence, not by looking only at PR badges.

7. Protect the default branch with the integration gate

Policy mutation: confirm the exact disposable owner/repo/branch before running this request. enforce_admins: true intentionally prevents the repository owner from casually bypassing the checkpoint gate.
printf 'protect=%s/%s:%s
' "$OWNER" "$REPO" "$DEFAULT_BRANCH"
gh api -X PUT   -H "Accept: application/vnd.github+json"   -H "X-GitHub-Api-Version: 2026-03-10"   "repos/$OWNER/$REPO/branches/$DEFAULT_BRANCH/protection"   --input - <<'JSON'
{
  "required_status_checks": {"strict": true, "contexts": ["integration-gate"]},
  "enforce_admins": true,
  "required_pull_request_reviews": null,
  "restrictions": null
}
JSON

gh api -H "X-GitHub-Api-Version: 2026-03-10"   "repos/$OWNER/$REPO/branches/$DEFAULT_BRANCH/protection/required_status_checks"   --jq '{strict,contexts}'

8. Demonstrate blocked auto-merge and safe recovery

git switch -c checkpoint/auto "$DEFAULT_BRANCH"
printf 'auto-gated
' >> app.conf
git add app.conf && git commit -m "test: checkpoint auto merge"
git push -u origin checkpoint/auto

AUTO_URL=$(gh pr create --base "$DEFAULT_BRANCH" --head checkpoint/auto   --title "[block] checkpoint: required gate"   --body "Auto-merge should wait for the deliberately failing required check.")
AUTO_PR=${AUTO_URL##*/}

gh pr checks "$AUTO_PR" || true
gh pr merge "$AUTO_PR" --auto --squash
gh pr view "$AUTO_PR" --json autoMergeRequest,mergeStateStatus,statusCheckRollup,headRefOid

Record the failing check and non-null auto-merge request. Then repair only the controlled cause:

gh pr edit "$AUTO_PR" --title "checkpoint: required gate repaired"
gh pr checks "$AUTO_PR" --watch
gh pr view "$AUTO_PR" --json state,mergedAt,mergeCommit,statusCheckRollup,autoMergeRequest

git switch "$DEFAULT_BRANCH" && git pull --ff-only
AUTO_MERGED=$(git rev-parse HEAD)
printf 'auto_merged_sha=%s
' "$AUTO_MERGED"

Do not claim deployment success. The evidence proves PR integration and required-check success only.

9. Controlled conflict under protection

Create the topic change from the current base, then advance the same line on the base through a separate passing PR. This avoids direct protected-branch pushes.

git switch "$DEFAULT_BRANCH" && git pull --ff-only
git switch -c checkpoint/conflict-topic
printf 'mode=topic
' > app.conf
git add app.conf && git commit -m "feat: topic sets mode"
git push -u origin checkpoint/conflict-topic
CONFLICT_URL=$(gh pr create --base "$DEFAULT_BRANCH" --head checkpoint/conflict-topic   --title "checkpoint: resolve competing line" --body "Conflict target PR.")
CONFLICT_PR=${CONFLICT_URL##*/}
CONFLICT_HEAD1=$(git rev-parse HEAD)

git switch "$DEFAULT_BRANCH" && git pull --ff-only
git switch -c checkpoint/base-change
printf 'mode=base
' > app.conf
git add app.conf && git commit -m "fix: base sets competing mode"
git push -u origin checkpoint/base-change
BASE_URL=$(gh pr create --base "$DEFAULT_BRANCH" --head checkpoint/base-change   --title "checkpoint: advance base" --body "Creates the competing base line.")
BASE_PR=${BASE_URL##*/}
gh pr checks "$BASE_PR" --watch
gh pr merge "$BASE_PR" --squash --delete-branch

# Return to the original topic and observe the conflict.
git switch checkpoint/conflict-topic
git fetch origin "$DEFAULT_BRANCH"
git merge "origin/$DEFAULT_BRANCH" || true
git status

Resolve locally to mode=resolved, commit, and push normally:

printf 'mode=resolved
' > app.conf
git add app.conf
git commit -m "fix: resolve checkpoint merge conflict"
git push
CONFLICT_HEAD2=$(git rev-parse HEAD)
printf 'before=%s after=%s
' "$CONFLICT_HEAD1" "$CONFLICT_HEAD2"

gh pr view "$CONFLICT_PR" --json headRefOid,mergeable,mergeStateStatus,statusCheckRollup
gh pr checks "$CONFLICT_PR" --watch
gh pr merge "$CONFLICT_PR" --squash --delete-branch

The before/after OIDs prove conflict resolution changed the PR head. In a repository with review-freshness requirements, that change may invalidate earlier approval and must be handled according to Chapter 08 policy.

10. Queue simulation for a busy protected branch

Initial target: main@M100
Queued PRs:     #31@H31, #32@H32
Group G31:      main@M100 + #31@H31  -> integration-gate must pass on merge_group SHA G31
If G31 merges:  main becomes M101
Group G32:      main@M101 + #32@H32  -> integration-gate must pass again on new speculative SHA G32
If workflow only triggers pull_request: required integration-gate is missing for G31/G32 -> queue cannot safely advance.

Optional live extension: repeat with a disposable organization-owned public repository and a rule that requires the merge queue. Preserve the queue entry, merge-group SHA/ref, and required check evidence. The mandatory personal-repository checkpoint remains complete without this extension.

11. Verify branch cleanup and archive evidence

git switch "$DEFAULT_BRANCH" && git pull --ff-only

git ls-remote --heads origin checkpoint/merge checkpoint/squash checkpoint/auto checkpoint/conflict-topic
# Heads merged with --delete-branch should not be returned.

git log --graph --decorate --oneline --all --max-count=40

gh pr list --state merged --json number,title,mergedAt,mergeCommit,headRefName,baseRefName

If a branch unexpectedly remains, inspect whether another open PR depends on it or a rule prevented deletion. Do not force-delete until the dependency is understood.

12. Write the busy-branch merge policy

Target: main
History rule: squash ordinary PRs; merge commits only for explicitly documented integration branches
Allowed methods: configure repository to match the policy; do not leave unused methods enabled indefinitely
Required checks: integration-gate and named security/test checks; unique stable check names
Auto-merge: allowed when all encoded requirements pass
Merge queue: required for high-volume organization-owned main when available; CI listens to merge_group
Conflict policy: prefer local resolution; never hide conflict cause with admin bypass; new head requires freshness review
Branch cleanup: auto-delete short-lived heads after merge only after dependency inventory
Bypass: incident-only under separate, auditable governance
Evidence: record final merged SHA/checks/reviews; deployment state verified separately

13. Final verification checklist

  • Two PRs demonstrably produced different histories: merge commit versus squash.
  • Repository merge settings were inspected and all policy mutations were confined to the disposable repository.
  • The integration-gate workflow has explicit least-privilege permissions and includes merge_group.
  • Branch protection requires integration-gate and was verified through the versioned REST API.
  • Auto-merge was observed blocked by the failing required check and later completed only after the condition was repaired.
  • No admin bypass or force push was used.
  • Conflict resolution changed the PR head through an ordinary commit and the new OID/check state was verified.
  • Queue semantics are labeled simulation unless a qualifying organization repository was used.
  • Merged head branches were checked through hosted ref inspection.
  • Merged SHA is not treated as deployment proof.

14. Cleanup / rollback

Archive the disposable repository to preserve the checkpoint evidence while preventing accidental future mutation. If you enabled an optional organization merge queue, remove the disposable branch rule/queue requirement before archiving only if your organization cleanup policy requires it. Permanent deletion is intentionally not mandatory.

cd ..
printf 'archive-check=%s/%s
' "$OWNER" "$REPO"
gh repo archive "$OWNER/$REPO"
rm -rf "$REPO"

15. What Chapter 09 adds to the production GitHub operating model

The operating model now defines not only who reviews a change, but exactly how reviewed commits enter a protected branch, which checks are authoritative at the final integration state, how busy-branch races are controlled, how conflicts change review scope, and when temporary refs can be removed. Merge state is now evidence-driven and distinct from deployment state.

16. Bridge to Chapter 10: protected branches, rulesets, push rules, and bypass governance

Chapter 09 used a minimal required check to make integration behavior observable. Chapter 10 generalizes that into repository governance: protected branches, rulesets, push rules, required checks, bypass lists, layering/conflicts between rules, and how to design enforcement without locking out safe maintenance.

Knowledge check

What independent evidence distinguished the two checkpoint merge policies?

Why was the auto-merge repair valid?

What must a queue-aware required Actions workflow include?

Why does a conflict-resolution commit matter to review policy?

Why is branch cleanup verified with hosted ref inspection?

What is the handoff to Chapter 10?

Next chapter

Protected Branches, Repository Rulesets, Push Rules, Required Checks, and Bypass Governance: Concepts, Architecture, and Mental Model

Authoritative references

 About merge methods on GitHub
 Automatically merging a pull request
 Merging a pull request with a merge queue
 Managing a merge queue
 Events that trigger workflows: merge_group
 Resolving a merge conflict using the command line
 Deleting and restoring branches in a pull request
 About protected branches
 gh pr merge
 gh repo edit
 REST API endpoints for protected branches
 REST API versions

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.