GitHub Pull Requests Complete Guide: Reviews, CI/CD & Best Practices

Published February 12, 2026 · 30 min read

Pull requests are the backbone of modern software development on GitHub. They are the mechanism through which code gets reviewed, tested, and merged, and they shape how teams collaborate, maintain quality, and ship software. Whether you are opening your first PR or trying to improve your team's review culture, this guide covers every aspect of the pull request workflow: from writing effective descriptions to configuring branch protection, integrating CI/CD pipelines, choosing merge strategies, and automating PR operations with GitHub Actions and the GitHub CLI.

This guide reflects GitHub's feature set as of early 2026, including the latest improvements to code review, merge queues, and repository rulesets.

Table of Contents

What Is a Pull Request?

A pull request is a proposal to merge a set of changes from one branch into another. On GitHub, a PR provides a structured place to describe your changes, discuss the implementation with teammates, run automated checks, and ultimately integrate the code into the target branch. The term "pull request" comes from the idea that you are requesting the maintainer to pull your changes into their branch.

Pull requests are more than a merge mechanism. They serve as:

The Pull Request Workflow

The standard GitHub PR workflow follows these steps:

  1. Create a branch from the target branch (usually main)
  2. Make commits on your feature branch
  3. Push the branch to the remote repository
  4. Open a pull request against the target branch
  5. Automated checks run (CI, linters, tests)
  6. Code review by one or more teammates
  7. Address feedback with additional commits or amendments
  8. Approval from required reviewers
  9. Merge the pull request
  10. Delete the branch (cleanup)
# Typical PR workflow from the command line
git checkout -b feature/add-user-search main
# ... make changes ...
git add -A
git commit -m "feat: add user search with fuzzy matching"
git push -u origin feature/add-user-search
# Then open PR on GitHub or use: gh pr create

For a deeper look at branching models, see our Git Branching Strategies Guide.

Creating Effective Pull Requests

The quality of a pull request is determined before you write a single line of code. Keep these principles in mind:

Keep PRs Small and Focused

Research consistently shows that smaller pull requests get reviewed faster, receive better feedback, and have fewer defects. A study by SmartBear found that review quality drops sharply after 400 lines of changes. Aim for PRs that change fewer than 300 lines when possible.

Write Meaningful Commit Messages

Even if you plan to squash-merge, good commit messages help reviewers understand the progression of your work:

# Good: explains what and why
git commit -m "feat: add fuzzy search to user lookup

Use Levenshtein distance for matching. This handles
typos in the admin dashboard search, which support
reported as the #2 complaint in Q4."

# Bad: describes what the diff already shows
git commit -m "updated search.js"

Self-Review Before Opening

Before requesting a review, go through your own diff on GitHub. You will catch debug statements, accidental whitespace changes, and missing tests that you would be embarrassed to have a reviewer point out.

Writing Great PR Descriptions

The PR description is the first thing a reviewer reads. A good description reduces review time and prevents misunderstandings. Here is a template that works well for most teams:

## What
One-line summary of the change.

## Why
Context: link to the issue, user story, or bug report.
Explain the motivation behind this approach.

## How
Describe the implementation approach. Mention key design
decisions and any alternatives you considered.

## Testing
- [ ] Unit tests added/updated
- [ ] Manual testing steps:
  1. Go to /admin/users
  2. Type a misspelled name in the search box
  3. Verify fuzzy results appear

## Screenshots
(For UI changes, include before/after screenshots)

## Related
- Closes #142
- Depends on #138
- See also: #130 (design doc)

GitHub automatically links Closes #142 to the referenced issue and will close it when the PR merges. Use Fixes, Resolves, or Closes followed by the issue number.

Code Review Best Practices

Code review is the highest-leverage activity in a pull request. Done well, it catches bugs, spreads knowledge, and improves design. Done poorly, it becomes a bottleneck that demoralizes the team.

For Reviewers

