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


88

เราได้ยินว่าหนึ่งไม่ควรตีพิมพ์ rebase ทำงานว่ามันเป็นอันตราย ฯลฯ แต่ฉันไม่ได้เห็นสูตรใด ๆ ที่โพสต์สำหรับวิธีการจัดการกับสถานการณ์ที่เกิดขึ้นในกรณีที่ rebase ถูกตีพิมพ์

ตอนนี้โปรดทราบว่าสิ่งนี้เป็นไปได้จริง ๆ หากที่เก็บนั้นถูกโคลนโดยกลุ่มคนที่รู้จัก (และควรมีขนาดเล็ก) เท่านั้นดังนั้นใครก็ตามที่ผลักดันการปรับฐานหรือรีเซ็ตสามารถแจ้งให้ทุกคนทราบว่าพวกเขาจะต้องให้ความสนใจในครั้งต่อไป ดึง (!)

วิธีแก้ปัญหาที่ชัดเจนวิธีหนึ่งที่ฉันได้เห็นจะใช้ได้ผลหากคุณไม่มีข้อผูกมัดในท้องถิ่นfooและได้รับการปรับใหม่:

git fetch
git checkout foo
git reset --hard origin/foo

สิ่งนี้จะทิ้งสถานะท้องถิ่นที่fooสนับสนุนประวัติศาสตร์ของมันตามที่เก็บระยะไกล

แต่จะจัดการกับสถานการณ์ได้อย่างไรหากมีการเปลี่ยนแปลงในท้องถิ่นอย่างมากในสาขานั้น


+1 สำหรับสูตรเคสง่ายๆ เหมาะอย่างยิ่งสำหรับการซิงโครไนซ์ส่วนบุคคลระหว่างเครื่องโดยเฉพาะอย่างยิ่งหากมีระบบปฏิบัติการที่แตกต่างกัน เป็นสิ่งที่ควรกล่าวถึงในคู่มือ
Philip Oakley

git pull --rebase && git pushสูตรที่เหมาะสำหรับการประสานส่วนบุคคล หากคุณทำงานmasterเพียงอย่างเดียวสิ่งนี้จะใกล้เคียงกับสิ่งที่ถูกต้องสำหรับคุณอย่างไม่ท้อถอยแม้ว่าคุณจะปฏิเสธและผลักดันในอีกด้านหนึ่งก็ตาม
Aristotle Pagaltzis

เนื่องจากฉันกำลังซิงโครไนซ์และพัฒนาระหว่างพีซีและเครื่อง Linux ฉันจึงพบว่าการใช้สาขาใหม่สำหรับ rebase / การอัปเดตทุกครั้งทำงานได้ดี ตอนนี้ฉันยังใช้ตัวแปรgit reset --hard @{upstream}ที่ฉันรู้แล้วว่าคาถา refspec สำหรับ "ลืมสิ่งที่ฉันมี / มีใช้สิ่งที่ฉันดึงมาจากรีโมท" ดูความคิดเห็นสุดท้ายของฉันที่stackoverflow.com/a/15284176/717355
Philip Oakley

คุณจะสามารถใช้ Git2.0 เพื่อค้นหาต้นกำเนิดเก่าของสาขาของคุณได้ (ก่อนที่สาขาต้นน้ำจะถูกเขียนใหม่ด้วย a push -f): ดูคำตอบของฉันด้านล่าง
VonC

คำตอบ:


75

การกลับมาซิงค์หลังจาก rebase แบบพุชนั้นไม่ได้ซับซ้อนขนาดนั้นในกรณีส่วนใหญ่

git checkout foo
git branch old-foo origin/foo # BEFORE fetching!!
git fetch
git rebase --onto origin/foo old-foo foo
git branch -D old-foo

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

การพูดซ้ำก็เหมือนความรุนแรงถ้ามันไม่ช่วยแก้ปัญหาของคุณคุณก็แค่ต้องการมันมากขึ้น ☺

คุณสามารถทำได้โดยไม่ต้องใช้บุ๊กมาร์กแน่นอนหากคุณค้นหาorigin/fooรหัสการคอมมิตpre-rebase และใช้สิ่งนั้น

นี่คือวิธีจัดการกับสถานการณ์ที่คุณลืมทำบุ๊กมาร์กก่อนดึงข้อมูล ไม่มีอะไรสูญหาย - คุณต้องตรวจสอบ reflog สำหรับสาขาระยะไกล:

git reflog show origin/foo | awk '
    PRINT_NEXT==1 { print $1; exit }
    /fetch: forced-update/ { PRINT_NEXT=1 }'

การดำเนินการนี้จะพิมพ์รหัสคอมมิตที่origin/fooชี้ไปก่อนการดึงข้อมูลล่าสุดที่เปลี่ยนประวัติ

จากนั้นคุณสามารถทำได้ง่ายๆ

git rebase --onto origin/foo $commit foo

11
บันทึกย่อ: ฉันคิดว่ามันค่อนข้างใช้งานง่าย แต่ถ้าคุณไม่รู้จัก awk ดี ... ซับเดียวนั้นก็แค่มองผ่านผลลัพธ์ของgit reflog show origin/fooบรรทัดแรกที่พูดว่า "fetch: forced-update"; นั่นคือสิ่งที่บันทึก git เมื่อการดึงข้อมูลทำให้สาขาระยะไกลทำอะไรก็ได้ยกเว้นกรอไปข้างหน้า (คุณสามารถทำได้ด้วยมือเช่นกันการอัปเดตแบบบังคับน่าจะเป็นสิ่งล่าสุด)
Cascabel

2
มันไม่มีอะไรเหมือนกับความรุนแรง ความรุนแรงเป็นเรื่องสนุกเป็นครั้งคราว
Iolo

5
@iolo True rebasing เป็นเรื่องสนุกเสมอ
Dan Bechard

1
เช่นเดียวกับความรุนแรงมักจะหลีกเลี่ยงการตำหนิ แต่มีเงื่อนงำอย่างไร
Bob Stein

2
หลีกเลี่ยงการผลักดันฐานข้อมูลใหม่ที่ผู้อื่นจะได้รับผลกระทบ
Aristotle Pagaltzis

11

ฉันจะบอกว่าการกู้คืนจากส่วนrebase ต้นน้ำของหน้าคน git-rebase ครอบคลุมทั้งหมดนี้

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


4
อ่ามันทำ แต่ตอนนี้ฉันเข้าใจสิ่งที่มันพูด แต่ฉันก็ไม่เคยมีมาก่อนก่อนที่จะคิดออกด้วยตัวเอง และไม่มีสูตรตำราอาหาร (อาจจะถูกต้องในเอกสารดังกล่าว) ฉันจะกล่าวด้วยว่าการเรียกฮาร์ดเคสนั้นเป็น FUD ฉันขอส่งว่าประวัติที่เขียนซ้ำนั้นสามารถจัดการได้เล็กน้อยในระดับของการพัฒนาภายในองค์กรส่วนใหญ่ วิธีที่เชื่อโชคลางในการปฏิบัติเรื่องนี้ทำให้ฉันรำคาญอยู่เสมอ
Aristotle Pagaltzis

4
@Aristotle: คุณคิดถูกแล้วที่สามารถจัดการได้มากเนื่องจากนักพัฒนาทุกคนรู้วิธีใช้คอมไพล์และคุณสามารถสื่อสารกับนักพัฒนาทุกคนได้อย่างมีประสิทธิภาพ ในโลกที่สมบูรณ์แบบนั่นคือจุดจบของเรื่องราว แต่มีโครงการจำนวนมากที่ใหญ่พอที่การสร้างฐานข้อมูลใหม่เป็นสิ่งที่น่ากลัว (แล้วก็มีสถานที่ต่างๆเช่นที่ทำงานของฉันซึ่งนักพัฒนาส่วนใหญ่ไม่เคยได้ยินเรื่อง rebase มาก่อน) ฉันคิดว่า "ไสยศาสตร์" เป็นเพียงวิธีการให้คำแนะนำทั่วไปที่ปลอดภัยที่สุดเท่าที่จะเป็นไปได้ ไม่มีใครอยากเป็นคนที่ทำให้เกิดหายนะใน repo ของคนอื่น
Cascabel

2
ใช่ฉันเข้าใจแรงจูงใจ และฉันเห็นด้วยกับมันอย่างเต็มที่ แต่มีโลกที่แตกต่างกันระหว่าง“ อย่าลองทำถ้าคุณไม่เข้าใจผลที่ตามมา” และ“ คุณไม่ควรทำอย่างนั้นเพราะมันชั่วร้าย” และสิ่งนี้คนเดียวที่ฉันมีปัญหา เป็นการดีกว่าเสมอที่จะออกคำสั่งมากกว่าการปลูกฝังความกลัว
Aristotle Pagaltzis

