◐ ai-teacher

⚙️ Productivity & Craft · Chapter 2

Git and code management for one person

Git is not a backup tool. It is a record of decisions — and interviewers read it.

Study this lesson free in the app →

The basics

Git is a version-control tool: it records snapshots of your code (commits), each with a message, so you can see what changed, when, and why — and safely try changes on the side.

The problem it solves: without it you have pipeline_final_v3_REAL.py, no idea which change broke the revenue number three weeks ago, and no way to work on a new feature without risking the version that runs. Even solo, you need a record you can search.

The mental model: a scientist's lab notebook, not a backup drive. Undo is the least interesting thing it does. The valuable part is answering questions later: when did this number go wrong? (git bisect), why was this decided? (git log), what am I about to ship? (git diff).

Words you must know 5

Commit
a snapshot plus a message. One idea per commit; the message says why, because the diff already shows what.
Branch
an independent line of work (feat/kafka-dedup) you can merge or throw away without touching main.
Staging
choosing what goes into the next commit. git add -p lets you review hunk by hunk.
revert vs reset
revert adds a new commit that undoes an old one (safe once pushed); reset moves history backwards (local only, destructive).
reflog
the log of everywhere HEAD has been; how "lost" commits come back.

And one rule outside the commands: a secret that was ever committed is compromised. Rotate it first.

Explained

$ git log --oneline
7a3f21b update
9c8e44a fix
2b1d90c update again
f40aa12 asdf

versus

$ git log --oneline
7a3f21b Dedup ride events on event_id before aggregation
9c8e44a Add DLQ for unparseable payloads with reason codes
2b1d90c Partition raw landing by event date, not ingest date

Same code. One of these gets read in an interview and answers the question "how do you work?" before you say a word.

The lab notebook

A scientist doesn't keep a notebook so they can undo an experiment. They keep it so that in six months, when a result looks strange, they can find which change caused it and what they were thinking at the time.

Git is that notebook. git revert is the least interesting thing it does.

The valuable operations are the ones that answer questions: when did this number start being wrong? (git bisect), who decided this and why? (git log, git blame), what exactly am I about to ship? (git diff).

A commit message is not a formality. It is the only place the why survives — the code can only ever show the what.

The loop, for a team of one

The PR step feels absurd when you're the only developer. Do it anyway — reading your own diff as a reviewer catches things you cannot see as an author.

The commands that cover 95% of days

# --- daily ---
git status                       # the most-run command in the world
git switch -c feat/kafka-dedup   # create and move to a branch
git add -p                       # stage HUNK BY HUNK — see what you're committing
git commit -m "Dedup events on event_id before aggregation"
git push -u origin feat/kafka-dedup

# --- looking around ---
git log --oneline --graph -20
git diff                         # unstaged changes
git diff --staged                # what's about to be committed
git show 7a3f21b                 # one commit in full

# --- undoing, from safest to sharpest ---
git restore file.py              # throw away uncommitted edits to a file
git restore --staged file.py     # unstage, keep the edits
git commit --amend               # fix the last message or add a file
git revert 7a3f21b               # a NEW commit undoing an old one — safe on shared history
git reset --hard HEAD~1          # delete the last commit. Local only. Destructive

# --- rescue ---
git stash                        # park work, do something urgent, git stash pop
git reflog                       # every HEAD you've had — how you recover 'lost' commits

git add -p is the highest-leverage habit here. It forces you to look at every hunk, which catches debug prints, commented-out code and stray credentials before they're history.

What makes a commit good

One commit = one idea. If the message needs an "and", it's two commits. A change you can't describe in one line is a change nobody can review — including you, later.

The message says why, not what. The diff already shows what.

✗ Update pipeline.py
✗ Fixed bug
✓ Dedup events on event_id — Kafka at-least-once was double-counting revenue
✓ Partition raw by event_date not ingest_date so backfills land correctly

A format worth adopting: imperative mood, under ~70 characters, then a blank line and a paragraph if the why needs room.

Add DLQ for unparseable payloads

A single malformed record was stalling the partition and lag climbed
until the on-call page. Records that fail parsing three times now go
to ride_events.dlq with a reason code, and the consumer commits past
them. Alert fires if the DLQ rate exceeds 0.5%.

Write that and in eight months, when someone asks why the DLQ threshold is 0.5%, the answer exists.

The number went wrong three weeks ago

Daily revenue has been ~8% low since roughly the 12th. Nobody knows which change did it. There are 60 commits in that window, and the pipeline takes 20 minutes to run.

You can test any given commit by running the pipeline and checking the total.

The credential in the history

You commit a config file with a database password. You notice ten minutes later, delete the line, and commit the fix.

