Protected branches
Intermediate Core Git GitLab UI GitHub UI
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
$ git pushremote: 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.
$ git switch -c docs/45-move-my-commits
$ git push -u origin docs/45-move-my-commitsYour commits are already on the new branch, because branching takes the current commit with it. Now put main back where the remote has it:
$ git switch main
$ git reset --hard origin/mainThen 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:
$ git pushremote: GitLab: You are not allowed to push code to protected branches on this project.The remote: prefix always means the server refused, not Git.
The push fails with a notification quoting the same remote: message; open Output → Git to read it in full. VS Code cannot change protection settings; use the platform's web interface.
A red Push failed notification appears, with the server message in the balloon and in Git → Show Git Log → Console tab. IntelliJ cannot change protection settings either.
Settings → Repository → Protected branches (maintainers and owners only) lists the protected branches and, for each, who may merge and who may push. The usual setting for main is Allowed to merge: Maintainers, Allowed to push and merge: No one.
Related settings live nearby: Settings → Merge requests holds the approval rules and the merge method, and Settings → Repository → Protected tags does the same for release tags.
Settings → Rules → Rulesets (or the older Settings → Branches → Branch protection rules) holds the same choices: require a pull request before merging, require approvals, require status checks, block force pushes, restrict deletions.
GitHub applies rulesets by branch-name pattern, so one rule can cover main and release/* at once. Administrators only.
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.
- On
mainin your playground, make a commit you did not intend: add a line toREADME.mdand commit it. - Pretend the push was refused. Create a branch from where you are:
git switch -c docs/practice-rescue. - Confirm the commit is on the new branch:
git log --oneline -1. - Put
mainback:git switch main, thengit reset --hard origin/main(orHEAD~1if your playground has no remote). - Confirm with
git log --oneline -2onmainand 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.