409 Stimmen

Wie und/oder warum ist das Zusammenführen in Git besser als in SVN?

Ich habe an einigen Stellen gehört, dass einer der Hauptgründe, warum verteilte Versionskontrollsysteme glänzen, das viel bessere Zusammenführen ist als bei traditionellen Tools wie SVN. Ist dies tatsächlich auf inhärente Unterschiede in der Arbeitsweise der beiden Systeme zurückzuführen, oder sind spezifisch DVCS-Implementierungen wie Git/Mercurial haben einfach cleverere Zusammenführungsalgorithmen als SVN?

567voto

Spoike Punkte 115938

Die Behauptung, warum das Zusammenführen in einem DVCS besser ist als in Subversion, basierte größtenteils darauf, wie Branching und Merge in Subversion vor einiger Zeit funktionierten. Subversion vor 1.5.0 speicherte keine Informationen darüber, wann Zweige zusammengeführt wurden. Wenn Sie also zusammenführen wollten, mussten Sie angeben, welcher Bereich von Revisionen zusammengeführt werden sollte.

Warum also hat Subversion Zusammenführungen saugen ?

Denken Sie über dieses Beispiel nach:

      1   2   4     6     8
trunk o-->o-->o---->o---->o
       \
        \   3     5     7
b1       +->o---->o---->o

Wenn wir wollen zusammenführen b1's Änderungen in den Stamm zu übertragen, müssen wir den folgenden Befehl eingeben, während wir uns in einem Ordner befinden, der den Stamm ausgecheckt hat:

svn merge -r 2:7 {link to branch b1}

die versuchen wird, die Änderungen von b1 in Ihr lokales Arbeitsverzeichnis. Dann übertragen Sie die Änderungen, nachdem Sie alle Konflikte gelöst und das Ergebnis getestet haben. Nach der Übergabe würde der Revisionsbaum wie folgt aussehen:

      1   2   4     6     8   9
trunk o-->o-->o---->o---->o-->o      "the merge commit is at r9"
       \
        \   3     5     7
b1       +->o---->o---->o

Diese Art der Angabe von Revisionsbereichen gerät jedoch schnell außer Kontrolle, wenn der Versionsbaum wächst, da Subversion keine Metadaten darüber hat, wann und welche Revisionen zusammengeführt wurden. Denken Sie darüber nach, was später passiert:

           12        14
trunk  …-->o-------->o
                                     "Okay, so when did we merge last time?"
              13        15
b1     …----->o-------->o

Dies ist größtenteils ein Problem des Repository-Designs von Subversion. Um einen Zweig zu erstellen, müssen Sie einen neuen virtuelles Verzeichnis im Repository, das eine Kopie des Stammes enthält, aber es speichert keine Informationen darüber, wann und was wieder eingefügt wurde. Das führt manchmal zu unangenehmen Konflikten beim Zusammenführen. Was noch schlimmer ist, ist dass Subversion standardmäßig das Zusammenführen in zwei Richtungen verwendet, was einige lähmende Einschränkungen beim automatischen Zusammenführen hat, wenn zwei Zweigköpfe nicht mit ihrem gemeinsamen Vorgänger verglichen werden.

Um dies abzumildern, speichert Subversion jetzt Metadaten für Verzweigungen und Zusammenführungen. Das würde doch alle Probleme lösen, oder?

Und ach ja, Subversion ist immer noch scheiße

Auf einem zentralisierten System, wie Subversion, virtuelle Verzeichnisse saugen. Und warum? Weil jeder Zugang zu ihnen hat sogar die experimentellen Müllseiten. Verzweigung ist gut, wenn man experimentieren will aber Sie wollen nicht sehen, wie jeder und ihre Tanten experimentieren . Dies ist ein ernsthafter kognitiver Lärm. Je mehr Zweige Sie hinzufügen, desto mehr Mist bekommen Sie zu sehen.

Je mehr öffentliche Zweige Sie in einem Repository haben, desto schwieriger wird es, den Überblick über all die verschiedenen Zweige zu behalten. Es stellt sich also die Frage, ob der Zweig noch in der Entwicklung ist oder ob er wirklich tot ist, was in einem zentralisierten Versionskontrollsystem schwer zu sagen ist.

