The Pull Request Lifecycle
A pull request (PR) is a request to merge your branch into a shared branch. The lifecycle looks like this:
1. Branch from main → 2. Commit work → 3. Push & open PR
↓
4. Automated CI runs (tests, lint, build)
↓
5. Code review (teammates leave comments)
↓
6. Address feedback → push new commits → re-review
↓
7. Approved → merge → branch deleted
Most teams require at least one approval and a passing CI build before merging. Some require two approvals for sensitive areas (auth, payments).
Writing a Good PR Description
A good PR description answers three questions before the reviewer even looks at the diff:
What changed? — A brief summary of the code changes. Why? — The problem being solved or feature being added (link to the issue). How? — Any non-obvious implementation choices worth explaining.
Template:
## Summary
Closes #123
Adds rate limiting to the `/api/search` endpoint to prevent abuse.
Limit is 100 requests per minute per IP, configurable via env var.
## Changes
- Added `RateLimiter` middleware using a sliding window algorithm
- Added `RATE_LIMIT_RPM` env var (default: 100)
- Added unit tests for the limiter and integration test for the endpoint
## Testing
- [ ] Unit tests pass (`npm test`)
- [ ] Manually tested: confirmed 429 returned after limit exceeded
- [ ] Tested limit reset after 60 seconds
## Notes
The Redis-based implementation was considered but skipped — the
in-memory store is sufficient for single-instance deployments.
Code Review Etiquette
As a reviewer:
- Review the code, not the person. “This function is too long” not “you wrote this wrong.”
- Ask questions before assuming mistakes: “I’m curious why you chose X over Y — is there a reason?”
- Distinguish between blocking issues and suggestions. Use prefixes like
nit:for minor style preferences that shouldn’t block the PR. - Be specific. “This could be improved” is not actionable. “Extract this into a helper function to reduce duplication on lines 34 and 67” is.
- Respond within a reasonable time. A PR that sits unreviewed for 3 days blocks the author.
As an author:
- Keep PRs small and focused. A 50-line PR gets thorough review. A 1000-line PR gets a skim.
- Respond to all comments, even if only to say “Fixed” or “Good point, left a comment explaining why I kept it.”
- Don’t resolve reviewer threads yourself — let the reviewer confirm the fix is acceptable.
- Mark work-in-progress PRs as drafts so reviewers know not to do a full review yet.
Merge Strategies
GitHub and GitLab offer three options when merging a PR:
Merge Commit
git merge --no-ff feature/my-feature
Preserves the full branch history. Creates a merge commit. Best when you want to see exactly which commits were part of which feature.
History looks like:
* Merge pull request #45 (merge commit)
|\
| * Add validation logic
| * Add form HTML
|/
* Previous main commit
Squash and Merge
All commits from the branch are squashed into a single commit on main.
Best for: Messy WIP histories (fix typo, oops, another fix) that don’t need to be preserved. Keeps main history clean and linear.
* Add login form with validation (squashed) ← one clean commit
* Previous main commit
Rebase and Merge
Replays each commit from the branch onto main without a merge commit.
Best for: When you want a fully linear history and your commits are already clean and atomic.
* Add CSS for login button
* Add form validation
* Add login form HTML
* Previous main commit
Recommendation for most teams: Use Squash and Merge as the default. It keeps main clean, each PR becomes one commit, and it’s easy to revert a feature by reverting a single commit.
Make Pull Requests Reviewable
A good pull request is small enough to understand and complete enough to test. Reviewers should be able to answer three questions quickly: what changed, why it changed, and how it was verified.
Write a short description that explains the intent, not just the files touched. Include screenshots for UI changes, sample requests for API changes, and migration notes for data changes. If there is a risky tradeoff, name it directly.
Code Review Tradeoffs
As an author, review your own diff before asking others to review it. Remove debug logs, unrelated formatting churn, and accidental files. As a reviewer, focus first on correctness, maintainability, security, and user impact. Style comments are useful only when they reflect an agreed standard.
Ask questions when intent is unclear. Do not turn every preference into a blocking comment. A healthy review process improves the code without making contributors dread opening a pull request.
Merge Strategy
Squash merges keep main history tidy when pull requests contain many small checkpoint commits. Merge commits preserve branch context. Rebase merges keep history linear. Pick one default and document exceptions.
For most small teams, squash merge plus clear pull request titles is simple and effective. For larger teams with release auditing needs, preserving merge commits may be worth the extra history noise.
What I Would Do In Practice
I would use short-lived branches, required CI, at least one review for meaningful changes, and a pull request template with verification steps. I would keep changes small enough that review is a normal part of daily work, not a ceremony that happens after weeks of isolation.
The point of a Git workflow is not process for its own sake. It is shared confidence: the team knows what changed, why it changed, and that the change passed the agreed checks before it reached main.
Keeping Forks and Branches Up to Date
When working from a fork (common in open source):
# Add the upstream remote once
git remote add upstream https://github.com/original/repo.git
# Fetch latest changes from upstream
git fetch upstream
# Rebase your branch onto upstream/main
git rebase upstream/main
# Force-push your updated branch
git push origin feature/my-fix --force-with-lease
When working on a long-running feature branch in a shared repo:
# Keep your feature branch current with main
git fetch origin
git rebase origin/main
# Or, if your team prefers merge over rebase:
git merge origin/main
Rebase more frequently to avoid large conflicts — daily is not excessive for active branches.
Automating Quality Gates with CI
Most teams integrate a CI system (GitHub Actions, GitLab CI, CircleCI) that automatically runs on every PR push:
# .github/workflows/ci.yml (example)
on: [pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run lint
- run: npm test
- run: npm run build
Branch protection rules enforce that CI must pass before merging. Set these in your repository settings to prevent accidental merges of broken code.
Keep The Workflow Human
The best Git workflow is not the one with the most rules. It is the one that helps people review small changes, understand intent, and recover when something goes wrong. Automation should reduce review burden, not replace judgment.
If pull requests keep getting stuck, look for process friction: unclear ownership, oversized diffs, flaky tests, missing screenshots, or review comments that arrive too late. Fixing those habits often improves delivery more than changing the merge button.