web-dev-qa-db-fra.com

Vraiment, un exemple concret que la fusion dans Git est plus facile que SVN?

Question de débordement de pile Comment et/ou pourquoi la fusion dans Git est-elle meilleure que dans SVN? est une excellente question avec quelques excellentes réponses . Cependant, aucun d’entre eux ne montre un exemple simple dans lequel la fusion dans Git fonctionne mieux que SVN .

Sur la chance que cette question sera fermée comme un duplicata, ce qui est

  1. Un scénario de fusion concret
  2. Comment c'est difficile en SVN?
  3. Comment la même fusion est-elle plus facile dans Git?

Quelques points:

  • Aucune philosophie ou explication profonde de ce qu'est un DVCS s'il vous plaît. Ce sont vraiment bien, mais je ne veux pas que leurs détails masquent la réponse à cette question (à mon humble avis).
  • Je me fiche de "SVN historique" pour le moment. Veuillez comparer Git moderne (1.7.5) à SVN moderne (1.6.15).
  • Pas de changement de nom s'il vous plaît - Je sais que Git détecte les changements de noms et se déplace, contrairement à SVN. C'est formidable, mais je cherche quelque chose de plus profond et un exemple qui n'implique pas de renommage ni de déplacement.
  • Pas de rebase ou autre opération "avancée" de Git. Montre-moi la fusion s'il te plaît.
58
ripper234

D'un point de vue pratique, la fusion a toujours été "difficile" en raison de ce que j'appelle le problème du "big bang merge". Supposons qu'un développeur travaille sur du code depuis un moment et qu'il n'ait pas encore engagé son travail (peut-être qu'il a l'habitude de travailler dans Subversion contre trunk et qu'il ne commet pas de code inachevé). Lorsque le développeur s’engage enfin, de nombreuses modifications seront intégrées dans un commit. Pour les autres développeurs qui souhaitent fusionner leur travail avec ce "big bang" commit, l'outil VCS ne disposera pas d'informations suffisantes sur la manière dont le premier développeur est arrivé au point auquel il s'est engagé, de sorte que vous obtenez "voici un conflit géant. dans toute cette fonction, allez le réparer ".

D'autre part, le habituel style de travailler avec Git et d'autres DVCS qui ont des branches locales bon marché est de s'engager régulièrement. Une fois que vous avez fait un travail qui a du sens, vous le commettez. Cela ne doit pas nécessairement être parfait, mais ce devrait être une unité de travail cohérente. Lorsque vous revenez à la fusion, vous avez toujours cet historique des plus petits commits qui indique comment vous êtes passé de l'état d'origine à l'état actuel. Lorsque le DVCS fusionne cela avec le travail des autres, il contient beaucoup plus d'informations sur les modifications apportées à ce moment-là et vous obtenez des conflits plus petits et moins.

Le fait est que vous pouvez toujours faire de la fusion un problème difficile avec Git en effectuant une seule validation big bang uniquement après avoir terminé quelque chose. Git vous encourage à faire de plus petits commits (en les rendant aussi simples que possible), ce qui facilite la fusion future.

22
Greg Hewgill

Je peux seulement vous raconter une petite expérience où Git n'était PAS meilleur que Subversion (mêmes problèmes).

Je m'interrogeais sur ce cas: vous commencez avec deux branches "mytest1" et "mytest2" toutes deux basées sur le même commit. Vous avez un fichier C qui contient une fonction blub (). Dans la branche mytest1, vous déplacez "blub ()" vers une position différente dans le fichier et vous validez. Dans la branche mytest2, vous modifiez blub () et validez. Sur la branche mytest2, vous essayez d'utiliser "git merge mytest1".

Semble donner un conflit de fusion. J'espérais que Git reconnaîtrait que "blub ()" avait été déplacé dans mytest1, puis pouvait fusionner automatiquement la modification dans mytest2 avec le déplacement dans mytest1. Mais au moins quand j'ai essayé, cela ne fonctionnait pas automatiquement ...

Donc, bien que je comprenne parfaitement que Git est bien meilleur pour suivre ce qui a été fusionné et ce qui ne l’a pas encore été, je me demande également s’il existe un cas de fusion "pure" dans lequel Git est meilleur que SVN ...

Maintenant, parce que cette question me dérange depuis longtemps, j'essayais vraiment de créer un exemple concret dans lequel Git est better , alors que la fusion en SVN échoue.

J'en ai trouvé un ici https://stackoverflow.com/a/2486662/1917520 , mais cela inclut un changement de nom et la question ici concernait un cas sans nom.

Voici donc un exemple de SVN qui essaie essentiellement ceci:

bob        +-----r3----r5---r6---+
          /                /      \
anna     /  +-r2----r4----+--+     \
        /  /                  \     \