@Aristotle: เห็นด้วย ฉันพยายามโน้มน้าวไปสู่จุดจบ "ให้แน่ใจว่าคุณรู้ว่าคุณกำลังทำอะไรอยู่" แต่โดยเฉพาะอย่างยิ่งทางออนไลน์ฉันพยายามให้น้ำหนักเพียงพอเพื่อให้ผู้เยี่ยมชมทั่วไปจาก Google รับทราบ คุณพูดถูกมากมันควรจะกระชับลง
Cascabel

11

เริ่มต้นด้วย git 1.9 / 2.0 Q1 2014 คุณจะไม่ต้องทำเครื่องหมายที่มาของสาขาก่อนหน้าก่อนที่จะทำการรีฐานข้อมูลบนสาขาต้นน้ำที่เขียนใหม่ตามที่อธิบายไว้ในคำตอบของAristotle Pagaltzis : ดูที่กระทำ 07d406bและกระทำ d96855f :

หลังจากทำงานกับtopicสาขาที่สร้างขึ้นgit checkout -b topic origin/masterประวัติของสาขาการติดตามระยะไกลorigin/masterอาจได้รับการกรอกลับและสร้างขึ้นใหม่ซึ่งนำไปสู่ประวัติของรูปร่างนี้:

                   o---B1
                  /
  ---o---o---B2--o---o---o---B (origin/master)
          \
           B3
            \
             Derived (topic)

ที่origin/masterใช้ในการกระทำที่จุดB3, B2, B1และตอนนี้ก็ชี้ที่Bและคุณtopicสาขาเริ่มต้นด้านบนของมันกลับมาเมื่ออยู่ที่ origin/masterB3

โหมดนี้ใช้ reflog ของorigin/masterเพื่อค้นหาB3เป็นจุดแยกเพื่อให้topicสามารถ rebased ที่ด้านบนของการปรับปรุงorigin/masterโดย:

$ fork_point=$(git merge-base --fork-point origin/master topic)
$ git rebase --onto origin/master $fork_point topic

นั่นคือเหตุผลที่git merge-baseคำสั่งมีตัวเลือกใหม่:

--fork-point::

ค้นหาจุดที่สาขา (หรือมีประวัติใด ๆ ที่นำไปสู่การ<commit>) คดเคี้ยวจากสาขาอื่น (หรือการอ้างอิงใด ๆ<ref>)
สิ่งนี้ไม่เพียงแค่มองหาบรรพบุรุษร่วมของทั้งสองคนเท่านั้น แต่ยังคำนึงถึง reflog <ref>เพื่อดูว่าประวัติศาสตร์ที่นำไปสู่การ<commit>แยกจากชาติก่อนของสาขา<ref>หรือไม่


ว่า " git pull --rebaseคำสั่ง" คำนวณจุดแยกของสาขาที่ถูก rebased ใช้รายการ reflog ของ " baseสาขา" (โดยปกติจะเป็นสาขาที่ห่างไกลการติดตาม) การทำงานของสาขาอยู่บนพื้นฐานในการสั่งซื้อที่จะรับมือกับกรณีที่ "ฐาน" สาขาได้รับการกรอใหม่และสร้างใหม่

ตัวอย่างเช่นหากประวัติดูเหมือนที่ไหน:

  • เคล็ดลับในปัจจุบันของ " baseสาขา" ที่Bแต่ก่อนหน้านี้ดึงข้อมูลข้อสังเกตว่าปลายที่เคยเป็นB3แล้วB2และจากนั้นB1 ก่อนที่จะเดินทางไปยังปัจจุบันกระทำและ
  • สาขาที่ถูก rebased ด้านบนของ "ฐาน" ล่าสุดอยู่บนพื้นฐานการกระทำB3,

ก็พยายามที่จะหาB3โดยจะผ่านการส่งออกของ " git rev-list --reflog base" (คือB, B1, B2, B3) จนกว่าจะพบการกระทำที่เป็นบรรพบุรุษของปลายปัจจุบัน " Derived (topic)"

