© Ozz Design/Shutterstock.com
Grundlegender Git-Guide für alle

Git: Basics und Best Practices


Egal, ob ihr euer Wissen auffrischen wollt, bisher noch nicht so viel mit Git zu tun hattet oder Neulinge in der Softwareentwicklung seid – dieser Artikel ist für euch!

In den drei folgenden Abschnitten geht es um die grundlegenden Aspekte des bekannten Versionskontrollsystems Git, wertvolle Best Practices und typische Schwierigkeiten.

Basics

Im ersten Abschnitt möchte ich auf die Basics, also die Grundlagen von Git, zu sprechen kommen. Dabei gehe ich sowohl auf die grundlegenden Konzepte als auch auf die wichtigsten Befehle ein, sodass ihr einen guten Überblick über das Versionskontrollsystem gewinnt.

Versionskontrolle

Bevor wir zu Git selbst kommen, sollte zuerst der Begriff der Versionskontrolle geklärt werden. Denn das ist das Konzept, das hinter allen Tools wie Git steht. Am besten lässt sich die Idee der Versionskontrolle mit der Karikatur in Abbildung 1 erklären.

rosenberg_gitbasics_1.tif_fmt1.jpgAbb. 1: Eine Welt ohne Versionskontrolle [1]

Die eher lästigen Versionsstände einer Datei in Form von mehreren einzelnen Dateien sind mit dem Einsatz von Versionskontrolle nicht mehr nötig. Mit aktiver Versionskontrolle kann nämlich jede Veränderung einer Datei erkannt werden, die dann zu einem gewünschten Zeitpunkt als neue Version gespeichert wird. Stellt euch diese Version auf einer zusätzlichen Ebene vor, die neben der Dateiebene existiert und an sich nichts mit dem Speichern der Datei zu tun hat. Diese Version reiht sich chronologisch zu den anderen Versionen der Datei ein. So entsteht eine Historie, deren einzelne Versionen bei Bedarf wiederhergestellt werden können, ohne dass spätere oder vorherige Versionen verloren gehen.

Versionskontrolle geht aber noch weiter als eben beschrieben: Sie überwacht nämlich alle Veränderung innerhalb eines ganzen Ordners, in der sie aktiv geschaltet ist. In dem Fall spricht man dann von einem Repository – dazu aber später noch mehr. Sollte sich in einer oder mehreren Dateien des Ordners nun etwas ändern, bekommt die Versionskontrolle das mit und ermöglicht dem Benutzer, diese Veränderungen als neue Version für den ganzen Ordner zu speichern. Das Wiederherstellen einer früheren Version des Ordners führt dazu, dass dessen gesamter Inhalt zurückgesetzt wird. Darüber hinaus ist es beispielsweise auch möglich, die Versionen miteinander zu vergleichen.

Besonders eignet sich Versionskontrolle für zeilenbasierte Textformate, also Dateitypen wie beispielsweise *.txt oder *.py. Unterschiede zwischen zwei Versionen für diese Dateitypen können nämlich an der Granularität einzelner Zeichen aufgezeigt werden. Binäre Dateitypen wie *.jpg und *.docx können zwar auch unter Versionskontrolle stehen, doch ist es hier nicht möglich, konkrete Veränderungen zwischen zwei Versionen detailliert darzustellen.

Das Versionskontrollsystem Git

Bei Git handelt es sich um ein quelloffenes verteiltes Versionskontrollsystem. Es wurde im Jahr 2005 von Linus Torvalds initiiert und hatte zum Ziel, möglichst sicher und effizient zu arbeiten. Veröffentlicht wurde Git unter der freien GNU-GPLv2-Lizenz. Das Tool eignet sich sowohl für kleine als auch große Projekte jeglicher Programmiersprachen. Es muss sich nicht einmal um ein Softwareprojekt handeln. Beispielsweise kann es auch hervorragend für die Versionskontrolle von wissenschaftlichen Arbeiten genutzt werden. Denn: Git bietet zwar eine ganze Reihe von Funktionalitäten, die über Befehle auf der Kommandozeile aufgerufen werden, doch wird für den täglichen Gebrauch nur eine Handvoll dieser Befehle benötigt. Damit ist die Bedienung auch für kleine Projekte unkompliziert und der Einstieg selbst für Nichtprogrammierer relativ einfach.

