Kann Git wirklich die Bewegung einer einzelnen Funktion von einer Datei in eine andere verfolgen? Wenn das so ist, wie?

73

Mehrmals bin ich auf die Aussage gestoßen, dass Git sie verfolgen kann, wenn Sie eine einzelne Funktion von einer Datei in eine andere verschieben. In diesem Eintrag heißt es beispielsweise : "Linus sagt, wenn Sie eine Funktion von einer Datei in eine andere verschieben, zeigt Git Ihnen den Verlauf dieser einzelnen Funktion während der Verschiebung an."

Aber ich bin mir ein bisschen bewusst, was Git unter der Haube zu bieten hat, und ich sehe nicht, wie das möglich ist. Ich frage mich also ... ist das eine korrekte Aussage? Und wenn ja, wie ist das möglich?

Mein Verständnis ist, dass Git den Inhalt jeder Datei als Blob speichert und jeder Blob eine global eindeutige Identität hat, die sich aus dem SHA-Hash seines Inhalts und seiner Größe ergibt. Git repräsentiert dann Ordner als Bäume. Alle Dateinameninformationen gehören zum Baum, nicht zum Blob, sodass eine Umbenennung einer Datei beispielsweise als Änderung eines Baums und nicht eines Blobs angezeigt wird.

Wenn ich also eine Datei namens "foo" mit 20 Funktionen und eine Datei namens "bar" mit 5 Funktionen habe und eine der Funktionen von foo in bar verschiebe (was 19 bzw. 6 ergibt), Wie kann Git erkennen, dass ich diese Funktion von einer Datei in eine andere verschoben habe?

Nach meinem Verständnis würde dies dazu führen, dass 2 neue Blobs existieren (einer für den modifizierten foo und einer für den modifizierten Balken). Mir ist klar, dass ein Unterschied berechnet werden kann, um zu zeigen, dass die Funktion von einer Datei in die andere verschoben wurde. Aber ich sehe nicht, wie der Verlauf der Funktion möglicherweise mit bar anstelle von foo in Verbindung gebracht werden könnte (jedenfalls nicht automatisch).

Wenn Git tatsächlich in einzelne Dateien schauen und einen Blob pro Funktion berechnen würde (was verrückt / unmöglich wäre, weil man wissen müsste, wie man eine mögliche Sprache analysiert), könnte ich sehen, wie dies möglich sein könnte.

Also ... ist die Aussage richtig oder nicht? Und wenn es richtig ist, was fehlt dann in meinem Verständnis?

Charlie Flowers
quelle
2
Ich denke nicht, dass es "Funktionen" verfolgt, sondern "Codeblöcke". Wenn Sie also eine 30-Zeilen-Funktion haben und diese in zwei 15-Zeilen-Funktionen aufteilen, wird dies auf die gleiche Weise verfolgt, als ob Sie hat die ganze Funktion verschoben. Jemand korrigiert mich, wenn ich falsch liege, bitte.
MatrixFrog
1
Mein Verständnis (was sehr wohl falsch sein kann und deshalb frage ich) ist, dass jede Datei höchstens einem Blob entspricht. Wenn Sie also eine Funktion in zwei kleinere Funktionen in derselben Datei aufteilen, wird Ihr alter Blob einfach durch einen neuen Blob ersetzt. Wenn das richtig ist, werden "Codestücke" nicht wirklich verfolgt, da es nie in eine Datei schaut. Mit anderen Worten, seine kleinste Granularität ist eine ganze Datei.
Charlie Flowers
1
Interessante Idee, GIT in Sprachparser zu integrieren. Ich denke, ich würde diese Funktionalität benötigen, damit die Delphi-Sprache ein einzelnes * .pas in mehrere * .pas-Dateien aufteilen kann, wobei jede pas-Datei ein einzelnes Objekt und eine Implementierung oder so enthält. Und dann halten Sie diese geteilten Dateien hoffentlich mit Änderungen an der Originaldatei auf dem neuesten Stand. Damit dies als "Stealth Tracking" verwendet werden kann;) kann von lokalen Restrukturierungsarbeiten profitiert werden, falls der Hauptwart nicht restrukturieren möchte.
Skybuck Flying

Antworten:

32

Diese Funktionalität wird durch bereitgestellt git blame -C <file>.

Die -COption veranlasst git, Übereinstimmungen zwischen dem Hinzufügen oder Löschen von Textblöcken in der zu überprüfenden Datei und den in denselben Änderungssätzen geänderten Dateien zu finden. Zusätzliche -C -Coder -C -C -Cerweitern Sie die Suche.

Versuchen Sie es selbst in einem Test-Repo mit git blame -Cund Sie werden sehen, dass der Codeblock, den Sie gerade verschoben haben, aus der Originaldatei stammt, zu der er gehört.

Von der git help blameHandbuchseite:

