จะฉีดยาระหว่างการกระทำโดยพลการสองครั้งในอดีตได้อย่างไร?


128

สมมติว่าฉันมีประวัติการกระทำต่อไปนี้ในสาขาในพื้นที่ของฉันเท่านั้น:

A -- B -- C

ฉันจะแทรกคอมมิตใหม่ระหว่างAและได้Bอย่างไร?


3
กระทำเรียกว่าĄ ? :)
Antek

ฉันมีคำถามที่คล้ายกันมากเกี่ยวกับวิธีแทรกเวอร์ชันใหม่ในอดีตแทนที่จะเป็นการคอมมิต
Pavel P

คำตอบ:


178

ง่ายกว่าในคำตอบของ OP

  1. git rebase -i <any earlier commit>. ซึ่งจะแสดงรายการคอมมิตในโปรแกรมแก้ไขข้อความที่กำหนดค่าไว้
  2. ค้นหาคอมมิตที่คุณต้องการแทรกหลัง (สมมติว่าเป็นa1b2c3d) ในการแก้ไขของคุณสำหรับสายที่เปลี่ยนไปpickedit
  3. เริ่มต้น rebase โดยปิดโปรแกรมแก้ไขข้อความของคุณ (บันทึกการเปลี่ยนแปลงของคุณ) ใบนี้คุณอยู่ที่คำสั่งพร้อมท์กับการกระทำก่อนหน้านี้คุณเลือก ( a1b2c3d) ราวกับว่ามันได้รับเพียงแค่มุ่งมั่น
  4. ทำการเปลี่ยนแปลงของคุณและgit commit( ไม่แก้ไขซึ่งแตกต่างจากส่วนใหญ่edit) สิ่งนี้จะสร้างคอมมิตใหม่หลังจากที่คุณเลือก
  5. git rebase --continue. สิ่งนี้จะเล่นซ้ำการกระทำที่ต่อเนื่องกันโดยปล่อยให้คอมมิตใหม่ของคุณแทรกในตำแหน่งที่ถูกต้อง

ระวังว่าสิ่งนี้จะเขียนประวัติศาสตร์ซ้ำและทำลายใครก็ตามที่พยายามดึง


1
สิ่งนี้เพิ่มการกระทำใหม่หลังจากการคอมมิตซึ่งอยู่หลังจากการคอมมิตที่ฉันกำลังทำการ rebasing (รวมถึงการกระทำสุดท้าย) แทนที่จะเป็นทันทีหลังจากที่ฉันกำลังทำการรีเบตใหม่ ผลลัพธ์ก็เหมือนกับว่าฉันได้ทำการคอมมิตใหม่ในตอนท้ายพร้อมกับการเปลี่ยนแปลงที่ฉันต้องการแทรก ประวัติของฉันกลายเป็นแทนที่ต้องการA -- B -- C -- D A -- D -- B -- C
XedinUnknown

2
@ XedinUnknown: แล้วคุณไม่ได้ใช้ Rebase อย่างถูกต้อง
SLaks

3
ตอนนี้Dอาจเป็นการกระทำที่ใดก็ได้ สมมติว่าเรามีA - B - Cและเรามีภาระกิจบางอย่างDที่ไม่ได้อยู่ในสาขานี้ เรารู้ว่า SHA มัน git rebase -i HEAD~3แต่เราสามารถทำ ตอนนี้ระหว่างAและB pickเส้นที่เราใส่ใหม่ pickบรรทัดที่บอกว่าให้กัญชาของที่ต้องการpick SHA Dไม่จำเป็นต้องเป็นแฮชแบบเต็ม แต่เป็นแฮชที่สั้นลง git rebase -iเพียงแค่เชอร์รี่เลือกสิ่งที่กระทำตามpickบรรทัดในบัฟเฟอร์ ไม่จำเป็นต้องเป็นของดั้งเดิมที่ระบุไว้สำหรับคุณ
Kaz

1
@Kaz ดูเหมือนจะเป็นคำตอบอื่นที่ถูกต้อง
BartoszKP

