Bagaimana cara git-cherry-pick hanya perubahan pada file tertentu?

587

Jika saya ingin bergabung menjadi cabang Git, perubahan hanya dilakukan pada beberapa file yang diubah dalam komit tertentu yang mencakup perubahan pada banyak file, bagaimana hal ini dapat dicapai?

Misalkan Git komit disebut stuffmemiliki perubahan ke file A, B, C, dan Dtapi saya ingin menggabungkan hanya stuff's perubahan ke file Adan B. Kedengarannya seperti pekerjaan git cherry-picktetapi cherry-pickhanya tahu cara menggabungkan seluruh komit, bukan bagian dari file.

Tobias Kienzler
sumber

Jawaban:

689

Saya akan melakukannya dengan cherry-pick -n( --no-commit) yang memungkinkan Anda memeriksa (dan memodifikasi) hasilnya sebelum melakukan:

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

Jika sebagian besar modifikasi adalah hal-hal yang tidak Anda inginkan, alih-alih memeriksa jalur individual (langkah tengah), Anda dapat mengatur ulang semuanya kembali, lalu tambahkan apa yang Anda inginkan:

# 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
sumber
10
Selain itu git checkout .saya akan merekomendasikan juga git clean -funtuk menghapus file baru tapi tidak diinginkan yang diperkenalkan oleh komit pilihan ceri.
rlat
4
Catatan tambahan untuk metode yang terakhir: Saya menggunakan git add -pyang memungkinkan Anda memutuskan secara interaktif perubahan yang ingin Anda tambahkan ke indeks per file
matthaeus
6
Ini tidak terlalu bagus jika komit yang dipilih tidak berlaku untuk copy pekerjaan saat ini karena sangat berbeda, tetapi satu file akan berlaku bersih.
Penebusan Terbatas
3
Anda juga dapat menghapus tahapan dengan git reset -p HEAD. Ini sama dengan add -ptetapi sangat sedikit yang tahu bahwa itu ada.
Patrick Schlüter
1
Trik yang sangat berguna. Saya telah meletakkannya di intisari jika seseorang membutuhkannya sebagai skrip cepat gist.github.com/PiDayDev/68c39b305ab9d61ed8bb2a1195ee1afc
Damiano
146

Metode lain tidak berfungsi untuk saya karena komit memiliki banyak perubahan dan konflik ke banyak file lainnya. Apa yang saya pikirkan adalah sederhana

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

Sebenarnya bukan addfile atau melakukan komit untuk Anda sehingga Anda mungkin perlu menindaklanjutinya

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

Atau jika Anda ingin melewatkan add Anda dapat menggunakan --cachedargumen untukgit apply

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

Anda juga dapat melakukan hal yang sama untuk seluruh direktori

git show SHA -- dir1 dir2 | git apply -
Michael Anderson
sumber
2
Metode yang menarik, terima kasih. Tetapi show SHA -- file | applypada dasarnya tidakkah melakukan hal yang sama checkout SHA -- fileseperti dalam jawaban Mark Longair ?
Tobias Kienzler
4
Tidak, checkout SHA -- fileakan checkout persis versi di SHA, sementara show SHA -- file | applyhanya akan menerapkan perubahan di SHA (seperti halnya cherry-pick). Itu penting jika (a) ada lebih dari satu komit yang mengubah file yang diberikan di cabang sumber, atau (b) ada komit yang mengubah file di cabang target Anda saat ini.
Michael Anderson
9
Baru saja menemukan kegunaan lain untuk ini: kembalikan selektif, karena ketika Anda hanya ingin mengembalikan satu file (karena git revertmembatalkan seluruh komit). Dalam hal ini gunakan sajagit show -R SHA -- file1.txt file2.txt | git apply -
Michael Anderson
2
@RoeiBahumi yang memiliki arti yang sangat berbeda. git diff SHA -- file1.txt file2.txt | git apply -berarti menerapkan semua perbedaan antara versi file saat ini dan versi di SHA ke versi saat ini. Pada dasarnya itu sama dengan git checkout SHA -- file1.txt file2.txt. Lihat komentar saya sebelumnya untuk mengapa itu berbeda dengan git showversi apa .
Michael Anderson
5
Jika Anda harus menyelesaikan konflik, gunakan git apply -3 -bukan hanya git apply -, maka jika konflik terjadi, Anda dapat menggunakan teknik resolusi konflik standar Anda, termasuk menggunakan git mergetool.
qwertzguy
87

