Best practices for protected branches

Branch protection is part of a powerful set of configuration options that give repository administrators the ability to enforce security policies by preventing accidental branch deletions, enforcing code reviews, and requiring successful automated checks before pull requests can be merged.

These options offer an effective way to elevate code quality without creating artificial obstructions to effective collaboration. Finding the right combination of settings to achieve the desired effect is very important.

This article will explore best practices to help you maintain a healthy codebase without impairing collaboration. You will learn when and how to use required status checks, branch restrictions, required reviews and more.

 

Branch protection for organizations

Collaborators with write access to a repository have complete write permissions on all its files and history. While this is good for opening up the doors to collaboration it’s not always desirable: for instance, a collaborator may accidentally delete an important branch or overwrite its commit history by force pushing incompatible changes. Another common scenario is that of a collaborator introducing bugs by adding work that hasn’t been tested.

GitHub offers a number of tools to achieve frictionless collaboration and help you keep your repositories in good shape without littering your development workflow with unnecessary obstructions.

Protect this branch

Protected branches are a safety net designed to protect your code from catastrophic actions rather than particular people.

You can select any branch in any repository and by enabling this setting you will ensure the following safeguards:

  • the branch cannot be deleted, either accidentally or intentionally
  • the branch commit history cannot be overwritten with an alternate set of changes (force push)

That applies to command line users as well as to the GitHub web UI.

:warning:  Please note : collaborators with write permissions pushing commits to the top of a protected branch, either directly or by merging pull requests without merge conflicts will remain unaffected by this setting. That’s because merging a pull request is in fact equivalent to pushing its commits to the target branch.

:white_check_mark:  Do:  enable branch protection to effectively prevent force pushing e.g. to prevent rewriting the commit history.  Tip: check the box for  Include administrators  at the bottom of the page to also apply this setting to organization and repository administrators.

:white_check_mark:  Do:  enable branch protection on the master branch or any branch meant for ongoing collaboration. Feel free to skip for “work in progress” style branches which are meant to be eventually merged and deleted.

:white_check_mark:  Do:  temporarily disable branch protection if it’s necessary to force push commits to the branch. Sometimes this may be unavoidable: in those circumstances we advise all necessary precautions should be used and collaborators notified.

:no_entry:  Don’t:  expect branch protection to prevent pushing commits which don’t alter the commit history. If you want to disable pushing directly to the branch, use the setting  Require pull request reviews before merging  instead.

Branch restrictions

It’s possible to restrict the teams or individuals who can push to a branch at all by enabling the option  Restrict who can push to this branch  and specifying their names on the list. By default, all collaborators who have been granted write permissions to the repository are able to push to the protected branch. By explicitly specifying permitted collaborators, you will narrow down the list of existing collaborators who can push to the branch.

When and how to use branch restrictions

It’s important to understand the implications of using this option especially in conjunction with the others. Let’s look at a common scenario:

I want to prevent someone from pushing their local changes directly to the remote branch; at the same time I don’t want to prevent their pull requests from being merged.

Restrict who can push to this branch  is intended to exclude users or teams from pushing to important branches using any method, including merging their own pull requests onto the target branch. As we already saw in this article, merging a pull request is effectively the same as pushing commits to a branch. By enabling this setting you can prevent a user from pushing commits to a branch while still allowing them to open pull requests.

This will prevent them from being able to merge their own pull requests and require that only those in the restricted list can proceed with the merge operation.

:white_check_mark:  Do:  create a “Maintainers” team of repository maintainers if you want to explicitly restrict push permissions to a specific set of collaborators. This allows you to simply add the team to the list instead of having to type in each maintainer individually. It’s also one less thing to maintain when you are ready to promote collaborators to the maintainers team!

:white_check_mark:  Do:  use this option as a way to bypass pull request based worfklows for those bots or other apps that need to be granted permissions to push directly to the branch.

