Part 4 of 4

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:

.github/workflows/test.yml
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.

on
The trigger. Pushes, pull requests, a schedule (cron), a manual button (workflow_dispatch), or a release being published.
jobs
Independent units of work. Jobs run in parallel by default β€” use needs: to make one wait for another.
runs-on
Which fresh virtual machine to use. It's clean every run, which is why step one is always checking out your code.
uses
Borrows a prebuilt action from the marketplace. Pin the version (@v4) β€” never leave it floating.
run
Just a shell command. Anything you'd type in a terminal.

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

Merge compared with rebase Merging keeps your branch as a visible fork that rejoins main at a merge commit. Rebasing lifts your commits off and replays them on the tip of main, producing a straight line with no merge commit. git merge origin/main history shows what really happened merge commit your commits main git rebase origin/main history reads as one clean sequence (where they were) replayed on top new IDs β€” same changes main
Rebasing rewrites commits. Your changes are identical, but each commit gets a new ID because its parent changed. That's harmless on a branch only you are using β€” and disruptive on one anybody else has pulled, because their copy and yours no longer share a history.
rebasing your branch onto current main
$ 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.

git rebase -i
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 β€” the undo history
$ 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
SituationCommandNotes
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 .env file, and put .env in .gitignore before you create it.
  • Commit a .env.example with 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 --staged before committing. It takes two seconds and catches most of these.

What GitHub does for you, free

Secret scanning
Recognises the shape of common credentials and alerts you β€” and often the issuing provider, who may auto-revoke the key. On by default for public repos.
Push protection
Blocks the push outright when it detects a known secret pattern. The best of the lot, because nothing ever reaches the server.
Dependabot alerts
Warns when a dependency you use has a published vulnerability, and opens pull requests to bump it.
CodeQL scanning
Static analysis for security bugs, run as an Action. Free on public repos.

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, in anger
$ 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.

tagging a version
$ 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.

GitHub Pages
Free static hosting straight from a repo. Push HTML, get a website. Ideal for docs and portfolios.
Projects
Kanban boards and tables built from your issues and pull requests. A light project management layer.
Discussions
Forum threads for questions and ideas that aren't work items. Keeps the issue tracker for actual work.
Wiki
A per-repo wiki. Most teams end up preferring a /docs folder, because that gets reviewed like code.
Gists
Single-file snippets with their own URL. Quietly, each one is a full Git repo.
Codespaces
A full development environment in the browser, spun up from your repo. Generous free tier.
Git LFS
Large File Storage β€” keeps big binaries (video, PSDs, datasets) out of the history proper, so clones stay fast.
Submodules
A repo nested inside another at a pinned commit. Powerful, and a well-known source of confusion β€” reach for it last.
Sponsors
Recurring funding for maintainers. Worth knowing exists if you come to depend on someone's weekend project.

8 Β· Cheat sheet

Everything on this site, condensed. This page prints reasonably if you want it on paper.

Every day

git statusWhere am I, what's changed β€” run it constantly
git switch mainMove to a branch
git switch -c nameCreate a branch and move to it
git pullFetch and merge the latest from GitHub
git add file / git add -pStage a file / stage chunk by chunk
git commit -m "…"Save a snapshot
git pushSend commits to GitHub
git push -u origin nameFirst push of a new branch

Looking around

git diffUnstaged changes
git diff --stagedWhat's about to be committed
git log --oneline --graphCompact, visual history
git log -p pathEvery change to one file, with diffs
git blame pathWho last touched each line
git branch -aAll branches, local and remote

Fixing things

git commit --amendChange the last commit (only if unpushed)
git restore fileThrow away uncommitted edits destructive
git restore --staged fileUnstage, keeping the edit
git revert <sha>Reverse a pushed commit safely
git reset --soft HEAD~1Undo last commit, keep changes staged
git stash / git stash popPark work / bring it back
git reflogEverywhere you've been β€” the undo of last resort
git merge --abortBack out of a conflicted merge
git rebase --abortBack out of a rebase

Working with others

git fetch originDownload updates without touching your files
git merge origin/mainBring main into your branch
git rebase origin/mainReplay your commits on top of main
git push --force-with-leasePush after a rebase, safely
git remote -vWhich 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

Back to home