For Authors

PR Review Comments and Suggestions

GitHub provides several ways to leave feedback on a pull request:

Single-Line and Multi-Line Comments

Click the + button next to any line in the diff to leave a comment. Drag to select multiple lines for a multi-line comment. These comments are anchored to specific code, making them precise and actionable.

Suggested Changes

Reviewers can propose exact code changes that the author can accept with a single click:

```suggestion
const MAX_RETRIES = 3;
for (let i = 0; i < MAX_RETRIES; i++) {
```

The author sees a "Commit suggestion" button and can batch multiple suggestions into a single commit. This is one of the most efficient review features on GitHub because it eliminates back-and-forth.

Review States

Always submit your comments as a review (click "Start a review" or "Finish your review") rather than posting them individually. Batched reviews send a single notification instead of spamming the author.

Draft Pull Requests

Draft PRs signal that your code is not ready for formal review. They are useful for:

# Create a draft PR from the command line
gh pr create --draft --title "feat: user search" --body "WIP: implementing fuzzy search"

# Convert a draft to ready for review
gh pr ready

Draft PRs cannot be merged until they are marked as ready. Some teams adopt the practice of opening a draft PR immediately after creating a branch, converting it to ready only when all checks pass and the code is complete.

PR Templates and CODEOWNERS

Pull Request Templates

Create a file at .github/pull_request_template.md to provide a default description for every new PR in your repository:

## What does this PR do?

## Why is this change needed?

## How was this tested?
- [ ] Unit tests
- [ ] Integration tests
- [ ] Manual testing

## Checklist
- [ ] Code follows project style guidelines
- [ ] Self-review completed
- [ ] Documentation updated (if applicable)
- [ ] No breaking changes (or migration guide included)

For repositories with different types of changes, you can create multiple templates in .github/PULL_REQUEST_TEMPLATE/ and select them via URL query parameters.

CODEOWNERS

The CODEOWNERS file automatically assigns reviewers based on which files a PR modifies. Place it at .github/CODEOWNERS or the repository root:

# .github/CODEOWNERS

# Default owners for everything
* @myorg/core-team

# Frontend code requires frontend team review
/src/components/ @myorg/frontend
/src/styles/ @myorg/frontend
*.css @myorg/frontend

# API changes require backend team review
/src/api/ @myorg/backend
/src/middleware/ @myorg/backend

# Infrastructure changes require DevOps review
Dockerfile @myorg/devops
docker-compose.yml @myorg/devops
.github/workflows/ @myorg/devops

# Security-sensitive files require security team
/src/auth/ @myorg/security @myorg/backend

When combined with branch protection rules that require CODEOWNERS approval, this ensures the right people always review the right code.

Branch Protection Rules

Branch protection rules prevent direct pushes to important branches and enforce quality gates before merging. Configure them in Settings → Branches → Branch protection rules.

Essential Protection Settings

Repository Rulesets

GitHub's newer repository rulesets provide more granular control than classic branch protection. They support targeting by branch name patterns, tag patterns, and can be applied at the organization level. Rulesets also support bypass lists, allowing specific roles or teams to bypass certain rules for emergencies without disabling them entirely.

CI/CD Integration with PRs

Pull requests are the natural integration point for continuous integration. Every push to a PR branch triggers automated checks that validate the code before a human reviewer even looks at it.

GitHub Actions Workflow for PRs

# .github/workflows/pr-checks.yml
name: PR Checks
on:
  pull_request:
    branches: [main, develop]
    types: [opened, synchronize, reopened]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci
      - run: npm run lint

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci
      - run: npm test -- --coverage
      - uses: actions/upload-artifact@v4
        with:
          name: coverage-report
          path: coverage/

  build:
    runs-on: ubuntu-latest
    needs: [lint, test]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci
      - run: npm run build

