Git
Chapters ▾ 2nd Edition

5.2 Verteiltes Git - An einem Projekt mitwirken

An einem Projekt mitwirken

Die größte Schwierigkeit bei der Beschreibung, wie man an einem Projekt mitwirkt, sind die zahlreichen Varianten, wie man das geschehen könnte. Da Git sehr flexibel ist, können die Personen auf viele Arten zusammenarbeiten. Es ist nicht einfach zu beschreiben, wie Sie dazu beitragen sollen, da jedes Projekt ein wenig anders ist. Einige der wichtigen Unbekannten sind die Anzahl der aktiven Mitwirkenden, der ausgewählte Arbeitsablauf, Ihre Zugriffsberechtigung und möglicherweise auch die Methode, wie externe Beiträge verwaltet werden sollen.

Die erste Unbekannte ist die Anzahl der aktiven Mitwirkenden – wie viele Benutzer tragen aktiv Quelltext zu diesem Projekt bei und wie oft? In vielen Fällen haben Sie zwei oder drei Entwickler mit ein paar Commits pro Tag oder möglicherweise weniger für etwas untätigere Projekte. Bei größeren Unternehmen oder Projekten kann die Anzahl der Entwickler in die Tausende gehen, wobei jeden Tag Hunderte oder Tausende von Commits getätigt werden können. Das ist deshalb von Bedeutung, da Sie mit einer wachsenden Anzahl von Entwicklern auch sicherstellen müssen, dass sich der Code problemlos anwenden lässt und leicht zusammengeführt werden kann. Von Ihnen übermittelte Änderungen können durch Arbeiten, die während Ihrer Arbeit oder während Ihrer Änderungen genehmigt oder eingearbeitet wurden, veraltet sein oder schwer beschädigt werden. Wie können Sie Ihren Quelltext konsistent auf dem neuesten Stand halten und dafür sorgen, dass Ihre Commits gültig sind?

Die nächste Unbekannte ist der für das Projekt verwendete Arbeitsablauf. Ist er zentralisiert, wobei jeder Entwickler den gleichen Schreibzugriff auf die Hauptentwicklungslinie hat? Verfügt das Projekt über einen Betreuer oder Integrationsmanager, der alle Patches überprüft? Sind alle Patches von Fachleuten geprüft und genehmigt? Sind Sie selbst in diesen Prozess involviert? Ist ein Leutnant System vorhanden und müssen Sie Ihre Arbeit zuerst bei diesen einreichen?

Die nächste Unbekannte ist Ihre Zugriffsberechtigung. Der erforderliche Arbeitsablauf, um zu einem Projekt beizutragen, unterscheidet sich erheblich, wenn Sie Schreibzugriff auf das Projekt haben, als wenn Sie diesen nicht haben. Wenn Sie keinen Schreibzugriff haben, wie bevorzugt das Projekt die Annahme von beigetragener Arbeit? Gibt es überhaupt eine Richtlinie? Wie umfangreich sind die Änderungen, die Sie jeweils beisteuern? Wie oft tragen Sie etwas bei?

All diese Fragen können sich darauf auswirken, wie Sie effektiv zu einem Projekt beitragen und welche Arbeitsabläufe bevorzugt oder überhaupt für sie verfügbar sind. Wir werden jeden dieser Aspekte in einer Reihe von Anwendungsfällen behandeln, wobei wir mit simplen Beispielen anfangen und später komplexere Szenarios besprechen. Sie sollten in der Lage sein, anhand dieser Beispiele die spezifischen Arbeitsabläufe zu erstellen, die Sie in der Praxis benötigen.

Richtlinien zur Zusammenführung (engl. Commits)

Bevor wir uns mit den spezifischen Anwendungsfällen befassen, finden Sie hier einen kurzen Hinweis zu Commit-Nachrichten. Ein guter Leitfaden zum Erstellen von Commits und das Befolgen desselbigen, erleichtert die Arbeit mit Git und die Zusammenarbeit mit anderen erheblich. Das Git-Projekt selber enthält ein Dokument, in dem einige nützliche Tipps zum Erstellen von Commits für die Übermittlung von Patches aufgeführt sind. Sie finden diese Tipps im Git-Quellcode in der Datei Documentation/SubmittingPatches.

Ihre Einsendungen sollten keine Leerzeichenfehler enthalten. Git bietet eine einfache Möglichkeit, dies zu überprüfen. Führen Sie vor dem Commit git diff --check aus, um mögliche Leerzeichenfehler zu identifizieren und diese für Sie aufzulisten.

Ausgabe von `git diff --check`.
Figure 57. Ausgabe von git diff --check.

Wenn Sie diesen Befehl vor einem Commit ausführen, können Sie feststellen, ob Leerzeichen Probleme auftreten, die andere Entwickler stören könnten.

Als Nächstes, versuchen Sie, aus jedem Commit einen logisch getrennten Satz von Änderungen zu machen. Wenn Sie können, versuchen Sie, Ihre Änderungen leicht verdaulich zu machen – arbeiten Sie nicht ein ganzes Wochenende an fünf verschiedenen Themen und übermitteln Sie dann all diese Änderungen in einem massiven Commit am Montag. Auch wenn Sie am Wochenende keine Commits durchführen, nutzen Sie am Montag die Staging Area, um Ihre Änderungen aufzuteilen in wenigstens einen Commit für jeden Teilaspekt mit jeweils einer sinnvollen Nachricht. Wenn einige der Änderungen dieselbe Datei modifizieren, benutzen Sie die Anweisung git add --patch, um Dateien partiell zur Staging Area hinzuzufügen (detailliert dargestellt im Abschnitt Interactive Staging). Der Schnappschuss vom Projekt an der Spitze des Branches ist der Selbe, ob Sie einen oder fünf Commits durchgeführt haben, solange nur all die Änderungen irgendwann hinzugefügt werden. Versuchen Sie also, die Dinge zu vereinfachen für Ihre Entwicklerkollegen, die Ihre Änderungen begutachten müssen.