Soweit ich weiß, wird eine Organisation in den meisten Fällen ohnehin nur eine große Niederlassung verwenden. Das ist schade, weil es dann schwierig ist, den Überblick über Test- und Release-Versionen zu behalten, und was auch immer sonst noch Gutes aus der Verzweigung kommt.

Warum also sind DVCS wie Git, Mercurial und Bazaar beim Verzweigen und Zusammenführen besser als Subversion?

Dafür gibt es einen ganz einfachen Grund: Verzweigung ist ein Konzept erster Klasse . Es gibt keine virtuellen Verzeichnisse und Zweige sind in DVCS harte Objekte, die es braucht, um einfach mit der Synchronisation von Repositories zu arbeiten (d.h. drücken. y ziehen ).

Das erste, was Sie tun, wenn Sie mit einem DVCS arbeiten, ist das Klonen von Repositories (git's clone , hg's clone und bzr's branch ). Klonen ist konzeptionell dasselbe wie das Erstellen eines Zweigs in der Versionskontrolle. Manche nennen dies Verzweigung o Verzweigung (obwohl letzteres oft auch für Zweigstellen mit gleichem Standort verwendet wird), aber es ist genau das Gleiche. Jeder Benutzer führt sein eigenes Repository, was bedeutet, dass Sie eine Verzweigung pro Benutzer läuft.

Die Versionsstruktur lautet kein Baum sondern vielmehr eine Grafik stattdessen. Genauer gesagt ein gerichteter azyklischer Graph (DAG, d.h. ein Graph, der keine Zyklen hat). Es ist nicht nötig, sich mit den Besonderheiten einer DAG zu befassen, außer dass jede Übertragung eine oder mehrere übergeordnete Referenzen hat (auf denen die Übertragung basiert). Aus diesem Grund werden in den folgenden Diagrammen die Pfeile zwischen den Revisionen in umgekehrter Reihenfolge dargestellt.

Ein sehr einfaches Beispiel für die Zusammenführung wäre folgendes: Stellen Sie sich ein zentrales Repository namens origin und eine Benutzerin, Alice, die das Repository auf ihren Rechner klont.

         a…   b…   c…
origin   o<---o<---o
                   ^master
         |
         | clone
         v

         a…   b…   c…
alice    o<---o<---o
                   ^master
                   ^origin/master

Beim Klonen wird jede Revision genau so auf Alice kopiert, wie sie war (was durch die eindeutig identifizierbaren Hash-id's bestätigt wird), und es wird markiert, wo sich die Zweige des Ursprungs befinden.

Alice arbeitet dann an ihrem Repository, überträgt in ihr eigenes Repository und beschließt, ihre Änderungen zu veröffentlichen:

         a…   b…   c…
origin   o<---o<---o
                   ^ master

              "what'll happen after a push?"

         a…   b…   c…   d…   e…
alice    o<---o<---o<---o<---o
                             ^master
                   ^origin/master

Die Lösung ist recht einfach, das einzige, was die origin Repository muss alle neuen Revisionen aufnehmen und seinen Zweig auf die neueste Revision verschieben (was Git "fast-forward" nennt):

         a…   b…   c…   d…   e…
origin   o<---o<---o<---o<---o
                             ^ master

         a…   b…   c…   d…   e…
alice    o<---o<---o<---o<---o
                             ^master
                             ^origin/master

Der Anwendungsfall, den ich oben dargestellt habe, muss nicht einmal etwas zusammenführen . Das Problem liegt also nicht in den Zusammenführungsalgorithmen, denn der Algorithmus für die Drei-Wege-Zusammenführung ist bei allen Versionskontrollsystemen so ziemlich derselbe. Es geht mehr um die Struktur als um irgendetwas anderes .

Wie wäre es also, wenn Sie mir ein Beispiel zeigen, das eine real verschmelzen?

Zugegeben, das obige Beispiel ist ein sehr einfacher Anwendungsfall, also lassen Sie uns einen viel komplizierteren, wenn auch häufigeren Fall untersuchen. Denken Sie daran, dass origin begann mit drei Überarbeitungen? Nun, der Typ, der sie gemacht hat, nennen wir ihn Bob hat auf eigene Faust gearbeitet und einen Commit auf sein eigenes Repository gemacht:

         a…   b…   c…   f…
bob      o<---o<---o<---o
                        ^ master
                   ^ origin/master

                   "can Bob push his changes?" 

         a…   b…   c…   d…   e…
origin   o<---o<---o<---o<---o
                             ^ master

Jetzt kann Bob seine Änderungen nicht mehr direkt in die origin Repository. Das System erkennt dies, indem es prüft, ob Bobs Revisionen direkt von origin was in diesem Fall nicht der Fall ist. Jeder Versuch, einen Push auszulösen, führt dazu, dass das System etwas sagt, das mit " Äh... ich fürchte, das kann ich Ihnen nicht gestatten, Bob. ."

Bob muss also die Änderungen einbringen und dann zusammenführen (mit git's pull ; oder hg's pull y merge ; oder bzr's merge ). Dies ist ein zweistufiger Prozess. Zunächst muss Bob die neuen Revisionen abrufen, die dann unverändert aus dem origin Repository. Wir können nun sehen, dass der Graph divergiert:

                        v master
         a…   b…   c…   f…
bob      o<---o<---o<---o
                   ^
                   |    d…   e…
                   +----o<---o
                             ^ origin/master

         a…   b…   c…   d…   e…
origin   o<---o<---o<---o<---o
                             ^ master

Der zweite Schritt des Pull-Prozesses besteht darin, die divergierenden Tipps zusammenzuführen und das Ergebnis zu übertragen:

                                 v master
         a…   b…   c…   f…       1…
bob      o<---o<---o<---o<-------o
                   ^             |
                   |    d…   e…  |
                   +----o<---o<--+
                             ^ origin/master

Hoffentlich kommt es bei der Zusammenführung nicht zu Konflikten (wenn Sie damit rechnen, können Sie die beiden Schritte manuell in Git mit fetch y merge ). Später müssen diese Änderungen dann wieder in die origin , was zu einem Fast-Forward-Merge führt, da der Merge-Commit ein direkter Nachkomme des letzten im origin Repository:

                                 v origin/master
                                 v master
         a…   b…   c…   f…       1…
bob      o<---o<---o<---o<-------o
                   ^             |
                   |    d…   e…  |
                   +----o<---o<--+

                                 v master
         a…   b…   c…   f…       1…
origin   o<---o<---o<---o<-------o
                   ^             |
                   |    d…   e…  |
                   +----o<---o<--+

Es gibt eine weitere Option zum Zusammenführen in git und hg, genannt neu gründen was Bobs Änderungen nach den neuesten Änderungen verschiebt. Da ich nicht möchte, dass diese Antwort noch ausführlicher wird, überlasse ich Ihnen das Lesen der git , . o Basar stattdessen die Dokumente darüber.

Versuchen Sie als Übung für den Leser, sich auszumalen, wie es mit einem anderen beteiligten Benutzer funktionieren würde. Dies geschieht in ähnlicher Weise wie im obigen Beispiel mit Bob. Das Merging zwischen Repositories ist einfacher als man denkt, da alle Revisionen/Commits eindeutig identifizierbar sind.

Es gibt auch das Problem des Versendens von Patches zwischen den einzelnen Entwicklern, das in Subversion ein großes Problem war, das in git, hg und bzr durch eindeutig identifizierbare Revisionen entschärft wird. Sobald jemand seine Änderungen zusammengeführt hat (d.h. einen Merge-Commit gemacht hat) und sie an alle anderen im Team sendet, um sie entweder in ein zentrales Repository zu pushen oder Patches zu senden, müssen sie sich nicht mehr um den Merge kümmern, da er bereits stattgefunden hat. Martin Fowler nennt diese Art der Arbeit promiskuitive Integration .

Da sich die Struktur von Subversion unterscheidet und stattdessen ein DAG verwendet wird, können Verzweigungen und Zusammenführungen nicht nur für das System, sondern auch für den Benutzer einfacher durchgeführt werden.

30voto

Andrew Aylett Punkte 37790

Historisch gesehen war Subversion nur in der Lage, eine direkte Zwei-Wege-Zusammenführung durchzuführen, da es keine Informationen über die Zusammenführung speicherte. Dies bedeutet, dass eine Reihe von Änderungen auf einen Baum angewendet werden. Selbst mit Zusammenführungsinformationen ist dies immer noch die am häufigsten verwendete Zusammenführungsstrategie.

Git verwendet standardmäßig einen 3-Wege-Zusammenführungsalgorithmus, bei dem ein gemeinsamer Vorfahre der zusammenzuführenden Köpfe gefunden und das auf beiden Seiten der Zusammenführung vorhandene Wissen genutzt wird. Dadurch kann Git Konflikte auf intelligentere Weise vermeiden.

Git verfügt auch über einen ausgefeilten Code zum Auffinden von Umbenennungen, was ebenfalls hilfreich ist. Es nicht Er speichert lediglich den Zustand der Dateien bei jeder Übergabe und verwendet Heuristiken, um Umbenennungen und Codeverschiebungen nach Bedarf zu lokalisieren (die Speicherung auf der Festplatte ist komplizierter, aber die Schnittstelle, die sie der Logikschicht präsentiert, zeigt keine Verfolgung).

17voto

Andreas Krey Punkte 5896

Vereinfacht ausgedrückt, ist die Durchführung der Zusammenführung besser in Git als in SVN . Vor 1.5 zeichnete SVN eine Merge-Aktion nicht auf, so dass es nicht in der Lage war, zukünftige Merges ohne Hilfe des Benutzers durchzuführen, der Informationen bereitstellen musste, die SVN nicht aufzeichnete. Mit 1.5 wurde es besser, und in der Tat ist das SVN-Speichermodell etwas leistungsfähiger als das DAG von Git. Aber SVN speicherte die Merge-Informationen in einer ziemlich verworrenen Form, die Merges massiv länger dauern lässt als in Git - ich habe Faktoren von 300 in der Ausführungszeit beobachtet.

Außerdem gibt SVN an, Umbenennungen zu verfolgen, um das Zusammenführen verschobener Dateien zu erleichtern. Tatsächlich werden sie aber immer noch als Kopie und separate Löschaktion gespeichert, und der Zusammenführungsalgorithmus stolpert immer noch über sie in Situationen, in denen eine Datei auf einem Zweig geändert und auf dem anderen Zweig umbenannt wird, und diese Zweige zusammengeführt werden sollen. Solche Situationen führen immer noch zu falschen Zusammenführungskonflikten, und im Fall von Verzeichnisumbenennungen führt es sogar zu einem stillen Verlust von Änderungen. (Die SVN-Leute neigen dann dazu, darauf hinzuweisen, dass die Änderungen immer noch in der Historie stehen, aber das hilft nicht viel, wenn sie nicht in einem Merge-Ergebnis stehen, wo sie erscheinen sollten.

Git hingegen verfolgt nicht einmal Umbenennungen, sondern findet sie erst im Nachhinein (beim Zusammenführen) heraus, und das auf ziemlich magische Weise.

Die SVN-Merge-Darstellung hat auch Probleme; in 1.5/1.6 konnte man automatisch vom Stamm zum Zweig zusammenführen, so oft man wollte, aber eine Zusammenführung in die andere Richtung musste angekündigt werden ( --reintegrate ), und ließ den Zweig in einem unbrauchbaren Zustand zurück. Viel später fanden sie heraus, dass dies nicht der Fall ist und dass a) die --reintegrate peut automatisch ermittelt werden, und b) wiederholte Zusammenführungen in beide Richtungen möglich sind.

Aber nach all dem (was IMHO zeigt einen Mangel an Verständnis von dem, was sie tun), würde ich (OK, ich bin) sehr vorsichtig sein, SVN in jedem nicht-trivialen Verzweigungsszenario zu verwenden, und würde idealerweise versuchen zu sehen, was Git denkt über das Merge-Ergebnis.

Andere Punkte, die in den Antworten genannt werden, wie z.B. die erzwungene globale Sichtbarkeit von Zweigen in SVN, sind für die Zusammenführungsmöglichkeiten nicht relevant (aber für die Benutzerfreundlichkeit). Auch die Aussage "Git speichert Änderungen, während SVN (etwas anderes) speichert" geht am Thema vorbei. Git speichert konzeptionell jeden Commit als eigenen Baum (wie einen tar Datei), und verwendet dann einige Heuristiken, um diese effizient zu speichern. Die Berechnung der Änderungen zwischen zwei Übertragungen ist von der Speicherimplementierung getrennt. Richtig ist, dass Git die History-DAG in einer viel einfacheren Form speichert als SVN seine Mergeinfo. Jeder, der versucht, letzteres zu verstehen, wird wissen, was ich meine.

Kurz und bündig: Git verwendet ein viel einfacheres Datenmodell zum Speichern von Revisionen als SVN und könnte daher eine Menge Energie in die eigentlichen Merge-Algorithmen stecken, anstatt zu versuchen, mit der Darstellung zurechtzukommen => praktisch besseres Merging.

11voto

daniel kullmann Punkte 12902

Eine Sache, die in den anderen Antworten nicht erwähnt wurde, und die wirklich ein großer Vorteil eines DVCS ist, ist, dass Sie lokal committen können, bevor Sie Ihre Änderungen veröffentlichen. Wenn ich in SVN eine Änderung hatte, die ich einchecken wollte, und jemand hatte in der Zwischenzeit bereits einen Commit auf dem gleichen Zweig gemacht, bedeutete das, dass ich einen svn update bevor ich mich festlegen konnte. Das bedeutet, dass meine Änderungen und die Änderungen der anderen Person nun miteinander vermischt sind, und es gibt keine Möglichkeit, die Zusammenführung abzubrechen (wie bei git reset o hg update -C ), weil es keinen Commit gibt, zu dem man zurückgehen könnte. Wenn die Zusammenführung nicht trivial ist, bedeutet dies, dass Sie nicht weiter an Ihrem Feature arbeiten können, bevor Sie das Ergebnis der Zusammenführung bereinigt haben.

Aber vielleicht ist das nur ein Vorteil für Leute, die zu dumm sind, getrennte Zweige zu benutzen (wenn ich mich richtig erinnere, hatten wir in der Firma, in der ich SVN benutzte, nur einen Zweig, der für die Entwicklung verwendet wurde).

10voto

Peter Punkte 961

EDIT: Dies richtet sich in erster Linie an dieser Teil der Frage:
Liegt das tatsächlich an inhärenten Unterschieden in der Arbeitsweise der beiden Systeme, oder haben bestimmte DVCS-Implementierungen wie Git/Mercurial einfach cleverere Zusammenführungsalgorithmen als SVN?
TL;DR - Diese speziellen Tools haben bessere Algorithmen. Die Verteilung hat einige Vorteile für den Arbeitsablauf, ist aber orthogonal zu den Vorteilen der Zusammenführung.
ENDE BEARBEITEN

Ich habe die akzeptierte Antwort gelesen. Sie ist schlichtweg falsch.

SVN Die Zusammenführung kann mühsam und umständlich sein. Aber lassen wir einmal außer Acht, wie es tatsächlich funktioniert. Es gibt keine Informationen, die Git behält oder ableiten kann, die SVN nicht auch behält oder ableiten kann. Noch wichtiger ist, dass es keinen Grund gibt, warum das Beibehalten von separaten (manchmal partiellen) Kopien des Versionskontrollsystems Ihnen mehr aktuelle Informationen liefern wird. Die beiden Strukturen sind völlig gleichwertig.

Nehmen wir an, Sie wollen "irgendeine clevere Sache" machen, die Git "besser kann". Und Ihre Sache ist in SVN eingecheckt.

Konvertieren Sie Ihr SVN in die äquivalente Git-Form, machen Sie es in Git, und überprüfen Sie dann das Ergebnis, vielleicht unter Verwendung mehrerer Commits, einiger zusätzlicher Zweige. Wenn Sie sich einen automatisierten Weg vorstellen können, ein SVN-Problem in ein Git-Problem umzuwandeln, dann hat Git keinen grundlegenden Vorteil.

Letztendlich kann ich mit jedem Versionskontrollsystem

1. Generate a set of objects at a given branch/revision.
2. Provide the difference between a parent child branch/revisions.

Außerdem ist es für die Zusammenführung nützlich (oder entscheidend), Folgendes zu wissen

3. The set of changes have been merged into a given branch/revision.

Mercurial Git und Subversion (jetzt nativ, vorher mit svnmerge.py) können alle drei Informationen liefern. Um etwas grundlegend Besseres mit DVC zu demonstrieren, zeigen Sie bitte eine vierte Information auf, die in Git/Mercurial/DVC verfügbar ist und in SVN/zentralisiertem VC nicht verfügbar ist.

Das heißt aber nicht, dass sie nicht bessere Werkzeuge sind!

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X