Comment git-cherry-pick uniquement les modifications apportées à certains fichiers?

587

Si je souhaite fusionner dans une branche Git les modifications apportées uniquement à certains des fichiers modifiés dans un commit particulier qui inclut des modifications sur plusieurs fichiers, comment cela peut-il être réalisé?

Supposons que le git commit appelé stuffa des modifications aux fichiers A, B, Cet , Dmais je veux fusionner seulement des stuffchangements de » aux fichiers Aet B. Cela ressemble à un travail git cherry-pickmais cherry-pickne sait que fusionner des validations entières, pas un sous-ensemble des fichiers.

Tobias Kienzler
la source

Réponses:

689

Je le ferais avec cherry-pick -n( --no-commit) qui vous permet d'inspecter (et de modifier) ​​le résultat avant de valider:

git cherry-pick -n <commit>

# unstage modifications you don't want to keep, and remove the
# modifications from the work tree as well.
# this does work recursively!
git checkout HEAD <path>

# commit; the message will have been stored for you by cherry-pick
git commit

Si la grande majorité des modifications sont des choses que vous ne voulez pas, au lieu de vérifier les chemins individuels (étape intermédiaire), vous pouvez tout réinitialiser, puis ajouter ce que vous voulez:

# unstage everything
git reset HEAD

# stage the modifications you do want
git add <path>

# make the work tree match the index
# (do this from the top level of the repo)
git checkout .
Cascabel
la source
10
En plus de git checkout .je recommanderais également git clean -fde supprimer tous les fichiers nouveaux mais indésirables introduits par le commit choisi par les cerises.
rlat
4
Remarque supplémentaire pour cette dernière méthode: j'utilise git add -pce qui vous permet de décider de manière interactive quelles modifications vous souhaitez ajouter à l'index par fichier
matthaeus
6
Ce n'est pas si grand dans le cas où la cerise choisi commit ne pas appliquer à la copie de travail actuel parce qu'il est tellement différent, mais un fichier serait appliquer proprement.
Expiation limitée du
3
Vous pouvez également annuler la mise en scène de manière sélective avec git reset -p HEAD. C'est l'équivalent add -pmais très peu savent qu'il existe.
Patrick Schlüter
1
Astuce très utile. Je l'ai mis dans un résumé au cas où quelqu'un en aurait besoin comme script rapide gist.github.com/PiDayDev/68c39b305ab9d61ed8bb2a1195ee1afc
Damiano
146

Les autres méthodes n'ont pas fonctionné pour moi car le commit a eu beaucoup de changements et de conflits avec beaucoup d'autres fichiers. Ce que j'ai trouvé, c'était simplement

git show SHA -- file1.txt file2.txt | git apply -

Il ne s'agit pas réellement adddes fichiers ou ne fait pas de commit pour vous, vous devrez donc peut-être le suivre avec

git add file1.txt file2.txt
git commit -c SHA

Ou si vous souhaitez ignorer l'ajout, vous pouvez utiliser l' --cachedargument pourgit apply

git show SHA -- file1.txt file2.txt | git apply --cached -

Vous pouvez également faire la même chose pour des répertoires entiers

git show SHA -- dir1 dir2 | git apply -
Michael Anderson
la source
2
Méthode intéressante, merci. Mais ne fait-il pas show SHA -- file | applyfondamentalement la même chose checkout SHA -- fileque dans la réponse de Mark Longair ?
Tobias Kienzler
4
Non, checkout SHA -- filevérifiera exactement la version sur SHA, tandis que show SHA -- file | applyn'appliquera que les changements dans SHA (tout comme le fait le choix de la cerise). Cela importe si (a) il y a plus d'un commit qui change le fichier donné dans la branche source, ou (b) il y a un commit qui change le fichier dans votre branche cible actuelle.
Michael Anderson
9
Je viens de trouver une autre grande utilité pour cela: la restauration sélective, lorsque vous ne souhaitez restaurer qu'un seul fichier (car git revertannule la validation entière). Dans ce cas, utilisez simplementgit show -R SHA -- file1.txt file2.txt | git apply -
Michael Anderson
2
@RoeiBahumi qui a une signification assez différente. git diff SHA -- file1.txt file2.txt | git apply -signifie appliquer toutes les différences entre la version actuelle du fichier et la version de SHA à la version actuelle. En substance, c'est la même chose que git checkout SHA -- file1.txt file2.txt. Voir mon commentaire précédent pour savoir pourquoi cela est différent de ce que la git showversion.
Michael Anderson
5
Dans le cas où vous devez résoudre des conflits, utilisez git apply -3 -au lieu de simplement git apply -, puis si un conflit se produit, vous pouvez utiliser votre technique de résolution de conflit standard, y compris en utilisant git mergetool.
qwertzguy
87

J'utilise généralement le -pdrapeau avec une vérification git de l'autre branche que je trouve plus facile et plus granulaire que la plupart des autres méthodes que j'ai rencontrées.

