J'ai entendu à quelques endroits que l'une des principales raisons pour lesquelles les systèmes de contrôle de version distribués brillent est la fusion bien meilleure que dans les outils traditionnels comme SVN. Est-ce réellement dû à des différences inhérentes au fonctionnement des deux systèmes, ou les implémentations DVCS spécifiques comme Git / Mercurial ont-elles simplement des algorithmes de fusion plus intelligents que SVN?
400
Réponses:
L'affirmation de la raison pour laquelle la fusion est meilleure dans un DVCS que dans Subversion était largement basée sur la façon dont la branche et la fusion fonctionnaient dans Subversion il y a quelque temps. Subversion antérieure à 1.5.0 ne stockait aucune information sur la fusion des branches, donc lorsque vous vouliez fusionner, vous deviez spécifier la plage de révisions à fusionner.
Alors pourquoi les fusions de Subversion sont-elles nulles ?
Méditez sur cet exemple:
Lorsque nous voulons fusionner les modifications de b1 dans le tronc, nous émettons la commande suivante, tout en se tenant sur un dossier dont le tronc a été extrait:
… Qui tentera de fusionner les modifications de
b1
dans votre répertoire de travail local. Et puis vous validez les modifications après avoir résolu tous les conflits et testé le résultat. Lorsque vous validez, l'arbre de révision ressemblerait à ceci:Cependant, cette façon de spécifier les plages de révisions devient rapidement incontrôlable lorsque l'arborescence des versions se développe, car subversion n'avait pas de métadonnées sur le moment et les révisions fusionnées. Réfléchissez à ce qui se passera plus tard:
C'est en grande partie un problème lié à la conception du référentiel de Subversion, afin de 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 quand et quoi les choses ont de nouveau fusionné. Cela conduira parfois à de mauvais conflits de fusion. Ce qui était encore pire, c'est que Subversion a utilisé la fusion bidirectionnelle par défaut, ce qui présente certaines limitations paralysantes dans la fusion automatique lorsque deux têtes de branche ne sont pas comparées à leur ancêtre commun.
Pour atténuer cette Subversion stocke désormais les métadonnées pour la branche et la fusion. Cela résoudrait tous les problèmes, non?
Et oh, au fait, Subversion est toujours nul…
Sur un système centralisé, comme la subversion, les répertoires virtuels sont nuls. Pourquoi? Parce que tout le monde a accès pour les voir… même les déchets expérimentaux. La ramification est bonne si vous voulez expérimenter mais vous ne voulez pas voir l'expérimentation de tout le monde et de ses tantes . Il s'agit d'un grave bruit cognitif. Plus vous ajoutez de branches, plus vous verrez de conneries.
Plus vous avez de branches publiques dans un référentiel, plus il sera difficile de garder une trace de toutes les différentes branches. Donc, la question que vous vous posez est de savoir si la branche est toujours 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 par défaut une grande branche de toute façon. Ce qui est dommage car à son tour, il sera difficile de garder une trace des versions de test et de sortie, et tout ce qui est bon vient de la branche.
Alors pourquoi les DVCS, tels que Git, Mercurial et Bazaar, sont-ils meilleurs que Subversion pour créer des branches et fusionner?
Il y a une raison très simple: la ramification 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 qui doivent être tels pour fonctionner simplement avec la synchronisation des référentiels (c'est -à- dire pousser et tirer ).
La première chose que vous faites lorsque vous travaillez avec un DVCS est de cloner des référentiels (git
clone
, hgclone
et bzrbranch
). Le clonage est conceptuellement la même chose que la création d'une branche dans le contrôle de version. Certains appellent cela une fourche ou une ramification (bien que cette dernière soit souvent utilisée pour désigner des branches colocalisées), mais c'est la même chose. Chaque utilisateur exécute son propre référentiel, ce qui signifie que vous avez une branche par utilisateur en cours.La structure de la version n'est pas un arbre , mais plutôt un graphique à la place. Plus précisément un graphe acyclique dirigé (DAG, c'est-à-dire un graphe sans cycle). Vous n'avez vraiment pas besoin de vous attarder sur les spécificités d'un DAG autre que chaque commit a une ou plusieurs références parentes (sur lesquelles était basé le commit). Les graphiques suivants montreront donc les flèches entre les révisions à l'envers à cause de cela.
Un exemple très simple de fusion serait celui-ci; imaginez un référentiel central appelé
origin
et un utilisateur, Alice, clonant le référentiel sur sa machine.Ce qui se passe pendant un clone est que chaque révision est copiée dans Alice exactement comme elle était (ce qui est validé par les identifiants de hachage identifiables de manière unique), et marque où se trouvent les branches de l'origine.
Alice travaille ensuite sur son dépôt, s'engageant dans son propre référentiel et décide de pousser ses modifications:
La solution est plutôt simple, la seule chose que le
origin
référentiel doit faire est de prendre en compte toutes les nouvelles révisions et de déplacer sa branche vers la dernière révision (que git appelle "fast-forward"):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 vraiment lié à la fusion des algorithmes, car l'algorithme de fusion à trois voies est à peu près le même entre tous les systèmes de contrôle de version. Le problème concerne plus la structure qu'autre chose .
Alors que diriez-vous de me montrer un exemple qui a une vraie fusion?
Certes, l'exemple ci-dessus est un cas d'utilisation très simple, alors faisons-en un bien plus tordu bien que plus courant. Rappelez-vous que cela a
origin
commencé avec trois révisions? Eh bien, le gars qui les a fait, appelons-le Bob , a travaillé seul et a fait un commit sur son propre référentiel:Maintenant, Bob ne peut pas pousser ses modifications directement vers le
origin
référentiel. La façon dont le système le détecte consiste à vérifier si les révisions de Bob descendent directement de cellesorigin
de, ce qui n'est pas le cas dans ce cas. Toute tentative de poussée entraînera dans le système quelque chose qui s'apparente à " Euh ... je crains que je ne puisse pas vous laisser faire ça Bob ."Donc, Bob doit faire un pull-in puis fusionner les modifications (avec git's
pull
, ou hg'spull
etmerge
; or bzr'smerge
). Il s'agit d'un processus en deux étapes. Bob doit d'abord récupérer les nouvelles révisions, qui les copieront telles quelles depuis leorigin
référentiel. On voit maintenant que le graphique diverge:La deuxième étape du processus d'extraction consiste à fusionner les conseils divergents et à valider le résultat:
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
etmerge
). Ce qui doit être fait plus tard est de réintroduire ces modifications dansorigin
, ce qui entraînera une fusion rapide car la validation de la fusion est une descendante directe de la dernière duorigin
référentiel:Il existe une autre option pour fusionner dans git et hg, appelée rebase , qui déplacera les modifications de Bob après les dernières modifications. Comme je ne veux pas que cette réponse soit plus verbeuse, je vous laisse plutôt lire les documents git , mercurial ou bazaar à ce sujet.
En tant qu'exercice pour le lecteur, essayez de découvrir comment cela fonctionnera avec un autre utilisateur impliqué. Il en est de même pour l'exemple ci-dessus avec Bob. La fusion entre référentiels est plus facile que vous ne le pensez car toutes les révisions / validations sont identifiables de manière unique.
Il y a aussi le problème 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'est-à-dire qu'il a effectué une validation de fusion) et l'envoie pour que tous les autres membres de l'équipe les consomment en les poussant vers un référentiel central ou en envoyant des correctifs, ils n'ont plus à se soucier de la fusion, car cela s'est déjà produit . Martin Fowler qualifie cette façon de travailler d' intégration de promiscuité .
Étant donné que la structure est différente de Subversion, en utilisant à la place un DAG, elle permet de créer des branchements et des fusions de manière plus simple non seulement pour le système mais aussi pour l'utilisateur.
la source
Historiquement, Subversion n'a pu effectuer une fusion bidirectionnelle que parce qu'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 un algorithme de fusion à 3 voies par défaut, ce qui implique de trouver un ancêtre commun aux têtes à fusionner et d'utiliser les connaissances qui existent des deux côtés de la fusion. Cela permet à Git d'être plus intelligent pour éviter les conflits.
Git a également du code de recherche de renommage sophistiqué, qui aide également. Il ne stocke pas les changements ni ne stocke aucune information de suivi - il stocke simplement l'état des fichiers à chaque validation et utilise des heuristiques pour localiser les renommages et les mouvements de code selon les besoins (le stockage sur disque est plus compliqué que cela, mais l'interface il présente à la couche logique n'expose aucun suivi).
la source
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 de futures fusions sans l'aide de l'utilisateur qui devait fournir des informations que SVN n'enregistrait pas. Avec la version 1.5, cela s'est amélioré, 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 renommages 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 bute toujours sur eux dans des situations de modification / renommage, c'est-à-dire, lorsqu'un fichier est modifié sur une branche et renommé sur l'autre, et ces branches sont à fusionner. De telles situations produiront toujours de faux conflits de fusion et, dans le cas de renommages de répertoires, cela entraînera même une perte silencieuse des modifications. (Les personnes SVN ont alors tendance à souligner que les modifications sont toujours dans l'histoire, mais cela n'aide pas beaucoup 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 renommages, mais les comprend après coup (au moment de la fusion), et le fait de manière assez magique.
La représentation de fusion SVN a également des problèmes; dans 1.5 / 1.6, vous pouviez fusionner de tronc à branche aussi souvent que vous le vouliez, automatiquement, mais une fusion dans l'autre sens devait être annoncée (
--reintegrate
), et laissait la branche dans un état inutilisable. Beaucoup plus tard, ils ont découvert que ce n'était pas le cas et que a) le problème--reintegrate
pouvait être déterminé automatiquement et b) des fusions répétées dans les deux sens étaient possibles.Mais après tout cela (qui à mon humble avis montre un manque de compréhension de ce qu'ils font), je serais (OK, je suis) très prudent d'utiliser SVN dans tout scénario de branchement non trivial, et j'essaierais idéalement de voir ce que Git pense de le résultat de la fusion.
D'autres points soulevés dans les réponses, comme la visibilité globale forcée des branches dans SVN, ne sont pas pertinents pour les capacités de fusion (mais pour la convivialité). De plus, les 'Git stocke les changements tandis que les magasins SVN (quelque chose de différent)' sont généralement hors de propos. Git stocke conceptuellement chaque commit dans une arborescence distincte (comme un fichier tar ), puis utilise pas mal d'heuristiques pour le stocker efficacement. Le calcul des modifications entre deux validations est distinct de l'implémentation du stockage. Ce qui est vrai, c'est que Git stocke l'historique DAG sous une forme beaucoup plus simple que SVN fait son mergeinfo. Quiconque essaie de comprendre ce dernier saura ce que je veux dire.
En bref: Git utilise un modèle de données beaucoup plus simple pour stocker les révisions que SVN, et donc il pourrait mettre beaucoup d'énergie dans les algorithmes de fusion réels plutôt que d'essayer de faire face à la représentation => fusion pratiquement meilleure.
la source
Une chose qui n'a pas été mentionnée dans les autres réponses, et qui est vraiment un gros avantage d'un DVCS, est que vous pouvez vous engager localement avant de pousser vos modifications. Dans SVN, lorsque j'ai eu quelques modifications, je voulais m'enregistrer, et que quelqu'un avait déjà fait un commit sur la même branche entre-temps, cela signifiait que je devais faire un
svn update
avant de pouvoir valider. Cela signifie que mes modifications et les modifications de l'autre personne sont maintenant mélangées et qu'il n'y a aucun moyen d'annuler la fusion (comme avecgit reset
ouhg update -C
), car il n'y a pas de validation sur laquelle revenir. Si la fusion n'est pas anodine, cela signifie que vous ne pouvez pas continuer à travailler sur votre fonctionnalité avant d'avoir nettoyé le résultat de la fusion.Mais alors, ce n'est peut-être qu'un avantage pour les gens qui sont trop stupides pour utiliser des branches distinctes (si je me souviens bien, nous n'avions qu'une seule branche qui était utilisée pour le développement dans l'entreprise où j'ai utilisé SVN).
la source
EDIT: Cela répond principalement à cette partie de la question:
est-ce réellement dû à des différences inhérentes au fonctionnement des deux systèmes, ou les implémentations DVCS spécifiques comme Git / Mercurial ont-elles simplement des algorithmes de fusion plus intelligents que SVN?
TL; DR - Ces outils spécifiques ont de meilleurs algorithmes. La distribution présente certains avantages pour le flux de travail, mais elle est orthogonale aux avantages de la fusion.
FIN DE LA MODIFICATION
J'ai lu la réponse acceptée. C'est tout simplement faux.
La fusion de SVN peut être une douleur, mais elle peut également être lourde. 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 pas ou ne peut pas dériver. Plus important encore, il n'y a aucune raison pour que la conservation de 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 totalement équivalentes.
Supposons que vous vouliez faire "quelque chose d'intelligent" Git est "meilleur". Et votre chose est enregistrée dans SVN.
Convertissez votre SVN dans le formulaire Git équivalent, faites-le dans Git, puis vérifiez le résultat dans, peut-être en utilisant plusieurs validations, quelques branches supplémentaires. Si vous pouvez imaginer un moyen automatisé de transformer un problème SVN en un problème Git, alors Git n'a aucun avantage fondamental.
À la fin de la journée, tout système de contrôle de version me permettra
De plus, pour la fusion, il est également utile (ou critique) de savoir
Mercurial , Git et Subversion (maintenant nativement, 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 non disponible dans SVN / VC centralisé.
Cela ne veut pas dire que ce ne sont pas de meilleurs outils!
la source
git merge-base
. Avec git, vous pouvez dire "branches a et b divisées à la révision x". Mais svn stocke "les fichiers ont été copiés de foo vers bar", vous devez donc utiliser une heuristique pour déterminer que la copie dans la barre créait une nouvelle branche au lieu de copier des fichiers dans un projet. L'astuce est qu'une révision dans svn est définie par le numéro de révision et le chemin de base. Même s'il est possible de supposer la plupart du temps un «tronc», il mord s'il y a effectivement des branches.SVN suit les fichiers tandis que Git suit
leschangements decontenu. Il est assez intelligent pour suivre un bloc de code qui a été refactorisé d'une classe / fichier à l'autre. Ils utilisent deux approches différentes et complètes pour suivre votre source.J'utilise toujours beaucoup SVN, mais je suis très satisfait des quelques fois où j'ai utilisé Git.
Une bonne lecture si vous avez le temps: pourquoi j'ai choisi Git
la source
Il suffit de lire un article sur le blog de Joel (malheureusement son dernier). Celui-ci concerne Mercurial, mais il parle en fait des avantages des systèmes VC distribués tels que Git.
Lisez l'article ici .
la source