S002 · Git & GitHub — Commits, Branches, PRs
Version control the way real teams use it.
Module M00: Setup & Tools · Session 2 of 130 · Track: Setup 🔀
What you'll be able to do after this session
- Explain Git & GitHub to a friend in one minute, out loud, no notes.
- Recognise it when you see it in real code / systems / papers.
- Complete the hands-on exercise at the end without looking up help.
Prerequisites
If you can't answer these in 30 seconds each, redo the linked session first:
Pre-read (skim before the session)
- 🎥 Intuition (5–15 min) · Git Explained in 100 Seconds — Fireship's rapid-fire mental model — watch twice.
- 🎥 Deep dive (30–60 min) · Git & GitHub Crash Course For Beginners — Traversy's hour-long walkthrough of commits, branches, remotes, PRs.
- 🎥 Hands-on demo (10–20 min) · Learn Git Branching — Interactive Tutorial (walkthrough) — Screencast of the interactive branching tutorial — pause and try each step.
- 📖 Canonical article · Pro Git Book — Chapter 2: Git Basics — The definitive free book. Chapter 2 alone covers 80 % of daily use.
- 📖 Book chapter / tutorial · Learn Git Branching (interactive) — The only Git tutorial that makes branching click. Do levels 1–4.
- 📖 Engineering blog · GitHub Blog — Scaling merge-ort across GitHub — How GitHub itself rebuilt the merge engine — a peek at Git at scale.
(a) Intuition · 5 min
The one-paragraph mental model. Why this concept exists, what problem it solves, and the analogy that makes it stick.
In one sentence: Version control the way real teams use it.
Think of Git as an infinite Ctrl+Z with a comment box. Every time you save a snapshot of your project (a "commit"), you type a note like "added login button" and Git tucks it away forever. Weeks later you can jump to that exact snapshot, see what changed, and — crucially — you can have two people editing the same file simultaneously and Git will merge their work.
GitHub is Git-on-a-server-with-a-web-UI. Git is the tool that lives on your laptop and tracks history; GitHub is the website that hosts the shared copy so your teammates (or future-you on another machine) can pull it down. You can use Git alone with no GitHub, but you basically never will.
The single most powerful idea: branches are cheap. A branch is just a movable label pointing at a commit. Making one costs zero milliseconds and zero disk space. That's why the workflow is: for every change, create a branch, do the work, open a Pull Request, get reviewed, merge. If you're editing directly on main, you're doing it wrong.
Gotcha to carry forever: git push --force on a shared branch will silently erase your teammates' commits. If you must rewrite history, use git push --force-with-lease — it refuses to push if someone else pushed since you last pulled.
(b) Visual walkthrough · 15 min
Diagrams, worked examples, step-by-step visuals.
The three "areas" of Git are the source of endless confusion. Here they are as one picture:
Worked example — the full lifecycle of one change:
| # | Command | State after |
|---|---|---|
| 1 | git init | Empty .git/ created; no commits. |
| 2 | Edit hello.py | Working dir has changes; staging + repo untouched. |
| 3 | git status | Shows hello.py as "modified" (red). |
| 4 | git add hello.py | File moves to staging (green). |
| 5 | git commit -m "Add greeting" | Snapshot recorded; new commit hash like a1b2c3d. |
| 6 | git log --oneline | Shows a1b2c3d Add greeting. |
| 7 | git branch feat/uppercase | New label created, still on main. |
| 8 | git checkout feat/uppercase | HEAD moves to the new branch. |
| 9 | Edit, git add, git commit -m "Uppercase name" | New commit on feat/uppercase only. |
| 10 | git checkout main; git merge feat/uppercase | Changes appear on main. |
| 11 | git remote add origin git@github.com:me/repo.git | Wire up GitHub. |
| 12 | git push -u origin main | Upload; -u remembers the pairing so future git push needs no args. |
Second worked example — the merge conflict. Two people edit line 3 of hello.py. When the second one runs git pull, Git can't decide which version wins and marks the file with <<<<<<< / ======= / >>>>>>> markers. You edit the file to keep what you want, delete the markers, git add + git commit, and the conflict is resolved. The conflict is a feature, not a bug — Git is refusing to guess.
(c) Hands-on · 20 min
Code you type and run. Not pseudo-code — real, runnable, copy-pasteable.
Do this in a fresh directory. It walks you through commit → branch → merge → remote in one go.
#!/usr/bin/env bash
set -euo pipefail
# 0. One-time global identity (do once per machine, not per project).
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
git config --global init.defaultBranch main
git config --global pull.rebase false
# 1. Fresh project.
mkdir -p ~/projects/learning/s002 && cd ~/projects/learning/s002
git init
# 2. First commit.
echo "# Learning Git" > README.md
git add README.md
git commit -m "chore: initial commit"
# 3. Branch off, add a feature.
git checkout -b feat/add-license
echo "MIT License" > LICENSE
git add LICENSE
git commit -m "feat: add MIT license"
# 4. Look at history.
git log --oneline --graph --all
# 5. Merge back to main.
git checkout main
git merge feat/add-license --no-ff -m "merge: add license"
git branch -d feat/add-license
# 6. Deliberately create a conflict.
echo "line-A" > notes.txt && git add notes.txt && git commit -m "add notes A"
git checkout -b feat/conflict HEAD~1
echo "line-B" > notes.txt && git add notes.txt && git commit -m "add notes B"
git checkout main
git merge feat/conflict || true # this WILL conflict — that's the lesson
# 7. Inspect the conflict markers, then resolve.
cat notes.txt
echo -e "line-A
line-B" > notes.txt
git add notes.txt
git commit -m "merge: resolve conflict by keeping both lines"
git log --oneline --graph --allChecklist — what to observe:
git log --graphafter step 4 shows a linear history (one branch).- After step 5, the graph shows a diamond shape (the merge commit).
- Step 6 prints
CONFLICT (add/add): Merge conflict in notes.txt— that's the merge failing safely. cat notes.txtbetween step 6 and 7 shows the<<<<<<<markers.- Final
git log --graphshows the second merge commit tying both lines of history together.
Try this modification: create a .gitignore that excludes *.log and .venv/, then touch debug.log and confirm git status no longer mentions it. Bonus: try git commit --amend -m "better message" to rewrite the last commit's message.
(d) Production reality · 10 min
How this shows up in real systems, gotchas, war stories, common bugs.
War story — the force-push disaster. A junior engineer at a fintech runs git push --force on main because "my local looked right." Two hours of teammates' commits vanish from the server. Recovery took a full day of digging through git reflog on individual laptops. Every serious team now enables branch protection on main: no force-push, no direct commits, PR review required.
War story — the secrets in history. A dev commits an AWS access key, notices five minutes later, deletes the file, and commits again. The key is still in history — anyone who clones can git log -p and find it. The correct response is: rotate the key immediately (assume it's compromised) and then rewrite history with git filter-repo. GitHub even scans public repos for leaked credentials and emails you within minutes. This is why teams enforce pre-commit hooks like gitleaks or trufflehog.
How top teams work: trunk-based development. main is always deployable. Feature branches live for hours or days, not weeks. PRs are small (< 400 lines is the Google research sweet spot for review quality). CI runs on every push. Squash-merge is the default so main's history stays clean. This is the workflow at Google, Facebook, Netflix, Stripe, and every YC startup you'll join.
Commit-message discipline pays off. Conventional Commits (feat:, fix:, chore:, docs:) let tools auto-generate changelogs and semver bumps. Adopt it from day one — future-you writing release notes will thank you.
The rules that will keep you out of trouble:
- Never commit directly to
main; always branch → PR → merge. - Never
git push --forceon a shared branch (use--force-with-leaseif you must). - Never commit secrets; use
.envfiles +.gitignore+ a secret manager. git statusbefore every commit;git diff --stagedbefore every push.- If you're lost,
git reflogremembers everything for 90 days. You almost never lose work permanently.
(e) Quiz + exercise · 10 min
- What is the difference between the working directory, the staging area, and the local repo?
- Why is
git push --forcedangerous on a shared branch, and what safer alternative exists? - When you
git commit, does the commit go to GitHub? Why or why not? - What creates a merge conflict, and whose job is it to resolve one?
- What is a Pull Request, and what does GitHub give you that plain Git does not?
Stretch problem (connects to S001): In your s001 project from last session, initialise a Git repo, create a .gitignore that excludes the .venv/ directory and __pycache__/, commit hello.py + requirements.txt, push to a new private GitHub repo, and confirm from github.com that .venv/ is not visible. Explain in one sentence why committing .venv/ would break your teammate's workflow.
Explain-out-loud test
If you can't teach these 3 things to a friend without notes, redo the session:
- What is Git & GitHub? (one sentence, no jargon)
- When would you use it? (one concrete example)
- When would you NOT use it? (one anti-pattern)
What comes next
- Next session (S003): The Command Line — bash, pipes, grep, jq
- Previous (S001): Dev Environment — Linux/WSL, Terminal, VS Code
- Hub: The 6-Month Learning Plan
Part of a 130-session evergreen learning series. Session structure: (a) intuition · (b) visual walkthrough · (c) hands-on · (d) production reality · (e) quiz + exercise. Duration: 60 minutes.
More from M00 · Setup & Tools
all modules →