web-dev-qa-db-fra.com

Comment et / ou pourquoi la fusion dans Git est-elle meilleure que dans SVN?

J'ai entendu dire à quelques endroits que l'une des principales raisons pour lesquelles les systèmes de contrôle de version distribués brillent est la fusion beaucoup mieux que dans les outils traditionnels tels que SVN. Est-ce réellement dû à des différences inhérentes dans le fonctionnement des deux systèmes, ou bien les implémentations de spécifiques DVCS telles que Git/Mercurial ont-elles des algorithmes de fusion plus intelligents que SVN?

397
Mr. Boy

L’affirmation selon laquelle la fusion est meilleure dans un DVCS que dans Subversion reposait en grande partie sur la façon dont fonctionnaient la création de branches et la fusion dans Subversion il ya quelque temps. Subversion antérieure à 1.5. ne stockait aucune information sur le moment où les branches étaient fusionnées. Par conséquent, lorsque vous souhaitez fusionner, vous devez spécifier la plage de révisions à fusionner.

Alors pourquoi Subversion fusionne-t-il suck ?

Réfléchissez à cet exemple:

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

Lorsque nous voulons fusionner les modifications de b1 dans le coffre, nous émettons la commande suivante, en nous tenant sur un dossier dont le coffre a été extrait:

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

… Qui tentera de fusionner les modifications de b1 dans votre répertoire de travail local. Et vous validez les modifications après avoir résolu les conflits et testé le résultat. Lorsque vous validez, l’arbre de révision ressemble à ceci:

      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

Cependant, cette manière de spécifier les plages de révisions devient rapidement incontrôlable lorsque l’arborescence des versions s’agrandit, car Subversion ne disposait pas de métadonnées pour savoir quand et quelles révisions avaient été fusionnées. Réfléchissez à ce qui se passera plus tard:

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

Ceci est principalement dû à la conception du référentiel de Subversion. Pour créer une branche, vous devez créer un nouveau répertoire virtuel dans le référentiel, qui hébergera une copie du tronc. mais il ne stocke aucune information concernant le moment et les éléments fusionnés. Cela conduira parfois à des conflits de fusion désagréables. Ce qui était encore pire, c’est que Subversion utilisait la fusion bidirectionnelle par défaut, ce qui présente certaines limitations rédhibitoires en matière de fusion automatique lorsque deux têtes de branche ne sont pas comparées à leur ancêtre commun.

Pour atténuer cela, Subversion stocke maintenant les métadonnées pour les branches et les fusions. Cela résoudrait tous les problèmes, non?

Et oh, au fait, Subversion craint toujours…

Sur un système centralisé, comme Subversion, les répertoires virtuels sont nuls. Pourquoi? Parce que tout le monde a accès à les regarder… même les déchets expérimentaux. Le branchement est bon si vous voulez expérimenter mais vous ne voulez pas voir l'expérimentation de tout le monde et de leurs tantes . C'est un bruit cognitif grave. Plus vous ajoutez de branches, plus vous allez voir de la merde.

Plus vous avez de branches publiques dans un référentiel, plus il sera difficile de garder une trace de toutes les branches différentes. La question que vous vous posez est donc de savoir si la branche est encore en développement ou si elle est vraiment morte, ce qui est difficile à dire dans un système de contrôle de version centralisé.

La plupart du temps, d'après ce que j'ai vu, une organisation utilisera de toute façon une grande succursale. Ce qui est dommage, car il sera difficile de garder une trace des versions de test et de publication, et tout ce qui est bénéfique provient de la création de branches.

Alors, pourquoi les systèmes DVCS, tels que Git, Mercurial et Bazaar, sont-ils meilleurs que Subversion pour la création de branches et la fusion?

Il y a une raison très simple pour laquelle: la création de branches est un concept de première classe . Il n'y a pas de répertoires virtuels de par leur conception et les branches sont des objets durs dans DVCS dont elle a besoin pour fonctionner simplement avec la synchronisation des référentiels (c'est-à-dire ). Poussez et tirez ).

La première chose que vous faites lorsque vous travaillez avec un système DVCS consiste à cloner des référentiels (git clone , hg's clone et bzr's branch ). Conceptuellement, le clonage revient à créer une branche dans le contrôle de version. Certains appellent cela forking ou branching (bien que ce dernier soit souvent aussi utilisé pour désigner des branches co-localisées), mais c'est juste la même chose. Chaque utilisateur exécute son propre référentiel, ce qui signifie que vous avez une branche par utilisateur .

