web-dev-qa-db-fra.com

Comment fusionner ou choisir sélectivement les modifications d'une autre branche de Git?

J'utilise git sur un nouveau projet comportant deux branches de développement parallèles - mais actuellement expérimentales -:

  • master: importation de la base de code existante plus quelques mods dont je suis généralement sûr 
  • exp1: branche expérimentale n ° 1
  • exp2: branche expérimentale n ° 2

exp1 et exp2 représentent deux approches architecturales très différentes. Jusqu'à ce que je continue, je n'ai aucun moyen de savoir lequel (si l'un ou l'autre) fonctionnera. Au fur et à mesure que je progresse dans une branche, j'ai parfois des modifications qui pourraient être utiles dans l'autre branche et j'aimerais fusionner celles-ci.

Quel est le meilleur moyen de fusionner des modifications sélectives d'une branche de développement en une autre tout en laissant derrière tout le reste?

Approches que j'ai envisagées:

  1. git merge --no-commit suivi de la décomposition manuelle d’un grand nombre de modifications que je ne souhaite pas mettre en commun entre les branches.

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

  3. Une variation sur le dessus. Abandonnez les branches exp pour le moment et utilisez deux référentiels locaux supplémentaires à des fins d’expérimentation. Cela rend la copie manuelle de fichiers beaucoup plus simple.

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

1257
David Joyner

Vous utilisez la commande cherry-pick pour obtenir des commits individuels d’une branche.

Si les modifications souhaitées 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 d'origine à éditer, puis git reset HEAD^ pour annuler sélectivement les modifications, puis git commit pour valider ce bit en tant que nouveau commit dans l'historique.

Il existe une autre méthode Nice ici dans Red Hat Magazine, dans laquelle ils utilisent git add --patch ou éventuellement git add --interactive, ce qui vous permet d’ajouter des parties d’un morceau si vous souhaitez fractionner différentes modifications dans un fichier individuel (recherchez dans cette page "Divisé").

Après avoir scindé les modifications, vous pouvez désormais sélectionner uniquement celles que vous souhaitez.

420
1800 INFORMATION

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

Résumé:

  • Vérifiez le ou les chemins de la branche que vous souhaitez fusionner,

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

    Astuce: Cela fonctionne aussi sans -- comme dans la publication liée.

  • ou pour fusionner sélectivement des mecs

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

    Sinon, utilisez reset puis ajoutez avec l'option -p,

    $ git reset <paths>...
    $ git add -p <paths>...
    
  • Enfin commettre

    $ git commit -m "'Merge' these changes"
    
888
Susheel Javadi

Pour fusionner de manière sélective des fichiers d’une branche à une autre, exécutez

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

branchX est la branche à partir de laquelle vous souhaitez fusionner dans la branche actuelle.

L'option --no-commit organisera les fichiers qui ont été fusionnés par Git sans les valider réellement. Cela vous donnera la possibilité de modifier les fichiers fusionnés comme vous le souhaitez, puis de les valider vous-même. 

Selon la manière dont vous souhaitez fusionner les fichiers, il existe quatre cas:

1) Vous voulez une vraie fusion.

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

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

Par exemple, vous souhaitez conserver la version de la branche actuelle et ignorer la version de la branche à partir de laquelle vous effectuez la fusion.

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

git checkout HEAD file1

Cela récupérera la version de file1 dans la branche actuelle et écrasera le file1 généré automatiquement par Git. 

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

Courir:

git checkout branchX file1

Cela va récupérer la version de file1 dans branchX et écraser file1 automatiquement fusionné par Git.

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

Dans ce cas, vous pouvez éditer directement le file1 modifié, le mettre à jour avec ce que vous souhaitez que la version de file1 devienne, puis valider.

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



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

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

Vous exécutez ensuite la commande git status pour afficher 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 git ont été fusionnés avec succès. 

Cela signifie que les modifications apportées aux master et branchX de ces trois fichiers ont été combinées sans aucun conflit.

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

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

Si vous trouvez une fusion indésirable, alors vous pouvez

  1. éditer le fichier directement
  2. enregistrer
  3. git commit

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

Courir

git checkout HEAD file1

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

Courir

git checkout branchX file2

Si vous voulez que file3 soit fusionné automatiquement, ne faites rien.

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


file4 ci-dessus est une fusion échouée par Git. Cela signifie que des changements dans les deux branches se produisent sur la même ligne. C'est ici que vous devrez résoudre les conflits manuellement. Vous pouvez ignorer la fusion effectuée en modifiant directement le fichier ou en exécutant la commande d'extraction pour la version de la branche que vous souhaitez que file4 devienne.


Enfin, n'oubliez pas de git commit.

276
alvinabad

Je n'aime pas les approches ci-dessus. Utiliser cherry-pick est idéal pour sélectionner un seul changement, mais il est difficile d’apporter tous les changements, à l’exception de certains mauvais. Voici mon approche.

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

