Git
Chapters ▾ 2nd Edition

7.1 Git Tools - Revisions-Auswahl

Inzwischen hast du die meisten der gängigen Befehle und Workflows kennen gelernt. Du benötigst sie, um ein Git-Repository für deine Quellcode-Kontrolle zu verwalten oder zu pflegen. Du hast die grundlegenden Aufgaben des Tracking und Committens von Dateien gelernt und du hast die Leistungsfähigkeit der Staging-Area und Branching- und Merging-Funktionen mit Feature-Branches genutzt.

Jetzt wirst du eine Reihe von anderen nützlichen Git-Funktionen entdecken. Du wirst 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 offensichtlich, aber es ist nützlich sie zu kennen.

Einzelne Revisionsstände

Du kannst dich 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 du auf jeden Commit verweisen kannst.

Kurz-SHA-1

Git ist intelligent genug, um herauszufinden, auf welchen Commit du dich beziehst, wenn du die ersten paar Zeichen des SHA-1-Hash angibst, 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 du z.B. einen bestimmten Commit untersuchen möchtest, kannst du 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

    Fix refs handling, add gc auto, update 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

    Add some blame and merge stuff

Angenommen, du bist an dem Commit interessiert, dessen Hash mit 1c002dd…​ beginnt. Du kannst den Commit mit einer der folgenden Varianten von git show überprüfen (vorausgesetzt, die verkürzten Hash-Versionen sind eindeutig):

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

Git kann eine eindeutige Abkürzung für deine SHA-1-Hashs ermitteln. Wenn du --abbrev-commit mit dem Befehl git log nutzt, verwendet die Ausgabe kürzere und eindeutige Hast-Werte. Standardmäßig werden sieben Zeichen verwendet. Diese werden jedoch bei Bedarf länger, um die Eindeutigkeit des SHA-1-Hashs zu gewährleisten:

$ git log --abbrev-commit --pretty=oneline
ca82a6d Change the version number
085bb3b Remove unnecessary test code
a11bef0 Initial commit

In der Regel sind acht bis zehn Zeichen mehr als genug, um innerhalb eines Projekts eindeutig 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.

Anmerkung
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 du ein Objekt, das auf den gleichen SHA-1-Wert wie ein vorhergehendes unterschiedliches Objekt in deinem Repository hasht, committest, wird Git das vorhergehende Objekt bereits in deiner Git-Datenbank sehen und davon ausgehen, dass es bereits geschrieben wurde und es einfach wiederverwenden. Wenn du versuchst, dieses Objekt irgendwann wieder auszuchecken, erhältst du immer die Daten des ersten Objekts.

Du solltest dir jedoch bewusst sein, wie 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 dir 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 geschoben wird, 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 organische SHA-1-Kollision unwahrscheinlicher, als wenn jedes Mitglied deines Programmierer-Teams in der gleichen Nacht von Wölfen angegriffen und auf eine andere Art in der selben Nacht getötet würde.

Wenn du Rechenleistung im Wert von mehreren Tausend US-Dollar dafür bereitstellst, könntest du zwei Dateien mit demselben Hash künstlich generieren, wie im Februar 2017 unter https://shattered.io/ nachgewiesen wurde. Git geht dazu über, SHA256 als Standard-Hashing-Algorithmus zu verwenden, der gegenüber Kollisionsangriffen wesentlich widerstandsfähiger ist und Code beinhaltet, um diesen Angriff abzuschwächen (obwohl er nicht vollständig beseitigt werden kann).

Branch Referenzen

Eine unkomplizierte Methode, auf einen bestimmten Commit zu verweisen, ist, wenn es sich um den Commit am Ende eines Branches handelt. In diesem Fall kannst du einfach den Branch-Namen in jedem Git-Befehl verwenden, der eine Referenz auf einen Commit erwartet. Wenn du beispielsweise das letzte Commit-Objekt in einem Branch untersuchen möchtest, sind die folgenden Befehle gleichwertig, vorausgesetzt, der Branch topic1 zeigt auf den Commit ca82a6d…​.:

