Ein KI-Assistent sagt, er habe Titelsuche zu einer Bücherliste hinzugefügt. git diff zeigt einen neuen Import und wenige geänderte Zeilen. Die Suchimplementierung fehlt in der Ausgabe, und der Vorschlag findet ein Buch aus dem Katalog nicht. Bevor Sie den Patch beurteilen, klären Sie, was Git tatsächlich verglichen hat.

Laden Sie die Git-Diff-Übung herunter: eine Anwendung mit sechs Büchern sowie einem Beispielpatch und Tests für diese Anleitung. Sie benötigen Node.js 22 oder neuer und Git, keine weiteren Abhängigkeiten oder Netzwerkverbindung.

Vor dem Lesen die beiden Zustände wählen

Git verwaltet Arbeitsbaum, Index mit vorgemerkt gespeicherten Inhalten und Commit-Stände. Diese Befehle beantworten unterschiedliche Fragen:

Befehl Vergleich
git diff Index → Arbeitsbaum: nicht vorgemerkte Änderungen versionierter Dateien
git diff --staged HEAD → Index: für den nächsten Commit vorgemerkte Änderungen
git diff HEAD HEAD → Arbeitsbaum: gesamte Änderung versionierter Dateien, vorgemerkt und nicht vorgemerkt
git diff base HEAD Die gespeicherten Commit-Stände base und HEAD
git diff base...HEAD Gemeinsamer Vorfahr → HEAD: Änderungen des head-Branches seit der Abzweigung

Gewöhnliche unversionierte Dateien fehlen in den ersten drei Vergleichen. Eine Datei kann zugleich vorgemerkte und nicht vorgemerkte Änderungen enthalten. Diese Vergleichsformen sind in der Git-Diff-Referenz beschrieben.

Ein Diff beschreibt Zustände, keine Urheberschaft. Sind frühere manuelle Änderungen mit Assistenten-Änderungen vermischt, zeigt der Patch nicht, welche Zeilen vom Assistenten stammen. Halten Sie bei Bedarf den Zustand vor der Aufgabe oder Commit-IDs fest. Auch Branch-Vergleiche brauchen die beabsichtigte Basis; eine andere Basis ändert die Frage.

Die vom einfachen Diff ausgelassene Datei finden

Entpacken Sie den Download und wechseln Sie nach git-diff-reading-lab. Erstellen Sie ein Wegwerf-Repository:

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

Das Setup lehnt vorhandene Ziele ab. Es committet den ursprünglichen Listencode, markiert den Commit mit base und lässt den Vorschlag uncommittet. Seine Ausgabe ist reproduzierbar, ohne ein bestehendes Repository zu verändern.

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

Bei diesem gewöhnlichen konfliktfreien Status beschreibt die erste Spalte den Index, die zweite den Arbeitsbaum. Das führende Leerzeichen in M ist wichtig: list.mjs ist geändert, aber nicht vorgemerkt. ?? kennzeichnet unversionierte Dateien. Diese Konventionen stehen im Kurzformat von Git status.

Führen Sie nun aus:

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

Nur src/list.mjs erscheint. Lesen Sie src/search.mjs und tests/search.test.mjs separat im Editor. Unter macOS oder Linux können Sie die neue Quelle mit git diff --no-index -- /dev/null src/search.mjs als Ergänzung anzeigen; Exit-Code 1 bedeutet unterschiedliche Dateien. Der enthaltene Patch patches/review.patch umfasst alle drei geänderten Dateien, der einfache Diff in diesem unversionierten Zustand dagegen nicht.

git diff --staged bleibt leer, weil nichts vorgemerkt ist. Auch git diff base...HEAD ist leer, weil der Vorschlag nicht committet wurde und base weiterhin HEAD entspricht. Keiner der Befehle hat den Arbeitsvorschlag geprüft.

Einen vollständigen Hunk lesen

Der Abschnitt list.mjs des mitgelieferten Patches lautet:

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);
 }