Voici l'alternative:

Vous avez quelques changements dans la 'fonctionnalité' de la branche et vous voulez en apporter certains mais pas tous à 'maîtriser' d'une manière non négligeable (c'est-à-dire que vous ne voulez pas sélectionner et valider chacun d'eux)

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

En résumé, dans un script Shell, modifiez master en $ to et changez la fonctionnalité 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
91
nosatalian

Il y a une autre façon d'y aller:

git checkout -p

C’est un mélange de git checkout et de git add -p et pourrait correspondre 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.
80
Chronial

Bien que certaines de ces réponses soient plutôt bonnes, j’ai le sentiment qu’aucune d’entre elles n’a répondu à la contrainte initiale de OP: sélectionner des fichiers particuliers dans des branches particulières. Cette solution fait cela, mais peut être fastidieuse s'il y a beaucoup de fichiers.

Disons que vous avez les branches master, exp1 et exp2. Vous souhaitez fusionner un fichier de chacune des branches expérimentales dans le fichier maître. Je ferais quelque chose comme ça:

git checkout master
git checkout exp1 path/to/file_a
git checkout exp2 path/to/file_b

# save these files as a stash
git stash
# merge stash with master
git merge stash

Cela vous donnera des différences dans le fichier pour chacun des fichiers que vous voulez. Rien de plus. Rien de moins. Il est utile de modifier radicalement les fichiers d’une version à l’autre - dans mon cas, la modification d’une application de Rails 2 à Rails 3.

EDIT: ceci fusionnera des fichiers, mais fera une fusion intelligente. Je ne pouvais pas comprendre comment utiliser cette méthode pour obtenir des informations de diff dans le fichier (peut-être que ce sera toujours le cas pour les différences extrêmes. Des petites choses gênantes telles que les espaces sont fusionnées à moins d'utiliser l'option -s recursive -X ignore-all-space)

49
Eric Hu

La réponse de 1800 INFORMATION est tout à fait correcte. En tant que git noob, "utiliser git cherry-pick" ne m'a pas suffi pour comprendre cela sans un peu plus de fouilles sur Internet, j'ai donc pensé poster un guide plus détaillé au cas où quelqu'un bateau similaire. 

Mon cas d'utilisation voulait attirer de manière sélective les modifications de la branche github de quelqu'un d'autre vers la mienne. Si vous avez déjà une branche locale avec les modifications, il vous suffit de suivre les étapes 2 et 5-7.

  1. Créez (si non créé) une branche locale avec les modifications que vous souhaitez apporter.

    $ git branch mybranch <base branch>

  2. Basculez dedans.

    $ git checkout mybranch

  3. Déroulez les modifications que vous souhaitez du compte de l'autre personne. Si vous ne l'avez pas déjà fait, vous voudrez les ajouter en tant que télécommande.

    $ git remote add repos-w-changes <git url>

  4. Déroulez tout de leur branche.

    $ git pull repos-w-changes branch-i-want

  5. Affichez les journaux de validation pour connaître les modifications souhaitées:

    $ git log 

  6. Revenez à la branche dans laquelle vous souhaitez extraire les modifications.

    $ git checkout originalbranch

  7. Choisissez vos commits, un par un, avec les hachages.

    $ git cherry-pick -x hash-of-commit

Astuce de chapeau: http://www.sourcemage.org/Git_Guide

45
Cory

Voici comment vous pouvez remplacer le fichier Myclass.Java dans la branche master par Myclass.Java dans la branche feature1. Cela fonctionnera même si Myclass.Java n'existe pas sur master.

git checkout master
git checkout feature1 Myclass.Java

Notez que cela écrasera - pas fusionnera - et ignorera plutôt les modifications locales dans la branche principale.

39
maestr0

Le moyen le plus simple de fusionner des fichiers spécifiques de deux branches, ne consiste pas simplement à remplacer des fichiers spécifiques par des fichiers d'une autre branche.

Première étape: Diffusez les branches

git diff branch_b > my_patch_file.patch

Crée un fichier de correctif de la différence entre la branche actuelle et branch_b

Deuxième étape: appliquez le correctif aux fichiers correspondant à un motif

git apply -p1 --include=pattern/matching/the/path/to/file/or/folder my_patch_file.patch

notes utiles sur les options

Vous pouvez utiliser * comme caractère générique dans le modèle d'inclusion. 

Les barres obliques n'ont pas besoin d'être échappées.

Vous pouvez aussi utiliser --exclude et l’appliquer à tout sauf aux fichiers correspondant au motif, ou inverser le patch avec -R. 

L'option -p1 est une retenue de la commande * unix patch et le fait que le contenu du fichier de correctif ajoute chaque nom de fichier avec a/ ou b/ (ou plus, selon la manière dont le fichier de correctif a été généré), que vous devez supprimer Déterminez le vrai fichier dans le chemin du fichier auquel le correctif doit être appliqué.

