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
Quelques points:
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.
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:
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:
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,
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".
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 "
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:
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.
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
Étant donné ce scénario simple, vous pouvez affirmer deux choses dans la différence entre Subversion et Git:
É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.
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.
Si vous excluez "l'Enfer refondé par la fusion", vous n'obtiendrez pas juste samples, car ils n'existent tout simplement pas
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: