What Is a Pull Request?

A pull request (PR) is GitHub’s mechanism for proposing that changes you made on one branch be reviewed and merged into another branch, usually main. Instead of merging locally and pushing straight to the shared branch, you push your work to its own branch and ask GitHub to open a structured conversation around it: a diff, inline comments, automated checks, and an explicit approval step. Pull requests are the backbone of collaborative workflows on GitHub — almost nothing lands on a shared branch without going through one.

Overview / How Pull Requests Work

It helps to be precise about what a pull request actually is. Git itself has no concept of a "pull request" — it is not a commit, a branch, or any object in Git’s object model (blobs, trees, commits). A PR is a piece of GitHub-side metadata: a record that says "compare ref A (the base) against ref B (the head), and let people discuss and eventually merge the difference." Everything you see on a PR page — the diff, the comment threads, the list of commits, the checkmarks from CI — is GitHub rendering a comparison between two branches (or a branch and a fork) using ordinary Git history underneath.

Recall that a branch is just a movable pointer to a commit, and HEAD normally points to whichever branch you have checked out. When you create a feature branch, make commits, and push it to GitHub, you now have two diverging pointers on the remote: main and feature/login-page, both eventually sharing a common ancestor commit. A pull request asks GitHub to compute the commits reachable from the head branch but not from the base branch (conceptually the same thing git log main..feature/login-page would show you locally) and display them as the proposed change.

The typical flow looks like this:

  1. Create a branch off main and commit your work there.
  2. Push the branch to GitHub.
  3. Open a pull request, choosing a base branch (where the change should land) and a head branch (your work).
  4. Reviewers read the diff, leave comments, request changes, or approve. Automated checks (tests, linters, GitHub Actions workflows) run against the head branch.
  5. Once approved and passing, the PR is merged into the base branch using one of several merge strategies.
  6. The head branch is typically deleted, since its commits now live in main‘s history (or have been squashed/replayed into it).

Same-repo branches vs. forks

Team members with write access usually open PRs from a branch inside the same repository. External contributors, who don’t have push access, first fork the repository (their own full copy on GitHub) and open a PR from a branch in their fork back to the upstream repository. Git and GitHub don’t actually care which case you’re in — a fork’s branch is just another ref that can serve as a head branch, GitHub just tracks the ownership boundary.

Draft pull requests

If you want early feedback or want CI to run before your work is finished, you can open a PR as a draft. Draft PRs show the same diff and run the same checks, but are visually marked as not-ready and normally cannot be merged until you click "Ready for review."

Merge strategies, briefly

When a PR is merged, GitHub offers three strategies. A merge commit creates a new commit with two parents, preserving every individual commit and the branch topology. Squash and merge collapses all of the PR’s commits into a single new commit on the base branch, giving a clean, linear history at the cost of losing the individual commit steps. Rebase and merge replays each of the PR’s commits individually onto the tip of the base branch, keeping them separate but avoiding a merge commit. None of these is universally "correct" — merge commits preserve full context, squash keeps main tidy, and rebase keeps granularity without merge noise. Many teams standardize on one strategy per repository via branch settings.

Syntax

There is no single Git command for opening a pull request, because a PR is a GitHub feature, not a Git one. You can open one from the GitHub web UI, or from the command line using GitHub’s official gh CLI. The general form of the relevant gh subcommands is:

gh pr create [--base <branch>] [--head <branch>] [--title <text>] [--body <text>] [--draft]
gh pr list [--state open|closed|merged|all]
gh pr view <number>
gh pr checkout <number>
gh pr merge <number> [--merge|--squash|--rebase] [--delete-branch]
Flag / subcommand Meaning
gh pr create Opens a new pull request from your current (or specified) branch.
--base The branch the change should be merged into (default: the repository’s default branch).
--head The branch containing your changes (default: the currently checked-out branch).
--title, --body The PR’s title and description text.
--draft Opens the PR in draft state.
gh pr list Lists pull requests in the current repository.
gh pr view <number> Shows details of a specific PR in the terminal.
gh pr checkout <number> Fetches a PR’s head branch and checks it out locally.
gh pr merge <number> Merges a PR using the chosen strategy, optionally deleting the head branch afterward.

Examples

Example 1: Push a branch and get GitHub’s PR link

The simplest way to start a PR is to push a new branch. Git’s remote (via GitHub’s server-side hook) prints a ready-made URL for opening the pull request.

git switch -c feature/login-page
git add login.html login.css
git commit -m "feat: add login page markup and styles"
git push -u origin feature/login-page

Output:

remote:
remote: Create a pull request for 'feature/login-page' on GitHub by visiting:
remote:      https://github.com/your-org/your-repo/pull/new/feature/login-page
remote:
To github.com:your-org/your-repo.git
 * [new branch]      feature/login-page -> feature/login-page
branch 'feature/login-page' set up to track 'origin/feature/login-page'.

The branch now exists on the remote, but nothing has been proposed for merge yet — a PR is a separate GitHub object you still have to create by visiting that link (or using gh pr create, below).

Example 2: Open a PR from the terminal with gh

gh pr create --base main --head feature/login-page --title "feat: add login page" --body "Adds the initial markup and styles for the login page. Closes #42."

Output:

https://github.com/your-org/your-repo/pull/57

