Come eseguire git-cherry-pick solo su alcuni file?


587

Se voglio unire in un ramo Git le modifiche apportate solo ad alcuni file sono cambiate in un particolare commit che include modifiche a più file, come può essere ottenuto?

Supponiamo che il git commit chiamata stuffha le modifiche ai file A, B, C, e Dma voglio fondere solo stuffcambiamenti 's ai file Ae B. Sembra un lavoro per git cherry-pickma cherry-picksa solo come unire interi commit, non un sottoinsieme dei file.

Risposte:


689

Lo farei con cherry-pick -n( --no-commit) che ti consente di ispezionare (e modificare) il risultato prima di eseguire il commit:

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

Se la stragrande maggioranza delle modifiche sono cose che non vuoi, invece di controllare i singoli percorsi (il passaggio intermedio), puoi ripristinare tutto, quindi aggiungere ciò che desideri:

# 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 .

10
Oltre a git checkout .raccomandare anche git clean -fdi rimuovere qualsiasi file nuovo ma indesiderato introdotto dal commit cherry-pick.
dal

4
Nota aggiuntiva per quest'ultimo metodo: utilizzo ciò git add -pche consente di decidere in modo interattivo quali modifiche si desidera aggiungere all'indice per file
matthaeus

6
Questo non è così eccezionale nel caso in cui il commit selezionato dalla ciliegia non si applichi alla copia di lavoro corrente perché è così diverso, ma che un file si applicherebbe in modo pulito.
Espiazione limitata il

3
Puoi anche rimuovere la scena in modo selettivo con git reset -p HEAD. È l'equivalente di add -pma pochissimi sanno che esiste.
Patrick Schlüter,

1
Trucco molto utile. L'ho messo in pratica nel caso in cui qualcuno ne abbia bisogno come script rapido gist.github.com/PiDayDev/68c39b305ab9d61ed8bb2a1195ee1afc
Damiano,

146

Gli altri metodi non hanno funzionato per me poiché il commit ha avuto molte modifiche e conflitti con molti altri file. Quello che mi è venuto in mente è stato semplicemente

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

In realtà non contiene addi file né esegue un commit, quindi potrebbe essere necessario seguirlo

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

Oppure, se vuoi saltare l'aggiunta, puoi usare l' --cachedargomento pergit apply

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

Puoi anche fare la stessa cosa per intere directory

git show SHA -- dir1 dir2 | git apply -

2
Metodo interessante, grazie. Ma non show SHA -- file | applyfondamentalmente fare lo stesso checkout SHA -- filecome nella risposta di Marco Longair ?
Tobias Kienzler,

4
No, checkout SHA -- fileverificherà esattamente la versione in SHA, mentre show SHA -- file | applyapplicheranno solo le modifiche in SHA (proprio come Cherry-Pick). È importante se (a) è presente più di un commit che modifica il file specificato nel ramo di origine, oppure (b) c'è un commit che modifica il file nel ramo di destinazione corrente.
Michael Anderson,

9
Ho appena trovato un altro grande uso per questo: ripristino selettivo, per quando si desidera ripristinare solo un file (poiché git revertannulla l'intero commit). In tal caso basta usaregit show -R SHA -- file1.txt file2.txt | git apply -
Michael Anderson il

2
@RoeiBahumi che ha un significato abbastanza diverso. git diff SHA -- file1.txt file2.txt | git apply -significa applicare tutte le differenze tra la versione corrente del file e la versione di SHA alla versione corrente. In sostanza è lo stesso di git checkout SHA -- file1.txt file2.txt. Vedi il mio commento precedente per sapere perché è diverso da quello della git showversione.
Michael Anderson,

5
Nel caso in cui sia necessario risolvere i conflitti, utilizzare git apply -3 -invece di solo git apply -, quindi se si verifica un conflitto, è possibile utilizzare la tecnica standard di risoluzione dei conflitti, incluso l'utilizzo git mergetool.
qwertzguy,

87

Di solito uso la -pbandiera con un checkout git dall'altro ramo che trovo più facile e più granulare della maggior parte degli altri metodi che ho incontrato.

In linea di principio:

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

esempio:

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

Viene quindi visualizzata una finestra di dialogo che chiede quali modifiche si desidera in "BLOB" questo funziona praticamente per ogni blocco di modifica continua del codice che è quindi possibile segnalare y(Sì) n(No) ecc. Per ogni blocco di codice.

L' opzione -po patchfunziona con una varietà di comandi in git, incluso quello git stash save -pche ti permette di scegliere ciò che vuoi nascondere dal tuo lavoro attuale

A volte uso questa tecnica quando ho fatto molto lavoro e vorrei separarla e impegnarmi in più commit basati sull'argomento usando git add -pe scegliendo ciò che voglio per ogni commit :)


3
Uso regolarmente git-add -p, ma non sapevo che git-checkoutabbia anche un -pflag - questo risolve i problemi di fusione che la non -prisposta ha?
Tobias Kienzler,

1
almeno -pconsentirebbe una modifica manuale per una sezione così contrastante, che cherry-pickprobabilmente produrrebbe comunque. Lo proverò la prossima volta che ne avrò bisogno, sicuramente un approccio interessante
Tobias Kienzler

2
Una delle due migliori risposte che non uccidono le modifiche simultanee ai rami.
Akostadinov,

1
Vedi questa risposta per come selezionare quali hunk applicare: stackoverflow.com/a/10605465/4816250 L'opzione 's' in particolare è stata molto utile.
jvd10,

1
git reset -p HEADconsente inoltre di -puscire a portata di mano quando si desidera rimuovere solo alcune patch dall'indice.
Patrick Schlüter,

42

Forse il vantaggio di questo metodo rispetto alla risposta di Jefromi è che non devi ricordare quale comportamento di git reset è quello giusto :)

 # 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

