Das Versionieren und Speichern von Quellcode übernimmt heute meist Git. Dieser Beitrag führt in die ersten Schritte mit dem mächtigen Werkzeug ein.
Die Versionskontrolle von Code und Dokumenten war früher ein schwieriges und auch riskantes Unterfangen: Nutzer waren nie ganz sicher, ob sie bei einem Wechsel auf einen anderen Entwicklungszweig ihre letzten Änderungen behielten oder verloren. Nicht selten führte der Versuch, die Resultate stundenlanger Arbeit am Ende zusammenzuführen, zu unerwarteten Ergebnissen.
Bevor sich Git [1] als De-facto-Standard durchsetzte, war CVS [2] als Versionsverwaltungssystem am weitesten verbreitet. Bei CVS müssen alle Änderungen auf einen zentralen Server geladen werden, zu dem der Nutzer eine funktionierende Netzwerkverbindung braucht. Außerdem speichert CVS immer nur die einzelnen Änderungen, jedoch nie eine komplette Version des Codes. Auch das Branching beim Zusammenführen der Arbeit zweier Beteiligter kann zu Konfliktsituationen führen, deren Lösung viel Zeit verschlingen kann. Andere Systeme wie Subversion (SVN [3]) oder Mercurial (Hg [4]) brachten zwischenzeitlich zwar eine deutliche Verbesserung, doch am Ende funktionierte dann doch Git am besten und fand die höchste Akzeptanz bei den Anwendern.
Revolutionärer Ansatz
Wer seinen Quellcode, Konfigurationsdateien oder Dokumente einer Versionsverwaltung anvertraut, möchte sichergehen, dass die entsprechenden Werkzeuge reibungslos funktionieren. Git schafft das mit einer revolutionären, neuen Herangehensweise: Es nutzt einen dezentralen Ansatz und spiegelt das ganze Repository samt der kompletten Historie aller Dateien auf die Rechner aller Nutzer. Gleichzeitig legt es immer komplette Snapshots des gesamten Repositorys an.
Was nach Ressourcenverschwendung und Ineffizienz klingt, war das Ergebnis von Linus Torvalds Erfahrungen mit den Vorgängersystemen von Git, darunter CVS. Er wollte ein schnelles, einfaches und robustes System, das auch großen und verteilten Projekten wie dem Linux-Kernel gerecht werden sollte. Nach dem ersten Release im Jahr 2005 setzte sich Git rasch als De-facto-Standard für Versionsverwaltungssysteme durch. Das Ökosystem rund um das Werkzeug ist inzwischen riesig und hat mit Plattformen wie Github [5] und Gitlab [6] eigene neue Firmen und Geschäftsmodelle hervorgebracht.
Git und sein Ökosystem bringen aber auch einen Wust neuer Begriffe, Arbeitsabläufe und Konventionen mit sich. Egal, wo man Git begegnet, im Unternehmen oder in der Open-Source-Welt: Viele seiner Konzepte (wie »git merge«) sind schon mit Funktionen einer bestimmten Code-Hosting-Plattform vermischt, was den Einstieg zusätzlich erschweren kann. Dieser Artikel möchte deswegen einen Überblick über die Arbeitsprozesse in Git sowie auf den typischen Plattformen geben. Er setzt professionelle Kenntnisse im Umgang mit Linux und dem Internet voraus.
Wahrscheinlich administrieren Sie schon das ein oder andere System und begegnen in der Zusammenarbeit mit Entwicklern immer wieder Git und dem Hosting von Repositories. Möglicherweise hatten Sie aber noch nie die Möglichkeit, sich einmal in Ruhe damit auseinanderzusetzen, wie das Ganze eigentlich funktioniert. Dann ist der vorliegende Überblick über die Konzepte und Begriffe hinter Git genau die richtige Lektüre für Sie.
Ein Repository starten
Wenn Sie Ihren eigenen Code, Konfigurationsdateien oder Dokumente mit Git verwalten möchten, dann starten Sie am besten auf einer Code-Hosting-Plattform wie Github, Gitlab oder Bitbucket [7]. Vielleicht administrieren Sie ja sogar schon eine Version von Gitlab in Ihrem Unternehmen.
Das erste Ziel besteht darin, ein neues Repository anzulegen, das als Sicherungskopie Ihres Codes fungiert. Dazu erzeugen Sie auf der jeweiligen Plattform zunächst einen Account. Anschließend finden Sie in Ihrem Nutzerprofil eine Funktion, mit der sich ein neues Repository anlegen lässt. Sie heißt nicht immer gleich, findet sich aber häufig unter dem Stichwort New.
Als Repository bezeichnet man übrigens jede Kopie eines Git-Projekts, ein anderer Name wäre Clone. Das Ursprungs-Repository auf der Plattform, auf der es hauptsächlich gehostet wird, nennt man auch Origin. Üblicherweise finden Sie diese Origin-Repositories auf den Plattformen unter der URL https://github.com/<I>User<I>|<I>Organisation<I>/<I>Repository<I>.
Für ein solches Repository, das auf der Plattform statt auf dem Rechner des Nutzers liegt, ist auch der Begriff Remote gebräuchlich. Ein Git-Repository kann mehrere Remotes haben, die auf unterschiedlichen Rechnern liegen. Entsprechend gehört zu jedem Remote eine eigene URL, unter der man es findet. Jedes Remote wird in Git mit einem Alias angelegt; der Alias für das Ursprungs-Repository auf der Plattform lautet eben »origin«.
Das neue Repository braucht nun Inhalt – dafür müssen Sie ein paar Codedateien erzeugen. Danach können Sie damit beginnen, das Repository auf Ihren Rechner zu klonen. (Das Klonen funktioniert auch, wenn das zu klonende Verzeichnis noch leer ist, produziert dann aber eine Warnung.) Zu diesem Zweck suchen Sie einen großen Button, der oft die Aufschrift Code trägt – so heißt er zumindest bei Github und Gitlab. Dort finden Sie dann den Link zum Code des Repositorys, den Sie zum Klonen benötigen.
Bei diesen Links gibt es zwei Varianten: eine beginnt mit https://, eine mit http://git@. Erstere handhabt sich für den Anfang leichter, da sie keine SSH-Keys benötigt. Allerdings müssen Sie sich dann mit Ihrem Benutzernamen und einem Personal Access Token einloggen. Die http://git@-Variante eignet sich für diejenigen, die bereits SSH-Keys eingerichtet haben und die Vorteile von SSH nutzen möchten.
Das Personal Access Token funktioniert wie ein individuelles Passwort für den Zugriff auf Ihr Git-Repository. Es wird separat erstellt, damit Sie nicht dauerhaft Ihr Github-Standardpasswort verwenden müssen. Sowohl das Personal Access Token als auch Ihren Public Key richten Sie in Ihrem Account auf der jeweiligen Plattform ein.
Um ein Personal Access Token bei Github anzulegen, wechseln Sie in die Einstellungen Ihres Accounts und wählen dort die Developer Settings (ganz unten auf der Seite). Dort finden Sie dann die Möglichkeit, einen Personal Access Token mit unterschiedlichen Berechtigungen zu erstellen. Achten Sie dabei besonders darauf, volle Berechtigungen für den Zugriff auf Ihre Repositories zu erteilen. Bei Gitlab finden Sie diese Möglichkeit übrigens ebenfalls in den Account-Einstellungen, dort unter dem Punkt Access Tokens.
Kopieren Sie das generierte Token, und speichern Sie es an einem sicheren Ort. Es ersetzt zukünftig beim Zugriff auf Github über die Befehlszeile oder die API Ihr Passwort. Vermeiden Sie, das Token in öffentlichen Repos oder Gists zu verwenden, da es dann automatisch widerrufen wird.
Auf der Kommandozeile lässt sich ein Klon des Repositorys mit dem Befehl »git clone URL« auf den eigenen Rechner holen. Git erstellt in der Folge ein Verzeichnis mit dem Namen des Repositorys und kopiert alle Dateien und die Historie des Repositorys in diesen Ordner (Abbildung 1).
Stattdessen könnten Sie auch in einem lokalen Ordner beginnen und dort ein neues Repository erstellen. Dazu navigieren Sie in das Verzeichnis und führen dort den Befehl »git init .« aus. Git erzeugt dann ein neues Repository, das allerdings noch keine Historie enthält. Auch Ihre lokalen Dateien müssen Sie erst in das neue Repository integrieren – das sehen wir uns im nächsten Abschnitt genauer an. Sie fügen dann diesem neuen Repository mit dem Befehl »git remotes add Remote URL« Remotes hinzu. Auf diesem Weg lässt sich übrigens auch nachträglich ein »origin«-Remote ergänzen. Am einfachsten ist es jedoch, wenn Sie mit einem Klon starten.
In jedem Fall erkennen Sie ein neues Git-Repository an dem Verzeichnisnamen ».git«. Dieses Verzeichnis enthält alle Informationen, die Git benötigt, um das Repository zu verwalten, sowie die Historie des Repositorys und die Konfiguration von Git. Alle anderen Dateien, die Sie im Repository sehen, bilden die sogenannte Working Copy oder das Working Directory. Mit diesen Dateien können Sie arbeiten, Veränderung vornehmen und neue Dateien hinzufügen.
Es sind aber noch weitere Dinge geschehen, als Sie das Repository geklont haben: Ihr vorliegendes Working Directory ist der neueste Stand des Default-Branch des Repositorys, also des Hauptentwicklungszweigs des Repos. Branches ermöglichen die parallele Weiterentwicklung ausgehend von einem gemeinsamen Startpunkt. So lassen sich beispielsweise zwei neue Features parallel entwickeln, ohne dass sich während der Arbeit ständig unfertige Versionen gegenseitig in die Quere kommen. Erst wenn die Entwicklung der Features abgeschlossen ist, überführt der Anwender sie wieder in einen gemeinsamen Entwicklungszweig, meist eben den Default-Branch.
Diesen Default-Branch nennt man »main« oder »master«. Manchmal entscheiden sich Teams auch, die in Entwicklung befindliche Version als Default-Branch zu definieren; dann heißt sie häufig »develop«. Prinzipiell lassen sich diese Namen frei wählen. Sie konfigurieren sie am besten direkt in den Einstellungen Ihres Repositorys auf der Code-Hosting-Plattform.
Außerdem hat das Klonen des Origin-Repositorys dafür gesorgt, dass Ihr »HEAD« – ein Verweis (Pointer) auf den aktuell ausgecheckten Commit oder Branch – durch den neuesten Commit des Default-Branches definiert wurde. Für ein frisch von einer Plattform ausgechecktes Repository ist das normalerweise der einzige und erste existierende Commit, der eine Datei »README.md« angelegt hat.
Im Wesentlichen verweist der »HEAD« auf den letzten Snapshot des Projekts und repräsentiert den Zustand der Dateien in Ihrem Repository zum Zeitpunkt, als der Commit erstellt wurde. Wenn Sie den Branch wechseln oder Änderungen committen, aktualisiert Git den »HEAD«, um den letzten Commit des dann aktiven Branches wiederzugeben. Der »HEAD« kann aber auch auf frühere Commits zeigen, wenn man das möchte.
Ein Commit ist quasi ein Snapshot des Zustands aller aktuellen Dateien eines Repositorys, zusammen mit einer Nachricht, was sich in diesem Commit im Vergleich zum letzten Snapshot geändert hat. Nimmt man Änderungen im Working Directory vor und fügt sie per neuem Commit dem Repository als neuen Snapshot hinzu, dann entsteht auf diese Weise eine Historie von Snapshots, die aufeinanderfolgen.
Diese Snapshots versieht Git grundsätzlich mit einem hexadezimalen Hash-Wert. Diese Folge von 40 Zeichen identifiziert einen Commit eindeutig. Da die Hashes sehr umständlich zu tippen sind, gibt man üblicherweise nur die ersten acht Zeichen für einen Commit an. Das genügt bei wenigen Commits häufig schon, um den Commit eindeutig zu identifizieren.
Nachdem Sie Ihr Repository frisch geklont haben, starten Sie immer mit dem neuesten Commit des Default-Branches. Sie könnten bei Bedarf aber auch in der Zeit zurückgehen. Dazu verschieben Sie den »HEAD« in der Liste der Commits in Richtung Vergangenheit.
Commits und Branches
Nun kann die Arbeit mit Ihrem lokalen Repository beginnen. Egal, ob Sie ein neues Repository erstellt haben oder Änderungen an einem bestehenden vornehmen möchten: Sie beginnen ganz einfach damit, die gewünschten Änderungen an Dateien im Working Directory vorzunehmen. Sie ändern also Dateien, fügen neue hinzu, benennen einige um oder löschen sie. Da Sie in einem Git-Repository arbeiten, können Sie jederzeit vergleichen, welche Änderungen in Ihrem Working Directory gegenüber dem »HEAD« ausgeführt wurden.
Dafür kennt Git den Befehl »git status«. Haben Sie im Working Directory beispielsweise eine Datei »LICENSE« und die Ordner »bin/«, »docs/«, »logs/« und »scripts/« hinzugefügt, liefert der Status-Befehl vermutlich in etwa die Ausgabe aus Abbildung 3. Sie sehen darin sehr genau, auf welchem Branch Sie sich befinden, welche Dateien welchen Änderungsstatus aufweisen und was Sie tun müssen, um der Versionsverwaltung neue Dateien hinzuzufügen.
Es liegt nahe anzunehmen, dass der Befehl »git add Datei« der Versionsverwaltung direkt eine Datei hinzufügt. Das wäre allerdings ein Trugschluss. Vielmehr fügt dieses Kommando die Datei der Staging Area hinzu. Die Staging Area dient als Werkbank für das Zusammenstellen eines neuen Commits, also eines Snapshots des aktuellen Repository-Zustands mit eindeutigem Hash und einer Beschreibung der vorgenommenen Änderungen als Nachricht.
Es ist möglich, alle Änderungen auf einmal der Staging Area hinzufügen. Dafür benutzen Sie das Kommando »git add .«. Leere Ordner lassen sich übrigens nicht versionieren. Deswegen fügen Sie solchen Verzeichnissen einfach eine Datei ».gitkeep« hinzu, wenn die ansonsten leeren Ordner für die Projektstruktur wichtig sind.
Der umgekehrte Weg funktioniert ebenfalls. Mit »git reset Datei« entfernen Sie einzelne Dateien wieder aus der Staging Area, mit »git reset« gleich alle. Git wird dabei allerdings verhindern, dass Änderungen in Ihrem Working Directory überschrieben werden oder verschwinden. Erst wenn Sie »git reset –hard« nutzen, setzt die Versionsverwaltung das Working Directory exakt in den Zustand des Commits von »HEAD« zurück.
Es gibt noch viele weitere Optionen für »git reset«. Beispielsweise kann man damit einen vergangenen Commit wieder rückgängig machen oder in der Historie zurückspringen. An dieser Stelle genügt es aber zu verstehen, dass »git reset« die Möglichkeit bietet, Dinge wieder aus der Staging Area zu entfernen.
Haben Sie dem Commit alle gewünschten Dateien hinzugefügt, überprüfen Sie mit »git status« noch einmal den vorbereiteten nächsten Commit. Das könnte dann so aussehen wie in der Abbildung 4.
Nun können Sie alle für den nächsten Commit vorgesehenen Änderungen auch wirklich dem lokalen Repository hinzufügen, indem Sie »git commit« aufrufen. Git öffnet daraufhin einen Editor, damit Sie eine Commit Message eingeben können. Sie beschreibt, welche Änderungen der Commit enthält. Schlauerweise nutzen Sie gleich die Variante »git commit -m”Beschreibung“«, denn ein Commit sollte niemals ohne Nachricht bleiben (Abbildung 5).
Jetzt haben Sie Ihre ersten Änderungen direkt im aktuellen Entwicklungszweig (Branch) im lokalen Repository gespeichert. Wenn Sie allein arbeiten, geht das auch völlig in Ordnung. Beim Zusammenspiel im Team ist es hingegen üblich, die eigenen Änderungen zuerst einmal in einem eigenen Branch abzulegen.
Dazu verwenden Sie den Befehl »git branch Branch«, der einen neuen Branch von »HEAD« erstellt. Um daran zu arbeiten, müssen Sie anschließend mit »git checkout Branch« in den neuen Zweig wechseln. Dieser recht häufig anfallende Vorgang lässt sich per »git checkout -b Branch« auch in einem Aufwasch erledigen.
Danach arbeiten Sie in einem eigenen neuen Branch und erstellen neue Commits nur dort. Für die Arbeit im Team spielt deswegen auch die Koordination von Branch-Namen eine Rolle. Wie schon erwähnt, heißt der Hauptentwicklungszweig häufig »master« oder »main«. Auf diesem Zweig befindet sich der produktiv betriebene Code eines Projekts. Im Branch »develop« fügen alle Team-Mitglieder neue Entwicklungen zusammen. Die Branches »feature/Feature« oder »bugfix/Bug« wären dann Beispiele für die Namen von Branches, auf denen einzelne Team-Mitglieder ihre Änderungen vornehmen.
Lokales Working Directory
Im vorigen Abschnitt haben wir uns angesehen, wie man dem Repository Dateien hinzufügt und die Staging Area nutzt. Nun stellt sich die Frage, welche Dateien nun eigentlich ins Repository gehören.
Üblicherweise findet sich in einem Projektverzeichnis ein Mix verschiedener Dateien. Manche sind wichtig und selbst erstellt, andere wurden automatisch erzeugt oder sind schlicht überflüssig (etwa temporäre Dateien oder ein Log-Verzeichnis). Diese Dateien sollten Sie von Anfang an aus der Versionsverwaltung ausschließen. Alle anderen Dateien fügen Sie der Versionsverwaltung hinzu und checken sämtliche Änderungen immer direkt in Commits im Repository ein. Im Optimalfall ist das Working Directory zum Start eines jeden Arbeitsschritts sauber, sprich: »git status« gibt keine Änderungen aus und listet auch keine unversionierten Dateien (Abbildung 6).
Um Dateien von der Versionierung auszuschließen, führen Sie in Git eine Liste zu ignorierender Dateien. Normalerweise würde Ihnen Git alle Dateien eines Working Directorys zum Hinzufügen per Commit anbieten. Das umfasst also auch Inhalte, die Sie dem Repository nicht hinzufügen möchten, etwa temporäre Files. Um sie auszuschließen, tragen Sie die Namen der entsprechenden Dateien und Ordner oder gleich ganze Namensmuster in das File ».gitignore« ein (Abbildung 7).
Diese Textdatei dient einzig und allein dem Zweck, Git mitzuteilen, was nicht ins Repository gelangen soll. Dafür wird sie selbst eingecheckt, denn üblicherweise sollen die ignorierten Dateien auch bei anderen Team-Mitgliedern von der Versionierung ausgenommen bleiben.
Manchmal gibt es auch Situationen, in denen Sie mitten in der Arbeit unterbrochen werden oder schnell einen Branch wechseln müssen, um einen Bug zu fixen oder ein Problem nachzuvollziehen. Dann brauchen Sie schnell ein sauberes Working Directory. Dabei befinden sich Ihre Änderungen sicher nicht immer in einem Zustand, in dem Sie sie dem Repository hinzufügen möchten. Sie brauchen also eine Möglichkeit, Ihre Arbeit quasi zur Seite zu legen, um später daran weiterzuarbeiten.
Git bietet dafür den Stash an, ein Stapel, auf den Sie die aktuellen Änderungen im Working Directory legen können, um später daran weiterzuarbeiten. Git räumt dabei das Working Directory auf und stellt einen sauberen Zustand ohne Änderungen her. Von dort aus können Sie sehr gut den Branch wechseln oder ohne aktuelle Änderungen ein Problem nachvollziehen.
Angenommen, Sie haben in Ihrem Working Directory Änderungen an den Dateien »README.md« und »LICENSE« gemacht. Dann lässt sich dieses Verzeichnis mit dem Befehl »git stash« sofort in einen sauberen Zustand bringen. Mit »git stash -u« nimmt Git auch neu hinzugefügte und noch unversionierte Dateien in den Stash auf. Das würde dann so aussehen wie in Abbildung 8. Am Ende ist das Working Directory sauber auf den letzten Commit (»HEAD«) des aktuellen Branches zurückgesetzt. In der Abbildung ist das der Commit »295fd04« mit der Message »last commit« auf dem Branch »develop«.
Sie können den vorherigen Zustand später mit »git stash pop« genauso einfach wiederherstellen. Dabei erlaubt »git stash«, die Arbeit beliebig oft zur Seite zu legen. Der Stapel wächst anschließend einfach immer weiter an. Den Inhalt lassen Sie sich dann mit »git stash list« anzeigen und stellen mit »git stash apply« einen ganz bestimmten Arbeitsstand wieder her.
Behalten Sie bei der Nutzung des Stash immer im Hinterkopf, dass er ausschließlich lokal vorliegt. Er wird weder in Ihr lokales Repository übertragen noch versioniert. Er gibt auch keine Möglichkeit, ihn in ein anderes Repository zu übertragen. Der Stash existiert nur auf dem Rechner, auf dem Sie die Änderungen vollzogen haben, und ist auch nur für das Working Directory verfügbar, in dem Sie zuletzt »git stash« aufgerufen haben (Abbildung 9).
Sichern auf Plattformen
Sie haben jetzt erfolgreich Dateien in Ihrem lokalen Repository geändert und das in Form von Commits festgehalten. Nun stehen Sie vor der Aufgabe, diese Änderungen auf der Plattform verfügbar zu machen, auf der Sie begonnen haben – etwa für die Zusammenarbeit mit Team-Mitgliedern oder auch nur zur Sicherung. Das erfolgt durch das Hochladen der Commits in das Remote-Repository, denn bisher befinden sich alle neuen Snapshots nur in Ihrem lokalen Repository.
Haben Sie zu Beginn Ihr lokales Repository von einer Plattform mit »git clone« geklont, dann müssen Sie gar nichts mehr tun: Git hat sich beim Klonen die Verbindung mit dem Remote-Repository gemerkt und es unter dem Namen »origin« für Sie hinterlegt. Falls Sie mit »git init .« begonnen haben, kommt stattdessen die Möglichkeit zum Zug, weitere Remotes hinzuzufügen.
Zunächst sollten Sie sicherstellen, dass Ihr lokales Repository auf dem neuesten Stand ist. Erinnern Sie sich, dass es sich bei Ihrem lokalen Repository um eine Kopie des Remote-Repositorys handelt. Mittlerweile können dort neue Commits hinzugekommen sein, die in Ihrem lokalen Repository noch fehlen. Selbst wenn Sie allein arbeiten, könnten Sie durch Änderungen an Dateien direkt auf der Plattform neue Commits ausgelöst haben.
Der Befehlsfolge »git fetch« gefolgt von »git status« ermittelt, ob Änderungen im Remote vorliegen, die Sie noch nicht integriert haben. Dabei bringt Git Ihr Working Directory noch nicht auf den neuesten Stand des Remote-Repositorys. Sie können sich die neuen Änderungen also erst einmal in Ruhe ansehen.
Aktualisierungen in Ihrem Working Directory erfolgen erst dann, wenn Sie »git pull« aufrufen. Daraufhin bringt Git die Dateien im Working Directory auf den aktuellen Stand des Remote Repositorys. Kommt es dabei zu Konflikten zwischen dem lokalen und dem entfernten Repo, fordert Git Sie auf, diese Merge-Konflikte zu lösen. Wie Sie das erledigen, sehen wir uns im nächsten Abschnitt an.
Ein »git pull« ist eigentlich nichts anderes als ein »git fetch«, gefolgt von einem »git merge« auf den neuesten Commit. Wenn Sie zu Beginn Ihr Repository lokal gestartet haben und nachträglich einen Origin-Remote über »git remotes add origin URL« hinzugefügt haben, kann es sein, dass Git bei einem einfachen »git pull« jetzt eine Fehlermeldung auswirft. Sie lautet ungefähr »fatal: refusing to merge unrelated histories«.
Diese Fehlermeldung rührt daher, dass Sie durch das separate Anlegen eines Repositorys auf der Plattform und eines lokalen Repositorys quasi zwei völlig separate Versionsverwaltungen gestartet haben. Git will nun verhindern, dass Sie beide vermischen. Im Detail rührt der Fehler daher, dass die Plattformen häufig eine »README.md« anlegt und diese einen ersten Commit auf der Plattform erzeugt. Dieser einzelne Commit stellt jetzt eine Historie dar, die nicht zu Ihrer lokalen Variante passt.
Sie können das aber trotzdem noch erreichen, und zwar über den Befehl »git pull origin main –allow-unrelated-histories«. Danach sind die beiden Historien dauerhaft verbunden, und Sie können normal weiterarbeiten.
Merge-Konflikte bewältigen
Wenn Sie anfangen, mit Git zu arbeiten, bereiten Merge-Konflikte häufig am meisten Probleme. Zum einen sehen die Rückmeldungen von Git bei solchen Konflikten wie Fehler aus, zum anderen sind sie in der Tat etwas diffizil zu beheben. Merge-Konflikte treten ja dann auf, wenn zwei Arbeitsstände zusammengeführt werden sollen, in denen jeweils dieselbe Datei bearbeitet wurde. In solchen Situationen gilt es zu entscheiden, welche Änderung an einer Datei beibehalten werden soll. Dazu benötigt Git fast immer Ihre Mithilfe.
Arbeiten Sie in einem Team, sollten Sie die Person hinzuziehen, die ebenfalls Änderungen an der Datei gemacht hat. Zusammen lässt sich besser entscheiden, wie die Datei nach dem Zusammenführen der Änderungen aussehen soll.
Es gibt aber keinen Grund, Merge-Konflikte als Problem anzusehen. Vielmehr stellen sie einen ganz normalen Vorfall beim Zusammenführen von Arbeitsständen dar. Nur selten gibt es wirklich massive Probleme beim Lösen von Merge-Konflikten – meistens dann, wenn im Team zwei Personen an denselben Features arbeiten. Häufig lassen sich Merge-Konflikte schnell über eine Durchsicht der Änderungen lösen.
Ein Merge-Konflikt tritt üblicherweise auf, wenn Sie einen Branch mit einem anderen zusammenführen. Nehmen wir einmal an, dass Sie Änderungen von einem Branch »feature-branch« in den Hauptentwicklungszweig »main« übernehmen möchten. Sie sollten dann immer den Branch auschecken, auf den Sie die Änderungen mergen möchten, im konkreten Fall also »main«. Mit »git status« ermitteln Sie, auf welchem Zweig Sie sich befinden, und wechseln bei Bedarf mit »git checkout main« den Branch.
Nun starten Sie über »git merge feature-branch« den Merge. An dieser Stelle muss es nun nicht unbedingt zu einem Merge-Konflikt kommen: Vielleicht gab es ja gar keine Änderungen an derselben Datei, oder Git konnte die Konflikte selbst auflösen. Tritt aber einen Merge-Konflikt auf, dann zeigt Git Ihnen das wie in Abbildung 10 an.
Git legt bei Merges grundsätzlich einen Merge-Commit an und fügt die Änderungen damit in einem neuen Commit zusammen. Sollte es also einen Merge-Konflikt gegeben haben, können Sie sich mit »git status« ansehen, welche Dateien der Konflikt betrifft (Abbildung 11).
Sie sollten keine weiteren Änderungen vornehmen, solange der Merge-Konflikt noch besteht. Wenn Sie sich jetzt gerade nicht um den Konflikt kümmern können, brechen Sie den Merge mit »git merge –abort« komplett ab und versuchen Ihr Glück später noch einmal. Git sperrt in diesem Zustand übrigens auch ein paar weitere Kommandos, die nicht funktionieren, bis Sie den Merge-Konflikt gelöst haben. Dazu gehören unter anderem die Befehle »git checkout Branch« zum Wechseln des Entwicklungszweigs und »git stash«, um Änderungen beiseite zu legen.
Um die Konflikte zu beheben, müssen Sie nun die betroffenen Dateien einzeln durchsehen. Git markiert die Stellen mit Konflikten mit speziellen Texthervorhebungen (Abbildung 12). Diese Stellen müssen Sie so bearbeiten, dass sie dem Zustand entsprechen, der nach dem Auflösen des Merge-Konflikts verbleiben soll. Auch die Markierungen »<<<<<<< HEAD«, »=======« und »>>>>>>> feature-branch« müssen Sie dabei entfernen. Am Schluss bleibt nur das übrig, was wirklich in der Datei stehenbleiben soll.
Nachdem Sie alle Dateien mit Konflikten bearbeitet haben, fügen Sie sie über »git add Datei« dem Merge-Commit hinzu und können diesen zu guter Letzt mit »git commit« abschließen. Damit ist der Merge-Konflikt gelöst.
Hochladen der Änderungen
Nachdem Sie das lokale Repository jetzt mit dem Remote-Repository abgeglichen, sämtliche Änderungen heruntergeladen und alle nötigen Merges ausgeführt haben, können Sie die Änderungen aus dem lokalen Repository in das Remote-Repository übertragen.
Dazu nutzen Sie den Befehl »git push«. Er überträgt die Änderungen aus dem aktuellen Branch Ihres »HEAD« in den Branch »main« im Remote-Repository. Das erfolgt allerdings nur für den aktuell aktiven Branch. Falls Sie einen neuen Branch erstellt und ihn noch nicht im Remote-Repository veröffentlicht haben, legen Sie mit »git push -u origin Branch« den lokalen Branch auch im Remote-Repo an. Damit verknüpfen Sie ihn gleichzeitig so, dass beim nächsten Mal auch ein »git push« ausreicht (Abbildung 13).

