Branch: your isolated copy of the code where you develop a feature or fix
PR description: answers three questions — what changed, why it changed, how to test it
Code review: at least one teammate reads your diff line by line before approving
Merge strategy: how your commits land on main — merge commit, squash, or rebase
Branch protection: rules that enforce reviews, tests, and prevent direct pushes to main
✦ Definition~90s read
What is GitHub Pull Requests and Code Review?
Before you can open a Pull Request, you need to understand branches — because a PR is really just a request to merge one branch into another.
★
Imagine you're writing a chapter for a shared school textbook.
Think of the main branch (often called main or master) as the official published version of your project — the printed textbook. A branch is your personal draft copy where you can experiment, write new features, or fix bugs without touching the published version. Once your work is ready and reviewed, you merge your branch back into main.
Here's the workflow in plain English: you copy the current state of main into a new branch, make your changes there, push that branch to GitHub, and then open a Pull Request to say 'hey team, I've made these changes on my branch — can someone review them before we include them in main?'
The branch name should describe the work you're doing. fix-login-button-alignment is a great branch name. my-branch or test123 will confuse everyone including future you. One branch per feature or bug fix is the golden rule — keep your PRs focused and small.
At production scale, branch naming conventions matter for automation. Teams use prefixes like feature/, fix/, hotfix/, and chore/ so CI/CD pipelines can auto-assign reviewers, auto-label PRs, and trigger different test suites based on branch type. A hotfix/ branch might skip integration tests and deploy directly to staging, while a feature/ branch runs the full suite.
Plain-English First
Imagine you're writing a chapter for a shared school textbook. Before your chapter gets printed, your teacher reads it, suggests edits, and only adds it to the book once everyone agrees it's good. A Pull Request is exactly that — you write your code on your own copy, then formally ask your teammates to read it, suggest changes, and approve it before it becomes part of the main project. The 'review' is the feedback your teacher gives you.
⚙ Browser compatibility
Latest versions — ✓ supported
Chrome
Firefox
Safari
Edge
✓
✓
✓
✓
Every professional software team on the planet shares code. Not by emailing zip files or copying folders — they use a structured process where every single change gets proposed, discussed, and approved before it touches the production codebase. GitHub Pull Requests are the mechanism that makes this possible, and they're the beating heart of collaborative software development at companies like Google, Netflix, and Spotify.
Without a review process, one developer's typo can break the app for every user at 2am on a Friday. One misunderstood requirement can send a team down the wrong path for a week. Pull Requests solve this by creating a formal checkpoint: before any code merges into the main branch, at least one other human looks at it. That human catches bugs, asks clarifying questions, and ensures the change actually fits the bigger picture.
The merge strategy you choose — merge commit, squash, or rebase — directly affects how debuggable your history is six months later when you're chasing a regression at 3am. Branch protection rules are what enforce the review process; without them, anyone can push directly to main.
By the end of this article you'll know exactly what a Pull Request is, how to open one from scratch on GitHub, how to leave a code review that your teammates will actually appreciate, and how to avoid the three mistakes that make junior developers cringe in retrospect. You'll also have real terminal commands you can run right now, today.
What Is a Pull Request — The Fundamentals
A Pull Request (PR) is a request to merge changes from one branch into another, typically from a feature branch into main or develop. PRs are the collaboration layer on top of Git — Git handles the merge mechanics; the PR platform (GitHub, GitLab, Bitbucket) handles the review workflow.
The PR lifecycle: 1. You create a feature branch, make commits, and push. 2. You open a PR on GitHub proposing to merge your branch into main. 3. Teammates review the diff, leave comments, request changes. 4. You update the branch (push new commits) in response. 5. CI runs tests on the merged state. 6. A maintainer approves and merges the PR.
What makes a good PR: - Small scope — One logical change. A PR touching 40 files is hard to review and likely contains unrelated changes. - Descriptive title and description — What changed, why, and how to verify. - Clean commit history — Squash messy commits before the PR merge. - CI passing — Every PR should have automated checks before review starts.
Code review principles: - Review code, not the author. Comments should be about the code. - Ask questions ('Why is this null check needed?') rather than making demands ('Remove this'). - Approve when the code is correct, not when it matches your style.
01_pr_workflow.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Create a feature branch and push it
git checkout -b feat/user-auth
echo 'Auth logic' > auth.py
git add . && git commit -m 'feat: add user authentication'
# Push to remote (GitHub) — now you can open a PR
git push -u origin feat/user-auth
# Review: check what your PR will show
git log main..feat/user-auth --oneline
git diff main..feat/user-auth --stat
# AfterPR approval, merge (squash-merge keeps main linear)
# This is done via GitHub's UI, not CLI
# After merge: delete the feature branch locally and remotely
git checkout main && git pull
git branch -d feat/user-auth
git push origin --delete feat/user-auth
Output
Enumerating objects: 4, done.
Counting objects: 100% (4/4)
* [new branch] feat/user-auth -> feat/user-auth
b2c3d4e feat: add user authentication
auth.py | 10 ++++++++++
1 file changed, 10 insertions(+)
💡Draft PRs for Early Feedback
GitHub Draft PRs signal 'this isn't ready to merge but I'd like early feedback on the approach.' Use them when you're unsure about design decisions but confident enough to share code. Convert to ready-for-review when CI passes and you believe the PR is complete.
📊 Production Insight
PR descriptions should include a testing section: 'How I verified this change' and a rollout plan: 'Can this be safely reverted? Any migrations?' Teams that mandate these sections reduce rollout incidents by 40%. The PR is not just a code review — it's the deployment plan documentation.
🎯 Key Takeaway
A PR is a request to merge branches with a code review layer. Good PRs are small, descriptive, and CI-verified. Review for correctness, not style. The PR description is your deployment plan documentation.
thecodeforge.io
Github Pull Requests Code Review
What Is a Branch and Why You Need One Before a Pull Request
Before you can open a Pull Request, you need to understand branches — because a PR is really just a request to merge one branch into another.
Think of the main branch (often called main or master) as the official published version of your project — the printed textbook. A branch is your personal draft copy where you can experiment, write new features, or fix bugs without touching the published version. Once your work is ready and reviewed, you merge your branch back into main.
Here's the workflow in plain English: you copy the current state of main into a new branch, make your changes there, push that branch to GitHub, and then open a Pull Request to say 'hey team, I've made these changes on my branch — can someone review them before we include them in main?'
The branch name should describe the work you're doing. fix-login-button-alignment is a great branch name. my-branch or test123 will confuse everyone including future you. One branch per feature or bug fix is the golden rule — keep your PRs focused and small.
At production scale, branch naming conventions matter for automation. Teams use prefixes like feature/, fix/, hotfix/, and chore/ so CI/CD pipelines can auto-assign reviewers, auto-label PRs, and trigger different test suites based on branch type. A hotfix/ branch might skip integration tests and deploy directly to staging, while a feature/ branch runs the full suite.
io/thecodeforge/git/create_feature_branch.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Make sure you start from an up-to-date main branch
# This avoids merge conflicts caused by working on stale code
git checkout main
git pull origin main
# Create a new branch with a descriptive name that explains the work
# 'checkout -b' creates the branch AND switches to it in one step
git checkout -b feature/add-user-profile-page
# Confirm you are now on the new branch — the asterisk (*) marks the active branch
git branch
# --- Make your changes to the code here ---
# Forthis example, we'll create a new file to simulate real work
echo "<h1>User Profile Page</h1>" > user-profile.html
# Stage the new file — 'add' tells Git to track this file in the next commit
git add user-profile.html
# Commit with a message that explains WHAT changed and WHY
# Present tense, imperative mood is the professional standard: 'Add' not 'Added'
git commit -m "Add initial user profile page HTML structure"
# Pushthis branch up to GitHub so others can see it
# '-u origin' links your local branch to the remote GitHubbranch (only needed first time)
git push -u origin feature/add-user-profile-page
Output
Already on 'main'
Your branch is up to date with 'origin/main'.
Switched to a new branch 'feature/add-user-profile-page'
* feature/add-user-profile-page
main
[feature/add-user-profile-page a3f92c1] Add initial user profile page HTML structure
1 file changed, 1 insertion(+)
create mode 100644 user-profile.html
Branch 'feature/add-user-profile-page' set up to track remote branch 'feature/add-user-profile-page' from 'origin'.
To https://github.com/your-username/your-project.git
Branch names should describe the work: fix-login-button-alignment, not my-branch
Use prefixes: feature/, fix/, hotfix/, chore/ — CI/CD pipelines can use these for automation
One branch per feature or bug fix — keep PRs focused and small
Never work directly on main — always branch off, even for one-line fixes
📊 Production Insight
The most common branch failure in production is working directly on main without branching. When a developer pushes a broken commit to main, every other developer who pulls main gets the broken code. CI/CD deploys the broken code to staging. The fix takes 10 minutes, but the disruption to the team takes an hour. Branch protection rules — requiring PRs before merging to main — prevent this entirely. The second most common failure is stale branches: a developer branches off main, works for 2 weeks, and by the time they open a PR, main has diverged so far that the merge conflict resolution takes longer than the original feature. The fix: rebase onto main daily, not just before opening the PR.
🎯 Key Takeaway
One branch per feature, never work directly on main. Rebase onto main daily to avoid merge conflict nightmares. Branch naming conventions (feature/, fix/, hotfix/) enable CI/CD automation. If your branch is older than 2 weeks, rebase before opening the PR.
Branch Creation Decision Tree
IfStarting new work that will take more than 1 commit
→
UseCreate a feature branch: git checkout -b feature/descriptive-name
IfFixing a bug reported in production
→
UseCreate a fix branch from main: git checkout -b fix/issue-description
IfEmergency production fix needed immediately
→
UseCreate a hotfix branch from main: git checkout -b hotfix/critical-issue. Deploy through PR but with expedited review.
IfYou are already on main and have uncommitted changes
→
UseStash changes first: git stash. Then create branch: git checkout -b feature/name. Then unstash: git stash pop.
IfYour branch is 2+ weeks old and main has diverged significantly
→
UseRebase onto main before opening PR: git fetch origin && git rebase origin/main. Resolve conflicts locally.
How to Open a Pull Request on GitHub Step by Step
Once your branch is pushed to GitHub, opening the Pull Request takes about two minutes — but writing a good PR description takes a bit more thought, and that effort pays off every single time.
After you push a branch, GitHub usually shows a yellow banner on the repository page saying 'Your recently pushed branch... Compare & pull request'. Click that button. If the banner is gone, click the 'Pull requests' tab, then 'New pull request', and select your branch from the dropdown.
The PR form has three key parts: the title, the description, and the reviewers. The title should be a one-line summary of what the change does. The description is where you explain the context — why does this change exist? What does it do? How can the reviewer test it? A template like 'What, Why, How to Test' makes this systematic.
Assigning reviewers is critical. GitHub lets you request specific people to review your code. In a team setting, at least one approval is typically required before merging. You can also add labels ('bug', 'enhancement'), link to a related issue, and mark a PR as a 'Draft' if it's not ready for review yet — that's a great way to share work-in-progress and get early feedback without it accidentally getting merged.
At senior levels, the PR description is also where you document your design decisions. If you chose Event Sourcing over CRUD, explain why. If you skipped a test type, explain why. Future developers (including future you) will read the PR description when investigating why a piece of code exists. A PR with no description is a black box six months later.
## WhatDoesThisPRDo?
<!-- One or two sentences. What is the end result of merging this branch? -->
Adds an initial HTML structure for the UserProfile page, which will display
the logged-in user's name, avatar, and recent activity feed.
## WhyIsThisChangeNeeded?
<!-- Link to the issue or explain the business reason -->
Addresses issue #47 — users currently have no dedicated profile page.
This is the first step in the Q3 personalisation milestone.
## How to TestIt
<!-- Tell the reviewer exactly what steps to follow to verify the change works -->
1. Pullthis branch locally: `git fetch && git checkout feature/add-user-profile-page`
2. Open `user-profile.html` in a browser
3. Confirm the heading 'User Profile Page' renders correctly
4. Confirm no existing pages are broken by checking `index.html` still loads
## DesignDecisions (if applicable)
<!-- DocumentWHY you chose this approach over alternatives -->
- Chose server-side rendering over client-side SPAforthis page because
the profile data is static and SEO is a requirement.
- UsedPostgreSQL `jsonb` column for user preferences instead of a separate
table because the data is read-heavy and rarely queried independently.
## RiskAssessment
<!-- For changes touching critical paths -->
- Risk level: Low — this is a new page with no dependencies on existing flows.
- Rollback plan: revert thisPR. No database migrations involved.
## Screenshots (if relevant)
<!-- Drag and drop images here — essential for any UI changes -->
[Before: no profile page exists]
[After: /user-profile.html shows heading]
## Checklist
- [x] I have tested this change locally
- [x] I have added comments to complex code sections
- [ ] I have updated the documentation (not needed forthis change)
- [x] Nonew console errors or warnings introduced
Output
-- This is a Markdown template, not runnable code --
When pasted into the GitHub PR description box, GitHub renders it as:
A formatted description with bold headings, a numbered test list,
an image drop zone, and interactive checkboxes that teammates
can tick as they verify each item.
🔥Draft PRs Are Your Secret Weapon
Draft PRs cannot be merged accidentally — GitHub enforces this
Use Draft PRs to get architecture feedback before writing all the code
Convert to 'Ready for Review' when done — GitHub auto-notifies assigned reviewers
Draft PRs are especially useful for large features where you want early buy-in on the approach
📊 Production Insight
The single biggest thing that slows down code reviews is a bad PR description. When a reviewer opens a PR and sees 'fixed stuff' or no description at all, they have to read every line of code to understand what changed and why. This turns a 10-minute review into a 45-minute archaeology session. The fix: spend 5 minutes writing a description that answers what, why, and how to test. This saves the reviewer 30 minutes and gets your code merged faster. At the team level, PR description templates enforced through GitHub's .github/PULL_REQUEST_TEMPLATE.md file ensure every PR follows the same structure. The second biggest bottleneck is missing reviewers — a PR with no assigned reviewers sits in limbo. Always assign at least one reviewer when you open the PR.
🎯 Key Takeaway
A PR description answers three questions: what changed, why it changed, how to test it. Skipping this is the single biggest thing that slows down code reviews. Use Draft PRs for early feedback. Always assign at least one reviewer. At senior level, document design decisions and risk assessments in the PR description.
PR Description Decision Tree
IfPR touches only one file with a clear, self-explanatory change
→
UseMinimal description is acceptable: one-line summary + link to issue. Add screenshots if UI changed.
IfPR touches multiple files or introduces a new pattern
→
UseFull description required: what, why, how to test, design decisions. Include a risk assessment.
IfPR touches a critical path (auth, payments, data migrations)
→
UseFull description + risk assessment + rollback plan. Tag a senior reviewer even if not required by branch rules.
IfPR is a work-in-progress and you want early feedback
→
UseOpen as Draft PR. Description can be lighter — focus on 'here is what I am thinking, does this approach make sense?'
IfPR has no description and reviewer asks clarifying questions
→
UseStop. Write the description before answering questions. Every question the reviewer asks is a question the description should have answered.
thecodeforge.io
Github Pull Requests Code Review
How to Do a Code Review That Actually Helps
Being asked to review code is a responsibility, not a chore. A good review catches real bugs, shares knowledge across the team, and makes the codebase better. A bad review is either rubber-stamping everything or leaving vague, discouraging comments.
On GitHub, click the 'Files changed' tab in a PR to see a side-by-side diff — the red lines are what was removed, the green lines are what was added. You can click the '+' icon that appears when you hover over any line to leave an inline comment on that specific line.
There are three things to look for when reviewing: correctness (does it actually work?), clarity (can I understand what this code does in 30 seconds?), and consistency (does it follow the patterns the rest of the codebase uses?).
When you leave a comment, be specific and constructive. Instead of 'this is wrong', say 'this function will throw a null reference error if the user has no profile photo set — what if we add a fallback here?' GitHub lets you prefix comments with Nitpick: for minor style preferences so the author knows what's critical versus optional.
When you're done reviewing, click 'Review changes' and choose one of three options: Comment (general feedback, no decision), Approve (looks good to merge), or Request Changes (specific issues that must be fixed first). Only approve when you'd genuinely be comfortable with this code shipping.
At the senior level, code review is also about architectural consistency. Does this new service follow the same error handling pattern as the rest of the codebase? Does it use the same logging format? Does it introduce a new dependency that the team needs to evaluate? These are the questions that separate a senior review from a junior review.
// This is the kind of code you might review in a PR// The inline comments below simulate what a good reviewer would flag// REVIEWER COMMENT on line 3:// 'getUserData' is too vague. What kind of data? Rename to 'fetchUserProfileById'// to make the intent clear without reading the implementation.functiongetUserData(id) {
// REVIEWER COMMENT on line 7:// No input validation here. What happens if 'id' is null, undefined,// or a negative number? This will hit the API and potentially return// confusing errors. Suggest adding: if (!id || id <= 0) return null;
const apiUrl = `https://api.example.com/users/${id}`;returnfetch(apiUrl)
.then(function(serverResponse) {
// REVIEWER COMMENT on line 14:// We should check serverResponse.ok before calling .json()// If the API returns a 404 or 500, .json() will still run// and we'll get a misleading error, not a clear 'user not found'.return serverResponse.json();
})
.then(function(userData) {
return userData;
})
// REVIEWER APPROVE NOTE:// Good job adding a .catch() — error handling is often forgotten.// Consider logging the error to your monitoring service here too.
.catch(function(networkError) {
console.error('Failed to fetch user profile:', networkError);
returnnull;
});
}
// IMPROVED VERSION after review feedback:functionfetchUserProfileById(userId) {
// Guard clause — fail fast with a clear reasonif (!userId || userId <= 0) {
console.warn('fetchUserProfileById called with invalid userId:', userId);
returnPromise.resolve(null);
}
const profileApiUrl = `https://api.example.com/users/${userId}`;returnfetch(profileApiUrl)
.then(function(serverResponse) {
// Explicitly check for HTTP errors before parsing the bodyif (!serverResponse.ok) {
thrownewError(`User profile API returned status: ${serverResponse.status}`);
}
return serverResponse.json();
})
.then(function(userProfileData) {
return userProfileData;
})
.catch(function(networkError) {
console.error('Failed to fetch user profile:', networkError.message);
returnnull;
});
}
Output
-- No runtime output for this example --
The value here is the inline review comments that demonstrate
Never click 'Approve' on a PR just to be friendly or avoid conflict. Your approval is a signal to the team that the code is safe to ship. If you find a PR too large to review properly, leave a comment saying 'This PR is too large for me to review thoroughly — can we break it into smaller pieces?' That's more helpful than a hollow approval.
📊 Production Insight
The most damaging code review anti-pattern is rubber-stamping: clicking 'Approve' without actually reading the code. This happens when reviewers are overloaded, when the PR is too large, or when the team culture treats reviews as a formality. The result: bugs reach production that a 10-minute review would have caught. The fix: enforce review quality through branch protection rules (require at least one approval), keep PRs small (200-400 lines), and create a team norm where 'Request Changes' is expected and not taken personally. The second anti-pattern is scope creep in reviews: a reviewer sees a PR to fix a login bug and leaves comments about the CSS on a completely unrelated page. Keep review comments scoped to the PR's stated purpose. If you see unrelated issues, open a separate issue or PR — do not hijack the current review.
🎯 Key Takeaway
Review for correctness, clarity, and consistency. Be specific and constructive — explain the WHY, not just 'this is wrong'. Never rubber-stamp approvals. If a PR is too large, say so and ask for it to be split. Approval means 'I'd be comfortable if this shipped right now.' Use Nitpick prefix for optional style feedback.
Code Review Decision Tree
IfPR is small (<100 lines), change is clear, tests pass
→
UseReview for correctness and consistency. If no issues: Approve. If minor style issues: Comment with Nitpick prefix, then Approve.
IfPR is medium (100-400 lines), touches multiple files
→
UseReview for correctness, clarity, consistency, and test coverage. Check for edge cases. If issues found: Request Changes with specific line references.
IfPR is large (400+ lines) or touches critical paths (auth, payments, DB migrations)
→
UseRequest the author to split the PR. If splitting is not possible, schedule a pair review session. Do not rubber-stamp.
IfReviewer disagrees with the author's approach
→
UseLeave a comment explaining your concern and suggesting an alternative. Use 'I'd suggest...' not 'You should...'. If the author explains their reasoning and it is sound, Approve.
IfPR has been open for 5+ days with no resolution
→
UseEscalate to tech lead. A stale PR blocks the team. Either merge with a follow-up issue, or close and rework.
Merging a Pull Request and Keeping History Clean
Once a PR has the required approvals and all CI checks pass (tests, linting, etc.), it's ready to merge. GitHub gives you three merge strategies and picking the right one matters for keeping your Git history readable.
'Create a merge commit' preserves every commit from your branch and adds a merge commit on top. This is great for long-running features where you want to see the full development history.
'Squash and merge' takes all your commits and compresses them into a single commit on main. This is perfect for small features or bug fixes where your branch had messy 'WIP' or 'fix typo' commits that aren't worth preserving. The result is a clean, linear history.
'Rebase and merge' replays your branch commits directly on top of main without a merge commit, keeping history linear. It's the cleanest option but can cause confusion if your branch had public commits others were building on.
After merging, always delete the feature branch — GitHub shows a 'Delete branch' button right after the merge. Stale branches pile up fast and confuse the whole team. Locally, run git branch -d to clean up too.
If the PR has merge conflicts — meaning main changed in ways that clash with your branch — you'll need to resolve them before merging. GitHub can handle simple conflicts in the browser, but for complex ones, resolve them locally.
At the production level, the merge strategy you choose affects how debuggable your history is. When you're chasing a regression at 3am using git bisect, a clean linear history (from squash or rebase) makes bisect dramatically faster. A branched history with 50 merge commits makes bisect nearly unusable because each merge commit is a diff of diffs.
io/thecodeforge/git/merge_and_cleanup.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# --- After your PR is approved and merged on GitHub ---
# Switch back to main and pull the freshly merged changes
git checkout main
git pull origin main
# Confirm your feature branch changes are now part of main
# The --oneline flag shows a compact one-line-per-commit view
git log --oneline -5
# Delete the local feature branch — the work is done, the branch is no longer needed
# '-d' (lowercase) is safe: it refuses to delete if the branch hasn't been merged yet
git branch -d feature/add-user-profile-page
# Confirm the branch is gone from your local machine
git branch
# --- If you need to resolve a merge conflict before merging ---
# First, pull the latest main into your feature branch to surface the conflict locally
git checkout feature/add-user-profile-page
git fetch origin
git merge origin/main
# Git will tell you which files have conflicts, e.g.:
# CONFLICT (content): Merge conflict in user-profile.html
# Open that file — you'll see conflict markers like this:
# <<<<<<< HEAD (your changes)
# <h1>UserProfile</h1>
# ======= (what's in main)
# <h1>UserDetails</h1>
# >>>>>>> origin/main
# Edit the file to keep the correct version, then:
git add user-profile.html
git commit -m "Resolve merge conflict: keep User Profile heading from feature branch"
git push origin feature/add-user-profile-page
# Now your PR on GitHub can be merged cleanly
# --- Verifying merge strategy on GitHub ---
# Check what merge options are available:
# Go to repo Settings > General > PullRequests
# Options: Allow merge commits, Allow squash merging, Allow rebase merging
# Enable the ones your team uses, disable the rest to enforce consistency
Output
Switched to branch 'main'
Your branch is up to date with 'origin/main'.
Already up to date.
a3f92c1 Add initial user profile page HTML structure
Squash and Merge collapses all branch commits into one clean commit on main
Use for small features and bug fixes where individual commit history doesn't matter
Do NOT squash when commit history is important for debugging (e.g., refactors where each step matters)
After merging, always delete the feature branch — stale branches confuse the team
📊 Production Insight
The merge strategy you choose has a direct impact on debugging speed six months later. When you're chasing a regression using git bisect, a clean linear history (from squash or rebase) makes bisect fast and reliable — each commit is a meaningful unit of change. A branched history with dozens of merge commits makes bisect nearly unusable because merge commits are diffs-of-diffs, and bisect can land on a merge commit that represents 30 changes at once. The trade-off: squash loses the individual commit history, which can be valuable for understanding the development process of a complex feature. The rule: squash for small, self-contained changes. Merge commit for large, multi-developer features where the commit-by-commit history is part of the documentation. The second production issue is stale branches: after merging, if you don't delete the branch, it stays in the remote. After 6 months, the repo has 200 stale branches and nobody knows which ones are safe to delete. Configure GitHub to auto-delete branches after merge (Settings > General > 'Automatically delete head branches').
🎯 Key Takeaway
Squash for small features and bug fixes — keeps main's history clean. Merge commit for large features where commit-by-commit history matters. Rebase for linear history with preserved commits. Always delete branches after merging. The merge strategy you choose affects how debuggable your history is when chasing regressions at 3am.
Merge Strategy Decision Tree
IfSmall feature or bug fix with messy WIP commits
→
UseSquash and Merge. One clean commit on main. History stays linear and readable.
IfLarge feature with meaningful commit-by-commit history
→
UseMerge Commit. Preserves the full development history. Useful when the commit messages document the evolution of the feature.
IfYou want linear history but individual commits matter
→
UseRebase and Merge. Replays commits on top of main without a merge commit. Cleanest history but use only when the branch has clean, meaningful commits.
IfBranch has merge conflicts with main
→
UseResolve locally first: git fetch origin && git merge origin/main. Fix conflicts, push, then merge on GitHub. Do not resolve complex conflicts in the GitHub web editor.
IfPR is merged and branch is no longer needed
→
UseDelete the branch immediately. On GitHub: click 'Delete branch' after merge. Locally: git branch -d branch-name. Configure GitHub to auto-delete branches after merge.
Why Azure DevOps Beats GitHub for PR Code Reviews in Enterprise
Azure DevOps isn't just a Microsoft tax. It's a fundamentally different code review architecture. GitHub treats pull requests as discussion threads with merge buttons. Azure DevOps treats them as state machines with enforceable governance.
If you've ever had a junior bypass required reviews by merging stale approvals, you'll appreciate Azure DevOps's branch policies. These aren't optional decorations — they block push access when vote thresholds aren't met, required reviewers haven't approved, or linked work items are missing. GitHub's branch protection rules are a joke by comparison; they don't enforce sequential approvals or vote types.
The vote system alone is worth the migration. Instead of GitHub's binary approve/request-changes, Azure DevOps gives you five vote options: Approved, Approved with Suggestions, Wait for Author, Rejected, and No Vote. Each maps to a numeric value that policies can aggregate. Rejected? Blocked until resolved. Wait for Author? The PR pauses until the author resets state. This prevents the silent-non-approval problem where no one objects but nothing merges.
azure-branch-policy.ymlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// io.thecodeforge — devops tutorial
// Enforce minimum approvers + auto-complete on success
resources:
repositories:
- repository: paymentService
type: git
name: PaymentService
policies:
branch: main
requiredReviewers:
minimumApproverCount: 2
allowSelfApproval: false
voteThreshold: 4 // Must hit 'Approved' (10) or 'Approved with
// Suggestions' (5) from 2 different people
commentResolution:
requireAllResolved: true
linkedWorkItems:
allowed: true
matchFields: ['System.Title', 'System.WorkItemType']
autoComplete:
enabled: true
squashMerge: true
Output
Branch policy applied to main.
Requires 2 non-self approvals.
Comments must be resolved before merge.
Work item must be linked.
Auto-complete enabled with squash merge.
⚠ Enterprise Trap:
Setting 'allowSelfApproval: false' stops the subtle pattern where a tech lead approves their own bugfix PR after a teammate's review expires. If someone needs to push directly, that's what emergency policies — not sloppy defaults — are for.
🎯 Key Takeaway
Azure DevOps branch policies enforce review completeness at the server level. GitHub lets you sidestep them with a config toggle.
Why Your PR Needs a Real Description — Not Just a Title
Every pull request is a contract. You're asking a team to trust your changes. Without a proper description, you're forcing reviewers to guess intent. That's lazy and wastes everyone's time.
A good PR description answers three questions: What changed? Why? How was it tested? Write it as if you're explaining to a junior dev who will have to fix your code in six months. Include screenshots for UI changes. Link to the ticket. Show before/after logs if it's a bug fix.
If you can't explain your PR in two paragraphs, you don't understand your own change well enough. Stop coding. Start thinking. Then write. Your reviewer will thank you with faster approvals and fewer nitpicks.
pr-description-example.ymlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// io.thecodeforge — devops tutorial
name: PRDescriptionTemplate
title: "fix: prevent timeout on user uploads #1234"
body: |
## What
- Increased upload timeout from 30s to 120s
- Added retry logic with exponential backoff
## WhyUsers with slow connections hit timeout during large file uploads.
Customer complaints spiked 340% last month.
## Testing
- Unit tests: pass
- Manual: uploaded 200MB file over throttled connection
- Screenshot: upload-complete.png attached
Output
Pull request created with structured body.
⚠ Production Trap:
Empty descriptions get reverted. If a reviewer can't figure out why a change exists, they'll hold the merge. You lose time and trust.
🎯 Key Takeaway
Every PR without a description is a hostage situation for your reviewer.
Code Review Etiquette — The Rules Nobody Tells Juniors
Code review isn't about proving you're smarter than the author. It's about shipping reliable software as a team. If your review contains more ego than substance, you're the problem.
Start with positive intent. Assume the author did their best. If something is wrong, state the problem first, not your preferred solution. Instead of 'Rewrite this with a switch statement', say 'This if-else chain is hard to follow. A switch might be clearer.'
Never approve a PR with unresolved open threads. That's not kindness — it's negligence. Authors: if the reviewer compliments a section, say thanks. If they suggest a change, accept it or explain why not. Don't ghost. And for the love of everything, use 'Request Changes' when the merge would break production. 'Comment' is for nits. 'Approve' is for ready.
Speed matters. Review within 24 hours or tell the author when you'll get to it. Blocking a PR for a week because you're busy is a dick move.
review-etiquette-rules.ymlYAML
1
2
3
4
5
6
7
8
9
10
11
12
// io.thecodeforge — devops tutorial
review_rules:
- author: always provide description and test steps
- reviewer:
- state the problem before the solution
- use "Request Changes"for blocking issues only
- respond within 24 hours or acknowledge delay
- both:
- resolve all threads before merge
- say thanks for good catches
- avoid "LGTM" without comment on why
Output
Team adopted review etiquette. PR cycle time dropped from 48h to 12h.
💡Senior Shortcut:
When you approve, leave a summary comment. 'Approved. The error boundary handles the edge case I raised earlier.' This leaves a clear audit trail.
🎯 Key Takeaway
Code reviews are for catching bugs, not inflating egos. Be direct, be kind, be fast.
● Production incidentPOST-MORTEMseverity: high
Force Push to Main: 47 Commits Lost, Production Deploy Blocked for 6 Hours
Symptom
A junior developer was working on a feature directly on the main branch (they didn't know about branches). When they tried to push, Git rejected it because their local copy was behind remote. They ran git push --force and overwrote 47 commits from 5 other developers. The CI/CD pipeline deployed the old code. Production broke within minutes.
Assumption
The junior developer assumed the force push was just 'updating their code to match what they had locally.' They didn't understand that force push replaces the remote history entirely — it doesn't merge, it overwrites. They also didn't know about branches, which would have isolated their work from main.
Root cause
1. The repository had no branch protection rules — anyone could push directly to main without a PR or review.
2. The junior developer had never been taught about branches — they were working directly on main.
3. When their push was rejected (because main had moved ahead), they googled 'git push rejected' and found --force as the solution.
4. The force push replaced the remote main branch history with their local history, which was 47 commits behind.
5. CI/CD automatically deployed the overwritten code to production.
6. The team had no backup strategy — the only recovery option was Git reflog on machines that still had the correct commits.
Fix
1. Immediate: identified the last known good commit SHA from the CI/CD pipeline logs and team members' local reflogs.
2. Recovered the lost commits using git reflog on the senior developer's machine (they had the most recent pull).
3. Force-pushed the correct history back to main from the recovered reflog.
4. Configured branch protection rules: require PR reviews, require CI checks to pass, disable force push to main.
5. Added a pre-push hook that warns when pushing to main without a PR.
6. Result: production restored in 6 hours. No data was permanently lost because team members had local copies.
Key lesson
Never force push to a shared branch — main, develop, or any branch others depend on. Force push replaces history; it does not merge.
Always work on a feature branch, never directly on main. One branch per feature is the golden rule.
Branch protection rules are not optional — they are the safety net that prevents catastrophic mistakes. Require PR reviews and CI checks before merging.
If you get a 'rejected' push error, the answer is never --force. The answer is git pull --rebase to incorporate remote changes first.
Production debug guideSystematic resolution paths for the most common PR and review failures in team workflows.5 entries
Symptom · 01
PR shows 50+ files changed and reviewers refuse to review it
→
Fix
1. The PR is too large — reviewers cannot review 50 files in one sitting.
2. Close the current PR. Split the work into 3-5 smaller PRs, each focused on one logical change.
3. Open the first small PR, get it reviewed and merged, then open the next.
4. Rule of thumb: if a PR takes more than 30 minutes to review, it is too large. Target 200-400 lines of diff per PR.
5. If the changes are truly inseparable, add a detailed PR description explaining why splitting is not possible, and offer to walk the reviewer through it on a call.
Symptom · 02
CI/CD pipeline fails on your PR but passes locally
→
Fix
1. Read the CI failure log — click the red 'X' on the PR and open the failing job.
2. Check for environment differences: CI runs on Linux, your machine may be macOS. Check for case-sensitive file paths, missing environment variables, or OS-specific dependencies.
3. Check for missing secrets: CI may not have access to API keys or tokens that are in your local .env file.
4. Reproduce locally: run the same commands CI runs (check the CI config file — .github/workflows/ for GitHub Actions).
5. If tests pass locally but fail in CI, the most common cause is timing-dependent tests (flaky tests) or missing test data setup.
Symptom · 03
PR has merge conflicts and GitHub says 'This branch has conflicts that must be resolved'
→
Fix
1. Do NOT resolve conflicts in the GitHub web editor for complex conflicts — it is error-prone.
2. Locally: git checkout your-branch && git fetch origin && git merge origin/main.
3. Open the conflicted files — Git marks conflicts with <<<<<<<, =======, >>>>>>> markers.
4. Decide which version to keep (or combine both), remove the markers, save the file.
5. Run git add on the resolved files, then git commit to complete the merge.
6. Push the updated branch — the PR on GitHub will update automatically.
Symptom · 04
Reviewer keeps requesting changes and the PR has been open for 2 weeks
→
Fix
1. This is a review bottleneck, not a code problem. Schedule a 15-minute pair review session with the reviewer.
2. Walk through the code together on a screen share — synchronous review is 5x faster than async comment threads.
3. If the reviewer is raising scope-creep concerns (adding new requirements not in the original ticket), escalate to the tech lead to clarify scope.
4. If the reviewer is unreachable, reassign to another reviewer. A stale PR blocks the entire team.
5. After merge, retro: why did this PR take 2 weeks? Was it too large? Was the reviewer overloaded? Fix the root cause.
Symptom · 05
PR was approved and merged but broke production within hours
→
Fix
1. Immediate: revert the merge commit. On GitHub, go to the PR and click 'Revert' — this creates a new PR that undoes the changes.
2. Merge the revert PR immediately to restore production.
3. Root cause analysis: why did the review not catch the bug? Was the test coverage insufficient? Was the reviewer unfamiliar with the code area?
4. Add a regression test that would have caught the bug.
5. Update the PR template to include a 'Risk Assessment' section for changes that touch critical paths.
★ Pull Request Triage Cheat SheetFast resolution paths for the most common PR failures. Use when a PR is blocking you or your team right now.
Merge conflict on PR — GitHub says 'cannot merge automatically'−
Why Azure DevOps Beats GitHub for PR Code Reviews in Enterpr
pr-description-example.yml
name: PR Description Template
Why Your PR Needs a Real Description
review-etiquette-rules.yml
review_rules:
Code Review Etiquette
Key takeaways
1
A Pull Request is a formal request to merge one branch into another
it's the review checkpoint that stands between your code and production, not just a Git feature.
2
A great PR description answers three questions
What changed, why it changed, and how to test it — skipping this is the single biggest thing that slows down code reviews.
3
Approve means 'I'd be comfortable if this shipped right now'
never click Approve to be polite; a hollow approval that lets a bug through is worse than a delayed review.
4
Squash and Merge keeps main's history readable by collapsing messy WIP commits into one clean entry
prefer it for small features and bug fixes unless individual commit history matters for debugging.
5
One branch per feature, never work directly on main. Rebase onto main daily to avoid merge conflict nightmares. Branch protection rules are not optional
they are the safety net.
6
Review for correctness, clarity, and consistency. Be specific and constructive. If a PR is too large, say so and ask for it to be split. Scope-creep in reviews hijacks the process.
INTERVIEW PREP · PRACTICE MODE
Interview Questions on This Topic
FAQ · 5 QUESTIONS
Frequently Asked Questions
01
What is the difference between a pull request and a merge request?
They're the same concept with different names. GitHub and Bitbucket call it a Pull Request (PR). GitLab calls it a Merge Request (MR). Both describe the same workflow: you propose merging one branch into another, get it reviewed, and then merge it once approved.
Was this helpful?
02
How many people need to approve a pull request before it can be merged?
That depends on the team's branch protection rules, which are configured in GitHub under Settings > Branches. Most professional teams require at least one approval, often two for critical repositories. As a beginner on a solo project, you can set it to zero — but getting into the habit of requesting reviews even from one peer dramatically improves code quality.
Was this helpful?
03
Can I keep committing to my branch after opening a pull request?
Yes, absolutely — and this is how it's meant to work. When you push new commits to your branch after the PR is open, GitHub automatically updates the PR with your new changes. Reviewers can see exactly what changed since their last review. This is how you address review feedback: make the fix locally, commit it, push, and the PR updates in real time.
Was this helpful?
04
What is --force-with-lease and why is it safer than --force?
--force pushes your local branch to remote and overwrites whatever is there, even if someone else pushed commits you haven't seen. --force-with-lease checks that the remote branch is at the commit you expect before pushing. If someone else pushed since your last fetch, --force-with-lease refuses and tells you. Always use --force-with-lease instead of --force when you must force push (e.g., after rebasing your branch).
Was this helpful?
05
How do I split a large PR into smaller ones?
Use interactive rebase: git rebase -i main. This lets you reorder, squash, or split your commits. Create separate branches for each logical group of changes using git cherry-pick or git checkout -b from specific commits. Open separate PRs for each branch and link them in the descriptions: 'This is part 2 of 3 — see PR #123 for part 1.'