Close

Git refs: An overview

In diesem Tutorial mit vermitteln wir dir anhand eines Weltraumszenarios die Grundlagen von Git.

Viele verschiedene Methoden zur Referenzierung eines Commits

Wenn du die vielen Methoden zur Referenzierung eines Commits kennst, gewinnst du einiges mehr an Möglichkeiten bei der Nutzung dieser Befehle. In diesem Kapitel beleuchten wir häufig genutzte Befehle wie git checkout, git branch und git push genauer, indem wir du vielen Methoden zur Referenzierung eines Commits durchgehen.

Außerdem lernen wir, wie anscheinend "verlorene" Commits wiederbelebt werden, indem man über den Reflog-Mechanismus von Git auf sie zugreift.


Hashes


Die direkteste Methode zur Referenzierung eines Commits führt über seinen SHA-1-Hash. Dieser fungiert als unverwechselbare ID für jeden Commit. Den Hash eines Commits findest du in der Ausgabe von git log.

commit 0c708fdec272bc4446c6cabea4f0022c2b616eba Author: Mary Johnson  Date: Wed Jul 9 16:37:42 2014 -0500 Some commit message

Wenn du einen Commit in einem Git-Befehl angibst, müssen lediglich ausreichend Zeichen vorhanden sein, um den Commit eindeutig zu identifizieren. Den obigen Commit könntest du z. B. mit git show untersuchen, indem du den folgenden Befehl ausführst:

git show 0c708f

Manchmal muss ein Branch, Tag oder eine andere indirekte Referenz in den entsprechenden Commit-Hash umgewandelt werden. Hierfür kannst du den Befehl git rev-parse verwenden. Der folgende Befehl gibt den Hash des Commits zurück, auf den der main-Branch verweist:

Datenbanken
Zugehöriges Material

Verschieben eines vollständigen Git-Repositorys

Bitbucket-Logo
Lösung anzeigen

Git kennenlernen mit Bitbucket Cloud

git rev-parse main

Dies ist besonders hilfreich beim Schreiben benutzerdefinierter Skripte, die eine Commit-Referenz akzeptieren. Statt die Commit-Referenz manuell zu parsen, kann git rev-parse die Eingabe für dich normalisieren.

Referenzen


Eine Referenz bzw. ref ist eine indirekte Methode zur Referenzierung eines Commits. Du kannst sie als einen benutzerfreundlichen Alias für einen Commit-Hash betrachten. Sie sind der interne Mechanismus von Git zur Repräsentierung von Branches und Tags.

Refs werden als normale Textdateien im .git/refs-Verzeichnis gespeichert, in dem .git normalerweise .git heißt. Zum Durchsuchen der Refs in einem deiner Repositorys gehst du zu .git/refs. Dort solltest du die folgende Struktur sehen, wobei natürlich je nach den in deinem Repo vorhandenen Branches, Tags und Remotes andere Dateien enthalten sein werden.

.git/refs/ heads/ main some-feature remotes/ origin/ main tags/ v0.9

Das Verzeichnis heads definiert alle lokalen Branches in deinem Repository. Jeder Dateiname entspricht dem Namen des jeweiligen Branches. In der Datei selbst gibt es einen Commit-Hash. Dieser Commit-Hash gibt an, wo sich die Branch-Spitze befindet. Das kannst du überprüfen, indem du die folgenden beiden Befehle aus dem Root-Verzeichnis des Git-Repositorys ausführst:

# Output the contents of `refs/heads/main` file: cat .git/refs/heads/main # Inspect the commit at the tip of the `main` branch: git log -1 main

Der Commit-Hash, der vom Befehl cat zurückgegeben wird, sollte mit der Commit-ID übereinstimmen, die mit git log angezeigt werden kann.

Um den Ort des main-Branch zu ändern, muss Git dies lediglich in der refs/heads/main-Datei ändern. Genauso lässt sich ein neuer Branch einfach erstellen, indem ein Commit-Hash in eine neue Datei eingetragen wird. Dies ist einer der Gründe, warum Git-Branches so viel schlanker sind als das SVN-Modell.

Das tags-Verzeichnis funktioniert auf dieselbe Weise, enthält jedoch Tags statt Branches. Das remotes-Verzeichnis listet alle Remote-Repositorys, die du mit git remote erstellt hast, als separate Unterverzeichnisse auf. In jedem einzelnen findest du alle Remote Branches, die du in dein Repository gezogen hast.

Specifying refs

Wenn du einem Git-Befehl eine Referenz anfügst, kannst du entweder den vollen Namen der Referenz oder einen Kurznamen verwenden, anhand dessen Git nach einer übereinstimmenden Referenz sucht. Du solltest bereits mit Kurznamen für Referenzen vertraut sein, da du diese jedes Mal verwendest, wenn du einen Branch beim Namen nennst.

git show some-feature

Das Argument some-feature im obigen Befehl ist eigentlich ein Kurzname für den Branch. Git wandelt dies vor der Verwendung in refs/heads/some-feature um. Du kannst in der Befehlszeile auch die komplette Ref angeben:

git show refs/heads/some-feature

Auf diese Weise werden Unklarheiten bezüglich des Orts der Ref vermieden. Dies ist z. B. dann erforderlich, wenn sowohl ein Branch und ein Tag namens some-feature vorhanden sind. Sofern du dich jedoch an die korrekten Benennungskonventionen hältst, sollten eigentlich keine Zweideutigkeiten aufkommen.

Wir werden im Abschnitt zu den Refspecs noch weitere vollständige Referenznamen sehen.

Packed refs


In großen Repositorys entfernt Git im Rahmen einer Speicherbereinigung in regelmäßigen Abständen überflüssige Objekte und komprimiert Referenzen in eine einzige Datei für eine Verbesserung der Performance. Diese Komprimierung während der Bereinigung kannst du mit dem folgenden Befehl erzwingen:

git gc

Hiermit werden alle Branch- und Tag-Dateien im refs-Ordner in eine einzige Datei namens packed-refs verschoben. Diese befindet sich ganz oben im .git-Verzeichnis. Wenn du diese Datei öffnest, wird dir eine Zuordnung von Commit-Hashes zu Referenzen angezeigt.

00f54250cf4e549fdfcafe2cf9a2c90bc3800285 refs/heads/feature 0e25143693cfe9d5c2e83944bbaf6d3c4505eb17 refs/heads/main bb883e4c91c870b5fed88fd36696e752fb6cf8e6 refs/tags/v0.9

Äußerlich wirkt sich dies überhaupt nicht auf die normale Git-Funktionalität aus. Aber wenn du dich gewundert hast, warum dein .git/refs-Ordner leer ist, weißt du jetzt, wo deine Referenzen sich nun befinden.

Special refs


Neben dem refs-Verzeichnis gibt es noch einige spezielle Refs, die sich im .git-Verzeichnis der obersten Ebene befinden. Diese sind im Folgenden aufgelistet:

  • HEAD: Der aktuell ausgecheckte Commit/Branch
  • FETCH_HEAD: Der letzte von einem Remote-Repo abgerufene Branch
  • ORIG_HEAD: Eine Backup-Referenz für den HEAD, bevor drastische Änderungen daran vorgenommen werden
  • MERGE_HEAD: Die Commits, die du mit git merge in den aktuellen Branch mergst
  • CHERRY_PICK_HEAD: Ein gezielt ausgewählter Commit

Diese Referenzen werden alle bei Bedarf von Git erstellt und aktualisiert. Der Befehl git pull führt z. B. zuerst git fetch aus, wodurch die FETCH_HEAD-Referenz aktualisiert wird. Anschließend wird git merge FETCH_HEAD ausgeführt, um den Pull der abgerufenen Branches in das Repository abzuschließen. Natürlich kannst du diese speziellen Refs wie alle anderen Refs auch verwenden, genauso wie du dies sicherlich auch mit HEAD gemacht hast.

Diese Dateien enthalten je nach Dateityp und Status in deinem Repository unterschiedliche Inhalte. Die HEAD-Ref kann entweder eine symbolische Ref, die einfach eine Referenz zu einer anderen Ref anstatt von einem Commit-Hash ist, oder einen Commit-Hash enthalten. Wirf z. B. einmal einen Blick auf die Inhalte von HEAD, wenn du dich im main-Branch befindest:

git checkout main cat .git/HEAD

This will output ref: refs/heads/main, which means that HEAD points to the refs/heads/main ref. This is how Git knows that the main branch is currently checked out. If you were to switch to another branch, the contents of HEAD would be updated to reflect the new branch. But, if you were to check out a commit instead of a branch, HEAD would contain a commit hash instead of a symbolic ref. This is how Git knows that it’s in a detached HEAD state.

Die meiste Zeit wird HEAD die einzige Referenz sein, die du direkt verwendest. Die anderen sind in der Regel nur dann von Nutzen, wenn du Low-Level-Skripte schreibst, die einen Zugriff auf die tieferliegende Funktionsweise von Git benötigen.

Refspecs


Eine Refspec ordnet einen Branch im lokalen Repository einem Branch in einem Remote-Repository zu. Dies ermöglicht das Management von Remote Branches mit Git-Befehlen und die Konfiguration von erweitertem git push- und git fetch-Verhalten.

Eine Refspec wird mit [+]<src>:<dst> angegeben. Der Parameter <src> steht für den Quell-Branch im lokalen Repository und der Parameter <dst> steht für den Ziel-Branch im Remote-Repository. Das optionale +-Zeichen zwingt das Remote-Repository zu einem Nicht-Fast-Forward-Update.