The password is still in the repository. Git keeps every version — that's the entire point of it. Anyone with the repo can run git log -p and read it.

If you've already pushed to GitHub, treat it as public. Bots scan public commits for credentials within minutes of a push.

What to actually do, in order:

  1. Rotate the credential. Immediately, before anything else. Assume it is compromised — this is not a code problem any more
  2. Only then clean the history (git filter-repo, or BFG). On a shared repo this rewrites history and everyone must re-clone
  3. Add the file to .gitignore and commit that
  4. Install a pre-commit secret scanner so the next one is caught before it becomes history

And the habit that prevents it entirely: secrets live in environment variables and a secrets manager, never in a file inside the repo. A .env file is fine — as long as .env is in .gitignore from the first commit, and .env.example (with fake values) is what's tracked.

Repo layout that survives you

rideflow/
  README.md            what it is, how to run it, in 20 lines
  .gitignore           .env, __pycache__, data/, .venv
  .env.example         every variable, with fake values
  pyproject.toml       declared dependencies
  uv.lock              locked dependencies — commit this
  src/                 the code
  tests/               the tests
  sql/                 models and migrations
  docs/adr/            why decisions were made

The two that separate a portfolio repo from a homework folder:

A README that lets a stranger run it. What problem it solves, the architecture in five lines, how to run it locally, what the trade-offs were. An interviewer opening your repo reads this and nothing else.

docs/adr/ — Architecture Decision Records. One short file per real decision: the context, the choice, the alternatives, the consequence. 0003-partition-by-event-date.md. Three paragraphs each.

ADRs are unusual in a personal project, and that's exactly why they land. They demonstrate that you make decisions deliberately rather than by default — which is the thing senior interviews are trying to establish.

Words to know

CommitA snapshot plus a message recording what changed and why.
BranchAn independent line of work you can develop and discard without touching main.
git add -pStage changes hunk by hunk, reviewing each one before it becomes history.
Pull requestA proposed change presented for review before merging — useful even solo.
git bisectBinary search through history to find the commit that introduced a problem.
git revertA new commit undoing an old one — safe on history others have pulled.
git reflogA log of every position HEAD has held; how you recover 'lost' commits.
ADRArchitecture Decision Record — a short file recording a decision and its reasoning.
Lock fileThe exact resolved dependency versions, committed so builds reproduce.

In two minutes

Test yourself

1. A bug appeared somewhere in the last 60 commits. Fastest way to find it?

  1. Read every diff
  2. git bisect — binary search, about 6 tests
  3. git blame on the whole file
  4. Revert all 60 and reapply
Show the answer

git bisect — binary search, about 6 tests — Bisect halves the search space each step: log₂(60) ≈ 6. It only works well if commits are small and individually runnable.

2. You committed a password, then deleted the line and committed again. Is it safe?

  1. Yes — the file no longer has it
  2. No — it is permanently in history; rotate the credential immediately
  3. Only if you never push
  4. Yes, after git gc
Show the answer

No — it is permanently in history; rotate the credential immediately — Git keeps every version. If pushed to a public repo, assume bots have already read it. Rotate first, clean history second.

3. What should a commit message explain?

  1. What lines changed
  2. Why the change was made
  3. Who made it
  4. When it was made
Show the answer

Why the change was made — The diff already shows what changed; Git records who and when. The why exists nowhere else — that's the message's whole job.

4. Why use git add -p instead of git add .?

  1. It is faster
  2. You review each hunk, catching debug prints, dead code and stray secrets
  3. It compresses better
  4. It is required for branches
Show the answer

You review each hunk, catching debug prints, dead code and stray secrets — It turns committing into a review step. Most accidental commits of debug code or credentials come from a reflexive git add ..

5. You need to undo a commit that others have already pulled. Use…

  1. git reset --hard
  2. git revert
  3. git commit --amend
  4. delete and re-clone
Show the answer

git revert — revert creates a new commit undoing the old one, leaving history intact. reset and amend rewrite history and break other clones.

6. You staged pipeline.py by mistake but want to keep your edits. Which command?

  1. git restore pipeline.py
  2. git reset --hard
  3. git restore --staged pipeline.py
  4. git revert pipeline.py
Show the answer

git restore --staged pipeline.py — --staged removes it from the next commit and leaves the working-copy edits alone. Plain restore throws your edits away; reset --hard throws everything away.

There are 11 questions, 3 puzzles and 7 recall cards for this lesson in the app — the recall cards come back on later days, just before you would forget.

Learn it so it stays. Read it, test yourself, then recall it on two different days. The app schedules that for you, free.

Start this lesson →