Saya biasanya menggunakan -pbendera dengan checkout git dari cabang lain yang saya temukan lebih mudah dan lebih rinci daripada kebanyakan metode lain yang saya temui.

Pada prinsipnya:

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

contoh:

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

Anda kemudian mendapatkan dialog yang menanyakan perubahan yang Anda inginkan di "gumpalan" ini cukup berhasil untuk setiap potongan perubahan kode kontinu yang kemudian Anda dapat memberi sinyal y(Ya) n(Tidak) dll untuk setiap potongan kode.

The -patau patchpilihan bekerja untuk berbagai perintah dalam git termasuk git stash save -pyang memungkinkan Anda untuk memilih apa yang ingin Anda menyimpan dari pekerjaan Anda saat ini

Saya kadang-kadang menggunakan teknik ini ketika saya telah melakukan banyak pekerjaan dan ingin memisahkannya dan melakukan lebih banyak komit berdasarkan topik menggunakan git add -pdan memilih apa yang saya inginkan untuk setiap komit :)

Tyrone Wilson
sumber
3
Saya secara teratur menggunakan git-add -p, tetapi saya tidak tahu git-checkoutjuga memiliki -pbendera - apakah itu memperbaiki masalah penggabungan yang dimiliki non- -pjawaban ?
Tobias Kienzler
1
setidaknya -pakan memungkinkan untuk mengedit manual untuk bagian yang saling bertentangan, yang cherry-pickmungkin juga akan menghasilkan pula. Saya akan menguji ini kali berikutnya saya membutuhkannya, pasti pendekatan yang menarik
Tobias Kienzler
2
Salah satu dari dua jawaban terbaik yang tidak membunuh perubahan bersamaan ke cabang.
akostadinov
1
Lihat jawaban ini untuk cara memilih bakhil yang akan diterapkan: stackoverflow.com/a/10605465/4816250 Opsi 's' khususnya sangat membantu.
jvd10
1
git reset -p HEADjuga memungkinkan -pyang dapat berhenti berguna ketika Anda hanya ingin menghapus beberapa tambalan dari indeks.
Patrick Schlüter
42

Mungkin keuntungan dari metode ini daripada jawaban Jefromi adalah bahwa Anda tidak harus mengingat perilaku reset git mana yang benar :)

 # 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
sumber
2
Terima kasih atas jawaban anda. Sekarang itu menginspirasi saya untuk berpikir, mengapa tidak melewati cherry-pickdan langsung menggunakan git checkout stuff -- A B? Dan dengan git commit -C stuffpesan komit akan tetap sama juga
Tobias Kienzler
8
@Tobias: Itu hanya akan berfungsi jika file yang diubah stuffbelum dimodifikasi pada cabang Anda saat ini atau di mana saja di antara leluhur umum HEADdan stuffdan ujung stuff. Jika sudah, maka cherry-pickbuat hasil yang benar (pada dasarnya hasil penggabungan), sementara metode Anda akan membuang perubahan di cabang saat ini, dan menjaga semua perubahan dari leluhur bersama hingga stuff- tidak hanya yang di dalam komit tunggal.
Cascabel
2
@Tobias Kienzler: Saya berasumsi bahwa titik awal Anda cukup berbeda dari induk stuffsehingga hasil dari cherry pick akan pergi Adan Bdengan konten yang berbeda dari konten mereka di komit stuff. Namun, jika itu akan sama, Anda benar - Anda bisa melakukan apa yang Anda katakan.
Mark Longair
@Jeromi, @Mark: terima kasih atas tanggapan Anda, dalam kasus saya, saya memperlakukan cabang dengan file yang sepenuhnya terpisah yang mengarahkan saya ke saran saya. Tapi memang saya telah mengalami masalah dengan itu cepat atau lambat, jadi terima kasih telah membawa ini
Tobias Kienzler
Saya pikir jawaban saya di utas lain ini mungkin apa yang Anda cari.
Ian
30

Cherry pick adalah untuk memilih perubahan dari "komit" tertentu. Solusi paling sederhana adalah dengan memilih semua perubahan file tertentu untuk digunakan

 git checkout source_branch <paths>...

Sebagai contoh:

$ 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

Sumber dan penjelasan lengkap http://jasonrudolph.com/blog/2009/02/25/git-tip-how-to-merge-specific-files-from-another-branch/

