Git cherry pick vs rebase


120

ฉันเพิ่งเริ่มทำงานกับ Git

เมื่อไปที่หนังสือ Gitออนไลน์ฉันพบสิ่งต่อไปนี้ในส่วน "Git Rebase":

ด้วยคำสั่ง rebase คุณสามารถนำการเปลี่ยนแปลงทั้งหมดที่เกิดขึ้นในสาขาหนึ่งและเล่นซ้ำในอีกสาขาหนึ่งได้

(อ้างจาก: http://git-scm.com/book/en/Git-Branching-Rebasing )

ฉันคิดว่านี่เป็นคำจำกัดความที่แน่นอนของ git cherry-pick (ใช้การคอมมิตใหม่หรือชุดของออบเจ็กต์คอมมิตในสาขาที่เช็คเอาต์ในปัจจุบัน)

อะไรคือความแตกต่างระหว่างทั้งสอง?

คำตอบ:


166

ตั้งแต่เวลาที่git cherry-pickเรียนรู้ที่จะสามารถใช้การกระทำหลาย ๆ ครั้งความแตกต่างก็กลายเป็นเรื่องเล็กน้อย แต่นี่เป็นสิ่งที่เรียกว่าวิวัฒนาการแบบบรรจบกัน ;-)

ความแตกต่างที่แท้จริงอยู่ที่เจตนาดั้งเดิมในการสร้างเครื่องมือทั้งสอง:

  • git rebaseภารกิจคือส่งต่อพอร์ตชุดของการเปลี่ยนแปลงที่นักพัฒนามีในที่เก็บส่วนตัวที่สร้างขึ้นจากเวอร์ชัน X ของสาขาต้นน้ำบางสาขาไปยังเวอร์ชัน Y ของสาขาเดียวกันนั้น (Y> X) สิ่งนี้เปลี่ยนฐานของการกระทำชุดนั้นได้อย่างมีประสิทธิภาพดังนั้นจึงเป็นการ "rebasing"

    (นอกจากนี้ยังช่วยให้นักพัฒนาสามารถปลูกถ่ายชุดของการกระทำลงบนการกระทำใด ๆ โดยพลการ แต่เป็นการใช้งานที่ไม่ชัดเจน)

  • git cherry-pickมีไว้เพื่อนำการกระทำที่น่าสนใจจากแนวการพัฒนาหนึ่งไปสู่อีกสายหนึ่ง ตัวอย่างคลาสสิกคือการแบ็คพอร์ตการแก้ไขความปลอดภัยที่ทำในสาขาการพัฒนาที่ไม่เสถียรไปยังสาขาที่มีเสถียรภาพ (การบำรุงรักษา) ซึ่งmergeไม่สมเหตุสมผลเนื่องจากจะทำให้เกิดการเปลี่ยนแปลงที่ไม่ต้องการมากมาย

    ตั้งแต่ปรากฏตัวครั้งแรกgit cherry-pickสามารถเลือกการกระทำหลายอย่างพร้อมกันทีละรายการ

ดังนั้นความแตกต่างที่โดดเด่นที่สุดระหว่างคำสั่งทั้งสองนี้ก็คือวิธีที่พวกเขาปฏิบัติกับสาขาที่พวกเขาทำงาน: git cherry-pickโดยปกติจะนำการกระทำจากที่อื่นและนำไปใช้กับสาขาปัจจุบันของคุณบันทึกการกระทำใหม่ในขณะที่git rebaseใช้สาขาปัจจุบันของคุณและเขียนใหม่ชุดทิปของตัวเองมุ่งมั่นไม่ทางใดก็ทางหนึ่ง ใช่นี่เป็นคำอธิบายที่ลดลงอย่างมากว่าgit rebaseสามารถทำอะไรได้บ้าง แต่เป็นความตั้งใจที่จะพยายามทำให้ความคิดทั่วไปจมลงไป

อัปเดตเพื่ออธิบายเพิ่มเติมเกี่ยวกับตัวอย่างการใช้ที่git rebaseกำลังกล่าวถึง

จากสถานการณ์นี้
สถานะของ repo ก่อนทำการ rebasing
หนังสือระบุว่า:

อย่างไรก็ตามมีอีกวิธีหนึ่งคือคุณสามารถนำการเปลี่ยนแปลงที่เกิดขึ้นใน C3 มาใช้และนำไปใช้ใหม่ที่ด้านบนของ C4 ใน Git เรียกว่า rebasing ด้วยคำสั่ง rebase คุณสามารถนำการเปลี่ยนแปลงทั้งหมดที่เกิดขึ้นในสาขาหนึ่งและนำไปใช้กับอีกสาขาหนึ่งได้

