The GitHub Flow & Pull Requests
How teams ship changes together without chaos — the GitHub Flow loop, what a pull request really is, and how to choose between merge, squash, and rebase.
What you'll learn
- The GitHub Flow loop: branch → commit → push → PR → review/CI → merge → delete
- What a pull request is: a merge request plus a review surface plus a CI gate
- Merge options on GitHub: when to pick squash-and-merge over a plain merge commit
Before you start
The loop at a glance
GitHub Flow is deliberately minimal. It has exactly one rule: main is always deployable. Everything else follows from that constraint.
The loop looks like this:
- Branch off
main. - Do your work in commits on that branch.
- Push the branch to GitHub.
- Open a pull request (PR) targeting
main. - Teammates review; CI runs automated checks.
- Address feedback with more commits on the same branch.
- Merge when approved and green.
- Delete the branch.
Repeat.
What a pull request actually is
A pull request is three things bundled together:
- A merge request. It says: “please merge branch
feat/loginintomain.” - A review surface. Teammates can comment on individual lines, approve, or request changes.
- A CI gate. GitHub runs status checks (tests, linters, security scans) against the PR branch before anyone can merge.
Nothing about a PR is magic. Under the hood it is just two branch names and a diff. The power comes from making the conversation and the checks happen before the code touches main.
Walking through the loop
1. Create a branch off main
git switch main
git pull # make sure you start from the latest
git switch -c feat/add-dark-mode # short, descriptive name
Branch names are cheap. Use them freely.
2. Commit your work
# ... edit files ...
git add src/theme.ts
git commit -m "add dark-mode token set"
# ... more edits ...
git add src/toggle.tsx
git commit -m "wire ThemeToggle to token set"
Keep commits focused. Each one should leave the code in a working state.
3. Push the branch
git push -u origin feat/add-dark-mode
The -u flag sets the upstream so future git push calls on this branch need no arguments.
4. Open a pull request
GitHub prints a URL to open a PR immediately after your first push. Click it, or use the GitHub CLI:
gh pr create \
--base main \
--title "Add dark-mode support" \
--body "Closes #42. Adds token set and wires the toggle."
Write a PR description that explains why, not just what. Reviewers who understand the context give faster, better feedback.
5. Review, CI, and iteration
While the PR is open:
- CI runs your test suite and linters automatically.
- Teammates leave comments or approve.
- You push more commits to address feedback — they appear in the same PR automatically.
# Address a reviewer's comment, then push
git add src/toggle.tsx
git commit -m "fix toggle aria-label per review"
git push
You never need to close and reopen the PR. The new commits show up in the existing thread.
6. Merge and delete
Once the PR is approved and all checks are green, merge it on GitHub. Then clean up:
git switch main
git pull # pull the merged commit
git branch -d feat/add-dark-mode # delete local branch
git push origin --delete feat/add-dark-mode # delete remote branch
GitHub can be set to delete the remote branch automatically after merge — turn that on in repository settings.
Fork-based contribution
When you do not have write access to a repository — as is common in open source — you cannot push a branch directly. Instead you fork the repo: GitHub copies it under your account, giving you full write access to your copy.
The flow is the same, but the push target is your fork, and the PR targets the original (upstream) repository’s main.
# After forking on github.com:
git clone https://github.com/YOUR-USERNAME/the-project.git
git switch -c fix/typo-in-readme
# ... edit ...
git push -u origin fix/typo-in-readme
gh pr create --repo original-owner/the-project --base main
For repos where you are a collaborator, skip the fork — push your branch directly.
Keeping a PR branch current
If main receives new commits while your PR is open, your branch falls behind. You have two options:
Option A — merge main in (safer, preserves history):
git switch feat/add-dark-mode
git fetch origin
git merge origin/main
git push
Option B — rebase onto main (cleaner history, rewrites commits):
git switch feat/add-dark-mode
git fetch origin
git rebase origin/main
git push --force-with-lease # required after rebase
For shared PR branches where a teammate may also have pushed, prefer the merge option. Rebase is fine on branches only you are using.
Merge options on GitHub
When you click the merge button, GitHub offers three strategies:
| Strategy | What it does | History shape |
|---|---|---|
| Merge commit | Creates a merge commit with two parents | Preserves every commit from the branch |
| Squash and merge | Collapses all PR commits into one commit on main | Flat, one commit per PR |
| Rebase and merge | Replays PR commits linearly onto main | Linear, multiple commits |
When to pick squash-and-merge: your PR branch has noisy intermediate commits (“fix typo”, “add back missing semicolon”, “WIP”) and you want main’s history to read as one clean unit of work per feature. Most teams standardize on squash-and-merge for this reason.
When to keep a merge commit: you want the full commit story preserved — useful on projects where git bisect needs granular commits to pinpoint regressions.
Reviewing etiquette
A few practices that make reviews faster and kinder:
- Reviewer: distinguish blocking feedback (“this will break X”) from suggestions (“we could simplify this, but it works either way”).
- Author: respond to every comment, even if just to say you pushed a fix or explain why you kept the original approach.
- Everyone: small PRs review faster. A 200-line diff gets thorough review in minutes. A 2000-line diff gets skimmed.
This connects directly to the merging lesson — understanding fast-forward vs. merge commit behavior there explains exactly what GitHub is doing under each of the three merge buttons here.