6. Python scripts under version control¶
Placement check
CDC's Class 10 computer textbook (2025 ed.) teaches Python (Unit 4). A Grade 9 syllabus has not been located, so students may not have met Python yet. Teach this module in Grade 10 (after Python is taught), or run it read-only with the provided files: the Git skill is the goal.
At a glance
Time: 2 periods · Tiers: 1–3 · You need: Python 3, a terminal, a copy of practice-repos/g09-ward-facts
Before this: Modules 2–5.
By the end you can: decide which files belong in a repository, write a .gitignore, write a useful README, and use history to find the commit that broke a program.
Hook (5 min)¶
Your program worked yesterday and breaks today. You changed "only a little". Can history tell you exactly what changed — and when it broke?
Understand it¶
What belongs in a repository?¶
A repository should contain everything a person needs to rebuild and understand the project, and nothing that the computer can regenerate or that must stay secret.
| Kind of file | Example | In Git? | Why |
|---|---|---|---|
| source code | facts.py |
yes | it is the work |
| documentation | README.md, DATA.md |
yes | people need it |
| small input data you are allowed to share | data/wards.csv |
yes | the script needs it |
| generated / cache files | __pycache__/, *.pyc |
no | Python recreates them |
| output you can recreate | result.txt, chart.png |
usually no | run the script again |
| local databases | ward.db |
no | rebuild from .sql (Module 7) |
| editor settings | .vscode/, .idea/ |
usually no | personal |
| secrets | passwords, API keys, .env |
never | anyone with the repo can read them — for ever |
.gitignore¶
A file named .gitignore in the top folder lists patterns of files Git should not track. Ignored files do not appear in git status and git add . skips them.
# Python cache files
__pycache__/
*.pyc
# local database built from schema.sql
*.db
# secrets — never commit
.env
Pattern rules: * matches any characters; a name ending with / means a folder; # starts a comment. .gitignore itself is committed, so the whole team ignores the same files. Ready-made templates for every language exist in github/gitignore (CC0 — copy freely).
.gitignore does not un-track
If a file was already committed, adding it to .gitignore does not remove it from history. Remove it from tracking with git rm --cached <file> and commit — and if it was a secret, treat it as leaked (Grade 12, Module 5).
A README that helps¶
Every repository needs a README.md that answers: What is this? How do I run it? Where does the data come from? Who made it, and under what licence? A stranger (or you next year) should be able to run the project using only the README.
History as a debugging tool¶
When a program breaks, ask what changed since it last worked?
git diff— what have I changed since my last commit?git log -p— every commit with its diff; scroll to find a suspicious change (qquits).git log -p facts.py— the same, only for one file.git show <id>— one commit's change.git restore --source=<good-id> facts.py— bring back the last working version (Grade 8, Module 5).
This is why small commits matter for code: if each commit changes one thing, the commit that broke the program is short and easy to read. (In Grade 12 you will learn git bisect, which finds the bad commit automatically.)
See it¶
$ python facts.py
Largest ward: Test Ward Four (2310 people)
$ git status --short --ignored
!! __pycache__/ <- !! = ignored, thanks to .gitignore
$ git log -p -1 facts.py
commit 4f1e2d7 ...
Rename variable top to biggest
diff --git a/facts.py b/facts.py
@@ -17,5 +17,5 @@
if __name__ == "__main__":
wards = load()
- top = largest_ward(wards)
+ biggest = largest_ward(wards)
print(f"Largest ward: {top['ward_name']} ({top['population']} people)")
The diff shows the bug at once: the variable was renamed on one line but not on the next, so top no longer exists. (Your ids will differ.)
Concept sort (10 min, unplugged)¶
Print 12 file names on slips (facts.py, __pycache__/facts.cpython-312.pyc, README.md, .env, ward.db, data/wards.csv, chart.png, .vscode/settings.json, schema.sql, passwords.txt, DATA.md, notes-for-me.txt). Pairs sort them into commit, ignore, never. Discuss the hard ones (chart.png, notes-for-me.txt).
PRIMM lab (50 min)¶
Repo: practice-repos/g09-ward-facts — copy the folder, then git init it and commit everything (git add . && git commit -m "Start from ward-facts starter"), or clone the bundle your teacher gives you.
- Predict: read
facts.py. What will it print? Runpython facts.py. - Investigate: open
.gitignore. Rungit status --short --ignored. Which file is ignored, and why did it appear only after running Python? - Investigate: temporarily rename
.gitignoretogitignore.txt. What doesgit statusshow now? Rename it back. - Modify: change the question the script answers (for example, the ward with the smallest population: use
min). Run, thengit diff. Commit with a message that says why. - Break it on purpose: rename a variable on one line only (as in See it), commit with an innocent message. Run the script — error! Use
git log -pto find which commit broke it. Fix it with a new commit whose message names the bad commit. - Make: on a branch
add-average, add a functionaverage_population(wards)and print it. Merge the branch intomain(Module 4). Update the README with one line: how to run.
Parsons practice (10 min)¶
Order: edit facts.py · run python · git diff · git add · git commit. Second set: program fails · git log -p · find bad commit · fix · commit "Fix … introduced in …".
Common mistakes¶
| Symptom | Cause | Fix |
|---|---|---|
__pycache__ shows as untracked |
no .gitignore |
add the patterns above |
File still tracked after adding to .gitignore |
it was committed before | git rm --cached <file>, commit |
.gitignore "doesn't work" |
saved as .gitignore.txt by Windows Notepad |
rename; turn on "show file extensions" |
python not found |
Python not installed / not on PATH | try python3 or py; ask the teacher |
| Committed a password | — | tell the teacher; change the password; see Grade 12 Module 5 |
Practise more with free resources¶
| # | Exercise | Tier | Time | Hand in |
|---|---|---|---|---|
| 1 | Software Carpentry Ignoring Things: do the exercises "Ignoring Nested Files" and "Including Specific Files". | 1 / 3 | 25 min | answers |
| 2 | Open the Python template in github/gitignore. Find three patterns we did not use and explain what they ignore. | 3 | 15 min | 3 patterns |
| 3 | Pro Git §2.2 Recording Changes, section "Ignoring Files": write the five glob-pattern rules in your own words. | 3 | 15 min | rules |
| 4 | GitHub Skills Communicate using Markdown (13+, account) — then improve your README. | 3 | 40 min | improved README |
| 5 | Debug race (Tier 1): the teacher gives a repo with 10 commits where one broke the script. Find it with git log -p faster than the other pairs. |
1 | 15 min | bad commit id |
Exit ticket (5 min)¶
- Why do we ignore
__pycache__/? - How can
git log -phelp debugging? - Misconception: "If the code works, the history does not matter."
Key points¶
- Commit source, docs and small shareable data; ignore generated files; never commit secrets.
.gitignoreis itself committed; it does not remove already-tracked files.- A README says what, how to run, where data comes from, who and licence.
git log -pand small commits turn history into a debugging tool.
New words¶
.gitignore, pattern / glob, generated file, secret, README, git log -p — see the glossary.
Extension quest¶
Write a requirements.txt (even if empty) and a "How to run" section that a Grade 8 student could follow. Ask one to try it.
Teacher notes¶
- Timing: P1: hook 5, understand 15, concept sort 10, lab 1–3 (10). P2: lab 4–6 (35), Parsons 5, exit 5.
- Hours depend on CDC placement of Python (unverified). If students have not met Python, use only the provided scripts (read/run/modify) — the Git skill is the goal.
- Debug race setup (exercise 5): make 10 small commits to
facts.py; in commit 6 introduce a typo (ward_nmae). Bundle it (git bundle create debug.bundle --all). - Concept sort answers: commit —
facts.py,README.md,data/wards.csv,schema.sql,DATA.md; ignore —__pycache__/…,ward.db,chart.png(if regenerated),.vscode/…,notes-for-me.txt(personal); never —.env,passwords.txt. - Answer key: Exit 1: Python regenerates them; they are not source. 2: it shows each commit's changes, so you can spot when a bug appeared. 3: False — history lets you understand, debug and undo. Lab 2:
__pycache__/appears after Python runs because Python creates it then.