Pull requests are where code becomes software. They're the gateway between individual work and shared codebases, the checkpoint for quality, and the primary collaboration touchpoint for development teams. This guide covers how to create, review, and merge pull requests effectively.
Anatomy of a Great Pull Request
┌─────────────────────────────────────────────────────────────┐
│ PULL REQUEST │
├─────────────────────────────────────────────────────────────┤
│ TITLE: [Type] Brief description of change │
│ feat: Add user authentication flow │
├─────────────────────────────────────────────────────────────┤
│ DESCRIPTION: │
│ ├── What changed and WHY │
│ ├── How to test │
│ ├── Screenshots (if UI) │
│ ├── Related issues: Fixes #123 │
│ └── Checklist │
├─────────────────────────────────────────────────────────────┤
│ METADATA: │
│ ├── Reviewers: @teammate │
│ ├── Labels: feature, needs-review │
│ ├── Milestone: v2.0 │
│ └── Project: Q1 Sprint │
├─────────────────────────────────────────────────────────────┤
│ CHECKS: │
│ ├── ✓ CI/CD Pipeline │
│ ├── ✓ Code Coverage │
│ ├── ✓ Security Scan │
│ └── ✓ Required Reviewers │
└─────────────────────────────────────────────────────────────┘
PR Title Conventions
Use consistent prefixes for scannable history:
| Prefix | Usage | Example |
|---|---|---|
feat: | New feature | feat: Add dark mode toggle |
fix: | Bug fix | fix: Resolve login timeout issue |
docs: | Documentation | docs: Update API reference |
refactor: | Code restructure | refactor: Extract auth logic to service |
test: | Test changes | test: Add unit tests for user service |
chore: | Maintenance | chore: Update dependencies |
perf: | Performance | perf: Optimize database queries |
style: | Formatting | style: Fix linting errors |
Pull Request Templates
Create .github/PULL_REQUEST_TEMPLATE.md in your repository:
## Description
<!-- What changed and why? Link to design docs if applicable -->
## Type of Change
- [ ] Bug fix (non-breaking change fixing an issue)
- [ ] New feature (non-breaking change adding functionality)
- [ ] Breaking change (fix or feature causing existing functionality to change)
- [ ] Documentation update
## How Has This Been Tested?
<!-- Describe the tests you ran and how to reproduce -->
- [ ] Unit tests
- [ ] Integration tests
- [ ] Manual testing
## Screenshots (if applicable)
<!-- Add screenshots for UI changes -->
## Checklist
- [ ] My code follows the project's style guidelines
- [ ] I have performed a self-review
- [ ] I have added tests that prove my fix/feature works
- [ ] New and existing unit tests pass locally
- [ ] I have updated documentation as needed
- [ ] My changes generate no new warnings
## Related Issues
<!-- Link related issues: Fixes #123, Relates to #456 -->
Multiple Templates for Different PR Types
Create .github/PULL_REQUEST_TEMPLATE/ directory:
.github/
└── PULL_REQUEST_TEMPLATE/
├── feature.md
├── bugfix.md
├── hotfix.md
└── documentation.md
Contributors select the appropriate template when creating PRs.
Code Review Best Practices
For Authors
Before requesting review:
┌─────────────────────────────────────────────────────────────┐
│ SELF-REVIEW CHECKLIST │
├─────────────────────────────────────────────────────────────┤
│ □ Review your own diff in "Files changed" tab │
│ □ Remove debug code, console.logs, commented code │
│ □ Check for typos in strings, comments, variable names │
│ □ Verify tests pass locally │
│ □ Run linter and fix issues │
│ □ Add comments on complex sections to guide reviewers │
│ □ Ensure PR description explains the WHY │
│ □ Link related issues │
│ □ Request specific reviewers who know this code │
└─────────────────────────────────────────────────────────────┘
Responding to feedback:
- Respond to every comment (even just "Done" or "Good catch")
- Explain your reasoning if you disagree
- Batch related changes into single commits
- Re-request review after addressing feedback
For Reviewers
Review mindset:
┌─────────────────────────────────────────────────────────────┐
│ REVIEW PRIORITIES │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. CORRECTNESS (Must fix) │
│ └── Does it work? Logic errors? Edge cases? │
│ │
│ 2. SECURITY (Must fix) │
│ └── Injection risks? Auth issues? Data exposure? │
│ │
│ 3. DESIGN (Should discuss) │
│ └── Architecture fit? Maintainability? Patterns? │
│ │
│ 4. READABILITY (Nice to have) │
│ └── Clear names? Good comments? Understandable? │
│ │
│ 5. STYLE (Automate instead) │
│ └── Formatting? Linting rules? Let tools handle it │
│ │
└─────────────────────────────────────────────────────────────┘
Effective review comments:
<!-- Bad: Vague -->
This is wrong.
<!-- Good: Specific with suggestion -->
This will throw a NullPointerException if `user` is null (line 42).
Consider adding a null check:
```java
if (user != null && user.isActive()) {
Use camelCase.
Our codebase uses camelCase for consistency with the existing API client. (Better: configure linter to catch this automatically)
**Review comment prefixes:**
| Prefix | Meaning | Action Required |
|--------|---------|-----------------|
| `blocking:` | Must fix before merge | Yes |
| `suggestion:` | Consider this approach | Optional |
| `question:` | Need clarification | Response needed |
| `nit:` | Minor nitpick | Optional |
| `praise:` | Good work! | None |
### Review Workflow
┌─────────────────────────────────────────────────────────────┐ │ REVIEW LIFECYCLE │ ├─────────────────────────────────────────────────────────────┤ │ │ │ Author creates PR │ │ │ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ Request Review │───► Reviewers notified │ │ └────────┬────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ Review Period │───► Comment, Approve, or │ │ └────────┬────────┘ Request Changes │ │ │ │ │ ┌───────┴───────┐ │ │ ▼ ▼ │ │ Approved Changes Requested │ │ │ │ │ │ │ ▼ │ │ │ Author addresses │ │ │ feedback │ │ │ │ │ │ │ ▼ │ │ │ Re-request review │ │ │ │ │ │ └───────┬───────┘ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ MERGE │ │ │ └─────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘
## Merge Strategies
### Comparison
┌─────────────────────────────────────────────────────────────┐ │ MERGE STRATEGIES │ ├─────────────────────────────────────────────────────────────┤ │ │ │ MERGE COMMIT │ │ main: A───B───C───────M─── │ │ \ / │ │ feature: D───E───F │ │ │ │ • Preserves all commits │ │ • Shows branch history │ │ • Creates merge commit (M) │ │ │ ├─────────────────────────────────────────────────────────────┤ │ │ │ SQUASH AND MERGE │ │ main: A───B───C───S─── │ │ │ │ • Combines all PR commits into one (S) │ │ • Clean, linear main history │ │ • Loses individual commit granularity │ │ │ ├─────────────────────────────────────────────────────────────┤ │ │ │ REBASE AND MERGE │ │ main: A───B───C───D'───E'───F'─── │ │ │ │ • Replays commits on top of main │ │ • Linear history, no merge commits │ │ • Rewrites commit hashes │ │ │ └─────────────────────────────────────────────────────────────┘
### When to Use Each
| Strategy | Best For | Avoid When |
|----------|----------|------------|
| **Merge commit** | Feature branches with meaningful commit history | Main has many merge commits already |
| **Squash and merge** | Feature branches with messy commits (WIP, fixup) | Commits should be preserved individually |
| **Rebase and merge** | Clean branches with atomic commits | Commits are already on main (double commits) |
### Configuring Default Strategy
In GitHub repository settings:
1. Go to **Settings > General**
2. Scroll to **Pull Requests**
3. Configure allowed merge methods:
- Allow merge commits
- Allow squash merging ✓ (default for most teams)
- Allow rebase merging
You can also set a default merge button behavior.
## Automated Checks
### Required Status Checks
Configure in **Settings > Branches > Branch protection rules**:
```yaml
# Example CI workflow for PR checks
name: PR Checks
on:
pull_request:
branches: [main]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run linter
run: npm run lint
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run tests
run: npm test
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: npm run build
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run security scan
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
PR Size Warnings
Automatically warn on large PRs:
name: PR Size Check
on: pull_request
jobs:
size-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check PR Size
run: |
ADDITIONS=$(gh pr view ${{ github.event.pull_request.number }} --json additions -q '.additions')
DELETIONS=$(gh pr view ${{ github.event.pull_request.number }} --json deletions -q '.deletions')
TOTAL=$((ADDITIONS + DELETIONS))
if [ $TOTAL -gt 500 ]; then
echo "::warning::Large PR detected ($TOTAL lines changed). Consider splitting into smaller PRs."
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Auto-labeling
Automatically label PRs based on files changed:
# .github/labeler.yml
frontend:
- 'src/components/**'
- 'src/pages/**'
- '**/*.css'
backend:
- 'src/api/**'
- 'src/services/**'
documentation:
- '**/*.md'
- 'docs/**'
tests:
- '**/*.test.ts'
- '**/*.spec.ts'
# .github/workflows/labeler.yml
name: Labeler
on: pull_request
jobs:
label:
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v5
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
CODEOWNERS for Automatic Reviewers
Create .github/CODEOWNERS:
# Default owners for everything
* @org/core-team
# Frontend code
/src/components/ @org/frontend-team
/src/pages/ @org/frontend-team
*.css @org/frontend-team
*.tsx @org/frontend-team
# Backend code
/src/api/ @org/backend-team
/src/services/ @org/backend-team
# Infrastructure
/terraform/ @org/devops-team
/.github/workflows/ @org/devops-team
Dockerfile @org/devops-team
# Security-sensitive code requires security team review
/src/auth/ @org/security-team @org/backend-team
/src/encryption/ @org/security-team
# Documentation
/docs/ @org/docs-team
*.md @org/docs-team
# Package management
package.json @org/core-team
package-lock.json @org/core-team
Enable CODEOWNERS enforcement:
- Settings > Branches > Branch protection
- Enable "Require review from Code Owners"
Team Collaboration Patterns
Handling Stacked PRs
When features depend on other PRs not yet merged:
┌─────────────────────────────────────────────────────────────┐
│ STACKED PRs │
├─────────────────────────────────────────────────────────────┤
│ │
│ main │
│ │ │
│ └──► feature/auth-base (PR #1) │
│ │ │
│ └──► feature/auth-oauth (PR #2) │
│ │ │
│ └──► feature/auth-ui (PR #3) │
│ │
│ Workflow: │
│ 1. Create PR #1 against main │
│ 2. Create PR #2 against feature/auth-base │
│ 3. Create PR #3 against feature/auth-oauth │
│ 4. Review and merge PR #1 │
│ 5. Retarget PR #2 to main (GitHub auto-updates) │
│ 6. Review and merge PR #2 │
│ 7. Retarget PR #3 to main │
│ 8. Review and merge PR #3 │
│ │
└─────────────────────────────────────────────────────────────┘
Draft PRs for Early Feedback
Use draft PRs when:
- You want early feedback on approach
- CI needs to run during development
- You're sharing progress with stakeholders
- You need to collaborate on complex changes
# Create draft PR with GitHub CLI
gh pr create --draft --title "feat: Add auth" --body "WIP: Authentication system"
# Mark ready for review when done
gh pr ready
Keeping PRs Updated
# Option 1: Merge main into your branch
git checkout feature-branch
git fetch origin
git merge origin/main
git push
# Option 2: Rebase onto main (cleaner but requires force push)
git checkout feature-branch
git fetch origin
git rebase origin/main
git push --force-with-lease
PR Metrics and Team Health
Track these metrics to improve PR workflows:
| Metric | Target | Why It Matters |
|---|---|---|
| Time to first review | < 4 hours | Fast feedback keeps developers productive |
| Review cycles | 1-2 rounds | More indicates unclear requirements |
| PR size | < 400 LOC | Large PRs get worse reviews |
| Time to merge | < 2 days | Long waits indicate bottlenecks |
| Review participation | Balanced | Avoid relying on one reviewer |
Measuring with GitHub Insights
# Using GitHub CLI to analyze PR metrics
gh pr list --state merged --json number,createdAt,mergedAt,additions,deletions \
--jq '.[] | "\(.number): \(.additions + .deletions) lines, \(((.mergedAt | fromdate) - (.createdAt | fromdate)) / 3600 | floor) hours"'
Quick Reference
PR Author Checklist
□ Self-review completed
□ Tests added/updated
□ Documentation updated
□ PR title follows conventions
□ Description explains what and why
□ Screenshots for UI changes
□ Related issues linked
□ Appropriate reviewers assigned
Reviewer Checklist
□ Understand the context (read description first)
□ Check for correctness and edge cases
□ Look for security issues
□ Verify tests are meaningful
□ Assess maintainability
□ Provide actionable feedback
□ Approve, request changes, or comment
Related Resources
- Git & GitHub Complete Guide - Hub for all Git guides
- Git Branching Strategies - GitFlow, GitHub Flow, trunk-based
- Squashing Git Commits - Clean up commits before merge
- GitHub Repository Security - Branch protection and access control
- Git Hooks Automation - Pre-commit hooks for quality