The daily loop
Branch, commit, push, pull request, merge. This is what working on GitHub actually consists of, day after day. Learn this page properly and you can hold your own on almost any team.
1 ยท Why branches exist
Imagine four people editing the same document at once, each saving over the top of the others. That's a shared project without branches.
A branch is a private lane. You take a copy of main, work in it for however long you need, and main stays exactly as it was for everyone else. When your work is finished and reviewed, it gets folded back in. If it turns out to be a bad idea, you throw the branch away and nothing was ever disturbed.
main didn't disturb your branch at all, and yours didn't disturb theirs. That independence is the entire point โ and it's also why merge conflicts happen when you do both touch the same lines. More on that in part 3.Rule of thumb: one branch per unit of work โ one bug, one feature, one document. Small branches get reviewed in minutes; sprawling ones sit for days and collect conflicts.
2 ยท Making and moving between branches
Before branching, make sure you're starting from a current copy of main. Branching off a stale main is one of the most common self-inflicted wounds.
$ git switch main
$ git pull
Updating 4b2c1e0..9df8a31 Fast-forward
# -c creates the branch and moves you onto it
$ git switch -c add-search-box
Switched to a new branch 'add-search-box'
| Command | What it does |
|---|---|
git switch -c name | Create a branch and move onto it |
git switch name | Move to an existing branch |
git switch - | Jump back to the branch you were on before |
git branch | List local branches; the one with * is where you are |
git branch -d name | Delete a branch once it's been merged |
You'll see git checkout in older material. It does branch switching too, plus half a dozen unrelated jobs, which is exactly why it was split up. git switch (branches) and git restore (files) are the modern, less ambiguous replacements. checkout still works and isn't going anywhere.
Naming branches
No rule, but conventions help. Most teams use a short prefix and a hyphenated description: fix/login-redirect, feat/csv-export, docs/api-examples. If your team tracks work in issues, including the number is a kindness: fix/312-login-redirect.
3 ยท Commits worth reading
A commit message is a note to a future stranger, and that stranger is usually you. The gold standard is simple: finish the sentence "if applied, this commit willโฆ"
Good
Fix crash when the search box is emptyAdd CSV export to the reports pageBump lodash to 4.17.21 for CVE-2021-23337
Less good
fixupdatesasdffinal version FINAL v2
For anything non-obvious, add a body. The subject line says what; the body says why, which is the part that can't be recovered from the code.
$ git commit
# opens your editor:
Debounce the search input by 300ms
Every keystroke was firing a request, so a five-letter query
meant five round trips and visible flicker as results raced
each other. 300ms tested as the point where it feels instant
but no longer stutters.
Closes #312
Writing Closes #312 is not decoration โ GitHub reads it, and when this commit reaches main it will close issue 312 automatically and link the two together forever. Fixes and Resolves work the same way.
Staging selectively
You don't have to commit everything you've touched. A few useful forms:
$ git add src/search.js src/search.css
# just these two files
$ git add -p
# walk through each chunk of change and answer y/n โ
# the best habit in this entire guide
$ git restore --staged src/search.css
# changed your mind โ unstage it (the edit itself is untouched)
git add -p deserves a moment. It shows you every chunk of every change and asks whether you want it in this commit. Beyond producing tidier history, it forces you to re-read your own work before it goes anywhere โ and it catches an astonishing number of stray debug lines.
4 ยท Reading a diff
A diff is the before-and-after of a change, and it's the format you'll spend most of your review time in. It looks cryptic once and then never again.
diff --git a/src/search.js b/src/search.js
@@ -12,7 +12,9 @@ function handleInput(event) {
const query = event.target.value;
- fetchResults(query);
+ clearTimeout(pending);
+ pending = setTimeout(() => fetchResults(query), 300);
}
- โ lines were removed, + lines were added. A "modified" line is shown as one of each.
- Lines with no marker are unchanged context, included so you can see where you are.
@@ -12,7 +12,9 @@means: this chunk starts at line 12 and covered 7 lines before, 9 after. You can safely ignore it most of the time.
$ git diff
# edits you haven't staged yet
$ git diff --staged
# what's about to go into your next commit
$ git diff main...HEAD
# everything your branch changes โ the same view your reviewer sees
On GitHub, the same information appears under the Files changed tab of any pull request, in colour, with room to leave a comment on any individual line.
5 ยท Opening a pull request
Despite the name, a pull request isn't a request to pull anything. It's a proposal โ "here are my commits, please look at them and merge them into main" โ wrapped in a discussion thread, a diff view, and (usually) automated checks.
-
Push your branch
$ git push -u origin add-search-box remote: Create a pull request for 'add-search-box' on GitHub by visiting: remote: https://github.com/acme/site/pull/new/add-search-box-ulinks your local branch to the one on GitHub, so from then on plaingit pushandgit pullknow what you mean. You only need it the first time. -
Open the PR
Follow the link Git printed, or use the banner GitHub shows on the repo page. Check the two branches at the top: you're merging
add-search-boxintomain. Getting these the wrong way round is a rite of passage.Prefer the terminal?
gh pr create --webdoes the same in one step. -
Write a description someone can act on
The person reviewing has no idea what you've been thinking for the last two hours. Three short headings cover almost every case:
## What Debounces the search input so it only fires a request 300ms after the user stops typing. ## Why Every keystroke was hitting the API. Closes #312. ## How to check it Open /search, type quickly, watch the network tab โ you should see one request, not eight.If the repo has a template, GitHub pre-fills this box for you. Fill it in rather than deleting it.
-
Open it as a draft if it isn't ready
The Create pull request button has a dropdown with Draft pull request. A draft can't be merged and doesn't ping reviewers, but it does run the automated checks and gives people a place to look. Ideal for "here's my direction, sanity-check me before I go further".
Your branch is still live. A pull request tracks a branch, so any commit you push to that branch afterwards appears in the PR automatically. Responding to review feedback is just: edit, commit, push. There is nothing to re-submit.
6 ยท Merging, and the three buttons
Once the PR is approved and the checks are green, someone presses merge. GitHub offers three ways to do it, and repos usually enforce one. Knowing the difference makes the button meaningful rather than mysterious.
| Option | What lands on main | Suits |
|---|---|---|
| Create a merge commit | All your commits, plus an extra commit recording the merge itself | Preserving exactly what happened. History branches visibly, as in the diagram above. |
| Squash and merge | One single commit containing all your changes | The most popular default. Keeps main a clean list of one-change-per-line, and means nobody has to see your fix typo commits. |
| Rebase and merge | Your commits replayed on top of main, no merge commit |
Teams who want each commit preserved and a perfectly straight history. Least forgiving of the three. |
If you're unsure, squash. It's the safest default and the most common in practice: one pull request becomes one commit on main, with your PR title as its message. Which is a good reason to write a decent PR title.
7 ยท Cleaning up and staying in sync
After the merge, tidy up. GitHub usually offers a Delete branch button on the merged PR โ take it. The commits are safely in main, and the branch has served its purpose. Then, locally:
$ git switch main
$ git pull
# now your main includes your merged work, and everyone else's
$ git branch -d add-search-box
Deleted branch add-search-box (was 8f21b0c).
Long-running branches drift
If your branch lives more than a day or two, main will move underneath it. Pull those changes in periodically rather than discovering all of them at merge time:
$ git switch add-search-box
$ git fetch origin
$ git merge origin/main
git fetch downloads what's new without changing any of your files โ it's always safe to run. git merge is the step that actually applies it. (git pull is simply those two commands in one.) If this produces a conflict, that's normal and part 3 walks through resolving one.
A daily habit worth forming: start the day with git switch main && git pull. Most tangles beginners hit come from having quietly worked on top of a week-old copy of the project.
8 ยท When it goes sideways
Four situations that will happen in your first month, and the one command that fixes each. Nothing here is dangerous.
not yet pushed
git commit --amend โ reopens the message for editing. Also folds any newly staged changes into that same commit, which is the neat way to handle "I forgot a file".mainby accident
git branch my-work then git reset --hard origin/main. The first bookmarks your commits under a new name; the second returns main to matching GitHub. Then git switch my-work and carry on properly.uncommitted edits
git restore path/to/file โ or git restore . for everything. This one genuinely destroys work, because uncommitted changes were never saved anywhere. Look twice.and switch tasks
git stash pockets your changes and gives you a clean tree; git stash pop puts them back when you return.Committed work is very hard to actually lose. Git keeps a log of everywhere you've been โ git reflog โ for around 90 days, including branches you deleted and commits you reset away. If you ever think you've destroyed something, you almost certainly haven't. Level up covers the recovery in detail.