$ git show ca82a6dff817ec66f44342007202690a93763949
$ git show topic1

Wenn du sehen willst, auf welchen spezifischen SHA-1 ein Branch zeigt, oder wenn du sehen willst, worauf sich die folgenden Beispiele in Bezug auf SHA-1s verkürzen, kannst du ein Git Basis-Befehl (engl. plumbing tool) mit dem Namen rev-parse verwenden. Du kannst in Git Interna weitere Details über Basisbefehl-Tools nachlesen. Der Befehlt rev-parse ist eine Low-Level-Befehl und ist normalerweise nicht für den täglichen Einsatz notwendig. Allerdings kann es gelegentlich hilfreich sein, wenn man herausfinden muss, was eigentlich passiert ist. So kannst du rev-parse auf deinem Branch ausführen.

$ git rev-parse topic1
ca82a6dff817ec66f44342007202690a93763949

RefLog Kurzformen

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

Du kannst dein Reflog sehen, indem du git reflog benutzt:

$ git reflog
734713b HEAD@{0}: commit: Fix refs handling, add gc auto, update tests
d921970 HEAD@{1}: merge phedders/rdocs: Merge made by the 'recursive' strategy.
1c002dd HEAD@{2}: commit: Add 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 das Ende deines Branch aus irgendeinem Grund aktualisiert wird, speichert Git diese Informationen für dich in dieser temporären Historie. Du kannst deine Reflog-Daten auch verwenden, um auf ältere Commits zu verweisen. Wenn du beispielsweise den fünft-letzten Wert des HEADs deines Repositorys sehen möchtest, kannst du den Verweis @{5} benutzen, damit du diese Reflog-Ausgabe erhältst:

$ git show HEAD@{5}

Du kannst diese Syntax auch verwenden, um zu sehen, wo sich ein Branch vor einer bestimmten Zeit befand. Um zum Beispiel zu sehen, wo dein master Branch gestern war, kannst du folgendes eingeben:

$ git show master@{yesterday}

Das würde dir zeigen, wo das Ende deines master Branchs gestern war. Diese Technik funktioniert nur für Daten, die sich noch in deinem Reflog befinden. Daher kannst du 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, kannst du git log -g aufrufen:

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

    Fix refs handling, add gc auto, update 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 du in deinem Repository getan hast. Diese Referenzen sind nicht die gleichen wie auf einer anderen Kopie des Repositorys. Gleich nachdem du ein Repository geklont hast, hast du ein leeres Reflog, da noch keine Aktivität in deinem lokalen Repository stattgefunden hat. Wenn du git show HEAD@{2.months.ago} ausführst, wird dir der passende Commit nur angezeigt, wenn du das Projekt vor mindestens zwei Monaten geklont haben. Wenn du es aber erst vor kurzem geklont hast, siehst du nur deinen ersten lokalen Commit.

Hinweis
Betrachte das Reflog als die Shell-Historie von Git.

Wenn du UNIX- oder Linux-Kenntnisse hast, kannst du dir das Reflog als die Git-Version der Shell-Historie vorstellen. Diese zeigt jedoch wie bei der Shell-Historie nur die Daten für deine „Sitzung“, die mit niemand anderem etwas zu tun hat, der am gleichen Client arbeiten könnte.

Anmerkung
Klammern in PowerShell maskieren

