Chapter 08Lesson 05~245 minutes

Checkpoint Lab — Approvals, CODEOWNERS, Protected Branches and Tags, Push Rules, and Merge Governance

Design, test, verify, and remove a disposable merge-governance policy across branches and tags, with Free enforcement plus realistic Premium/Ultimate approval and push-rule fixtures.

CheckpointBranch + tag policyPredictionsEvidenceCleanupChapter 09 bridge

Learning objectives

  • Design a small merge-governance policy before touching GitLab and predict actor/ref outcomes.
  • Implement Free-compatible branch and tag enforcement on disposable refs and capture at least one real denial.
  • Prove policy behavior through independent Git and GitLab/API observations.
  • Evaluate Premium/Ultimate approval, CODEOWNERS, and push-rule fixtures without claiming live Free enforcement.
  • Restore the project to its pre-lab governance state and bridge to merge methods and integration strategy.
Availability baseline (verified 2026-08-21). GitLab Free supports optional merge-request approvals and project-level protected branches and protected tags on GitLab.com, Self-Managed, and Dedicated. Required approval rules, Code Owners/CODEOWNERS, Code Owner approval enforcement, push rules, and group-level protected-branch governance are Premium/Ultimate. The current project UI is moving protected-branch management into Settings → Repository → Branch rules; older Self-Managed releases can expose different labels. Mandatory work in this chapter therefore uses Free protected branch/tag controls, while paid governance is taught with documentation and synthetic fixtures.

1. Checkpoint mission and preflight

Your task is to govern a tiny synthetic release flow without modifying the project’s real default-branch policy. You will create a disposable target branch, protect it against direct push, protect a synthetic release-tag namespace against creation, route one change through an MR, record the rejection and successful path, and then remove every lab rule/ref.

Required: GitLab Free-compatible project, Maintainer/Owner role in that disposable project, Git, and glab authentication from Chapter 02. Not required: Premium/Ultimate, second human account, CI compute, custom runner, secret, production registry, cloud, Kubernetes, or administrator access.

2. Write the policy before implementing it

Before clicking a setting, write this policy record and fill in the actor you will use:

checkpoint_policy:
  branch: governance-checkpoint
  allowed_merge: Maintainer-or-Owner-lab-identity
  allowed_direct_push: none
  force_push: disabled
  tag_pattern: checkpoint-v*
  allowed_tag_creation: none
  required_approvals_live: false   # Free path
  codeowners_live: false           # paid feature fixture only
  push_rules_live: false           # paid feature fixture only
  exception: remove only lab rule after evidence capture

Now predict outcomes:

Action Prediction before test Control responsible
Direct push to governance-checkpoint after protection Denied Protected branch: push = No one
Push normal source branch Allowed Source branch is not the protected target
Merge MR into protected target as authorized lab Maintainer/Owner Allowed if other merge checks permit Allowed to merge
Create checkpoint-v0.0.1 while wildcard allows No one Denied Protected tag rule
Merge without required approval on Free Not blocked by approval count alone Free approvals are optional

3. Capture before-state and create disposable refs

REPO="GROUP/governance-sandbox"
PROJECT_ID="123456"
BASE="main"  # verify from project API
TARGET="governance-checkpoint"
SOURCE="governance-checkpoint-change"
TAG="checkpoint-v0.0.1"
mkdir -p ch08-checkpoint-evidence

glab api "projects/$PROJECT_ID"   --jq '{path_with_namespace,default_branch,visibility}'   | tee ch08-checkpoint-evidence/project.json

glab api "projects/$PROJECT_ID/protected_branches" --paginate   | tee ch08-checkpoint-evidence/branches-before.json
glab api "projects/$PROJECT_ID/protected_tags" --paginate   | tee ch08-checkpoint-evidence/tags-before.json

git fetch origin --prune --tags
git switch "$BASE"
git pull --ff-only origin "$BASE"
git switch -c "$TARGET"
printf 'checkpoint base
' > ch08-checkpoint.txt
git add ch08-checkpoint.txt
git commit -m "ch08: checkpoint governance target"
git push -u origin "$TARGET"
BASELINE_TARGET_SHA="$(git rev-parse HEAD)"

