style(no-slop): remove every em-dash + banned words across all modules + capstone

Apply the no-ai-slop standard (now binding in AGENTS.md): the em-dash character is
banned outright (restructured, not blind-replaced), plus the banned word/phrase
list (delve, leverage, robust, seamless, truly, unlock, etc.). 0 em-dashes remain
in modules + capstone; the only "robust" left is the planted M10 ai-change.patch
trap. Module H1 titles use a colon separator.

All deliberate teaching devices preserved; labs compile/parse (py/sh/yaml/json);
no junk. AGENTS.md updated with the hard no-slop rules.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TfzV5QvtPDz8LJS3Pu5VLT
This commit is contained in:
2026-06-22 23:21:09 -04:00
parent 513d7e7ac8
commit 389ac2e460
99 changed files with 1324 additions and 1315 deletions
+66 -66
View File
@@ -1,4 +1,4 @@
# Module 8 Remotes and Hosting: GitHub, the Alternatives, and Owning Your Repo
# Module 8: Remotes and Hosting (GitHub, the Alternatives, and Owning Your Repo)
> **One repo on one laptop is one spilled coffee away from gone.** A remote gets your history
> off your machine and somewhere durable. And because every clone carries the full history, a
@@ -8,13 +8,13 @@
## Prerequisites
- **Module 2** you have a Git repo (`tasks-app`) with real commits, and you understand commits as
- **Module 2**: you have a Git repo (`tasks-app`) with real commits, and you understand commits as
checkpoints and the repo as durable memory. This module gets that history *off the one disk it
lives on*.
- **Module 5** you committed your agentic tool's instructions file into the repo. A remote is what
- **Module 5**: you committed your agentic tool's instructions file into the repo. A remote is what
finally makes that config *shared*: push it once and every teammate (and every agent) pulls the
same setup.
- **Module 6** you can work on branches. Pushing is per-branch, so knowing what a branch is matters
- **Module 6**: you can work on branches. Pushing is per-branch, so knowing what a branch is matters
here.
Helpful but not required: **Module 7** (worktrees). Everything below works the same whether you have
@@ -26,12 +26,12 @@ one working directory or several.
By the end of this module you can:
1. Explain what a remote *is* a named pointer to another copy of the same repo and why "it's just
1. Explain what a remote *is* (a named pointer to another copy of the same repo) and why "it's just
another copy" is the whole reason hosting is provider-neutral.
2. Add a remote, push your history to it, and pull changes back, on any forge, with the same commands.
3. Recover from the three failure modes that bite everyone on first push: authentication, a
non-empty remote, and a branch-name mismatch.
4. Choose a host deliberately hosted vs. self-hosted using a current, dated comparison instead of
4. Choose a host deliberately, hosted vs. self-hosted, using a current, dated comparison instead of
defaulting to GitHub by reflex.
5. State precisely where "pushing to a remote" is and isn't a backup, and how a normal team workflow
accidentally satisfies most of the 3-2-1 rule.
@@ -68,7 +68,7 @@ git clone <URL> # make a brand-new local copy from a remote (histo
```
`origin` is just the conventional name for "the place I push to." You can have more than one remote
(a personal fork *and* the team's repo, say), and they can live on different hosts entirely one on
(a personal fork *and* the team's repo, say), and they can live on different hosts entirely: one on
a SaaS forge, one on a box in your closet. Git doesn't care.
### Getting a remote: you create the empty repo first
@@ -77,13 +77,13 @@ The one piece the commands above assume is that a remote repo *exists* to push i
the shape is the same:
1. In the host's web UI (or its CLI/API), create a **new, empty** repository. Give it a name; do
**not** let it add a README, license, or `.gitignore` you want it empty so your local history
**not** let it add a README, license, or `.gitignore`; you want it empty so your local history
is the first thing in it.
2. Copy the URL it gives you. You'll see two flavours:
- **HTTPS** `https://host/you/tasks-app.git`. Authenticates with a username + a personal access
token (not your account password password auth over Git is gone on essentially every modern
- **HTTPS**: `https://host/you/tasks-app.git`. Authenticates with a username + a personal access
token (not your account password; password auth over Git is gone on essentially every modern
host).
- **SSH** `git@host:you/tasks-app.git`. Authenticates with an SSH key you've added to your
- **SSH**: `git@host:you/tasks-app.git`. Authenticates with an SSH key you've added to your
account. More setup once, less friction forever.
3. Register the remote on the local side and push the history up. The shape of that exchange, with a
first push to an empty remote, looks like this:
@@ -128,15 +128,15 @@ and the callout below walks the shape of getting one.
> The exact menu names and scope labels drift per host, so treat these as the *shape*, not gospel
> (**Verify-before-publish** the specific UI wording for your forge):
>
> - **Scope is the gotcha check it first.** In the host's **Settings → developer / access tokens →
> - **Scope is the gotcha; check it first.** In the host's **Settings → developer / access tokens →
> create token**, you must grant the token write access to repositories: usually a scope literally
> named `repo`, or a "read **and write**" toggle on the repositories resource. A token created
> *without* it authenticates and then `403`s on push it looks like an auth failure, but the fix is
> *without* it authenticates and then `403`s on push; it looks like an auth failure, but the fix is
> to **edit the token's scopes**, not to delete and recreate it.
> - **The token is shown once.** Hosts reveal the value a single time at creation. Copy it the moment
> it appears; if you lose it you create a new one rather than recover the old.
> - **Pasting it is invisible, and only happens once.** When Git prompts for your "password," paste
> the token most terminals show *nothing* as you paste a secret, which is normal, not a failure.
> the token; most terminals show *nothing* as you paste a secret, which is normal, not a failure.
> A **credential helper** (`git config --global credential.helper …`, e.g. `store`, `cache`, or your
> OS keychain) remembers it after the first success so you aren't pasting it on every push.
> - **SSH is the alternative.** A key you've added to the host skips passwords entirely: more setup
@@ -145,18 +145,18 @@ and the callout below walks the shape of getting one.
**2. The remote isn't empty (non-fast-forward).** You let the host create the repo *with* a README,
then push, and get `! [rejected] ... (fetch first)` or `non-fast-forward`. The remote has a commit
your local history doesn't, so Git refuses to overwrite it. The simple fix is to **recreate the remote
empty** and push again. (The alternative you'll see online `git pull --rebase origin main`, then
push replays your commits on top of the remote's, but `rebase` is an advanced, history-rewriting
empty** and push again. (The alternative you'll see online is `git pull --rebase origin main` then
push: it replays your commits on top of the remote's, but `rebase` is an advanced, history-rewriting
operation this course doesn't teach as a step here, so prefer the empty-remote fix for now. And note
that plain `git pull` won't rescue you against an auto-README remote it refuses to merge unrelated
that plain `git pull` won't rescue you against an auto-README remote; it refuses to merge unrelated
histories.) This is the same "someone else pushed before me" situation you'll hit constantly once
you're collaborating Module 11 except here the "someone else" was the host's auto-generated README.
you're collaborating (Module 11), except here the "someone else" was the host's auto-generated README.
**3. Branch-name mismatch.** Your local default branch is `master` but the host expects `main` (or
vice versa). `git push -u origin main` then errors with `src refspec main does not match any`. Fix:
check what you actually have with `git branch`, and either push the branch you have
(`git push -u origin master`) or rename it first (`git branch -m main`). If you initialized with
`git init -b main` back in Module 2, you're already on `main` and this one won't bite you here — but
`git init -b main` back in Module 2, you're already on `main` and this one won't bite you here. But
it's the classic wall for any repo that started life on `master`, so it's worth recognizing.
### Pull, fetch, and the everyday loop
@@ -168,9 +168,9 @@ Once the remote exists, day-to-day work adds two moves to the Module 2 loop:
- **`git push`** after you've committed, to send your new checkpoints up.
When you want to *see* what the remote has before you let it touch your working files, use
**`git fetch`** instead it downloads the remote's commits into `origin/main` but leaves your branch
**`git fetch`** instead: it downloads the remote's commits into `origin/main` but leaves your branch
untouched, so you can `git log main..origin/main` to read exactly what's incoming before merging.
That "look before you leap" habit matters more the moment other contributors human or agent are
That "look before you leap" habit matters more the moment other contributors (human or agent) are
pushing to the same place.
### Choosing a host: the comparison
@@ -183,10 +183,10 @@ for a team with on-prem, air-gapped, or data-control requirements (a real and co
this audience) it may be the wrong default. The genuine choice is between **hosted** (someone runs
the forge; you just use it) and **self-hosted** (you run the forge on your own infrastructure).
> ### Hosting comparison as of 2026-06-22
> ### Hosting comparison (as of 2026-06-22)
>
> Pricing and feature claims drift fast. Everything in these two tables was checked on the date above
> and must be re-verified before you rely on it see the **Verify-before-publish** checklist at the
> and must be re-verified before you rely on it; see the **Verify-before-publish** checklist at the
> end. List prices are per-user/month at the entry paid tier, billed annually, in USD; promotional
> and volume discounts are common and not shown.
@@ -194,18 +194,18 @@ the forge; you just use it) and **self-hosted** (you run the forge on your own i
| Platform | Pricing (entry → paid) | Built-in CI/CD | AI-tooling integration | Ease of operation |
|---|---|---|---|---|
| **GitHub** | Free; Team ~$4/user; Enterprise ~$21/user | GitHub Actions, built in (Free tier includes a monthly minutes allowance for private repos; unlimited for public) | **Deepest.** Most agents, MCP servers, and AI reviewers target GitHub first | Zero ops pure SaaS |
| **GitLab** (SaaS) | Free (capped users/namespace, small CI allowance); Premium ~$29/user; Ultimate ~$99/user | GitLab CI/CD among the most mature, deeply integrated pipelines | Strong; first-party AI assistant plus growing agent support | Zero ops as SaaS; also self-hostable (see below) |
| **GitHub** | Free; Team ~$4/user; Enterprise ~$21/user | GitHub Actions, built in (Free tier includes a monthly minutes allowance for private repos; unlimited for public) | **Deepest.** Most agents, MCP servers, and AI reviewers target GitHub first | Zero ops, pure SaaS |
| **GitLab** (SaaS) | Free (capped users/namespace, small CI allowance); Premium ~$29/user; Ultimate ~$99/user | GitLab CI/CD, among the most mature, deeply integrated pipelines | Strong; first-party AI assistant plus growing agent support | Zero ops as SaaS; also self-hostable (see below) |
| **Bitbucket** (Atlassian) | Free (≤5 users); Standard ~$3.65/user; Premium ~$7.25/user | Pipelines, built in (small free monthly build-minute allowance) | Growing; tightest value is deep Jira/Atlassian tie-in | Zero ops as SaaS; Data Center edition self-hostable (enterprise pricing) |
| **Azure DevOps** | First 5 users free; Basic ~$6/user beyond; pipelines ~$40/parallel job after a free job | Azure Pipelines, built in (one free parallel job + monthly minutes) | Good within the Microsoft ecosystem; Copilot integration | Zero ops as SaaS; Azure DevOps Server self-hostable |
| **Codeberg** | Free (FOSS projects only; soft repo/storage caps) | Forgejo Actions (it runs Forgejo) | Via API/MCP; not a first-tier agent target | Zero ops; nonprofit-run, no commercial/closed-source hosting |
| **SourceHut** | Paid to host: ~$5 / $10 / $15 (all tiers buy the *same* service "pay what's fair"); reduced ~$2 rate / financial aid if the full price is a hardship; free to *contribute* | builds.sr.ht, built in | Minimal first-class AI tooling; reachable via API | Zero ops as SaaS; fully self-hostable (it's open source) |
| **SourceHut** | Paid to host: ~$5 / $10 / $15 (all tiers buy the *same* service, "pay what's fair"); reduced ~$2 rate / financial aid if the full price is a hardship; free to *contribute* | builds.sr.ht, built in | Minimal first-class AI tooling; reachable via API | Zero ops as SaaS; fully self-hostable (it's open source) |
**Self-hostable open-source forges (you run it):**
| Forge | License / cost | Built-in CI/CD | AI-tooling integration | Ease of operation |
|---|---|---|---|---|
| **Forgejo** | Free, open source (you pay infra + ops) | Forgejo Actions runs GitHub-Actions-compatible workflow YAML | Full REST API; community MCP servers; agents work over git + API | **Easiest.** Single Go binary, runs on a tiny VPS (~256 MB RAM). Community/nonprofit governed |
| **Forgejo** | Free, open source (you pay infra + ops) | Forgejo Actions, runs GitHub-Actions-compatible workflow YAML | Full REST API; community MCP servers; agents work over git + API | **Easiest.** Single Go binary, runs on a tiny VPS (~256 MB RAM). Community/nonprofit governed |
| **Gitea** | Free, open source | Gitea Actions (GitHub-Actions-compatible YAML) | Full REST API; community MCP servers | Single Go binary, same light footprint as Forgejo; company-backed |
| **GitLab CE** | Free, open source | Full GitLab CI/CD + container registry + more, in one install | Same first-party AI direction as GitLab SaaS, self-hosted | **Heaviest.** Wants ~8 GB+ RAM (Postgres/Redis/Sidekiq/Gitaly); upgrades can't skip versions |
| **Gogs** | Free, open source | None built in | API only | Lightest of all; single binary, runs on a Raspberry Pi. Slower development; no CI |
@@ -214,7 +214,7 @@ the forge; you just use it) and **self-hosted** (you run the forge on your own i
Two things to read out of those tables rather than memorize the numbers:
- **GitLab spans both camps.** It's a hosted SaaS *and* a self-hostable Community Edition from the
same project useful if you want SaaS now and the *option* to bring it in-house later without
same project; useful if you want SaaS now and the *option* to bring it in-house later without
changing tools.
- **"Self-hosted" trades a per-user bill for an ops bill.** The license is free; your cost is the
server, the upgrades, the backups, and the on-call. Forgejo/Gitea make that bill small (a single
@@ -224,10 +224,10 @@ Two things to read out of those tables rather than memorize the numbers:
### The self-hosted-forge track (optional)
If you're in the air-gapped/on-prem audience, you can run this module's lab against a forge you stand
up yourself instead of a SaaS account. The teaching point is precisely that **nothing changes** you
up yourself instead of a SaaS account. The teaching point is precisely that **nothing changes**: you
create an empty repo on your forge, copy its URL, `git remote add origin <URL>`, and `git push`. The
lab below flags exactly where the only difference is (the URL and how you authenticate to your own
box). Standing the forge up is its own exercise Forgejo or Gitea is a single binary and the fastest
box). Standing the forge up is its own exercise; Forgejo or Gitea is a single binary and the fastest
path; the *git* half is identical to the hosted track.
### Backup thesis, part one: distribution is the backup
@@ -241,8 +241,8 @@ Recall the standard **3-2-1 backup rule**: keep **3** copies of your data, on **
with **1** offsite. Now look at what a normal team doing normal work ends up with, without anyone
"doing backups":
- Your laptop has a full copy **complete history**, not just current files.
- The remote has a full copy **offsite**, on someone else's hardware (or your other box).
- Your laptop has a full copy: **complete history**, not just current files.
- The remote has a full copy: **offsite**, on someone else's hardware (or your other box).
- Every teammate who has cloned the repo has *another* full copy, each with the entire history,
because **clone copies everything**, not a snapshot.
@@ -255,13 +255,13 @@ a forge and a working team almost for free.
Be precise about the division of labor, because the course is honest about where analogies stop:
- **Recovery power comes from commits (Module 2, and Module 12 for the harder cases).** That's your
point-in-time restore go back to any checkpoint.
point-in-time restore: go back to any checkpoint.
- **Backup power comes from remotes and distribution (this module).** That's your offsite,
redundant, survives-the-disk copy.
You need both. Commits without a remote survive a mistake but not a dead drive. A remote without good
commits survives a dead drive but gives you a junk drawer to restore from. Module 12 picks up the
*recovery* half in full and is just as honest about what Git is **not** a backup for your database,
*recovery* half in full and is just as honest about what Git is **not** a backup for: your database,
your secrets, your uncommitted work, your large binaries. We'll hold that thought there.
---
@@ -275,14 +275,14 @@ A remote isn't only about durability. It's what the AI parts of this course run
operate on the *remote* repo through its API and web UI. Until your history is pushed, none of that
machinery has anything to act on. A remote is the precondition for every agent-in-the-loop module
that follows.
- **GitHub's "integrates first" status is a real, current bias name it, then decide.** Because the
- **GitHub's "integrates first" status is a real, current bias; name it, then decide.** Because the
largest forge is where AI tooling lands first, picking a less-common host or self-hosting can mean
thinner first-class agent support and more wiring-it-yourself over the API. That's a legitimate cost
to weigh against control and data-residency *not* a reason to abandon the choice. The git
to weigh against control and data-residency; *not* a reason to abandon the choice. The git
mechanics are identical everywhere; it's the AI ecosystem maturity that varies, and that gap is the
thing to check (it narrows constantly).
- **The committed AI config from Module 5 only pays off once it's pushed.** Locally, your agent's
instructions file just configures *your* agent. Pushed to the remote, it configures *everyone's*
instructions file just configures *your* agent. Pushed to the remote, it configures *everyone's*:
every teammate who clones, and every automated agent that later operates on the repo, inherits the
same conventions instead of each drifting into a private setup. The remote is what turns "my AI
config" into "the project's AI config."
@@ -308,13 +308,13 @@ WSL, or Git Bash on Windows. Continues the `tasks-app` repo from Module 2.
to your account. This is the one part you set up by hand in the host's web UI, since it's account
security, not git. Do it first; failure mode #1 above is the most common first-push wall.
- Claude Code (or sub your own agent) in your terminal, set up as in Module 4. In this lab you
*direct the agent* to do the git work add the remote, push, clone, fetch, pull and you verify
*direct the agent* to do the git work (add the remote, push, clone, fetch, pull) and you verify
each result yourself. You don't type the git commands by hand.
### Part A Create the empty remote and push
### Part A: Create the empty remote and push
1. On your host's web UI, create a **new, empty** repository named `tasks-app`. Do **not** add a
README, license, or `.gitignore` leave it empty so your local history goes in clean. Copy the URL
README, license, or `.gitignore`; leave it empty so your local history goes in clean. Copy the URL
it shows you (HTTPS or SSH).
> **Self-hosted track:** identical step, on your own forge's UI. The only thing that differs from
@@ -342,10 +342,10 @@ WSL, or Git Bash on Windows. Continues the `tasks-app` repo from Module 2.
commit history from Module 2 are now sitting on hardware that is not your laptop. **That is the
backup half the course promised.**
### Part B Prove distribution is redundancy
### Part B: Prove distribution is redundancy
You're going to demonstrate the 3-2-1 claim with your own eyes: that a clone is a *complete,
independent* copy, history and all not a snapshot.
independent* copy, history and all, not a snapshot.
4. Direct your agent to make a change and ship it in one go:
@@ -379,16 +379,16 @@ independent* copy, history and all — not a snapshot.
The script confirms (a) you have a remote configured, (b) your local branch is fully pushed
(nothing stranded only on your disk), and (c) a fresh clone of the remote carries the exact same
commit count as your local repo i.e. the offsite copy is complete, not partial. Read its output;
commit count as your local repo, i.e. the offsite copy is complete, not partial. Read its output;
the green line is your evidence that the backup is real.
> On the **HTTPS + token** path with a *private* repo, the clone check (c) needs your credential
> helper to have cached the token from your earlier push otherwise it can't authenticate to clone.
> helper to have cached the token from your earlier push; otherwise it can't authenticate to clone.
> The script won't hang waiting for a prompt (it disables interactive credential prompts); it just
> reports a `NOTE` that it couldn't clone, and the push checks above still stand. SSH and public
> repos clone with no credential at all.
### Part C The everyday loop
### Part C: The everyday loop
7. From the *teammate* clone, direct your agent to make and ship a change:
@@ -415,7 +415,7 @@ independent* copy, history and all — not a snapshot.
you let it touch your files. You've now pushed *and* pulled across two independent copies through
one remote, the complete remotes mechanic.
### Part D (optional) A second remote
### Part D (optional): A second remote
9. Direct your agent to add a *second* remote (a personal fork on another host, or even a bare repo on
a USB drive or a box on your LAN) and push to it too:
@@ -430,20 +430,20 @@ independent* copy, history and all — not a snapshot.
## Where it breaks
The honest limits the backup analogy especially needs them.
The honest limits; the backup analogy especially needs them.
- **A remote backs up what you *pushed*, nothing else.** Uncommitted edits, untracked files, and
anything `.gitignore` excludes (like `tasks.json` runtime state) never leave your laptop. "I pushed"
is not "everything is safe" it's "every *committed and pushed* change is safe." The defense is the
is not "everything is safe"; it's "every *committed and pushed* change is safe." The defense is the
Module 2 habit: commit often, and now, push often too.
- **Git is not a backup for non-Git things.** Your database, your secrets (which shouldn't be in the
repo anyway Module 17), large binaries, and build artifacts are not covered by pushing code. The
repo anyway, see Module 17), large binaries, and build artifacts are not covered by pushing code. The
3-2-1-by-accident win applies to your *versioned source*, full stop. Module 12 is blunt about this.
- **One remote is one vendor.** Distribution across a team is great redundancy against *disk* failure;
it's weaker against *account* failure. If your whole team only ever pushes to one host and that
account is suspended, locked, or the provider has an outage, your offsite copy is temporarily out of
reach (your local clones are fine). Part D's second remote, or a periodic clone to storage you
control, is the answer for anyone who needs it — and it's the on-ramp to the self-hosting argument.
control, is the answer for anyone who needs it. It's also the on-ramp to the self-hosting argument.
- **"GitHub integrates first" is true today and a moving target.** Don't treat the AI-ecosystem gap
between hosts as permanent; it's exactly the kind of claim that ages. Re-check it for your tooling
before you let it decide your host.
@@ -461,16 +461,16 @@ The honest limits — the backup analogy especially needs them.
- You have pushed at least one commit and pulled at least one commit back, across two copies of the
repo through one remote.
- `verify-backup.sh` reports a clean, fully-pushed state and a clone whose commit count matches your
local repo's you've *seen* that the offsite copy is complete.
local repo's: you've *seen* that the offsite copy is complete.
- You can explain, in your own words, why a four-person team pushing to one remote roughly satisfies
3-2-1 without running a backup tool and name two things that win does *not* cover.
3-2-1 without running a backup tool, and name two things that win does *not* cover.
- You can state why the choice of host is a logistics decision, not a Git one, and name at least one
hosted alternative to GitHub and one self-hostable forge.
When pushing feels like the natural end of "commit" and you trust that your history is no longer
trapped on one disk, you have the *backup* half of the backup-and-recovery thread. Module 9 starts
using the remote for more than storage issues, the task layer where humans and agents pick up
work and Module 12 returns to finish the *recovery* half.
using the remote for more than storage (issues, the task layer where humans and agents pick up
work), and Module 12 returns to finish the *recovery* half.
---
@@ -479,27 +479,27 @@ work — and Module 12 returns to finish the *recovery* half.
This module makes dated pricing and feature claims that drift. Re-check each before relying on the
tables, and update the "as of" date when you do.
- [ ] **GitHub** tiers and prices Free / Team / Enterprise per-user/month, and the Free-tier CI
- [ ] **GitHub** tiers and prices: Free / Team / Enterprise per-user/month, and the Free-tier CI
minutes allowance for private repos.
- [ ] **GitLab** tiers Free (user/namespace caps, CI allowance), Premium, Ultimate per-user/month,
- [ ] **GitLab** tiers: Free (user/namespace caps, CI allowance), Premium, Ultimate per-user/month,
and the SaaS-vs-self-managed price split.
- [ ] **Bitbucket** tiers Free user cap, Standard (~$3.65), Premium (~$7.25) per-user/month, and
- [ ] **Bitbucket** tiers: Free user cap, Standard (~$3.65), Premium (~$7.25) per-user/month, and
free build-minute allowance. (Reconciled against Atlassian's own pricing page on 2026-06-22;
stale third-party listings still quote ~$2/$5 trust Atlassian's page, and re-confirm.)
- [ ] **Azure DevOps** free-user count, Basic per-user/month, and the per-parallel-job pipeline
stale third-party listings still quote ~$2/$5; trust Atlassian's page, and re-confirm.)
- [ ] **Azure DevOps**: free-user count, Basic per-user/month, and the per-parallel-job pipeline
price plus free job/minutes.
- [ ] **Codeberg** that it remains FOSS-only and free, and its current soft repo/storage caps.
- [ ] **SourceHut** paid-to-host tiers ($5/$10/$15): the 2026 prices are now *in effect* for new
- [ ] **Codeberg**: that it remains FOSS-only and free, and its current soft repo/storage caps.
- [ ] **SourceHut** paid-to-host tiers ($5/$10/$15): the 2026 prices are now *in effect* for new
accounts (confirmed 2026-06-22), so they're no longer "proposed." Note all tiers buy the same
service ("pay what's fair"), with a reduced rate (~the earlier minimum) and financial aid for
hardship re-confirm before relying on it.
- [ ] **Self-hosted forges** that Forgejo/Gitea still ship GitHub-Actions-compatible CI, GitLab CE's
hardship; re-confirm before relying on it.
- [ ] **Self-hosted forges**: that Forgejo/Gitea still ship GitHub-Actions-compatible CI, GitLab CE's
current minimum resource footprint, and whether OneDev/Gogs CI status has changed.
- [ ] **"GitHub integrates first" / AI-ecosystem maturity** re-assess which forges are first-tier
- [ ] **"GitHub integrates first" / AI-ecosystem maturity**: re-assess which forges are first-tier
agent and MCP targets; this gap narrows fast.
- [ ] **Self-host/hosted spans** confirm GitLab still offers CE self-host, and Bitbucket/Azure DevOps
- [ ] **Self-host/hosted spans**: confirm GitLab still offers CE self-host, and Bitbucket/Azure DevOps
still offer their self-hostable editions, before describing either as spanning both camps.
- [ ] **Credential/token UI** the "Getting a credential" callout names menu paths and the
- [ ] **Credential/token UI**: the "Getting a credential" callout names menu paths and the
write-scope label (`repo` / "read and write") generically; confirm the current wording and
scope name on the default-example host before publishing.
- [ ] Update the comparison's **"as of" date** to the build date.
@@ -1,13 +1,13 @@
#!/usr/bin/env bash
#
# verify-backup.sh prove that your remote is a real, complete offsite backup.
# verify-backup.sh: prove that your remote is a real, complete offsite backup.
#
# Module 8 lab helper. Run it from inside your tasks-app repo:
# bash verify-backup.sh
#
# It checks three things, the three that make "I pushed" actually mean "it's backed up":
# 1. A remote is configured at all.
# 2. Your current branch is fully pushed no commits stranded only on this disk.
# 2. Your current branch is fully pushed; no commits stranded only on this disk.
# 3. A fresh clone of the remote carries the EXACT SAME commit count as your local repo,
# i.e. the offsite copy is the whole history, not a snapshot.
#
@@ -64,7 +64,7 @@ if [ -z "$upstream" ]; then
else
ahead="$(git rev-list --count "${upstream}..HEAD" 2>/dev/null || echo "?")"
if [ "$ahead" = "0" ]; then
pass "Branch '$branch' is fully pushed to $upstream nothing stranded on this disk."
pass "Branch '$branch' is fully pushed to $upstream, nothing stranded on this disk."
else
fail "Branch '$branch' is $ahead commit(s) ahead of $upstream. Run: git push"
status=1
@@ -85,7 +85,7 @@ if git clone --quiet "$remote_url" "$tmp/clone" 2>/dev/null; then
fi
if [ "$clone_count" = "$local_count" ]; then
pass "Fresh clone has $clone_count commit(s) identical to your local $local_count."
pass "Fresh clone has $clone_count commit(s), identical to your local $local_count."
printf "\n%sThe offsite copy is COMPLETE: every commit, not just the latest files.%s\n" "$GREEN$BOLD" "$RESET"
printf "That is the backup half of the course's backup-and-recovery thread.\n"
else