diff --git a/Makefile b/Makefile index 38f0499..233d326 100644 --- a/Makefile +++ b/Makefile @@ -67,12 +67,16 @@ package: ## Build release zip locally (same output as CI) # ----------------------------------------------------------------------------- .PHONY: lint -lint: lint-spell lint-i18n lint-nav lint-links lint-a11y lint-shell lint-ci lint-powershell ## Run all linters +lint: lint-spell lint-content lint-i18n lint-nav lint-links lint-a11y lint-shell lint-ci lint-powershell ## Run all linters .PHONY: lint-spell lint-spell: ## Run cspell on FR + EN content npm run spell +.PHONY: lint-content +lint-content: ## Check content for forbidden git command patterns (regression prevention) + @scripts/makefile/run-lint-content.sh + .PHONY: lint-i18n lint-i18n: ## Check FR/EN page parity npm run check:i18n @@ -136,7 +140,7 @@ ifeq ($(BATS),) @exit 1 else @echo "▶ Running bats tests…" - bats $(TESTS_DIR) + bats $(TESTS_DIR) tests/lib endif .PHONY: test-pester diff --git a/exercises/01-la-guilde-des-archivistes/verifier.sh b/exercises/01-la-guilde-des-archivistes/verifier.sh index dddbeb7..c0c8907 100755 --- a/exercises/01-la-guilde-des-archivistes/verifier.sh +++ b/exercises/01-la-guilde-des-archivistes/verifier.sh @@ -22,5 +22,7 @@ show_banner "$QUEST_TITLE" check_step 1 "Git est installé" 'command -v git >/dev/null 2>&1' check_step 2 "Ton nom est configuré" '[ -n "$(git config --global user.name)" ]' check_step 3 "Ton email est configuré" '[ -n "$(git config --global user.email)" ]' +check_step 4 "Ta branche par défaut est configurée sur 'main'" \ + '[ "$(git config --global init.defaultBranch)" = "main" ]' show_score diff --git a/exercises/02-les-trois-salles-du-savoir/verifier.sh b/exercises/02-les-trois-salles-du-savoir/verifier.sh index d72ab6a..64dbda6 100755 --- a/exercises/02-les-trois-salles-du-savoir/verifier.sh +++ b/exercises/02-les-trois-salles-du-savoir/verifier.sh @@ -28,16 +28,16 @@ check_step 1 \ "git rev-parse --is-inside-work-tree" \ || true -# ── Step 2 : Le fichier parchemin.txt existe ─────────────────────────────── +# ── Step 2 : Le fichier parchemin.txt existe et contient du texte ────────── check_step 2 \ "Le fichier parchemin.txt existe dans la Salle de Travail" \ - "test -f parchemin.txt" \ + "test -s parchemin.txt" \ || true # ── Step 3 : parchemin.txt est dans le staging area ──────────────────────── check_step 3 \ "parchemin.txt est dans la Salle de Préparation (staging area)" \ - "git diff --cached --name-only | grep -q parchemin.txt" \ + "git diff --cached --name-only | grep -qFx parchemin.txt" \ || true # ── Step 4 : Pas encore de commit (pas de HEAD valide) ───────────────────── diff --git a/exercises/03-le-premier-parchemin/verifier.sh b/exercises/03-le-premier-parchemin/verifier.sh index 5f86934..461cde8 100755 --- a/exercises/03-le-premier-parchemin/verifier.sh +++ b/exercises/03-le-premier-parchemin/verifier.sh @@ -45,4 +45,8 @@ check_step 4 "Ton premier commit a un message personnalisé" \ && [ "$premier_msg" != "init" ] ' +# --- Step 5 : mission.txt a été modifié depuis le parchemin fourni --- +check_step 5 "Tu as complété le parchemin (mission.txt diffère du modèle fourni)" \ + '! cmp -s mission.txt "$SCRIPT_DIR/parchemins/mission.txt"' + show_score diff --git a/exercises/04-larchive-est-partout/verifier.sh b/exercises/04-larchive-est-partout/verifier.sh index b05dcb2..09b588b 100755 --- a/exercises/04-larchive-est-partout/verifier.sh +++ b/exercises/04-larchive-est-partout/verifier.sh @@ -32,25 +32,62 @@ WORKDIR="$(cd "$WORKDIR" 2>/dev/null && pwd)" || { printf " %sDossier analyse : %s%s\n\n" "${CLR_CYAN}" "$WORKDIR" "${CLR_RESET}" +show_section "Setup des depots" + # ---- Step 1 : ma-copie/ existe et c'est un repo git ---- check_step 1 "ma-copie/ existe et c'est un repo Git" \ '[ -d "$WORKDIR/ma-copie/.git" ]' -# ---- Step 2 : ma-copie a une remote origin pointant vers un chemin local ---- -check_step 2 "ma-copie/ a une remote origin vers un chemin local" \ - 'grep -q "url = " "$WORKDIR/ma-copie/.git/config" 2>/dev/null && \ - ! grep "url = http" "$WORKDIR/ma-copie/.git/config" >/dev/null 2>&1 && \ - ! grep "url = git@" "$WORKDIR/ma-copie/.git/config" >/dev/null 2>&1' +# ---- Step 2 : ma-copie/ a une remote nommee 'origin' ---- +check_step 2 "ma-copie/ a une remote nommee 'origin'" \ + 'assert_remote_name "$WORKDIR/ma-copie" "origin"' + +# ---- Step 3 : ma-copie/ est sur la branche 'main' ---- +check_step 3 "ma-copie/ est sur la branche 'main'" \ + 'assert_branch_is "$WORKDIR/ma-copie" "main"' + +show_section "Depot central (bare)" -# ---- Step 3 : archive-centrale.git/ est un bare repo ---- -check_step 3 "archive-centrale.git/ existe et c'est un bare repo" \ +# ---- Step 4 : archive-centrale.git/ est un bare repo ---- +check_step 4 "archive-centrale.git/ existe et c'est un bare repo" \ '[ -f "$WORKDIR/archive-centrale.git/HEAD" ] && \ [ -d "$WORKDIR/archive-centrale.git/objects" ] && \ [ -d "$WORKDIR/archive-centrale.git/refs" ] && \ ! [ -d "$WORKDIR/archive-centrale.git/.git" ]' -# ---- Step 4 : clone-depuis-bare/ existe et c'est un repo git ---- -check_step 4 "clone-depuis-bare/ existe et c'est un repo Git" \ +# ---- Step 5 : archive-centrale.git/HEAD pointe vers main ---- +check_step 5 "archive-centrale.git/ pointe HEAD vers main" \ + '[ "$(cat "$WORKDIR/archive-centrale.git/HEAD" 2>/dev/null)" = "ref: refs/heads/main" ]' + +show_section "Clone depuis le bare" + +# ---- Step 6 : clone-depuis-bare/ existe et c'est un repo git ---- +check_step 6 "clone-depuis-bare/ existe et c'est un repo Git" \ '[ -d "$WORKDIR/clone-depuis-bare/.git" ]' +# ---- Step 7 : clone-depuis-bare/ est sur la branche 'main' ---- +check_step 7 "clone-depuis-bare/ est sur la branche 'main'" \ + 'assert_branch_is "$WORKDIR/clone-depuis-bare" "main"' + +# ---- Step 8 : clone-depuis-bare/ a une remote nommee 'origin' ---- +check_step 8 "clone-depuis-bare/ a une remote nommee 'origin'" \ + 'assert_remote_name "$WORKDIR/clone-depuis-bare" "origin"' + +show_section "Depot source lie au bare" + +# ---- Step 9 : mon-archive/ a une remote nommee 'origin' ---- +# This is the pedagogical action taught in the quest body: inside +# mon-archive, the student must 'git remote add origin ../archive-centrale.git' +# and 'git push origin main'. Before the rigor sweep, this action was +# completely untested because the verifier only looked at ma-copie +# (whose remote comes from the automatic clone, not the taught step). +check_step 9 "mon-archive/ a une remote nommee 'origin'" \ + 'assert_remote_name "$WORKDIR/mon-archive" "origin"' + +show_section "Coherence inter-depots" + +# ---- Step 10 : Les trois depots partagent le meme HEAD ---- +check_step 10 "Les trois depots partagent le meme HEAD" \ + 'assert_same_head "$WORKDIR/mon-archive" "$WORKDIR/ma-copie" "$WORKDIR/clone-depuis-bare"' + show_score diff --git a/exercises/05-les-lignes-du-temps/verifier.sh b/exercises/05-les-lignes-du-temps/verifier.sh index 4eb8d35..c9709b4 100755 --- a/exercises/05-les-lignes-du-temps/verifier.sh +++ b/exercises/05-les-lignes-du-temps/verifier.sh @@ -27,9 +27,11 @@ check_step 1 "Tu es dans un dépôt Git" \ check_step 2 "Le fichier .gitignore existe" \ '[ -f .gitignore ]' -# ---- Step 3 : Le .gitignore contient un pattern pour les .log ---- +# ---- Step 3 : .gitignore couvre effectivement les fichiers .log ---- +# We rely on git check-ignore rather than a hardcoded regex so that the +# student can use any valid pattern (*.log, **/*.log, debug.log, ...). check_step 3 "Le .gitignore ignore les fichiers .log" \ - 'grep -qE "^\*\.log$|^debug\.log$" .gitignore' + 'printf "%s\n" "debug.log" | git check-ignore --stdin -q' # ---- Step 4 : debug.log n'est plus tracké ---- check_step 4 "debug.log n'est plus suivi par Git" \ diff --git a/exercises/06-larbre-des-possibles/verifier.sh b/exercises/06-larbre-des-possibles/verifier.sh index 55bbecd..b20814f 100755 --- a/exercises/06-larbre-des-possibles/verifier.sh +++ b/exercises/06-larbre-des-possibles/verifier.sh @@ -43,4 +43,8 @@ check_step 5 "La branche expedition-sud contient un commit propre" \ check_step 6 "Il y a au moins 3 branches" \ '[ "$(git branch | wc -l)" -ge 3 ]' +# ---- Step 7 : De retour sur main ---- +check_step 7 "Tu es de retour sur la branche main" \ + 'assert_branch_is . main' + show_score diff --git a/exercises/10-le-protocole-des-guildes/verifier.sh b/exercises/10-le-protocole-des-guildes/verifier.sh index 3261587..d3496d3 100755 --- a/exercises/10-le-protocole-des-guildes/verifier.sh +++ b/exercises/10-le-protocole-des-guildes/verifier.sh @@ -39,4 +39,8 @@ check_step 4 "Un commit de merge existe (Protocole respecté)" \ check_step 5 "La branche de proposition a été nettoyée" \ '[ "$(git branch | grep -v "main\|master\|\*" | wc -l)" -eq 0 ]' +# ---- Step 6 : Retour sur la branche principale ---- +check_step 6 "Tu es de retour sur la branche principale" \ + 'assert_branch_is . main || assert_branch_is . master' + show_score diff --git a/exercises/11-le-tisseur-de-temps/verifier.sh b/exercises/11-le-tisseur-de-temps/verifier.sh index b314f70..35bf1e3 100755 --- a/exercises/11-le-tisseur-de-temps/verifier.sh +++ b/exercises/11-le-tisseur-de-temps/verifier.sh @@ -27,12 +27,19 @@ check_step 1 "Tu es dans un dépôt Git" \ check_step 2 "Au moins 2 branches existent" \ '[ "$(git branch -a 2>/dev/null | wc -l)" -ge 2 ]' -# ---- Step 3 : Le reflog montre une activité de stash ---- -check_step 3 "Le reflog montre une activité de stash" \ - 'git reflog show stash 2>/dev/null | grep -q . || git log -g refs/stash --oneline 2>/dev/null | grep -q . || git reflog 2>/dev/null | grep -qi stash' +# ---- Step 3 : La pile de stash est vide (stash appliqué ou dépilé) ---- +# Note: once git stash pop empties the stash stack, refs/stash is deleted, +# so there is no durable reflog evidence of past stash activity. We can +# only check the terminal state: nothing pending. +check_step 3 "Aucun stash en attente (le tissage du temps est complet)" \ + '[ -z "$(git stash list 2>/dev/null)" ]' # ---- Step 4 : Au moins 3 commits existent ---- check_step 4 "Il y a au moins 3 commits dans l'historique" \ '[ "$(git log --all --oneline 2>/dev/null | wc -l)" -ge 3 ]' +# ---- Step 5 : Retour sur main ---- +check_step 5 "Tu es de retour sur la branche main" \ + 'assert_branch_is . main' + show_score diff --git a/exercises/12-loracle-du-code/verifier.sh b/exercises/12-loracle-du-code/verifier.sh index 87e4aa8..f361b5a 100755 --- a/exercises/12-loracle-du-code/verifier.sh +++ b/exercises/12-loracle-du-code/verifier.sh @@ -30,9 +30,28 @@ check_step 1 "Tu es dans un dépôt Git" \ check_step 2 "git bisect a été utilisé (trace dans le reflog)" \ '[ "$(git reflog | grep -c "checkout: moving from [0-9a-f]\{7,\} to [0-9a-f]\{7,\}")" -ge 2 ]' -# ---- Step 3 : git cherry-pick a été utilisé (trace dans le reflog) ---- -check_step 3 "git cherry-pick a été utilisé (trace dans le reflog)" \ - 'git reflog | grep -q "cherry-pick"' +# ---- Step 3 : Le commit de la branche correctif est présent sur HEAD ---- +# State-based check: the student must have cherry-picked the fix commit +# from origin/correctif (or a local 'correctif' branch in the test harness). +# Cherry-pick copies the subject line, so HEAD's log contains an entry +# whose subject matches the correctif tip. A manual edit with a +# different commit message cannot satisfy this check. +check_step 3 "Le commit de la branche correctif a été appliqué (cherry-pick)" \ + ' + ( + fix_ref="" + for candidate in origin/correctif correctif refs/remotes/origin/correctif; do + if git rev-parse --verify --quiet "$candidate" >/dev/null 2>&1; then + fix_ref="$candidate" + break + fi + done + [ -n "$fix_ref" ] || exit 1 + fix_subject="$(git log -1 --format=%s "$fix_ref")" + [ -n "$fix_subject" ] || exit 1 + git log HEAD --format=%s | grep -qF -- "$fix_subject" + ) + ' # ---- Step 4 : Le grimoire ne contient plus le mot CORROMPU ---- check_step 4 "Le grimoire ne contient plus le mot CORROMPU" \ diff --git a/exercises/13-les-sceaux-magiques/verifier.sh b/exercises/13-les-sceaux-magiques/verifier.sh index 9bfe447..49b9c97 100755 --- a/exercises/13-les-sceaux-magiques/verifier.sh +++ b/exercises/13-les-sceaux-magiques/verifier.sh @@ -31,8 +31,10 @@ check_step 2 "Au moins 2 tags existent" \ check_step 3 "Au moins un tag annoté existe" \ 'found=false; for t in $(git tag); do if [ "$(git cat-file -t "$t" 2>/dev/null)" = "tag" ]; then found=true; break; fi; done; $found' -# ---- Step 4 : Les tags suivent le format de versionnage (v*) ---- -check_step 4 "Les tags suivent le format de versionnage (v*)" \ - 'git tag | grep -qE "^v[0-9]+\.[0-9]+\.[0-9]+$"' +# ---- Step 4 : Tous les tags suivent le format de versionnage (vX.Y.Z) ---- +# Require every tag to match semver, not just one - a student mixing +# semver tags with garbage ones (e.g. release-notes) should not pass. +check_step 4 "Tous les tags suivent le format de versionnage (vX.Y.Z)" \ + '[ "$(git tag | wc -l)" -gt 0 ] && ! git tag | grep -qvE "^v[0-9]+\.[0-9]+\.[0-9]+$"' show_score diff --git a/exercises/14-les-outils-de-larchiviste/verifier.sh b/exercises/14-les-outils-de-larchiviste/verifier.sh index eb357df..839d739 100755 --- a/exercises/14-les-outils-de-larchiviste/verifier.sh +++ b/exercises/14-les-outils-de-larchiviste/verifier.sh @@ -32,8 +32,16 @@ check_step 3 "Le hook pre-commit existe et est exécutable" \ '[ -x "$(git rev-parse --git-dir)/hooks/pre-commit" ]' # ---- Step 4 : Le hook pre-commit fonctionne (détecte les TODO) ---- -check_step 4 "Le hook pre-commit détecte les TODO dans les fichiers" \ - 'grep -q "TODO" "$(git rev-parse --git-dir)/hooks/pre-commit"' +# Require the hook to both mention TODO AND have a non-zero exit path, +# so a file that is only a comment ("# blocks TODO markers\nexit 0") +# does not pass. +check_step 4 "Le hook pre-commit détecte les TODO et bloque le commit" \ + ' + hook="$(git rev-parse --git-dir)/hooks/pre-commit" + [ -f "$hook" ] \ + && grep -q "TODO" "$hook" \ + && grep -qE "exit[[:space:]]+[1-9]|exit[[:space:]]+\\\$" "$hook" + ' # ---- Step 5 : Le hook commit-msg existe et est exécutable ---- check_step 5 "Le hook commit-msg existe et est exécutable" \ diff --git a/exercises/15-les-forges-eternelles/verifier.sh b/exercises/15-les-forges-eternelles/verifier.sh index 436b399..dc4a80e 100755 --- a/exercises/15-les-forges-eternelles/verifier.sh +++ b/exercises/15-les-forges-eternelles/verifier.sh @@ -23,9 +23,9 @@ show_banner "$QUEST_TITLE" check_step 1 "Tu es dans un dépôt Git" \ 'git rev-parse --is-inside-work-tree' -# ---- Step 2 : Au moins un remote est configuré ---- -check_step 2 "Au moins un remote est configuré" \ - 'git remote 2>/dev/null | grep -q .' +# ---- Step 2 : Un remote nommé 'origin' est configuré ---- +check_step 2 "Un remote nommé 'origin' est configuré" \ + 'assert_remote_name . origin' # ---- Step 3 : Au moins un push a été effectué ---- check_step 3 "Au moins un push a été effectué (des refs distantes existent)" \ @@ -35,4 +35,8 @@ check_step 3 "Au moins un push a été effectué (des refs distantes existent)" check_step 4 "Le dépôt contient au moins 3 commits" \ '[ "$(git rev-list --count HEAD 2>/dev/null)" -ge 3 ]' +# ---- Step 5 : Le dépôt local est sur la branche main ---- +check_step 5 "Tu es sur la branche main" \ + 'assert_branch_is . main' + show_score diff --git a/exercises/17-les-epreuves-automatiques/verifier.sh b/exercises/17-les-epreuves-automatiques/verifier.sh index 6f00ea4..55365bb 100755 --- a/exercises/17-les-epreuves-automatiques/verifier.sh +++ b/exercises/17-les-epreuves-automatiques/verifier.sh @@ -31,9 +31,14 @@ check_step 2 "Le dossier .github/workflows existe" \ check_step 3 "Un workflow contient une stratégie de matrice (matrix)" \ 'grep -rl "matrix:" .github/workflows/*.yml .github/workflows/*.yaml 2>/dev/null | grep -q .' -# ---- Step 4 : Le workflow contient plusieurs étapes (steps) ---- +# ---- Step 4 : Le workflow contient plusieurs etapes (steps) ---- +# Counts indented "- name:" entries which is the conventional YAML form for +# step entries under "steps:". This avoids the false-positive of the previous +# implementation which counted bare "name:" lines (workflow name, job name). check_step 4 "Le workflow contient plusieurs étapes (steps)" \ - 'fichier=$(ls .github/workflows/*.yml .github/workflows/*.yaml 2>/dev/null | head -1) && [ -n "$fichier" ] && [ "$(grep -c "name:" "$fichier")" -ge 2 ]' + 'fichier=$(ls .github/workflows/*.yml .github/workflows/*.yaml 2>/dev/null | head -1) \ + && [ -n "$fichier" ] \ + && [ "$(grep -cE "^[[:space:]]+- name:" "$fichier")" -ge 2 ]' # ---- Step 5 : Au moins un script de test existe ---- check_step 5 "Au moins un script de test existe" \ diff --git a/exercises/18-le-deploiement-sacre/verifier.sh b/exercises/18-le-deploiement-sacre/verifier.sh index 9d1c1ea..fa57502 100755 --- a/exercises/18-le-deploiement-sacre/verifier.sh +++ b/exercises/18-le-deploiement-sacre/verifier.sh @@ -27,9 +27,15 @@ check_step 1 "Tu es dans un dépôt Git" \ check_step 2 "Un dossier .github/workflows existe avec un workflow" \ 'ls .github/workflows/*.yml .github/workflows/*.yaml 2>/dev/null | grep -q .' -# ---- Step 3 : Le workflow mentionne le déploiement (production ou deploy) ---- +# ---- Step 3 : Le workflow mentionne le déploiement hors commentaires ---- +# Strip comment-only lines before matching so a placeholder workflow +# with "# TODO: deploy to production" at the top does not pass. check_step 3 "Le workflow mentionne le déploiement (production ou deploy)" \ - 'grep -rqlE "production|deploy" .github/workflows/ 2>/dev/null' + ' + find .github/workflows -type f \( -name "*.yml" -o -name "*.yaml" \) \ + -exec grep -hvE "^[[:space:]]*#" {} + 2>/dev/null \ + | grep -qE "(deploy|production)" + ' # ---- Step 4 : Au moins un tag existe ---- check_step 4 "Au moins un tag de version existe" \ diff --git a/exercises/19-les-autres-forges/verifier.sh b/exercises/19-les-autres-forges/verifier.sh index 29d529f..0799ba3 100755 --- a/exercises/19-les-autres-forges/verifier.sh +++ b/exercises/19-les-autres-forges/verifier.sh @@ -23,17 +23,26 @@ show_banner "$QUEST_TITLE" check_step 1 "Tu es dans un dépôt Git" \ 'git rev-parse --is-inside-work-tree' -# ---- Step 2 : Le fichier GitHub Actions existe ---- -check_step 2 "Le fichier .github/workflows/ci.yml existe" \ - 'ls .github/workflows/*.yml >/dev/null 2>&1' - -# ---- Step 3 : Le fichier GitLab CI existe ---- -check_step 3 "Le fichier .gitlab-ci.yml existe" \ - '[ -f ".gitlab-ci.yml" ]' - -# ---- Step 4 : Le fichier Bitbucket Pipelines existe ---- -check_step 4 "Le fichier bitbucket-pipelines.yml existe" \ - '[ -f "bitbucket-pipelines.yml" ]' +# ---- Step 2 : Le fichier GitHub Actions existe et déclare au moins un job +# Wrapped in a subshell so the loop-internal 'exit' only returns from the +# check, not from the verifier as a whole. +check_step 2 "Le fichier .github/workflows/*.yml déclare un 'jobs:'" \ + ' + ( + for f in .github/workflows/*.yml .github/workflows/*.yaml; do + [ -f "$f" ] && assert_file_contains "$f" "^jobs:" && exit 0 + done + exit 1 + ) + ' + +# ---- Step 3 : Le fichier GitLab CI existe et définit au moins un job +check_step 3 "Le fichier .gitlab-ci.yml définit au moins un job (script:)" \ + 'assert_file_contains ".gitlab-ci.yml" "^[[:space:]]*script:"' + +# ---- Step 4 : Le fichier Bitbucket Pipelines existe et déclare 'pipelines:' +check_step 4 "Le fichier bitbucket-pipelines.yml déclare 'pipelines:'" \ + 'assert_file_contains "bitbucket-pipelines.yml" "^pipelines:"' # ---- Step 5 : Le script de test existe ---- check_step 5 "Le script de test scripts/test.sh existe" \ diff --git a/lib/common.sh b/lib/common.sh index 738079b..dfcf177 100644 --- a/lib/common.sh +++ b/lib/common.sh @@ -279,3 +279,70 @@ check_in_repo() { return 1 fi } + +# ----------------------------------------------------------------------------- +# Predicate helpers for rigor-focused verifier checks +# +# These helpers are silent (no output, return 0 or 1). They are designed to +# be called from inside check_step's test_command string, so the verifier's +# output stays uniform (handled by check_step itself). +# +# Usage from a verifier: +# check_step 2 "ma-copie/ a une remote nommee 'origin'" \ +# 'assert_remote_name "$WORKDIR/ma-copie" "origin"' +# ----------------------------------------------------------------------------- + +# Returns 0 if has a remote named exactly . +# Returns 1 if the path is not a git repo or the remote is missing. +assert_remote_name() { + local repo="$1" + local expected="$2" + [ -d "$repo/.git" ] || return 1 + git -C "$repo" remote 2>/dev/null | grep -qx -- "$expected" +} + +# Returns 0 if is currently on branch . +# Returns 1 if HEAD is detached, branch differs, or path is not a repo. +assert_branch_is() { + local repo="$1" + local expected="$2" + local actual + actual="$(git -C "$repo" symbolic-ref --short HEAD 2>/dev/null)" || return 1 + [ "$actual" = "$expected" ] +} + +# Returns 0 if exists AND its content matches the extended regex +# . Returns 1 otherwise. +assert_file_contains() { + local file="$1" + local pattern="$2" + [ -f "$file" ] && grep -qE -- "$pattern" "$file" +} + +# Returns 0 if all share the same HEAD commit SHA. +# Returns 1 if any path is not a repo, or if any HEAD differs from the first. +assert_same_head() { + local first="" + local repo + for repo in "$@"; do + local sha + sha="$(git -C "$repo" rev-parse HEAD 2>/dev/null)" || return 1 + if [ -z "$first" ]; then + first="$sha" + elif [ "$sha" != "$first" ]; then + return 1 + fi + done +} + +# ----------------------------------------------------------------------------- +# show_section +# +# Prints a small underlined cyan section header. Cosmetic only, does not +# affect SCORE/TOTAL. Use to group related check_step calls in a verifier. +# ----------------------------------------------------------------------------- +show_section() { + local title="$1" + printf "\n %s-- %s --%s\n" \ + "${CLR_CYAN}${CLR_BOLD}" "$title" "${CLR_RESET}" +} diff --git a/scripts/makefile/run-lint-content.sh b/scripts/makefile/run-lint-content.sh new file mode 100755 index 0000000..cc06778 --- /dev/null +++ b/scripts/makefile/run-lint-content.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +# +# Lint src/**/*.njk for forbidden git command patterns. +# +# This is a regression-prevention linter. Each pattern below corresponds to a +# bug that was fixed in the rigor sweep and should never be reintroduced in +# user-facing course content. Matches inside bash comments ('# ...') are +# ignored so pedagogical counter-examples (e.g. 'Instead of "git checkout +# main"') stay valid. + +set -u + +echo "▶ Running lint-content…" + +fail=0 + +# ----------------------------------------------------------------------------- +# Pattern 1 : 'origine' used as a git remote name (francization typo). +# Matches the token form: git (remote add|push|pull|fetch|clone) origine +# Any non-token use of the French word 'origine' in prose is left alone. +# ----------------------------------------------------------------------------- +hits_origine=$(grep -rnE --include='*.njk' \ + 'git[[:space:]]+(remote[[:space:]]+add|push|pull|fetch|clone)[[:space:]]+origine' \ + src/ 2>/dev/null | grep -v '#.*origine' || true) + +if [ -n "$hits_origine" ]; then + echo "$hits_origine" + echo "✗ lint-content: 'origine' used as a git remote name (should be 'origin')" >&2 + fail=1 +fi + +# ----------------------------------------------------------------------------- +# Pattern 2 : 'git checkout <branch>' in a code block. +# Since Git 2.23, 'git switch' is the preferred way to change branches and +# 'git switch -c' replaces 'git checkout -b'. Quest 06 explicitly teaches +# this, so no other quest should contradict it by using the legacy form. +# +# Filters applied: +# * Matches inside bash comments ('# ...') are ignored so pedagogical +# counter-examples (e.g. 'Instead of "git checkout main"') stay valid. +# * Version tag checkouts ('git checkout v1.2.3') are allowed because +# 'git switch' cannot detach onto a tag without --detach. Quest 18 +# (le-deploiement-sacre) uses this form legitimately. +# * Quest 06 files are allow-listed entirely: the quest intentionally +# shows both 'git switch' (modern) and 'git checkout' (legacy) side by +# side to teach the historical context. +# * File-restore form ('git checkout -- <path>') is NOT matched because +# its first argument starts with '--', which is rejected by the regex +# requiring a word-character first char. That form is the job of +# 'git restore', but that is a separate sweep. +# ----------------------------------------------------------------------------- +hits_checkout=$(grep -rnE --include='*.njk' \ + 'git[[:space:]]+checkout[[:space:]]+(-[bB][[:space:]]+)?[a-zA-Z0-9._][a-zA-Z0-9._-]*' \ + src/ 2>/dev/null \ + | grep -vE '#.*git[[:space:]]+checkout' \ + | grep -vE 'git[[:space:]]+checkout[[:space:]]+v[0-9]' \ + | grep -vE '/06-larbre-des-possibles\.njk:' \ + | grep -vE '/06-the-tree-of-possibilities\.njk:' \ + || true) + +if [ -n "$hits_checkout" ]; then + echo "$hits_checkout" + echo "✗ lint-content: 'git checkout <branch>' in code (prefer 'git switch')" >&2 + fail=1 +fi + +if [ "$fail" -eq 0 ]; then + echo "✓ lint-content: clean." +fi + +exit "$fail" diff --git a/src/en/quests/01-the-archivists-guild.njk b/src/en/quests/01-the-archivists-guild.njk index 4c2218d..99dae1a 100644 --- a/src/en/quests/01-the-archivists-guild.njk +++ b/src/en/quests/01-the-archivists-guild.njk @@ -191,6 +191,16 @@ git config --global user.email</code></pre> <p>These commands should display your name and email respectively.</p> + <h3>Configure your default branch</h3> + + <p>Since 2020, the modern convention is to call the main branch <code>main</code> instead of the historical <code>master</code>. Configure this preference globally so that all your future <span class="git-cmd">git init</span> commands automatically create a <code>main</code> branch:</p> + + <pre data-lang="bash"><code>git config --global init.defaultBranch main</code></pre> + + <div class="tip"> + <p>This configuration is <strong>important for the upcoming quests</strong>. Without it, some instructions that assume a <code>main</code> branch will fail on your first <span class="git-cmd">git init</span>.</p> + </div> + <h3>Per-project configuration</h3> <p>You might want to use a <strong>nickname</strong> on your personal online projects, but your <strong>real name</strong> (or a trigram) on your professional projects. Git allows this thanks to the <span class="git-cmd">--local</span> option, which overrides the global configuration <strong>only for the current repository</strong>:</p> @@ -235,15 +245,16 @@ git config --local user.email "jsmith@company.com"</code></pre> <pre data-lang="powershell"><code>.\verifier.ps1</code></pre> - <p>The script checks three things:</p> + <p>The script checks four things:</p> <ol class="steps"> <li>Git is <span class="highlight">installed</span> on your machine</li> <li>Your <span class="keyword">name</span> is configured (<span class="git-cmd">user.name</span>)</li> <li>Your <span class="keyword">email</span> is configured (<span class="git-cmd">user.email</span>)</li> + <li>Your <span class="keyword">default branch</span> is <code>main</code> (<span class="git-cmd">init.defaultBranch</span>)</li> </ol> - <p>If all three steps pass, congratulations! You are officially a member of the Archivists' Guild. The next quest awaits.</p> + <p>If all four steps pass, congratulations! You are officially a member of the Archivists' Guild. The next quest awaits.</p> </section> <div class="fancy-separator"></div> diff --git a/src/en/quests/02-the-three-halls-of-knowledge.njk b/src/en/quests/02-the-three-halls-of-knowledge.njk index 35c650d..a70bb5f 100644 --- a/src/en/quests/02-the-three-halls-of-knowledge.njk +++ b/src/en/quests/02-the-three-halls-of-knowledge.njk @@ -193,7 +193,7 @@ permalink: /en/quests/02-the-three-halls-of-knowledge/ <p>Open a terminal and create a new folder, then initialize a Git repository:</p> <pre data-lang="bash"><code>mkdir atelier-archiviste cd atelier-archiviste -git init</code></pre> +git init -b main</code></pre> </div> <!-- Step 2 --> diff --git a/src/en/quests/03-the-first-scroll.njk b/src/en/quests/03-the-first-scroll.njk index bf3f5d3..c8a7f71 100644 --- a/src/en/quests/03-the-first-scroll.njk +++ b/src/en/quests/03-the-first-scroll.njk @@ -28,7 +28,7 @@ permalink: /en/quests/03-the-first-scroll/ <pre data-lang="bash"><code>mkdir mon-projet cd mon-projet -git init</code></pre> +git init -b main</code></pre> <p>Expected output:</p> @@ -186,7 +186,7 @@ git add .</code></pre> <pre data-lang="bash"><code>mkdir mon-archive cd mon-archive -git init</code></pre> +git init -b main</code></pre> <p>Verify that everything is in order:</p> @@ -282,6 +282,7 @@ a1b2c3d Add the Guild's mission order</code></pre> <li>You have at least 2 commits</li> <li>The file <code>mission.txt</code> is tracked by Git</li> <li>Your first <span class="keyword">commit</span> message is personalized (not <code>"Initial commit"</code>)</li> + <li>You have completed the scroll (<code>mission.txt</code> differs from the shipped template)</li> </ol> <!-- ================================================ --> diff --git a/src/en/quests/04-the-archive-is-everywhere.njk b/src/en/quests/04-the-archive-is-everywhere.njk index 951d6e4..92d6b20 100644 --- a/src/en/quests/04-the-archive-is-everywhere.njk +++ b/src/en/quests/04-the-archive-is-everywhere.njk @@ -95,20 +95,20 @@ git log --oneline</code></pre> <p>A <strong><span class="keyword">bare repo</span></strong> is a repo that contains <strong>only</strong> the contents of <span class="keyword"><code>.git/</code></span> - no working files. It's the format used by <strong>Git servers</strong>.</p> <pre data-lang="bash"><code># Create a bare repo -git init --bare ./archive-centrale.git +git init -b main --bare ./archive-centrale.git # Add as remote and push cd mon-archive -git remote add origine ../archive-centrale.git -git push origine main</code></pre> +git remote add origin ../archive-centrale.git +git push origin main</code></pre> <pre data-lang="powershell"><code># Create a bare repo -git init --bare .\archive-centrale.git +git init -b main --bare .\archive-centrale.git # Add as remote and push cd mon-archive -git remote add origine ..\archive-centrale.git -git push origine main</code></pre> +git remote add origin ..\archive-centrale.git +git push origin main</code></pre> <p>This is <strong>exactly</strong> what a Git server does. When you <span class="git-cmd">git push</span> to GitHub, you're sending your commits to a <span class="keyword">bare repo</span> hosted on their servers. Nothing magical about it.</p> @@ -231,7 +231,7 @@ git push origine main</code></pre> <p>You need the <code>mon-archive/</code> repo from quest 03. If you don't have it:</p> <pre data-lang="bash"><code>mkdir mon-archive && cd mon-archive -git init +git init -b main echo "Welcome to the Archive" > README.md git add README.md git commit -m "First parchment" @@ -243,12 +243,12 @@ cd ..</code></pre> git clone ./mon-archive ./ma-copie # 2. Bare repo -git init --bare ./archive-centrale.git +git init -b main --bare ./archive-centrale.git # 3. Add the remote and push cd mon-archive -git remote add origine ../archive-centrale.git -git push origine main +git remote add origin ../archive-centrale.git +git push origin main cd .. # 4. Clone from the bare repo diff --git a/src/en/quests/06-the-tree-of-possibilities.njk b/src/en/quests/06-the-tree-of-possibilities.njk index fa940de..92efd2a 100644 --- a/src/en/quests/06-the-tree-of-possibilities.njk +++ b/src/en/quests/06-the-tree-of-possibilities.njk @@ -197,7 +197,7 @@ HEAD -> expedition-nord -> commit C4</code></pre> <pre data-lang="bash"><code>mkdir chroniques-paralleles cd chroniques-paralleles -git init</code></pre> +git init -b main</code></pre> <h3>Step 2 - First commit on main</h3> @@ -276,6 +276,7 @@ git commit -m "Add the southern expedition journal"</code></pre> <li>The <code>expedition-nord</code> branch contains at least one unique commit</li> <li>The <code>expedition-sud</code> branch contains at least one unique commit</li> <li>There are at least 3 branches in total</li> + <li>You are back on the <code>main</code> branch</li> </ol> </section> diff --git a/src/en/quests/08-rewriting-history.njk b/src/en/quests/08-rewriting-history.njk index ea9701f..7abe101 100644 --- a/src/en/quests/08-rewriting-history.njk +++ b/src/en/quests/08-rewriting-history.njk @@ -85,7 +85,7 @@ A---B---E---F (main)</code></pre> <h3>How to rebase</h3> <pre data-lang="bash"><code># Switch to the branch to rebase -git checkout feature +git switch feature # Rebase onto main git rebase main</code></pre> @@ -103,7 +103,7 @@ git rebase main</code></pre> <h3>Merge (fusion)</h3> - <pre data-lang="bash"><code>git checkout main + <pre data-lang="bash"><code>git switch main git merge feature</code></pre> <pre><code> C---D (feature) @@ -118,7 +118,7 @@ A---B---E---F---G (main, merge commit)</code></pre> <h3>Rebase</h3> - <pre data-lang="bash"><code>git checkout feature + <pre data-lang="bash"><code>git switch feature git rebase main</code></pre> <pre><code> C'---D' (feature) @@ -291,7 +291,7 @@ git reset --hard <hash-of-lost-commit></code></pre> <pre data-lang="bash"><code>mkdir chroniques-nettoyees cd chroniques-nettoyees -git init</code></pre> +git init -b main</code></pre> <h3>Step 2 - Record three entries (with an error)</h3> @@ -323,7 +323,7 @@ git commit -m "Addd Day 3 entry"</code></pre> <p>Create a <code>refonte-archives</code> branch and add entries to it:</p> - <pre data-lang="bash"><code>git checkout -b refonte-archives + <pre data-lang="bash"><code>git switch -c refonte-archives echo "Day 4: Construction of the fortress" >> registre.txt git add registre.txt @@ -337,7 +337,7 @@ git commit -m "Add Day 5 entry"</code></pre> <p>Return to <code>main</code> and add a commit:</p> - <pre data-lang="bash"><code>git checkout main + <pre data-lang="bash"><code>git switch main echo "Appendix: List of kings" >> annexes.txt git add annexes.txt @@ -347,7 +347,7 @@ git commit -m "Add the list of kings as appendix"</code></pre> <p>Rebase <code>refonte-archives</code> onto <code>main</code> to get a linear history:</p> - <pre data-lang="bash"><code>git checkout refonte-archives + <pre data-lang="bash"><code>git switch refonte-archives git rebase main</code></pre> <h3>Step 7 - Verify the result</h3> diff --git a/src/en/quests/09-the-distant-portals.njk b/src/en/quests/09-the-distant-portals.njk index 8a2973a..0128ade 100644 --- a/src/en/quests/09-the-distant-portals.njk +++ b/src/en/quests/09-the-distant-portals.njk @@ -57,7 +57,7 @@ origin /path/to/repo.git (push)</code></pre> <p>Example:</p> - <pre data-lang="bash"><code>git remote add portail /tmp/portail-central.git</code></pre> + <pre data-lang="bash"><code>git remote add origin /tmp/portail-central.git</code></pre> <p>By convention:</p> <ul> @@ -76,17 +76,17 @@ origin /path/to/repo.git (push)</code></pre> <p>Example:</p> - <pre data-lang="bash"><code>git push portail main</code></pre> + <pre data-lang="bash"><code>git push origin main</code></pre> - <p>This sends all commits from your <code>main</code> branch that do not yet exist on the <code>portail</code> remote.</p> + <p>This sends all commits from your <code>main</code> branch that do not yet exist on the <code>origin</code> remote.</p> <h3>Setting up tracking with <code>-u</code></h3> <p>The first time you push a branch, you can set up <span class="keyword">tracking</span>:</p> - <pre data-lang="bash"><code>git push -u portail main</code></pre> + <pre data-lang="bash"><code>git push -u origin main</code></pre> - <p>The <code>-u</code> option (or <code>--set-upstream</code>) creates a permanent link between your local <code>main</code> branch and the <code>main</code> branch on the <code>portail</code> remote. After that, you can simply write:</p> + <p>The <code>-u</code> option (or <code>--set-upstream</code>) creates a permanent link between your local <code>main</code> branch and the <code>main</code> branch on the <code>origin</code> remote. After that, you can simply write:</p> <pre data-lang="bash"><code>git push</code></pre> @@ -103,7 +103,7 @@ origin /path/to/repo.git (push)</code></pre> <pre data-lang="bash"><code>git fetch <remote></code></pre> - <p><span class="git-cmd">git fetch</span> downloads new commits from the remote, but <strong><span class="highlight-red">does not modify anything in your work</span></strong>. The changes are stored in <span class="keyword">tracking branches</span> (like <code>portail/main</code>), not in your local branches.</p> + <p><span class="git-cmd">git fetch</span> downloads new commits from the remote, but <strong><span class="highlight-red">does not modify anything in your work</span></strong>. The changes are stored in <span class="keyword">tracking branches</span> (like <code>origin/main</code>), not in your local branches.</p> <div class="tip"> <p><span class="git-cmd">git fetch</span> is like looking through the portal to see what other Archivists have done, <strong>without touching your own scrolls</strong>.</p> @@ -173,24 +173,24 @@ git branch -a</code></pre> <p>You will see something like:</p> <pre data-lang="bash"><code>* main - remotes/portail/main</code></pre> + remotes/origin/main</code></pre> - <h3><code>portail/main</code> vs <code>main</code></h3> + <h3><code>origin/main</code> vs <code>main</code></h3> <ul> <li><strong><code>main</code></strong>: your local branch. This is where you work.</li> - <li><strong><code>portail/main</code></strong> (or <code>remotes/portail/main</code>): the tracking branch. It is the <strong>last known snapshot</strong> of the <code>main</code> branch on the <code>portail</code> remote.</li> + <li><strong><code>origin/main</code></strong> (or <code>remotes/origin/main</code>): the tracking branch. It is the <strong>last known snapshot</strong> of the <code>main</code> branch on the <code>origin</code> remote.</li> </ul> <div class="key-message"> - <p>After a <span class="git-cmd">git fetch</span>, the tracking branch <code>portail/main</code> is up to date with the remote, but your local <code>main</code> branch has not moved. It is up to you to decide when to merge.</p> + <p>After a <span class="git-cmd">git fetch</span>, the tracking branch <code>origin/main</code> is up to date with the remote, but your local <code>main</code> branch has not moved. It is up to you to decide when to merge.</p> </div> <pre data-lang="bash"><code># View remote commits not yet merged -git log main..portail/main --oneline +git log main..origin/main --oneline # Merge when you are ready -git merge portail/main</code></pre> +git merge origin/main</code></pre> <div class="fancy-separator"></div> @@ -203,7 +203,7 @@ git merge portail/main</code></pre> <p>Example:</p> - <pre data-lang="bash"><code>git push portail --delete ancienne-expedition</code></pre> + <pre data-lang="bash"><code>git push origin --delete old-expedition</code></pre> <div class="tip"> <p>This deletes the branch <strong>on the remote only</strong>. Your local branch is not affected.</p> @@ -219,8 +219,8 @@ git merge portail/main</code></pre> <div class="steps"> <ol> <li><strong><span class="git-cmd">git fetch</span></strong> - Download the latest changes</li> - <li><strong>Examine</strong> - <code>git log main..portail/main</code> to see what changed</li> - <li><strong>Merge or rebase</strong> - <code>git merge portail/main</code> or <code>git rebase portail/main</code></li> + <li><strong>Examine</strong> - <code>git log main..origin/main</code> to see what changed</li> + <li><strong>Merge or rebase</strong> - <code>git merge origin/main</code> or <code>git rebase origin/main</code></li> <li><strong>Work</strong> - Make your own commits</li> <li><strong>Push</strong> - <span class="git-cmd">git push</span> to share your work</li> </ol> @@ -249,7 +249,7 @@ cd portails-distants # Create your working repository mkdir citadelle cd citadelle -git init +git init -b main echo "# Chroniques de la Citadelle" > chroniques.md git add chroniques.md git commit -m "Fonder la chronique de la Citadelle" @@ -257,19 +257,19 @@ cd ..</code></pre> <h3>Step 2 - Create the Central Portal (bare repo)</h3> - <pre data-lang="bash"><code>git init --bare portail-central.git</code></pre> + <pre data-lang="bash"><code>git init -b main --bare portail-central.git</code></pre> <h3>Step 3 - Open the portal from your citadel</h3> <pre data-lang="bash"><code>cd citadelle -git remote add portail ../portail-central.git +git remote add origin ../portail-central.git git remote -v cd ..</code></pre> <h3>Step 4 - Send your chronicles</h3> <pre data-lang="bash"><code>cd citadelle -git push -u portail main +git push -u origin main cd ..</code></pre> <h3>Step 5 - Another Archivist arrives</h3> @@ -286,14 +286,14 @@ cd ..</code></pre> <h3>Step 6 - Discover the changes (fetch)</h3> <pre data-lang="bash"><code>cd citadelle -git fetch portail +git fetch origin # Examine what changed -git log main..portail/main --oneline</code></pre> +git log main..origin/main --oneline</code></pre> <h3>Step 7 - Integrate the changes (merge)</h3> - <pre data-lang="bash"><code>git merge portail/main + <pre data-lang="bash"><code>git merge origin/main # Verify the file contains both contributions cat chroniques.md</code></pre> diff --git a/src/en/quests/10-the-guild-protocol.njk b/src/en/quests/10-the-guild-protocol.njk index 437ff1b..ecfe62a 100644 --- a/src/en/quests/10-the-guild-protocol.njk +++ b/src/en/quests/10-the-guild-protocol.njk @@ -357,7 +357,7 @@ feature-1: C---D G---H</code></pre> <pre data-lang="bash"><code>mkdir protocole-des-guildes cd protocole-des-guildes -git init +git init -b main echo "# Defenses du Royaume" > defenses.txt echo "" >> defenses.txt echo "Les defenses actuelles protegent la cite." >> defenses.txt diff --git a/src/en/quests/11-the-time-weaver.njk b/src/en/quests/11-the-time-weaver.njk index bfcc8ab..1f9d5c7 100644 --- a/src/en/quests/11-the-time-weaver.njk +++ b/src/en/quests/11-the-time-weaver.njk @@ -228,7 +228,7 @@ git stash drop stash@{1}</code></pre> <pre data-lang="bash"><code>mkdir time-weaver cd time-weaver -git init +git init -b main echo "# Chronicles of the Kingdom" > chronicle.txt echo "" >> chronicle.txt echo "Times are calm in the kingdom." >> chronicle.txt diff --git a/src/en/quests/13-the-magic-seals.njk b/src/en/quests/13-the-magic-seals.njk index 478a5c5..3380b01 100644 --- a/src/en/quests/13-the-magic-seals.njk +++ b/src/en/quests/13-the-magic-seals.njk @@ -268,7 +268,7 @@ git tag -l "v*"</code></pre> <pre data-lang="bash"><code>mkdir magic-seals cd magic-seals -git init</code></pre> +git init -b main</code></pre> <p>Create the first scroll and make the first commit:</p> diff --git a/src/en/quests/14-the-archivists-tools.njk b/src/en/quests/14-the-archivists-tools.njk index e013859..daaece6 100644 --- a/src/en/quests/14-the-archivists-tools.njk +++ b/src/en/quests/14-the-archivists-tools.njk @@ -297,7 +297,7 @@ git config --global --edit</code></pre> <pre data-lang="bash"><code>mkdir archivists-tools cd archivists-tools -git init</code></pre> +git init -b main</code></pre> <h3>Step 2 - Configure the aliases</h3> diff --git a/src/en/quests/15-the-eternal-forges.njk b/src/en/quests/15-the-eternal-forges.njk index a44eb47..78ff515 100644 --- a/src/en/quests/15-the-eternal-forges.njk +++ b/src/en/quests/15-the-eternal-forges.njk @@ -380,7 +380,7 @@ cat ~/.ssh/id_ed25519.pub</code></pre> <pre data-lang="bash"><code>mkdir chronicles-forge cd chronicles-forge -git init</code></pre> +git init -b main</code></pre> <h3>Step 2 - Write the first scroll</h3> @@ -430,7 +430,7 @@ git commit -m "Add notes about the forges and the CI/CD pipeline"</code></pre> <h3>Step 4 - Create the Forge (bare repository)</h3> <pre data-lang="bash"><code>cd .. -git init --bare eternal-forge.git</code></pre> +git init -b main --bare eternal-forge.git</code></pre> <div class="tip"> <p>A <strong>bare repository</strong> is a Git repository without a working directory - exactly like a GitHub or GitLab server. It only contains Git data, not the working files.</p> diff --git a/src/en/quests/16-the-kingdoms-actions.njk b/src/en/quests/16-the-kingdoms-actions.njk index fcfac1c..22e4c8f 100644 --- a/src/en/quests/16-the-kingdoms-actions.njk +++ b/src/en/quests/16-the-kingdoms-actions.njk @@ -520,7 +520,7 @@ jobs: <pre data-lang="bash"><code>mkdir forge-actions cd forge-actions -git init</code></pre> +git init -b main</code></pre> <h3>Step 2 - Create the workflow structure</h3> diff --git a/src/en/quests/17-the-automated-trials.njk b/src/en/quests/17-the-automated-trials.njk index bd39534..25da58c 100644 --- a/src/en/quests/17-the-automated-trials.njk +++ b/src/en/quests/17-the-automated-trials.njk @@ -307,7 +307,7 @@ jobs: <pre data-lang="bash"><code>mkdir automated-trials cd automated-trials -git init</code></pre> +git init -b main</code></pre> <h3>Step 2 - Create the calculation script</h3> diff --git a/src/en/quests/18-the-sacred-deployment.njk b/src/en/quests/18-the-sacred-deployment.njk index a6f9975..53aa4af 100644 --- a/src/en/quests/18-the-sacred-deployment.njk +++ b/src/en/quests/18-the-sacred-deployment.njk @@ -524,7 +524,7 @@ git push --tags</code></pre> <pre data-lang="bash"><code>mkdir sacred-deployment cd sacred-deployment -git init</code></pre> +git init -b main</code></pre> <h3>Step 2 - Create the static site</h3> diff --git a/src/en/quests/19-the-other-forges.njk b/src/en/quests/19-the-other-forges.njk index 4669adb..9197c80 100644 --- a/src/en/quests/19-the-other-forges.njk +++ b/src/en/quests/19-the-other-forges.njk @@ -480,7 +480,7 @@ script: <pre data-lang="bash"><code>mkdir multi-forge cd multi-forge -git init</code></pre> +git init -b main</code></pre> <h3>Step 2 - Create the test script</h3> diff --git a/src/en/quests/20-the-free-paths.njk b/src/en/quests/20-the-free-paths.njk index 39cb10e..5f2ab00 100644 --- a/src/en/quests/20-the-free-paths.njk +++ b/src/en/quests/20-the-free-paths.njk @@ -370,7 +370,7 @@ git maintenance start</code></pre> <pre data-lang="bash"><code>mkdir my-journey cd my-journey -git init</code></pre> +git init -b main</code></pre> <h3>Step 2 - Write your reflection</h3> diff --git a/src/en/quests/21-the-travelers-client.njk b/src/en/quests/21-the-travelers-client.njk index 6bde3b6..ec41708 100644 --- a/src/en/quests/21-the-travelers-client.njk +++ b/src/en/quests/21-the-travelers-client.njk @@ -127,7 +127,7 @@ rad auth <pre data-lang="bash"><code># Create a normal Git repo mkdir my-project cd my-project -git init +git init -b main echo "# My Project" > README.md git add README.md git commit -m "First commit" @@ -378,7 +378,7 @@ rad auth <pre data-lang="bash"><code># Create a new repo mkdir travelers-workshop cd travelers-workshop -git init +git init -b main # Create a first file echo "# Traveler's Workshop" > README.md diff --git a/src/en/quests/22-the-watchtower.njk b/src/en/quests/22-the-watchtower.njk index 933f964..a8b8e28 100644 --- a/src/en/quests/22-the-watchtower.njk +++ b/src/en/quests/22-the-watchtower.njk @@ -475,7 +475,7 @@ rad node status</code></pre> <pre data-lang="bash"><code># If you created a project in Quest 21, note its RID # Otherwise, create one quickly: mkdir test-seed && cd test-seed -git init +git init -b main echo "test" > file.txt git add file.txt git commit -m "test" diff --git a/src/en/quests/23-the-peer-network.njk b/src/en/quests/23-the-peer-network.njk index a0335b4..8760e7f 100644 --- a/src/en/quests/23-the-peer-network.njk +++ b/src/en/quests/23-the-peer-network.njk @@ -435,7 +435,7 @@ mkdir -p ~/radicle-exercise/bob</code></pre> cd ~/radicle-exercise/alice # Create a Git repository -git init shared-project +git init -b main shared-project cd shared-project # Create some content diff --git a/src/en/quests/A1-the-titans-forges.njk b/src/en/quests/A1-the-titans-forges.njk index b2e1728..594d3b9 100644 --- a/src/en/quests/A1-the-titans-forges.njk +++ b/src/en/quests/A1-the-titans-forges.njk @@ -554,7 +554,7 @@ dvc add data/dataset.csv <pre data-lang="bash"><code># Create the project mkdir titans-forges-project cd titans-forges-project -git init +git init -b main # Initialize LFS git lfs install</code></pre> @@ -562,7 +562,7 @@ git lfs install</code></pre> <pre data-lang="powershell"><code># Create the project mkdir titans-forges-project cd titans-forges-project -git init +git init -b main # Initialize LFS git lfs install</code></pre> diff --git a/src/en/quests/A2-the-infinite-archives.njk b/src/en/quests/A2-the-infinite-archives.njk index d468c8e..7b11412 100644 --- a/src/en/quests/A2-the-infinite-archives.njk +++ b/src/en/quests/A2-the-infinite-archives.njk @@ -87,7 +87,7 @@ dvc version</code></pre> <pre data-lang="bash"><code># DVC initializes in an existing Git repository cd my-ml-project -git init +git init -b main dvc init # DVC creates a .dvc/ folder and a .dvcignore file @@ -534,7 +534,7 @@ wandb/</code></pre> <pre data-lang="bash"><code># Create the project mkdir infinite-archives-project cd infinite-archives-project -git init +git init -b main dvc init # Create the structure @@ -547,7 +547,7 @@ git commit -m "Initialize project with Git and DVC"</code></pre> <pre data-lang="powershell"><code># Create the project mkdir infinite-archives-project cd infinite-archives-project -git init +git init -b main dvc init # Create the structure diff --git a/src/en/quests/A3b-the-city-arcana.njk b/src/en/quests/A3b-the-city-arcana.njk index aa24343..789cb2a 100644 --- a/src/en/quests/A3b-the-city-arcana.njk +++ b/src/en/quests/A3b-the-city-arcana.njk @@ -448,7 +448,7 @@ jobs: <pre data-lang="bash"><code># Create the monorepo mkdir world-city-monorepo cd world-city-monorepo -git init +git init -b main # Create the structure mkdir -p apps/frontend/src @@ -461,7 +461,7 @@ mkdir -p docs</code></pre> <pre data-lang="powershell"><code># Create the monorepo mkdir world-city-monorepo cd world-city-monorepo -git init +git init -b main # Create the structure New-Item -ItemType Directory -Force -Path apps/frontend/src diff --git a/src/en/quests/A4-the-artificers-plans.njk b/src/en/quests/A4-the-artificers-plans.njk index beacc06..68df945 100644 --- a/src/en/quests/A4-the-artificers-plans.njk +++ b/src/en/quests/A4-the-artificers-plans.njk @@ -474,7 +474,7 @@ gh release create v1.0-rev-a my-project-v1.0-rev-a-gerbers.zip \ <pre data-lang="bash"><code># Create the repository mkdir temperature-sensor cd temperature-sensor -git init +git init -b main # Create the hardware directory tree mkdir -p hw/electronics/libs/symbols diff --git a/src/en/quests/A5-the-currents-of-destiny.njk b/src/en/quests/A5-the-currents-of-destiny.njk index e95c98e..494f332 100644 --- a/src/en/quests/A5-the-currents-of-destiny.njk +++ b/src/en/quests/A5-the-currents-of-destiny.njk @@ -600,7 +600,7 @@ git diff d4e5f6g..a1b2c3d</code></pre> <pre data-lang="bash"><code># Create the repository mkdir tavern-gitops cd tavern-gitops -git init +git init -b main # Create the directory tree mkdir -p base diff --git a/src/en/quests/A6-the-illuminators-workshop.njk b/src/en/quests/A6-the-illuminators-workshop.njk index 1bdbc14..0bfd2f8 100644 --- a/src/en/quests/A6-the-illuminators-workshop.njk +++ b/src/en/quests/A6-the-illuminators-workshop.njk @@ -532,7 +532,7 @@ cat build/scss/_variables.scss <pre data-lang="bash"><code># Create the repository mkdir visual-grimoire cd visual-grimoire -git init +git init -b main # Create the directory tree mkdir -p assets/raw/illustrations diff --git a/src/fr/quetes/01-la-guilde-des-archivistes.njk b/src/fr/quetes/01-la-guilde-des-archivistes.njk index 720dc7f..da7e8b3 100644 --- a/src/fr/quetes/01-la-guilde-des-archivistes.njk +++ b/src/fr/quetes/01-la-guilde-des-archivistes.njk @@ -191,6 +191,16 @@ git config --global user.email</code></pre> <p>Ces commandes doivent afficher respectivement ton nom et ton email.</p> + <h3>Configurer la branche par défaut</h3> + + <p>Depuis 2020, la convention moderne est d'appeler la branche principale <code>main</code> au lieu de l'historique <code>master</code>. Configure cette préférence globalement pour que tous tes futurs <span class="git-cmd">git init</span> créent automatiquement une branche <code>main</code> :</p> + + <pre data-lang="bash"><code>git config --global init.defaultBranch main</code></pre> + + <div class="tip"> + <p>Cette configuration est <strong>importante pour les quêtes suivantes</strong>. Sans elle, certaines instructions qui supposent une branche <code>main</code> échoueront sur ton premier <span class="git-cmd">git init</span>.</p> + </div> + <h3>Configuration par projet</h3> <p>Tu peux vouloir utiliser un <strong>pseudo</strong> sur tes projets personnels en ligne, mais ton <strong>vrai nom</strong> (ou un trigramme) sur tes projets professionnels. Git le permet grâce à l'option <span class="git-cmd">--local</span>, qui remplace la configuration globale <strong>uniquement pour le dépôt en cours</strong> :</p> @@ -235,15 +245,16 @@ git config --local user.email "jdupont@entreprise.com"</code></pre> <pre data-lang="powershell"><code>.\verifier.ps1</code></pre> - <p>Le script vérifie trois choses :</p> + <p>Le script vérifie quatre choses :</p> <ol class="steps"> <li>Git est <span class="highlight">installé</span> sur ta machine</li> <li>Ton <span class="keyword">nom</span> est configuré (<span class="git-cmd">user.name</span>)</li> <li>Ton <span class="keyword">email</span> est configuré (<span class="git-cmd">user.email</span>)</li> + <li>Ta <span class="keyword">branche par défaut</span> est <code>main</code> (<span class="git-cmd">init.defaultBranch</span>)</li> </ol> - <p>Si les trois étapes sont validées, félicitations ! Tu es officiellement membre de la Guilde des Archivistes. La prochaine quête t'attend.</p> + <p>Si les quatre étapes sont validées, félicitations ! Tu es officiellement membre de la Guilde des Archivistes. La prochaine quête t'attend.</p> </section> <div class="fancy-separator"></div> diff --git a/src/fr/quetes/02-les-trois-salles-du-savoir.njk b/src/fr/quetes/02-les-trois-salles-du-savoir.njk index 70cd94c..8b53d83 100644 --- a/src/fr/quetes/02-les-trois-salles-du-savoir.njk +++ b/src/fr/quetes/02-les-trois-salles-du-savoir.njk @@ -193,7 +193,7 @@ permalink: /fr/quetes/02-les-trois-salles-du-savoir/ <p>Ouvre un terminal et crée un nouveau dossier, puis initialise un dépôt Git :</p> <pre data-lang="bash"><code>mkdir atelier-archiviste cd atelier-archiviste -git init</code></pre> +git init -b main</code></pre> </div> <!-- Étape 2 --> diff --git a/src/fr/quetes/03-le-premier-parchemin.njk b/src/fr/quetes/03-le-premier-parchemin.njk index b0b43d5..08e8f07 100644 --- a/src/fr/quetes/03-le-premier-parchemin.njk +++ b/src/fr/quetes/03-le-premier-parchemin.njk @@ -28,7 +28,7 @@ permalink: /fr/quetes/03-le-premier-parchemin/ <pre data-lang="bash"><code>mkdir mon-projet cd mon-projet -git init</code></pre> +git init -b main</code></pre> <p>Résultat attendu :</p> @@ -186,7 +186,7 @@ git add .</code></pre> <pre data-lang="bash"><code>mkdir mon-archive cd mon-archive -git init</code></pre> +git init -b main</code></pre> <p>Vérifie que tout est en ordre :</p> @@ -282,6 +282,7 @@ a1b2c3d Ajouter l'ordre de mission de la Guilde</code></pre> <li>Tu as au moins 2 commits</li> <li>Le fichier <code>mission.txt</code> est suivi par Git</li> <li>Ton premier message de <span class="keyword">commit</span> est personnalisé (pas <code>"Initial commit"</code>)</li> + <li>Tu as complété le parchemin (<code>mission.txt</code> diffère du modèle fourni)</li> </ol> <!-- ================================================ --> diff --git a/src/fr/quetes/04-larchive-est-partout.njk b/src/fr/quetes/04-larchive-est-partout.njk index 732ad67..e8315c5 100644 --- a/src/fr/quetes/04-larchive-est-partout.njk +++ b/src/fr/quetes/04-larchive-est-partout.njk @@ -95,20 +95,20 @@ git log --oneline</code></pre> <p>Un <strong><span class="keyword">bare repo</span></strong> est un repo qui contient <strong>uniquement</strong> le contenu de <span class="keyword"><code>.git/</code></span> - pas de fichiers de travail. C'est le format utilisé par les <strong>serveurs Git</strong>.</p> <pre data-lang="bash"><code># Créer un bare repo -git init --bare ./archive-centrale.git +git init -b main --bare ./archive-centrale.git # Ajouter comme remote et pousser cd mon-archive -git remote add origine ../archive-centrale.git -git push origine main</code></pre> +git remote add origin ../archive-centrale.git +git push origin main</code></pre> <pre data-lang="powershell"><code># Créer un bare repo -git init --bare .\archive-centrale.git +git init -b main --bare .\archive-centrale.git # Ajouter comme remote et pousser cd mon-archive -git remote add origine ..\archive-centrale.git -git push origine main</code></pre> +git remote add origin ..\archive-centrale.git +git push origin main</code></pre> <p>C'est <strong>exactement</strong> ce que fait un serveur Git. Quand tu fais <span class="git-cmd">git push</span> vers GitHub, tu envoies tes commits vers un <span class="keyword">bare repo</span> hébergé sur leurs serveurs. Rien de magique.</p> @@ -231,7 +231,7 @@ git push origine main</code></pre> <p>Tu as besoin du repo <code>mon-archive/</code> de la quête 03. Si tu ne l'as pas :</p> <pre data-lang="bash"><code>mkdir mon-archive && cd mon-archive -git init +git init -b main echo "Bienvenue dans l'Archive" > README.md git add README.md git commit -m "Premier parchemin" @@ -243,12 +243,12 @@ cd ..</code></pre> git clone ./mon-archive ./ma-copie # 2. Bare repo -git init --bare ./archive-centrale.git +git init -b main --bare ./archive-centrale.git # 3. Ajouter la remote et pousser cd mon-archive -git remote add origine ../archive-centrale.git -git push origine main +git remote add origin ../archive-centrale.git +git push origin main cd .. # 4. Clone depuis le bare repo diff --git a/src/fr/quetes/06-larbre-des-possibles.njk b/src/fr/quetes/06-larbre-des-possibles.njk index e61441d..c959664 100644 --- a/src/fr/quetes/06-larbre-des-possibles.njk +++ b/src/fr/quetes/06-larbre-des-possibles.njk @@ -197,7 +197,7 @@ HEAD -> expedition-nord -> commit C4</code></pre> <pre data-lang="bash"><code>mkdir chroniques-paralleles cd chroniques-paralleles -git init</code></pre> +git init -b main</code></pre> <h3>Étape 2 - Premier commit sur main</h3> @@ -276,6 +276,7 @@ git commit -m "Ajouter le journal de l'expédition sud"</code></pre> <li>La branche <code>expedition-nord</code> contient au moins un commit propre</li> <li>La branche <code>expedition-sud</code> contient au moins un commit propre</li> <li>Il y a au moins 3 branches au total</li> + <li>Tu es de retour sur la branche <code>main</code></li> </ol> </section> diff --git a/src/fr/quetes/08-reecrire-lhistoire.njk b/src/fr/quetes/08-reecrire-lhistoire.njk index 59e6077..f8edcbf 100644 --- a/src/fr/quetes/08-reecrire-lhistoire.njk +++ b/src/fr/quetes/08-reecrire-lhistoire.njk @@ -85,7 +85,7 @@ A---B---E---F (main)</code></pre> <h3>Comment faire un rebase</h3> <pre data-lang="bash"><code># Se placer sur la branche à rebaser -git checkout feature +git switch feature # Rebaser sur main git rebase main</code></pre> @@ -103,7 +103,7 @@ git rebase main</code></pre> <h3>Merge (fusion)</h3> - <pre data-lang="bash"><code>git checkout main + <pre data-lang="bash"><code>git switch main git merge feature</code></pre> <pre><code> C---D (feature) @@ -118,7 +118,7 @@ A---B---E---F---G (main, commit de merge)</code></pre> <h3>Rebase</h3> - <pre data-lang="bash"><code>git checkout feature + <pre data-lang="bash"><code>git switch feature git rebase main</code></pre> <pre><code> C'---D' (feature) @@ -291,7 +291,7 @@ git reset --hard <hash-du-commit-perdu></code></pre> <pre data-lang="bash"><code>mkdir chroniques-nettoyees cd chroniques-nettoyees -git init</code></pre> +git init -b main</code></pre> <h3>Étape 2 - Consigner trois entrées (avec une erreur)</h3> @@ -323,7 +323,7 @@ git commit -m "Ajoouter l'entrée du Jour 3"</code></pre> <p>Crée une branche <code>refonte-archives</code> et ajoute-y des entrées :</p> - <pre data-lang="bash"><code>git checkout -b refonte-archives + <pre data-lang="bash"><code>git switch -c refonte-archives echo "Jour 4 : Construction de la forteresse" >> registre.txt git add registre.txt @@ -337,7 +337,7 @@ git commit -m "Ajouter l'entrée du Jour 5"</code></pre> <p>Retourne sur <code>main</code> et ajoute un commit :</p> - <pre data-lang="bash"><code>git checkout main + <pre data-lang="bash"><code>git switch main echo "Annexe : Liste des rois" >> annexes.txt git add annexes.txt @@ -347,7 +347,7 @@ git commit -m "Ajouter la liste des rois en annexe"</code></pre> <p>Rebase <code>refonte-archives</code> sur <code>main</code> pour obtenir un historique linéaire :</p> - <pre data-lang="bash"><code>git checkout refonte-archives + <pre data-lang="bash"><code>git switch refonte-archives git rebase main</code></pre> <h3>Étape 7 - Vérifier le résultat</h3> diff --git a/src/fr/quetes/09-les-portails-distants.njk b/src/fr/quetes/09-les-portails-distants.njk index feec0c4..02f0fe8 100644 --- a/src/fr/quetes/09-les-portails-distants.njk +++ b/src/fr/quetes/09-les-portails-distants.njk @@ -57,7 +57,7 @@ origin /chemin/vers/depot.git (push)</code></pre> <p>Exemple :</p> - <pre data-lang="bash"><code>git remote add portail /tmp/portail-central.git</code></pre> + <pre data-lang="bash"><code>git remote add origin /tmp/portail-central.git</code></pre> <p>Par convention :</p> <ul> @@ -76,17 +76,17 @@ origin /chemin/vers/depot.git (push)</code></pre> <p>Exemple :</p> - <pre data-lang="bash"><code>git push portail main</code></pre> + <pre data-lang="bash"><code>git push origin main</code></pre> - <p>Cela envoie tous les commits de ta branche <code>main</code> qui n'existent pas encore sur le remote <code>portail</code>.</p> + <p>Cela envoie tous les commits de ta branche <code>main</code> qui n'existent pas encore sur le remote <code>origin</code>.</p> <h3>Configurer le suivi avec <code>-u</code></h3> <p>La première fois que tu pousses une branche, tu peux configurer le <span class="keyword">suivi</span> (tracking) :</p> - <pre data-lang="bash"><code>git push -u portail main</code></pre> + <pre data-lang="bash"><code>git push -u origin main</code></pre> - <p>L'option <code>-u</code> (ou <code>--set-upstream</code>) crée un lien permanent entre ta branche locale <code>main</code> et la branche <code>main</code> du remote <code>portail</code>. Après cela, tu pourras simplement écrire :</p> + <p>L'option <code>-u</code> (ou <code>--set-upstream</code>) crée un lien permanent entre ta branche locale <code>main</code> et la branche <code>main</code> du remote <code>origin</code>. Après cela, tu pourras simplement écrire :</p> <pre data-lang="bash"><code>git push</code></pre> @@ -103,7 +103,7 @@ origin /chemin/vers/depot.git (push)</code></pre> <pre data-lang="bash"><code>git fetch <remote></code></pre> - <p><span class="git-cmd">git fetch</span> télécharge les nouveaux commits depuis le remote, mais <strong><span class="highlight-red">ne modifie rien dans ton travail</span></strong>. Les changements sont stockés dans les <span class="keyword">branches de suivi</span> (comme <code>portail/main</code>), pas dans tes branches locales.</p> + <p><span class="git-cmd">git fetch</span> télécharge les nouveaux commits depuis le remote, mais <strong><span class="highlight-red">ne modifie rien dans ton travail</span></strong>. Les changements sont stockés dans les <span class="keyword">branches de suivi</span> (comme <code>origin/main</code>), pas dans tes branches locales.</p> <div class="tip"> <p><span class="git-cmd">git fetch</span>, c'est comme regarder à travers le portail pour voir ce que les autres Archivistes ont fait, <strong>sans toucher à tes propres parchemins</strong>.</p> @@ -173,24 +173,24 @@ git branch -a</code></pre> <p>Tu verras quelque chose comme :</p> <pre data-lang="bash"><code>* main - remotes/portail/main</code></pre> + remotes/origin/main</code></pre> - <h3><code>portail/main</code> vs <code>main</code></h3> + <h3><code>origin/main</code> vs <code>main</code></h3> <ul> <li><strong><code>main</code></strong> : ta branche locale. C'est là que tu travailles.</li> - <li><strong><code>portail/main</code></strong> (ou <code>remotes/portail/main</code>) : la branche de suivi. C'est la <strong>dernière image connue</strong> de la branche <code>main</code> sur le remote <code>portail</code>.</li> + <li><strong><code>origin/main</code></strong> (ou <code>remotes/origin/main</code>) : la branche de suivi. C'est la <strong>dernière image connue</strong> de la branche <code>main</code> sur le remote <code>origin</code>.</li> </ul> <div class="key-message"> - <p>Après un <span class="git-cmd">git fetch</span>, la branche de suivi <code>portail/main</code> est à jour avec le remote, mais ta branche locale <code>main</code> n'a pas bougé. C'est à toi de décider quand fusionner.</p> + <p>Après un <span class="git-cmd">git fetch</span>, la branche de suivi <code>origin/main</code> est à jour avec le remote, mais ta branche locale <code>main</code> n'a pas bougé. C'est à toi de décider quand fusionner.</p> </div> <pre data-lang="bash"><code># Voir les commits distants non encore fusionnés -git log main..portail/main --oneline +git log main..origin/main --oneline # Fusionner quand tu es prêt -git merge portail/main</code></pre> +git merge origin/main</code></pre> <div class="fancy-separator"></div> @@ -203,7 +203,7 @@ git merge portail/main</code></pre> <p>Exemple :</p> - <pre data-lang="bash"><code>git push portail --delete ancienne-expedition</code></pre> + <pre data-lang="bash"><code>git push origin --delete ancienne-expedition</code></pre> <div class="tip"> <p>Cela supprime la branche <strong>sur le remote seulement</strong>. Ta branche locale n'est pas affectée.</p> @@ -219,8 +219,8 @@ git merge portail/main</code></pre> <div class="steps"> <ol> <li><strong><span class="git-cmd">git fetch</span></strong> - Télécharge les nouveautés</li> - <li><strong>Examine</strong> - <code>git log main..portail/main</code> pour voir ce qui a changé</li> - <li><strong>Fusionne ou rebase</strong> - <code>git merge portail/main</code> ou <code>git rebase portail/main</code></li> + <li><strong>Examine</strong> - <code>git log main..origin/main</code> pour voir ce qui a changé</li> + <li><strong>Fusionne ou rebase</strong> - <code>git merge origin/main</code> ou <code>git rebase origin/main</code></li> <li><strong>Travaille</strong> - Fais tes propres commits</li> <li><strong>Pousse</strong> - <span class="git-cmd">git push</span> pour partager ton travail</li> </ol> @@ -249,7 +249,7 @@ cd portails-distants # Crée ton dépôt de travail mkdir citadelle cd citadelle -git init +git init -b main echo "# Chroniques de la Citadelle" > chroniques.md git add chroniques.md git commit -m "Fonder la chronique de la Citadelle" @@ -257,19 +257,19 @@ cd ..</code></pre> <h3>Étape 2 - Créer le Portail Central (bare repo)</h3> - <pre data-lang="bash"><code>git init --bare portail-central.git</code></pre> + <pre data-lang="bash"><code>git init -b main --bare portail-central.git</code></pre> <h3>Étape 3 - Ouvrir le portail depuis ta citadelle</h3> <pre data-lang="bash"><code>cd citadelle -git remote add portail ../portail-central.git +git remote add origin ../portail-central.git git remote -v cd ..</code></pre> <h3>Étape 4 - Envoyer tes chroniques</h3> <pre data-lang="bash"><code>cd citadelle -git push -u portail main +git push -u origin main cd ..</code></pre> <h3>Étape 5 - Un autre Archiviste arrive</h3> @@ -286,14 +286,14 @@ cd ..</code></pre> <h3>Étape 6 - Découvrir les changements (fetch)</h3> <pre data-lang="bash"><code>cd citadelle -git fetch portail +git fetch origin # Examine ce qui a changé -git log main..portail/main --oneline</code></pre> +git log main..origin/main --oneline</code></pre> <h3>Étape 7 - Intégrer les changements (merge)</h3> - <pre data-lang="bash"><code>git merge portail/main + <pre data-lang="bash"><code>git merge origin/main # Vérifie que le fichier contient les deux contributions cat chroniques.md</code></pre> diff --git a/src/fr/quetes/10-le-protocole-des-guildes.njk b/src/fr/quetes/10-le-protocole-des-guildes.njk index 221e4ed..bdbaabf 100644 --- a/src/fr/quetes/10-le-protocole-des-guildes.njk +++ b/src/fr/quetes/10-le-protocole-des-guildes.njk @@ -357,7 +357,7 @@ feature-1: C---D G---H</code></pre> <pre data-lang="bash"><code>mkdir protocole-des-guildes cd protocole-des-guildes -git init +git init -b main echo "# Defenses du Royaume" > defenses.txt echo "" >> defenses.txt echo "Les defenses actuelles protegent la cite." >> defenses.txt diff --git a/src/fr/quetes/11-le-tisseur-de-temps.njk b/src/fr/quetes/11-le-tisseur-de-temps.njk index adfd670..a99a4b7 100644 --- a/src/fr/quetes/11-le-tisseur-de-temps.njk +++ b/src/fr/quetes/11-le-tisseur-de-temps.njk @@ -228,7 +228,7 @@ git stash drop stash@{1}</code></pre> <pre data-lang="bash"><code>mkdir tisseur-de-temps cd tisseur-de-temps -git init +git init -b main echo "# Chroniques du Royaume" > chronique.txt echo "" >> chronique.txt echo "Les temps sont calmes dans le royaume." >> chronique.txt diff --git a/src/fr/quetes/13-les-sceaux-magiques.njk b/src/fr/quetes/13-les-sceaux-magiques.njk index dfa5acb..b37d6a4 100644 --- a/src/fr/quetes/13-les-sceaux-magiques.njk +++ b/src/fr/quetes/13-les-sceaux-magiques.njk @@ -268,7 +268,7 @@ git tag -l "v*"</code></pre> <pre data-lang="bash"><code>mkdir sceaux-magiques cd sceaux-magiques -git init</code></pre> +git init -b main</code></pre> <p>Crée le premier parchemin et fais le premier commit :</p> diff --git a/src/fr/quetes/14-les-outils-de-larchiviste.njk b/src/fr/quetes/14-les-outils-de-larchiviste.njk index 0b9b13f..2f055a0 100644 --- a/src/fr/quetes/14-les-outils-de-larchiviste.njk +++ b/src/fr/quetes/14-les-outils-de-larchiviste.njk @@ -297,7 +297,7 @@ git config --global --edit</code></pre> <pre data-lang="bash"><code>mkdir outils-archiviste cd outils-archiviste -git init</code></pre> +git init -b main</code></pre> <h3>Étape 2 - Configurer les alias</h3> diff --git a/src/fr/quetes/15-les-forges-eternelles.njk b/src/fr/quetes/15-les-forges-eternelles.njk index 15a62e8..af42932 100644 --- a/src/fr/quetes/15-les-forges-eternelles.njk +++ b/src/fr/quetes/15-les-forges-eternelles.njk @@ -380,7 +380,7 @@ cat ~/.ssh/id_ed25519.pub</code></pre> <pre data-lang="bash"><code>mkdir chroniques-forge cd chroniques-forge -git init</code></pre> +git init -b main</code></pre> <h3>Étape 2 - Écrire le premier parchemin</h3> @@ -430,7 +430,7 @@ git commit -m "Ajouter les notes sur les forges et le pipeline CI/CD"</code></pr <h3>Étape 4 - Créer la Forge (dépôt bare)</h3> <pre data-lang="bash"><code>cd .. -git init --bare forge-eternelle.git</code></pre> +git init -b main --bare forge-eternelle.git</code></pre> <div class="tip"> <p>Un <strong>dépôt bare</strong> est un dépôt Git sans répertoire de travail - exactement comme un serveur GitHub ou GitLab. Il ne contient que les données Git, pas les fichiers de travail.</p> diff --git a/src/fr/quetes/16-les-actions-du-royaume.njk b/src/fr/quetes/16-les-actions-du-royaume.njk index 8064ec3..44f4c60 100644 --- a/src/fr/quetes/16-les-actions-du-royaume.njk +++ b/src/fr/quetes/16-les-actions-du-royaume.njk @@ -520,7 +520,7 @@ jobs: <pre data-lang="bash"><code>mkdir forge-actions cd forge-actions -git init</code></pre> +git init -b main</code></pre> <h3>Étape 2 - Créer la structure des workflows</h3> diff --git a/src/fr/quetes/17-les-epreuves-automatiques.njk b/src/fr/quetes/17-les-epreuves-automatiques.njk index 75b4c57..5d1d3a6 100644 --- a/src/fr/quetes/17-les-epreuves-automatiques.njk +++ b/src/fr/quetes/17-les-epreuves-automatiques.njk @@ -307,7 +307,7 @@ jobs: <pre data-lang="bash"><code>mkdir epreuves-automatiques cd epreuves-automatiques -git init</code></pre> +git init -b main</code></pre> <h3>Étape 2 - Créer le script de calcul</h3> diff --git a/src/fr/quetes/18-le-deploiement-sacre.njk b/src/fr/quetes/18-le-deploiement-sacre.njk index da40b79..a0b0110 100644 --- a/src/fr/quetes/18-le-deploiement-sacre.njk +++ b/src/fr/quetes/18-le-deploiement-sacre.njk @@ -524,7 +524,7 @@ git push --tags</code></pre> <pre data-lang="bash"><code>mkdir deploiement-sacre cd deploiement-sacre -git init</code></pre> +git init -b main</code></pre> <h3>Étape 2 - Créer le site statique</h3> diff --git a/src/fr/quetes/19-les-autres-forges.njk b/src/fr/quetes/19-les-autres-forges.njk index 8a672bd..3182c42 100644 --- a/src/fr/quetes/19-les-autres-forges.njk +++ b/src/fr/quetes/19-les-autres-forges.njk @@ -480,7 +480,7 @@ script: <pre data-lang="bash"><code>mkdir multi-forge cd multi-forge -git init</code></pre> +git init -b main</code></pre> <h3>Étape 2 - Créer le script de test</h3> diff --git a/src/fr/quetes/20-les-chemins-libres.njk b/src/fr/quetes/20-les-chemins-libres.njk index aebf2e4..a974caf 100644 --- a/src/fr/quetes/20-les-chemins-libres.njk +++ b/src/fr/quetes/20-les-chemins-libres.njk @@ -370,7 +370,7 @@ git maintenance start</code></pre> <pre data-lang="bash"><code>mkdir mon-parcours cd mon-parcours -git init</code></pre> +git init -b main</code></pre> <h3>Étape 2 - Écrire ta réflexion</h3> diff --git a/src/fr/quetes/21-le-client-du-voyageur.njk b/src/fr/quetes/21-le-client-du-voyageur.njk index 1b20a58..8a4ce56 100644 --- a/src/fr/quetes/21-le-client-du-voyageur.njk +++ b/src/fr/quetes/21-le-client-du-voyageur.njk @@ -127,7 +127,7 @@ rad auth <pre data-lang="bash"><code># Crée un dépôt Git normal mkdir mon-projet cd mon-projet -git init +git init -b main echo "# Mon Projet" > README.md git add README.md git commit -m "Premier commit" @@ -378,7 +378,7 @@ rad auth <pre data-lang="bash"><code># Crée un nouveau dépôt mkdir atelier-voyageur cd atelier-voyageur -git init +git init -b main # Crée un premier fichier echo "# Atelier du Voyageur" > README.md diff --git a/src/fr/quetes/22-la-tour-de-guet.njk b/src/fr/quetes/22-la-tour-de-guet.njk index 52b518b..8b15b31 100644 --- a/src/fr/quetes/22-la-tour-de-guet.njk +++ b/src/fr/quetes/22-la-tour-de-guet.njk @@ -475,7 +475,7 @@ rad node status</code></pre> <pre data-lang="bash"><code># Si tu as créé un projet dans la quête 21, note son RID # Sinon, crée-en un rapidement : mkdir test-seed && cd test-seed -git init +git init -b main echo "test" > fichier.txt git add fichier.txt git commit -m "test" diff --git a/src/fr/quetes/23-le-reseau-des-pairs.njk b/src/fr/quetes/23-le-reseau-des-pairs.njk index 701c0a3..786293d 100644 --- a/src/fr/quetes/23-le-reseau-des-pairs.njk +++ b/src/fr/quetes/23-le-reseau-des-pairs.njk @@ -435,7 +435,7 @@ mkdir -p ~/radicle-exercice/bob</code></pre> cd ~/radicle-exercice/alice # Crée un dépôt Git -git init projet-commun +git init -b main projet-commun cd projet-commun # Crée du contenu diff --git a/src/fr/quetes/A1-les-forges-du-titan.njk b/src/fr/quetes/A1-les-forges-du-titan.njk index 516a776..830af28 100644 --- a/src/fr/quetes/A1-les-forges-du-titan.njk +++ b/src/fr/quetes/A1-les-forges-du-titan.njk @@ -554,7 +554,7 @@ dvc add data/dataset.csv <pre data-lang="bash"><code># Créer le projet mkdir projet-forges-titan cd projet-forges-titan -git init +git init -b main # Initialiser LFS git lfs install</code></pre> @@ -562,7 +562,7 @@ git lfs install</code></pre> <pre data-lang="powershell"><code># Créer le projet mkdir projet-forges-titan cd projet-forges-titan -git init +git init -b main # Initialiser LFS git lfs install</code></pre> diff --git a/src/fr/quetes/A2-les-archives-infinies.njk b/src/fr/quetes/A2-les-archives-infinies.njk index 41a7937..1d3af1a 100644 --- a/src/fr/quetes/A2-les-archives-infinies.njk +++ b/src/fr/quetes/A2-les-archives-infinies.njk @@ -87,7 +87,7 @@ dvc version</code></pre> <pre data-lang="bash"><code># DVC s'initialise dans un dépôt Git existant cd mon-projet-ml -git init +git init -b main dvc init # DVC crée un dossier .dvc/ et un fichier .dvcignore @@ -534,7 +534,7 @@ wandb/</code></pre> <pre data-lang="bash"><code># Créer le projet mkdir projet-archives-infinies cd projet-archives-infinies -git init +git init -b main dvc init # Créer la structure @@ -547,7 +547,7 @@ git commit -m "Initialiser le projet avec Git et DVC"</code></pre> <pre data-lang="powershell"><code># Créer le projet mkdir projet-archives-infinies cd projet-archives-infinies -git init +git init -b main dvc init # Créer la structure diff --git a/src/fr/quetes/A3b-les-arcanes-de-la-cite.njk b/src/fr/quetes/A3b-les-arcanes-de-la-cite.njk index 131ceb6..c3de4ec 100644 --- a/src/fr/quetes/A3b-les-arcanes-de-la-cite.njk +++ b/src/fr/quetes/A3b-les-arcanes-de-la-cite.njk @@ -448,7 +448,7 @@ jobs: <pre data-lang="bash"><code># Créer le monorepo mkdir cite-monde-monorepo cd cite-monde-monorepo -git init +git init -b main # Créer la structure mkdir -p apps/frontend/src @@ -461,7 +461,7 @@ mkdir -p docs</code></pre> <pre data-lang="powershell"><code># Créer le monorepo mkdir cite-monde-monorepo cd cite-monde-monorepo -git init +git init -b main # Créer la structure New-Item -ItemType Directory -Force -Path apps/frontend/src diff --git a/src/fr/quetes/A4-les-plans-de-lartificier.njk b/src/fr/quetes/A4-les-plans-de-lartificier.njk index 8e4747d..6f4bcd2 100644 --- a/src/fr/quetes/A4-les-plans-de-lartificier.njk +++ b/src/fr/quetes/A4-les-plans-de-lartificier.njk @@ -474,7 +474,7 @@ gh release create v1.0-rev-a mon-projet-v1.0-rev-a-gerbers.zip \ <pre data-lang="bash"><code># Créer le dépôt mkdir capteur-temperature cd capteur-temperature -git init +git init -b main # Créer l'arborescence hardware mkdir -p hw/electronics/libs/symbols diff --git a/src/fr/quetes/A5-les-courants-du-destin.njk b/src/fr/quetes/A5-les-courants-du-destin.njk index b3e41fd..3495520 100644 --- a/src/fr/quetes/A5-les-courants-du-destin.njk +++ b/src/fr/quetes/A5-les-courants-du-destin.njk @@ -600,7 +600,7 @@ git diff d4e5f6g..a1b2c3d</code></pre> <pre data-lang="bash"><code># Créer le dépôt mkdir taverne-gitops cd taverne-gitops -git init +git init -b main # Créer l'arborescence mkdir -p base diff --git a/src/fr/quetes/A6-latelier-des-enlumineurs.njk b/src/fr/quetes/A6-latelier-des-enlumineurs.njk index e6436ab..4927ec7 100644 --- a/src/fr/quetes/A6-latelier-des-enlumineurs.njk +++ b/src/fr/quetes/A6-latelier-des-enlumineurs.njk @@ -532,7 +532,7 @@ cat build/scss/_variables.scss <pre data-lang="bash"><code># Créer le dépôt mkdir grimoire-visuel cd grimoire-visuel -git init +git init -b main # Créer l'arborescence mkdir -p assets/raw/illustrations diff --git a/tests/lib/common-helpers.bats b/tests/lib/common-helpers.bats new file mode 100644 index 0000000..a921695 --- /dev/null +++ b/tests/lib/common-helpers.bats @@ -0,0 +1,123 @@ +#!/usr/bin/env bats +# +# Tests for the predicate helpers added to lib/common.sh by the +# rigor-sweep work. These helpers are used by quest verifiers via +# check_step, so they must behave correctly in isolation first. + +setup() { + TMP_DIR="$(mktemp -d)" + export TMP_DIR + export GIT_CONFIG_GLOBAL="$TMP_DIR/.gitconfig" + export GIT_CONFIG_SYSTEM=/dev/null + git config --global user.email "test@git-chronicles.local" + git config --global user.name "Test Runner" + git config --global init.defaultBranch main + git config --global commit.gpgsign false + + REPO_ROOT="$(cd "$(dirname "${BATS_TEST_FILENAME}")/../.." && pwd)" + # shellcheck source=/dev/null + source "$REPO_ROOT/lib/common.sh" +} + +teardown() { + [[ -n "${TMP_DIR:-}" && -d "$TMP_DIR" ]] && rm -rf "$TMP_DIR" +} + +# --- assert_remote_name ---------------------------------------------------- + +@test "assert_remote_name returns 0 when the named remote exists" { + cd "$TMP_DIR" + git init -q repo + git -C repo remote add origin /tmp/dummy.git + run assert_remote_name "$TMP_DIR/repo" "origin" + [ "$status" -eq 0 ] +} + +@test "assert_remote_name returns 1 when the named remote is missing" { + cd "$TMP_DIR" + git init -q repo + git -C repo remote add origine /tmp/dummy.git + run assert_remote_name "$TMP_DIR/repo" "origin" + [ "$status" -eq 1 ] +} + +@test "assert_remote_name returns 1 when the path is not a git repo" { + mkdir -p "$TMP_DIR/not-a-repo" + run assert_remote_name "$TMP_DIR/not-a-repo" "origin" + [ "$status" -eq 1 ] +} + +# --- assert_branch_is ------------------------------------------------------ + +@test "assert_branch_is returns 0 when the current branch matches" { + cd "$TMP_DIR" + git init -q repo + ( cd repo && echo a > a.txt && git add a.txt && git commit -q -m "init" ) + run assert_branch_is "$TMP_DIR/repo" "main" + [ "$status" -eq 0 ] +} + +@test "assert_branch_is returns 1 when the current branch differs" { + cd "$TMP_DIR" + git init -q repo + ( cd repo && echo a > a.txt && git add a.txt && git commit -q -m "init" \ + && git branch -m main master ) + run assert_branch_is "$TMP_DIR/repo" "main" + [ "$status" -eq 1 ] +} + +@test "assert_branch_is returns 1 on a non-repo path" { + mkdir -p "$TMP_DIR/not-a-repo" + run assert_branch_is "$TMP_DIR/not-a-repo" "main" + [ "$status" -eq 1 ] +} + +# --- assert_file_contains -------------------------------------------------- + +@test "assert_file_contains returns 0 when pattern matches" { + echo "hello world" > "$TMP_DIR/f.txt" + run assert_file_contains "$TMP_DIR/f.txt" "hello" + [ "$status" -eq 0 ] +} + +@test "assert_file_contains returns 1 when pattern is missing" { + echo "hello world" > "$TMP_DIR/f.txt" + run assert_file_contains "$TMP_DIR/f.txt" "goodbye" + [ "$status" -eq 1 ] +} + +@test "assert_file_contains returns 1 when file does not exist" { + run assert_file_contains "$TMP_DIR/missing.txt" "anything" + [ "$status" -eq 1 ] +} + +# --- assert_same_head ------------------------------------------------------ + +@test "assert_same_head returns 0 when all repos share the same HEAD" { + cd "$TMP_DIR" + git init -q a + ( cd a && echo x > x.txt && git add x.txt && git commit -q -m "init" ) + git clone -q a b + git clone -q a c + run assert_same_head "$TMP_DIR/a" "$TMP_DIR/b" "$TMP_DIR/c" + [ "$status" -eq 0 ] +} + +@test "assert_same_head returns 1 when one repo diverges" { + cd "$TMP_DIR" + git init -q a + ( cd a && echo x > x.txt && git add x.txt && git commit -q -m "init" ) + git clone -q a b + ( cd b && echo y > y.txt && git add y.txt && git commit -q -m "diverge" ) + run assert_same_head "$TMP_DIR/a" "$TMP_DIR/b" + [ "$status" -eq 1 ] +} + +@test "assert_same_head returns 1 when one path is not a git repo" { + cd "$TMP_DIR" + git init -q a + ( cd a && echo x > x.txt && git add x.txt && git commit -q -m "init" ) + mkdir -p b + run assert_same_head "$TMP_DIR/a" "$TMP_DIR/b" + [ "$status" -eq 1 ] +} diff --git a/tests/verifiers/quest-01.bats b/tests/verifiers/quest-01.bats index 7bdffc8..98a7913 100644 --- a/tests/verifiers/quest-01.bats +++ b/tests/verifiers/quest-01.bats @@ -9,11 +9,11 @@ teardown() { tmp_teardown; } QUEST="01-la-guilde-des-archivistes" -@test "quest 01: verifier passes with git + user.name + user.email configured" { - # tmp_setup already sets user.name + user.email in the sandbox. +@test "quest 01: verifier passes with git + user.name + user.email + init.defaultBranch configured" { + # tmp_setup already sets user.name + user.email + init.defaultBranch in the sandbox. mkdir -p "$TMP_DIR/work" run run_verifier "$QUEST" "work" - [[ "$output" == *"3 / 3"* ]] + [[ "$output" == *"4 / 4"* ]] [[ "$output" == *"Congratulations"* || "$output" == *"CONGRATULATIONS"* ]] } @@ -21,7 +21,7 @@ QUEST="01-la-guilde-des-archivistes" git config --global --unset user.name mkdir -p "$TMP_DIR/work" run run_verifier "$QUEST" "work" - [[ "$output" != *"3 / 3"* ]] + [[ "$output" != *"4 / 4"* ]] [[ "$output" == *"nom est configuré"* ]] } @@ -29,6 +29,22 @@ QUEST="01-la-guilde-des-archivistes" git config --global --unset user.email mkdir -p "$TMP_DIR/work" run run_verifier "$QUEST" "work" - [[ "$output" != *"3 / 3"* ]] + [[ "$output" != *"4 / 4"* ]] [[ "$output" == *"email est configuré"* ]] } + +@test "quest 01: fails when init.defaultBranch is not set to main" { + git config --global --unset init.defaultBranch || true + mkdir -p "$TMP_DIR/work" + run run_verifier "$QUEST" "work" + [[ "$output" != *"4 / 4"* ]] + [[ "$output" == *"main"* ]] +} + +@test "quest 01: fails when init.defaultBranch is set to master" { + git config --global init.defaultBranch master + mkdir -p "$TMP_DIR/work" + run run_verifier "$QUEST" "work" + [[ "$output" != *"4 / 4"* ]] + [[ "$output" == *"main"* ]] +} diff --git a/tests/verifiers/quest-02.bats b/tests/verifiers/quest-02.bats index 27c61a6..cac5757 100644 --- a/tests/verifiers/quest-02.bats +++ b/tests/verifiers/quest-02.bats @@ -46,3 +46,27 @@ QUEST="02-les-trois-salles-du-savoir" [[ "$output" != *"4 / 4"* ]] [[ "$output" == *"pas de commit"* ]] } + +@test "quest 02: fails when parchemin.txt is empty (zero bytes)" { + mkdir -p "$TMP_DIR/work" + cd "$TMP_DIR/work" + git init -q + : > parchemin.txt + git add parchemin.txt + cd - >/dev/null + + run run_verifier "$QUEST" "work" + [[ "$output" != *"4 / 4"* ]] +} + +@test "quest 02: fails when staged file is named mon-parchemin.txt instead of parchemin.txt" { + mkdir -p "$TMP_DIR/work" + cd "$TMP_DIR/work" + git init -q + echo "contenu du parchemin" > mon-parchemin.txt + git add mon-parchemin.txt + cd - >/dev/null + + run run_verifier "$QUEST" "work" + [[ "$output" == *"2 / 4"* ]] +} diff --git a/tests/verifiers/quest-03.bats b/tests/verifiers/quest-03.bats index 2eafebb..ae2e39d 100644 --- a/tests/verifiers/quest-03.bats +++ b/tests/verifiers/quest-03.bats @@ -14,16 +14,24 @@ QUEST="03-le-premier-parchemin" # --- Helpers -------------------------------------------------------------- -# Build the canonical "happy path" scenario: a Git repo with 2 commits, -# the second one adding parchemins/mission.txt. +# Build the canonical "happy path" scenario: a Git repo with 2 commits +# (add the shipped template, then complete the mission report), matching +# the pedagogical flow taught in the quest. build_pass_scenario() { mkdir -p "$TMP_DIR/mon-archive" cd "$TMP_DIR/mon-archive" git init -q - git commit -q --allow-empty -m "Initialiser l'archive de la Guilde" cp "$REPO_ROOT/exercises/$QUEST/parchemins/mission.txt" . git add mission.txt git commit -q -m "Ajouter l'ordre de mission de la Guilde" + cat >> mission.txt <<'EOF' + +- Lieu : Foret de Sombrebois, poste n7 +- Creature observee : Wyverne a ecailles argentees +- Niveau de danger (1-5) : 4 +EOF + git add mission.txt + git commit -q -m "Completer le rapport d'observation des frontieres" cd - >/dev/null } @@ -40,7 +48,7 @@ build_pass_scenario() { build_pass_scenario run run_verifier "$QUEST" "mon-archive" [ "$status" -eq 0 ] - [[ "$output" == *"4 / 4"* ]] + [[ "$output" == *"5 / 5"* ]] [[ "$output" == *"CONGRATULATIONS"* || "$output" == *"Congratulations"* ]] } @@ -48,7 +56,7 @@ build_pass_scenario() { build_pass_scenario run run_verifier "$QUEST" "mon-archive" --lang fr [ "$status" -eq 0 ] - [[ "$output" == *"4 / 4"* ]] + [[ "$output" == *"5 / 5"* ]] [[ "$output" == *"FÉLICITATIONS"* ]] } @@ -59,7 +67,7 @@ build_pass_scenario() { # assert on the score line and the absence of the "congratulations" marker. assert_failure_score() { - [[ "$output" != *"4 / 4"* ]] + [[ "$output" != *"5 / 5"* ]] [[ "$output" != *"Congratulations"* && "$output" != *"CONGRATULATIONS"* ]] [[ "$output" != *"FÉLICITATIONS"* ]] } @@ -112,3 +120,17 @@ assert_failure_score() { assert_failure_score [[ "$output" == *"personnalisé"* ]] } + +@test "verifier fails when mission.txt is identical to the shipped template" { + mkdir -p "$TMP_DIR/mon-archive" + cd "$TMP_DIR/mon-archive" + git init -q + cp "$REPO_ROOT/exercises/$QUEST/parchemins/mission.txt" . + git add mission.txt + git commit -q -m "Ajouter l'ordre de mission de la Guilde" + git commit -q --allow-empty -m "Deuxieme commit sans modifier le parchemin" + cd - >/dev/null + + run run_verifier "$QUEST" "mon-archive" + assert_failure_score +} diff --git a/tests/verifiers/quest-04.bats b/tests/verifiers/quest-04.bats index 1ec938b..5f13186 100644 --- a/tests/verifiers/quest-04.bats +++ b/tests/verifiers/quest-04.bats @@ -23,6 +23,11 @@ build_pass_scenario() { # archive-centrale.git is a bare repo. git clone -q --bare ./mon-archive ./archive-centrale.git + # The quest pedagogically teaches the student to link mon-archive to the + # bare repo and push main. Reproduce the same final state here so the + # verifier can validate that the 'git remote add' action was performed. + ( cd mon-archive && git remote add origin ../archive-centrale.git ) + # clone-depuis-bare/ is cloned from the bare repo. git clone -q ./archive-centrale.git ./clone-depuis-bare @@ -32,14 +37,14 @@ build_pass_scenario() { @test "quest 04: verifier passes with full remote + clone + bare setup" { build_pass_scenario run run_verifier "$QUEST" "work" - [[ "$output" == *"4 / 4"* ]] + [[ "$output" == *"10 / 10"* ]] } @test "quest 04: fails when ma-copie/ is missing" { build_pass_scenario rm -rf "$TMP_DIR/work/ma-copie" run run_verifier "$QUEST" "work" - [[ "$output" != *"4 / 4"* ]] + [[ "$output" != *"10 / 10"* ]] [[ "$output" == *"ma-copie"* ]] } @@ -47,6 +52,75 @@ build_pass_scenario() { build_pass_scenario rm -rf "$TMP_DIR/work/archive-centrale.git" run run_verifier "$QUEST" "work" - [[ "$output" != *"4 / 4"* ]] + [[ "$output" != *"10 / 10"* ]] [[ "$output" == *"bare"* ]] } + +@test "quest 04: fails when ma-copie has remote named 'origine' instead of 'origin'" { + build_pass_scenario + cd "$TMP_DIR/work/ma-copie" + git remote rename origin origine + cd - >/dev/null + run run_verifier "$QUEST" "work" + [[ "$output" != *"10 / 10"* ]] + [[ "$output" == *"origin"* ]] +} + +@test "quest 04: fails when ma-copie is on branch master instead of main" { + build_pass_scenario + cd "$TMP_DIR/work/ma-copie" + git branch -m main master + cd - >/dev/null + run run_verifier "$QUEST" "work" + [[ "$output" != *"10 / 10"* ]] + [[ "$output" == *"main"* ]] +} + +@test "quest 04: fails when bare repo HEAD points to nonexistent master" { + build_pass_scenario + echo "ref: refs/heads/master" > "$TMP_DIR/work/archive-centrale.git/HEAD" + run run_verifier "$QUEST" "work" + [[ "$output" != *"10 / 10"* ]] +} + +@test "quest 04: fails when clone-depuis-bare is on branch master" { + build_pass_scenario + cd "$TMP_DIR/work/clone-depuis-bare" + git branch -m main master + cd - >/dev/null + run run_verifier "$QUEST" "work" + [[ "$output" != *"10 / 10"* ]] +} + +@test "quest 04: fails when ma-copie and clone-depuis-bare HEADs diverge" { + build_pass_scenario + cd "$TMP_DIR/work/ma-copie" + echo "extra" > extra.txt + git add extra.txt + git commit -q -m "Diverging commit" + cd - >/dev/null + run run_verifier "$QUEST" "work" + [[ "$output" != *"10 / 10"* ]] +} + +@test "quest 04: fails when clone-depuis-bare has remote named 'origine' instead of 'origin'" { + build_pass_scenario + cd "$TMP_DIR/work/clone-depuis-bare" + git remote rename origin origine + cd - >/dev/null + run run_verifier "$QUEST" "work" + [[ "$output" != *"10 / 10"* ]] + [[ "$output" == *"origin"* ]] +} + +@test "quest 04: fails when mon-archive has no remote (skipped 'git remote add' step)" { + build_pass_scenario + # Undo the pedagogical action: simulate a student who never ran + # 'git remote add origin ../archive-centrale.git' on mon-archive. + cd "$TMP_DIR/work/mon-archive" + git remote remove origin + cd - >/dev/null + run run_verifier "$QUEST" "work" + [[ "$output" != *"10 / 10"* ]] + [[ "$output" == *"mon-archive"* ]] +} diff --git a/tests/verifiers/quest-05.bats b/tests/verifiers/quest-05.bats index 6674d09..1a1f895 100644 --- a/tests/verifiers/quest-05.bats +++ b/tests/verifiers/quest-05.bats @@ -42,3 +42,28 @@ build_from_bundle() { [[ "$output" != *"5 / 5"* ]] [[ "$output" == *".gitignore"* ]] } + +@test "quest 05: fails when .gitignore exists but does not ignore .log files" { + build_from_bundle + echo "*.tmp" > .gitignore + git rm --cached debug.log 2>/dev/null || true + git add .gitignore + git commit -q -m "Ignore temporary files but not logs" + cd - >/dev/null + + run run_verifier "$QUEST" "work" + [[ "$output" != *"5 / 5"* ]] +} + +@test "quest 05: passes when .gitignore uses a nested glob such as **/*.log" { + build_from_bundle + echo "**/*.log" > .gitignore + echo "noisy debug line" > debug.log + git rm --cached debug.log 2>/dev/null || true + git add .gitignore + git commit -q -m "Ignore log files everywhere in the tree" + cd - >/dev/null + + run run_verifier "$QUEST" "work" + [[ "$output" == *"5 / 5"* ]] +} diff --git a/tests/verifiers/quest-06.bats b/tests/verifiers/quest-06.bats index 8b42ce0..370ce2f 100644 --- a/tests/verifiers/quest-06.bats +++ b/tests/verifiers/quest-06.bats @@ -35,7 +35,7 @@ build_pass_scenario() { @test "quest 06: verifier passes with two expedition branches" { build_pass_scenario run run_verifier "$QUEST" "work" - [[ "$output" == *"6 / 6"* ]] + [[ "$output" == *"7 / 7"* ]] } @test "quest 06: fails when expedition-nord branch is missing" { @@ -44,6 +44,16 @@ build_pass_scenario() { git branch -D expedition-nord cd - >/dev/null run run_verifier "$QUEST" "work" - [[ "$output" != *"6 / 6"* ]] + [[ "$output" != *"7 / 7"* ]] [[ "$output" == *"expedition-nord"* ]] } + +@test "quest 06: fails when the student is left on an expedition branch" { + build_pass_scenario + cd "$TMP_DIR/work" + git checkout -q expedition-nord + cd - >/dev/null + run run_verifier "$QUEST" "work" + [[ "$output" != *"7 / 7"* ]] + [[ "$output" == *"main"* ]] +} diff --git a/tests/verifiers/quest-10.bats b/tests/verifiers/quest-10.bats index c8b8902..6aad2a8 100644 --- a/tests/verifiers/quest-10.bats +++ b/tests/verifiers/quest-10.bats @@ -24,7 +24,7 @@ QUEST="10-le-protocole-des-guildes" cd - >/dev/null run run_verifier "$QUEST" "work" - [[ "$output" == *"5 / 5"* ]] + [[ "$output" == *"6 / 6"* ]] } @test "quest 10: fails when the proposition branch has not been cleaned up" { @@ -41,6 +41,43 @@ QUEST="10-le-protocole-des-guildes" cd - >/dev/null run run_verifier "$QUEST" "work" - [[ "$output" != *"5 / 5"* ]] + [[ "$output" != *"6 / 6"* ]] [[ "$output" == *"nettoyée"* ]] } + +@test "quest 10: fails when the merge was fast-forward (no merge commit)" { + mkdir -p "$TMP_DIR/work" + cd "$TMP_DIR/work" + git init -q + echo "a" > a.txt && git add a.txt && git commit -q -m "A" + echo "b" > b.txt && git add b.txt && git commit -q -m "B" + git checkout -q -b proposition + echo "p" > p.txt && git add p.txt && git commit -q -m "Proposition" + git checkout -q main + git merge -q --ff-only proposition + git branch -D proposition + cd - >/dev/null + + run run_verifier "$QUEST" "work" + [[ "$output" != *"6 / 6"* ]] + [[ "$output" == *"Protocole"* ]] +} + +@test "quest 10: fails when the student is still on the proposition branch" { + mkdir -p "$TMP_DIR/work" + cd "$TMP_DIR/work" + git init -q + echo "a" > a.txt && git add a.txt && git commit -q -m "A" + echo "b" > b.txt && git add b.txt && git commit -q -m "B" + git checkout -q -b proposition + echo "p" > p.txt && git add p.txt && git commit -q -m "Proposition" + git checkout -q main + git merge -q --no-ff -m "Intégrer la proposition" proposition + # The student forgot to switch back to main and stays on proposition. + git checkout -q proposition + cd - >/dev/null + + run run_verifier "$QUEST" "work" + [[ "$output" != *"6 / 6"* ]] + [[ "$output" == *"main"* ]] +} diff --git a/tests/verifiers/quest-11.bats b/tests/verifiers/quest-11.bats index cbea9af..104948b 100644 --- a/tests/verifiers/quest-11.bats +++ b/tests/verifiers/quest-11.bats @@ -18,28 +18,50 @@ QUEST="11-le-tisseur-de-temps" echo "c" > c.txt && git add c.txt && git commit -q -m "C" git checkout -q -b travail - # Trigger a real stash so refs/stash exists in the reflog. We don't pop it, - # because `git stash pop` removes refs/stash once the stash stack is empty - # and the verifier's step 3 needs a reflog entry on that ref. echo "wip" >> a.txt git stash push -q -m "Travail en cours" + git stash pop -q + git checkout -q main cd - >/dev/null run run_verifier "$QUEST" "work" - [[ "$output" == *"4 / 4"* ]] + [[ "$output" == *"5 / 5"* ]] } -@test "quest 11: fails without any stash activity" { +@test "quest 11: fails when a stash is still pending (never popped/applied)" { mkdir -p "$TMP_DIR/work" cd "$TMP_DIR/work" git init -q echo "a" > a.txt && git add a.txt && git commit -q -m "A" echo "b" > b.txt && git add b.txt && git commit -q -m "B" echo "c" > c.txt && git add c.txt && git commit -q -m "C" + git checkout -q -b travail + echo "wip" >> a.txt + git stash push -q -m "Travail en cours" + # Deliberately do NOT pop, so the stash stack stays non-empty. + git checkout -q main + + cd - >/dev/null + run run_verifier "$QUEST" "work" + [[ "$output" != *"5 / 5"* ]] +} + +@test "quest 11: fails when the student is left on the work branch" { + mkdir -p "$TMP_DIR/work" + cd "$TMP_DIR/work" + git init -q + echo "a" > a.txt && git add a.txt && git commit -q -m "A" + echo "b" > b.txt && git add b.txt && git commit -q -m "B" + echo "c" > c.txt && git add c.txt && git commit -q -m "C" + git checkout -q -b travail + echo "wip" >> a.txt + git stash push -q -m "Travail en cours" + git stash pop -q + # Student forgets to switch back to main. cd - >/dev/null run run_verifier "$QUEST" "work" - [[ "$output" != *"4 / 4"* ]] - [[ "$output" == *"stash"* ]] + [[ "$output" != *"5 / 5"* ]] + [[ "$output" == *"main"* ]] } diff --git a/tests/verifiers/quest-12.bats b/tests/verifiers/quest-12.bats index c626ed3..75d23f5 100644 --- a/tests/verifiers/quest-12.bats +++ b/tests/verifiers/quest-12.bats @@ -14,51 +14,88 @@ QUEST="12-loracle-du-code" [ -f "$REPO_ROOT/exercises/$QUEST/preparer-archive.sh" ] } -@test "quest 12: verifier passes with bisect + cherry-pick reflog and clean grimoire" { +# Build a repo that mirrors the quest's shipped bundle: main with a +# CORROMPU bug, plus a 'correctif' branch whose tip fixes it. Simulates +# the student's full workflow (bisect + cherry-pick from correctif). +# +# The CORROMPU line is inserted in the middle of the file and +# subsequent commits append at the end, so the correctif's "remove +# CORROMPU" diff cherry-picks cleanly onto main. +build_pass_scenario() { mkdir -p "$TMP_DIR/work" cd "$TMP_DIR/work" git init -q - echo "ligne 1" > grimoire.txt + printf 'ligne 1\nMARKER\nligne 3\n' > grimoire.txt git add grimoire.txt git commit -q -m "Grimoire initial" - - for i in 2 3 4; do - echo "ligne $i" >> grimoire.txt - git commit -q -am "Ajout ligne $i" - done - h1=$(git rev-parse HEAD) - # Introduce a CORROMPU line (the "bug") then clean again. - echo "CORROMPU" >> grimoire.txt + # Introduce a CORROMPU line by replacing MARKER (middle of file). + sed -i 's/MARKER/CORROMPU/' grimoire.txt git commit -q -am "Ligne corrompue" h2=$(git rev-parse HEAD) - echo "ligne 6" >> grimoire.txt - git commit -q -am "Ajout ligne 6" + # Later commits append lines at the end - they do not touch the + # CORROMPU line, so the correctif diff stays clean. + echo "ligne 4" >> grimoire.txt + git commit -q -am "Ajout ligne 4" h3=$(git rev-parse HEAD) + echo "ligne 5" >> grimoire.txt + git commit -q -am "Ajout ligne 5" - # Fix commit we will cherry-pick after bisect. - git checkout -q -b fix - sed -i '/CORROMPU/d' grimoire.txt - git commit -q -am "Purifier le grimoire" + # Fix commit on a dedicated branch, mirroring origin/correctif. + git checkout -q -b correctif "$h2" + sed -i 's/CORROMPU/ligne 2/' grimoire.txt + git commit -q -am "Corriger la formule corrompue de la Lance de Feu" fix_sha=$(git rev-parse HEAD) git checkout -q main - # Simulate bisect traces: step 2 of the verifier counts reflog entries of - # the form "checkout: moving from <hash> to <hash>" and needs >= 2. A plain - # `checkout main` ends on a branch name (not a hash), so we chain at least - # three detached checkouts to guarantee two hash→hash transitions. + # Simulate bisect traces (>= 2 hash->hash transitions). git -c advice.detachedHead=false checkout -q "$h1" git -c advice.detachedHead=false checkout -q "$h2" git -c advice.detachedHead=false checkout -q "$h3" git checkout -q main - # Cherry-pick the fix onto main (cherry-pick has no -q flag). + # Cherry-pick the fix onto main. git cherry-pick "$fix_sha" >/dev/null cd - >/dev/null +} + +@test "quest 12: verifier passes with bisect + cherry-pick and clean grimoire" { + build_pass_scenario run run_verifier "$QUEST" "work" [[ "$output" == *"4 / 4"* ]] } + +@test "quest 12: fails when grimoire was fixed manually (no cherry-pick commit on HEAD)" { + mkdir -p "$TMP_DIR/work" + cd "$TMP_DIR/work" + git init -q + + printf 'ligne 1\nMARKER\nligne 3\n' > grimoire.txt + git add grimoire.txt + git commit -q -m "Grimoire initial" + sed -i 's/MARKER/CORROMPU/' grimoire.txt + git commit -q -am "Ligne corrompue" + h2=$(git rev-parse HEAD) + echo "ligne 4" >> grimoire.txt + git commit -q -am "Ajout ligne 4" + + # Ship a fix on a separate branch (mirrors origin/correctif). + git checkout -q -b correctif "$h2" + sed -i 's/CORROMPU/ligne 2/' grimoire.txt + git commit -q -am "Corriger la formule corrompue de la Lance de Feu" + git checkout -q main + + # Student cheats: edits the file directly, commits under a different + # subject. No cherry-pick of the correctif commit. + sed -i 's/CORROMPU/ligne 2/' grimoire.txt + git commit -q -am "Nettoyage express" + + cd - >/dev/null + run run_verifier "$QUEST" "work" + [[ "$output" != *"4 / 4"* ]] + [[ "$output" == *"correctif"* ]] +} diff --git a/tests/verifiers/quest-13.bats b/tests/verifiers/quest-13.bats index a3281db..eef259d 100644 --- a/tests/verifiers/quest-13.bats +++ b/tests/verifiers/quest-13.bats @@ -38,3 +38,29 @@ build_pass_scenario() { [[ "$output" != *"4 / 4"* ]] [[ "$output" == *"versionnage"* ]] } + +@test "quest 13: fails when no tag is annotated (all lightweight)" { + mkdir -p "$TMP_DIR/work" + cd "$TMP_DIR/work" + git init -q + echo "a" > a.txt && git add a.txt && git commit -q -m "Initial" + git tag v1.0.0 + git tag v1.1.0 + cd - >/dev/null + run run_verifier "$QUEST" "work" + [[ "$output" != *"4 / 4"* ]] + [[ "$output" == *"annoté"* ]] +} + +@test "quest 13: fails when one of the tags does not follow semver" { + mkdir -p "$TMP_DIR/work" + cd "$TMP_DIR/work" + git init -q + echo "a" > a.txt && git add a.txt && git commit -q -m "Initial" + git tag v1.0.0 + git tag -a release-notes -m "Not a semver tag" + cd - >/dev/null + run run_verifier "$QUEST" "work" + [[ "$output" != *"4 / 4"* ]] + [[ "$output" == *"versionnage"* ]] +} diff --git a/tests/verifiers/quest-14.bats b/tests/verifiers/quest-14.bats index 03cd6bc..9d11a9a 100644 --- a/tests/verifiers/quest-14.bats +++ b/tests/verifiers/quest-14.bats @@ -51,3 +51,16 @@ EOF [[ "$output" != *"5 / 5"* ]] [[ "$output" == *"TODO"* ]] } + +@test "quest 14: fails when pre-commit only mentions TODO in a comment (no failure path)" { + build_pass_scenario + cat > "$TMP_DIR/work/.git/hooks/pre-commit" <<'EOF' +#!/usr/bin/env bash +# This hook is supposed to block TODO markers. +exit 0 +EOF + chmod +x "$TMP_DIR/work/.git/hooks/pre-commit" + run run_verifier "$QUEST" "work" + [[ "$output" != *"5 / 5"* ]] + [[ "$output" == *"TODO"* ]] +} diff --git a/tests/verifiers/quest-15.bats b/tests/verifiers/quest-15.bats index 8aa6845..e17cf0c 100644 --- a/tests/verifiers/quest-15.bats +++ b/tests/verifiers/quest-15.bats @@ -25,7 +25,7 @@ QUEST="15-les-forges-eternelles" cd - >/dev/null run run_verifier "$QUEST" "work" - [[ "$output" == *"4 / 4"* ]] + [[ "$output" == *"5 / 5"* ]] } @test "quest 15: fails without a remote" { @@ -40,6 +40,47 @@ QUEST="15-les-forges-eternelles" cd - >/dev/null run run_verifier "$QUEST" "work" - [[ "$output" != *"4 / 4"* ]] + [[ "$output" != *"5 / 5"* ]] [[ "$output" == *"remote"* ]] } + +@test "quest 15: fails when the remote is not named origin" { + git init -q --bare "$TMP_DIR/forge.git" + + mkdir -p "$TMP_DIR/work" + cd "$TMP_DIR/work" + git init -q + for i in 1 2 3; do + echo "commit $i" > "f$i.txt" + git add "f$i.txt" + git commit -q -m "Commit $i" + done + git remote add portail "$TMP_DIR/forge.git" + git push -q -u portail main + cd - >/dev/null + + run run_verifier "$QUEST" "work" + [[ "$output" != *"5 / 5"* ]] + [[ "$output" == *"origin"* ]] +} + +@test "quest 15: fails when the student is not on main at the end" { + git init -q --bare "$TMP_DIR/forge.git" + + mkdir -p "$TMP_DIR/work" + cd "$TMP_DIR/work" + git init -q + for i in 1 2 3; do + echo "commit $i" > "f$i.txt" + git add "f$i.txt" + git commit -q -m "Commit $i" + done + git remote add origin "$TMP_DIR/forge.git" + git push -q -u origin main + git checkout -q -b travail + cd - >/dev/null + + run run_verifier "$QUEST" "work" + [[ "$output" != *"5 / 5"* ]] + [[ "$output" == *"main"* ]] +} diff --git a/tests/verifiers/quest-17.bats b/tests/verifiers/quest-17.bats index dedaf0c..4ed550d 100644 --- a/tests/verifiers/quest-17.bats +++ b/tests/verifiers/quest-17.bats @@ -61,3 +61,63 @@ EOF [[ "$output" != *"5 / 5"* ]] [[ "$output" == *"matrix"* ]] } + +@test "quest 17: fails when workflow has bare 'name:' lines but zero indented steps" { + mkdir -p "$TMP_DIR/work/.github/workflows" + cd "$TMP_DIR/work" + git init -q + cat > .github/workflows/ci.yml <<'YAML' +name: Fake Workflow +on: push +jobs: + fake: + runs-on: ubuntu-latest + name: fake-job + strategy: + matrix: + os: [ubuntu, macos] +YAML + cat > test-script.sh <<'SH' +#!/usr/bin/env bash +echo ok +SH + chmod +x test-script.sh + git add . && git commit -q -m "fake workflow" + cd - >/dev/null + + run run_verifier "$QUEST" "work" + # Step 4 (multiple steps) should fail because the workflow has zero + # indented '- name:' entries even though it has 2 bare 'name:' lines. + [[ "$output" != *"5 / 5"* ]] +} + +@test "quest 17: fails when workflow has exactly one indented step" { + mkdir -p "$TMP_DIR/work/.github/workflows" "$TMP_DIR/work/tests" + cd "$TMP_DIR/work" + git init -q + cat > .github/workflows/ci.yml <<'YAML' +name: Single Step Workflow +on: [push] +jobs: + test: + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout + uses: actions/checkout@v4 +YAML + cat > tests/run.sh <<'SH' +#!/usr/bin/env bash +echo ok +SH + chmod +x tests/run.sh + git add . && git commit -q -m "single-step workflow" + cd - >/dev/null + + run run_verifier "$QUEST" "work" + # Step 4 requires at least TWO indented '- name:' entries. A workflow + # with exactly one step should therefore fail step 4 and not reach 5/5. + [[ "$output" != *"5 / 5"* ]] +} diff --git a/tests/verifiers/quest-18.bats b/tests/verifiers/quest-18.bats index 721a5d7..5e552f2 100644 --- a/tests/verifiers/quest-18.bats +++ b/tests/verifiers/quest-18.bats @@ -49,3 +49,29 @@ EOF [[ "$output" != *"4 / 4"* ]] [[ "$output" == *"tag"* ]] } + +@test "quest 18: fails when deploy/production are only mentioned in comments" { + mkdir -p "$TMP_DIR/work/.github/workflows" + cd "$TMP_DIR/work" + git init -q + cat > .github/workflows/deploy.yml <<'EOF' +# This workflow will one day deploy to production. +name: Lint +on: + push: + branches: [main] +jobs: + lint: + runs-on: ubuntu-latest + steps: + - run: echo "lint only, nothing real" +EOF + git add . + git commit -q -m "Add placeholder workflow" + git tag v1.0.0 + cd - >/dev/null + + run run_verifier "$QUEST" "work" + [[ "$output" != *"4 / 4"* ]] + [[ "$output" == *"déploiement"* ]] +} diff --git a/tests/verifiers/quest-19.bats b/tests/verifiers/quest-19.bats index 4b59065..592a821 100644 --- a/tests/verifiers/quest-19.bats +++ b/tests/verifiers/quest-19.bats @@ -61,3 +61,24 @@ EOF [[ "$output" != *"6 / 6"* ]] [[ "$output" == *"gitlab-ci"* ]] } + +@test "quest 19: fails when .github/workflows/ci.yml is empty" { + build_pass_scenario + : > "$TMP_DIR/work/.github/workflows/ci.yml" + run run_verifier "$QUEST" "work" + [[ "$output" != *"6 / 6"* ]] +} + +@test "quest 19: fails when .gitlab-ci.yml has no job definition" { + build_pass_scenario + echo "# GitLab CI placeholder" > "$TMP_DIR/work/.gitlab-ci.yml" + run run_verifier "$QUEST" "work" + [[ "$output" != *"6 / 6"* ]] +} + +@test "quest 19: fails when bitbucket-pipelines.yml has no pipelines key" { + build_pass_scenario + echo "# Not a pipeline yet" > "$TMP_DIR/work/bitbucket-pipelines.yml" + run run_verifier "$QUEST" "work" + [[ "$output" != *"6 / 6"* ]] +}