trunk  r1-+-------------------r7-- Conflict

L'idée ici est:

  • Anna et Bob sont tous les deux développeurs avec leurs propres branches (créées dans r2, r3).
  • Anna fait quelques modifications (r4),
  • Bob fait quelques modifications (r5).
  • Bob fusionne les modifications d'Anna dans sa branche. cela donne des conflits que Bob corrige puis valide (r6).
  • Les modifications annulaires sont fusionnées dans le coffre (r7).
  • Bob tente de fusionner sa modification dans le coffre, ce qui crée à nouveau un conflit.

Voici un script Bash , qui génère ce conflit (avec SVN 1.6.17 et SVN 1.7.9):

#!/bin/bash
cd /tmp
rm -rf rep2 wk2
svnadmin create rep2
svn co file:///tmp/rep2 wk2
cd wk2
mkdir trunk
mkdir branches
echo -e "A\nA\nB\nB" > trunk/f.txt
svn add trunk branches
svn commit -m "Initial file"
svn copy ^/trunk ^/branches/anna -m "Created branch anna"
svn copy ^/trunk ^/branches/bob  -m "Created branch bob"
svn up 
echo -e "A\nMA\nA\nB\nB" > branches/anna/f.txt
svn commit -m "anna added text"
echo -e "A\nMB\nA\nB\nMB\nB" > branches/bob/f.txt
svn commit -m "bob added text"
svn up
svn merge --accept postpone ^/branches/anna branches/bob
echo -e "A\nMAB\nA\nB\nMB\nB" > branches/bob/f.txt
svn resolved branches/bob/f.txt
svn commit -m "anna merged into bob with conflict"
svn up
svn merge --reintegrate ^/branches/anna trunk
svn commit -m "anna reintegrated into trunk"
svn up
svn merge --reintegrate --dry-run ^/branches/bob trunk

Le dernier "--dry-run" vous indique qu'il y aura un conflit. Si vous essayez plutôt de fusionner la réintégration d'Anna dans la branche de Bob, vous obtenez également un conflit; donc si vous remplacez le dernier svn merge par

svn merge ^/trunk branches/bob

cela montre aussi des conflits.

Voici la même chose avec Git 1.7.9.5:

#!/bin/bash
cd /tmp
rm -rf rep2
mkdir rep2
cd rep2
git init .
echo -e "A\nA\nB\nB" > f.txt
git add f.txt
git commit -m "Initial file"
git branch anna
git branch bob
git checkout anna
echo -e "A\nMA\nA\nB\nB" > f.txt
git commit -a -m "anna added text"
git checkout bob
echo -e "A\nMB\nA\nB\nMB\nB" > f.txt
git commit -a -m "bob added text"
git merge anna
echo -e "A\nMAB\nA\nB\nMB\nB" > f.txt
git commit -a -m "anna merged into bob with conflict"
git checkout master
git merge anna
git merge bob

Le contenu de f.txt change comme ceci.

Version initiale

A
A
B
B

Les modifications d'Anna

A
MA
A
B
B

Les modifications de Bob

A
MB
A
B
MB
B

Après la fusion de la branche d'Anna avec celle de Bob

A
MAB
A
B
MB
B

Comme beaucoup de personnes l'ont déjà souligné: Le problème est que Subversion ne peut pas se rappeler que Bob a déjà résolu un conflit. Ainsi, lorsque vous essayez de fusionner la branche de Bob dans le coffre, vous devez alors résoudre à nouveau le conflit.

Git fonctionne complètement différemment. Voici quelques représentations graphiques de ce que fait git

bob         +--s1----s3------s4---+
           /                /      \
anna      /  +-s1----s2----+--+     \
         /  /                  \     \
master  s1-+-------------------s2----s4

s1/s2/s3/s4 sont les instantanés du répertoire de travail que git prend.