Dieser Ansatz macht es außerdem einfacher, einen Satz von Änderungen zu entfernen oder rückgängig zu machen, falls das später nötig wäre. Rewriting History beschreibt eine Reihe nützlicher Git-Tricks zum Umschreiben des Verlaufs oder um interaktiv Dateien zur Staging Area hinzuzufügen. Verwenden Sie diese Werkzeuge, um einen sauberen und leicht verständlichen Verlauf aufzubauen, bevor Sie Ihre Arbeit jemand anderem schicken.

Als letztes darf die Commit-Nachricht nicht vergessen werden. Macht man es sich zur Gewohnheit, qualitativ hochwertige Commit-Nachrichten zu erstellen, erleichtert dies die Verwendung und die Zusammenarbeit mit Git erheblich. In der Regel sollten Ihre Nachrichten mit einer einzelnen Zeile beginnen, die nicht länger als 50 Zeichen ist. Diese sollte ihre Änderungen kurz und bündig beschreiben. Darauf folgen eine leere Zeile und eine ausführliche Erläuterung. Für das Git-Projekt ist es erforderlich, dass die ausführliche Erläuterung Ihre Motivation für die Änderung enthält. Außerdem sollte das Ergebnis ihre Implementierung mit dem vorherigen Verhalten des Projekts gegenüber gestellt werden. Dies ist eine gute Richtlinie, an die man sich halten sollte. Es empfiehlt sich außerdem, die Gegenwartsform des Imperativs in diesen Nachrichten zu benutzen. Mit anderen Worten, verwenden Sie Anweisungen. Anstatt „Ich habe Test hinzugefügt für“ oder „Tests hinzufügend für“ benutzen Sie „Füge Tests hinzu für“. Hier ist eine E-Mail-Vorlage, die ursprünglich von Tim Pope geschrieben wurde:

Kurzfassung der Änderung (50 Zeichen oder weniger)

Gegebenenfalls ausführlicher, erläuternder Text. Pro Zeile etwa 72
Zeichen. In einigen Kontexten wird die erste Zeile wie der Betreff einer
E-Mail gehandelt und der Rest des Textes als Textkörper. Die Leerzeile,
welche die Zusammenfassung vom Text trennt ist von entscheidender Bedeutung
(es sei denn, Sie lassen den Textkörper ganz weg). Werkzeuge wie rebase
können durcheinander kommen, wenn Sie diese Trennung nicht einhalten.

Weitere Absätze folgen nach Leerzeilen.

- Aufzählungszeichen sind auch in Ordnung

- In der Regel wird für das Aufzählungszeichen ein Bindestrich oder ein
  Sternchen verwendet, gefolgt von einem Leerzeichen. Zwischen den einzelnen
  Aufzählungen werden Leerzeilen eingefügt. Diese Konventionen variieren
  jedoch.

      Verwenden Sie einen hängenden Einzug

Wenn alle ihre Commit-Nachrichten diesem Modell folgen, wird es für Sie und die Entwickler, mit denen Sie zusammenarbeiten, viel einfacher sein. Das Git-Projekt selber enthält gut formatierte Commit-Nachrichten. Versuchen Sie, git log --no-merges auszuführen, um zu sehen, wie ein gut formatierter Commit-Verlauf des Projekts aussieht.

Note
Tun sie das, was wir sagen und nicht das, was wir tun.

Der Kürze halber haben viele der Beispiele in diesem Buch keine gut formatierten Commit-Nachrichten. Stattdessen verwenden wir einfach die Option -m, um git commit auszuführen.

Kurz gesagt, tun Sie es wie wir es sagen und nicht wie wir es tun.

Kleines, privates Team

Das einfachste Setup, auf das Sie wahrscheinlich stoßen werden, ist ein privates Projekt mit einem oder zwei anderen Entwicklern. Privat bedeutet in diesem Zusammenhang „closed source“ – es ist für die Außenwelt nicht öffentlich zugänglich. Sie und die anderen Entwickler haben alle Schreibzugriff (Push-Zugriff) auf das Repository.

In dieser Umgebung können Sie einem Arbeitsablauf folgen, der dem ähnelt, den Sie mit Subversion oder einem anderen zentralisierten System ausführen würden. Sie haben immer noch die Vorteile von Dingen wie Offline-Commit und ein wesentlich einfacheres Verzweigungs- (engl.branching) und Zusammenführungsmodel (engl. merging), aber der Arbeitsablauf kann sehr ähnlich sein. Hauptunterschied ist, dass das Zusammenführen eher auf der Client-Seite stattfindet als auf dem Server beim Durchführen eines Commits. Mal sehen, wie es aussehen könnte, wenn zwei Entwickler beginnen, mit einem gemeinsam genutzten Repository zusammenzuarbeiten. Der erste Entwickler, John, klont das Repository, nimmt eine Änderung vor und commitet es lokal. (Die Protokollnachrichten wurden in diesen Beispielen durch ... ersetzt, um sie etwas zu verkürzen.)

# John's Machine
$ git clone john@githost:simplegit.git
Cloning into 'simplegit'...
...
$ cd simplegit/
$ vim lib/simplegit.rb
$ git commit -am 'remove invalid default value'
[master 738ee87] remove invalid default value
 1 files changed, 1 insertions(+), 1 deletions(-)

Die zweite Entwicklerin, Jessica, tut dasselbe — sie klont das Repository, ändert etwas und führt einen Commit durch.

# Jessica's Machine
$ git clone jessica@githost:simplegit.git
Cloning into 'simplegit'...
...
$ cd simplegit/
$ vim TODO
$ git commit -am 'add reset task'
[master fbff5bc] add reset task
 1 files changed, 1 insertions(+), 0 deletions(-)