Neben Git gibt es auch einige andere Versionskontrollsysteme, die in manchen Aspekten anders funktionieren. Beispielsweise wird bei vielen anderen Tools nur das Delta zwischen zwei Versionsständen gespeichert. Git hingegen speichert den aktuellen Zustand eines Ordners als Snapshot ab: Wenn sich eine Datei verändert hat, wird diese in dem neuen Snapshot gespeichert. Hat sich eine Datei nicht verändert, wird stattdessen eine Referenz zu der Datei des Snapshots gespeichert, in dem sich die Datei zuletzt geändert hatte.

Ein weiterer Aspekt, der Git von einigen anderen Versionskontrollsystemen unterscheidet, ist, dass es sich dabei um ein verteiltes System handelt. Es existiert nicht nur ein zentrales, meist online befindliches Repository für ein Projekt, sondern häufig auch eine lokale eigenständige Kopie davon, mit der dann in der Praxis gearbeitet wird. Die Änderungen an der lokalen Kopie können dann zu einem gewünschten Zeitpunkt mit dem zentralen Repository synchronisiert werden.

Installation und Konfiguration

Wollt ihr Git nutzen, ist der erste Schritt die Installation des Tools. Über die Webseite von Git [2] könnt ihr die Software herunterladen. Die Standardeinstellungen während der Installation können ruhig beibehalten werden. Wenn ihr in eurem Terminal (bei den Standardeinstellungen ist das MinTTY) die installierte Git-Version mit dem folgenden Befehl erhaltet, habt ihr Git erfolgreich installiert.

git –version git version 2.28.0.windows.1

Wie viele andere Tools besitzt auch Git eine Konfigurationsdatei, in der Einstellungen vorgenommen werden können. Wenn ihr im Firmennetzwerk arbeitet, kann es beispielsweise nötig sein, dort Informationen zum Firmen-Proxy zu hinterlegen; auch euer Name und eure E-Mail-Adresse werden in der Datei hinterlegt. Sie wird automatisch erzeugt, sobald ihr euren ersten Snapshot erstellt, denn dafür müssen euer Name und die entsprechende E-Mail-Adresse hinzugefügt werden. Ihr könnt diese Informationen alternativ auch manuell über die Kommandozeile folgendermaßen vornehmen:

git config --global user.name "John Doe" git config --global user.email "john.doe@example.com"

Nutzt ihr Windows, sollte sich die Konfigurationsdatei unter C:\Users\<user>\.gitconfig befinden. Bei dem Inhalt dieser Konfigurationsdatei handelt es sich um globale Einstellungen. Ergänzend kann eine projektspezifische Konfigurationsdatei verwendet werden, um Einstellungen vorzunehmen, die nur für ein bestimmtes Projekt gelten sollen. Die dort befindlichen Einträge werden von Git stets vorrangig gewählt. Die projektspezifische Konfigurationsdatei befindet sich im versteckten .git-Ordner innerhalb des jeweiligen Repositorys.

Repository

Bei einem Repository handelt es sich um das Verzeichnis, das unter Versionskontrolle steht. Bei einem Git-Repository befindet sich darin der bereits erwähnte versteckte .git-Ordner, in dem alle Git-internen Informationen zum Repository enthalten sind.

Ein Repository kann sowohl lokal als auch remote (z. B. online) existieren. Ein lokales Repository muss nicht zwingend remote vorliegen, und ebenso wenig muss ein remote gespeichertes Repository lokal existieren. Beide Repositorys sind grundsätzlich unabhängig voneinander, können jedoch bei Bedarf zusammengeführt werden. Vermutlich werdet ihr in den meisten Fällen ein zentrales Repository remote vorliegen haben und auf einer lokalen Kopie dieses Repositorys arbeiten. Beide werdet ihr regelmäßig synchronisieren, um lokal auf dem neuesten Stand zu arbeiten und remote eure Änderungen bereitzustellen. Abbildung 2 zeigt diesen Workflow.

rosenberg_gitbasics_2.tif_fmt1.jpgAbb. 2: Synchronisation zwischen remote und lokalem Repository