2
grazie per la tua risposta. Ora che mi ha ispirato a pensare, perché non saltare cherry-picke usare direttamente git checkout stuff -- A B? E con git commit -C stuffil messaggio di commit rimarrebbe lo stesso
Tobias Kienzler

8
@Tobias: che avrebbe funzionato solo se i file modificati sul stuffnon sono stati modificati sul ramo corrente o in qualsiasi punto tra l'antenato comune HEADed stuffe la punta della stuff. In tal caso, cherry-pickcrea il risultato corretto (essenzialmente il risultato di un'unione), mentre il metodo eliminerebbe le modifiche nel ramo corrente e manterrebbe tutte le modifiche dall'antenato comune fino a stuff- non solo quelle in quella commit singolo.
Cascabel,

2
@Tobias Kienzler: supponevo che il tuo punto di partenza fosse sufficientemente diverso dal genitore stuffche il risultato della scelta della ciliegia sarebbe partito Ae Bcon contenuti diversi dal loro contenuto nel commit stuff. Tuttavia, se fosse lo stesso, hai ragione: potresti fare solo quello che dici.
Mark Longair,

@Jeromi, @Mark: grazie per il tuo feedback, nel mio caso sto trattando i rami con file completamente disgiunti che mi hanno portato al mio suggerimento. Ma in effetti prima o poi mi ero trovato nei guai, quindi grazie per averlo sollevato
Tobias Kienzler,

Penso che la mia risposta in questo altro thread potrebbe essere ciò che stai cercando.
Ian,

30

Cherry pick è quello di scegliere le modifiche da un "commit" specifico. La soluzione più semplice è scegliere tutte le modifiche di determinati file da utilizzare

 git checkout source_branch <paths>...

Per esempio:

$ 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

Fonti e spiegazione completa http://jasonrudolph.com/blog/2009/02/25/git-tip-how-to-merge-specific-files-from-another-branch/

AGGIORNARE:

Con questo metodo, git non MERGE il file, sostituirà semplicemente qualsiasi altra modifica apportata al ramo di destinazione. Dovrai unire le modifiche manualmente:

$ git diff Nome file HEAD


5
Lo pensavo anch'io , ma questo fallisce orribilmente se i file sono cambiati su entrambi i rami poiché scarta i cambiamenti del tuo ramo attuale
Tobias Kienzler

Hai ragione, è d'obbligo chiarire che in questo modo git non MERGE, ma semplicemente ignora. Puoi quindi fare "git diff HEAD nomefile" per vedere cosa è cambiato e fare l'unione manualmente.
cminatti,

18

La situazione:

Sei nel tuo ramo, diciamo mastere hai il tuo impegno su qualsiasi altro ramo. Devi scegliere solo un file da quel particolare commit.

L'approccio:

Passaggio 1: verifica sul ramo richiesto.

git checkout master

Passaggio 2: assicurarsi di aver copiato l'hash di commit richiesto.

git checkout commit_hash path\to\file

Passaggio 3: ora hai le modifiche del file richiesto sul ramo desiderato. Hai solo bisogno di aggiungere e impegnarli.

git add path\to\file
git commit -m "Your commit message"

1
Eccezionale! Ha funzionato anche per tutte le modifiche in una directory con \ path \ to \ directory \ per me
zaggi il

13

Vorrei solo scegliere tutto, quindi fare questo:

git reset --soft HEAD^

Quindi vorrei ripristinare le modifiche che non desidero, quindi effettuare un nuovo commit.


11

Utilizzare git merge --squash branch_namequesto otterrà tutte le modifiche dall'altro ramo e preparerà un commit per te. Ora rimuovi tutte le modifiche non necessarie e lascia quello che desideri. E Git non saprà che c'è stata una fusione.


Grazie, non sapevo di quell'opzione di unione. È un'alternativa praticabile se vuoi raccogliere la maggior parte di un intero ramo di ciliegie (ma a differenza di quella di ciliegie non funzionerà se non ci sono antenati comuni)
Tobias Kienzler,

4

Ho trovato un altro modo che impedisce qualsiasi fusione conflittuale sulla raccolta delle ciliegie che l'IMO è un po 'facile da ricordare e capire. Dal momento che in realtà non stai selezionando un commit, ma parte di esso, devi prima dividerlo e quindi creare un commit che soddisfi le tue esigenze e sceglierlo.

Per prima cosa crea un ramo dal commit che vuoi dividere e controlla:

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

Quindi ripristinare il commit precedente:

$ git reset HEAD~1

Quindi aggiungere i file / le modifiche che si desidera selezionare:

$ git add FILE

e impegnalo:

$ git commit -m "pick me"

nota l'hash di commit, chiamiamolo PICK-SHA e torniamo al tuo ramo principale, master per esempio forzando il checkout:

$ git checkout -f master

e scegli il commit:

$ git cherry-pick PICK-SHA

ora puoi eliminare il ramo temporaneo:

$ git branch -d temp -f

2

Unisci un ramo in uno nuovo (squash) e rimuovi i file non necessari:

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

2

Per completezza, ciò che funziona meglio per me è:

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

git status

Fa esattamente quello che vuole OP. Fa la risoluzione dei conflitti quando necessario, allo stesso modo come lo mergefa. Lo fa addma non le committue nuove modifiche.


1

Puoi usare:

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

La notazione <commit>^specifica il (primo) genitore di <commit>. Pertanto, questo comando diff seleziona le modifiche apportate al <path>commit <commit>.

Nota che questo non impegnerà ancora nulla (come git cherry-pickfa). Quindi, se lo desideri, dovrai fare:

git add <path>
git commit
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.