From dfa0c6c9e40d4214062a7a8d1b60d7e5a107d92d Mon Sep 17 00:00:00 2001 From: Dxsk <43427519+Dxsk@users.noreply.github.com> Date: Sat, 11 Apr 2026 19:05:46 +0200 Subject: [PATCH 01/27] feat(lib): add rigor predicate helpers and show_section Add silent predicate helpers (assert_remote_name, assert_branch_is, assert_file_contains, assert_same_head) and a cosmetic show_section header to lib/common.sh. These will back the rigor sweep across the quest verifiers. Helpers are tested in isolation in tests/lib/common-helpers.bats, and make test-bats now picks up the new tests/lib directory. --- Makefile | 2 +- lib/common.sh | 67 ++++++++++++++++++ tests/lib/common-helpers.bats | 123 ++++++++++++++++++++++++++++++++++ 3 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 tests/lib/common-helpers.bats diff --git a/Makefile b/Makefile index 38f0499..8cee467 100644 --- a/Makefile +++ b/Makefile @@ -136,7 +136,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/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/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 ] +} From 121c3bf97a7e3c2e9008e0fb88f9e517c9d36ab8 Mon Sep 17 00:00:00 2001 From: Dxsk <43427519+Dxsk@users.noreply.github.com> Date: Sat, 11 Apr 2026 19:08:02 +0200 Subject: [PATCH 02/27] feat(quest-01): teach init.defaultBranch=main as global config Teach students to set init.defaultBranch=main globally as part of the first-time git configuration. This is the systemic root fix (Option B from the rigor sweep spec) for the Murphy bug, where 7 subsequent quests do 'git init' and then assume a 'main' branch exists. With this config in place, all those quests just work without per-quest patches. Verifier and bats tests updated to enforce the new config. --- .../01-la-guilde-des-archivistes/verifier.sh | 2 ++ src/en/quests/01-the-archivists-guild.njk | 15 +++++++++-- .../quetes/01-la-guilde-des-archivistes.njk | 15 +++++++++-- tests/verifiers/quest-01.bats | 26 +++++++++++++++---- 4 files changed, 49 insertions(+), 9 deletions(-) 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/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/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/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"* ]] +} From 73607cd7bd4c922c8257e26ba3d602ee78aacc21 Mon Sep 17 00:00:00 2001 From: Dxsk <43427519+Dxsk@users.noreply.github.com> Date: Sat, 11 Apr 2026 19:09:16 +0200 Subject: [PATCH 03/27] fix(quest-04): enforce remote name, branch, and cross-repo consistency Quest 04's verifier now uses the new rigor helpers to validate that ma-copie has a remote named exactly 'origin', that all three repos are on branch 'main', that the bare repo's HEAD points to main, and that ma-copie and clone-depuis-bare share the same HEAD commit. Five new bats negative tests reproduce the broken states that Murphy's report (issue #9) hit, and they all fail before the verifier fix and pass after. Step count goes from 4 to 8. --- exercises/04-larchive-est-partout/verifier.sh | 40 ++++++++++---- tests/verifiers/quest-04.bats | 53 +++++++++++++++++-- 2 files changed, 81 insertions(+), 12 deletions(-) diff --git a/exercises/04-larchive-est-partout/verifier.sh b/exercises/04-larchive-est-partout/verifier.sh index b05dcb2..3d62174 100755 --- a/exercises/04-larchive-est-partout/verifier.sh +++ b/exercises/04-larchive-est-partout/verifier.sh @@ -32,25 +32,47 @@ 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"' -# ---- Step 3 : archive-centrale.git/ est un bare repo ---- -check_step 3 "archive-centrale.git/ existe et c'est un bare repo" \ +show_section "Depot central (bare)" + +# ---- 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"' + +show_section "Coherence inter-depots" + +# ---- Step 8 : Les trois depots partagent le meme HEAD ---- +check_step 8 "Les trois depots partagent le meme HEAD" \ + 'assert_same_head "$WORKDIR/ma-copie" "$WORKDIR/clone-depuis-bare"' + show_score diff --git a/tests/verifiers/quest-04.bats b/tests/verifiers/quest-04.bats index 1ec938b..5498d23 100644 --- a/tests/verifiers/quest-04.bats +++ b/tests/verifiers/quest-04.bats @@ -32,14 +32,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" == *"8 / 8"* ]] } @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" != *"8 / 8"* ]] [[ "$output" == *"ma-copie"* ]] } @@ -47,6 +47,53 @@ build_pass_scenario() { build_pass_scenario rm -rf "$TMP_DIR/work/archive-centrale.git" run run_verifier "$QUEST" "work" - [[ "$output" != *"4 / 4"* ]] + [[ "$output" != *"8 / 8"* ]] [[ "$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" != *"8 / 8"* ]] + [[ "$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" != *"8 / 8"* ]] + [[ "$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" != *"8 / 8"* ]] +} + +@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" != *"8 / 8"* ]] +} + +@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" != *"8 / 8"* ]] +} From 13d56bb3568201e5475d37083c85f30203de0221 Mon Sep 17 00:00:00 2001 From: Dxsk <43427519+Dxsk@users.noreply.github.com> Date: Sat, 11 Apr 2026 19:10:30 +0200 Subject: [PATCH 04/27] fix(quest-04): replace origine with origin in code blocks Quest 04's prose explicitly says the remote should be named 'origin' but the code blocks (and their PowerShell/recap variants) all teach 'git remote add origine ...' due to a francization typo. Since 'origine' is not a standard remote name, students following the instructions literally cannot complete the quest. Twelve occurrences fixed total (6 in FR, 6 in EN). Closes part of issue #9. --- src/en/quests/04-the-archive-is-everywhere.njk | 12 ++++++------ src/fr/quetes/04-larchive-est-partout.njk | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/en/quests/04-the-archive-is-everywhere.njk b/src/en/quests/04-the-archive-is-everywhere.njk index 951d6e4..7132431 100644 --- a/src/en/quests/04-the-archive-is-everywhere.njk +++ b/src/en/quests/04-the-archive-is-everywhere.njk @@ -99,16 +99,16 @@ git init --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 # 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> @@ -247,8 +247,8 @@ git init --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/fr/quetes/04-larchive-est-partout.njk b/src/fr/quetes/04-larchive-est-partout.njk index 732ad67..eb94ce1 100644 --- a/src/fr/quetes/04-larchive-est-partout.njk +++ b/src/fr/quetes/04-larchive-est-partout.njk @@ -99,16 +99,16 @@ git init --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 # 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> @@ -247,8 +247,8 @@ git init --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 From c0048f159bdc2a61a3b33c20681007cb7f50f6fe Mon Sep 17 00:00:00 2001 From: Dxsk <43427519+Dxsk@users.noreply.github.com> Date: Sat, 11 Apr 2026 19:11:39 +0200 Subject: [PATCH 05/27] fix(quest-08): use git switch instead of deprecated git checkout Quest 06 explicitly teaches 'git switch' as the modern way to change branches (since Git 2.23). Quest 08 uses 'git checkout main' twice in each language, contradicting that lesson. This commit aligns quest 08 with the modern style for pedagogical consistency. The commands are functionally equivalent, no verifier change needed. --- src/en/quests/08-rewriting-history.njk | 4 ++-- src/fr/quetes/08-reecrire-lhistoire.njk | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/en/quests/08-rewriting-history.njk b/src/en/quests/08-rewriting-history.njk index ea9701f..1ccfdbf 100644 --- a/src/en/quests/08-rewriting-history.njk +++ b/src/en/quests/08-rewriting-history.njk @@ -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) @@ -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 diff --git a/src/fr/quetes/08-reecrire-lhistoire.njk b/src/fr/quetes/08-reecrire-lhistoire.njk index 59e6077..8cc1ab9 100644 --- a/src/fr/quetes/08-reecrire-lhistoire.njk +++ b/src/fr/quetes/08-reecrire-lhistoire.njk @@ -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) @@ -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 From 94783dde585998d3f4206f4122ed321a6f746007 Mon Sep 17 00:00:00 2001 From: Dxsk <43427519+Dxsk@users.noreply.github.com> Date: Sat, 11 Apr 2026 19:12:41 +0200 Subject: [PATCH 06/27] fix(quest-17): correctly count workflow steps in verifier Step 4 used grep -c 'name:' to detect 'multiple steps' in a workflow, but this counted ALL name fields (workflow name, job name, etc.), not just the indented step entries. A workflow with zero real steps but two bare 'name:' lines would silently pass. Now counts indented '- name:' entries, which is the conventional YAML form for step list items. A new bats test exposes the original bug and locks in the fix. --- .../17-les-epreuves-automatiques/verifier.sh | 9 ++++-- tests/verifiers/quest-17.bats | 29 +++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) 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/tests/verifiers/quest-17.bats b/tests/verifiers/quest-17.bats index dedaf0c..aa21fa8 100644 --- a/tests/verifiers/quest-17.bats +++ b/tests/verifiers/quest-17.bats @@ -61,3 +61,32 @@ 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"* ]] +} From 59d575ce737a3108f435e8d513188ee6e6d5a138 Mon Sep 17 00:00:00 2001 From: Dxsk <43427519+Dxsk@users.noreply.github.com> Date: Sat, 11 Apr 2026 19:46:03 +0200 Subject: [PATCH 07/27] feat(lint): add lint-content target for git command regression prevention Add a new linter that scans src/**/*.njk for two forbidden patterns: 1. 'origine' used as a git remote name (francization typo that caused part of issue #9). 2. 'git checkout main/master' in code blocks (deprecated in favor of 'git switch' since Git 2.23, contradicted the teaching of quest 06). Both patterns were fixed in the rigor sweep. This linter prevents them from creeping back in via future edits. Matches inside bash comments ('# ...') are ignored so pedagogical counter-examples stay valid. Integrated as 'make lint-content', wired into the top-level 'make lint' and therefore 'make check'. --- Makefile | 6 +++- scripts/makefile/run-lint-content.sh | 53 ++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100755 scripts/makefile/run-lint-content.sh diff --git a/Makefile b/Makefile index 8cee467..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 diff --git a/scripts/makefile/run-lint-content.sh b/scripts/makefile/run-lint-content.sh new file mode 100755 index 0000000..7fb7d10 --- /dev/null +++ b/scripts/makefile/run-lint-content.sh @@ -0,0 +1,53 @@ +#!/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 main/master' in a code block. +# Since Git 2.23, 'git switch' is the preferred way to change branches. Quest +# 06 teaches this, so later quests should not contradict it by using the +# legacy form in their code blocks. Matches that are commented out ('# ...') +# are ignored so counter-example sentences remain valid. +# ----------------------------------------------------------------------------- +hits_checkout=$(grep -rnE --include='*.njk' \ + 'git[[:space:]]+checkout[[:space:]]+(main|master)' \ + src/ 2>/dev/null | grep -vE '#.*git[[:space:]]+checkout' || true) + +if [ -n "$hits_checkout" ]; then + echo "$hits_checkout" + echo "✗ lint-content: 'git checkout main/master' in code (prefer 'git switch')" >&2 + fail=1 +fi + +if [ "$fail" -eq 0 ]; then + echo "✓ lint-content: clean." +fi + +exit "$fail" From 5edb304759b5acdf7f9def47beea264fe540bba6 Mon Sep 17 00:00:00 2001 From: Dxsk <43427519+Dxsk@users.noreply.github.com> Date: Sat, 11 Apr 2026 19:46:39 +0200 Subject: [PATCH 08/27] test(quest-17): add boundary test for exactly one indented step Quest 17 step 4 requires at least TWO indented '- name:' entries. The existing coverage had 'zero steps' (negative) and 'many steps' (happy path), but not the boundary case of exactly one step. Add that test to lock in the '>= 2' threshold. --- tests/verifiers/quest-17.bats | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/verifiers/quest-17.bats b/tests/verifiers/quest-17.bats index aa21fa8..4ed550d 100644 --- a/tests/verifiers/quest-17.bats +++ b/tests/verifiers/quest-17.bats @@ -90,3 +90,34 @@ SH # 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"* ]] +} From a1c1fc0574f27ff5bd3150d053a9be7042d1349a Mon Sep 17 00:00:00 2001 From: Dxsk <43427519+Dxsk@users.noreply.github.com> Date: Sat, 11 Apr 2026 19:48:13 +0200 Subject: [PATCH 09/27] fix(quest-04): require clone-depuis-bare to have a remote named 'origin' The rigor sweep already validated that ma-copie has a remote named 'origin', but clone-depuis-bare was only checked for being a git repo, not for its remote nomenclature. A student could finish the quest with a correctly-named remote on ma-copie and a badly-named remote on clone-depuis-bare, and the verifier would silently pass. Add a new step 8 (clone-depuis-bare has remote 'origin') before the inter-repo HEAD consistency check (now step 9). A new bats negative test reproduces the broken state and locks in the fix. Step count goes from 8 to 9. --- exercises/04-larchive-est-partout/verifier.sh | 8 ++++-- tests/verifiers/quest-04.bats | 26 +++++++++++++------ 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/exercises/04-larchive-est-partout/verifier.sh b/exercises/04-larchive-est-partout/verifier.sh index 3d62174..71bb3e8 100755 --- a/exercises/04-larchive-est-partout/verifier.sh +++ b/exercises/04-larchive-est-partout/verifier.sh @@ -69,10 +69,14 @@ check_step 6 "clone-depuis-bare/ existe et c'est un repo Git" \ 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 "Coherence inter-depots" -# ---- Step 8 : Les trois depots partagent le meme HEAD ---- -check_step 8 "Les trois depots partagent le meme HEAD" \ +# ---- Step 9 : Les trois depots partagent le meme HEAD ---- +check_step 9 "Les trois depots partagent le meme HEAD" \ 'assert_same_head "$WORKDIR/ma-copie" "$WORKDIR/clone-depuis-bare"' show_score diff --git a/tests/verifiers/quest-04.bats b/tests/verifiers/quest-04.bats index 5498d23..a5f1d7c 100644 --- a/tests/verifiers/quest-04.bats +++ b/tests/verifiers/quest-04.bats @@ -32,14 +32,14 @@ build_pass_scenario() { @test "quest 04: verifier passes with full remote + clone + bare setup" { build_pass_scenario run run_verifier "$QUEST" "work" - [[ "$output" == *"8 / 8"* ]] + [[ "$output" == *"9 / 9"* ]] } @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" != *"8 / 8"* ]] + [[ "$output" != *"9 / 9"* ]] [[ "$output" == *"ma-copie"* ]] } @@ -47,7 +47,7 @@ build_pass_scenario() { build_pass_scenario rm -rf "$TMP_DIR/work/archive-centrale.git" run run_verifier "$QUEST" "work" - [[ "$output" != *"8 / 8"* ]] + [[ "$output" != *"9 / 9"* ]] [[ "$output" == *"bare"* ]] } @@ -57,7 +57,7 @@ build_pass_scenario() { git remote rename origin origine cd - >/dev/null run run_verifier "$QUEST" "work" - [[ "$output" != *"8 / 8"* ]] + [[ "$output" != *"9 / 9"* ]] [[ "$output" == *"origin"* ]] } @@ -67,7 +67,7 @@ build_pass_scenario() { git branch -m main master cd - >/dev/null run run_verifier "$QUEST" "work" - [[ "$output" != *"8 / 8"* ]] + [[ "$output" != *"9 / 9"* ]] [[ "$output" == *"main"* ]] } @@ -75,7 +75,7 @@ build_pass_scenario() { build_pass_scenario echo "ref: refs/heads/master" > "$TMP_DIR/work/archive-centrale.git/HEAD" run run_verifier "$QUEST" "work" - [[ "$output" != *"8 / 8"* ]] + [[ "$output" != *"9 / 9"* ]] } @test "quest 04: fails when clone-depuis-bare is on branch master" { @@ -84,7 +84,7 @@ build_pass_scenario() { git branch -m main master cd - >/dev/null run run_verifier "$QUEST" "work" - [[ "$output" != *"8 / 8"* ]] + [[ "$output" != *"9 / 9"* ]] } @test "quest 04: fails when ma-copie and clone-depuis-bare HEADs diverge" { @@ -95,5 +95,15 @@ build_pass_scenario() { git commit -q -m "Diverging commit" cd - >/dev/null run run_verifier "$QUEST" "work" - [[ "$output" != *"8 / 8"* ]] + [[ "$output" != *"9 / 9"* ]] +} + +@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" != *"9 / 9"* ]] + [[ "$output" == *"origin"* ]] } From d3b4a4875ec910b5c136d90c15d02de5dcc20494 Mon Sep 17 00:00:00 2001 From: Dxsk <43427519+Dxsk@users.noreply.github.com> Date: Sat, 11 Apr 2026 20:51:23 +0200 Subject: [PATCH 10/27] fix(quest-08): replace remaining git checkout with git switch The previous quest 08 commit fixed 'git checkout main' -> 'git switch main' on lines 106 and 340 but left four other 'git checkout' calls untouched. The result was an internally inconsistent practical exercise (steps 4-6) alternating old and new syntax within the same code blocks. Fix the four remaining occurrences: - line 88: git checkout feature -> git switch feature - line 121: git checkout feature -> git switch feature - line 326: git checkout -b refonte-... -> git switch -c refonte-... - line 350: git checkout refonte-... -> git switch refonte-... Quest 08 now uses 'git switch' exclusively, consistent with quest 06 which explicitly teaches it as the modern form. No verifier change needed (the verifier checks state, not commands). --- src/en/quests/08-rewriting-history.njk | 8 ++++---- src/fr/quetes/08-reecrire-lhistoire.njk | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/en/quests/08-rewriting-history.njk b/src/en/quests/08-rewriting-history.njk index 1ccfdbf..bb62735 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> @@ -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) @@ -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 @@ -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/fr/quetes/08-reecrire-lhistoire.njk b/src/fr/quetes/08-reecrire-lhistoire.njk index 8cc1ab9..db6d8b7 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> @@ -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) @@ -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 @@ -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> From 4220b7cf8de47e46206c92e62085116eaf570e1a Mon Sep 17 00:00:00 2001 From: Dxsk <43427519+Dxsk@users.noreply.github.com> Date: Sat, 11 Apr 2026 20:58:21 +0200 Subject: [PATCH 11/27] fix(quests): make git init commands robust with explicit -b main The rigor sweep's Option B (teach init.defaultBranch=main in quest 01) fixes the Murphy bug ONLY if the student completes quest 01 before any other quest that runs git init. In practice, the 50+ quest files containing 'git init' in their exercises had zero explicit mention of this prerequisite, so any student skipping quest 01 would reproduce the original bug on each of them. Instead of adding verbose 'prerequisite: quest 01' notices to every affected file, make each git init command robust on its own: git init -> git init -b main git init --bare <path> -> git init -b main --bare <path> git init <dir> -> git init -b main <dir> The -b/--initial-branch flag has been available since Git 2.28 (2020) and is now the canonical way to pin the initial branch name regardless of the global configuration. This is a stronger fix than Option B: * Works for students who skip or reorder quests. * Works on machines where init.defaultBranch is set to something unusual (master, trunk, etc.). * Zero hidden dependency on quest ordering. * No verbose prerequisite notices cluttering each quest. Quest 01 still teaches init.defaultBranch=main as a good-practice global setting, so students learn the underlying concept. Touched 50 files (25 FR quests + 25 EN quests), 70 line changes total. Cheatsheets (git-essentials, radicle) are left untouched as they document the CLI surface rather than drive an exercise. --- src/en/quests/02-the-three-halls-of-knowledge.njk | 2 +- src/en/quests/03-the-first-scroll.njk | 4 ++-- src/en/quests/04-the-archive-is-everywhere.njk | 8 ++++---- src/en/quests/06-the-tree-of-possibilities.njk | 2 +- src/en/quests/08-rewriting-history.njk | 2 +- src/en/quests/09-the-distant-portals.njk | 2 +- src/en/quests/10-the-guild-protocol.njk | 2 +- src/en/quests/11-the-time-weaver.njk | 2 +- src/en/quests/13-the-magic-seals.njk | 2 +- src/en/quests/14-the-archivists-tools.njk | 2 +- src/en/quests/15-the-eternal-forges.njk | 4 ++-- src/en/quests/16-the-kingdoms-actions.njk | 2 +- src/en/quests/17-the-automated-trials.njk | 2 +- src/en/quests/18-the-sacred-deployment.njk | 2 +- src/en/quests/19-the-other-forges.njk | 2 +- src/en/quests/20-the-free-paths.njk | 2 +- src/en/quests/21-the-travelers-client.njk | 4 ++-- src/en/quests/22-the-watchtower.njk | 2 +- src/en/quests/23-the-peer-network.njk | 2 +- src/en/quests/A1-the-titans-forges.njk | 4 ++-- src/en/quests/A2-the-infinite-archives.njk | 6 +++--- src/en/quests/A3b-the-city-arcana.njk | 4 ++-- src/en/quests/A4-the-artificers-plans.njk | 2 +- src/en/quests/A5-the-currents-of-destiny.njk | 2 +- src/en/quests/A6-the-illuminators-workshop.njk | 2 +- src/fr/quetes/02-les-trois-salles-du-savoir.njk | 2 +- src/fr/quetes/03-le-premier-parchemin.njk | 4 ++-- src/fr/quetes/04-larchive-est-partout.njk | 8 ++++---- src/fr/quetes/06-larbre-des-possibles.njk | 2 +- src/fr/quetes/08-reecrire-lhistoire.njk | 2 +- src/fr/quetes/09-les-portails-distants.njk | 2 +- src/fr/quetes/10-le-protocole-des-guildes.njk | 2 +- src/fr/quetes/11-le-tisseur-de-temps.njk | 2 +- src/fr/quetes/13-les-sceaux-magiques.njk | 2 +- src/fr/quetes/14-les-outils-de-larchiviste.njk | 2 +- src/fr/quetes/15-les-forges-eternelles.njk | 4 ++-- src/fr/quetes/16-les-actions-du-royaume.njk | 2 +- src/fr/quetes/17-les-epreuves-automatiques.njk | 2 +- src/fr/quetes/18-le-deploiement-sacre.njk | 2 +- src/fr/quetes/19-les-autres-forges.njk | 2 +- src/fr/quetes/20-les-chemins-libres.njk | 2 +- src/fr/quetes/21-le-client-du-voyageur.njk | 4 ++-- src/fr/quetes/22-la-tour-de-guet.njk | 2 +- src/fr/quetes/23-le-reseau-des-pairs.njk | 2 +- src/fr/quetes/A1-les-forges-du-titan.njk | 4 ++-- src/fr/quetes/A2-les-archives-infinies.njk | 6 +++--- src/fr/quetes/A3b-les-arcanes-de-la-cite.njk | 4 ++-- src/fr/quetes/A4-les-plans-de-lartificier.njk | 2 +- src/fr/quetes/A5-les-courants-du-destin.njk | 2 +- src/fr/quetes/A6-latelier-des-enlumineurs.njk | 2 +- 50 files changed, 70 insertions(+), 70 deletions(-) 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..91b068d 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> diff --git a/src/en/quests/04-the-archive-is-everywhere.njk b/src/en/quests/04-the-archive-is-everywhere.njk index 7132431..92d6b20 100644 --- a/src/en/quests/04-the-archive-is-everywhere.njk +++ b/src/en/quests/04-the-archive-is-everywhere.njk @@ -95,7 +95,7 @@ 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 @@ -103,7 +103,7 @@ 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 @@ -231,7 +231,7 @@ git push origin 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,7 +243,7 @@ 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 diff --git a/src/en/quests/06-the-tree-of-possibilities.njk b/src/en/quests/06-the-tree-of-possibilities.njk index fa940de..9866a5c 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> diff --git a/src/en/quests/08-rewriting-history.njk b/src/en/quests/08-rewriting-history.njk index bb62735..7abe101 100644 --- a/src/en/quests/08-rewriting-history.njk +++ b/src/en/quests/08-rewriting-history.njk @@ -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> diff --git a/src/en/quests/09-the-distant-portals.njk b/src/en/quests/09-the-distant-portals.njk index 8a2973a..24d41d4 100644 --- a/src/en/quests/09-the-distant-portals.njk +++ b/src/en/quests/09-the-distant-portals.njk @@ -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" 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/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..a141eb1 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> diff --git a/src/fr/quetes/04-larchive-est-partout.njk b/src/fr/quetes/04-larchive-est-partout.njk index eb94ce1..e8315c5 100644 --- a/src/fr/quetes/04-larchive-est-partout.njk +++ b/src/fr/quetes/04-larchive-est-partout.njk @@ -95,7 +95,7 @@ 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 @@ -103,7 +103,7 @@ 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 @@ -231,7 +231,7 @@ git push origin 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,7 +243,7 @@ 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 diff --git a/src/fr/quetes/06-larbre-des-possibles.njk b/src/fr/quetes/06-larbre-des-possibles.njk index e61441d..28f9a89 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> diff --git a/src/fr/quetes/08-reecrire-lhistoire.njk b/src/fr/quetes/08-reecrire-lhistoire.njk index db6d8b7..f8edcbf 100644 --- a/src/fr/quetes/08-reecrire-lhistoire.njk +++ b/src/fr/quetes/08-reecrire-lhistoire.njk @@ -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> diff --git a/src/fr/quetes/09-les-portails-distants.njk b/src/fr/quetes/09-les-portails-distants.njk index feec0c4..e886fb0 100644 --- a/src/fr/quetes/09-les-portails-distants.njk +++ b/src/fr/quetes/09-les-portails-distants.njk @@ -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" 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 From 3f520736134bbcfafb7361fe076922f668f05c7f Mon Sep 17 00:00:00 2001 From: Dxsk <43427519+Dxsk@users.noreply.github.com> Date: Sat, 11 Apr 2026 21:01:04 +0200 Subject: [PATCH 12/27] fix(quest-09): standardize remote name to 'origin' instead of 'portail' Quest 09 used 'portail' as the literal git remote name in all its code blocks, so students were learning commands like: git remote add portail /tmp/portail-central.git git push -u portail main git fetch portail git merge portail/main This is the same class of error that quest 04 had with 'origine': a francization/narrative leak into the actual technical identifier. A student following quest 09 literally ends up with a repository whose only remote is named 'portail', which no other tutorial in the world will match. Standardize all command tokens to 'origin' (the canonical default name), while keeping 'portail' everywhere it is used as a metaphor in the narrative prose. The quest is still about 'Distant Portals' in spirit; only the literal remote name is brought in line with git conventions. Also fix a P1 regression: line 260 had git init inline inside a single <pre><code>...</code></pre> HTML tag on the same line, so the prior sweep regex did not catch it. It now correctly says 'git init -b main --bare portail-central.git'. The quest 09 verifier does not check the remote's name (only that one exists), so bats tests still pass. Folder names 'portail-central.git' and 'portails-distants' are kept for narrative consistency. --- src/en/quests/09-the-distant-portals.njk | 42 +++++++++++----------- src/fr/quetes/09-les-portails-distants.njk | 42 +++++++++++----------- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/src/en/quests/09-the-distant-portals.njk b/src/en/quests/09-the-distant-portals.njk index 24d41d4..782a467 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 ancienne-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> @@ -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/fr/quetes/09-les-portails-distants.njk b/src/fr/quetes/09-les-portails-distants.njk index e886fb0..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> @@ -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> From e596481b3822ae39302ceaaec6682b5180dec66c Mon Sep 17 00:00:00 2001 From: Dxsk <43427519+Dxsk@users.noreply.github.com> Date: Sat, 11 Apr 2026 21:04:29 +0200 Subject: [PATCH 13/27] feat(lint): extend lint-content to catch any git checkout <branch> The initial lint-content target only caught 'git checkout main/master'. The quest 08 completion sweep revealed four more 'git checkout <branch>' calls (feature, refonte-archives, ...) that would not have been caught by regression. Widen the pattern to 'git checkout [-b] <word>' and add two filters so the baseline stays clean: * 'git checkout v[0-9]...' is allowed so quest 18's tag detach ('git checkout v1.0.0') is not flagged. 'git switch' cannot detach onto a tag without --detach, so the legacy form is kept here. * Quest 06 files are allow-listed entirely because the quest intentionally teaches 'git switch' vs 'git checkout' side by side as a historical contrast. * The first argument must start with a word-char, not '-', so the file-restore form ('git checkout -- path') is not matched. A future sweep can convert 'git checkout -- path' to 'git restore path', but that is a separate concern. --- scripts/makefile/run-lint-content.sh | 34 +++++++++++++++++++++------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/scripts/makefile/run-lint-content.sh b/scripts/makefile/run-lint-content.sh index 7fb7d10..cc06778 100755 --- a/scripts/makefile/run-lint-content.sh +++ b/scripts/makefile/run-lint-content.sh @@ -30,19 +30,37 @@ if [ -n "$hits_origine" ]; then fi # ----------------------------------------------------------------------------- -# Pattern 2 : 'git checkout main/master' in a code block. -# Since Git 2.23, 'git switch' is the preferred way to change branches. Quest -# 06 teaches this, so later quests should not contradict it by using the -# legacy form in their code blocks. Matches that are commented out ('# ...') -# are ignored so counter-example sentences remain valid. +# 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:]]+(main|master)' \ - src/ 2>/dev/null | grep -vE '#.*git[[:space:]]+checkout' || true) + '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 main/master' in code (prefer 'git switch')" >&2 + echo "✗ lint-content: 'git checkout <branch>' in code (prefer 'git switch')" >&2 fail=1 fi From 6dc130fc895704f09cf5328a675e3f4cff46b1a3 Mon Sep 17 00:00:00 2001 From: Dxsk <43427519+Dxsk@users.noreply.github.com> Date: Sat, 11 Apr 2026 21:07:33 +0200 Subject: [PATCH 14/27] fix(quest-04): verify mon-archive remote (the actual pedagogical action) The previous rigor sweep tightened quest 04 to check ma-copie, archive-centrale.git, and clone-depuis-bare. But the quest body explicitly teaches the student to do the following on mon-archive: cd mon-archive git remote add origin ../archive-centrale.git git push origin main And mon-archive was not tested at all. The verifier passed on the auto-generated remote of ma-copie (inherited from 'git clone') instead of the remote that the student actually has to add by hand. So a student who never ran 'git remote add' on mon-archive could still get a green verifier, as long as the clone chain was set up. Add an explicit step 9 validating that mon-archive has a remote named 'origin', and widen the cross-repo HEAD consistency check (step 10) to include mon-archive alongside ma-copie and clone-depuis-bare. The bats build_pass_scenario is updated to reproduce the 'git remote add origin ../archive-centrale.git' step so the happy path stays green, and a new negative test reproduces a student who skipped that pedagogical step. Step count goes from 9 to 10. --- exercises/04-larchive-est-partout/verifier.sh | 17 +++++++-- tests/verifiers/quest-04.bats | 35 ++++++++++++++----- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/exercises/04-larchive-est-partout/verifier.sh b/exercises/04-larchive-est-partout/verifier.sh index 71bb3e8..09b588b 100755 --- a/exercises/04-larchive-est-partout/verifier.sh +++ b/exercises/04-larchive-est-partout/verifier.sh @@ -73,10 +73,21 @@ check_step 7 "clone-depuis-bare/ est sur la branche 'main'" \ 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 9 : Les trois depots partagent le meme HEAD ---- -check_step 9 "Les trois depots partagent le meme HEAD" \ - 'assert_same_head "$WORKDIR/ma-copie" "$WORKDIR/clone-depuis-bare"' +# ---- 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/tests/verifiers/quest-04.bats b/tests/verifiers/quest-04.bats index a5f1d7c..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" == *"9 / 9"* ]] + [[ "$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" != *"9 / 9"* ]] + [[ "$output" != *"10 / 10"* ]] [[ "$output" == *"ma-copie"* ]] } @@ -47,7 +52,7 @@ build_pass_scenario() { build_pass_scenario rm -rf "$TMP_DIR/work/archive-centrale.git" run run_verifier "$QUEST" "work" - [[ "$output" != *"9 / 9"* ]] + [[ "$output" != *"10 / 10"* ]] [[ "$output" == *"bare"* ]] } @@ -57,7 +62,7 @@ build_pass_scenario() { git remote rename origin origine cd - >/dev/null run run_verifier "$QUEST" "work" - [[ "$output" != *"9 / 9"* ]] + [[ "$output" != *"10 / 10"* ]] [[ "$output" == *"origin"* ]] } @@ -67,7 +72,7 @@ build_pass_scenario() { git branch -m main master cd - >/dev/null run run_verifier "$QUEST" "work" - [[ "$output" != *"9 / 9"* ]] + [[ "$output" != *"10 / 10"* ]] [[ "$output" == *"main"* ]] } @@ -75,7 +80,7 @@ build_pass_scenario() { build_pass_scenario echo "ref: refs/heads/master" > "$TMP_DIR/work/archive-centrale.git/HEAD" run run_verifier "$QUEST" "work" - [[ "$output" != *"9 / 9"* ]] + [[ "$output" != *"10 / 10"* ]] } @test "quest 04: fails when clone-depuis-bare is on branch master" { @@ -84,7 +89,7 @@ build_pass_scenario() { git branch -m main master cd - >/dev/null run run_verifier "$QUEST" "work" - [[ "$output" != *"9 / 9"* ]] + [[ "$output" != *"10 / 10"* ]] } @test "quest 04: fails when ma-copie and clone-depuis-bare HEADs diverge" { @@ -95,7 +100,7 @@ build_pass_scenario() { git commit -q -m "Diverging commit" cd - >/dev/null run run_verifier "$QUEST" "work" - [[ "$output" != *"9 / 9"* ]] + [[ "$output" != *"10 / 10"* ]] } @test "quest 04: fails when clone-depuis-bare has remote named 'origine' instead of 'origin'" { @@ -104,6 +109,18 @@ build_pass_scenario() { git remote rename origin origine cd - >/dev/null run run_verifier "$QUEST" "work" - [[ "$output" != *"9 / 9"* ]] + [[ "$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"* ]] +} From 9ffc93d9c160343821cd0a1b304d52afc70c2a6c Mon Sep 17 00:00:00 2001 From: Dxsk <43427519+Dxsk@users.noreply.github.com> Date: Sun, 12 Apr 2026 20:42:00 +0200 Subject: [PATCH 15/27] fix(quest-09): translate branch name to English in EN version The example 'git push origin --delete ancienne-expedition' kept the French branch name in the English quest file. Use 'old-expedition'. --- src/en/quests/09-the-distant-portals.njk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/en/quests/09-the-distant-portals.njk b/src/en/quests/09-the-distant-portals.njk index 782a467..0128ade 100644 --- a/src/en/quests/09-the-distant-portals.njk +++ b/src/en/quests/09-the-distant-portals.njk @@ -203,7 +203,7 @@ git merge origin/main</code></pre> <p>Example:</p> - <pre data-lang="bash"><code>git push origin --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> From d8618eeeafe101a3f7c1b9f3cca1c7005bdbb4a1 Mon Sep 17 00:00:00 2001 From: Dxsk <43427519+Dxsk@users.noreply.github.com> Date: Thu, 16 Apr 2026 12:19:39 +0200 Subject: [PATCH 16/27] fix(quest-02): require parchemin.txt non-empty and exact filename match Previously the verifier accepted an empty parchemin.txt (test -f) and grep -q matched any filename containing 'parchemin.txt' (including mon-parchemin.txt), producing false positives on step 3. - Step 2: test -s (file must exist AND be non-empty). - Step 3: grep -qFx (exact line match against the staged filename). Adds two negative bats tests covering the empty-file and wrong-name scenarios. --- .../02-les-trois-salles-du-savoir/verifier.sh | 6 ++--- tests/verifiers/quest-02.bats | 24 +++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) 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/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"* ]] +} From a932f8eeb734dd607c3d76562a8742a55df2741e Mon Sep 17 00:00:00 2001 From: Dxsk <43427519+Dxsk@users.noreply.github.com> Date: Thu, 16 Apr 2026 12:22:20 +0200 Subject: [PATCH 17/27] fix(quest-03): require mission.txt to differ from the shipped template Previously the verifier only checked that mission.txt was tracked by git, so a student who committed the unmodified shipped parchemin and then made a second empty commit would pass the quest without ever completing the report (step 4 in the narrative). - Add a 5th verifier step: cmp -s against the shipped parchemin. - Rebuild the bats pass scenario to match the pedagogical flow (2 commits: add, then complete). - Add a negative test for the unmodified-template case. - Update FR + EN quest prose to list the new step. --- exercises/03-le-premier-parchemin/verifier.sh | 4 +++ src/en/quests/03-the-first-scroll.njk | 1 + src/fr/quetes/03-le-premier-parchemin.njk | 1 + tests/verifiers/quest-03.bats | 34 +++++++++++++++---- 4 files changed, 34 insertions(+), 6 deletions(-) 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/src/en/quests/03-the-first-scroll.njk b/src/en/quests/03-the-first-scroll.njk index 91b068d..c8a7f71 100644 --- a/src/en/quests/03-the-first-scroll.njk +++ b/src/en/quests/03-the-first-scroll.njk @@ -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/fr/quetes/03-le-premier-parchemin.njk b/src/fr/quetes/03-le-premier-parchemin.njk index a141eb1..08e8f07 100644 --- a/src/fr/quetes/03-le-premier-parchemin.njk +++ b/src/fr/quetes/03-le-premier-parchemin.njk @@ -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/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 +} From ef357d53847d0417322b129f7706d3c067afcca9 Mon Sep 17 00:00:00 2001 From: Dxsk <43427519+Dxsk@users.noreply.github.com> Date: Thu, 16 Apr 2026 12:25:31 +0200 Subject: [PATCH 18/27] fix(quest-05): use git check-ignore instead of hardcoded .log regex The previous regex ('^\*\.log$|^debug\.log$') accepted only two literal patterns, rejecting valid equivalents like **/*.log or logs/*.log. Delegating to git check-ignore lets any pattern pass that actually causes debug.log to be ignored - which is the real learning objective of the quest. Adds a negative test for a .gitignore that does not cover .log files, and a positive test for the **/*.log nested glob. --- exercises/05-les-lignes-du-temps/verifier.sh | 6 +++-- tests/verifiers/quest-05.bats | 25 ++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) 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/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"* ]] +} From 4d85bfbabfdd2d6789359743a5b6a78d1ec17a57 Mon Sep 17 00:00:00 2001 From: Dxsk <43427519+Dxsk@users.noreply.github.com> Date: Thu, 16 Apr 2026 12:26:51 +0200 Subject: [PATCH 19/27] fix(quest-06): assert the student is back on main at the end The exercise ends with 'git switch main' to return to the trunk, but the verifier never checked the final branch state. A student who stayed on expedition-nord (or left HEAD detached) would still pass. - Add Step 7: assert_branch_is . main. - Add a negative bats test for the 'still on expedition-nord' case. - Update FR + EN prose to list the new step. --- exercises/06-larbre-des-possibles/verifier.sh | 4 ++++ src/en/quests/06-the-tree-of-possibilities.njk | 1 + src/fr/quetes/06-larbre-des-possibles.njk | 1 + tests/verifiers/quest-06.bats | 14 ++++++++++++-- 4 files changed, 18 insertions(+), 2 deletions(-) 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/src/en/quests/06-the-tree-of-possibilities.njk b/src/en/quests/06-the-tree-of-possibilities.njk index 9866a5c..92efd2a 100644 --- a/src/en/quests/06-the-tree-of-possibilities.njk +++ b/src/en/quests/06-the-tree-of-possibilities.njk @@ -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/fr/quetes/06-larbre-des-possibles.njk b/src/fr/quetes/06-larbre-des-possibles.njk index 28f9a89..c959664 100644 --- a/src/fr/quetes/06-larbre-des-possibles.njk +++ b/src/fr/quetes/06-larbre-des-possibles.njk @@ -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/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"* ]] +} From 86651523c82a8c4fc7eb863da2f26557c473fdd6 Mon Sep 17 00:00:00 2001 From: Dxsk <43427519+Dxsk@users.noreply.github.com> Date: Thu, 16 Apr 2026 12:28:36 +0200 Subject: [PATCH 20/27] fix(quest-10): add final 'back on main/master' assertion The quest ends with 'git switch main' after cleaning up the proposition branch, but the verifier never checked HEAD. A student who stayed on the proposition branch (or on a detached HEAD) would still score 5/5. - Add Step 6: assert_branch_is . main (with master fallback so older students who skipped quest 01 are not broken). - Add a bats test for the 'still on proposition' case. - Add a bats test for the fast-forward-only merge case (regression guard against a future change to step 4). - Update existing score assertions from 5/5 to 6/6. --- .../10-le-protocole-des-guildes/verifier.sh | 4 ++ tests/verifiers/quest-10.bats | 41 ++++++++++++++++++- 2 files changed, 43 insertions(+), 2 deletions(-) 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/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"* ]] +} From f1085e2f88ac5fea2d3bae60a6490df3de5c77b7 Mon Sep 17 00:00:00 2001 From: Dxsk <43427519+Dxsk@users.noreply.github.com> Date: Thu, 16 Apr 2026 12:31:11 +0200 Subject: [PATCH 21/27] fix(quest-11): require an empty stash stack and a final main branch The previous step 3 accepted any reflog mention of 'stash', so a student who ran 'git stash list' but never actually stashed could pass. Worse, a student who pushed a stash but forgot to pop it also passed. Note: once git stash pop empties the stack, refs/stash is deleted and there is no durable reflog evidence of past activity. We trade the 'stash was created at some point' check for the stronger 'the tissage is complete' check: git stash list must be empty. - Step 3: '[ -z "$(git stash list)" ]' (no pending stash). - Step 5: assert_branch_is . main (back on trunk). - Rework the pass scenario to push, pop, then return to main. - Add two negative bats tests: pending stash, wrong final branch. --- exercises/11-le-tisseur-de-temps/verifier.sh | 13 +++++-- tests/verifiers/quest-11.bats | 36 ++++++++++++++++---- 2 files changed, 39 insertions(+), 10 deletions(-) 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/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"* ]] } From 1422392bdc5aec794065a4b83a1ec8d6e6c5e0ce Mon Sep 17 00:00:00 2001 From: Dxsk <43427519+Dxsk@users.noreply.github.com> Date: Thu, 16 Apr 2026 12:32:33 +0200 Subject: [PATCH 22/27] fix(quest-13): require all tags to follow semver (not just one) Step 4 previously accepted as long as AT LEAST ONE tag matched vX.Y.Z, so a student could create a single semver tag plus any number of garbage tags (release-notes, wip-fix, ...) and pass. - Invert the test: fail as soon as any tag does NOT match semver. - Add a regression bats test for that scenario. - Add an explicit bats test for the 'no annotated tag' case that already fails on step 3 - documents the current guarantee. --- exercises/13-les-sceaux-magiques/verifier.sh | 8 +++--- tests/verifiers/quest-13.bats | 26 ++++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) 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/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"* ]] +} From 7d6f23f1506b87227d4f2d567b68dfbc8a304cc0 Mon Sep 17 00:00:00 2001 From: Dxsk <43427519+Dxsk@users.noreply.github.com> Date: Thu, 16 Apr 2026 12:33:49 +0200 Subject: [PATCH 23/27] fix(quest-14): require pre-commit hook to have a non-zero exit path Step 4 previously matched any file containing the literal string 'TODO', so a hook made of nothing but a comment ('# blocks TODO') and an 'exit 0' would satisfy the check without ever blocking a commit. Require the hook to also contain 'exit N' (N>=1) or 'exit $?', which is the minimum needed to fail a commit. - Tighten step 4. - Add a bats test for the comment-only hook case. --- exercises/14-les-outils-de-larchiviste/verifier.sh | 12 ++++++++++-- tests/verifiers/quest-14.bats | 13 +++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) 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/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"* ]] +} From 99c6cb1da9380e7c4affd7da034fbb0318be952d Mon Sep 17 00:00:00 2001 From: Dxsk <43427519+Dxsk@users.noreply.github.com> Date: Thu, 16 Apr 2026 12:34:52 +0200 Subject: [PATCH 24/27] fix(quest-15): enforce remote name 'origin' and final main branch Step 2 accepted any remote name, so a student who used 'portail', 'upstream' or any other name would pass despite the quest teaching 'origin' consistently (27 mentions in the prose). - Step 2: assert_remote_name . origin. - Step 5: assert_branch_is . main. - Two new bats tests: wrong remote name, wrong final branch. --- .../15-les-forges-eternelles/verifier.sh | 10 +++-- tests/verifiers/quest-15.bats | 45 ++++++++++++++++++- 2 files changed, 50 insertions(+), 5 deletions(-) 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/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"* ]] +} From df2c7f99c0aa1b328de4ca7854b01ce40da40338 Mon Sep 17 00:00:00 2001 From: Dxsk <43427519+Dxsk@users.noreply.github.com> Date: Thu, 16 Apr 2026 12:36:01 +0200 Subject: [PATCH 25/27] fix(quest-18): skip comment lines when checking for deploy/production Step 3 matched anywhere in the workflow file, so a placeholder that only mentioned deploy/production in a comment (e.g. '# TODO: deploy to production') passed even though no real deploy step existed. Strip '^[[:space:]]*#' lines before grepping so only real YAML content (keys or values) can satisfy the check. --- exercises/18-le-deploiement-sacre/verifier.sh | 10 +++++-- tests/verifiers/quest-18.bats | 26 +++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) 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/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"* ]] +} From 334516a013d3e0a31cddbd08258d22dda0dcf4de Mon Sep 17 00:00:00 2001 From: Dxsk <43427519+Dxsk@users.noreply.github.com> Date: Thu, 16 Apr 2026 12:37:24 +0200 Subject: [PATCH 26/27] fix(quest-19): validate CI config content, not just file existence Steps 2-4 previously required only file existence, so an empty ci.yml / .gitlab-ci.yml / bitbucket-pipelines.yml would score 6/6 despite describing no pipeline at all. - GitHub: require '^jobs:' (root-level jobs key). - GitLab: require '^[[:space:]]*script:' (at least one job has a script block). - Bitbucket: require '^pipelines:' (root key). GitHub check is wrapped in a subshell so the loop's 'exit 0' only exits the check_step eval, not the verifier itself. Adds three negative bats tests covering an empty/placeholder file in each forge. --- exercises/19-les-autres-forges/verifier.sh | 31 ++++++++++++++-------- tests/verifiers/quest-19.bats | 21 +++++++++++++++ 2 files changed, 41 insertions(+), 11 deletions(-) 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/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"* ]] +} From 8b276c90d537066f72cd98ed30f009c0b86ac4f4 Mon Sep 17 00:00:00 2001 From: Dxsk <43427519+Dxsk@users.noreply.github.com> Date: Thu, 16 Apr 2026 12:45:06 +0200 Subject: [PATCH 27/27] fix(quest-12): detect cherry-pick via commit subject, not reflog grep The old step 3 checked only that 'cherry-pick' appeared somewhere in the reflog. A student who ran 'git cherry-pick --abort' or even never pressed through still left that word in the reflog, and an inventive student who fixed the grimoire by hand still passed step 4 on its own. Replace the heuristic with a state-based check: HEAD's history must contain a commit whose subject matches the tip of origin/correctif (or a local 'correctif' branch during tests). Since cherry-pick copies the subject verbatim, a manual fix with a different commit message now fails step 3. - Rebuild the bats pass scenario around a real correctif branch so the test exercises the actual pedagogical workflow. - Add a negative test for the 'fixed by hand' cheat path. Caveats documented inline: post-'bisect reset' there is no durable trace of bisect in git, so step 2 remains heuristic (reflog pattern). --- exercises/12-loracle-du-code/verifier.sh | 25 +++++++- tests/verifiers/quest-12.bats | 79 +++++++++++++++++------- 2 files changed, 80 insertions(+), 24 deletions(-) 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/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"* ]] +}