MEMPERBARUI:

Dengan metode ini, git tidak akan MERGE file, itu hanya akan mengesampingkan perubahan lain yang dilakukan pada cabang tujuan. Anda harus menggabungkan perubahan secara manual:

$ git diff nama file HEAD

cminatti
sumber
5
Saya juga berpikir begitu , tetapi ini gagal total jika file-file telah berubah di kedua cabang karena membuang perubahan cabang Anda saat ini
Tobias Kienzler
Anda benar, itu adalah suatu keharusan untuk mengklarifikasi bahwa cara ini git tidak MERGE, itu hanya menimpa. Anda kemudian dapat melakukan "git diff HEAD filename" untuk melihat apa yang berubah dan melakukan penggabungan secara manual.
cminatti
18

Situasi:

Anda berada di cabang Anda, katakanlah masterdan Anda memiliki komitmen Anda pada cabang lain. Anda harus memilih hanya satu file dari komit tertentu.

Pendekatan:

Langkah 1: Checkout di cabang yang diperlukan.

git checkout master

Langkah 2: Pastikan Anda telah menyalin hash komit yang diperlukan.

git checkout commit_hash path\to\file

Langkah 3: Sekarang Anda memiliki perubahan file yang diperlukan pada cabang yang Anda inginkan. Anda hanya perlu menambahkan dan mengkomitnya.

git add path\to\file
git commit -m "Your commit message"
techdreams
sumber
1
Luar biasa! Juga berfungsi untuk semua perubahan dalam direktori dengan \ path \ to \ directory \ for me
zaggi
13

Saya hanya akan memilih semuanya, lalu melakukan ini:

git reset --soft HEAD^

Lalu saya akan mengembalikan perubahan yang tidak saya inginkan, lalu membuat komit baru.

funroll
sumber
11

Gunakan git merge --squash branch_nameini akan mendapatkan semua perubahan dari cabang lain dan akan menyiapkan komitmen untuk Anda. Sekarang hapus semua perubahan yang tidak dibutuhkan dan tinggalkan yang Anda inginkan. Dan git tidak akan tahu bahwa ada penggabungan.

Gigih
sumber
Terima kasih, saya tidak tahu tentang opsi penggabungan itu. Ini adalah alternatif yang baik jika Anda ingin memetik sebagian besar dari seluruh cabang (tetapi berbeda dengan memetik ceri, itu tidak akan berfungsi jika tidak ada leluhur yang sama)
Tobias Kienzler
4

Saya menemukan cara lain yang mencegah penggabungan yang saling bertentangan pada memetik ceri yang IMO agak mudah diingat dan dipahami. Karena Anda sebenarnya tidak memilih cherry pick, tetapi menjadi bagian darinya, Anda harus membaginya terlebih dahulu dan kemudian membuat commit yang sesuai dengan kebutuhan Anda dan memilih cherry-pick.

Pertama buat cabang dari komit yang ingin Anda bagi dan checkout:

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

Kemudian kembalikan komit sebelumnya:

$ git reset HEAD~1

Kemudian tambahkan file / perubahan yang ingin Anda pilih:

$ git add FILE

dan lakukan itu:

$ git commit -m "pick me"

perhatikan hash komit, sebut saja PICK-SHA dan kembali ke cabang utama Anda, master misalnya memaksa checkout:

$ git checkout -f master

dan ceri-pilih komit:

$ git cherry-pick PICK-SHA

sekarang Anda dapat menghapus cabang temp:

$ git branch -d temp -f
Alexey
sumber
2

Gabungkan cabang menjadi yang baru (squash) dan hapus file yang tidak diperlukan:

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
sumber
2

Demi kelengkapan, yang terbaik bagi saya adalah:

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

git status

Ia melakukan persis seperti yang diinginkan OP. Itu resolusi konflik ketika diperlukan, sama bagaimana mergemelakukannya. Itu addtetapi tidak commitperubahan baru Anda.

kubanczyk
sumber
1

Kamu bisa menggunakan:

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

Notasi <commit>^menentukan induk (pertama) dari <commit>. Oleh karena itu, perintah diff ini mengambil perubahan yang dilakukan <path>dalam komit<commit> .

Perhatikan bahwa ini belum akan melakukan apa pun (seperti git cherry-pickhalnya). Jadi jika Anda menginginkannya, Anda harus melakukan:

git add <path>
git commit
Garogolun
sumber