8 Einführung in Git

Git ist ein verteiltes Versionskontrollsystem (VCS), das es Entwicklern ermöglicht, den Quellcode eines Projekts über die Zeit hinweg nachzuvollziehen, zu verändern und zu verwalten. Ursprünglich von Linus Torvalds entwickelt, um den Entwicklungsprozess des Linux-Kernels zu unterstützen, hat sich Git zu einem Standard-Tool in der Softwareentwicklung entwickelt.

Git unterscheidet sich von zentralisierten VCS wie Subversion (SVN), da es vollständig dezentral arbeitet. Jeder Entwickler hat eine vollständige Kopie des gesamten Repositorys, einschließlich der Historie. Dies macht Git sehr robust gegenüber Netzwerkproblemen und ermöglicht paralleles Arbeiten ohne zentrale Abhängigkeiten.

8.1 Kernprinzipien von Git

8.1.1 Snapshots statt Delta-basierte Versionierung

Im Gegensatz zu vielen älteren VCS, die nur Änderungen (Deltas) speichern, basiert Git auf einem Snapshot-Modell. Jeder Zustand des Repositorys wird als vollständiger Snapshot der Dateien gespeichert. Git optimiert dies jedoch durch einen Mechanismus, der unveränderte Dateien nicht erneut speichert, sondern auf die existierenden Daten verweist.

Wenn ein Entwickler eine Änderung committet, erstellt Git eine Momentaufnahme (Snapshot) des aktuellen Projektzustands. Jede dieser Momentaufnahmen wird durch einen eindeutigen Hash identifiziert, der auf dem Inhalt der Dateien, dem Commit-Metadaten und dem Eltern-Commit basiert. Dies bedeutet, dass zwei identische Dateien immer denselben Hash erhalten, unabhängig von ihrem Speicherort im Repository.

8.1.2 Hash-basierte Identifikation von Objekten

Git verwendet kryptografische SHA-1-Hashes zur Identifizierung von Objekten im Repository. Jeder Commit, jeder Baum und jede Datei wird durch einen eindeutigen Hash repräsentiert. Dieser Hash garantiert die Integrität der Daten, da jede Änderung sofort zu einem neuen Hash führt. Folgende Objekttypen werden in Git gespeichert:

8.1.3 Vergleich mit Merkle Trees

Die Art und Weise, wie Git Objekte hasht und diese Beziehungen verwaltet, ähnelt einem Merkle Tree. Bei einem Merkle Tree handelt es sich um eine baumartige Datenstruktur, bei der jeder Knoten durch den Hash seiner Kinderknoten bestimmt wird. Eine Änderung in einem Teil des Baumes führt dazu, dass sich der Hash des gesamten Baumes ändert. Dies stellt sicher, dass jede Modifikation in Git sofort erkennbar ist und die Datenintegrität jederzeit gewahrt bleibt.

8.1.4 Verzeichnisbaumstruktur

Ein Git-Repository besteht aus einer Verzeichnisbaumstruktur, in der die Dateien des Projekts organisiert sind. Der oberste Tree ist der Wurzelbaum, der in weiteren Trees und Blobs strukturiert ist. Diese hierarchische Struktur erlaubt es Git, Verzeichnisse effizient darzustellen und die Änderungen zwischen Commits präzise zu verfolgen.

Ein wichtiger Aspekt dieser Baumstruktur ist, dass sie nicht nur die Dateien im Projekt darstellt, sondern auch die Verbindungen zwischen verschiedenen Versionen. Jeder Commit verweist auf einen Baum und ermöglicht dadurch die Rekonstruktion des Projekts zu einem bestimmten Zeitpunkt.

8.1.5 Commits und Historie

Commits sind das zentrale Element in Git. Ein Commit stellt eine abgeschlossene Einheit dar, die den Zustand des Projekts zu einem bestimmten Zeitpunkt beschreibt. Jeder Commit enthält die folgenden Informationen:

Git verwendet diese Commit-Historie, um eine lineare oder verzweigte Zeitachse des Projektfortschritts zu erstellen. Durch die kryptografischen Hashes ist jeder Commit eindeutig und kann nicht nachträglich verändert werden, ohne dass dies in der Historie sichtbar wird.

8.1.6 Branches als Labels

Ein Branch in Git ist nichts weiter als ein Label, das auf einen spezifischen Commit verweist. Dieser Commit stellt den aktuellen Stand der Arbeit auf diesem Branch dar. Branches sind in Git extrem leichtgewichtig, da sie nur den Hash des letzten Commits speichern.

Ein Branch kann jederzeit verschoben werden, indem der Referenz-Commit geändert wird. Dies unterscheidet Branches in Git von traditionellen VCS, wo Branches oft schwergewichtiger und komplexer zu handhaben sind.

8.1.7 HEAD Datei und Branches

Die HEAD-Datei in Git spielt eine zentrale Rolle. Sie zeigt immer auf den aktuell ausgecheckten Branch oder Commit. Wenn du auf einem Branch arbeitest, enthält die Datei einen Pfad zu refs/heads/branch_name, der wiederum den Hash des obersten Commits dieses Branches enthält.

Wenn du direkt auf einen Commit arbeitest, zeigt HEAD auf den Hash dieses Commits, was als “detached HEAD” bezeichnet wird.

$ cat .git/HEAD
ref: refs/heads/main

Dieser Eintrag zeigt, dass der aktuelle Branch main ist und dass die Datei refs/heads/main den Hash des obersten Commits enthält.

8.1.8 Staging Area

Die Staging Area, auch als Index bekannt, ist eine Art Zwischenspeicher, in dem Änderungen vor dem Commit gesammelt werden. Sie ermöglicht es Entwicklern, gezielt einzelne Änderungen zum nächsten Commit hinzuzufügen, anstatt alle Modifikationen im Arbeitsverzeichnis sofort zu committen. Nur Änderungen, die in die Staging Area übernommen wurden, werden in den nächsten Commit aufgenommen.

Die Staging Area erlaubt eine feingranulare Kontrolle über den Commit-Prozess und bietet Entwicklern die Möglichkeit, ihre Arbeitsschritte besser zu strukturieren.

8.1.9 Lokale und Remote-Repositories

Git unterscheidet zwischen lokalen und Remote-Repositories. Ein lokales Repository befindet sich auf dem Rechner des Entwicklers, während ein Remote-Repository auf einem Server liegt und von mehreren Entwicklern gemeinsam genutzt werden kann. Das zentrale Konzept in Git ist jedoch, dass das lokale Repository vollständig unabhängig vom Remote-Repository ist. Änderungen werden lokal erfasst und können später synchronisiert werden.

Die Kommunikation zwischen lokalem und Remote-Repository erfolgt typischerweise über Befehle wie git fetch, git pull und git push. Diese Befehle ermöglichen es, Änderungen aus dem Remote-Repository abzurufen oder lokale Änderungen hochzuladen.