Git - Wie kann ich den Änderungsverlauf einer Methode / Funktion anzeigen?

90

Ich habe also die Frage gefunden, wie der Änderungsverlauf einer Datei angezeigt werden soll, aber der Änderungsverlauf dieser bestimmten Datei ist riesig und ich bin wirklich nur an den Änderungen einer bestimmten Methode interessiert. Wäre es also möglich, den Änderungsverlauf nur für diese bestimmte Methode anzuzeigen?

Ich weiß, dass dies Git erfordern würde, um den Code zu analysieren, und dass die Analyse für verschiedene Sprachen unterschiedlich wäre, aber Methoden- / Funktionsdeklarationen sehen in den meisten Sprachen sehr ähnlich aus, daher dachte ich, dass möglicherweise jemand diese Funktion implementiert hat.

Die Sprache, mit der ich derzeit arbeite, ist Objective-C, und das SCM, das ich derzeit verwende, ist git. Es würde mich jedoch interessieren, ob diese Funktion für SCM / Sprache vorhanden ist.

Erik B.
quelle
1
Ich habe solche Funktionen im Git GSoG-Vorschlag gesehen.
Vi.
Ist das der Vorschlag, über den Sie gesprochen haben? listen-archives.org/git/…
Erik B
@lpapp Die Frage hier wurde 10 Monate zuvor gestellt, die andere Frage sollte als Betrogener dieser Frage markiert werden (wenn es sich überhaupt um Betrüger handelt).
Dirty-Flow
2
@lpapp Das sind zwei völlig unterschiedliche Fragen. Sie könnten möglicherweise ein Skript schreiben, das einen Funktionsnamen in einen Zeilenbereich auflöst, und dann die Technik aus dieser Frage verwenden, um den Verlauf für diese Zeilen abzurufen, aber an sich beantwortet es diese Frage nicht.
Erik B

Antworten:

94

Neuere Versionen haben git logeine spezielle Form des -LParameters gelernt :

-L: <Funktionsname>: <Datei>

Verfolgen Sie die Entwicklung des Zeilenbereichs, der durch "<start>,<end>"(oder den Funktionsnamen Regex <funcname>) innerhalb von angegeben wird <file>. Sie dürfen keine Pfadspezifikationsbegrenzer angeben. Dies ist derzeit auf einen Spaziergang beschränkt, der von einer einzelnen Revision ausgeht, dh Sie können nur null oder ein positives Revisionsargument angeben. Sie können diese Option mehrmals angeben.
...
Wenn “:<funcname>”anstelle von <start>und angegeben wird <end>, ist dies ein regulärer Ausdruck, der den Bereich von der ersten übereinstimmenden Funktionsnamenzeile <funcname>bis zur nächsten Funktionsnamenszeile angibt. “:<funcname>”sucht ab dem Ende des vorherigen -LBereichs, falls vorhanden, andernfalls ab dem Dateianfang. “^:<funcname>”sucht vom Dateianfang an.

Mit anderen Worten: Wenn Sie Git darum bitten git log -L :myfunction:path/to/myfile.c, wird der Änderungsverlauf dieser Funktion nun gerne gedruckt.

eckes
quelle
14
Dies funktioniert möglicherweise sofort für Objective-C. Wenn Sie dies jedoch für andere Sprachen (z. B. Python, Ruby usw.) tun, müssen Sie möglicherweise die entsprechende Konfiguration in eine .gitattributes-Datei einfügen, damit git die Funktion / Methode erkennt Erklärungen in dieser Sprache. Für Python verwenden Sie * .py diff = Python, für Ruby verwenden Sie * .rb diff = Ruby
Samaspin
Wie verfolgt Git die Funktion?
nn0p
@ nn0p Ich gehe davon aus, dass ich Syntaxkenntnisse in mehreren Sprachen habe und somit weiß, wie man eine Funktion isoliert und ihre Änderungen verfolgt.
JasonGenX
2
Wenn Sie den Kommentar von @ samaspin erweitern, können Sie für andere Sprachen auf die Dokumente hier verweisen: git-scm.com/docs/gitattributes#_generating_diff_text
edhgoose
Dies funktioniert nicht für Sprachen wie Scala und Java und sogar C, die verschachtelte geschweifte Klammern verwenden (reguläre Ausdrücke können das im Allgemeinen nicht verarbeiten), und selbst das Start- und Endformular funktioniert nicht, wenn die Funktion in die Datei verschoben wird und im gleichen Commit geändert.
Robin Green
16

Die Verwendung git gui blameist in Skripten schwer zu verwenden, und obwohl git log -Gund git log --pickaxekann Ihnen jeweils zeigen, wann die Methodendefinition angezeigt oder verschwunden ist, habe ich keine Möglichkeit gefunden, sie dazu zu bringen, alle Änderungen aufzulisten, die am Hauptteil Ihrer Methode vorgenommen wurden.

