diff --git a/README.md b/README.md index a2a898d..f8b1dc2 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,73 @@ -# developers +# Developer Documentation + Developer documentation about all things Git-Mastery + +This repository contains the source for the Git-Mastery developer docs site, built with Jekyll and the Just the Docs theme. + +## Prerequisites + +Install the following tools first: + +- Ruby 3.2.2 + + | Note: the installation commands below are for macOS using Homebrew only. Adjust as needed for your OS and package manager. + + ```bash + brew install rbenv ruby-build + rbenv install 3.2.2 + echo 'eval "$(rbenv init - zsh)"' >> ~/.zshrc + source ~/.zshrc + rbenv global 3.2.2 + ruby -v + ``` + +- Bundler + + ```bash + gem install bundler + ``` + +## Run locally + +From the repository root: + +1. Install dependencies: + + ```bash + bundle install + ``` + +2. Start the local docs server: + + ```bash + bundle exec jekyll serve --livereload + ``` + +3. Open the site at: + + ```text + http://127.0.0.1:4000/developers/ + ``` + +Note: this repository uses `baseurl: "/developers"`, so the local path includes `/developers/`. + +## Build for production + +To generate a static build in `_site/`: + +```bash +bundle exec jekyll build +``` + +## Authoring notes + +- Add or edit docs in `docs/`. +- Use frontmatter fields like `title`, `parent`, and `nav_order` so pages appear correctly in navigation. +- Keep links and examples consistent with the current Git-Mastery workflows. + +## Troubleshooting + +- `bundle: command not found`: install Bundler with `gem install bundler`. +- Shell still reports wrong Ruby version: run `rbenv version` to confirm 3.2.2 is active; if not, run `rbenv global 3.2.2` and restart the terminal. +- Port `4000` already in use: run `bundle exec jekyll serve --port 4001`. +- Styling or content not updating: restart `jekyll serve` and hard refresh your browser. diff --git a/docs/app/command.md b/docs/app/command.md new file mode 100644 index 0000000..09677aa --- /dev/null +++ b/docs/app/command.md @@ -0,0 +1,216 @@ +--- +title: How to add a command +parent: App +nav_order: 1 +--- + +# How to add a command + +This guide walks through adding a new top-level command to the `gitmastery` CLI. The CLI uses [Click](https://click.palletsprojects.com/), a Python library for building command-line interfaces. + +We'll use a simple `greet` command as an example throughout. + +{: .warning } + +> This is for demonstration purposes. The `greet` command is not a real feature and should not be merged into the codebase. When adding real commands, follow the same steps but implement the actual functionality needed. + +--- + +## 1. Create the command file + +Create a new file under `app/app/commands/`: + +``` +app/app/commands/greet.py +``` + +Every command is a Python function decorated with `@click.command()`. Use the shared output helpers from `app.utils.click` instead of `print()`. + +```python +# app/app/commands/greet.py +import click + +from app.utils.click import info, success + + +@click.command() +@click.argument("name") +def greet(name: str) -> None: + """ + Greets the user by name. + """ + info(f"Hello, {name}!") + success("Greeting complete.") +``` + +### Output helpers + +| Helper | When to use | +| -------------- | -------------------------------- | +| `info(msg)` | Normal status messages | +| `success(msg)` | Command completed successfully | +| `warn(msg)` | Non-fatal issues or warnings | +| `error(msg)` | Fatal issues — exits immediately | + +--- + +## 2. Register the command in `cli.py` + +Open `app/app/cli.py` and add two things: + +1. Import your command at the top. +2. Add it to the `commands` list in `start()`. + +```python +# app/app/cli.py + +# 1. Import +from app.commands.greet import greet # add this + +# 2. Register +def start() -> None: + commands = [check, download, greet, progress, setup, verify, version] # add greet + for command in commands: + cli.add_command(command) + cli(obj={}) +``` + +--- + +## 3. Verify it works locally + +Run the CLI directly from source to confirm the command appears and works: + +```bash +uv run python main.py greet Alice +``` + +Expected output: + +``` + INFO Hello, Alice! + SUCCESS Greeting complete. +``` + +Also check it shows in `--help`: + +```bash +uv run python main.py --help +``` + +--- + +## 4. Add an E2E test + +Every new command needs an E2E test. Create a new file under `tests/e2e/commands/`: + +``` +tests/e2e/commands/test_greet.py +``` + +```python +# tests/e2e/commands/test_greet.py +from pathlib import Path + +from ..runner import BinaryRunner + + +def test_greet(runner: BinaryRunner, gitmastery_root: Path) -> None: + """greet prints the expected message.""" + res = runner.run(["greet", "Alice"], cwd=gitmastery_root) + res.assert_success() + res.assert_stdout_contains("Hello, Alice!") +``` + +### `RunResult` assertion methods + +| Method | Description | +| --------------------------------- | ------------------------------------------ | +| `.assert_success()` | Asserts exit code is 0 | +| `.assert_stdout_contains(text)` | Asserts stdout contains an exact substring | +| `.assert_stdout_matches(pattern)` | Asserts stdout matches a regex pattern | + +--- + +## 5. Run the E2E tests + +Build the binary first, then run the suite: + +```bash +# Build +uv run pyinstaller --onefile main.py --name gitmastery + +# Set binary path (Unix) +export GITMASTERY_BINARY="$PWD/dist/gitmastery" + +# Set binary path (Windows, PowerShell) +$env:GITMASTERY_BINARY = "$PWD\dist\gitmastery.exe" + +# Run only your new test +uv run pytest tests/e2e/commands/test_greet.py -v + +# Run the full E2E suite +uv run pytest tests/e2e/ -v +``` + +All tests should pass before opening a pull request. + +--- + +## Command group (optional) + +If you want to add a command that has subcommands (like `gitmastery progress show`), use `@click.group()` and register subcommands with `.add_command()`. + +```python +# app/app/commands/greet.py +import click + +from app.utils.click import info + + +@click.group() +def greet() -> None: + """Greet people in different ways.""" + pass + + +@click.command() +@click.argument("name") +def hello(name: str) -> None: + """Say hello.""" + info(f"Hello, {name}!") + + +@click.command() +@click.argument("name") +def goodbye(name: str) -> None: + """Say goodbye.""" + info(f"Goodbye, {name}!") + + +greet.add_command(hello) +greet.add_command(goodbye) +``` + +Register `greet` the same way in `cli.py`. The user then runs: + +```bash +gitmastery greet hello Alice +gitmastery greet goodbye Alice +``` + +--- + +## Checklist + +Before opening a pull request for a new command: + +- [ ] Command file created under `app/app/commands/` +- [ ] Command registered in `app/app/cli.py` +- [ ] Command works locally with `uv run python main.py` +- [ ] E2E test file created under `tests/e2e/commands/` +- [ ] E2E tests pass with the built binary + +{: .reference } + +See [E2E testing flow](/developers/docs/app/e2e-testing-flow) for a full explanation of the test infrastructure and how to write more complex tests. diff --git a/docs/app/configuration.md b/docs/app/configuration.md new file mode 100644 index 0000000..018db8c --- /dev/null +++ b/docs/app/configuration.md @@ -0,0 +1,92 @@ +--- +title: Configuration reference +parent: App +nav_order: 2 +--- + +# Configuration reference + +The app uses two JSON files to manage state: one for the Git-Mastery workspace and one per downloaded exercise. + +## `.gitmastery.json` + +Created by `gitmastery setup` in the Git-Mastery root directory. + +| Field | Type | Description | +| ------------------ | -------- | ------------------------------------------- | +| `progress_local` | `bool` | Whether local progress tracking is enabled | +| `progress_remote` | `bool` | Whether progress is synced to a GitHub fork | +| `exercises_source` | `object` | Where the app fetches exercises from | + +### `exercises_source` + +Two source types are supported: + +**Remote** (default): + +```json +{ + "type": "remote", + "username": "git-mastery", + "repository": "exercises", + "branch": "main" +} +``` + +**Local** (for co-developing `app` and `exercises`): + +```json +{ + "type": "local", + "repo_path": "/absolute/path/to/exercises" +} +``` + +With `type: local`, the app copies the local exercises directory into a temp directory at runtime. This means changes you make in your local `exercises/` clone are picked up immediately without needing to push to GitHub — useful when developing `app` and `exercises` together. + +To point at a fork or a feature branch, change `username` or `branch` in the remote config. + +--- + +## `.gitmastery-exercise.json` + +Created per exercise by `gitmastery download`. Lives in the exercise root. + +| Field | Type | Description | +| ----------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------- | +| `exercise_name` | `string` | Exercise identifier used for progress tracking | +| `tags` | `string[]` | Used for indexing on the exercise directory | +| `requires_git` | `bool` | If true, app checks Git installation and `user.name`/`user.email` before download | +| `requires_github` | `bool` | If true, app checks GitHub CLI authentication before download | +| `base_files` | `object` | Map of destination filename to source path in `res/`; these files are copied to the exercise root alongside `README.md` | +| `exercise_repo` | `object` | Controls what working repository the student receives | +| `downloaded_at` | `string` | ISO timestamp of when the exercise was downloaded | + +### `exercise_repo` + +| Field | Type | Description | +| ------------------- | -------- | ------------------------------------------------------------ | +| `repo_type` | `string` | One of `local`, `remote`, `ignore`, `local-ignore` | +| `repo_name` | `string` | Name of the subfolder created for the student | +| `init` | `bool` | Whether to run `git init` (used with `local`) | +| `create_fork` | `bool` | Whether to fork the remote repo to the student's account | +| `fork_all_branches` | `bool` | Whether all branches are included in the fork (remote only) | +| `repo_title` | `string` | Name of the GitHub repository to clone or fork (remote only) | + +### `repo_type` values + +| Value | Behaviour | +| -------------- | --------------------------------------------------------------- | +| `local` | Creates a local folder, optionally runs `git init` | +| `remote` | Clones or fork-and-clones a GitHub repository | +| `ignore` | No repository created; `exercise.repo` is a null wrapper | +| `local-ignore` | Creates a folder without `git init`; student runs it themselves | + +## How commands use these files + +| Command | Reads | Writes | +| ---------- | --------------------------- | ----------------------------------- | +| `setup` | — | `.gitmastery.json` | +| `download` | `.gitmastery.json` | `.gitmastery-exercise.json` | +| `verify` | `.gitmastery-exercise.json` | `progress/progress.json` | +| `progress` | `.gitmastery.json` | `.gitmastery.json` (on sync toggle) | diff --git a/docs/app/e2e-testing-flow.md b/docs/app/e2e-testing-flow.md new file mode 100644 index 0000000..3b5b8fc --- /dev/null +++ b/docs/app/e2e-testing-flow.md @@ -0,0 +1,146 @@ +--- +title: E2E testing flow +parent: App +nav_order: 4 +--- + +# E2E testing flow + +The `app` repository has an end-to-end test suite that runs the compiled `gitmastery` binary against real CLI commands. Tests assert on exit codes, stdout content, and side-effects such as file creation. + +## Why E2E tests + +Unit tests cover individual functions. E2E tests cover the full user-facing CLI path: argument parsing, command dispatch, file I/O, and cross-platform packaging behavior. Regressions caught only at this level include platform-specific path handling, binary encoding, and REPL dispatch. + +## Test structure + +``` +tests/e2e/ +├── conftest.py # shared pytest fixtures +├── constants.py # constants for test configuration +├── runner.py # executes the built binary and returns RunResult +├── result.py # helpers for assertions on exit code and stdout +├── utils.py # helper utility functions +└── commands/ # tests for commands + └── test_*.py +``` + +## Key components + +### `BinaryRunner` + +`BinaryRunner` wraps `subprocess.run` against the built binary. It is instantiated once per test session from the `GITMASTERY_BINARY` environment variable, falling back to `dist/gitmastery` (or `dist/gitmastery.exe` on Windows) if the variable is not set. + +```python +runner.run(["check", "git"]) +runner.run(["setup"], cwd=work_dir, stdin_text="\n") +runner.run(["progress", "sync", "off"], cwd=gitmastery_root, stdin_text="y\n") +``` + +### `RunResult` + +Every `runner.run(...)` call returns a `RunResult` with chainable assertion helpers: + +| Method | Description | +| --------------------------------- | ----------------------------------------- | +| `.assert_success()` | Assert exit code is 0 | +| `.assert_stdout_contains(text)` | Assert stdout contains an exact substring | +| `.assert_stdout_matches(pattern)` | Assert stdout matches a regex pattern | + +```python +res = runner.run(["version"]) +res.assert_success() +res.assert_stdout_contains("Git-Mastery app is") +res.assert_stdout_matches(r"v\d+\.\d+\.\d+") +``` + +### Fixtures in `conftest.py` + +| Fixture | Scope | Description | +| ------------------------- | -------- | ---------------------------------------------------------------- | +| `runner` | session | Single `BinaryRunner` shared across all tests | +| `gitmastery_root` | session | Runs `gitmastery setup`, yields the root path, cleans up on exit | +| `setup_gitmastery_root` | function | Same as above but for `test_setup.py` only | +| `downloaded_exercise_dir` | session | Downloads exercise once, returns its path | +| `downloaded_hands_on_dir` | session | Downloads hands-on once, returns its path | +| `verified_exercise_dir` | session | Runs `verify` on the downloaded exercise, returns its path | + +Session-scoped fixtures run once for the entire test run. This keeps the suite fast by reusing the same workspace across related tests. + +## Running E2E tests locally + +The tests run against the built binary, not the Python source directly. You must build first: + +```bash +# Build the binary +uv run pyinstaller --onefile main.py --name gitmastery + +# Point tests at the binary (Unix) +export GITMASTERY_BINARY="$PWD/dist/gitmastery" + +# Point tests at the binary (Windows, PowerShell) +$env:GITMASTERY_BINARY = "$PWD\dist\gitmastery.exe" + +# Run the suite +uv run pytest tests/e2e/ -v +``` + +{: .note } +E2E tests interact with GitHub (via `GH_TOKEN`) to download exercises and test progress sync. Ensure `gh auth status` succeeds and the `delete_repo` scope is granted before running locally. + +```bash +gh auth refresh -s delete_repo +``` + +## CI workflows + +Two GitHub Actions workflows run the E2E suite: + +### `test.yml` — runs on every push to `main` + +Steps: + +1. Validate `GH_PAT` is present +2. Check out source +3. Install dependencies with `uv sync` +4. Build binary with `uv run pyinstaller --onefile main.py --name gitmastery` +5. Set `GITMASTERY_BINARY` path (platform-aware) +6. Configure Git user (`github-actions[bot]`) +7. Run `uv run pytest tests/e2e/ -v` with `GH_TOKEN=${{ secrets.GH_PAT }}` + +### `test-pr.yml` — runs on pull requests + +This workflow uses `pull_request_target` so it can access repository secrets. The `e2e-test` environment gate means a maintainer must approve the workflow run before secrets are available. This prevents untrusted PRs from accessing secrets while still allowing E2E tests to run in PRs from forks. + +{: .note } + +> Maintainers must do due diligence on PRs before approving the workflow run to ensure they do not contain malicious code that could access secrets. Look for unexpected changes to test files or the addition of new test files that could exfiltrate secrets. If in doubt, request changes and ask the contributor to provide evidence of what the test is doing and why it needs secrets access. + +Steps are identical to `test.yml` except for the checkout step. + +## When to add or update E2E tests + +| Change | Action | +| ---------------------- | --------------------------------------------------- | +| New top-level command | Add `tests/e2e/commands/test_.py` | +| New subcommand or flag | Add a test case in the relevant test file | +| Changed stdout message | Update `assert_stdout_contains` strings | +| Changed exit behavior | Update `assert_success()` or add failure assertions | +| Changed REPL behavior | Update `test_repl.py` | + +Prefer one test per distinct behavior. Keep assertions focused on user-visible output rather than internal state where possible. + +## Writing a new E2E test + +```python +from ..runner import BinaryRunner + + +def test_my_command(runner: BinaryRunner, gitmastery_root: Path) -> None: + """my-command does X.""" + res = runner.run(["my-command"], cwd=gitmastery_root) + res.assert_success() + res.assert_stdout_contains("Expected output text") +``` + +Use the `gitmastery_root` fixture for commands that require a setup workspace. Use a fresh `setup_gitmastery_root` fixture only when the test must inspect the workspace in its initial pristine state. diff --git a/docs/app/index.md b/docs/app/index.md new file mode 100644 index 0000000..867439d --- /dev/null +++ b/docs/app/index.md @@ -0,0 +1,33 @@ +--- +title: App +nav_order: 5 +has_children: true +has_toc: false +--- + +# App + +The [`app`](https://github.com/git-mastery/app) repository contains the `gitmastery` CLI. + +## Main responsibilities + +- Set up a local Git-Mastery workspace +- Fetch hands-ons and exercises from the configured exercises source +- Execute verification logic from the `exercises` repository +- Maintain local and remote progress tracking + +## Suggested reading + +1. [CLI command reference](/developers/docs/app/command-reference) +2. [Configuration reference](/developers/docs/app/configuration) +3. [Download and verification flow](/developers/docs/app/download-and-verify-flow) +4. [Progress tracking](/developers/docs/app/progress-integration) +5. [E2E testing flow](/developers/docs/app/e2e-testing-flow) +6. [Publishing flow](/developers/docs/app/publishing-flow) +7. [How to add a command](/developers/docs/app/how-to-add-a-command) + +## Testing expectations + +`app` has an end-to-end test suite under `app/tests/e2e` that exercises the built CLI across setup, check, download, verify, progress, version, and REPL flows. + +When you add a new command or change command behavior, update or add E2E tests for the user-visible behavior. See [E2E testing flow](/developers/docs/app/e2e-testing-flow) for a full guide. diff --git a/docs/app/progress-integration.md b/docs/app/progress-integration.md new file mode 100644 index 0000000..4b16dee --- /dev/null +++ b/docs/app/progress-integration.md @@ -0,0 +1,65 @@ +--- +title: Progress tracking +parent: App +nav_order: 3 +--- + +# Progress tracking + +The app records exercise progress locally after every `verify` run, and can optionally sync it to a GitHub-hosted progress repository. + +## Local progress + +`gitmastery setup` creates a `progress/` folder inside the Git-Mastery root with an empty `progress.json`. + +After each `gitmastery verify`, the app appends a new entry: + +```json +{ + "exercise_name": "branch-bender", + "started_at": 1700000000.0, + "completed_at": 1700000010.0, + "comments": ["Great work!"], + "status": "Completed" +} +``` + +The `status` field maps from `GitAutograderStatus`: + +| `GitAutograderStatus` | Written as | +| --------------------- | -------------- | +| `SUCCESSFUL` | `"Completed"` | +| `UNSUCCESSFUL` | `"Incomplete"` | +| `ERROR` | `"Error"` | + +{: .note } +If the exercise already has a `Completed` entry, subsequent attempts are not recorded. + +`gitmastery progress show` displays the latest entry per exercise. + +## Remote sync + +```mermaid +flowchart TD + A(["gitmastery progress sync on"]) --> B["Check Git and GitHub CLI"] + B --> C["Fork git-mastery/progress"] + C --> D["Clone fork into progress/"] + D --> E["Merge local and remote entries"] + E --> F["Push to fork"] + F --> G{"PR exists?"} + G -- No --> H["Open PR to git-mastery/progress"] + G -- Yes --> I(["Done"]) + H --> I +``` + +Once sync is on, every subsequent `verify` run pushes the new progress entry to the fork. `progress reset` also removes the exercise entry from the fork. + +`gitmastery progress sync off` removes the fork from GitHub, switches `.gitmastery.json` back to local-only mode, and recreates a plain local `progress/` folder preserving existing entries. + +## Reset + +`gitmastery progress reset` removes the current exercise's entries from `progress.json` and recreates the exercise workspace. + +{: .reference } + +See [Download and verification flow](/developers/docs/app/download-and-verify-flow) for how progress is updated during the verify step. diff --git a/docs/app/publishing-flow.md b/docs/app/publishing-flow.md new file mode 100644 index 0000000..e58b25b --- /dev/null +++ b/docs/app/publishing-flow.md @@ -0,0 +1,117 @@ +--- +title: Publishing flow +parent: App +nav_order: 5 +--- + +# Publishing flow + +Releases are fully automated. Merging a labelled PR to `main` triggers a version bump, which in turn triggers a multi-platform build and publish. + +## How to trigger a release + +Label the PR with one of: + +| Label | Effect | +| ------------ | -------------------------------------------------- | +| `bump:major` | Bumps the major version (e.g. `v1.2.3` → `v2.0.0`) | +| `bump:minor` | Bumps the minor version (e.g. `v1.2.3` → `v1.3.0`) | +| `bump:patch` | Bumps the patch version (e.g. `v1.2.3` → `v1.2.4`) | + +When the labelled PR is merged to `main`, the pipeline runs automatically. + +## Pipeline overview + +```mermaid +flowchart TD + A([PR merged to main with bump label]) --> B[bump-version.yml] + B --> C[Shared action creates version tag] + C --> D[publish.yml triggered] + D --> E["prepare job - get latest tag"] + E --> F{should_publish?} + F -- No --> G([Stop]) + F -- Yes --> H[Build jobs run in parallel] + + H --> H1["linux-build
amd64 + arm64"] + H --> H2["arch-build
amd64"] + H --> H3["windows
amd64"] + H --> H4["macos-build
amd64 + arm64"] + + H1 --> I1[debian-build] + I1 --> I2[debian-publish-apt] + + H2 --> J1["arch-publish
AUR"] + + H3 --> K1[winget-publish] + + H4 --> L1["macos-publish
Homebrew tap"] + L1 --> L2["macos-test
Smoke test via brew install"] +``` + +## Workflow files + +| File | Purpose | +| ------------------------------------ | --------------------------------------------------------------------------------------------- | +| `.github/workflows/bump-version.yml` | Listens for closed PRs on `main`, delegates to the shared `git-mastery/actions` bump workflow | +| `.github/workflows/publish.yml` | Triggered after the bump tag is created; builds and publishes all platform targets | + +## Build and publish targets + +### Linux (amd64 and arm64) + +1. `linux-build`: builds the binary with PyInstaller on `ubuntu-latest` and `ubuntu-24.04-arm`, writes the version into `app/version.py`, and uploads both binaries to the GitHub Release. +2. `debian-build`: downloads the Linux binary artifact, packages it as a `.deb` file using `dpkg-buildpackage`, and uploads the `.deb` to the GitHub Release. +3. `debian-publish-apt`: pushes the `.deb` to the `gitmastery-apt-repo` via a reusable workflow so it is available via APT. + +### Arch Linux (amd64) + +1. `arch-build`: builds the binary inside an `archlinux:base-devel` Docker container, uploads it to the GitHub Release. +2. `arch-publish`: clones the AUR package repository via SSH, updates `PKGBUILD` and `.SRCINFO`, and force-pushes to publish the new version on the AUR. + +### Windows (amd64) + +1. `windows`: builds `gitmastery.exe` on `windows-latest`, uploads it to the GitHub Release. +2. `winget-publish`: submits the new version to WinGet via the `vedantmgoyal9/winget-releaser` action. + +### macOS (amd64 and arm64) + +1. `macos-build`: builds architecture-specific binaries (`gitmastery-amd64`, `gitmastery-arm64`) on `macos-15-intel` and `macos-latest`, computes SHA256 checksums, and uploads both to the GitHub Release. +2. `macos-publish`: clones the `git-mastery/homebrew-gitmastery` tap repository, writes a new `gitmastery.rb` formula with the correct download URLs and checksums, and pushes to the tap. +3. `macos-test`: runs a smoke test on both macOS architectures by installing via `brew tap git-mastery/gitmastery && brew install gitmastery && gitmastery --help`. + +## How the version is embedded + +During every build job, the version is written directly into `app/version.py` before PyInstaller runs: + +```bash +# Unix +echo "__version__ = \"$REF_NAME\"" > app/version.py + +# Windows (PowerShell) +'__version__ = "{0}"' -f $env:REF_NAME | Out-File app/version.py -Encoding utf8 +``` + +This means the version in the binary always matches the release tag and does not rely on runtime package metadata. + +## The `prepare` gate + +The `publish.yml` workflow starts with a `prepare` job that calls the shared `get-latest-tag` workflow. This job outputs `should_publish` and `ref_name`. All downstream build jobs check `if: needs.prepare.outputs.should_publish == 'true'` before running. + +This gate prevents accidental re-runs of publish jobs when the workflow is triggered manually (`workflow_dispatch`) without a new tag. + +## Secrets and environments + +| Secret | Used by | +| ----------------- | ------------------------------------------------------------- | +| `GITHUB_TOKEN` | Creating GitHub Releases (automatic, no configuration needed) | +| `ORG_PAT` | Updating the Homebrew tap and WinGet submission | +| `SSH_PRIVATE_KEY` | Pushing to the AUR repository | + +The `arch-publish` job runs in the `Main` environment, which gates secret access. + +## Contributor expectations + +- **Do not modify `app/version.py` manually** — it is overwritten during every release build. +- **Do not change publishing logic without review** — changes to `publish.yml` affect all distribution channels simultaneously. +- **Label PRs correctly** — missing or wrong bump labels mean no release is triggered after merge. +- **Packaging changes** (Debian control files, AUR PKGBUILD, Homebrew formula) should be tested locally where possible and reviewed carefully before merging. diff --git a/docs/architecture/deployment-pipeline.md b/docs/architecture/deployment-pipeline.md deleted file mode 100644 index 703661c..0000000 --- a/docs/architecture/deployment-pipeline.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -title: Deployment Pipeline -parent: Architecture -nav_order: 5 ---- - -## Exercises - -Unit tests and [exercise configuration](/developers/docs/architecture/exercise-structure/#configuration-structure) will be run during the PR and every time a branch is merged into `main`. - -These are controlled through the `CI` action on Github Actions ([here](https://github.com/git-mastery/exercises/actions/workflows/ci.yml)). - -## Git-Mastery app - -A new version of the Git-Mastery application is published when a new tag is pushed to `main`. - -The versioning is done as follows: `..` where - -1. ``: for when major changes to the app occur (i.e. rewrites) where the application could break entirely -2. ``: for when critical bug fixes occur that users should be aware of -3. ``: for when minor bug fixes occur (i.e. cosmetic) that users don't need to update to - -The app automatically checks for the latest version published and warns users if their local version is out of date if the `` or `` versions are wrong. - -If you do not have permissions to push a new tag, contact for assistance. - -{: .note } - -For more information for packaging for Linux, refer to [this Notion page](https://woojiahao.notion.site/linux-packaging-226f881eda0580d68bc8dc6f8e1d5d0d?source=copy_link). diff --git a/docs/architecture/download-workflow.md b/docs/architecture/download-workflow.md deleted file mode 100644 index a6993c7..0000000 --- a/docs/architecture/download-workflow.md +++ /dev/null @@ -1,102 +0,0 @@ ---- -title: Download Workflow -parent: Architecture -nav_order: 3 ---- - -## General download sequence - -Both hands-on and exercises are downloaded using the same `gitmastery download` command. - -Hands-on will have the `hp-` prefix before the name of the hands-on. - -```mermaid -flowchart -a[gitmastery download] --> b[Check if prefixed with hp-] -b -- prefixed with hp- --> c[Download hands-on] -b -- else --> d[Download exercise] -``` - -Refer to the following sections for the specific download sequences. - -## Hands-on download - -These are handled within the app's `download.py` command, through the `download_hands_on` function. - -```mermaid -flowchart -a[Download hands-on] --> b[Check that hands-on exists in the hands_on/ folder] -b -- does not exist --> c[Error and stop the download] -b -- else --> d[Check if hands-on already exists] -d -- yes --> e[Delete hands-on and re-download] -e --> f["Create hands-on folder (hp-)"] -d -- no --> f -f --> g[Change directory to hands-on directory] -g --> h[Load hands-on file as namespace to extract variables and functions] -h --> i[Check Git if toggled] -i --> j[Check Github if toggled] -j --> k[Execute the download function in the hands-on file] -``` - -## Exercise download - -These are handled within the app's `download.py` command, through the `download_exercise` function. - -```mermaid -flowchart -A[Check if exercise exists] --> B{Exercise exists?} -B -- No --> C[Error and stop the download] -B -- Yes --> D[Check if exercise folder already exists] -D -- Yes --> E[Delete existing folder] -D -- No --> F[Create exercise folder] -E --> F -F --> G[Change directory to exercise folder] -G --> H["Download base files (.gitmastery-exercise.json, README.md)"] -H --> I[Read config file] - -I --> J{Requires Git?} -J -- Yes --> K[Check Git setup] -K -- Not setup --> L[Rollback, remove folder, stop download] -J -- No --> M[Proceed] - -M --> N{Requires Github?} -N -- Yes --> O[Check Github setup] -O -- Not setup --> P[Rollback, remove folder, stop download] -N -- No --> Q[Proceed] - -Q --> R{Config has base files?} -R -- Yes --> S[Download additional resources] -R -- No --> T[Skip resource download] - -S --> U{"Repo type != 'ignore'?"} -T --> U -U -- Yes --> V[Setup exercise folder] -U -- No --> Z1[Save config with timestamp
Print cd into exercise folder] - -%% setup_exercise_folder details -V --> V1[Save metadata to .gitmastery-exercise.json] -V1 --> V2{Repo type: local or remote?} -V2 -- Local --> V3[Create local folder] -V2 -- Remote --> V4[Retrieve from GitHub] -V4 --> V5{Fork required?} -V5 -- Yes --> V6[Check/delete existing fork
Create new fork
Clone fork] -V5 -- No --> V7[Clone repository] -V3 --> V8[cd into repo folder] -V6 --> V8 -V7 --> V8 -V8 --> V9[Fetch resources via download.py] -V9 --> V10{Resources exist?} -V10 -- Yes --> V11[Download and save resources] -V10 -- No --> V12[Skip resource download] -V11 --> V13{Repo init enabled?} -V12 --> V13 -V13 -- Yes --> V14[Initialize repo
Commit initial state] -V13 -- No --> V15[Skip repo init] -V14 --> V16["Execute setup() from download.py"] -V15 --> V16 -V16 --> V17[Print cd into exercise repo folder] - -Z1 --> Z2[Download complete] -V17 --> Z2 -``` - diff --git a/docs/architecture/exercise-structure.md b/docs/architecture/exercise-structure.md deleted file mode 100644 index db5080b..0000000 --- a/docs/architecture/exercise-structure.md +++ /dev/null @@ -1,98 +0,0 @@ ---- -title: Exercise structure -parent: Architecture -nav_order: 2 ---- - -Exercises follow a fixed structure for the Git-Mastery app to pick up on: - -```text - -├── .gitmastery-exercise.json -├── README.md -├── __init__.py -├── download.py -├── res -│ └── ... -├── tests -│ ├── specs -│ │ └── base.yml -│ └── test_verify.py -└── verify.py -``` - -- `.gitmastery-exercise.json`: contains the exercise configuration -- `README.md`: contains the instructions for the exercise for the students to attempt -- `download.py`: contains the download instructions to setup the student's exercise -- `verify.py`: contains the verification script for the exercise attempt -- `res/`: contains resources that are available to students (see this section about [types of resources](#types-of-resources)) -- `tests/specs/`: contains specification files written using [`repo-smith`](https://github.com/git-mastery/git-autograder) -- `tests/test_verify.py`: contains unit tests for verification script - -## What students see - -When a student downloads an exercise, they will see the following folder structure: - -```text - -├── .gitmastery-exercise.json -├── README.md -└── - ├── .git - └── ... -``` - -The root of the exercise will contain the `README.md` and `.gitmastery-exercise.json` configured from your template. - -It also contains the sub-folder configured in `.gitmastery-exercise.json`, which is where students will attempt the exercise. - -## Configuration structure - -`.gitmastery-exercise.json` is used to tell the [Git-Mastery app](https://git-mastery.github.io/app) how to setup the student's exercise. - -{: .note } - -We opted to use a standardized configuration for exercises because they often follow a certain shape for being setup so it is easier if the application -standardizes the setup via the exercise configuration. - -The `new.sh` script should have already generated one for you, but you may change your mind with the configuration and modify the file directly: - -- `exercise_name`: raw exercise name that will be indexed; recommended to use [kebab case](https://developer.mozilla.org/en-US/docs/Glossary/Kebab_case) -- `tags`: used during indexing on the [exercise directory](https://git-mastery.github.io/exercises) -- `requires_git`: performs a check to ensure that Git is installed and the user has already configured their `user.name` and `user.email` -- `requires_github`: performs a check to ensure that Github CLI is installed and the user has already authenticated themselves -- `base_files`: specifies the files from `res/` to be downloaded into the exercise root; typically used for the `answers.txt` (more about grading types [here]()) -- `exercise_repo`: controls the sub-folder that is generated; this is where students work on the exercise - - `repo_type`: `local` or `remote`; if `remote`, then the sub-folder is generated from a remote repository - - `repo_name`: name of the sub-folder; required for both `repo_type` - - `init`: determines if `git init` is run for the sub-folder; required only for `local` - - `create_fork`: determines if a fork is created on the user's Github account; required only for `remote` - - `repo_title`: name of the remote repository to fork + clone; required only for `remote` - -## Exercise resource types - -There are two distinct types of resources: - -1. Base files: configured through the `base_files` property in `.gitmastery-exercise.json` in your template; files located in `res/` are downloaded to the root of the exercise folder - - ```text - - ├── .gitmastery-exercise.json - ├── README.md - ├── <-- here - └── - ├── .git - └── ... - ``` - -2. Resources: configured through the `__resources__` field in `download.py`; supporting files for the exercise with files located in `res/` downloaded into the sub folder - - ```text - - ├── .gitmastery-exercise.json - ├── README.md - ├── - └── - ├── .git - └── <-- here - ``` diff --git a/docs/architecture/hands-on-structure.md b/docs/architecture/hands-on-structure.md deleted file mode 100644 index 0d0493d..0000000 --- a/docs/architecture/hands-on-structure.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -title: Hands-on Structure -parent: Architecture -nav_order: 1 ---- - -All hands-ons are stored within the `hands_on` folder of the [`exercises`](https://github.com/git-mastery/exercises). - -They are represented by a single Python file, whose name is the hands-on ID (replacing `_` with `-`). - -Hands-ons are not graded and the progress is not tracked. They are only present to help setup the student's folder structure for a given lesson! - -{: .note } - -Git-Mastery uses the format `hp-` for hands-on names (e.g., `hp-init-repo`), to differentiate them from exercise names. Internally, we use a `hands_on/` folder (instead of the `hp-` prefix) to differentiate hands-on files from exercise files (e.g., `hands_on/init_repo.py`). - -## File structure - -Since the hands-on is only comprised of a single file, there isn't a lot of complexity to it. Given below is an example: - -```python -import os - -from exercise_utils.cli import run_command -from exercise_utils.file import append_to_file, create_or_update_file -from exercise_utils.git import add, init - -__requires_git__ = True -__requires_github__ = False - - -def download(verbose: bool): - os.makedirs("things") - os.chdir("things") - init(verbose) - create_or_update_file( - "fruits.txt", - """ - apples - bananas - cherries - """, - ) - add(["fruits.txt"], verbose) - run_command(["git", "add", "fruits.txt"], verbose) - append_to_file("fruits.txt", "dragon fruits") - -``` - -The setup instructions of the hands-on go under the `download` function. - -`__requires_git__` and `__requires_github__` tells the Git-Mastery app whether to run automatic verification that the student has already setup Git and/or Github CLI correctly! diff --git a/docs/architecture/index.md b/docs/architecture/index.md deleted file mode 100644 index bbe47f0..0000000 --- a/docs/architecture/index.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Architecture -nav_order: 3 ---- - -These are the various components of the Git-Mastery ecosystem! diff --git a/docs/architecture/verification-workflow.md b/docs/architecture/verification-workflow.md deleted file mode 100644 index 5b34fca..0000000 --- a/docs/architecture/verification-workflow.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: Verification Workflow -parent: Architecture -nav_order: 4 ---- - -```mermaid -flowchart -a[Verify exercise] --> b["Check if in exercise (using .gitmastery-exercise.json)"] -b -- not in exercise --> c[Cancel] -b -- in exercise --> d[Execute verification script on exercise folder] -``` diff --git a/docs/contributing/exercise.md b/docs/contributing/exercise.md deleted file mode 100644 index d6e1309..0000000 --- a/docs/contributing/exercise.md +++ /dev/null @@ -1,128 +0,0 @@ ---- -title: Exercise -parent: Contributing -nav_order: 3 ---- - -1. TOC -{:toc} - -## Before contributing - -If you are proposing a new exercise (i.e., not implementing an [already approved exercise proposal](https://github.com/git-mastery/exercises/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22exercise%20discussion%22%20label%3A%22help%20wanted%22)) make sure that you have done the following: - -- [ ] Create an [exercise discussion](https://github.com/git-mastery/exercises/issues/new?template=exercise_discussion.yaml) -- [ ] Obtain approval on the exercise -- [ ] File a [remote repository request](https://github.com/git-mastery/exercises/issues/new?template=request_exercise_repository.yaml) - -## Create a new exercise - -Use the provided `new.sh` script to generate the scaffolding for a new exercise: - -```bash -./new.sh -``` - -The script will first prompt if you want to create a hands-on or exercise: - -Enter `exercise` or `e` to create a new exercise. - -Then, the script will prompt you for: - -1. The name of the exercise -- likely to be specified in the corresponding exercise discussion (you should be using kebab case for the exercise name) -2. The exercise tags (split by space) -- likely to be specified in the corresponding exercise discussion -3. The exercise configuration (read the [exercise configuration](/developers/docs/architecture/exercise-structure#configuration-structure) section for more info on this) - -{: .reference } - -Refer to the [exercise structure document](/developers/docs/architecture/exercise-structure) for more information about the folder structure generated. - -## Download setup - -The `download.py` contains the instructions to setup the local repository. - -{: .reference } - -For more information about how Git-Mastery downloads exercises, refer to the [Download Workflow](/developers/docs/architecture/download-workflow) - -{: .note } - -> `exercises` comes with a set of utility functions in the `exercise_utils` module that are made available during the download flow. They provide simple wrappers around common functionality such as `exercise_utils.cli.run_command` to invoke any command and `exercise_utils.file.create_or_update_file` to create or update a given file. -> -> For the full list of utility functions, refer [here](/developers/docs/tooling/exercise-utils). - -These are some references for download setups for other exercises: - -- [ignoring-somethings](https://raw.githubusercontent.com/git-mastery/exercises/refs/heads/main/ignoring_somethings/download.py) -- [branch-bender](https://raw.githubusercontent.com/git-mastery/exercises/refs/heads/main/branch_bender/download.py) -- [amateur-detective](https://raw.githubusercontent.com/git-mastery/exercises/refs/heads/main/amateur_detective/download.py) - -### Download conventions - -1. Any operations should use OS agnostic options (e.g. opting to use `shutil.rmtree` over `run_command(["rm"])`) -2. If you need to compare the states before and after the student has started to add commits, use the given "start tag" as `git-autograder` is designed to read that if necessary -3. For any commands that require `gh` (Github CLI), make sure that the `requires_github` configuration is set to `true` so that the app will automatically check that the student has correctly setup Github CLI - -### Testing downloads - -To test that your download script works, we have provided a script to simulate the download process in this repository for you to verify. - -```bash -./test-download.sh -``` - -You can find the downloaded repository under the `test-downloads/` folder. - -## Verification setup - -The verification process is controlled by the `verify.py`. - -{: .reference } - -For more information about how Git-Mastery verifies exercise attempts, refer to the [Verification Workflow](/developers/docs/architecture/verification-workflow) - -The [`git-autograder`](https://github.com/git-mastery/git-autograder) is built as a wrapper around [`GitPython`](https://github.com/gitpython-developers/GitPython). As a result, if you are writing any verification scripts and there is no available helper function with `git-autograder`, you can fall back to the underlying `Repo` object: - -```python -def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: - # Access the underlying GitPython repo: - exercise.repo.repo - - return exercise.to_output([], GitAutograderStatus.SUCCESSFUL) -``` - -Refer to existing `verify.py` scripts to understand what are the available helper functions to streamline the grading. Open an issue if there is something that is not yet supported or if you have a question. - -Some examples of verifications: - -- [branch-bender](https://raw.githubusercontent.com/git-mastery/exercises/refs/heads/main/branch_bender/verify.py) -- [conflict-mediator](https://raw.githubusercontent.com/git-mastery/exercises/refs/heads/main/conflict_mediator/verify.py) - -### Verification conventions - -1. Store the comments of the verification as constants so that they can be imported and used reliably in unit tests -2. For any remote behavior to verify, provide a mock to substitute the behavior in the unit tests - -### Testing verification - -To test the verification, we rely on [`repo-smith`](https://github.com/git-mastery/repo-smith) to simulate exercise states and write unit tests to verify the verification script's behavior. You don't need to simulate the entire flow, just the end states that you require for your verification script. - -Refer to existing `test_verify.py` to see examples of unit testing the verification script. - -You can run the unit tests of your exercise via: - -```bash -./test.sh -``` - -## Submitting the exercise for review - -Create a pull request from your fork using the provided pull request template. - -Fill in all of the details necessary. - -## Example - -1. Exercise discussion: -2. Remote repository request: -3. Exercise PR: diff --git a/docs/contributing/hands-on.md b/docs/contributing/hands-on.md deleted file mode 100644 index 955706a..0000000 --- a/docs/contributing/hands-on.md +++ /dev/null @@ -1,93 +0,0 @@ ---- -title: Hands-on -parent: Contributing -nav_order: 2 ---- - -1. TOC -{:toc} - -## What is a hands-on? - -[Lessons accompanying Git-Mastery App](https://nus-cs2103-ay2526s1.github.io/website/se-book-adapted/git-trail/index.html) contains hands-on practicals for students to get 'hands-on' experience of the Git concepts covered by the lesson. Some of those hands-on practicals need to set up a 'sandbox' containing the required folders, files, repos before the student can do the practical. Git-Mastery app can set up that sandbox for students so that they can get to the 'practical' part more easily (we refer to this as 'downloading' the hands-on), without having to set up the sandbox manually. For example, a student can run `gitmastery download ` (e.g., `gitmastery download hp-init-repo`) to set up the sandbox for a specific hands-on practical. - -Some examples of how Git-Mastery app helps to create the sandbox for a hands-on practical can be seen in [this Git Tour](https://nus-cs2103-ay2526s1.github.io/website/book/gitAndGithub/trail/recordingFolderHistory/index.html) (see T1L3, T1L4). - -## Before contributing - -New contributors are recommended to start by implementing an [already approved hands-on proposal]((https://github.com/git-mastery/exercises/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22hands-on%20discussion%22%20label%3A%22help%20wanted%22)). - -If you are proposing a new hands-on instead, make sure that you have done the following: - -- [ ] Create an [hands-on discussion](https://github.com/git-mastery/exercises/issues/new?template=hands_on_discussion.yaml) -- [ ] Obtain approval on the hands-on -- [ ] File a [remote repository request](https://github.com/git-mastery/exercises/issues/new?template=request_exercise_repository.yaml) - -## Implementing a hands-on - -First, run the provided `new.sh` script to generate the scaffolding for a new hands-on sandbox: - -```bash -./new.sh -``` - -The script will first prompt if you want to create a hands-on or exercise: - -Enter `hands-on` or `h` to create a new hands-on. - -Then, the script will prompt you for: - -1. The name of the hands-on -- likely to be specified in the corresponding hands-on discussion (without the `hp-` prefix!) (you should be using kebab case for the hands-on name) -2. Does the hands-on require Git? -3. Does the hands-on require Github? - -{: .reference } - -Refer to the [hands-on structure document](/developers/docs/architecture/hands-on-structure) for more information about the folder structure generated. - - -Each hands-on is implemented as a single file `hands_on/.py`, containing the instructions to set up the hands-on sandbox . - -{: .reference } - -For more information about how Git-Mastery downloads a hands-on, refer to the [Download Workflow](/developers/docs/architecture/download-workflow) - -{: .note } - -> `exercises` repo comes with a set of utility functions in the `exercise_utils` module that are made available during the download flow. They provide simple wrappers around common functionality such as `exercise_utils.cli.run_command` to invoke any command and `exercise_utils.file.create_or_update_file` to create or update a given file. -> -> For the full list of utility functions, refer [here](/developers/docs/tooling/exercise-utils). - -These are some references for download setups for other exercises: - -- [init-repo](https://raw.githubusercontent.com/git-mastery/exercises/refs/heads/main/hands_on/init_repo.py) -- [add-files](https://raw.githubusercontent.com/git-mastery/exercises/refs/heads/main/hands_on/add_files.py) -- [stage-modified](https://raw.githubusercontent.com/git-mastery/exercises/refs/heads/main/hands_on/stage_modified.py) - -### Conventions - -1. Any operations should use OS-agnostic options (e.g. opting to use `shutil.rmtree` over `run_command(["rm"])`) -2. For any commands that require `gh` (GitHub CLI), make sure that the `__requires_github__` variable in the download setup is set to `True` so that the app will automatically check that the student has correctly setup Github CLI - -### Testing downloads - -To test that your download script works, we have provided a script to simulate the download process in this repository for you to verify. - -```bash -./test-download.sh -``` - -You can find the sandbox created by the script under the `test-downloads/` folder. Check it manually to verify it is as expected. - -## Submitting the hands-on for review - -Create a pull request from your fork using the provided pull request template. - -Fill in all the details necessary. - -## Example - -1. Hands-on discussion: -2. Remote repository request: -3. Hands-on PR: - diff --git a/docs/contributing/index.md b/docs/contributing/index.md deleted file mode 100644 index 4bf6f78..0000000 --- a/docs/contributing/index.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Contributing -nav_order: 2 ---- - -[![CI](https://github.com/git-mastery/exercises/actions/workflows/ci.yml/badge.svg)](https://github.com/git-mastery/exercises/actions/workflows/ci.yml) - diff --git a/docs/contributing/setup.md b/docs/contributing/setup.md deleted file mode 100644 index a224e59..0000000 --- a/docs/contributing/setup.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -title: Setup -parent: Contributing -nav_order: 1 ---- - -Both hands-on and exercises reside in the same repository: [`git-mastery/exercises`](https://github.com/git-mastery/exercises). So before you start to contributing, you should have these setup already: - -- Bash environment -- Python 3.13+ -- Github CLI [installed and authenticated](https://github.com/cli/cli#installation) for testing the download script -- uv [installed](https://docs.astral.sh/uv/getting-started/installation/) - -## Setup - -1. Fork the repository: -2. Clone the fork - - ```bash - git clone https://github.com//exercises - ``` - -3. Run the following command to set up virtual environment and install dependencies: - - ```bash - uv sync - ``` - -4. Set up pre-commit hooks (for linting, formatting and type checking) using LeftHook - - ```bash - uv run lefthook install - ``` diff --git a/docs/exercises/download-workflow.md b/docs/exercises/download-workflow.md new file mode 100644 index 0000000..73b5b14 --- /dev/null +++ b/docs/exercises/download-workflow.md @@ -0,0 +1,72 @@ +--- +title: Download flow +parent: Exercises +nav_order: 4 +--- + +# Download flow + +## General download sequence + +Both hands-ons and exercises are downloaded using the same `gitmastery download` command. + +Hands-ons use the `hp-` prefix before the hands-on name. + +```mermaid +flowchart +a[gitmastery download] --> a1[Read exercises_source from .gitmastery.json] +a1 --> b[Check if prefixed with hp-] +b -- prefixed with hp- --> c[Download hands-on] +b -- else --> d[Download exercise] +``` + +{: .note } + +> The source that exercises and hands-ons are fetched from is controlled by `exercises_source` in `.gitmastery.json`. See [`exercises_source`](/developers/docs/app/configuration#exercises_source) for the full details and override options. + +Refer to the following sections for the specific download sequences. + +## Hands-on download + +These are handled within the app's `download.py` command through the `download_hands_on` function. + +```mermaid +flowchart +a[Download hands-on] --> b[Check that hands-on exists in the hands_on/ folder] +b -- does not exist --> c[Error and stop the download] +b -- else --> d[Check if hands-on already exists] +d -- yes --> e[Delete hands-on and re-download] +e --> f[Create hands-on folder] +d -- no --> f +f --> g[Change directory to hands-on directory] +g --> h[Load hands-on file as namespace to extract variables and functions] +h --> i[Check Git if toggled] +i --> j[Check GitHub if toggled] +j --> k[Execute the download function in the hands-on file] +``` + +## Exercise download + +These are handled within the app's `download.py` command through the `download_exercise` function. + +```mermaid +flowchart TD +A[Check exercise exists] -->|Not found| ERR[Error and stop] +A -->|Found| B[Prepare exercise folder] +B --> C[Download base files and read config] +C --> D{Prerequisites met?\nGit / GitHub} +D -->|No| ROLL[Rollback and stop] +D -->|Yes| E[Download additional resources if any] +E --> F{Repo type managed?} +F -->|No| G[Save config and print next steps] +F -->|Yes| H[Save metadata to .gitmastery-exercise.json] +H --> I{Repo type} +I -->|local| J[Create local repo] +I -->|remote| K{Fork required?} +K -->|Yes| L[Fork and clone] +K -->|No| M[Clone repository] +J & L & M --> N[Run download.py setup] +N --> O[Print next steps] +G --> DONE[Download complete] +O --> DONE +``` diff --git a/docs/exercises/exercise-structure.md b/docs/exercises/exercise-structure.md new file mode 100644 index 0000000..b81856d --- /dev/null +++ b/docs/exercises/exercise-structure.md @@ -0,0 +1,108 @@ +--- +title: Exercise format reference +parent: Exercises +nav_order: 3 +--- + +# Exercise format reference + +Exercises follow a fixed structure for the Git-Mastery app to pick up on: + +```text + +├── .gitmastery-exercise.json +├── README.md +├── __init__.py +├── download.py +├── res +│ └── ... +├── test_verify.py +└── verify.py +``` + +- `.gitmastery-exercise.json`: contains the exercise configuration +- `README.md`: contains the instructions for students +- `download.py`: contains the download instructions to set up the student's exercise +- `verify.py`: contains the verification script for the exercise attempt +- `res/`: contains resources that are available to students +- `test_verify.py`: contains unit tests for the verification script, written using [`repo-smith`](https://github.com/git-mastery/repo-smith) + +## What students see + +When a student downloads a typical exercise, they will see the following folder structure: + +```text + +├── .gitmastery-exercise.json +├── README.md +└── + ├── .git + └── ... +``` + +The root of the exercise contains the configured `README.md` and `.gitmastery-exercise.json`. + +It also contains the subfolder configured in `.gitmastery-exercise.json`, which is where students attempt the exercise. + +Some exercises intentionally differ: + +- `repo_type: ignore`: no managed repository subfolder is created by the app; the student works directly in the exercise root. +- `repo_type: local-ignore`: the app creates the working folder but does not run `git init` for the student. + +## Configuration structure + +`.gitmastery-exercise.json` is used to tell the [Git-Mastery app](https://git-mastery.github.io/app) how to set up the student's exercise. + +{: .note } + +We opted to use a standardized configuration for exercises because they often follow a common setup shape. + +The `new.sh` script generates one for you, but you can modify the file directly: + +- `exercise_name`: raw exercise name that will be indexed; recommended to use [kebab case](https://developer.mozilla.org/en-US/docs/Glossary/Kebab_case) +- `tags`: specify topic related to exercise +- `requires_git`: performs a check to ensure that Git is installed and `user.name` and `user.email` are configured +- `requires_github`: performs a check to ensure that GitHub CLI is installed and the user has authenticated +- `base_files`: specifies the files from `res/` to be downloaded into the exercise root +- `exercise_repo`: controls the subfolder that is generated; this is where students work on the exercise + - `repo_type`: one of `local`, `remote`, `ignore`, or `local-ignore` + - `repo_name`: name of the subfolder + - `init`: determines if `git init` is run for the subfolder; used for local or local-ignore style repositories + - `create_fork`: determines if a fork is created on the user's GitHub account; used for remote repositories + - `fork_all_branches`: optional remote-only setting controlling whether all branches are forked + - `repo_title`: name of the remote repository to fork and clone; used for remote repositories + +### Repo type meanings + +- `local`: create a local subfolder and optionally initialize it as a Git repository. +- `remote`: clone a GitHub repository, optionally via a student fork. +- `ignore`: do not create or manage a working repository; verification should not depend on `exercise.repo`. +- `local-ignore`: create a local subfolder but do not initialize Git. This is useful for exercises where the student is expected to run `git init` themselves. + +## Exercise resource types + +There are two distinct types of resources: + +1. Base files: configured through the `base_files` property in `.gitmastery-exercise.json`; files located in `res/` are downloaded to the root of the exercise folder. + + ```text + + ├── .gitmastery-exercise.json + ├── README.md + ├── + └── + ├── .git + └── ... + ``` + +2. Resources: configured through the `__resources__` field in `download.py`; supporting files from `res/` are downloaded into the subfolder. + + ```text + + ├── .gitmastery-exercise.json + ├── README.md + ├── + └── + ├── .git + └── + ``` diff --git a/docs/exercises/exercise-utils.md b/docs/exercises/exercise-utils.md new file mode 100644 index 0000000..57273cb --- /dev/null +++ b/docs/exercises/exercise-utils.md @@ -0,0 +1,38 @@ +--- +title: Exercise utilities reference +parent: Exercises +nav_order: 7 +--- + +# Exercise utilities reference + +These utilities are contained within the [`exercises/exercise_utils` module](https://github.com/git-mastery/exercises/tree/main/exercise_utils). + +They are loaded during exercise download, which means that the `app` has access to these functions while setting up a hands-on or exercise. + +## Available utility modules + +- `exercise_utils.cli`: generic CLI calls +- `exercise_utils.file`: file creation and appending helpers +- `exercise_utils.git`: common Git operations such as commit, repository initialization, and adding files +- `exercise_utils.gitmastery`: Git-Mastery specific functions such as creating the start tag +- `exercise_utils.github_cli`: GitHub CLI wrappers for forking, cloning, deleting, and creating repositories, as well as looking up the authenticated user +- `exercise_utils.test`: testing utilities including `GitAutograderTestLoader` and `assert_output` for unit testing verification scripts + +### `exercise_utils.cli` functions + +The existing utility functions should cover most use cases, but if there is no existing utility function for your use case, `exercise_utils.cli` functions are available to execute other CLI calls. These should be used as a last resort. + +`exercise_utils.cli` exposes three functions for running shell commands: + +- `run_command`: runs a command and exits the process if it fails; use for steps that must succeed +- `run_command_no_exit`: runs a command and returns `None` on failure without exiting; use for optional or conditional steps +- `run`: runs a command and returns a `CommandResult` you can inspect (`.is_success()`, `.stdout`, `.returncode`); use when you need to branch on the result + +## Contributing utility functions + +If you believe that more utility functions should be supported, feel free to open a pull request adding one. + +{: .note } + +If you add a new file such as `exercise_utils/general.py`, remember to update `app` to include the file in the `EXERCISE_UTILS_FILES` constant. diff --git a/docs/exercises/exercise.md b/docs/exercises/exercise.md new file mode 100644 index 0000000..185d595 --- /dev/null +++ b/docs/exercises/exercise.md @@ -0,0 +1,180 @@ +--- +title: How to add an exercise +parent: Exercises +nav_order: 2 +--- + +# How to add an exercise + +## What is an exercise? + +Exercises are graded Git challenges that students complete independently to demonstrate their understanding of Git concepts. Unlike hands-ons, which only set up a sandbox for a lesson, exercises have a full lifecycle: the app downloads a starting repository state, the student performs the required Git operations, and the app verifies their work against a grading script. + +For example, a student can run `gitmastery download branch-bender` to receive a repository in a specific state, then work through the challenge before running `gitmastery verify branch-bender` to get feedback. + +Each exercise consists of two main components: + +- **`download.py`** — sets up the initial repository state the student works from. +- **`verify.py`** — inspects the student's repository after they attempt the exercise and returns a pass or fail result with feedback. + +Some examples of how Git-Mastery app uses exercises can be found [here](https://git-mastery.org/exercises-directory/index.html). + +## Before contributing + +If you are proposing a new exercise, instead of implementing an [already approved exercise proposal](https://github.com/git-mastery/exercises/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22exercise%20discussion%22%20label%3A%22good%20first%20issue%22), make sure that you have done the following: + +- [ ] Create an [exercise discussion](https://github.com/git-mastery/exercises/issues/new?template=exercise_discussion.yaml) +- [ ] Obtain approval on the exercise +- [ ] File a [remote repository request](https://github.com/git-mastery/exercises/issues/new?template=request_exercise_repository.yaml) (if needed) + +## Create a new exercise + +Use the provided `new.sh` script to generate the scaffolding for a new exercise: + +```bash +./new.sh +``` + +The script will first prompt if you want to create a hands-on or exercise. + +Enter `e` to create a new exercise. + +Then, the script will prompt you for: + +1. The name of the exercise, likely specified in the corresponding exercise discussion. Use kebab case. +2. The exercise tags, split by spaces, likely specified in the corresponding exercise discussion. +3. The exercise repository type. The scaffold currently supports `local`, `remote`, and `ignore` directly. + +{: .reference } + +Refer to the [Exercise format reference](/developers/docs/exercises/exercise-structure) page for more information about the generated folder structure and repository type options. + +## Download setup + +`download.py` contains the instructions to set up the local repository. + +The app loads `download.py` dynamically from the exercises source, reads `__resources__` if present, and executes `setup(...)` inside the student's working repository. + +{: .reference } + +For more information about how Git-Mastery downloads exercises, refer to the [Download flow](/developers/docs/exercises/download-workflow). + +{: .note } + +> `exercises` comes with a set of utility functions in the `exercise_utils` module that are made available during the download flow. Prefer these abstractions over raw CLI calls — for example, use `exercise_utils.file.create_or_update_file` to create or update files, and `exercise_utils.git.commit` to commit changes. Fall back to `exercise_utils.cli.run_command` only when no suitable abstraction exists. +> +> For the full list of utility functions, refer to [Exercise utilities reference](/developers/docs/exercises/exercise-utils). + +These are some references for download setups for other exercises: + +- [ignoring-somethings](https://raw.githubusercontent.com/git-mastery/exercises/refs/heads/main/ignoring_somethings/download.py) +- [branch-bender](https://raw.githubusercontent.com/git-mastery/exercises/refs/heads/main/branch_bender/download.py) +- [amateur-detective](https://raw.githubusercontent.com/git-mastery/exercises/refs/heads/main/amateur_detective/download.py) + +### Download conventions + +1. Any operations should use OS-agnostic options, for example `shutil.rmtree` instead of `run_command(["rm"])`. +2. If you need to compare repository state before and after the student's work, create the start tag during setup using the shared Git-Mastery utilities. +3. For any commands that require `gh`, make sure that the `requires_github` configuration is set to `true` so that the app automatically checks that the student has set up GitHub CLI correctly. +4. `setup(...)` may receive a `repo-smith` helper object as `rs`; use it instead of shelling out directly when possible. + +## Verification setup + +The verification process is controlled by `verify.py`. This file contains the grading logic that inspects the student's work and returns a result with a status and feedback comments. + +{: .reference } + +For more information about how Git-Mastery verifies exercise attempts, refer to the [Verification flow](/developers/docs/exercises/verification-workflow). + +The [`git-autograder`](https://github.com/git-mastery/git-autograder) library builds the `GitAutograderExercise` object passed to `verify(...)`. It exposes the exercise config, the working repository, and optional answer parsing. + +If there is no helper for a check you need, you can still fall back to the underlying `GitPython` repository (however, this should be avoided): + +```python +from git_autograder import ( + GitAutograderExercise, + GitAutograderOutput, + GitAutograderStatus, +) + + +def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: + repo = exercise.repo + + return exercise.to_output([], GitAutograderStatus.SUCCESSFUL) +``` + +Refer to existing `verify.py` scripts to understand the available helper functions that streamline grading. Open an issue if there is something that is not yet supported or if you have a question. + +For `repo_type: ignore` exercises, `exercise.repo` is a null repository wrapper. In that mode, verify against files such as `answers.txt` or exercise metadata rather than Git state. + +Some examples of verifications: + +- [branch-bender](https://raw.githubusercontent.com/git-mastery/exercises/refs/heads/main/branch_bender/verify.py) +- [conflict-mediator](https://raw.githubusercontent.com/git-mastery/exercises/refs/heads/main/conflict_mediator/verify.py) + +### Verification conventions + +1. Store the comments of the verification as constants so that they can be imported and used reliably in unit tests. +2. For any remote behavior to verify, provide a mock to substitute the behavior in unit tests. +3. Prefer raising `exercise.wrong_answer([...])` for incorrect submissions and reserve unexpected exceptions for genuine errors. + + +## Testing + +### Testing downloads without app + +To test that your download script works locally without the app, use the provided script: + +```bash +./test-download.sh +``` + +You can find the sandbox created by the script under `test-downloads/`. Check it manually to verify that it is as expected. + +### Testing verify logic without app + +Use [`repo-smith`](https://github.com/git-mastery/repo-smith) to simulate possible student answers and verify that your grading logic correctly accepts valid attempts and flags expected mistakes. + +Refer to existing `test_verify.py` files to see examples of unit testing the verification script. + +{: .reference } + +See [Testing guide](/developers/docs/exercises/testing-patterns) for the recommended `GitAutograderTestLoader` workflow. + +You can run the unit tests of your exercise via: + +```bash +./test.sh +``` + +### Testing downloads and verify using app + +To test the full student workflow, push your changes to a branch in a repository and modify the `.gitmastery.json` file in your `gitmastery-exercises` directory such that `exercises_source` points to this branch. + +```json +{ + "type": "remote", + "username": "your-github-username", + "repository": "your-repository-name", + "branch": "your-branch-name" +} +``` + +Run `gitmastery download `. Check that the download is correct manually. +Navigate into the exercise folder and run `gitmastery verify`. This should cause a failure as the exercise has not been complicated. +Manually simulate a student's workflow within the exercise folder (both correct and incorrect outputs) and run `gitmastery verify` to ensure your verification logic correctly grades these scenarios. + +{: .warning } + +This will cause all subsequent `gitmastery download` and `gitmastery verify` commands run within this folder to pull exercises from your branch. After you are done testing, revert your [`.gitmastery.json`](/developers/docs/app/configuration/) file to its default state, where `exercises_source` is set to the Git-Mastery organisation repository. + +## Submitting the exercise for review + +Create a pull request from your fork using the provided pull request template. + +## Example + +1. Exercise discussion: +2. Remote repository request: +3. Exercise PR: diff --git a/docs/exercises/hands-on.md b/docs/exercises/hands-on.md new file mode 100644 index 0000000..f1cbf0c --- /dev/null +++ b/docs/exercises/hands-on.md @@ -0,0 +1,154 @@ +--- +title: How to add a hands-on +parent: Exercises +nav_order: 1 +--- + +# How to add a hands-on + +## What is a hands-on? + +[Lessons accompanying Git-Mastery App](https://git-mastery.org/lessons/index.html) contains hands-on practicals for students to get hands-on experience of the Git concepts covered by the lesson. Some of those hands-on practicals need to set up a sandbox containing the required folders, files, and repositories before the student can start. Git-Mastery app can set up that sandbox for students so that they can get to the practical part more easily. + +For example, a student can run `gitmastery download hp-init-repo` to set up a sandbox for a specific hands-on practical. + +Some examples of how Git-Mastery app helps create the sandbox for a hands-on practical can be seen in [this Git Tour](https://git-mastery.org/lessons/trail/recordingFolderHistory/#git-mastery-lessons) (see T1L3 and T1L4). + +## Before contributing + +New contributors are recommended to start by implementing an [already approved hands-on proposal](https://github.com/git-mastery/exercises/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22hands-on%20discussion%22%20label%3A%22good%20first%20issue%22). + +If you are proposing a new hands-on instead, make sure that you have done the following: + +- [ ] Create a [hands-on discussion](https://github.com/git-mastery/exercises/issues/new?template=hands_on_discussion.yaml) +- [ ] Obtain approval on the hands-on +- [ ] File a [remote repository request](https://github.com/git-mastery/exercises/issues/new?template=request_exercise_repository.yaml) (if needed) + +## Implementing a hands-on + +First, run the provided `new.sh` script to generate the scaffolding for a new hands-on sandbox: + +```bash +./new.sh +``` + +The script will first prompt if you want to create a hands-on or exercise. + +Enter `h` to create a new hands-on. + +Then, the script will prompt you for: + +1. The name of the hands-on, likely specified in the corresponding hands-on discussion, without the `hp-` prefix. Use kebab case. +2. Whether the hands-on requires Git +3. Whether the hands-on requires GitHub + +Each hands-on is implemented as a single file at `hands_on/.py`, containing the instructions to set up the hands-on sandbox. + +## Hands-on file format + +All hands-ons are stored within the `hands_on` folder of the [`exercises`](https://github.com/git-mastery/exercises) repository. + +They are represented by a single Python file whose name is the hands-on ID. + +Hands-ons are not graded and progress is not tracked. They only help set up the student's folder structure for a given lesson. + +{: .note } + +Git-Mastery uses the format `hp-` for hands-on names, for example `hp-init-repo`, to differentiate them from exercise names. Internally, the `hands_on/` folder stores the Python files, for example `hands_on/init_repo.py`. + +```python +import os + +from exercise_utils.file import append_to_file, create_or_update_file +from exercise_utils.git import add, init + +__requires_git__ = True +__requires_github__ = False + + +def download(verbose: bool): + os.makedirs("things") + os.chdir("things") + init(verbose) + create_or_update_file( + "fruits.txt", + """ + apples + bananas + cherries + """, + ) + add(["fruits.txt"], verbose) + append_to_file("fruits.txt", "dragon fruits") +``` + +The setup instructions go under the `download` function. + +`__requires_git__` and `__requires_github__` tell the app whether to run automatic verification that the student has set up Git and GitHub CLI correctly. + +{: .warning } + +> We are currently working on doing a one-time migration to reposmith for download functions, tracked in this [issue](https://github.com/git-mastery/exercises/issues/268). +> For now, we will still use this current syntax. + +{: .reference } + +For more information about how Git-Mastery downloads a hands-on, refer to the [Download flow](/developers/docs/exercises/download-workflow). + +{: .note } + +> `exercises` comes with a set of utility functions in the `exercise_utils` module that are made available during the download flow. Prefer these abstractions over raw CLI calls — for example, use `exercise_utils.file.create_or_update_file` to create or update files, and `exercise_utils.git.commit` to commit changes. Fall back to `exercise_utils.cli.run_command` only when no suitable abstraction exists. +> +> For the full list of utility functions, refer to [Exercise utilities reference](/developers/docs/exercises/exercise-utils). + +These are some references for download setups for other hands-ons: + +- [init-repo](https://raw.githubusercontent.com/git-mastery/exercises/refs/heads/main/hands_on/init_repo.py) +- [add-files](https://raw.githubusercontent.com/git-mastery/exercises/refs/heads/main/hands_on/add_files.py) +- [stage-modified](https://raw.githubusercontent.com/git-mastery/exercises/refs/heads/main/hands_on/stage_modified.py) + +### Conventions + +1. Any operations should use OS-agnostic options, for example `shutil.rmtree` instead of `run_command(["rm"])`. +2. For any commands that require `gh`, make sure that the `__requires_github__` variable in the download setup is set to `True` so that the app automatically checks that the student has set up GitHub CLI correctly. + +## Testing + +### Testing downloads without app + +To test that your download script works locally without the app, use the provided script: + +```bash +./test-download.sh +``` + +You can find the sandbox created by the script under `test-downloads/`. Check it manually to verify that it is as expected. + +### Testing downloads using app + +To test the full student workflow, push your changes to a branch in a repository and modify the `.gitmastery.json` file in your `gitmastery-exercises` directory such that `exercises_source` points to this branch. + +```json +{ + "type": "remote", + "username": "your-github-username", + "repository": "your-repository-name", + "branch": "your-branch-name" +} +``` + +Run `gitmastery download hp-`. Check that the download is correct manually. + +{: .warning } + +This will cause all subsequent `gitmastery download` commands run within this folder to pull exercises from your branch. After you are done testing, revert your [`.gitmastery.json`](/developers/docs/app/configuration/) file to its default state, where `exercises_source` is set to the Git-Mastery organisation repository. + +## Submitting the hands-on for review + +Create a pull request from your fork using the provided pull request template. + +## Example + +1. Hands-on discussion: +2. Remote repository request: +3. Hands-on PR: diff --git a/docs/exercises/index.md b/docs/exercises/index.md new file mode 100644 index 0000000..028aa60 --- /dev/null +++ b/docs/exercises/index.md @@ -0,0 +1,10 @@ +--- +title: Exercises +nav_order: 4 +has_children: true +has_toc: true +--- + +# Exercises + +The [`exercises`](https://github.com/git-mastery/exercises) repository contains both hands-ons and graded exercises. diff --git a/docs/exercises/testing-patterns.md b/docs/exercises/testing-patterns.md new file mode 100644 index 0000000..80e29d4 --- /dev/null +++ b/docs/exercises/testing-patterns.md @@ -0,0 +1,107 @@ +--- +title: Testing guide +parent: Exercises +nav_order: 6 +--- + +# Testing guide + +Exercise verification tests usually focus on repository end states rather than replaying the entire student workflow. + +## Recommended approach + +1. Use `GitAutograderTestLoader` from `exercise_utils.test` +2. Build the repository state you need with `repo-smith` +3. Run `verify(...)` +4. Assert on `GitAutograderStatus` and important comments + +## Basic pattern + +```python +from git_autograder import GitAutograderStatus +from exercise_utils.test import GitAutograderTestLoader, assert_output + +from .verify import verify + +REPOSITORY_NAME = "my-exercise" + +loader = GitAutograderTestLoader(REPOSITORY_NAME, verify) + + +def test_base() -> None: + with loader.start() as (test, rs): + rs.git.create_file("notes.txt", "hello") + rs.git.add(["notes.txt"]) + rs.git.commit("Add notes") + + output = test.run() + + assert_output(output, GitAutograderStatus.SUCCESSFUL) +``` + +## What the loader gives you + +`GitAutograderTestLoader` sets up a temporary exercise directory, patches exercise config loading, and constructs a real `GitAutograderExercise` object for the test run. + +Within `with loader.start() as (test, rs):` + +- `test.run()` executes the exercise's `verify(...)` +- `rs` is a [`RepoSmith`](/developers/docs/libraries/repo-smith) helper rooted at the student's working repository. It abstracts the underlying Git repository, letting you set up state with calls like `rs.git.commit(...)`, `rs.git.create_file(...)`, etc. + +## Useful test options + +### Start from an existing repository + +Use `clone_from=...` when the exercise needs a prepared repository as its starting point. + +```python +with loader.start(clone_from="https://github.com/git-mastery/some-repo") as (test, rs): + ... +``` + +### Mock `answers.txt` + +Use `mock_answers` for answer-driven exercises. + +```python +with loader.start(mock_answers={"What is Git?": "Version control"}) as (test, rs): + output = test.run() +``` + +### Include a remote repository + +Use `include_remote_repo=True` when verification depends on remote behavior (eg. pushing to remote). + +```python +with loader.start(include_remote_repo=True) as (test, rs, rs_remote): + ... +``` + +## What to assert + +- `output.status` for the overall result +- key comments, not necessarily every comment +- repository state only when it helps explain the verification behavior + +`exercise_utils.test.assert_output(...)` is useful when you want to check the status and ensure specific comments are present. + +## Testing guidance + +- Prefer one test per verification rule or scenario +- Keep each test focused on the minimal repository state needed +- Store verification comments as constants in `verify.py` so tests can import them safely +- Mock remote interactions instead of making real network calls + +## Running tests + +In `exercises`, run: + +```bash +./test.sh +``` + +Or run the full suite with: + +```bash +pytest . -s -vv +``` diff --git a/docs/exercises/verification-workflow.md b/docs/exercises/verification-workflow.md new file mode 100644 index 0000000..9b8bd2c --- /dev/null +++ b/docs/exercises/verification-workflow.md @@ -0,0 +1,33 @@ +--- +title: Verification flow +parent: Exercises +nav_order: 5 +--- + +# Verification flow + +Exercise verification is driven by the student's local exercise metadata and the exercise's `verify.py` script. + +```mermaid +flowchart +a[Verify exercise] --> b[Check if in exercise using .gitmastery-exercise.json] +b -- not in exercise --> c[Cancel] +b -- in exercise --> d[Load exercise config and verification module] +d --> e[Run verify.py against the student's working repository] +e --> f[Return structured grading output] +f --> g[Save progress locally] +g --> h{Remote sync enabled?} +h -- yes --> i[Commit and push progress fork and create PR if needed] +h -- no --> j[Finish] +i --> j +``` + +## In practice + +- The app first confirms that the current directory belongs to a Git-Mastery exercise. +- It loads the local exercise metadata and the corresponding verification logic from the exercises source. +- `verify.py` receives a `GitAutograderExercise` object, which loads `.gitmastery-exercise.json` and prepares either a real repository wrapper or a null repository wrapper for `ignore` exercises. +- `verify.py` uses `git-autograder` to inspect repository state, parse `answers.txt` when needed, and produce structured comments and a status. +- `GitAutograderWrongAnswerException` becomes an unsuccessful verification result, while invalid state and unexpected exceptions become error results. +- After verification, the app appends an entry to local `progress/progress.json` and optionally syncs it to the student's progress fork. +- Exercise unit tests use `repo-smith` to construct end states that validate verification behavior. diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md new file mode 100644 index 0000000..264903e --- /dev/null +++ b/docs/getting-started/index.md @@ -0,0 +1,16 @@ +--- +title: Getting started +nav_order: 3 +has_children: true +has_toc: false +--- + +# Getting started + +This section helps contributors choose the right repository, set up a local development environment, and follow common Git-Mastery workflows. + +## Typical starting points + +- Implement or update a hands-on or exercise: [Exercises](/developers/docs/exercises) +- Work on the CLI experience: [App](/developers/docs/app) +- Make improvements to shared libraries: [Shared libraries](/developers/docs/libraries) diff --git a/docs/getting-started/setup.md b/docs/getting-started/setup.md new file mode 100644 index 0000000..c3e7cae --- /dev/null +++ b/docs/getting-started/setup.md @@ -0,0 +1,101 @@ +--- +title: Development setup +parent: Getting started +nav_order: 1 +--- + +# Development setup + +Git-Mastery is standardizing on a `uv`-first local development workflow. + +Use the guidance on this page as the default when setting up repositories locally. + +{: .warning } + +> Some repositories are still in transition, and may be using `pip` and `venv` for local development. + +## Prerequisites + +- Git +- Python 3.13+ +- `uv` (refer to [installation guide](https://docs.astral.sh/uv/getting-started/installation/)) +- GitHub CLI (`gh`) installed and authenticated when working on flows that interact with GitHub +- Git-Mastery app installed and configured (refer to [the setup guide](https://git-mastery.org/companion-app/index.html#installation-and-setup)) + +## Repository setup (uv) + +1. Fork the repository you want to work on. +2. Clone your fork. + + ```bash + git clone https://github.com// + cd + ``` + +3. Run the following command to set up virtual environment and install dependencies: + + {: .note } + + > The `--all-groups` flag ensures all dependencies are installed, including those for development and testing. + > Development and testing dependencies are needed for running pre-commit hooks and tests locally. + + ```bash + uv sync --all-groups + ``` + +4. Set up pre-commit hooks using LeftHook + + {: .note } + + > Recommended to install for formatting and linting support. + + ```bash + uv run lefthook install + ``` + +## Repository setup (pip) + +{: .warning-title } + +> Deprecated +> +> As we are transitioning to uv, this setup is still relevant for some repositories. + +1. Fork the repository you want to work on. +2. Clone your fork. + + ```bash + git clone https://github.com// + cd + ``` + +3. Run the following command to set up virtual environment and install dependencies: + + ```bash + pip install -r requirements.txt + python -m venv .venv + source .venv/bin/activate # macOS/Linux + source .venv/Scripts/activate # Windows + ``` + +4. Set up pre-commit hooks using LeftHook + + {: .note } + + > Recommended to install for formatting and linting support. + + ```bash + lefthook install + ``` + +## Troubleshooting + +### Pre-commit hooks not running as expected after migration to `uv` + +1. Delete existing hooks in `.git/hooks/` to remove any old `pip`-based hooks. + + ``` + rm -rf .git/hooks/pre-commit + ``` + +2. Re-run `uv run lefthook install` to set up the new hooks. diff --git a/docs/libraries/git-autograder.md b/docs/libraries/git-autograder.md new file mode 100644 index 0000000..cb5d739 --- /dev/null +++ b/docs/libraries/git-autograder.md @@ -0,0 +1,301 @@ +--- +title: git-autograder reference +parent: Shared libraries +nav_order: 2 +--- + +# git-autograder reference + +`git-autograder` is the grading library that loads a Git-Mastery exercise attempt and turns repository or answer checks into structured verification results. + +## Installation + +```bash +pip install git-autograder +``` + +## Basic structure of a `verify.py` + +```python +from git_autograder import ( + GitAutograderExercise, + GitAutograderOutput, + GitAutograderStatus, +) + +SOME_ERROR = "You haven't done X yet." + + +def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: + main = exercise.repo.branches.branch("main") + + if not main.user_commits: + raise exercise.wrong_answer([SOME_ERROR]) + + return exercise.to_output(["Well done!"], GitAutograderStatus.SUCCESSFUL) +``` + +The app calls `verify(exercise)` directly. All exception handling and output formatting is done by the app. + +--- + +## `GitAutograderExercise` + +The main object passed to every `verify(exercise)` function. + +| Attribute / Method | Description | +|---|---| +| `exercise.exercise_name` | Exercise identifier (from config) | +| `exercise.exercise_path` | Path to the exercise root directory | +| `exercise.config` | Parsed `.gitmastery-exercise.json` as `ExerciseConfig` | +| `exercise.repo` | `GitAutograderRepo` (or `NullGitAutograderRepo` for `ignore` exercises) | +| `exercise.answers` | Parsed `answers.txt` as `GitAutograderAnswers` | +| `exercise.to_output(comments, status)` | Build the return value | +| `exercise.wrong_answer(comments)` | Raise a grading failure | +| `exercise.read_config(key)` | Read a key from `.gitmastery-exercise.json` | +| `exercise.write_config(key, value)` | Write a key to `.gitmastery-exercise.json` | + +### Exception types + +| Exception | When to use | +|---|---| +| `GitAutograderWrongAnswerException` | The student's attempt is incorrect → `UNSUCCESSFUL` | +| `GitAutograderInvalidStateException` | The exercise is in an invalid state → `ERROR` | + +Use `raise exercise.wrong_answer([...])` for grading failures. Reserve bare exceptions for unexpected errors only. + +--- + +## `exercise.repo` — repository helpers + +`exercise.repo` is a `GitAutograderRepo` for most exercises. For `ignore` and `local-ignore` exercises it is a `NullGitAutograderRepo` that raises on any Git access. + +### `exercise.repo.branches` — `BranchHelper` + +```python +branch = exercise.repo.branches.branch("main") # raises if missing +branch = exercise.repo.branches.branch_or_none("main") # returns None if missing +exists = exercise.repo.branches.has_branch("feature/login") +``` + +#### `GitAutograderBranch` + +| Property / Method | Description | +|---|---| +| `branch.name` | Branch name | +| `branch.commits` | All commits (newest first) | +| `branch.user_commits` | Commits after the start tag (student's work) | +| `branch.latest_commit` | Most recent commit | +| `branch.latest_user_commit` | Most recent student commit | +| `branch.start_commit` | The Git-Mastery start tag commit | +| `branch.reflog` | List of `GitAutograderReflogEntry` | +| `branch.has_non_empty_commits()` | True if any student commit changed files | +| `branch.has_edited_file(path)` | True if the file was modified since the start tag | +| `branch.has_added_file(path)` | True if the file was added since the start tag | +| `branch.checkout()` | Checkout this branch | + +Example — check that the student committed on `main`: + +```python +main = exercise.repo.branches.branch("main") +if not main.user_commits: + raise exercise.wrong_answer(["You have no commits on main yet."]) +``` + +### `exercise.repo.commits` — `CommitHelper` + +```python +commit = exercise.repo.commits.commit("HEAD") +commit = exercise.repo.commits.commit_or_none("abc1234") +``` + +#### `GitAutograderCommit` + +| Property / Method | Description | +|---|---| +| `commit.hexsha` | Full commit SHA | +| `commit.stats` | GitPython `Stats` object (files changed) | +| `commit.parents` | List of `GitAutograderCommit` | +| `commit.branches` | Branch names that contain this commit | +| `commit.is_child(parent)` | True if this commit descends from `parent` | +| `commit.file_change_type(file)` | `"A"`, `"M"`, `"D"`, or `None` | +| `commit.file(path)` | Context manager yielding the file contents at this commit | +| `commit.checkout()` | Detach HEAD to this commit | + +### `exercise.repo.remotes` — `RemoteHelper` + +```python +remote = exercise.repo.remotes.remote("origin") # raises if missing +remote = exercise.repo.remotes.remote_or_none("origin") # returns None +exists = exercise.repo.remotes.has_remote("origin") +``` + +#### `GitAutograderRemote` + +Wraps a GitPython `Remote`. + +| Method | Description | +|---|---| +| `remote.remote` | The underlying GitPython `Remote` object | +| `remote.is_for_repo(owner, repo_name)` | True if the remote URL points to `owner/repo_name` on GitHub (supports both HTTPS and SSH URLs) | +| `remote.track_branches(branches)` | Check out remote-tracking branches locally | + +Example — verify the remote points to the right repository: + +```python +origin = exercise.repo.remotes.remote("origin") +if not origin.is_for_repo("git-mastery", "exercises"): + raise exercise.wrong_answer(["Your remote 'origin' does not point to the correct repository."]) +``` + +### `exercise.repo.files` — `FileHelper` + +```python +# Open a file if it exists +with exercise.repo.files.file_or_none("notes.txt") as f: + content = f.read() if f else None + +# Open a file (raises if missing) +with exercise.repo.files.file("notes.txt") as f: + content = f.read() + +# List untracked files +untracked = exercise.repo.files.untracked_files() +``` + +### Raw GitPython access + +If no helper covers your use case, access the underlying GitPython repo directly: + +```python +exercise.repo.repo # GitPython Repo object +``` + +--- + +## `exercise.answers` — answer file support + +For exercises where students fill in an `answers.txt` file. + +### `answers.txt` format + +``` +Q: What does git add do? +A: Stages changes for the next commit + +Q: What does git commit do? +A: Records staged changes to the repository +``` + +### Usage + +`answers.question(q)` and `answers.question_or_none(q)` return a `GitAutograderAnswersRecord` with two attributes: + +| Attribute | Description | +|---|---| +| `record.question` | The question string | +| `record.answer` | The student's answer string | + +```python +answers = exercise.answers + +# Get a specific answer (raises GitAutograderInvalidStateException if missing) +record = answers.question("What does git add do?") +print(record.answer) + +# Safe access — returns None if the question is not present +record = answers.question_or_none("What does git add do?") +if record is None: + raise exercise.wrong_answer(["Missing answer for 'What does git add do?'"]) + +# Validate answers with rules, then call validate() to apply them all at once +from git_autograder.answers.rules.not_empty_rule import NotEmptyRule +from git_autograder.answers.rules.has_exact_value_rule import HasExactValueRule +from git_autograder.answers.rules.contains_value_rule import ContainsValueRule + +answers.add_validation("What does git add do?", NotEmptyRule()) +answers.add_validation("Name a git command", HasExactValueRule("git commit")) +answers.validate() # raises GitAutograderWrongAnswerException if any rule fails +``` + +### Available answer rules + +| Rule | Description | +|---|---| +| `NotEmptyRule` | Answer must not be blank | +| `HasExactValueRule(value)` | Answer must equal `value` exactly (case-insensitive) | +| `ContainsValueRule(value)` | Answer must contain `value` (case-insensitive) | +| `ContainsListRule(values)` | Answer must contain all values in the list | +| `HasExactListRule(values)` | Answer must match all values in the list exactly | + +All rules are in `git_autograder.answers.rules`. + +### In tests + +Mock `answers.txt` content via `GitAutograderTestLoader`: + +```python +with loader.start(mock_answers={"What does git add do?": "Stages changes"}) as (test, rs): + output = test.run() + assert_output(output, GitAutograderStatus.SUCCESSFUL) +``` + +--- + +## `GitAutograderStatus` + +| Value | Meaning | +|---|---| +| `SUCCESSFUL` | Student completed the exercise correctly | +| `UNSUCCESSFUL` | Student's attempt is incorrect or incomplete | +| `ERROR` | Exercise is in an invalid or unexpected state | + +--- + +## `GitAutograderOutput` + +Built by `exercise.to_output(comments, status)`. Contains: + +| Field | Type | Description | +|---|---|---| +| `exercise_name` | `str` | Exercise identifier | +| `started_at` | `datetime` | When `GitAutograderExercise` was constructed | +| `completed_at` | `datetime` | When `to_output` was called | +| `comments` | `List[str]` | Feedback shown to the student | +| `status` | `GitAutograderStatus` | Final result | + +--- + +## Full example — branch check + +```python +from git_autograder import ( + GitAutograderExercise, + GitAutograderOutput, + GitAutograderStatus, +) + +NOT_ON_MAIN = "You aren't on the main branch. Run 'git checkout main'." +NO_COMMITS = "You haven't committed your changes yet." +SUCCESS = "Great work! Your changes are committed to main." + + +def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: + try: + active = exercise.repo.repo.active_branch.name + except TypeError: + raise exercise.wrong_answer(["You are in a detached HEAD state."]) + + if active != "main": + raise exercise.wrong_answer([NOT_ON_MAIN]) + + main = exercise.repo.branches.branch("main") + if not main.user_commits: + raise exercise.wrong_answer([NO_COMMITS]) + + return exercise.to_output([SUCCESS], GitAutograderStatus.SUCCESSFUL) +``` + +{: .reference } + +See [How to add an exercise](/developers/docs/exercises/exercise) and [Verification flow](/developers/docs/exercises/verification-workflow) for how this fits into the full exercise lifecycle. diff --git a/docs/libraries/index.md b/docs/libraries/index.md new file mode 100644 index 0000000..ef1d549 --- /dev/null +++ b/docs/libraries/index.md @@ -0,0 +1,26 @@ +--- +title: Shared libraries +nav_order: 6 +has_children: true +has_toc: false +--- + +# Shared libraries + +Git-Mastery maintains reusable libraries that support exercise verification and testing. + +These libraries are most relevant when you are working on reusable grading behavior rather than a single exercise. + +## Main libraries + +- [`repo-smith`](https://github.com/git-mastery/repo-smith): repository construction for unit tests +- [`git-autograder`](https://github.com/git-mastery/git-autograder): grading helpers and verification abstractions + +## When to use which library + +- Reach for `repo-smith` when your test needs a specific repository history or working tree state. +- Reach for `git-autograder` when multiple exercises would benefit from the same verification helper or abstraction. + +## Scope of this section + +This section focuses on the shared libraries that Git-Mastery contributors are most likely to touch while working on verification behavior or test infrastructure. diff --git a/docs/libraries/repo-smith.md b/docs/libraries/repo-smith.md new file mode 100644 index 0000000..9e201af --- /dev/null +++ b/docs/libraries/repo-smith.md @@ -0,0 +1,254 @@ +--- +title: repo-smith reference +parent: Shared libraries +nav_order: 1 +--- + +# repo-smith reference + +`repo-smith` is a test-support library that creates and mutates local and remote Git repository states through helper objects, so exercise verification scenarios can be set up deterministically in tests. + +{: .warning } + +The YAML-based spec format from repo-smith v1 is deprecated. All Git-Mastery exercises use the Python v2 API directly through `create_repo_smith` and the `RepoSmith` helper objects. + +## Installation + +```bash +pip install -U repo-smith +``` + +## Core API + +### `create_repo_smith` + +The main entrypoint. Returns a context manager that yields a `RepoSmith` instance. + +```python +from repo_smith.repo_smith import create_repo_smith + +with create_repo_smith(verbose=False) as rs: + rs.git.commit(message="Initial commit", allow_empty=True) +``` + +#### Options + +| Option | Type | Description | +|---|---|---| +| `verbose` | `bool` | Print commands as they run | +| `existing_path` | `str` | Use an existing directory instead of creating a temp one | +| `clone_from` | `str` | Clone from a URL before starting | +| `null_repo` | `bool` | Create a `RepoSmith` with no underlying repository | + +### `RepoSmith` + +Yielded by `create_repo_smith`. Provides three built-in helpers: + +| Attribute | Description | +|---|---| +| `rs.git` | Git operations | +| `rs.files` | File system operations | +| `rs.gh` | GitHub CLI operations | +| `rs.repo` | Underlying `GitPython` `Repo` object | + +Custom helpers can be registered with `rs.add_helper(MyHelper)` and accessed with `rs.helper(MyHelper)`. + +--- + +## `rs.git` — Git operations + +### Commit and stage + +```python +rs.git.add("notes.txt") +rs.git.add(["a.txt", "b.txt"]) +rs.git.add(all=True) + +rs.git.commit(message="Initial commit", allow_empty=True) +rs.git.commit(message="Add file") +``` + +### Branches + +```python +rs.git.checkout("feature/login", branch=True) # create and switch +rs.git.checkout("main") # switch to existing branch +rs.git.branch("feature/payments") # create without switching +rs.git.branch("new-name", old_branch="old-name", move=True) # rename +rs.git.branch("old-branch", delete=True) # delete +``` + +### Merge + +```python +rs.git.merge("feature/login", no_ff=True) +rs.git.merge("feature/dashboard") +``` + +### Tags + +```python +rs.git.tag("v1.0") +rs.git.tag("v1.0", "abc1234") # tag a specific commit +``` + +### Reset and revert + +```python +rs.git.reset("HEAD~1", hard=True) +rs.git.revert("HEAD") +``` + +### Remotes and push/fetch + +```python +rs.git.remote_add("origin", "https://github.com/example/repo.git") +rs.git.remote_rename("origin", "upstream") +rs.git.remote_remove("origin") + +rs.git.push("origin", "main", set_upstream=True) +rs.git.push("origin", ":old-branch") # the colon prefix deletes the remote branch +rs.git.fetch("origin") +rs.git.fetch(all=True) +``` + +### Restore + +```python +rs.git.restore("notes.txt") +rs.git.restore("notes.txt", staged=True) +``` + +--- + +## `rs.files` — File operations + +```python +rs.files.create_or_update("notes.txt", "hello world") # create or overwrite +rs.files.create_or_update("empty.txt") # create empty file +rs.files.append("notes.txt", "\nmore content") +rs.files.delete("notes.txt") +rs.files.delete("some-dir/") +rs.files.mkdir("subdir") +rs.files.cd("subdir") +``` + +--- + +## `rs.gh` — GitHub CLI operations + +Used for exercises that test remote repository behavior. + +```python +rs.gh.repo_create(owner=None, repo="my-repo", public=True) +rs.gh.repo_clone(owner="git-mastery", repo="exercises", directory="local-dir") +rs.gh.repo_fork(owner="git-mastery", repo="exercises", clone=True) +rs.gh.repo_delete(owner=None, repo="my-repo") +rs.gh.repo_view(owner="git-mastery", repo="exercises") +``` + +--- + +## Custom helpers + +Custom helpers extend `RepoSmith` with domain-specific operations. Subclass `Helper` and register it before use: + +```python +from git import Repo +from repo_smith.helpers.helper import Helper + + +class GitMasteryHelper(Helper): + def __init__(self, repo: Repo, verbose: bool) -> None: + super().__init__(repo, verbose) + + def create_start_tag(self) -> None: + """Creates the git-mastery-start- tag required by git-autograder.""" + all_commits = list(self.repo.iter_commits()) + first_commit = list(reversed(all_commits))[0] + tag = f"git-mastery-start-{first_commit.hexsha[:7]}" + self.repo.create_tag(tag) + + +# Register once on the RepoSmith instance, then call via rs.helper(...) +rs.add_helper(GitMasteryHelper) +rs.helper(GitMasteryHelper).create_start_tag() +``` + +{: .note } +`GitMasteryHelper` is already defined in `exercise_utils.test` and is registered automatically when using `GitAutograderTestLoader`. You only need to implement it yourself if you are calling `create_repo_smith` directly outside of exercises. + +--- + +## Typical test pattern in exercises + +Tests in `exercises` do not call `create_repo_smith` directly. Instead they use `GitAutograderTestLoader` from `exercise_utils.test`, which sets up the exercise environment and provides `rs` automatically. + +### Basic test + +```python +from exercise_utils.test import GitAutograderTestLoader, GitMasteryHelper, assert_output +from git_autograder import GitAutograderStatus +from .verify import verify, NO_MERGES, MISSING_MERGES + +REPOSITORY_NAME = "branch-bender" + +loader = GitAutograderTestLoader(REPOSITORY_NAME, verify) + + +def test_successful_merge() -> None: + with loader.start() as (test, rs): + rs.git.commit(message="Initial commit", allow_empty=True) + rs.helper(GitMasteryHelper).create_start_tag() + + rs.git.checkout("feature/login", branch=True) + rs.git.commit(message="Add login", allow_empty=True) + rs.git.checkout("main") + rs.git.merge("feature/login", no_ff=True) + + output = test.run() + assert_output(output, GitAutograderStatus.SUCCESSFUL) + + +def test_no_merges() -> None: + with loader.start() as (test, rs): + rs.git.commit(message="Initial commit", allow_empty=True) + rs.helper(GitMasteryHelper).create_start_tag() + + output = test.run() + assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [NO_MERGES]) +``` + +### With a remote repository + +```python +def test_remote_branch_rename() -> None: + with loader.start(include_remote_repo=True) as (test, rs, rs_remote): + remote_path = str(rs_remote.repo.git_dir) + + rs.git.commit(message="Initial commit", allow_empty=True) + rs.git.remote_add("origin", remote_path) + rs.git.branch("feature/old") + rs.git.push("origin", "feature/old") + + # Simulate the student's action + rs.git.branch("feature/new", old_branch="feature/old", move=True) + rs.git.push("origin", "feature/new") + rs.git.push("origin", ":feature/old") + + output = test.run() + assert_output(output, GitAutograderStatus.SUCCESSFUL) +``` + +### Starting from a cloned repository + +```python +def test_from_clone() -> None: + with loader.start(clone_from="https://github.com/git-mastery/some-repo") as (test, rs): + output = test.run() + assert_output(output, GitAutograderStatus.SUCCESSFUL) +``` + +{: .reference } + +See [Testing guide](/developers/docs/exercises/testing-patterns) for the full `GitAutograderTestLoader` API. diff --git a/docs/overview/index.md b/docs/overview/index.md new file mode 100644 index 0000000..656254a --- /dev/null +++ b/docs/overview/index.md @@ -0,0 +1,33 @@ +--- +title: Overview +nav_order: 2 +has_children: true +has_toc: false +--- + +# Overview + +Git-Mastery is a multi-repository project, and the documentation is organized around the different repositories and components. This overview page serves as a map to the ecosystem, guiding you to the relevant documentation for each part of the project. + +## Core repositories + +These are the main repositories that make up the Git-Mastery ecosystem, and are the most relevant for contributors: + +- [`exercises`](https://github.com/git-mastery/exercises): hands-ons, exercises, download setup, and verification tests +- [`app`](https://github.com/git-mastery/app): the `gitmastery` CLI used by students and contributors +- [`repo-smith`](https://github.com/git-mastery/repo-smith): test support library that creates Git repository states through helper objects so exercise verification scenarios can be set up deterministically in tests +- [`git-autograder`](https://github.com/git-mastery/git-autograder): grading library that loads a Git-Mastery exercise attempt and turns repository or answer checks into structured verification result + +## Supporting repositories + +These repositories are part of the wider ecosystem and may receive dedicated documentation later: + +- [`progress-dashboard`](https://github.com/git-mastery/progress-dashboard): visualizes learner progress data +- [`difflib-parser`](https://github.com/git-mastery/difflib-parser): library for parsing diff output into structured data + +## Where to start + +- New contributor: start with [Getting started](/developers/docs/getting-started) +- Exercise contributor: go to [Exercises](/developers/docs/exercises) +- CLI contributor: go to [App](/developers/docs/app) +- Library contributor: go to [Shared libraries](/developers/docs/libraries) diff --git a/docs/overview/system-overview.md b/docs/overview/system-overview.md new file mode 100644 index 0000000..2871ce6 --- /dev/null +++ b/docs/overview/system-overview.md @@ -0,0 +1,23 @@ +--- +title: System overview +parent: Overview +nav_order: 1 +--- + +# System overview + +The main Git-Mastery repositories work together as follows: + +```mermaid +flowchart LR + exercises --> gitautograder[git-autograder] + exercises --> reposmith[repo-smith] + app -->|downloads| exercises + app -->|updates| progress[progress-dashboard] +``` + +1. `app` downloads and verifies hands-ons and exercises through the `gitmastery` CLI. +2. `exercises` is where contributors define hands-ons, exercises, resources, and verification tests. +3. `git-autograder` provides the verification model used by exercise `verify.py` scripts. +4. `repo-smith` creates repository states for unit tests that validate `verify.py` behavior. +5. `progress-dashboard` visualizes learner progress data sent by the `gitmastery` CLI. diff --git a/docs/tooling/developer-tooling.md b/docs/tooling/developer-tooling.md deleted file mode 100644 index 38f53df..0000000 --- a/docs/tooling/developer-tooling.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Developer tooling -nav_order: 3 -parent: Tooling ---- - -## Publishing new versions of libraries - -When working on `app` or the other supported libraries, if you attach one of the following labels: - -- `bump:major` -- `bump:minor` -- `bump:patch` - -The CI will automatically tag the merge commit when the pull request is merged and automatically perform a publish for the library/tool. diff --git a/docs/tooling/exercise-utils.md b/docs/tooling/exercise-utils.md deleted file mode 100644 index 3108d19..0000000 --- a/docs/tooling/exercise-utils.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: Exercise utilities -nav_order: 1 -parent: Tooling ---- - -These are all contained within the [`exercises/exercise_utils` module](https://github.com/git-mastery/exercises/tree/main/exercise_utils). - -These are loaded when downloading an exercise, which means that the `app` has access to these functions. - -## Available utility functions - -There are functions broadly for the following categories: - -- `exercise_utils.cli`: Generic CLI calls -- `exercise_utils.file`: File creation/appending -- `exercise_utils.git`: Common Git operations like commit, initializing a repository, and adding files -- `exercise_utils.gitmastery`: Git-Mastery specific functions like creating the start tag - -## Contributing utility functions - -These should cover around 80% of all use cases, but if there isn't an existing utility function for your use case, `exercise_utils.cli.run_command` is available to execute any other CLI calls. - -If you believe that more utility functions should be supported, feel free to open a PR adding one. - -{: .note } - -If you add a new file, for example `exercise_utils/general.py`, please update `app` to include the file in the `EXERICSE_UTILS_FILES` constant [here](https://github.com/git-mastery/app/blob/6786aa998e9c8f7ca63c77534bfaf5b7514a89c2/app/utils/gitmastery.py#L212). diff --git a/docs/tooling/index.md b/docs/tooling/index.md deleted file mode 100644 index 108a45c..0000000 --- a/docs/tooling/index.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Tooling -nav_order: 4 ---- - -Git-Mastery maintains various tools to support development. diff --git a/docs/tooling/libraries.md b/docs/tooling/libraries.md deleted file mode 100644 index 0f1bc29..0000000 --- a/docs/tooling/libraries.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: Published libraries -nav_order: 2 -parent: Tooling ---- - -## Published Python packages - -Git-Mastery publishes and maintains several public packages: - -- [`repo-smith`](https://github.com/git-mastery/repo-smith) -- [`git-autograder`](https://github.com/git-mastery/git-autograder) -- [`difflib-parser`](https://github.com/git-mastery/difflib-parser) diff --git a/index.markdown b/index.markdown deleted file mode 100644 index f1ebbb4..0000000 --- a/index.markdown +++ /dev/null @@ -1,32 +0,0 @@ ---- -title: Home -layout: home -nav_order: 1 ---- - -# Git-Mastery Developers - -{: .fs-6 .fw-300 } -Developer documentation about all things Git-Mastery. - -## What is Git-Mastery? - -Git-Mastery is a Git education tool developed by the National University of Singapore School of Computing to help students master Git through hands-on, guided tours with real-world scenario exercises. - -## Try Git-Mastery! - -Before contributing to Git-Mastery, it would be good if you tried it out to get a sense of what students experience. - -To setup Git-Mastery, refer to [this setup guide](https://git-mastery.org/companion-app/index.html) for your OS. - -We recommend following along with [tour 1](https://git-mastery.org/lessons/trail/recordingFolderHistory/) and [tour 2](https://git-mastery.org/lessons/trail/backingUpOnCloud/) of the Git-Mastery curriculum! - -## Contributor's progression - -You are welcome to start contributing to Git-Mastery in any way you see fit! - -However, if you are looking for a certain angle to start at, we recommend [contributing 1-2 hands-on](/developers/docs/contributing/hands-on) before diving into [contributing exercises](/developers/docs/contributing/exercise)! - -## Noticed something wrong with the documentation? - -File an issue on the [repository](https://github.com/git-mastery/developers/issues/new)! diff --git a/index.md b/index.md new file mode 100644 index 0000000..9f45be9 --- /dev/null +++ b/index.md @@ -0,0 +1,49 @@ +--- +title: Home +layout: home +nav_order: 1 +--- + +# Git-Mastery Developers + +{: .fs-6 .fw-300 } +Developer documentation for the Git-Mastery ecosystem. + +## What is Git-Mastery? + +Git-Mastery is a Git education tool developed by the National University of Singapore School of Computing to help students master Git through hands-on, guided tours with real-world scenario exercises. + +## Try Git-Mastery! + +Before contributing to Git-Mastery, it would be good if you tried it out to get a sense of what students experience. + +To setup Git-Mastery, refer to this setup guide for your OS. + +We recommend following along with [tour 1](https://git-mastery.org/lessons/trail/recordingFolderHistory/index.html) and [tour 2](https://git-mastery.org/lessons/trail/backingUpOnCloud/index.html) of the Git-Mastery curriculum! + +## Start here + +- [Getting started](/developers/docs/getting-started) +- [Overview](/developers/docs/overview) +- [Exercises](/developers/docs/exercises) +- [App](/developers/docs/app) +- [Shared libraries](/developers/docs/libraries) + +## Repositories + +- [`exercises`](https://github.com/git-mastery/exercises) +- [`app`](https://github.com/git-mastery/app) +- [`repo-smith`](https://github.com/git-mastery/repo-smith) +- [`git-autograder`](https://github.com/git-mastery/git-autograder) +- [`difflib-parser`](https://github.com/git-mastery/difflib-parser) +- [`progress-dashboard`](https://github.com/git-mastery/progress-dashboard) + +## First contribution recommendation + +If you are looking for a good starting point, begin with 1 to 2 hands-ons in `exercises` before moving into exercises, CLI and library work! + +Refer to [issues](https://github.com/git-mastery/exercises/issues) tagged with `good first issue` for recommended starting points. + +## Noticed something wrong with the documentation? + +File an issue on the [repository](https://github.com/git-mastery/developers/issues/new)!