:no_entry:  Don’t:  enable branch restrictions if your goal is to ensure changes need to be approved before collaborators can merge their own work. Use  Require pull request reviews before merging  instead to spare maintainers from having to merge each individual pull requests.

Require status checks to pass before merging

If you test your code with a continuous integration system that sends build statues back to GitHub (such as CircleCI, Travis or Jenkins) you can avoid broken code by requiring one ore more status checks to pass on a branch before allowing pull requests to be merged.

Each individual CI system connected to your repository will be listed here and you can individually specify required ones.

Let’s take a look at the following:

I want to prevent pull requests being merged unless all the tests are all passing and nobody should be able to push directly to master.

The above can be achieved by enabling branch protection on master, turning on  Require status checks to pass before merging  and by specifying the tool(s) executing the test suite as required. To avoid direct pushes, simply enable  Require pull request reviews before merging  (more below).

Another very common request is:

I want my CI checks to run again if something has changed on master since a pull request has been opened.

By also enabling  Require branches to be up to date before merging  you can make sure that checks are ran against the latest state of the target branch. If the code on the target branch has changed since a pull request has been opened, a message will appear to indicate that those changes need to be merged upstream into the pull request branch before merging. Once the changes are integrated a new build will be triggered and the status checks will update to reflect the latest state.

:white_check_mark:  Do:  use the checkbox to  Include administrators  to ensure this is applied to all collaborators.

:white_check_mark:  Do:  use required checks in conjunction with  Require pull request reviews before merging  to achieve an effective collaboration workflow based on pull requests.

Require pull request reviews before merging

Code reviews are a critical part of any development workflow. You can require at least one review and approval by checking the  Require pull request reviews before merging  checkbox. This option alone is very useful and when used in conjunction with required status checks it provides teams certain “collaboration guardrails” to help with writing better code while maintaining high levels of productivity.

In fact when you enable this setting you will ensure that:

  • no pull request can be merged without at least one  approved  review
  • prevent collaborators from pushing directly to the branch (and merging their own pull requests)

Pull request authors must either respond to requested changes, or dismiss a review, if they are not restricted from doing so by the  Restrict who can dismiss pull request reviews option.

:white_check_mark:  Do:  use automated code review tools to enforce objective code quality aspects (such as coding style guidelines) to introduce a point of objectiveness in the process of code review and help keep discussions between developers focused on more important things.

:white_check_mark:  Do:  enable the extra flag  Dismiss stale pull request approvals when new commits are pushed  to make sure that reviewers are notified when new changes are pushed to a pull request they have already approved.

Require review from Code Owners

Another important tool that repository administrators have at their disposal is  Require review from Code Owners. Let’s review the following situation:

I want to ensure that all changes touching CSS files (e.g. *.css) are reviewed by our design team; at least one administrator needs to approve the pull request before merging.

Repository administrators can define exactly which people and teams need to review projects using the CODEOWNERS file. This new feature automatically assigns reviewers based on Code Owners when a pull request changes any owned files, using the same syntax as the .gitignore file.

:white_check_mark:  Do:  use the CODEOWNERS file to ensure that changes to specific areas of the codebase are always reviewed by those with the most expertise.

Conclusions

In this article we have seen how branch protection can increase team productivity without hindering collaboration while also giving great levels of flexibility to repository and organization administrators looking to enforce the right collaboration policies to secure their software development workflows.

If you want to learn more we recommend the following list of links from the documentation and support pages:

18 Likes

@pierluigi, I’m interested in this particular idea: " Do:  use this option as a way to bypass pull request based worfklows for those bots or other apps that need to be granted permissions to push directly to the branch" (w.r.t. to the " Restrict who can push to this branch" setting). However, I don’t see a way of making this work – i.e. I don’t know how to configure branch protection to allow a subset of users to push directly to the branch while requiring everyone else to submit a PR. Could you elaborate on how to do this?

Thanks!

2 Likes