La structure de la version n'est pas un arbre , mais plutôt un graphique . Plus précisément un graphe acyclique dirigé (DAG, ce qui signifie un graphe qui n'a pas de cycle). Vous n'avez vraiment pas besoin de vous attarder sur les spécificités d'un DAG, car chaque commit a une ou plusieurs références parent (sur lesquelles le commit était basé). Les graphiques suivants montrent donc les flèches entre les révisions en sens inverse.

Voici un exemple très simple de fusion. imaginez un référentiel central appelé Origin et un utilisateur, Alice, clonant le référentiel sur sa machine.

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

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

Ce qui se passe pendant un clone, c'est que chaque révision est copiée dans Alice exactement telle qu'elle était (ce qui est validé par l'identificateur de hachage identifiable de manière unique), ainsi que les marques indiquant l'emplacement des branches de l'origine.

Alice travaille ensuite sur son référentiel, s’engage dans son propre référentiel et décide de pousser ses modifications:

         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

La solution est assez simple, la seule chose que le référentiel Origin doit faire est de prendre en compte toutes les nouvelles révisions et de déplacer sa branche vers la révision la plus récente (qui s'appelle git "fast-forward"):

         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

Le cas d'utilisation que j'ai illustré ci-dessus n'a même pas besoin de fusionner quoi que ce soit . Le problème n'est donc pas lié à la fusion des algorithmes, car l'algorithme de fusion à trois voies est pratiquement identique pour tous les systèmes de contrôle de version. Le problème concerne davantage la structure que tout le reste .

Alors que diriez-vous de me montrer un exemple qui a une fusion réel ?

Certes, l'exemple ci-dessus est un cas d'utilisation très simple, nous allons donc en faire un beaucoup plus tordu, bien que plus courant. Rappelez-vous que Origin a commencé avec trois révisions? Eh bien, le gars qui les a fait, appelons-le Bob , travaille de son côté et s’est engagé sur son propre référentiel:

         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

Maintenant, Bob ne peut pas transférer ses modifications directement dans le référentiel Origin. Le système détecte cela en vérifiant si les révisions de Bob descendent directement de Origin, ce qui n'est pas le cas dans ce cas. Toute tentative de Push aboutira dans le système à dire quelque chose qui ressemble à " Euh ... j'ai bien peur de ne pas vous laisser faire ça Bob ."

Bob doit donc intégrer et ensuite fusionner les modifications (avec git: pull ; ou hg's pull et merge ; ou bzr's merge ). C'est un processus en deux étapes. Tout d'abord, Bob doit récupérer les nouvelles révisions, qui les copieront telles qu'elles sont à partir du référentiel Origin. Nous pouvons maintenant voir que le graphique diverge:

                        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

La deuxième étape du processus d'extraction consiste à fusionner les astuces divergentes et à engager le résultat:

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

Espérons que la fusion ne rencontrera pas de conflits (si vous les anticipez, vous pouvez effectuer les deux étapes manuellement dans git avec fetch et merge ). Ce qu'il faut faire par la suite, c'est d'insérer à nouveau ces modifications dans Origin, ce qui entraînera une fusion rapide, car la validation de fusion est un descendant direct de la dernière dans le référentiel Origin:

                                 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<--+

Il existe une autre option de fusion dans git et hg, appelée rebase , qui déplacera les modifications de Bob après les dernières modifications. Puisque je ne veux pas que cette réponse soit plus verbeuse, je vous laisserai lire le git , Mercurial ou Bazaar docs à ce sujet .

Comme exercice pour le lecteur, essayez de comprendre comment cela fonctionnera avec un autre utilisateur impliqué. C'est pareil que l'exemple ci-dessus avec Bob. La fusion entre les référentiels est plus facile que ce que vous pensez, car toutes les révisions/validations sont identifiables de manière unique.

Il y a aussi la question de l'envoi de correctifs entre chaque développeur, ce qui était un énorme problème dans Subversion qui est atténué dans git, hg et bzr par des révisions identifiables de manière unique. Une fois que quelqu'un a fusionné ses modifications (c.-à-d. Effectué une validation de fusion) et l'a envoyé à tous les autres membres de l'équipe à consommer en le déplaçant vers un référentiel central ou en envoyant des correctifs, il n'a pas à s'inquiéter de la fusion car elle s'est déjà produite. . Martin Fowler appelle cette méthode de travail intégration promiscuous .

La structure étant différente de Subversion, l'utilisation d'un DAG facilite la création de branches et la fusion, non seulement pour le système, mais également pour l'utilisateur.

552
Spoike

Historiquement, Subversion n’a pu effectuer une fusion directe que dans les deux sens, car elle ne stockait aucune information de fusion. Cela implique de prendre un ensemble de modifications et de les appliquer à un arbre. Même avec les informations de fusion, c'est toujours la stratégie de fusion la plus utilisée.

Git utilise par défaut un algorithme de fusion à 3 voies, qui consiste à trouver un ancêtre commun aux têtes en cours de fusion et à utiliser les connaissances existantes des deux côtés de la fusion. Cela permet à Git d'être plus intelligent pour éviter les conflits.

Git a également un code de recherche de renommage sophistiqué, qui aide également. Il ne ne stocke pas les changesets ni aucune information de suivi - il enregistre simplement l’état des fichiers à chaque validation et utilise des méthodes heuristiques pour localiser les renomations et les mouvements de code selon les besoins (le stockage sur disque est plus important). compliqué que cela, mais l’interface qu’il présente à la couche logique n’expose aucun suivi).

29
Andrew Aylett

En termes simples, l'implémentation de la fusion se fait mieux dans Git que dans SVN . Avant la version 1.5, SVN n’enregistrait pas d’action de fusion. Il était donc impossible d’effectuer des fusions futures sans l’aide de l’utilisateur, qui devait fournir des informations que SVN n’avait pas enregistrées. Avec la version 1.5, les améliorations ont été apportées et le modèle de stockage SVN est légèrement plus performant que le DAG de Git. Mais SVN a stocké les informations de fusion sous une forme plutôt compliquée qui permet aux fusions de prendre beaucoup plus de temps que dans Git - j'ai observé des facteurs de 300 dans le temps d'exécution.

En outre, SVN prétend suivre les renommage pour faciliter la fusion des fichiers déplacés. Mais en réalité, il les stocke toujours en tant que copie et action de suppression distincte, et l'algorithme de fusion les trébuche toujours dans des situations de modification/changement de nom, c'est-à-dire lorsqu'un fichier est modifié sur une branche et renommé sur l'autre, et ces branches sont être fusionné. De telles situations produiront toujours de faux conflits de fusion et, dans le cas d'un changement de nom de répertoire, cela entraînera même une perte silencieuse de modifications. (Les gens de SVN ont alors tendance à souligner que les modifications sont encore dans l’historique, mais cela n’aide en rien, quand elles ne sont pas dans un résultat de fusion où elles devraient apparaître.

Git, d’autre part, ne suit même pas les renommage, mais les trouve après coup (au moment de la fusion), et le fait comme par magie.

La représentation de fusion de SVN a également des problèmes; dans 1.5/1.6, vous pouvez fusionner automatiquement d'une branche à l'autre autant de fois que vous le souhaitez, mais une fusion dans l'autre sens devait être annoncée (--reintegrate) et laisser la branche dans un état inutilisable. Beaucoup plus tard, ils ont découvert que ce n'était en fait pas le cas et que a) le --reintegrate peut être déterminé automatiquement, et b ) des fusions répétées dans les deux sens sont possibles.