Der Ursprung von Zeilen wird automatisch bei Umbenennungen ganzer Dateien verfolgt (derzeit gibt es keine Option zum Deaktivieren der Umbenennungsfolge). Informationen zum Verfolgen von Zeilen, die von einer Datei in eine andere verschoben wurden, oder zum Verfolgen von Zeilen, die aus einer anderen Datei usw. kopiert und eingefügt wurden, finden Sie in den Optionen -Cund -M.

JN Avila
quelle
Als Test habe ich ein Repo mit drei Dateien erstellt und Datei1 eine Zeile hinzugefügt, die dann festgeschrieben wurde. Ich habe diese Zeile dann in Datei2 verschoben und erneut festgeschrieben. Dann zu Datei3 und festgeschrieben. git blame -C10 file3Dann wurde das erste Commit angezeigt, bei dem diese Zeile zu Datei1 hinzugefügt wurde, aber ich wollte unbedingt das letzte Commit sehen, das diese Zeile verschoben hat (dh das Commit, das die Zeile zu Datei2 verschoben hat.) Gibt es eine Möglichkeit, dies zu erreichen? Ich habe einige nützliche Informationen erhalten git log -S'my interesting line', aber immer noch nicht ganz das, wonach ich suche.
Johann
@ Johann es scheint, dass schlicht git blamedafür geeignet wäre.
Andrybak
@andrybak Es ist 4 Jahre später, also erinnere ich mich nicht, was ich wirklich versucht habe zu erreichen. Aber git blamewürde zeigen , nur die letzte Änderung an der Linie (ob eine Bewegung oder auch nicht), wo mein Kommentar gefragt für die „letzte begehen , die diese Zeile bewegt “ (vermutlich nach einige weitere Commits Änderung der Linie gemacht wurden).
Johann
2
-CCund -CCCscheint nicht zu funktionieren ... hier git version 2.15.0.rc0muss ich den isolierten -CSchalter mehrmals separat übergeben, damit er den dokumentierten Effekt hat. Die Dokumentation irgendwie zeigt dies, zumindest implizit. Diese Antwort und andere Kommentare weisen jedoch darauf hin, dass dies in der Vergangenheit funktioniert hat. Hmmm.
underscore_d
Ab Git 2.15 gibt es meiner Meinung nach einen besseren Weg .
Inigo
16

Ab Git 2.15 git diffunterstützt jetzt die Erkennung von verschobenen Linien mit der --color-movedOption. Es funktioniert für Verschiebungen zwischen Dateien.

Dies funktioniert natürlich für kolorierte Terminalausgänge. Soweit ich das beurteilen kann, gibt es keine Möglichkeit, Bewegungen im Nur-Text-Patch-Format anzugeben, aber das ist sinnvoll.

Versuchen Sie es mit dem Standardverhalten

git diff --color-moved

Der Befehl nimmt auch Optionen, die derzeit no, default,plain , zebraund dimmed_zebra(Verwenden Sie git help diffdie aktuellen Optionen und die Beschreibungen zu bekommen). Zum Beispiel:

git diff --color-moved=zebra

In Bezug auf , wie es geschehen ist, kann man ein gewisses Verständnis von aufzulesen diesem E - Mail Austausch vom Autor der Funktionalität .

Inigo
quelle
1
Gibt es eine Möglichkeit, git so zu konfigurieren, dass die --color-movedOption standardmäßig angewendet wird?
Eugen Konkov
2
@EugenKonkov Ja, git configzum Einstellen verwenden diff.colorMoved.
Inigo
6

Ein Teil dieser Funktionalität befindet sich in git gui blame(+ Dateiname). Es zeigt eine Anmerkung der Zeilen einer Datei, die jeweils angibt, wann sie erstellt und wann sie zuletzt geändert wurde. Bei der Codeverschiebung in einer Datei wird das Festschreiben der Originaldatei als Erstellung und das Festschreiben angezeigt, bei dem es als letzte Änderung zur aktuellen Datei hinzugefügt wurde . Versuch es.

Was ich wirklich möchte, ist, git logals Argument zusätzlich zu einem Dateipfad einen Zeilennummernbereich anzugeben und dann den Verlauf dieses Codeblocks anzuzeigen. Es gibt keine solche Option, wenn die Dokumentation richtig ist. Ja, nach Linus 'Aussage würde auch ich denken, dass ein solcher Befehl leicht verfügbar sein sollte.

