Git
Chapters ▾ 2nd Edition

7.1 Git Tools - Revisions-Auswahl

Inzwischen haben Sie die meisten der gängigen Befehle und Workflows kennen gelernt. Sie benötigen sie, um ein Git-Repository für Ihre Quellcode-Kontrolle zu verwalten oder zu pflegen. Sie haben die grundlegenden Aufgaben des Tracking und Commitens von Dateien gelöst und Sie haben die Leistungsfähigkeit der Staging Area und der einfachen Branching- und Merging-Funktionen von Topics-Branches genutzt.

Jetzt werden Sie eine Reihe von sehr nützlichen Anwendungen entdecken, die Git ausführen kann. Sie werden diese nicht unbedingt im Alltag einsetzen müssen, aber vielleicht irgendwann einmal benötigen.

Revisions-Auswahl

Es gibt eine Reihe von Wegen um auf einen einzelnen Commit, einen Satz von Commits oder einen Bereich von Commits zu verweisen. Nicht alle sind zwangsläufig offensichtlich, aber es ist nützlich sie zu kennen.

Einzelne Revisionsstände

Sie können sich natürlich auf jeden einzelnen Commit mit seinem vollen, 40-stelligen SHA-1-Hash beziehen, aber es gibt auch benutzerfreundlichere Möglichkeiten, sich auf Commits zu beziehen. Dieses Kapitel beschreibt die verschiedenen Möglichkeiten, wie Sie auf jeden Commit verweisen können.

Kurz-SHA-1

Git ist intelligent genug, um herauszufinden, auf welchen Commit Sie sich beziehen, wenn Sie die ersten paar Zeichen des SHA-1-Hash angeben, solange dieser Teil-Hash mindestens vier Zeichen lang und eindeutig ist; d.h. kein anderes Objekt in der Objektdatenbank darf einen Hash haben, der mit dem gleichen Präfix beginnt.

Wenn Sie z.B. einen bestimmten Commit untersuchen möchten, von dem Sie wissen, dass Sie gewisse Funktionen hinzugefügt haben, könnten Sie zuerst den Befehl git log ausführen, um den Commit zu finden:

$ git log
commit 734713bc047d87bf7eac9674765ae793478c50d3
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri Jan 2 18:32:33 2009 -0800

    fixed refs handling, added gc auto, updated tests

commit d921970aadf03b3cf0e71becdaab3147ba71cdef
Merge: 1c002dd... 35cfb2b...
Author: Scott Chacon <schacon@gmail.com>
Date:   Thu Dec 11 15:08:43 2008 -0800

    Merge commit 'phedders/rdocs'

commit 1c002dd4b536e7479fe34593e72e6c6c1819e53b
Author: Scott Chacon <schacon@gmail.com>
Date:   Thu Dec 11 14:58:32 2008 -0800

    added some blame and merge stuff

Angenommen, Sie sind an dem Commit interessiert, dessen Hash mit 1c002dd... beginnt. Sie können den Commit mit einer der folgenden Varianten von git show überprüfen (vorausgesetzt, die verkürzten Versionen sind eindeutig):

$ git show 1c002dd4b536e7479fe34593e72e6c6c1819e53b
$ git show 1c002dd4b536e7479f
$ git show 1c002d

Git kann eine eindeutige Abkürzung für Ihre SHA-1-Werte ermitteln. Wenn Sie --abbrev-commit an den Befehl git log übergeben, verwendet die Ausgabe kürzere Werte, aber sie bleiben eindeutig. Es werden standardmäßig sieben Zeichen verwendet, aber bei Bedarf werden sie verlängert, um den SHA-1 eindeutig zu halten:

$ git log --abbrev-commit --pretty=oneline
ca82a6d changed the version number
085bb3b removed unnecessary test code
a11bef0 first commit

In der Regel sind acht bis zehn Zeichen mehr als genug, um innerhalb eines Projekts unverwechselbar zu sein. Zum Beispiel hat der Linux-Kernel (ein ziemlich großes Projekt) seit Februar 2019 über 875.000 Commits und fast sieben Millionen Objekte in seiner Objektdatenbank, wobei keine zwei Objekte vorhanden sind, deren SHA-1s in den ersten 12 Zeichen identisch sind.

Note
Eine kurze Anmerkung zu SHA-1

Viele Leute machen sich zu einem bestimmten Zeitpunkt Sorgen, dass sie zufällig zwei verschiedene Objekte in ihrem Repository haben könnten, die den gleichen SHA-1-Wert haben. Was dann?

