1755 votes

Comment fusionner ou prélever sélectivement les modifications d'une autre branche dans Git ?

J'utilise Git sur un nouveau projet qui possède deux branches de développement parallèles, mais actuellement expérimentales :

  • master Importation de la base de code existante et quelques modifications dont je suis généralement sûr.
  • exp1 : branche expérimentale n°1
  • exp2 : branche expérimentale n°2

exp1 y exp2 représentent deux approches architecturales très différentes. Tant que je ne suis pas plus avancé, je n'ai aucun moyen de savoir laquelle (si l'une ou l'autre) fonctionnera. Lorsque je progresse dans une branche, il m'arrive d'avoir des modifications qui seraient utiles dans l'autre branche et j'aimerais ne fusionner que celles-ci.

Quelle est la meilleure façon de fusionner des changements sélectifs d'une branche de développement à une autre tout en laissant derrière soi tout le reste ?

Les approches que j'ai envisagées :

  1. git merge --no-commit suivi d'un déstockage manuel d'un grand nombre de modifications que je ne veux pas rendre communes entre les branches.

  2. Copie manuelle des fichiers communs dans un répertoire temporaire, suivie de git checkout pour passer à l'autre branche, puis une copie plus manuelle du répertoire temporaire dans l'arbre de travail.

  3. Une variation de ce qui précède. Abandonnez le exp pour le moment et utiliser deux dépôts locaux supplémentaires pour l'expérimentation. Cela rend la copie manuelle des fichiers beaucoup plus simple.

Ces trois approches semblent fastidieuses et sujettes aux erreurs. J'espère qu'il existe une meilleure approche, quelque chose de semblable à un paramètre de chemin de filtre qui rendrait git-merge plus sélectif.

8 votes

Si les changements dans vos branches expérimentales sont bien organisés dans des commits séparés, il est préférable de penser en termes de fusion sélective. commet au lieu de fichiers sélectifs. La plupart des réponses ci-dessous supposent que c'est le cas.

2 votes

Est-ce qu'une combinaison de git merge -s ours --no-commit suivi de quelques git read-tree serait une bonne solution pour cela ? Voir stackoverflow.com/questions/1214906/

41 votes

Une question plus récente a une réponse d'une ligne, bien rédigée : stackoverflow.com/questions/10784523/

1149voto

Susheel Javadi Points 1084

J'ai eu exactement le même problème que celui mentionné par vous ci-dessus. Mais j'ai trouvé este plus claire dans l'explication de la réponse.

Résumé :

  • Vérifiez le(s) chemin(s) de la branche que vous voulez fusionner,

     $ git checkout source_branch -- <paths>...
    
    Hint: It also works without `--` like seen in the linked post.
  • ou pour fusionner sélectivement les hunks

     $ git checkout -p source_branch -- <paths>...

Vous pouvez également utiliser l'option "reset" et ensuite "add". -p ,

    $ git reset <paths>...
    $ git add -p <paths>...
  • S'engager enfin

     $ git commit -m "'Merge' these changes"

12 votes

L'article de Bart J. est la meilleure approche. Clair, simple, une seule commande. C'est celle que je m'apprête à utiliser :)

316 votes

Ce n'est pas une vraie fusion. Vous choisissez les changements par fichier plutôt que par commit, et vous perdrez toute information existante sur le commit (auteur, message). D'accord, c'est une bonne chose si vous voulez fusionner tous les changements dans certains fichiers et il est normal que vous deviez refaire tous les commits. Mais si les fichiers contiennent à la fois des changements à fusionner et d'autres à écarter, l'une des méthodes fournies dans d'autres réponses vous servira mieux.

0 votes

Semble fonctionner, malheureusement, je ne vois pas les changements avec git diff

565voto

1800 INFORMATION Points 55907

Vous utilisez le cueillir des cerises pour obtenir les commits individuels d'une branche.

Si le ou les changements que vous souhaitez ne sont pas dans des commits individuels, utilisez la méthode présentée ici pour diviser le commit en commits individuels . En gros, vous utilisez git rebase -i pour obtenir le commit original à modifier, puis git reset HEAD^ pour rétablir sélectivement les changements, puis git commit pour valider ce bit comme un nouveau commit dans l'historique.