Bei Verwendung von PowerShell sind geschweifte Klammern wie { und } Sonderzeichen und müssen maskiert werden. Du kannst sie mit einem Backtick ` maskieren oder die Commit-Referenz in Anführungszeichen setzen:

$ git show HEAD@{0}     # wird nicht funktionieren
$ git show HEAD@`{0`}   # OK
$ git show "HEAD@{0}"   # OK

Abstammung der Referenzen

Die andere Methode, um einen Commit anzugeben, ist über seine Abstammung. Wenn du ein ^ (Zirkumflex) am Ende einer Referenz anhängt, löst Git es auf, um das Eltern (engl. parent) Element dieses Commits anzusprechen. Angenommen, du schaust auf den Verlauf deines Projekts:

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

Dann könntest du den vorherigen Commit sehen, indem du HEAD^ angibst, 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'
Anmerkung
Den Zirkumflex (^) in Windows umgehen

In der Eingabeaufforderung von Windows (cmd.exe) ist ^ ein Sonderzeichen und muss anders behandelt werden. Du kannst 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

Du kannst 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 du beim Mergen warst (in der Regel master). Der zweite Elternteil eines Merge-Commits stammt aus dem Branch, 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

    Add 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 du eine Zahl angibst. HEAD~2 meint den „ersten Elternteil des ersten Elternteils“ oder „den Großelternteil“ – er passiert den ersten Elternteil so oft wie du angegeben hast. 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

Du kannst diese Syntax auch kombinieren – so kannst du den zweiten Elternteil der vorhergehenden Referenz (vorausgesetzt, es handelt sich um einen Merge Commit) erhalten, indem du HEAD~3^2 verwendest, und so weiter.

Commit-Bereiche

Nachdem du jetzt einzelne Commits angeben kannst, möchten wir dir zeigen, wie du einen Bereich von Commits festlegen kannst. Besonders nützlich ist das für die Verwaltung deines Branches. Bei vielen Branches kannst du mit Hilfe von Bereichs(engl. 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 aufzulösen, die von einem bestimmten Commit erreichbar sind, aber von einem anderen nicht. Angenommen, du hast eine Commit-Historie, die wie Beispiel – Verlauf zur Bereichsauswahl aussieht.

Beispiel – Verlauf zur Bereichsauswahl
Abbildung 136. Beispiel – Verlauf zur Bereichsauswahl

Angenommen, du willst nun wissen, was sich in deinem Branch experiment befindet, das noch nicht mit deinem Branch master gemerged wurde. Du kannst Git fragen, ob es dir 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 du das Gegenteil sehen wollen – alle Commits in master, die nicht in experiment sind – dann kannst du die Branch-Namen umkehren. experiment..master zeigt dir alles in master, was von experiment nicht erreichbar ist:

$ git log experiment..master
F
E

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

$ git log origin/master..HEAD

Dieser Befehl zeigt dir alle Commits in deinem aktuellen Branch an, die sich nicht im Branch master auf deinem Remote origin befinden. Wenn du ein git push ausführst und dein 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. Du kannst auch eine Seite der Syntax weglassen, so dass Git HEAD auf der fehlenden Seite annimmt. Zum Beispiel kannst du die gleichen Ergebnisse wie im vorherigen Beispiel erhalten, indem du git log origin/master.. angibst. Git ersetzt in diesem Fall`HEAD`, wenn eine Seite fehlt.

Mehrere Punkte

Die Doppelte-Punkt-Syntax ist als Kurzform nützlich, aber möglicherweise möchtest du mehr als zwei Branches angeben, um deinen Revisions-Stand anzuzeigen. So könntest du beispielsweise feststellen, welche Commits in einem oder mehreren Branches vorhanden sind aber sich nicht in dem Branch befinden, in dem du dich gerade aufhältst. Git ermöglicht dir dies mit dem Zeichen ^ oder dem Zusatz --not vor einer Referenz, von der du keinen Commits sehen möchtest. 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 du mit dieser Syntax mehr als zwei Referenzen in deiner Abfrage angeben kannst. Das ist mit der Doppelte-Punkt-Syntax (engl. Double-Dot-Syntax) nicht möglich. Wenn du zum Beispiel alle Commits sehen möchtest, die von refA oder refB aus erreichbar sind, aber nicht von refC aus, kannst du 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 dir dabei helfen sollte, festzustellen, was in deinen Branches gerade enthalten ist.

Dreifacher Punkt

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

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

Auch hier erhältst du eine normale log Ausgabe. Es werden dir jedoch nur die Commit-Informationen für diese vier Commits angezeigt, die in der normalen Reihenfolge der Commit-Daten erscheinen.

Ein gängiger Parameter, der hier mit dem log Befehl verwendet werden kann ist --left-right. Er zeigt dir, 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 kannst du Git viel einfacher mitteilen, welche Commits du überprüfen möchtest.

scroll-to-top