it-swarm.com.de

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, die bessere Verschmelzung ist als bei herkömmlichen Tools wie SVN. Liegt dies tatsächlich an inhärenten Unterschieden in der Funktionsweise der beiden Systeme, oder haben bestimmte DVCS-Implementierungen wie Git/Mercurial einfach cleverere Zusammenführungsalgorithmen als SVN?

397
Mr. Boy

Die Behauptung, warum das Zusammenführen in einem DVCS besser ist als in Subversion, beruhte größtenteils darauf, wie das Verzweigen und Zusammenführen vor einiger Zeit in Subversion funktioniert hat. Subversion vor 1.5. hat keine Informationen darüber gespeichert, wann Zweige zusammengeführt wurden. Wenn Sie also zusammenführen möchten, müssen Sie angeben, welcher Bereich von Revisionen zusammengeführt werden soll.

Warum hat Subversion suck zusammengeführt?

Betrachte dieses Beispiel:

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

Wenn wir merge b1s Änderungen in den Trunk einfügen möchten, geben wir den folgenden Befehl ein, während wir in einem Ordner stehen, in dem der Trunk ausgecheckt ist:

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

... der versucht, die Änderungen von b1 In Ihrem lokalen Arbeitsverzeichnis zusammenzuführen. Anschließend übernehmen Sie die Änderungen, nachdem Sie alle Konflikte gelöst und das Ergebnis getestet haben. Wenn Sie einen Commit durchführen, sieht der Revisionsbaum folgendermaßen aus:

      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 wird jedoch schnell außer Kontrolle geraten, wenn der Versionsbaum wächst, da Subversion keine Metadaten darüber hatte, wann und welche Revisionen zusammengeführt wurden. Überlegen Sie, 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 eine Verzweigung zu erstellen, müssen Sie im Repository ein neues virtuelles Verzeichnis erstellen, das eine Kopie des Trunks enthält, dies jedoch nicht Es werden keine Informationen darüber gespeichert, wann und in welchen Dingen wieder zusammengeführt wurde. Dies kann zu unangenehmen Zusammenführungskonflikten führen. Was noch schlimmer war, ist, dass Subversion standardmäßig die bidirektionale Zusammenführung verwendet. Dies hat einige lästige Einschränkungen bei der automatischen Zusammenführung, wenn zwei Zweigköpfe nicht mit ihrem gemeinsamen Vorfahren verglichen werden.

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

Und ach übrigens, Subversion ist immer noch zum Kotzen ...

Auf einem zentralen System wie Subversion virtuelle Verzeichnisse saugen. Warum? Weil jeder Zugang hat, um sie anzusehen ... sogar die experimentellen Müll. Das Verzweigen ist gut, wenn Sie experimentieren möchten , aber Sie möchten nicht, dass alle und ihre Tanten experimentieren . Dies ist schwerwiegendes kognitives Rauschen. Je mehr Zweige du hinzufügst, desto mehr Mist wirst du sehen.

Je mehr öffentliche Zweigstellen sich in einem Repository befinden, desto schwieriger wird es, die verschiedenen Zweigstellen im Auge zu behalten. Die Frage ist also, ob sich der Zweig noch in der Entwicklung befindet oder ob er wirklich tot ist, was in einem zentralen Versionskontrollsystem schwer zu sagen ist.

In den meisten Fällen wird eine Organisation, wie ich gesehen habe, ohnehin standardmäßig einen großen Zweig verwenden. Was schade ist, denn es wird wiederum schwierig sein, den Überblick über Test- und Release-Versionen zu behalten, und was auch immer gut ist, ergibt sich aus der Verzweigung.

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

Es gibt einen sehr einfachen Grund, warum: Verzweigung ist ein erstklassiges Konzept . Es gibt keine virtuellen Verzeichnisse von Natur aus und Verzweigungen sind harte Objekte in DVCS, die es sein muss, um einfach mit der Synchronisation von Repositorys zu arbeiten (dh Push und ziehen).

Das erste, was Sie bei der Arbeit mit einem DVCS tun, ist das Klonen von Repositorys (gits clone , hgs clone und bzrs branch ). Das Klonen ist konzeptionell dasselbe wie das Erstellen einer Verzweigung in der Versionskontrolle. Einige nennen dies Verzweigen oder Verzweigen (obwohl letzteres oft auch für zusammenliegende Zweige verwendet wird), aber es ist genau dasselbe. Jeder Benutzer führt ein eigenes Repository, was bedeutet, dass eine pro Benutzer Verzweigung stattfindet.

Die Versionsstruktur ist kein Baum , sondern ein Graph . Genauer gesagt ein gerichteter azyklischer Graph (DAG, dh ein Graph, der keine Zyklen hat). Sie müssen sich wirklich nicht mit den Einzelheiten einer DAG befassen, außer dass für jedes Commit ein oder mehrere übergeordnete Verweise vorhanden sind (auf denen das Commit basiert). Die folgenden Grafiken zeigen daher die Pfeile zwischen den Revisionen in umgekehrter Reihenfolge.

Ein sehr einfaches Beispiel für das Zusammenführen wäre dies; Stellen Sie sich ein zentrales Repository mit dem Namen Origin und eine Benutzerin namens Alice vor, die das Repository auf ihren Computer klont.

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

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

Während eines Klons wird jede Revision genau so nach Alice kopiert, wie sie war (was durch die eindeutig identifizierbaren Hash-IDs bestätigt wird) und markiert, wo sich die Zweige des Ursprungs befinden.

Alice arbeitet dann an ihrem Repo, legt sich in ihrem eigenen Repository fest und beschließt, ihre Änderungen zu pushen:

         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 ziemlich einfach. Das einzige, was das Origin -Repository tun muss, ist, alle neuen Revisionen aufzunehmen und den Zweig auf die neueste Revision zu verschieben (die 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

In dem Anwendungsfall , den ich oben dargestellt habe, muss nicht einmal etwas zusammengeführt werden . Das Problem betrifft also nicht die Zusammenführungsalgorithmen, da der Drei-Wege-Zusammenführungsalgorithmus zwischen allen Versionskontrollsystemen ziemlich gleich ist. Bei dem Thema geht es mehr um Struktur als um irgendetwas .

Wie wäre es, wenn Sie mir ein Beispiel zeigen, bei dem real zusammengeführt wird?

Zugegeben, das obige Beispiel ist ein sehr einfacher Anwendungsfall. Lassen Sie uns also einen viel verdrehteren, wenn auch häufigeren Fall machen. Denken Sie daran, dass Origin mit drei Revisionen begann? Nun, der Typ, der sie gemacht hat, nennt ihn Bob, hat alleine gearbeitet und ein Commit für 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 direkt in das Repository Origin übertragen. Das System erkennt dies, indem es überprüft, ob die Revisionen von Bob direkt von den Revisionen von Origin stammen, was in diesem Fall nicht der Fall ist. Jeder Versuch zu pushen wird dazu führen, dass das System etwas sagt, das " Äh ... ich fürchte, Sie können das nicht zulassen, Bob ".

Also muss Bob die Änderungen ziehen und dann zusammenführen (mit gits pull ; oder hgs pull und merge ; oder bzrs merge ). Dies ist ein zweistufiger Prozess. Zuerst muss Bob die neuen Revisionen abrufen, die sie so kopieren, wie sie aus dem Repository Origin stammen. Wir können jetzt sehen, dass die Grafik 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 festzuschreiben:

                                 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 in git mit fetch und merge manuell ausführen =). Was später getan werden muss, ist, diese Änderungen erneut in Origin zu pushen, was zu einer schnellen Zusammenführung führt, da der Merge-Commit ein direkter Nachfolger des neuesten im Repository von Origin ist :

                                 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, die in git und hg zusammengeführt werden kann. Sie heißt rebase und verschiebt Bobs Änderungen nach den neuesten Änderungen. Da ich nicht möchte, dass diese Antwort ausführlicher ist, lasse ich Sie stattdessen die git , Mercurial oder Bazaar Dokumente darüber lesen .

Versuchen Sie als Übung für den Leser herauszufinden, wie es mit einem anderen beteiligten Benutzer funktionieren wird. Es ist ähnlich wie im obigen Beispiel mit Bob gemacht. Das Zusammenführen zwischen Repositorys ist einfacher als gedacht, da alle Revisionen/Commits eindeutig identifizierbar sind.

Es gibt auch das Problem des Versendens von Patches zwischen den Entwicklern, was ein großes Problem in Subversion war, das in git, hg und bzr durch eindeutig identifizierbare Revisionen gemildert wird. Sobald jemand seine Änderungen zusammengeführt hat (dh ein Zusammenführungs-Commit durchgeführt hat) und es an alle anderen im Team sendet, um es zu verbrauchen, indem es entweder an ein zentrales Repository schiebt oder Patches sendet, müssen sie sich nicht mehr um die Zusammenführung kümmern, da dies bereits geschehen ist . Martin Fowler nennt diese Arbeitsweise promiskuitive Integration .

Da sich die Struktur von Subversion unterscheidet, wird durch die Verwendung einer DAG das Verzweigen und Zusammenführen nicht nur für das System, sondern auch für den Benutzer einfacher.

552
Spoike

In der Vergangenheit war Subversion nur in der Lage, eine direkte wechselseitige Zusammenführung durchzuführen, da keine Zusammenführungsinformationen gespeichert wurden. Dazu müssen eine Reihe von Änderungen vorgenommen und auf einen Baum angewendet werden. Dies ist auch bei Zusammenführungsinformationen die am häufigsten verwendete Zusammenführungsstrategie.

