-
1. Erste Schritte
- 1.1 Über Versionskontrolle
- 1.2 Eine kurze Geschichte von Git
- 1.3 Was ist Git?
- 1.4 Die Kommandozeile
- 1.5 Git installieren
- 1.6 Erstmalige Git-Einrichtung
- 1.7 Hilfe bekommen
- 1.8 Zusammenfassung
-
2. Git Grundlagen
-
3. Git Branching
- 3.1 Branches im Überblick
- 3.2 Grundlegendes 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 Generieren Ihres SSH-Public-Keys
- 4.4 Einrichten des Servers
- 4.5 Git Daemon
- 4.6 Smart HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 Drittanbieter-Hosting-Optionen
- 4.10 Zusammenfassung
-
5. Verteiltes Git
-
6. GitHub
-
7. Git-Werkzeuge
- 7.1 Revisionsauswahl
- 7.2 Interaktives Staging
- 7.3 Stashing und Bereinigen
- 7.4 Ihre Arbeit signieren
- 7.5 Suchen
- 7.6 Historie umschreiben
- 7.7 Reset entmystifiziert
- 7.8 Fortgeschrittenes Merging
- 7.9 Rerere
- 7.10 Debugging mit Git
- 7.11 Submodule
- 7.12 Bundling
- 7.13 Ersetzen
- 7.14 Credential-Speicher
- 7.15 Zusammenfassung
-
8. Git anpassen
-
9. Git und andere Systeme
- 9.1 Git als Client
- 9.2 Migration zu Git
- 9.3 Zusammenfassung
-
10. Git-Interna
- 10.1 Plumbing und Porcelain
- 10.2 Git-Objekte
- 10.3 Git-Referenzen
- 10.4 Packfiles
- 10.5 Die Refspec
- 10.6 Übertragungsprotokolle
- 10.7 Wartung und Datenwiederherstellung
- 10.8 Umgebungsvariablen
- 10.9 Zusammenfassung
-
Anhang A: Git in anderen Umgebungen
- A1.1 Grafische Oberflächen
- 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
-
Anhang B: Git in Ihre Anwendungen einbetten
- A2.1 Kommandozeilen-Git
- A2.2 Libgit2
- A2.3 JGit
- A2.4 go-git
- A2.5 Dulwich
-
Anhang C: Git-Befehle
- A3.1 Einrichtung und Konfiguration
- A3.2 Projekte abrufen und erstellen
- A3.3 Grundlegendes Snapshotting
- A3.4 Branching und Merging
- A3.5 Projekte teilen und aktualisieren
- A3.6 Inspektion und Vergleich
- A3.7 Debugging
- A3.8 Patching
- A3.9 E-Mail
- A3.10 Externe Systeme
- A3.11 Administration
- A3.12 Plumbing-Befehle
3.6 Git Branching - Rebasing
Rebasing
In Git gibt es zwei Hauptmethoden, um Änderungen von einem Branch in einen anderen zu integrieren: merge und rebase. In diesem Abschnitt lernen Sie, was Rebasing ist, wie es funktioniert, warum es ein ziemlich erstaunliches Werkzeug ist und in welchen Fällen Sie es nicht verwenden sollten.
Das grundlegende Rebase
Wenn Sie zu einem früheren Beispiel aus Basic Merging zurückkehren, können Sie sehen, dass Sie Ihre Arbeit verzweigt und Commits auf zwei verschiedenen Branches erstellt haben.
Die einfachste Methode, die Branches zu integrieren, ist, wie wir bereits behandelt haben, der Befehl merge. Er führt einen Drei-Wege-Merge zwischen den beiden neuesten Branch-Snapshots (C3 und C4) und dem letzten gemeinsamen Vorfahren der beiden (C2) durch und erstellt einen neuen Snapshot (und Commit).
Es gibt jedoch noch eine andere Möglichkeit: Sie können den Patch der Änderung, die in C4 eingeführt wurde, nehmen und ihn auf C3 neu anwenden. In Git wird dies als Rebasing bezeichnet. Mit dem Befehl rebase können Sie alle Änderungen, die in einem Branch committet wurden, auf einen anderen Branch übertragen.
Für dieses Beispiel würden Sie zum Branch experiment wechseln und ihn dann wie folgt auf den Branch master rebassen:
$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command
Diese Operation funktioniert, indem sie zum gemeinsamen Vorfahren der beiden Branches (dem, auf dem Sie sich befinden, und dem, auf den Sie rebassen) geht, die Unterschiede ermittelt, die von jedem Commit des aktuellen Branches eingeführt wurden, diese Unterschiede in temporären Dateien speichert, den aktuellen Branch auf denselben Commit zurücksetzt wie den Branch, auf den Sie rebassen, und schließlich jede Änderung nacheinander anwendet.
C4 eingeführten Änderung auf C3An diesem Punkt können Sie zum Branch master zurückkehren und einen Fast-Forward-Merge durchführen.
$ git checkout master
$ git merge experiment
master-BranchesJetzt ist der von C4' referenzierte Snapshot genau derselbe wie der, der in dem Merge-Beispiel von C5 referenziert wurde. Es gibt keinen Unterschied im Endergebnis der Integration, aber Rebasing sorgt für eine sauberere Historie. Wenn Sie das Log eines gerebassa-ten Branches untersuchen, sieht es wie eine lineare Historie aus: Es scheint, dass die gesamte Arbeit nacheinander stattgefunden hat, auch wenn sie ursprünglich parallel stattgefunden hat.
Oft werden Sie dies tun, um sicherzustellen, dass Ihre Commits sauber auf einem Remote-Branch angewendet werden – vielleicht in einem Projekt, zu dem Sie beitragen möchten, das Sie aber nicht pflegen. In diesem Fall würden Sie Ihre Arbeit in einem Branch erledigen und dann Ihre Arbeit auf origin/master rebassen, wenn Sie bereit sind, Ihre Patches an das Hauptprojekt zu übermitteln. Auf diese Weise muss der Maintainer keine Integrationsarbeit leisten – nur einen Fast-Forward oder eine saubere Anwendung.
Beachten Sie, dass der von dem endgültigen Commit referenzierte Snapshot, auf den Sie am Ende gelangen, sei es der letzte der gerebassa-ten Commits für ein Rebase oder der endgültige Merge-Commit nach einem Merge, derselbe Snapshot ist – nur die Historie ist unterschiedlich. Rebasing wendet Änderungen von einer Arbeitslinie auf eine andere in der Reihenfolge an, in der sie eingeführt wurden, während Merging die Endpunkte nimmt und sie zusammenführt.
Interessantere Rebases
Sie können Ihr Rebase auch auf etwas anderes als den Rebase-Ziel-Branch anwenden lassen. Nehmen Sie zum Beispiel eine Historie wie Eine Historie mit einem Topic-Branch von einem anderen Topic-Branch. Sie haben einen Topic-Branch (server) erstellt, um dem Projekt serverseitige Funktionalität hinzuzufügen, und einen Commit erstellt. Dann haben Sie davon abgeleitet, um clientseitige Änderungen vorzunehmen (client) und einige Commits erstellt. Schließlich sind Sie zu Ihrem server-Branch zurückgekehrt und haben einige weitere Commits erstellt.
Nehmen wir an, Sie entscheiden, dass Sie Ihre clientseitigen Änderungen in Ihre Hauptlinie für eine Veröffentlichung integrieren möchten, aber die serverseitigen Änderungen zurückhalten möchten, bis sie weiter getestet sind. Sie können die Änderungen an client, die nicht auf server sind (C8 und C9), nehmen und sie auf Ihren master-Branch anwenden, indem Sie die Option --onto von git rebase verwenden.
$ git rebase --onto master server client
Dies bedeutet im Grunde: „Nimm den client-Branch, ermittle die Patches, seit er sich vom server-Branch getrennt hat, und wende diese Patches im client-Branch an, als ob er direkt auf dem master-Branch basieren würde.“ Es ist etwas komplex, aber das Ergebnis ist ziemlich cool.
Jetzt können Sie Ihren master-Branch per Fast-Forward aktualisieren (siehe Fast-Forwarding Ihres master-Branches zur Aufnahme der Änderungen des client-Branches)
$ git checkout master
$ git merge client
master-Branches zur Aufnahme der Änderungen des client-BranchesNehmen wir an, Sie beschließen, auch Ihren server-Branch zu integrieren. Sie können den server-Branch auf den master-Branch rebassen, ohne ihn vorher auschecken zu müssen, indem Sie git rebase <basebranch> <topicbranch> ausführen – dies checkt den Topic-Branch (in diesem Fall server) für Sie aus und wendet ihn auf den Basis-Branch (master) an.
$ git rebase master server
Dies wendet Ihre server-Arbeit auf Ihre master-Arbeit an, wie in Rebasing Ihres server-Branches auf Ihren master-Branch gezeigt.
server-Branches auf Ihren master-BranchDann können Sie den Basis-Branch (master) per Fast-Forward aktualisieren.
$ git checkout master
$ git merge server
Sie können die Branches client und server löschen, da alle Arbeiten integriert sind und Sie sie nicht mehr benötigen. Die Historie dieses gesamten Prozesses sieht dann wie in Endgültige Commit-Historie aus.
$ git branch -d client
$ git branch -d server
Die Gefahren des Rebasing
Aber die Glückseligkeit des Rebasing ist nicht ohne Nachteile, die sich in einer einzigen Zeile zusammenfassen lassen:
Rebasen Sie keine Commits, die außerhalb Ihres Repositories existieren und auf denen andere möglicherweise Arbeit aufgebaut haben.
Wenn Sie diese Richtlinie befolgen, werden Sie keine Probleme haben. Wenn nicht, werden die Leute Sie hassen und Sie werden von Freunden und Familie verachtet werden.
Wenn Sie Dinge rebassen, verwerfen Sie bestehende Commits und erstellen neue, die ähnlich, aber unterschiedlich sind. Wenn Sie Commits irgendwo pushen und andere sie herunterladen und darauf aufbauend arbeiten, und Sie dann diese Commits mit git rebase umschreiben und wieder hochpushen, müssen Ihre Mitarbeiter ihre Arbeit erneut zusammenführen, und es wird chaotisch, wenn Sie versuchen, ihre Arbeit wieder in Ihre zu integrieren.
Betrachten wir ein Beispiel dafür, wie das Rebasing von öffentlich gemachter Arbeit zu Problemen führen kann. Angenommen, Sie klonen von einem zentralen Server und erledigen dann einige Arbeiten daran. Ihre Commit-Historie sieht so aus:
Nun leistet jemand anderes weitere Arbeit, die einen Merge beinhaltet, und pusht diese Arbeit zum zentralen Server. Sie holen sie und mergen den neuen Remote-Branch in Ihre Arbeit, sodass Ihre Historie ungefähr so aussieht:
Als Nächstes beschließt die Person, die den gemergten Code gepusht hat, zurückzugehen und stattdessen ihre Arbeit zu rebassen; sie führt einen git push --force aus, um die Historie auf dem Server zu überschreiben. Sie holen dann von diesem Server und erhalten die neuen Commits.
Jetzt sind Sie beide in der Klemme. Wenn Sie git pull ausführen, erstellen Sie einen Merge-Commit, der beide Historienzweige enthält, und Ihr Repository sieht so aus:
Wenn Sie git log ausführen, wenn Ihre Historie so aussieht, sehen Sie zwei Commits mit demselben Autor, Datum und derselben Nachricht, was verwirrend ist. Wenn Sie diese Historie dann wieder auf den Server pushen, werden Sie all diese gerebassa-ten Commits erneut auf dem zentralen Server einführen, was die Leute weiter verwirren kann. Es ist ziemlich sicher anzunehmen, dass der andere Entwickler C4 und C6 nicht in der Historie haben möchte; deshalb haben sie in erster Linie gerebasst.
Rebase bei Rebase
Wenn Sie sich in einer solchen Situation befinden, gibt es in Git weitere Magie, die Ihnen helfen kann. Wenn jemand in Ihrem Team Änderungen mit Gewalt pusht, die Arbeit überschreiben, auf der Sie Ihre Arbeit aufgebaut haben, besteht Ihre Herausforderung darin, herauszufinden, was Ihnen gehört und was sie umgeschrieben haben.
Es stellt sich heraus, dass Git neben der Commit-SHA-1-Prüfsumme auch eine Prüfsumme berechnet, die nur auf dem mit dem Commit eingeführten Patch basiert. Dies wird als "patch-id" bezeichnet.
Wenn Sie umgeschriebene Arbeit herunterladen und sie auf die neuen Commits Ihres Partners anwenden, kann Git oft erfolgreich feststellen, was einzigartig von Ihnen ist, und es wieder auf den neuen Branch anwenden.
Zum Beispiel, im vorherigen Szenario, wenn wir statt eines Merges bei Jemand pusht gerebassa-te Commits und verwirft Commits, auf denen Sie Ihre Arbeit aufgebaut haben git rebase teamone/master ausführen, wird Git:
-
Ermitteln, welche Arbeit einzigartig für unseren Branch ist (
C2,C3,C4,C6,C7) -
Ermitteln, welche keine Merge-Commits sind (
C2,C3,C4) -
Ermitteln, welche nicht in den Ziel-Branch umgeschrieben wurden (nur
C2undC3, daC4derselbe Patch ist wieC4') -
Diese Commits oben auf
teamone/masteranwenden.
Anstatt des Ergebnisses, das wir in Sie mergen die gleiche Arbeit erneut in einem neuen Merge-Commit sehen, würden wir etwas Ähnliches wie in Rebase auf zwangsweise gepushte Rebase-Arbeit erhalten.
Dies funktioniert nur, wenn C4 und C4', die Ihr Partner erstellt hat, fast genau derselbe Patch sind. Andernfalls kann das Rebase nicht erkennen, dass es sich um ein Duplikat handelt, und fügt einen weiteren C4-ähnlichen Patch hinzu (der wahrscheinlich nicht sauber angewendet werden kann, da die Änderungen bereits teilweise vorhanden wären).
Sie können dies auch vereinfachen, indem Sie git pull --rebase anstelle eines normalen git pull ausführen. Oder Sie könnten es manuell mit einem git fetch gefolgt von einem git rebase teamone/master in diesem Fall tun.
Wenn Sie git pull verwenden und --rebase zum Standard machen möchten, können Sie den Konfigurationswert pull.rebase mit etwas wie git config --global pull.rebase true setzen.
Wenn Sie nur Commits rebassen, die Ihr eigenes Computer nie verlassen haben, ist alles in Ordnung. Wenn Sie Commits rebassen, die gepusht wurden, aber auf denen andere keine Commits aufgebaut haben, ist ebenfalls alles in Ordnung. Wenn Sie Commits rebassen, die bereits öffentlich gepusht wurden und auf denen andere möglicherweise Arbeit aufgebaut haben, könnten Sie auf frustrierende Probleme stoßen und die Verachtung Ihrer Teammitglieder auf sich ziehen.
Wenn Sie oder ein Partner es zu einem bestimmten Zeitpunkt für notwendig erachtet, stellen Sie sicher, dass jeder weiß, dass git pull --rebase ausgeführt werden soll, um die Nachwirkungen nach dem Auftreten zu vereinfachen.
Rebase vs. Merge
Nachdem Sie nun Rebasing und Merging in Aktion gesehen haben, fragen Sie sich vielleicht, welches besser ist. Bevor wir dies beantworten können, lassen Sie uns einen Schritt zurücktreten und darüber sprechen, was Historie bedeutet.
Ein Standpunkt dazu ist, dass die Commit-Historie Ihres Repositories eine Aufzeichnung dessen ist, was tatsächlich passiert ist. Es ist ein historisches Dokument, das an sich wertvoll ist und nicht manipuliert werden sollte. Aus diesem Blickwinkel ist die Änderung der Commit-Historie fast blasphemisch; Sie lügen darüber, was tatsächlich geschehen ist. Was macht es, wenn es eine unübersichtliche Reihe von Merge-Commits gab? So ist es passiert, und das Repository sollte dies für die Nachwelt erhalten.
Der entgegengesetzte Standpunkt ist, dass die Commit-Historie die Geschichte der Entstehung Ihres Projekts ist. Sie würden nicht den ersten Entwurf eines Buches veröffentlichen, warum also Ihre unübersichtliche Arbeit zeigen? Wenn Sie an einem Projekt arbeiten, benötigen Sie möglicherweise eine Aufzeichnung all Ihrer Fehltritte und Sackgassen, aber wenn es an der Zeit ist, Ihre Arbeit der Welt zu zeigen, möchten Sie vielleicht eine kohärentere Geschichte erzählen, wie man von A nach B gelangt. Personen in diesem Lager verwenden Werkzeuge wie rebase und filter-branch, um ihre Commits zu überschreiben, bevor sie in den Haupt-Branch integriert werden. Sie verwenden Werkzeuge wie rebase und filter-branch, um die Geschichte so zu erzählen, wie es für zukünftige Leser am besten ist.
Nun zur Frage, ob Merging oder Rebasing besser ist: Hoffentlich erkennen Sie, dass es nicht so einfach ist. Git ist ein mächtiges Werkzeug und ermöglicht es Ihnen, viele Dinge mit Ihrer Historie zu tun, aber jedes Team und jedes Projekt ist anders. Jetzt, da Sie wissen, wie beides funktioniert, liegt es an Ihnen zu entscheiden, was für Ihre spezielle Situation am besten ist.
Sie können das Beste aus beiden Welten haben: Lokale Änderungen rebassen, bevor Sie pushen, um Ihre Arbeit zu bereinigen, aber niemals etwas rebassen, das Sie irgendwohin gepusht haben.