Workflow Git et questions de rebase contre fusion

J’utilise Git maintenant depuis quelques mois sur un projet avec un autre développeur. J’ai plusieurs années d’expérience avec SVN , alors je suppose que je suis très attaché à la relation.

J’ai entendu dire que Git est excellent pour les twigments et les fusions, et jusqu’à présent, je ne le vois pas. Bien sûr, les twigments sont simples, mais quand j’essaie de fusionner, tout se passe bien. Maintenant, je suis habitué à cela depuis SVN, mais il me semble que je viens d’échanger un système de version inférieur à un autre.

Mon partenaire me dit que mes problèmes découlent 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 défini:

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 

Pour l’essentiel, créez une twig de fonctionnalité, TOUJOURS se rebasculer de maître à la twig et fusionner de la twig à la base. Il est important de noter que la succursale rest toujours locale.

Voici le workflow avec lequel j’ai commencé

 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 toujours la fusion au lieu de la rebaser, et je pousse ma twig d’entités (et mes commits de twig) vers le référentiel distant.

Mon raisonnement pour la twig à distance 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 complet. Par conséquent, je déteste avoir du code sur mon ordinateur portable qui ne soit pas reflété ailleurs.

Mon raisonnement pour la fusion au lieu de rebase est que la fusion semble être standard et que rebase semble être une fonctionnalité avancée. Mon instinct 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 le rebase.

Quoi qu’il en soit, je suivais mon stream de travail sur une twig récente, et quand j’ai essayé de le fusionner pour le maîsortingser, tout est allé en enfer. Il y avait des tonnes de conflits avec des choses qui n’auraient pas dû être importantes. Les conflits n’ont tout simplement pas de sens pour moi. Il m’a fallu une journée pour tout régler et finalement abouti à une impulsion forcée sur le maître distant, car tous les conflits ont été résolus avec mon maître local, mais la télécommande n’était toujours pas satisfaite.

Quel est le workflow “correct” pour quelque chose comme ça? Git est censé simplifier la création de twigs et la fusion, et je ne le vois pas.

Mise à jour 2011-04-15

Cela semble être une question très populaire, alors j’ai pensé que je mettrais à jour avec mes deux années d’expérience depuis que j’ai demandé pour la première fois.

Il s’avère que le stream de travail original est correct, du 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 workflow est un peu différent, car nous avons tendance à faire des fusions de squash au lieu de fusions brutes. ( Remarque: Ceci est controversé, voir ci-dessous. ) Cela nous permet de transformer notre twig entière en un seul engagement sur le maître. Ensuite, nous supprimons notre twig de fonctionnalité. Cela nous permet de structurer logiquement nos commits sur master, même s’ils sont un peu en désordre dans nos succursales. 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 de squash – Comme plusieurs commentateurs l’ont fait remarquer, la fusion de squash jettera toute l’histoire sur votre twig. 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?) Demandes de tirage – Si vous vous demandez comment la fusion / rebase est liée aux demandes de tirage, je vous recommande de suivre toutes les étapes ci-dessus jusqu’à ce que vous soyez prêt à fusionner. Au lieu de fusionner manuellement avec git, vous acceptez simplement le PR. Spécifiquement, 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’adore Git et je ne veux plus jamais retourner à SVN. Si vous éprouvez des difficultés, respectez-le et vous verrez éventuellement la lumière au bout du tunnel.

“Conflits” signifie “évolutions parallèles d’un même contenu”. Donc, si cela se passe “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 meilleure qu’une fusion est que:

  • vous réécrivez votre historique de validation local avec celui du maître (puis réappliquez votre travail, résolvant alors tout conflit)
  • la fusion finale sera certainement une “avance rapide”, car elle aura tout l’historique de validation du maître, plus seulement vos modifications à réappliquer.

Je confirme que le bon workflow dans ce cas (évolutions sur un ensemble de fichiers commun) est d’ abord rebasé, puis fusionne .

Cependant, cela signifie que si vous poussez votre twig locale (pour des raisons de sauvegarde), cette twig ne doit pas être extraite (ou du moins utilisée) par quelqu’un d’autre (puisque l’historique de la validation sera réécrit par les rebases successives).


Sur ce sujet (rebase puis fusion workflow), barraponto mentionne dans les commentaires deux articles intéressants, tous deux de randyfay.com :

  • Un stream de travail de rebase pour Git : nous rappelle de récupérer en premier, de rebaser:

En utilisant cette technique, votre travail passe toujours par-dessus la twig publique comme un patch qui est à jour avec HEAD actuel.

