Un assistant IA dit avoir ajouté la recherche par titre à une liste de livres. git diff montre un nouvel import et quelques lignes modifiées. L’implémentation de recherche manque, et le code proposé ne trouve pas un livre présent au catalogue. Avant de juger le correctif, déterminez ce que Git a réellement comparé.

Téléchargez l’atelier Git diff : une application de six livres, avec un correctif et des tests écrits pour ce tutoriel. Il faut Node.js 22 ou ultérieur et Git, sans dépendances à installer ni accès réseau.

Choisir les deux états avant de lire les lignes

Git conserve un arbre de travail, un index contenant l’instantané préparé et des instantanés validés. Ces commandes répondent à différentes questions :

Commande Comparaison
git diff Index → arbre de travail : changements non indexés des fichiers suivis
git diff --staged HEAD → index : contenu préparé pour le prochain commit
git diff HEAD HEAD → arbre de travail : changement net des fichiers suivis, indexé ou non
git diff base HEAD Les deux instantanés validés nommés base et HEAD
git diff base...HEAD Base de fusion → HEAD : changements de la branche head depuis la divergence

Les fichiers ordinaires non suivis sont absents des trois premières comparaisons. Un fichier peut aussi avoir des changements indexés et non indexés. Voir la référence Git diff pour ces formes de comparaison.

Un diff décrit des états, pas des auteurs. Si des modifications manuelles antérieures se mêlent à celles de l’assistant, le correctif n’identifie pas ses lignes. Conservez l’état initial ou les identifiants de commits si cette distinction compte. Une comparaison de branches exige aussi la bonne base ; en changer modifie la question.

Trouver le fichier absent du diff simple

Décompressez l’archive et entrez dans git-diff-reading-lab. Créez un dépôt jetable :

node scripts/create-workspace.mjs review-workspace
cd review-workspace
git status --short --untracked-files=all

Le script refuse une destination existante. Il valide le code de liste original, marque le commit base et laisse la proposition non validée. Sa sortie est reproductible sans toucher à un dépôt existant.

 M src/list.mjs
?? src/search.mjs
?? tests/search.test.mjs

Dans cet état ordinaire sans conflit, la première colonne décrit l’index et la seconde l’arbre de travail. L’espace initial de M compte : list.mjs est modifié mais non indexé. ?? signale les fichiers non suivis. Ces conventions figurent dans le format court de Git status.

Lancez maintenant :

git diff --name-status
git diff --stat
git diff -- src/list.mjs

Seul src/list.mjs apparaît. Lisez séparément src/search.mjs et tests/search.test.mjs dans l’éditeur. Sous macOS ou Linux, git diff --no-index -- /dev/null src/search.mjs affiche aussi le nouveau source comme ajout ; le code 1 signifie que les fichiers diffèrent. patches/review.patch contient les trois fichiers modifiés, contrairement au diff simple dans cet état non suivi.

git diff --staged est vide car rien n’est indexé. git diff base...HEAD est aussi vide, car la proposition n’est pas validée et base vaut toujours HEAD. Aucune de ces commandes n’a examiné la proposition de travail.

Lire un bloc complet

La partie list.mjs du correctif fourni est :

diff --git a/src/list.mjs b/src/list.mjs
index f9ed85e..b551297 100644
--- a/src/list.mjs
+++ b/src/list.mjs
@@ -1,6 +1,8 @@
 import { pageBooks } from "./page.mjs";
+import { searchBooks } from "./search.mjs";
 // Keep the caller's array unchanged.