Git verwendet standardmäßig einen 3-Wege-Zusammenführungsalgorithmus, bei dem ein gemeinsamer Vorfahr für die zusammenzuführenden Köpfe gesucht und das Wissen genutzt wird, das auf beiden Seiten der Zusammenführung vorhanden ist. Dadurch kann Git Konflikte intelligenter vermeiden.

Git hat auch einige ausgefeilte Umbenennungsfindungs-Codes, die auch helfen. Es nicht speichert Änderungssätze oder Verfolgungsinformationen - es speichert nur den Status der Dateien bei jedem Commit und verwendet Heuristiken, um Umbenennungen und Codebewegungen nach Bedarf zu lokalisieren (der Festplattenspeicher ist mehr komplizierter als dies, aber die Schnittstelle, die sie für die Logikschicht darstellt, macht keine Nachverfolgung sichtbar.

29
Andrew Aylett

Einfach ausgedrückt, wird die Merge-Implementierung in Git besser ausgeführt als in SVN . Bevor SVN 1.5 keine Zusammenführungsaktion aufzeichnete, war es nicht möglich, zukünftige Zusammenführungen ohne Hilfe des Benutzers durchzuführen, der Informationen bereitstellen musste, die SVN nicht aufzeichnete. Mit 1.5 wurde es besser, und tatsächlich ist das SVN-Speichermodell etwas leistungsfähiger als Gits DAG. Aber SVN hat die Zusammenführungsinformationen in einer ziemlich verworrenen Form gespeichert, so dass Zusammenführungen erheblich mehr Zeit in Anspruch nehmen als in Git - ich habe Faktoren von 300 in der Ausführungszeit beobachtet.

SVN behauptet auch, Umbenennungen zu verfolgen, um das Zusammenführen von verschobenen Dateien zu unterstützen. Tatsächlich werden sie jedoch immer noch als Kopie und als separate Löschaktion gespeichert, und der Zusammenführungsalgorithmus stolpert immer noch über sie, wenn eine Datei in einem Zweig geändert und in dem anderen umbenannt wird zusammengeführt werden. Solche Situationen führen immer noch zu falschen Zusammenführungskonflikten, und im Fall von Verzeichnisumbenennungen führt dies sogar zu einem stillen Verlust von Änderungen. (Die SVN-Leute neigen dann dazu, darauf hinzuweisen, dass die Änderungen noch im Verlauf sind, aber das hilft nicht viel, wenn sie nicht in einem Zusammenführungsergebnis sind, in dem sie erscheinen sollen.

Auf der anderen Seite verfolgt Git nicht einmal Umbenennungen, sondern findet sie nachträglich (zum Zeitpunkt des Zusammenführens) heraus und macht dies auf magische Weise.

Die SVN-Zusammenführungsdarstellung weist ebenfalls Probleme auf. in 1.5/1.6 konnte man beliebig oft automatisch von Stamm zu Zweig zusammenführen, aber es musste eine Zusammenführung in die andere Richtung angekündigt werden (--reintegrate) und hat den Zweig unbrauchbar gemacht. Viel später fanden sie heraus, dass dies tatsächlich nicht der Fall ist und dass a) das --reintegrate kann automatisch ermittelt werden, und b) wiederholte Zusammenführungen in beide Richtungen sind möglich.

Aber nach all dem (was meiner Meinung nach ein Mangel an Verständnis dafür ist, was sie tun), wäre ich (OK, ich bin) sehr vorsichtig, SVN in jedem nicht-trivialen Verzweigungsszenario zu verwenden, und würde idealerweise versuchen, zu sehen, was Git denkt das Ergebnis der Zusammenführung.

Andere in den Antworten aufgeführte Punkte, wie die erzwungene globale Sichtbarkeit von Zweigniederlassungen in SVN, sind für die Zusammenführungsfunktionen nicht relevant (jedoch für die Benutzerfreundlichkeit). Auch die "Git speichert Änderungen, während SVN speichert (etwas anderes)" sind meistens falsch. Git speichert jedes Commit konzeptionell als separaten Baum (wie eine tar - Datei) und verwendet dann einige Heuristiken, um dies effizient zu speichern. Die Berechnung der Änderungen zwischen zwei Festschreibungen erfolgt unabhängig von der Speicherimplementierung. Was wahr ist, ist, dass Git die Verlaufs-DAG in einer viel einfacheren Form speichert, als SVN seine Mergeinfo macht. Jeder, der versucht, Letzteres zu verstehen, wird wissen, was ich meine.

Auf den Punkt gebracht: Git verwendet ein viel einfacheres Datenmodell zum Speichern von Revisionen als SVN und könnte daher viel Energie in die eigentlichen Zusammenführungsalgorithmen stecken, anstatt zu versuchen, mit der Darstellung fertig zu werden => praktisch besseres Zusammenführen.

