web-dev-qa-db-fra.com

Fusion: Hg / Git vs SVN

J'ai souvent lu que Hg (et Git et ...) sont meilleurs pour fusionner que SVN mais je n'ai jamais vu d'exemples pratiques où Hg/Git peut fusionner quelque chose où SVN échoue (ou où SVN a besoin d'une intervention manuelle). Pourriez-vous publier quelques listes pas à pas des opérations de branchement/modification/validation /...- qui montrent où SVN échouerait pendant que Hg/Git se déplace avec bonheur? Des cas pratiques, pas très exceptionnels s'il vous plaît ...

Quelques informations: nous avons quelques dizaines de développeurs travaillant sur des projets utilisant SVN, avec chaque projet (ou groupe de projets similaires) dans son propre référentiel. Nous savons comment appliquer les branches des versions et des fonctionnalités afin de ne pas rencontrer de problèmes très souvent (c'est-à-dire que nous y sommes allés, mais nous avons appris à surmonter problèmes de Joel de "un programmeur" causant un traumatisme à toute l'équipe "ou" ayant besoin de six développeurs pendant deux semaines pour réintégrer une branche "). Nous avons des branches de publication qui sont très stables et ne sont utilisées que pour appliquer des corrections de bugs. Nous avons des troncs qui devraient être suffisamment stables pour pouvoir créer une version en une semaine. Et nous avons des branches de fonctionnalités sur lesquelles des développeurs uniques ou des groupes de développeurs peuvent travailler. Oui, ils sont supprimés après la réintégration afin de ne pas encombrer le référentiel. ;)

J'essaie donc toujours de trouver les avantages de Hg/Git sur SVN. J'adorerais avoir une expérience pratique, mais il n'y a pas encore de plus gros projets que nous pourrions passer à Hg/Git, donc je suis coincé avec jouer avec de petits projets artificiels qui ne contiennent que quelques fichiers constitués. Et je cherche quelques cas où vous pouvez ressentir la puissance impressionnante du Hg/Git, car jusqu'à présent, j'ai souvent lu à leur sujet mais je ne les ai pas trouvés moi-même.

141
stmax

Je n'utilise pas Subversion moi-même, mais d'après les notes de publication de Subversion 1.5: suivi des fusionnements (fondamentaux) il semble qu'il y ait les différences suivantes par rapport au fonctionnement complet du suivi des fusions - DAG systèmes de contrôle de version comme Git ou Mercurial.

  • La fusion de tronc en branche est différente de la fusion de branche en tronc: pour une raison quelconque, la fusion de tronc en branche nécessite --reintegrate option pour svn merge.

    Dans les systèmes de contrôle de version distribués comme Git ou Mercurial, il n'y a pas de différence technique entre le tronc et la branche: toutes les branches sont créées égales (il peut y avoir social différence, cependant ). La fusion dans les deux sens se fait de la même manière.

  • Vous devez fournir un nouveau -g (--use-merge-history) option pour svn log et svn blame pour prendre en compte le suivi des fusions.

    Dans Git et Mercurial, le suivi des fusions est automatiquement pris en compte lors de l'affichage de l'historique (journal) et du blâme. Dans Git, vous ne pouvez demander à suivre le premier parent qu'avec --first-parent (Je suppose qu'une option similaire existe également pour Mercurial) pour "supprimer" les informations de suivi de fusion dans git log.

  • D'après ce que je comprends svn:mergeinfo La propriété stocke des informations par chemin sur les conflits (Subversion est basée sur les changements), tandis que dans Git et Mercurial, il s'agit simplement de valider des objets qui peuvent avoir plusieurs parents.

  • "Problèmes connus" sous-section pour le suivi de fusion dans Subversion suggère que la fusion répétée/cyclique/réfléchissante pourrait ne pas fonctionner correctement. Cela signifie qu'avec les historiques suivants, la deuxième fusion peut ne pas faire la bonne chose ('A' peut être un tronc ou une branche, et 'B' peut être une branche ou un tronc, respectivement):

     * --- * --- x --- * --- y --- * --- * --- * --- M2 <- A 
    \\/
     - * ---- M1 --- * --- * ---/<- B 
    

    Dans le cas où l'art ASCII ci-dessus est cassé: la branche 'B' est créée (bifurquée) à partir de la branche 'A' à la révision 'x', puis la branche 'A' plus tard est fusionnée à la révision 'y' dans la branche 'B' comme fusionnez 'M1', et enfin la branche 'B' est fusionnée dans la branche 'A' en tant que fusion 'M2'.

     * --- * --- x --- * ----- M1 - * --- * --- M2 <- A 
     \// 
    \- * --- y --- * --- * ---/<- B 
    

    Dans le cas où l'art ASCII ci-dessus est cassé: la branche 'B' est créée (bifurquée) à partir de la branche 'A' à la révision 'x', elle est fusionnée dans la branche 'A' à 'y' en tant que 'M1', et plus tard fusionné à nouveau dans la branche "A" comme "M2".

  • Subversion peut ne pas prendre en charge le cas avancé de fusion croisée .

     * --- b ----- B1 - M1 - * --- M3 
    \\//
    \X /
     \/\ /
    \- B2 - M2 - * 
    

    Git gère très bien cette situation dans la pratique en utilisant une stratégie de fusion "récursive". Je ne suis pas sûr de Mercurial.

  • Dans "Problèmes connus" il y a un avertissement indiquant que le suivi des fusions peut ne pas fonctionner avec les renommages de fichiers, par exemple quand un côté renomme le fichier (et peut-être le modifie), et le second modifie le fichier sans renommer (sous l'ancien nom).

    Git et Mercurial gèrent très bien un tel cas dans la pratique: Git en utilisant détection de renommage , Mercurial en utilisant suivi du renommage .

HTH

90
Jakub Narębski

Moi aussi, je cherchais un cas où, disons, Subversion ne parvient pas à fusionner une branche et Mercurial (et Git, Bazaar, ...) fait la bonne chose.

Le livre SVN décrit comment les fichiers renommés sont fusionnés de manière incorrecte . Cela s'applique à Subversion 1.5 , 1.6 , 1.7 et 1.8 ! J'ai essayé de recréer la situation ci-dessous:

 cd /tmp[.____. E5Erm -rf svn-repo svn-checkout 
 svnadmin create svn-repo 
 svn checkout file: /// tmp/svn-repo svn -checkout 
 cd svn-checkout 
 mkdir trunk branches 
 echo 'Goodbye, World!' > trunk/hello.txt 
 svn add trunk branches 
 svn commit -m 'Initial import.' 
 svn copy '^/trunk' '^/branches/rename' -m 'Créer une branche.' 
 Commutateur svn '^/trunk'. 
 Echo 'Hello, World!' > hello.txt 
 svn commit -m 'Update on trunk.' 
 svn switch '^/branches/rename'. 
 svn rename hello.txt hello.en.txt 
 svn commit -m 'Renommer sur la branche.' 
 commutateur svn '^/trunk'. 
 svn merge --reintegrate '^/branches/rename' 

Selon le livre, la fusion devrait se terminer proprement, mais avec des données incorrectes dans le fichier renommé car la mise à jour sur trunk est oubliée. Au lieu de cela, j'obtiens un conflit d'arborescence (c'est avec Subversion 1.6.17, la dernière version de Debian au moment de la rédaction):

 --- Fusion des différences entre les URL de référentiel dans '.': 
 Un hello.en.txt 
 C hello.txt 
 Résumé des conflits: 
 Conflits d'arbre: 1 

Il ne devrait pas y avoir de conflit du tout - la mise à jour doit être fusionnée dans le nouveau nom du fichier. Alors que Subversion échoue, Mercurial gère cela correctement:

rm -rf /tmp/hg-repo
hg init /tmp/hg-repo
cd /tmp/hg-repo
echo 'Goodbye, World!' > hello.txt
hg add hello.txt
hg commit -m 'Initial import.'
echo 'Hello, World!' > hello.txt
hg commit -m 'Update.'
hg update 0
hg rename hello.txt hello.en.txt
hg commit -m 'Rename.'
hg merge

Avant la fusion, le référentiel ressemble à ceci (de hg glog):

 @ changeset: 2: 6502899164cc 
 | tag: astuce 
 | parent: 0: d08bcebadd9e 
 | utilisateur: Martin Geisler 
 | date: jeu 01 avr 12:29:19 2010 +0200 
 | résumé: Renommer. 
 | 
 | o changeset: 1: 9d06fa155634 
 |/utilisateur: Martin Geisler 
 | date: jeu 01 avr 12:29:18 2010 +0200 
 | résumé: mise à jour. 
 | 
 o changeset: 0: d08bcebadd9e 
 utilisateur: Martin Geisler 
 date: jeu 01 avr 12:29:18 2010 +0200 
 résumé: importation initiale. 

La sortie de la fusion est:

 fusion de hello.en.txt et hello.txt en hello.en.txt 
 0 fichier mis à jour, 1 fichier fusionné, 0 fichier supprimé, 0 fichier non résolu 
 (fusion de branche, n'oubliez pas de vous engager) 

En d'autres termes: Mercurial a pris le changement de la révision 1 et l'a fusionné dans le nouveau nom de fichier de la révision 2 (hello.en.txt). Le traitement de ce cas est bien sûr essentiel pour prendre en charge le refactoring et le refactoring est exactement le genre de chose que vous voudrez faire sur une branche.

119
Martin Geisler

Sans parler des avantages habituels (commits offline, processus de publication , ...) voici un exemple de "fusion" I comme:

Le scénario principal que je continue de voir est une branche sur laquelle ... deux des tâches non liées sont réellement développées
(il est parti d'une fonctionnalité, mais il a conduit au développement de cette autre fonctionnalité.
Ou il est parti d'un correctif, mais il a conduit au développement d'une autre fonctionnalité).

Comment fusionner une seule des deux fonctionnalités sur la branche principale?
Ou Comment isolez-vous les deux entités dans leurs propres branches?

Vous pouvez essayer de générer une sorte de correctifs, le problème est que vous n'êtes plus sûr des dépendances fonctionnelles qui aurait pu exister entre:

  • les commits (ou révision pour SVN) utilisés dans vos patchs
  • l'autre ne fait pas partie du patch

Git (et Mercurial aussi je suppose) proposent l'option rebase --onto pour rebaser (réinitialiser la racine de la branche) une partie d'une branche:

De Message de Jefromi

- x - x - x (v2) - x - x - x (v2.1)
           \
            x - x - x (v2-only) - x - x - x (wss)

vous pouvez démêler cette situation où vous avez des correctifs pour la v2 ainsi qu'une nouvelle fonctionnalité wss en:

- x - x - x (v2) - x - x - x (v2.1)
          |\
          |  x - x - x (v2-only)
           \
             x - x - x (wss)

, vous permettant de:

  • tester chaque branche isolément pour vérifier si tout se compile/fonctionne comme prévu
  • fusionnez uniquement ce que vous souhaitez conserver.

L'autre fonctionnalité que j'aime (dont l'influence fusionne) est la possibilité de squash commits (dans une branche pas encore poussée vers une autre repo) afin de présenter:

  • une histoire plus propre
  • des validations plus cohérentes (au lieu de commit1 pour la fonction1, commit2 pour la fonction2, commit3 à nouveau pour la fonction1 ...)

Cela garantit des fusions beaucoup plus faciles, avec moins de conflits.

17
VonC

Nous avons récemment migré de SVN vers GIT et avons été confrontés à cette même incertitude. Il y avait beaucoup de preuves anecdotiques que GIT était meilleur, mais il était difficile de trouver des exemples.

Je peux vous dire cependant que GIT est BEAUCOUP MIEUX à la fusion que SVN. C'est évidemment anecdotique, mais il y a un tableau à suivre.

Voici quelques-unes des choses que nous avons trouvées:

  • SVN lançait de nombreux conflits d'arbres dans des situations où il semblait que cela ne devrait pas. Nous ne sommes jamais allés au fond de cela, mais cela ne se produit pas dans GIT.
  • Bien qu'il soit meilleur, GIT est beaucoup plus compliqué. Passez un peu de temps sur la formation.
  • Nous étions habitués à Tortoise SVN, ce que nous avons aimé. Tortoise GIT n'est pas aussi bon et cela peut vous rebuter. Cependant, j'utilise maintenant la ligne de commande GIT que je préfère de loin à Tortoise SVN ou à n'importe quelle interface graphique GIT.

Lorsque nous évaluions GIT, nous avons effectué les tests suivants. Ceux-ci montrent GIT comme le gagnant en matière de fusion, mais pas autant. En pratique, la différence est beaucoup plus grande, mais je suppose que nous n'avons pas réussi à reproduire les situations que SVN gère mal.

GIT vs SVN Merging Evaluation

7
cedd

D'autres en ont couvert les aspects les plus théoriques. Je peux peut-être apporter une perspective plus pratique.

Je travaille actuellement pour une entreprise qui utilise SVN dans un modèle de développement "branche de fonctionnalité". C'est:

  • Aucun travail ne peut être effectué sur le coffre
  • Chaque développeur peut avoir créé ses propres branches
  • Les succursales devraient durer pendant la durée de la tâche entreprise
  • Chaque tâche doit avoir sa propre branche
  • La fusion avec le coffre doit être autorisée (normalement via bugzilla)
  • Parfois, lorsque des niveaux de contrôle élevés sont nécessaires, les fusions peuvent être effectuées par un contrôleur d'accès

En général, cela fonctionne. SVN peut être utilisé pour un flux comme celui-ci, mais ce n'est pas parfait. Il y a certains aspects du SVN qui gênent et façonnent le comportement humain. Cela lui donne des aspects négatifs.

  • Nous avons eu pas mal de problèmes avec les branchements de personnes à partir de points inférieurs à ^/trunk. Ces portées fusionnent les enregistrements d'informations dans l'arbre et finissent par interrompre le suivi de la fusion. De faux conflits commencent à apparaître et la confusion règne.
  • Ramasser les changements du tronc dans une branche est relativement simple. svn merge fait ce que vous voulez. La fusion de vos modifications nécessite (nous dit-on) --reintegrate sur la commande de fusion. Je n'ai jamais vraiment compris ce commutateur, mais cela signifie que la branche ne peut plus être fusionnée dans le tronc. Cela signifie que c'est une branche morte et vous devez en créer une nouvelle pour continuer à travailler. (Voir la note)
  • Toute l'activité consistant à effectuer des opérations sur le serveur via des URL lors de la création et de la suppression de branches déroute et fait vraiment peur aux gens. Alors ils l'évitent.
  • Il est facile de se tromper entre les branches, en laissant une partie d'un arbre regarder la branche A, tout en laissant une autre partie en regardant la branche B. Donc, les gens préfèrent faire tout leur travail dans une seule branche.

Ce qui a tendance à se produire, c'est qu'un ingénieur crée une branche le premier jour. Il commence son travail et l'oublie. Quelque temps plus tard, un patron arrive et lui demande s'il peut libérer son travail dans le coffre. L'ingénieur redoute ce jour car la réintégration signifie:

  • Fusionner sa branche longue vie dans le tronc et résoudre tous les conflits, et libérer du code sans rapport qui aurait dû être dans une branche distincte, mais qui ne l'était pas.
  • Supprimer sa branche
  • Créer une nouvelle branche
  • Passer sa copie de travail à la nouvelle branche

... et parce que l'ingénieur fait cela le moins possible, il ne se souvient pas de l '"incantation magique" pour effectuer chaque étape. De mauvais commutateurs et URL se produisent, et soudain, ils sont en désordre et ils vont chercher "l'expert".

Finalement, tout se calme et les gens apprennent à gérer les lacunes, mais chaque nouveau démarreur rencontre les mêmes problèmes. La réalité éventuelle (par opposition à ce que j'ai exposé au début) est:

  • Aucun travail n'est effectué sur le coffre
  • Chaque développeur a une branche principale
  • Les succursales durent jusqu'à la libération du travail
  • Les bogues corrigés ont tendance à avoir leur propre branche
  • Les fusions vers le coffre sont effectuées lorsqu'elles sont autorisées

...mais...

  • Parfois, le travail le rend à la jonction alors qu'il ne devrait pas parce qu'il est dans la même branche que quelque chose d'autre.
  • Les gens évitent toute fusion (même les choses faciles), donc les gens travaillent souvent dans leurs petites bulles
  • Les grosses fusions ont tendance à se produire et provoquent une quantité limitée de chaos.

Heureusement, l'équipe est assez petite pour faire face, mais elle ne s'agrandira pas. Le fait est que rien de tout cela n'est un problème avec CVCS, mais plus que parce que les fusions ne sont pas aussi importantes qu'en DVCS, elles ne sont pas aussi lisses. Cette "fusion de friction" provoque un comportement qui signifie qu'un modèle "Feature Branch" commence à se décomposer. Les bonnes fusions doivent être une caractéristique de tous les VCS, pas seulement des DVCS.


Selon this il y a maintenant un --record-only commutateur qui pourrait être utilisé pour résoudre le --reintegrate problème, et apparemment v1.8 choisit quand faire une réintégration automatiquement, et cela n'entraîne pas la mort de la branche par la suite

5
Paul S

Avant Subversion 1.5 (si je ne me trompe pas), Subversion avait un inconvénient important en ce qu'elle ne se souviendrait pas de l'historique des fusions.

Regardons le cas décrit par VonC:

- x - x - x (v2) - x - x - x (v2.1)
          |\
          |  x - A - x (v2-only)
           \
             x - B - x (wss)

Notez les révisions A et B. Supposons que vous ayez fusionné les modifications de la révision A sur la branche "wss" à la branche "v2 uniquement" à la révision B (pour une raison quelconque), mais que vous avez continué à utiliser les deux branches. Si vous essayez de fusionner à nouveau les deux branches à l'aide de Mercurial, il ne fusionnera les modifications qu'après les révisions A et B. Avec Subversion, vous devrez tout fusionner, comme si vous n'aviez pas effectué de fusion auparavant.

Ceci est un exemple de ma propre expérience, où la fusion de B à A a pris plusieurs heures en raison du volume de code: cela aurait été une vraie douleur à traverser à nouveau , ce qui aurait été le cas avec Subversion pré-1.5.

Une autre différence probablement plus pertinente dans le comportement de fusion de Hginit: Subversion Re-education :

Imaginez que vous et moi travaillions sur du code, et que nous branchions ce code, et nous partions chacun dans nos espaces de travail séparés et apportions beaucoup, beaucoup de changements à ce code séparément, donc ils ont divergé un peu.

Quand nous devons fusionner, Subversion essaie de regarder les deux révisions — mon code modifié et votre code modifié — et il essaie de deviner comment les briser ensemble dans un gros gâchis impie. Il échoue généralement, produisant des pages et des pages de "conflits de fusion" qui ne sont pas vraiment des conflits, simplement des endroits où Subversion n'a pas réussi à comprendre ce que nous avons fait.

En revanche, alors que nous travaillions séparément dans Mercurial, Mercurial était occupé à conserver une série de changements. Et donc, quand nous voulons fusionner notre code ensemble, Mercurial a en fait beaucoup plus d'informations: il sait ce que chacun de nous a changé et peut réappliquer ces changements, plutôt que de simplement regarder le produit final et essayer de deviner comment le mettre ensemble.

Bref, la façon dont Mercurial analyse les différences est (était?) Supérieure à celle de Subversion.