Il existe une autre méthode intéressante ici dans Red Hat Magazine, où ils utilisent git add --patch ou éventuellement git add --interactive qui vous permet d'ajouter seulement des parties d'un hunk, si vous voulez diviser différentes modifications dans un fichier individuel (recherchez "split" dans cette page).

Après avoir divisé les changements, vous pouvez maintenant sélectionner ceux qui vous intéressent.

24 votes

D'après ce que j'ai compris, cette réponse est inutilement plus compliquée que la réponse la plus votée.

72 votes

C'est techniquement la bonne réponse, la bonne réponse semble "alambiquée". --- La réponse la plus votée n'est qu'une réponse rapide et sale "qui fait l'affaire", ce qui, pour la plupart des gens, est tout ce qui compte ( :

3 votes

@akaihola : HEAD^ est correct. Voir man git-rev-parse : Un suffixe ^ à un paramètre de révision signifie le premier parent de cet objet commit. La notation du préfixe ^ est utilisée pour exclure les commits accessibles depuis un commit.

448voto

alvinabad Points 621

Pour fusionner sélectivement les fichiers d'une branche dans une autre branche, exécutez

git merge --no-ff --no-commit branchX

donde branchX est la branche à partir de laquelle vous voulez fusionner dans la branche courante.

El --no-commit permet de mettre en scène les fichiers qui ont été fusionnés par Git sans pour autant les commiter. Cela vous donnera la possibilité de modifier les fichiers fusionnés comme vous le souhaitez, puis de les valider vous-même.

Selon la façon dont vous souhaitez fusionner des fichiers, il existe quatre cas de figure :

1) Vous voulez une véritable fusion.

Dans ce cas, vous acceptez les fichiers fusionnés de la manière dont Git les a fusionnés automatiquement, puis vous les livrez.

2) Il y a certains fichiers que vous ne voulez pas fusionner.

Par exemple, vous voulez conserver la version de la branche actuelle et ignorer la version de la branche à partir de laquelle vous fusionnez.

Pour sélectionner la version dans la branche actuelle, exécutez :

git checkout HEAD file1

Cela permettra de récupérer la version de file1 dans la branche courante et écraser le file1 automatisé par Git.

3) Si vous voulez la version dans la brancheX (et non une vraie fusion).

Cours :

git checkout branchX file1

Cela permettra de récupérer la version de file1 en branchX et écraser file1 auto-fusionné par Git.

4) Le dernier cas est si vous voulez sélectionner seulement des fusions spécifiques dans file1 .

Dans ce cas, vous pouvez modifier le file1 directement, mettez-le à jour en fonction de la version que vous souhaitez pour la version de file1 à devenir, puis à s'engager.

Si Git ne peut pas fusionner un fichier automatiquement, il signalera le fichier comme étant " non immergé " et produire une copie où vous devrez résoudre les conflits manuellement.


Pour expliquer davantage avec un exemple, disons que vous voulez fusionner branchX dans la branche actuelle :

git merge --no-ff --no-commit branchX

Vous exécutez ensuite le git status pour visualiser l'état des fichiers modifiés.

Par exemple :

git status

# On branch master
# Changes to be committed:
#
#       modified:   file1
#       modified:   file2
#       modified:   file3
# Unmerged paths:
#   (use "git add/rm <file>..." as appropriate to mark resolution)
#
#       both modified:      file4
#

file1 , file2 et file3 sont les fichiers que git a réussi à fusionner automatiquement.

Ce que cela signifie, c'est que les changements dans le master y branchX pour ces trois dossiers ont été combinés ensemble sans aucun conflit.

Vous pouvez vérifier comment la fusion a été effectuée en exécutant la commande git diff --cached ;

git diff --cached file1
git diff --cached file2
git diff --cached file3

Si vous trouvez une fusion indésirable, vous pouvez

  1. modifier directement le fichier
  2. sauver
  3. git commit

Si vous ne voulez pas fusionner file1 et que vous voulez conserver la version de la branche actuelle

Exécuter

git checkout HEAD file1

Si vous ne voulez pas fusionner file2 et ne veulent que la version dans branchX

