วิธี git-cherry-pick เปลี่ยนแปลงเฉพาะไฟล์บางไฟล์เท่านั้น?


586

ถ้าฉันต้องการรวมเข้ากับสาขา Git การเปลี่ยนแปลงที่เกิดขึ้นกับไฟล์บางไฟล์ที่ถูกเปลี่ยนแปลงในคอมมิทเฉพาะที่รวมการเปลี่ยนแปลงหลายไฟล์สิ่งนี้จะเกิดขึ้นได้อย่างไร?

สมมติว่าคอมมิทใช้เรียกว่าstuffมีการเปลี่ยนแปลงไฟล์A, BและC, Dแต่ฉันต้องการรวมstuffการเปลี่ยนแปลงเฉพาะไฟล์AและB. ดูเหมือนว่าเป็นงานgit cherry-pickแต่cherry-pickรู้เพียงวิธีการรวมคอมมิททั้งหมดไม่ใช่ส่วนย่อยของไฟล์

คำตอบ:


688

ฉันต้องการใช้กับcherry-pick -n( --no-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

หากการดัดแปลงส่วนใหญ่เป็นสิ่งที่คุณไม่ต้องการแทนที่จะตรวจสอบแต่ละเส้นทาง (ขั้นตอนกลาง) คุณสามารถรีเซ็ตทุกอย่างกลับมาแล้วเพิ่มสิ่งที่คุณต้องการ:

# 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
นอกจากนี้git checkout .ฉันจะแนะนำgit clean -fให้ลบไฟล์ใหม่ แต่ไฟล์ที่ไม่พึงประสงค์ที่แนะนำโดยการกระทำที่เลือกโดยเชอร์รี่
rlat

4
หมายเหตุเพิ่มเติมสำหรับเมธอดหลัง: ฉันใช้git add -pซึ่งให้คุณตัดสินใจแบบโต้ตอบซึ่งการเปลี่ยนแปลงที่คุณต้องการเพิ่มในดัชนีต่อไฟล์
matthaeus

6
สิ่งนี้ไม่ดีนักในกรณีที่การมอบหมายที่เชอร์รี่เลือกใช้ไม่ได้กับสำเนาการทำงานปัจจุบันเพราะมันแตกต่างกันมาก แต่ไฟล์หนึ่งไฟล์จะใช้อย่างหมดจด
การชดเชย จำกัด

3
คุณยังสามารถ unstage git reset -p HEADการคัดเลือกด้วย มันเทียบเท่าadd -pแต่น้อยคนนักที่จะรู้ว่ามันมีอยู่จริง
Patrick Schlüter

1
เคล็ดลับที่มีประโยชน์มาก ฉันใส่มันลงในส่วนสำคัญในกรณีที่มีคนต้องการเป็นสคริปต์อย่างรวดเร็วgist.github.com/PiDayDev/68c39b305ab9d61ed8bb2a1195ee1afc
Damiano

146

วิธีอื่นไม่ได้ผลสำหรับฉันเนื่องจากการกระทำมีการเปลี่ยนแปลงและความขัดแย้งกับไฟล์อื่นจำนวนมาก สิ่งที่ฉันคิดไว้คือ

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

จริงๆแล้วมันไม่ได้addเป็นไฟล์หรือกระทำเพื่อคุณดังนั้นคุณอาจต้องติดตามมันด้วย

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

หรือถ้าคุณต้องการข้ามการเพิ่มคุณสามารถใช้--cachedอาร์กิวเมนต์เพื่อgit apply

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

คุณยังสามารถทำสิ่งเดียวกันสำหรับไดเรกทอรีทั้งหมด

git show SHA -- dir1 dir2 | git apply -

2
วิธีการที่น่าสนใจขอบคุณ แต่show SHA -- file | applyโดยทั่วไปแล้วจะไม่ทำแบบเดียวcheckout SHA -- fileกับในคำตอบของ Mark Longairใช่ไหม
โทเบียส Kienzler

4
ไม่checkout SHA -- fileจะชำระเงินให้ตรงกับรุ่นที่ SHA ในขณะที่show SHA -- file | applyจะใช้เฉพาะการเปลี่ยนแปลงใน SHA (เช่นเดียวกับการคัดเชอร์รี่) มันสำคัญหาก (ก) มีมากกว่าหนึ่งกระทำการเปลี่ยนไฟล์ที่กำหนดในสาขาที่มาหรือ (b) มีการกระทำที่เปลี่ยนแปลงไฟล์ในสาขาเป้าหมายปัจจุบันของคุณ
Michael Anderson

9
เพิ่งค้นพบการใช้งานที่ยอดเยี่ยมสำหรับสิ่งนี้: เปลี่ยนกลับเลือกเมื่อคุณต้องการย้อนกลับหนึ่งไฟล์เท่านั้น (เนื่องจากgit revertยกเลิกการกระทำทั้งหมด) ในกรณีนี้ให้ใช้git show -R SHA -- file1.txt file2.txt | git apply -
Michael Anderson

2
@RoeiBahumi ที่มีความหมายแตกต่างกันมาก git diff SHA -- file1.txt file2.txt | git apply -หมายถึงใช้ความแตกต่างทั้งหมดระหว่างเวอร์ชันปัจจุบันของไฟล์และเวอร์ชันที่ SHA กับเวอร์ชันปัจจุบัน git checkout SHA -- file1.txt file2.txtในสาระสำคัญมันเป็นเช่นเดียวกับ ดูความคิดเห็นก่อนหน้าของฉันสำหรับสาเหตุที่แตกต่างกับgit showรุ่น
Michael Anderson

5
ในกรณีที่คุณมีความขัดแย้งการแก้ไขให้ใช้git apply -3 -แทนเพียงแล้วถ้าความขัดแย้งที่เกิดขึ้นคุณสามารถใช้เทคนิคการแก้ไขความขัดแย้งมาตรฐานของคุณรวมถึงการใช้git apply - git mergetool
qwertzguy

87

ฉันมักจะใช้การ-pตั้งค่าสถานะกับ git ชำระเงินจากสาขาอื่นซึ่งฉันค้นหาง่ายและละเอียดกว่าวิธีอื่น ๆ ส่วนใหญ่ที่ฉันได้เจอ

ในหลักการ:

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

ตัวอย่าง:

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

จากนั้นคุณจะได้รับกล่องโต้ตอบถามคุณว่าการเปลี่ยนแปลงที่คุณต้องการใน "blobs" นี้ทำงานได้ดีกับการเปลี่ยนแปลงรหัสอย่างต่อเนื่องทุกครั้งซึ่งคุณสามารถส่งสัญญาณy(ใช่) n(ไม่) ฯลฯ สำหรับรหัสแต่ละอัน

-pหรือpatchตัวเลือกที่ทำงานเพื่อความหลากหลายของคำสั่งในการคอมไพล์รวมทั้งgit stash save -pช่วยให้คุณสามารถเลือกสิ่งที่คุณต้องการที่จะเก็บสะสมจากการทำงานในปัจจุบันของคุณ

บางครั้งฉันใช้เทคนิคนี้เมื่อฉันทำงานหนักมากและต้องการแยกออกจากกันและกระทำในหัวข้อเพิ่มเติมตามข้อผูกพันในการใช้git add -pและเลือกสิ่งที่ฉันต้องการสำหรับแต่ละข้อตกลง :)