Nun lädt Jessica ihre Änderungen auf den Server hoch. Das funktioniert problemlos:

# Jessica's Machine
$ git push origin master
...
To jessica@githost:simplegit.git
   1edee6b..fbff5bc  master -> master

Die letzte Zeile der obigen Ausgabe zeigt eine nützliche Rückmeldung der Push Operation. Das Grundformat ist <oldref> .. <newref> fromref -> toref, wobei oldref die alte Referenz bedeutet, newref die neue Referenz bedeutet, fromref der Name der lokalen Referenz ist, die übertragen wird, und toref ist der Name der entfernten Referenz, die aktualisiert werden soll. Eine ähnliche Ausgabe finden Sie weiter unten in den Diskussionen. Wenn Sie also ein grundlegendes Verständnis der Bedeutung dieser Angaben haben, dann können Sie die verschiedenen Zustände der Repositorys besser verstehen. Weitere Informationen dazu finden Sie in der Dokumentation für git-push.

Wenn wir mit diesem Beispiel fortfahren, nimmt John einige Änderungen vor, schreibt sie in sein lokales Repository und versucht, sie auf den gleichen Server zu übertragen:

# John's Machine
$ git push origin master
To john@githost:simplegit.git
 ! [rejected]        master -> master (non-fast forward)
error: failed to push some refs to 'john@githost:simplegit.git'

John ist es nicht gestattet, seine Änderungen hochzuladen, weil Jessica vorher ihre hochgeladen hat. Dies ist wichtig zu verstehen, wenn Sie an Subversion gewöhnt sind. Wie Sie sicherlich bemerkt haben, haben die beiden Entwickler nicht dieselbe Datei bearbeitet. Obwohl Subversion eine solche Zusammenführung automatisch auf dem Server durchführt, wenn verschiedene Dateien bearbeitet werden, müssen Sie bei Git die Commits zuerst lokal zusammenführen. Mit anderen Worten, John muss zuerst Jessicas Änderungen abrufen und in seinem lokalen Repository zusammenführen, bevor ihm das Hochladen gestattet wird.

Als ersten Schritt holt John, Jessicas Änderungen (dies holt nur Jessicas Änderungen, diese werden noch nicht mit Johns Änderungen zusammengeführt):

$ git fetch origin
...
From john@githost:simplegit
 + 049d078...fbff5bc master     -> origin/master

Zu diesem Zeitpunkt sieht Johns lokales Repository ungefähr so aus:

Johns abzweigender Verlauf.
Figure 58. Johns abzweigender Verlauf.

Jetzt kann John, Jessicas abgeholte Änderungen, zu seinen eigenen lokalen Änderungen zusammenführen:

$ git merge origin/master
Merge made by the 'recursive' strategy.
 TODO |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

Wenn diese lokale Zusammenführung reibungslos verläuft, sieht der aktualisierte Verlauf von John nun folgendermaßen aus:

Johns Repository nach der Zusammenführung `origin/master`.
Figure 59. Johns Repository nach der Zusammenführung origin/master.

Zu diesem Zeitpunkt möchte John möglicherweise diesen neuen Code testen, um sicherzustellen, dass sich keine der Arbeiten von Jessica auf seine auswirkt. Wenn alles in Ordnung ist, kann er die neu zusammengeführten Änderungen schließlich auf den Server übertragen:

$ git push origin master
...
To john@githost:simplegit.git
   fbff5bc..72bbc59  master -> master

Am Ende sieht Johns Commit-Verlauf so aus:

Johns Verlauf nach Hochladen auf den `origin`-Server.
Figure 60. Johns Verlauf nach Hochladen auf den origin-Server.

In der Zwischenzeit hat Jessica einen neuen Branch mit dem Namen issue54 erstellt und drei Commits auf diesem Branch vorgenommen. Sie hat Johns Änderungen noch nicht abgerufen, daher sieht ihr Commit-Verlauf folgendermaßen aus:

Jessicas Themen Branch.
Figure 61. Jessicas Themen Branch.

Nun erfährt Jessica, dass John einige neue Arbeiten auf den Server geschoben hat und sie möchte sich diese ansehen. Sie kann alle neuen Inhalte von dem Server abrufen über die sie noch nicht verfügt:

# Jessica's Machine
$ git fetch origin
...
From jessica@githost:simplegit
   fbff5bc..72bbc59  master     -> origin/master

Dies zieht die Arbeit herunter (Pull), die John in der Zwischenzeit hochgeladen hat. Jessicas Verlauf sieht jetzt so aus:

Jessicas Verlauf nach dem Abholen von Johns Änderungen.
Figure 62. Jessicas Verlauf nach dem Abholen von Johns Änderungen.

Jessica denkt, dass ihr Themen Branch nun fertig ist. Sie möchte jedoch wissen, welchen Teil von Johns abgerufenen Arbeiten sie in ihre Arbeit einbinden muss, damit sie hochladen kann. Sie führt git log aus, um das herauszufinden:

$ git log --no-merges issue54..origin/master
commit 738ee872852dfaa9d6634e0dea7a324040193016
Author: John Smith <jsmith@example.com>
Date:   Fri May 29 16:01:27 2009 -0700

   remove invalid default value

Die Syntax issue54..origin/master ist ein Logfilter, der Git anweist, nur die Commits anzuzeigen, die sich im letzterem Branch befinden (in diesem Fall origin/master) und nicht im ersten Branch (in diesem Fall issue54). Wir werden diese Syntax in Commit-Bereiche genauer erläutern.

Aus der obigen Ausgabe können wir sehen, dass es einen einzigen Commit gibt, den John gemacht hat, welchen Jessica nicht in ihre lokale Arbeit eingebunden hat. Wenn sie origin/master zusammenführt, ist dies der einzige Commit, der ihre lokale Arbeit verändert.

