Git

Aus Das Sopra Wiki


Git ist ein Versionsverwaltungssystem. Es wird verwendet um

  • Änderungen am Projekt zu protokollieren und dieses zu archivieren.
  • Ältere Versionen wiederherzustellen.
  • Gemeinsames Arbeiten auf der selben Datenbasis zu ermöglichen.

Wir verwenden Git im Sopra um genau diese Punkte zu ermöglichen. Jede Gruppe erhält ihr eigenes Repository auf Gitea (einer Plattform ähnlich zu GitHub), um ihr Projekt zu verwalten.

Git in einer Nussschale

Um mit Git arbeiten zu können, ist es wichtig die Prinzipielle Arbeitsweise von Git zu verstehen. Hat man die technische Umsetzung im Hinterkopf, werden die Befehle und Arbeitsweisen um git zu bedienen klarer.

Git protokolliert und verwaltet ein Dateiverzeichnis und alle Änderungen die an den verwalteten Dateien gemacht werden. Jede Änderung produziert dabei einen neuen "Schnappschuß" (Commit) - den aktuellen Zustand des Verzeichnisses und der Dateien denen ein HASH (SHA-1) zugeordnet wird. Jeder Schnappschuss kennt seine Vorgänger, so dass mit der Zeit ein großer gerichteter azyklischer Graph entsteht. Git kümmert sich darum, dass dies speichereffizient abläuft und verwendet dazu ein speziellen Verzeichniss das .git heißt und in dem verwalteten Dateiverzeichnis liegt. Dieses die gesammte Historie beinhaltende .git Verzeichnis ist ein Git Repository. Meistens wird aber das verwaltete Verzeichnis synonym als Repository bezeichnet, was wir ab jetzt auch tun. Ein einfaches Repository mit nur einer Datei "README.md" sieht demnach so aus:

.
├── .git
└── README.md

Die gesammte Historie des Repository ist also (meistens) lokal vorhanden. Um mit mehreren Personen an dem Repository zu arbeiten, müssen die Teilnehmer es mit einem "remote" Repository synchronisieren.

Git arbeitet mit 3 Zuständen. Jede versionierte Datei kann in einem der Zustände sein wobei es nicht sein muss, dass zu einem Zeitpunkt alle Dateien den gleichen Zustand haben. Die 3 Zustände sind:

  • committed -> Die Datei ist so wie sie ist im repository gespeichert.
  • modified -> Die Datei ist zum letzten gespeicherten Zustand verändert.
  • staged -> Die Datei (vorher im "Modified" Zustand oder eine neue Datei) wurde von dem git Benutzer markiert, sodass die Änderungen gespeichert werden sollen.

Daneben gibt es auch noch `untracked files` dies sind Dateien, die noch nicht in die Versionskontrolle aufgenommen wurden.

Zentrale Begriffe

Commit

Ein Commit repräsentiert einen Schnappschuss des Reposity und impliziert eine Menge an Änderungen an einer Datei (oder mehreren Dateien). Jedes mal wenn der git commit Befehl ausgeführt wird, speichert Git den aktuellen Zustand zusammen mit zusätzlichen Informationen (Author, Nachricht.. ) in dem Commit und weist ihm eine eindeutige ID (dem SHA-1 Hash) zu. Eine Serie an Commits erzeugt eine verkettete Liste an Commits wobei ein Commit immer seinen Vorgänger kennt. Benutzt man auch Branches im Repository, entsteht so ein gerichteter Graph.

Branch

Ein Branch ist ein unabhängiger Abzweig des Repository. Ausgehend von einem Commit kann mittels des git branch befehls ein neuer Branch erzeugt werden. Ein branch funktioniert wie ein eigenes Repository mit der Besonderheit, dass der Branch ausgehend von einem Commit (Schnappschuss) des Repository erstellt wurde - technisch ist ein Branch einfach ein Pointer auf einen Commit - und mit anderen Branches des Repository wieder vereinigt werden kann (Merge).

Master-Branch