Exécuter

git checkout branchX file2

Si vous voulez file3 pour être fusionné automatiquement, ne faites rien.

Git l'a déjà fusionné à ce stade.

file4 ci-dessus est une fusion ratée par Git. Cela signifie qu'il y a des changements dans les deux branches qui se produisent sur la même ligne. C'est là que vous devrez résoudre les conflits manuellement. Vous pouvez écarter la fusion réalisée en éditant directement le fichier ou en exécutant la commande checkout pour la version dans la branche que vous souhaitez file4 pour devenir.

Enfin, n'oubliez pas de git commit .

10 votes

Attention toutefois : si le git merge --no-commit branchX est juste une avance rapide, le pointeur sera mis à jour, et le --no-commit est donc silencieusement ignoré.

17 votes

@cfi Et si on ajoutait --no-ff pour empêcher ce comportement ?

5 votes

Je suggère définitivement de mettre à jour cette réponse avec l'option "--no-ff" d'Eduardo. J'ai lu l'ensemble du document (qui était par ailleurs génial) pour que ma fusion soit accélérée.

118voto

nosatalian Points 1621

Je n'aime pas les approches ci-dessus. L'utilisation du cherry-pick est très bien pour choisir un seul changement, mais c'est une douleur si vous voulez apporter tous les changements sauf les mauvais. Voici mon approche.

Il n'y a pas --interactive que vous pouvez passer à git merge.

Voici l'alternative :

Vous avez des modifications dans la branche 'feature' et vous voulez en transférer certaines, mais pas toutes, vers 'master' d'une manière non négligeable (c'est-à-dire que vous ne voulez pas choisir et valider chacune d'entre elles).

git checkout feature
git checkout -b temp
git rebase -i master

# Above will drop you in an editor and pick the changes you want ala:
pick 7266df7 First change
pick 1b3f7df Another change
pick 5bbf56f Last change

# Rebase b44c147..5bbf56f onto b44c147
#
# Commands:
# pick = use commit
# edit = use commit, but stop for amending
# squash = use commit, but meld into previous commit
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

git checkout master
git pull . temp
git branch -d temp

Il suffit donc d'envelopper cela dans un shell script, de changer master en $to et de changer feature en $from et vous êtes prêt à partir :

#!/bin/bash
# git-interactive-merge
from=$1
to=$2
git checkout $from
git checkout -b ${from}_tmp
git rebase -i $to
# Above will drop you in an editor and pick the changes you want
git checkout $to
git pull . ${from}_tmp
git branch -d ${from}_tmp

0 votes

J'ai corrigé le formatage - c'est une bonne méthode, si vous voulez faire une sélection de commits.

0 votes

J'utilise cette technique maintenant et cela semble avoir très bien fonctionné.

4 votes

Vous pourriez vouloir changer git rebase -i $to à git rebase -i $to || $SHELL afin que l'utilisateur puisse appeler git --skip etc, si nécessaire en cas d'échec du rebasement. Cela vaut également la peine de chaîner les lignes ensemble avec && à la place des nouvelles lignes.

112voto

Chronial Points 15402

Il y a une autre façon de faire :

git checkout -p

C'est un mélange entre git checkout y git add -p et pourrait bien être exactement ce que vous recherchez :

   -p, --patch
       Interactively select hunks in the difference between the <tree-ish>
       (or the index, if unspecified) and the working tree. The chosen
       hunks are then applied in reverse to the working tree (and if a
       <tree-ish> was specified, the index).

       This means that you can use git checkout -p to selectively discard
       edits from your current working tree. See the “Interactive Mode”
       section of git-add(1) to learn how to operate the --patch mode.

14 votes

C'est de loin la méthode la plus facile et la plus simple, tant que vous n'avez qu'un nombre raisonnable de modifications à fusionner. J'espère que d'autres personnes remarqueront cette réponse et la voteront. Exemple : git checkout --patch exp1 file_to_merge

2 votes

Une réponse similaire a été postée sur cette question : stackoverflow.com/a/11593308/47185

0 votes

Oh, je ne savais pas que la caisse avait un patch ! J'ai fait checkout/reset/add -p à la place.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X