Kapitel 6. Multiplayer Git

Anfangs benutzte ich Git bei einem privaten Projekt, bei dem ich der einzige Entwickler war. Unter den Befehlen im Zusammenhang mit Git’s verteilter Art, brauchte ich nur pull und clone, damit konnte ich das selbe Projekt an unterschiedlichen Orten halten.

Später wollte ich meinen Code mit Git veröffentlichen und Änderungen von Mitstreitern einbinden. Ich musste lernen, wie man Projekte verwaltet, an denen mehrere Entwickler aus aller Welt beteiligt waren. Glücklicherweise ist das Git’s Stärke und wohl auch seine Daseinsberechtigung.

Wer bin ich?

Jeder Commit enthält Name und eMail-Adresse des Autors, welche mit git log angezeigt werden. Standardmäßig nutzt Git Systemeinstellungen, um diese Felder auszufüllen. Um diese Angaben explizit zu setzen, gib ein:

$ git config --global user.name "Max Mustermann"
$ git config --global user.email maxmustermann@beispiel.de

Lasse den -global Schalter weg, um diese Einstellungen für das aktuelle Repository zu setzen.

Git über SSH, HTTP

Angenommen, Du hast einen SSH-Zugang zu einem Webserver, aber Git ist nicht installiert. Wenn auch nicht so effizient wie mit dem systemeigenen Protokoll, kann Git über HTTP kommunizieren.

Lade Git herunter, compiliere und installiere es unter Deinem Benutzerkonto und erstellen ein Repository in Deinem Webverzeichnis:

$ GIT_DIR=proj.git git init
$ cd proj.git
$ git --bare update-server-info
$ cp hooks/post-update.sample hooks/post-update

Bei älteren Git Versionen funktioniert der copy-Befehl nicht, stattdessen gib ein:

$ chmod a+x hooks/post-update

Nun kannst Du Deine letzten Änderungen über SSH von jedem Clone aus veröffentlichen.

$ git push web.server:/pfad/zu/proj.git master

und jedermann kann Dein Projekt abrufen mit:

$ git clone http://web.server/proj.git

Git über alles

Willst Du Repositories ohne Server synchronisieren oder gar ohne Netzwerkverbindung? Musst Du während eines Notfalls improvisieren? Wir haben gesehen, dass man mit git fast-export und git fast-import Repositories in eine einzige Datei konvertieren kann und zurück. Wir können solche Dateien hin und her schicken, um Git Repositories über jedes beliebige Medium zu transportieren, aber ein effizienteres Werkzeug ist git bundle.

Der Absender erstellt ein Bundle:

$ git bundle create einedatei HEAD

und transportiert das Bundle einedatei irgendwie zum anderen Beteiligten: per eMail, USB-Stick, einen xxd Hexdump und einen OCR Scanner, Morsecode über Telefon, Rauchzeichen usw. Der Empfänger holt sich die Commits aus dem Bundle durch Eingabe von:

$ git pull einedatei

Der Empfänger kann das sogar mit einem leeren Repository tun. Trotz seiner Größe, einedatei enthält das komplette original Git Repository.

In größeren Projekten vermeidest Du Datenmüll, indem Du nur Änderungen bundlest, die in den anderen Repositories fehlen. Zum Beispiel, nehmen wir an, der Commit „1b6d…“ ist der aktuellste, den beide Parteien haben:

$ git bundle create einedatei HEAD ^1b6d

Macht man das regelmäßig, kann man leicht vergessen, welcher Commit zuletzt gesendet wurde. Die Hilfeseiten schlagen vor, Tags zu benutzen, um dieses Problem zu lösen. Das heißt, nachdem Du ein Bundle gesendet hast, gib ein:

$ git tag -f letztesbundle HEAD

und erstelle neue Aktualisierungsbundles mit:

$ git bundle create neuesbundle HEAD ^letztesbundle

Patches: Das globale Zahlungsmittel

Patches sind die Klartextdarstellung Deiner Änderungen, die von Computern und Menschen gleichermaßen einfach verstanden werden. Dies verleiht ihnen eine universelle Anziehungskraft. Du kannst einen Patch Entwicklern schicken, ganz egal, was für ein Versionsverwaltungssystem sie benutzen. Solange Deine Mitstreiter ihre eMails lesen können, können sie auch Deine Änderungen sehen. Auch auf Deiner Seite ist alles was Du brauchst ein eMail-Konto: es gibt keine Notwendigkeit ein Online Git Repository aufzusetzen.

Erinnere Dich an das erste Kapitel:

$ git diff 1b6d > mein.patch

gibt einen Patch aus, der zur Diskussion einfach in eine eMail eingefügt werden kann. In einem Git Repository gib ein:

$ git apply < mein.patch

um den Patch anzuwenden.

In einer offizielleren Umgebung, wenn Autorennamen und eventuell Signaturen aufgezeichnet werden sollen, erstelle die entsprechenden Patches nach einem bestimmten Punkt durch Eingabe von:

$ git format-patch 1b6d

Die resultierenden Dateien können an git-send-email übergeben werden oder von Hand verschickt werden. Du kannst auch eine Gruppe von Commits angeben:

$ git format-patch 1b6d..HEAD^^

Auf der Empfängerseite speichere die eMail in eine Datei, dann gib ein:

$ git am < email.txt

Das wendet den eingegangenen Patch an und erzeugt einen Commit, inklusive der Informationen wie z.B. den Autor.

Mit einer Webmail Anwendung musst Du eventuell ein Button anklicken, um die eMail in ihrem rohen Originalformat anzuzeigen, bevor Du den Patch in eine Datei sicherst.