ในตัวอย่างนี้คุณจะต้องเรียกใช้สิ่งต่อไปนี้:

$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command

"สิ่งที่จับได้" ในที่นี้คือในตัวอย่างนี้สาขา "การทดสอบ" (หัวข้อสำหรับการ rebasing) เดิมแยกออกจากสาขา "หลัก" และด้วยเหตุนี้จึงมีการแบ่งปัน C0 ถึง C2 ด้วย - อย่างมีประสิทธิภาพ "การทดสอบ" คือ " master "ขึ้นไปและรวมถึง C2 plus คอมมิต C3 ไว้ด้านบน (นี่เป็นกรณีที่ง่ายที่สุดแน่นอนว่า "การทดสอบ" อาจมีข้อตกลงหลายสิบข้อที่อยู่ด้านบนของฐานเดิม)

ตอนนี้git rebaseได้รับคำสั่งให้ทำการ "ทดลอง" อีกครั้งในเคล็ดลับปัจจุบันของ "master" และgit rebaseจะเป็นดังนี้:

  1. เรียกใช้git merge-baseเพื่อดูว่าการกระทำสุดท้ายที่ใช้ร่วมกันโดยทั้ง "การทดสอบ" และ "หลัก" คืออะไร (หรืออีกนัยหนึ่งคือจุดเปลี่ยน) นี่คือ C2
  2. บันทึกการกระทำทั้งหมดที่เกิดขึ้นตั้งแต่จุดเบี่ยงเบน ในตัวอย่างของเล่นของเรามันเป็นแค่ C3
  3. กรอหัวกลับ (ซึ่งชี้ไปที่ส่วนปลายของ "การทดสอบ" ก่อนที่การดำเนินการจะเริ่มทำงาน) เพื่อชี้ไปที่ส่วนปลายของ "ต้นแบบ" - เรากำลังพิจารณาใหม่
  4. พยายามใช้การคอมมิตที่บันทึกไว้ (เช่นเดียวกับgit apply) ตามลำดับ ในตัวอย่างของเล่นของเรามันเป็นเพียงหนึ่งการกระทำ C3 สมมติว่าแอปพลิเคชันจะสร้างการคอมมิต C3 '
  5. หากทุกอย่างเป็นไปด้วยดีการอ้างอิง "การทดสอบ" จะได้รับการอัปเดตเพื่อชี้ไปที่การกระทำที่เป็นผลมาจากการใช้คอมมิตที่บันทึกไว้ล่าสุด (C3 'ในกรณีของเรา)

ตอนนี้กลับไปที่คำถามของคุณ อย่างที่คุณเห็นในทางเทคนิค git rebaseแล้วที่นี่ในทางเทคนิคการปลูกถ่ายชุดของการกระทำจาก "การทดลอง" ไปยังปลายของ "ผู้เชี่ยวชาญ" ดังนั้นคุณสามารถบอกได้อย่างถูกต้องว่ามี "สาขาอื่น" อยู่ในกระบวนการ แต่สาระสำคัญคือเคล็ดลับที่กระทำจาก "การทดสอบ" กลายเป็นทิปใหม่ที่กระทำใน "การทดสอบ" มันเพิ่งเปลี่ยนฐาน:
สถานะหลังจากการรวม

อีกครั้งในทางเทคนิคคุณสามารถบอกได้ว่าที่git rebaseนี่ได้รวมการกระทำบางอย่างจาก "master" ไว้ด้วยและนี่เป็นสิ่งที่ถูกต้อง


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

1
พยายามอธิบายเรื่องนี้โดยอัปเดตคำตอบของฉัน
kostix

98

ด้วยการเลือกเชอร์รี่การคอมมิต / กิ่งดั้งเดิมจะเกาะติดและสร้างคอมมิตใหม่ ด้วย rebase สาขาทั้งหมดจะถูกย้ายโดยสาขาที่ชี้ไปที่คอมมิตที่เล่นซ้ำ

สมมติว่าคุณเริ่มต้นด้วย:

      A---B---C topic
     /
D---E---F---G master

rebase:

$ git rebase master topic

คุณได้รับ:

              A'--B'--C' topic
             /
D---E---F---G master

เชอร์รี่รับ:

$ git checkout master -b topic_new
$ git cherry-pick A^..C

คุณได้รับ:

      A---B---C topic
     /
D---E---F---G master
             \
              A'--B'--C' topic_new

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับคอมไพล์หนังสือเล่มนี้มีเกือบทั้งหมด(http://git-scm.com/book)


3
ตอบได้ดี. เป็นเรื่องปกติที่คุณอาจต้องการ cherrypick เพียงแค่ A และ B กระทำ แต่ปล่อยให้ C แขวนไว้ในกรณีเหล่านี้คุณอาจต้องการเก็บสาขาไว้และเลือกเชอร์รี่ที่เพื่อนร่วมงานอาจต้องการเห็น Git ถูกสร้างขึ้นมาเพื่อทำงานร่วมกับผู้คนดังนั้นหากคุณไม่เห็นประโยชน์ของบางสิ่งเมื่อทำงานคนเดียวก็มักจะใช้กันมากขึ้นเมื่อทำงานเป็นกลุ่มใหญ่
Pablo Jomer

หากมีการทำ rebase แบบโต้ตอบแทนโดยทิ้งความมุ่งมั่นอย่างน้อยหนึ่งข้อคุณจะมีสาขาใดในตอนท้าย ถ้ามันถูกtopicrebased ด้านบนmasterเท่านั้นมันไม่มีการคอมมิตด้านซ้ายแล้วพวกเขาจะเป็นส่วนหนึ่งของสาขาอะไร?
Anthony

ฉันต้องการเพิ่มอีกสิ่งหนึ่ง: ถ้าคุณgit checkout topicและgit reset --hard C'หลังจากนั้นหลังจากเก็บเชอร์รี่แล้วคุณจะได้ผลลัพธ์เช่นเดียวกับหลังจากรีเบต ฉันช่วยตัวเองจากความขัดแย้งในการรวมหลายครั้งโดยใช้การเลือกเชอร์รี่มากกว่าการลดราคาเพราะบรรพบุรุษร่วมกันกลับมา
sorrymissjackson

@anthony - stackoverflow.com/questions/11835948/… : เท่าที่ฉันเข้าใจว่ามันหายไป ฉันไม่ใช่ - gitกูรู แต่นี่rebase/ cherry-pickอยู่ในรายละเอียดทั้งหมดgitที่ฉันมีปัญหาในการทำความเข้าใจ
thoni56

1
กราฟของคุณทำอันตรายมากกว่าผลดีเนื่องจากมีลักษณะการทำงานเหมือนกัน ความแตกต่างเพียงอย่างเดียวคือสาขาที่สร้างขึ้นโดยที่มีอะไรจะทำอย่างไรกับgit checkout -b git cherry-pickวิธีที่ดีกว่าที่จะอธิบายสิ่งที่คุณกำลังพยายามที่จะพูดจะเป็น“คุณเรียกใช้git rebaseในtopicสาขาและผ่านมันmaster; คุณทำงานgit cherry-pickในmasterสาขาและส่งผ่าน (commits from) topic
Rory O'Kane

14

งานเก็บเชอร์รี่สำหรับการกระทำแต่ละครั้ง

เมื่อคุณทำการ rebasing จะนำคอมมิตทั้งหมดในประวัติไปใช้กับ HEAD ของสาขาที่ไม่มีอยู่


ขอบคุณ คุณรู้หรือไม่ว่าสิ่งเหล่านี้ทำงานเหมือนกันภายใต้ฝาครอบหรือไม่? (จัดเก็บเอาต์พุตระดับกลางไว้ที่ไฟล์ "patch" ฯลฯ )
กรดไลเซอร์จิก

Afaik ใช่ ใช้แพทช์ทั้งหมดทีละรายการ นั่นเป็นเหตุผลว่าทำไมบางครั้งคุณต้องแก้ไขความขัดแย้งในการผสานระหว่างฐานข้อมูลใหม่ก่อนที่จะดำเนินการต่อ
iltempo

6
@iltempo มันใช้งานได้สำหรับแต่ละคอมมิตใน Git เวอร์ชันเก่าเท่านั้น ในปัจจุบันคุณสามารถทำบางสิ่งบางอย่างเช่นgit cherry-pick foo~3..fooและรับยอดไม้จาก "foo" ที่เลือกทีละรายการ
kostix

1
git-rebase ใช้ api เดียวกับการเลือกเชอร์รี่ใน codebase, iirc
ทางเลือกที่

ฉันไม่คิดว่าพวกเขาจะทำงานเหมือนกันภายใต้ผ้าคลุม ฉันได้ลองรีเบตคอมมิทหลายพันรายการแล้วและฉันคิดว่าคอมไพล์สร้างไฟล์เมลบ็อกซ์ขนาดใหญ่จากนั้นเรียกgit amใช้งาน ในขณะที่การเลือกเชอร์รี่ใช้การกระทำโดยการกระทำ (อาจโดยการสร้างกล่องจดหมายข้อความเดียวสำหรับแต่ละแพตช์) rebase ของฉันล้มเหลวเนื่องจากไฟล์เมลบ็อกซ์ที่สร้างขึ้นไม่มีพื้นที่ว่างในไดรฟ์ แต่การเลือกเชอร์รี่ด้วยช่วงการแก้ไขเดียวกันทำได้สำเร็จ (และดูเหมือนว่าจะทำงานได้เร็วขึ้น)
เฉพาะ

11

คำตอบสั้น ๆ :

  • git cherry-pick มากกว่า "ระดับต่ำ"
  • ดังนั้นจึงสามารถเลียนแบบ git rebase

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

ไม่แนะนำให้แทนที่ "git rebase" ด้วยลำดับการกระทำนี้เป็นเพียง "การพิสูจน์แนวคิด" ซึ่งฉันหวังว่าจะช่วยให้เข้าใจว่าสิ่งต่างๆทำงานอย่างไร

ให้ที่เก็บของเล่นต่อไปนี้:

$ git log --graph --decorate --all --oneline
* 558be99 (test_branch_1) Test commit #7
* 21883bb Test commit #6
| * 7254931 (HEAD -> master) Test commit #5
| * 79fd6cb Test commit #4
| * 48c9b78 Test commit #3
| * da8a50f Test commit #2
|/
* f2fa606 Test commit #1

สมมติว่าเรามีการเปลี่ยนแปลงที่สำคัญบางอย่าง (ยอมรับ # 2 ถึง # 5) ในต้นแบบที่เราต้องการรวมไว้ใน test_branch_1 ของเรา โดยปกติเราแค่เปลี่ยนไปใช้ branch และทำ "git rebase master" แต่ในขณะที่เราแสร้งทำเป็นว่าเราติดตั้ง "git cherry-pick" เท่านั้นเราจึงทำดังนี้

$ git checkout 7254931                # Switch to master (7254931 <-- master <-- HEAD)
$ git cherry-pick 21883bb^..558be99   # Apply a range of commits (first commit is included, hence "^")    

หลังจากการดำเนินการทั้งหมดนี้กราฟคอมมิตของเราจะมีลักษณะดังนี้:

* dd0d3b4 (HEAD) Test commit #7
* 8ccc132 Test commit #6
* 7254931 (master) Test commit #5
* 79fd6cb Test commit #4
* 48c9b78 Test commit #3
* da8a50f Test commit #2
| * 558be99 (test_branch_1) Test commit #7
| * 21883bb Test commit #6
|/
* f2fa606 Test commit #1

อย่างที่เราเห็นการคอมมิต # 6 และ # 7 ถูกนำไปใช้กับ 7254931 (ทิปของมาสเตอร์) HEAD ถูกย้ายและชี้การกระทำซึ่งโดยพื้นฐานแล้วเป็นส่วนปลายของสาขาที่ถูกปรับลด ตอนนี้สิ่งที่เราต้องทำคือลบตัวชี้สาขาเก่าและสร้างตัวชี้ใหม่:

$ git branch -D test_branch_1
$ git checkout -b test_branch_1 dd0d3b4

test_branch_1ถูกรูทจากตำแหน่งหลักล่าสุดแล้ว ทำ!


แต่ rebase สามารถจำลอง git cherry-pick ได้หรือไม่?
หมายเลข 945

เนื่องจากcherry-pickสามารถใช้ช่วงของการกระทำได้ฉันคิดว่าใช่ แม้ว่านี่จะเป็นวิธีที่แปลกบิตของการทำสิ่งที่ไม่มีอะไรป้องกันคุณจากเชอร์รี่ยกกระทำทั้งหมดในสาขาคุณลักษณะของคุณอยู่ด้านบนของแล้วลบสาขาคุณลักษณะและสร้างใหม่อีกครั้งมันดังกล่าวว่ามันชี้ไปที่ปลายสุดของmaster masterคุณสามารถคิดgit rebaseเป็นลำดับของgit cherry-pick feature_branch, และgit branch -d feature_branch git branch feature_branch master
raiks

7

ทั้งคู่เป็นคำสั่งสำหรับเขียนคอมมิทของสาขาหนึ่งทับอีกสาขา: ความแตกต่างอยู่ที่สาขา - "ของคุณ" (ที่เช็คเอาต์ในปัจจุบันHEAD) หรือ "ของพวกเขา" (สาขาที่ส่งผ่านเป็นอาร์กิวเมนต์ของคำสั่ง) - คือฐานสำหรับการเขียนนี้

git rebaseใช้เวลาเริ่มต้นกระทำและไกลของคุณกระทำว่ามาหลังจากที่พวกเขา (เริ่มต้นกระทำ)

git cherry-pickรับชุดของการกระทำและเล่นซ้ำการกระทำของพวกเขาว่ามาหลังจากคุณ (ของคุณHEAD)

กล่าวอีกนัยหนึ่งคำสั่งทั้งสองอยู่ในลักษณะการทำงานหลักของพวกเขา (โดยไม่สนใจลักษณะการทำงานที่แตกต่างกันการเรียกใช้การประชุมและตัวเลือกการเพิ่มประสิทธิภาพ) สมมาตร : การตรวจสอบสาขาbarและการรันgit rebase fooจะตั้งค่าbarสาขาเป็นประวัติเดียวกับการตรวจสอบสาขาfooและการรันgit cherry-pick ..barจะตั้งค่าfooเป็น (การเปลี่ยนแปลงจากfooตามด้วยการเปลี่ยนแปลงจากbar)

การตั้งชื่ออย่างชาญฉลาดความแตกต่างระหว่างคำสั่งทั้งสองสามารถจำได้ว่าแต่ละคำอธิบายถึงสิ่งที่ทำกับสาขาปัจจุบัน : rebaseทำให้อีกฝ่ายเป็นฐานใหม่สำหรับการเปลี่ยนแปลงของคุณในขณะที่การcherry-pickเลือกการเปลี่ยนแปลงจากสาขาอื่นและวางไว้ด้านบนของ ของคุณHEAD (เหมือนเชอร์รี่อยู่ด้านบนของซันเดย์)


1
ฉันไม่เข้าใจคำตอบใด ๆ นอกจากของคุณ! กระชับและเข้าท่าโดยไม่ต้องใช้ถ้อยคำที่ไม่จำเป็น
neoxic

4

ทั้งสองทำสิ่งที่คล้ายกันมาก ความแตกต่างของแนวคิดหลักคือ (ในแง่ง่าย) ที่:

  • rebaseกระทำย้ายจากสาขาในปัจจุบันไปสาขาอื่น

  • เชอร์รี่รับสำเนากระทำจากสาขาอื่นไปยังสาขาในปัจจุบัน

การใช้แผนภาพคล้ายกับคำตอบของ @Kenny Ho :

ระบุสถานะเริ่มต้นนี้:

A---B---C---D master
     \
      E---F---G topic

... และสมมติว่าคุณต้องการรับคอมมิตจากtopicสาขาที่เล่นซ้ำที่ด้านบนของmasterสาขาปัจจุบันคุณมีสองตัวเลือก:

  1. การใช้ rebase: ก่อนอื่นคุณไปที่การtopicทำgit checkout topicจากนั้นย้ายสาขาโดยการเรียกใช้git rebase masterการผลิต:

    A---B---C---D master
                 \
                  E'---F'---G' topic
    

    ส่งผลให้เกิด:สาขาปัจจุบันของคุณtopicได้รับการ rebased (ย้าย) masterไปยัง สาขาได้รับการปรับปรุงในขณะที่สาขาอยู่ในสถานที่
    topicmaster

  2. การใช้เชอร์รี่เลือก : ก่อนอื่นคุณต้องไปที่การmasterทำgit checkout masterจากนั้นคัดลอกสาขาโดยเรียกใช้git cherry-pick topic~3..topic(หรือเทียบเท่าgit cherry-pick B..G) ผลิต:

    A---B---C---D---E'---F'---G' master
         \
          E---F---G topic
    

    ผลลัพธ์: คอมมิตจากtopicถูกคัดลอกไปmasterยัง สาขาได้รับการปรับปรุงในขณะที่สาขาอยู่ในสถานที่
    mastertopic


แน่นอนว่าที่นี่คุณต้องบอกอย่างชัดเจนเชอร์รี่เลือกที่จะเลือกลำดับของการกระทำโดยใช้สัญกรณ์ช่วง foo..barหากคุณเพิ่งผ่านชื่อสาขาเช่นเดียวกับในgit cherry-pick topicมันจะหยิบเฉพาะการกระทำที่ส่วนปลายของสาขาส่งผลให้:

A---B---C---D---G' master
     \
      E---F---G topic
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.