3
ง่ายยิ่งกว่านั้นคุณสามารถใช้breakคีย์เวิร์ดในตัวแก้ไขในบรรทัดของมันเองระหว่างสองคอมมิต (หรือในบรรทัดแรกเพื่อแทรกคอมมิตก่อนคอมมิตที่คุณระบุ)
SimonT

30

จะเปิดออกจะค่อนข้างง่ายคำตอบที่พบที่นี่ สมมติว่าคุณอยู่ในสาขาbranchหนึ่ง ทำตามขั้นตอนเหล่านี้:

  • สร้างสาขาชั่วคราวจากคอมมิตหลังจากที่คุณต้องการแทรกคอมมิตใหม่ (ในกรณีนี้คอมมิตA):

    git checkout -b temp A
    
  • ทำการเปลี่ยนแปลงและคอมมิตสร้างคอมมิตเรียกว่าN:

    git commit -a -m "Message"
    

    (หรือgit addตามด้วยgit commit)

  • rebase คอมมิตที่คุณต้องการหลังจากคอมมิตใหม่ (ในกรณีนี้คอมมิตBและC) เข้าคอมมิตใหม่:

    git rebase temp branch
    

(เป็นไปได้ว่าคุณต้องใช้-pเพื่อรักษาการผสานหากมี - ต้องขอบคุณความคิดเห็นที่ไม่มีอยู่แล้วโดยciekawy )

  • ลบสาขาชั่วคราว:

    git branch -d temp
    

หลังจากนี้ประวัติมีลักษณะดังนี้:

A -- N -- B -- C

เป็นไปได้แน่นอนที่ความขัดแย้งบางอย่างจะปรากฏขึ้นในขณะที่กำลังทำการรีไฟแนนซ์

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


2
ฉันไม่สามารถทำตามคำตอบที่ยอมรับโดย SLaks แต่สิ่งนี้ใช้ได้ผลสำหรับฉัน หลังจากฉันได้รับประวัติการกระทำที่ต้องการฉันต้องgit push --forceเปลี่ยน repo ระยะไกล
escapeecharacter

1
เมื่อใช้ rebase ใช้ -Xtheirs git rebase temp branch -Xtheirsแก้ไขตัวเลือกอัตโนมัติความขัดแย้งได้อย่างถูกต้องดังนั้น คำตอบที่เป็นประโยชน์สำหรับการฉีดในสคริปต์!
David C

สำหรับ noobs อย่างฉันฉันอยากจะเพิ่มหลังจากgit rebase temp branchนั้น แต่ก่อนหน้าgit branch -d tempนี้สิ่งที่คุณต้องทำคือแก้ไขและจัดการกับความขัดแย้งและปัญหาที่รวมเข้าด้วยกันgit rebase --continueนั่นคือไม่จำเป็นต้องกระทำอะไรเลย ฯลฯ
Pugsley

19

วิธีแก้ปัญหาที่ง่ายยิ่งขึ้น:

  1. สร้างคอมมิตใหม่ในตอนท้าย D. ตอนนี้คุณมี:

    A -- B -- C -- D
    
  2. จากนั้นเรียกใช้:

    $ git rebase -i hash-of-A
    
  3. Git จะเปิดโปรแกรมแก้ไขของคุณและจะมีลักษณะดังนี้:

    pick 8668d21 B
    pick 650f1fc C
    pick 74096b9 D
    
  4. เพียงแค่เลื่อน D ไปด้านบนแบบนี้จากนั้นบันทึกและออก

    pick 74096b9 D
    pick 8668d21 B
    pick 650f1fc C
    
  5. ตอนนี้คุณจะมี:

    A -- D -- B -- C
    

6
เป็นความคิดที่ดีอย่างไรก็ตามอาจเป็นเรื่องยากที่จะแนะนำ D บน C เมื่อคุณตั้งใจให้การเปลี่ยนแปลงเหล่านี้เป็น WRT ถึง A.
BartoszKP