Der Master-Branch ist der erste Branch der standardmäßig von Git erzeugt wird. Lässt man alle Branch Befehle unberührt, spielt sich also jede Änderung am Repository nur auf dem Master-Branch ab.

Merge

Ein Merge ist das einfügen von Änderungen aus einem fremden (Source-) Branch in den Aktuellen (Target-) Branch.

Merge-Konflikt

Ein Merge-Konflikt entsteht wenn im Source- und Target-Branch Änderungen an der selben Datei vorhanden sind, die nicht von git aufgelöst werden können und manuelles beseitigen des Konfliktes erfordern.

Clone

Ein Clone ist eine Kopie eines schon bestehenden Git Repository. Dabei möchte man meistens die Quelle als Remote behalten um Änderungen mit dieser Synchronisieren zu können.

Remote

Remote ist die Bezeichnung für ein Repository, das zur synchronisierung verwendet wird. Meistens ist dies auf einem externen Server und benötigt eine Authentifizierungsmethode um von diesem Änderungen zu holen (fetch) oder Änderungen hochzuladen (push). Im SOPRA verwendet jede Gruppe dazu ein eigenes auf SOPRA Gitea gehostetes repository.

Arbeiten mit Git

Wir beschreiben hier kurz und sehr abstrakt die einzelnen Funktionen, die ein Git-Client generell zur Verfügung stellt. Wie die Befehle in den einzelnen GUI Programmen implementiert sind ist unterschiedlich.

Name und Email einstellen

Jeder commit wird mit Namen und Email versehen. Diese muss man (entweder per repository oder global) einstellen.

git config --global user.email "jane.doe@example.com"
git config --global user.name "Jane Doe"

Repository erstellen

Um einen Ordner unter Git Versionskontrolle zu stellen fürt man in dem ordner den init Befehl aus:

git init

Git antwortet darauf mit

Initialized empty Git repository in <pfad zum ordner>/.git/

Repository clonen

Um ein bestehendes Repository in einen Ordner zu Clonen benutzt man den clone Befehl:

git clone https://sopragit.informatik.uni-freiburg.de/<semester>/<gruppe>/<repository>.git

Bzw. für die Authentifizierung mit SSH ist der äquivalente Befehl

git clone gitea@sopragit.informatik.uni-freiburg.de:<gruppe>/<repository>.git

Am Repository Arbeiten

Remote Änderungen synchronisieren

Eigene Änderungen hinzufügen

Änderungen Rückgängig machen

Dateien Ignorieren

Man kann in Git mittels einer .gitignore Datei andere Dateien ignorieren, d.h. sie explizit nicht unter Versionskontrolle stellen. Das ist insbesondere für temporäre Dateien (die z.B. bei jedem Build neu erzeugt werden) oder Benutzer-spezifische Einstellungen sinnvoll und muss von jedem verwendet werden. Die folgenden Dateien und Verzeichnisse müssen auf jedenfall auf die Ignore-Liste und dürfen nicht mit eingecheckt werden:

Warum?
binDie Dateien in diesem Ordner werden automatisch beim Kompillieren erstellt, sie hochzuladen ist also unnötig. Zudem sind auch binäre Dateien dabei die sich beim kompilieren häufig ändern. Da binäre Dateien nicht gemerged werden können wird das zu häufigen Konflikten mit den Commits anderer Teammitglieder führen.
objSiehe bin.
_ReSharper.#PROJEKTNAME#Dieses Verzeichnis wird vom Resharper automatisch generiert. Automatisch generierte Ordner sollen nicht ins SVN, siehe bin.
*.suoDie Dateien mit der Endung .suo (Solution User Options) beinhalten eine Reihe von Benutzer-spezifischen Einstellungen für Visual Studio, die niemanden außer den Benutzer selber interessieren. Sie enthalten außerdem eine Reihe von absoluten Pfadangaben, die bei anderen Benutzern massive Probleme auslösen können. Sie sollen auf keinen Fall zum Repository hinzugefügt werden.
*.cachefileSiehe bin. Auch merkt sich MonoGame hier, welche Dateien es schon in ein ihm genehmes Format konvertiert hat. Wenn ein anderes Teammitglied eine neue Datei hinzufügt und diese Datei im .cachefile als bereits konvertiert markiert ist, kann es passieren daß sich XNA denkt "Hey die Datei hab ich doch schon" und sie nicht neu konvertiert.
*.DotSettings.userDiese Datei enthält Benutzerspezifische Resharper Einstellungen und soll entsprechend auch nicht eingecheckt werden.
*.thumbVon Windows generierte Datei, die Vorschaubilder für die Miniaturansicht im Explorer enthält.
thumbs.dbSiehe .thumb
.vsVon Visual Studio generiertes Verzeichnis für interne Einstellungen.