4. Implement the two Free enforcement rules

Use the current UI on the disposable project:

  1. Branch rule: Settings → Repository → Branch rules → rule for governance-checkpoint. Allow merge to the lab role; set Allowed to push and merge = No one; leave force push disabled.
  2. Protected tag: Settings → Repository → Protected tags → wildcard checkpoint-v*; set Allowed to create = No one.

Then export both policies:

glab api "projects/$PROJECT_ID/protected_branches/$TARGET"   | tee ch08-checkpoint-evidence/branch-rule.json
glab api "projects/$PROJECT_ID/protected_tags" --paginate   | tee ch08-checkpoint-evidence/tag-rules.json

5. Test one denied path and one allowed path

Create the source revision, then deliberately target the protected branch directly. Preserve the failure before using the allowed source-branch path.

git switch -c "$SOURCE" "origin/$TARGET"
printf 'checkpoint governed change
' >> ch08-checkpoint.txt
git add ch08-checkpoint.txt
git commit -m "ch08: checkpoint proposed change"
PROPOSED_SHA="$(git rev-parse HEAD)"

# Prediction 1: DENIED.
git push origin "HEAD:$TARGET" 2>&1   | tee ch08-checkpoint-evidence/direct-push-denied.txt

# Independent proof: remote target must still equal BASELINE_TARGET_SHA.
git ls-remote origin "refs/heads/$TARGET"   | tee ch08-checkpoint-evidence/target-after-denial.txt

# Prediction 2: ALLOWED.
git push -u origin "$SOURCE"

6. Route the same source revision through the MR path

Create an MR whose target is the disposable protected branch. Inspect source and target before merging.

glab mr create -R "$REPO"   --source-branch "$SOURCE"   --target-branch "$TARGET"   --title "ch08 checkpoint: governed merge"   --description "Disposable governance checkpoint. No production resources."   --yes

MR_IID="REPLACE_WITH_IID"
glab api "projects/$PROJECT_ID/merge_requests/$MR_IID"   --jq '{iid,state,source_branch,target_branch,sha,diff_refs,detailed_merge_status}'   | tee ch08-checkpoint-evidence/mr-before.json

Before selecting Merge, answer: Is the source SHA equal to PROPOSED_SHA? Is the target exactly governance-checkpoint? Is any approval requirement shown, and if not, are you correctly treating Free approvals as optional rather than “passed governance”?

Merge the synthetic MR only after those checks. Then:

git fetch origin "$TARGET"
AFTER_MERGE_SHA="$(git rev-parse "origin/$TARGET")"
printf 'baseline=%s
after_merge=%s
proposed=%s
'   "$BASELINE_TARGET_SHA" "$AFTER_MERGE_SHA" "$PROPOSED_SHA"   | tee ch08-checkpoint-evidence/ref-identities.txt

glab api "projects/$PROJECT_ID/merge_requests/$MR_IID"   --jq '{iid,state,merged_at,merge_commit_sha,squash_commit_sha,sha}'   | tee ch08-checkpoint-evidence/mr-after.json

Do not assume the post-merge target SHA must equal the source SHA; the exact result depends on the project’s merge method, which Chapter 09 studies next. The checkpoint only requires that the protected target changed through the permitted MR path, not through the denied direct push.

7. Test protected-tag behavior

git tag "$TAG" "origin/$TARGET"

# Prediction 3: DENIED by protected-tag wildcard.
git push origin "refs/tags/$TAG" 2>&1   | tee ch08-checkpoint-evidence/tag-create-denied.txt

git ls-remote --tags origin "refs/tags/$TAG"   | tee ch08-checkpoint-evidence/tag-remote-proof.txt

The empty remote result plus non-zero push is independent evidence that the protected tag was not created. Do not create a real release merely to test tag governance.

9. Failure drill: a rule appears stricter but is ineffective

Add this paper-only conflicting rule to your policy worksheet: wildcard governance-* with Developers + Maintainers allowed to push. Predict the effective behavior of governance-checkpoint if both rules existed.

