Back to the lesson·Lesson 2 of 8·The project on disk
Git does not forget, which is the point and the danger
The same deck as the downloads, rendered as a page. Start the slideshow to present it full screen — arrow keys or a click advance one slide, Escape leaves.
What this lesson covers
- The commit that cannot be taken back
- The
.gitignoreyou write before the first commit - What "personal data" means here, precisely
- The check that runs before the commit does
- What to do if it has already happened
- Commits on analysis work
- What a repository of programme analysis should contain
- Report it whole
- What comes next
Speaker notes
A repository remembers every version of every file it has ever held, which is exactly what you want for code and exactly what you cannot allow for a beneficiary list. Deleting the file in a later commit does not remove it.The commit that cannot be taken back — Shell
git add data/raw/beneficiaries.csv git commit -m "add survey data" # ... realised three days later ... git rm data/raw/beneficiaries.csv git commit -m "remove data"The commit that cannot be taken back
- The file is still in the repository — It is in the history, it is in every clone, it is on the remote, and it is in the…
- On a repository holding protection or health data, that is a disclosure — Not a mistake to fix later — a disclosure,…
- Which is why the rule is preventive rather than corrective — You cannot un-commit personal data; you can only stop it…
Speaker notes
The file is still in the repository. It is in the history, it is in every clone, it is on the remote, and it is in the local copy of everyone who pulled. The second commit removed it from the current state and from nothing else. On a repository holding protection or health data, that is a disclosure. Not a mistake to fix later — a disclosure, with the same obligations as any other. Which is why the rule is preventive rather than corrective. You cannot un-commit personal data; you can only stop it entering.The
.gitignoreyou write before the first commit — Example (cont.)# Raw data. Nothing under here is ever committed. data/raw/* !data/raw/.gitkeep !data/raw/CHECKSUMS # Derived data, regenerated by the pipeline outputs/ # Credentials, in every form they arrive in .env *.pem *_secret* config.local.* # Editor and language noise .RhistoryThe
.gitignoreyou write before the first commit — Example (cont.).RData __pycache__/ *.pyc .DS_Store .ipynb_checkpoints/The
.gitignoreyou write before the first commit- Write it first, commit it first — A
.gitignoreadded after the first commit is a.gitignorethat arrived too late… - The negations matter —
data/raw/*excludes everything, then!data/raw/CHECKSUMSlets back exactly the file that…
Speaker notes
Write it first, commit it first. A.gitignoreadded after the first commit is a.gitignorethat arrived too late for the thing it was meant to catch. The negations matter.data/raw/*excludes everything, then!data/raw/CHECKSUMSlets back exactly the file that proves which export was used — provenance without content.- Write it first, commit it first — A
What "personal data" means here, precisely
Looks anonymous Is not, because A case ID It joins to a case management system that has the name GPS coordinates of a household It is an address Date of birth plus commune plus sex Three fields identify most people in a small commune A photograph of a form Every field on it A free-text "notes" column It contains whatever the caseworker typed Speaker notes
It is broader than a name column, and this sector's datasets make the point.What "personal data" means here, precisely
- The protection course established the arithmetic — a four-way disaggregation of an anonymous dataset produced nineteen…
- So the test is not "does it have a name column" — It is whether any combination of what the file holds could pick one…
Speaker notes
The protection course established the arithmetic: a four-way disaggregation of an anonymous dataset produced nineteen cells of one, and a cell of one is an identification. So the test is not "does it have a name column". It is whether any combination of what the file holds could pick one person out — and in a small commune it usually can.The check that runs before the commit does — Shell
# .git/hooks/pre-commit — or the pre-commit framework, or a CI step if git diff --cached --name-only | grep -qE '^data/raw/'; then echo "Refusing to commit anything under data/raw/" >&2 exit 1 fiThe check that runs before the commit does — In R
# The same check as an R function, run by whatever CI you have.The check that runs before the commit does
- A hook that refuses is worth more than a policy that reminds — The failure mode is someone in a hurry at 6pm, and a…
- Add a scan for the shapes secrets take — because a token pasted into a script is the other common case
Speaker notes
A hook that refuses is worth more than a policy that reminds. The failure mode is someone in a hurry at 6pm, and a reminder does not survive that. Add a scan for the shapes secrets take, because a token pasted into a script is the other common case.gitleaksanddetect-secretsboth do this in a CI step.What to do if it has already happened
- One: report it — Your organisation has a data-incident procedure
- Two: rotate anything that was a credential — A committed token is compromised permanently; deleting it changes nothing
- Three: rewrite the history — with
git filter-repoor BFG, and force-push - Four: assume it was fetched — On a public repository, assume the file was retrieved and mirrored, and treat the…
Speaker notes
The order matters and the first step is not technical. One: report it. Your organisation has a data-incident procedure. This is one, and whether the repository was public decides how urgent rather than whether. Two: rotate anything that was a credential. A committed token is compromised permanently; deleting it changes nothing. Three: rewrite the history, withgit filter-repoor BFG, and force-push. Every existing clone must be deleted and re-cloned — a clone nobody re-cloned still has the file. Four: assume it was fetched. On a public repository, assume the file was retrieved and mirrored, and treat the disclosure as complete.What to do if it has already happened — Shell
git filter-repo --path data/raw/beneficiaries.csv --invert-pathsWhat to do if it has already happened
- Step three is the only one that looks like a fix and it is the least important
Speaker notes
Step three is the only one that looks like a fix and it is the least important.Commits on analysis work
- Commit the code and the output together when the output is committed — A figure and the script that made it belong in…
- Write the why in the message —
fix commune namesis what the diff already shows
Speaker notes
The habits differ from software work in two ways worth naming. Commit the code and the output together when the output is committed. A figure and the script that made it belong in one commit, so a reviewer can see whether the chart matches the change. Write the why in the message.fix commune namesis what the diff already shows. "Normalise four spellings of Nord-Ouest; ungrouped, the worst district splits into four fragments and none of them looks alarming" is the thing a successor needs and cannot recover.Commits on analysis work — Shell
git commit -m "Group four spellings of Nord-Ouest before computing coverage Ungrouped, the worst district splits into fragments of 727, 33, 22 and 20 households and none of them looks alarming. The 20-household fragment shows 0% open defecation, which a district table would report as a solved problem."Commits on analysis work
- Branch per analysis question, not per day — A branch that answers "does coverage differ by district" can be reviewed…
Speaker notes
Branch per analysis question, not per day. A branch that answers "does coverage differ by district" can be reviewed and merged; a branch calledworkcannot.What a repository of programme analysis should contain — Example
project/ .gitignore committed first README.md what it produces, how to run it, what to know uv.lock the environment, pinned src/ every transformation tests/ the checks that fail loudly data/ raw/ .gitkeep and CHECKSUMS only reference/ committed: thresholds, admin codes, lookup tables outputs/ ignoredWhat a repository of programme analysis should contain
- Reference tables are committed and raw data is not — and the line between them is whether the file describes people
Speaker notes
Reference tables are committed and raw data is not, and the line between them is whether the file describes people. WHO growth standards, IPC thresholds and admin boundary codes are reference; a household list is not.Report it whole — Example
Data handling in this analysis No raw data is committed. data/raw/ is excluded by .gitignore, with only a CHECKSUMS file tracked so the exports used can be identified. A pre-commit hook refuses any staged file under data/raw/ and a CI step scans for credential patterns. Reference tables (WHO growth standards, IPC thresholds, admin codes) are committed; they describe no individual. The repository is private and access is limited to the M&E team. Raw exports are held in [the organisation's storage], not here.Report it whole
- The last line is the one that gets forgotten and it is the one an audit asks for — Saying where the data is matters…
Speaker notes
The last line is the one that gets forgotten and it is the one an audit asks for. Saying where the data is matters as much as saying where it is not.What comes next
- Code in version control still does not reproduce a number if the environment differs.
Speaker notes
Code in version control still does not reproduce a number if the environment differs. The next lesson pins it, and finds the four things that make a rerun differ when nothing in the repository changed at all.