Branch protection and rulesets
Intermediate GitHub UI
Why this matters
The rules are why you cannot push to main, why your pull request needs two approvals, and why a merge button is grey. None of that is personal or broken. Reading the rules takes a minute and replaces a lot of guessing, and anyone with Read access can do it.
Two systems, both live
GitHub has protected branches twice over, and a repository can use either or both:
| Branch protection rules | Rulesets | |
|---|---|---|
| Age | The original system | The newer one |
| Applies to | One rule per branch pattern | Several rulesets can apply to one branch |
| Statuses | On or absent | Active or Disabled |
| Bypass | A list of people or teams | A bypass list, optionally "for pull requests only" |
| Visible to | Admins | Anyone with Read |
When both cover the same branch, they are enforced together and the most restrictive version wins. The practical consequence for a contributor is simple: read what applies, and do not assume one page is the whole story.
The rules you will meet
| Rule | What it does to you |
|---|---|
| Require a pull request before merging | You cannot push to the branch; you open a pull request instead |
| Require approvals | A number of reviewers must approve before the merge button works |
| Dismiss stale approvals | Pushing a new commit clears existing approvals, and you need them again |
| Require review from Code Owners | A named team or person must approve, because of the files you touched |
| Require status checks to pass | The named checks must be green (lesson 10.10) |
| Require branches to be up to date | You must merge or rebase the latest main before merging |
| Require conversation resolution | Every review thread must be resolved |
| Block force pushes | git push --force is refused (lesson 6.7) |
| Restrict deletions | The branch cannot be deleted |
| Require signed commits | Commits must be signed (lesson 8.8) |
None of these are obstacles invented for you; each one exists because something went wrong once. "Require branches to be up to date" in particular is what stops a change that passed its checks in isolation from breaking main when combined with someone else's.
CODEOWNERS
A file at .github/CODEOWNERS maps paths to people or teams. When a rule requires code owner review, GitHub reads this file, works out who owns the files you changed, and requests their review automatically.
# Everything, unless a later rule matches
* @northwind-trails/engineering
# Documentation belongs to the writers
/docs/ @northwind-trails/docs
*.md @northwind-trails/docs
# The release notes need both
/CHANGELOG.md @northwind-trails/docs @devThe last matching rule wins, which is the opposite of what most people assume. Being listed here is worth asking for if you are the person who should be reading documentation changes: it converts "please remember to ask Ana" into an automatic review request.
Reading the rules that apply
Anyone with Read access can see active rulesets, so you can answer "what will my pull request need?" before opening it:
- On Settings → Rules → Rulesets, if you can open Settings.
- On the branch's page, where a message names the rules that apply.
- On the pull request itself, where the merge box lists every unmet requirement in order.
The third is the one you will use daily, and it is a checklist rather than an error: it says what is missing, and it updates itself as each requirement is met.
How to do it
The rules announce themselves when you push:
remote: error: GH006: Protected branch update failed for refs/heads/main.
remote: error: Changes must be made through a pull request.That is not a failure of your Git; it is the rule working. Move the commits to a branch and open a pull request (lesson 12.7 if you committed to main by accident).
The push fails with the same message in a notification. VS Code cannot show you the rules; read them on the pull request page.
The Git tool window shows the rejection text. IntelliJ's Pull Requests window shows the required checks and reviews once the pull request exists.
GitLab does the same with protected branches plus merge request approval rules and push rules, and CODEOWNERS works the same way. See lesson 9.5.
- Settings → Rules → Rulesets → New ruleset → New branch ruleset creates one; Admin only.
- Give it a name, set Enforcement status to Active, and choose the target branches.
- Tick the rules: require a pull request, approvals, status checks, conversation resolution, block force pushes.
- Add a bypass list only if a specific automation genuinely needs it.
- As a contributor, read the requirements in the merge box on your pull request; they update as each is satisfied.
Common mistakes
- Reading only one of the two systems. Rulesets and branch protection are enforced together.
- Assuming the greyed-out merge button is a bug. The box above it says exactly what is missing.
- Pushing after approval in a repository that dismisses stale approvals, then wondering where the approvals went.
- Not knowing about CODEOWNERS, then chasing reviewers who were never required.
- Expecting force push to work on a protected branch. It is blocked, deliberately.
Try it yourself
Goal: protect your own branch and feel the rule from both sides.
- On your practice repository, create a ruleset targeting
main, Active, requiring a pull request and one approving review. - From your clone, commit something on
mainand try to push it. Read the rejection. - Move the commit to a branch, push, and open a pull request.
- Read the merge box and list what it says is missing.
- Remove the approval requirement, refresh, and watch the merge box change.
Expected result: a rejected push with GitHub's protected-branch message, and a merge box that names each unmet requirement and updates when the rule changes.
Show solution
Step 4 is the transferable skill. On someone else's repository you cannot change the rules, but the merge box always tells you the complete list of what is missing, in the order it will be checked. Reading it is faster than asking, and it is right every time.