SovranCode
HomeCourses Git & GitHub GitHub Branch Protection
This device
Course contentsGitHub Branch Protection · 41 topics

1. Git Fundamentals

Git IntroductionGit Version ControlGit vs GitHubGit InstallationGit Working TreeGit RepositoryGit CommitGit .gitignoreGit Project: Learning Journal

2. Git History

Git LogGit DiffGit ResetGit RevertGit StashGit Project: Messy Repository

3. Branches & History

Git BranchGit MergeGit Merge ConflictsGit RebaseGit Cherry-PickGit TagsGit Project: Feature Branch

4. Remotes and GitHub

Git RemoteGitHub AuthenticationGitHub RepositoryGitHub ForkGitHub IssuesGitHub PagesGit Project: Two Clone Sync

5. GitHub Collaboration

GitHub Pull RequestsGitHub Code ReviewGit Branching WorkflowGitHub Branch ProtectionGitHub CollaborationGitHub ContributingGit Project: Reviewed Pull Request

6. Automation and Professional Git

GitHub ActionsGit InternalsGit RecoveryGitHub SecurityGit Project: Team Repository CIProject
Learn Git & GitHub40 complete · 1 planned

1. Git Fundamentals

Git IntroductionGit Version ControlGit vs GitHubGit InstallationGit Working TreeGit RepositoryGit CommitGit .gitignoreGit Project: Learning Journal

2. Git History

Git LogGit DiffGit ResetGit RevertGit StashGit Project: Messy Repository

3. Branches & History

Git BranchGit MergeGit Merge ConflictsGit RebaseGit Cherry-PickGit TagsGit Project: Feature Branch

4. Remotes and GitHub

Git RemoteGitHub AuthenticationGitHub RepositoryGitHub ForkGitHub IssuesGitHub PagesGit Project: Two Clone Sync

5. GitHub Collaboration

GitHub Pull RequestsGitHub Code ReviewGit Branching WorkflowGitHub Branch ProtectionGitHub CollaborationGitHub ContributingGit Project: Reviewed Pull Request

6. Automation and Professional Git

GitHub ActionsGit InternalsGit RecoveryGitHub SecurityGit Project: Team Repository CIProject
PREVIOUS LESSONGit Branching Workflow
NEXT LESSONGitHub Collaboration
5. GitHub Collaboration 70 min

GitHub Branch Protection

Stop accidental pushes to the default branch and require review or checks before history can move.

What you will leave with

You will put a GitHub rule on main that refuses a direct push, prove that rejection, land the same change through a pull request, and add a CODEOWNERS file GitHub can read. You will not require two reviewers, will not lock admins out of a solo repo, and will not require a GitHub Actions check until that workflow exists.

Protection is GitHub, not Git

Git Branching Workflow wrote a contract: short-lived branches return through pull requests. A contract that nobody enforces is a README. GitHub branch protection (a branch ruleset, or a classic protection rule) is GitHub refusing to move a hosted branch unless the rule is satisfied. It is not a Git command. There is no git protect.

GIT

Local main

You can still commit on main. git push origin main is still the Git request to update the remote. Git does not know about GitHub rules.

GITHUB

Hosted main

GitHub can answer that request with a rejection. The pull-request path from GitHub Pull Requests is how main is supposed to move.

# These are Git. None of them is GitHub branch protection.
git switch main
git push origin main
git branch --list

Use a disposable GitHub repository you own. Do not edit protection on a project you do not maintain. Do not lock the original main of a fork you just created.

Do not lock yourself out of a one-person repo

Require a pull request. Leave required approving reviews at zero unless a second person will actually review. Do not enable “Do not allow bypassing” / enforce rules for administrators on a solo practice repository until you have another way in.

Require a pull request, not a lockout

On GitHub: Settings → Rules → Rulesets (or the older Settings → Branches classic rule). Target the default branch, usually main. The job for this lesson is one switch with a clear meaning.

Require a pull request

Direct pushes to main should fail. This is the GitHub enforcement of GitHub Flow.

Block force pushes

