Was macht Wer hat geändert?
Beantworte „Wer hat das gebaut, unter welchem Ticket und wann?", ohne das Terminal zu verlassen. Alles Nötige ist bereits in der lokalen Git-Historie vorhanden. Der einzige Beitrag des Code-Hosts ist die PR-Erzählung, und die Ausgabe liefert einen Link dorthin.
Er ersetzt eine manuelle Schleife: Datei öffnen, Feature finden, Blame-Ansicht öffnen, Hash kopieren, in den Code-Host einfügen, zur PR klicken, Ticket und Autor ablesen.
Was der Benutzer bekommt
Autorenname, Commit-Hash, Ticketnummer, Branchname, Datum, PR-Nummer und klickbare Commit- und PR-Links. Ein Leiturteil für den Bereich, dann eine Tabelle aller Commits, die ihn berührt haben.
Skript ausführen
bash ~/.claude/skills/who-changed/scripts/who-changed.sh <datei> [start ende] [raw]
Wenn das Skill pro Projekt installiert ist, lautet der Pfad stattdessen
.claude/skills/who-changed/scripts/who-changed.sh.
<datei>allein: Ganzes-Datei-Modus. Top-Contributors plus die letzten 12 Commits.<datei> <start> <ende>(oder eine Zeilennummer): Zeilenbereich-Modus. Zeilenbesitz pro Commit.rawanhängen: Blame ohne-w -C -M, für den Fall, dass der Benutzer die Byte-Ebene sehen möchte.
Umgebungsvariablen: WHO_CHANGED_BASE setzt den Ref, auf den die PR-Suche zusteuert (Standard: origin/HEAD, dann origin/main, origin/master, main, master);
WHO_CHANGED_TICKET_REGEX ändert das Ticketmuster (Standard [A-Z][A-Z0-9]+-[0-9]+).
Das Skript gibt eine tabulatorgetrennte Ausgabe aus und schreibt niemals ins Repository oder ins Netzwerk. Formatiere es für den Benutzer um – füge das rohe TSV nicht ein.
Schritt 1: Ziel in einen Zeilenbereich auflösen
Das Skript erwartet Zahlen, daher muss ein Funktionsname zuerst in einen Bereich umgewandelt werden. Git's
-L :funktionsname:-Kürzel hängt von einem sprachspezifischen Funktionsmuster ab und schlägt bei vielen Sprachen (insbesondere TypeScript-Klassenmitgliedern) mit no match fehl.
Löse den Bereich selbst auf:
- Grepe nach der Deklaration, z. B.
^\s*(public|private|protected)?\s*(async\s+)?<name>\s*[(<], und<name>\s*[:=]\s*(function|\()für Arrow-Function-Eigenschaften. Für HTML oder CSS grepe nach dem wörtlichen Selektor, Schlüssel oder Block. - Lies ab dieser Zeile und vergleiche die Klammern ab der ersten
{, um die schließende Zeile zu finden. - Wenn der Name an mehreren Stellen übereinstimmt, liste die Übereinstimmungen auf und frage, welche gemeint ist. Wähle niemals stillschweigend die erste – gebräuchliche Namen existieren oft in mehreren Dateien.
Die Zeilennummern stammen aus dem Arbeitsverzeichnis, sodass sie mit dem übereinstimmen, was der Benutzer in seinem Editor sieht.
Bei einem expliziten Bereich (285-300, 285, L285-L300) verwende ihn wie angegeben. Bei nur einem Dateipfad verwende den Ganzes-Datei-Modus.
Schritt 2: Ausgabe interpretieren
Der Block ## meta enthält base (der Ref, auf den die PR-Suche zusteuert),
base_tip_date, web und host (Linkbasis, abgeleitet von git remote), den Blame-Modus und ob die Datei unbestätigte Änderungen hat.
Zeilen in ## commits sind nach besessenen Zeilen sortiert. Das Urteil ist die erste Zeile – der Commit, der die meisten Zeilen des Bereichs besitzt. Erwähne den Zweitplatzierten, wenn der Bereich wirklich geteilt ist; eine von vier Personen über drei Jahre erstellte Funktion sollte nicht als einer Person zugehörig gemeldet werden.
Das Skript erstellt bereits commit_url und pr_url für Bitbucket-, GitHub- und GitLab-Remotes. Wenn sie - sind, gibt es kein erkennbares Remote – sage das und lasse Links weg, anstatt einen Host zu erfinden.
Präsentiere es so (die Namen unten sind erfunden):
### calculateTotal - src/cart/cart.service.ts:42-61
**Alice Moreno** · `a1b2c3d` · 2023-02-08 · besitzt 13 von 20 Zeilen
Ticket SHOP-88 · Branch `SHOP-88-cart-totals` · PR #41
| Zeilen | Autor | Commit | Datum | Ticket | Branch | PR |
|---|---|---|---|---|---|---|
| 13 | Alice Moreno | a1b2c3d | 2023-02-08 | SHOP-88 | SHOP-88-cart-totals | #41 |
| 6 | Ben Carter | 9f8e7d6 | 2026-03-11 | SHOP-310 | SHOP-310-rounding-fix | nicht gemerged |
| 1 | Chen Wei | 4c5d6e7 | 2024-06-20 | — | release-2024-06 | #112 |
Dann die Links, dann eine Warnzeile, wenn ein Vertrauensindikator ausgelöst wurde.
Vertrauensindikatoren und was dazu zu sagen ist
| Indikator | Bedeutung | Sag dem Benutzer |
|---|---|---|
ok | Ticket-Branch über einen PR gemerged | Nichts nötig |
local-only | Commit ist noch nicht auf base | „Noch nicht auf den Basis-Branch gemerged“ – es gibt keinen PR |
no-pr-merge | Auf dem Hauptzweig, aber kein PR-Merge trägt ihn | Direkt auf den Hauptzweig committet, oder der Merge-Subjekt hat die Konvention gebrochen |
squash | PR-Nummer aus einem (#123)-Suffix gelesen | Squash-gemerged, daher ist die zeilenweise Historie innerhalb dieses PRs abgeflacht |
low-no-ticket | Kein Ticket in der Nachricht oder im Branch | Gib an, dass kein Ticket aufgezeichnet wurde. Rate keins |
low-integration-branch | Über einen Branch gemerged, der kein Ticket im Namen hat | Der PR ist der Ort, an dem der Integrationsbranch gelandet ist, nicht wo der Code geschrieben wurde. Geringes Vertrauen |
uncommitted | Noch nicht committet | Lokale unbestätigte Änderungen; vom Urteil ausgeschlossen |
Älterer Code kommt oft über langlebige Integrationsbranches (develop,
release-*, ui-dev). Dafür ist die ehrliche Antwort „über PR #112 auf den Hauptzweig gemerged, aber dieser PR ist ein Integrationsbranch – es ist nicht der PR, in dem dieser Code geschrieben wurde.“
Warum -w -C -M der Standard ist
-w ignoriert Leerzeichen, -M folgt innerhalb der Datei verschobenen Zeilen, -C folgt aus einer anderen Datei kopierten Zeilen. Das ist nicht kosmetisch. Nimm einen Block, der 2023 geschrieben und erst 2026 neu eingerückt wurde (illustrative Zahlen):
- intelligent (
-w -C -M): 9 von 9 Zeilen gehen an den ursprünglichen Autor, 2023 – korrekt - roh: 8 von 9 Zeilen gehen an die Person, die neu eingerückt hat, 2026 – schreibt ihr alten Code zu
Der genannte Autor ist also die letzte Person, die das Verhalten geändert hat, nicht die letzte, die Bytes berührt hat. Sage das, wenn es darauf ankommt, und biete raw als Folgeaktion an.
Ehrlichkeitsregeln
- Erfinde niemals eine Ticket-ID. Kein Ticket in der Commit-Nachricht oder im Branch: Melde
—und sage, dass kein Ticket aufgezeichnet wurde. - Erfinde niemals einen Autor. Wenn die Schuldzuweisung nicht eindeutig ist, nenne die in Frage kommenden Commits.
- Unterscheide „gemerged über“ von „geschrieben in“, wann immer ein Indikator mit geringem Vertrauen ausgelöst wird.
- Wenn
base_tip_dateveraltet aussieht, weise darauf hin, dass der Basis-Ref seit kurzem nicht mehr abgerufen wurde und neuere Merges möglicherweise übersehen werden. Schlagegit fetch originvor – führe es nicht ungefragt aus.
Grenzen, die erwähnenswert sind, wenn relevant
- Das „Warum“ ist nicht in Git. PR-Beschreibung, Reviewer und Ticket-Titel benötigen die API und Anmeldedaten des Code-Hosts oder Trackers. Der PR-Link ist der Übergabepunkt.
- Squash-Merges flachen die Historie ab. Wenn ein PR gesquasht wurde, besitzt ein Commit das gesamte Feature und die zeilenweise Zuordnung darin ist verloren.
--ancestry-pathwählt den ersten Merge, der den Commit auf den Hauptzweig gebracht hat. Bei verschachtelten Feature-Branches kann das ein Zwischen-Merge sein, nicht der PR des Tickets selbst.- Ganzes-Datei-Contributoren folgen keinen Umbenennungen.
git shortloghat kein--follow; die Commit-Liste folgt Umbenennungen, die Contributor-Liste nicht. -Cüberrascht gelegentlich, indem es kopierten Code der Datei zuschreibt, aus der er stammt.- PR-Erkennung erkennt drei Merge-Nachrichtenstile: Bitbucket
(
Merged in <branch> (pull request #N)), GitHub (Merge pull request #N from ...), und Squash (... (#N)). Andere Stile fallen aufno-pr-mergezurück.
Fallback, wenn das Skript nicht verfügbar ist
git blame -w -C -M -L <start>,<ende> --date=short -- <datei>
git log -1 --date=short --pretty=format:"%H%n%an%n%ad%n%s%n%b" <sha>
git merge-base --is-ancestor <sha> origin/main # kein Vorfahre -> ungemerged
git log --merges --ancestry-path --reverse --pretty=format:"%h|%s" <sha>..origin/main | grep -m1 "pull request"
git branch --contains <sha> # ungemerged-Fall
Analysiere den Merge-Subjekt auf Branch und PR-Nummer. Zwei Fallstricke: git shortlog benötigt ein explizites HEAD, sonst liest es stillschweigend von stdin und gibt nichts zurück, und Branchnamen können Präfixe haben (feature/SHOP-88-...), daher verankere den Ticket-Regex nicht am Anfang der Zeichenkette.