Consultez la page de manuel de git-apply pour plus d'options. 

Troisième étape: il n'y a pas d'étape trois

De toute évidence, vous voudriez valider vos modifications, mais qui peut dire que vous ne souhaitez pas effectuer d’autres modifications connexes avant de vous engager; 

25
masukomi

Voici comment vous pouvez faire en sorte que l'historique suive quelques fichiers d'une autre branche avec un minimum de complications, même si une fusion plus "simple" aurait entraîné beaucoup plus de changements que vous ne souhaitiez pas.

Tout d’abord, vous allez prendre l’étape inhabituelle de déclarer à l’avance que ce que vous êtes sur le point de commettre est une fusion, sans rien faire des fichiers dans votre répertoire de travail:

git merge --no-ff --no-commit -s ours branchname1

. . . où "nom de branche" est ce que vous prétendez fusionner. Si vous deviez vous engager immédiatement, cela ne ferait aucun changement, mais cela montrerait tout de même une ascendance de l'autre branche. Vous pouvez ajouter plus de branches/tags/etc. à la ligne de commande si vous avez besoin, aussi bien. À ce stade cependant, il n'y a aucune modification à valider, aussi récupérez les fichiers des autres révisions, ensuite.

git checkout branchname1 -- file1 file2 etc

Si vous effectuez une fusion à partir de plusieurs autres branches, recommencez si nécessaire.

git checkout branchname2 -- file3 file4 etc

Maintenant, les fichiers de l'autre branche sont dans l'index, prêts à être validés, avec un historique.

git commit

et vous aurez beaucoup d'explications à faire dans ce message de validation.

S'il vous plaît noter cependant, au cas où ce n'était pas clair, que c'est une chose foirée à faire. Ce n’est pas dans l’esprit de ce qu’est une "branche", et choisir à la perfection est une façon plus honnête de faire ce que vous feriez, ici. Si vous souhaitez effectuer une autre "fusion" pour d'autres fichiers de la même branche que vous n'avez pas apportés la dernière fois, le message "déjà à jour" vous arrêtera. C'est un symptôme de ne pas créer de branche quand nous aurions dû, dans la branche "de" devrait être plus d'une branche différente.

21
jejese

J'ai trouvé ce post contenir la réponse la plus simple. Faire simplement:

$ #git checkout <branch from which you want files> <file paths>

Exemple:

$ #pulling .gitignore file from branchB into current branch
$ git checkout branchB .gitignore

Voir le post pour plus d'informations.

14
Stunner

Je sais que je suis un peu en retard, mais ceci est mon flux de travail pour la fusion de fichiers sélectifs.

#make a new branch ( this will be temporary)
git checkout -b newbranch
# grab the changes 
git merge --no-commit  featurebranch
# unstage those changes
git reset HEAD
(you can now see the files from the merge are unstaged)
# now you can chose which files are to be merged.
git add -p
# remember to "git add" any new files you wish to keep
git commit
14
Felix

Le moyen le plus simple est de définir votre référentiel sur la branche avec laquelle vous souhaitez fusionner

git checkout [branch with file] [path to file you would like to merge]

Si vous courez 

git status

vous verrez le fichier déjà mis en scène ...

Puis courir 

git commit -m "Merge changes on '[branch]' to [file]"

Simple. 

13
dsieczko

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

Commande depuis le lien ci-dessus:

#You are in the branch you want to merge to
git checkout <branch_you_want_to_merge_from> <file_paths...>
7
Susheel Javadi

Ce n'est pas exactement ce que vous cherchiez, mais cela m'a été utile:

git checkout -p <branch> -- <paths> ...

C'est un mélange de quelques réponses.

7
Felipe

J'aime la réponse 'git-interactive-merge' ci-dessus, mais il y en a une plus facile. Laissez git le faire pour vous en utilisant une combinaison de bases interactive et sur:

      A---C1---o---C2---o---o feature
     /
----o---o---o---o master

Le cas est donc que vous souhaitiez C1 et C2 à partir de la branche 'fonctionnalité' (point de branche 'A'), mais pas du reste pour l'instant.

# git branch temp feature
# git checkout master
# git rebase -i --onto HEAD A temp

Comme ci-dessus, vous êtes redirigé vers l'éditeur interactif dans lequel vous sélectionnez les lignes de sélection pour C1 et C2 (comme ci-dessus). Enregistrez et quittez, puis la rebase continuera et vous donnera la branche 'temp' ainsi que HEAD sur le maître + C1 + C2:

      A---C1---o---C2---o---o feature
     /
----o---o---o---o-master--C1---C2 [HEAD, temp]