Wenn Sie ein Objekt, das auf den gleichen SHA-1-Wert wie ein vorhergehendes unterschiedliches Objekt in Ihrem Repository hasht, committen, wird Git das vorhergehende Objekt bereits in Ihrer Git-Datenbank sehen und davon ausgehen, dass es bereits geschrieben wurde und es einfach wiederverwenden. Wenn Sie versuchen, dieses Objekt irgendwann wieder auszuchecken, erhalten Sie immer die Daten des ersten Objekts.

Sie sollten sich jedoch bewusst sein, wie lächerlich unwahrscheinlich dieses Szenario ist. Der SHA-1 Hashwert beträgt 20 Bytes oder 160 Bit. Die Anzahl der zufällig gehashten Objekte, die benötigt werden, um eine 50%ige Wahrscheinlichkeit einer einzelnen Kollision zu erreichen, beträgt etwa 280. Die Formel zur Bestimmung der Kollisionswahrscheinlichkeit ist p = (n(n-1)/2) * (1/2^160). 280 sind 1.2 x 1024 oder 1 Million Milliarden Milliarden. Das ist das 1.200-fache der Anzahl der Sandkörner auf der Erde.

Hier ist ein Beispiel, um Ihnen eine Vorstellung davon zu geben, was nötig wäre, um eine SHA-1-Kollision zu erhalten. Wenn alle 6,5 Milliarden Menschen auf der Erde programmierten würden und jeder jede Sekunde soviel Code produzieren würde, die der Menge des gesamten Verlaufs des Linux-Kernels entspräche (6,5 Millionen Git-Objekte) und dann alles in ein riesiges Git-Repository schieben wollte, dann würde es etwa 2 Jahre dauern, bis dieses Repository genügend Objekte enthielte, um eine 50%ige Wahrscheinlichkeit einer einzelnen SHA-1-Objektkollision zu erzielen. Somit ist eine SHA-1-Kollision unwahrscheinlicher, als wenn jedes Mitglied Ihres Programmierer-Teams in der gleichen Nacht von Wölfen angegriffen und bei unabhängigen Zwischenfällen getötet würde.

Branch Referenzen

Eine unkomplizierte Methode, auf einen bestimmten Commit zu verweisen, ist, wenn es sich um den Commit an der Spitze von einem Branch handelt. In diesem Fall können Sie einfach den Branch-Namen in jedem Git-Befehl verwenden, der eine Referenz auf einen Commit erwartet. Wenn Sie beispielsweise das letzte Commit-Objekt in einem Branch untersuchen möchten, sind die folgenden Befehle gleichwertig, vorausgesetzt, der Branch topic1 zeigt auf den Commit ca82a6d....:

$ git show ca82a6dff817ec66f44342007202690a93763949
$ git show topic1

Wenn Sie sehen wollen, auf welchen spezifischen SHA-1 einen Branch zeigt, oder wenn Sie sehen wollen, worauf sich eines dieser Beispiele in Bezug auf SHA-1s verkürzt, können Sie ein Git Basis-Befehl-Tool (engl. plumbing tool) mit dem Namen rev-parse verwenden. Sie können in Git Interna weitere Details über Basisbefehl-Tools nachlesen. Im Grunde genommen gibt es rev-parse für Low-Level-Operationen und ist nicht für den Einsatz im täglichen Betrieb konzipiert. Allerdings kann es gelegentlich hilfreich sein, wenn man herausfinden muss, was eigentlich passiert. So können Sie rev-parse auf Ihrem Branch ausführen.

$ git rev-parse topic1
ca82a6dff817ec66f44342007202690a93763949

RefLog Kurzformen

Eine der Dinge, die Git im Hintergrund macht, während Sie arbeiten, ist, einen „Reflog“ zu aufzuzeichnen – ein Protokoll darüber, wo sich Ihre HEAD- und Branch-Referenzen in den letzten Monaten befunden haben.

Sie können Ihr Reflog sehen, indem Sie git reflog benutzen:

$ git reflog
734713b HEAD@{0}: commit: fixed refs handling, added gc auto, updated
d921970 HEAD@{1}: merge phedders/rdocs: Merge made by the 'recursive' strategy.
1c002dd HEAD@{2}: commit: added some blame and merge stuff
1c36188 HEAD@{3}: rebase -i (squash): updating HEAD
95df984 HEAD@{4}: commit: # This is a combination of two commits.
1c36188 HEAD@{5}: rebase -i (squash): updating HEAD
7e05da5 HEAD@{6}: rebase -i (pick): updating HEAD

