GitLab’s Merge Request (MR) approval rules aren’t just about making sure someone looks at your code; they’re about establishing a distributed, auditable consensus on code quality and correctness before it ever hits main.
Let’s see this in action. Imagine a project with a production branch that requires a rigorous review process.
# .gitlab-ci.yml
stages:
- test
- deploy
lint:
stage: test
script:
- echo "Running linter..."
- pylint my_module.py
review:
stage: test
script:
- echo "No CI job needed for manual review."
when: manual # This job won't run automatically, requires manual trigger
deploy_production:
stage: deploy
script:
- echo "Deploying to production..."
only:
- main # Only deploy from the main branch
when: manual # Requires manual trigger for deployment
Now, let’s configure the MR approval rules within GitLab. Navigate to your project’s Settings > General > Merge request approvals.
Here, you can define rules that trigger based on the target branch. For our main branch, we can set up the following:
- Target branch:
main - Approvals required: 2
- Code owners:
@alice,@bob - Approvers:
@charlie
This setup means that for any MR targeting the main branch:
- Two distinct users must approve the MR.
- At least one of the approvers must be from the defined Code Owners (
@aliceor@bob). - The other approval can come from anyone, including
@charlieor any other project member with sufficient permissions.
The system enforces this before the MR can be merged. If an MR has only one approval, or if the approvals come from users who aren’t Code Owners (and the second approval is also missing), the "Merge" button will be disabled.
The problem this solves is the "hero programmer" or "bus factor" problem: a single person can’t unilaterally decide what goes into critical branches. It also provides a clear audit trail. Every approval is logged, showing who approved what and when. This is invaluable for compliance, debugging regressions, and understanding the evolution of the codebase.
Think about how approvals cascade. If @alice approves, and then @bob approves, the MR is ready to merge. If @charlie approves, but neither @alice nor @bob have approved, the MR still isn’t ready because the Code Owner requirement hasn’t been met. The system doesn’t just count approvals; it checks them against defined criteria.
The flexibility extends to specific files. Using a CODEOWNERS file in the root of your repository (or in a .gitlab/ directory), you can specify which users or groups are responsible for which parts of the codebase.
# CODEOWNERS
* @alice @bob # All files by default
src/api/ @charlie @david # API specific
tests/ @eve # All tests
When an MR includes changes to files listed in CODEOWNERS, those specific owners must approve the changes related to their files, in addition to the overall MR approval rules. So, if @alice approves an MR that only touches src/api/, it doesn’t satisfy the src/api/ Code Owner requirement for @charlie. The system intelligently tracks which parts of the MR have been reviewed by the relevant parties.
A common point of confusion is how the "approvals required" count interacts with "Code Owners." If you set "2 approvals required" and list @alice as a Code Owner, and @alice approves, that counts as one of the two required approvals. You still need one more approval from any valid approver (which could be @alice again, or someone else). The system doesn’t require two separate Code Owner approvals unless you explicitly configure it that way or have multiple Code Owner lines for the same files.
The most surprising aspect is how GitLab handles approval resets. By default, if new commits are pushed to an MR after it has been approved, all approvals are reset. This ensures that reviewers are always looking at the latest version of the code. You can disable this behavior in the project’s MR settings, but it’s generally a good practice to keep it enabled to prevent accidental merges of outdated code.
The next step in mastering MR workflows is understanding how to integrate these approval rules with protected branches and CI/CD pipeline status checks.