The safe answer is that the wildcard could make push/merge behavior more permissive because multiple matching protection rules compose according to GitLab’s documented rule behavior. The production response is to eliminate the permissive overlap, not rely on the exact-name rule to override it.

10. Verification, evidence manifest, and cleanup

Before cleanup, verify these invariants:

  • The rejected direct push did not move the protected target.
  • The permitted MR path did move the target.
  • The protected tag was never created remotely.
  • No required approval/CODEOWNERS/push-rule enforcement was falsely claimed on Free.
  • Only chapter-specific disposable refs and rules were created.
python - <<'PY2'
from pathlib import Path
import hashlib, json, datetime
root=Path('ch08-checkpoint-evidence')
files=[]
for p in sorted(root.glob('*')):
    if p.is_file():
        files.append({'file':p.name,'bytes':p.stat().st_size,'sha256':hashlib.sha256(p.read_bytes()).hexdigest()})
out={'captured_at_utc':datetime.datetime.now(datetime.timezone.utc).isoformat(),'files':files}
Path('ch08-checkpoint-manifest.json').write_text(json.dumps(out,indent=2)+'
')
PY2
cat ch08-checkpoint-manifest.json
Cleanup is destructive only to synthetic resources. First remove the checkpoint-v* protected-tag rule and the governance-checkpoint Branch rule in the UI. Verify those specific rules are gone before deleting their refs.
glab api "projects/$PROJECT_ID/protected_branches" --paginate   > ch08-checkpoint-evidence/branches-after-rule-removal.json
glab api "projects/$PROJECT_ID/protected_tags" --paginate   > ch08-checkpoint-evidence/tags-after-rule-removal.json

# Delete only the verified lab branches/tag, without hiding failures.
if git ls-remote --exit-code --heads origin "$SOURCE" >/dev/null 2>&1; then
  git push origin --delete "$SOURCE"
else
  printf 'remote source already absent: %s
' "$SOURCE"
fi
if git ls-remote --exit-code --heads origin "$TARGET" >/dev/null 2>&1; then
  git push origin --delete "$TARGET"
else
  printf 'remote target already absent: %s
' "$TARGET"
fi
if git show-ref --verify --quiet "refs/tags/$TAG"; then
  git tag -d "$TAG"
else
  printf 'local tag already absent: %s
' "$TAG"
fi
git switch "$BASE"
for branch in "$SOURCE" "$TARGET"; do
  if git show-ref --verify --quiet "refs/heads/$branch"; then
    git branch -D "$branch"
  else
    printf 'local branch already absent: %s
' "$branch"
  fi
done

11. What Chapter 08 adds to a production GitLab operating model

You now have a governance model that distinguishes collaboration from enforcement. Normal changes can be forced through merge requests by branch protection; release tag names can be reserved; paid approval/CODEOWNERS/push validation can be layered when available; exception authority is treated as privileged configuration; and every control is validated through an allowed/denied behavior pair.

Chapter 09 picks up exactly where this leaves off. Once a change is authorized to merge, the team still must decide how GitLab integrates it—merge commit, semi-linear history, fast-forward, merge trains, conflict handling, and branch cleanup.

Knowledge check

The direct push was denied and the MR merge succeeded. What did the checkpoint prove?

Why is the post-merge target SHA not assumed to equal the source SHA?

The local protected tag exists but the remote lookup is empty. What does that mean?

What is the correct response if a broad wildcard protection makes the branch more permissive?

What three paid controls were intentionally simulated rather than required?

What is the next governance question after authorization is correct?

Checkpoint summary

You designed policy before implementation, predicted actor/ref outcomes, proved a real direct-push denial and protected-tag denial, used the allowed MR path, kept Premium/Ultimate controls as explicit fixtures, hashed the evidence packet, and restored the disposable rules/refs. The chapter closes with governance separated cleanly from the merge mechanics that follow.

Official references

Chapter 09

Merge methods, merge trains, mergeability checks, conflict resolution, and integration strategies

Chapter 09 explains what GitLab does to commit history after governance says a change is allowed to integrate, and how teams choose reliable integration strategies.

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
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.