ฉันมีสถานการณ์ที่ฉันมี 3 ข้อตกลงที่ฉันต้องการสร้างฐานใหม่ด้วยกันและการกระทำที่อยู่ตรงกลางซึ่งไม่เกี่ยวข้องกัน นี่เป็นเรื่องดีมากที่สามารถย้ายการกระทำก่อนหน้านี้หรือในภายหลังไปตามแนวการกระทำได้
unflores

13

สมมติว่าประวัติการกระทำคือpreA -- A -- B -- Cหากคุณต้องการแทรกการกระทำระหว่างAและBขั้นตอนจะเป็นดังนี้:

  1. git rebase -i hash-of-preA

  2. Git จะเปิดตัวแก้ไขของคุณ เนื้อหาอาจเป็นเช่นนี้:

    pick 8668d21 A
    pick 650f1fc B
    pick 74096b9 C
    

    เปลี่ยนคนแรกpickเป็นedit:

    edit 8668d21 A
    pick 650f1fc B
    pick 74096b9 C
    

    บันทึกและออก.

  3. แก้ไขรหัสของคุณแล้ว git add . && git commit -m "I"

  4. git rebase --continue

ตอนนี้ประวัติการคอมมิต Git ของคุณคือ preA -- A -- I -- B -- C


หากคุณพบข้อขัดแย้ง Git จะหยุดที่คอมมิตนี้ คุณสามารถใช้git diffเพื่อค้นหาเครื่องหมายความขัดแย้งและแก้ไขได้ หลังจากแก้ไขปัญหาความขัดแย้งทุกอย่างที่คุณจำเป็นต้องใช้git add <filename>จะบอกว่า Git git rebase --continueความขัดแย้งได้รับการแก้ไขแล้ววิ่ง

หากคุณต้องการที่จะยกเลิกการ rebase git rebase --abortการใช้


11

นี่คือกลยุทธ์ที่หลีกเลี่ยงการ "แก้ไขแฮ็ก" ในระหว่าง rebase ที่เห็นในคำตอบอื่น ๆ ที่ฉันได้อ่าน

โดยใช้git rebase -iคุณได้รับรายการคอมมิตตั้งแต่คอมมิตนั้น เพียงแค่เพิ่ม "ตัวแบ่ง" ที่ด้านบนของไฟล์ซึ่งจะทำให้ rebase แตกที่จุดนั้น

break
pick <B's hash> <B's commit message>
pick <C's hash> <C's commit message>

เมื่อเปิดตัวgit rebaseแล้วตอนนี้จะหยุดที่จุด "พัก" ตอนนี้คุณสามารถแก้ไขไฟล์และสร้างคอมมิตได้ตามปกติ จากนั้นคุณสามารถดำเนินการต่อ rebase git rebase --continueด้วย สิ่งนี้อาจทำให้เกิดความขัดแย้งที่คุณต้องแก้ไข git rebase --abortหากคุณได้รับหายไปอย่าลืมคุณสามารถยกเลิกการใช้

กลยุทธ์นี้สามารถสรุปได้ทั่วไปเพื่อแทรกคอมมิตที่ใดก็ได้เพียงแค่ใส่ "แบ่ง" ตรงจุดที่คุณต้องการแทรกคอมมิต

git push -fหลังจากที่เขียนประวัติศาสตร์ไม่ลืมที่จะ คำเตือนตามปกติเกี่ยวกับบุคคลอื่นที่ดึงสาขาของคุณมาใช้


ขออภัยฉันมีปัญหาในการทำความเข้าใจว่า "การหลีกเลี่ยง rebase" เป็นอย่างไร คุณกำลังวิ่งอยู่rebaseที่นี่ ไม่มีความแตกต่างมากนักไม่ว่าคุณจะสร้างคอมมิตระหว่างการรีเบสหรือทำล่วงหน้า
BartoszKP

อ๊ะฉันหมายถึงการหลีกเลี่ยง "แฮ็กแก้ไข" ในระหว่างการสร้างฐานข้อมูลใหม่ฉันเดาว่าฉันใช้วลีนั้นไม่ดี
axerologementy

ขวา. คำตอบของฉันยังไม่ใช้คุณสมบัติ "แก้ไข" ของ rebase ถึงกระนั้นนี่ก็เป็นอีกแนวทางที่ถูกต้อง - ขอบคุณ! :-)
BartoszKP

นี่เป็นทางออกที่ดีที่สุด!
Theodore

6

คำตอบที่ดีมากมายที่นี่แล้ว ฉันแค่อยากจะเพิ่มโซลูชัน "no rebase" ใน 4 ขั้นตอนง่ายๆ


สรุป

git checkout A
git commit -am "Message for commit D"
git cherry-pick A..C
git branch -f master HEAD

คำอธิบาย

(หมายเหตุ: ข้อดีอย่างหนึ่งของโซลูชันนี้คือคุณจะไม่แตะต้องสาขาของคุณจนกว่าจะถึงขั้นตอนสุดท้ายเมื่อคุณแน่ใจ 100% ว่าคุณพอใจกับผลลัพธ์สุดท้ายคุณจึงมีขั้นตอน "การยืนยันล่วงหน้า" ที่สะดวกมากอนุญาตให้ทำการทดสอบ AB )


สถานะเริ่มต้น (ฉันสันนิษฐานว่าmasterเป็นชื่อสาขาของคุณ)

A -- B -- C <<< master <<< HEAD

1) เริ่มต้นด้วยการชี้ HEAD ไปที่ตำแหน่งที่ถูกต้อง

git checkout A

     B -- C <<< master
    /
   A  <<< detached HEAD

(ทางเลือกที่นี่แทนที่จะแยก HEAD เราสามารถสร้างสาขาชั่วคราวด้วยgit checkout -b temp Aซึ่งเราจะต้องลบเมื่อสิ้นสุดกระบวนการตัวแปรทั้งสองทำงานได้ตามที่คุณต้องการเนื่องจากทุกอย่างยังคงเหมือนเดิม)


2) สร้างคอมมิต D ใหม่ที่จะแทรก

# at this point, make the changes you wanted to insert between A and B, then

git commit -am "Message for commit D"

     B -- C <<< master
    /
   A -- D <<< detached HEAD (or <<< temp <<< HEAD)

3) จากนั้นนำสำเนาของการกระทำ B และ C ที่หายไปล่าสุด (จะเป็นบรรทัดเดียวกันหากมีการกระทำมากกว่านี้)

git cherry-pick A..C

# (if any, resolve any potential conflicts between D and these last commits)

     B -- C <<< master
    /
   A -- D -- B' -- C' <<< detached HEAD (or <<< temp <<< HEAD)

(การทดสอบ AB แบบสบาย ๆ ที่นี่หากจำเป็น)

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


4)ขึ้นอยู่กับการทดสอบของคุณระหว่างCและC'ว่าตกลงหรือเป็น KO

(อย่างใดอย่างหนึ่ง) 4-OK) สุดท้ายย้ายการอ้างอิงของmaster

git branch -f master HEAD

     B -- C <<< (B and C are candidates for garbage collection)
    /
   A -- D -- B' -- C' <<< master

(หรือ) 4-KO) ปล่อยให้masterไม่เปลี่ยนแปลง

หากคุณสร้างสาขาชั่วคราวให้ลบออกด้วยgit branch -d <name>แต่ถ้าคุณไปตามเส้นทาง HEAD ที่แยกออกมาคุณไม่จำเป็นต้องดำเนินการใด ๆ ในตอนนี้การคอมมิตใหม่จะมีสิทธิ์ได้รับการรวบรวมขยะหลังจากที่คุณHEADเชื่อมต่ออีกครั้งด้วยgit checkout master

ในทั้งสองกรณีนั้น (ตกลงหรือ KO) ณ จุดนี้ให้ชำระเงินmasterอีกครั้งเพื่อติดตั้งHEADใหม่

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