Git Course 0%

Protected branches

Intermediate Core Git GitLab UI GitHub UI ≈ 7 min

What you will learn

  • What a protected branch prevents, and what the rejection message looks like
  • The protections teams typically enable and why each exists
  • What to do when your push to main is refused

After this lesson you can

  • I recognize a protection rejection instantly and know it is not my mistake
  • I can explain why the team protects main to someone who finds it inconvenient

Why this matters

Sooner or later you will commit something on main by habit and try to push it. The refusal that follows looks alarming and reads like a permissions problem with your account. It is neither: it is the team's deliberate rule, working exactly as intended, and the fix takes a minute.

What it looks like

Terminal
$ git push
remote: GitLab: You are not allowed to push code to protected branches on this project.
To https://gitlab.com/northwind-trails/trailguide.git
 ! [remote rejected] main -> main (pre-receive hook declined)
error: failed to push some refs to 'https://gitlab.com/northwind-trails/trailguide.git'

Read it from the top. The remote: line is GitLab talking, not Git: the server refused. [remote rejected] names the branch, and pre-receive hook declined is the mechanism. GitHub's wording differs slightly but the shape is the same, and both mean one thing: this branch does not accept direct pushes.

Nothing was lost. Your commits are still on your computer; only the upload was refused.

What protection typically blocks

Teams choose from a similar menu on both platforms:

Rule What it prevents Why
No direct push git push to main Every change is reviewed
No force push git push --force to main History that everyone shares cannot be rewritten
No deletion Deleting the branch Obvious, and it happens
Required approvals Merging without N reviewers A second pair of eyes
Required status checks Merging with a red pipeline Broken code never reaches main
Code owners Merging without the owner of the changed files The right reviewer, automatically
Linear history Merge commits Keeps main a straight line

Which of these are on is a project setting, changed by a maintainer or owner (lesson 9.5 for GitLab, lesson 10.5 for GitHub). You cannot turn them off, and that is the design.

What to do when you hit it

You have commits on main that should have been on a branch. Move them; nothing is lost.

Terminal
$ git switch -c docs/45-move-my-commits
$ git push -u origin docs/45-move-my-commits

Your commits are already on the new branch, because branching takes the current commit with it. Now put main back where the remote has it:

Terminal
$ git switch main
$ git reset --hard origin/main

Then open a merge request from the new branch. Lesson 12.7 covers this rescue in full, including the case where you also have uncommitted work.

How to do it

There is no command to protect a branch: protection is a platform setting, not a Git feature. What the terminal gives you is the diagnosis:

Terminal
$ git push
remote: GitLab: You are not allowed to push code to protected branches on this project.

The remote: prefix always means the server refused, not Git.

Common mistakes

  • Reading the rejection as an account problem. "You are not allowed to push code to protected branches" is about the branch, not about you. Your token and permissions are fine.
  • Asking for the protection to be lifted. Almost never the right request; the branch workflow costs a minute.
  • Force-pushing to get around it. Protection blocks that too, and on an unprotected shared branch it would overwrite colleagues' work.
  • Deleting the local commits in a panic. They are your work. Branch first, reset second.

Try it yourself

Goal: rehearse the rescue, without needing a protected branch.

  1. On main in your playground, make a commit you did not intend: add a line to README.md and commit it.
  2. Pretend the push was refused. Create a branch from where you are: git switch -c docs/practice-rescue.
  3. Confirm the commit is on the new branch: git log --oneline -1.
  4. Put main back: git switch main, then git reset --hard origin/main (or HEAD~1 if your playground has no remote).
  5. Confirm with git log --oneline -2 on main and on the branch.

Expected result: the commit exists on docs/practice-rescue and is gone from main; no work was lost.

Show solution

Branching before resetting is the whole trick: git switch -c leaves a label on the commit you are about to remove from main, so the reset moves only main's label. Reversing the order would still be recoverable through the reflog, but there is no reason to make it exciting. Clean up with git branch -D docs/practice-rescue.

Check yourself

1. Your push is refused with "You are not allowed to push code to protected branches". What happened?
2. After being refused, what is the first command to run?
3. Which of these is typically not a branch protection rule?

Key terms

Protected branch main Merge request (MR) Maintainer