This creates PR #57, comparing feature/login-page (head) against main (base). Writing Closes #42 in the body links the PR to issue #42 and will automatically close that issue once the PR is merged — a small but very useful GitHub convention.

Example 3: Check out someone else’s PR locally to review it

Reviewing code is often easier with it actually checked out, so you can run it, search it, or test edge cases. Every open PR’s commits live on a special ref GitHub exposes on the remote.

git fetch origin pull/57/head:pr-57
git switch pr-57

Output:

From github.com:your-org/your-repo
 * [new ref]         refs/pull/57/head -> pr-57
Switched to branch 'pr-57'

This works because GitHub stores every PR’s head commit under refs/pull/<number>/head on the remote, even for PRs opened from forks you don’t otherwise have direct branch access to. The equivalent, shorter command is gh pr checkout 57, which does the same fetch-and-switch for you.

How It Works Step by Step

When you open a pull request, GitHub does not create any new Git objects for the comparison itself. Under the hood it performs the equivalent of a three-dot diff: it finds the merge base (common ancestor) of the base and head branches and shows everything the head branch has added since that point, which is exactly what running the commands below locally would show you.

git merge-base main feature/login-page
git log --oneline main..feature/login-page

When a reviewer approves and the PR is merged, real Git history is finally written: a merge commit is created (pointing to both branch tips as parents), or a new squash commit is created (a single commit whose tree is the final state, with only main‘s previous tip as parent), or the head commits are individually rebased and appended to main. In every case, the main branch pointer on GitHub’s copy of the repository moves forward to the newly created commit. Your local main only catches up once you run git fetch or git pull.

Common Mistakes

Mistake 1: Opening the PR against the wrong base branch

It’s easy to leave the base branch on GitHub’s default suggestion when you actually meant to target a release branch or another feature branch, producing a diff full of unrelated commits.

# Wrong: PR silently compares against the default branch
gh pr create --title "feat: add login page"

Always double-check the base before creating the PR, or fix it afterward:

gh pr edit 57 --base develop

Mistake 2: Force-pushing a PR branch other people are also using

Rewriting a PR branch’s history (for example after an interactive rebase to clean up commits) and force-pushing can silently discard commits a reviewer or collaborator already pulled.

# Risky: overwrites the remote branch unconditionally
git push --force origin feature/login-page

Prefer --force-with-lease, which refuses to push if the remote branch has commits you haven’t fetched yet, protecting collaborators from a silent overwrite:

git push --force-with-lease origin feature/login-page

Mistake 3: Letting the PR branch drift far from main

Leaving a PR open for weeks while main moves on produces large, conflict-ridden merges. Periodically bring the base branch’s changes into your PR branch (via merge or rebase, depending on team convention) instead of doing it all at once right before merging.

Mistake 4: Forgetting to delete the branch after merge

Merged branches that are never deleted clutter the branch list and make it harder to tell what’s still active. GitHub can delete the head branch automatically when you merge — use it.

Best Practices

  • Keep pull requests small and focused on one logical change — they’re easier and faster to review.
  • Write PR titles and commit messages in Conventional Commits style, e.g. feat: add login page or fix: correct off-by-one in pagination.
  • Use a draft PR when you want early CI feedback or visibility but aren’t ready for review.
  • Reference related issues in the PR body (Closes #42) so they close automatically on merge.
  • Request specific reviewers rather than leaving review ownership ambiguous.
  • Keep your branch reasonably up to date with its base branch to avoid painful late-stage conflicts.
  • Agree on one merge strategy (merge commit, squash, or rebase) per repository and enforce it in the repo’s merge settings.
  • Delete the head branch immediately after merging, either via the "Delete branch" button or gh pr merge --delete-branch.
  • Use --force-with-lease instead of bare --force whenever you must rewrite a pushed PR branch.

Practice Exercises

  1. In a repository you own on GitHub, create a branch named feature/readme-update, make a small edit to README.md, commit it with a Conventional Commits message, push it, and open a pull request against main using gh pr create. Expected end state: a new open PR whose diff shows only your README.md change.
  2. Find any open-source PR number in a public repository you have read access to (or one of your own), and check it out locally using gh pr checkout <number>. Confirm with git branch --show-current that you’re now on the PR’s head branch.
  3. Open a PR, then push an additional commit that amends your very first commit and force-push it. Try pushing first with plain --force and then, after resetting the scenario, with --force-with-lease. Hint: to see the protective behavior of --force-with-lease, have a "collaborator" (a second local clone) push a commit to the branch first, then attempt your force-push without re-fetching.

Summary

  • A pull request is GitHub metadata comparing a head branch against a base branch — it is not part of Git’s own object model.
  • The typical flow is: branch, commit, push, open PR, review and run checks, merge, delete branch.
  • External contributors usually open PRs from a fork; team members usually use a branch in the same repository.
  • Merging offers three strategies — merge commit, squash and merge, and rebase and merge — each with different history tradeoffs.
  • gh pr create, gh pr checkout, and gh pr merge let you manage the entire PR lifecycle from the terminal.
  • Every open PR’s commits are fetchable via refs/pull/<number>/head on the remote, even from forks.
  • Prefer --force-with-lease over bare --force when rewriting a pushed PR branch.