Lesen Sie die Header vor der Funktion:

  • a/ und b/ kennzeichnen alte und neue Seite, keine Branch-Namen.
  • index f9ed85e..b551297 enthält gekürzte IDs von Dateiinhalt-Objekten, nicht die beiden Commit-IDs. 100644 ist der unveränderte Modus einer regulären, nicht ausführbaren Datei.
  • --- bezeichnet die alte, +++ die neue Datei. Bei einer neuen Datei steht auf der alten Seite /dev/null, wie die nächste Datei in review.patch zeigt.
  • @@ -1,6 +1,8 @@ beginnt bei alter Zeile 1 und umfasst 6 alte Zeilen, dann bei neuer Zeile 1 mit 8 neuen Zeilen. Das sind Bereiche, keine Anzahlen gelöschter und hinzugefügter Zeilen.
  • Im Hunk markiert ein Leerzeichen erhaltenen Kontext, - eine entfernte und + eine hinzugefügte Zeile. Hier ergeben 4 Kontextzeilen plus 2 entfernte den alten Bereich; 4 Kontextzeilen plus 4 Ergänzungen den neuen. Auch der erhaltene Kommentar zählt als Kontext.

Die Dokumentation des Git-Patch-Formats erklärt die Header. Die Bereichsrechnung lässt sich direkt an der sechszeiligen Basisdatei und dem achtzeiligen Vorschlag im Download prüfen. Dies sind normale zweiseitige Hunks; kombinierte Merge-Diffs haben ein anderes Format.

Den neuen Aufruf bis zu seiner Eingabe verfolgen

Die Anforderung ist konkret: Den gesamten Katalog ohne Beachtung der Groß-/Kleinschreibung nach Titeln durchsuchen und erst danach die Treffer paginieren. Bestehende Titelreihenfolge und Eingabe sollen unverändert bleiben.

Der neue Helfer filtert jede Liste, die er erhält:

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

Der Helfer kann korrekt arbeiten, während der Aufrufer die falsche Liste übergibt. Öffnen Sie das unveränderte src/page.mjs: Es kürzt sortierte Bücher auf eine Seite. Im vorgeschlagenen Hunk enthält currentPage bereits diesen Ausschnitt, wenn er searchBooks erreicht.

Bei der Seitengröße zwei enthält die ursprüngliche erste Seite Amber Atlas und Moon Garden. Star Notes steht später im Katalog. Die Suche nach sTaR auf der ersten Seite liefert [], obwohl die gewünschte erste Ergebnisseite Buch b5 enthalten sollte. Die Korrektur in patches/fix.patch durchsucht zuerst sorted und übergibt danach Treffer an pageBooks.

Dieses Verhalten gilt es zu prüfen. Bei größeren Änderungen mit vielen Verbrauchern hilft die Auswirkungsanalyse von Codeänderungen; eine Liste geänderter Dateien zählt sie nicht vollständig auf.

Prüfen, was der bestandene Test tatsächlich aufgerufen hat

Wechseln Sie aus review-workspace zurück in den obersten Übungsordner und führen Sie diese Befehle einzeln aus:

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

Der vorgeschlagene Autorentest besteht: Er übergibt searchBooks ein vollständiges Array aus zwei Büchern und prüft eine Anfrage mit gemischter Schreibweise. Er ruft nie listBooks auf und testet daher die Paginierung vor der Suche nicht. Keine alte Assertion wurde entfernt oder abgeschwächt.

Dieselben drei Anforderungen an die öffentliche Liste laufen gegen jede Version. Zusätzlich enthält der Download zwei unveränderte Regressionstests für Reihenfolge, Paginierung und unveränderte Eingabe:

Version Bestehende Regressionstests Neue Listenanforderungen Exit-Code der Anforderungen
base: keine Suche 2 bestanden 0 bestanden, 3 fehlgeschlagen 1
proposed: Suche nach Paginierung 2 bestanden 1 bestanden, 2 fehlgeschlagen 1
fixed: Suche vor Paginierung 2 bestanden 3 bestanden 0