Hi @cobrabr thanks for the comment. Please note that: The ability to restrict branches is a type of branch protection that’s available for public and private repositories owned by organizations in GitHub Team, GitHub Enterprise Cloud, and GitHub Enterprise Server. From: https://help.github.com/en/github/administering-a-repository/enabling-branch-restrictionsHope this helps!

Yeah, I know where to do it, but I don’t know how to use it to bypass the PR workflow, as you suggested. If I turn it on in the same rule in which I enable  Require pull request reviews before merging , then only the users I specify can actually merge PRs as well, which is not what I need. What I need is to set up a bypass for a few users – everyone else should need to submit a PR, but those users should be able to push/merge directly. That’s what I’m having trouble setting up.

4 Likes

@pierluigi I have a similar question to @cobrabr . I really want to enable “Require status checks to pass before merging” but I only want it to affect PRs. I want all users to still be able to push directly to the branch if they want. Is there some way to set “Require status checks to pass before merging” only for PRs without locking our direct pushes to the branch?

3 Likes

Hi @pierluigi

I have the same question as @cobrabr, I need to give permission to a specific account to be able to push to the master branch without having to do a PR since it’s all executed during a GH action. But as of now it looks like there’s no way to do that because even if I add the user in the “Restrict who can push to matching branches” whenever he pushes GitHub raise an error telling me that he has to do a PR and that this PR has to be approved by one person.

Can you explain us how we can use this option?

Thank you!

@pierluigi Adding my voice to the others here. There does not seem to be any way to just exclude a user/app from the checks. Being able to have CI commit a version bump to the main branch seems like a pretty normal use case. We are currently migrating from Bitbucket to GitHub and this is a major blocker for us.

Hi @dblanchette @rlejeune @evhaus @cobrabr – the easiest way to allow merging PRs while bypassing required checks would be to leave the “include Administrators” flag unchecked – that allows users with Admin permissions to bypass the checks entirely (https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/enabling-required-status-checks)

Another option would be to define 2 different Actions workflows – one targeting the pull_request event and the other the more generic on: push branches: -master using branch filters.

You can further specify conditions for example based on specific usernames (e.g. using contexts and expressions e.g. if: ${{github.actor == "username"}}

By creating 2 different Actions workflow definitions you can then make only the ones targeting a pull request event a required check. See this related thread: Github Actions as Status checks?

Hope that helps!

Thanks for the detailed answer! Unfortunately it does not work for us:

  • We cannot give Admin access to a bot user because that means we would need to share that user’s personal access token with all of our users, which means essentially giving everyone in our org admin rights.
  • I tried the two different workflows options and still get an access denied if I don’t also remove the need for approvals. Is this a bug?

So far, here are the options we have:

  • Have a GitHub action open a PR to make the version bump commit and have it approved by a human. We have other actions to take after the version bump so the CI would need to wait for the approval of the PR before continuing,
  • Create an internal API that has admin rights to our GitHub org and that exposes an endpoint that will temporarily disable branch protection. This has the benefit of only giving the rights to disable protection and not all of admin rights, but would be time consuming to implement
  • Have the GitHub app open a PR for the version bump instead of pushing directly to the main branch and have a second app approve the PR and merge it. This would not work for repos which requires more than one approval. We also need the PR check in CI to approve the PR when it detects it is a version bump commit
  • Just remove branch protections in repos where we want to use version bump commits
  • Just make our bot user an admin and share the token

What would work for us is to have an option that allow a specific app or user to push to a branch bypassing all checks. Yes, that could still be used to bypass branch protection, but it is better than giving admin rights to everyone.

Any help appreciated, thank you!

Hey there – I recommend getting in touch with our Support team as they can better understand your specific request and help you find a solution that works for your needs: https://support.github.com

1 Like

I’m a bit late to the party, but I have been dealing with the same issue. I ended up unchecking “enforce admins” and creating a service account as the only user with admin rights. I then shared the credentials with my team. This works for now, but it is less than ideal.

As others have mentioned, it would be best if I had the ability to allow specific users to bypass branch restrictions.

2 Likes