Jetzt kann Jessica ihre Arbeit in ihrem Master Branch zusammenführen, Johns Arbeit (origin/master) in ihrem master-Branch zusammenführen und dann wieder auf den Server hochladen.

Als erstes wechselt Jessica (nachdem sie alle Änderungen in ihrem Themen Branch issue54 commitet hat) zurück zu ihrem master-Branch, um diese Integration vorzubereiten:

$ git checkout master
Switched to branch 'master'
Your branch is behind 'origin/master' by 2 commits, and can be fast-forwarded.

Jessica kann entweder origin/master oder issue54 mit ihrem lokalem master zusammenführen – beide sind ihrem master vorgelagert. Daher spielt die Reihenfolge keine Rolle. Der finale Schnappschuss sollte unabhängig von der gewählten Reihenfolge identisch sein. Nur der Verlauf wird anders sein. Sie beschließt, zuerst den Branch issue54 zusammenzuführen:

$ git merge issue54
Updating fbff5bc..4af4298
Fast forward
 README           |    1 +
 lib/simplegit.rb |    6 +++++-
 2 files changed, 6 insertions(+), 1 deletions(-)

Es treten keine Probleme auf. Wie Sie sehen, handelte es sich um einen einfachen Schnellvorlauf Zusammenführung (engl. Fast-Forward). Jessica schließt nun den lokalen Zusammenführungsprozess ab, indem sie Johns zuvor abgerufene Arbeit zusammenführt, die sich im Branch origin/master befindet:

$ git merge origin/master
Auto-merging lib/simplegit.rb
Merge made by the 'recursive' strategy.
 lib/simplegit.rb |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

Alles kann sauber zusammengeführt werden. Jessicas Verlauf sieht nun so aus:

Jessicas Verlauf nach Zusammenführung mit Johns Änderungen.
Figure 63. Jessicas Verlauf nach Zusammenführung mit Johns Änderungen.

Jetzt ist origin/master über Jessicas master-Branch erreichbar, sodass sie erfolgreich pushen kann (vorausgesetzt, John hat in der Zwischenzeit keine weiteren Änderungen hochgeladen):

$ git push origin master
...
To jessica@githost:simplegit.git
   72bbc59..8059c15  master -> master

Jeder Entwickler hat einige Commits durchgeführt und die Arbeit des jeweils anderen erfolgreich zusammengeführt.

Jessicas Verlauf nach hochladen aller Änderungen auf den Server.
Figure 64. Jessicas Verlauf nach hochladen aller Änderungen auf den Server.

Das ist einer der einfachsten Arbeitsabläufe. Sie arbeiten eine Weile (in der Regel in einem Themen Branch) und führen diese Arbeiten in Ihrem Branch master zusammen, sobald sie für die Integration bereit sind. Wenn Sie diese Arbeit teilen möchten, rufen Sie Ihren master von origin/master ab und führen ihn zusammen, falls er sich geändert hat. Anschließend pushen sie ihn in den master Branch auf dem Server. Die allgemeine Reihenfolge sieht in etwa so aus:

  1. Allgemeine Abfolge von Ereignissen für einen einfachen Arbeitsablauf mit mehreren Entwicklern. image::images/small-team-flow.png[Allgemeine Abfolge von Ereignissen für einen einfachen Arbeitsablauf mit mehreren Entwicklern.]

Geführtes, privates Team

In diesem Szenario sehen Sie sich die Rollen der Mitwirkenden in einem größeren, geschlossenen Team an. Sie lernen, wie Sie in einer Umgebung arbeiten, in der kleine Gruppen an der Entwicklung einzelner Funktonen zusammenarbeiten. Anschließend werden diese teambasierten Beiträge von einem anderen Beteiligten integriert.

Nehmen wir an, John und Jessica arbeiten gemeinsam an einem Feature (nennen wir dieses FeatureA), während Jessica und Josie, eine dritte Entwicklerin, an einem zweiten Feature arbeiten (sagen wir FeatureB). In diesem Fall verwendet das Unternehmen einen Arbeitsablauf mit Integrationsmanager. Bei diesem kann die Arbeit der einzelnen Gruppen nur von bestimmten Beteiligten integriert werden. Der Master-Branch des Haupt Repositorys kann nur von diesen Beteiligten aktualisiert werden kann. In diesem Szenario werden alle Arbeiten in teambasierten Branches ausgeführt und später vom Integrationsmanager zusammengeführt.

Folgen wir Jessicas Arbeitsablauf, während sie an ihren beiden Features tätig ist und parallel mit zwei verschiedenen Entwicklern in dieser Umgebung arbeitet. Wir nehmen an, sie hat ihr Repository bereits geklont. Zuerst beschließt sie an featureA zu arbeiten. Sie erstellt einen neuen Branch für das Feature und führt dort einige Änderungen aus:

# Jessica's Machine
$ git checkout -b featureA
Switched to a new branch 'featureA'
$ vim lib/simplegit.rb
$ git commit -am 'add limit to log function'
[featureA 3300904] add limit to log function
 1 files changed, 1 insertions(+), 1 deletions(-)

Zu diesem Zeitpunkt muss sie ihre Arbeit mit John teilen, also lädt sie ihre featureA Branch Commits auf den Server hoch. Jessica hat keinen Push-Zugriff auf den master Branch, nur die Integrationsmanager haben das. Sie muss daher auf einen anderen Branch hochladen, um mit John zusammenzuarbeiten:

$ git push -u origin featureA
...
To jessica@githost:simplegit.git
 * [new branch]      featureA -> featureA

Jessica schickt John eine E-Mail, um ihm mitzuteilen, dass sie einige Arbeiten in einen Branch mit dem Namen featureA hochgeladen hat. Er kann sie sich jetzt ansehen. Während sie auf Rückmeldung von John wartet, beschließt Jessica, mit Josie an featureB zu arbeiten. Zunächst startet sie einen neuen Feature-Branch, der auf dem master Branch des Servers basiert:

# Jessica's Machine
$ git fetch origin
$ git checkout -b featureB origin/master
Switched to a new branch 'featureB'

Jetzt macht Jessica ein paar Commits auf dem Branch featureB:

$ vim lib/simplegit.rb
$ git commit -am 'made the ls-tree function recursive'
[featureB e5b0fdc] made the ls-tree function recursive
 1 files changed, 1 insertions(+), 1 deletions(-)
$ vim lib/simplegit.rb
$ git commit -am 'add ls-files'
[featureB 8512791] add ls-files
 1 files changed, 5 insertions(+), 0 deletions(-)

Jessicas Repository sieht nun folgendermaßen aus:

Jessicas initialer Commit Verlauf.
Figure 65. Jessicas initialer Commit Verlauf.

Sie ist bereit, ihre Arbeit hochzuladen, erhält jedoch eine E-Mail von Josie, dass ein Branch mit einigen anfänglichen featureB Aufgaben bereits als featureBee Branch auf den Server übertragen wurde. Jessica muss diese Änderungen mit ihren eigenen zusammenführen, bevor sie ihre Arbeit auf den Server übertragen kann. Jessica holt sich zuerst Josies Änderungen mit git fetch:

$ git fetch origin
...
From jessica@githost:simplegit
 * [new branch]      featureBee -> origin/featureBee

Angenommen Jessica befindet sich noch in ihrem ausgecheckten featureB Branch. Dann kann sie nun Josies Arbeit mit git merge in diesen Branch zusammenführen:

$ git merge origin/featureBee
Auto-merging lib/simplegit.rb
Merge made by the 'recursive' strategy.
 lib/simplegit.rb |    4 ++++
 1 files changed, 4 insertions(+), 0 deletions(-)

Nun möchte Jessica die gesamte zusammengeführte Arbeit an featureB zurück auf den Server übertragen. Jedoch möchte sie nicht einfach ihren eigenen Branch featureB übertragen. Da Josie bereits einen Upstream Branch featureBee gestartet hat, möchte Jessica auf diesen Zweig hochladen, was sie auch folgendermaßen tut:

$ git push -u origin featureB:featureBee
...
To jessica@githost:simplegit.git
   fba9af8..cd685d1  featureB -> featureBee

Dies wird als refspec bezeichnet. Unter Die Referenzspezifikation (engl. Refspec) finden Sie eine detailliertere Beschreibung der Git-Refspecs und der verschiedenen Möglichkeiten, die Sie damit haben. Beachten Sie auch die -u Option. Dies ist die Abkürzung für --set-upstream, mit der die Branches so konfiguriert werden, dass sie später leichter gepusht und gepullt werden können.

Als nächstes erhält Jessica eine E-Mail von John, der ihr mitteilt, dass er einige Änderungen am Branch featureA vorgenommen hat, an dem sie zusammenarbeiten. Er bittet Jessica, sie sich anzusehen. Wieder führt Jessica ein einfaches git fetch durch, um alle neue Inhalte vom Server abzurufen, einschließlich (natürlich) Johns neuester Arbeit:

$ git fetch origin
...
From jessica@githost:simplegit
   3300904..aad881d  featureA   -> origin/featureA

Jessica kann sich Johns neue Arbeit ansehen, indem sie den Inhalt des neu abgerufenen Branches featureA mit ihrer lokalen Kopie desselben Branches vergleicht:

$ git log featureA..origin/featureA
commit aad881d154acdaeb2b6b18ea0e827ed8a6d671e6
Author: John Smith <jsmith@example.com>
Date:   Fri May 29 19:57:33 2009 -0700

    changed log output to 30 from 25

Wenn ihr Johns neue Arbeit gefällt, kann sie sie mit ihrem lokalen Branch featureA zusammenführen:

$ git checkout featureA
Switched to branch 'featureA'
$ git merge origin/featureA
Updating 3300904..aad881d
Fast forward
 lib/simplegit.rb |   10 +++++++++-
1 files changed, 9 insertions(+), 1 deletions(-)

Schließlich möchte Jessica noch ein paar geringfügige Änderungen an dem gesamten, zusammengeführten Inhalt vornehmen. Sie kann diese Änderungen vornehmen, sie in ihren lokalen Branch featureA comitten und das Endergebnis zurück auf den Server übertragen.

$ git commit -am 'small tweak'
[featureA 774b3ed] small tweak
 1 files changed, 1 insertions(+), 1 deletions(-)
$ git push
...
To jessica@githost:simplegit.git
   3300904..774b3ed  featureA -> featureA

Jessicas commit Verlauf sieht nun in etwa so aus:

Jessicas Verlauf nach committen auf einem Feature Branch.
Figure 66. Jessicas Verlauf nach committen auf einem Feature Branch.

Irgendwann informieren Jessica, Josie und John die Integratoren, dass die Branches featureA und featureBee auf dem Server für die Integration in die Hauptlinie bereit sind. Nachdem die Integratoren diese Branches in der Hauptlinie zusammengeführt haben, holt ein Abruf den neuen Zusammenführungs-Commit ab, sodass der Verlauf wie folgt aussieht:

Jessicas Verlauf nach Zusammenführung ihrer beiden Themen Branches.
Figure 67. Jessicas Verlauf nach Zusammenführung ihrer beiden Themen Branches.

Viele Teams wechseln zu Git, da sie parallel arbeiten können und die verschiedenen Entwicklungslinien zu einem späteren Zeitpunkt zusammengeführt werden können. Ein großer Vorteil von Git besteht darin, dass man in kleinen Untergruppen eines Teams über entfernte Branches zusammenarbeiten kann, ohne notwendigerweise das gesamte Team zu involvieren oder zu behindern. Die Vorgehensweise dieses Arbeitsablaufes, sieht in etwa so aus:

Grundlegende Vorgehensweise bei einem geführten Team.
Figure 68. Grundlegende Vorgehensweise bei einem geführten Team.

Verteiltes, öffentliches Projekt

An öffentlichen Projekten mitzuwirken ist ein wenig anders. Da Sie nicht die Berechtigung haben, Branches im Projekt direkt zu aktualisieren, müssen Sie ihre Arbeit auf andere Weise an die Projektbetreuer weiterleiten. In diesem ersten Beispiel wird beschrieben, wie Sie auf Git Hosts, die einfaches „Forking“ unterstützen, via „Forking“ mitwirken können. Viele Hosting-Sites unterstützen dies (einschließlich GitHub, BitBucket, repo.or.cz und andere) und viele Projektbetreuer erwarten diese Art der Mitarbeit. Der nächste Abschnitt befasst sich mit Projekten, die bereitgestellte Patches bevorzugt per E-Mail akzeptieren.

Zunächst möchten Sie wahrscheinlich das Hauptrepository klonen, einen Branch für den Patch oder die Patch Serien erstellen, die Sie beisteuern möchten, und dort Ihre Arbeit erledigen. Der Prozess sieht im Grunde so aus:

$ git clone <url>
$ cd project
$ git checkout -b featureA
  ... work ...
$ git commit
  ... work ...
$ git commit
Note

Sie können rebase -i verwenden, um Ihre Arbeit auf ein einzelnes Commit zu reduzieren. Sie können auch die Änderungen in den Commits neu anordnen, damit der Betreuer den Patch einfacher überprüfen kann – siehe Rewriting History für weitere Informationen zum interaktiven Rebasing.

Wenn Ihre Arbeit am Branch abgeschlossen ist und Sie bereit sind, sie an die Betreuer weiterzuleiten, wechseln Sie zur ursprünglichen Projektseite. Dort klicken Sie auf die Schaltfläche Fork, um Ihren eigenen schreibbaren Fork des Projekts zu erstellen. Anschließend müssen Sie diese Repository-URL als neue Remote-Adresse Ihres lokalen Repositorys hinzufügen. Nennen wir es in diesem Beispiel myfork:

$ git remote add myfork <url>

Anschließend müssen Sie Ihre neue Arbeit in dieses Repository hochladen. Es ist am einfachsten, den Branch, an dem Sie arbeiten, in Ihr geforktes Repository hochzuladen, anstatt diese Arbeit in Ihrem Master-Branch zusammenzuführen und diesen hochzuladen. Der Grund dafür ist, dass Sie Ihren master-Branch nicht zurücksetzen müssen, wenn Ihre Arbeit nicht akzeptiert bzw. nur teilweise ausgewählt (cherry-pick) wurde (die Git-Operation zum cherry-pick wird ausführlicher in Rebasing und Cherry-Picking Workflows behandelt). Wenn die Betreuer Ihre Arbeit per mergen, rebase oder cherry-pick übernehmen, erhalten Sie ihre Arbeit sowieso zurück, wenn Sie aus dem Repository der Betreuer pullen.

Auf jedem Fall können Sie Ihre Arbeit hochladen mit:

$ git push -u myfork featureA

Sobald Ihre Arbeit an Ihren Fork des Repositorys hochgeladen wurde, müssen Sie den Betreuern des ursprünglichen Projekts mitteilen, dass Sie Änderungen haben, die sie zusammenführen möchten. Dies wird oft als Pull Request bezeichnet. Sie generieren eine solche Anfrage entweder über die Website – GitHub hat einen eigenen „Pull-Request-Mechanismus“, den wir in GitHub behandeln werden, oder Sie können den Befehl git request-pull ausführen und die nachfolgende Ausgabe manuell per E-Mail an den Projektbetreuer senden.

Der Befehl git request-pull verwendet den Basis Branch, in den Ihr Themen Branch abgelegt werden soll. Außerdem wird die Git-Repository-URL angegeben aus dem er gezogen werden soll. Er erstellt damit eine Zusammenfassung aller Änderungen, um deren Übernahme Sie bitten. Wenn bspw. Jessica an John eine Pull Request senden möchte und sie zwei Commits für den gerade hochgeladenen Themen Branch ausgeführt hat, kann sie folgendes ausführen:

$ git request-pull origin/master myfork
The following changes since commit 1edee6b1d61823a2de3b09c160d7080b8d1b3a40:
Jessica Smith (1):
        added a new function

are available in the git repository at:

  git://githost/simplegit.git featureA

Jessica Smith (2):
      add limit to log function
      change log output to 30 from 25

 lib/simplegit.rb |   10 +++++++++-
 1 files changed, 9 insertions(+), 1 deletions(-)

Diese Ausgabe kann an den Betreuer gesendet werden. Sie teilt ihm mit, von wo die Arbeit gebranched wurde, fasst die Commits zusammen und gibt an, von wo die neue Arbeit abgerufen werden soll.

Bei einem Projekt, für das Sie nicht der Betreuer sind, ist es im Allgemeinen einfacher, einen Branch wie master zu haben, der immer origin/master verfolgt. Ihre Arbeit können Sie dann in Themen Branches erledigen, die Sie einfach verwerfen können, wenn ihre Änderungen abgelehnt werden. Durch das Isolieren von Änderungen in Themen Branches wird es für Sie auch einfacher, Ihre Arbeit neu zu strukturieren. Falls sich das Haupt-Repositorys in der Zwischenzeit weiter entwickelt hat und Ihre Commits nicht mehr sauber angewendet werden können. Wenn Sie beispielsweise ein zweites Thema an das Projekt senden möchten, arbeiten Sie nicht weiter an dem Branch, den Sie gerade hochgeladen haben. Beginnen Sie erneut im master Branch des Haupt-Repositorys:

$ git checkout -b featureB origin/master
  ... work ...
$ git commit
$ git push myfork featureB
$ git request-pull origin/master myfork
  ... email generated request pull to maintainer ...
$ git fetch origin

Jetzt ist jedes Ihrer Themen in einer Art Silo enthalten, ähnlich wie bei einer Patch-Warteschlange. Dieses können Sie umarbeiten, zurücksetzen oder ändern, ohne dass die Themen sich gegenseitig stören oder voneinander abhängig sind.

Initialer Commit Verlauf mit `featureB` Änderungen.
Figure 69. Initialer Commit Verlauf mit featureB Änderungen.

Nehmen wir an, der Projektbetreuer hat eine Reihe weiterer Patches übernommen und Ihren ersten Branch einfließen lassen, der jedoch nicht mehr ordnungsgemäß zusammengeführt werden kann. In diesem Fall können Sie versuchen, diesen Branch auf ‘origin/master’ zu reorganisieren, die Konflikte für den Betreuer zu lösen und Ihre Änderungen erneut zu übermitteln:

$ git checkout featureA
$ git rebase origin/master
$ git push -f myfork featureA

Dadurch wird Ihr Verlauf so umgeschrieben, sodass er jetzt folgendermaßen aussieht Commit Verlauf nach featureA Änderungen..

Commit Verlauf nach `featureA` Änderungen.
Figure 70. Commit Verlauf nach featureA Änderungen.

Da Sie den Branch reorganisiert haben, müssen Sie das -f für Ihren Push-Befehl angeben, um den featureA Branch auf dem Server mit einen Commit ersetzen zu können, der nicht vom gegenwärtig letzten Commit des entfernten Branches abstammt. Eine Alternative wäre, diese neue Arbeit in einen anderen Branch auf dem Server hochzuladen (beispielsweise als featureAv2).

Schauen wir uns ein weiteres mögliches Szenario an: Der Betreuer hat sich die Arbeit in Ihrem zweiten Branch angesehen und mag ihr Konzept, möchte aber, dass Sie ein Implementierungsdetail ändern. Sie nutzen diese Gelegenheit, um Ihre Änderungen zu verschieben, damit diese auf dem aktuellen master Branch des Projektes basieren. Sie starten einen neuen Branch, der auf den aktuellen Branch origin/master basiert und fassen die Änderungen an featureB dort zusammen. Dabei lösen sie etwaige Konflikte, machen die Implementierungsänderungen und laden diese Arbeiten als neuen Branch hoch:

$ git checkout -b featureBv2 origin/master
$ git merge --squash featureB
  ... change implementation ...
$ git commit
$ git push myfork featureBv2

Mit der Option --squash wird die gesamte Arbeit an dem zusammengeführten Branch in einen Änderungssatz komprimiert. Dadurch wird ein Repository-Status erzeugt, als ob eine echter Commit stattgefunden hätte, ohne dass tatsächlich ein Merge-Commit durchgeführt wurde. Dies bedeutet, dass Ihr zukünftiger Commit nur einen übergeordneten Vorgänger hat. Das erlaubt ihnen alle Änderungen aus einem anderen Branch einzuführen und weitere Änderungen vorzunehmen, bevor Sie den neuen Commit aufnehmen. Auch die Option --no-commit kann nützlich sein, um den Merge-Commit im Falle des Standard-Merge-Prozesses zu verzögern.

Nun können Sie den Betreuer darüber informieren, dass Sie die angeforderten Änderungen vorgenommen haben und dass er diese Änderungen in Ihrem Branch featureBv2 findet.

Commit Verlauf nach getaner `featureBv2` Arbeit.
Figure 71. Commit Verlauf nach getaner featureBv2 Arbeit.

Öffentliche Projekte via Email

Viele Projekte haben fest definierte Prozesse, um Änderungen entgegenzunehmen. Sie müssen die spezifischen Regeln dieser Projekte kennen, da sie sich oft unterscheiden. Da es viele alte und große Projekte gibt, die Änderungen über eine Entwickler-Mailingliste akzeptieren, werden wir jetzt solch ein Beispiel durchgehen.

Der Workflow ähnelt dem vorherigen Anwendungsfall: Sie erstellen Themen Branches für jede Patch Serie, an der Sie arbeiten. Der Unterschied besteht darin, wie Sie diese Änderungen an das Projekt senden. Anstatt das Projekt zu forken und auf Ihr eigenes geforktes Repository hochzuladen, generieren Sie E-Mail-Versionen jeder Commit-Serie und senden diese per E-Mail an die Entwickler-Mailingliste:

$ git checkout -b topicA
  ... work ...
$ git commit
  ... work ...
$ git commit

Jetzt haben Sie zwei Commits, die Sie an die Mailingliste senden können. Sie verwenden git format-patch, um die mbox-formatierten Dateien zu generieren, die Sie anschließend per E-Mail an die Mailingliste senden. Dabei wird jedes Commit in eine E-Mail-Nachricht umgewandelt. Die erste Zeile der Commit-Nachricht wird als Betreff verwendet. Der Rest der Commit-Nachricht plus den Patch, den der Commit einführt wird als Mail-Körper verwendet. Der Vorteil daran ist, dass durch das Anwenden eines Patches aus einer mit format-patch erstellten E-Mail alle Commit-Informationen ordnungsgemäß erhalten bleiben.

$ git format-patch -M origin/master
0001-add-limit-to-log-function.patch
0002-changed-log-output-to-30-from-25.patch

Der Befehl format-patch gibt die Namen der von ihm erstellten Patch-Dateien aus. Die -M Option weist Git an, nach Umbenennungen zu suchen. Die Dateien sehen am Ende folgendermaßen aus:

