Level up
The intermediate half. None of this is needed to be useful on a team β but each one turns something that felt like magic, or like a disaster, into something routine. Dip in as you need it.
1 Β· Actions: making the robots do it
GitHub Actions runs code on GitHub's machines when something happens in your repo. Most commonly: someone opens a pull request, so run the tests. That's continuous integration, and it's the difference between "I think this works" and "the machine confirms this works".
A workflow is a YAML file in .github/workflows/. Here's a complete, working one:
name: Tests
# when to run
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
# get the code onto the runner
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm test
Commit that file and it's live. Every pull request now gets a green tick or a red cross, and the whole team can see it.
oncron), a manual button (workflow_dispatch), or a release being published.jobsneeds: to make one wait for another.runs-onuses@v4) β never leave it floating.runReading a failure
A red cross on your pull request means a job failed. Click Details next to it, then the failed step, which expands to the log. The genuinely useful trick: search the log for the first error, not the last. Later errors are usually consequences of the first one.
Actions are free for public repositories and metered by the minute for private ones, with a monthly free allowance. Worth knowing before you set a workflow to run every five minutes.
Beyond tests, the same mechanism handles linting, security scans, building containers, publishing packages, deploying on merge, and labelling stale issues. If a sentence starts "every time someoneβ¦", it's probably an Action.
2 Β· Rebase vs merge
Both answer the same question β my branch is out of date, how do I catch it up? β and they produce different histories.
$ git fetch origin
$ git rebase origin/main
# conflicts are handled one commit at a time:
# fix the file, then git add, then:
$ git rebase --continue
# lost? this always undoes the whole thing:
$ git rebase --abort
The golden rule of rebasing: never rebase commits that other people have already pulled. Rewriting shared history forces everyone else into a mess. Your own unpushed or unshared branch? Rebase freely.
If you rebase a branch you've already pushed, the push will be rejected. The correct fix is git push --force-with-lease β which, unlike plain --force, refuses if someone else pushed in the meantime. Make it your habit; plain --force is how people destroy a colleague's afternoon.
Interactive rebase: tidying before review
git rebase -i origin/main opens an editor listing your commits, and lets you reorder, reword, drop, or squash them together before anyone sees them.
pick 4a91c2e Add search box markup
squash 8f21b0c fix typo
squash c72e4d1 actually fix typo
reword 1d9a5f3 wire up teh handler
# pick = keep as is
# reword = keep the change, rewrite the message
# squash = fold into the commit above it
# drop = delete the commit entirely
Four scrappy commits become two clean ones. Optional β many teams just squash on merge and never think about it β but genuinely satisfying.
3 Β· Undoing almost anything
The single most reassuring fact about Git: it keeps a private log of every position HEAD has occupied, for roughly 90 days. Deleted branches, reset commits, botched rebases β nearly all of it is still on disk.
$ git reflog
9df8a31 HEAD@{0}: reset: moving to origin/main
8f21b0c HEAD@{1}: commit: Add CSV export β the work I thought I'd lost
4b2c1e0 HEAD@{2}: checkout: moving from main to add-export
# get it back on a fresh branch:
$ git switch -c recovered 8f21b0c
| Situation | Command | Notes |
|---|---|---|
| Undo a commit that's already public | git revert <sha> |
The safe one. Creates a new commit that reverses the old one. Nothing is rewritten, so it's fine on shared branches. |
| Undo the last commit, keep the edits | git reset --soft HEAD~1 |
The commit vanishes, your changes stay staged. Good for "that should have been two commits". |
| Undo the last commit and the edits | git reset --hard HEAD~1 |
Discards both. Recoverable via reflog β but only for committed work. |
| Grab one commit from another branch | git cherry-pick <sha> |
Copies that single commit onto your current branch. Handy for backporting a fix. |
Restore a file to how main has it |
git restore --source=main path |
Surgical. Leaves everything else alone. |
| Find which commit broke something | git bisect |
Binary search through history. Ten steps to find the culprit among a thousand commits. |
| Find who changed a line, and why | git blame path |
Less accusatory than it sounds β usually you're after the commit message. GitHub has a Blame button on every file. |
Revert or reset? If the commit has been pushed and other people may have it: revert, always. If it's only ever existed on your machine: either is fine, and reset is tidier.
4 Β· Keeping secrets out
Committing an API key is the most common genuinely costly mistake on GitHub, and it happens to experienced people constantly. Public repos are scraped by bots within seconds of a push.
If you've just pushed a secret, the first move is not Git. Deleting the commit does not help β the key is already out. Go and revoke or rotate the credential right now, at whatever service issued it. Only once it's dead is it worth cleaning the history.
Then, to purge it: git-filter-repo or the BFG Repo-Cleaner rewrite the history, after which you force-push and ask collaborators to re-clone. Any existing forks and clones keep their copy regardless β which is exactly why revoking comes first.
Not committing them in the first place
- Keep secrets in a
.envfile, and put.envin.gitignorebefore you create it. - Commit a
.env.examplewith the variable names and blank values, so collaborators know what's needed. - For Actions, use Settings β Secrets and variables and read them as
${{ secrets.MY_KEY }}β they're encrypted and masked in logs. - Run
git diff --stagedbefore committing. It takes two seconds and catches most of these.
What GitHub does for you, free
Find all of these under Settings β Code security. On a new project, turning them all on takes under a minute.
5 Β· The gh command line tool
Everything on the GitHub website, from your terminal. The real payoff is not typing fewer characters β it's never breaking flow to go and click something.
$ gh pr create --fill
# opens a PR using your commit message as the title and body
$ gh pr status
# your PRs, what's been reviewed, what's failing
$ gh pr checkout 42
# check out someone else's PR locally to try it β even from a fork
$ gh pr checks --watch
# live CI status without refreshing a browser tab
$ gh run view --log-failed
# jump straight to the failing step's output
$ gh repo clone acme/site
$ gh issue list --label "good first issue"
$ gh browse
# open this repo's page in your browser
gh pr checkout is the standout. Reviewing a change by actually running it beats reading a diff, and it's one command.
6 Β· Tags and releases
A tag is a permanent name pinned to one commit β almost always a version number. Unlike a branch, it never moves.
$ git tag -a v1.2.0 -m "Search and CSV export"
$ git push origin v1.2.0
# tags are not pushed by plain `git push` β they need naming
A release is GitHub's layer on top: a tag plus release notes, plus any files you want to attach (compiled binaries, installers). GitHub can generate the notes from your merged pull requests, which is a strong argument for decent PR titles.
Most projects number releases MAJOR.MINOR.PATCH β semantic versioning. Patch for fixes, minor for new features that don't break anything, major for changes that do.
7 Β· The rest of GitHub, briefly
Things you'll see in the sidebar and wonder about. One line each so the names stop being mysterious.
/docs folder, because that gets reviewed like code.8 Β· Cheat sheet
Everything on this site, condensed. This page prints reasonably if you want it on paper.
Every day
git status | Where am I, what's changed β run it constantly |
git switch main | Move to a branch |
git switch -c name | Create a branch and move to it |
git pull | Fetch and merge the latest from GitHub |
git add file / git add -p | Stage a file / stage chunk by chunk |
git commit -m "β¦" | Save a snapshot |
git push | Send commits to GitHub |
git push -u origin name | First push of a new branch |
Looking around
git diff | Unstaged changes |
git diff --staged | What's about to be committed |
git log --oneline --graph | Compact, visual history |
git log -p path | Every change to one file, with diffs |
git blame path | Who last touched each line |
git branch -a | All branches, local and remote |
Fixing things
git commit --amend | Change the last commit (only if unpushed) |
git restore file | Throw away uncommitted edits destructive |
git restore --staged file | Unstage, keeping the edit |
git revert <sha> | Reverse a pushed commit safely |
git reset --soft HEAD~1 | Undo last commit, keep changes staged |
git stash / git stash pop | Park work / bring it back |
git reflog | Everywhere you've been β the undo of last resort |
git merge --abort | Back out of a conflicted merge |
git rebase --abort | Back out of a rebase |
Working with others
git fetch origin | Download updates without touching your files |
git merge origin/main | Bring main into your branch |
git rebase origin/main | Replay your commits on top of main |
git push --force-with-lease | Push after a rebase, safely |
git remote -v | Which remotes this repo knows about |
git cherry-pick <sha> | Copy one commit onto this branch |
Three things to remember
Run git status
It answers "what now?" more often than any search engine will.
Commit early, commit small
Committed work is recoverable. Uncommitted work is the only kind you can truly lose.
Nothing here is permanent
Branches, merges, resets β reflog and revert mean there's a way back from nearly everything.
Where to go from here
You've covered the whole arc β setup, the daily loop, collaboration, and the intermediate tooling. What actually cements it is a real project, however small. Put a side project on GitHub, fix a typo in a piece of documentation you use, or offer to take a good first issue on something you like.
- GitHub Docs β the official reference, genuinely well written
- Pro Git β the free, complete book on Git itself. Chapters 2 and 3 are the ones worth your time
- Learn Git Branching β an interactive visualiser for branching and rebasing; the fastest way to make merge vs rebase click
- Oh Shit, Git!?! β a short list of common disasters and the exact commands out of them