Navigate Protected Branch Workflows
Configure branch protection rules and navigate a full PR review lifecycle on GitHub.
Introduction
30 Second Summary
Imagine someone on your team accidentally pushes broken code straight to the main branch. The whole team's work grinds to a halt because there was no review process in place.
In this project, you will set up production-grade branch protection on a GitHub repository and walk through an entire pull request lifecycle where every safeguard fires, blocks your merge, and forces you to resolve it before proceeding.
What You'll Build
A private GitHub repository where you open a pull request, watch it get blocked by every protection rule you configured, diagnose each block, and resolve them one by one until you earn a clean merge.
By the end of this project, you'll have:
- A branch protection rule with seven interconnected settings that prevent unchecked code from reaching main.
- A CODEOWNERS file that automatically assigns the right reviewer the moment you open a pull request.
- Firsthand experience resolving every common merge block: missing approvals, unresolved conversations, stale reviews, and failed status checks.
- Secret Mission: Engineer an "out-of-date branch" scenario and handle the Update Branch workflow when main advances while your PR is in review.
Are there any prerequisites?
You need a GitHub Enterprise account, Git installed locally, and a colleague willing to spend about 10 minutes acting as your reviewer.
Your colleague will need their own GitHub account and will approve, comment on, and re-approve your pull requests so you can experience the full protection lifecycle.
Before We Start
Before we dive in, let's lock in what you're about to build and why it matters for your daily work with your team.
Set Up the Repository
To experience the full branch protection lifecycle, you need a repository with real files a CI workflow can check, and a teammate standing by to review your code.
In this step, you will create a private repository on GitHub, populate it with starter files, add a CI workflow that fires on pull requests, and invite your colleague as a collaborator.
In this step, get ready to:
- Create and clone a private repository with starter files.
- Add a GitHub Actions CI workflow and push everything to main.
- Invite your colleague as a collaborator with Write access.
Create the repository and project files
You will create a new private repository on GitHub, clone it to your machine, and add the starter files that give Git something to work with throughout the project.
- Open GitHub in your browser and sign in to your Enterprise account.
- Click the + icon in the top-right corner and select New repository.
- Enter branch-protection-lab in the Repository name field.
- Set the visibility to Private.
- Check the Add a README file checkbox.
- Click Create repository.
GitHub creates your repository with a default README. Now clone it to your local machine.
- Open your terminal. Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar, type Terminal, and press Enter.
- Move to your Desktop by running this command:
cd ~/Desktop
- Clone the repository by running this command:
git clone [[REPO_URL="https://github.com/your-username/branch-protection-lab.git"]]
Where do I find the clone URL?
On your new repository page, click the green Code button. Copy the HTTPS URL shown in the dropdown.
- Move into the cloned repository by running:
cd branch-protection-lab
- Confirm you are inside the repository by running:
ls
You should see README.md listed in the output.
Don't see README.md?
Make sure you checked Add a README file when creating the repo. If you forgot, create the file now with echo "# Branch Protection Lab" > README.md and continue.
Help me troubleshoot my cloned repository.
Now create the directory structure and starter files for the project.
- Create the project directories by running this command:
mkdir -p src docs
- Create src/app.js with a starter comment by running:
echo "// app.js - starter file" > src/app.js
- Confirm the directories and file exist by running:
ls -R
You should see src/ and docs/ directories, with app.js inside src/.
Next, update README.md to describe the project structure. Open README.md in your text editor and replace its contents with the following:
# Branch Protection Lab
A sample project for learning GitHub protected branch workflows.
## Structure
- `src/` - Application source code
- `docs/` - Project documentation
What does this README do?
This README describes your project layout so anyone visiting the repository understands what lives where.
- Create src/utils.py in your text editor with the following content:
def calculate_total(items):
"""Calculate the total price from a list of items."""
return sum(item['price'] for item in items)
Why a Python file alongside JavaScript?
This utility function gives you a Python file next to your JavaScript file. Later, your CODEOWNERS file will assign reviewers based on file extension, so having multiple languages lets you test those patterns.
- Confirm both source files exist by running:
ls src/
You should see app.js and utils.py listed.
- Create docs/guide.md in your text editor with the following content:
# Project Guide
## Getting Started
This project demonstrates protected branch workflows on GitHub.
## Review Process
All changes require approval from a code owner before merging.
What is this documentation file for?
This file gives you content in the docs/ directory. Later, your CODEOWNERS file will assign a specific reviewer for anything inside /docs/.
- Verify the full project structure by running:
ls -R
You should see README.md at the top level, app.js and utils.py inside src/, and guide.md inside docs/.
Add the CI workflow and push to main
Your branch protection rule will require status checks to pass before merging. For that to work, you need at least one GitHub Actions workflow that runs on pull requests.
- Create the workflow directory by running:
mkdir -p .github/workflows
- Create .github/workflows/ci.yml in your text editor with the following content:
name: CI
on:
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- run: echo "All checks passed"
What does this workflow do?
This workflow triggers whenever someone opens a pull request targeting main. It checks out the code and prints a success message.
The workflow is intentionally minimal. Its only job is to exist as a status check that your branch protection rule can require to pass.
- Stage all your new files, commit them, and push to main by running these commands:
git add .
git commit -m "Add starter files and CI workflow"
git push origin main
What do these commands do?
- git add . stages every new and modified file in the repository.
- git commit -m saves a snapshot of those changes with a message.
- git push origin main uploads your commit to the main branch on GitHub.
Before you check the result, how many commits do you expect to see in the log? Think about the README commit GitHub created and the one you just pushed.
- Verify your commits by running:
git log --oneline
You should see two commits: the initial commit GitHub created when it added the README, and your new commit with all the starter files.
Push rejected or authentication error?
If Git asks for credentials, you may need to set up a personal access token or authenticate through your browser. GitHub no longer accepts passwords for HTTPS pushes.
If you see a message about the remote being ahead, run git pull --rebase origin main first, then push again.
Help me fix a push error to my GitHub repository.
✔️ Awesome, I've got everything!
Great. Make sure you have saved all your files. Your repository is ready for the next substep.
ⓧ I'd like to double check the full code
Here is the CI workflow file for comparison:
name: CI
on:
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- run: echo "All checks passed"
And here is the full README:
# Branch Protection Lab
A sample project for learning GitHub protected branch workflows.
## Structure
- `src/` - Application source code
- `docs/` - Project documentation
Scroll up and confirm you also have src/app.js (one comment line), src/utils.py (the calculate_total function), and docs/guide.md (project guide with two sections).
Invite your colleague as a collaborator
Your colleague needs Write access to the repository so they can submit reviews on your pull requests.
- On your branch-protection-lab repository page on GitHub, click Settings in the top navigation bar.
- In the left sidebar, click Collaborators.
- Click the Add people button.
- Search for your colleague by their GitHub username or email.
- Select them from the results and click Add to send the invitation.
- Ask your colleague to accept the invitation (they will receive a notification or email).
Why does my colleague need Write access?
On GitHub, only collaborators with Write access (or higher) can be requested as reviewers and can approve pull requests. Read access alone is not enough to participate in the review workflow.
- Confirm your colleague appears in the collaborator list on the Collaborators settings page with their invitation accepted.
You should see your colleague listed with Write access. If their invitation is still pending, wait for them to accept before continuing.
Colleague not showing up?
If you are on a GitHub Enterprise organization, your colleague may need to be a member of the organization first. Check with your admin if the invitation does not appear for them.
Help me troubleshoot inviting a collaborator to my GitHub repository.
Your repository is fully set up with starter files, a CI workflow, and a collaborator ready to review. Next up, you will open your first pull request and see how easily unprotected code reaches main.
Merge Without Guardrails
Your repository is set up with starter files and your colleague has Write access. But right now, main has zero protection. Anyone with push access can merge code directly without a single review.
In this step, you will open a pull request and merge it immediately. No reviewer assigned, no approval needed. Experiencing this gap firsthand makes the guardrails in the next step feel essential.
In this step, get ready to:
- Create a feature branch and push a code change.
- Open and merge a pull request with no review required.
Create a feature branch and push your changes
A feature branch lets you make changes in isolation before proposing them to main. You will create a branch, add a function, and push it to GitHub.
- Create a new branch called add-greeting by running this command:
git checkout -b add-greeting
You should see Switched to a new branch 'add-greeting' in your terminal.
- Open src/app.js in your code editor and replace its entire contents with this code:
function greet(name) {
return `Hello, ${name}!`;
}
module.exports = { greet };
What does this code do?
The greet function takes a name and returns a greeting string.
The module.exports line makes the function available to other files. This is a small, self-contained change that is perfect for a single pull request.
- Save src/app.js.
- Stage, commit, and push your change by running these commands:
git add src/app.js
git commit -m "Add greet function"
git push -u origin add-greeting
You should see output ending with * [new branch] add-greeting -> add-greeting confirming your branch was pushed to GitHub.
Push rejected or permission error?
- Make sure you are on the add-greeting branch, not main. Run git branch to check.
- If you see an authentication error, confirm your Git credentials are set up for this repository (SSH key or personal access token).
Need more help? Why is my git push being rejected?
Open a pull request and merge it immediately
With your branch pushed, you can now open a pull request on GitHub. Watch what happens when there are no branch protection rules in place.
- Go to your branch-protection-lab repository on GitHub.
- You should see a yellow banner that says your branch had recent pushes. Click the Compare & pull request button.
- Leave the title as-is and click Create pull request.
Your pull request page loads. Take a moment to look at the right sidebar and the merge area at the bottom.
- In the right sidebar, look at the Reviewers section. Nobody was automatically assigned.
- Scroll to the bottom. The green Merge pull request button is already active with no warnings.
- Click Merge pull request, then click Confirm merge.
The PR page now shows a purple Merged badge. Your code went straight to main without a single human reviewing it.
Why is this a problem?
On a real team, this means a typo, a security vulnerability, or a breaking change can land in production with no one else looking at it. Branch protection rules exist to prevent exactly this scenario.
- Switch back to your terminal. Pull the merged changes to your local main branch by running these commands:
git checkout main
git pull origin main
Before you run the next command, predict: how many commits do you expect to see on main?
- Verify your merge by running:
git log --oneline
You should see your "Add greet function" commit in the log. It merged with no review, no approval, and no automated checks blocking it.
Don't see your commit in the log?
- Make sure you checked out main before pulling. Run git branch to confirm you are on main.
- If git pull shows "Already up to date" but the commit is missing, confirm you merged the PR on GitHub (check the PR page for the Merged badge).
Still stuck? Help me figure out why my merged commit isn't showing locally.
Your merge went through without a single safeguard. In the next step, you will lock down main with branch protection rules and a CODEOWNERS file so that every future change requires a real review.
Lock Down main with CODEOWNERS and Branch Protection
In the previous step, you merged a pull request with zero friction. No reviewer was assigned, no approval was needed, and the code landed on main unchecked.
That ease is the problem. On a real team, unreviewed code on main means bugs ship to production with no safety net. In this step, you will add the guardrails that real teams use so that every change to main must go through review.
In this step, get ready to:
- Create a CODEOWNERS file that auto-assigns your colleague as reviewer.
- Configure a branch protection rule with seven interconnected settings.
- Verify the protection by attempting a direct push to main and seeing it rejected.
Create the CODEOWNERS file
GitHub uses a special file called CODEOWNERS to automatically assign reviewers when a pull request touches files they own. The file must exist on the base branch (main) for review requests to trigger.
What is CODEOWNERS?
A CODEOWNERS file maps file patterns to GitHub usernames. When someone opens a pull request that modifies files matching a pattern, the listed owner is automatically requested as a reviewer.
The last matching pattern in the file takes precedence. This lets you set a broad default owner for all files and then override with more specific owners for certain directories or file types.
- In your terminal, make sure you are on the main branch inside your branch-protection-lab folder.
- Create the CODEOWNERS file in the .github directory by running:
touch .github/CODEOWNERS
- Open .github/CODEOWNERS in your editor and paste the following content:
# Default owner for all files
* @[[COLLEAGUE_USERNAME="your colleague's GitHub username"]]
# JavaScript source files
*.js @[[COLLEAGUE_USERNAME="your colleague's GitHub username"]]
# Documentation directory
/docs/ @[[COLLEAGUE_USERNAME="your colleague's GitHub username"]]
What does this file do?
- * matches all files in the repository. This is the catch-all default owner.
- *.js matches any JavaScript file anywhere in the repo.
- /docs/ matches everything inside the docs directory at the repository root.
- Since the last matching pattern wins, changing a .js file triggers the *.js rule rather than the * rule. In this project, all patterns point to the same colleague so the effect is identical, but the syntax matters for real teams with multiple owners.
- Save .github/CODEOWNERS.
- Commit and push this file directly to main by running:
git add .github/CODEOWNERS
git commit -m "Add CODEOWNERS file"
git push origin main
This is the last time you will push directly to main. Once branch protection is enabled in the next substep, this path is permanently closed.
- Confirm the push succeeded by checking the output. You should see a message ending with a branch reference like main -> main.
Push failed with permission errors?
- Make sure you are on the main branch. Run git branch to check.
- If you see an authentication error, confirm your Git credentials are configured for this repository.
- help me push my CODEOWNERS file to main
Configure the branch protection rule
Now you will lock down the main branch so that all future changes must pass through a pull request with approval, passing checks, and resolved conversations.
- In your browser, navigate to your branch-protection-lab repository on GitHub.
- Click Settings in the repository navigation bar.
- In the left sidebar under Code and automation, click Branches.
- Click Add branch protection rule.
- In the Branch name pattern field, type main.
Now enable each protection setting. Check the following boxes in order:
- Check Require a pull request before merging.
- Under it, set the required number of approvals to 1.
- Check Dismiss stale pull request approvals when new commits are pushed.
- Check Require review from Code Owners.
- Check Require conversation resolution before merging.
- Check Require status checks to pass before merging.
- In the search box that appears, search for and select the build status check (this is the job name from your CI workflow).
- Check Require branches to be up to date before merging (a sub-option under status checks).
- Check Require linear history.
Why seven settings together?
Each setting closes a different gap. Required approvals ensure a human reviews the code. Stale review dismissal forces re-review after changes. Code owner review makes sure the right person signs off. Conversation resolution prevents unaddressed feedback from slipping through.
Status checks prove the code builds. Up-to-date branch prevents merging outdated code that conflicts with recent changes. Linear history keeps git log clean by banning merge commits.
- Click Create at the bottom of the page to save the rule.
You should see the new rule listed on the Branches settings page with main as the protected branch.
Don't see the build status check in the search?
- The CI workflow must have run at least once for GitHub to recognize the check name. Since you merged a PR in the previous step, the build job should already appear. If it does not, open a quick PR (you can close it immediately) to trigger the workflow, then return to this page and search again.
- help me find the build status check in branch protection settings
Verify protection by attempting a direct push
The real proof that your protection works is seeing it reject a push. You will make a small commit locally and try to push it straight to main.
- Switch back to your terminal.
- Create a test commit on main by running:
echo "test" >> README.md
git add README.md
git commit -m "Test direct push"
Before you push, predict what will happen. Do you think this push will succeed or fail?
- Attempt the push by running:
git push origin main
Your push is rejected. You will see an error containing:
remote: error: GH006: Protected branch update failed for refs/heads/main.
remote: error: Changes must be made through a pull request.
That rejection is exactly what you want. The protection rule is working. Every future change to main must go through a pull request.
- Reset your local main branch to remove the test commit by running:
git reset --hard HEAD~1
- Confirm your branch is clean by running:
git log --oneline -3
You should see your CODEOWNERS commit at the top with no trace of the test commit.
Push was not rejected?
- Double check that you created the rule for the branch name pattern main (not master or another name).
- If you have admin privileges and the rule does not include administrators, it may not apply to you. Re-open the rule and ensure Do not allow bypassing the above settings is checked (if available), or that no bypass actors are listed.
- help me figure out why my branch protection rule is not blocking direct pushes
Main is now locked down. No one can push directly. Every change requires a pull request, an approval from your colleague, passing CI, resolved conversations, and a linear history. Next up, you will open a PR and hit every one of these walls firsthand.
Open a PR and Hit the Merge Wall
Your branch protection is live and direct pushes to main are rejected. Now it is time to see what happens when you try to merge through a pull request.
In this step, you will open a PR and experience the "merge wall" for the first time. Even after your colleague approves your code, you will discover that approval alone is not enough to merge.
In this step, get ready to:
- Create a feature branch with a new function and open a pull request.
- See CODEOWNERS automatically assign your colleague as a reviewer.
- Hit the merge wall and identify why merging is still blocked after approval.
Create a feature branch and add the farewell function
You will add a second function to src/app.js on a new branch, then open a PR to get it merged into main.
- Switch to your terminal and create a new branch by running this command:
git checkout -b add-farewell
- Open src/app.js in your editor and replace its contents with the following code:
// Returns a greeting message for the given name
function greet(name) {
return `Hello, ${name}!`;
}
// Returns a farewell message for the given name
function farewell(name) {
return `See you later, ${name}!`;
}
// Export both functions for use in other modules
module.exports = { greet, farewell };
What does this code do?
You added a farewell function that returns a goodbye message, mirroring the existing greet function.
The module.exports line now exports both functions so other files can use them.
- Save src/app.js.
- Stage, commit, and push your changes by running these commands:
git add src/app.js
git commit -m "Add farewell function"
git push origin add-farewell
You should see output confirming a new branch was pushed to your remote repository.
Push rejected or permission error?
- Make sure you are pushing to add-farewell, not main. Direct pushes to main are now blocked by your protection rule.
- Confirm you are on the correct branch by running git branch. The active branch should show add-farewell.
Still stuck? Why is my push to add-farewell being rejected?
- Open your repository on GitHub in the browser.
- Click the Compare & pull request banner that appears for your recently pushed branch.
- Leave the base branch as main and the compare branch as add-farewell.
- Enter a title like Add farewell function for the pull request.
- Click Create pull request.
No banner showing?
If you do not see the Compare & pull request banner, click the Pull requests tab, then click New pull request. Select add-farewell as the compare branch and main as the base.
✔️ Awesome, I've got everything!
Great. Double check you saved src/app.js and that your PR is now open on GitHub.
ⓧ I'd like to double check the full code
Here is the complete src/app.js file at this point:
function greet(name) {
return `Hello, ${name}!`;
}
function farewell(name) {
return `See you later, ${name}!`;
}
module.exports = { greet, farewell };
Observe CODEOWNERS auto-assignment and the blocked merge
Your CODEOWNERS file tells GitHub to automatically request a review from your colleague whenever files matching its patterns are changed. Since src/app.js matches the *.js pattern, your colleague should already be listed.
- On the PR page, look at the Reviewers section in the right sidebar. Your colleague should appear with a review request icon.
- Scroll down to the merge area at the bottom of the PR.
Before your colleague reviews, what do you think the merge button shows right now?
- Look at the merge area. You should see the merge button is blocked with a message indicating that a review is required.
- Wait for the CI status check to complete. After a minute or so, you should see a green checkmark next to the build check confirming your GitHub Actions workflow passed.
Why is the merge blocked even though CI passed?
Passing CI is only one of several requirements you configured. The branch protection rule also requires at least one approving review from a code owner. Until your colleague approves, the merge button stays disabled.
CI check not appearing?
- The ci.yml workflow triggers on pull requests to main. Confirm your PR targets main as the base branch.
- If the check still does not appear after two minutes, click the Checks tab on the PR to see if the workflow was queued or errored.
Need help? Why isn't my CI status check showing up on my pull request?
Get approved but stay blocked
Now ask your colleague to review your PR. Here is the important part: they will approve it AND leave a line comment. You will see that approval alone does not unlock the merge button.
- Message your colleague and ask them to open your PR on GitHub.
- Ask them to click the Files changed tab.
- Ask them to click the + icon next to the farewell function line and type a comment like: "Consider making this message more formal."
- Ask them to click Start a review (not "Add single comment") so the comment becomes part of their review.
- Ask them to click Review changes at the top of the Files changed page, select Approve, then click Submit review.
Why both Approve and a line comment?
A reviewer can approve the overall PR while still leaving specific feedback on individual lines. The line comment creates an unresolved conversation. Your branch protection rule requires all conversations to be resolved before merging.
This means an approval and unresolved conversations can coexist, and the unresolved conversation alone is enough to block the merge.
Do you think the merge button is enabled now that your colleague has approved? Take a look.
- Return to the Conversation tab of your PR.
- Scroll down to the merge area at the bottom.
Even with an approval and a passing CI check, the merge button still shows that merging is blocked. The remaining blocker is the unresolved conversation your colleague left.
What should I see?
The merge area displays a blocked state with reasons listed. You have a green checkmark for CI and a green approval icon, but the unresolved conversation prevents merging.
This is the merge wall in action. Every single protection rule you configured must be satisfied before the merge button becomes available.
You have now hit the merge wall. Approval, passing CI, and code owner review are all in place, but a single unresolved conversation keeps the merge button locked. In the next step, you will resolve this conversation, push a fix, and navigate the full re-approval cycle to finally merge your code.
Resolve All Blocks and Merge
You have a PR that is approved yet still blocked. The merge button is greyed out because of an unresolved conversation, and you know that any new commit will dismiss the existing approval entirely.
In this step, you will push a fix (triggering stale review dismissal), resolve the conversation, get re-approved, and finally merge with a clean linear history. This is the full re-approval cycle that real teams navigate every day.
In this step, get ready to:
- Push a fix commit and observe the previous approval dismissed as stale.
- Resolve conversations and get re-approved by your colleague.
- Merge with squash to maintain linear history.
Push a fix commit and trigger stale review dismissal
Your colleague left a comment suggesting a change to your code. When you push a commit addressing that feedback, GitHub automatically dismisses the previous approval because the code has changed since it was reviewed.
- In your terminal, make sure you are on the add-farewell branch by running:
git checkout add-farewell
- Open src/app.js in your editor and update the farewell function to address your colleague's feedback.
The updated function should look like this:
function farewell(name) {
return `See you later, ${name}!`;
}
What does this change accomplish?
The specific wording change addresses your colleague's review comment. The important thing here is not the edit itself. It is the workflow that happens after you push: the existing approval gets dismissed.
- Save src/app.js.
- Stage, commit, and push your fix by running these commands:
git add src/app.js
git commit -m "Address review feedback on farewell message"
git push origin add-farewell
Before you check the PR page, what do you think happened to your colleague's approval now that new code has been pushed?
- Return to your pull request page on GitHub.
- Look at the review section. You should see that your colleague's previous approval has been dismissed as stale because you pushed new code.
- The merge button now shows Review required again.
Why did the approval disappear?
You enabled "Dismiss stale pull request approvals when new commits are pushed" in your branch protection rule. This ensures that every approval applies to the exact code that will be merged. Nobody can approve a PR and then sneak in extra changes afterwards.
Approval not dismissed?
If your colleague's approval still shows as active, confirm that the "Dismiss stale pull request approvals when new commits are pushed" checkbox is enabled in your branch protection rule under Settings > Branches.
Also verify that your push actually succeeded by checking that the new commit appears in the PR's commit list.
Why isn't the stale review dismissal working on my PR?
Resolve the conversation and get re-approved
Your PR is now blocked for two reasons: the approval was dismissed, and the original conversation thread is still unresolved. You need to clear both blocks.
- On the PR page, scroll to your colleague's line comment.
- Click Resolve conversation on the comment thread.
The conversation block is now cleared. The remaining block is the missing approval.
- Ask your colleague to open the PR and submit a new review.
- Your colleague should click Files changed, then click Review changes.
- They should select Approve and click Submit review.
After your colleague re-approves, check the merge area of the PR. All checks should now pass: CI is green, conversations are resolved, and the approval is in place.
What is the full re-approval cycle?
You just completed a full re-approval cycle. You pushed a fix, which dismissed the stale approval. You resolved the conversation thread. Then your colleague reviewed the updated code and approved again.
On real teams, this cycle can repeat multiple times per PR. Each round of feedback, fix, and re-approval ensures the final merged code has been explicitly signed off.
Merge still blocked after re-approval?
If the merge button is still greyed out, check each requirement listed under the merge button. Look for any remaining unresolved conversations you may have missed, or confirm the CI workflow has finished running on your latest commit.
Help me figure out why my PR merge is still blocked after approval.
Squash merge and verify linear history
Because you enabled "Require linear history" in your branch protection rule, GitHub removes the standard "Create a merge commit" option. Only Squash and merge and Rebase and merge are available.
- Click the merge button dropdown on the PR page.
- Select Squash and merge.
- Click Confirm squash and merge.
Why squash merge?
Squash merge takes all the commits on your feature branch (the initial farewell commit, the fix commit) and combines them into a single commit on main. This keeps the main branch history clean and readable. Each merged PR becomes one line in the log.
Your PR now shows status Merged with the full review timeline visible: initial approval, stale dismissal, conversation resolved, re-approval, and squash merge.
- Pull the merged changes to your local main branch by running these commands:
git checkout main
git pull origin main
What do you expect to see in the git log? Think about how many commits the squash merge created on main.
- Verify the linear history by running:
git log --oneline
You should see a single squash-merge commit for the farewell feature. Despite having multiple commits on your branch (the original and the fix), main received just one clean commit.
Seeing multiple commits for the farewell feature?
If you see separate commits for the initial farewell and the fix, you may have used "Rebase and merge" instead of "Squash and merge." Rebase preserves individual commits while squash combines them. Both maintain linear history, but squash produces fewer commits on main.
Why do I see multiple commits on main after merging my PR?
Secret mission
Handle the "Branch Out of Date" Scenario
In real team environments, other PRs merge to main while yours is still in review. When "Require branches to be up to date" is enabled, your branch falls behind and must be updated before merging. Can you engineer this scenario and navigate the extra re-approval cycle it triggers?
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely on GitHub with a free private repository, so there are no ongoing costs.
Resources you used:
- Private GitHub repository (branch-protection-lab).
- Local clone of the repository on your Desktop.
- GitHub Actions CI minutes (negligible, well within the free tier).
Keep everything running
No action needed. Choose this if you want to keep experimenting with additional branch protection rules, rulesets, or invite more collaborators to practice multi-reviewer workflows.
- Your branch-protection-lab repository stays accessible on GitHub.
- Your local clone remains in your ~/Desktop/branch-protection-lab folder.
- Your colleague retains collaborator access until you remove them.
Pause - I'll come back to this later
Archive the repository to make it read-only. This preserves all your branch protection rules, PR history, and CODEOWNERS file while signalling that the repo is not actively maintained.
- On GitHub, navigate to your branch-protection-lab repository.
- Click Settings.
- Scroll down to the Danger Zone section.
- Click Archive this repository.
- Type the repository name to confirm, then click I understand the consequences, archive this repository.
- You can unarchive later from the same Settings page if you want to resume experimenting.
Delete - I don't want to use this again
Remove all project resources and start fresh. This permanently deletes the repository and all its history.
- Delete the GitHub repository:
- On GitHub, navigate to your branch-protection-lab repository.
- Click Settings.
- On the General settings page, scroll down to the Danger Zone section.
- Click Delete this repository.
- Click I want to delete this repository.
- Click I have read and understand these effects.
- Type branch-protection-lab to confirm, then click Delete this repository.
Delete the local clone by running the appropriate command for your operating system:
macOS
rm -rf ~/Desktop/branch-protection-lab
Windows
rmdir /s /q %USERPROFILE%\Desktop\branch-protection-lab
Nice Work!
Nice Work!
You just navigated an entire protected branch workflow from start to finish. Every safeguard that real engineering teams rely on to protect production code fired, blocked your merge, and you resolved each one.
You've learned how to:
- Configure a branch protection rule with 7 interconnected settings that enforce approvals, stale review dismissal, code owner reviews, conversation resolution, status checks, up-to-date branches, and linear history.
- Create a CODEOWNERS file that automatically assigns the correct reviewer based on file patterns whenever a pull request is opened.
- Navigate the full re-approval cycle: push a fix, watch stale review dismissal revoke your approval, resolve conversations, get re-approved, and merge with a clean linear history using squash merge.
- Secret Mission: Handled the "branch out of date" scenario where main advances while your PR is in review, triggering yet another re-approval cycle.
Ready to quiz yourself?