(une technique similaire existe pour le bazar )

  • Éviter les catastrophes de Git: une histoire sanglante : à propos des dangers de git push --force (au lieu de git pull --rebase par exemple)

TL; DR

Un stream de travail de rebit git ne vous protège pas des personnes qui ne sont pas à même de résoudre les conflits ou des personnes habituées à un stream de travail SVN, comme suggéré dans Eviter les catastrophes de Git: une histoire sanglante . Cela ne fait que rendre la résolution des conflits plus fastidieuse et rend plus difficile la récupération après une mauvaise résolution des conflits. Au lieu de cela, utilisez diff3 pour que ce ne soit pas si difficile en premier lieu.


Le workflow de rebase n’est pas meilleur pour la résolution des conflits!

Je suis très pro-rebase pour nettoyer l’histoire. Cependant, si je rencontre un conflit, j’annule immédiatement le rebase et fusionne à la place! Cela me tue vraiment que les gens recommandent un workflow de rebase comme une meilleure alternative à un workflow de fusion pour la résolution de conflits (ce qui était exactement le but de cette question).

Si ça se passe “tout en enfer” lors d’une fusion, ça ira “en enfer” lors d’une rebase, et potentiellement beaucoup plus en enfer! Voici pourquoi:

Raison n ° 1: Résoudre les conflits une fois, au lieu d’une fois pour chaque validation

Lorsque vous rebase au lieu de fusionner, vous devez effectuer la résolution des conflits autant de fois que vous vous êtes engagé à le rebaser, pour le même conflit!

Scénario réel

Je twig du maître à la refactorisation d’une méthode compliquée dans une twig. Mon travail de refactoring est composé de 15 commits au total alors que je travaille pour le refactoriser et obtenir des critiques de code. Une partie de mon refactoring consiste à réparer les tabs et espaces mixtes présents dans master auparavant. Cela est nécessaire, mais malheureusement, il sera en conflit avec tout changement apporté ultérieurement à cette méthode dans master. Bien sûr, pendant que je travaille sur cette méthode, quelqu’un effectue un changement simple et légitime à la même méthode dans la twig principale qui devrait être fusionnée avec mes modifications.

Quand il est temps de fusionner ma twig avec master, j’ai deux options:

Git Merge: J’ai un conflit. Je vois le changement qu’ils ont fait pour le maîsortingser et le fusionner avec (le produit final de) ma twig. Terminé.

git rebase: J’ai un conflit avec mon premier commit. Je résous le conflit et continue le rebase. Je suis en conflit avec mon deuxième engagement. Je résous le conflit et continue le rebase. Je suis en conflit avec mon troisième engagement. Je résous le conflit et continue le rebase. Je suis en conflit avec mon quasortingème engagement. Je résous le conflit et continue le rebase. Je suis en conflit avec mon cinquième engagement. Je résous le conflit et continue le rebase. Je suis en conflit avec mon sixième engagement. Je résous le conflit et continue le rebase. Je suis en conflit avec mon septième engagement. Je résous le conflit et continue le rebase. Je suis en conflit avec mon huitième engagement. Je résous le conflit et continue le rebase. Je suis en conflit avec mon neuvième engagement. Je résous le conflit et continue le rebase. J’ai un conflit avec mon dixième engagement. Je résous le conflit et continue le rebase. J’ai un conflit avec mon onzième engagement. Je résous le conflit et continue le rebase. Je suis en conflit avec mon douzième engagement. Je résous le conflit et continue le rebase. Je suis en conflit avec mon treizième engagement. Je résous le conflit et continue le rebase. Je suis en conflit avec mon quatorzième engagement. Je résous le conflit et continue le rebase. Je suis en conflit avec mon quinzième engagement. Je résous le conflit et continue le rebase.

Vous devez vous moquer de moi si c’est votre stream de travail préféré. Tout ce qu’il faut, c’est un correctif d’espace qui est en conflit avec une modification apscope à master, et chaque validation est en conflit et doit être résolue. Et c’est un scénario simple avec seulement un conflit d’espace. Le paradis vous interdit un conflit réel impliquant des modifications majeures du code dans les fichiers et vous devez le résoudre plusieurs fois.

Avec toute la résolution de conflit supplémentaire que vous devez faire, cela augmente la possibilité que vous commettiez une erreur . Mais les erreurs sont bonnes en git puisque vous pouvez les défaire, non? Sauf bien sûr …

Raison n ° 2: Avec rebase, il n’y a pas de défaire!

Je pense que nous pouvons tous convenir que la résolution des conflits peut être difficile et que certaines personnes sont très mauvaises. Elle peut être très sujette à des erreurs, ce qui explique pourquoi Git facilite la tâche!

