-
1. Erste Schritte
-
2. Git Grundlagen
-
3. Git Branching
- 3.1 Branches auf einen Blick
- 3.2 Einfaches Branching und Merging
- 3.3 Branch-Management
- 3.4 Branching-Workflows
- 3.5 Remote-Branches
- 3.6 Rebasing
- 3.7 Zusammenfassung
-
4. Git auf dem Server
- 4.1 Die Protokolle
- 4.2 Git auf einem Server einrichten
- 4.3 Erstellung eines SSH-Public-Keys
- 4.4 Einrichten des Servers
- 4.5 Git-Daemon
- 4.6 Smart HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 Von Drittanbietern gehostete Optionen
- 4.10 Zusammenfassung
-
5. Verteiltes Git
-
6. GitHub
-
7. Git Tools
- 7.1 Revisions-Auswahl
- 7.2 Interaktives Stagen
- 7.3 Stashen und Bereinigen
- 7.4 Deine Arbeit signieren
- 7.5 Suchen
- 7.6 Den Verlauf umschreiben
- 7.7 Reset entzaubert
- 7.8 Fortgeschrittenes Merging
- 7.9 Rerere
- 7.10 Debuggen mit Git
- 7.11 Submodule
- 7.12 Bundling
- 7.13 Replace (Ersetzen)
- 7.14 Anmeldeinformationen speichern
- 7.15 Zusammenfassung
-
8. Git einrichten
- 8.1 Git Konfiguration
- 8.2 Git-Attribute
- 8.3 Git Hooks
- 8.4 Beispiel für Git-forcierte Regeln
- 8.5 Zusammenfassung
-
9. Git und andere VCS-Systeme
- 9.1 Git als Client
- 9.2 Migration zu Git
- 9.3 Zusammenfassung
-
10. Git Interna
-
A1. Anhang A: Git in anderen Umgebungen
- A1.1 Grafische Schnittstellen
- A1.2 Git in Visual Studio
- A1.3 Git in Visual Studio Code
- A1.4 Git in IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMine
- A1.5 Git in Sublime Text
- A1.6 Git in Bash
- A1.7 Git in Zsh
- A1.8 Git in PowerShell
- A1.9 Zusammenfassung
-
A2. Anhang B: Git in deine Anwendungen einbetten
- A2.1 Die Git-Kommandozeile
- A2.2 Libgit2
- A2.3 JGit
- A2.4 go-git
- A2.5 Dulwich
-
A3. Anhang C: Git Kommandos
- A3.1 Setup und Konfiguration
- A3.2 Projekte importieren und erstellen
- A3.3 Einfache Snapshot-Funktionen
- A3.4 Branching und Merging
- A3.5 Projekte gemeinsam nutzen und aktualisieren
- A3.6 Kontrollieren und Vergleichen
- A3.7 Debugging
- A3.8 Patchen bzw. Fehlerkorrektur
- A3.9 E-mails
- A3.10 Externe Systeme
- A3.11 Administration
- A3.12 Basisbefehle
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 tun könnte. Da Git sehr flexibel ist, können die Beteiligten auf viele Arten zusammenarbeiten. Es ist nicht einfach zu beschreiben, wie du dazu beitragen solltest, da jedes Projekt ein wenig anders ist. Einige der wichtigen Unbekannten sind die Anzahl der aktiven Mitwirkenden, der ausgewählte Arbeitsablauf, deine 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 hast du zwei oder drei Entwickler mit ein paar Commits pro Tag oder möglicherweise weniger für etwas schlummernde 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 du mit einer wachsenden Anzahl von Entwicklern auch sicherstellen musst, dass sich der Code problemlos anwenden lässt und leicht zusammengeführt werden kann. Von dir übermittelte Änderungen können durch Arbeiten, die während deiner Arbeit oder während deiner Änderungen genehmigt oder eingearbeitet wurden, veraltet sein oder nicht mehr funktionieren. Wie kannst du deinen Quelltext konsistent auf dem neuesten Stand halten und dafür sorgen, dass deine 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? Bist du selbst in diesen Prozess involviert? Ist ein Leutnant System vorhanden und musst du deine Arbeit zuerst bei diesen einreichen?
Die nächste Unbekannte ist deine Zugriffsberechtigung. Der erforderliche Arbeitsablauf, um zu einem Projekt beizutragen, unterscheidet sich erheblich, wenn du Schreibzugriff auf das Projekt hast, als wenn du diesen nicht hast. Wenn du keinen Schreibzugriff hast, in welcher Reihenfolge erfolgt die Annahme von beigetragener Arbeit? Gibt es überhaupt eine Richtlinie? Wie umfangreich sind die Änderungen, die du jeweils beisteuerst? Wie oft trägst du etwas bei?
All diese Fragen können sich darauf auswirken, wie du 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. Du solltest in der Lage sein, anhand dieser Beispiele die spezifischen Arbeitsabläufe zu erstellen, die du in der Praxis benötigst.
Richtlinien zur Zusammenführung (engl. Commits)
Bevor wir uns mit den spezifischen Anwendungsfällen befassen, findest du hier einen kurzen Hinweis zu Commit-Nachrichten.
Ein guter Leitfaden zum Erstellen von Commits und das Befolgen desselben, 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. Du findest diese Tipps im Git-Quellcode in der Datei Documentation/SubmittingPatches
.
Deine Einsendungen sollten keine Leerzeichenfehler enthalten.
Git bietet eine einfache Möglichkeit, dies zu überprüfen. Führe vor dem Commit git diff --check
aus, um mögliche Leerzeichenfehler zu identifizieren und diese aufzulisten.
git diff --check
Wenn du diesen Befehl vor einem Commit ausführst, kannst du feststellen, ob Leerzeichen Probleme auftreten, die andere Entwickler stören könnten.
Als Nächstes, versuchst du, aus jedem Commit einen logisch getrennten Satz von Änderungen zu machen.
Versuche deine Änderungen leicht verständlich zu machen. Arbeiten nicht ein ganzes Wochenende an fünf verschiedenen Features und übermittel all diese Änderungen in einem massiven Commit am Montag.
Auch wenn du am Wochenende keine Commits durchführst, nutze am Montag die Staging-Area, um deine Änderungen aufzuteilen in wenigstens einen Commit für jeden Teilaspekt mit jeweils einer sinnvollen Nachricht.
Wenn einige der Änderungen dieselbe Datei modifizieren, benutze die Anweisung git add --patch
, um Dateien partiell zur Staging-Area hinzuzufügen (detailliert dargestellt im Abschnitt Interaktives Stagen).
Der Schnappschuss vom Projekt am Ende des Branches ist der Selbe, ob du einen oder fünf Commits durchgeführt hast, solange nur all die Änderungen irgendwann hinzugefügt werden. Versuche also, die Dinge für deine Entwicklerkollegen zu vereinfachen, die deine Ä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 sein sollte. Den Verlauf umschreiben beschreibt eine Reihe nützlicher Git-Tricks zum Umschreiben des Verlaufs oder um interaktiv Dateien zur Staging-Area hinzuzufügen. Verwende diese Werkzeuge, um einen sauberen und leicht verständlichen Verlauf aufzubauen, bevor du deine Arbeit jemand anderem schickst.
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 sollte deine Nachrichten mit einer einzelnen Zeile beginnen, die nicht länger als 50 Zeichen ist. Diese sollte deine Ä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 deine Motivation für die Änderung enthält. Außerdem sollte das Ergebnis deiner 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, verwende Anweisungen. Anstatt „Ich habe Test hinzugefügt für“ oder „Tests hinzufügend für“ benutze „Füge Tests hinzu für“. Hier ist eine Vorlage, die du nutzen kannst. Wir haben sie leicht angepasst. Das Original findest du hier: 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, du lässt den Textkörper ganz weg). Werkzeuge wie rebase
können Fehler machen, wenn du diese Leerzeile nicht einhältst.
Schreiben deine Commit Nachrichten im Imperativ: "Fix bug" und nicht
"Fixed bug" oder "Fixes bug". Diese Konvention stimmt mit Commit-Nachrichten
überein, die von Befehlen wie git merge und git revert generiert werden.
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.
Verwende einen hängenden Einzug
Wenn alle deine Commit-Nachrichten diesem Modell folgen, wird es für dich und die Entwickler, mit denen du zusammenarbeitest, viel einfacher sein.
Das Git-Projekt selber enthält gut formatierte Commit-Nachrichten. Versuche, git log --no-merges
auszuführen, um zu sehen, wie ein gut formatierter Commit-Verlauf des Projekts aussieht.
Anmerkung
|
Tue 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 Kurz gesagt, tue es wie wir es sagen und nicht wie wir es tun. |
Kleines, privates Team
Das einfachste Setup, auf das du wahrscheinlich stoßen wirst, 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. Du und die anderen Entwickler haben alle Schreibzugriff (Push-Zugriff) auf das Repository.
In dieser Umgebung kannst du einem Arbeitsablauf folgen, der dem ähnelt, den du mit Subversion oder einem anderen zentralisierten System ausführen würdest.
Du hast 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 findst du weiter unten in den Diskussionen. Wenn du also ein grundlegendes Verständnis der Bedeutung dieser Angaben hast, dann kannst du die verschiedenen Zustände der Repositorys besser verstehen.
Weitere Informationen dazu findest du 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 du an Subversion gewöhnt bist. Wie du sicherlich bemerkt hast, 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, musst du 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:
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:
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:
origin
ServerIn 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:
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:
Jessica denkt, dass ihr Feature 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 FeatureBranch 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 du siehst, handelte es sich um eine einfache 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:
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.
Das ist einer der einfachsten Arbeitsabläufe.
Du arbeitest eine Weile (in der Regel in einem Feature Branch) und führst diese Arbeiten in deinem Branch master
zusammen, sobald sie für die Integration bereit sind.
Wenn du diese Arbeit teilen möchtest, rufst du deinen master
von origin/master
ab und führst ihn zusammen, falls er sich geändert hat. Anschließend pushst du ihn in den master
Branch auf dem Server.
Die allgemeine Reihenfolge sieht in etwa so aus:
Geführtes, privates Team
In diesem Szenario sehen wir uns die Rollen der Mitwirkenden in einem größeren, geschlossenen Team an. Du lernst, wie Du in einer Umgebung arbeitest, in der kleine Gruppen an der Entwicklung einzelner Funktionen 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 'Make ls-tree function recursive'
[featureB e5b0fdc] Make 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:
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 Branch 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) findest du eine detailliertere Beschreibung der Git-Refspecs und der verschiedenen Möglichkeiten, die du damit hast.
Beachte 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
Increase 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, indem sie in ihren lokalen Branch featureA
comittet und das Endergebnis zurück auf den Server überträgt (pusht):
$ git commit -am 'Add small tweak to merged content'
[featureA 774b3ed] Add small tweak to merged content
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:
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:
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:
Verteiltes, öffentliches Projekt
An öffentlichen Projekten mitzuwirken ist ein wenig anders. Da du nicht die Berechtigung hast, Branches im Projekt direkt zu aktualisieren, musst du deine Arbeit auf andere Weise an die Projektbetreuer weiterleiten. In diesem ersten Beispiel wird beschrieben, wie du auf Git Hosts, die einfaches „Forking“ unterstützen, via „Forking“ mitwirken kannst. 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öchtest du wahrscheinlich das Hauptrepository klonen, einen Branch für den Patch oder die Patch Serien erstellen, die du beisteuern möchtest, und dort deine Arbeit erledigen. Der Prozess sieht im Grunde so aus:
$ git clone <url>
$ cd project
$ git checkout -b featureA
... work ...
$ git commit
... work ...
$ git commit
Anmerkung
|
Du kannst |
Wenn deine Arbeit am Branch abgeschlossen ist und du bereit bist, sie an die Betreuer weiterzuleiten, wechsel zur ursprünglichen Projektseite. Dort klickst du auf die Schaltfläche Fork
, um deinen eigenen schreibbaren Fork des Projekts zu erstellen.
Anschließend musst du diese Repository-URL als neue Remote-Adresse deines lokalen Repositorys hinzufügen. Nennen wir es in diesem Beispiel myfork
:
$ git remote add myfork <url>
Anschließend musst du deine neue Arbeit in dieses Repository hochladen.
Es ist am einfachsten, den Branch, an dem du arbeitest, in dein geforktes Repository hochzuladen, anstatt diese Arbeit in deinem ´master´-Branch zusammenzuführen und diesen hochzuladen.
Der Grund dafür ist, dass du deinen master
Branch nicht zurücksetzen musst, wenn deine Arbeit nicht akzeptiert bzw. nur teilweise übernommen (cherry-pick) wurde (die Git-Operation zum cherry-pick
wird ausführlicher in Rebasing und Cherry-Picking Workflows behandelt).
Wenn die Betreuer deine Arbeit per merge
, rebase
oder cherry-pick
übernehmen, erhältst du deine Arbeit sowieso zurück, wenn du aus dem Repository der Betreuer pullst.
Auf jedem Fall kannst du deine Arbeit hochladen mit:
$ git push -u myfork featureA
Sobald deine Arbeit an deinem Fork des Repositorys hochgeladen wurde, musst du den Betreuern des ursprünglichen Projekts mitteilen, dass es Änderungen gibt, die sie zusammenführen möchten.
Dies wird oft als Pull Request bezeichnet. Du generierst eine solche Anfrage entweder über die Website – GitHub hat einen eigenen „Pull-Request-Mechanismus“, den wir in GitHub behandeln werden, oder du kannst 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 dein Feature 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 du bittest.
Wenn bspw. Jessica an John eine Pull Request senden möchte und sie zwei Commits für den gerade hochgeladenen Featurebranch ausgeführt hat, kann sie folgendes ausführen:
$ git request-pull origin/master myfork
The following changes since commit 1edee6b1d61823a2de3b09c160d7080b8d1b3a40:
Jessica Smith (1):
Create new function
are available in the git repository at:
https://githost/simplegit.git featureA
Jessica Smith (2):
Add limit to log function
Increase 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 du nicht der Betreuer bist, ist es im Allgemeinen einfacher, einen Branch wie master
zu haben, der immer origin/master
folgt. Deine Arbeit kannst du dann in Feature Branches erledigen, die du einfach verwerfen kannst, wenn deine Änderungen abgelehnt werden.
Durch das Isolieren von Änderungen in Feature Branches wird es für dich auch einfacher, deine Arbeit neu zu strukturieren. Falls sich das Haupt-Repositorys in der Zwischenzeit weiter entwickelt hat und deine Commits nicht mehr sauber angewendet werden können.
Wenn du beispielsweise ein zweites Feature an das Projekt senden möchtest, arbeitest du nicht weiter an dem Branch, den du gerade hochgeladen hast. Beginne 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 deiner Features in einer Art Silo enthalten, ähnlich wie bei einer Patch-Warteschlange. Dieses kannst du umarbeiten, zurücksetzen oder ändern, ohne dass die Features sich gegenseitig stören oder voneinander abhängig sind.
featureB
ÄnderungenNehmen wir an, der Projektbetreuer hat eine Reihe weiterer Patches übernommen und deinen ersten Branch einfließen lassen, der jedoch nicht mehr ordnungsgemäß zusammengeführt werden kann. In diesem Fall kannst du versuchen, diesen Branch auf `origin/master' zu reorganisieren, die Konflikte für den Betreuer zu lösen und deine Änderungen erneut zu übermitteln:
$ git checkout featureA
$ git rebase origin/master
$ git push -f myfork featureA
Dadurch wird dein Verlauf so umgeschrieben, sodass er jetzt folgendermaßen aussieht Commit Verlauf nach featureA
Änderungen.
featureA
ÄnderungenDa du den Branch reorganisiert hast, musst du das -f
für deinen 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 deinem zweiten Branch angesehen und mag dein Konzept, möchte aber, dass du ein Implementierungsdetail änderst.
Du nutzt diese Gelegenheit, um deine Änderungen zu verschieben, damit diese auf dem aktuellen master
Branch des Projektes basieren.
Du startest einen neuen Branch, der auf den aktuellen Branch origin/master
basiert und fasst die Änderungen an featureB
dort zusammen. Dabei löst du etwaige Konflikte, machst die Implementierungsänderungen und lädst 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 dein zukünftiger Commit nur einen übergeordneten Vorgänger hat. Das erlaubt dir alle Änderungen aus einem anderen Branch einzuführen und weitere Änderungen vorzunehmen, bevor du den neuen Commit aufnimmst.
Auch die Option --no-commit
kann nützlich sein, um den Merge-Commit im Falle des Standard-Merge-Prozesses zu verzögern.
Nun kannst du den Betreuer darüber informieren, dass du die angeforderten Änderungen vorgenommen hast und dass du diese Änderungen in deinem Branch featureBv2
findest.
featureBv2
ArbeitÖffentliche Projekte via Email
Viele Projekte haben fest definierte Prozesse, um Änderungen entgegenzunehmen. Du musst 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: Du erstellst Feature Branches für jede Patch Serie, an der du arbeitest. Der Unterschied besteht darin, wie du diese Änderungen an das Projekt sendest. Anstatt das Projekt zu forken und auf dein eigenes geforktes Repository hochzuladen, generierst du E-Mail-Versionen jeder Commit-Serie und sendest diese per E-Mail an die Entwickler-Mailingliste:
$ git checkout -b topicA
... work ...
$ git commit
... work ...
$ git commit
Jetzt hast du zwei Commits, die du an die Mailingliste senden kannst.
Du verwendest git format-patch
, um die mbox-formatierten Dateien zu generieren, die du anschließend per E-Mail an die Mailingliste sendest. Dabei wird jeder 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-increase-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
Du kannst 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 du Text zwischen der Zeile ---
und dem Beginn des Patches (der Zeile diff --git
) einfügst, 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, kannst du 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 du ordnungsgemäß formatierte Patches über IMAP senden kannst, was einfacher für dich sein könnte.
Wir zeigen dir, wie du einen Patch über Google Mail sendest. Dies ist der E-Mail-Agent, mit dem wir uns am besten auskennen. Detaillierte Anweisungen für eine Reihe von anderen Mail-Programmen findest du am Ende der oben genannten Datei Documentation/SubmittingPatches
im Git-Quellcode.
Zuerst musst du den Abschnitt imap in deiner ~/.gitconfig
Datei einrichten.
Du kannst jeden Wert separat mit einer Reihe von git config
Befehlen festlegen oder manuell hinzufügen. Am Ende sollte deine 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 dein 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, kannst du 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 solltest du in der Lage sein, in deinem Entwurfsordner zu wechseln und dort das Feld An der generierten Email in die Mailinglist-Adresse zu ändern, an die du den Patch senden willst. Möglicherweise willst du auch den Betreuer oder die Person in Kopie nehmen, die für diesen Abschnitt verantwortlich ist. Anschließend kannst du die Mail versenden.
Du kannst die Patches auch über einen SMTP-Server senden.
Wie zuvor kannst du jeden Wert separat mit einer Reihe von git config
Befehlen festlegen oder manuell im Abschnitt sendemail in deiner ~/.gitconfig
Datei hinzufügen:
[sendemail]
smtpencryption = tls
smtpserver = smtp.gmail.com
smtpuser = user@gmail.com
smtpserverport = 587
Danach kannst du deine Patches mit git send-email
versenden:
$ git send-email *.patch
0001-add-limit-to-log-function.patch
0002-increase-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 du versendest, 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] Add 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
Hinweis
|
Weitere Informationen zum Konfigurieren deines Systems und deiner E-Mail-Adresse, weitere Tipps und Tricks sowie eine Sandbox zum Senden eines Test-Patches per E-Mail findest du unter git-send-email.io. |
Zusammenfassung
In diesem Abschnitt haben wir mehrere Workflows behandelt und über die Unterschiede zwischen der Arbeit an an Closed-Source-Projekten in einem kleinen Teams und der Mitarbeit an einem großen öffentlichen Projekt gesprochen. Du musst vor dem Committen nach White-Space-Fehlern suchen und kannst großartige Commit-Beschreibungen hinzufügen. Du hast gelernt, wie du Patches formatieren und per E-Mail an eine Entwickler-Mailingliste senden kannst. Der Umgang mit Merges wurde auch im Zusammenhang mit den verschiedenen Arbeitsabläufen behandelt. Du bist jetzt gut vorbereitet, an jedem Projekt mitzuarbeiten.
Als Nächstes erfährst du, wie du auf der anderen Seite arbeitest: als Verwalter (Maintainer) eines Git-Projektes. Du lernst, wie man als wohlwollender Diktator oder Integrationsmanager korrekt arbeitet.