Protected main should not accept git push --force. You already refused force-push after review started; GitHub can refuse it on the default branch too.

Required approvals

Leave this off (zero) on a repository where you are the only reviewer. One required approval with no second person is a closed door.

Required status checks

Leave this off until GitHub Actions has a workflow that has already passed. A required check that does not exist blocks every pull request.

Rulesets and classic branch protection are both GitHub. If the UI offers a ruleset, use it. The proof is the same: git push origin main is rejected, and a pull request still merges.

Prove origin refuses a direct push

After the rule targets main, do not guess. Make a local commit on main and push. GitHub should refuse. Read the error. That refusal is the lesson, not a broken remote.

# On a clone of a GitHub repository YOU own, after protection
# requires a pull request before merging into main.
git switch main
git pull origin main

# A tiny local commit on main is still legal Git.
# GitHub can still refuse to move origin/main.
echo "protection probe" >> PROTECTION.md
git add PROTECTION.md
git commit -m "Probe a direct push to protected main"
git push origin main
QUICK CHECK

Test what you learned

Type the Git command that asks origin to update main from your local main (protection should refuse it).

Local main now has a commit origin/main does not. That is the same “ahead” state Git Remote taught. The difference is GitHub’s policy, not a new Git object.

Recover through a pull request

Do not force-push. Do not turn the rule off to sneak the commit through. Move the probe onto a feature branch, reset local main to origin/main, and open a pull request. That is the path protection is there to keep.

# Leave the probe commit on a feature branch instead of fighting main.
git switch -c feature/protection-probe
git push -u origin feature/protection-probe
git switch main
git reset --hard origin/main

gh pr create --base main --title "Add protection probe note" --body "$(cat <<'EOF'
## Summary
Proves the default branch refuses a direct push and still accepts a pull request.

## How to verify
1. Confirm git push origin main is rejected while this rule is on.
2. Confirm this pull request can still merge.
EOF
)"

git reset --hard origin/main on local main is safe here only because the probe commit is already on feature/protection-probe. If you reset first, you can lose the commit. Match the order above. Then merge the pull request the way GitHub Code Review already taught.

QUICK CHECK

Test what you learned

Type the command that creates and switches to a branch named feature/protection-probe.

CODEOWNERS is GitHub, stored in Git

A CODEOWNERS file is an ordinary path in the repository. Git records it. GitHub reads it on the default branch to suggest or require reviewers for matching paths. Put it at .github/CODEOWNERS so a stranger can find it.

# .github/CODEOWNERS on the default branch
# Replace with YOUR GitHub username. This is not a Git command.
* @YOUR_GITHUB_USERNAME
README.md @YOUR_GITHUB_USERNAME

Replace YOUR_GITHUB_USERNAME with the account that owns the practice remote. A made-up handle is a GitHub error, not a Git error. Requiring a review from listed owners is a separate protection checkbox. On a solo repo, add the file and leave that requirement off so you can still merge.

QUICK CHECK

Test what you learned

Type the Git command that stages .github/CODEOWNERS.

Inspect the rule

# From the same clone, after a rule targets main
gh api repos/{owner}/{repo}/branches/main/protection
git ls-remote origin
git branch -vv

gh api repos/{owner}/{repo}/branches/main/protection talks to GitHub. If you created a classic rule, you should see JSON that mentions pull requests or reviews. A 404 can mean there is no classic rule—rulesets live at a different API. In that case, confirm the ruleset in Settings and keep the rejected-push proof; do not pretend Git stored the policy.

git ls-remote origin still only lists refs. It will not print protection. That is the Git versus GitHub split again.

What not to enable yet

Required Actions checks

A check that never runs blocks every merge. Add the workflow in the Actions lesson, then require it.

Two reviewers on a solo repo

There is nobody to approve. Zero required approvals plus “require a pull request” is enough proof.

Admin lockout

“Do not allow bypassing” can strand you. Leave owner bypass available on this disposable repo.

Someone else's rules

Do not open Settings on a popular repository. Do not protect upstream from a fork.