-export function listBooks(books, { page = 1, pageSize = 2 } = {}) {
+export function listBooks(books, { page = 1, pageSize = 2, query = "" } = {}) {
   const sorted = [...books].sort((a, b) => a.title.localeCompare(b.title, "en"));
-  return pageBooks(sorted, { page, pageSize });
+  const currentPage = pageBooks(sorted, { page, pageSize });
+  return searchBooks(currentPage, query);
 }

Lisez les en-têtes avant la fonction :

  • a/ et b/ indiquent les côtés ancien et nouveau, pas des noms de branches.
  • index f9ed85e..b551297 contient des identifiants abrégés d’objets de contenu, pas ceux des deux commits. 100644 est le mode inchangé d’un fichier ordinaire non exécutable.
  • --- désigne l’ancien fichier et +++ le nouveau. Un fichier ajouté utilise /dev/null côté ancien, comme le montre le fichier suivant dans review.patch.
  • @@ -1,6 +1,8 @@ commence à l’ancienne ligne 1 sur 6 lignes, puis à la nouvelle ligne 1 sur 8 lignes. Ce sont des plages, pas des nombres de suppressions et d’ajouts.
  • Dans le bloc, un espace précède le contexte conservé, - une ligne supprimée et + une ligne ajoutée. Ici, 4 lignes de contexte et 2 suppressions forment l’ancienne plage ; 4 lignes de contexte et 4 ajouts forment la nouvelle. Le commentaire conservé compte aussi comme contexte.

La documentation du format de correctif Git explique les en-têtes. Vérifiez le calcul directement sur le fichier de base de six lignes et la proposition de huit lignes. Ce sont des blocs ordinaires à deux côtés ; un diff de fusion combiné a un autre format.

Suivre le nouvel appel jusqu’à son entrée

La demande est précise : rechercher par titre dans tout le catalogue sans tenir compte de la casse, puis paginer les résultats. Conserver l’ordre des titres et ne pas modifier l’entrée.

La nouvelle fonction filtre la liste qu’elle reçoit :

export function searchBooks(books, query) {
  const needle = query.toLowerCase();
  return books.filter((book) => book.title.toLowerCase().includes(needle));
}

La fonction peut être correcte alors que son appelant fournit la mauvaise liste. Ouvrez src/page.mjs, inchangé : il réduit les livres triés à une page. Dans le bloc proposé, currentPage contient déjà cette tranche lorsqu’il atteint searchBooks.

Avec une taille de page de deux, la première page originale contient Amber Atlas et Moon Garden. Star Notes vient plus tard. Chercher sTaR sur la première page renvoie [], alors que la première page des résultats devrait contenir b5. La correction dans patches/fix.patch recherche d’abord dans sorted, puis transmet les correspondances à pageBooks.

C’est le comportement à vérifier. Pour un changement plus large avec de nombreux consommateurs, poursuivez avec l’analyse d’impact du code ; la liste des fichiers modifiés ne les énumère pas tous.

Vérifier ce que le test réussi a réellement appelé

Revenez de review-workspace au dossier principal de l’atelier et lancez séparément ces commandes :

cd ..
node scripts/run-tests.mjs proposed author
node scripts/run-tests.mjs proposed requirement
node scripts/run-tests.mjs fixed requirement
node scripts/example.mjs

Le test proposé par l’auteur passe : il donne à searchBooks un tableau complet de deux livres et vérifie une requête à casse mixte. Il n’appelle jamais listBooks, donc ne teste pas la pagination avant la recherche. Aucune ancienne assertion n’a été supprimée ni affaiblie.

Les trois mêmes exigences de liste publique s’exécutent sur chaque version. Le téléchargement fournit aussi deux vérifications de régression inchangées pour l’ordre, la pagination et l’absence de mutation :

Version Régressions existantes Nouvelles exigences de liste Code de sortie des exigences
base : sans recherche 2 réussites 0 réussite, 3 échecs 1
proposed : recherche après pagination 2 réussites 1 réussite, 2 échecs 1
fixed : recherche avant pagination 2 réussites 3 réussites 0

Lancez node scripts/run-tests.mjs base requirement, ou remplacez version et suite pour reproduire les autres lignes. Les échecs de base indiquent l’absence du nouveau service, pas une régression de la liste. Les échecs proposés concernent une page pleine et un résultat au-delà de la première page originale. Les deux suites échouées sortent bien avec 1 ; && arrêterait les commandes suivantes.

scripts/example.mjs affiche un objet JSON par version. Dans chaque results, sTaR vaut ["b1","b2"] pour base, [] pour proposed et ["b5"] pour fixed. Ces vérifications ciblées répondent à la question de l’ordre, sans certifier une recherche de production. Si un correctif change aussi ses assertions, consultez le guide de revue des tests IA.

Comprendre pourquoi un diff vide peut encore cacher un correctif à relire

Après inspection des trois fichiers, indexez-les uniquement dans le dépôt jetable :

cd review-workspace
git add -- src/list.mjs src/search.mjs tests/search.test.mjs
git diff
git diff --staged --stat
git diff --staged

Le diff simple est maintenant vide, car les fichiers de travail correspondent à l’index. Le diff indexé contient les trois fichiers, y compris la fonction et le test ajoutés. L’exercice modifie l’index sans valider la proposition. En vraie revue, évitez d’indexer des fichiers sans rapport juste pour les faire apparaître dans un diff.

Comparer la proposition sous forme de deux versions validées

Pour utiliser le même exemple dans le lecteur Git diff de Yenpo pour Mac, créez un dépôt séparé avec les deux versions validées. Revenez à git-diff-reading-lab — cd .. si vous êtes encore dans review-workspace — puis lancez :

node scripts/create-workspace.mjs committed-workspace --committed
cd committed-workspace
git status --short --untracked-files=all
git rev-parse base
git rev-parse proposal
git diff base proposal --name-status
git diff base proposal

Le nouveau mode valide les fichiers originaux comme base, copie et indexe les trois changements, puis crée un second commit marqué proposal. Il affiche les identifiants réels. proposal est HEAD ; base est son parent. L’ancien review-workspace reste inchangé.

La commande d’état ne doit rien afficher. Les deux instantanés sont validés, donc git diff et git diff --staged sont vides. La comparaison explicite base → proposal contient toujours src/list.mjs modifié, src/search.mjs ajouté et tests/search.test.mjs ajouté. Ouvrez aussi src/page.mjs, inchangé : la liste transmet toujours un sous-ensemble paginé à la recherche.

Ces commits contiennent le même source que versions/base et versions/proposed. Pour sTaR, l’observation reste ["b1","b2"] avant la proposition et [] après ; le résultat demandé est ["b5"]. Valider la proposition conserve son problème d’ordre. L’instantané séparé fixed et ses exigences réussies restent dans l’atelier ; ce mode ne crée aucun commit fixed.

L’installation n’accepte qu’un nouveau sous-dossier et refuse fichiers, dossiers ou liens symboliques existants. Elle utilise une identité pédagogique fictive explicite et des dates fixes, ignore l’identité et la configuration Git héritées et écrit uniquement dans le nouveau dépôt. Elle ne change ni votre identité globale ni un autre dépôt.

Ouvrez committed-workspace dans Yenpo. Utilisez les deux identifiants pour vérifier les versions selon le guide de comparaison Git : ancien côté base, nouveau côté proposal. La comparaison porte sur ces instantanés validés. Elle n’inclut ni changements indexés ni non indexés et n’identifie pas les modifications d’un tour de discussion IA.