Jedes Mal, wenn die Spitze Ihrer Branch aus irgendeinem Grund aktualisiert wird, speichert Git diese Informationen für Sie in dieser temporären Historie. Sie können Ihre Reflog-Daten auch verwenden, um auf ältere Commits zu verweisen. Wenn Sie beispielsweise den fünft-letzten Wert des HEADs Ihres Repositorys sehen möchten, können Sie den Verweis @{5} benutzen, damit Sie diese Reflog-Ausgabe erhalten:

$ git show HEAD@{5}

Sie können diese Syntax auch verwenden, um zu sehen, wo sich ein Branch vor einer bestimmten Zeit befand. Um zum Beispiel zu sehen, wo Ihr master Branch gestern war, können Sie folgendes eingeben

$ git show master@{yesterday}

Das würde Ihnen zeigen, wo die Spitze Ihres master Branchs gestern war. Diese Technik funktioniert nur für Daten, die sich noch in Ihrem Reflog befinden, daher können Sie sie nicht verwenden, um nach Commits zu suchen, die älter als ein paar Monate sind.

Um die Reflog-Informationen so zu formatieren, wie die Ausgabe von git log, können Sie git log -g aufrufen:

$ git log -g master
commit 734713bc047d87bf7eac9674765ae793478c50d3
Reflog: master@{0} (Scott Chacon <schacon@gmail.com>)
Reflog message: commit: fixed refs handling, added gc auto, updated
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri Jan 2 18:32:33 2009 -0800

    fixed refs handling, added gc auto, updated tests

commit d921970aadf03b3cf0e71becdaab3147ba71cdef
Reflog: master@{1} (Scott Chacon <schacon@gmail.com>)
Reflog message: merge phedders/rdocs: Merge made by recursive.
Author: Scott Chacon <schacon@gmail.com>
Date:   Thu Dec 11 15:08:43 2008 -0800

    Merge commit 'phedders/rdocs'

Es ist jedoch wichtig festzuhalten, dass die Reflog-Informationen ausschließlich lokale Informationen sind – es ist nur ein Protokoll dessen, was Sie in Ihrem Repository getan haben. Die Referenzen sind nicht die gleichen wie auf einer anderen Kopie des Repositorys. Gleich nachdem Sie ein Repository geklont haben, haben Sie ein leeres Reflog, da noch keine Aktivität in Ihrem Repository stattgefunden hat. Wenn Sie git show HEAD@{2.months.ago} ausführen, wird Ihnen der passende Commit nur angezeigt, wenn Sie das Projekt vor mindestens zwei Monaten geklont haben – wenn Sie es aber erst vor kurzem geklont haben, sehen Sie nur Ihren ersten lokalen Commit.

Tip
Betrachten Sie das Reflog als die Shell-Historie von Git.

Wenn Sie UNIX- oder Linux-Kenntnisse haben, können Sie sich das Reflog als die Gits-Version der Shell-Historie vorstellen, mit der Betonung, dass das, was es anzeigt, eindeutig nur für Sie und Ihre „Sitzung“ relevant ist und mit niemand anderem etwas zu tun hat, der an der gleichen Maschine arbeiten könnte.

Abstammung der Referenzen

Die andere Hauptmethode, um einen Commit anzugeben, ist über seine Abstammung. Wenn Sie ein ^ (Zirkumflex) am Ende einer Referenz platzieren, löst Git es auf, um das übergeordnete Element dieses Commits zu bezeichnen. Angenommen, Sie schauen auf den Verlauf Ihres Projekts:

$ git log --pretty=format:'%h %s' --graph
* 734713b fixed refs handling, added gc auto, updated tests
*   d921970 Merge commit 'phedders/rdocs'
|\
| * 35cfb2b Some rdoc changes
* | 1c002dd added some blame and merge stuff
|/
* 1c36188 ignore *.gem
* 9b29157 add open3_detach to gemspec file list

Dann könnten Sie den vorherigen Commit sehen, indem Sie HEAD^ angeben, das das „Elternteil von HEAD“ bedeutet:

$ git show HEAD^
commit d921970aadf03b3cf0e71becdaab3147ba71cdef
Merge: 1c002dd... 35cfb2b...
Author: Scott Chacon <schacon@gmail.com>
Date:   Thu Dec 11 15:08:43 2008 -0800

    Merge commit 'phedders/rdocs'
