Question Git workflow et rebase vs fusionner des questions


J'utilise Git depuis quelques mois sur un projet avec un autre développeur. J'ai plusieurs années d'expérience avec SVN, donc je suppose que j'apporte beaucoup de bagages à la relation.

J'ai entendu dire que Git est excellent pour la branche et la fusion, et jusqu'à présent, je ne le vois tout simplement pas. Bien sûr, ramifier est mort simple, mais quand j'essaye de fusionner, tout va en enfer. Maintenant, je suis habitué à cela par SVN, mais il me semble que j'ai juste échangé un système de versionnage inférieur à un autre.

Mon partenaire me dit que mes problèmes proviennent de mon désir de fusionner bon gré mal gré, et que je devrais utiliser rebase au lieu de fusionner dans de nombreuses situations. Par exemple, voici le workflow qu'il a établi:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature
git checkout master
git merge my_new_feature

Essentiellement, créer une branche de fonctionnalité, TOUJOURS rebasculer du maître à la branche, et fusionner de la branche vers le maître. Il est important de noter que la succursale reste toujours locale.

Voici le flux de travail que j'ai commencé avec

clone remote repository
create my_new_feature branch on remote repository
git checkout -b --track my_new_feature origin/my_new_feature
..work, commit, push to origin/my_new_feature
git merge master (to get some changes that my partner added)
..work, commit, push to origin/my_new_feature
git merge master
..finish my_new_feature, push to origin/my_new_feature
git checkout master
git merge my_new_feature
delete remote branch
delete local branch

Il y a deux différences essentielles (je pense): j'utilise fusionner toujours au lieu de rebaser, et je pousse ma branche d'entité (et ma branche de fonction committe) vers le référentiel distant.

Mon raisonnement pour la branche distante est que je veux que mon travail soit sauvegardé pendant que je travaille. Notre référentiel est automatiquement sauvegardé et peut être restauré en cas de problème. Mon ordinateur portable n'est pas, ou pas aussi complètement. Par conséquent, je déteste avoir du code sur mon ordinateur portable qui n'est pas reflété ailleurs.

Mon raisonnement pour la fusion au lieu de rebaser est que la fusion semble être standard et que le rebase semble être une fonctionnalité avancée. Mon intuition est que ce que j'essaie de faire n'est pas une configuration avancée, donc rebase devrait être inutile. J'ai même parcouru le nouveau livre de programmation pragmatique sur Git, et ils couvrent largement la fusion et mentionnent à peine rebase.

Quoi qu'il en soit, je suivais mon flux de travail sur une branche récente, et quand j'ai essayé de le ramener au master, tout est allé en enfer. Il y avait des tonnes de conflits avec des choses qui n'auraient pas dû avoir d'importance. Les conflits n'avaient tout simplement aucun sens pour moi. Il m'a fallu un jour pour tout régler, et a finalement abouti à une poussée forcée au maître à distance, puisque mon maître local a tous les conflits résolus, mais le distant n'était toujours pas heureux.

Quel est le flux de travail "correct" pour quelque chose comme ça? Git est supposé rendre super facile le branchement et la fusion, et je ne le vois tout simplement pas.

Mise à jour 2011-04-15

Cela semble être une question très populaire, alors j'ai pensé mettre à jour avec mes deux années d'expérience depuis que j'ai demandé.

Il s'avère que le workflow d'origine est correct, au moins dans notre cas. En d'autres termes, c'est ce que nous faisons et cela fonctionne:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature, commit
git rebase master
git checkout master
git merge my_new_feature

