๐Ÿ“˜ Phase A ยท Module 3

Git & GitHub

โฑ ~8 hours ๐Ÿ“… Week 3 ๐ŸŽฏ The safety net that saves everything

By the end of this module you will:

Commits Branches Merge vs rebase git log / blame Pull Requests Reset & revert .gitignore & secrets

Why Git is non-negotiable with AI agents

Tina Huang said something important: vibe coders who didn't know Git destroyed their codebases when AI agents went wrong. This is not hypothetical. An AI coding agent that confidently refactors the wrong thing will touch 20 files in 30 seconds. Without Git, you have no way to undo that.

With Git, you can always say: "Revert to 10 minutes ago." That's the safety net. Your job is to commit frequently enough that the safety net is there when you need it.

The mental model

Git tracks a history of snapshots of your code. Each snapshot is a commit. A commit records: what changed, when, who changed it, and a message explaining why.

Three places your code lives in Git:

The commands you'll use 95% of the time

# Check current state โ€” run this constantly
git status

# See exactly what changed
git diff
git diff --staged    # what's staged (about to be committed)

# Stage files
git add file.py             # specific file
git add src/               # whole directory
git add -p                 # interactive โ€” stage specific chunks (powerful!)

# Commit
git commit -m "feat: add task priority validation"

# Push to GitHub
git push

# Pull latest from GitHub
git pull

# See history
git log --oneline          # compact one-line view
git log --oneline --graph  # with branch visualization

Branches โ€” work in isolation

A branch is a parallel version of your code where you can make changes without affecting the main codebase. Every feature should be its own branch.

# Create and switch to new branch
git checkout -b feature/task-priorities
# (modern equivalent)
git switch -c feature/task-priorities

# Switch between branches
git switch main
git switch feature/task-priorities

# See all branches
git branch

# Merge your feature branch into main
git switch main
git merge feature/task-priorities

# Delete branch after merging
git branch -d feature/task-priorities

Merge vs rebase โ€” the one thing to know

Merge combines two branches and creates a merge commit. The history shows that the branches existed and were joined. Clear, safe, standard.

Rebase replays your commits on top of another branch, rewriting history to look like you branched off later. Produces a cleaner, linear history. Never rebase shared branches โ€” if someone else is working off the same branch, you'll break their history.

Rule for now: use merge. Learn rebase once you're comfortable.

Writing good commit messages

This matters more than most beginners think. In six months, a good commit message tells you why a change was made. A bad one tells you nothing.

# โŒ Useless commit messages
git commit -m "fix"
git commit -m "changes"
git commit -m "asdf"

# โœ“ Good commit messages โ€” what changed and why
git commit -m "fix: prevent division by zero in weight calculation"
git commit -m "feat: add priority queue for task ordering"
git commit -m "refactor: extract user validation into separate function"

The conventional commit format โ€” type: description โ€” is widely used and makes scanning history much faster. Common types: feat (new feature), fix (bug fix), refactor (code cleanup, no behaviour change), docs, test, chore.

Reading git log and git blame

When you join a codebase or review someone's AI-generated code, git log and git blame are invaluable.

# See compact history with file changes
git log --oneline --stat

# See what a specific commit changed
git show a3f8c21

# Who last changed each line of a file (and when)
git blame src/calculator.py

# History for a specific file
git log --oneline src/calculator.py

# Find when a specific string was added
git log -S "def process_weight" --source --all

git blame is your friend when reading code and wondering "why is this written this way?" โ€” you can find the commit that introduced it and read the message. This is how you understand context in an unfamiliar codebase.

Recovering from mistakes

Everyone makes mistakes. The important thing is knowing which recovery command to use:

# Undo staged changes (unstage, but keep the file changes)
git restore --staged file.py

# Discard changes in working directory (permanent โ€” file goes back to last commit)
git restore file.py

# Undo the last commit, keep changes staged
git reset --soft HEAD~1

# Undo the last commit, keep changes unstaged
git reset HEAD~1

# Create a new commit that undoes a previous one (safe โ€” doesn't rewrite history)
git revert a3f8c21

# Save work-in-progress without committing (e.g. to switch branches quickly)
git stash
git stash pop    # bring it back
โš ๏ธ git reset --hard is dangerous

git reset --hard permanently discards uncommitted changes. There's no undo. Use git stash if you're unsure โ€” you can always pop the stash later.

Secrets and .gitignore

This section is critical โ€” it's the kind of mistake that leaks API keys to the public internet and costs real money or causes security incidents.

Never commit secrets to Git

API keys, passwords, database URLs, OAuth tokens โ€” none of these should ever appear in a file that gets committed. GitHub scans public repos for common secret patterns and notifies providers (like Anthropic, AWS) when they're found. They revoke the keys immediately. You then need to rotate them, which is annoying and potentially costly.

The pattern: put secrets in a .env file, add it to .gitignore. Your code reads from the environment, not from hardcoded strings.

# .env โ€” never committed
OPENAI_API_KEY=sk-abc123...
DATABASE_URL=postgresql://user:password@localhost/mydb

# .gitignore โ€” tells Git to ignore these files
.env
.env.local
__pycache__/
*.pyc
.venv/
node_modules/

# Your code reads from environment
import os
api_key = os.environ["OPENAI_API_KEY"]  # raises if missing
# or with python-dotenv:
from dotenv import load_dotenv
load_dotenv()
api_key = os.getenv("OPENAI_API_KEY")

If you accidentally commit a secret

If you realise immediately (before pushing): git reset HEAD~1, remove the secret, re-commit. If you've already pushed: rotate the key immediately โ€” assume it's already been seen. Then use git filter-branch or git filter-repo to remove it from history. But rotating the key is the critical step.

Pull Requests โ€” how code review works

A Pull Request (PR) is how you propose changes to a codebase. Even when working solo, PRs are valuable because they give you a diff view of all your changes before they land on main. Reviewing your own PR catches more than you'd expect.

A good PR:

Resources for this module

๐Ÿ’ป Exercise โ€” do this with Cody

Open Claude Code and say: /learn A3

Take the Task manager from A1, init a git repo, make 8+ meaningful commits with good messages, create a feature branch, open a self-PR, and review your own diff. Cody will help you through every step and review your commit message quality.

๐Ÿ”
Flaw Drill โ€” what's wrong with this repo?
Three separate problems in this git history and codebase setup.
# git log --oneline output:
a3f9c12 stuff
b2e1d89 fixed it
c8d3a01 added openai key to config.py
d4f5b23 more changes

# config.py (committed to main branch):
OPENAI_API_KEY = "sk-proj-abc123realkey..."
DB_PASSWORD = "mysecretpassword"

# .gitignore is empty

Identify all three problems and explain exactly what to do about each. Then verify with /learn A3 flaw-drill.

โ† A2: Problem-Solving Next: Reading Code โ†’