17
Andreas Krey

Eine Sache, die in den anderen Antworten nicht erwähnt wurde und die wirklich ein großer Vorteil eines DVCS ist, ist, dass Sie ein lokales Commit durchführen können, bevor Sie Ihre Änderungen übermitteln. In SVN, als ich eine Änderung hatte, die ich einchecken wollte und in der Zwischenzeit bereits ein Commit für denselben Zweig ausgeführt hatte, bedeutete dies, dass ich ein svn update bevor ich mich festlegen konnte. Dies bedeutet, dass meine Änderungen und die Änderungen der anderen Person jetzt gemischt werden und es keine Möglichkeit gibt, die Zusammenführung abzubrechen (wie bei git reset oder hg update -C), weil es kein Commit gibt, zu dem zurückgekehrt werden kann. Wenn die Zusammenführung nicht trivial ist, bedeutet dies, dass Sie nicht weiter an Ihrem Feature arbeiten können, bevor Sie das Zusammenführungsergebnis bereinigt haben.

Aber dann ist das vielleicht nur ein Vorteil für Leute, die zu dumm sind, um separate Zweige zu verwenden (wenn ich mich recht entsinne, hatten wir nur einen Zweig, der für die Entwicklung in der Firma verwendet wurde, in der ich SVN verwendet habe).

11
daniel kullmann

EDIT: Dies ist in erster Linie angesprochen dieser Teil der Frage:
Ist dies tatsächlich auf inhärente Unterschiede in der Funktionsweise der beiden Systeme zurückzuführen, oder haben bestimmte DVCS-Implementierungen wie Git/Mercurial nur cleverere Zusammenführungsalgorithmen als SVN?
TL; DR - Diese spezifischen Tools haben bessere Algorithmen. Die Verteilung hat einige Workflow-Vorteile, ist jedoch orthogonal zu den Vorteilen der Zusammenführung.
ENDE BEARBEITEN

Ich habe die akzeptierte Antwort gelesen. Es ist einfach falsch.

SVN Das Zusammenführen kann schmerzhaft und auch umständlich sein. Aber ignorieren Sie für eine Minute, wie es tatsächlich funktioniert. Es gibt keine Informationen, die Git beibehält oder ableiten kann, die SVN ebenfalls nicht beibehält oder ableiten kann. Noch wichtiger ist, dass es keinen Grund gibt, separate (manchmal teilweise) Kopien des Versionskontrollsystems zu führen, die Ihnen aktuellere Informationen liefern. Die beiden Strukturen sind völlig gleichwertig.

Angenommen, Sie möchten "eine clevere Sache" machen. Git ist "besser in". Und dein Ding ist in SVN eingecheckt.

Konvertieren Sie Ihre SVN in die entsprechende Git-Form, führen Sie sie in Git aus und überprüfen Sie dann das Ergebnis in einigen zusätzlichen Zweigen, wobei Sie möglicherweise mehrere Commits verwenden. Wenn Sie sich einen automatisierten Weg vorstellen können, ein SVN-Problem in ein Git-Problem umzuwandeln, hat Git keinen grundsätzlichen Vorteil.

Letztendlich lässt mich jedes Versionskontrollsystem

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

Für das Zusammenführen ist es außerdem nützlich (oder kritisch) zu wissen

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

Mercurial , Git und Subversion (jetzt ursprünglich mit svnmerge.py) können alle drei Informationen bereitstellen. Um etwas grundlegend Besseres mit DVC zu demonstrieren, weisen Sie bitte auf eine vierte Information hin, die in Git/Mercurial/DVC verfügbar ist, nicht in SVN/Centralized VC.

Das heißt nicht, dass sie keine besseren Werkzeuge sind!

10
Peter

SVN verfolgt Dateien, während Git verfolgt inhalt Änderungen. Es ist klug genug, einen Codeblock zu verfolgen, der von einer Klasse/Datei in eine andere überarbeitet wurde. Sie verwenden zwei völlig unterschiedliche Ansätze, um Ihre Quelle zu verfolgen.

Ich benutze SVN immer noch stark, aber ich bin sehr zufrieden mit den wenigen Malen, bei denen ich Git benutzt habe.

Eine nette Lektüre, wenn Sie die Zeit haben: Warum ich mich für Git entschieden habe

8
used2could

Lies einfach einen Artikel in Joels Blog (leider sein letzter). Hier geht es um Mercurial, aber es geht tatsächlich um die Vorteile von Distributed VC Systemen wie Git.

Bei der verteilten Versionskontrolle ist der verteilte Teil eigentlich nicht der interessanteste Teil. Der interessante Teil ist, dass diese Systeme in Form von Änderungen und nicht in Form von Versionen denken.

Lesen Sie den Artikel hier .

6
rubayeet