En fait, notre flux de travail est un peu différent, comme nous avons tendance à le faire les mélanges de courges au lieu de fusions brutes. (Note: Ceci est controversé, voir ci-dessous.Ceci nous permet de transformer toute notre branche en un seul commit sur master. Ensuite, nous supprimons notre branche de fonctionnalité. Cela nous permet de structurer logiquement nos commits sur master, même s'ils sont un peu désordonnés sur nos branches. Donc, c'est ce que nous faisons:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature, commit
git rebase master
git checkout master
git merge --squash my_new_feature
git commit -m "added my_new_feature"
git branch -D my_new_feature

Controverse sur la fusion des courges - Comme plusieurs commentateurs l'ont signalé, la fusion de squash jettera tout l'historique de votre branche. Comme son nom l'indique, il écrase tous les commits en un seul. Pour les petites fonctionnalités, cela a du sens car il se condense en un seul paquet. Pour les fonctionnalités plus importantes, ce n'est probablement pas une bonne idée, surtout si vos commits individuels sont déjà atomiques. Cela dépend vraiment des préférences personnelles.

Github et Bitbucket (autres?) Pull Requests - Dans le cas où vous vous demandez comment fusionner / rebaser se rapporte à des demandes de tirage, je vous recommande de suivre toutes les étapes ci-dessus jusqu'à ce que vous soyez prêt à revenir au maître. Au lieu de fusionner manuellement avec git, vous acceptez simplement le PR. Plus précisément, cela fonctionne comme ceci:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature, commit
git rebase master
git push # May need to force push
...submit PR, wait for a review, make any changes requested for the PR
git rebase master
git push # Will probably need to force push (-f), due to previous rebases from master
...accept the PR, most likely also deleting the feature branch in the process
git checkout master
git branch -d my_new_feature
git remote prune origin

J'en suis venu à aimer Git et je ne veux plus jamais retourner à SVN. Si vous éprouvez des difficultés, tenez-vous-y et, éventuellement, vous verrez la lumière au bout du tunnel.


895
2018-01-19 15:16


origine


Réponses:


Les "conflits" signifient "des évolutions parallèles d'un même contenu". Donc, si ça va "tout en enfer" lors d'une fusion, cela signifie que vous avez des évolutions massives sur le même ensemble de fichiers.

La raison pour laquelle une rebase est alors meilleure qu'une fusion est la suivante:

  • vous réécrivez votre historique de commit local avec celui du master (puis réappliquez votre travail, résolvez tout conflit ensuite)
  • la fusion finale sera certainement une "avance rapide", car elle aura tout l'historique de commit du maître, plus seulement vos modifications à réappliquer.

Je confirme que le workflow correct dans ce cas (évolutions sur un ensemble commun de fichiers) est rebaser d'abord, puis fusionner.

Cependant, cela signifie que, si vous poussez votre branche locale (pour des raisons de sauvegarde), cette branche ne devrait pas être tirée (ou au moins utilisée) par quelqu'un d'autre (puisque l'historique des commit sera réécrit par les rebasages successifs).


Sur ce sujet (rebaser puis fusionner workflow), barraponto mentionne dans les commentaires deux messages intéressants, à la fois de randyfay.com:

En utilisant cette technique, votre travail va toujours au-dessus de la branche publique comme un patch qui est à jour avec le courant HEAD.

(une technique similaire existe pour le bazar)


342
2018-01-19 15:32



TL; DR

Un workflow git rebase ne vous protège pas contre les personnes qui sont mauvaises dans la résolution des conflits ou les personnes habituées à un flux de travail SVN, comme suggéré dans Éviter les catastrophes Git: une histoire de Gory. Cela ne fait que rendre la résolution des conflits plus pénible pour eux et rend plus difficile la récupération suite à la résolution de conflits. Au lieu de cela, utilisez diff3 pour que ce ne soit pas si difficile en premier lieu.


Le workflow Rebase n'est pas meilleur pour la résolution de conflit!

Je suis très pro-rebase pour nettoyer l'histoire. Toutefois, si J'ai déjà rencontré un conflit, j'abandonne immédiatement le rebase et fais une fusion à la place!Cela me tue vraiment que les gens recommandent un flux de travail de rebase comme une meilleure alternative à un flux de travail de fusion pour la résolution de conflit (ce qui est exactement ce que cette question était).

Si ça va "tout en enfer" lors d'une fusion, ça ira "tout en enfer" lors d'un rebase, et potentiellement beaucoup plus d'enfer aussi! Voici pourquoi:

Raison # 1: Résoudre les conflits une fois, au lieu d'une fois pour chaque commit

Quand vous rebassez au lieu de fusionner, vous devrez effectuer une résolution de conflit jusqu'à autant de fois que vous vous êtes engagé à rebaser, pour le même conflit!

Scénario réel

Je branche du maître pour refactoriser une méthode compliquée dans une branche. Mon travail de refactoring comprend un total de 15 commits pendant que je travaille pour le refactoriser et obtenir des critiques de code. Une partie de mon refactoring consiste à réparer les onglets mixtes et les espaces qui étaient présents dans le maître avant. Ceci est nécessaire, mais malheureusement, il sera en conflit avec tout changement apporté par la suite à cette méthode dans le maître. Effectivement, pendant que je travaille sur cette méthode, quelqu'un apporte une modification simple et légitime à la même méthode dans la branche master qui devrait être fusionnée avec mes changements.

Quand il est temps de fusionner ma branche avec master, j'ai deux options:

git fusionner:  Je reçois un conflit. Je vois le changement qu'ils ont fait pour maîtriser et fusionner avec (le produit final de) ma branche. Terminé.

git rebase:  Je reçois un conflit avec mon premier commettre. Je résous le conflit et continue le rebase. Je reçois un conflit avec mon seconde commettre. Je résous le conflit et continue le rebase. Je reçois un conflit avec mon troisième commettre. Je résous le conflit et continue le rebase. Je reçois un conflit avec mon Quatrième commettre. Je résous le conflit et continue le rebase. Je reçois un conflit avec mon cinquième commettre. Je résous le conflit et continue le rebase. Je reçois un conflit avec mon sixième commettre. Je résous le conflit et continue le rebase. Je reçois un conflit avec mon septième commettre. Je résous le conflit et continue le rebase. Je reçois un conflit avec mon huitième commettre. Je résous le conflit et continue le rebase. Je reçois un conflit avec mon neuvième commettre. Je résous le conflit et continue le rebase. Je reçois un conflit avec mon dixième commettre. Je résous le conflit et continue le rebase. Je reçois un conflit avec mon onzième commettre. Je résous le conflit et continue le rebase. Je reçois un conflit avec mon douzième commettre. Je résous le conflit et continue le rebase. Je reçois un conflit avec mon treizième commettre. Je résous le conflit et continue le rebase. Je reçois un conflit avec mon quatorzième commettre. Je résous le conflit et continue le rebase. Je reçois un conflit avec mon quinzième commettre. Je résous le conflit et continue le rebase.

Tu dois te moquer de moi si ce est votre flux de travail préféré. Tout ce qu'il faut, c'est une correction d'espace qui est en conflit avec un changement effectué sur le maître, et chaque commit sera en conflit et doit être résolu. Et c'est un simple scénario avec seulement un conflit d'espaces. Dieu vous garde d'avoir un vrai conflit impliquant des changements majeurs de code à travers les fichiers et doivent résoudre cette plusieurs fois.

Avec toute la résolution de conflit supplémentaire que vous devez faire, cela augmente la possibilité que vous ferez une erreur. Mais les erreurs sont bien dans Git puisque vous pouvez annuler, non? Sauf bien sûr ...

Raison n ° 2: Avec rebase, il n'y a pas d'annulation!

Je pense que nous pouvons tous convenir que la résolution des conflits peut être difficile, et aussi que certaines personnes sont très mauvaises. Il peut être très sujet à des erreurs, ce qui explique pourquoi il est si génial que git le rend facile à défaire!

Lorsque vous fusionnez une branche, git crée un commit de fusion qui peut être rejeté ou modifié si la résolution du conflit est mauvaise. Même si vous avez déjà envoyé le mauvais commit de fusion au référentiel public / officiel, vous pouvez utiliser git revertannuler les modifications introduites par la fusion et refaire la fusion correctement dans un nouveau commit de fusion.

Lorsque vous rebassez une branche, dans le cas où la résolution de conflit est mal faite, vous êtes foutu. Chaque commit contient maintenant la mauvaise fusion, et vous ne pouvez pas refaire la rebase *. Au mieux, vous devez revenir en arrière et modifier chacun des commits concernés. Pas drôle.

Après un rebasage, il est impossible de déterminer ce qui était à l'origine partie des commits et ce qui a été introduit à la suite d'une mauvaise résolution de conflit.

* Il est possible d'annuler un rebasage si vous pouvez extraire les anciennes références à partir des journaux internes de git, ou si vous créez une troisième branche qui pointe vers le dernier commit avant de rebaser.

Sortir de la résolution des conflits: utiliser diff3

Prenez ce conflit par exemple:

<<<<<<< HEAD
TextMessage.send(:include_timestamp => true)
=======
EmailMessage.send(:include_timestamp => false)
>>>>>>> feature-branch

En regardant le conflit, il est impossible de dire ce que chaque branche a changé ou quelle était son intention. C'est la principale raison à mon avis pour laquelle la résolution des conflits est confuse et difficile.

diff3 à la rescousse!

git config --global merge.conflictstyle diff3

Quand vous utilisez le diff3, chaque nouveau conflit aura une 3ème section, l'ancêtre commun fusionné.

<<<<<<< HEAD
TextMessage.send(:include_timestamp => true)
||||||| merged common ancestor
EmailMessage.send(:include_timestamp => true)
=======
EmailMessage.send(:include_timestamp => false)
>>>>>>> feature-branch

Commencez par examiner l'ancêtre commun fusionné. Puis comparez chaque côté pour déterminer l'intention de chaque branche. Vous pouvez voir que HEAD a changé EmailMessage en TextMessage. Son intention est de changer la classe utilisée pour TextMessage, en passant les mêmes paramètres. Vous pouvez également voir que l'intention de la branche de fonctionnalité est de transmettre false au lieu de vrai pour l'option: include_timestamp. Pour fusionner ces modifications, combinez l'intention des deux:

TextMessage.send(:include_timestamp => false)

En général:

  1. Comparez l'ancêtre commun avec chaque branche, et déterminez quelle branche a le changement le plus simple
  2. Appliquez cette simple modification à la version du code de l'autre branche, afin qu'elle contienne à la fois le changement le plus simple et le plus complexe
  3. Supprime toutes les sections de code de conflit autres que celle que vous venez de fusionner

Alternate: Résolvez en appliquant manuellement les modifications de la branche

Enfin, certains conflits sont terribles à comprendre même avec diff3. Cela arrive surtout lorsque diff trouve des lignes communes qui ne sont pas sémantiquement communes (par exemple, les deux branches ont une ligne vide au même endroit!). Par exemple, une branche modifie l'indentation du corps d'une classe ou réorganise des méthodes similaires. Dans ces cas, une meilleure stratégie de résolution peut être d'examiner le changement de chaque côté de la fusion et d'appliquer manuellement le diff à l'autre fichier.

Regardons comment nous pourrions résoudre un conflit dans un scénario où la fusion origin/feature1 où lib/message.rb conflits.

  1. Décidez si notre branche actuellement vérifiée (HEAD, ou --ours) ou la branche que nous fusionnons (origin/feature1, ou --theirs) est un changement plus simple à appliquer. Utilisation de diff avec triple point (git diff a...b) montre les changements qui sont survenus b depuis sa dernière divergence de aou, en d'autres termes, comparez l'ancêtre commun de a et b avec b.

    git diff HEAD...origin/feature1 -- lib/message.rb # show the change in feature1
    git diff origin/feature1...HEAD -- lib/message.rb # show the change in our branch
    
  2. Découvrez la version plus compliquée du fichier. Cela supprimera tous les marqueurs de conflit et utilisera le côté que vous choisissez.

    git checkout --ours -- lib/message.rb   # if our branch's change is more complicated
    git checkout --theirs -- lib/message.rb # if origin/feature1's change is more complicated
    
  3. Avec le changement compliqué extrait, tirez le diff du changement le plus simple (voir l'étape 1). Appliquez chaque changement de ce diff au fichier en conflit.


358
2018-06-27 04:25



Dans mon flux de travail, je rebase autant que possible (et j'essaie de le faire souvent) Ne pas laisser les écarts s'accumuler de manière drastique réduit la quantité et la gravité des collisions entre les branches).

Cependant, même dans un flux de travail basé principalement sur le rebasage, il y a une place pour les fusions.

Rappelez-vous que la fusion crée réellement un noeud qui a deux parents. Considérons maintenant la situation suivante: J'ai deux brances indépendantes A et B, et je veux maintenant développer des choses sur la branche C qui dépend à la fois de A et de B, tandis que A et B sont passés en revue.

Ce que je fais ensuite, c'est ce qui suit:

  1. Créer (et extraire) la branche C au-dessus de A.
  2. Fusionner avec B

Maintenant, la branche C inclut des changements de A et B, et je peux continuer à développer dessus. Si je fais un changement à A, alors je reconstruis le graphique des branches de la façon suivante:

  1. créer une branche T sur le nouveau sommet de A
  2. fusionner T avec B
  3. rebaser C sur T
  4. supprimer la branche T

De cette façon, je peux réellement maintenir des graphiques arbitraires de branches, mais faire quelque chose de plus complexe que la situation décrite ci-dessus est déjà trop complexe, étant donné qu'il n'y a pas d'outil automatique pour effectuer le rebasage lorsque le parent change.


29
2018-05-12 20:54



N'utilisez PAS l'origine git push --mirror SOUS PRESQUE N'IMPORTE QUELLE CIRCONSTANCE.

Il ne vous demande pas si vous êtes sûr de vouloir faire cela, et vous feriez mieux de vous en assurer, car il effacera toutes vos branches distantes qui ne sont pas dans votre boîte locale.

http://twitter.com/dysinger/status/1273652486


20
2018-04-10 01:06



J'ai une question après avoir lu votre explication: Serait-ce que vous n'avez jamais fait

git checkout master
git pull origin
git checkout my_new_feature

avant de faire le 'git rebase / merge master' dans votre branche de fonctionnalité?

Car votre La branche master ne sera pas mise à jour automatiquement à partir du référentiel de votre ami. Vous devez le faire avec le git pull origin. C'est à dire. Peut-être que vous rebastiez toujours à partir d'une branche maîtresse locale en constante évolution? Et puis vient le temps de pousser, vous êtes en train de pousser dans un dépôt qui a des engagements (locaux) que vous n'avez jamais vu et donc la poussée échoue.


13
2018-05-25 20:52



Dans votre situation, je pense que votre partenaire est correct. Ce qu'il y a de bien dans le rebasage, c'est que vos changements ressemblent à ceux de l'extérieur. Ça signifie

  • vos changements sont très faciles à revoir
  • vous pouvez continuer à faire de petits commits, mais vous pouvez rendre public des ensembles de ces commits (en fusionnant dans master) tout à la fois
  • Quand vous regardez la branche principale publique, vous verrez différentes séries de validations pour différentes fonctionnalités par différents développeurs, mais elles ne seront pas toutes mélangées

Vous pouvez toujours continuer à pousser votre branche de développement privée vers le référentiel distant pour des raisons de sauvegarde, mais d'autres ne devraient pas traiter cela comme une branche «publique» puisque vous allez rebasculer. BTW, une commande facile pour ce faire est git push --mirror origin .

L'article Logiciel d'emballage utilisant Git fait un assez bon travail expliquant les compromis dans la fusion par rapport à rebasage. C'est un contexte un peu différent, mais les principes sont les mêmes - il s'agit essentiellement de savoir si vos succursales sont publiques ou privées et comment vous prévoyez de les intégrer dans la ligne principale.


12
2018-01-19 16:15



Quoi qu'il en soit, je suivais mon flux de travail sur une branche récente, et quand j'ai essayé de le ramener au master, tout est allé en enfer. Il y avait des tonnes de conflits avec des choses qui n'auraient pas dû avoir d'importance. Les conflits n'avaient tout simplement aucun sens pour moi. Il m'a fallu un jour pour tout régler, et a finalement abouti à une poussée forcée au maître à distance, puisque mon maître local a tous les conflits résolus, mais le distant n'était toujours pas heureux.

Ni dans les flux de travail de votre partenaire ni dans ceux suggérés, vous devez avoir rencontré des conflits qui n'ont pas de sens. Même si vous aviez, si vous suivez les flux de travail suggérés, après une résolution, une poussée «forcée» ne devrait pas être nécessaire. Cela suggère que vous n'avez pas réellement fusionné la branche vers laquelle vous aviez poussé, mais que vous avez dû pousser une branche qui n'était pas une descendante de la pointe éloignée.

Je pense que vous devez regarder attentivement ce qui s'est passé. Est-ce que quelqu'un d'autre pourrait avoir (délibérément ou non) rembobiné la branche maître distante entre votre création de la branche locale et le point où vous avez tenté de la fusionner dans la branche locale?

Comparé à de nombreux autres systèmes de contrôle de version, j'ai trouvé que l'utilisation de Git implique moins de combattre l'outil et vous permet de travailler sur les problèmes qui sont fondamentaux pour vos flux sources. Git n'effectue pas de magie, donc les changements conflictuels provoquent des conflits, mais cela devrait faciliter la tâche d'écriture par son suivi de parenté de commit.


11
2018-01-19 19:30



D'après ce que j'ai observé, la fusion git tend à garder les branches séparées même après fusion, alors que rebase puis fusionne le combine en une seule branche. Ce dernier sort beaucoup plus propre, alors que dans le premier cas, il serait plus facile de savoir quels commits appartiennent à quelle branche même après la fusion.


6
2017-08-25 05:40