B. Software Development · Software Development: Managing a Team Software ProjectManaging Source Code: Shared Folders and Version Control with Git
B1.7About 75 minutes
Learning goals- I can organize a project's files in shared folders with clear names and date stamps.
- I can explain what a version control system does and why every team project needs one.
- I can use Git to create a repository, stage and commit changes, view history and differences, and restore a file.
- I can explain how Git identifies content with hashes, and why this protects the history.
- I can write clear commit messages and keep secrets and generated files out of a repository.
Words to know
version control system (VCS)
Software that records every change to a set of files, who made it, when and why, so that any earlier version can be recovered and team members' changes can be combined.
Git
A free, widely used distributed version control system. Every copy of a Git repository contains the full history.
repository (repo)
A project folder whose history is tracked by Git; the history lives in a hidden .git folder.
commit
A saved snapshot of the project with an author, a timestamp, a message and a link to the previous commit.
staging area (index)
Where you place the changes you want to include in the next commit, using git add.
hash (commit ID)
A long code calculated from the contents, such as 49303f1.... Any change to the contents gives a completely different hash.
diff
A listing of the lines that were added (+) and removed (−) between two versions of a file.
.gitignore
A file listing names and patterns that Git should not track, such as __pycache__/ folders and files holding passwords.
final_v2_REALLY_final.py
You have probably seen a folder like this: essay.docx, essay_v2.docx, essay_final.docx, essay_final_FIXED.docx. Now imagine four people doing that with twelve program files at once. Someone emails a module to a teammate who has already changed it. Someone copies an old file over a new one. A bug appears and nobody knows which change caused it, or how to get back the version that worked yesterday.
The curriculum asks you to use shared resources to manage source code effectively and securely, for example by organizing software components using shared files and folders with timestamps, and proper version control. This lesson covers both: an organized shared folder, and Git, the version control system used by most of the software industry.
Level 1: an organized shared folder
Even before version control, a team needs a clear, shared folder structure that everyone follows:
- One folder per kind of thing: source code (
src), tests (tests), sample data (data), documents (docs). - Clear, consistent names in lower case with no spaces:
test_loans.py, not Test Loans NEW.py. - Date stamps in ISO order for documents that come in a series:
2026-11-20_status-report-3.md. Putting the year first means alphabetical order is time order. - One copy of each file in the shared place, which everyone reads from and saves to; no private copies emailed around.
A shared folder alone cannot stop two people from overwriting each other's work or recover yesterday's version. For that you need version control.
LendIt's project layout. The same structure works for your own project. A README.md at the top explains what the project is and how to run it.Level 2: version control with Git
A version control system records every change: what changed, who changed it, when, and why. With it you can:
- go back to any earlier version of any file;
- see exactly which change introduced a bug;
- let several people work on the same project at once and combine their changes safely (next lesson);
- keep a backup: every team member's copy of a Git repository contains the full history.
Git is free and runs on Windows, macOS and Linux (download it from git-scm.com, or ask a parent or teacher to install it). It is used from a terminal; most editors, including VS Code and Thonny, also have Git buttons. If you cannot install anything, free hosting sites such as GitHub let you create a repository and commit files in a web browser (check the site's age rules and ask a parent first).
Three places your work can be
Git thinks about your files in three places:
- The working directory: the normal files you edit.
- The staging area: the changes you have chosen for the next commit (
git add puts them there). - The repository: the permanent history of commits (
git commit saves a snapshot there).
The staging area lets you commit related changes together. If you fixed a bug and also started a new feature, you can stage and commit the bug fix by itself, with its own message.
The everyday Git workflow: edit, git add, git commit. git restore copies a file back from the last commit, undoing changes you have not committed.Your first repository
Here is Priya starting LendIt's repository early in the project. The lines starting with $ are commands typed in a terminal; the other lines are Git's replies. (This listing is a terminal session, not Python.)
$ git init -b main lendit
Initialized empty Git repository in .../lendit/.git/
$ cd lendit
$ git config user.name "Priya Sharma"
$ git config user.email "priya@example.com"
(now create loans.py in your editor, with two lines:
LOAN_DAYS = 14 and FEE_PER_DAY = 0.25)
$ git status
On branch main
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
loans.py
nothing added to commit but untracked files present (use "git add" to track)
$ git add loans.py
$ git commit -m "Add loan constants (R2, R3)"
[main (root-commit) 8b9160b] Add loan constants (R2, R3)
1 file changed, 2 insertions(+)
create mode 100644 loans.py
Terminal session 1. git init creates the repository; git config records who you are (once per computer); git status shows that loans.py is not tracked yet; git add stages it and git commit saves the first snapshot. Your commit ID (8b9160b here) will be different.
Now Priya adds a line, checks what changed, and commits again. git status --short gives a one-line summary (M means modified). git diff shows the exact lines: + for added, - for removed.
(add a third line to loans.py: MAX_FEE = 5.00)
$ git status --short
M loans.py
$ git diff
diff --git a/loans.py b/loans.py
index f859512..831de34 100644
--- a/loans.py
+++ b/loans.py
@@ -1,2 +1,3 @@
LOAN_DAYS = 14
FEE_PER_DAY = 0.25
+MAX_FEE = 5.00
$ git add loans.py
$ git commit -m "Add maximum late fee (R3)"
[main 49303f1] Add maximum late fee (R3)
1 file changed, 1 insertion(+)
$ git log --oneline
49303f1 Add maximum late fee (R3)
8b9160b Add loan constants (R2, R3)
$ git log --format="%h %an %ad %s" --date=short
49303f1 Priya Sharma 2026-11-03 Add maximum late fee (R3)
8b9160b Priya Sharma 2026-11-03 Add loan constants (R2, R3)
Terminal session 2. git log --oneline lists commits newest first with short IDs; the longer --format shows author and date. Your IDs and dates will differ, but the diff (including the file IDs f859512 and 831de34) will be the same, because those IDs depend only on the file contents.
Worked exampleExample 1: Getting yesterday's version back
On Wednesday, Jordan edits loans.py, and now nothing works. He has not committed the broken version. On Thursday he discovers that a change he did commit on Tuesday (commit c41d7e2) is the real cause. What should he do in each case?
- Uncommitted mess:
git restore loans.py replaces the working file with the version from the last commit. The broken edits are thrown away (so be sure you want that). - Look before acting:
git log --oneline loans.py lists the commits that changed loans.py. git show c41d7e2 shows exactly what that commit changed. - Undo a committed change:
git revert c41d7e2 makes a new commit that reverses that change. The history still shows both the mistake and its correction, which is honest and safe when others already have the commit. - Then run the tests to confirm that everything works again, and commit.
Uncommitted: git restore. Committed: find it with git log, inspect with git show, undo with git revert, which adds a correcting commit instead of rewriting history.
How Git knows that nothing was changed behind its back
Git names every file version, folder listing and commit by a hash: a 40-character code calculated from the contents with the SHA-1 function (by default). The same contents always give the same hash; changing even one letter gives a completely different one. Because each commit's hash includes its parent's hash, changing any old file would change every later commit's ID, so tampering or disk damage is easy to detect. This program calculates a file's ID the way Git does: the word blob, the length, a zero byte, then the contents.
import hashlib
def git_blob_id(data: bytes) -> str:
"""Return the ID that Git gives a file with these exact contents."""
header = f"blob {len(data)}\0".encode()
return hashlib.sha1(header + data).hexdigest()
print(git_blob_id(b"hello\n"))
print(git_blob_id(b"hello\n"))
print(git_blob_id(b"Hello\n"))
print(git_blob_id(b"LOAN_DAYS = 14\nFEE_PER_DAY = 0.25\n")[:7])
Output: ce013625030ba8dba906f756967f9e9ca394464a twice (same contents, same ID), a completely different ID for Hello with a capital H, and f859512, which matches the first file ID in Priya's diff above.
Writing good commit messages
A project's history is only useful if you can read it. Teams agree on a message style, such as:
- A summary line of at most about 50 characters, starting with a capital letter, no full stop.
- The imperative mood, as if giving an order: "Add late fee cap", not "Added" or "Adds". (Read it as "If applied, this commit will add late fee cap.")
- Mention the requirement or task ID: "(R3)" or "(task F)". This links the code to the traceability matrix and the schedule.
- If needed, a blank line and then a longer explanation of why.
Commit small, commit often: one logical change per commit, and commit whenever the tests pass. Small commits are easy to review and easy to undo.
def check_message(msg):
"""Return a list of problems with a commit message summary line."""
problems = []
if len(msg) > 50:
problems.append("longer than 50 characters")
if not msg[:1].isupper():
problems.append("should start with a capital letter")
if msg.endswith("."):
problems.append("no full stop at the end")
if msg.split()[0].lower() in ("added", "fixed", "changed", "updated"):
problems.append("use the imperative: Add, Fix, Change, Update")
if msg.lower() in ("stuff", "changes", "update", "fix", "wip"):
problems.append("says nothing about what changed")
return problems
for m in ["Add late fee cap of $5.00 (R3)",
"fixed bug.",
"stuff",
"Add search by partial name and make the menu work in both languages and fix typos"]:
print(m[:30], "->", check_message(m) or "OK")
Output: the first message is OK; fixed bug. gets three problems (capital letter, full stop, imperative); stuff gets two; the long message is flagged as longer than 50 characters. m[:30] prints only the first 30 characters of each message.
Keeping a repository secure and clean
"Securely" matters in B1.7. Three rules:
- Never commit secrets: passwords, access keys, members' personal data. Everything in the history stays there, even after you delete the file, and anyone with a copy of the repository can read it. Keep secrets in a separate file listed in
.gitignore, and use made-up sample data (like sample_items.csv) for testing. - Ignore generated files:
__pycache__/ folders and other files that Python or your editor rebuilds automatically. List them in .gitignore so they never clutter git status. - Control access: a shared repository should be private, shared only with team members and the teacher, unless the client agrees to make it public.
And make backups: a repository on a hosting site, a school server or a USB key kept in a different place from the main computer.
Worked exampleExample 2: A .gitignore for LendIt
LendIt's folder contains __pycache__/, a file real_members.csv with the centre's real member list (used only on the centre's laptop), settings_local.py with the laptop's printer password, and sample_items.csv. Which files should be ignored? Write the .gitignore.
__pycache__/ is generated: ignore it.real_members.csv holds real personal information: it must never enter the repository. Ignore it.settings_local.py holds a password: ignore it. Commit a harmless settings_example.py that shows the format, so that others know what to create.sample_items.csv is made-up test data that everyone needs: commit it.- The
.gitignore file has one pattern per line: __pycache__/, real_members.csv, settings_local.py. The .gitignore itself is committed so the whole team uses the same rules.
Ignore __pycache__/, real_members.csv and settings_local.py; commit sample_items.csv, settings_example.py and .gitignore.
A repository's history is a chain of commits, each pointing to its parent. The branch name main points to the newest commit, and HEAD marks where you are now.Just for funIn British slang, "git" is a mild insult meaning an annoying person. Linus Torvalds, who wrote Git in 2005 to manage the Linux kernel, has joked that he names all his projects after himself.
Hands-on activityProject step 6: Put your project under version control
You need: a computer with Git installed (free from git-scm.com, with a parent's or teacher's permission), or a free account on a code-hosting site that works in the browser.
- Arrange your project folder like the LendIt layout:
src, tests, data, docs, and a README.md that says in two or three sentences what the project is and how to run it. - In a terminal, go to the folder and run
git init -b main, then set your name and email with git config (use a school or made-up email if you prefer). - Create a
.gitignore with at least __pycache__/ and any file that holds private data. git add . (the dot means everything not ignored), check with git status, then make your first commit: "Add project documents and module stubs".- Change one stub, look at
git diff, and commit with a good message that names a requirement ID. - Practise recovery: make a deliberate mess in one file, then
git restore it. - Run
git log --oneline and copy the output into docs/plan.md as evidence.
No Git at all? Make the history by hand: copy the src folder into snapshots/2026-11-03_add-stubs/ (ISO date first) each time you finish a step, and keep a CHANGELOG.md with one line per snapshot. It is clumsy, which is exactly why version control was invented.
What to look for: a clean git status, at least two commits with imperative messages, and no private data in the repository.
Your turn
1Name the three places Git keeps track of, in the order changes move through them.
Working directory → staging area → repository.
2Which command shows the exact lines changed in a file since the last commit?
3Improve this commit message: updated stuff.
Example: Add 3-item borrowing limit (R7): capital letter, imperative, specific, requirement ID, no full stop.
4What does git restore loans.py do? When is it dangerous?
It replaces the working copy of loans.py with the version from the last commit. It is dangerous because uncommitted changes are lost for good.
5Why should real_members.csv never be committed, even if you plan to delete it later?
It contains real personal information. A commit stays in the history even after the file is deleted, and everyone with a copy of the repository could read it.
6Two files have exactly the same contents. Will Git give them the same blob ID? Why?
Yes. The ID is a hash of the contents (with the length), so identical contents give identical IDs. Git stores that content only once.
7Why is a file named 2026-11-20_status-report-3.md easier to manage than Status report Nov 20.md?
The ISO date comes first, so the files sort in time order; there are no spaces; and the name is consistent and unambiguous.
More practice: warm-up
2Which command creates a new Git repository?
git init (with -b main to name the first branch main).
3Which command saves a snapshot of the staged changes?
git commit (with -m "message").
4In git status --short, what does M mean?
The file has been modified.
5Name one kind of file that belongs in .gitignore.
Any of: __pycache__/ and other generated files; files with passwords or keys; files with real personal data.
More practice: on your own
1Explain why every commit's ID changes if someone secretly edits an old commit.
Each commit's hash is calculated from its contents including its parent's hash. Changing an old commit changes its hash, which changes its child's contents (the parent link), and so on down the chain to the newest commit. Everyone else's IDs no longer match, so the change is detected.
2Use the commit-message checker on Fix crash when loans.csv is missing. What does it print? Explain.
It prints Fix crash when loans.csv is mi -> OK (the message is cut to 30 characters by m[:30]). It has 35 characters, starts with a capital, has no final full stop, starts with an imperative verb, and is specific.
3Jordan changed loans.py (a bug fix) and menu.py (half of a new feature). How should he commit?
Stage and commit only the bug fix: git add loans.py then git commit -m "Fix late fee for same-day returns (R3)". Leave menu.py uncommitted until the feature works, then commit it separately. One logical change per commit.
4What is the difference between git restore and git revert?
git restore throws away uncommitted changes in your working files. git revert <commit> undoes a committed change by adding a new commit that reverses it, keeping the history.
5A teammate says, "We have a shared folder on the school drive; we don't need Git." Give three things Git does that the folder does not.
Any three: keeps every earlier version and lets you recover it; records who changed what, when and why; shows exact line-by-line differences; helps combine two people's changes to the same file safely instead of one overwriting the other; gives everyone a full backup of the history.
Challenge
1Use git_blob_id to explain why Git can tell instantly that a 2 MB file has not changed since the last commit, without storing two copies of it.
Git compares the file's hash with the hash stored for the last commit. Equal hashes mean equal contents (accidental collisions are practically impossible), so Git needs to compare only 40 characters, and it stores the content once under that ID no matter how many commits include it. (In practice Git first checks the file's size and modification time and recalculates the hash only when those changed.)
2Extend check_message so that it also reports a problem when the message contains no requirement or task ID in brackets, such as (R3) or (task F). Write the extra lines.
Before return problems: if "(" not in msg or ")" not in msg: problems.append("add a requirement or task ID in brackets"). With the test messages, fixed bug., stuff and the long message would now also get this problem; the first message still prints OK.
3A team member accidentally committed a file containing the printer password and then deleted it in the next commit. Explain why this is still a security problem and what the team should do.
The password is still in the earlier commit, and every copy of the repository (including any hosted copy) contains it. The team should change the password immediately (treat it as leaked), add the file to .gitignore, and ask the teacher or lead developer about removing it from the history with a history-rewriting tool. Changing the password is the essential step, because copies of the old history may already exist.
Check your understandingQuiz
1. Which command moves a changed file into the staging area?
2. Which is the best commit message?
3. Why does Git use hashes of the contents as IDs?
4. A file holds the centre's Wi-Fi password. What should the team do?
5. You want to undo a change that was committed two days ago and already shared. Which command fits best?
6. Why name a document 2026-12-04_status-report-5.md rather than Dec 4 status report.md?
For parents and teachersBig idea: B1.7 asks students to use shared resources to manage source code effectively and securely, with organized shared folders, timestamps and proper version control. This lesson introduces Git, the version control system most of the software industry uses, and good habits: small commits with clear messages, and no passwords or personal data in the repository.
How to help: Git is free (git-scm.com) and safe to install. If your teen would like to use a free code-hosting site, check its age rules together and make the repository private. Ask them to show you git log --oneline: a long list of clear messages is a sign of steady, well-organized work.
Common difficulties:
- Forgetting
git add before git commit (nothing gets committed). - Losing work with
git restore by mistake. Commit often so there is always a recent safe point. - Committing everything at the end in one giant commit, which defeats the purpose.
- Committing secrets or real personal data.