Sie können jedoch gitattributesund die textconvEigenschaft verwenden, um eine Lösung zusammenzusetzen, die genau das tut. Obwohl diese Funktionen ursprünglich dazu gedacht waren, Ihnen bei der Arbeit mit Binärdateien zu helfen, funktionieren sie hier genauso gut.

Der Schlüssel ist, dass Git alle Zeilen außer denen, an denen Sie interessiert sind, aus der Datei entfernt, bevor Sie Diff-Operationen ausführen. Dann git logwird git diffusw. nur der Bereich angezeigt, an dem Sie interessiert sind.

Hier ist der Überblick darüber, was ich in einer anderen Sprache mache. Sie können es für Ihre eigenen Bedürfnisse optimieren.

  • Schreiben Sie ein kurzes Shell-Skript (oder ein anderes Programm), das ein Argument verwendet - den Namen einer Quelldatei - und nur den interessanten Teil dieser Datei ausgibt (oder nichts, wenn nichts davon interessant ist). Sie können sedbeispielsweise Folgendes verwenden:

    #!/bin/sh
    sed -n -e '/^int my_func(/,/^}/ p' "$1"
  • Definieren Sie einen Git- textconvFilter für Ihr neues Skript. ( gitattributesWeitere Informationen finden Sie in der Manpage.) Der Name des Filters und die Position des Befehls können beliebig sein.

    $ git config diff.my_filter.textconv /path/to/my_script
  • Weisen Sie Git an, diesen Filter zu verwenden, bevor Sie Differenzen für die betreffende Datei berechnen.

    $ echo "my_file diff=my_filter" >> .gitattributes
  • Nun, wenn Sie verwenden -G.( man beachte die .) alle Commits aufzulisten , die sichtbaren Veränderungen erzeugen , wenn der Filter angewendet wird, haben Sie genau die Commits , dass Sie daran interessiert sind. Jede andere Optionen , die Git Diff - Routinen zu verwenden, wie zum Beispiel --patch, wird erhalten Sie auch diese eingeschränkte Ansicht.

    $ git log -G. --patch my_file
  • Voilà!

Eine nützliche Verbesserung, die Sie möglicherweise vornehmen möchten, besteht darin, dass Ihr Filterskript einen Methodennamen als erstes Argument (und die Datei als zweites) verwendet. Auf diese Weise können Sie eine neue Methode von Interesse angeben, indem Sie einfach aufrufen git config, anstatt Ihr Skript bearbeiten zu müssen. Zum Beispiel könnten Sie sagen:

$ git config diff.my_filter.textconv "/path/to/my_command other_func"

Natürlich kann das Filterskript alles tun, was Sie wollen, mehr Argumente annehmen oder was auch immer: Es gibt viel Flexibilität, die über das hinausgeht, was ich hier gezeigt habe.

Paul Whittaker
quelle
1
Das Argument, mehr als ein Argument zu verwenden, hat bei mir nicht funktioniert, aber den Funktionsnamen in hart zu setzen, funktioniert einwandfrei und das ist wirklich großartig!
qwertzguy
Genial, obwohl ich mich frage, wie (un) bequem es ist, zwischen vielen verschiedenen Methoden zu wechseln. Kennen Sie auch ein Programm, das eine ganze C-ähnliche Funktion ausführen kann?
Nafg
12

Git Log hat die Option '-G', um alle Unterschiede zu finden.

-G Suchen Sie nach Unterschieden, deren hinzugefügte oder entfernte Zeile mit der angegebenen übereinstimmt <regex>.

Geben Sie ihm einfach eine korrekte Regex des Funktionsnamens, den Sie interessieren. Beispielsweise,

$ git log --oneline -G'^int commit_tree'
40d52ff make commit_tree a library function
81b50f3 Move 'builtin-*' into a 'builtin/' subdirectory
7b9c0a6 git-commit-tree: make it usable from other builtins
lht
quelle
5
Ich habe den Befehl nicht ausgeführt, aber es sieht für mich so aus, als würde dieser Befehl nur die Commits anzeigen, die die Zeile berühren, die dem regulären Ausdruck entspricht, was nicht die gesamte Methode / Funktion ist.
Erik B
wenn Sie mehr Kontext möchten, können Sie ersetzen --onelinemit-p
lfender6445
3
Was aber, wenn die Methode um 20 Zeilen geändert wurde?
Nafg
+1. Für mich hat die Top-Antwort funktioniert, aber nur das letzte Commit angezeigt. Möglicherweise aufgrund von Rebases oder der Funktion, die sich einige Male bewegt, nicht sicher. Durch die Suche nach der tatsächlichen Codezeile anstelle der Funktion, in der sie sich befand (wenn auch ein Einzeiler), konnte ich mit dieser Antwort das gesuchte Commit leicht erkennen. Zum Glück schreibe ich normalerweise nützliche Commit-Nachrichten!
Dave S
11

