Ein KI-Assistent schlägt vor, eine Prüfung aus einer kleinen Funktion zum Lesen von Zeilen zu entfernen. „Der Aufrufer liefert bereits gültige Zeilennummern“, sagt er. Die Funktion sieht tatsächlich einfach aus. Bevor Sie zustimmen, prüfen Sie die Git-Historie – und die Zeile verweist auf einen Commit namens „Compact comparison spacing“.

Das gibt Ihnen einen weiteren Ansatzpunkt. Entscheidend ist, vor welcher Eingabe die Prüfung schützt, wann dieses Verhalten entstand und ob heutige Aufrufer sie noch brauchen. Eine kleine reproduzierbare Historie macht diese Fragen greifbar.

Bei der verdächtigen Zeile beginnen

Angenommen, ein Quelltextbetrachter nummeriert Zeilen ab eins. Seine aktuelle Funktion sieht so aus:

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]

Bei den zwei Zeilen ["alpha", "omega"] soll Zeile 1 alpha und Zeile 2 omega liefern. In Python liest Index -1 jedoch das letzte Element. Ohne Untergrenzenprüfung wird Zeilennummer 0 zu Index -1 und liefert omega. Zeilennummer -1 wird zu Index -2 und liefert alpha.

Das lässt sich aus dem aktuellen Code erklären. Die Historie beantwortet eine andere Frage: Wann wurde die Ablehnung Teil der Funktion, und was umfasste diese Änderung?

Die vier Commits nachbauen

Laden Sie git-history-lab.py in einen leeren Arbeitsordner herunter. Sie brauchen Git 2.29+ und Python 3.9+, ohne Paketabhängigkeiten. Führen Sie dies im eigenen Terminal aus:

python3 git-history-lab.py create history-lab --record history-check
cd history-lab
git log --reverse --format='%h %s' HEAD

Unter Windows ersetzen Sie python3 bei Bedarf durch Ihren Python-3-Starter. Der Ablauf verwendet Git und Python ohne macOS-APIs. Der dokumentierte Lauf erfolgte mit Git 2.50.1 und Python 3.9.6 unter macOS; Windows und Linux wurden nicht separat getestet.

Das Skript erstellt ein neues lokales Repository, führt Prüfungen aus und speichert deren Ausgabe in history-check. Ein vorhandenes Ziel lehnt es ab, es verwendet nur für seine eigenen Commits einen erfundenen Autor und ändert keine globale Git-Konfiguration. Alle Namen, Daten, Commitnachrichten und das Betrachterszenario sind Lehr-Fixtures.

ce4c0cf Add one-based source line lookup
e87088e Reject nonpositive source line numbers
7d41ebc Rename line reader module
27a0dc5 Compact comparison spacing

Jeder Commit hat außerdem ein lesbares Tag:

Tag Änderung
baseline Fügt line_reader.py hinzu; Tests prüfen erste Zeile, letzte Zeile und eine Eingabe hinter dem Ende
fix-positive-lines Ergänzt die Untergrenzenprüfung und Tests für 0, -1 und eine leere Eingabeliste
rename-module Benennt die Datei in source_lines.py um und aktualisiert den Testimport
format-spacing Ändert line_number < 1 in line_number<1

Die kompakte Schreibweise ist absichtlich auffällig, damit die letzte Änderung leicht erkennbar ist.

Mit blame zum nächsten Diff gelangen

Prüfen Sie Zeile 3:

git blame -L 3,3 HEAD -- source_lines.py
git show --format=fuller format-spacing -- source_lines.py

Blame ordnet die Zeile 27a0dc57, dem Leerraum-Commit, zu. Sein Patch enthält:

-    if line_number < 1:
+    if line_number<1:

Lassen Sie Git nun beim Verfolgen der Zeile Leerraum ignorieren:

git blame -w -L 3,4 HEAD -- source_lines.py