Paŭlo Ebermann
quelle
4
Ich habe gerade zum ersten Mal Gui-Schuld gesehen. Nett. Ich fange an zu denken, dass Linus das vielleicht so gemeint hat. Nicht , dass Git intern speichert Informationen sagen , dass die Funktion von einer Datei zur anderen bewegt, aber das, angesichts der Informationen Git tut Speicher, können Sie feststellen , dass die Funktion bewegt (wie git gui Schuld der Fall ist, oder über ein diff wie ich in der genannten Frage). Wenn ja, würde dies bedeuten, dass mein ursprüngliches Verständnis richtig ist, dass es sich nur um Commits, Bäume und Blobs handelt und Git niemals in eine Datei schaut. Aber das sind genug Informationen, damit Sie eine Funktionsbewegung durch Analyse erkennen können. Vielleicht.
Charlie Flowers
Ja, ich denke das ist es. Das Git-Backend macht jetzt nichts mehr mit dem Dateiinhalt (abgesehen davon, dass sie möglicherweise etwas größenoptimiert als Unterschiede gespeichert werden), aber die Frontend-Tools müssen alles tun.
Paŭlo Ebermann
Es scheint nur ein Problem zu geben ... wie gehe ich in chronologischer Reihenfolge durch die Geschichte? Es ist ein bisschen top-posted ...
@AgentFriday Sie könnten installieren müssen , dass getrennt. Unter Ubuntu ist es beispielsweise im git-guiPaket verfügbar .
Paŭlo Ebermann
4

git verfolgt Umbenennungen überhaupt nicht . Ein Umbenennen ist nur ein Löschen und Hinzufügen, das ist alles. Alle Tools, die Umbenennungen anzeigen, rekonstruieren sie aus diesen Verlaufsinformationen.

Das Umbenennen von Tracking-Funktionen ist daher eine einfache Angelegenheit, bei der die Unterschiede aller Dateien in jedem Commit nachträglich analysiert werden. Daran ist nichts besonders Unmögliches; Das vorhandene Umbenennungs-Tracking behandelt bereits 'Fuzzy'-Umbenennungen, bei denen einige Änderungen an der Datei vorgenommen und umbenannt werden. Dies erfordert das Betrachten des Inhalts der Dateien. Es wäre eine einfache Erweiterung, auch nach Funktionsumbenennungen zu suchen.

Ich weiß jedoch nicht, ob die Basis-Git-Tools dies tatsächlich tun - sie versuchen, sprachneutral zu sein, und die Funktionsidentifikation ist sehr viel nicht sprachneutral.

bdonlan
quelle
Ich bezog mich nicht auf "Funktionsumbenennungen". Ich frage vielmehr nach dem Fall, dass eine Teilmenge des Textes einer Datei aus dieser Datei in eine andere Datei verschoben wird.
Charlie Flowers
Sie haben Recht, aber Ihr Kommentar ist unklar und die ersten paar Worte würden (mich) darauf hindeuten, dass Sie Q missverstanden, bearbeitet oder etwas anderes bitte. Zum Thema verwendet Git (System?) Diff und das ist die ganze Macht, die es darüber hat. Es kann die Umbenennung von Funktionen "verfolgen", ist aber nicht besonders klug. Es ist im Grunde nur ein Zeilendifferenz und Sie können das Ding verfolgen.
Tomas Pruzina
2

Das git diffzeigt Ihnen, dass bestimmte Zeilen verschwunden sind foound wieder aufgetaucht sind bar. Wenn diese Dateien im selben Commit keine weiteren Änderungen enthalten, ist die Änderung leicht zu erkennen.

Ein intellektueller gitKunde kann Ihnen zeigen, wie Zeilen von einer Datei in eine andere verschoben wurden. Eine sprachbewusste IDE könnte diese Änderung einer bestimmten Funktion zuordnen.

Ähnliches passiert, wenn eine Datei umbenannt wird. Es verschwindet nur unter einem Namen und erscheint unter einem anderen wieder, aber jedes vernünftige Tool kann es bemerken und als Umbenennung darstellen.

9000
quelle
2
Gibt es einen vorhandenen Client, mit dem eine Person den Verlauf einer Funktion anzeigen kann?
William Pursell
1
William: Sie sollten versuchen, "Git Gui Schuld Pfad / zu / Dateiname.ext" oder "Git Schuld -CCCw Pfad / zu / Dateiname.ext" (wobei die erstere eine ziemlich brauchbare GUI hat und die letztere eine bessere Diagnose für harte Bewegungen und Kopien). Leider denke ich, dass es keine Möglichkeit gibt, "-CCCw" -Optionen zu übergeben, um GUI-Schuld zu geben.
Mikko Rantalainen
Tatsächlich kann "Git Gui Blame" verwendet werden, um Ergebnisse von "Git Guit -CCCw" zu erhalten, indem Git neuer als 1.5.3 verwendet und nach dem Laden der Datei im Kontextmenü der rechten Maustaste "Vollständige Kopiererkennung durchführen" ausgewählt wird (ich habe es gerade überprüft) die Quelldatei unter /usr/share/git-gui/lib/blame.tcl).
Mikko Rantalainen
@MikkoRantalainen Hat -CCoder hat es -CCCjemals funktioniert? Sie scheinen jetzt sicherlich nicht (Git-Version 2.15.0.rc0)
underscore_d
@underscore_d Erhalten Sie eine Warnmeldung? Scheint immer noch zu funktionieren git version 2.7.4und git help blameweiß -C: "Wenn diese Option dreimal angegeben wird, sucht der Befehl in jedem Commit zusätzlich nach Kopien aus anderen Dateien."
Mikko Rantalainen