Search Tech Journey

Find topics, journeys and posts

6-month learning plan2 / 130
back to blog
systemsbeginner 15m read

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)


(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:

#CommandState after
1git initEmpty .git/ created; no commits.
2Edit hello.pyWorking dir has changes; staging + repo untouched.
3git statusShows hello.py as "modified" (red).
4git add hello.pyFile moves to staging (green).
5git commit -m "Add greeting"Snapshot recorded; new commit hash like a1b2c3d.
6git log --onelineShows a1b2c3d Add greeting.
7git branch feat/uppercaseNew label created, still on main.
8git checkout feat/uppercaseHEAD moves to the new branch.
9Edit, git add, git commit -m "Uppercase name"New commit on feat/uppercase only.
10git checkout main; git merge feat/uppercaseChanges appear on main.
11git remote add origin git@github.com:me/repo.gitWire up GitHub.
12git push -u origin mainUpload; -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 --all

Checklist — what to observe:

  1. git log --graph after step 4 shows a linear history (one branch).
  2. After step 5, the graph shows a diamond shape (the merge commit).
  3. Step 6 prints CONFLICT (add/add): Merge conflict in notes.txt — that's the merge failing safely.
  4. cat notes.txt between step 6 and 7 shows the <<<<<<< markers.
  5. Final git log --graph shows 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:

  1. Never commit directly to main; always branch → PR → merge.
  2. Never git push --force on a shared branch (use --force-with-lease if you must).
  3. Never commit secrets; use .env files + .gitignore + a secret manager.
  4. git status before every commit; git diff --staged before every push.
  5. If you're lost, git reflog remembers everything for 90 days. You almost never lose work permanently.

(e) Quiz + exercise · 10 min

  1. What is the difference between the working directory, the staging area, and the local repo?
  2. Why is git push --force dangerous on a shared branch, and what safer alternative exists?
  3. When you git commit, does the commit go to GitHub? Why or why not?
  4. What creates a merge conflict, and whose job is it to resolve one?
  5. 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:

  1. What is Git & GitHub? (one sentence, no jargon)
  2. When would you use it? (one concrete example)
  3. When would you NOT use it? (one anti-pattern)

What comes next


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 →
  1. 001Dev Environment — Linux/WSL, Terminal, VS Code
  2. 003The Command Line — bash, pipes, grep, jq
  3. 004Reading Docs & Effective Googling — the Meta-Skill