Das Ergebnis verweist auf e87088e0 und den alten Pfad line_reader.py. Git blame folgt vollständigen Dateiumbenennungen automatisch; -w ignoriert Leerraum bei der Zuordnung. Hier verursachte nicht die Umbenennung das irreführende Ergebnis, sondern die spätere Formatierung. Siehe die offizielle git-blame-Dokumentation.

Für diese konkrete Änderung vergleicht das Prüfskript auch Python-Syntaxbäume und führt die Tests vorher und nachher aus. Beides stimmt überein. Leerraum zu ignorieren hilft bei der Zuordnung, ist aber kein Verhaltenstest: Einrückung und Leerzeichen in Zeichenfolgen können in Python relevant sein.

Nach dem Prüfen eines bekannten Formatierungscommits können Sie ihn auch explizit ausschließen:

git blame --ignore-rev format-spacing -L 3,3 HEAD -- source_lines.py

Auch das erreicht hier die Korrektur. Schließen Sie einen Commit nicht allein wegen „format“ im Titel aus; lesen Sie zuerst den Patch. Blame erfasst Zeilenzuordnungen, nicht die Überlegungen eines Entwicklers oder seine Verantwortung für einen Fehler.

Nach Text suchen, der die Überarbeitung überlebt

Die Fehlermeldung blieb bei Umbenennung und Formatierung gleich. Suchen Sie nach ihrer Einführung:

git log --format='%h %s' -p -S 'line number must be positive' HEAD -- '*.py'

Das liefert die Korrektur samt Patch in line_reader.py. Die Anführungszeichen um *.py überlassen Git die Pfadauswahl und erfassen auch den alten Dateinamen. In diesem Repository gibt es nur einen Commit, der diese Meldung hinzufügt oder entfernt.

-S wählt Änderungen der Anzahl eines Zeichenfolgenvorkommens aus. Es erfasst nicht jede Änderung in der Nähe eines Treffers. Siehe Git zur Pickaxe-Suche.

Versuchen Sie eine Zeichenfolge, die sich bei der Formatierung geändert hat:

git log --format='%h %s' -p -S 'line_number < 1' HEAD -- '*.py'

Nun enthält die Ausgabe sowohl den Formatierungscommit, der genau diese Schreibweise entfernt, als auch die Korrektur, die sie hinzufügt. Der zusätzliche Treffer hilft: Lesen Sie die Änderung und wählen Sie bei Bedarf ein stabileres Fragment. Auch ein fehlendes Ergebnis braucht Interpretation; Text kann sich ändern, ohne dass sich seine Vorkommenszahl ändert.

Für eine bekannte Datei funktioniert hier auch:

git log --follow --format='%h %s' -p -S 'line number must be positive' HEAD -- source_lines.py

--follow verfolgt die Umbenennungshistorie einer Datei. Es ist keine allgemeine dateiübergreifende Suche. Prüfen Sie in echten Untersuchungen ältere Pfade oder erweitern Sie die Pfadauswahl, wenn eine enge Suche zu früh endet.

Zeilen mit log -L durch die Zeit verfolgen

Wenn Sie den relevanten Bereich kennen, fragen Sie nach seinen historischen Patches:

git log --format='%h %s' -L 3,4:source_lines.py HEAD

Hier enthält die Ausgabe die Leerraumänderung und das ursprüngliche Hinzufügen der Schutzprüfung. Sie folgt der reinen Dateiumbenennung, zeigt für den unverändert umbenannten Block aber keinen eigenen Patch. Für die vollständige Folge nutzen Sie das vorherige Protokoll der vier Commits.

Für die gesamte Funktion führen Sie Folgendes aus:

git log --format='%h %s' -L :read_line:source_lines.py HEAD

Das erreicht auch den ursprünglichen Indexausdruck bei baseline. Das Projekt setzt *.py diff=python in .gitattributes, damit Git die Python-Regeln für Funktionsköpfe nutzt. Die Erkennung hängt von Sprache und Diff-Konfiguration ab; siehe benutzerdefinierte Hunk-Köpfe. Für diese Datei können Sie alternativ Zahlen verwenden:

git log --format='%h %s' -L 1,7:source_lines.py HEAD

Bei log -L gehört der Pfad in das Bereichsargument; hängen Sie keinen Begrenzer -- path an. Der Bereich muss in der Startrevision existieren, und der Durchlauf beginnt bei genau einer Revision. Diese Einschränkungen stehen in der offiziellen git-log-Dokumentation.

Echte Historien können Merges, kopierten Code, umgeschriebene Funktionen oder fehlende ältere Commits in einem flachen Klon enthalten. Die Zeilenverfolgung ist ein Einstieg ins Lesen von Patches; nie festgehaltene Begründungen kann sie nicht zurückholen.

Die Erklärung an beiden Versionen testen

Lesen Sie Implementierung und Tests der gefundenen Korrektur zusammen:

git show --format=fuller fix-positive-lines -- line_reader.py test_lines.py

Der Patch ergänzt die Untergrenzenprüfung und einen Test, der für 0 und -1 ValueError erwartet. Das Prüfskript extrahiert eingecheckte Quellen und Tests in temporäre Ordner. Es führt dieselbe Regressionssuite gegen die Basisimplementierung aus, danach die eigenen Tests jedes Commits. Ihr Checkout wird nicht gewechselt.

Beobachtung im dokumentierten Lauf Was sie stützt
Die Basis liefert für 0 omega und für -1 alpha Diese Eingaben erreichen Pythons negative Indizierung
Die Tests der Korrektur scheitern an der Basis für beide Eingaben Die neuen Assertions erkennen das alte Verhalten
Korrektur, Umbenennung und Formatierung bestehen jeweils fünf Tests Die geprüften Fälle verhalten sich nach der Korrektur wie erwartet
Die Umbenennung erhält die Quellbytes, die Formatierung den Syntaxbaum Die späteren Änderungen erhalten die Implementierung dieser Funktion

Auch die drei ursprünglichen Basistests bestehen. Eine erfolgreiche Suite kann schlicht die relevante Eingabe auslassen. Die vollständigen erwarteten Ergebnisse beschreiben die Prüfungen; Ihre Datei history-check/transcript.txt enthält die tatsächlichen Befehle und Ausgaben einschließlich der zwei erwarteten Fehlschläge.

Die Beispiel-Commitnachricht beschreibt, dass Zeile null die letzte Zeile liefert; die Tests reproduzieren dieses Funktionsverhalten. Prüfen Sie vor dem Entfernen der Schutzprüfung, wie aktuelle Aufrufer null und negative Zeilennummern ablehnen. Das Beispiel setzt Ganzzahlen voraus; andere Typen brauchen eine eigene Validierung.

Der KI eine überprüfbare Frage stellen

Wenn Revisionen, Patch und Testausgabe vorliegen, bitten Sie den Assistenten, sie zu verbinden:

Erkläre, warum diese Untergrenzenprüfung existiert. Nutze die vorgelegte Korrektur und ihren Elterncommit, zeige die Ergebnisse für 0 und -1 und benenne die Regressions-Assertions. Trenne die Belege aus Diff und ausgeführten Tests von den Aussagen der Commitnachricht. Prüfe danach aktuelle Aufrufer, bevor du das Entfernen empfiehlst. Fehlen Aufrufer oder historische Berichte, nenne die offene Schlussfolgerung.

Eine nützliche Antwort enthält drei Teile: Verhaltensänderung, berichteter Anlass und offene Fragen zu heutigen Aufrufern. Behauptet sie allein aufgrund der Commitnachricht einen tatsächlichen Vorfall, bitten Sie um eine engere Aussage. Schlägt sie die Löschung vor, verlangen Sie einen Nachweis, dass jeder unterstützte Einstiegspfad die fehlerhaften Eingaben ausschließt. Einem Wert durch fremden Code folgen hilft bei dieser Untersuchung; einen KI-Patch prüfen zeigt den Weg von einer Erklärung zu einem gezielten Regressionstest.