ภายในเรามีget_merge_bases_many()ที่สามารถคำนวณสิ่งนี้ได้ในครั้งเดียว
เราต้องการการผสานฐานระหว่างDerivedและการผสานรวมที่สมมติขึ้นซึ่งจะเกิดจากการรวมเคล็ดลับทางประวัติศาสตร์ทั้งหมดของ " base (origin/master)"
เมื่อมีการคอมมิตดังกล่าวเราควรได้ผลลัพธ์เดียวซึ่งตรงกับหนึ่งในรายการ reflog ของ " base"


Git 2.1 (Q3 2014) จะเพิ่มทำให้คุณลักษณะนี้มีประสิทธิภาพมากขึ้น: ดูการกระทำ 1e0dacdโดยJohn Keeping ( johnkeeping)

จัดการกับสถานการณ์ที่เรามีโทโพโลยีต่อไปนี้อย่างถูกต้อง:

    C --- D --- E  <- dev
   /
  B  <- master@{1}
 /
o --- B' --- C* --- D*  <- master

ที่ไหน:

  • B'เป็นรุ่นที่ได้รับการแก้ไขแล้วของBที่ไม่ได้แพทช์เหมือนกันกับB;
  • C*และD*เป็นแพทช์เหมือนกันCและDตามลำดับและขัดแย้งกันทางข้อความหากนำไปใช้ในลำดับที่ไม่ถูกต้อง
  • Eขึ้นอยู่ textually Dบน

ผลที่ถูกต้องของการgit rebase master devเป็นสิ่งที่Bถูกระบุว่าเป็นส้อมจุดdevและmasterเพื่อให้C, D, Eเป็นกระทำที่จะต้องมีการย้อนสู่master; แต่CและDมีแพทช์เหมือนกันกับC*และD*และเพื่อให้สามารถปรับตัวลดลงเพื่อให้ผลลัพธ์ที่ได้คือ:

o --- B' --- C* --- D* --- E  <- dev

หากไม่ได้ระบุจุดส้อมการเลือกBไปยังสาขาที่มีB'ผลลัพธ์ในความขัดแย้งและหากระบุการกระทำที่เหมือนกันของแพทช์ไม่ถูกต้องการเลือกCไปยังสาขาที่มีD(หรือเทียบเท่าD*) จะทำให้เกิดความขัดแย้ง


ว่า " --fork-pointโหมด" ของ " git rebase" ถดถอยเมื่อคำสั่งที่ถูกเขียนใหม่ใน C ย้อนกลับไปในยุค 2.20 ซึ่งได้รับการแก้ไขด้วย Git 2.27 (Q2 2020)

ดูกระทำ f08132f (9 ธันวาคม 2019) โดยJunio C Hamano (gitster )
(ผสานโดยJunio ​​C Hamano - gitster-ในการกระทำ fb4175b , 27 มี.ค. 2020)

rebase: --fork-pointการแก้ไขการถดถอย

ลงนามโดย: Alex Torok
[jc: ปรับปรุงการแก้ไขและใช้การทดสอบของ Alex]
ลงนามโดย: Junio ​​C Hamano

" git rebase --fork-point master" เคยใช้งานได้ตามที่เรียกภายใน " git merge-base --fork-point" ซึ่งรู้วิธีจัดการ refname สั้น ๆ และย่อให้เป็น refname แบบเต็มก่อนที่จะเรียกget_fork_point()ฟังก์ชันพื้นฐาน

สิ่งนี้ไม่เป็นความจริงอีกต่อไปหลังจากที่เขียนคำสั่งใหม่ในภาษา C เนื่องจากการเรียกภายในที่โทรโดยตรงไปยังget_fork_point()ไม่ได้ลดการอ้างอิงสั้น ๆ

ย้ายอาร์กิวเมนต์ "dwim the refname ไปยัง refname แบบเต็ม" ที่ใช้ใน "git merge-base" ไปยังget_fork_point()ฟังก์ชันพื้นฐานเพื่อให้ผู้เรียกใช้ฟังก์ชันอื่นในการใช้ "git rebase" ทำงานในลักษณะเดียวกันในการแก้ไข การถดถอยนี้


1
โปรดทราบว่าตอนนี้ git push --force สามารถทำได้ (git 1.8.5) อย่างรอบคอบมากขึ้น: stackoverflow.com/a/18505634/6309
VonC
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.