Das Nächste, was Sie tun können, ist, die Position Ihrer Funktion in der Datei zu bestimmen (z. B. sagen wir, Ihre Funktion i_am_buggybefindet sich in den Zeilen 241 bis 263 von foo/bar.c), und dann Folgendes auszuführen:

git log -p -L 200,300:foo/bar.c

Dadurch wird weniger geöffnet (oder ein gleichwertiger Pager). Jetzt können Sie /i_am_buggy(oder Ihr Pager-Äquivalent) eingeben und die Änderungen schrittweise durchführen.

Dies kann je nach Codestil sogar funktionieren:

git log -p -L /int i_am_buggy\(/,+30:foo/bar.c

Dies begrenzt die Suche vom ersten Treffer dieser Regex (idealerweise Ihrer Funktionsdeklaration) auf dreißig Zeilen danach. Das Endargument kann auch ein regulärer Ausdruck sein, obwohl es eine zweifelhaftere Aussage ist , dies mit regulären Ausdrücken zu erkennen.

badp
quelle
Süss! Zu Ihrer Information, dies ist neu in Git v1.8.4. (Ich denke, ich sollte ein Upgrade durchführen.) Eine genauere Lösung wäre allerdings schön ... als würde jemand die Antwort von Paul Whittaker schreiben.
Greg Price
@GregPrice Anscheinend können die Kanten der Suche sogar reguläre Ausdrücke sein , sodass Sie zumindest einen mehr oder weniger genauen Startpunkt haben können.
Badp
Oh wow. In der Tat: Anstatt Ihren eigenen regulären Ausdruck zu schreiben, können Sie einfach sagen -L ":int myfunc:foo/bar.c"und sich auf die Funktion mit diesem Namen beschränken. Das ist fantastisch - danke für den Zeiger! Wenn nur die Funktionserkennung etwas zuverlässiger wäre ...
Greg Price
3

Der richtige Weg ist, git log -L :function:path/to/filewie in eckes Antwort erklärt zu verwenden .

Wenn Ihre Funktion jedoch sehr lang ist, möchten Sie möglicherweise nur die Änderungen sehen, die verschiedene Commits eingeführt haben, nicht die gesamten Funktionszeilen, die unverändert enthalten sind, für jedes Commit, das möglicherweise nur eine dieser Zeilen berührt. Wie ein normaler difftut.

Normalerweise git logkönnen Unterschiede mit angezeigt werden -p, dies funktioniert jedoch nicht -L. Sie müssen also grep git log -Lnur die beteiligten Zeilen und den Header für Commits / Dateien anzeigen, um sie zu kontextualisieren. Der Trick hier besteht darin, nur terminale farbige Linien, die einen --colorSchalter hinzufügen , mit einem regulären Ausdruck abzugleichen. Schließlich:

git log -L :function:path/to/file --color | grep --color=never -E -e "^(^[\[[0-9;]*[a-zA-Z])+" -3

Beachten Sie, dass ^[dies tatsächlich wörtlich sein sollte ^[. Sie können sie eingeben, indem Sie ^ B ^ [in Bash drücken, dh Ctrl+ V, Ctrl+ [. Referenz hier .

Mit dem letzten -3Schalter können vor und nach jeder übereinstimmenden Zeile 3 Zeilen des Ausgabekontexts gedruckt werden. Möglicherweise möchten Sie es an Ihre Bedürfnisse anpassen.

Hazzard17
quelle
2

Git Schuld zeigt Ihnen, wer zuletzt jede Zeile der Datei geändert hat; Sie können die zu untersuchenden Zeilen angeben, um zu vermeiden, dass der Verlauf von Zeilen außerhalb Ihrer Funktion angezeigt wird.

Wille
quelle
4
mit können git gui blameSie ältere Revisionen navigieren.
Vi.
0
  1. Funktionsverlauf anzeigengit log -L :<funcname>:<file> wie in eckes 'Antwort und Git-Dokument gezeigt

    Wenn nichts angezeigt wird, lesen Sie Definieren eines benutzerdefinierten Hunk-Headers , um *.java diff=javader .gitattributes Datei etwas hinzuzufügen , das Ihre Sprache unterstützt.

  2. Funktionsverlauf zwischen Commits mit anzeigen git log commit1..commit2 -L :functionName:filePath

  3. Zeigen Sie den überladenen Funktionsverlauf an (es kann viele Funktionen mit demselben Namen, aber unterschiedlichen Parametern geben) mit git log -L :sum\(double:filepath

Kris Roofe
quelle