En principe:

git checkout <other_branch_name> <files/to/grab in/list/separated/by/spaces> -p

exemple:

git checkout mybranch config/important.yml app/models/important.rb -p

Vous obtenez alors une boîte de dialogue vous demandant quelles modifications vous voulez dans les "blobs", cela fonctionne à peu près à chaque bloc de changement de code continu que vous pouvez ensuite signaler y(Oui) n(Non), etc. pour chaque bloc de code.

L' option -pou patchfonctionne pour une variété de commandes dans git notamment git stash save -pqui vous permet de choisir ce que vous voulez cacher de votre travail actuel

J'utilise parfois cette technique lorsque j'ai fait beaucoup de travail et que je voudrais le séparer et effectuer des validations basées sur des sujets en utilisant git add -pet en choisissant ce que je veux pour chaque validation :)

Tyrone Wilson
la source
3
J'utilise régulièrement git-add -p, mais je ne savais pas qu'il git-checkouty avait également un -pindicateur - cela résout-il les problèmes de fusion de la non- -préponse ?
Tobias Kienzler
1
au moins -ppermettrait une modification manuelle pour une section aussi conflictuelle, qui cherry-pickdonnerait probablement aussi de toute façon. Je testerai cela la prochaine fois que j'en aurai besoin, certainement une approche intéressante
Tobias Kienzler
2
L'une des deux meilleures réponses qui ne tuent pas les modifications simultanées des branches.
akostadinov
1
Voir cette réponse pour savoir comment sélectionner les mecs à appliquer: stackoverflow.com/a/10605465/4816250 L'option 's' en particulier a été très utile.
jvd10
1
git reset -p HEADpermet également de -pquitter ce qui est pratique lorsque vous souhaitez uniquement supprimer certains correctifs de l'index.
Patrick Schlüter
42

L'avantage de cette méthode par rapport à la réponse de Jefromi est peut-être que vous n'avez pas à vous rappeler quel comportement de git reset est le bon :)

 # Create a branch to throw away, on which we'll do the cherry-pick:
 git checkout -b to-discard

 # Do the cherry-pick:
 git cherry-pick stuff

 # Switch back to the branch you were previously on:
 git checkout -

 # Update the working tree and the index with the versions of A and B
 # from the to-discard branch:
 git checkout to-discard -- A B

 # Commit those changes:
 git commit -m "Cherry-picked changes to A and B from [stuff]"

 # Delete the temporary branch:
 git branch -D to-discard