Ein Remote-Repository wird über eine Webanwendung zur Verfügung gestellt. Es gibt dabei sowohl Cloud- als auch selbst gehostete Varianten. Bekannte Vertreter sind GitHub, GitLab und Bitbucket, ebenfalls in Abbildung 2 zu sehen. Neben dem eigentlichen Hosten des Repositorys bieten diese Webanwendungen nützliche Funktionalitäten wie Benutzerverwaltung, Issue Tracking, Wiki, Third-Party-Zugriff und Pull Requests. Auf das letzte Feature kommen wir im Abschnitt „Best Practices“ noch zurück.

Lokal werden Remote-Repositorys dann als sogenannte Remotes verwaltet. Es ist auch möglich, mehrere Remote-Repositorys für ein lokales Repository zu haben. Die zugehörigen Remotes müssen nur unterschiedliche Bezeichnungen haben. Standardmäßig wird der erste eingerichtete Remote eines Projekts „origin“ genannt. Wenn ein bestimmtes Remote-Repository angesprochen werden soll, muss also im Git-Befehl der entsprechende Remote mitgegeben werden.

Commit

Durch einen Commit wird ein neuer Snapshot erstellt. Einfach gesagt verhält sich ein Commit wie ein Containerobjekt, in dem der Snapshot zusammen mit den folgenden Informationen gespeichert wird:

  • SHA: der 40-stellige Commit Hash, der als eindeutige ID fungiert und aus allen nachstehenden Werten gebildet wird

  • Tree: der 40-stellige Snapshot Hash (oft fünf- oder siebenstellig dargestellt)

  • Parent: der SHA des vorherigen Commits

  • Author: Name und E-Mail-Adresse inkl. Zeitstempel des Benutzers, der den Inhalt des Commits erstellt hat

  • Committer: Name und E-Mail-Adresse inkl. Zeitstempel des Benutzers, der den Commit erstellt hat; meist sind diese Informationen identisch mit denen von Author

  • Message: Beschreibung des Commits, bestehend aus zwei Teilen: einem Titel und einer Beschreibung, die durch eine Leerzeile voneinander getrennt werden

Aufgrund dieser Informationen entsteht eine revisionssichere Commit-Historie. Würde ein Commit verändert, wäre die Historie nicht mehr valide, da die SHA-Werte nicht mehr schlüssig sein können.

In Abbildung 3 wird das Zusammenspiel zwischen Commits und Snapshots gezeigt. Im zweiten Commit hat sich in der Datei a etwas verändert, weshalb sie und der dazugehörige Ordner im neuen Commit gespeichert werden und die neue Version 2 haben. In allen anderen Dateien gab es keine Veränderungen, weshalb auch keine neue Version gespeichert wurde. Stattdessen werden diese Dateien und Ordner nur referenziert.

rosenberg_gitbasics_3.tif_fmt1.jpgAbb. 3: Speicherung von Daten in Form von Snapshots

Branch

Ein wesentliches Feature von Git ist die Möglichkeit, die Commit-Historie mit Hilfe von Branches zu verzweigen. Dadurch wird zum einen die Teamarbeit erleichtert, da mehrere Personen unabhängig voneinander an einer Teilaufgabe arbeiten können, zum anderen ist es mit Branches möglich, die Software in unterschiedliche Entwicklungsstände zu organisieren.

Git erstellt zu Beginn automatisch einen initialen Branch, von dem alle zukünftigen Branches abgehen. Standardmäßig wird dieser Branch master genannt. Die Bezeichnung aller abgehenden Branches ist bei deren Erstellung wählbar. Seit der Git-Version 2.28 kann der verwendete Standardname für Haupt-Branches aber auch beispielsweise zu main geändert werden. Das lässt sich wie folgt durchführen:

git config --global init.defaultBranch main

Branches können zu jeder Zeit durch einen Merge miteinander vereint werden, sodass die Änderungen von einem Branch auf einen anderen (meist auf den hierarchisch höheren) Branch gebracht werden. Git unterscheidet zwischen zwei Merge-Typen: Fast-Forward und Three-Way. Bei einem Fast-Forward Merge können die Commits von Branch a einfach an Branch b angehängt werden, da auf Branch b zwischenzeitlich keine anderen Commits passiert sind. Andernfalls muss ein Three-Way Merge durchgeführt werden. Hierbei werden die beiden Branches mit Hilfe eines zusätzlichen Merge Commits verknüpft. Dieser Merge Commit besitzt dadurch zwei Parents, nämlich einmal den vorherigen Commit auf Branch b und zusätzlich den letzten Commit von Branch a. Auf diese Weise bleibt die Commit-Historie intakt.

In Abbildung 4 sind die bisher beschriebenen Aspekte dargestellt. Es sind in dem Beispiel drei Branches zu sehen, der Haupt-Branch und zwei davon abgehende Branches. Die darauf befindlichen Commits sind als Punkte angedeutet und chronologisch sortiert; der neueste Commit befindet sich ganz rechts. Drei Merges sind im Beispiel dargestellt. Der erste und der letzte Merge verlaufen zum Haupt-Branch hin. Bei diesen beiden handelt es sich um Fast-Forward Merges, die (anders als dargestellt) eigentlich keinen Merge Commit erzeugen. Sie werden nur zur besseren Lesbarkeit eingezeichnet. Der zweite Merge hingegen ist ein Three-Way Merge und geht vom Haupt-Branch aus zum unteren der beiden abgehenden Branches. So ein Merge kann sinnvoll sein, wenn man den stabilen neuesten Entwicklungsstand für die aktuelle Aufgabe auf einem anderen Branch benötigt.

rosenberg_gitbasics_4.tif_fmt1.jpgAbb. 4: Branches, Commits und Merges

Objects und References

Im Grunde genommen ist Git einfach nur ein Key-Value-Speicher, der an das Dateisystem von UNIX angelehnt ist. Der Value ist dabei der Inhalt, also die zu speichernden Daten (genauer: Git Objects), der Key ist ein Hashwert, der anhand der Daten automatisch erzeugt wird. Zum Hashen wird SHA1 genutzt – eine kryptografische Hashfunktion, die anhand eines Inputs eine 40-stellige hexadezimale Nummer erstellt. Genau diese Hashwerte haben wir schon bei den Commits gesehen. Commits sind nämlich Git-Objekte und haben daher einen Hash-Wert als Key. Neben Commits gibt es noch drei weitere Objekttypen: Blobs, Trees und Tags. Die Architektur von Git wird in Abbildung 5 in Form eines Klassendiagramms beschrieben.

rosenberg_gitbasics_5.tif_fmt1.jpgAbb. 5: Klassendiagramm über die internen Git-Objekte (basiert auf einem Diagramm von Susan Potter [3])

Blobs ähneln einer Datei. Durch mode wird spezifiziert, ob es sich um eine normale Datei, eine Anwendung oder einen symbolischen Link handelt. Tree-Objekte entsprechen Ordnern und können ein oder mehrere Blob- und/oder Tree-Objekte referenzieren. In einem Commit-Objekt werden Trees und Blobs zu einem Snapshot zusammengeführt. Tags sind Pointer-Objekte, die auf ein bestimmtes Commit-Objekt zeigen. Sie können genutzt werden, um einem Commit eine sprechende Bezeichnung (z. B. Versionsnummer) zu geben, die sich später leichter wiederfinden lässt. Eine beispielhafte Situation, bei der einige der beschriebenen Git-Objekte miteinander in Relation treten, wird in Abbildung 6 visualisiert. Zwei Dateien liegen im Ordner bak, der zusammen mit einer weiteren Datei im Root-Verzeichnis des Repositorys liegt. Das Verzeichnis wird mit einem Commit-Objekt verbunden.

rosenberg_gitbasics_6.tif_fmt1.jpgAbb. 6: Git-Objekte in einer beispielhaften Situation (basiert auf einem Diagramm aus dem Buch Pro Git [4])