The pull_request trigger runs workflows in the context of the PR, which means they have read-only access to the base repository. This is a security feature that prevents malicious PRs from exfiltrating secrets. For workflows that need write access, use pull_request_target with caution.

For a comprehensive look at GitHub Actions, see our GitHub Actions CI/CD Complete Guide.

Required Status Checks

After your CI workflow runs for the first time, you can make it a required status check in branch protection. This means the PR cannot be merged unless the specified checks pass. Configure this in Settings → Branches → Require status checks → search for your job names (lint, test, build).

Merge Strategies: Merge Commit, Squash, and Rebase

GitHub offers three ways to merge a pull request. Each produces a different commit history.

Merge Commit (Create a Merge Commit)

This is the default. It creates a merge commit that ties the feature branch history into the target branch. All individual commits are preserved.

# Result on main:
# a - b - c - M  (main, M is the merge commit)
#    \       /
#     d - e      (feature branch commits)

Best for: Teams that want to preserve full history and see exactly what happened on each branch. Works well with Git Flow.

Squash and Merge

Compresses all commits from the PR into a single commit on the target branch. The individual commits are discarded from the main branch history.

# Result on main:
# a - b - c - S  (main, S contains all changes from d + e)
# Feature branch commits d, e are not on main

Best for: Most teams. Keeps main clean with one commit per feature or fix. Each commit on main corresponds to one PR, making git log and git bisect straightforward. This is the most popular strategy in 2026.

Rebase and Merge

Replays each commit from the PR individually onto the target branch. No merge commit is created. Commit hashes change because each commit gets a new parent.

# Result on main:
# a - b - c - d' - e'  (main, d' and e' are rebased versions)

Best for: Teams that want linear history but also want to preserve individual commits. Requires clean, atomic commits on the feature branch. For more on rebase, see our Git Rebase Complete Guide.

Choosing Your Strategy

You can enforce a single merge strategy in repository settings (Settings → General → Pull Requests). Many teams allow only squash merge on main and merge commits on release branches.

Handling Merge Conflicts in PRs

Merge conflicts happen when the PR branch and the target branch have both modified the same lines. GitHub shows a warning banner and blocks the merge button until conflicts are resolved.

Resolving via the GitHub Web Editor

For simple conflicts, GitHub provides a browser-based conflict editor. Click "Resolve conflicts" on the PR page, edit the conflict markers, and commit directly.

Resolving Locally with Merge

# Fetch the latest target branch
git fetch origin main

# Merge main into your feature branch
git checkout feature/user-search
git merge origin/main

# Resolve conflicts in your editor, then:
git add .
git commit -m "resolve merge conflicts with main"
git push

Resolving Locally with Rebase

# Rebase your branch onto the latest main
git fetch origin main
git checkout feature/user-search
git rebase origin/main

# Resolve each conflict as it comes up:
# Edit the file, then:
git add .
git rebase --continue

# After all conflicts are resolved:
git push --force-with-lease

For a deep dive into conflict resolution, see our Git Merge Conflicts Guide.

Preventing Conflicts

GitHub CLI (gh) for PR Management

The GitHub CLI (gh) lets you manage pull requests entirely from the terminal. It is the fastest way to create, review, and merge PRs without leaving your editor.

Creating Pull Requests

# Create a PR interactively
gh pr create

# Create with title and body
gh pr create --title "feat: add user search" \
  --body "Implements fuzzy search for the admin dashboard. Closes #142."

# Create a draft PR
gh pr create --draft --title "WIP: search feature"

# Create and assign reviewers
gh pr create --title "feat: search" --reviewer alice,bob

# Create targeting a specific base branch
gh pr create --base develop --title "feat: search"

Reviewing Pull Requests

# List open PRs
gh pr list

# View a specific PR
gh pr view 42

# Check out a PR locally for testing
gh pr checkout 42

# View the diff
gh pr diff 42

# Approve a PR
gh pr review 42 --approve

