Branch protection is server-side enforcement against direct changes and force-pushes to a branch. Configure it under Settings -> Branches on GitHub (on GitLab this is "Protected branches"; on Bitbucket, "Branch restrictions"). Without it, a branch differs from any other only by name. With it, a push attempt is checked against the rules and rejected if any rule is violated.
What to enable on main
A minimal set for a working repo:
- Require a pull request before merging: blocks direct pushes; changes can only land via a merged PR.
- Require approvals (minimum 1): at least one other team member must approve.
- Dismiss stale pull request approvals when new commits are pushed: approvals are dismissed after a new commit is pushed. Guards against "got an approval, added five hundred lines, merged."
- Require status checks to pass: selected CI jobs must be green.
- Require conversation resolution: all review comments must be resolved.
- Do not allow bypassing the above: admins follow the rules too. Without this checkbox, protection becomes a suggestion.
- Allow force pushes and Allow deletions: leave these off. In the UI these are "allow" toggles: turning them on removes the branch's protection against overwrites and deletion.
Optional settings
- Require linear history: forbids merge commits; only squash or rebase. A matter of team preference.
- Require signed commits: all commits must be signed (gpg-signing). Useful on security-sensitive repos.
- Require review from Code Owners: combined with codeowners, blocks merge until an owner of the changed path approves.
- Restrict who can push: an explicit list of people or teams.
What gets blocked
Without a bypass, a protected branch refuses:
git push origin main(direct push)git push --force origin main(force-push)git push origin --delete main(deletion)- merging a PR that does not satisfy the rules
Pitfalls
- Settings -> Branches is only accessible to repo admins. If you are not an admin, only the owner can configure this.
- Status checks become required by name. If you rename a CI workflow, the old name stays "required" but never runs. Go to branch protection and update it.
- Protection applies to a pattern, not just a single branch.
You can protect
release/*as a whole. - On GitLab the equivalent is "Protected branches"; on Bitbucket it is "Branch restrictions." The concept is the same; the available options differ slightly.