Note
Den Zirkumflex (^) in Windows umgehen

In der Eingabeaufforderung von Windows (cmd.exe) ist ^ ein Sonderzeichen und muss anders behandelt werden. Sie können es entweder verdoppeln oder die Commit-Referenz in Anführungszeichen setzen:

$ git show HEAD^     # wird in Windows NICHT funktionieren
$ git show HEAD^^    # OK
$ git show "HEAD^"   # OK

Sie können auch eine Zahl nach dem ^ angeben, um den gewünschten Elternteil zu identifizieren. So bedeutet beispielsweise d921970^2 den „zweiten Elternteil von d921970“. Diese Syntax ist nur für Merge-Commits nützlich, die mehr als einen Elternteil haben – der erste Elternteil eines Merge-Commits stammt aus dem Branch, in dem Sie beim Mergen waren (häufig master), während der zweite Elternteil eines Merge-Commits aus dem Zweig stammt, der zusammengeführt wurde (z.B. topic):

$ git show d921970^
commit 1c002dd4b536e7479fe34593e72e6c6c1819e53b
Author: Scott Chacon <schacon@gmail.com>
Date:   Thu Dec 11 14:58:32 2008 -0800

    added some blame and merge stuff

$ git show d921970^2
commit 35cfb2b795a55793d7cc56a6cc2060b4bb732548
Author: Paul Hedderly <paul+git@mjr.org>
Date:   Wed Dec 10 22:22:03 2008 +0000

    Some rdoc changes

Die andere wichtige Abstammungsangabe ist die ~ (Tilde). Sie bezieht sich auch auf den ersten Elternteil, so dass HEAD~ und HEAD^ gleichbedeutend sind. Der Unterschied wird deutlich, wenn Sie eine Zahl angeben. HEAD~2 meint den „ersten Elternteil des ersten Elternteils“ oder „den Großelternteil“ – er passiert den ersten Elternteil so oft wie Sie angegeben haben. In der zuvor aufgelisteten Historie wäre z. B. HEAD~3 folgendes gewesen

$ git show HEAD~3
commit 1c3618887afb5fbcbea25b7c013f4e2114448b8d
Author: Tom Preston-Werner <tom@mojombo.com>
Date:   Fri Nov 7 13:47:59 2008 -0500

    ignore *.gem

Das kann auch als HEAD~~~ geschrieben werden. Auch hier handelt es sich um den ersten Elternteil des ersten Elternteils des ersten Elternteils:

$ git show HEAD~~~
commit 1c3618887afb5fbcbea25b7c013f4e2114448b8d
Author: Tom Preston-Werner <tom@mojombo.com>
Date:   Fri Nov 7 13:47:59 2008 -0500

    ignore *.gem

Sie können diese Syntax auch kombinieren – so können Sie den zweiten Elternteil der vorhergehenden Referenz (vorausgesetzt, es handelt sich um einen Merge Commit) erhalten, indem Sie HEAD~3^2 verwenden, und so weiter.

Commit-Bereiche

Nachdem Sie jetzt einzelne Commits angeben können, möchten wir Ihnen zeigen, wie Sie einen Bereich von Commits festlegen können. Besonders nützlich ist das für die Verwaltung Ihrer Branches – bei vielen Branches können Sie mit Hilfe von Range-Spezifikationen Fragen beantworten wie: „Welche Arbeit ist in diesem Branch, die ich noch nicht mit meiner Haupt-Branch zusammengeführt habe?“

Doppelter Punkt

Die gebräuchlichste Bereichsspezifikation ist die Doppelte-Punkt-Syntax. Hiermit wird Git im Wesentlichen aufgefordert, eine Reihe von Commits zu bearbeiten, die von einem bestimmten Commit erreichbar sind, aber von einem anderen nicht. Angenommen, Sie haben eine Commit-Historie, die wie Beispiel – Verlauf zur Bereichsauswahl aussieht.

Beispiel – Verlauf zur Bereichsauswahl
Figure 136. Beispiel – Verlauf zur Bereichsauswahl