Ensuite, vous pouvez simplement mettre à jour master vers HEAD et supprimer la branche temporaire. Vous pouvez continuer:

# git branch -f master HEAD
# git branch -d temp
6
Wade

Je ferais un

git diff commit1..commit2 filepattern | git-apply --index && git commit

De cette façon, vous pouvez limiter le nombre de commits d'un fichier à partir d'une branche.

Volé à: http://www.gelato.unsw.edu.au/archives/git/0701/37964.html

6
lumpidu

Je sais que cette question est ancienne et qu'il existe de nombreuses autres réponses, mais j'ai écrit mon propre script appelé «pmerge» pour fusionner partiellement des répertoires. C'est un travail en cours et j'apprends toujours les scripts git et bash.

Cette commande utilise git merge --no-commit et applique ensuite les modifications qui ne correspondent pas au chemin fourni.

Utilisation: git pmerge branch path
Exemple: git merge develop src/

Je ne l'ai pas testé de manière approfondie. Le répertoire de travail ne doit contenir aucune modification non validée ni aucun fichier non suivi.

#!/bin/bash

E_BADARGS=65

if [ $# -ne 2 ]
then
    echo "Usage: `basename $0` branch path"
    exit $E_BADARGS
fi

git merge $1 --no-commit
IFS=$'\n'
# list of changes due to merge | replace nulls w newlines | strip lines to just filenames | ensure lines are unique
for f in $(git status --porcelain -z -uno | tr '\000' '\n' | sed -e 's/^[[:graph:]][[:space:]]\{1,\}//' | uniq); do
    [[ $f == $2* ]] && continue
    if git reset $f >/dev/null 2>&1; then
        # reset failed... file was previously unversioned
        echo Deleting $f
        rm $f
    else
        echo Reverting $f
        git checkout -- $f >/dev/null 2>&1
    fi
done
unset IFS
5
Andy

Qu'en est-il de git reset --soft branch? Je suis surpris que personne n'en ait encore parlé.

Pour moi, c’est le moyen le plus simple de sélectionner sélectivement les modifications d’une autre branche, car cette commande place dans mon arbre de travail toutes les modifications et que je peux facilement choisir ou annuler celle dont j’ai besoin. De cette façon, j'ai un contrôle total sur les fichiers validés.

3
GarryOne

Vous pouvez utiliser read-tree pour lire ou fusionner une arborescence distante donnée dans l'index actuel, par exemple:

git remote add foo [email protected]/foo.git
git fetch foo
git read-tree --prefix=my-folder/ -u foo/master:trunk/their-folder

Pour effectuer la fusion, utilisez plutôt -m.

Voir aussi: Comment fusionner un sous-répertoire dans git?

2
kenorb

Une approche simple pour la fusion/validation sélective par fichier:

git checkout dstBranch git merge srcBranch // make changes, including resolving conflicts to single files git add singleFile1 singleFile2 git commit -m "message specific to a few files" git reset --hard # blow away uncommitted changes

1
Dave C

Lorsque seuls quelques fichiers ont changé entre les validations actuelles des deux branches, je fusionne manuellement les modifications en parcourant les différents fichiers.

git difftoll <branch-1>..<branch-2>

1
raratiru

Si vous avez seulement besoin de fusionner un répertoire particulier et de laisser tout le reste intact tout en préservant l'historique, vous pouvez éventuellement essayer ceci ... créez un nouveau target-branch à partir de la master avant de faire des expériences.

Les étapes ci-dessous supposent que vous avez deux branches target-branch et source-branch et que le répertoire dir-to-merge que vous souhaitez fusionner se trouve dans le source-branch. Supposez également que vous avez d'autres répertoires tels que dir-to-retain dans la cible que vous ne souhaitez pas modifier et conserver l'historique. En outre, supposons qu'il existe des conflits de fusion dans le dir-to-merge.

git checkout target-branch
git merge --no-ff --no-commit -X theirs source-branch
# the option "-X theirs", will pick theirs when there is a conflict. 
# the options "--no--ff --no-commit" prevent a commit after a merge, and give you an opportunity to fix other directories you want to retain, before you commit this merge.

# the above, would have messed up the other directories that you want to retain.
# so you need to reset them for every directory that you want to retain.
git reset HEAD dir-to-retain
# verify everything and commit.
0
code4kix

Si vous n'avez pas trop de fichiers qui ont changé, cela ne vous laissera pas de commits supplémentaires.

1. Double branche temporairement
$ git checkout -b temp_branch

2. Réinitialiser au dernier commit voulu
$ git reset --hard HEAD~n, où n est le nombre de commits dont vous avez besoin pour revenir en arrière

3. Extraire chaque fichier de la branche d'origine
$ git checkout Origin/original_branch filename.ext

Maintenant, vous pouvez valider et forcer Push (pour écraser la télécommande), si nécessaire.

0
JBaczuk