Refspecs können zusammen mit git push dazu genutzt werden, dem Remote-Branch einen anderen Namen zu geben. Der folgende Befehl pusht z. B. den main-Branch zum origin-Remote-Repo, genauso wie dies mit einem einfachen git push-Befehl geschehen würde, aber in diesem Fall wird qa-main als Branch-Name für den Branch im origin-Repository verwendet. Dies ist für QS-Teams, die ihre eigenen Branches in ein Remote-Repository pushen müssen, hilfreich.

git push origin main:refs/heads/qa-main

You can also use refspecs for deleting remote branches. This is a common situation for feature-branch workflows that push the feature branches to a remote repo (e.g., for backup purposes). The remote feature branches still reside in the remote repo after they are deleted from the local repo, so you get a build-up of dead feature branches as your project progresses. You can delete them by pushing a refspec that has an empty parameter, like so:

git push origin :some-feature

Das ist äußerst praktisch, da du dich nicht in dein Remote-Repository einloggen und den Remote Branch manuell löschen musst. Hinweis: Ab Git v1.7.0 kannst du die Option --delete statt der oben beschriebenen Methode anwenden. Der folgende Befehl hat denselben Effekt wie der obige:

git push origin --delete some-feature

Du kannst das Verhalten von git fetch mit Refspecs ändern, indem du deiner Git-Konfigurationsdatei ein paar Zeilen hinzufügst. In den Standardeinstellungen ruft git fetch alle Branches im Remote-Repository ab. Dies geschieht aufgrund des folgenden Abschnitts in der .git/config-Datei:

[remote "origin"] url = https://git@github.com:mary/example-repo.git fetch = +refs/heads/*:refs/remotes/origin/*

Die fetch-Zeile weist git fetch an, alle Branches vom origin-Repository herunterzuladen. Aber manche Workflows benötigen nicht alle Branches. In vielen Continuous-Integration-Workflows ist beispielsweise nur der main-Branch von Bedeutung. Um nur den main-Branch abzurufen, änderst du die fetch-Zeile folgendermaßen:

[remote "origin"] url = https://git@github.com:mary/example-repo.git fetch = +refs/heads/main:refs/remotes/origin/main

Du kannst auch git push auf ähnliche Weise konfigurieren. Wenn du z. B. den main-Branch zum qa-main im origin-Remote pushen möchtest (wie wir dies oben gemacht haben), änderst du die Konfigurationsdatei folgendermaßen:

[remote "origin"] url = https://git@github.com:mary/example-repo.git fetch = +refs/heads/main:refs/remotes/origin/main push = refs/heads/main:refs/heads/qa-main

Mithilfe der Refspecs erhältst du die absolute Kontrolle darüber, wie die verschiedenen Git-Befehle Branches zwischen Repositorys übertragen. Über sie kannst du Branches umbenennen und von deinem lokalen Repository löschen, Branches mit anderen Namen abrufen/pushen und git push sowie git fetch so konfigurieren, dass die Befehle nur auf die gewünschten Branches angewendet werden.

Relative refs


Du kannst auch Commits im Verhältnis zu einem anderen Commit referenzieren. Mit dem Zeichen ~ erreichst du Parent-Commits. Folgendermaßen wird beispielsweise der Grandparent von HEAD angezeigt:

git show HEAD~2

Doch wenn du mit Merge-Commits arbeitest, wird es ein wenig komplizierter. Da Merge-Commits mehr als einen Parent haben, kannst du auch mehrere Pfaden folgen. Bei einem 3-Wege-Merge stammt der erste Parent von dem Branch, in dem du dich befandst, als du den Merge durchgeführt hast, und der zweite Parent stammt von dem Branch, den du in deinem git merge-Befehl angegeben hast.

Das Zeichen ~ folgt immer dem ersten Parent eines Merge-Commits. Wenn du dem Pfad eines anderen Parents folgen möchtest, musst du diesen mit dem Zeichen ^ bestimmen. Ist beispielsweise HEAD ein Merge-Commit, gibt Folgendes den zweiten Parent von HEAD zurück.

git show HEAD^2

Du kannst das Zeichen ^ mehrmals verwenden, wenn du mehr als eine Generation zurückgehen möchtest. Beim folgenden Beispiel wir der Grandparent vom HEAD (angenommen, es handelt sich um einen Merge-Commit), der sich im zweiten Parent befindet, angezeigt.

git show HEAD^2^1

Um zu verdeutlichen, wie ~ und ^ funktionieren, ist in der folgenden Abbildung dargestellt, wie ein Commit von A aus mit relativen Referenzen erreicht wird. In manchen Fällen gibt es mehrere Wege zum Erreichen eines Commits.

Zugriff auf Commits mithilfe von relativen Referenzen

