Part 2 of 4

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.

A branch splitting from main and merging back The main branch runs along the bottom. At the second commit a feature branch splits upward, gains two commits of its own, and then merges back into main further along. Meanwhile main has received an unrelated commit from a teammate. main add-search-box earlier work you branch here a teammate ships something unrelated your PR merges first draft review fixes
Note the teammate's commit in the middle. Their work landing on 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.

starting a piece of work
$ 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'
CommandWhat it does
git switch -c nameCreate a branch and move onto it
git switch nameMove to an existing branch
git switch -Jump back to the branch you were on before
git branchList local branches; the one with * is where you are
git branch -d nameDelete 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 empty
  • Add CSV export to the reports page
  • Bump lodash to 4.17.21 for CVE-2021-23337

Less good

  • fix
  • updates
  • asdf
  • final 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.

a commit with a body
$ 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:

choosing what goes in
$ 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.

git diff
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.
the three diffs you'll want
$ 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.

  1. Push your branch

    first push of a new 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
    

    -u links your local branch to the one on GitHub, so from then on plain git push and git pull know what you mean. You only need it the first time.

  2. 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-box into main. Getting these the wrong way round is a rite of passage.

    Prefer the terminal? gh pr create --web does the same in one step.

  3. 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:

    pull request description
    ## 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.

  4. 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.

OptionWhat lands on mainSuits
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:

after a merge
$ 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:

catching your branch up
$ 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.

Bad commit message,
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".
Committed to main
by 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.
Want to abandon
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.
Need to park work
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.

Back to home