Angenommen, Sie wollen wissen, was sich in Ihrer Branch experiment befindet, die noch nicht mit Ihrer Branch master gemergt wurde. Sie können Git fragen, ob es Ihnen ein Log der Commits mit master..experiment anzeigen kann – d.h. „alle Commits, die von experiment aus erreichbar sind, von master aus aber nicht“. Um die Kürze und Übersichtlichkeit dieser Beispiele zu erhalten, werden die Buchstaben der Commit-Objekte aus der Abbildung anstelle der eigentlichen Protokollausgabe verwendet, in der Reihenfolge, in der sie angezeigt werden:

$ git log master..experiment
D
C

Wenn Sie andererseits das Gegenteil sehen wollen – alle Commits in master, die nicht in experiment sind – dann können Sie die Branch-Namen umkehren. experiment..master zeigt Ihnen alles in master, was von experiment nicht erreichbar ist:

$ git log experiment..master
F
E

Dieses Vorgehen ist praktisch, wenn Sie den Branch experiment auf dem aktuellen Stand halten und eine Vorschau darauf erhalten möchten, was Sie gerade verschmelzen wollen. Eine weitere häufige Anwendung dieser Syntax besteht darin, zu überprüfen, was Sie auf einen Remote pushen möchten:

$ git log origin/master..HEAD

Dieser Befehl zeigt Ihnen alle Commits in Ihrem aktuellen Branch an, die sich nicht im Branch master auf Ihrem Remote origin befinden. Wenn Sie ein git push ausführen und Ihr aktueller Branch trackt origin/master, dann sind die Commits, die mit git log origin/master..HEAD aufgelistet werden, die Commits, die an den Server übertragen werden. Sie können auch eine Seite der Syntax weglassen, so dass Git HEAD annimmt. Zum Beispiel können Sie die gleichen Ergebnisse wie im vorherigen Beispiel erhalten, indem Sie git log origin/master.. – Git ersetzt HEAD, wenn eine Seite fehlt.

Mehrere Punkte

Die Doppelte-Punkt-Syntax ist als Kurzform nützlich, aber möglicherweise möchten Sie mehr als zwei Branches angeben, um Ihren Revisions-Stand anzuzeigen. So können Sie beispielsweise feststellen, welche Commits in einem oder mehreren Branches vorhanden sind aber sich nicht in dem Branch befinden, in dem Sie sich gerade aufhalten. Git ermöglicht Ihnen mit dem Zeichen ^ oder dem Zusatz --not vor einer Referenz, dass Sie keinen der erreichbaren Commits sehen möchten. Die folgenden drei Befehle sind daher vergleichbar:

$ git log refA..refB
$ git log ^refA refB
$ git log refB --not refA

Das ist auch deshalb interessant, weil Sie mit dieser Syntax mehr als zwei Referenzen in Ihrer Abfrage angeben können. Das ist mit der Doppelte-Punkt-Syntax (engl. Double-Dot-Syntax) nicht möglich. Wenn Sie zum Beispiel alle Commits sehen möchten, die von refA oder refB aus erreichbar sind, aber nicht von refC aus, können Sie eine der folgenden Optionen verwenden:

$ git log refA refB ^refC
$ git log refA refB --not refC

Das sorgt für ein sehr leistungsfähiges Revisions-Abfragesystem, das Ihnen dabei helfen sollte, festzustellen, was in Ihren Branches gerade enthalten ist.

Dreifacher Punkt

Die letzte wichtige Syntax für die Bereichsauswahl ist die Triple-Dot-Syntax (Dreifach-Punkt-Syntax), die alle Commits angibt, die durch eine der beiden Referenzen erreichbar sind, aber nicht durch beide. Schauen Sie sich dazu die Commit-Historie in Beispiel – Verlauf zur Bereichsauswahl an. Wenn Sie wissen wollen, was sich in master oder experiment befindet, aber nicht in gemeinsamen Referenzen, können Sie diese Funktion ausführen:

$ git log master...experiment
F
E
D
C

Auch hier erhalten Sie eine normale log Ausgabe. Es werden Ihnen jedoch nur die Commit-Informationen für diese vier Commits angezeigt, die in der traditionellen Reihenfolge der Commit-Daten erscheinen.

Ein gängiger Parameter, der in hier mit dem log Befehl verwendet werden kann ist --left-right. Er zeigt Ihnen, auf welcher Seite des Bereichs sich der Commit gerade befindet. Auf diese Weise wird die Ausgabe besser auswertbar:

$ git log --left-right master...experiment
< F
< E
> D
> C

Mit diesen Tools können Sie Git viel einfacher mitteilen, welche Commits Sie überprüfen möchten.