For developers
This section documents the design of the Making With Code CLI at a technical level. It is intended for software developers who want to understand, modify, or extend the codebase. Students and teachers do not need to read this.
The source code is hosted at github.com/cproctor/making-with-code-courseware.
Architecture overview
The CLI has four main concerns, each with a corresponding module or package:
Account management (
mwc_accounts_api.py): HTTP client to the MWC accounts server. Handles authentication and provides the roster of students and their per-student git tokens.Curriculum (
curriculum.py): Fetches the course manifest from a curriculum site URL. The manifest is a JSON document listing every course, unit, and module, including each module’s template repo URL.Git backend (
git_backend/): Abstracts over the git server. Currently onlyMWCBackendis implemented, targeting the Gitea instance atgit.makingwithcode.org.Settings (
settings.py): Reads and writes a TOML config file, defaulting to~/.config/mwc/settings.toml. The path can be overridden with theMWC_CONFIGenvironment variable or--configflag, which supports running multiple MWC identities on one machine.
How mwc update wraps git
Each curriculum module is distributed as a git repository. When a student
runs mwc update, the CLI:
Calls the accounts server to get the student’s section memberships.
For each section, fetches the curriculum manifest to get the list of modules.
For each module, calls
MWCBackend.init_module()(if the repo does not yet exist locally) orMWCBackend.update()(if it does).
init_module() uses the Gitea API to fork the template repo into the
student’s account, then clones it with credentials embedded in the HTTPS
URL. It also writes a local git config (user.name, user.email,
core.editor, commit.template) so students are not prompted for
global git identity.
update() delegates to safe_pull(), which is the main focus of
this section.
safe_pull()
A bare git pull fails in surprisingly many ways for beginner
students who may have left their repos in unusual states. safe_pull()
in git_backend/mwc_backend.py wraps the pull in a sequence of
pre-flight checks and recovery steps before attempting the pull, and
classifies failures into typed exceptions afterward.
Pre-flight recovery
Before pulling, safe_pull() uses helpers from git_wrapper.py to
repair common bad states:
In-progress merge, rebase, or cherry-pick — Detected by checking for
MERGE_HEAD,rebase-merge/,rebase-apply/, orCHERRY_PICK_HEADin the.gitdirectory. Fixed by running the appropriate--abortcommand.Detached HEAD — Detected when
git branch --show-currentreturns an empty string. Fixed by stashing any local changes, checking out the default branch (discovered fromrefs/remotes/origin/HEAD), and popping the stash.Stale index lock —
.git/index.lockis removed if present.
Conflict resolution policy
The pull uses git pull -X theirs, which means incoming changes
(from the teacher) always win in a merge conflict. This is appropriate
because teacher pushes are typically feedback annotations, test updates,
or bug-fixes to starter code. Student work is preserved: if the
student has uncommitted changes to files that the incoming pull would
overwrite, those changes are auto-committed before the pull with the
message [auto-saved before update].
The -X theirs extended option also handles the case where local and
remote histories have diverged (e.g. after a force-push), since it
implies a merge strategy and does not require pull.rebase to be
configured.
Error classification
If the pull fails after pre-flight and conflict handling, safe_pull()
parses the git stderr output and raises a typed exception from the
GitError hierarchy (defined in errors.py):
Exception |
Condition |
|---|---|
|
Host resolution failure, connection timeout, or refused connection. |
|
Authentication failure or HTTP 403. Student should re-run |
|
Corrupt objects or bad refs in the local repo. |
|
Any other non-zero exit. Git output is echoed in dim style above the friendly message. |
These exceptions all inherit from MWCError, so the caller in
update/__init__.py can catch them by type and display a beginner-friendly
message without a Python traceback.
How mwc submit wraps git
mwc submit (in submit.py) is a wrapper around
git add --all && git commit && git push. Running it from anywhere
inside a module directory commits all changes and pushes to the MWC git
server.
Pre-flight
The same git_wrapper helpers used by safe_pull() run before
any git operations: in-progress operations are aborted, detached HEAD
is recovered, and the index lock is cleared.
Unpushed commits (S1)
repo_has_changes() checks git status --porcelain, which only
reflects the working tree and index. If a previous mwc submit run
committed successfully but the push failed (for example, due to a network
dropout), the working tree appears clean while commits are stranded locally.
has_unpushed_commits() catches this by running git log @{u}..HEAD.
When it detects stranded commits, the submit skips straight to the push step.
Empty commit message (S2)
git commit opens the configured editor. If the student closes it
without writing anything, git exits non-zero with "Aborting commit due
to empty commit message" in its output. The submit detects this,
unstages all files with git restore --staged ., and prompts the
student to try again.
Push rejection (S3)
If git push is rejected as non-fast-forward, it means the teacher
pushed to the student’s repo (typically feedback or corrected starter
files) since the student last pulled. The submit auto-recovers by
running git pull -X ours (student’s committed code wins in any
conflict, since this is a submit operation) and retrying the push.
Network and authentication errors (S4–S5)
If push fails due to a network error, the student is told explicitly
that their commit is safe locally and that re-running mwc submit
will send it when connectivity is restored. Authentication failures
direct the student to re-run mwc setup.
Error handling design
Two design constraints shape the error handling throughout:
Student work is never silently lost. Any operation that modifies the working tree (auto-commit before pull, stash during detached-HEAD recovery) either commits the work into git history or restores it afterward.
Friendly messages, raw output as secondary. When a git operation fails, the raw git stderr is echoed in dim style (using the
debugformatter fromstyles.py) before the friendly message, so a teacher who is helping a student can still see what git actually said.
Key source files
File |
Purpose |
|---|---|
|
Stateless helpers for inspecting and repairing repo state.
Used by both |
|
|
|
Abstract base class. |
|
|
|
|
|
|
|
Low-level Gitea API client used by teacher-side commands
( |