De Git et rebase vs fusion questions

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 , donc je suppose que j'apporte beaucoup de bagages à la relation.

j'ai entendu dire que Git est excellent pour la ramification et la fusion, et jusqu'à présent, je ne le vois tout simplement pas. Bien sûr, la ramification est mort simple, mais quand j'essaie de fusionner, tout va en enfer. Maintenant, je suis habitué à cela de la part de SVN, mais il me semble que je je viens d'échanger un système de sous-Version contre un autre.

mon partenaire me dit que mes problèmes proviennent de mon désir de fusionner à volonté, 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

essentiellement, créer une branche de fonctionnalité, toujours rebaser de maître à la branche, et fusionner de la branche de retour à maître. Il est Important de noter que la branche reste toujours local.

voici le workflow 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 la fusion toujours au lieu de rebaser, et je pousse ma branche de fonctionnalité (et ma branche de fonctionnalité s'engage) vers le dépôt distant.

mon raisonnement pour la branche distante est que je veux que mon travail soit sauvegardé pendant que je travaille. Notre dépôt est sauvegardé automatiquement et peut être restauré si quelque chose tourne mal. Mon ordinateur portable n'est pas, ou pas complètement. Par conséquent, je déteste avoir du code sur mon ordinateur portable qui n'est pas reproduit ailleurs.

mon raisonnement pour la fusion au lieu de rebase est que la fusion semble être standard et 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 lu le nouveau manuel de programmation pragmatique sur Git, et ils couvrent merge extensively et à peine mention rebase.

quoi qu'il en soit, je suivais mon workflow sur une branche récente, et quand j'ai essayé de le fusionner à nouveau à master, tout est allé à l'enfer. Il y avait des tonnes de conflits avec des choses qui n'auraient pas dû avoir d'importance. Les conflits juste fait aucun sens pour moi. Cela m'a pris une journée pour tout régler, et a finalement abouti à une poussée forcée vers le maître éloigné, puisque mon maître local a résolu tous les conflits, mais le maître éloigné n'était toujours pas heureux.

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

mise à jour 2011-04-15

cela semble être une question très populaire, donc j'ai pensé que je voudrais mettre à jour avec mes deux années d'expérience depuis la première fois que je l'ai demandé.

il s'avère que le workflow 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 flux de travail est un peu différent, car nous avons tendance à faire squash merges au lieu de RAW merges. ( Note: Ceci est controversé, voir ci-dessous. ) cela nous permet de transformer l'ensemble de notre branche feature en un seul commit sur master. Puis nous supprimer notre branche. 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 de Squash - comme plusieurs commentateurs l'ont souligné, la fusion de squash va jeter toute l'histoire sur votre branche de fonctionnalité. Comme son nom l'indique,il écrase toutes les propagations en une seule. Pour les petites fonctionnalités, cela fait sens car il condense en un seul paquet. Pour des fonctionnalités plus larges, ce n'est probablement pas une bonne idée, surtout si vos propagations individuelles sont déjà atomiques. Il s'agit vraiment de personnels préférence.

Github et Bitbucket (others?) Pull Requests - dans le cas où vous vous demandez comment merge/rebase se rapporte aux Pull Requests, je recommande de suivre toutes les étapes ci-dessus jusqu'à ce que vous êtes prêt à fusionner de nouveau avec master. Au lieu de fusionner manuellement avec git, vous acceptez simplement le PR. Notez que ceci ne fera pas une fusion squash (au moins pas par défaut), mais non-squash, non-fast-forward est la convention de fusion acceptée dans la communauté Pull Request (autant que je sache). 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

je suis venu pour aimer Git et ne veux jamais retourner à SVN. Si vous êtes en difficulté, il suffit de rester avec elle et éventuellement vous verrez la lumière au bout du tunnel.

901
demandé sur Micah 2009-01-19 18:16:52
la source

10 ответов

"conflits"signifie" évolutions parallèles d'un même contenu". Si elle va "tous en enfer" lors d'une fusion, cela signifie que vous avez massive des évolutions sur le même ensemble de fichiers.

la raison pour laquelle un rebase est alors mieux qu'une fusion est que:

  • vous réécrivez votre histoire locale de commit avec celle du maître (et puis vous présentez de nouveau votre travail, résolvant tout conflit alors)
  • la fusion finale sera certainement un " fast forward " one, parce qu'il aura toute l'histoire de commit du maître, plus seulement vos modifications à présenter de nouveau.

je confirme que le flux de travail correct dans ce cas (évolutions sur l'ensemble commun de fichiers) est rebase d'abord, puis fusionner .

cependant, cela signifie que, si vous poussez votre branche locale( pour une raison de sauvegarde), cette branche ne devrait pas être retirée (ou du moins utilisée) par quelqu'un d'autre (puisque l'historique de propagation sera réécrit par le rebase suivant).


sur ce sujet (rebase puis fusionner workflow), barraponto mentionne dans les commentaires deux postes intéressants, tous les deux de randyfay.com :

à l'Aide de cette technique, votre travail va toujours au-dessus de la branche publique comme un patch qui est à jour avec le HEAD actuel .

(une technique similaire existe pour le bazar )

344
répondu VonC 2017-05-23 14:55:10
la source

TL; DR

un workflow git rebase ne vous protège pas des personnes qui sont mauvaises en résolution de conflits ou des personnes qui sont habituées à un workflow SVN, comme suggéré dans Eviter les catastrophes Git: une histoire Gore . Cela ne fait que rendre la résolution des conflits plus fastidieuse pour eux et rend plus difficile de se remettre d'une mauvaise résolution des conflits. Au lieu de cela, utilisez diff3 pour que ce ne soit pas si difficile en premier lieu.


le flux de travail Rebase n'est pas meilleur pour la résolution de conflits!

je suis très pro-rebase pour nettoyer l'histoire. Cependant si j'ai déjà frappé un conflit, j'annule immédiatement le rebase et faire 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 conflits (ce qui est exactement ce que cette question était à propos).

Si il va "tous en enfer" lors d'une fusion, ça va aller "tous 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 rebasez au lieu de fusionner, vous devrez effectuer la résolution de conflit jusqu'à autant de fois que vous avez s'engage à rebaser, pour le même conflit!

Vrai "scénario de 1519370920"

je branche hors du maître pour refactor a méthode compliquée dans une branche. Mon travail de remaniement est composé de 15 commits total que je travaille à le remanier et obtenir des critiques de code. Une partie de mon remaniement consiste à réparer les onglets et les espaces mixtes qui étaient présents dans master avant. Cela est nécessaire, mais malheureusement cela va à l'encontre de tout changement apporté par la suite à cette méthode dans master. Bien sûr, pendant que je travaille sur cette méthode, quelqu'un fait un changement simple et légitime à la même méthode dans la branche principale qui devrait être fusionnée dans avec mes modifications.

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

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

git rebase: J'obtiens un conflit avec mon premier commit. Je résous le conflit et je continue la reprise. Je reçois un conflit avec mon deuxième s'engager. Je résous le conflit et je continue la reprise. J'obtiens un conflit avec mon troisième commit. Je résous le conflit et je continue la reprise. J'obtiens un conflit avec mon quatrième commit. Je résous le conflit et je continue la reprise. J'obtiens un conflit avec mon cinquième commit. Je résous le conflit et je continue la reprise. Je reçois un conflit avec mon sixième s'engager. Je résous le conflit et je continue la reprise. J'obtiens un conflit avec mon septième commit. Je résous le conflit et je continue la reprise. J'obtiens un conflit avec mon huitième commit. Je résous le conflit et je continue la reprise. J'obtiens un conflit avec mon neuvième commit. Je résous le conflit et je continue la reprise. J'obtiens un conflit avec mon dixième commit. Je résolvez le conflit et continuez la reprise. J'obtiens un conflit avec mon onzième commit. Je résous le conflit et je continue la reprise. J'obtiens un conflit avec mon douzième commit. Je résous le conflit et je continue la reprise. J'obtiens un conflit avec mon treizième commit. Je résous le conflit et je continue la reprise. J'obtiens un conflit avec mon quatorzième commit. - Je résoudre le conflit et de continuer le rebase. J'obtiens un conflit avec mon quinzième commit. Je résous le conflit et je continue la reprise.

vous devez vous moquer de moi si ce est votre flux de travail préféré. Tout ce qu'il faut, c'est un correctif d'espace qui entre en conflit avec un changement fait sur master, et chaque commit va entrer en conflit et doit être résolu. Et c'est un scénario simple avec seulement un espace conflit. à dieu ne plaise, vous avez un réel conflit impliquant les principaux changements de code dans des fichiers et ont à résoudre que à plusieurs reprises.

avec toute la résolution de conflit supplémentaire que vous devez faire, il augmente juste la possibilité que vous allez faire une erreur . Mais les erreurs sont bonnes en git puisque vous pouvez défaire, Non? Sauf, bien sûr...

Raison #2: avec rebase, il est sans l'annuler!

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

lorsque vous fusionnez une branche, git crée une commit de fusion qui peut être écartée ou modifiée si la résolution du conflit va mal. Même si vous avez déjà poussé la mauvaise Fusion à la public / authoritative repo, vous pouvez utiliser git revert pour annuler les changements introduits par la fusion et refaire la fusion correctement dans une nouvelle commit de fusion.

quand vous rebasez une branche, dans le cas probable que la résolution de conflit est mal faite, vous êtes foutu. Chaque livraison contient maintenant la mauvaise fusion, et vous ne pouvez pas simplement refaire le rebase*. Au mieux, vous devez revenir en arrière et modifier chacun de ces validations. Pas de plaisir.

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

*il peut être possible de défaire un rebase si vous pouvez extraire les anciens refs des logs internes de git, ou si vous créez une troisième branche qui pointe vers le dernier commit avant de rebaser.

Prendre l'enfer hors de résolution des conflits: l'utilisation diff3

Take 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, pourquoi la résolution des conflits est confuse et difficile.

diff3 à la rescousse!

git config --global merge.conflictstyle diff3

Lorsque vous utilisez le diff3, chaque nouveau conflit 3e section, la fusion ancêtre commun.

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

First examinez l'ancêtre commun fusionné. Ensuite, comparez chaque côté pour déterminer l'intention de chaque branche. Vous pouvez voir que la tête a changé EmailMessage en TextMessage. Son but 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 fonctionnalité-branch est de passer false au lieu de true pour l'option :include_timestamp. Pour fusionner ces changements, combiner 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. appliquer ce simple changement à la version du code de l'autre succursale, de sorte qu'il contient à la fois le changement le plus simple et le plus complexe
  3. Supprimer toutes les sections de conflit code autre que celui que vous venez de fusionner les modifications dans

Suppléant: Résoudre en appliquant manuellement les changements de branche

Enfin, certains conflits sont terribles à comprendre, même avec diff3. Cela se produit surtout lorsque diff trouve des lignes en commun qui ne sont pas sémantiquement communes (par ex. les deux branches avaient une ligne blanche au même endroit!). Par exemple, une branche 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 être d'examiner le changement de chaque côté de la fusion et appliquer manuellement la diff pour les autres fichier.

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

  1. décider si notre succursale actuellement cochée ( HEAD , ou --ours ) ou la succursale que nous fusionnons ( origin/feature1 , ou --theirs ) est un changement plus simple à appliquer. Utiliser diff avec triple point ( 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, comparer 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 la 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é vérifié, tirer vers le haut la diff du changement plus simple (voir étape 1). Appliquer chaque passer de cette différence au dossier conflictuel.

362
répondu Edward Anderson 2015-09-29 18:13:37
la source

Dans mon travail, je rebase autant que possible (et j'essaie de le faire souvent. Le fait de ne pas laisser les écarts s'accumuler réduit considérablement le nombre et la gravité des collisions entre branches).

cependant, même dans un workflow essentiellement basé sur rebase, il y a une place pour les fusions.

rappelle que la fusion crée en fait un noeud qui a deux parents. Considérons maintenant la situation suivante: j'ai deux fonctions indépendantes, les branches A et B, et maintenant vous voulez développer des trucs sur la branche c qui dépend à la fois de A et B, alors que A et B sont en train de faire l'objet d'une révision.

Ce que je fais, alors, est la suivante:

  1. Créer (et de départ) branche C sur le dessus de A.
  2. fusionner avec B

maintenant la branche C inclut les changements de A et B, et je peux continuer à développer sur elle. Si je fais n'importe quel changement à A, alors je reconstruis le graphique des branches dans le

  1. créer la branche T sur le nouveau sommet d'un
  2. fusionner T avec B
  3. rebase C sur T
  4. supprimer la branche T

de cette façon, je peux effectivement 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 faire le rebasage lorsque le parent change.

29
répondu Alex Gontmakher 2009-05-13 00:54:12
la source

N'utilisez pas git push origin --mirror dans presque toutes les circonstances.

il ne demande pas si vous êtes sûr que vous voulez faire cela, et vous feriez mieux d'être sûr, parce qu'il va effacer toutes vos branches éloignées qui ne sont pas sur votre boîte locale.

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

20
répondu Scott Brown 2009-04-10 05:06:06
la source

j'ai une question après avoir lu votre explication: se pourrait-il que vous n'ayez jamais fait un

git checkout master
git pull origin
git checkout my_new_feature

avant de faire le " git rebase/fusion de master dans votre branche?

parce que votre branche principale ne sera pas mise à jour automatiquement à partir du dépôt de votre ami. Il faut faire ça avec le git pull origin . C'est-à-dire: peut-être que tu rebasais toujours d'une branche de maître locale qui ne change jamais? Et puis venir pousser de temps, vous sont en train de pousser dans un dépôt qui a des propagations (locales) que vous n'avez jamais vues et donc la poussée échoue.

13
répondu knweiss 2015-08-20 16:38:39
la source

dans votre situation, je pense que votre partenaire a raison. Ce qui est bien avec le rebasement, c'est que pour l'outsider, vos changements ont l'air d'être arrivés dans une séquence propre, tout seuls. Cela signifie

  • vos modifications sont très faciles à réviser
  • vous pouvez continuer à faire nice, small commits et pourtant vous pouvez faire des ensembles de ces commits publics (en fusionnant en master) tout à la fois
  • quand on regarde le public branche master 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 continuer à pousser votre branche de développement privé vers le dépôt à distance pour des raisons de sauvegarde, mais d'autres ne devraient pas traiter cela comme une branche "publique" puisque vous allez rebaser. BTW, une commande facile pour faire ceci est git push --mirror origin .

l'article logiciel D'Emballage utilisant Git fait un travail assez agréable expliquant les compromis dans la fusion versus rebasing. 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
répondu Pat Notz 2009-01-19 19:15:02
la source

quoi qu'il en soit, je suivais mon workflow sur une branche récente, et quand j'ai essayé de le fusionner à nouveau à master, tout est allé à l'enfer. Il y avait des tonnes de conflits avec des choses qui n'auraient pas dû avoir d'importance. Les conflits juste fait aucun sens pour moi. Cela m'a pris une journée pour tout régler, et a finalement abouti à une poussée forcée vers le maître éloigné, puisque mon maître local a résolu tous les conflits, mais le maître éloigné n'était toujours pas heureux.

ni dans les workflows de votre partenaire ni dans ceux que vous avez suggérés si vous avez rencontré des conflits qui n'avaient pas de sens. Même si vous aviez, si vous suivez les workflows suggérés, alors après 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 poussiez, mais que vous avez dû pousser une branche qui n'était pas une descendante de la pointe distante.

je pense que vous devez regarder attentivement ce qui s'est passé. Pourrait quelqu'un d'autre l'ont (volontairement ou non) remonté le maître branche entre votre création de l'agence locale et le point où vous avez tenté de l'intégrer dans la branche locale?

comparé à beaucoup d'autres systèmes de contrôle de version, j'ai trouvé que l'utilisation de Git implique moins de lutte contre l'outil et vous permet de vous mettre au travail sur les problèmes qui sont fondamentaux pour vos flux source. Git ne fait pas de magie, donc les changements conflictuels causent des conflits, mais il devrait facilitez la tâche d'écriture en traçant la parenté de commit.

11
répondu CB Bailey 2016-07-04 22:07:25
la source

D'après ce que j'ai observé, git merge tend à garder les branches séparées même après la fusion, alors que rebase fusionne ensuite les combine en une seule branche. Le dernier ressort beaucoup plus propre, alors que dans le premier, il serait plus facile de découvrir quelles commits appartiennent à quelle branche même après la fusion.

6
répondu Pepe 2011-08-25 09:40:35
la source

" même si vous êtes un seul développeur avec seulement quelques branches, cela vaut la peine de prendre l'habitude d'utiliser rebase et de fusionner correctement. Le modèle de travail de base ressemblera à:

  • "créer une nouvelle branche B à partir de la branche a existante

  • Ajouter / Modifier les changements relatifs à la branche B

  • mises à jour de Rebase de la branche a

  • Fusionner les changements de la branche B à la branche a "

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

6
répondu Rakka Rage 2015-08-27 01:14:10
la source

avec Git il n'y a pas de flux de travail" correct". Utilisez ce qui flotte sur votre bateau. Cependant, si vous rencontrez constamment des conflits lors de la fusion de branches, peut-être devriez-vous mieux coordonner vos efforts avec vos collègues développeurs? On dirait que vous deux continuez à éditer les mêmes fichiers. Attention également aux mots-clés whitespace et subversion (i.e., "$Id$" et autres).

2
répondu Bombe 2009-01-19 18:25:30
la source

Autres questions sur git version-control git-rebase git-merge