Branch Protection, Rulesets & Team Permissions
Why Protect Branches
In a repository with multiple contributors, nothing stops someone from pushing directly to main and bypassing code review, or deleting the main branch accidentally, or force-pushing over a colleague's commits. Branch protection rules are the guardrails that prevent these accidents and enforce the workflow policies your team has agreed on.
Protection rules enforce:
- Changes to
mainmust go through a pull request (no direct pushes) - PRs require at least N approving reviews before merging
- Required status checks (CI must pass) must be green before merging
- Conversations on the PR must be resolved before merging
- History cannot be rewritten (no force pushes)
- The branch cannot be deleted
These aren't just bureaucratic controls — they are the technical encoding of your team's quality standards. A developer can't accidentally skip code review because the platform won't let them.
Branch Protection Rules
Branch protection rules are configured per-branch (or branch pattern) in Settings → Branches → Add branch protection rule.
Matching Branches
The rule applies to branches matching a pattern:
main— exact match, only themainbranchrelease/*— all branches starting withrelease/v*— all branches starting withv(e.g.,v1,v2.0)*— all branches (use with care)
Require a Pull Request Before Merging
The most important protection: no direct pushes to the protected branch. All changes must come through a pull request.
When enabled:
Dismiss stale pull request approvals: If someone approves a PR and then new commits are pushed, the approval is automatically dismissed. This prevents the anti-pattern of getting approval early and then pushing significant changes after.
Require review from Code Owners: Uses the CODEOWNERS file (covered in lesson 4) to determine which reviewers are required. If a PR touches files owned by a team, someone from that team must approve.
Require Status Checks to Pass Before Merging
Prevents merging when CI is red. You must specify which status checks are required:
Require branches to be up to date: The PR's branch must be current with the base branch before merging. This ensures the CI ran against the latest state, not a state that may have diverged.
To add a status check to the required list, the check must have run at least once in the repository so GitHub knows the check name. Run the CI workflow at least once on a branch, then add the check name to the required list.
Require Conversation Resolution Before Merging
All review comment threads must be marked resolved before the PR can be merged. Prevents issues from being merged while reviewers have outstanding concerns.
Require Signed Commits
Every commit on the protected branch must be GPG-signed or SSH-signed, verifying the commit's author identity. Protects against commit author spoofing. Requires contributors to set up commit signing.
Do Not Allow Bypassing the Above Settings
By default, repository administrators can bypass branch protection rules. Checking this option removes that bypass — even admins must go through PRs. Use this for the most critical repositories where even admin humans should not skip review.
Restrict Who Can Push to Matching Branches
Even with PR requirements, this setting limits who can create and merge into the protected branch. Useful for release branches where only release managers should have merge access.
Allow Force Pushes and Allow Deletions
These are disabled by default. Force pushes rewrite history, which can cause problems for anyone who has based work on the rewritten commits. Allowing deletions lets people delete the protected branch, which is usually catastrophic for the default branch.
Keep both disabled for main and all release branches.
Rulesets: The Modern Approach
Rulesets were introduced as a more flexible, scalable replacement for branch protection rules. Key advantages over classic branch protection:
- Can be applied to multiple repositories across an organization simultaneously
- Support more granular "bypass actors" (specific users, teams, or apps that can bypass specific rules)
- Have an Evaluate mode for testing rules before enforcing them
- Are versioned and exportable as JSON
- Can be applied to tag patterns as well as branch patterns
Creating a Ruleset
Go to Settings → Rules → Rulesets → New ruleset → New branch ruleset.
Enforcement status options:
- Active — rules are enforced
- Evaluate — rules are logged but not enforced (useful for testing impact)
- Disabled — ruleset exists but does nothing
Target branches: Use patterns to target branches:
Bypass list: Actors who can bypass the ruleset:
- Organization admin
- Repository admin
- Specific users or teams
- Specific GitHub Apps (e.g., Dependabot or your deploy bot)
Rules Available in Rulesets
Require Linear History
Enforces that all PRs must use squash merge or rebase merge — no merge commits allowed. This maintains a perfectly linear main branch history, which is easier to read with git log --oneline and makes git bisect more reliable.
Require Deployments to Succeed
Links the merge gate to actual deployments. Before a PR can merge, a deployment to the specified environments must succeed. Useful for workflows where every PR is deployed to a preview environment that must pass health checks.
Required Status Checks in Practice
Setting up required status checks works as follows:
Step 1: Create the CI Workflow
Step 2: Run the Workflow at Least Once
Push this workflow to main or open a PR to trigger it. The check names CI / Test and CI / Lint (format: workflow-name / job-name) now appear in the repository.
Step 3: Add to Required Checks
In Settings → Branches → edit the protection rule → under Require status checks to pass → search for and add CI / Test and CI / Lint.
Now every PR to main must have both checks pass before merging is enabled.
Team Permissions
GitHub has a five-level permission system for repository access:
Repository Permission Levels
| Level | Can Do | |-------|--------| | Read | View and clone the repository, create issues, comment on issues/PRs | | Triage | Everything in Read, plus manage issues/PRs (apply labels, close, reopen) — cannot push code | | Write | Everything in Triage, plus push to non-protected branches, manage releases | | Maintain | Everything in Write, plus manage repository settings (except destructive actions), manage topics, pages | | Admin | Full access including dangerous operations: transfer, delete, manage secrets, manage webhooks |
Assigning Collaborators
For personal repositories, go to Settings → Collaborators → Add people.
For organization repositories, permissions are managed through teams rather than individual collaborators (though both are possible).
Organization Teams
Teams are groups of organization members with shared permissions. Benefits:
- Assign one team to multiple repositories instead of adding individuals one by one
- Team membership changes propagate automatically to all repositories the team has access to
- CODEOWNERS can reference teams:
@org/frontend-team
Creating a team:
- Go to your organization → Teams → New team
- Give the team a name (e.g.,
frontend,backend,devops) - Set visibility: Visible (members can see the team) or Secret (only members and org owners can see it)
- Add members
Assigning a team to a repository:
- Go to the repository → Settings → Collaborators and teams → Add teams
- Search for the team and set the permission level
Nested Teams
Teams can be nested: a parent team whose sub-teams inherit access. Example:
Base Permissions
Organization owners can set a base permission level that applies to all members on all repositories:
Settings → Member privileges → Base permissions
Options: No permission, Read, Write, Admin. Setting this to Read means all organization members can view all repositories by default (but not push).
Restricting Who Can Push to Protected Branches
Even with PR requirements, you may want to restrict who can merge PRs into critical branches:
Release Branch Workflow
A common pattern: main is protected so only PRs can be merged into it, but only the release-managers team can approve merges into release/* branches:
Regular developers can open PRs targeting release branches, but only release managers can approve and merge them.
Hotfix Workflow
For emergency hotfixes that must bypass the normal review process:
The bypass is logged — you can see who bypassed the rules and when in the repository's audit log.
Practical Exercises
Exercise 1 — Add Basic Branch Protection
- Create a repository with a
mainbranch and at least one commit. - Add a branch protection rule for
main:- Require a pull request before merging (1 required review)
- Dismiss stale reviews on push
- Create a new branch and try to push directly to
main— observe the rejection. - Open a PR from your branch to
main— observe the merge button is disabled until approved.
Exercise 2 — Required Status Checks
- Create a
.github/workflows/ci.ymlworkflow that runs tests. - Open a PR and let the workflow run.
- Add the workflow's job as a required status check in the branch protection rule.
- Deliberately break a test, push, and observe that the PR cannot be merged.
- Fix the test, push, and observe the merge button becomes available after CI passes.
Exercise 3 — Create a Ruleset
- Go to Settings → Rules → Rulesets → New branch ruleset.
- Create a ruleset targeting
mainthat:- Blocks force pushes
- Requires a pull request
- Requires status checks to pass
- Set the enforcement to Evaluate mode first, then switch to Active.
Exercise 4 — Team Permissions (Requires Organization)
If you have or can create a GitHub Organization:
- Create two teams:
developersandmaintainers. - Assign
developersWrite access to a repository. - Assign
maintainersMaintain access. - Create a branch protection rule that restricts merging to the
maintainersteam. - As a member of
developers, verify you can open a PR but not merge without amaintainersreview.