Mais après tout cela (dont IMHO montre un manque de compréhension de ce qu’ils font), je serais (OK, je le suis) très prudent d’utiliser SVN dans tout scénario de branchement non trivial, et essaierais idéalement de voir ce que Git pense de le résultat de la fusion.

Les autres points soulevés dans les réponses, comme la visibilité globale forcée des branches dans SVN, ne sont pas pertinents pour la fusion des capacités (mais pour la facilité d’utilisation). En outre, les "magasins Git changent, tandis que les magasins SVN (quelque chose de différent)" sont pour la plupart hors sujet. Git stocke conceptuellement chaque commit sous forme d'arborescence distincte (comme un fichier tar ), puis utilise plusieurs méthodes heuristiques pour le stocker efficacement. Le calcul des modifications entre deux commits est distinct de la mise en œuvre du stockage. Ce qui est vrai, c’est que Git stocke l’historique du DAG sous une forme beaucoup plus simple que SVN ne fusionne que ses informations de fusion. Quiconque essaie de comprendre ce dernier saura ce que je veux dire.

En un mot: Git utilise un modèle de données beaucoup plus simple pour stocker les révisions que SVN, et pourrait donc consacrer beaucoup d’énergie aux algorithmes de fusion actuels plutôt que d’essayer de gérer la représentation => une fusion pratiquement meilleure.

