Merge vs Rebase
ความต่างระหว่าง merge (เก็บประวัติเดิม + merge commit) กับ rebase (เขียนประวัติใหม่เป็นเส้นตรง) พร้อม golden rule และการใช้ force push ให้ปลอดภัย
ใน lesson ที่แล้วคุณเห็น merge รวม 2 branch เป็นหนึ่งโดยสร้าง merge commit ที่มี parent 2 ตัว แต่ Git มีวิธีรวม branch อีกแบบที่ให้ผลลัพธ์ต่างกันคือ rebase
ทั้งสองวิธีได้ผลเหมือนกันในแง่ “เอา work ของ feature ไปรวมกับ main” แต่ประวัติที่ได้ต่างกันชัดเจน ทำความเข้าใจความต่างจะช่วยให้เลือกใช้ถูกที่
ลองเทียบทั้งสองแบบ
ด้านล่างคือ widget เดียวกัน แต่ให้เลือกกลยุทธ์ได้ 2 แบบ ลองกดทั้ง merge และ rebase ดูว่ากราฟต่างกันยังไง reset ก่อนเปลี่ยนทางเลือกได้
d84c7f1 ส่วน feature แยกออกไปตั้งแต่ 5f2a8c9 และมี commit ใหม่ 2 อัน ทั้งสองฝั่งเดินหน้าคนละทาง ลองเลือกกลยุทธ์แล้วกดปุ่มดูว่าผลเป็นยังไงRebase ทำอะไรกันแน่
ขั้นตอนของ rebase (โดยย่อ):
- Git หา common ancestor (commit “ต้นน้ำ” ที่ 2 branch แยกออกจากกัน) ของ feature กับ main
- หยิบ commit ของ feature ทุกก้อนตั้งแต่ common ancestor มาวางพัก
- ย้าย base ของ feature ไปที่ main tip (นี่คือที่มาของชื่อ re-base)
- Replay (เล่นซ้ำ คือเอา diff ของแต่ละ commit ไปใช้บน base ใหม่) commit ทีละก้อน ได้ hash ใหม่ทุกก้อนเพราะ parent เปลี่ยน
ผลคือประวัติเป็นเส้นตรง เหมือนคุณทำ feature ต่อจาก main ใหม่เลยตั้งแต่ต้น ไม่มี merge commit ไม่มีรูปเพชรในกราฟ
เทียบข้อดี-ข้อเสีย
- เก็บประวัติเดิมครบ + merge commit เชื่อม
- เห็นว่า feature เกิดจาก branch ไหน
- ปลอดภัย ทำเมื่อไหร่ก็ได้
- ประวัติรกถ้าทีมใหญ่ merge บ่อย
- ประวัติเส้นตรงสะอาด อ่านง่าย
- เขียนประวัติใหม่ hash เปลี่ยนทุกก้อน
- commit ที่ push แล้ว ห้ามใช้ (golden rule)
เมื่อไหร่ต้อง force push + ใช้แบบไหน
Git push ธรรมดาทำงานแบบ fast-forward only แปลไทยคือ “ต่อของใหม่ท้ายของเก่าเท่านั้น ทับของเก่าไม่ได้”
ถ้า history ของ local ไม่ต่อเนื่อง กับ remote (เช่นหลัง rebase ที่เพิ่งเรียน หรือ git commit --amend ที่แก้ commit ล่าสุดให้ได้ hash ใหม่) Git จะ reject ทันทีพร้อม error “non-fast-forward”
Scenario เห็นภาพ: commit --amend
ลองนึกภาพง่ายๆ คุณเพิ่ง push commit ที่ message ว่า "fix login" ขึ้น remote แล้วรู้ว่า message ผิด อยากเปลี่ยนเป็น "fix: login button disabled"
วิธีแก้คือ git commit --amend -m "fix: login button disabled" (แก้ commit ล่าสุดโดยไม่สร้างก้อนใหม่ แต่ commit จะได้ hash ใหม่)
แต่ตอน push ต่อ:
git push→ reject “non-fast-forward” ติดตาย ทำอะไรไม่ได้git push --force→ เขียนทับ remote ทันทีไม่ว่ามีอะไรเกิดขึ้น อันตราย: ถ้ามีเพื่อน push commit เพิ่มมาระหว่างที่คุณ amend งานเพื่อนจะหายไปกับการทับของคุณgit push --force-with-lease→ ทับได้ แต่เช็คก่อน ว่า remote ยังอยู่ที่จุดที่คุณเห็นล่าสุดไหม- ถ้า ไม่มีใครแตะ remote → ผ่าน ทับของเก่าเรียบร้อย
- ถ้า มีคนอื่น push เข้ามา → reject ปกป้องงานเพื่อน บังคับให้คุณ
git fetchดูของใหม่ก่อนทับ
ทีมส่วนใหญ่ใช้แบบไหน
- Rebase ก่อน PR: หลายทีมให้ developer rebase feature branch ให้ทันสมัยกับ main ก่อน เปิด PR เพื่อประวัติสะอาด
- Squash + merge: GitHub เสนอ option นี้ รวมทุก commit ใน PR ให้เหลือ 1 ก้อน แล้วค่อย merge เข้า main
- Pure merge: บางทีม (เช่น Linux kernel) ยืน merge อย่างเดียว เพื่อเห็นทุก commit จริงของแต่ละคน
สรุป
- Merge กับ rebase รวม branch ได้เหมือนกัน แต่ประวัติต่างกัน
- Merge เก็บประวัติเดิม + เพิ่ม merge commit (รูปเพชร)
- Rebase เขียนประวัติใหม่ให้เป็นเส้นตรง (hash ใหม่ทุก commit)
- อย่า rebase commit ที่ push ไปแล้ว (golden rule)
- ทีมส่วนใหญ่ rebase ก่อน PR เพื่อประวัติสะอาด
lesson ถัดไปเราจะเริ่มเรื่อง remote + GitHub กันจริงจัง ว่า git บนเครื่องคุณคุยกับ repo ออนไลน์ยังไง push/pull/fetch คืออะไร