Un assistant IA propose de retirer une vérification d’une petite fonction de lecture de lignes. « L’appelant fournit déjà des numéros de ligne valides », dit-il. La fonction paraît effectivement simple. Avant d’accepter, vous consultez l’historique Git : la ligne renvoie à un commit intitulé « Compact comparison spacing ».
Vous avez un nouvel endroit où chercher. Les questions utiles sont : quelle entrée cette vérification protège-t-elle, quand ce comportement est-il apparu et les appelants actuels en ont-ils encore besoin ? Un petit historique reproductible rend ces questions concrètes.
Partir de la ligne suspecte
Supposons qu’une visionneuse de sources numérote les lignes à partir de un. Sa fonction actuelle ressemble à ceci :
def read_line(lines, line_number):
"""Read a source line using a one-based line number."""
if line_number<1:
raise ValueError("line number must be positive")
if line_number > len(lines):
raise IndexError("line number is past the end")
return lines[line_number - 1]
Pour les deux lignes ["alpha", "omega"], la ligne 1 doit renvoyer alpha et la ligne 2 omega. Mais en Python, l’indice -1 lit le dernier élément. Sans la vérification de borne inférieure, le numéro 0 devient l’indice -1 et renvoie omega. Le numéro -1 devient l’indice -2 et renvoie alpha.
Le code actuel suffit à expliquer cela. L’historique répond à une autre question : quand le rejet est-il devenu partie intégrante de cette fonction et que comprenait la modification ?
Reconstruire les quatre commits
Téléchargez git-history-lab.py dans un dossier de travail vide. Il faut Git 2.29+ et Python 3.9+, sans dépendances de paquets. Exécutez ceci dans votre terminal :
python3 git-history-lab.py create history-lab --record history-check
cd history-lab
git log --reverse --format='%h %s' HEAD
Sous Windows, remplacez python3 par votre lanceur Python 3 si nécessaire. Le parcours utilise Git et Python, sans API macOS. L’exécution consignée utilisait Git 2.50.1 et Python 3.9.6 sur macOS ; Windows et Linux n’ont pas été testés séparément.
Le script crée un nouveau dépôt local, exécute les vérifications et enregistre leur sortie dans history-check. Il refuse une destination existante, utilise un auteur fictif uniquement pour ses propres commits et ne modifie pas votre configuration Git globale. Tous les noms, dates, messages de commit et le scénario de visionneuse sont des fixtures pédagogiques.
ce4c0cf Add one-based source line lookup
e87088e Reject nonpositive source line numbers
7d41ebc Rename line reader module
27a0dc5 Compact comparison spacing
Chaque commit possède aussi un tag lisible :
| Tag | Modification |
|---|---|
baseline |
Ajoute line_reader.py ; les tests couvrent la première ligne, la dernière et une entrée au-delà de la fin |
fix-positive-lines |
Ajoute la borne inférieure et les tests pour 0, -1 et une liste d’entrée vide |
rename-module |
Renomme le fichier en source_lines.py et met à jour l’import du test |
format-spacing |
Remplace line_number < 1 par line_number<1 |
L’espacement compact est volontairement visible pour reconnaître facilement la dernière modification.
Laisser blame indiquer le prochain diff
Inspectez la ligne 3 :
git blame -L 3,3 HEAD -- source_lines.py
git show --format=fuller format-spacing -- source_lines.py
Blame attribue la ligne à 27a0dc57, le commit d’espacement. Son correctif contient :
- if line_number < 1:
+ if line_number<1:
Demandez maintenant à Git d’ignorer les espaces en retraçant la ligne :
git blame -w -L 3,4 HEAD -- source_lines.py
Le résultat pointe vers e87088e0 et l’ancien chemin, line_reader.py. Git blame suit automatiquement les renommages de fichiers entiers ; -w ignore les espaces dans l’attribution. Ce n’est pas le renommage qui rendait le résultat trompeur ici, mais la modification d’espacement ultérieure. Voir la documentation officielle de git-blame.
Pour cette modification précise, le vérificateur compare aussi les arbres syntaxiques Python et exécute les tests avant et après. Les deux concordent. Ignorer les espaces sert à examiner l’attribution, pas à tester le comportement : l’indentation et les espaces dans les chaînes peuvent compter en Python.
Après avoir inspecté un commit de formatage identifié, vous pouvez aussi l’exclure explicitement :
git blame --ignore-rev format-spacing -L 3,3 HEAD -- source_lines.py
Cela atteint aussi la correction dans ce laboratoire. N’excluez pas un commit uniquement parce que son titre dit « format » ; lisez d’abord son patch. Blame indique l’attribution des lignes, pas le raisonnement d’un développeur ni sa responsabilité dans un défaut.
Chercher le texte qui survit à la réécriture
Le message d’erreur est resté identique lors du renommage et du formatage. Cherchez son introduction :
git log --format='%h %s' -p -S 'line number must be positive' HEAD -- '*.py'
Cela renvoie la correction et son patch dans line_reader.py. Mettre *.py entre guillemets laisse Git sélectionner les chemins et inclut l’ancien nom. Ce dépôt ne contient qu’un commit ajoutant ou retirant ce message.
-S sélectionne les changements du nombre d’occurrences d’une chaîne. Il ne sélectionne pas chaque modification proche d’un résultat. Voir l’explication de pickaxe dans Git.
Essayez une chaîne modifiée pendant le formatage :
git log --format='%h %s' -p -S 'line_number < 1' HEAD -- '*.py'
La sortie comprend maintenant le commit d’espacement, qui retire cette graphie exacte, et la correction, qui l’ajoute. Le résultat supplémentaire est utile : lisez la modification, puis choisissez si besoin un fragment plus stable. Une absence de résultat demande aussi une interprétation ; le texte peut changer sans que son nombre d’occurrences change.
Pour un fichier connu, ceci fonctionne aussi dans le laboratoire :
git log --follow --format='%h %s' -p -S 'line number must be positive' HEAD -- source_lines.py
--follow traite l’historique de renommage d’un seul fichier. Ce n’est pas une recherche générale entre fichiers. Dans une enquête réelle, vérifiez les anciens chemins ou élargissez la sélection si une recherche étroite s’arrête trop tôt.
Suivre les lignes dans le temps avec log -L
Si vous connaissez la plage pertinente, demandez ses correctifs dans l’historique :
git log --format='%h %s' -L 3,4:source_lines.py HEAD
Dans ce dépôt, la sortie comprend la modification d’espaces et l’ajout initial de la garde. Elle traverse le simple renommage de fichier, mais le bloc renommé inchangé ne reçoit pas de patch distinct. Utilisez le journal des quatre commits précédent pour la séquence complète.
Pour la fonction entière, exécutez :
git log --format='%h %s' -L :read_line:source_lines.py HEAD
Cela atteint aussi l’expression d’indexation initiale à baseline. Le laboratoire déclare *.py diff=python dans .gitattributes pour que Git utilise ses règles d’en-têtes de fonctions Python. La reconnaissance dépend du langage et de la configuration du diff ; voir la définition d’un en-tête de bloc personnalisé. Une alternative numérique pour ce fichier est :
git log --format='%h %s' -L 1,7:source_lines.py HEAD
Avec log -L, le chemin fait partie de l’argument de plage ; n’ajoutez pas de limiteur -- path. La plage doit exister dans la révision de départ et le parcours commence à une seule révision. Ces restrictions figurent dans la documentation officielle de git-log.
Les historiques réels peuvent contenir des fusions, du code copié, des fonctions réécrites ou des commits anciens absents d’un clone superficiel. Le suivi de lignes est un point de départ pour lire les patchs ; il ne peut retrouver une justification jamais consignée.
Tester l’explication sur les deux versions
Lisez ensemble l’implémentation et les tests de la correction trouvée :
git show --format=fuller fix-positive-lines -- line_reader.py test_lines.py
Le patch ajoute la borne inférieure et un test attendant ValueError pour 0 et -1. Le vérificateur extrait les sources et tests des commits dans des dossiers temporaires. Il exécute la même suite de régression sur l’implémentation de base, puis les tests propres à chaque commit. Il ne change pas votre checkout.
| Observation de l’exécution consignée | Ce qu’elle étaye |
|---|---|
La base renvoie omega pour 0 et alpha pour -1 |
Ces entrées atteignent l’indexation négative de Python |
| La suite de correction échoue sur la base pour les deux entrées | Les assertions ajoutées détectent l’ancien comportement |
| Les versions corrigée, renommée et formatée passent chacune cinq tests | Les cas testés se comportent comme prévu après la correction |
| Le renommage préserve les octets source ; le formatage préserve l’arbre syntaxique | Ces modifications ultérieures préservent l’implémentation de la fonction |
Les trois tests initiaux de la base passent aussi. Une suite verte peut simplement omettre l’entrée qui vous intéresse. Les résultats attendus complets décrivent les vérifications ; votre fichier history-check/transcript.txt contient les commandes et sorties réelles, dont les deux échecs attendus.
Le message du commit d’exemple décrit une ligne zéro renvoyant la dernière ligne ; les tests reproduisent ce comportement. Avant de supprimer la garde dans votre projet, vérifiez comment les appelants actuels rejettent zéro et les numéros négatifs. Cet exemple suppose des entiers ; les autres types nécessitent une validation distincte.
Donner à l’IA une question vérifiable
Une fois les révisions, le patch et la sortie des tests réunis, demandez à l’assistant de les relier :
Explique pourquoi cette borne inférieure existe. Utilise la correction fournie et son parent, montre le résultat pour 0 et -1 et identifie les assertions de régression. Sépare ce que montrent le diff et les tests exécutés de ce qu’affirme le message de commit. Inspecte ensuite les appelants actuels avant de recommander la suppression. Si ces appelants ou les rapports historiques manquent, indique quelle conclusion reste ouverte.
Une réponse utile comporte trois parties : le changement de comportement, la motivation rapportée et ce qui reste inconnu sur les appelants actuels. Si elle affirme qu’un incident a eu lieu uniquement parce que le message le dit, demandez de restreindre l’affirmation. Si elle propose de supprimer la garde, demandez comment chaque point d’entrée pris en charge exclut les entrées fautives. Suivre une valeur dans du code inconnu aide à examiner les appelants ; relire un correctif écrit par IA montre comment transformer une explication en test de régression ciblé.