Mit mehreren Branches arbeiten

Im SOPRA verwenden wir hauptsächlich 2 Branches:

  • master => Hier ist der aktuelle Stand des Projekts in Lauffähigem Zustand mit fertig implementierten Tasks. Dieser Branch ist Grundlage für die Bewertung des Spiels und muss zu jeder Zeit ein compilier und lauffähiges Spiel darstellen.
  • develop => Hier werden die Tasks entwickelt. Sie müssen nicht zwangsläufig fertig sein, aber der develop Branch soll zu jeder Zeit kompillieren und laufen.
  • feature/<task> => Feature branches können als Erweiterung des develop Branches gesehen werden. Hier wird ein einzelner Task implementiert bis er fertig ist und in den develop Branch gemerged wird.

Wichtige branch Befehle

git status                    # Zeigt neben dem aktuellen Zustand des Repos auch auf welchem Branch man gerade ist.
git branch -v                 # Zeigt verfügbare Branches mit aktuellem HEAD.
git checkout <branch_name>    # Wechselt den branch nach <branch_name>.
git branch <branch_name>      # Erstellt einen neuen Branch <branch_name>.
git checkout -b <branch_name> # Erstellt <branch_name> und wechselt in diesen falls er noch nicht existiert.
git branch -d <branch_name>   # Löscht den <branch_name> Branch.

Branch mergen

Möchte man einen feature Branch nach develop mergen, wechselt man zunächst in den develop branch

git checkout develop

Jetzt merged man den feature branch mit

git merge feature/<task>

Je nach Zustand des Repository gibt es nun mehrere Szenarios was passiert:

  • Fast-forward Merge Falls man den aktuellen HEAD des develop Branch durch einfaches zurücklaufen in der History des feature/<task> Branch erreichen kann, sind offensichtlich Konflikte ausgeschlossen und Git kann einfach den HEAD von develop auf den HEAD des feautre/<task> Branches zeigen lassen. Effektiv wird also alles was in feature/<brach> seit dem erstellen des feature/<brach> passiert ist auch in develop passieren.
  • Recursive merge Falls in der Zwischenzeit der develop Branch weiterentwickelt wurde, ist ein Fast-forward Merge nicht mehr möglich. Falls es aber keine Dateien gibt, die in jetzt unterschiedliche Inhalte haben, wird bei einem recursive merge ein neuer Commit erstellt, der die Vereinigung der Änderungen beider Branches darstellt. Git fordert den Nutzer in diesem Fall auf eine Commit Nachricht anzugeben. In der Regel Sollte diese dann lauten: "Merge feature/<task> Evtl zusätzliche info oder Zusammenfassung für neues feature."
  • Merge Konflikt Falls in beiden Branches Änderungen an gleichen Inhalten gemacht wurden, weiß Git nicht wie diese aufzulösen sind. Wie man Merge Konflikte löst ist in Konflikte lösen beschrieben.
  • Weitere möglichkeiten zu mergen werden hier beschrieben: https://git-scm.com/docs/merge-strategies

Konflikte lösen

History

Git Installieren

Es gibt unzählige Git Clients. Empfehlenswert ist vor allem für den Einstieg Der offizielle Git client. Wer gerne ein graphisches Interface hat kann tortoisegit oder einen der zahlreichen Alternativen verwenden.