Guided practice: protect main

  1. 01
    Pick a repository you own

    Confirm git remote -v is your GitHub URL. Update local main.

  2. 02
    Add a rule on main

    Require a pull request before merging. Block force pushes. Do not require extra approvals or status checks. Do not enforce the rule on administrators.

  3. 03
    Prove the rejection

    Commit on local main and run git push origin main. Save the error text.

  4. 04
    Land it through a pull request

    Move the commit to feature/protection-probe, reset local main to origin/main, open and merge the pull request.

  5. 05
    Add CODEOWNERS

    Commit .github/CODEOWNERS with your username, through another short-lived branch. Inspect with gh api or the Settings page.

Independent lab: branch protection

  1. On a GitHub repository you own, add a ruleset or classic rule that requires a pull request before merging into main. Do not require two reviewers. Do not require a status check. Do not lock administrators out.
  2. Make a local commit on main and run git push origin main. Record that GitHub refused. Do not force-push. Do not turn the rule off to sneak the commit through.
  3. Move the work onto a feature branch, reset local main to origin/main, open a pull request, and merge it.
  4. Add .github/CODEOWNERS naming your GitHub username. Show gh api repos/{owner}/{repo}/branches/main/protection or a screenshot of the ruleset, plus the rejected-push error.
  5. Write six lines: repository URL, which GitHub setting you enabled, the rejected command, how the change still landed, the CODEOWNERS path, and one sentence that distinguishes GitHub protection from git push. Do not change protection on a project you do not maintain.
Definition of done

You can explain that protection is GitHub, prove git push origin main is refused, merge the same change through a pull request, and keep CODEOWNERS as a GitHub file stored in Git—on a repository you own.

Common protection mistakes

Thinking Git enforces the rule

Local commits on main still work. The refusal happens when GitHub receives the push.

Requiring a check that does not exist

Every pull request sits blocked. Wait for Actions.

Requiring reviewers you do not have

A solo practice repo needs a required pull request, not a two-person approval quorum.

Turning the rule off to push

That is not proof. The feature-branch path is the proof.

Lesson review

You can protect main on GitHub so a direct push is refused, keep a pull-request path open, and store CODEOWNERS in Git for GitHub to read. Repeatable issue and pull request shape is GitHub Collaboration. Required status checks are GitHub Actions.

  • I know GitHub branch protection is not a Git command and not a Git object.
  • I can require a pull request on main without locking a one-person repo or inventing a status check.
  • I can prove git push origin main is refused, then land the same commit through a pull request.
  • I can add .github/CODEOWNERS and inspect the rule with gh api or Settings, on a repository I own.

Related lessons

  • GitHub Code Review — Required reviews are a protection rule, not a Git command.
  • Git Branching Workflow — Protection enforces a model; it does not choose GitHub Flow for you.
  • GitHub Pull Requests — A required pull request is how protected main is supposed to move.
  • Git Remote — Git can still commit on main; origin can refuse the push.
  • GitHub Actions — Required checks often come from a workflow.
KNOWLEDGE CHECK

Check your GitHub Branch Protection model

Keep protection on GitHub, prove that origin can refuse git push origin main, and leave required Actions checks for the next unit.

01What is GitHub branch protection?
02Does git push origin main always update GitHub’s main after protection is on?
03Which protection belongs in a solo practice repository?
04What does a CODEOWNERS file do?
05Which command inspects classic protection on main for this GitHub repository?
06Should you turn on required status checks in this lesson?
07Where should you practice branch protection for this lesson?
PREVIOUS LESSONGit Branching Workflow
NEXT LESSONGitHub Collaboration
ON THIS PAGEGitHub Branch ProtectionProtection is GitHub, not GitRequire a pull request, not a lockoutProve origin refuses a direct pushRecover through a pull requestCODEOWNERS is GitHub, stored in GitInspect the ruleWhat not to enable yetGuided practice: protect mainIndependent lab: branch protectionCommon protection mistakesLesson reviewKnowledge checkRelated lessons
Course contents