Führen Sie node scripts/run-tests.mjs base requirement aus oder ersetzen Sie Version und Suite für andere Zeilen. Die Fehler der Basis bedeuten, dass das Feature fehlt, nicht dass die alte Liste regressiert. Der Vorschlag scheitert an einer vollen Ergebnisseite und einem Treffer außerhalb der ursprünglichen ersten Seite. Beide fehlerhaften Suiten enden tatsächlich mit 1; && würde die folgenden Befehle stoppen.

scripts/example.mjs gibt pro Version ein JSON-Objekt aus. In dessen results ist sTaR für base gleich ["b1","b2"], für proposed [] und für fixed ["b5"]. Diese gezielten Prüfungen beantworten die Frage nach der Suchreihenfolge, zertifizieren aber keine produktive Suche. Ändert ein Patch auch Assertions, nutzen Sie die Anleitung zum Review von KI-Tests.

Warum trotz leerem Diff noch ein Patch zu prüfen ist

Nach dem Lesen der drei Dateien merken Sie diese nur im Wegwerf-Repository vor:

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

Der einfache Diff ist nun leer, weil Arbeitsdateien und Index übereinstimmen. Der vorgemerkte Diff enthält alle drei Dateien einschließlich neuem Helfer und Test. Die Übung ändert den Index, committet den Vorschlag aber nicht. Merken Sie in echten Reviews keine fremden Dateien nur deshalb vor, damit der Diff sie zeigt.

Den Vorschlag als zwei Commit-Versionen vergleichen

Um dasselbe Beispiel in Yenpos Git-Diff-Viewer für Mac zu nutzen, erstellen Sie ein separates Übungsrepository mit beiden Versionen als Commits. Kehren Sie nach git-diff-reading-lab zurück – mit cd .., falls Sie noch in review-workspace sind – und führen Sie aus:

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

Der neue Modus committet die ursprünglichen Listendateien als base, kopiert und merkt die drei Änderungen vor und erstellt einen zweiten Commit mit Tag proposal. Er gibt die tatsächlichen IDs beider Commits aus. proposal ist HEAD, base sein Eltern-Commit. Das frühere review-workspace bleibt unverändert.

Der Statusbefehl sollte nichts ausgeben. Beide Stände sind committet, daher bleiben auch git diff und git diff --staged leer. Der explizite Vergleich base → proposal enthält trotzdem das geänderte src/list.mjs, das neue src/search.mjs und das neue tests/search.test.mjs. Öffnen Sie auch das unveränderte src/page.mjs: Die Listenfunktion übergibt weiterhin einen paginierten Ausschnitt an die Suche.

Diese Commits enthalten denselben Code wie versions/base und versions/proposed der Übung. Die Beobachtung für sTaR bleibt vor dem Vorschlag ["b1","b2"], danach []; gewünscht ist ["b5"]. Das Committen bewahrt den Suchreihenfolgefehler. Der separate Stand fixed mit bestandenen Anforderungen bleibt in der Übung; dieser Modus erstellt keinen fixed-Commit.

Das Setup akzeptiert nur ein neues Unterverzeichnis und lehnt vorhandene Dateien, Ordner oder Symlinks ab. Es nutzt eine ausdrücklich fiktive Lehridentität und feste Daten, ignoriert geerbte Git-Identität und -Konfiguration und schreibt Einstellungen nur in das neue Übungsrepository. Globale Git-Identität und andere Repositorys bleiben unberührt.

Öffnen Sie committed-workspace in Yenpo. Prüfen Sie mit den beiden ausgegebenen IDs die ältere und neuere Version anhand der Git-Vergleichsanleitung: alte Seite base, neue Seite proposal. Der Vergleich betrifft diese Commit-Stände. Er enthält weder vorgemerkte noch nicht vorgemerkte Änderungen und identifiziert keine Änderungen einer einzelnen KI-Gesprächsrunde.