From 58703322244ef83ab545370702fcbd72b92193e2 Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:17:58 +0600 Subject: [PATCH 01/35] Add repository quality gate validator --- scripts/validate_repo.py | 137 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 scripts/validate_repo.py diff --git a/scripts/validate_repo.py b/scripts/validate_repo.py new file mode 100644 index 0000000..e085f9d --- /dev/null +++ b/scripts/validate_repo.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +"""Repository quality gates for the Git & GitHub course. + +The validator intentionally uses only the Python standard library so a fresh clone can +run the same checks locally and in CI without installing project dependencies. +""" + +from __future__ import annotations + +import re +import sys +from pathlib import Path +from urllib.parse import unquote + +ROOT = Path(__file__).resolve().parents[1] + +REQUIRED_ROOT_FILES = { + "README.md", + "START_HERE.md", + "STUDENT_LAB.md", + "SAFETY.md", + "ASSESSMENTS.md", + "CHEATSHEET.md", + "CONTRIBUTING.md", + "LICENSE", +} + +MODULES = [ + ("Module_0_Setup", "Module_0_Guide.md", range(1, 4)), + ("Module_1_Daily_Core", "Module_1_Guide.md", range(4, 9)), + ("Module_2_Branching", "Module_2_Guide.md", range(9, 13)), + ("Module_3_Remotes", "Module_3_Guide.md", range(13, 17)), + ("Module_4_Collaboration", "Module_4_Guide.md", range(17, 21)), + ("Module_5_Fixing_Mistakes", "Module_5_Guide.md", range(21, 25)), + ("Module_6_Real_World", "Module_6_Guide.md", range(25, 29)), +] + +MARKDOWN_LINK = re.compile(r"(? None: + errors.append(message) + + +def validate_structure(errors: list[str]) -> None: + for relative in sorted(REQUIRED_ROOT_FILES): + if not (ROOT / relative).is_file(): + fail(errors, f"Missing required root file: {relative}") + + capstone = ROOT / "Capstone_First_Contribution" / "README.md" + if not capstone.is_file(): + fail(errors, "Missing capstone README") + + for module_dir, guide_name, lessons in MODULES: + module = ROOT / module_dir + if not module.is_dir(): + fail(errors, f"Missing module directory: {module_dir}") + continue + + if not (module / guide_name).is_file(): + fail(errors, f"Missing module guide: {module_dir}/{guide_name}") + + exercises = module / "Exercises" + exercise_docs = list(exercises.glob("*.md")) if exercises.is_dir() else [] + if not exercise_docs: + fail(errors, f"Module has no exercise sheet: {module_dir}/Exercises") + + for lesson in lessons: + log = module / "Logs" / f"Lesson_{lesson:02d}.md" + if not log.is_file(): + fail(errors, f"Missing lesson log: {log.relative_to(ROOT)}") + + +def validate_markdown_links(errors: list[str]) -> None: + for md in sorted(ROOT.rglob("*.md")): + text = md.read_text(encoding="utf-8") + for match in MARKDOWN_LINK.finditer(text): + raw_target = match.group(1).strip() + if not raw_target or raw_target.startswith(("http://", "https://", "mailto:", "#")): + continue + + # Markdown links may include an optional quoted title after the destination. + target = raw_target.split(maxsplit=1)[0].strip("<>") + target = unquote(target).split("#", 1)[0] + if not target: + continue + + resolved = (md.parent / target).resolve() + try: + resolved.relative_to(ROOT) + except ValueError: + fail(errors, f"Link escapes repository: {md.relative_to(ROOT)} -> {raw_target}") + continue + + if not resolved.exists(): + fail(errors, f"Broken local link: {md.relative_to(ROOT)} -> {raw_target}") + + +def validate_file_hygiene(errors: list[str]) -> None: + for path in sorted(ROOT.rglob("*")): + if not path.is_file(): + continue + + relative = path.relative_to(ROOT) + if ".git" in relative.parts: + continue + + if path.stat().st_size == 0: + fail(errors, f"Unexpected empty file: {relative}") + + if path.suffix.lower() == ".md": + text = path.read_text(encoding="utf-8") + match = PLACEHOLDER.search(text) + if match: + fail(errors, f"Placeholder marker {match.group(0)!r} in {relative}") + + +def main() -> int: + errors: list[str] = [] + validate_structure(errors) + validate_markdown_links(errors) + validate_file_hygiene(errors) + + if errors: + print("QUALITY GATES: FAIL") + for error in errors: + print(f" - {error}") + return 1 + + print("QUALITY GATES: PASS") + print("Verified required structure, 28 lesson logs, exercise sheets, local links, and file hygiene.") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) From 67d87ed0914500bd77d803bb16df46124f52150f Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:18:04 +0600 Subject: [PATCH 02/35] Add cross-platform repository quality workflow --- .github/workflows/quality.yml | 36 +++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .github/workflows/quality.yml diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml new file mode 100644 index 0000000..f2ccec2 --- /dev/null +++ b/.github/workflows/quality.yml @@ -0,0 +1,36 @@ +name: Course quality gates + +on: + push: + pull_request: + +permissions: + contents: read + +concurrency: + group: course-quality-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + validate: + name: Validate on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Validate course structure and links + run: python scripts/validate_repo.py + + - name: Compile validator + run: python -m py_compile scripts/validate_repo.py From b71c73d40304ec5e82815cba4c404854ebdc53b0 Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:18:14 +0600 Subject: [PATCH 03/35] Document contribution and quality rules --- CONTRIBUTING.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..4743a07 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,42 @@ +# Contributing + +Thanks for helping improve this course. + +This repository is a textbook and reference implementation. Learner practice belongs in a separate disposable `git-github-lab` repository unless a contribution specifically improves the curriculum itself. + +## Before opening a change + +1. Read `SAFETY.md` if the change teaches recovery, reset, rebase, force operations, or history rewriting. +2. Keep the course beginner-accessible. Prefer a precise mental model and a small reproducible exercise over adding more commands. +3. Do not add claims of mastery that the learner cannot demonstrate. +4. Do not add secrets, personal tokens, real credentials, or private repository data. +5. Keep external resources supplementary; the core lesson must remain understandable without hunting through tutorials. + +## Required local check + +Run: + +```bash +python scripts/validate_repo.py +``` + +The same validator runs on both Linux and Windows in GitHub Actions. + +## Pull request standard + +A useful pull request should explain: + +- what learner problem it solves +- what behavior or understanding changes +- how the change was tested +- any safety implications + +For changes that add or modify a Git command exercise, include the expected observable repository state before and after the exercise. + +## Course design rule + +The learning loop is: + +> **LEARN → PREDICT → DO → INSPECT → BREAK/RECOVER → EXPLAIN → PROVE** + +Not every lesson needs every stage as a heading, but major capabilities should eventually require evidence rather than passive reading. From 8dc3441dd1e69a45cc9fd077cea934f7dec935b6 Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:18:22 +0600 Subject: [PATCH 04/35] Add MIT license --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..526bad7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Adham Mahmood + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From eefa21e7e7c6169b4e0b83de0178aad1c69f3a14 Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:18:32 +0600 Subject: [PATCH 05/35] Add setup module challenge sheet --- Module_0_Setup/Exercises/README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 Module_0_Setup/Exercises/README.md diff --git a/Module_0_Setup/Exercises/README.md b/Module_0_Setup/Exercises/README.md new file mode 100644 index 0000000..cf734e0 --- /dev/null +++ b/Module_0_Setup/Exercises/README.md @@ -0,0 +1,26 @@ +# Module 0 Exercises — Setup and Mental Model + +Use your disposable `git-github-lab` repository. Do not perform these exercises in the course repository. + +## Challenge 1 — Prove your environment + +Without copying commands from the guide, prove that: + +- Git is installed and callable from your terminal +- your Git identity is configured intentionally +- you know which directory you are currently in +- you can distinguish a normal folder from a Git repository + +**Evidence:** record the commands you chose and explain what each output proved. + +## Challenge 2 — Repository boundary + +Create one normal folder and one Git repository. Put a file in each. Predict what `git status` will do in both locations, then test your prediction. + +**Pass when:** you can explain what `.git` represents and why Git commands care about repository boundaries. + +## Challenge 3 — Rebuild from memory + +Delete only your disposable practice repository, recreate it from scratch, initialize Git, create a first file, inspect state, make the first commit, and show the history. + +**Do not advance** if you need a command-by-command recipe to complete this challenge. From c333eece879e680aa98cd948584bdb433b6d7415 Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:18:40 +0600 Subject: [PATCH 06/35] Add daily Git core challenge sheet --- Module_1_Daily_Core/Exercises/README.md | 41 +++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 Module_1_Daily_Core/Exercises/README.md diff --git a/Module_1_Daily_Core/Exercises/README.md b/Module_1_Daily_Core/Exercises/README.md new file mode 100644 index 0000000..9b9ccda --- /dev/null +++ b/Module_1_Daily_Core/Exercises/README.md @@ -0,0 +1,41 @@ +# Module 1 Exercises — Daily Core + +Work only in your disposable student lab. + +## Challenge 1 — Three states of one file + +Create a file, stage it, then modify it again before committing. + +Before running any corrective command, predict what each of these will show: + +```bash +git status +git diff +git diff --staged +``` + +Explain why the same file can appear in both staged and unstaged state. + +## Challenge 2 — Selective commit + +Create changes in at least three files. Make one focused commit that intentionally includes only part of the work. + +**Evidence:** show the staged diff before the commit and explain why the omitted changes did not belong in it. + +## Challenge 3 — Ignore-rule trap + +Track a harmless fake configuration file, then add its pattern to `.gitignore`. + +Predict whether Git stops tracking it. Test the prediction and explain why `.gitignore` is not a secret-removal mechanism. + +## Boss check — Inspect before acting + +Have another person or future-you make several mixed changes in the lab. Without immediately staging anything, determine: + +- what changed +- what is staged +- what is not staged +- what is untracked +- what belongs in the next commit + +**Pass when:** your first instinct is inspection, not `git add .`. From 72081769176a7d37778d7bb20df770dcd09fee9e Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:18:50 +0600 Subject: [PATCH 07/35] Add branching challenge sheet --- Module_2_Branching/Exercises/README.md | 33 ++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 Module_2_Branching/Exercises/README.md diff --git a/Module_2_Branching/Exercises/README.md b/Module_2_Branching/Exercises/README.md new file mode 100644 index 0000000..27d5b05 --- /dev/null +++ b/Module_2_Branching/Exercises/README.md @@ -0,0 +1,33 @@ +# Module 2 Exercises — Branching + +Use the disposable student lab. + +## Challenge 1 — Predict branch movement + +Create two commits on `main`, create a feature branch, add two more commits there, and draw the commit graph you expect before inspecting it. + +Then run: + +```bash +git log --oneline --graph --decorate --all +``` + +Explain what the branch names point to. + +## Challenge 2 — Fast-forward vs merge commit + +Produce one merge that can fast-forward and one merge where both branches have diverged. + +Before each merge, predict the resulting graph. + +**Pass when:** you can explain why the outcomes differ without describing branches as folders or copies. + +## Challenge 3 — Branch cleanup + +Merge a finished feature, verify the work is reachable from `main`, then delete the local feature branch. + +Explain why deleting the branch name does not delete commits that remain reachable from `main`. + +## Boss check — Unknown graph + +Create a small history with at least three branches and six commits. Leave it for a while, return without notes, inspect it, and explain which work is merged and which work remains isolated before running a merge command. From 15330af8eccc1e8243868c9a8cc5c630e6f051b2 Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:18:58 +0600 Subject: [PATCH 08/35] Add remotes challenge sheet --- Module_3_Remotes/Exercises/README.md | 33 ++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 Module_3_Remotes/Exercises/README.md diff --git a/Module_3_Remotes/Exercises/README.md b/Module_3_Remotes/Exercises/README.md new file mode 100644 index 0000000..79d084f --- /dev/null +++ b/Module_3_Remotes/Exercises/README.md @@ -0,0 +1,33 @@ +# Module 3 Exercises — Remotes + +Use your disposable student lab plus its GitHub remote. + +## Challenge 1 — Local vs remote truth + +Create a local commit without pushing it. Then inspect: + +```bash +git status +git log --oneline --decorate --graph --all +git branch -vv +``` + +Explain what exists locally and what GitHub does not know yet. + +## Challenge 2 — Remote changes first + +Make a harmless change on GitHub, then return to your local clone. + +Do **not** immediately run `git pull`. + +First fetch and inspect the difference between your local branch and its remote-tracking branch. Decide how to integrate only after you understand the state. + +## Challenge 3 — Two clones + +Clone the same practice repository into two separate folders. Make a commit in clone A and push it. In clone B, inspect the stale state, fetch, explain what changed, then integrate deliberately. + +## Boss check — Divergence diagnosis + +Create one unpublished local commit and one different remote commit so the branch diverges. Before integrating anything, draw the graph and explain your available choices. + +**Pass when:** `fetch → inspect → decide` feels natural and `pull` is no longer a mystery command. From 18525f7ff762d6e4a3a2ccc7143c34890f911c04 Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:19:06 +0600 Subject: [PATCH 09/35] Add collaboration challenge sheet --- Module_4_Collaboration/Exercises/README.md | 31 ++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 Module_4_Collaboration/Exercises/README.md diff --git a/Module_4_Collaboration/Exercises/README.md b/Module_4_Collaboration/Exercises/README.md new file mode 100644 index 0000000..4b2b51a --- /dev/null +++ b/Module_4_Collaboration/Exercises/README.md @@ -0,0 +1,31 @@ +# Module 4 Exercises — Collaboration + +Use your disposable lab or a repository where collaboration is welcome. + +## Challenge 1 — Issue to pull request + +Create an Issue describing a small, testable change. Create a branch from current `main`, implement only that scope, push it, and open a pull request that links the Issue. + +**Evidence:** the PR explains what changed, how it was checked, and what remains out of scope. + +## Challenge 2 — Review without rubber-stamping + +Review a harmless practice PR. Leave at least: + +- one question about intent or behavior +- one concrete improvement request +- one approval only after the requested change is resolved + +Explain the difference between reviewing code and merely confirming that a diff exists. + +## Challenge 3 — Update the same PR + +Respond to review feedback by committing to the existing PR branch. Do not open a replacement PR. + +Explain why the pull request updates automatically. + +## Boss check — Fork and upstream + +Fork a practice repository, clone your fork, add the original repository as `upstream`, fetch from it, and show which refs belong to `origin` versus `upstream`. + +**Pass when:** you can describe the contributor workflow without confusing forks, branches, remotes, and pull requests. From a9532c0d9ffd218845f3492033cb369f285171cb Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:19:14 +0600 Subject: [PATCH 10/35] Add recovery challenge sheet --- Module_5_Fixing_Mistakes/Exercises/README.md | 30 ++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 Module_5_Fixing_Mistakes/Exercises/README.md diff --git a/Module_5_Fixing_Mistakes/Exercises/README.md b/Module_5_Fixing_Mistakes/Exercises/README.md new file mode 100644 index 0000000..2ca9974 --- /dev/null +++ b/Module_5_Fixing_Mistakes/Exercises/README.md @@ -0,0 +1,30 @@ +# Module 5 Exercises — Fixing Mistakes and Recovery + +Use only your disposable student lab. Read `../SAFETY.md` before destructive commands. + +## Challenge 1 — Choose the least destructive tool + +Create four separate mistakes: + +- staged the wrong file +- changed a tracked file but want its last committed version back +- committed the right change with the wrong message +- committed something that should be undone after it has been shared + +For each case, state your intended final state **before** choosing a command. + +## Challenge 2 — Stash deliberately + +Create tracked work in progress, stash it, verify the working tree state, make an unrelated commit, then restore the stash and resolve any conflict if one appears. + +Explain what the stash protected and what it did not. + +## Challenge 3 — Recover a displaced commit + +Create a disposable commit, move the branch away from it, then recover it using reflog by creating a recovery branch at the correct commit. + +**Pass when:** you can prove the commit is reachable again and explain why reflog was useful. + +## Boss check — Shared-history decision + +Given a mistake already pushed to a branch another person may have pulled, explain why `revert` is usually safer than rewriting that history. Then demonstrate the safe repair in your practice repository. From f96bb948190499f6ee749960bae1d745339f5e15 Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:19:24 +0600 Subject: [PATCH 11/35] Add real-world Git challenge sheet --- Module_6_Real_World/Exercises/README.md | 33 +++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 Module_6_Real_World/Exercises/README.md diff --git a/Module_6_Real_World/Exercises/README.md b/Module_6_Real_World/Exercises/README.md new file mode 100644 index 0000000..0a0c7b6 --- /dev/null +++ b/Module_6_Real_World/Exercises/README.md @@ -0,0 +1,33 @@ +# Module 6 Exercises — Real-World Git + +Use the disposable student lab. Read [`../../SAFETY.md`](../../SAFETY.md) before rewriting history. + +## Challenge 1 — Conflict from divergence + +Create a genuine same-line conflict by changing one committed line differently on two diverged branches. Before resolving it, explain: + +- what `HEAD` represents +- what each conflict side contains +- what final content you intend to keep + +Resolve, commit, and prove the working tree is clean. + +## Challenge 2 — Rebase boundary + +Rebase an unpublished feature branch onto updated `main`. Compare commit IDs before and after. + +Then explain why doing the same rewrite to a shared branch can disrupt collaborators. + +## Challenge 3 — Release evidence + +Create an annotated version tag in your lab, inspect it, push it, and create a GitHub Release with concise notes tied to actual changes. + +## Challenge 4 — Controlled CI lab + +Complete the self-contained exercise in [`../../../examples/actions/README.md`](../../../examples/actions/README.md). You must be able to explain the workflow trigger, job, steps, and why the intentionally failing test blocks the check. + +## Boss check — Disaster lab + +Combine at least three failures in one practice repository: a merge conflict, remote divergence, and a displaced commit recoverable through reflog. Diagnose before repairing. + +**Pass when:** you can narrate repository state first, choose the least destructive appropriate operation, and verify the final graph and working tree. From b209e5177464e011b34f1988d96aea985dc3fd93 Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:19:36 +0600 Subject: [PATCH 12/35] Add self-contained GitHub Actions lab --- examples/actions/README.md | 43 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 examples/actions/README.md diff --git a/examples/actions/README.md b/examples/actions/README.md new file mode 100644 index 0000000..59de628 --- /dev/null +++ b/examples/actions/README.md @@ -0,0 +1,43 @@ +# Controlled GitHub Actions Lab + +This lab removes the need to hunt for a random public workflow. Run it in your disposable `git-github-lab` repository. + +## 1. Copy the demo + +Copy these files into your student lab: + +- `examples/actions/demo/app.py` → `app.py` +- `examples/actions/demo/tests/test_app.py` → `tests/test_app.py` +- `examples/actions/basic-python-ci.yml` → `.github/workflows/ci.yml` + +Commit and push them. + +## 2. Inspect before running + +Open `.github/workflows/ci.yml` and identify: + +- the events under `on` +- the job under `jobs` +- the runner +- each step +- the command that decides whether the check passes + +## 3. Prove green + +Open the Actions tab or the commit checks. Confirm the tests pass. + +## 4. Break it deliberately + +Change `add(2, 3)` in the test expectation so the test is wrong, commit, and push. + +Predict the result before checking GitHub. + +Confirm the workflow fails and inspect the failing test output. + +## 5. Repair + +Fix the test, commit, push, and verify the workflow returns to green. + +## Pass condition + +You can explain why CI is useful, what caused the red check, and why a green check only proves the checks that actually ran—not all possible correctness. From ce2875e5fa863d373022c69fd7b08d6b1ee2ff01 Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:19:41 +0600 Subject: [PATCH 13/35] Add copyable beginner CI workflow --- examples/actions/basic-python-ci.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 examples/actions/basic-python-ci.yml diff --git a/examples/actions/basic-python-ci.yml b/examples/actions/basic-python-ci.yml new file mode 100644 index 0000000..a81a07d --- /dev/null +++ b/examples/actions/basic-python-ci.yml @@ -0,0 +1,23 @@ +name: Python CI + +on: + push: + pull_request: + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Run tests + run: python -m unittest discover -s tests -v From 4c940d8ec792ff9b873c37429a848696730e4552 Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:19:45 +0600 Subject: [PATCH 14/35] Add CI demo application --- examples/actions/demo/app.py | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 examples/actions/demo/app.py diff --git a/examples/actions/demo/app.py b/examples/actions/demo/app.py new file mode 100644 index 0000000..e1829c3 --- /dev/null +++ b/examples/actions/demo/app.py @@ -0,0 +1,2 @@ +def add(a: int, b: int) -> int: + return a + b From 0eba994a6a6105f40c0db7e8a9c71ee6a5151519 Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:19:51 +0600 Subject: [PATCH 15/35] Add CI demo test --- examples/actions/demo/tests/test_app.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 examples/actions/demo/tests/test_app.py diff --git a/examples/actions/demo/tests/test_app.py b/examples/actions/demo/tests/test_app.py new file mode 100644 index 0000000..f4c8738 --- /dev/null +++ b/examples/actions/demo/tests/test_app.py @@ -0,0 +1,12 @@ +import unittest + +from app import add + + +class AddTests(unittest.TestCase): + def test_adds_two_numbers(self) -> None: + self.assertEqual(add(2, 3), 5) + + +if __name__ == "__main__": + unittest.main() From f36b0544ce608b8a9289c6c31647dc57beeb828f Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:19:59 +0600 Subject: [PATCH 16/35] Fix recovery exercise safety link --- Module_5_Fixing_Mistakes/Exercises/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Module_5_Fixing_Mistakes/Exercises/README.md b/Module_5_Fixing_Mistakes/Exercises/README.md index 2ca9974..f2df129 100644 --- a/Module_5_Fixing_Mistakes/Exercises/README.md +++ b/Module_5_Fixing_Mistakes/Exercises/README.md @@ -1,6 +1,6 @@ # Module 5 Exercises — Fixing Mistakes and Recovery -Use only your disposable student lab. Read `../SAFETY.md` before destructive commands. +Use only your disposable student lab. Read [`../../SAFETY.md`](../../SAFETY.md) before destructive commands. ## Challenge 1 — Choose the least destructive tool From 0606859c77d09f0cb899ffa330b1f8086a6510d0 Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:20:15 +0600 Subject: [PATCH 17/35] Fix real-world exercise Actions lab link --- Module_6_Real_World/Exercises/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Module_6_Real_World/Exercises/README.md b/Module_6_Real_World/Exercises/README.md index 0a0c7b6..4cf8cc1 100644 --- a/Module_6_Real_World/Exercises/README.md +++ b/Module_6_Real_World/Exercises/README.md @@ -24,7 +24,7 @@ Create an annotated version tag in your lab, inspect it, push it, and create a G ## Challenge 4 — Controlled CI lab -Complete the self-contained exercise in [`../../../examples/actions/README.md`](../../../examples/actions/README.md). You must be able to explain the workflow trigger, job, steps, and why the intentionally failing test blocks the check. +Complete the self-contained exercise in [`../../examples/actions/README.md`](../../examples/actions/README.md). You must be able to explain the workflow trigger, job, steps, and why the intentionally failing test blocks the check. ## Boss check — Disaster lab From a0712bae3a0223d5992b20b77793b64c8fdcc600 Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:20:27 +0600 Subject: [PATCH 18/35] Remove obsolete empty exercise placeholder --- Module_0_Setup/Exercises/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 Module_0_Setup/Exercises/.gitkeep diff --git a/Module_0_Setup/Exercises/.gitkeep b/Module_0_Setup/Exercises/.gitkeep deleted file mode 100644 index e69de29..0000000 From 01afac91960a99307209045608c48fd282657a4d Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:20:32 +0600 Subject: [PATCH 19/35] Remove obsolete empty exercise placeholder --- Module_1_Daily_Core/Exercises/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 Module_1_Daily_Core/Exercises/.gitkeep diff --git a/Module_1_Daily_Core/Exercises/.gitkeep b/Module_1_Daily_Core/Exercises/.gitkeep deleted file mode 100644 index e69de29..0000000 From f71b48599a6f40962528464ee4693436fb9acb77 Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:20:38 +0600 Subject: [PATCH 20/35] Remove obsolete empty exercise placeholder --- Module_2_Branching/Exercises/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 Module_2_Branching/Exercises/.gitkeep diff --git a/Module_2_Branching/Exercises/.gitkeep b/Module_2_Branching/Exercises/.gitkeep deleted file mode 100644 index e69de29..0000000 From 7d1a1efa565a5430b1edf390899303bfbfe85ecd Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:20:41 +0600 Subject: [PATCH 21/35] Remove obsolete empty exercise placeholder --- Module_3_Remotes/Exercises/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 Module_3_Remotes/Exercises/.gitkeep diff --git a/Module_3_Remotes/Exercises/.gitkeep b/Module_3_Remotes/Exercises/.gitkeep deleted file mode 100644 index e69de29..0000000 From f82feedf13215db153b1963155769b3ef811ee2a Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:20:45 +0600 Subject: [PATCH 22/35] Remove obsolete empty exercise placeholder --- Module_4_Collaboration/Exercises/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 Module_4_Collaboration/Exercises/.gitkeep diff --git a/Module_4_Collaboration/Exercises/.gitkeep b/Module_4_Collaboration/Exercises/.gitkeep deleted file mode 100644 index e69de29..0000000 From fe376a2f2c70f5ba4716023bb058a04f16aab9aa Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:20:49 +0600 Subject: [PATCH 23/35] Remove obsolete empty exercise placeholder --- Module_5_Fixing_Mistakes/Exercises/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 Module_5_Fixing_Mistakes/Exercises/.gitkeep diff --git a/Module_5_Fixing_Mistakes/Exercises/.gitkeep b/Module_5_Fixing_Mistakes/Exercises/.gitkeep deleted file mode 100644 index e69de29..0000000 From 88935f49c043b7dbbfbe6282e2d88f00daca041b Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:20:53 +0600 Subject: [PATCH 24/35] Remove obsolete empty exercise placeholder --- Module_6_Real_World/Exercises/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 Module_6_Real_World/Exercises/.gitkeep diff --git a/Module_6_Real_World/Exercises/.gitkeep b/Module_6_Real_World/Exercises/.gitkeep deleted file mode 100644 index e69de29..0000000 From 469c85bc0e231cf7844e7fd373b0a1d1afd1ef33 Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:21:01 +0600 Subject: [PATCH 25/35] Make course CI verify the controlled Actions demo --- .github/workflows/quality.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index f2ccec2..a618664 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -34,3 +34,7 @@ jobs: - name: Compile validator run: python -m py_compile scripts/validate_repo.py + + - name: Verify controlled CI demo + working-directory: examples/actions/demo + run: python -m unittest discover -s tests -v From fa55fe6a69cdba8bca9d918fdde25b5d6bfbae1c Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:21:27 +0600 Subject: [PATCH 26/35] Make real-world module self-contained and strengthen rewrite safety --- Module_6_Real_World/Module_6_Guide.md | 32 ++++++++++++++++++--------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/Module_6_Real_World/Module_6_Guide.md b/Module_6_Real_World/Module_6_Guide.md index 1d689fa..8d997f1 100644 --- a/Module_6_Real_World/Module_6_Guide.md +++ b/Module_6_Real_World/Module_6_Guide.md @@ -100,6 +100,8 @@ Rebase creates new commit identities for the replayed commits. Therefore the imp Rebasing your own feature/PR branch is common in many teams. Rebasing a shared stable branch is a very different risk. +If a team workflow explicitly allows you to rewrite your own published feature branch, `git push --force-with-lease` is safer than raw `--force` because it refuses to overwrite unexpected remote work. It is still a history rewrite and should not be used casually. + ### DO Create divergence: @@ -178,23 +180,30 @@ A workflow normally lives under: .github/workflows/*.yml ``` -At this stage you do not need to become a CI engineer. You need to be able to open a workflow and identify: +This course uses its own workflow at [`../.github/workflows/quality.yml`](../.github/workflows/quality.yml). It validates the repository on both Linux and Windows and also proves that the controlled CI demo still runs. + +At this stage you do not need to become a CI engineer. You do need to identify: -- what triggers it (`on`) +- what triggers a workflow (`on`) - its jobs (`jobs`) +- the runner used by each job - the steps each job performs (`steps`) -- whether a failed check should stop a merge +- the command that determines whether a check passes +- why a failed required check should block a merge A professional GitHub profile is useful too, but profile cosmetics are not evidence of engineering ability. Strong repositories, clear READMEs, useful commits, and real contributions matter more. -### DO -1. Open a workflow from one of your own or a reputable public repository. -2. Identify the trigger, jobs, and major steps. -3. Find a commit or pull request with automated checks and inspect what passed/failed. -4. If you want a profile README, create a public repo named exactly your username and add a truthful README. Do not claim technologies you have not actually used. +### DO — controlled lab first + +1. Open this course's [quality workflow](../.github/workflows/quality.yml) and identify its trigger, job, matrix, and validation commands. +2. Complete the self-contained [`examples/actions`](../examples/actions/README.md) lab in your disposable `git-github-lab` repository. +3. Make the demo workflow pass. +4. Break the demo test deliberately, push it, and inspect the red check. +5. Repair it and verify the check returns to green. +6. Inspect one additional workflow from a reputable project only after you understand the controlled example. ### TRANSITION CONDITION -You can explain what CI/Actions does, locate a workflow, identify its trigger/jobs/steps, and explain why a green check is useful but does not prove software correctness by itself. +You can explain what CI/Actions does, read a basic workflow, produce both a passing and intentionally failing check in your own lab, and explain why green CI proves only the checks that actually ran. --- @@ -203,7 +212,8 @@ You can explain what CI/Actions does, locate a workflow, identify its trigger/jo - [ ] You can deliberately create and resolve a real merge conflict - [ ] You understand divergence rather than treating conflicts as random errors - [ ] You can rebase your own branch and explain the shared-history boundary +- [ ] You can explain when `--force-with-lease` is safer than `--force` and why both still rewrite history - [ ] You can tag and release a version deliberately -- [ ] You can read a basic GitHub Actions workflow +- [ ] You can read and deliberately break/repair a basic GitHub Actions workflow -Then complete **Gate 6 — Real-World Git / Disaster Lab** in [`../ASSESSMENTS.md`](../ASSESSMENTS.md). Pass it before beginning the capstone. \ No newline at end of file +Then complete **Gate 6 — Real-World Git / Disaster Lab** in [`../ASSESSMENTS.md`](../ASSESSMENTS.md). Pass it before beginning the capstone. From 95418e190e95ad946e6914275a2f811f18572e49 Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:21:46 +0600 Subject: [PATCH 27/35] Integrate module exercises into the learner path --- START_HERE.md | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/START_HERE.md b/START_HERE.md index c7d6939..f891acb 100644 --- a/START_HERE.md +++ b/START_HERE.md @@ -26,15 +26,20 @@ All practice, deliberate mistakes, lesson logs, branches, conflicts, resets, and See **[STUDENT_LAB.md](STUDENT_LAB.md)** for the exact setup. -## 3. How every lesson works +## 3. How the course works -Each lesson uses this loop: +Use this loop: -> **LEARN → PREDICT → DO → INSPECT → BREAK/RECOVER → EXPLAIN → TRANSITION CHECK** +> **LEARN → PREDICT → DO → INSPECT → BREAK/RECOVER → EXPLAIN → PROVE** -A lesson is finished only when you can pass its Transition Condition without copying commands from the guide. +For each module: -When a lesson contains a destructive command, first read **[SAFETY.md](SAFETY.md)**. +1. work through the module guide in your student lab, +2. complete the module's `Exercises/README.md` challenges, +3. complete the matching cumulative gate in [ASSESSMENTS.md](ASSESSMENTS.md), +4. advance only when you can do the required work without copying a recipe. + +When a task contains a destructive or history-rewriting command, first read **[SAFETY.md](SAFETY.md)**. ## 4. Course order @@ -44,7 +49,7 @@ When a lesson contains a destructive command, first read **[SAFETY.md](SAFETY.md 4. **Module 3 — Remotes**: clone, push, fetch, pull, upstream tracking, divergence 5. **Module 4 — Collaboration**: forks, pull requests, review, issues, contribution etiquette 6. **Module 5 — Recovery**: restore, stash, amend, revert, reset, reflog -7. **Module 6 — Real World**: conflicts, rebase, tags/releases, Actions awareness +7. **Module 6 — Real World**: conflicts, rebase, tags/releases, controlled GitHub Actions lab 8. **Final Capstone**: complete a real contribution workflow and demonstrate recovery skills ## 5. What to do with the lesson logs @@ -77,7 +82,8 @@ You are ready to move on when you can: - run it, - inspect whether your prediction was correct, - explain the result, -- recover if you deliberately create a failure. +- recover if you deliberately create a failure, +- pass the module challenge and cumulative gate without command-by-command copying. Finishing pages is not mastery. @@ -89,4 +95,4 @@ Finishing pages is not mastery. If you get lost later, return to this sentence: -> **Read the lesson. Work in the lab. Inspect constantly. Pass the transition check from memory.** +> **Read the lesson. Work in the lab. Inspect constantly. Complete the challenge. Prove the gate.** From 815f41bcb9b28d7688cc93bb0d66ff5a7ca15aba Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:22:01 +0600 Subject: [PATCH 28/35] Document executable exercises and repository quality gates --- README.md | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0e47b59..c88a5d5 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,10 @@ Every module ends in evidence, not just reading. | **3 — Remotes** | clone, push, fetch, pull, tracking, remote/local relationships | | **4 — Collaboration** | forks, PRs, reviews, Issues, contribution etiquette | | **5 — Recovery** | restore, stash, amend, revert, reset, reflog, recovery branches | -| **6 — Real World** | conflicts, divergence, rebase boundaries, tags/releases, CI awareness | +| **6 — Real World** | conflicts, divergence, rebase boundaries, tags/releases, controlled CI | | **Capstone** | complete a real contribution workflow and demonstrate recovery skills | -The original course contains 28 lessons. v2 keeps the accessible lesson-based structure while strengthening technical accuracy, safety, cumulative assessment, and professional workflow. +The course contains 28 lessons, module challenge sheets, six cumulative gates plus capstone assessment, and a self-contained GitHub Actions lab. ## Important: use a separate practice repository @@ -68,9 +68,21 @@ You should be able to: - use reflog to recover displaced committed work - understand the shared-history boundary for rebase/reset/force operations - tag a version and understand GitHub Releases -- read a basic GitHub Actions workflow +- read, run, deliberately fail, and repair a basic GitHub Actions workflow - make a small, respectful open-source contribution +## The repository practices what it teaches + +This course has its own cross-platform GitHub Actions quality gate. On every push and pull request it checks the required course structure, all 28 lesson logs, module exercise sheets, local Markdown links, file hygiene, and the runnable CI demo. + +Run the same core validation locally: + +```bash +python scripts/validate_repo.py +``` + +The controlled Actions lab lives at [`examples/actions/README.md`](examples/actions/README.md). + ## Core commands are not the course You will use these constantly: @@ -95,6 +107,12 @@ But competence comes from knowing **what state they change and why**, not from m - [Pro Git](https://git-scm.com/book/en/v2) — free official Git book - [GitHub Docs](https://docs.github.com/en/get-started) — GitHub workflows and product documentation +## Contributing and license + +Contributions should preserve the inspection-first, evidence-based learning model. See [`CONTRIBUTING.md`](CONTRIBUTING.md). + +Licensed under the [`MIT License`](LICENSE). + ## Begin Open **[`START_HERE.md`](START_HERE.md)** and create your student lab before Lesson 1. From 47cb7f52ea4b8336eafde0cf135b2682b7c94503 Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:32:11 +0600 Subject: [PATCH 29/35] Refresh Actions versions and test current Python --- .github/workflows/quality.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index a618664..680a338 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -13,21 +13,22 @@ concurrency: jobs: validate: - name: Validate on ${{ matrix.os }} + name: Validate ${{ matrix.os }} / Python ${{ matrix.python }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest] + python: ["3.12", "3.14"] steps: - name: Check out repository - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v7 with: - python-version: "3.12" + python-version: ${{ matrix.python }} - name: Validate course structure and links run: python scripts/validate_repo.py From 4e5ee6ba5769445e073a933b02bc3576692316e0 Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:32:16 +0600 Subject: [PATCH 30/35] Refresh controlled CI lab to current Actions --- examples/actions/basic-python-ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/actions/basic-python-ci.yml b/examples/actions/basic-python-ci.yml index a81a07d..98627b0 100644 --- a/examples/actions/basic-python-ci.yml +++ b/examples/actions/basic-python-ci.yml @@ -12,12 +12,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v7 with: - python-version: "3.12" + python-version: "3.14" - name: Run tests run: python -m unittest discover -s tests -v From e0b9207a5603aab09520a3e82e9938914b41a419 Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:32:37 +0600 Subject: [PATCH 31/35] Make collaboration review challenge solo-compatible --- Module_4_Collaboration/Exercises/README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Module_4_Collaboration/Exercises/README.md b/Module_4_Collaboration/Exercises/README.md index 4b2b51a..8200a61 100644 --- a/Module_4_Collaboration/Exercises/README.md +++ b/Module_4_Collaboration/Exercises/README.md @@ -10,17 +10,19 @@ Create an Issue describing a small, testable change. Create a branch from curren ## Challenge 2 — Review without rubber-stamping -Review a harmless practice PR. Leave at least: +Inspect a harmless practice PR as if you were its reviewer. Record at least: -- one question about intent or behavior -- one concrete improvement request -- one approval only after the requested change is resolved +- one question about intent or behavior, +- one concrete improvement request, +- the final decision you would make after the concern is resolved and why. -Explain the difference between reviewing code and merely confirming that a diff exists. +If a real collaborator is available, perform the review on their PR. If you are studying solo, keep the review notes in your lab; GitHub does not allow you to approve your own PR, and this course does not require another person just to pass the exercise. + +Explain the difference between reviewing a change and merely confirming that a diff exists. ## Challenge 3 — Update the same PR -Respond to review feedback by committing to the existing PR branch. Do not open a replacement PR. +Respond to your review feedback by committing to the existing PR branch. Do not open a replacement PR. Explain why the pull request updates automatically. From 0e46e9f49577b09b4f37948c0fda3c3b223ec735 Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:32:43 +0600 Subject: [PATCH 32/35] Clarify intentional CI failure step --- examples/actions/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/actions/README.md b/examples/actions/README.md index 59de628..24c25cb 100644 --- a/examples/actions/README.md +++ b/examples/actions/README.md @@ -28,7 +28,7 @@ Open the Actions tab or the commit checks. Confirm the tests pass. ## 4. Break it deliberately -Change `add(2, 3)` in the test expectation so the test is wrong, commit, and push. +In `tests/test_app.py`, change the expected result for `add(2, 3)` from `5` to `6`, commit, and push. Predict the result before checking GitHub. @@ -36,7 +36,7 @@ Confirm the workflow fails and inspect the failing test output. ## 5. Repair -Fix the test, commit, push, and verify the workflow returns to green. +Restore the correct expectation, commit, push, and verify the workflow returns to green. ## Pass condition From 5b7dc4b3659bc30a4922c5107b57d14f66c6db3d Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:36:43 +0600 Subject: [PATCH 33/35] Add secret-pattern hygiene to course quality gates --- scripts/validate_repo.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/scripts/validate_repo.py b/scripts/validate_repo.py index e085f9d..d0b25b1 100644 --- a/scripts/validate_repo.py +++ b/scripts/validate_repo.py @@ -37,6 +37,13 @@ MARKDOWN_LINK = re.compile(r"(? None: @@ -80,7 +87,6 @@ def validate_markdown_links(errors: list[str]) -> None: if not raw_target or raw_target.startswith(("http://", "https://", "mailto:", "#")): continue - # Markdown links may include an optional quoted title after the destination. target = raw_target.split(maxsplit=1)[0].strip("<>") target = unquote(target).split("#", 1)[0] if not target: @@ -108,13 +114,21 @@ def validate_file_hygiene(errors: list[str]) -> None: if path.stat().st_size == 0: fail(errors, f"Unexpected empty file: {relative}") + continue + if path.suffix.lower() not in TEXT_SUFFIXES: + continue + + text = path.read_text(encoding="utf-8") if path.suffix.lower() == ".md": - text = path.read_text(encoding="utf-8") match = PLACEHOLDER.search(text) if match: fail(errors, f"Placeholder marker {match.group(0)!r} in {relative}") + for label, pattern in SECRET_PATTERNS: + if pattern.search(text): + fail(errors, f"Possible {label} committed in {relative}") + def main() -> int: errors: list[str] = [] @@ -129,7 +143,7 @@ def main() -> int: return 1 print("QUALITY GATES: PASS") - print("Verified required structure, 28 lesson logs, exercise sheets, local links, and file hygiene.") + print("Verified structure, 28 lesson logs, exercises, local links, placeholders, empty files, and secret-pattern hygiene.") return 0 From 360b84701e34886565d109316a2414744cd44745 Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:37:57 +0600 Subject: [PATCH 34/35] Add executable Git conflict and recovery smoke test --- scripts/smoke_git_behaviors.py | 77 ++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 scripts/smoke_git_behaviors.py diff --git a/scripts/smoke_git_behaviors.py b/scripts/smoke_git_behaviors.py new file mode 100644 index 0000000..a7aa1a8 --- /dev/null +++ b/scripts/smoke_git_behaviors.py @@ -0,0 +1,77 @@ +from __future__ import annotations + +import subprocess +import tempfile +from pathlib import Path + + +def run(repo: Path, *args: str, check: bool = True) -> subprocess.CompletedProcess[str]: + return subprocess.run( + ["git", *args], + cwd=repo, + check=check, + text=True, + capture_output=True, + ) + + +def write(path: Path, text: str) -> None: + path.write_text(text, encoding="utf-8") + + +def main() -> None: + with tempfile.TemporaryDirectory() as temp: + repo = Path(temp) / "lab" + repo.mkdir() + run(repo, "init", "-b", "main") + run(repo, "config", "user.name", "Course Smoke Test") + run(repo, "config", "user.email", "course-smoke@example.invalid") + + color = repo / "color.txt" + write(color, "Color: blue\n") + run(repo, "add", "color.txt") + run(repo, "commit", "-m", "Add baseline color") + + run(repo, "switch", "-c", "change-color") + write(color, "Color: red\n") + run(repo, "add", "color.txt") + run(repo, "commit", "-m", "Change color to red") + + run(repo, "switch", "main") + write(color, "Color: green\n") + run(repo, "add", "color.txt") + run(repo, "commit", "-m", "Change color to green") + + conflict = run(repo, "merge", "change-color", check=False) + if conflict.returncode == 0: + raise AssertionError("expected same-line merge conflict") + if "UU color.txt" not in run(repo, "status", "--short").stdout: + raise AssertionError("conflicted path was not reported as unmerged") + + write(color, "Color: purple\n") + run(repo, "add", "color.txt") + run(repo, "commit", "-m", "Resolve color conflict") + if run(repo, "status", "--porcelain").stdout.strip(): + raise AssertionError("working tree should be clean after conflict resolution") + + recoverable = repo / "recoverable.txt" + write(recoverable, "recover me\n") + run(repo, "add", "recoverable.txt") + run(repo, "commit", "-m", "Add recoverable commit") + recoverable_sha = run(repo, "rev-parse", "HEAD").stdout.strip() + + run(repo, "reset", "--hard", "HEAD^") + reflog = run(repo, "reflog", "--format=%H").stdout.splitlines() + if recoverable_sha not in reflog: + raise AssertionError("reflog did not retain the displaced commit") + + run(repo, "branch", "recovery", recoverable_sha) + recovered = run(repo, "show", "recovery:recoverable.txt").stdout + if recovered != "recover me\n": + raise AssertionError("recovery branch did not restore displaced committed work") + + print("GIT BEHAVIOR SMOKE TEST: PASS") + + +if __name__ == "__main__": + main() From 99f2d30cf739b9681e15fb4878e2e1d78fdaeec1 Mon Sep 17 00:00:00 2001 From: adhamcodes <219492688+adhamcodes@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:38:03 +0600 Subject: [PATCH 35/35] Run real Git behavior smoke test in CI --- .github/workflows/quality.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 680a338..f495df8 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -39,3 +39,6 @@ jobs: - name: Verify controlled CI demo working-directory: examples/actions/demo run: python -m unittest discover -s tests -v + + - name: Verify Git conflict and reflog behavior + run: python scripts/smoke_git_behaviors.py