Abbildung 13: Eine schematische Darstellung der Funktionsweise von »git push« respektive »git pull«.
Review-Prozess und Merges
Nun sind Ihre Änderungen im Remote-Repository verfügbar und können auch von anderen gesehen und genutzt werden. Für eine Situation, in der Sie allein an Konfigurationsdateien oder einem eigenen Projekt arbeiten, passt das so weit.
Allerdings ist es in der Team-Arbeit nicht üblich, Änderungen direkt in den Hauptentwicklungszweig (häufig »develop« genannt) oder sogar in den Produktiventwicklungszweig (in der Regel »main«) zu pushen. Stattdessen möchten viele Teams von der Möglichkeit Gebrauch machen, Änderungen vor der Aufnahme in den Hauptentwicklungszweig zu überprüfen. Dafür werden Merge Requests verwendet und Code-Reviews vorgenommen. Ein Merge Request ist keine echte Funktion von Git, sondern vielmehr eine Funktion der Code-Hosting-Plattform, die Merge Requests für das Zusammenführen von unterschiedlichen Branches in ihrer Oberfläche anbietet.
Haben Sie zum Beispiel gerade einen Entwicklungszweig »features-branch« auf Github oder Gitlab veröffentlicht, sehen Sie dort eine Aufforderung, einen Merge Request für die Änderungen zu erstellen. Dabei konfigurieren Sie dann den Quell- und den Zielentwicklungszweig, zum Beispiel von »feature-branch« nach »develop«. Die Plattformen sammeln dann alle Änderungen zwischen den beiden Branches und zeigen sie zum Review an. Nach dem Bestätigen der Änderungen und der Freigabe (auch Approval genannt) führt die Plattform die Änderungen über einen »git merge« zusammen.
Sollte es Merge-Konflikte auf der Plattform geben, zeigt diese üblicherweise eine Meldung wie »Can not merge because of conflicts« an. Dann ist es gut, wenn Sie Ihren Entwicklungszweig (etwa »feature-branch«) zuerst noch einmal mit den Änderungen des Zweigs aktualisieren, für den Sie den Merge-Request erstellt haben (beispielsweise »main«) und dabei Merge-Konflikte lokal auf Ihrem Rechner lösen. Dann kann ein Team-Mitglied den Request inklusive der nötigen Änderungen für Merges prüfen und anschließend mergen.
Mehr als Versionskontrolle
Git hat mit Konzepten wie Dezentralität und Snapshots die Versionskontrolle grundlegend revolutioniert und sie deutlich effizienter und robuster gemacht. Wie Sie auf dieser Grundlage selbst mit Git arbeiten und die Konzepte erleben können, haben Sie in diesem Artikel kennengelernt.
Ebenfalls sehr spannend sind zudem die Arbeitsprozesse, die rund um Git entstanden sind. Git entwickelte sich so von einer rein technischen Lösung zur Versionsverwaltung hin zu einer Lösung zur vollständigen Steuerung von Entwicklungsprozessen in Teams. Entwickler und Administratoren, die Git mit allen seinen Funktionen – von verteilten Repositories über Branches bis zu Merge Requests – nutzen, organisieren sich allein dadurch bereits sehr strukturiert.
Git eignet sich dabei auch hervorragend zur Organisation und Versionierung aller möglichen eigenen Arbeiten. Vom kleinen Coding-Projekt bis hin zur großen Dokumentenverwaltung kann es so helfen, Dateien sicher und verteilt abzulegen. Auf diese Weise geht hoffentlich nie wieder ein Arbeitsstand aufgrund der eingesetzten Versionsverwaltung verloren, und Sie können sich auf das konzentrieren, was wirklich zählt: das Lösen von technischen oder infrastrukturellen Problemen oder einfach nur das Realisieren eines Projekts. (jcb)
Der Autor
Der freiberufliche Tech-Lead, Senior-Entwickler und Coding-Coach Tim Adler arbeitete früher als CTO unter anderem für Chefkoch.de, verschiedene Startups und ein Innovation Lab samt Startup-Inkubator. Er schrieb auch am Buch “Git kurz & gut” von Nina Siessegger mit, das bei O’Reilly erschienen ist.
Infos
- Git: https://git-scm.com
- CVS: https://savannah.nongnu.org/projects/cvs
- Subversion: https://subversion.apache.org
- Mercurial: https://www.mercurial-scm.org
- Github: https://github.com
- Gitlab: https://about.gitlab.com
- Bitbucket: https://bitbucket.org


















