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 les branchements et les fusions, et jusqu'à présent, je ne le vois pas. Bien sûr, la ramification est très simple, mais quand j'essaye de fusionner, tout va tout au diable. Maintenant, je suis habitué à cela de SVN, mais il me semble que je viens d'échanger un système de version sous-pair pour 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 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éez une branche de fonction, TOUJOURS rebaser du maître à la branche et fusionner de la branche au maître. Il est important de noter que la succursale reste 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 du rebasage, et je pousse ma branche de fonctionnalité (et ma branche de fonctionnalité se valide) 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 ne se reflète pas ailleurs.
Mon raisonnement pour la fusion au lieu de rebaser est que la fusion semble être standard et rebaser semble être une fonctionnalité avancée. Mon instinct est que ce que j'essaie de faire n'est pas une configuration avancée, donc le rebase ne devrait pas être nécessaire. 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 flux de travail sur une branche récente, et quand j'ai essayé de le fusionner à nouveau en master, tout est allé en enfer. Il y a eu 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 une journée pour tout régler, et a finalement abouti à une poussée forcée vers le maître distant, car mon maître local a résolu tous les conflits, mais le distant n'était toujours pas satisfait.
Quel est le flux de travail "correct" pour quelque chose comme ça? Git est censé rendre les branchements et les fusions super faciles, 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é que je mettrais à jour mes deux années d'expérience depuis ma première demande.
Il s'avère que le flux de travail 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, car nous avons tendance à faire des fusions de squash au lieu de fusions brutes. ( Remarque: cela est controversé, voir ci-dessous. ) Cela nous permet de transformer l'ensemble de notre branche de fonctionnalités en un seul commit sur master. Ensuite, nous supprimons notre branche de fonctionnalités. Cela nous permet de structurer logiquement nos commits sur master, même s'ils sont un peu désordonnés sur nos branches. Voici donc 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 de fusion de squash - Comme plusieurs commentateurs l'ont souligné, la fusion de squash va jeter tout l'historique de votre branche de fonctionnalités. Comme son nom l'indique, il écrase tous les commits en un seul. Pour les petites fonctionnalités, cela a du sens car il les condense vers le bas dans 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 se résume vraiment à la préférence personnelle.
Github et Bitbucket (autres?) Demandes Pull - Au cas où vous vous demandez comment la fusion / rebase est liée aux demandes Pull, je vous recommande de suivre toutes les étapes ci-dessus jusqu'à ce que vous soyez prêt à fusionner à nouveau vers le maître. Au lieu de fusionner manuellement avec git, vous acceptez simplement le PR. Notez que cela ne fera pas de fusion de squash (du moins pas par défaut), mais que le non-squash, l'avance rapide n'est pas la convention de fusion acceptée dans la communauté Pull Request (pour 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
J'ai appris à aimer Git et je ne veux plus jamais revenir à SVN. Si vous avez du mal, restez avec et finalement vous verrez la lumière au bout du tunnel.
rebase
comprendreRéponses:
"Conflits" signifie "é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 un rebasage est alors meilleur qu'une fusion est que:
Je confirme que le flux de travail correct dans ce cas (évolutions sur un ensemble commun de fichiers) est d' abord rebaser, puis fusionner .
Cependant, cela signifie que, si vous poussez votre branche locale (pour une raison de sauvegarde), cette branche ne doit pas être tirée (ou au moins utilisée) par quelqu'un d'autre (car l'historique de validation sera réécrit par la rebase successive).
Sur ce sujet (rebaser puis fusionner le workflow), barraponto mentionne dans les commentaires deux articles intéressants, tous deux de randyfay.com :
(une technique similaire existe pour le bazar )
git push --force
(au lieu d'ungit pull --rebase
par exemple)la source
TL; DR
Un workflow git rebase ne vous protège pas contre les personnes qui sont mauvaises dans la résolution des conflits ou contre les personnes habituées à un workflow SVN, comme suggéré dans Éviter les catastrophes Git: une histoire sanglante . Cela rend la résolution des conflits plus fastidieuse pour eux et rend plus difficile la récupération après une mauvaise résolution des conflits. Utilisez plutôt diff3 pour que ce ne soit pas si difficile en premier lieu.
Rebaser le workflow n'est pas mieux pour la résolution des conflits!
Je suis très pro-rebase pour nettoyer l'histoire. Cependant, si je frappe un conflit, j'interromps immédiatement le rebase et je fais une fusion à la place! Cela me tue vraiment que les gens recommandent un workflow de rebase comme meilleure alternative à un workflow de fusion pour la résolution des conflits (c'est exactement ce sur quoi portait cette question).
S'il va "tout en enfer" lors d'une fusion, il ira "tout en enfer" lors d'un rebase, et potentiellement beaucoup plus en enfer aussi! Voici pourquoi:
Raison n ° 1: résoudre les conflits une fois, au lieu d'une fois pour chaque validation
Lorsque vous rebaserez au lieu de fusionner, vous devrez effectuer la résolution des conflits autant de fois que vous vous êtes engagé à rebaser, pour le même conflit!
Scénario réel
Je dérive du master pour refactoriser une méthode compliquée dans une branche. Mon travail de refactoring est composé de 15 commits au total alors que je travaille pour le refactoriser et obtenir des révisions de code. Une partie de ma refactorisation consiste à corriger les tabulations et les espaces mixtes qui étaient présents dans master auparavant. Ceci est nécessaire, mais malheureusement, il entrera en conflit avec toute modification apportée par la suite à cette méthode dans master. 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 principale qui devrait être fusionnée avec mes modifications.
Lorsqu'il est temps de fusionner ma branche avec Master, j'ai deux options:
git merge: j'ai un conflit. Je vois le changement qu'ils ont fait pour maîtriser et le fusionner avec (le produit final de) ma branche. Terminé.
git rebase: J'ai un conflit avec mon premier commit. Je résous le conflit et continue le rebase. J'ai un conflit avec mon deuxième commit. Je résous le conflit et continue le rebase. J'ai un conflit avec mon troisième commit. Je résous le conflit et continue le rebase. J'ai un conflit avec mon quatrième engagement. Je résous le conflit et continue le rebase. J'ai un conflit avec mon cinquième commit. Je résous le conflit et continue le rebase. J'ai un conflit avec mon sixième engagement. Je résous le conflit et continue le rebase. J'ai un conflit avec mon septièmecommettre. Je résous le conflit et continue le rebase. J'ai un conflit avec mon huitième engagement. Je résous le conflit et continue le rebase. J'ai un conflit avec mon neuvième engagement. Je résous le conflit et continue le rebase. J'ai un conflit avec mon dixième commit. Je résous le conflit et continue le rebase. J'ai un conflit avec mon onzième commit. Je résous le conflit et continue le rebase. J'ai un conflit avec mon douzième commit. Je résous le conflit et continue le rebase. J'ai un conflit avec mon treizième engagement. Je résous le conflit et continue le rebase. J'ai un conflit avec mon quatorzièmecommettre. Je résous le conflit et continue le rebase. J'ai un conflit avec mon quinzième commit. Je résous le conflit et continue le rebase.
Vous avez plaisantez si c'est votre flux de travail préféré. Il suffit d'un correctif d'espace qui entre en conflit avec une modification effectuée sur le maître, et chaque validation entrera en conflit et devra être résolue. Et ceci est un scénario simple avec seulement un conflit d'espaces. Que le ciel interdise que vous ayez un vrai conflit impliquant des changements de code majeurs entre les fichiers et que vous deviez le résoudre plusieurs fois.
Avec toute la résolution de conflit supplémentaire que vous devez faire, cela augmente simplement la possibilité que vous fassiez une erreur . Mais les erreurs sont bonnes 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 dans ce domaine. Il peut être très sujet aux erreurs, c'est 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 ignoré ou modifié si la résolution du conflit se passe mal. Même si vous avez déjà poussé la mauvaise validation de fusion vers le référentiel public / faisant autorité, vous pouvez utiliser
git revert
pour annuler les modifications apportées par la fusion et refaire la fusion correctement dans une nouvelle validation de fusion.Lorsque vous rebasez une branche, dans le cas probable où la résolution des conflits est mal effectuée, vous êtes foutu. Chaque commit 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 des commits concernés. Pas drôle.
Après un rebase, il est impossible de déterminer ce qui faisait initialement partie des validations et ce qui a été introduit suite à une mauvaise résolution de conflit.
* Il peut être possible d'annuler un rebase si vous pouvez extraire les anciennes références des journaux internes de git, ou si vous créez une troisième branche qui pointe vers le dernier commit avant le rebasage.
Sortez de la résolution des conflits: utilisez diff3
Prenez ce conflit par exemple:
En regardant le conflit, il est impossible de dire ce que chaque branche a changé ou quelle était son intention. C'est à mon avis la principale raison pour laquelle la résolution des conflits est confuse et difficile.
diff3 à la rescousse!
Lorsque vous utilisez diff3, chaque nouveau conflit aura une 3ème section, l'ancêtre commun fusionné.
Examinez d'abord l'ancêtre commun fusionné. Comparez ensuite 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 en TextMessage, en passant les mêmes paramètres. Vous pouvez également voir que l'intention de la branche de fonctionnalité est de passer false au lieu de true pour l'option: include_timestamp. Pour fusionner ces modifications, combinez l'intention des deux:
En général:
Autre solution: résoudre en appliquant manuellement les modifications de la branche
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 branches se trouvaient avoir une ligne vierge 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 consister à examiner la modification de chaque côté de la fusion et à appliquer manuellement le diff à l'autre fichier.
Voyons comment nous pourrions résoudre un conflit dans un scénario où la fusion
origin/feature1
où leslib/message.rb
conflits.Décidez si notre branche actuellement extraite (
HEAD
, ou--ours
) ou la branche que nous fusionnons (origin/feature1
, ou--theirs
) est une modification plus simple à appliquer. L'utilisation de diff avec triple point (git diff a...b
) montre les changements survenusb
depuis sa dernière divergence par rapport àa
, ou en d'autres termes, compare l'ancêtre commun de a et b à b.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.
Avec le changement compliqué vérifié, tirez vers le haut le diff du changement plus simple (voir l'étape 1). Appliquez chaque modification de ce diff au fichier en conflit.
la source
Dans mon flux de travail, je rebase autant que possible (et j'essaye de le faire souvent. Ne pas laisser les écarts s'accumuler réduit drastiquement la quantité et la gravité des collisions entre les branches).
Cependant, même dans un flux de travail basé principalement sur la refonte, il existe une place pour les fusions.
Rappelez-vous que la fusion crée en fait un nœud qui a deux parents. Considérons maintenant la situation suivante: j'ai deux brances de fonctionnalités indépendantes A et B, et je souhaite maintenant développer des éléments sur la branche de fonctionnalités C qui dépend à la fois de A et de B, tandis que A et B sont examinés.
Ce que je fais alors, c'est ce qui suit:
Maintenant, la branche C inclut des modifications à la fois de A et de B, et je peux continuer à développer dessus. Si je modifie A, je reconstruis le graphique des branches de la manière suivante:
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.
la source
NE PAS utiliser git push origin --mirror SOUS PRESQUE AUCUNE CIRCONSTANCE.
Il ne vous demande pas si vous êtes sûr de vouloir faire cela, et vous feriez mieux d'être sûr, car cela effacera toutes vos branches distantes qui ne sont pas sur votre box locale.
http://twitter.com/dysinger/status/1273652486
la source
Instructions to this machine may lead to unintended consequences, loss of work/data, or even death (at the hands of the sysad). Remember that you are solely responsible for the consequences of your actions
dans le MOTD.J'ai une question après avoir lu votre explication: se pourrait-il que vous n'ayez jamais fait
avant de faire le 'git rebase / merge master' dans votre branche de fonctionnalité?
Parce que votre branche principale ne se mettra pas à jour automatiquement à partir du référentiel de votre ami. Vous devez le faire avec le
git pull origin
. C'est-à-dire que vous rebaseriez toujours à partir d'une branche maîtresse locale sans changement? Et puis viennent le temps de push, vous poussez dans un référentiel qui a des commits (locaux) que vous n'avez jamais vus et donc le push échoue.la source
Dans votre situation, je pense que votre partenaire a raison. Ce qui est bien avec le rebasage, c'est que pour l'extérieur, vos changements semblent tous se produire dans une séquence propre par eux-mêmes. Ça signifie
Vous pouvez toujours continuer à pousser votre branche de développement privé vers le référentiel distant pour des raisons de sauvegarde, mais d'autres ne devraient pas traiter cela comme une branche "publique" car vous allez rebaser. BTW, une commande simple pour ce faire est
git push --mirror origin
.L'article Logiciel d'emballage utilisant Git explique assez bien les compromis entre fusion et 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 envisagez de les intégrer au réseau principal.
la source
origin
, vous devez mettre en miroir vers un troisième référentiel de sauvegarde dédié.Dans vos flux de travail, ni ceux de votre partenaire, ni ceux que vous avez suggérés, vous n'auriez pas dû rencontrer des conflits qui n'avaient aucun sens. Même si vous l'aviez fait, si vous suivez les workflows suggérés, une résolution forcée ne devrait plus être nécessaire après la résolution. 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 un descendant de la pointe distante.
Je pense que vous devez regarder attentivement ce qui s'est passé. Quelqu'un d'autre aurait-il (délibérément ou non) rembobiné la branche maître distante entre votre création de la branche locale et le point auquel vous avez tenté de la fusionner à nouveau dans la branche locale?
Par rapport à de nombreux 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 travailler sur les problèmes fondamentaux de vos flux source. Git n'effectue pas de magie, donc les changements conflictuels provoquent des conflits, mais il devrait faciliter la tâche d'écriture par le suivi de la filiation de la validation.
la source
"Même si vous êtes un développeur unique avec seulement quelques branches, cela vaut la peine de prendre l'habitude d'utiliser correctement le rebase et la fusion. Le modèle de travail de base ressemblera à:
Créer une nouvelle branche B à partir de la branche A existante
Ajouter / valider des modifications sur la branche B
Rebaser les mises à jour de la branche A
Fusionner les modifications de la branche B vers la branche A "
https://www.atlassian.com/git/tutorials/merging-vs-rebasing/
la source
D'après ce que j'ai observé, git merge a tendance à garder les branches séparées même après la fusion, tandis que rebase puis merge les combine en une seule branche. Ce dernier sort beaucoup plus propre, alors que dans le premier, il serait plus facile de savoir quels commits appartiennent à quelle branche même après la fusion.
la source
Avec Git, il n'y a pas de flux de travail «correct». Utilisez tout ce qui flotte sur votre bateau. Cependant, si vous rencontrez constamment des conflits lors de la fusion de succursales, vous devriez peut-être mieux coordonner vos efforts avec vos collègues développeurs? On dirait que vous continuez à éditer les mêmes fichiers. Faites également attention aux mots clés d'espaces et de subversion (c.-à-d. «$ Id $» et autres).
la source
J'utilise uniquement le flux de travail de rebase, car il est visuellement plus clair (non seulement dans GitKraken, mais aussi dans Intellij et dans
gitk
, mais je recommande le plus souvent): vous avez une branche, elle provient du maître et elle retourne au maître . Lorsque le diagramme est propre et beau, vous saurez que rien ne va jamais en enfer .Mon flux de travail est presque le même que le vôtre, mais avec une seule petite différence: j'en
squash
engage un dans ma branche locale avantrebase
ma branche sur les dernières modificationsmaster
, car:ce qui signifie que si vous avez 15 commits changeant la même ligne que
master
vous, vous devez vérifier 15 fois si vous ne courez pas, mais ce qui compte, c'est le résultat final, non?Ainsi, l'ensemble du flux de travail est:
Commander
master
et tirer pour vous assurer que vous disposez de la dernière versionÀ partir de là, créez une nouvelle succursale
Faites votre travail là-bas, vous pouvez librement vous engager plusieurs fois, et pousser à distance, pas de soucis, car c'est votre branche.
Si quelqu'un vous dit, "hé, mon PR / MR est approuvé, maintenant il est fusionné pour maîtriser", vous pouvez les chercher / les retirer. Vous pouvez le faire à tout moment ou à l'étape 6.
Après avoir fait tout votre travail, validez-les, et si vous avez plusieurs validations , écrasez-les (ce sont toutes vos tâches, et combien de fois vous modifiez une ligne de code n'a pas d'importance; la seule chose importante est la version finale). Poussez-le ou non, cela n'a pas d'importance.
Passez à la caisse
master
,pull
encore une fois pour vous assurer que vous disposez des dernières informationsmaster
locales. Votre diagramme doit ressembler à ceci:Comme vous pouvez le voir, vous êtes sur votre branche locale, qui provient d'un statut obsolète
master
, tandis quemaster
(local et distant) a progressé avec les changements de votre collègue.master
et à choisir "Rebase"; une autre raison pour laquelle j'aime ça.) Après cela, vous serez comme:Alors maintenant, vous avez toutes les modifications les plus récentes
master
, combinées avec les modifications de votre branche. Vous pouvez maintenant pousser vers votre télécommande et, si vous avez déjà poussé, vous devrez forcer la poussée; Git vous dira que vous ne pouvez pas simplement avancer rapidement. C'est normal, à cause du rebase, vous avez changé le point de départ de votre branche. Mais vous ne devriez pas avoir peur: utilisez la force avec sagesse . Au final, la télécommande est aussi votre branche donc vous n'affectez pasmaster
même si vous faites quelque chose de mal.Créez le PR / MR et attendez qu'il soit approuvé,
master
votre contribution aura donc . Félicitations! Ainsi, vous pouvez maintenantmaster
passer à la caisse , retirer vos modifications et supprimer votre branche locale afin de nettoyer le diagramme. La branche distante doit également être supprimée, si cela n'est pas fait lorsque vous la fusionnez dans master.Le diagramme final est à nouveau clair et net:
la source