17
Andreas Krey

Une chose qui n’a pas été mentionnée dans les autres réponses, et c’est vraiment un gros avantage d’un DVCS, est que vous pouvez valider localement avant de pousser vos modifications. Dans SVN, lorsque j'avais un changement à effectuer, je souhaitais m'enregistrer et que quelqu'un avait déjà effectué un commit dans la même branche entre temps, cela signifiait que je devais faire un svn update avant de pouvoir m'engager. Cela signifie que mes modifications, et celles de l'autre personne, sont maintenant combinées et qu'il n'y a aucun moyen d'annuler la fusion (comme avec git reset ou hg update -C), car il n'y a pas d'engagement à partir. retour à. Si la fusion est non triviale, cela signifie que vous ne pouvez pas continuer à travailler sur votre entité avant d'avoir nettoyé le résultat de la fusion.

Mais alors, peut-être n’est-ce qu’un avantage pour les personnes trop bêtes pour utiliser des branches distinctes (si je me souviens bien, nous n’avions qu’une branche utilisée pour le développement dans la société où j’utilisais SVN).

11
daniel kullmann

EDIT: Ceci concerne principalement cette partie de la question:
Est-ce réellement dû à des différences inhérentes dans le fonctionnement des deux systèmes, ou des implémentations DVCS spécifiques telles que Git/Mercurial ont-elles des algorithmes de fusion plus intelligents que SVN?
TL; DR - Ces outils spécifiques ont de meilleurs algorithmes. La distribution présente certains avantages en termes de flux de travail, mais elle est orthogonale aux avantages de la fusion.
END EDIT

J'ai lu la réponse acceptée. C'est tout simplement faux.

SVN La fusion peut être pénible, mais aussi fastidieuse. Mais ignorez comment cela fonctionne réellement pendant une minute. Il n'y a aucune information que Git conserve ou peut dériver que SVN ne conserve ni ne peut dériver. Plus important encore, il n'y a aucune raison pour que conserver des copies séparées (parfois partielles) du système de contrôle de version vous fournisse des informations plus réelles. Les deux structures sont complètement équivalentes.

Supposons que vous vouliez faire "quelque chose d'intelligent", Git est "meilleur à". Et votre chose est vérifiée dans SVN.

Convertissez votre SVN en un formulaire Git équivalent, faites-le dans Git, puis vérifiez le résultat, éventuellement avec plusieurs commits, avec quelques branches supplémentaires. Si vous pouvez imaginer un moyen automatisé de transformer un problème en SVN en un problème Git, alors Git ne présente aucun avantage fondamental.

En fin de compte, tout système de contrôle de version me permettra

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

De plus, pour fusionner, il est également utile (ou critique) de savoir

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

Mercurial , Git et Subversion (maintenant en natif, utilisant auparavant svnmerge.py) peuvent tous fournir les trois informations. Afin de démontrer quelque chose de fondamentalement meilleur avec DVC, veuillez indiquer un quatrième élément d’information disponible dans Git/Mercurial/DVC et non disponible dans SVN/VC centralisé.

Cela ne veut pas dire qu'ils ne sont pas de meilleurs outils!

10
Peter

SVN suit les fichiers tandis que Git suit contenu changements. Il est assez intelligent pour suivre un bloc de code qui a été refactoré d’une classe/fichier à un autre. Ils utilisent deux approches complètement différentes pour suivre votre source.

J'utilise encore beaucoup SVN, mais je suis très heureux des quelques fois où j'ai utilisé Git.

Une bonne lecture si vous avez le temps: Pourquoi j'ai choisi Git

8
used2could

Il suffit de lire un article sur le blog de Joel (malheureusement le dernier). Celui-ci concerne Mercurial, mais il parle en fait des avantages des systèmes distribués VC tels que Git.

Avec le contrôle de version distribuée, la partie distribuée n'est en réalité pas la partie la plus intéressante. La partie intéressante est que ces systèmes pensent en termes de changements, pas en termes de versions.

Lire l'article ici .

6
rubayeet