Remarques:

  • Quand anna et bob créent leurs branches de développement, ceciPASne créera aucun commet sous git. git se souviendra que les deux branches font initialement référence au même objet commit que la branche master. (Ce commit se référera à l'instantané s1).
  • Lorsque anna implémente sa modification, cela crée un nouvel instantané "s2" + un objet commit. Un objet commit comprend:
    • Une référence à l'instantané (s2 ici)
    • Un message de commit
    • Informations sur les ancêtres (autres objets de validation)
  • Lorsque bob implémente sa modification, cela créera un autre instantané s3 + un objet commit
  • Lorsque bob fusionne les modifications apportées par annas dans sa branche de développement, cela créera un autre instantané s4 (contenant une fusion de ses modifications et des modifications apportées à anna) + encore un autre objet de validation.
  • Lorsque anna fusionne ses modifications dans la branche maître, il s'agira d'une fusion "rapide" dans l'exemple présenté, car le maître n'a pas changé entre-temps. "Avance rapide" signifie ici que le maître pointe simplement sur l'instantané s2 d'Anna sans rien fusionner. Avec une telle "avance rapide", il n'y aura même pas un autre objet de validation. La branche "master" se référera maintenant directement au dernier commit de la branche "anna"
  • Lorsque Bob fusionne maintenant ses modifications dans le coffre, il se passera ce qui suit:
    • git découvrira que le commit d’Anna qui a créé le snapshot s2 est un ancêtre (direct) du commit bobs, qui a créé le snapshot s4.
    • à cause de cela, git va à nouveau "avancer rapidement" la branche master jusqu'au dernier commit de la branche "bob".
    • encore une fois, cela ne créera même pas un nouvel objet commit. La branche "master" sera simplement dirigée vers le dernier commit de la branche "bob".

Voici la sortie de "git ref-log" qui montre tout cela:

88807ab HEAD@{0}: merge bob: Fast-forward
346ce9f HEAD@{1}: merge anna: Fast-forward
15e91e2 HEAD@{2}: checkout: moving from bob to master
88807ab HEAD@{3}: commit (merge): anna merged into bob with conflict
83db5d7 HEAD@{4}: commit: bob added text
15e91e2 HEAD@{5}: checkout: moving from anna to bob
346ce9f HEAD@{6}: commit: anna added text
15e91e2 HEAD@{7}: checkout: moving from master to anna
15e91e2 HEAD@{8}: commit (initial): Initial file

Comme vous pouvez le voir,

  • lorsque nous allons dans la branche de développement d’Anna (HEAD @ {7}), nous ne faisons pas nous changeons pour un commit différent, nous conservons le commit; git se souvient juste que nous sommes maintenant sur une autre branche
  • À HEAD @ {5}, nous passons à la branche initiale de bob; cela déplacera la copie de travail dans le même état que la branche principale, car bob n'a encore rien changé
  • Dans HEAD @ {2}, nous revenons à la branche master. Ainsi, vers le même objet commit, tout a commencé.
  • Head @ {1}, HEAD @ {0} indiquent les fusions "fast-forward", qui ne créent pas de nouveaux objets de validation.

Avec "git cat-file HEAD @ {8} -p", vous pouvez inspecter les détails complets de l'objet de validation initial. Pour l'exemple ci-dessus, j'ai eu:

tree b634f7c9c819bb524524bcada067a22d1c33737f
author Ingo <***> 1475066831 +0200
committer Ingo <***> 1475066831 +0200

Initial file

La ligne "tree" identifie la capture instantanée s1 (== b634f7c9c819bb524524bcada067a22d1c33737f) à laquelle cette validation se réfère.

Si je fais "git cat-file HEAD @ {3} -p" je reçois:

tree f8e16dfd2deb7b99e6c8c12d9fe39eda5fe677a3
parent 83db5d741678908d76dabb5fbb0100fb81484302
parent 346ce9fe2b613c8a41c47117b6f4e5a791555710
author Ingo <***> 1475066831 +0200
committer Ingo <***> 1475066831 +0200

anna merged into bob with conflict

Ce qui précède montre l’objet commit, créé par Bob lors de la fusion de la branche de développement d’Anna. Encore une fois, la ligne "arbre" fait référence à l'instantané créé (s3 ici). Notez également les lignes "parent". Le second, qui commence par "parent 346ce9f" plus tard, indique à git, lorsque vous essayez de fusionner la branche de développement de bob dans la branche maître, que ce dernier commit de bob a le dernier commit d'Anna en tant qu'ancêtre. C'est pourquoi git sait que la fusion de la branche de développement de bob dans la branche principale est une "avance rapide".

15
Ingo Blackman

Je n'ai pas d'exemples concrets, mais tout type de fusion répétée est difficile, en particulier ce que l'on appelle fusion croisée.

   a
  / \
 b1  c1
 |\ /|
 | X |
 |/ \|
 b2  c2

fusion de b2 et c2


La page wiki sur le wiki Subversion décrivant les différences entre fusionnerinfo La fusion asymétrique basée sur Subversion (avec les directions 'sync' et 'reintegrate') et la fusion (fusion) {suivi} [] dans DVCS comporte une section " Fusion symétrique avec fusion croisée "

6
Jakub Narębski

L'exemple le plus concret auquel je puisse penser est la fusion la plus simple qui ne provoque pas de conflits de fusion. Cependant (TL; DR) avec cet exemple, Git est toujours une procédure plus simple qu'avec Subversion. Permet de revoir pourquoi:

Subversion

Considérez le scénario suivant dans Subversion; le coffre et la branche de fonctionnalité:

   1  2  3
…--o--o--o              trunk
          \4  5
           o--o         branches/feature_1

Pour fusionner, vous pouvez utiliser la commande suivante dans Subversion:

# thank goodness for the addition of the --reintegrate flag in SVN 1.5, eh?
svn merge --reintegrate central/repo/path/to/branches/feature_1

# build, test, and then... commit the merge
svn commit -m "Merged feature_1 into trunk!"

Dans Subversion, la fusion des modifications nécessite un autre commit. Cela permet de publier les modifications apportées par la fusion en appliquant les modifications du répertoire virtuel de la branche de fonctionnalité dans la jonction. Ainsi, tout le monde travaillant avec le coffre peut maintenant l'utiliser et le graphe de révision ressemble à ceci:

   1  2  3      6
…--o--o--o------o       /trunk
          \4  5/
           o--o         /branches/feature_1

Voyons comment cela se fait dans git.

Git

Dans Git, cette validation de fusion n’est vraiment pas nécessaire car les branches sont des signets glorifiés sur le graphique de révision. Donc, avec le même type de structure de graphe de révision, cela ressemble à ceci:

         v-- master, HEAD

   1  2  3
…--o--o--o
          \4  5
           o--o

              ^-- feature_branch

Avec la tête actuellement sur la branche principale, nous pouvons effectuer une fusion simple avec la branche caractéristique:

# Attempt a merge
git merge feature_branch

# build, test, and then... I am done with the merge

... et il va avancer rapidement la branche vers la validation vers laquelle la branche de fonctionnalité pointe. Ceci est rendu possible parce que Git sait que l'objectif de la fusion est un descendant direct et que la branche actuelle n'a besoin que de prendre en compte tous les changements survenus. Le graphique de révision finira par ressembler à ceci:

   1  2  3  4  5
…--o--o--o--o--o
               ^-- feature_branch, master, HEAD

Les modifications n'ont pas besoin d'un nouveau commit car tout ce que git a fait, c'est de déplacer les références de branches plus en avant. Tout ce qui reste à faire est de le publier dans le référentiel public si vous en avez:

# build, test, and then... just publish it
git Push

Conclusion

Étant donné ce scénario simple, vous pouvez affirmer deux choses dans la différence entre Subversion et Git:

  • SVN nécessite au moins quelques commandes pour finaliser la fusion et forces vous permet de publier votre fusion en tant que commit.
  • Git nécessite seulement une commande et ne vous force pas à publier votre fusion.

Étant donné que ce scénario de fusion est le plus simple, il est difficile de soutenir que Subversion est plus facile que git.

Dans des scénarios de fusion plus difficiles, git vous offre également la possibilité de rebaser la branche qui produit un graphique de révision plus simple au prix de récriture de l'historique}. Une fois que vous avez compris et évitez de publier des réécritures d’historique de choses déjà publiées, ce n’est pas si grave que cela.

Rebases interactives est en dehors du champ de la question mais pour être honnête; il vous permet de réorganiser, d’écraser et de supprimer les commits. Je ne voudrais pas volontiers revenir à Subversion, car la réécriture de l'historique n'est pas possible par conception.

4
Spoike

Garder la réponse courte - Dans DVCS, étant donné que vous avez un contrôle de source local, si quelque chose est endommagé dans le processus de fusion (ce qui se produira probablement lors de grandes fusions), vous pouvez toujours revenir à une version locale antérieure contenant les modifications que vous avez effectuées. avez fait avant de fusionner, puis réessayez.

Donc, fondamentalement, vous pouvez fusionner sans craindre que vos modifications locales ne soient endommagées au cours du processus.

2
TheGreyMatter

Si vous excluez "l'Enfer refondé par la fusion", vous n'obtiendrez pas juste samples, car ils n'existent tout simplement pas

1
Lazy Badger

On dirait que c'est un mythe que la fusion dans Git est plus facile que dans SVN ...

Par exemple, Git ne peut pas fusionner dans un arbre de travail comportant des modifications, contrairement à SVN.

Considérez le scénario simple suivant: vous avez quelques modifications dans votre arborescence de travail et souhaitez intégrer des modifications à distance sans commettre les vôtres .

SVN: update, [résoudre les conflits].

Git : stash, fetch, rebase, stash pop, [résoudre les conflits], [stash drop s'il y a eu des conflits].

Ou connaissez-vous un moyen plus facile à Git?


Au fait, ce cas d'utilisation semble être tellement important qu'IntelliJ a même implémenté la fonctionnalité manquante de «Mise à jour de projet» pour Git (mise à jour d'analogon vers SVN), qui permet d'automatiser les étapes manuelles décrites ci-dessus:

 IntelliJ IDEA - Update Project

0
Eugen Labun