A Pull Request (or Merge Request in GitLab/Bitbucket) is not part of Git. It is a hosting wrapper. Git knows only branches and commits; a "merge request" is:
- two branches (source and target);
- the set of commits between them;
- a page for discussion and review;
- a binding to CI checks;
- a button for the final merge.
Different platforms use different names (GitHub: PR, GitLab: MR, Bitbucket: PR), but the mechanics are identical.
Lifecycle
-
Create a branch, make commits, push:
bashgit switch -c feat/login
# ... commits ...
git push -u origin feat/login
-
Open a PR via the UI (or
gh pr create/glab mr create). Specify the target (usually main), describe the changes, and list reviewers. -
CI runs checks: tests, linter, build, security scan. A green CI run is a prerequisite for merge in most projects.
-
Review. Colleagues comment on the code, leave suggestions, request changes, or approve.
-
Revision cycle. Add new commits or amend on the same branch, then
git push. The PR updates automatically. -
Merge. After approval and green CI, click the merge button. The strategy (merge commit / squash / rebase-merge) is chosen here (see merge-strategies).
-
Delete the branch. After merge the branch is usually deleted (GitHub has an auto-delete setting).
PR description
A useful structure (templates available in the repo via
.github/PULL_REQUEST_TEMPLATE.md):
## Why
Two sentences explaining the motivation. Link to the ticket.
## What changed
- feat: added password reset form
- refactor: extracted validator into a separate module
- test: coverage 87% -> 92%
## How to test
1. Run locally
2. Open /reset-password
3. Enter email and check the inbox
## Screenshots
(for UI changes)
## Checklist
- [x] Tests added
- [x] Documentation updated
- [ ] Migration required -> run in staging
A good PR description saves hours for the reviewer and for the developer who stumbles on the change a year later.
PR size
Size is the primary factor in review speed and quality. No one has measured exact percentages of bugs caught, but every team points the same direction:
- Small (tens to low hundreds of lines): readable in one sitting; the reviewer sees every line.
- Medium (several hundred lines): still manageable, but requires a dedicated block of time and focus.
- Large (500+ lines): the reviewer gets tired, skims diagonally, and misses bugs. Approvals often happen to avoid blocking the author.
If a PR grows too large, split it. A series of four small PRs is better than one at 800 lines.
Stacked PRs
When a feature is large and cannot be split into independent PRs, use a series of dependent PRs merged in order:
PR #1: feat/login-form-skeleton (target: main)
PR #2: feat/login-form-validation (target: feat/login-form-skeleton)
PR #3: feat/login-form-styling (target: feat/login-form-validation)
Each PR is reviewed and merged separately, in dependency order. Tools like Graphite or git-stack automate rebuilding the stack after the base PR is merged.
Pitfalls
- Do not force-push the PR branch after review has started. Comments attached to specific lines will break (new SHAs have no binding). Force-pushing before review begins is fine; after that, add new commits and squash at merge time.
- Conflict with main occurs when main has moved ahead. Resolve
it by rebasing your branch onto main (
git rebase main) or merging main into your branch (git merge main). Rebase gives a cleaner history; merge is simpler. - Auto-merge is a GitHub setting: the PR merges automatically once all conditions (approve + CI) are met. Convenient, but keep it in mind: sometimes you want to hold the PR before landing.