Skip to main content

Command Palette

Search for a command to run...

GitHub Branching Best Practices and Protection Rules

Streamline Your Workflow: A Developer's Guide

Updated
6 min read
GitHub Branching Best Practices and Protection Rules
B

Enthusiastic and dedicated Full Stack Developer with a passion for crafting efficient, user-centric, and innovative web solutions. Since 2019 I’ve been providing high-level support to agencies, startups and freelancing in various positions.

GitHub has revolutionized collaboration in the world of software development. Its branching model, built on Git, allows teams to work concurrently on new features and fixes without disrupting stable code. However, without best practices and proper safeguards, your repository can quickly become chaotic, leading to bugs, broken builds, and production downtime.

This blog post will guide you through the essential GitHub branching best practices and show you how to leverage branch protection rules for a robust, reliable, and high-quality codebase.


The Goal: A Healthy, Stable main Branch and a Reliable staging Environment

The primary objective of any branching strategy is to ensure your main branch is always stable, passing all tests, and ready to deploy at any moment. Your new staging branch will serve as a pre-release integration area where multiple features are collected and tested together before a major release.

Branching Best Practices in Action

Here are the fundamental rules for efficient and effective branching:

1. Adopt a Clear Strategy (GitFlow/Staging Variation Recommended)

For medium to large teams that coordinate releases on a regular cadence (once or twice a week/month), a main -> staging flow is an effective variation of the GitFlow strategy.

  • main: This branch holds production-ready code only. Merges are infrequent, typically only when a release is ready.

  • staging: This is the integration branch. Feature branches are merged here first. It acts as a testing ground for the next release bundle.

  • Feature/Bugfix Branches: All development work is isolated here.

2. Name Branches Consistently

A consistent naming convention makes it easy to understand the purpose and context of a branch at a glance.

  • Use prefixes: feature/, bugfix/, hotfix/.

  • Include issue IDs: feature/issue-451-optimize-api-response.

  • Keep it readable: Use hyphens as separators.

3. Keep Feature Branches Short-Lived

While staging and main are long-running, feature branches should still be merged into staging quickly to prevent complex merge conflicts.

  • Commit small, logical changes often.

  • Pull the latest staging into your feature branch regularly to stay current with team progress.

4. Use Pull Requests for Every Change

A pull request (PR) is more than a request to merge code; it's a vital communication and quality control tool.

  • All feature branches create a PR to merge into the staging branch.

  • A final PR is created from staging to main when the full release is ready.


Enforcing Quality with Branch Protection Rules

Branch protection rules enforce specific workflows and prevent accidental mistakes, guaranteeing the integrity of your key branches.

1. Core Review and Merge Checks

These rules establish mandatory human oversight and workflow procedures.

  • Require a pull request review before merging

    • Function: This is the most critical rule. It completely blocks direct pushes to the protected branch, forcing all code changes through the formalized Pull Request (PR) process.

    • Settings within this rule:

      • Required approving review count: Defines the number of mandatory approvals (usually 1 or 2) needed before the "Merge pull request" button becomes active.

      • Dismiss stale pull request approvals when new commits are pushed: (Highly Recommended) If a developer pushes a new commit to an already-approved PR, the existing approvals are automatically removed. This guarantees that reviewers are always looking at the absolute latest version of the code before it merges, preventing unreviewed changes from slipping in.

      • Require approval from code owners: Integrates with a CODEOWNERS file in your repository. This file designates specific teams or users responsible for certain directories or file types. A PR cannot merge unless the designated owner of the modified code approves it.

      • Do not allow bypassing the above settings: Prevents users who have "write" access (or even "admin" access, depending on the organizational settings) from ignoring the review count or code owner requirements.

2. Automated Status Checks (CI/CD Gates)

This links your repository's automated testing pipelines (GitHub Actions, Jenkins, etc.) directly to the merge process.

  • Require status checks to pass before merging

    • Function: Specifies that certain automated checks must report a 'success' status via the GitHub API before a merge is permitted. If your build fails, the merge button is disabled.

    • Settings within this rule:

      • Require branches to be up to date before merging: (Highly Recommended for TBD/CI) The feature branch must incorporate the latest changes from the target branch (via merge or rebase) and pass all status checks again before merging. This prevents integration issues by ensuring the changes work alongside the absolute latest code in main or staging.

      • List of checks: A dropdown menu appears allowing you to select specific CI jobs (e.g., "Build and Test," "Linting," "E2E Tests") that must pass. Notice that I had to type in the job name for github to recognize the check

3. History Management and Integrity

These rules help maintain a clean, readable, and predictable commit history.

  • Require linear history

    • Function: Enforces that pull requests are merged using a squash merge or rebase merge strategy, disallowing standard merge commits. This prevents the creation of complex, branching histories within your main branches, making the project history easy to read and understand.

    • Note: This often needs the repository's main "Merge button" settings configured to allow only squash or rebase merges.

  • Allow force pushes

    • Function: Generally unchecked for protected branches. A force push (git push --force) overwrites the history on the remote branch entirely. Allowing this on main can delete other people's work or introduce chaos. It should only be enabled in rare, controlled circumstances (if ever).
  • Allow deletions

    • Function: Generally unchecked. Prevents anyone from deleting the protected branch entirely. While you might delete feature branches after merging, you never want your main or staging branches accidentally deleted.

4. Advanced/Administrative Rules

  • Restrict who can push to matching branches

    • Function: If you absolutely must allow direct pushes that bypass PRs for a select few individuals (e.g., repository admins during an emergency hotfix), you use this rule to specify exactly which users or teams have that elevated privilege. This is often left empty in highly regulated environments.

Summary of the Flow

  1. Developer creates feature/X branch from staging.

  2. Developer works and creates a PR from feature/X to staging.

  3. PR is reviewed, tests pass, and it merges into staging.

  4. staging is deployed to a testing environment where QAs test the collected features.

  5. Once the release candidate is approved, a final PR is opened from staging to main.

  6. This release PR is approved by managers, final tests pass, and it merges into main, triggering the production deployment.

By combining robust branching best practices with strict branch protection rules, you create a system that guarantees the integrity of your codebase across different environments. You can manage these settings within your project's GitHub repository settings under the "Branches" tab.