Approvals, CODEOWNERS, Protected Branches and Tags, Push Rules, and Merge Governance: Concepts, Architecture, and Mental Model
Build a beginner-first model of GitLab merge governance by separating reviewer assignment, approvals, CODEOWNERS metadata, protected refs, push-time validation, merge checks, and exception authority.
Learning objectives
- Explain why reviewer assignment, approval, ownership metadata, protected refs, push-time validation, and merge checks are separate controls.
- Distinguish an optional approval on Free from a required approval rule on Premium/Ultimate.
- Explain how CODEOWNERS path ownership becomes enforceable only when combined with supported approval/protection settings.
- Reason about protected branches and tags as GitLab authorization policy around ordinary Git refs.
- Identify bypass and exception paths that can defeat separation of duties even when the UI appears strongly governed.
1. Why “someone reviewed it” is not an enforceable policy
Chapter 07 showed how people collaborate around a merge request.
That workflow can be excellent and still be optional. A
reviewer can be assigned but never respond. An approval can be
recorded but not required. A CODEOWNERS file can
describe responsibility without producing an enforceable gate. A
protected branch can block direct pushes while still allowing a role
with broad push-and-merge authority to bypass the merge-request path
entirely.
Merge governance turns expectations into controls with explicit enforcement points. The operator must be able to answer four questions: who owns this path, who must approve, who can change the ref, and who can override the rule? If any answer is vague, the governance model is incomplete.
2. Mental model: six control planes around one proposed change
flowchart LR
SRC[Source SHA] --> MR[Merge request]
OWN[CODEOWNERS metadata] --> MR
REV[Reviewer / approvals] --> GATE{Merge checks}
MR --> GATE
PIPE[Pipeline / discussions] --> GATE
GATE -->|permitted merge| BR[Protected target branch]
DEV[Direct git push] -->|branch protection| BR
TAGGER[Tag creation] -->|protected tag rule| TAG[Protected tag]
PUSH[Incoming push] -->|push rules, paid| REPO[Repository]
EXC[Maintainer / Owner / policy exception] -.high-impact authority.-> BR
EXC -.can alter governance.-> GATE
The diagram contains different enforcement points. Approval rules act on the merge request. Protected branch rules act on ref updates and merge permission. Protected tags govern tag creation/deletion. Push rules inspect incoming pushes before GitLab accepts them. CODEOWNERS supplies ownership metadata used by supported approval/protection features. Exception authority sits outside the normal path and therefore needs its own governance.
3. Reviewer, approver, approval rule: three different ideas
A reviewer is someone asked to inspect an MR. An approval is a recorded decision by an eligible user. An approval rule says how many approvals are required and which users or groups are eligible. These are not synonyms.
| Concept | Free path | Premium/Ultimate path | What it proves |
|---|---|---|---|
| Reviewer assignment | Available | Available | Who was asked to review; not proof of approval. |
| Recorded approval | Optional; does not block merge by itself | Can satisfy an approval rule | A named eligible user approved a specific MR state. |
| Required approval rule | Not an enforceable Free gate | Supported | Merge cannot complete until rule requirements are satisfied. |
| Approval settings | Basic behavior only | Can prevent author/committer approval, override, and configure approval removal | How approvals behave as the MR changes. |
A production review policy should never infer enforcement from the presence of a green approval icon. Inspect the rule that made the approval necessary and the authority that can edit that rule.
4. CODEOWNERS: path ownership metadata, not a magic firewall
A CODEOWNERS file maps repository paths to responsible
users or groups. GitLab checks one recognized file location and uses
that ownership information in Code Owners features. In the current
product, Code Owners is Premium/Ultimate.
# Synthetic ownership fixture — identities are intentionally non-real.
* @platform-lab/maintainers
/docs/ @platform-lab/docs
/.gitlab-ci.yml @platform-lab/platform
/infrastructure/** @platform-lab/platform
The file itself is version-controlled text. Enforcement requires GitLab features around it. To require Code Owner approval for a protected target branch, the project must be on a supporting tier, the branch must be protected, and Code Owner approval must be enabled. A file that merely exists in the repository does not prove any approval gate exists.
5. Protected branches and protected tags are policies around refs
Git still sees a branch such as refs/heads/main and a
tag such as refs/tags/v1.2.0. GitLab adds authorization
policy around updates to those refs. Protection is therefore hosted
policy, not a special Git object type.
For a protected branch, two fields are especially easy to confuse: Allowed to merge governs merging through merge requests, while Allowed to push and merge grants direct push capability and also implies merge capability. Current GitLab documentation warns that leaving push permission merely unconfigured is not the same as explicitly selecting No one when the goal is to force merge requests.
Protected tags similarly restrict who may create matching tags and
protect matching tags from accidental update/deletion. A wildcard
such as release-* can guard an entire release naming
family.
6. Push rules validate incoming changes before acceptance
Push rules are GitLab-managed pre-receive validations. They can enforce patterns for branch names or commit messages, reject unsigned commits, validate user identity, or restrict certain file behavior. Current GitLab documents push rules as Premium/Ultimate.
Push rules differ from branch protection. Branch protection asks “is this actor allowed to update this protected ref?” A push rule asks “does this incoming update satisfy repository validation rules?” A push can pass one control and fail the other.
| Control | Primary question | Enforcement point |
|---|---|---|
| Protected branch | May this actor update/merge this branch? | Ref authorization |
| Protected tag | May this actor create/delete this tag? | Tag authorization |
| Push rule | Does this incoming push satisfy repository validation? | Pre-receive validation |
| Approval rule | Has the MR received required eligible approvals? | Merge-request gate |
| CODEOWNERS | Who owns these changed paths? | Ownership metadata feeding approval/protection |
7. Composition matters more than any single checkbox
Controls overlap. If two branch-protection patterns match the same
branch, current GitLab protection-rule behavior can apply the
most permissive matching rule for push/merge
permissions, while Code Owner approval uses the most restrictive
applicable rule. An exact branch pattern does not automatically
override a wildcard. That means “we added a stricter rule for
main” is not enough evidence—operators must inspect
every matching rule.
Another important bypass path is direct push authority. A user allowed to push and merge to a protected branch can integrate changes without using a merge request, so MR approval rules and Code Owner requirements may never become the enforcement point. Separation of duties requires controlling that authority, not only configuring approvals.
8. Read-only inspection before governance changes
Begin with evidence. The following calls do not modify the project and avoid printing authentication material. Use the project ID rather than embedding a credential in a URL.
REPO="GROUP/governance-sandbox"
PROJECT_ID="123456" # disposable project ID
# Confirm authenticated host/account without printing the credential.
glab auth status
# Project/default-branch context.
glab api "projects/$PROJECT_ID" --jq '{id,path_with_namespace,visibility,default_branch}'
# Current protected branches and tags. Empty arrays are valid observations.
glab api "projects/$PROJECT_ID/protected_branches" --paginate --jq '.[] | {name,push_access_levels,merge_access_levels,allow_force_push}'
glab api "projects/$PROJECT_ID/protected_tags" --paginate --jq '.[] | {name,create_access_levels}'
On Free, do not interpret the absence of approval-rule or push-rule controls as a permission error until you check the tier. Conversely, on Premium/Ultimate, a missing control can still be caused by role, inherited policy, administrator configuration, or a moved UI.
9. DevOps connection: governance is a tested system
Reliable delivery depends on the composition of ownership, review, ref protection, CI evidence, and emergency authority. A policy document is useful, but the production control is the actual behavior of a push, merge, tag creation, and exception attempt.
The safe operating pattern is therefore: inspect → predict → change one disposable control → attempt an allowed and denied operation → preserve the rejection → restore the original state → verify restoration. The next lesson performs that sequence on Free-compatible branch and tag protections.
Knowledge check
A merge request has a reviewer and one approval on GitLab Free. Is merge necessarily blocked until that approval exists?
No. Free approvals are optional. Required approval rules are Premium/Ultimate.
Why does a CODEOWNERS file not by itself prove enforceable ownership?
It is ownership metadata. Enforced Code Owner approval requires supported tier/configuration plus a protected target branch and the relevant approval setting.
Why can Allowed to push and merge undermine approval governance?
A user with direct protected-branch push authority can update the branch without traversing the merge-request approval gate.
Two protection patterns match the same branch. Which assumption is unsafe?
Assuming the exact-name rule automatically wins. GitLab documents most-permissive behavior for overlapping push/merge rules, so inspect every matching rule.
What is the conceptual difference between a protected branch and a push rule?
Branch protection is ref authorization; a push rule validates properties of the incoming push before acceptance.
Summary
Merge governance is a composition of independent controls: reviewers and approvals, paid required approval rules, paid Code Owners metadata/enforcement, Free protected branches and tags, paid push-time validation, merge checks, and exception authority. Effective governance is proven by behavior and evidence, not by the presence of configuration screens.
Official references
- GitLab Docs — Merge request approvals
- GitLab Docs — Merge request approval rules
- GitLab Docs — Merge request approval settings
- GitLab Docs — Code Owners
- GitLab Docs — Branch rules
- GitLab Docs — Protected branches
- GitLab Docs — Protection rules and permissions
- GitLab Docs — Protected tags
- GitLab Docs — Push rules
- GitLab Docs — Protect your repository
- GitLab Docs — Merge requests
- GitLab Docs — Protected branches API
- GitLab Docs — Protected tags API
- GitLab Docs — Project push rules API
- GitLab Docs — REST API pagination
- GitLab Docs — glab api
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.