Mark Longair
la source
2
Merci pour votre réponse. Maintenant que cela m'a inspiré à penser, pourquoi ne pas sauter le cherry-picket utiliser directement git checkout stuff -- A B? Et avec git commit -C stuffle message de validation resterait le même aussi
Tobias Kienzler
8
@Tobias: Cela ne fonctionnerait que si les fichiers modifiés sur stuffn'ont pas été modifiés sur votre branche actuelle ou n'importe où entre l'ancêtre commun de HEADet stuffet la pointe de stuff. Si c'est le cas, cherry-pickcrée le résultat correct (essentiellement le résultat d'une fusion), tandis que votre méthode supprimerait les modifications dans la branche actuelle et conserverait toutes les modifications de l'ancêtre commun jusqu'à stuff- pas seulement celles de ce validation unique.
Cascabel
2
@Tobias Kienzler: Je supposais que votre point de départ était suffisamment différent du parent de stuffcelui que le résultat de la sélection de cerises laisserait Aet Bavec un contenu différent de leur contenu dans le commit stuff. Cependant, si ce devait être la même chose, vous avez raison - vous pouvez simplement faire ce que vous dites.
Mark Longair
@Jeromi, @Mark: merci pour vos commentaires, dans mon cas, je traite les branches avec des fichiers entièrement disjoints, ce qui m'a conduit à ma suggestion. Mais en effet, j'avais eu des problèmes tôt ou tard, alors merci d'avoir soulevé cette question
Tobias Kienzler
Je pense que ma réponse dans cet autre fil peut être ce que vous recherchez.
Ian
30

Cherry pick consiste à sélectionner les modifications à partir d'un "commit" spécifique. La solution la plus simple consiste à sélectionner toutes les modifications de certains fichiers est d'utiliser

 git checkout source_branch <paths>...

Par exemple:

$ git branch
* master
  twitter_integration
$ git checkout twitter_integration app/models/avatar.rb db/migrate/20090223104419_create_avatars.rb test/unit/models/avatar_test.rb test/functional/models/avatar_test.rb
$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   new file:   app/models/avatar.rb
#   new file:   db/migrate/20090223104419_create_avatars.rb
#   new file:   test/functional/models/avatar_test.rb
#   new file:   test/unit/models/avatar_test.rb
#
$ git commit -m "'Merge' avatar code from 'twitter_integration' branch"
[master]: created 4d3e37b: "'Merge' avatar code from 'twitter_integration' branch"
4 files changed, 72 insertions(+), 0 deletions(-)
create mode 100644 app/models/avatar.rb
create mode 100644 db/migrate/20090223104419_create_avatars.rb
create mode 100644 test/functional/models/avatar_test.rb
create mode 100644 test/unit/models/avatar_test.rb

Sources et explication complète http://jasonrudolph.com/blog/2009/02/25/git-tip-how-to-merge-specific-files-from-another-branch/

MISE À JOUR:

Avec cette méthode, git ne fusionnera pas le fichier, il remplacera simplement toute autre modification effectuée sur la branche de destination. Vous devrez fusionner les modifications manuellement:

$ git diff HEAD nom de fichier

cminatti
la source
5
Je le pensais aussi , mais cela échoue horriblement si les fichiers ont changé sur les deux branches car il annule les changements de votre branche actuelle
Tobias Kienzler
Vous avez raison, il est indispensable de préciser que de cette façon, git ne fusionne pas, il remplace simplement. Vous pouvez ensuite faire "git diff HEAD filename" pour voir ce qui a changé et faire la fusion manuellement.
cminatti
18

La situation:

Vous êtes sur votre branche, disons masteret vous avez votre engagement sur une autre branche. Vous devez choisir un seul fichier de ce commit particulier.

L'approche:

Étape 1: Caisse sur la branche requise.

git checkout master

Étape 2: assurez-vous d'avoir copié le hachage de validation requis.

git checkout commit_hash path\to\file

Étape 3: Vous avez maintenant les modifications du fichier requis sur la branche souhaitée. Il vous suffit de les ajouter et de les valider.

git add path\to\file
git commit -m "Your commit message"
techdreams
la source
1
Impressionnant! A également fonctionné pour toutes les modifications dans un répertoire avec \ path \ to \ directory \ for me
zaggi
13

Je choisirais simplement tout, puis je ferais ceci:

git reset --soft HEAD^

Ensuite, je reviendrais sur les modifications que je ne veux pas, puis je ferais un nouveau commit.

funroll
la source
11

Utilisez git merge --squash branch_namececi pour obtenir toutes les modifications de l'autre branche et préparer un commit pour vous. Maintenant, supprimez toutes les modifications inutiles et laissez celle que vous souhaitez. Et git ne saura pas qu'il y a eu une fusion.

Indomptable
la source
Merci, je ne connaissais pas cette option de fusion. C'est une alternative viable si vous voulez choisir la plupart d'une branche entière (mais contrairement à la sélection, cela ne fonctionnera pas s'il n'y a pas d'ancêtre commun)
Tobias Kienzler
4

J'ai trouvé un autre moyen qui empêche toute fusion conflictuelle sur la cueillette des cerises dont l'OMI est assez facile à retenir et à comprendre. Étant donné que vous n'êtes pas en train de choisir un commit, mais une partie de celui-ci, vous devez d'abord le diviser, puis créer un commit qui conviendra à vos besoins et le choisir.

Créez d'abord une branche à partir du commit que vous souhaitez fractionner et extrayez-la:

$ git checkout COMMIT-TO-SPLIT-SHA -b temp

Revenez ensuite au commit précédent:

$ git reset HEAD~1

Ajoutez ensuite les fichiers / modifications que vous souhaitez sélectionner:

$ git add FILE

et engagez-le:

$ git commit -m "pick me"

notez le hachage de validation, appelons-le PICK-SHA et revenons à votre branche principale, master forçant par exemple le checkout:

$ git checkout -f master

et choisissez le commit:

$ git cherry-pick PICK-SHA

vous pouvez maintenant supprimer la branche temporaire:

$ git branch -d temp -f
Alexey
la source
2

Fusionnez une branche en une nouvelle (squash) et supprimez les fichiers inutiles:

git checkout master
git checkout -b <branch>
git merge --squash <source-branch-with-many-commits>
git reset HEAD <not-needed-file-1>
git checkout -- <not-needed-file-1>
git reset HEAD <not-needed-file-2>
git checkout -- <not-needed-file-2>
git commit
nvd
la source
2

Par souci d'exhaustivité, ce qui fonctionne le mieux pour moi est:

git show YOURHASH --no-color -- file1.txt file2.txt dir3 dir4 | git apply -3 --index -

git status

Il fait exactement ce que souhaite OP. Il résout les conflits en cas de besoin, de la même manière merge. Ce n'est addpas commitle cas mais pas vos nouveaux changements.

kubanczyk
la source
1

Vous pouvez utiliser:

git diff <commit>^ <commit> -- <path> | git apply

La notation <commit>^spécifie le (premier) parent de <commit>. Par conséquent, cette commande diff sélectionne les modifications apportées au <path>commit <commit>.

Notez que cela git cherry-pickn'engagera rien encore (comme le fait). Donc, si vous le souhaitez, vous devrez faire:

git add <path>
git commit
Garogolun
la source