# Request changes
gh pr review 42 --request-changes --body "Need error handling for empty input"

# Leave a comment
gh pr review 42 --comment --body "Looks good overall, one suggestion below"

Merging and Closing

# Merge with default strategy
gh pr merge 42

# Squash merge
gh pr merge 42 --squash

# Rebase merge
gh pr merge 42 --rebase

# Merge and delete the branch
gh pr merge 42 --squash --delete-branch

# Close without merging
gh pr close 42

Advanced gh Commands

# List PRs that need your review
gh pr list --search "review-requested:@me"

# List PRs by a specific author
gh pr list --author alice

# View PR checks/status
gh pr checks 42

# Re-request review after addressing feedback
gh pr edit 42 --add-reviewer alice

# Add labels
gh pr edit 42 --add-label "ready-for-review,frontend"

PR Automation with GitHub Actions

GitHub Actions can automate repetitive PR tasks, reducing manual work for maintainers and contributors.

Auto-Label PRs by File Path

# .github/workflows/label-pr.yml
name: Label PR
on:
  pull_request:
    types: [opened, synchronize]

jobs:
  label:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - uses: actions/labeler@v5
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}
          # Reads label config from .github/labeler.yml
# .github/labeler.yml
frontend:
  - changed-files:
    - any-glob-to-any-file: ['src/components/**', '*.css', '*.tsx']

backend:
  - changed-files:
    - any-glob-to-any-file: ['src/api/**', 'src/middleware/**']

docs:
  - changed-files:
    - any-glob-to-any-file: ['docs/**', '*.md']

ci:
  - changed-files:
    - any-glob-to-any-file: ['.github/workflows/**']

Auto-Assign Reviewers

# .github/workflows/assign-reviewers.yml
name: Assign Reviewers
on:
  pull_request:
    types: [opened, ready_for_review]

jobs:
  assign:
    if: github.event.pull_request.draft == false
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - uses: kentaro-m/auto-assign-action@v2
        with:
          configuration-path: '.github/auto-assign.yml'

PR Size Check

# .github/workflows/pr-size.yml
name: PR Size Check
on:
  pull_request:
    types: [opened, synchronize]

jobs:
  size-check:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    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))
          echo "PR size: +$ADDITIONS -$DELETIONS ($TOTAL total lines)"
          if [ "$TOTAL" -gt 500 ]; then
            gh pr comment ${{ github.event.pull_request.number }} \
              --body "**Warning**: This PR changes $TOTAL lines. Consider splitting into smaller PRs for easier review."
          fi
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

For more automation patterns with Git, see our Git Hooks Complete Guide.

Conventional Commits and PR Titles

The Conventional Commits specification provides a structured format for commit messages and PR titles that enables automated changelog generation and semantic versioning.

Format

<type>(<scope>): <description>

# Examples:
feat(auth): add OAuth2 login with Google
fix(api): handle null response from user service
docs(readme): update installation instructions
refactor(search): extract fuzzy matching into utility
test(cart): add integration tests for checkout flow
chore(deps): upgrade express from 4.18 to 4.21
ci(deploy): add staging environment workflow
perf(db): add index on users.email column

Common Types

When using squash merge, the PR title becomes the commit message on main. Enforce conventional PR titles with a GitHub Action that validates the title format before the PR can be merged.

Stacked PRs and Large PR Strategies

When a feature is too large for a single PR, break it into a series of dependent PRs that build on each other. This approach is called "stacked PRs" or "PR chains."

How Stacking Works

# PR 1: Base infrastructure (targets main)
git checkout -b feat/search-models main
# Add database models and migrations
git push -u origin feat/search-models
gh pr create --base main --title "feat(search): add search models and migrations"

# PR 2: API layer (targets PR 1 branch)
git checkout -b feat/search-api feat/search-models
# Add API endpoints
git push -u origin feat/search-api
gh pr create --base feat/search-models --title "feat(search): add search API endpoints"