Lorsque vous fusionnez une twig, git crée une validation de fusion qui peut être ignorée ou modifiée si la résolution du conflit est mauvaise. Même si vous avez déjà poussé la validation de mauvaise fusion vers le repository public / authoritative, vous pouvez utiliser git revert pour annuler les modifications introduites par la fusion et refaire la fusion correctement dans un nouveau commit de fusion.

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

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

* Il est possible d’annuler une rebase si vous pouvez extraire les anciens refs des journaux internes de git, ou si vous créez une troisième twig qui pointe vers le dernier commit avant de procéder au rebasage.

Sortez de la résolution des conflits: utilisez 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 twig a changé ou quelle était son intention. C’est la plus grande raison pour laquelle, à mon avis, la résolution des conflits est difficile et compliquée.

diff3 à la rescousse!

 git config --global merge.conflictstyle diff3 

Lorsque 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

Examinez d’abord l’ancêtre commun fusionné. Ensuite, comparez chaque côté pour déterminer l’intention de chaque twig. Vous pouvez voir que HEAD a changé EmailMessage en TextMessage. Son but est de changer la classe utilisée pour TextMessage en transmettant les mêmes parameters. Vous pouvez également voir que l’intention de cette twig est de transmettre la valeur false au lieu de true pour l’option: include_timestamp. Pour fusionner ces modifications, combinez l’intention des deux:

 TextMessage.send(:include_timestamp => false) 

En général:

  1. Comparer l’ancêtre commun à chaque twig et déterminer quelle twig a le changement le plus simple
  2. Appliquez cette simple modification à la version du code de l’autre twig, de sorte qu’elle contienne à la fois la modification la plus simple et la plus complexe
  3. Supprimez toutes les sections du code de conflit autres que celui que vous venez de fusionner dans

Alternate: Résoudre en appliquant manuellement les modifications de la twig

Enfin, certains conflits sont terribles à comprendre même avec diff3. Cela se produit surtout lorsque diff trouve des lignes communes qui ne sont pas sémantiquement communes (par exemple, les deux twigs avaient une ligne vide au même endroit!). Par exemple, une twig modifie l’indentation du corps d’une classe ou réordonne des méthodes similaires. Dans ces cas, une meilleure stratégie de résolution peut consister à examiner le changement de part et d’autre de la fusion et à appliquer manuellement le diff à l’autre fichier.

Voyons comment nous pouvons résoudre un conflit dans un scénario où la fusion origin/feature1 conflit avec lib/message.rb .

  1. Décidez si notre twig actuellement --ours ( HEAD ou --ours ) ou la twig que nous --ours ( origin/feature1 ou --theirs ) est une modification plus simple à appliquer. Utiliser diff avec un point sortingple ( git diff a...b ) montre les changements qui se sont produits sur b depuis sa dernière divergence par rapport à a , ou en d’autres termes, compare 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. Une fois la modification compliquée extraite, affichez le diff du plus simple changement (voir étape 1). Appliquez chaque modification de ce diff au fichier en conflit.

Dans mon stream de travail, je rebase autant que possible (et j’essaie de le faire souvent. Ne pas laisser les écarts s’accumuler réduit considérablement la quantité et la gravité des collisions entre les twigs).

Cependant, même dans un stream de travail principalement basé sur les rebases, il existe une place pour les fusions.

Rappelez-vous que la fusion crée réellement un nœud qui a deux parents. Considérons maintenant la situation suivante: j’ai deux groupes de fonctions A et B indépendants, et je veux maintenant développer des fonctionnalités sur la twig C qui dépend à la fois de A et de B, tandis que A et B sont en cours de révision.

Ce que je fais alors est le suivant:

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

Maintenant, la twig C inclut les changements de A et B, et je peux continuer à développer dessus. Si je change quelque chose à A, alors je reconstruis le graphique des twigs de la manière suivante:

  1. créer une twig T sur le nouveau sumt de A
  2. fusionner T avec B
  3. rebase C sur T
  4. supprimer la twig T

De cette façon, je peux réellement gérer des graphiques arbitraires de twigs, mais faire quelque chose de plus complexe que la situation décrite ci-dessus est déjà trop complexe, étant donné qu’il n’existe pas d’outil automatique pour effectuer la re-génération lorsque le parent change.

N’UTILISEZ PAS git push origin –mirror SOUS TOUTE CIRCONSTANCE.

Il ne vous demande pas si vous êtes sûr de vouloir faire cela, et vous devriez être sûr, car cela effacera toutes vos twigs distantes qui ne figurent pas dans votre boîte locale.

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

