ฉันจะแยก Git ที่ฝังไว้ในประวัติศาสตร์ได้อย่างไร?


292

ฉันลบล้างประวัติของฉันและต้องการเปลี่ยนแปลงบางอย่าง ปัญหาคือฉันมีการกระทำที่มีการเปลี่ยนแปลงที่ไม่เกี่ยวข้องสองรายการและการกระทำนี้ล้อมรอบด้วยการเปลี่ยนแปลงอื่น ๆ ในประวัติศาสตร์ท้องถิ่นของฉัน

ฉันต้องการแยกการกระทำนี้ออกก่อนที่จะผลักดันออกไป แต่คำแนะนำส่วนใหญ่ที่ฉันเห็นจะเกี่ยวข้องกับการแยกการกระทำล่าสุดของคุณหรือการเปลี่ยนแปลงในท้องถิ่นที่ปราศจากข้อผูกมัด เป็นไปได้ไหมที่จะทำเช่นนี้กับการกระทำที่ฝังอยู่ในประวัติศาสตร์สักเล็กน้อยโดยไม่ต้อง "ทำซ้ำ" หลังจากนั้น?


คำตอบ:


450

มีแนวทางในการกระทำแยกเป็นใน manpage สรุปอย่างรวดเร็วคือ:

  • ดำเนินการ rebase เชิงโต้ตอบรวมถึงเป้าหมายที่กระทำ (เช่นgit rebase -i <commit-to-split>^ branch) และทำเครื่องหมายเพื่อแก้ไข

  • เมื่อรีบูตถึงการส่งมอบให้ใช้git reset HEAD^เพื่อรีเซ็ตเป็นก่อนการส่งมอบ

  • เพิ่มการเปลี่ยนแปลงและการคอมมิชชันที่เพิ่มขึ้นทำให้มากขึ้นตามที่ต้องการ add -pอาจมีประโยชน์ในการเพิ่มการเปลี่ยนแปลงเพียงบางส่วนในไฟล์ที่กำหนด ใช้commit -c ORIG_HEADหากคุณต้องการใช้ข้อความการส่งข้อความดั้งเดิมอีกครั้งสำหรับการยืนยันที่แน่นอน

  • หากคุณต้องการทดสอบสิ่งที่คุณทำ (ความคิดที่ดี!) ใช้git stashเพื่อซ่อนส่วนที่คุณไม่ได้ทำ (หรือstash --keep-indexก่อนที่คุณจะยอมรับ) ให้ทดสอบแล้วจึงgit stash popส่งส่วนที่เหลือกลับไปที่แผนผังการทำงาน มุ่งมั่นที่จะทำต่อไปจนกว่าคุณจะได้รับการแก้ไขทั้งหมดมุ่งมั่นคือมีต้นไม้งานที่สะอาด

  • เรียกใช้git rebase --continueเพื่อดำเนินการต่อการใช้ความมุ่งมั่นหลังจากกระทำแยกในขณะนี้


17
... แต่อย่าทำอย่างใดอย่างหนึ่งถ้าคุณผลักประวัติไปแล้วตั้งแต่ความมุ่งมั่นที่จะแยก
wilhelmtell

29
@ Wilhelmtell: ฉันละเว้น "อาจเป็นอันตรายได้ตามปกติโปรดดู" การกู้คืนจากการอัปเดตต้นน้ำ "" เพราะต้นแบบ OP กล่าวอย่างชัดเจนว่าเขาไม่ได้ผลักประวัตินี้
Cascabel

2
และคุณอ่านสมบูรณ์แบบ ฉันพยายามหลีกเลี่ยง 'สำเร็จรูป' เมื่อฉันระบุว่ายังไม่ได้แชร์ประวัติ :) ไม่ว่าในกรณีใดฉันได้ประสบความสำเร็จกับคำแนะนำของคุณ มันเป็นความเจ็บปวดครั้งใหญ่ที่จะทำสิ่งนี้หลังจากข้อเท็จจริง ฉันได้เรียนรู้บทเรียนที่นี่และเพื่อให้แน่ใจว่าการคอมมิทั่มนั้นเริ่มต้นอย่างถูกต้อง!
เบ็น

2
git rebase -i <sha1_of_the_commit_to_split>^ branchขั้นตอนแรกอาจจะระบุไว้ที่ดีกว่า และgit guiเป็นเครื่องมือที่ดีสำหรับงานแบ่งซึ่งสามารถใช้เพื่อเพิ่มส่วนต่าง ๆ ของไฟล์ลงในความมุ่งมั่นที่แตกต่างกัน
Qiang Xu

3
@ XiangXu: ข้อแรกเป็นข้อเสนอแนะที่สมเหตุสมผล อย่างที่สองคือเหตุผลที่ฉันแนะนำgit add -pซึ่งสามารถทำได้มากกว่าgit guiในแผนกนี้ (โดยเฉพาะการแก้ไข hunks การแสดงทุกอย่างเริ่มต้นจากก้อนใหญ่ในปัจจุบันและการค้นหา hunks โดย regex)
Cascabel

3

นี่คือวิธีการที่จะทำกับMagit

สมมติว่ากระทำ ed417ae เป็นสิ่งที่คุณต้องการเปลี่ยนแปลง มันมีการเปลี่ยนแปลงที่ไม่เกี่ยวข้องสองรายการและถูกฝังอยู่ภายใต้การกระทำอย่างน้อยหนึ่งอย่าง กดllปุ่มเพื่อแสดงบันทึกและไปที่ ed417ae:

บันทึกเริ่มต้น

จากนั้นกดrเพื่อเปิดป๊อปอัป rebase

rebase ป๊อปอัพ

และmเพื่อปรับการกระทำที่จุด

โปรดสังเกตว่า@ขณะนี้มีการกระทำที่คุณต้องการแยกซึ่งหมายความว่า HEAD อยู่ในการกระทำนั้น

แก้ไขการกระทำ

เราต้องการย้าย HEAD ไปที่พาเรนต์ดังนั้นไปที่พาเรนต์ (47e18b3) แล้วกดx( magit-reset-quicklyซึ่งถูกผูกไว้oหากคุณกำลังใช้งานevil-magit) และป้อนเพื่อพูดว่า "ใช่ฉันหมายถึงมุ่งมั่นที่จุด" บันทึกของคุณควรมีลักษณะดังนี้:

บันทึกหลังจากรีเซ็ต

ตอนนี้กดqเพื่อไปที่สถานะ Magit ปกติจากนั้นใช้uคำสั่งunstage ปกติเพื่อ unstage สิ่งที่ไม่ไปในการคอมมิทครั้งแรกคอมมิตcที่เหลือตามปกติจากนั้นstage และcommit สิ่งที่เกิดขึ้นในคอมมิทที่สองและเมื่อเสร็จแล้ว: กดrเพื่อเปิดป๊อปอัป rebase

rebase ป๊อปอัพ

และอีกรายการหนึ่งrเพื่อดำเนินการต่อและคุณทำเสร็จแล้ว! llตอนนี้แสดง:

บันทึกทั้งหมดเสร็จแล้ว


1

ในการแยกการคอมมิท<commit>และเพิ่มการคอมมิทใหม่ก่อนหน้านี้และบันทึกวันที่ของผู้เขียน<commit>- ขั้นตอนต่อไปนี้:

  1. แก้ไขคำสั่งก่อนหน้า <commit>

    git rebase -i <commit>^^
    

    หมายเหตุ: อาจจำเป็นต้องแก้ไข<commit>ด้วยเช่นกัน

  2. เชอร์รี่เลือก<commit>ลงในดัชนี

    git cherry-pick -n <commit>
    
  3. รีเซ็ตการเปลี่ยนแปลงที่ไม่จำเป็นจากดัชนีและรีเซ็ตแผนผังการทำงาน

    git reset -p && git checkout-index -f -a
    

    เป็นทางเลือกเพียงซ่อนการเปลี่ยนแปลงที่ไม่จำเป็นไว้แบบโต้ตอบ: git stash push -p -m "tmp other changes"

  4. ทำการเปลี่ยนแปลงอื่น ๆ (ถ้ามี) และสร้างการส่งมอบใหม่

    git commit -m "upd something" .
    

    เลือกทำซ้ำรายการ 2-4 เพื่อเพิ่มการคอมมิทระดับกลางเพิ่มเติม

  5. ทำการรีบูตต่อไป

    git rebase --continue
    

0

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

  1. the_fileใช้โปรแกรมแก้ไขและลบเส้นที่คุณต้องการสารสกัดจาก ปิดthe_file. นั่นเป็นรุ่นเดียวที่คุณต้องการส่วนที่เหลือทั้งหมดเป็นเพียงคำสั่งคอมไพล์
  2. สเตจที่ลบในดัชนี:

    git  add  the_file
    
  3. กู้คืนบรรทัดที่คุณเพิ่งลบกลับไปที่ไฟล์โดยไม่กระทบกับดัชนี !

    git show HEAD:./the_file > the_file
    
  4. "SHA1" คือการส่งมอบที่คุณต้องการแยกบรรทัดจาก:

    git commit -m 'fixup! SHA1' 
    
  5. สร้างรายการที่สองใหม่เอี่ยมพร้อมเนื้อหาเพื่อแยกคืนโดยขั้นตอนที่ 3:

    git commit -m 'second and new commit' the_file 
    
  6. อย่าแก้ไขอย่าหยุด / ทำต่อ - ยอมรับทุกสิ่ง:

    git rebase --autosquash -i SHA1~1
    

แน่นอนยิ่งเร็วขึ้นเมื่อความมุ่งมั่นที่จะดึงมาจากการกระทำสุดท้าย:

4. git commit -C HEAD --amend
5. git commit -m 'second and new commit' thefile
6. no rebase, nothing

หากคุณใช้magitขั้นตอนที่ 4, 5 และ 6 จะเป็นการกระทำเดียว: Commit, Fixup ทันที


-2

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


14
ไม่จำเป็นต้องเคลื่อนย้ายไปทุกที่ แยกมันอยู่ที่ไหน
Cascabel

1
น่าเสียดายที่นี่ไม่ได้ผลสำหรับฉันเพราะประวัติบางอย่างหลังจากการกระทำนั้นขึ้นอยู่กับฉันดังนั้นฉันจึงถูก จำกัด เล็กน้อย อย่างไรก็ตามนี่จะเป็นตัวเลือกแรกของฉัน
เบ็น

@Ben: ไม่เป็นไร - ความมุ่งมั่นหลังจากนั้นไม่จำเป็นต้องเปลี่ยนแปลงเลย (สมมติว่าคุณเก็บการเปลี่ยนแปลงทั้งหมดแทนที่จะทิ้งบางส่วน) ข้อมูลเพิ่มเติมได้ที่นี่ - stackoverflow.com/questions/1440050/…
อีเธอร์
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.