web-dev-qa-db-fra.com

git: fusionner deux branches: quelle direction?

Nous avons la situation suivante:

             A --- B --- C --- ... --- iphone
           /
  ... --- last-working --- ... --- master

Entre la dernière version et l'iPhone, 32 commits ont été effectués. Entre le dernier travail et le maître, beaucoup de commits ont été effectués.

Ce que je veux maintenant, c'est une nouvelle branche où j'ai fusionné l'iphone et le maître actuel. Et à un moment ultérieur, cela devrait être fusionné dans le maître.

Tout d'abord, je comptais faire:

git checkout iphone -b iphone31
git merge master

Mais alors j'ai pensé, s'il valait mieux faire:

git checkout master -b iphone31
git merge iphone

Maintenant, je me demande. Quelle serait la différence dans le résultat? La fusion se comporterait-elle différemment?

J'ai déjà essayé les deux et comme je m'y attendais, j'ai de nombreux conflits parce que l'iphone est vraiment vieux par rapport au maître. Je me demande maintenant quelle est la manière la plus simple de les fusionner.

Peut-être même que commencer avec master et y fusionner chaque commit d'iphone serait plus facile? Comme faire ça:

git checkout master -b iphone31
git merge A
git merge B
git merge C
...
git merge iphone

À la toute fin, lorsque cette fusion est effectuée (c'est-à-dire que tous les conflits sont résolus et que cela fonctionne), je veux le faire:

git checkout master
git merge iphone31
60
Albert

Concernant les alternatives

git checkout iphone -b iphone31
git merge master

et

git checkout master -b iphone31
git merge iphone

ils auront la même facilité ou difficulté, c'est comme se demander si un verre est à moitié plein ou à moitié vide.

Perception de l'arborescence des versions

Notre façon de voir les arbres de versions n'est en quelque sorte que notre perception arbitraire. Disons que nous avons une arborescence de versions comme celle-ci:

    A----------+
    |          |
    |          |
   \_/        \_/
    B          X
    |          |
    |          |
   \_/        \_/
    C          Y
    |
    |
   \_/
    D
    |
    |
   \_/
    E

Et disons que nous voulons faire une nouvelle version Z extraite de Y sur la base des changements de C en E mais sans inclure les changements effectués de A en C.

"Oh, ce sera difficile car il n'y a pas de point de départ commun." Eh bien pas vraiment. Si nous plaçons juste les objets un peu différemment dans la disposition graphique comme celle-ci

      /
    C+---------+
    | \        |
    |          |
   \_/         |
    D          B
    |         / \
    |          |
   \_/         |
    E          A
               |
               |
              \_/
               X
               |
               |
              \_/
               Y

maintenant, les choses semblent prometteuses. Notez que je n'ai changé aucune relation ici, les flèches pointent toutes de la même manière que dans l'image précédente et la version A est toujours la base commune. Seule la mise en page est modifiée.

Mais il est maintenant trivial d'imaginer un arbre différent

    C'---------+
    |          |
    |          |
   \_/        \_/
    D          B'
    |          |
    |          |
   \_/        \_/
    E          A
               |
               |
              \_/
               X
               |
               |
              \_/
               Y

où la tâche serait simplement de fusionner la version E normalement.

Vous pouvez donc fusionner tout ce que vous voulez, la seule chose qui influence la facilité ou la difficulté est l'agrégat des changements effectués entre l'endroit où vous sélectionnez comme point de départ ou base commune et où vous fusionnez. Vous n'êtes pas limité à utiliser le point de départ naturel suggéré par votre outil de versioning.

Cela peut ne pas être simple avec certains systèmes/outils de contrôle de version, mais si tout le reste échoue, rien ne vous empêche de le faire manuellement en extrayant la version C et en enregistrant le fichier en tant que fichier1, en extrayant la version E et en enregistrant le fichier en tant que fichier2 , extraire la version Y et enregistrer le fichier en tant que fichier3, puis exécutez kdiff3 -o merge_result file1 file2 file3.

Répondre

Maintenant, pour votre situation spécifique, il est difficile de dire exactement quelle stratégie produira le moins de problèmes, mais s'il y a de nombreux changements qui créent une sorte de conflit, il est probablement plus facile de diviser et de fusionner des parties plus petites.

Ma suggestion serait qu'étant donné qu'il y a 32 commits entre le dernier travail et l'iphone, vous pouvez par exemple commencer par créer une branche de master puis fusionner dans les 16 premiers commits. Si cela vous pose trop de problèmes, revenez en arrière et essayez de fusionner les 8 premiers commits. Etc. Dans le pire des cas, vous finissez par fusionner chacun des 32 commits un par un, mais ce serait probablement plus facile que d'avoir à gérer tous les conflits accumulés en une seule opération de fusion (et dans ce cas, vous travaillez avec un vraiment base de code divergente).

Conseils:

Dessinez sur papier un arbre de version et notez avec des flèches ce que vous voulez fusionner. Rayez les choses au fur et à mesure qu'elles se font si vous divisez le processus en plusieurs étapes. Cela vous donnera une image plus claire de ce que vous voulez réaliser, de ce que vous avez fait jusqu'à présent et de ce qui reste.

Je peux vraiment recommander KDiff , c'est un excellent outil de diff/fusion.

43
hlovdal

Il existe une différence.


Vérifiez toujours la branche cible lors d'une fusion (c.-à-d. Si vous fusionnez A en B, cochez B).


Les gens disent souvent que la direction de la fusion n'a pas d'importance, mais c'est faux. Bien que le contenu résultant soit le même quelle que soit la direction de la fusion, plusieurs aspects sont différents:

  1. Les différences répertoriées dans la fusion-validation résultante seront différentes selon la direction.
  2. La plupart des visualiseurs de branche décideront quelle branche est "principale" en utilisant la direction de fusion.

Pour élaborer, imaginez cet exemple exagéré:

  • Vous vous êtes éloigné de MASTER à 1000 commits derrière, et vous l'avez nommé DEVELOP (ou dans un scénario de branche de suivi, vous n'avez pas récupéré depuis un certain temps).
  • Vous ajoutez un commit dans DEVELOP. Vous savez qu'il n'y a aucun conflit pour ce changement.
  • Vous voulez pousser vos modifications dans MASTER.
  • Vous fusionnez incorrectement MASTER into DEVELOP (c.-à-d. DEVELOP est extrait pendant la fusion). Ensuite, vous poussez DEVELOP en tant que nouveau MASTER.
  • Les différences dans la fusion-validation résultante afficheront les 1000 validations qui se sont produites dans MASTER, car DEVELOP est le point de référence.