J’ai une question après avoir lu votre explication: pourrait-il être que vous n’avez jamais fait un

 git checkout master git pull origin git checkout my_new_feature 

avant de faire le «maître de fusion / rebase git» dans votre twig d’entités?

Parce que votre twig principale ne sera pas mise à jour automatiquement à partir du référentiel de votre ami. Vous devez le faire avec l’ git pull origin . Ie peut-être vous rebatteriez toujours d’une twig principale locale sans changement? Et puis, vient le temps de pousser, vous poussez dans un repository qui a des commits (locaux) que vous n’avez jamais vus et donc le push échoue.

Dans votre situation, je pense que votre partenaire est correct. Ce qui est bien à propos de la reformulation, c’est que pour l’extérieur, vos modifications semblent toutes se passer toutes seules. Ça signifie

  • vos changements sont très faciles à revoir
  • vous pouvez continuer à faire de beaux petits commits et pourtant vous pouvez créer des ensembles de ces commits publics (en les fusionnant dans master)
  • quand vous regardez la twig maître publique, vous verrez différentes séries de commits pour différentes fonctionnalités par différents développeurs, mais ils ne seront pas tous mélangés

Vous pouvez toujours continuer à transférer votre twig de développement privée vers le référentiel distant à des fins de sauvegarde, mais les autres utilisateurs ne devraient pas considérer cela comme une twig “publique” car vous devrez procéder à un changement de nom. BTW, une commande facile pour faire cela est git push --mirror origin .

L’article Logiciel de packaging utilisant Git fait un assez bon travail en expliquant les compromis en matière de fusion et de rebasage. Le contexte est 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 envisagez de les intégrer au réseau principal.

Quoi qu’il en soit, je suivais mon stream de travail sur une twig récente, et quand j’ai essayé de le fusionner pour le maîsortingser, tout est allé en enfer. Il y avait des tonnes de conflits avec des choses qui n’auraient pas dû être importantes. Les conflits n’ont tout simplement pas de sens pour moi. Il m’a fallu une journée pour tout régler et finalement abouti à une impulsion forcée sur le maître distant, car tous les conflits ont été résolus avec mon maître local, mais la télécommande n’était toujours pas satisfaite.

Si vous ne rencontrez pas de conflits, cela n’a pas de sens. Même si vous aviez, si vous suivez les workflows suggérés, une résolution forcée ne devrait pas être requirejse. Cela suggère que vous n’avez pas réellement fusionné la twig vers laquelle vous étiez en train de pousser, mais que vous avez dû pousser une twig qui ne descendait pas de la pointe distante.

Je pense que vous devez examiner attentivement ce qui s’est passé. Quelqu’un d’autre aurait-il (délibérément ou non) rembobiné la twig principale distante entre votre création de la twig locale et le moment où vous avez tenté de la fusionner dans la twig locale?

Comparé à de nombreux autres systèmes de contrôle de version, j’ai trouvé que l’utilisation de Git impliquait moins de lutte contre l’outil et vous permettait de travailler sur les problèmes fondamentaux de vos stream sources. Git n’effectue pas de magie, de sorte que les changements contradictoires provoquent des conflits, mais cela devrait faciliter la tâche d’écriture par le suivi de la parenté de validation.

D’après ce que j’ai observé, git merge a tendance à séparer les twigs même après la fusion, alors que rebase puis merge le combine en une seule twig. Ce dernier sort beaucoup plus propre, alors que dans le premier cas, il serait plus facile de savoir quels commits appartiennent à quelle twig même après la fusion.

“Même si vous êtes un développeur unique avec seulement quelques twigs, cela vaut la peine d’avoir l’habitude d’utiliser le rebase et de fusionner correctement. Le modèle de travail de base ressemblera à ceci:

  • Créer une nouvelle twig B à partir de la twig existante A

  • Ajouter / valider les modifications sur la twig B

  • Rebase les mises à jour de la twig A

  • Fusionne les modifications de la twig B vers la twig A ”

https://www.atlassian.com/git/tutorials/merging-vs-rebasing/

Avec Git, il n’y a pas de workflow «correct». Utilisez tout ce qui flotte sur votre bateau. Cependant, si vous rencontrez constamment des conflits lors de la fusion de twigs, vous devriez peut-être mieux coordonner vos efforts avec les autres développeurs? On dirait que vous continuez à éditer les mêmes fichiers. Aussi, faites attention aux mots-clés d’espacement et de subversion (par exemple, “$ Id $” et autres).