Sscribnet.io
Zero to Dangerous

GitHub

From "what's a repo" to shipping websites, automating your checks, and scripting GitHub like it's your own API — version control without the fear.

6 chapters~30 min readBeginner
Chapter 1

Git in 15 Minutes

Every project eventually hits the same wall: you've got config.txt, config.txt.bak, config-FINAL.txt, and config-FINAL-v2-really.txt, and no idea which one actually worked. Git is the tool that kills that workflow forever. It records the full history of your files so you can rewind, compare, and experiment without ever losing work.

The mental model that makes everything click: Git stores snapshots, not diffs. Every time you commit, Git takes a complete picture of your project at that moment and files it away with a message, an author, and a timestamp. Think of commits as save-game slots — each one captures the entire game state, and you can always reload an earlier slot. The "diffs" you see in tools are computed on demand by comparing two snapshots; they're a view, not the storage.

To make that work, Git keeps three areas. Your working directory is the files you're editing right now. The staging area (also called the index) is the loading dock: the exact set of changes you want in your next snapshot. And the repository — the hidden .git folder — is the vault where snapshots live permanently. The whole game is moving changes deliberately from working directory, to staging, to repo. That deliberate two-step is what lets you craft clean snapshots instead of committing whatever happened to be lying around.

Here it is, end to end:

$ git init my-notes
$ cd my-notes
$ echo "hello" > notes.txt
$ git add notes.txt        # working dir -> staging
$ git commit -m "First note"   # staging -> repo (one snapshot)

Two commands tell you what's going on at any moment. git status shows which files changed and which are staged — run it constantly, it's free. git log --oneline shows your snapshot history:

$ git log --oneline
9f2c1a4 First note
4b8d2e1 Add the todo list

That weird string is the commit's hash — its unique ID. You'll use the first few characters whenever you need to point at a specific snapshot, like checking out an old version to see what changed.

Tip: Write commit messages like you're explaining the change to a coworker at 2am during an outage. "Fix bug" says nothing; "Fix login crash when password contains a space" saves a future you.
Footgun: git commit -a -m "stuff" skips the staging area and snapshots every tracked file at once. Convenient, dangerous — it bundles unrelated changes into one mystery snapshot. Use git add deliberately until the habit is automatic.

The full command reference lives at git-scm.com/docs — bookmark it; you'll outgrow tutorials but never this page.

Chapter 2

Branches and the Collaboration Loop

Once you can snapshot, the next problem is obvious: how do you try something risky without endangering the version that works? Branches are the answer, and they're cheaper than you think. A branch is just a movable pointer to a commit — creating one doesn't copy any files, it just sticks a label on your current snapshot. The analogy that earns its keep: a branch is an alternate timeline that costs nothing to open and nothing to delete.

The loop looks like this. You're on main, the timeline everyone trusts. You open a branch for one unit of work:

$ git switch -c add-search
$ # ... edit files, commit a few times ...
$ git switch main
$ git merge add-search   # fold the timeline back in

If the two timelines touched different files, the merge is automatic and boring — which is exactly what you want. If both changed the same lines, Git stops and asks you to decide. This is the merge conflict, and it's the part where beginners panic. Don't. Git marks the disputed region right in the file:

<<<<<<< HEAD
total = price * 1.0
=======
total = price * 1.08
>>>>>>> add-tax

That's not an error message — it's a question with both answers printed. Pick the right one (or write a third), delete the markers, then git add and git commit to finish the merge. Conflicts are Git refusing to guess on your behalf. You'd rather it ask than silently pick wrong.

Pull requests are a conversation, not a merge button

On GitHub, the collaboration loop runs through pull requests. You push your branch to GitHub and open a PR against main. Yes, it ends with a merge — but treating it as just a merge button wastes the whole point. A PR is where the change gets discussed: reviewers leave line-by-line comments, automated checks run against it, and the author pushes follow-up commits until everyone is satisfied. The merge is the period at the end of the sentence, not the sentence.

This is also where the loop closes for teams: git pull brings everyone else's merged work into your local repo, and short-lived branches (open it, finish the work, merge it, delete it) keep the timeline history readable. Long-lived branches that drift for weeks are where merge conflicts go to become merge disasters.

Footgun: Never commit directly to main on a shared repo. It bypasses review, breaks the conversation, and trains everyone else to do the same. Branches are free — act like it.
Chapter 3

GitHub Pages: Free Hosting for Your Stuff

Here's the problem GitHub Pages solves: you built a static site — some HTML, CSS, maybe JavaScript — and now you need it on the internet without renting a server. Pages takes a branch of your repo and serves it as a website, free, with TLS. Push to the branch, the site updates. That's the whole pitch, and it's genuinely that simple.

Setup takes about a minute. In your repo: Settings → Pages → "Deploy from a branch," pick main and /(root). Push an index.html to that branch and your site goes live at https://<your-org>.github.io/<repo>/. Every push rebuilds and redeploys automatically — your deploy pipeline is literally git push.

Custom domains and the certificate wait

Want your own domain instead of the github.io address? Two steps. First, add a file named CNAME (no extension) to the root of your site branch, containing just your domain:

www.<your-domain>

Second, at your DNS provider, point a CNAME record for that hostname at <your-org>.github.io. GitHub sees the CNAME file, verifies the DNS, and starts provisioning a TLS certificate for your domain automatically.

Now the part everyone rushes: do not flip "Enforce HTTPS" before the certificate exists. Certificate issuance takes anywhere from a few minutes to roughly a day. The "Enforce HTTPS" checkbox only becomes available once the cert is actually issued — trying to enable it early fails (via the API you'll get a blunt "certificate does not exist yet"). The correct sequence is: set the domain, wait for the cert, then enforce HTTPS. Patience here is a feature, not a delay.

Tip: Batch your changes into one commit per deploy. Rapid-fire pushes trigger a build race — GitHub can't build Pages that fast in succession, and the intermediate builds fail while only the last one succeeds. One commit, one push, one clean build.

The official walkthrough is at docs.github.com/en/pages.

Chapter 4

Actions: CI That Does Your Chores

Every project accumulates chores: run the tests, lint the code, build the site, check that nothing broke. Humans are terrible at doing these consistently — we forget, we skip "just this once," and that's how broken code lands on main. GitHub Actions is the part of GitHub that does the chores for you, automatically, on every event you care about.

The core idea: a workflow is a YAML file in .github/workflows/ that says "when X happens, do Y on a fresh virtual machine." The "when" is the trigger — push, pull_request, a cron schedule, or a manual workflow_dispatch button. The "do" is a list of steps that run in order. One sharp analogy: Actions is a robot intern watching your repo — you write it a checklist once, and it runs that checklist perfectly every time the doorbell rings.

Here's a real one. This runs your test suite on every push to main and every pull request, then saves the test report as an artifact (a downloadable file attached to the run):

# .github/workflows/checks.yml
name: checks
on:
  push:
    branches: [main]
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "22"
      - run: npm ci
      - run: npm test
      - uses: actions/upload-artifact@v4
        with:
          name: test-report
          path: test-results/

Read it top to bottom: name the workflow, declare the triggers, define a job on a fresh Ubuntu runner, check out the code, set up Node, install dependencies, run the tests, stash the results. If any step fails, the run goes red and — this is the payoff — the pull request shows a big red X before anyone merges it. Broken code has to get past the robot first.

Footgun: Pin your third-party actions to a version (@v4, not @main). An unpinned action can change underneath you overnight and break every workflow in the repo — or worse, run code you didn't review.

The workflow syntax reference is at docs.github.com/en/actions — dense but complete.

Chapter 5

Scripting GitHub: the API and gh CLI

Clicking through a web UI is fine once. Doing it fifty times is a job for a script. GitHub exposes nearly everything — repos, issues, pull requests, releases, Actions runs — through a REST API, and ships a command-line tool, gh, that wraps it. The rule of thumb: if you're about to do something repetitive in the browser, there's a command for it.

Start with gh auth login — it walks you through signing in once, then every later command just works. From there, the daily drivers:

$ gh issue list --limit 5
$ gh issue create --title "Login page crashes on Safari" \
    --body "Steps to reproduce: ..."
$ gh pr create --title "Add search" --body "Closes #12"
$ gh pr merge 14 --squash

That last one is the whole PR loop from the terminal: create the branch, push it, open the PR with a body, merge it with a squash — no browser tabs involved. For anything gh doesn't cover directly, gh api drops you straight onto the REST API with your auth handled:

$ gh api repos/<your-org>/hello-world --jq '.full_name, .default_branch'

The API's shape

The REST API itself is beautifully predictable: nouns are URLs, verbs are HTTP methods. GET /repos/<owner>/<repo> reads a repo, POST /repos/<owner>/<repo>/issues creates an issue, PATCH edits. You can see the shape with plain curl against GitHub's public demo repo — no auth needed:

$ curl -s https://api.github.com/repos/octocat/Hello-World | head -8
{
  "id": 1296269,
  "node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5",
  "name": "Hello-World",
  "full_name": "octocat/Hello-World",
  "private": false,
  ...

Everything returns JSON, everything is paginated, and the docs list every endpoint. Once you internalize "noun = URL, verb = method," you can script bulk operations — label a hundred issues, open PRs across ten repos, pull Actions logs nightly — that would take an afternoon of clicking.

Tip: Prefer gh over raw curl with tokens whenever you can. gh stores your auth securely and handles pagination and JSON for you — one less secret to leak, one less script to debug.

Full endpoint reference at docs.github.com/en/rest.

Chapter 6

Keeping Repos Clean

A repo is a public artifact — even a private one gets cloned onto laptops, CI runners, and eventually someone's new job. Everything you commit should be something you'd be comfortable explaining. This chapter is the hygiene that separates a repo you're proud to link from one you hope nobody opens.

Start with .gitignore: a file in the repo root listing patterns Git should never track. Dependencies, build output, OS noise, and above all secrets:

# dependencies and build output
node_modules/
dist/

# secrets — never commit these
.env
*.pem
*.key

# OS noise
.DS_Store
Thumbs.db

Check with git status before every commit — if something shows up that shouldn't be there, fix the ignore file first. And if a file is already tracked, ignoring it later isn't enough; untrack it without deleting your local copy via git rm --cached .env.

Footgun: Pushing a secret is a compromise, not a typo. Deleting the file in a later commit doesn't help — it's still in history. Rotate the credential immediately, then clean the history. The only safe secret is one that was never committed.

Sanitize like it's public

Assume every repo becomes public eventually and write accordingly. No real usernames, no real IP schemas, no emails, no org names in committed files. Use placeholders: <your-org> for the org, labuser for usernames, 192.0.2.0/24 (TEST-NET-1, reserved for documentation) for IPs. Your real values live in local, gitignored config files — never in the repo.

CHANGELOG discipline

A CHANGELOG.md is the human-readable story of what changed and when. The format that works: dated sections, newest first, written in the same commit as the change itself — never as an afterthought three commits later.

## 2026-09-29
- Added search to the launcher page
- Fixed crash on empty playlists

## 2026-09-28
- Shipped the podcast episode grid

Pair that with the one-commit deploy habit from Chapter 3 — batch related changes, one commit, one push — and your history reads like a table of contents instead of a crime scene.

The dangerous checklist