Arrange a clear src-layout project, remove working-directory assumptions, ignore generated files, and record understandable changes with essential Git commands.
You will learn: Give source, checks, documentation, configuration, and generated output predictable homes; explain why a src layout requires installation; and inspect, stage, commit, ignore, and branch in a disposable Git repository.
Practice in: A local editor and terminal using a disposable project
Imagine opening a collaborator’s folder and finding Python files, virtual environments, exported reports, notes, and old copies mixed together. Which file starts the program? Which code may callers import? Which output can be deleted? Which changes will the next Git commit contain? A project layout answers those questions before a person has to open every file.
You will arrange the constellation package and record three small snapshots. Keep these questions nearby:
What responsibility does each top-level path communicate?
Why does src/ deliberately prevent an uninstalled import from the repository root?
How can commands avoid relying on an accidental working directory?
What is the difference between working-tree, staged, and committed content?
Which files are source, and which are reproducible local artifacts?
1. Give every project responsibility a visible home
standardized build and project metadata plus tool configuration
README.md
human-facing purpose and first-use instructions
src/constellation/
importable product source
checks/
executable assertions that verify visible behavior
.gitignore
local/generated paths Git should normally leave untracked
.git/
Git’s private repository database, created by git init
Do not create folders merely because a template has them. Add docs/, data/, or notebooks/ when the project truly owns those responsibilities. A clear small tree is better than a large empty architecture.
Create the starting directories from the project root:
On Windows PowerShell, use New-Item -ItemType Directory and New-Item or create the paths in the editor. The tree is the contract; shell spelling can vary by platform.
Source belongs under one import-package directory
# src/constellation/angles.pyFULL_CIRCLE =360def normalize_degrees(value):"""Return an angle in the range 0 <= result < 360."""return value % FULL_CIRCLE
src is not part of the import name. After installation, callers use import constellation, not import src.constellation.
The repository contains source and project support files. Installation makes the package importable in an environment; callers do not import through the repository folder name.
With package source directly at the repository root, launching Python there can make that source importable even if the project was never installed. A src layout removes that convenient coincidence.
Before installation, this command from constellation-report/ should fail:
python-c"import constellation"
That failure is useful. It prevents a check from passing against source files that a built distribution might forget to include. Lesson 6 will add an editable installation during development and a clean wheel installation for final proof.
Do not repair the failure this way:
# Avoid in project source.import syssys.path.insert(0, "src")
The hard-coded path depends on the current working directory and lets every entry script invent a different import configuration. Install the project into the intended environment instead.
Keep resource and configuration roots explicit
Avoid guessing a repository root by walking a fixed number of parents from __file__:
# Brittle: package depth and installation layout are not configuration contracts.from pathlib import Pathguessed_repository = Path(__file__).resolve().parents[2]
After wheel installation, that path points inside the environment, not back to the user’s source checkout. Choose among these contracts instead:
caller-supplied input/output paths;
current-working-directory paths when the command explicitly documents that interface;
installed package resources accessed with importlib.resources; or
configuration supplied through the later command/configuration layer.
For a function that writes a report, pass the destination:
from pathlib import Pathdef save_report(text, destination):"""Write report text to an explicit destination and return that path.""" path = Path(destination) path.write_text(text, encoding="utf-8")return path
The caller owns location policy. The package owns how to write the text.
This check supplies its dependency and cleans up its artifact. It works no matter where the notebook itself started.
3. Ignore artifacts that can be recreated locally
Python, environments, build tools, editors, and operating systems create local files. Record project source and deliberate configuration; normally ignore reproducible caches and outputs:
# Virtual environments.venv/# Python caches and installed-project metadata__pycache__/*.py[cod]*.egg-info/# Build outputsbuild/dist/# Tool caches.pytest_cache/.mypy_cache/.ruff_cache/# Local editor and operating-system files.DS_Store
Do not blindly ignore all data, configuration, notebooks, or lock files. Ask whether the repository deliberately owns the file and whether collaborators need the same content. A generated report normally differs from a hand-curated example fixture.
After adding .gitignore, inspect ignored paths:
git status --short--ignoredgit check-ignore -v .venv/ dist/example.whl
git check-ignore -v reports the exact ignore rule and file that matched. If a file was already tracked, adding it to .gitignore does not erase it from history or automatically stop tracking it. Ignore rules primarily affect untracked paths.
In short status, the first column describes the index/staging state; the second describes the working tree. For example, M means staged modification, M means unstaged modification, and ?? means untracked.
Commit only after the staged diff contains what the message claims:
A commit is local. It does not automatically upload anywhere. Remotes, hosting, and collaboration workflows build on these local concepts.
5. Stage a file twice when it changes twice
Staging copies the file’s current content into the proposed snapshot. If you edit the working file again, the staged version and working version differ.
The staged diff contains the title. The unstaged diff contains the later description relative to the staged version. Run git add README.md again only if both lines belong in the next commit.
This distinction prevents “I staged it earlier” from becoming accidental loss of later work. Always review git diff --staged immediately before committing.
Unstage a mistaken path without deleting the work
Suppose git diff --staged reveals that notes/private-scratch.txt entered the proposal accidentally. Unstage it while leaving the working file available:
git restore --staged notes/private-scratch.txtgit status --shortgit diff --staged
--staged changes the index proposal. It does not mean “delete my working file.” This is different from:
# Read before using: this discards unstaged working-tree edits to README.md.git restore README.md
Never paste a restore command merely because status looks untidy. First decide which copy—current commit, staged proposal, or working edit—you intend to keep. When the work matters, copy it outside the experiment or make a deliberate snapshot before practicing destructive recovery.
For a new untracked file, git restore has no committed version to recover. Untracked work needs its own deletion or preservation decision. git clean can delete many untracked files and is intentionally outside this beginner lab.
Translate short status one path at a time
Use this sequence whenever the two columns are confusing:
git status --shortgit diff -- README.mdgit diff --staged-- README.md
Then fill a small table:
Question
Answer for README.md
Is a version tracked in the current commit?
inspect status/history
What content is staged?
inspect git diff --staged -- README.md
What later content is only in the working tree?
inspect git diff -- README.md
What should the next commit claim?
compare both diffs with the intended change
Path-limited diffs reduce noise without changing any state. Once the answer is clear, stage again, unstage, or keep editing. Inspection commands are safe to repeat.
Write commits around one understandable change
Useful beginner commits might be:
Create constellation package skeleton
Add normalized observation report
Document local verification command
Avoid one commit that mixes a feature, unrelated formatting, generated build artifacts, and experimental notes. Small coherent snapshots make history useful for explanation and recovery.
6. Branches let names point at different lines of work
A branch is a movable name for a commit, not a duplicate folder by definition. Create a short-lived branch before trying a report-format change:
Edit and commit the focused change, then inspect the graph:
git log --oneline--decorate--graph--all-n 8
Switching branches updates tracked working-tree files to the selected snapshot. Git may refuse when uncommitted changes would be overwritten. Read that refusal instead of forcing it. Merging, resolving conflicts, rebasing, and remote branch collaboration deserve later guided practice; this lesson’s boundary is creating, identifying, and inspecting a branch without losing work.
Read history as a project explanation
git log --oneline abbreviates identifiers and subjects for navigation. Ask Git for one complete snapshot when you need its actual change:
git show --stat HEADgit show --format=fuller --no-ext-diff HEAD
The first summarizes affected paths. The second displays commit metadata and patch. A message such as Update files explains almost nothing; Add normalized observation report lets a reader predict what the patch should contain.
Commit identifiers are derived from commit content and relationships, not sequence numbers such as “commit 3.” Two learners following the same lesson can receive different identifiers because author, timestamp, or content differs. Refer to an identifier or branch name when discussing a particular snapshot.
Before switching away from report-header, run:
git status --shortgit log --oneline--decorate--graph--all-n 8
If status is not clean, decide whether the work belongs in a coherent commit or should remain unfinished. Do not assume branch switching stores arbitrary working changes as a new snapshot; commits create the history you can later explain.
7. Record a three-snapshot project story
Complete the constellation tree and create these snapshots:
Snapshot 1: the package skeleton
.gitignore
README.md with project purpose
src/constellation/__init__.py
placeholder pyproject.toml comment if Lesson 6 metadata is not written yet
Snapshot 2: observable behavior
Add angles.py, reports.py, and this built-in assertion script:
Exact hashes differ because each commit identifier depends on its content and metadata. The important evidence is the parent sequence, the two branch labels, and clean git status --short output after each committed snapshot. dist/, .venv/, caches, and *.egg-info/ should not enter the staged diff.
Close files and begin at the repository root. Without relying on editor tabs, answer these questions from the tree and Git:
git status --short--ignoredgit log --oneline--decorate--graph--all-n 8git ls-files
Can you identify importable product source without opening checks or build output?
Can you name the supported import package without including src?
Does git ls-files contain only deliberate source, configuration, documentation, and checks?
Are environment, cache, installed-metadata, and build paths either absent or reported as ignored?
Does each commit subject match its staged patch?
Which commit do main and report-header currently name?
Use Python to classify tracked paths by their first component:
import subprocessfrom collections import Counterfrom pathlib import Pathresult = subprocess.run( ["git", "ls-files"], text=True, capture_output=True, check=True,)tracked = [Path(line) for line in result.stdout.splitlines()]groups = Counter(path.parts[0] for path in tracked if path.parts)print(groups)
The counts are descriptive rather than pass/fail rules. An unexpected .venv, dist, or cache group deserves investigation; an unfamiliar source folder may be a valid project decision. Explain every top-level group in one sentence.
Finally, clone-like confidence requires more than a clean status. In Lesson 6, a new environment will install declared source and run it away from the repository. This audit establishes that the repository contains the inputs for that proof rather than machine history masquerading as source.
Do the audit again from a plain terminal, not only an editor’s source-control panel. Graphical tools are useful, but the command output creates portable evidence another learner can compare. Record the project root and branch name with that output so a clean status from the wrong repository cannot be mistaken for the intended proof. Keep that compact audit with the handoff notes for later comparison.
8. Key points for navigable, versioned projects
A useful tree separates importable source, checks, human documentation, project metadata, and generated/local artifacts.
In a src layout, src is not part of the import name. Installation makes the package importable and exposes accidental repository-root imports.
Pass paths or configuration through explicit interfaces; do not infer a repository by walking a fixed number of parents from __file__.
.gitignore keeps reproducible local artifacts out of untracked-path reports; it does not automatically untrack existing content.
Git distinguishes working-tree content, staged content, and committed snapshots. Inspect each state before moving to the next.
git add stages current content, so edit-after-stage creates both staged and unstaged changes.
A commit is a local snapshot with a parent relationship. A branch is a movable name pointing into that history.