Neben den beschriebenen Git-Objekten gibt es noch Git References – Dateien, in denen einfach nur ein Commit Hash liegt. Mit ihnen werden intern besondere Commits referenziert. Sie befinden sich innerhalb des Repository-Ordners unter .git\refs. Git-Referenzen sind Branches, Remotes, Tags und der HEAD. Tags referenzieren einen bestimmten User-definierten Commit. In einer Branch-Referenz wird der neueste Commit eines Branch referenziert. Alle älteren Commits des Branch werden dabei anhand des referenzierten Commits zurückverfolgt. Bei den Remote-Referenzen verhält es sich sehr ähnlich – für jeden Branch eines Remotes existiert eine Datei, in der der lokal neueste bekannte Commit referenziert ist. Hier ist es wichtig zu wissen, dass auf dem tatsächlichen Remote-Branch bereits neuere Commits existieren können, die Git lokal jedoch noch nicht kennt. Das liegt daran, dass Git nicht automatisch nach Aktualisierungen Ausschau hält.

Zuletzt gibt es dann noch den HEAD, einen Pointer auf den aktuell ausgecheckten (sichtbaren) Commit. Das ist also genau der Commit, dessen Snapshot gerade geladen ist. Es gibt sowohl eine HEAD-Datei für das lokale Repository als auch für das Remote-Repository. Abbildung 7 zeigt eine beispielhafte Situation, in der mehrere Git-Referenzen auf bestimmte Commits zeigen. Bei Remote-Referenzen wird der Name des Remotes immer mit angegeben. In der beschriebenen Situation ist der lokale Branch develop mit dem Commit 1a410e ausgecheckt und darüber hinaus als Version v.1.1 getaggt. Im Remote-Repository gibt es den Branch develop entweder noch nicht oder Git hat ihn noch nicht im lokalen Remote origin vorliegen. Sowohl der lokale als auch der remote liegende Branch master zeigen auf den Commit cac0ca. Nach aktuellem Stand zeigt der HEAD des Remote-Repositorys auf den Commit cac0ca. Aber auch hier gilt: Die Informationen des Remotes für den Branch origin/master und origin/HEAD müssen nicht aktuell sein.

rosenberg_gitbasics_7.tif_fmt1.jpgAbb. 7: Git-Referenzen in einer beispielhaften Situation (basiert auf einem Diagramm aus [4])

Wichtige Befehle und Stages

Wie bereits erwähnt, umfasst Git eine Vielzahl von Befehlen, von denen aber glücklicherweise nur wenige für den täglichen Gebrauch benötigt werden. Auf diese Befehle möchte ich in diesem Abschnitt eingehen.

Im ersten Schritt werdet ihr vermutlich entweder ein neues Repository erstellen oder ein vorhandenes als lokale Kopie einrichten wollen. Um ein neues Repository z. B. mit dem Namen foo zu erstellen, müsst ihr lediglich den folgenden Befehl anwenden:

git init foo Initialized empty Git repository in C:/Users/lisar/Projects/foo/.git/

In dem aktuellen Ordner wird ein neuer Ordner foo erstellt, in dem sich nun das gleichnamige Repository befindet. Wenn ihr keinen Namen im Befehl spezifiziert, versucht Git im aktuellen Ordner ein Repository direkt zu initialisieren.

Um eine Kopie eines Remote-Repositorys zu erhalten, müsst ihr es klonen. Hierfür gibt es einen anderen Befehl, mit dem an aktueller Stelle ein Ordner erstellt wird, der den Namen des Remote-Repositorys erhält (Listing 1). Falls ihr eine andere Bezeichnung für die lokale Kopie verwenden wollt, fügt sie einfach hinter dem URL hinzu. Beim URL werden zwei verschiedene Protokolle unterstützt: HTTPS und SSH, auf die ich im Abschnitt „Common Problems“ noch einmal genauer zurückkommen werde.

Listing 1: Ein existierendes Repository klonen

git clone https://github.com/lisa-rosenberg/bar.git Cloning into 'bar'... remote: Enumerating objects: 531, done. remote: Total 531 (delta 0), reused 0 (delta 0), pack-reused 531 Receiving objects: 100% (531/531), 43.24 MiB | 16.40 MiB/s, done. Resolving del...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Gewinner-Teams

Wir bieten Lizenz-Lösungen für Teams jeder Größe: Finden Sie heraus, welche Lösung am besten zu Ihnen passt.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang