diff --git a/.github/ISSUE_TEMPLATE/bug-or-broken-content.md b/.github/ISSUE_TEMPLATE/bug-or-broken-content.md
new file mode 100644
index 0000000..d65de01
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug-or-broken-content.md
@@ -0,0 +1,15 @@
+---
+name: Bug / Broken Content
+about: Report a broken link, a wrong command, or an outdated explanation
+title: "[Module 0X] Short description"
+labels: bug
+---
+
+**Where**
+Which module/file, and roughly which section (a heading or a direct link is perfect)?
+
+**What's wrong**
+What does the course currently say, and why is it wrong or broken?
+
+**What it should say instead**
+If you know the fix, say it here — if not, that's fine too.
diff --git a/.github/ISSUE_TEMPLATE/content-suggestion.md b/.github/ISSUE_TEMPLATE/content-suggestion.md
new file mode 100644
index 0000000..3c29f21
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/content-suggestion.md
@@ -0,0 +1,15 @@
+---
+name: Content Suggestion
+about: Suggest a missing concept, a clearer explanation, or a new Try It / lab
+title: "[Suggestion] Short description"
+labels: enhancement
+---
+
+**What's missing or could be clearer**
+A concept, command, or scenario you think belongs in the course.
+
+**Where it would fit**
+Which existing module, if any? If it's genuinely new ground, say that instead.
+
+**Why it's worth adding**
+What would a learner be able to do afterward that they can't today?
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000..601ede9
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,14 @@
+## What
+
+
+
+## Why
+
+
+
+## Checklist
+
+- [ ] Ran `python3 .github/scripts/check_docs.py` locally — no issues
+- [ ] Kept existing examples/analogies/labs intact (or has a good reason not to)
+- [ ] Used dummy data only — no real names, emails, credentials, or URLs
+- [ ] New commands/concepts include a `callout-tryit` block the reader can actually run
diff --git a/.github/scripts/check_docs.py b/.github/scripts/check_docs.py
new file mode 100644
index 0000000..43253fd
--- /dev/null
+++ b/.github/scripts/check_docs.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python3
+"""Validate this repo's own Markdown: balanced code fences, resolvable
+relative links, and balanced custom HTML components (
/).
+Used locally and by .github/workflows/validate-docs.yml in CI.
+"""
+import os
+import re
+import sys
+import glob
+
+REPO_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+
+def check_file(path):
+ problems = []
+ with open(path, encoding="utf-8") as fh:
+ content = fh.read()
+
+ fence_count = len(re.findall(r"^```", content, re.MULTILINE))
+ if fence_count % 2 != 0:
+ problems.append(f"Unbalanced code fences ({fence_count} found)")
+
+ div_open = len(re.findall(r"
")
+ if div_open != div_close:
+ problems.append(f"Unbalanced
tags ({div_open} open, {div_close} close)")
+
+ details_open = content.count("")
+ details_close = content.count("")
+ if details_open != details_close:
+ problems.append(f"Unbalanced tags ({details_open} open, {details_close} close)")
+
+ for match in re.finditer(r"\]\(([^)]+)\)", content):
+ link = match.group(1)
+ if link.startswith(("http://", "https://", "mailto:", "#")):
+ continue
+ path_part = link.split("#")[0]
+ if not path_part:
+ continue
+ resolved = os.path.normpath(os.path.join(os.path.dirname(path), path_part))
+ if not os.path.exists(resolved):
+ problems.append(f"Broken relative link: {link}")
+
+ return problems
+
+
+def main():
+ md_files = glob.glob(os.path.join(REPO_ROOT, "**", "*.md"), recursive=True)
+ all_problems = {}
+ for f in md_files:
+ problems = check_file(f)
+ if problems:
+ all_problems[os.path.relpath(f, REPO_ROOT)] = problems
+
+ if not all_problems:
+ print(f"OK — {len(md_files)} Markdown files checked, no issues found.")
+ return 0
+
+ for f, problems in sorted(all_problems.items()):
+ for p in problems:
+ print(f"{f}: {p}")
+ print(f"\n{sum(len(p) for p in all_problems.values())} issue(s) in {len(all_problems)} file(s).")
+ return 1
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/.github/workflows/validate-docs.yml b/.github/workflows/validate-docs.yml
new file mode 100644
index 0000000..1305d9d
--- /dev/null
+++ b/.github/workflows/validate-docs.yml
@@ -0,0 +1,14 @@
+name: Validate Docs
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+
+jobs:
+ check-docs:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - run: python3 .github/scripts/check_docs.py
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0a4b93a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+# Jekyll build output (local preview only — GitHub Pages builds this itself)
+_site/
+.jekyll-cache/
+.jekyll-metadata
+.sass-cache/
diff --git a/00-introduction/README.md b/00-introduction/README.md
index e87a33f..e8b7ae4 100644
--- a/00-introduction/README.md
+++ b/00-introduction/README.md
@@ -1,3 +1,8 @@
+---
+title: "Module 00 — Introduction & Setup"
+description: "What version control actually is, installing Git, and the one-time config that saves you from a confusing commit history."
+---
+
+
+**By the end of this module you will be able to:**
+
+✓ Explain what Git actually *is*, and why it beat older version control systems
+✓ Install Git and set your identity correctly (global vs. local)
+✓ Create a real Git repository from scratch with `git init`
+✓ Read `git status`'s output and know what it's telling you to do next
+✓ Pull up Git's built-in help without leaving the terminal
+
+
+
---
## 📑 In This Module
+- [Set Up Your Safe Sandbox](#-set-up-your-safe-sandbox)
- [Git Home — What Even Is This](#-git-home--what-even-is-this)
- [Git Intro — Version Control, Explained Like You're Five (a smart five-year-old)](#-git-intro--version-control-explained-like-youre-five-a-smart-five-year-old)
- [Git Install](#-git-install)
@@ -23,6 +41,33 @@
---
+## 🛟 Set Up Your Safe Sandbox
+
+Every "Try It" and "Your Turn" block in this course — starting right now — assumes you have one
+disposable folder to actually type commands into. Not a real project. Something you can break on
+purpose, over and over, with zero consequences.
+
+
+
+**⌨️ TRY IT YOURSELF**
+
+```bash
+mkdir ~/git-playground
+cd ~/git-playground
+```
+
+That's it for now — you don't even have `git init` yet, because the very next section is about
+to teach you what that command does and why. Just get the folder made and remember where it is;
+every module from here forward will say "in your playground" and mean this exact folder.
+
+
+
+> [!TIP]
+> Keep this folder for the entire course. Delete everything inside it whenever you want a clean
+> slate — nothing in here should ever be work you actually care about keeping.
+
+---
+
## 🏠 Git Home — What Even Is This
**Git** is a program that tracks changes to files over time, so you can:
@@ -101,9 +146,9 @@ graph TB
E1 -.->|Works fine offline, syncs later| E1
end
- style C1 fill:#ffcccc
- style E1 fill:#d4edda
- style E2 fill:#d4edda
+ style C1 fill:#ffcccc,color:#1a1a1a
+ style E1 fill:#d4edda,color:#1a1a1a
+ style E2 fill:#d4edda,color:#1a1a1a
```
In a **centralized** system, only the server has the full history — if it goes down, nobody can
@@ -151,6 +196,21 @@ git --version
> added to it. (The other 20% of the time, you'll be Googling PATH environment variables at
> midnight. We've all been there.)
+
+
+**⌨️ TRY IT YOURSELF**
+
+```bash
+git --version
+git help -a
+```
+
+**What should I see?** A version number on the first command, and a long alphabetical list of
+every Git subcommand that exists on the second. You don't need to read all of it — just confirm
+it printed something. That list existing at all is the whole point of the next section.
+
+
+
---
## ⚙️ Git Config
@@ -195,30 +255,83 @@ git config user.email "you@company.com"
> override it with a `--local` (no flag) config inside your work repos specifically — not the
> other way around, unless you enjoy explaining commit history to HR.
+
+
+**⌨️ TRY IT YOURSELF**
+
+```bash
+git config --global user.name "Your Name"
+git config --global user.email "you@example.com"
+git config --list
+```
+
+**What should I see?** A list of key=value settings, including `user.name=Your Name` and
+`user.email=you@example.com` somewhere in it. If you don't see them, the config didn't save —
+check for a typo in the command before moving on.
+
+
+
+
+
+**🎯 YOUR TURN**
+
+Without looking at the example above: set your email to one thing **globally**, then go into
+your `~/git-playground` folder and override it to something different, **locally, just for that
+one folder**. Then run `git config user.email` *inside* the playground folder, and run it again
+from somewhere *outside* it. Can you explain out loud why the two commands print different
+answers?
+
+
+💡 Show solution
+
+```bash
+git config --global user.email "global@example.com"
+
+cd ~/git-playground
+git config user.email "local-override@example.com"
+
+git config user.email # prints "local-override@example.com" — local wins here
+cd ..
+git config user.email # prints "global@example.com" — no local override outside the playground
+```
+
+Local config lives in that specific repo's `.git/config` file and always wins over global when
+both exist — that's the entire mechanism behind "why do my personal commits show my work email"
+bugs: someone set a local override once, forgot about it, and every commit since has used it.
+
+
+
+
+
---
## 🚀 Git Get Started
-Time to actually create a repository.
+Time to actually create a repository — in the `~/git-playground` folder you made earlier.
+
+
-### Step by step
+**⌨️ TRY IT YOURSELF**
```bash
-# 1. Make a new folder and go into it
-mkdir my-first-repo
-cd my-first-repo
+cd ~/git-playground
-# 2. Turn it into a Git repository
+# Turn this folder into a Git repository
git init
-# Initialized empty Git repository in .../my-first-repo/.git/
+# Initialized empty Git repository in .../git-playground/.git/
-# 3. Check what Git thinks is going on (you'll run this CONSTANTLY)
+# Check what Git thinks is going on (you'll run this CONSTANTLY)
git status
# On branch main
# No commits yet
# nothing to commit (create/copy files and use "git add" to track)
```
+**What should I see?** `git init`'s one-line confirmation, then `git status` reporting a brand
+new, empty repo on branch `main` with nothing to commit yet.
+
+
+
That `.git` folder `git init` just created is where **all** of Git's magic lives — the entire
history, every branch, every commit. Delete that folder, and as far as Git is concerned, the
project's history never existed (the files themselves are untouched — only the tracking is gone).
@@ -229,6 +342,48 @@ project's history never existed (the files themselves are untouched — only the
> staging, after committing. It always tells you exactly what state you're in and, usually, the
> exact next command to run.
+
+
+**🎯 YOUR TURN**
+
+Before running anything — predict out loud what `git status` will print if you create a brand
+new empty file right now and run it, **without** running `git add` first. Then actually do it
+and check your prediction.
+
+
+💡 Show solution
+
+```bash
+touch notes.txt
+git status
+```
+
+```
+Untracked files:
+ (use "git add ..." to include in what will be committed)
+ notes.txt
+```
+
+If you predicted "it'll show up as untracked, not staged" — that's exactly right, and it's the
+single most important mental model from this entire module: Git notices new files immediately,
+but never tracks them until you explicitly say so with `git add`.
+
+
+
+
+
+
+
+**🧪 SDET SCENARIO**
+
+Every automation repo you'll ever work in — a Playwright suite, a Selenium framework, a Postman
+collection exported to Newman — starts with exactly this: `git init` (or `git clone`, if someone
+already created it), then an immediate `.gitignore` for `node_modules/`, test reports, and
+screenshots nobody wants committed. You'll see the real version of this in
+[Module 05](../05-advanced-git/#-gitignore).
+
+
+
---
## 🆘 Git Help
@@ -278,6 +433,18 @@ If any of those feel shaky, that's completely normal for a first pass — skim b
click. If you're solid on all three, you're ready for the module where things actually get typed
into files.
+
+
+**🧠 You can now:**
+
+✓ Install Git and verify it from the terminal
+✓ Set your identity globally, and override it locally when you need to
+✓ Turn any folder into a Git repository with `git init`
+✓ Read `git status` and know the difference between "untracked" and "nothing to commit"
+✓ Reach for `git help` instead of leaving the terminal to Google a flag
+
+
+
---
diff --git a/01-git-fundamentals/README.md b/01-git-fundamentals/README.md
index 0e3bfd8..f547815 100644
--- a/01-git-fundamentals/README.md
+++ b/01-git-fundamentals/README.md
@@ -1,3 +1,8 @@
+---
+title: "Module 01 — Git Fundamentals"
+description: "Staging, commits, branching, and merging — the core loop you'll run hundreds of times a week, with hands-on labs for every concept."
+---
+
+
+**By the end of this module you will be able to:**
+
+✓ Explain why Git has a staging area at all, not just "what it does"
+✓ Stage exactly the changes you want, even out of a messy working directory
+✓ Write commit messages your future self will actually thank you for
+✓ Create, switch, and merge branches — the loop you'll run dozens of times a day
+✓ Use `git stash` to context-switch without committing half-finished work
+
+
+
+> [!TIP]
+> Still have `~/git-playground` from [Module 00](../00-introduction/#-set-up-your-safe-sandbox)?
+> Use it. Every Try It block below assumes a real terminal, not just reading along.
+
---
## 📑 In This Module
@@ -46,6 +67,20 @@ Untracked files:
**Untracked** = "Git sees this file exists, but has zero interest in it until you tell it
otherwise." This is a feature, not Git being lazy.
+
+
+**⌨️ TRY IT YOURSELF** (in `~/git-playground`)
+
+```bash
+echo "console.log('hello world')" > app.js
+git status
+```
+
+**What should I see?** `app.js` listed under "Untracked files," with Git explicitly suggesting
+`git add` — not an error, just Git telling you exactly what to do next.
+
+
+
---
## 📦 Staging — The Most Important Concept in Git
@@ -65,9 +100,9 @@ graph LR
A[Working Directory 📝 your messy desk] -->|git add| B[Staging Area 📦 the box you're packing]
B -->|git commit| C[Repository / .git history 🚚 shipped & permanent]
- style A fill:#fff3cd
- style B fill:#cfe2ff
- style C fill:#d4edda
+ style A fill:#fff3cd,color:#1a1a1a
+ style B fill:#cfe2ff,color:#1a1a1a
+ style C fill:#d4edda,color:#1a1a1a
```
### 💻 The technical example
@@ -113,6 +148,50 @@ git reset file.txt # UN-stage a file (doesn't touch its actual content
> accidentally left a `console.log('here')` debug line in the same file, and want to commit only
> the fix.
+
+
+**⌨️ TRY IT YOURSELF** (in `~/git-playground`)
+
+```bash
+echo "fix bug" >> login.js
+echo "new feature" >> signup.js
+git add login.js
+git status
+```
+
+**What should I see?** `login.js` under "Changes to be committed," `signup.js` still sitting
+under "Changes not staged for commit" — proof the staging area really does let you pick exactly
+what goes in, even when more than one file changed.
+
+
+
+
+
+**🎯 YOUR TURN**
+
+You have two modified files: `login.js` and `signup.js`. Stage **only** `signup.js`, confirm it
+with `git status`, then **unstage** it again without touching its actual content. Can you do it
+without looking at the solution first?
+
+
+💡 Show solution
+
+```bash
+git add signup.js
+git status # signup.js now under "Changes to be committed"
+git reset signup.js
+git status # signup.js back under "Changes not staged" — content untouched
+```
+
+`git reset ` (no flags, just a filename) only moves a file *out of the box* — it never
+discards the edit itself. That distinction matters a lot more once you hit
+[Module 04](../04-undo-and-recovery/), where `reset` can also discard real work if you're not
+careful with it.
+
+
+
+
+
---
## ✅ Commit
@@ -152,6 +231,32 @@ The convention `type: description` (feat/fix/refactor/docs/test/chore) isn't req
itself, but it's so widely adopted that `git log --oneline --grep="fix:"` instantly showing every
bug fix in your project's history is now basically an industry standard trick.
+
+
+**⌨️ TRY IT YOURSELF** (in `~/git-playground`)
+
+```bash
+git commit -m "fix: login validation bug"
+git log --oneline -1
+```
+
+**What should I see?** A one-line confirmation from `git commit` (branch, short hash, file stats),
+then that same commit as a single line from `git log`.
+
+
+
+
+
+**🚨 COMMON MISTAKE**
+
+Committing "today's work" as one giant commit at the end of the day. It feels efficient in the
+moment and is genuinely painful later — `git revert` (Module 04) undoes *one commit*, so a
+50-file mega-commit means reverting the one real bug inside it also reverts 49 unrelated changes
+you didn't want touched. Small, focused commits aren't a style preference; they're what makes
+every later Git operation (revert, cherry-pick, bisect) actually usable.
+
+
+
---
## 📜 History
@@ -178,6 +283,21 @@ without asking anyone or guessing.
git log -p --follow -- path/to/file.js
```
+
+
+**⌨️ TRY IT YOURSELF** (in `~/git-playground`)
+
+```bash
+git log --oneline
+git log --graph --oneline --all
+```
+
+**What should I see?** The same commits, but the second command draws them as a small ASCII
+tree. On one branch with no merges yet it'll look like a straight line — that's expected. It gets
+genuinely useful once branches exist, a few sections from now.
+
+
+
---
## 🏷️ Tagging
@@ -199,6 +319,21 @@ shipped to customers as Version 2.0."** Branches move forward constantly; tags d
— once set, `v1.0.0` always points at the exact same commit, forever, so you can always answer
"what code was actually in production on release day" with certainty.
+
+
+**⌨️ TRY IT YOURSELF** (in `~/git-playground`)
+
+```bash
+git tag -a v0.1.0 -m "First playground milestone"
+git tag
+git show v0.1.0
+```
+
+**What should I see?** `v0.1.0` listed by `git tag`, and `git show` printing the tag's own
+message plus the full commit it points at.
+
+
+
---
## 🧳 Stash
@@ -227,6 +362,36 @@ git stash push -m "half-done payment form" # stash WITH a note, for when you'll
> clearly if this happens, don't panic, it's the same conflict-resolution process as a merge
> conflict (covered below).
+
+
+**⌨️ TRY IT YOURSELF** (in `~/git-playground`)
+
+```bash
+echo "half-done work" >> login.js
+git status # shows login.js modified
+git stash
+git status # clean — login.js looks untouched
+git stash pop
+git status # the change is back
+```
+
+**What should I see?** `git status` reporting a clean working directory right after `stash`, then
+the exact same modification reappearing after `pop` — nothing was lost, it was just parked.
+
+
+
+
+
+**🧪 SDET SCENARIO**
+
+You're mid-way through writing a new Playwright test, it's not passing yet, and a flaky test in
+CI just paged you to investigate *right now*. You can't commit broken, half-finished test code —
+but you also can't lose 40 minutes of locator work. `git stash`, switch branches, debug the
+flaky test, fix it, switch back, `git stash pop`, and you're exactly where you left off. This is
+the single most common real reason test engineers reach for stash.
+
+
+
---
## 🌿 Branch
@@ -270,6 +435,46 @@ git branch -D feature/checkout # force-delete (no safety check — know what
> branch per feature, per bug fix, per experiment) in a way that would've been unthinkably slow in
> older version control systems.
+
+
+**⌨️ TRY IT YOURSELF** (in `~/git-playground`)
+
+```bash
+git switch -c feature/login
+git branch
+```
+
+**What should I see?** `feature/login` listed with a `*` next to it, confirming you're now on
+the new branch — `main` is still exactly where you left it.
+
+
+
+
+
+**🎯 YOUR TURN**
+
+You're currently on `feature/login`. Create a second branch called `feature/payment`, make one
+commit on it (any trivial change), then switch back to `main`. Do NOT look at the solution first.
+
+
+💡 Show solution
+
+```bash
+git switch -c feature/payment
+echo "payment stub" >> payment.js
+git add payment.js
+git commit -m "feat: stub out payment module"
+git switch main
+```
+
+Run `git log --oneline --graph --all` afterward — you'll see `main`, `feature/login`, and
+`feature/payment` as three separate pointers, with `feature/payment` one commit ahead of where
+it branched off.
+
+
+
+
+
---
## 🔀 Merge
@@ -291,8 +496,8 @@ graph TB
subgraph "Three-Way Merge"
B1[main moved forward too] --> B2[Git combines BOTH histories] --> B3[Creates a new merge commit]
end
- style A3 fill:#d4edda
- style B3 fill:#cfe2ff
+ style A3 fill:#d4edda,color:#1a1a1a
+ style B3 fill:#cfe2ff,color:#1a1a1a
```
- **Fast-forward**: nobody touched `main` while you were on your branch — Git just moves `main`'s
@@ -309,6 +514,30 @@ one final document (three-way merge) — and if both edited the *exact same sent
that's a **merge conflict**, which needs a human to decide. (Full conflict-resolution workflow is
in [Module 05](../05-advanced-git/).)
+
+
+**⌨️ TRY IT YOURSELF** (in `~/git-playground`)
+
+```bash
+git switch main
+git merge feature/login
+git log --oneline --graph --all
+```
+
+**What should I see?** Since `main` hasn't moved since you branched, this is a **fast-forward** —
+no merge commit, `main`'s pointer just catches up to `feature/login`'s latest commit.
+
+
+
+
+🧠 Quick Check: If you'd made a NEW commit directly on main before merging feature/login in, would this still be a fast-forward?
+
+No — the moment `main` has a commit `feature/login` doesn't know about, Git can't just "catch up"
+a pointer anymore. It has to combine two divergent histories, which means a three-way merge and a
+dedicated merge commit, even if there's no actual conflict between the changes.
+
+
+
---
## 🔁 Workflow — Putting It All Together
@@ -325,8 +554,8 @@ flowchart LR
E -->|No, ready| F[git checkout main]
F --> G[git merge your branch]
- style D fill:#cfe2ff
- style G fill:#d4edda
+ style D fill:#cfe2ff,color:#1a1a1a
+ style G fill:#d4edda,color:#1a1a1a
```
```bash
@@ -346,6 +575,41 @@ That's genuinely most of Git, in one loop. Everything else in this course is eit
on this loop (Module 02's GitHub push/pull), a safety net for when it goes wrong (Module 04), or
a specialization for specific situations (Module 05).
+
+
+**🎯 YOUR TURN — Run the whole loop yourself**
+
+In `~/git-playground`, from `main`:
+
+1. Create a branch called `feature/dark-mode`
+2. Make two separate, small commits on it (anything — even two one-line `echo` changes to
+ different files)
+3. Switch back to `main` and merge it in
+4. Delete the now-merged branch
+
+Do all four steps without looking at the solution first — this is the exact loop you'll run for
+the rest of your career, so it's worth getting your fingers to do it without thinking.
+
+
+💡 Show solution
+
+```bash
+git switch -c feature/dark-mode
+echo "dark mode css" >> styles.css
+git add styles.css
+git commit -m "feat: add dark mode styles"
+echo "dark mode toggle" >> settings.js
+git add settings.js
+git commit -m "feat: add dark mode toggle"
+git switch main
+git merge feature/dark-mode
+git branch -d feature/dark-mode
+```
+
+
+
+
+
---
## 🧠 Best Practices
@@ -395,6 +659,18 @@ a specialization for specific situations (Module 05).
If #3 didn't immediately bring "`git stash`" to mind, that's worth a re-read of that section —
it's one of the most practically useful commands in this entire module.
+
+
+**🧠 You can now:**
+
+✓ Explain why the staging area exists, not just recite what it does
+✓ Stage exactly the changes you want, chunk by chunk if needed
+✓ Write commit messages that'll still make sense to you in six months
+✓ Create, switch, merge, and delete branches — the daily loop
+✓ Shelve half-finished work with `git stash` without committing it
+
+
+
---
diff --git a/02-git-and-github/README.md b/02-git-and-github/README.md
index ec3b8ef..03b551e 100644
--- a/02-git-and-github/README.md
+++ b/02-git-and-github/README.md
@@ -1,3 +1,8 @@
+---
+title: "Module 02 — Git + GitHub"
+description: "SSH keys, remotes, push/pull, GitHub Flow, and GitHub Pages — where Git on your laptop becomes Git the internet can see."
+---
+
# 🌐 Module 02 — Git + GitHub
@@ -9,6 +14,24 @@
+
+
+**By the end of this module you will be able to:**
+
+✓ Authenticate to GitHub with SSH instead of typing a password every time
+✓ Connect a local repo to a real GitHub repo and push to it
+✓ Explain the actual difference between `fetch` and `pull` — and why cautious people fetch first
+✓ Push a branch and know what happens to it on GitHub's side
+✓ Describe GitHub Flow well enough to follow it on a real team
+
+
+
+> [!NOTE]
+> Unlike Modules 00–01, the Try It blocks below need a **real, free GitHub account** and (for the
+> push/pull ones) an empty repo you created on github.com — this module is specifically about
+> talking to the internet, so there's no way around that. Nothing here touches real production
+> code; create a throwaway repo, same spirit as `~/git-playground`.
+
---
## 📑 In This Module
@@ -83,6 +106,20 @@ ssh -T git@github.com
> anywhere but your own machine) and `id_ed25519.pub` (public — this is the one that's safe to
> give to GitHub). Mixing these up is a genuine security incident, not a typo.
+
+
+**⌨️ TRY IT YOURSELF**
+
+```bash
+ls -la ~/.ssh
+```
+
+**What should I see?** If you see `id_ed25519` and `id_ed25519.pub` (or an `id_rsa` pair from an
+older setup), you already have a key and can skip straight to adding the `.pub` one to GitHub.
+If the folder doesn't exist or is empty, run the `ssh-keygen` command above first.
+
+
+
---
## 🔗 Set Remote
@@ -110,6 +147,24 @@ git remote remove origin
`banana` and Git wouldn't care. Everyone calls it `origin` because everyone else does, which is
its own kind of consistency worth keeping.
+
+
+**⌨️ TRY IT YOURSELF**
+
+1. On github.com, create a new **empty** repository (no README, no .gitignore — genuinely empty)
+2. Copy its SSH URL (the green "Code" button → SSH tab)
+
+```bash
+cd ~/git-playground
+git remote add origin git@github.com:yourusername/your-repo-name.git
+git remote -v
+```
+
+**What should I see?** Two lines back from `git remote -v` — `origin` listed for both fetch and
+push, pointing at the repo you just created.
+
+
+
---
## ✏️ Edit Code — the GitHub Web Editor
@@ -145,9 +200,9 @@ graph LR
A[Local commits on your laptop] -->|git push| B[GitHub the shared remote]
B -->|git pull| C[Someone else's laptop]
- style A fill:#fff3cd
- style B fill:#cfe2ff
- style C fill:#d4edda
+ style A fill:#fff3cd,color:#1a1a1a
+ style B fill:#cfe2ff,color:#1a1a1a
+ style C fill:#d4edda,color:#1a1a1a
```
> [!WARNING]
@@ -156,6 +211,20 @@ graph LR
> teammate's just-pushed work. `git push --force-with-lease` is the safer version: it refuses to
> push if the remote has changes you haven't seen yet, instead of blindly overwriting.
+
+
+**⌨️ TRY IT YOURSELF** (using the `origin` you set up above)
+
+```bash
+git push -u origin main
+```
+
+**What should I see?** Git prints upload progress, then confirms `main -> main` with the
+upstream now set. Refresh the repo's page on github.com — your commits from Modules 00–01 are
+now visible there, publicly (or privately, if you made the repo private).
+
+
+
---
## ⬇️ Pull from GitHub
@@ -177,6 +246,33 @@ cabinet, grabbing those new pages, AND immediately filing them into your own not
right place. `git fetch` alone is just "go look at the cabinet and see what's new" — without
touching your own notebook yet. Cautious people `fetch` first, review, then `merge` or `pull`.
+
+
+**🎯 YOUR TURN**
+
+Make a small edit directly on GitHub's web editor (press `.` on your repo, edit a file, commit)
+— *without* touching your local clone. Then, locally, run `git fetch origin` followed by
+`git log origin/main`. Can you see that remote commit before running `git pull`? What does that
+prove about what `fetch` actually does?
+
+
+💡 Show solution
+
+```bash
+git fetch origin
+git log origin/main -1 # the web-editor commit shows up here already
+git log main -1 # your LOCAL main still doesn't have it
+git pull origin main # now it does
+```
+
+This proves `fetch` genuinely downloads the data without touching your working branch —
+`origin/main` and `main` are two separate references, and only `merge` (or `pull`, which runs it
+for you) moves your local branch forward.
+
+
+
+
+
---
## 🌿 GitHub Branch — Push/Pull a Branch
@@ -195,6 +291,24 @@ git checkout feature/their-branch
Once pushed, that branch is visible to your whole team on GitHub — anyone can view its diff,
comment on it, or open a **pull request** from it (Module 03 goes deep on this).
+
+
+**⌨️ TRY IT YOURSELF**
+
+```bash
+git checkout -b feature/new-navbar
+echo "navbar stub" >> navbar.js
+git add navbar.js
+git commit -m "feat: stub out new navbar"
+git push -u origin feature/new-navbar
+```
+
+**What should I see?** GitHub's output includes a direct link to open a pull request for the
+branch you just pushed — and refreshing the repo's "Branches" page on github.com shows it listed
+there, separate from `main`.
+
+
+
---
## 🔄 GitHub Flow
@@ -212,8 +326,8 @@ flowchart LR
E -->|Changes needed| C
F --> G[Deploy]
- style A fill:#d4edda
- style F fill:#d4edda
+ style A fill:#d4edda,color:#1a1a1a
+ style F fill:#d4edda,color:#1a1a1a
```
1. `main` is always in a deployable state
@@ -226,6 +340,17 @@ It's deliberately simpler than Git Flow (an older model with `develop`, `release
branches) — GitHub Flow assumes you can deploy frequently and safely, which most modern teams with
good CI/CD can.
+
+
+**🧪 SDET SCENARIO**
+
+In a real automation repo, step 4 ("CI passes") is almost always your test suite — a pull request
+against `main` triggers a GitHub Actions run of the full Playwright/Selenium suite automatically
+(Module 05 covers the actual YAML for this). Opening the PR as a Draft early, per step 3, means
+you find out your new test is flaky in CI *before* a reviewer looks at it, not after.
+
+
+
---
## 📄 GitHub Pages
@@ -293,6 +418,18 @@ If #3 surprised you, that's the most commonly-missed point in this whole module
early (as a Draft) is a feature, not a mistake, because it makes your in-progress work visible for
early feedback instead of a surprise 400-line diff at the end.
+
+
+**🧠 You can now:**
+
+✓ Authenticate to GitHub over SSH instead of a password every time
+✓ Connect a local repo to GitHub and push real commits to it
+✓ Tell `fetch` and `pull` apart, and know why cautious people fetch first
+✓ Push a branch and find it on GitHub, ready for a pull request
+✓ Follow GitHub Flow on a real team without needing it explained again
+
+
+
---
diff --git a/03-contributing-to-open-source/README.md b/03-contributing-to-open-source/README.md
index 05ea7dc..aa60a92 100644
--- a/03-contributing-to-open-source/README.md
+++ b/03-contributing-to-open-source/README.md
@@ -1,3 +1,8 @@
+---
+title: "Module 03 — Contributing to Open Source"
+description: "Fork, clone, branch, pull request — the exact sequence behind every open-source contribution you've ever seen on GitHub."
+---
+
# 🤝 Module 03 — Contributing to Open Source
@@ -9,6 +14,17 @@
+
+
+**By the end of this module you will be able to:**
+
+✓ Fork a repository and explain what that actually created
+✓ Keep your fork in sync with the original project (`origin` vs. `upstream`)
+✓ Open a pull request that's likely to get merged quickly
+✓ Find a genuinely beginner-friendly issue to contribute to, today
+
+
+
---
## 📑 In This Module
@@ -39,6 +55,18 @@ You do **not** have push access to the original repo — that's the entire point
how strangers on the internet can propose changes to projects they don't have write access to,
without any risk to the original.
+
+
+**⌨️ TRY IT YOURSELF**
+
+Pick any small public repo on GitHub (even this `git-tutorials` repo works fine for practice) and
+click **Fork** (top right). That's the whole action — no terminal needed yet.
+
+**What should I see?** A new repo under *your* account, named the same thing, with "forked from"
+and a link back to the original shown right under the repo name.
+
+
+
---
## 📥 Clone from GitHub
@@ -73,6 +101,33 @@ git merge upstream/main
git push origin main # update YOUR fork's copy on GitHub too
```
+
+
+**⌨️ TRY IT YOURSELF** (using the fork you just created)
+
+```bash
+git clone git@github.com:yourusername/your-fork-name.git
+cd your-fork-name
+git remote add upstream
+git remote -v
+```
+
+**What should I see?** `origin` pointing at your fork, `upstream` pointing at the original — four
+lines total from `git remote -v` (fetch + push for each).
+
+
+
+
+🧠 Quick Check: You accidentally run git push upstream main instead of git push origin main. What happens?
+
+It fails — you don't have write access to `upstream` (the original repo), which is the entire
+point of the fork workflow. This is a genuinely safe mistake to make: Git will refuse with a
+permission error, not silently do something destructive. The more dangerous version of this
+mix-up is the opposite direction — accidentally *pulling* from `origin` when you meant
+`upstream`, which doesn't error, it just silently leaves you out of sync with the real project.
+
+
+
---
## 📬 Send a Pull Request
@@ -120,6 +175,27 @@ Ran the corrected command on a fresh clone — build script now completes succes
> harder, and sits unreviewed far longer. If you notice something else worth fixing, that's a
> **second** PR, not scope creep on the first one.
+
+
+**🎯 YOUR TURN — Your first real contribution**
+
+Using the fork you set up above (any public repo, including this one): find one genuinely small,
+real thing worth fixing — a typo, a broken link, an outdated command — make the fix on a properly
+named branch, push it to `origin`, and open the pull request. This is a real PR, not a drill.
+
+
+💡 Not sure where to find something to fix?
+
+Search GitHub for issues labeled `good first issue` across any language you're comfortable in
+(or check [goodfirstissue.dev](https://goodfirstissue.dev/)). If genuinely nothing jumps out, even
+fixing a typo in this course's own docs and opening a PR against
+[ghanendra-sdet/git-tutorials](https://github.com/ghanendra-sdet/git-tutorials) counts — the
+mechanism is identical regardless of project size.
+
+
+
+
+
---
## 🔁 The Full Contribution Flow, End to End
@@ -137,9 +213,9 @@ flowchart TD
I -->|Changes requested| F
I -->|Approved| J["Merged! 🎉"]
- style B fill:#fff3cd
- style H fill:#cfe2ff
- style J fill:#d4edda
+ style B fill:#fff3cd,color:#1a1a1a
+ style H fill:#cfe2ff,color:#1a1a1a
+ style J fill:#d4edda,color:#1a1a1a
```
That's genuinely the entire mechanism behind every open-source contribution you've ever seen —
@@ -177,6 +253,17 @@ from a one-line typo fix to a massive feature added to a project with a million
Opening a new PR for review feedback is a surprisingly common beginner instinct, and it's
unnecessary extra noise for the maintainer.)
+
+
+**🧠 You can now:**
+
+✓ Fork a repo and explain exactly what that did and didn't give you access to
+✓ Keep a fork in sync with the real project via `upstream`
+✓ Open a pull request that's actually likely to get reviewed and merged quickly
+✓ Find a real `good first issue` and make your first genuine open-source contribution
+
+
+
---
diff --git a/04-undo-and-recovery/README.md b/04-undo-and-recovery/README.md
index 5f9aae5..f809ee5 100644
--- a/04-undo-and-recovery/README.md
+++ b/04-undo-and-recovery/README.md
@@ -1,3 +1,8 @@
+---
+title: "Module 04 — Undo & Recovery"
+description: "Revert, reset, amend, rebase, and reflog — plus hands-on disaster labs for every 'I think I just lost three days of work' scenario."
+---
+
+
+**By the end of this module you will be able to:**
+
+✓ Choose correctly between `revert` and `reset` based on whether history is shared
+✓ Explain what `--soft`/`--mixed`/`--hard` actually do to your changes, not just recite the flags
+✓ Fix the last commit cleanly with `--amend`, before it's pushed
+✓ Use `reflog` to recover a commit you were sure was gone forever
+✓ Walk through 7 realistic Git disasters and fix every one of them yourself
+
+
+
+> [!CAUTION]
+> This module teaches commands that can discard real work. Do every Try It and Lab below in
+> `~/git-playground` (from [Module 00](../00-introduction/#-set-up-your-safe-sandbox)), never in
+> a repo you actually care about, until the commands are second nature.
+
---
## 📑 In This Module
@@ -18,7 +40,7 @@
- [Amend](#-amend)
- [Rebase](#-rebase)
- [Reflog — The Undo Button for Your Undo Button](#-reflog--the-undo-button-for-your-undo-button)
-- [Recovery Scenarios, Worked](#-recovery-scenarios-worked)
+- [💥 Disaster Labs](#-disaster-labs)
- [Glossary (Module 04 Terms)](#-glossary-module-04-terms)
---
@@ -41,13 +63,31 @@ fix," rather than pretending it never happened.
graph LR
A[commit 1] --> B[commit 2 the bug] --> C[commit 3] --> D["commit 4 git revert commit 2"]
- style B fill:#ffcccc
- style D fill:#d4edda
+ style B fill:#ffcccc,color:#1a1a1a
+ style D fill:#d4edda,color:#1a1a1a
```
**This is why `revert` is the safe choice for anything already pushed and shared** — it doesn't
rewrite history other people may have already pulled, so nobody's local copy gets confused.
+
+
+**⌨️ TRY IT YOURSELF** (in `~/git-playground`)
+
+```bash
+echo "a deliberate bug" >> app.js
+git add app.js
+git commit -m "feat: add a feature (with a bug)"
+git log --oneline -1 # note this commit's hash
+git revert --no-edit HEAD
+git log --oneline -3
+```
+
+**What should I see?** A brand new commit on top, undoing the previous one — the "buggy" line is
+gone from `app.js`, but both commits are still visible in `git log`. Nothing was erased.
+
+
+
---
## ⏮️ Reset — Rewrite History Locally
@@ -78,9 +118,9 @@ graph TB
subgraph "git reset --hard"
H1[Commit undone] --> H2[Changes GONE completely]
end
- style S2 fill:#d4edda
- style M2 fill:#fff3cd
- style H2 fill:#ffcccc
+ style S2 fill:#d4edda,color:#1a1a1a
+ style M2 fill:#fff3cd,color:#1a1a1a
+ style H2 fill:#ffcccc,color:#1a1a1a
```
> [!WARNING]
@@ -96,6 +136,32 @@ graph TB
| Safe on a pushed/shared branch? | ✅ Yes | ❌ No — never reset a branch others have pulled |
| Safe on your own local, unpushed work? | Overkill, but fine | ✅ Yes — this is exactly what it's for |
+
⚠️ Destructive command
+
+
+
+**⌨️ TRY IT YOURSELF** (in `~/git-playground` — this one discards changes on purpose)
+
+```bash
+echo "temporary experiment" >> app.js && git add app.js && git commit -m "wip: experiment"
+git reset --soft HEAD~1
+git status # staged, ready to re-commit — nothing lost
+
+echo "temporary experiment" >> app.js && git add app.js && git commit -m "wip: experiment"
+git reset --mixed HEAD~1
+git status # the change exists but is UNSTAGED — still nothing lost
+
+echo "temporary experiment" >> app.js && git add app.js && git commit -m "wip: experiment"
+git reset --hard HEAD~1
+git status # clean — the change is genuinely gone from app.js
+```
+
+**What should I see?** The exact same commit, undone three different ways — `git status` showing
+staged, then unstaged, then nothing at all. That progression *is* the whole lesson: same command,
+one flag, three completely different outcomes for your actual work.
+
+
+
---
## 🔧 Amend
@@ -115,6 +181,25 @@ item, and reseals it as if it was always right. This **only** works cleanly on t
and **only** before you've pushed it — amending a commit that's already been pushed and pulled by
someone else creates the exact "rewriting shared history" problem `revert` exists to avoid.
+
+
+**⌨️ TRY IT YOURSELF** (in `~/git-playground`)
+
+```bash
+git commit --allow-empty -m "fx: typo in this very message"
+git log --oneline -1 # note this hash
+git commit --amend -m "fix: corrected commit message"
+git log --oneline -1 # the message is fixed...
+git log --oneline -2 # ...but there's still only ONE commit here, not two
+```
+
+**What should I see?** The commit message corrected, and a *different* hash than before —
+`--amend` doesn't edit the old commit in place, it replaces it with a brand new commit object and
+moves the branch pointer to it. `git log --oneline -2` confirms there's still exactly one commit
+here, not a correction stacked on top of the original.
+
+
+
---
## 🌱 Rebase
@@ -139,8 +224,8 @@ graph TB
subgraph "After: git rebase main"
C1["feature: A-B-C-D'-E' (D and E replayed on top of C)"]
end
- style A2 fill:#fff3cd
- style C1 fill:#d4edda
+ style A2 fill:#fff3cd,color:#1a1a1a
+ style C1 fill:#d4edda,color:#1a1a1a
```
**Merge vs. rebase — genuinely one of the most debated topics in all of Git:**
@@ -157,6 +242,30 @@ graph TB
> commits, their history and yours now permanently disagree, and reconciling that is a genuinely
> painful mess. Rebase your own local, not-yet-shared work freely — never rebase `main`.
+
+
+**⌨️ TRY IT YOURSELF** (in `~/git-playground`)
+
+```bash
+git switch main
+git switch -c feature/rebase-practice
+echo "feature work" >> feature.js && git add feature.js && git commit -m "feat: feature work"
+
+git switch main
+echo "main moved forward" >> readme.md && git add readme.md && git commit -m "docs: update readme"
+
+git log --oneline --graph --all # two diverged branches, same starting point
+git switch feature/rebase-practice
+git rebase main # replay feature's commit on top of main's latest
+git log --oneline --graph --all # now a straight line — no merge commit
+```
+
+**What should I see?** Before the rebase, `--graph` shows two separate lines diverging. After,
+one straight line — `feature/rebase-practice`'s commit now sits directly on top of `main`'s, as
+if it had been written after it, not alongside it.
+
+
+
---
## 🕵️ Reflog — The Undo Button for Your Undo Button
@@ -189,36 +298,294 @@ actually gone. It's sitting in Git's internal object database, unreferenced by a
> reachable commits, 30 for unreachable) — but that's an enormous safety window for "I just did
> this 30 seconds ago."
+
+
+**⌨️ TRY IT YOURSELF** (in `~/git-playground`)
+
+```bash
+echo "important work" >> app.js && git add app.js && git commit -m "feat: important work"
+git log --oneline -1 # note this hash
+
+git reset --hard HEAD~1 # "lose" the commit
+git log --oneline -1 # it's really gone from here
+
+git reflog # ...but it's right there in the reflog
+git branch recovered HEAD@{1} # bring it back as a new branch, without touching current HEAD
+git log --oneline recovered -1 # the "lost" commit, fully intact
+```
+
+**What should I see?** The commit missing from `git log` after the reset, then reappearing the
+moment you check `reflog` and recover it onto a new branch — proof that "gone" and "unreferenced"
+aren't the same thing in Git.
+
+
+
---
-## 🚑 Recovery Scenarios, Worked
+## 💥 Disaster Labs
+
+Everything above was one concept at a time. Real Git panic is never that clean — it's always
+"wait, which command do I even start with." These seven labs are realistic, worked disasters.
+Read the scenario, try to fix it yourself first, then check the solution.
+
+### 💥 Lab 1 — Wrong Branch
-**"I committed to the wrong branch"**
+You meant to be on `feature/checkout`, but you committed directly to `main` instead.
+
+
+
+**Set up the disaster:**
```bash
-git log --oneline -1 # note the commit hash
-git reset --hard HEAD~1 # remove it from the wrong branch
-git checkout correct-branch
-git cherry-pick # apply it to the right one
+git switch main
+echo "this should NOT be on main" >> checkout.js
+git add checkout.js
+git commit -m "feat: checkout logic"
```
-**"I deleted a branch and need it back"**
+**🎯 Your task:** move that commit off `main` and onto a new `feature/checkout` branch, leaving
+`main` exactly as it was before.
+
+
+💡 Show solution
+
```bash
-git reflog # find the last commit hash the deleted branch pointed to
-git branch recovered-branch
+git log --oneline -1 # note the commit hash
+git reset --hard HEAD~1 # remove it from main
+git switch -c feature/checkout
+git cherry-pick # bring it onto the new branch
```
-**"I ran `reset --hard` and lost uncommitted work"**
+`main` is back to clean, and `feature/checkout` has exactly the one commit that belonged there.
+
+
+
+
+
+### 💥 Lab 2 — Lost Commit
+
+You've already done this one above in the Reflog Try It — this lab is the same mechanism, framed
+as a cold-open disaster instead of a guided walkthrough.
+
+
+
+**Set up the disaster:**
```bash
-# Unfortunately: reflog only helps with COMMITTED work.
-# Uncommitted changes discarded by --hard are NOT in reflog — this is the one true "gone" scenario.
-# This is exactly why "commit often, even messy WIP commits" beats "wait until it's perfect."
+echo "three hours of work" >> big-feature.js && git add big-feature.js
+git commit -m "feat: three hours of work"
+git reset --hard HEAD~1
```
+**🎯 Your task:** get that commit back, without undoing anything else.
+
+
+💡 Show solution
+
+```bash
+git reflog
+git branch recovered HEAD@{1}
+```
+
+
+
+
+
> [!WARNING]
-> That last scenario is the one real gap in Git's safety net: **reflog protects commits, not
-> uncommitted changes.** This is the strongest possible argument for committing early and often —
-> even an ugly `"wip: half done, don't judge"` commit is fully recoverable. Uncommitted work that
-> gets discarded is genuinely gone.
+> **This lab only works because the work was committed first.** If the three hours of work had
+> never been committed — just sitting in the working directory when `reset --hard` ran — reflog
+> cannot help you; uncommitted changes aren't tracked by it at all. That's the one real gap in
+> Git's entire safety net, and it's the single strongest argument for committing early and often,
+> even an ugly `"wip: half done, don't judge"` commit.
+
+### 💥 Lab 3 — Merge Conflict
+
+Create conflicting changes on two branches and resolve them by hand. The full marker-by-marker
+mechanics live in [Module 05](../05-advanced-git/#-merge-conflicts-properly) — this lab
+is the hands-on version.
+
+
+
+**Set up the disaster:**
+```bash
+git switch main
+echo "const rate = 0.10;" > pricing.js && git add pricing.js && git commit -m "feat: add pricing"
+
+git switch -c feature/pricing-update
+echo "const rate = 0.15;" > pricing.js && git add pricing.js && git commit -m "feat: raise rate"
+
+git switch main
+echo "const rate = 0.12;" > pricing.js && git add pricing.js && git commit -m "feat: adjust rate"
+git merge feature/pricing-update
+```
+
+**🎯 Your task:** Git will stop with a conflict in `pricing.js`. Open it, resolve it to whatever
+value you decide is correct, then complete the merge.
+
+
+💡 Show solution
+
+```bash
+# Edit pricing.js by hand — remove the <<<<<<<, =======, >>>>>>> markers,
+# keep whichever line (or a new one) is actually correct
+git add pricing.js
+git commit
+```
+
+Changed your mind entirely? `git merge --abort` backs out to exactly how things were before you
+started the merge — no harm done.
+
+
+
+
+
+### 💥 Lab 4 — Detached HEAD
+
+```bash
+git checkout
+```
+
+Git will print a long, slightly alarming note about being in **"detached HEAD"** state.
+
+
+
+**💡 CONCEPT**
+
+Normally `HEAD` points at a *branch*, which points at a commit. Checking out a specific commit
+(or tag) directly skips the branch entirely — `HEAD` now points **straight at a commit**, with
+no branch name attached. You can look around freely, but any new commit you make here has no
+branch pointing at it, and is one `git switch` away from becoming an orphaned, reflog-only commit.
+
+
+
+
+
+**Set up the scenario:**
+```bash
+git switch main
+git log --oneline -3 # pick a commit a couple back
+git checkout # "You are in 'detached HEAD' state"
+```
+
+**🎯 Your task:** did you lose any work? Make one commit here out of curiosity, then get safely
+back to `main` — with or without keeping that commit.
+
+
+💡 Show solution
+
+```bash
+echo "experiment" >> scratch.js && git add scratch.js && git commit -m "wip: detached experiment"
+
+# To KEEP it: turn this detached commit into a real branch before leaving
+git switch -c rescued-from-detached
+
+# To DISCARD it and just go back:
+git switch main
+```
+
+Nothing was ever truly at risk — the commit you made while detached is sitting in the reflog
+either way, exactly like Lab 2, for as long as the reflog retention window lasts.
+
+
+
+
+
+### 💥 Lab 5 — Bad Commit Message
+
+You already practiced the mechanism in the Amend Try It above — this is the realistic version of
+*when* you'd reach for it.
+
+
+
+**Set up the disaster:**
+```bash
+git commit --allow-empty -m "fix bug"
+```
+
+**🎯 Your task:** that message tells nobody anything. Fix it — but only because you **haven't
+pushed it yet**.
+
+
+💡 Show solution
+
+```bash
+git commit --amend -m "fix: prevent duplicate charge on double-click checkout"
+```
+
+
+
+
+
+### 💥 Lab 6 — Wrong Commit on the Wrong Branch
+
+A teammate's fix landed on `main`, but a `release/v1.2` branch — already out and in use — also
+needs that exact fix, without pulling in anything else `main` has picked up since.
+
+
+
+**Set up the scenario:**
+```bash
+git switch main
+echo "critical fix" >> auth.js && git add auth.js && git commit -m "fix: critical auth bug"
+git tag v1.2-base # pretend this is where release/v1.2 branched from, earlier
+
+git switch -c release/v1.2 v1.2-base # the release branch, WITHOUT the fix above yet
+```
+
+**🎯 Your task:** get just that one fix onto `release/v1.2`, without merging all of `main`.
+
+
+💡 Show solution
+
+```bash
+git log main --oneline -1 # find the fix commit's hash
+git switch release/v1.2
+git cherry-pick
+```
+
+Full cherry-pick mechanics (and the patch-file alternative for when you can't push directly) are
+in [Module 05](../05-advanced-git/#-cherry-pick--patch).
+
+
+
+
+
+### 💥 Lab 7 — Production Hotfix
+
+The realistic, end-to-end version of everything above: `v1.0.0` is live in production. A critical
+bug is found. `main` has unrelated, half-finished work on it that absolutely cannot ship yet.
+
+
+
+**Set up the scenario:**
+```bash
+git switch main
+git tag v1.0.0 # pretend this is the live production release
+echo "unfinished feature" >> big-feature.js && git add big-feature.js
+git commit -m "wip: half-finished feature, not ready to ship"
+```
+
+**🎯 Your task:** fix the production bug based on the `v1.0.0` tag specifically — not on `main`,
+which now has unshippable work on it — then get the fix both released and merged back into
+`main`, without shipping the unfinished feature.
+
+
+💡 Show solution
+
+```bash
+git switch -c hotfix/critical-bug v1.0.0 # branch from the TAG, not main
+echo "fix applied" >> auth.js && git add auth.js
+git commit -m "fix: critical production bug"
+
+git tag v1.0.1 hotfix/critical-bug # the new, fixed release
+git switch main
+git merge hotfix/critical-bug # bring the fix into main too, unfinished work untouched
+```
+
+`main`'s half-finished feature was never touched — the hotfix branch came from the tag, not from
+`main`'s current tip, so the only thing that reached production was the fix itself.
+
+
+
+
---
@@ -232,6 +599,7 @@ git branch recovered-branch
| **Rebase** | Replay commits on top of a new base, producing linear history — only safe on unshared branches |
| **Reflog** | A log of every place `HEAD` has ever pointed, including "lost" commits — Git's real safety net |
| **Cherry-pick** | Apply one specific commit from anywhere onto your current branch |
+| **Detached HEAD** | `HEAD` pointing directly at a commit instead of at a branch — new commits here aren't on any branch until you make one |
---
@@ -246,6 +614,29 @@ If #2 didn't immediately bring "uncommitted changes, protected by committing oft
that's the single most practically important lesson in this entire module — worth re-reading the
Reflog section above.
+
+🧠 Quick Check: You run git checkout <old-commit-hash> directly and Git warns you about "detached HEAD." Have you broken something?
+
+No — you're just looking at history from a point that has no branch name attached to it. You can
+look around freely. The only thing to be careful about is making NEW commits while detached and
+then switching away without turning them into a real branch first (`git switch -c some-name`) —
+otherwise that commit becomes reachable only through the reflog, like any other "lost" commit.
+
+
+
+
+
+**🧠 You can now:**
+
+✓ Choose `revert` vs. `reset` correctly based on whether history is shared
+✓ Explain what `--soft`/`--mixed`/`--hard` each actually do to your changes
+✓ Fix a commit message or forgotten file with `--amend`, before pushing
+✓ Recover a commit you were sure was gone forever, using `reflog`
+✓ Walk through a wrong branch, a lost commit, a merge conflict, a detached HEAD, a bad commit
+ message, a misplaced commit, and a production hotfix — and fix every one of them
+
+
+
+**By the end of this module you will be able to:**
+
+✓ Keep junk and secrets out of a repo with `.gitignore` — and know its one real limitation
+✓ Apply one specific commit anywhere you need it with cherry-pick
+✓ Resolve a real merge conflict by hand, calmly
+✓ Hunt down exactly which commit introduced a regression, using `git bisect`
+✓ Explain what a pre-commit hook checks vs. what CI checks, and why teams use both
+
+
+
---
## 📑 In This Module
@@ -19,6 +36,7 @@
- [Signing Commits & Tags](#-signing-commits--tags)
- [Cherry-pick & Patch](#-cherry-pick--patch)
- [Merge Conflicts, Properly](#-merge-conflicts-properly)
+- [🔍 git bisect — Finding the Commit That Broke Things](#-git-bisect--finding-the-commit-that-broke-things)
- [Git in CI/CD](#-git-in-cicd)
- [Git Hooks](#-git-hooks)
- [Submodules](#-submodules)
@@ -62,6 +80,23 @@ git commit -m "chore: stop tracking .env, add to gitignore"
> not just removing the file. [gitignore.io](https://www.toptal.com/developers/gitignore) generates
> solid starter files for any language/framework combination.
+
+
+**⌨️ TRY IT YOURSELF** (in `~/git-playground`)
+
+```bash
+echo "node_modules/" > .gitignore
+echo ".env" >> .gitignore
+mkdir node_modules && touch node_modules/some-package.js
+touch .env
+git status
+```
+
+**What should I see?** Neither `node_modules/` nor `.env` show up anywhere in `git status` —
+not even as untracked. `.gitignore` is working.
+
+
+
+**⌨️ TRY IT YOURSELF** (in `~/git-playground`)
+
+```bash
+git switch main
+git switch -c release/v1.2 # pretend this is an earlier release branch
+git switch main
+echo "shared fix" >> shared.js && git add shared.js && git commit -m "fix: shared bug"
+git log --oneline -1 # note this hash
+
+git switch release/v1.2
+git cherry-pick
+git log --oneline -1 # the fix, now here too — with a NEW hash
+```
+
+**What should I see?** The exact same change, now on `release/v1.2`, but as a brand new commit
+with its own hash — cherry-pick copies the *change*, not the original commit object.
+
+
+
**Patch** — export changes as a portable file, apply them elsewhere (useful when you can't push
directly, e.g. emailing a fix to an offline system):
@@ -214,6 +270,98 @@ do, and writing the version that's genuinely correct.
> conflicting changes. A branch merged after 2 days has far fewer conflicts than the same branch
> merged after 3 weeks.
+
+🧠 Quick Check: In a conflict block, which side is HEAD and which is "incoming"?
+
+```
+<<<<<<< HEAD
+const discountRate = 0.10;
+=======
+const discountRate = 0.15;
+>>>>>>> feature/pricing-update
+```
+
+Everything between `<<<<<<< HEAD` and `=======` is **your current branch's** version (the one
+you're merging *into*). Everything between `=======` and `>>>>>>> branch-name` is the **incoming**
+branch's version (the one you're merging *in*). Here, `0.10` is what's on your current branch;
+`0.15` is what `feature/pricing-update` wants to change it to.
+
+
+
+> [!TIP]
+> Want to actually resolve one hands-on instead of just reading markers? [Module 04's Disaster
+> Lab 3](../04-undo-and-recovery/#-lab-3--merge-conflict) sets up a real conflict and walks
+> through fixing it.
+
+---
+
+## 🔍 git bisect — Finding the Commit That Broke Things
+
+A regression test started failing. You know it passed a week ago, across maybe 40 commits since.
+Reading every diff by hand would work — eventually — but there's a faster, structured way:
+**binary search through your own commit history.**
+
+```bash
+git bisect start
+git bisect bad # the current commit is confirmed broken
+git bisect good
+
+# Git checks out a commit exactly halfway between good and bad.
+# Run your test / reproduce the bug, then tell Git the result:
+git bisect good # this commit is fine — the bug is somewhere later
+# or
+git bisect bad # this commit already has the bug — it's somewhere earlier
+
+# Git keeps halving the range automatically. Repeat until it reports:
+# " is the first bad commit"
+
+git bisect reset # done — return to your original HEAD
+```
+
+### 🎭 The real-life example
+
+It's the "guess a number between 1 and 100" game, except the number is "which of these 40
+commits broke the checkout flow," and each guess costs you running the test suite once instead
+of just saying a number. 40 commits, checked one at a time, is 40 checks worst case. Bisected,
+it's **at most ~6** (log₂ 40) — Git throws away half the remaining suspects every single round.
+
+
+
+**🧪 SDET SCENARIO**
+
+Your regression suite flags that `test_checkout_total_with_discount` started failing sometime in
+the last 40 commits, but nobody noticed exactly when because it only runs nightly. Instead of
+checking out commits one at a time and re-running the full suite by hand, point bisect at the
+test itself and let it fully automate the search:
+
+```bash
+git bisect start HEAD
+git bisect run npm test -- --grep "checkout_total_with_discount"
+```
+
+`git bisect run` checks out each candidate commit, runs that exact command, and uses its **exit
+code** to decide good/bad automatically — 0 means good, nonzero means bad. It reports the exact
+first broken commit without you touching a checkout command yourself. This is one of the
+highest-leverage automation-adjacent Git skills a test engineer can have: it turns "something
+broke somewhere in the last two weeks" from a multi-hour manual hunt into one unattended command.
+
+
+
+> [!TIP]
+> `git bisect` needs a command (or your own judgment) that can cleanly say "good" or "bad" for
+> any commit in the range — which is exactly what an automated test assertion already does. This
+> is why bisect and test automation pair so naturally: the test *is* the oracle bisect needs.
+
+
+🧠 Quick Check: Why does git bisect run need a command, not just a test file path?
+
+Because bisect decides good/bad purely from the command's **exit code** — it needs something it
+can actually execute and check the result of, not a file to read. `npm test -- --grep "..."` (or
+`pytest -k "..."`, or any equivalent) is a command that exits 0 on pass and nonzero on failure,
+which is exactly the signal bisect's automated loop depends on.
+
+
+
---
## ⚙️ Git in CI/CD
@@ -222,7 +370,7 @@ Git isn't just for humans typing commands — CI/CD pipelines (GitHub Actions, J
entirely around Git events.
```yaml
-# .github/workflows/test.yml — a real, minimal example
+# .github/workflows/test.yml — a real, minimal example for a Playwright test suite
name: Run Tests
on:
push:
@@ -236,7 +384,8 @@ jobs:
steps:
- uses: actions/checkout@v4 # this step IS a git clone, done for you
- run: npm install
- - run: npm test
+ - run: npx playwright install --with-deps
+ - run: npx playwright test
```
### 🎭 The real-life example
@@ -245,6 +394,19 @@ CI/CD is an automatic inspector standing at the shared filing cabinet, who reads
the moment it's filed (`git push`) and immediately checks it for problems — without waiting for a
human to ask. `on: push` / `on: pull_request` are literally Git events triggering that inspection.
+
+
+**🧪 SDET SCENARIO**
+
+This is the exact mechanism behind "every pull request automatically runs the test suite" —
+which is what Module 02's GitHub Flow called "CI passes" without showing the YAML behind it.
+`actions/checkout@v4` is doing a `git clone` of the exact commit the PR is built on, and the
+suite then runs against that precise snapshot — which is also why a flaky test that only fails
+in CI, not locally, is so often a real environment/data difference, not a ghost: it's running
+against genuinely different, freshly-cloned code every time.
+
+
+
---
## 🪝 Git Hooks
@@ -278,6 +440,28 @@ before allowing a push).
> [Husky](https://typicode.github.io/husky/) solve this by making hooks a normal, committed,
> shareable part of the project instead.
+
+
+**⌨️ TRY IT YOURSELF** (in `~/git-playground`)
+
+```bash
+cat > .git/hooks/pre-commit << 'EOF'
+#!/bin/sh
+echo "🪝 pre-commit hook running..."
+exit 1
+EOF
+chmod +x .git/hooks/pre-commit
+
+echo "test" >> app.js && git add app.js
+git commit -m "this should get blocked"
+```
+
+**What should I see?** The commit is refused — `exit 1` tells Git the hook failed, so the commit
+never happens, even though you ran `git commit` correctly. Change `exit 1` to `exit 0` and try
+again to see it succeed instead.
+
+
+
---
## 🧩 Submodules
@@ -336,6 +520,7 @@ told yet.
| **Signed commit** | A commit cryptographically verified to actually be from you |
| **Cherry-pick** | Apply one specific commit from any branch onto your current one |
| **Merge conflict** | When Git can't automatically reconcile two changes to the same lines — needs human resolution |
+| **`git bisect`** | Binary search through commit history to find exactly which commit introduced a regression |
| **Git hook** | A script that runs automatically at a specific point in Git's workflow (e.g. before a commit) |
| **Submodule** | A Git repository nested inside another, tracked as a reference to a specific commit |
@@ -345,13 +530,26 @@ told yet.
1. Why doesn't adding a file to `.gitignore` remove it from history if it was already committed?
2. When would you reach for `cherry-pick` instead of a full `merge`?
-3. What's the actual difference between what a `pre-commit` hook checks vs. what CI checks — and
+3. Why does `git bisect` need a pass/fail signal (like a test's exit code) to actually automate?
+4. What's the actual difference between what a `pre-commit` hook checks vs. what CI checks — and
why do teams often use both, not just one?
-(#3: a `pre-commit` hook is fast, local, and optional (someone COULD skip it with `--no-verify`)
+(#4: a `pre-commit` hook is fast, local, and optional (someone COULD skip it with `--no-verify`)
— CI is slower, remote, and mandatory. Teams use both: hooks for a fast local safety net, CI as
the actual, unskippable gate.)
+
+
+**🧠 You can now:**
+
+✓ Keep junk and secrets out of a repo with `.gitignore`, and know what it can't undo
+✓ Apply one specific commit anywhere you need it with cherry-pick
+✓ Read merge conflict markers and resolve a conflict calmly
+✓ Hunt down the exact commit that broke something with `git bisect` — manually or automated
+✓ Tell a local hook apart from a CI check, and know why real teams use both
+
+
+
---
diff --git a/06-certification-prep/README.md b/06-certification-prep/README.md
index 074310a..e5de5c0 100644
--- a/06-certification-prep/README.md
+++ b/06-certification-prep/README.md
@@ -1,3 +1,8 @@
+---
+title: "Module 06 — Certification Prep"
+description: "A structured review pass across every module, framed as certification-style, scenario-based practice."
+---
+
# 🎓 Module 06 — Certification Prep
@@ -94,8 +99,14 @@ module before moving on — don't just re-read, actually re-explain it to yourse
**Advanced ([Module 05](../05-advanced-git/))**
- [ ] `.gitignore` vs. `.gitattributes` — different jobs entirely
- [ ] What a merge conflict actually is, and how to resolve one calmly
+- [ ] How `git bisect` finds a regression, and why it needs a pass/fail signal to automate
- [ ] The difference between a local hook and a CI check
+**Disaster Labs ([Module 04](../04-undo-and-recovery/#-disaster-labs))**
+- [ ] Moving a commit off the wrong branch with `cherry-pick`
+- [ ] What "detached HEAD" actually means, and why it isn't automatically dangerous
+- [ ] Why a production hotfix branches from a tag, not from `main`'s current tip
+
---
## ⚡ Rapid-Fire Concept Check
@@ -146,6 +157,21 @@ you use, and why?**
> created — Git just moves `main`'s pointer forward to match, since there's no divergent history
> to actually combine.
+**Q: A regression test started failing sometime in the last 30 commits, but nobody knows which
+one. Walk through how you'd find it.**
+> `git bisect start`, mark the current commit `bad` and a known-working older commit `good`. Git
+> checks out the midpoint; run the test, report `good` or `bad`, and Git halves the remaining
+> range each time — at most ~5 checks for 30 commits, instead of up to 30. If the check is itself
+> a test command, `git bisect run ` automates the entire loop using the command's exit
+> code, with no manual checkout steps at all.
+
+**Q: You run `git checkout` on an old commit hash directly and Git warns about "detached HEAD."
+Is this a problem?**
+> Not by itself — you're just viewing history with no branch name attached to `HEAD`. The only
+> real risk is making a new commit here and switching away without turning it into a real branch
+> first (`git switch -c name`); until then it's one `git switch` away from being reachable only
+> through the reflog.
+
---
## 🗣️ The "Explain It Out Loud" Test
diff --git a/07-exercises-and-study-plan/README.md b/07-exercises-and-study-plan/README.md
index 95af21a..de8c637 100644
--- a/07-exercises-and-study-plan/README.md
+++ b/07-exercises-and-study-plan/README.md
@@ -1,3 +1,8 @@
+---
+title: "Module 07 — Exercises & Study Plan"
+description: "Progressive exercises, a capstone Git Mastery Challenge, and a 20-question quiz bank — where reading turns into muscle memory."
+---
+
# 🏁 Module 07 — Exercises & Study Plan (Capstone)
@@ -9,6 +14,16 @@
+
+
+**By the end of this module you will be able to:**
+
+✓ Run the full branch → commit → PR → merge → recover → bisect loop from memory, not notes
+✓ Self-assess exactly which modules need a second pass, using the quiz bank
+✓ Point at a real, well-organized Git history as proof you know this, not just a certificate
+
+
+
---
## 📑 In This Module
@@ -16,7 +31,7 @@
- [Syllabus — The Whole Course, One Page](#-syllabus--the-whole-course-one-page)
- [Study Plan — Suggested Pacing](#-study-plan--suggested-pacing)
- [Exercises — Progressive, By Module](#-exercises--progressive-by-module)
-- [Capstone Exercise — One Continuous Scenario](#-capstone-exercise--one-continuous-scenario)
+- [🏆 Git Mastery Challenge (Capstone)](#-git-mastery-challenge-capstone)
- [Quiz Bank](#-quiz-bank)
- [You're Done — Now What](#-youre-done--now-what)
@@ -113,27 +128,125 @@ gantt
---
-## 🎯 Capstone Exercise — One Continuous Scenario
+## 🏆 Git Mastery Challenge (Capstone)
+
+This is the final "prove you can actually use Git" exercise — one continuous scenario using
+everything from Modules 00–05 together, the single best way to confirm the concepts connected
+into one coherent mental model instead of staying isolated facts.
+
+
+
+**🎯 THE SCENARIO**
+
+You're working on a small automation project. Starting from a fresh GitHub repo, you need to:
+
+1. Set up `.gitignore` properly from the start
+2. Create a feature branch, make commits, push it
+3. Open a real Pull Request and merge it via GitHub
+4. Deliberately create a merge conflict between two branches, and resolve it
+5. Rebase a local feature branch onto `main` before opening its PR
+6. Make a commit with a typo in the message, `--amend` it before pushing
+7. Push a bad commit, then `revert` it cleanly on a branch that's already shared
+8. Deliberately "lose" a commit with `reset --hard`, then recover it with `reflog`
+9. Use `git bisect` to identify exactly which of several commits introduced a deliberately
+ planted bug
+10. Tag a release, add a `pre-commit` hook, and write a clean, genuinely good README
+
+Don't look at the hints yet. Try the whole thing from memory first — that's the actual point.
+
+
+
+
+💡 Stuck? Hints (not the solution)
+
+- Steps 1–4 are the GitHub Flow loop from Module 02, plus the conflict lab from Module 04
+- Step 5: rebase your feature branch *before* opening the PR, not after — that's the whole reason
+ teams rebase local branches instead of `main`
+- Step 8 only works if the "lost" commit was actually committed first — `reflog` can't rescue
+ uncommitted changes, by design
+- Step 9: plant a bug in one commit among several on purpose, mark the current state `bad`, an
+ earlier known-good commit `good`, and either bisect manually or write one script you can hand
+ to `git bisect run`
+
+
+
+
+💡 Partial solution — steps 1–4
+
+```bash
+git init my-automation-project && cd my-automation-project
+echo "node_modules/" > .gitignore && echo ".env" >> .gitignore
+git add .gitignore && git commit -m "chore: initial gitignore"
+git remote add origin
+git push -u origin main
+
+git switch -c feature/first-change
+echo "feature work" >> feature.js && git add feature.js
+git commit -m "feat: first change"
+git push -u origin feature/first-change
+# Open the PR on GitHub, merge it, then locally:
+git switch main && git pull
+```
+
+
+
+
+💡 Complete solution — all 10 steps
+
+Steps 5–10, continuing from the state above:
+
+```bash
+# 5. Rebase before opening a PR
+git switch -c feature/second-change
+echo "more work" >> feature2.js && git add feature2.js && git commit -m "feat: second change"
+git fetch origin && git rebase origin/main
+
+# 6. Amend a typo'd message before pushing
+git commit --allow-empty -m "fx: typo"
+git commit --amend -m "fix: corrected message"
+
+# 7. Revert a bad commit already shared
+git push -u origin feature/second-change
+echo "oops" >> feature2.js && git add feature2.js && git commit -m "feat: broken change" && git push
+git revert --no-edit HEAD && git push
+
+# 8. Lose and recover a commit
+echo "important" >> feature2.js && git add feature2.js && git commit -m "feat: important work"
+git reset --hard HEAD~1
+git reflog
+git branch recovered HEAD@{1}
+
+# 9. Bisect a planted regression (simplified — exit code stands in for a real test)
+git bisect start
+git bisect bad HEAD
+git bisect good
+# Git checks out a midpoint each round — inspect it, then:
+git bisect good # or: git bisect bad
+# ...until Git reports the first bad commit, then:
+git bisect reset
+
+# 10. Tag, hook, README
+git tag -a v1.0.0 -m "First release"
+echo '#!/bin/sh\necho "checking..."' > .git/hooks/pre-commit && chmod +x .git/hooks/pre-commit
+# Write the README by hand — this part doesn't have a shortcut
+```
+
+
+
+
+💡 Explanation — why these 10 steps specifically
-Do this as one continuous exercise, using everything from Modules 00–05 together — this is the
-single best way to confirm the concepts actually connected into one coherent mental model, not
-just isolated facts:
+Every step maps to a real failure mode this course spent a whole module on: steps 1–4 are the
+daily loop (Modules 01–02), step 5 is the "clean up before review" habit senior engineers expect,
+steps 6–8 are Module 04's safety net exercised for real, step 9 is the single highest-leverage
+debugging skill this course teaches, and step 10 is "does your own repo practice what this course
+preaches" — the same bar [Module 04's Disaster Labs](../04-undo-and-recovery/#-disaster-labs) and
+this very repository hold themselves to.
-1. Create a new GitHub repo for a small project (anything — a script, a simple app, doesn't
- matter)
-2. Set up `.gitignore` properly from the start
-3. Work in feature branches for every change — never commit directly to `main`
-4. Push every branch, open a real Pull Request for each one (even solo, reviewing your own PR is
- good practice), merge via GitHub
-5. Deliberately create one merge conflict between two branches and resolve it
-6. Make one commit with a typo in the message, `--amend` it before pushing
-7. Push a bad commit, then `revert` it cleanly
-8. Tag a "release" with an annotated tag
-9. Add a `pre-commit` hook that checks something trivial
-10. Write a clean, genuinely good README for the finished project
+
-If you can do all 10 steps without needing to look anything up beyond a quick syntax check, you've
-completed this course in the way that actually matters — not "read about Git" but "can use Git."
+If you can do all 10 steps — even needing the hints, not the full solution — you've completed
+this course in the way that actually matters: not "read about Git" but "can use Git."
---
@@ -161,6 +274,8 @@ Quick self-test — cover the answers, go through all 20, then check yourself.
18. What's the actual job of `.gitignore` vs `.gitattributes`?
19. What are the three sections inside a merge conflict's `<<<<<<<`/`=======`/`>>>>>>>` markers?
20. What's the difference between a Git hook and a CI/CD check?
+21. What does "detached HEAD" actually mean, and is it dangerous by itself?
+22. Why does `git bisect run` need a command with an exit code, not just a file to look at?
**Answers:** every single one of these is directly covered in Modules 00–05 — if any felt shaky,
that's your specific, targeted review list. Don't re-read whole modules; jump straight to the
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..9bbd820
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,18 @@
+---
+title: "Code of Conduct"
+description: "A short, genuine code of conduct for contributing to GIT Tutorials."
+---
+
+# Code of Conduct
+
+This is a learning resource. People contributing here are, by definition, often still learning —
+including the beginner fixing their first-ever typo PR.
+
+- Be patient with questions that seem basic. Everyone in this course's own target audience was
+ once the person who didn't know what a staging area was.
+- Critique the content, not the person. "This command is wrong" is useful; anything aimed at the
+ contributor instead of the contribution isn't.
+- No harassment, discrimination, or personal attacks, in issues, PRs, or comments — no exceptions.
+
+Violations can be reported by opening an issue or contacting the maintainer directly through
+GitHub. Reports are taken seriously and handled privately.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..cd8a46b
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,58 @@
+---
+title: "Contributing"
+description: "How to contribute to GIT Tutorials — the exact fork/clone/branch/PR flow this course itself teaches in Module 03."
+---
+
+# Contributing to GIT Tutorials
+
+This course explicitly invites you to practice [Module 03](./03-contributing-to-open-source/)
+on this very repo — a typo fix, a broken link, an outdated command, or a clearer explanation are
+all genuinely welcome. This file is the specific version of the generic flow that module teaches.
+
+## The flow
+
+1. **Fork** this repo (top right on GitHub)
+2. **Clone your fork**, then add this repo as `upstream`:
+ ```bash
+ git clone git@github.com:yourusername/git-tutorials.git
+ cd git-tutorials
+ git remote add upstream git@github.com:ghanendra-sdet/git-tutorials.git
+ ```
+3. **Branch** for your specific change — never commit directly to `main`:
+ ```bash
+ git switch -c fix/broken-link-in-module-04
+ ```
+4. **Make the change.** Keep it small and focused — one fix per PR, per
+ [Module 03](./03-contributing-to-open-source/#-send-a-pull-request)'s own advice.
+5. **Check it** before pushing — see Content Checks below.
+6. **Push to your fork, open a PR** against `ghanendra-sdet/git-tutorials` `main`.
+
+## Content checks before opening a PR
+
+This repo validates two things automatically in CI on every PR (see
+[`.github/workflows/validate-docs.yml`](./.github/workflows/validate-docs.yml)):
+
+- Every fenced code block (` ``` `) has a matching close
+- Every relative Markdown link resolves to a real file
+
+You can run the same check locally before pushing:
+
+```bash
+python3 .github/scripts/check_docs.py
+```
+
+## What makes a good contribution here specifically
+
+- **Preserve the teaching philosophy.** Every section pairs a real-life analogy with a runnable
+ command — if you're adding a new concept, keep that pairing, not just the command.
+- **Hands-on over read-only.** If you're adding a new command or concept, a `callout-tryit` block
+ the reader can actually run beats another paragraph of explanation.
+- **Dummy data only.** Every example uses placeholder names/emails/URLs — never anything that
+ looks like a real credential or real personal data.
+- **Don't remove existing examples, analogies, or labs** to make room for new ones unless they're
+ factually wrong — this course's own ground rule for itself.
+
+## Reporting an issue instead of fixing it yourself
+
+That's genuinely fine too — open an issue using the templates under **New Issue**. A clearly
+described problem is still a real contribution.
diff --git a/README.md b/README.md
index e688cd5..7f4534b 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,8 @@
+---
+title: "GIT Tutorials"
+description: "A free, hands-on Git + GitHub course. Learn → See → Do → Break → Fix → Remember, with real commands, real recovery labs, and zero corporate-training energy."
+---
+
# 🐙 GIT Tutorials — From `git init` to "wait, how do I undo this"
@@ -11,11 +16,23 @@
**A structured, hands-on Git + GitHub course — real commands, real analogies, real "oh no I force-pushed to main" recovery stories, zero corporate-training energy.**
+**Learn → See → Do → Break → Fix → Remember.** You don't just read about a command — you run it,
+in a disposable sandbox, immediately.
+
+[](./00-introduction/)
+[](#️-the-path)
+[](./04-undo-and-recovery/#-disaster-labs)
+[](./resources/cheatsheet.md)
+
**🌐 Browse it live:** [ghanendra-sdet.github.io/git-tutorials](https://ghanendra-sdet.github.io/git-tutorials/)
**📦 View source on GitHub:** [github.com/ghanendra-sdet/git-tutorials](https://github.com/ghanendra-sdet/git-tutorials)
+
+
+
Progress is tracked only in your own browser (localStorage) — no login, nothing sent anywhere. The checklist above will stay empty until JavaScript runs, on the live site.
+
---
## Why this exists
@@ -43,16 +60,36 @@ This one is different for three reasons:
## 🗺️ The Path
+```mermaid
+flowchart TD
+ M00["00 · Introduction & Setup"] --> M01["01 · Git Fundamentals"]
+ M01 --> M02["02 · Git + GitHub"]
+ M02 --> M03["03 · Contributing to Open Source"]
+ M03 --> M04["04 · Undo & Recovery"]
+ M04 --> M05["05 · Advanced Git"]
+ M05 --> M06["06 · Certification Prep"]
+ M06 --> M07["07 · Exercises & Study Plan"]
+
+ style M00 fill:#d4edda,color:#1a1a1a
+ style M01 fill:#d4edda,color:#1a1a1a
+ style M02 fill:#cfe2ff,color:#1a1a1a
+ style M03 fill:#cfe2ff,color:#1a1a1a
+ style M04 fill:#fff3cd,color:#1a1a1a
+ style M05 fill:#ffcccc,color:#1a1a1a
+ style M06 fill:#ede9fe,color:#1a1a1a
+ style M07 fill:#ede9fe,color:#1a1a1a
```
-00 → Introduction & Setup "What even is version control, and why does everyone use this one"
-01 → Git Fundamentals "The 90% of Git you'll use every single day"
-02 → Git + GitHub "Your laptop's Git talking to the internet's Git"
-03 → Contributing to Open Source "Fork, clone, pull request, profit (reputation-wise)"
-04 → Undo & Recovery "You didn't break it. Well — you did. But it's fixable."
-05 → Advanced Git "The stuff that shows up in senior job descriptions"
-06 → Certification Prep "Prove it, on paper"
-07 → Exercises & Study Plan "Stop reading, start typing"
-```
+
+| # | Module | Level | "In one sentence" |
+|---|---|---|---|
+| 00 | [Introduction & Setup](./00-introduction/) | 🟢 Beginner | What even is version control, and why does everyone use this one |
+| 01 | [Git Fundamentals](./01-git-fundamentals/) | 🟢 Beginner | The 90% of Git you'll use every single day |
+| 02 | [Git + GitHub](./02-git-and-github/) | 🟢 Beginner→Intermediate | Your laptop's Git talking to the internet's Git |
+| 03 | [Contributing to Open Source](./03-contributing-to-open-source/) | 🔵 Intermediate | Fork, clone, pull request, profit (reputation-wise) |
+| 04 | [Undo & Recovery](./04-undo-and-recovery/) | 🔵 Intermediate | You didn't break it. Well — you did. But it's fixable |
+| 05 | [Advanced Git](./05-advanced-git/) | 🔴 Advanced | The stuff that shows up in senior job descriptions |
+| 06 | [Certification Prep](./06-certification-prep/) | 🟣 All levels | Prove it, on paper |
+| 07 | [Exercises & Study Plan](./07-exercises-and-study-plan/) | 🟣 All levels | Stop reading, start typing |
Follow the numbers in order for modules 00–02 — everything after that builds directly on branching
and merging, which is the one concept you genuinely cannot skip.
@@ -114,9 +151,13 @@ progression. This is where reading turns into muscle memory.
## 🧠 Practice & Recall
-Reading a module once won't make the commands stick — using them will. Two resources built
+Reading a module once won't make the commands stick — using them will. Three resources built
specifically for that:
+- **[Interactive Cheatsheet Matrix](./resources/interactive-cheatsheet.html)** — every command,
+ searchable and filterable by "how much you're panicking," with one-click copy and a live,
+ consequence-free visual Git tree sandbox (click "commit," "branch," "merge" and watch the graph
+ update in real time).
- **[Command Recall Drill](./resources/command-recall-drill.md)** — scenario-based, fold-to-reveal
quizzing across every module. Do 10-15 a day, not all of them at once — this is spaced
repetition, not a reading list.
diff --git a/_config.yml b/_config.yml
new file mode 100644
index 0000000..f2994d7
--- /dev/null
+++ b/_config.yml
@@ -0,0 +1,23 @@
+theme: minima
+markdown: kramdown
+
+plugins:
+ - jekyll-readme-index
+ - jekyll-relative-links
+
+# Every README.md in this repo has front matter (added for SEO titles/
+# descriptions) — the plugin ignores README files with front matter
+# unless this is explicitly enabled.
+readme_index:
+ with_frontmatter: true
+
+defaults:
+ - scope:
+ path: ""
+ values:
+ layout: "default"
+
+exclude:
+ - Gemfile
+ - Gemfile.lock
+ - vendor
diff --git a/_includes/head-custom.html b/_includes/head-custom.html
index 74e481f..2baf12b 100644
--- a/_includes/head-custom.html
+++ b/_includes/head-custom.html
@@ -1,38 +1,21 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
diff --git a/_includes/head.html b/_includes/head.html
new file mode 100644
index 0000000..4268e38
--- /dev/null
+++ b/_includes/head.html
@@ -0,0 +1,11 @@
+
+
+
+
+ {%- seo -%}
+ {%- feed_meta -%}
+ {%- if jekyll.environment == 'production' and site.google_analytics -%}
+ {%- include google-analytics.html -%}
+ {%- endif -%}
+ {%- include head-custom.html -%}
+
diff --git a/_layouts/default.html b/_layouts/default.html
new file mode 100644
index 0000000..53886f6
--- /dev/null
+++ b/_layouts/default.html
@@ -0,0 +1,43 @@
+
+
+
+ {%- include head.html -%}
+
+
+