Relative Referenzen können mit demselben Befehl wie eine normale Referenz verwendet werden. Die folgenden Befehle nutzen z. B. alle eine relative Referenz:

# Only list commits that are parent of the second parent of a merge commit git log HEAD^2 # Remove the last 3 commits from the current branch git reset HEAD~3 # Interactively rebase the last 3 commits on the current branch git rebase -i HEAD~3

The reflog


Der Reflog ist das Sicherheitsnetz von Git. Er zeichnet fast jede Änderung auf, die du in deinem Repository vornimmst – unabhängig davon, ob du einen Snapshot committet hast oder nicht. Er ist im Prinzip ein chronologischer Verlauf aller Aktionen, die du in deinem lokalen Repository durchgeführt hast. Um den Reflog anzuzeigen, führst du den Befehl git reflog aus. Die Ausgabe sollte in etwa folgendermaßen aussehen:

400e4b7 HEAD@{0}: checkout: moving from main to HEAD~2 0e25143 HEAD@{1}: commit (amend): Integrate some awesome feature into `main` 00f5425 HEAD@{2}: commit (merge): Merge branch ';feature'; ad8621a HEAD@{3}: commit: Finish the feature

Dies kann folgendermaßen übersetzt werden:

  • Du hast soeben HEAD~2 ausgecheckt.
  • Davor hast du eine Commit-Nachricht geändert.
  • Davor hast du den feature-Branch in den main-Branch gemergt.
  • Davor hast du einen Snapshot committet.

Über die Syntax HEAD{} kannst du im Reflog gespeicherte Commits referenzieren. Dies funktioniert so ähnlich wie mit den HEAD~-Referenzen aus dem vorigen Abschnitt, aber bezieht sich hier auf einen Eintrag im Reflog anstatt auf den Commit-Verlauf.

Du kannst auf diese Weise einen Commit auf einen andernfalls verloren gegangenen Status zurücksetzen. Nehmen wir an, du hast gerade ein neues Feature mit git reset verworfen. Dein Reflog könnte in etwa so aussehen:

ad8621a HEAD@{0}: reset: moving to HEAD~3 298eb9f HEAD@{1}: commit: Some other commit message bbe9012 HEAD@{2}: commit: Continue the feature 9cb79fa HEAD@{3}: commit: Start a new feature

Die drei Commits vor dem git reset sind nun defekt, d. h. sie können nicht referenziert werden – außer über das Reflog. Nehmen wir an, du stellst fest, dass du nicht deine ganze Arbeit hättest verwerfen sollen. In diesem Fall musst du lediglich den HEAD@{1}-Commit auschecken, um zurück zu dem Zustand deines Repositorys vor dem Ausführen von git reset zu gelangen.

git checkout HEAD@{1}

Dadurch wechselst du in einen Zustand mit losgelöstem HEAD. Von hier aus kannst du einen neuen Branch erstellen und an deinem Feature weiterarbeiten.

Zusammenfassung


Nun solltest du in der Lage sein, problemlos Commits in einem Git-Repository referenzieren. Wir haben gelernt, wie Branches und Tags als Referenzen im .git-Unterverzeichnis gespeichert werden, wie eine packed-refs-Datei gelesen wird, wie HEAD dargestellt wird, wie Refspecs für erweiterte Push- und Fetch-Vorgänge genutzt wird und wie mit den relativen Operatoren ~ und ^ eine Branch-Hierarchie durchkämmt werden kann.

Außerdem haben wir uns das Reflog angesehen, das zur Referenzierung von Commits dient, die durch andere Mittel nicht verfügbar sind. Dies ist eine hervorragende Möglichkeit zur Wiederherstellung in Situationen, in denen wir denken: "Ups, das hätte ich besser nicht machen sollen."

Der Zweck dieser Übung war, zu lernen, wie du in jedem erdenklichen Entwicklungsszenario genau den Commit auswählst, den du benötigst. Diese Kenntnisse kannst du leicht mit deinem bereits vorhandenen Wissen zu Git kombinieren, da die gebräuchlichsten Befehle Refs als Argument akzeptieren. Hierzu zählen git log, git show, git checkout, git reset, git revert, git rebase und noch viele andere Befehle.


Diesen Artikel teilen
Nächstes Thema

Lesenswert

Füge diese Ressourcen deinen Lesezeichen hinzu, um mehr über DevOps-Teams und fortlaufende Updates zu DevOps bei Atlassian zu erfahren.

Mitarbeiter arbeiten mit unzähligen Tools zusammen

Bitbucket-Blog

Abbildung: DevOps

DevOps-Lernpfad

Demo Den: Feature-Demos mit Atlassian-Experten

So funktioniert Bitbucket Cloud mit Atlassian Open DevOps

Melde dich für unseren DevOps-Newsletter an

Thank you for signing up