Non seulement ces données sont inutiles, mais il est difficile de lire ce qui se passe. La plupart des visualiseurs donneront l'impression que votre ligne DEVELOP a toujours été la principale, avec 1000 commits.

Ma suggestion est la suivante: Vérifiez toujours la branche cible lors d'une fusion (c.-à-d. Si vous fusionnez A en B, cochez B).

  • Si vous travaillez sur une branche parallèle et que vous souhaitez apporter périodiquement des modifications à partir d'une branche principale, vérifiez votre branche parallèle. Les différences auront un sens - vous verrez les changements effectués dans la branche principale par rapport à votre branche.
  • Lorsque vous avez terminé de travailler en parallèle et que vous souhaitez fusionner vos modifications dans la branche principale, extrayez la branche principale et fusionnez avec votre branche parallèle. Le diff aura à nouveau un sens - il montrera quels sont vos changements parallèles par rapport à la branche principale.

La lisibilité du journal, à mon avis, est importante.

12
Ryuu

La meilleure approche dépend vraiment si d'autres personnes ont des copies à distance de votre code. Si la branche master se trouve uniquement sur votre machine locale, vous pouvez utiliser la commande rebase pour appliquer de manière interactive les validations de la branche feature dans master:

git checkout master -b iphone-merge-branch
git rebase -i iphone

Notez que cela modifie l'historique de validation de votre nouvelle branche iphone-merge-branch , ce qui peut causer des problèmes à quiconque essaie de tirer vos modifications dans sa caisse plus tard. En revanche, la commande de fusion applique les modifications en tant que nouvelle validation, ce qui est plus sûr lors de la collaboration car elle n'affecte pas l'historique de la branche. Voir cet article pour quelques conseils utiles sur l'utilisation de rebase.

Si vous devez synchroniser l'historique de vos validations, il vaut mieux effectuer une fusion. Vous pouvez utiliser git mergetool pour résoudre interactivement les conflits un par un à l'aide d'un outil de diff visuel (un tutoriel à ce sujet peut être trouvé ici ):

git checkout master -b iphone-merge-branch
git merge iphone
git mergetool -t kdiff3

Une troisième option, si vous voulez un contrôle absolu sur le processus, serait d'utiliser git cherry-pick . Vous pouvez utiliser gitk (ou votre visualiseur d'historique préféré) pour afficher les hachages de validation dans la branche iphone, les noter et les sélectionner individuellement dans la branche fusionnée - en résolvant les conflits au fur et à mesure. Une explication de ce processus peut être trouvée ici . Ce processus sera le plus lent, mais pourrait être la meilleure option de secours si les autres méthodes ne fonctionnent pas:

gitk iphone
<note down the 35 commit SHA hashes in this branch>
git checkout master -b iphone-merge-branch
git cherry-pick b50788b
git cherry-pick g614590
...
6
seanhodges

Vous dites:

la branche iphone est très ancienne par rapport au maître

Voulez-vous vraiment les fusionner pour former une nouvelle branche?

Le but des branches master et iphone aurait maintenant été très différent (car l'iphone est très ancien). Une nouvelle branche fusionnant l'iphone avec un ancêtre de maître serait mieux? Penses-y.

Je vous recommande fortement de lire Fun avec les fusions et les objectifs des branches .

Après avoir lu cet article, si vous sentez toujours que vous souhaitez fusionner l'iphone et le master, @seanhodges explique comment gérer les conflits très bien.

2
ardsrk

Vous voudrez peut-être faire une sauvegarde locale de votre travail avant d'expérimenter, afin que vous puissiez redémarrer, si vous arrêtez de comprendre ce qui s'est passé.

Créez une branche de sauvegarde de votre travail afin de ne pas la perdre lors du rebase, si vous souhaitez la conserver pour référence.

git checkout iphone -b iphone_backup

Créer une nouvelle branche à partir du maître

git checkout master -b iphone31

puis rebasez dessus.

git rebase -i --onto iphone31 iphone

Sean ci-dessus a raison de rebaser en appliquant un commit à la fois. Mais ne vous inquiétez pas des commits existants sur la branche sur laquelle vous rebasiez - ils ne seront pas modifiés. Rebase ajoute de nouveaux commits (adaptés). Une fois la validation effectuée, vous contrôlez mieux les conflits. Ce qui est important cependant, vous devez rebaser votre travail sur le master et non l'inverse, afin que l'historique du master ne change pas.

Alternativement, vous ne pouvez faire que

git rebase -i master iphone

si vous ne vous souciez pas de sauvegarder la branche iphone. Regardez dans la documentation de rebase pour les détails et les alternatives http://git-scm.com/docs/git-rebase .

1
Thinkeye