Creating a Pull Request
A pull request (PR) is how you propose that a branch’s commits be merged into another branch on GitHub — it packages up your changes, shows a diff, runs your automated checks, and gives your team a place to discuss and review before anything lands on main. This lesson covers the full workflow for creating one: pushing a branch, opening the PR from the GitHub web UI or the gh command-line tool, and the extra step required when you are contributing to a repository you do not own.
Overview: What a Pull Request Actually Is
It helps to remember that a pull request is not a Git object. Git itself has no idea what a "pull request" is — it only knows about commits, trees, blobs, and refs (branches and tags). A commit object stores a snapshot: a pointer to a tree object (which represents the state of the whole directory), pointers to blob objects (the actual file contents, each addressed by a SHA-1 hash of its bytes), the commit’s author/committer info, a message, and a pointer to its parent commit(s). A branch like main or feature/login-page is nothing more than a small file containing a commit hash — a movable pointer. When you run git push, you are asking the remote (GitHub) to update its copy of that pointer to match yours, and to store any commit, tree, and blob objects it doesn’t already have.
A pull request is a feature GitHub layers on top of that. Once your branch exists on GitHub, you ask GitHub to compare two refs: a base branch (where you want the changes to end up, usually main) and a head branch (your branch, containing the new commits). GitHub walks the commit graph and shows you every commit that is reachable from the head branch but not from the base branch — that diff is the pull request. This is also why keeping your branch up to date with the base branch matters: the further the two diverge, the messier the comparison and the more likely you’ll hit a merge conflict when it’s time to merge.
There are two shapes this workflow takes. If you have write access to the repository, you push your branch directly to it and open a PR from feature/login-page into main — both branches live in the same repository. If you don’t have write access (the common case for open-source contributions), you first fork the repository — GitHub makes you a personal copy — push your branch to your fork, and open a PR whose head is your-username:feature/login-page targeting the upstream repository’s main. Git itself doesn’t distinguish these cases; the only difference is which remote you push to.
Once a pull request is open, it stays live and tracks your branch: every additional commit you push to that same branch automatically appears in the PR, re-triggers any pull_request-triggered GitHub Actions workflows, and re-runs required status checks. You do not need to close and reopen the PR to add more commits.
Syntax
You can open a pull request from the GitHub web UI or from the terminal with the GitHub CLI (gh). The general form for the CLI is:
gh pr create [flags]
Run with no flags inside a repository with an unpushed or pushed branch, gh pr create walks you through an interactive prompt. In scripts or when you want to skip the prompts, pass flags directly:
| Flag | Meaning |
|---|---|
--title "<text>" |
The pull request title shown in the PR list and merge commit. |
--body "<text>" |
The PR description. Supports Markdown and GitHub issue-linking keywords like Closes #42. |
--base <branch> |
The branch you want to merge into. Defaults to the repository’s default branch (typically main). |
--head <branch> |
The branch containing your changes. For a fork, use username:branch. Defaults to your current branch. |
--draft |
Opens the PR in draft state — visible for feedback, but not mergeable and doesn’t request reviewers by default. |
--fill |
Auto-fills the title and body from your branch’s commit(s) instead of prompting. |
--reviewer <user> |
Requests a review from the given GitHub username or team. |
--label <name> |
Applies an existing label to the PR. |
--web |
Opens the PR creation page in your browser instead of creating it via the API. |
To create a PR from the web UI without the CLI: push your branch, go to the repository on GitHub, and either click the yellow "Compare & pull request" banner that appears for a recently pushed branch, or open the Pull requests tab and click New pull request, then choose the base and compare (head) branches from the two dropdowns, fill in a title and description, and click Create pull request.
Examples
Example 1: A same-repo feature branch
git switch -c feature/login-page
# ...edit files...
git add src/login.html
git commit -m "feat: add login page markup"
git push -u origin feature/login-page
Output:
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 512 bytes | 512.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
remote:
remote: Create a pull request for 'feature/login-page' on GitHub by visiting:
remote: https://github.com/yourname/your-repo/pull/new/feature/login-page
remote:
To github.com:yourname/your-repo.git
* [new branch] feature/login-page -> feature/login-page
branch 'feature/login-page' set up to track 'origin/feature/login-page'.
Pushing with -u creates the branch ref on the remote and sets up tracking, which is why Git’s own output already hands you the URL to open a PR. Now create the PR itself from the terminal:
gh pr create --title "feat: add login page markup" --body "Adds the initial HTML structure for the login page. Closes #42."
Output:
Creating pull request for feature/login-page into main in yourname/your-repo
https://github.com/yourname/your-repo/pull/57
GitHub now compares feature/login-page (head) against main (base), shows the one new commit, and, because the description contains Closes #42, will automatically close issue #42 when this PR is merged.
Example 2: A draft PR for work in progress
git switch -c feature/checkout-refactor
git commit --allow-empty -m "chore: start checkout refactor"
git push -u origin feature/checkout-refactor
gh pr create --draft --title "refactor: rework checkout flow" --body "Work in progress, do not merge yet."
Output:
https://github.com/yourname/your-repo/pull/58
A draft PR behaves like a normal PR for CI and comments, but is clearly marked unfinished and blocks merging until you flip it to "ready." When the work is done:
gh pr ready 58
Output:
Pull request #58 is marked as ready for review
Example 3: Contributing to a repository you don’t own (fork workflow)
gh repo fork octocat/Hello-World --clone
cd Hello-World
git switch -c fix/typo-in-readme
# ...edit README.md...
git commit -am "docs: fix typo in installation section"
git push -u origin fix/typo-in-readme
gh pr create --base main --head yourname:fix/typo-in-readme --title "docs: fix typo in installation section" --body "Fixes a small typo in the installation instructions."
Output:
https://github.com/octocat/Hello-World/pull/1204
gh repo fork --clone creates your personal fork on GitHub and clones it locally with origin pointing at your fork. Because your branch lives on your fork, not on octocat/Hello-World, the --head flag must be qualified with your username so GitHub knows which repository to pull the branch from, while --base main still refers to the upstream repository’s branch.
How It Works Step by Step
When you push a branch and then create a pull request, here is what actually happens:
- Local commit:
git commitwrites new blob and tree objects for any changed file content, then a commit object pointing at that tree and at the previous commit, and moves your current branch pointer to the new commit. - Push:
git pushsends any objects the remote is missing and asks it to fast-forward (or create) the matching branch ref on the server. - Compare: When you open a PR, GitHub walks the commit graph from the head ref backward until it hits a commit that is also an ancestor of the base ref — everything in between becomes the PR’s commit list and diff.
- PR record created: GitHub stores a PR record (number, title, body, base ref, head ref, state) and fires a
pull_requestwebhook event, which is what triggerson: pull_requestGitHub Actions workflows. - Further pushes: Any later commit pushed to the same head branch updates the same PR in place — the comparison is recalculated and checks re-run; no new PR is created.
- Merging (covered in its own lesson) eventually applies the head branch’s commits to the base branch as a merge commit, a squash commit, or replayed individual commits, depending on the merge strategy chosen.
Common Mistakes
Mistake 1: Creating the PR before pushing the branch
git switch -c feature/payment-retry
git commit -am "feat: retry failed payments"
gh pr create --title "feat: retry failed payments" --body "Adds retry logic."
Output:
aborted: you must first push the current branch to a remote before creating a pull request
GitHub can only compare refs that exist on the remote — your local branch is invisible to it until pushed. Fix it by pushing first:
git push -u origin feature/payment-retry
gh pr create --title "feat: retry failed payments" --body "Adds retry logic."
Mistake 2: Opening the PR against the wrong base branch
It’s easy to accept whatever base gh pr create or the web UI defaults to without checking, especially if you branched off a teammate’s unmerged feature branch instead of main. The result is a PR that shows their commits mixed in with yours, confusing reviewers. Check the base shown before creating the PR, and if you already created it wrong, fix the target rather than closing and reopening:
gh pr edit 63 --base main
Mistake 3: Trying to open a PR from a branch against itself
git switch main
gh pr create --base main --head main --title "fix: typo" --body "Small fix."
Output:
pull request create failed: GraphQL: No commits between main and main (createPullRequest)
This happens when someone commits straight to main instead of creating a feature branch first — there is nothing to compare. The fix is procedural, not a flag: always create a dedicated branch with git switch -c before committing work you intend to send through review.
Best Practices
- Always branch off an up-to-date base (
git switch main && git pull, thengit switch -c <branch>) before starting new work. - Write PR titles that follow the same Conventional Commits style as your commit messages (
feat: ...,fix: ...,docs: ...) so history and PR lists stay scannable. - Keep pull requests small and focused on one logical change — large PRs are slower to review and more likely to hide bugs.
- Use closing keywords (
Closes #42,Fixes #17) in the PR body so linked issues close automatically on merge. - Open a
--draftPR early for visibility on work in progress, and mark it ready only once it’s actually reviewable. - Request specific reviewers with
--reviewerinstead of leaving review assignment to chance. - Keep your branch current with the base branch (merge or rebase it in) before asking for review, so the diff reviewers see matches what will actually merge.
- Never force-push over a branch other people have already reviewed or pulled without warning them; prefer
git push --force-with-leaseover a bare--forceif you must rewrite pushed commits.
Practice Exercises
- Exercise 1: In a repository you own, create a branch named
feature/add-contact-page, add a new file, commit it with a Conventional Commits-style message, push the branch, and open a pull request usinggh pr createthat closes a made-up issue number in its description. - Exercise 2: Create a second branch, open it as a
--draftpull request, push one more commit to the same branch, and confirm in the GitHub UI (or withgh pr view) that the new commit appears in the same PR. Then mark it ready withgh pr ready. - Exercise 3: Fork a small public repository with
gh repo fork --clone, create a branch, make a trivial documentation fix, push it to your fork, and open a pull request targeting the upstream repository’smainbranch using an explicitly qualified--head username:branch.
Summary
- A pull request is a GitHub-side comparison between a base ref and a head ref, not a Git object — Git only knows about the branch pointers underneath it.
- You must push your branch to a remote before GitHub can create a PR from it.
- Use the web UI’s "Compare & pull request" banner or
gh pr createwith--title/--body/--base/--headto open one. - Contributing to a repo you don’t own requires forking first and qualifying
--headwith your username. --draftlets you open a PR for early feedback without it being mergeable;gh pr readypromotes it later.- Pushing more commits to the same branch updates the existing PR automatically — you never need to recreate it.