# PR 3: Frontend (targets PR 2 branch)
git checkout -b feat/search-ui feat/search-api
# Add UI components
git push -u origin feat/search-ui
gh pr create --base feat/search-api --title "feat(search): add search UI components"

Merge from the bottom up: merge PR 1 into main first, then retarget PR 2 to main, merge it, and so on. GitHub automatically retargets downstream PRs when their base branch is merged.

Tools for Managing Stacked PRs

Other Strategies for Large Changes

PR Metrics and Team Velocity

Tracking pull request metrics helps identify bottlenecks in your development process and improve team throughput.

Key Metrics to Track

Querying PR Metrics with GitHub CLI

# List merged PRs from the last 30 days with timing data
gh pr list --state merged --limit 50 --json number,title,createdAt,mergedAt,additions,deletions \
  | jq '.[] | {number, title, days_open: (((.mergedAt | fromdateiso8601) - (.createdAt | fromdateiso8601)) / 86400 | floor), size: (.additions + .deletions)}'

# Average PR size
gh pr list --state merged --limit 100 --json additions,deletions \
  | jq '[.[] | .additions + .deletions] | add / length | floor'

# PRs currently waiting for review (older than 24 hours)
gh pr list --search "review:required created:<$(date -d '1 day ago' +%Y-%m-%d)" \
  --json number,title,createdAt

Improving PR Velocity

Frequently Asked Questions

What is the difference between squash merge, rebase merge, and merge commit on GitHub?

A merge commit preserves every individual commit from the feature branch and adds a merge commit on top, keeping full history. Squash merge compresses all commits into a single commit on the target branch, giving a clean history but losing per-commit detail. Rebase merge replays each commit individually onto the target branch without a merge commit, creating a linear history. Most teams use squash merge for feature branches to keep main clean, and reserve merge commits for long-lived release branches.

How many reviewers should a pull request have?

For most teams, one required reviewer is the minimum and two reviewers is ideal for production code. Research from Microsoft and Google shows that the first reviewer catches the majority of issues, with diminishing returns after the second reviewer. Configure branch protection rules to require at least one approving review before merge. For critical systems like authentication or payment processing, require two or more approvals and use CODEOWNERS to route reviews to domain experts.

Should I use draft pull requests or regular pull requests for work in progress?

Use draft pull requests for any work that is not ready for formal review. Drafts signal to teammates that the code is still evolving and prevents accidental merges. They are useful for getting early design feedback, running CI checks before the code is complete, and showing progress on long-running features. Convert the draft to a regular PR by clicking "Ready for review" when you want formal code review. Some teams open draft PRs immediately after creating a branch to make all work visible.

How do I resolve merge conflicts in a GitHub pull request?

You have three options. First, use GitHub's web editor for simple conflicts by clicking the "Resolve conflicts" button on the PR page. Second, resolve locally by running git fetch origin, git checkout your-branch, git merge origin/main, fixing the conflicts in your editor, then git add and git commit. Third, rebase locally with git rebase origin/main, resolve conflicts during rebase, and force-push with git push --force-with-lease. The local approach gives you your full editor and test suite, which is better for complex conflicts.

What should I include in a pull request description?

A good PR description includes: a one-line summary of what changed, the motivation or context explaining why the change is needed, a description of the approach taken and any alternatives considered, testing instructions or evidence that the change works, screenshots or recordings for UI changes, links to related issues or tickets, and a checklist of any deployment steps needed. Use a PR template to enforce this structure across your team so that reviewers always have the context they need.

Continue Learning

This guide is part of our Git and GitHub deep-dive series. Explore the related guides to build a complete understanding of collaborative development workflows:

Related Tools

Git Diff Viewer
Visualize and compare diffs in the browser
JSON Formatter
Format and validate JSON data instantly
YAML Formatter
Format and validate YAML configurations
Git Cheat Sheet
One-page quick reference for all Git commands