$ cat 0001-add-limit-to-log-function.patch
From 330090432754092d704da8e76ca5c05c198e71a8 Mon Sep 17 00:00:00 2001
From: Jessica Smith <jessica@example.com>
Date: Sun, 6 Apr 2008 10:17:23 -0700
Subject: [PATCH 1/2] add limit to log function

Limit log functionality to the first 20

---
 lib/simplegit.rb |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/lib/simplegit.rb b/lib/simplegit.rb
index 76f47bc..f9815f1 100644
--- a/lib/simplegit.rb
+++ b/lib/simplegit.rb
@@ -14,7 +14,7 @@ class SimpleGit
   end

   def log(treeish = 'master')
-    command("git log #{treeish}")
+    command("git log -n 20 #{treeish}")
   end

   def ls_tree(treeish = 'master')
--
2.1.0

Sie können diese Patch-Dateien auch bearbeiten, um weitere Informationen für die E-Mail-Liste hinzuzufügen, die nicht in der Commit-Nachricht angezeigt werden sollen. Wenn Sie Text zwischen der Zeile --- und dem Beginn des Patches (der Zeile diff --git) einfügen, können die Entwickler diesen Text lesen. Der Inhalt wird jedoch vom Patch-Vorgang ignoriert.

Um dies nun per E-Mail an eine Mailingliste zu senden, können Sie die Datei entweder an eine Mail anhängen oder über ein Befehlszeilenprogramm direkt versenden. Das Einfügen von Text führt häufig zu Formatierungsproblemen, insbesondere bei „intelligenten“ Clients, bei denen Zeilenumbrüche und andere Leerzeichen nicht ordnungsgemäß beibehalten werden. Glücklicherweise bietet Git ein Tool, mit dem Sie ordnungsgemäß formatierte Patches über IMAP senden können, was einfacher für Sie sein könnte. Wir zeigen Ihnen, wie Sie einen Patch über Google Mail senden. Dies ist der E-Mail-Agent, mit dem wur uns am besten auskennen. Detaillierte Anweisungen für eine Reihe von anderen Mail-Programmen finden Sie am Ende der oben genannten Datei Documentation/SubmittingPatches im Git-Quellcode.

Zuerst müssen Sie den Abschnitt imap in Ihrer ~/.gitconfig Datei einrichten. Sie können jeden Wert separat mit einer Reihe von git config Befehlen festlegen oder manuell hinzufügen. Am Ende sollte Ihre Konfigurationsdatei ungefähr so aussehen:

[imap]
  folder = "[Gmail]/Entwürfe"
  host = imaps://imap.gmail.com
  user = user@gmail.com
  pass = YX]8g76G_2^sFbd
  port = 993
  sslverify = false

Wenn Ihr IMAP-Server kein SSL verwendet, sind die letzten beiden Zeilen wahrscheinlich nicht erforderlich. Der Hostwert lautet dann imap:// anstelle von imaps://. Wenn dies eingerichtet ist, können Sie git imap-send verwenden, um die Patch-Reihe im Ordner Entwürfe des angegebenen IMAP-Servers abzulegen:

$ cat *.patch |git imap-send
Resolving imap.gmail.com... ok
Connecting to [74.125.142.109]:993... ok
Logging in...
sending 2 messages
100% (2/2) done

Zu diesem Zeitpunkt sollten Sie in der Lage sein, in Ihren Entwurfsordner zu wechseln und dort das Feld An der generierten Email in die Mailinglist-Adresse zu ändern, an die Sie den Patch senden wollen. Möglicherweise wollen sie auch den Betreuer oder die Person in Kopie nehmen, die für diesen Abschnitt verantwortlich ist. Anschließend können sie die Mail versenden.

Sie können die Patches auch über einen SMTP-Server senden. Wie zuvor können Sie jeden Wert separat mit einer Reihe von git config Befehlen festlegen oder manuell im Abschnitt sendemail in Ihrer ~/.gitconfig Datei hinzufügen:

[sendemail]
  smtpencryption = tls
  smtpserver = smtp.gmail.com
  smtpuser = user@gmail.com
  smtpserverport = 587

Danach können Sie Ihre Patches mit git send-email versenden:

$ git send-email *.patch
0001-added-limit-to-log-function.patch
0002-changed-log-output-to-30-from-25.patch
Who should the emails appear to be from? [Jessica Smith <jessica@example.com>]
Emails will be sent from: Jessica Smith <jessica@example.com>
Who should the emails be sent to? jessica@example.com
Message-ID to be used as In-Reply-To for the first email? y

Git gibt anschließend für jeden Patch, den Sie versenden, eine Reihe von Protokollinformationen aus, die in etwa so aussehen:

(mbox) Adding cc: Jessica Smith <jessica@example.com> from
  \line 'From: Jessica Smith <jessica@example.com>'
OK. Log says:
Sendmail: /usr/sbin/sendmail -i jessica@example.com
From: Jessica Smith <jessica@example.com>
To: jessica@example.com
Subject: [PATCH 1/2] added limit to log function
Date: Sat, 30 May 2009 13:29:15 -0700
Message-Id: <1243715356-61726-1-git-send-email-jessica@example.com>
X-Mailer: git-send-email 1.6.2.rc1.20.g8c5b.dirty
In-Reply-To: <y>
References: <y>

Result: OK

Zusammenfassung

In diesem Abschnitt wurden eine Reihe gängiger Arbeitsabläufe für den Umgang mit verschiedenen Arten von Git-Projekten behandelt. Außerdem wurden einige neue Tools vorgestellt, die Sie bei der Verwaltung dieser Prozesse unterstützen. Als Nächstes erfahren Sie, wie Sie auf der anderen Seite arbeiten: als Verwalter (Maintainer) eines Git-Projektes. Sie lernen, wie man sich als wohlwollender Diktator oder Integrationsmanager korrekt arbeitet.