Es gibt geringfügige Unterschiede bei mbox-basierten eMail Anwendungen, aber wenn Du eine davon benutzt, gehörst Du vermutlich zu der Gruppe Personen, die damit einfach umgehen können, ohne Anleitungen zu lesen.!

Entschuldigung, wir sind umgezogen.

Nach dem Clonen eines Repositories, wird git push oder git pull automatisch auf die original URL zugreifen. Wie macht Git das? Das Geheimnis liegt in der Konfiguration, die beim Clonen erzeugt wurde. Lasst uns einen Blick riskieren:

$ git config --list

Die remote.origin.url Option kontrolliert die Quell-URL; „origin“ ist der Spitzname, der dem Quell-Repository gegeben wurde. Wie mit der „master“ Branch Konvention können wir diesen Spitznamen ändern oder löschen, aber es gibt für gewöhnlich keinen Grund, dies zu tun.

Wenn das original Repository verschoben wird, können wir die URL aktualisieren mit:

$ git config remote.origin.url git://neue.url/proj.git

Die branch.master.merge Option definiert den Standard-Remote-Branch bei einem git pull. Während des ursprünglichen Clonen wird sie auf den aktuellen Branch des Quell-Repository gesetzt, so dass selbst dann, wenn der HEAD des Quell-Repository inzwischen auf einen anderen Branch gewechselt hat, ein späterer pull treu dem original Branch folgen wird.

Diese Option gilt nur für das Repository, von dem als erstes gecloned wurde, was in der Option branch.master.remote hinterlegt ist. Bei einem pull aus anderen Repositories müssen wir explizit angeben, welchen Branch wir wollen:

$ git pull git://beispiel.com/anderes.git master

Das obige erklärt, warum einige von unseren früheren push und pull Beispielen keine Argumente hatten.

Entfernte Branches

Wenn Du ein Repository clonst, clonst Du auch alle seine Branches. Das hast Du vielleicht noch nicht bemerkt, denn Git versteckt diese: Du musst speziell danach fragen. Das verhindert, dass Branches vom entfernten Repository Deine lokalen Branches stören, und es macht Git einfacher für Anfänger.

Zeige die entfernten Branches an mit:

$ git branch -r

Du solltes etwas sehen wie:

origin/HEAD
origin/master
origin/experimentell

Diese Liste zeigt die Branches und den HEAD des entfernten Repository, welche auch in regulären Git Anweisungen verwendet werden können. Zum Beispiel, angenommen Du hast viele Commits gemacht und möchtest einen Vergleich zur letzten abgeholten Version machen. Du kannst die Logs nach dem entsprechenden SHA1 Hashwert durchsuchen, aber es ist viel einfacher, folgendes einzugeben:

$ git diff origin/HEAD

Oder Du kannst schauen, was auf dem Branch „experimentell“ los war:

$ git log origin/experimentell

Mehrere Remotes

Angenommen, zwei andere Entwickler arbeiten an Deinem Projekt, und wir wollen beide im Auge behalten. Wir können mehr als ein Repository gleichzeitig beobachten mit:

$ git remote add other git://example.com/some_repo.git
$ git pull other some_branch

Nun haben wir einen Branch vom zweiten Repository eingebunden und wir haben einfachen Zugriff auf alle Branches von allen Repositories:

$ git diff origin/experimentell^ other/some_branch~5

Aber was, wenn wir nur deren Änderungen vergleichen wollen, ohne unsere eigene Arbeit zu beeinflussen? Mit anderen Worten, wir wollen ihre Branches untersuchen, ohne dass deren Änderungen in unser Arbeitsverzeichnis einfließen. Anstatt pull benutzt Du dann:

$ git fetch        # Fetch vom origin, der Standard.
$ git fetch other  # Fetch vom zweiten Programmierer.

Dies holt lediglich die Chroniken. Obwohl das Arbeitsverzeichnis unverändert bleibt, können wir nun jeden Branch aus jedem Repository in einer Git Anweisung referenzieren, da wir eine lokale Kopie besitzen.

Erinnere Dich, dass ein Pull hinter den Kulissen einfach ein fetch gefolgt von einem merge ist. Normalerweise machen wir einen pull, weil wir die letzten Commits abrufen und einbinden wollen. Die beschriebene Situation ist eine erwähnenswerte Ausnahme.

Siehe git help remote, um zu sehen, wie man Remote-Repositories entfernt, bestimmte Branches ignoriert und mehr.

Meine Einstellungen

Für meine Projekte bevorzuge ich es, wenn Unterstützer Repositories vorbereiten, von denen ich pullen kann. Verschiedene Git Hosting Anbieter lassen Dich mit einem Klick deine eigene Fork eines Projekts hosten.

Nachdem ich einen Zweig abgerufen habe, benutze ich Git Anweisungen, um durch die Änderungen zu navigieren und zu untersuchen, die idealerweise gut organisiert und dokumentiert sind. Ich merge meine eigenen Änderungen und führe eventuell weitere Änderungen durch. Wenn ich zufrieden bin, pushe ich in das zentrale Repository.

Obwohl ich nur unregelmäßig Beiträge erhalte, glaube ich, dass diese Methode sich auszahlt. Siehe diesen Blog Beitrag von Linus Torvalds (englisch).

In der Git Welt zu bleiben, ist etwas bequemer als Patch-Dateien, denn es erspart mir, sie in Git Commits zu konvertieren. Außerdem kümmert sich Git um die Details wie Autorname und eMail-Adresse, genauso wie um Datum und Uhrzeit und es fordert den Autor zum Beschreiben seiner eigenen Änderungen auf.