3
ฉันใช้เป็นประจำgit-add -pแต่ไม่ทราบว่าgit-checkoutมีการ-pตั้งค่าสถานะ - นั่นจะแก้ไขปัญหาการรวมที่ไม่มี-pคำตอบได้หรือไม่
Tobias Kienzler

1
อย่างน้อย-pจะอนุญาตให้มีการแก้ไขด้วยตนเองสำหรับส่วนที่ขัดแย้งกันซึ่งcherry-pickอาจจะให้ผลเช่นกัน ฉันจะทดสอบในครั้งต่อไปที่ฉันต้องการมันเป็นแนวทางที่น่าสนใจอย่างแน่นอน
Tobias Kienzler

2
หนึ่งในสองคำตอบที่ดีที่สุดที่ไม่ฆ่าการเปลี่ยนแปลงที่เกิดขึ้นพร้อมกันในสาขา
akostadinov

1
ดูคำตอบนี้สำหรับวิธีเลือก hunks ที่จะใช้: stackoverflow.com/a/10605465/4816250 ตัวเลือก 's' โดยเฉพาะอย่างยิ่งมีประโยชน์มาก
jvd10

1
git reset -p HEADยังช่วยให้สิ่ง-pที่สามารถเลิกสะดวกเมื่อคุณต้องการลบแพทช์บางส่วนออกจากดัชนี
Patrick Schlüter

42

บางทีข้อดีของวิธีนี้เหนือคำตอบของ Jefromi ก็คือคุณไม่ต้องจำไว้ว่าพฤติกรรมการรีเซ็ต git แบบใดที่เหมาะสม :)

 # 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
ขอบคุณสำหรับคำตอบ. ตอนนี้เป็นแรงบันดาลใจให้ฉันคิดว่าทำไมไม่ข้ามcherry-pickและใช้โดยตรงgit checkout stuff -- A B? และด้วยgit commit -C stuffข้อความยืนยันจะยังคงเหมือนเดิม
Tobias Kienzler

8
@Tobias: นั่นจะทำงานเฉพาะในกรณีที่ไฟล์ที่แก้ไขล่าสุดเมื่อstuffไม่ได้รับการแก้ไขในสาขาปัจจุบันของคุณหรือที่ใดก็ได้ระหว่างบรรพบุรุษร่วมกันของHEADและและปลายของstuff stuffหากพวกเขามีแล้วcherry-pickสร้างผลลัพธ์ที่ถูกต้อง (เป็นผลมาจากการผสาน) ในขณะที่วิธีการของคุณจะทิ้งการเปลี่ยนแปลงในสาขาปัจจุบันและเก็บการเปลี่ยนแปลงทั้งหมดจากบรรพบุรุษร่วมกันstuff- ไม่ใช่แค่ในนั้น กระทำเดียว
Cascabel

2
@Tobias KIENZLER: ฉันถูกสมมติว่าจุดเริ่มต้นของคุณพอแตกต่างจากแม่ของstuffว่าผลจากการเลือกเชอร์รี่จะออกAและมีเนื้อหาที่แตกต่างจากเนื้อหาของพวกเขาในการกระทำB stuffอย่างไรก็ตามถ้ามันจะเหมือนกันคุณก็พูดถูก - คุณสามารถทำตามที่คุณพูดได้
Mark Longair

@ Jeromi, @Mark: ขอบคุณสำหรับคำติชมของคุณในกรณีของฉันฉันกำลังรักษากิ่งไม้ด้วยไฟล์ที่แยกทั้งหมดซึ่งทำให้ฉันมีคำแนะนำของฉัน แต่แน่นอนว่าฉันต้องเจอปัญหากับมันไม่ช้าก็เร็วขอบคุณมากที่นำสิ่งนี้ขึ้นมา
Tobias Kienzler

ฉันคิดว่าคำตอบของฉันในหัวข้ออื่นนี้อาจเป็นสิ่งที่คุณหลังจาก
เอียน

30

Cherry pick คือการเลือกการเปลี่ยนแปลงจาก "การกระทำ" ที่เฉพาะเจาะจง ทางออกที่ง่ายที่สุดคือเลือกการเปลี่ยนแปลงทั้งหมดของไฟล์ที่จะใช้

 git checkout source_branch <paths>...

ในตัวอย่าง:

$ 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

แหล่งที่มาและคำอธิบายแบบเต็มhttp://jasonrudolph.com/blog/2009/02/25/git-tip-how-to-merge-specific-files-from-another-branch/

UPDATE:

ด้วยวิธีนี้ git จะไม่รวมไฟล์ แต่จะแทนที่การเปลี่ยนแปลงอื่น ๆ ที่ทำในสาขาปลายทาง คุณจะต้องรวมการเปลี่ยนแปลงด้วยตนเอง:

$ git diff ชื่อไฟล์ HEAD


5
ฉันคิดอย่างนั้นเช่นกันแต่มันก็ล้มเหลวอย่างน่ากลัวถ้าไฟล์มีการเปลี่ยนแปลงทั้งสองสาขาเพราะมันทิ้งการเปลี่ยนแปลงของสาขาปัจจุบันของคุณ
Tobias Kienzler

คุณถูกต้องมันเป็นสิ่งที่ต้องชี้แจงว่าวิธีนี้คอมไพล์ไม่ได้รวมกันมันแค่เขียนทับ จากนั้นคุณสามารถทำ "ชื่อไฟล์ git diff HEAD" เพื่อดูว่ามีอะไรเปลี่ยนแปลงและทำการผสานด้วยตนเอง
cminatti

18

สถานการณ์:

คุณอยู่ในสาขาของคุณสมมติmasterและคุณมีความมุ่งมั่นของคุณในสาขาอื่น ๆ คุณต้องเลือกไฟล์เดียวจากการกระทำที่เฉพาะเจาะจงนั้น

วิธีการ:

ขั้นตอนที่ 1:ชำระเงินที่สาขาที่ต้องการ

git checkout master

ขั้นตอนที่ 2:ตรวจสอบว่าคุณได้คัดลอกแฮชการกระทำที่ต้องการ

git checkout commit_hash path\to\file

ขั้นตอนที่ 3:ตอนนี้คุณมีการเปลี่ยนแปลงของไฟล์ที่ต้องการในสาขาที่คุณต้องการ คุณเพียงแค่ต้องเพิ่มและกระทำพวกเขา

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

1
! น่ากลัว ใช้ได้กับทุกการเปลี่ยนแปลงในไดเรกทอรีด้วย \ path \ to \ directory \ for me
zaggi

13

ฉันแค่จะเลือกเก็บเชอร์รี่จากนั้นทำสิ่งนี้:

git reset --soft HEAD^

จากนั้นฉันจะยกเลิกการเปลี่ยนแปลงที่ฉันไม่ต้องการแล้วทำการคอมมิทใหม่


11

ใช้git merge --squash branch_nameสิ่งนี้จะได้รับการเปลี่ยนแปลงทั้งหมดจากสาขาอื่นและจะเตรียมการมอบอำนาจให้คุณ ตอนนี้ลบการเปลี่ยนแปลงที่ไม่จำเป็นทั้งหมดแล้วปล่อยให้การเปลี่ยนแปลงที่คุณต้องการ และคอมไพล์จะไม่ทราบว่ามีการรวม


ขอบคุณฉันไม่รู้เกี่ยวกับตัวเลือกการรวมนั้น มันเป็นทางเลือกที่ทำงานได้ถ้าคุณต้องการเชอร์รี่เลือกเกือบทั้งหมดของสาขา (แต่ตรงกันข้ามกับเชอร์รี่เลือกมันจะไม่ทำงานถ้าไม่มีบรรพบุรุษร่วมกัน)
Tobias Kienzler

4

ฉันพบวิธีอื่นที่ป้องกันไม่ให้เกิดการรวมกันของการหยิบเชอร์รี่ซึ่ง IMO นั้นง่ายต่อการจดจำและเข้าใจ เนื่องจากคุณไม่ได้เป็นคนเก็บเชอร์รี่ แต่เป็นส่วนหนึ่งของมันคุณต้องแบ่งมันก่อนแล้วสร้างคอมมิชชันที่เหมาะกับความต้องการของคุณและเลือกเชอร์รี่

ขั้นแรกให้สร้างสาขาจากการกระทำที่คุณต้องการแยกและชำระเงิน:

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

จากนั้นยกเลิกการคอมมิชชันก่อนหน้า:

$ git reset HEAD~1

จากนั้นเพิ่มไฟล์ / การเปลี่ยนแปลงที่คุณต้องการเลือก:

$ git add FILE

และกระทำมัน:

$ git commit -m "pick me"

สังเกตแฮชการกระทำให้เรียกมันว่า PICK-SHA แล้วกลับไปที่สาขาหลักของคุณเป็นต้นตัวอย่างเช่นบังคับให้ชำระเงิน:

$ git checkout -f master

และเชอร์รี่เลือกกระทำ:

$ git cherry-pick PICK-SHA

ตอนนี้คุณสามารถลบสาขา temp:

$ git branch -d temp -f

2

รวมสาขาเข้าเป็นสาขาใหม่ (สควอช) และลบไฟล์ที่ไม่ต้องการ:

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

สิ่งที่ดีที่สุดสำหรับฉันคือ:

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

git status

มันเป็นสิ่งที่ OP ต้องการอย่างแท้จริง มันจะแก้ไขข้อขัดแย้งเมื่อจำเป็นเช่นเดียวกับที่mergeมันทำ มันทำaddแต่ไม่ใช่commitการเปลี่ยนแปลงใหม่ของคุณ


1

คุณสามารถใช้ได้:

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

สัญกรณ์<commit>^ระบุ (ตอนแรก) <commit>แม่ของ ดังนั้นคำสั่ง diff นี้หยิบเปลี่ยนแปลงที่ทำกับในการกระทำ<path><commit>

โปรดทราบว่าสิ่งนี้จะไม่ส่งผลใด ๆ (เช่นgit cherry-pick) ดังนั้นหากคุณต้องการคุณจะต้องทำ:

git add <path>
git commit
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.