← All posts

Git Merge, Rebase & Squash — ตอนที่ 4: Interactive Rebase และ Rebase ขั้นสูง

Data-Sci & Digital HealthBasic InfoTech & Computing Nexus
Git Merge, Rebase & Squash — ตอนที่ 4: Interactive Rebase และ Rebase ขั้นสูง
On this page

บทคัดย่อ

การจัดการประวัติการทำงานให้มีความเป็นระเบียบก่อนการนำเสนอโค้ดมีความสำคัญอย่างยิ่งต่อการรักษาโครงสร้างของ commit ให้สามารถตรวจสอบและทำความเข้าใจได้ง่าย กระบวนการ interactive rebase เป็นเครื่องมือที่ช่วยให้นักพัฒนาสามารถเข้าไปปรับปรุง commit ย่อยได้ทีละรายการผ่านรายการคำสั่งต่างๆ ได้แก่ pick, reword, edit, squash, fixup และ drop รวมถึงสามารถสลับลำดับของการแก้ไขได้อย่างอิสระ นอกจากนี้ยังมีการเพิ่มประสิทธิภาพการทำงานผ่านแฟล็ก --autosquash เพื่อจัดการ commit ที่ต้องการรวมกันแบบอัตโนมัติ สำหรับสถานการณ์ที่มีการแตก branch ซ้อนทับกัน ผู้ใช้งานสามารถย้ายเฉพาะบางส่วนของ branch ไปยังฐานใหม่ได้ด้วยคำสั่ง rebase --onto หรือหากต้องการรักษารูปแบบโครงสร้างการ merge ดั้งเดิมไว้โดยไม่ให้กราฟแบนราบเป็นเส้นตรงก็สามารถประยุกต์ใช้แฟล็ก --rebase-merges ได้ รูปแบบการใช้งานขั้นสูงมักควบคู่ไปกับการทำ fast-forward merge ซึ่งช่วยให้ประวัติการทำงานเป็นเส้นตรงโดยที่ยังคงเก็บรายละเอียดของทุก commit ไว้ได้ หรือการประยุกต์ใช้ควบคู่กับ squash เพื่อรวบรวมประวัติให้เป็น commit เดียวอย่างเป็นขั้นตอนก่อนทำการผสาน เนื่องจากการทำงานเหล่านี้ถือเป็นการเขียนประวัติใหม่ทั้งหมด จึงต้องจำกัดการใช้งานไว้บน branch ส่วนตัวและใช้ --force-with-lease ทุกครั้งที่มีการอัปเดตข้อมูล บทความนี้รวบรวมกลไกเชิงลึกและคำสั่งที่จำเป็นเหล่านี้เพื่อเสริมสร้างความสามารถในการจัดการประวัติของโครงการอย่างเป็นระบบ


อินโฟกราฟิกสรุปเนื้อหา Git Merge, Rebase & Squash — ตอนที่ 4: Interactive Rebase และ Rebase ขั้นสูง
Visual summary · ภาพสรุป

ตอนที่ 4 นี้ต่อยอดจาก rebase พื้นฐานในตอนที่ 3 ไปสู่การใช้งานขั้นสูง เริ่มจาก interactive rebase ที่ให้เราจัดระเบียบ commit ทีละตัว (แก้ข้อความ รวม ลบ สลับลำดับ) ต่อด้วย autosquash, rebase --onto สำหรับย้ายเฉพาะบางส่วนของ branch ไปยังฐานใหม่, การพยายามรักษา merge topology ด้วย --rebase-merges และปิดท้ายด้วยสองแพตเทิร์นที่ใช้จริงบ่อยคือ rebase + fast-forward merge และ rebase + squash

เมื่ออ่านจบ คุณจะจัด history ของ feature branch ให้สะอาดก่อนเปิด PR ได้ และเข้าใจว่า rebase ขั้นสูงแต่ละคำสั่งเขียน กราฟ commit ใหม่อย่างไร ทุกอย่างในตอนนี้คือการ rewrite history จึงควรทำบน branch ส่วนตัวและใช้ git push --force-with-lease เมื่อ push ทับของเดิม

Interactive rebase: จัดระเบียบ commit ก่อนเปิด PR

Interactive rebase ใช้จัดระเบียบ commits ก่อนเปิดหรือ merge PR

git rebase -i HEAD~4

Git จะเปิดหน้า todo list ให้แก้ โดยแสดง commits ล่าสุด 4 ตัว:

pick a111111 Add login form
pick b222222 Fix typo
pick c333333 Fix validation
pick d444444 Change button text

เราแก้คำสั่งที่อยู่ด้านหน้าแต่ละ commit เพื่อบอก Git ว่าจะทำอะไรกับ commit นั้น มีคำสั่งหลักที่ต้องรู้ 6 ตัว

pick

เก็บ commit ไว้ตามเดิม ไม่เปลี่ยนอะไร

pick a111111 Add login form

reword

เก็บการเปลี่ยนแปลงไว้ แต่แก้เฉพาะ commit message

reword a111111 Add login form

edit

หยุดที่ commit นั้นเพื่อแก้ไขเนื้อหา (แก้ไฟล์จริง)

edit a111111 Add login form

เมื่อ rebase หยุดที่ commit นั้น ให้แก้ไฟล์แล้วใช้:

git add .
git commit --amend
git rebase --continue

squash

รวม commit นี้เข้ากับ commit ก่อนหน้า และเปิดให้รวมข้อความ commit ของทั้งสองตัวเข้าด้วยกัน

pick a111111 Add login form
squash b222222 Fix typo
squash c333333 Fix validation

ผลลัพธ์คือ commit เดียว

fixup

เหมือน squash แต่ทิ้ง commit message ของ commit ที่ถูก fixup ไป

pick a111111 Add login form
fixup b222222 Fix typo
fixup c333333 Fix validation

ผลลัพธ์จะใช้ message ของ a111111 เท่านั้น

drop

ลบ commit ออกจาก history ทั้งการเปลี่ยนแปลงและ message

drop b222222 Debug logging

สรุปคำสั่ง interactive rebase ทั้ง 6

คำสั่งเก็บการเปลี่ยนแปลง?เก็บ message?ใช้เมื่อ
pickเก็บเก็บคง commit ไว้ตามเดิม
rewordเก็บแก้ข้อความต้องการแก้ commit message อย่างเดียว
editเก็บ (หยุดให้แก้)เก็บต้องการแก้เนื้อหาไฟล์ใน commit นั้น
squashเก็บ (รวมกับตัวก่อนหน้า)รวมข้อความทั้งสองรวม commit เข้ากับตัวก่อนหน้า และเก็บทั้งสอง message
fixupเก็บ (รวมกับตัวก่อนหน้า)ทิ้งข้อความของตัวเองรวม commit เข้ากับตัวก่อนหน้า โดยไม่สนใจ message ของมัน
dropไม่เก็บไม่เก็บลบ commit ทิ้งทั้งตัว

เปลี่ยนลำดับ commit

นอกจากเปลี่ยนคำสั่ง เรายังสลับลำดับบรรทัดใน todo list ได้ด้วย

เดิม:

pick a111111 Add UI
pick b222222 Add tests
pick c333333 Add API

แก้เป็น:

pick c333333 Add API
pick a111111 Add UI
pick b222222 Add tests

Git จะ replay commits ตามลำดับใหม่ที่เราจัด อาจเกิด conflict ได้หาก commits พึ่งพากัน (เช่น commit หลังแก้ไฟล์ที่ commit ก่อนสร้างขึ้น)

Autosquash: fixup อัตโนมัติ

หากรู้ตั้งแต่ตอน commit ว่าต้องการยัด commit ใหม่เข้าไปรวมกับ commit เก่าตัวใดตัวหนึ่ง ให้สร้าง fixup commit ผูกกับ commit เป้าหมายไว้ก่อน

git commit --fixup=<commit-hash>

ตัวอย่าง:

git commit --fixup=a111111

จากนั้นสั่ง rebase พร้อม --autosquash:

git rebase -i --autosquash HEAD~5

Git จะย้าย fixup commit ไปวางไว้หลัง commit เป้าหมายและตั้งคำสั่งในบรรทัดนั้นเป็น fixup ให้อัตโนมัติ เราจึงไม่ต้องเลื่อนบรรทัดในหน้า todo list เอง

workflow ที่ใช้จริง:

git add .
git commit --fixup=a111111
git rebase -i --autosquash main

Rebase --onto: ย้ายเฉพาะบางส่วนของ branch

ใช้เมื่ออยากย้ายเฉพาะบางส่วนของ branch ไปยังฐานใหม่ ไม่ใช่ย้ายทั้งสาย

สมมติมีโครงสร้างนี้ โดย feature-b แตกออกมาจาก feature-a อีกที:

A B C D E F G H main feature-a feature-b
ก่อน rebase --onto: feature-b (G–H) ซ้อนอยู่บน feature-a (E–F) ซึ่งแตกออกจาก main ที่ C

ตอนนี้เราต้องการย้าย feature-b ให้หลุดออกจาก feature-a ไปต่อจาก main โดยตรง

git rebase --onto main feature-a feature-b

ผลลัพธ์ commit G และ H ถูกสร้างใหม่เป็น G' และ H' ต่อท้าย main ส่วน feature-a ยังอยู่ที่เดิม:

A B C D G′ H′ E F feature-b main feature-a
หลัง git rebase --onto main feature-a feature-b: G และ H ถูกสร้างใหม่เป็น G′ และ H′ ต่อจาก main (ที่ D) ส่วน feature-a ยังอยู่ที่เดิม

รูปแบบทั่วไปของคำสั่ง:

git rebase --onto <new-base> <old-base> <branch>

ความหมาย:

เอา commits ที่อยู่หลัง old-base บน branch ไปวางบน new-base

ในตัวอย่างข้างบน new-base คือ main, old-base คือ feature-a (จุดที่ feature-b แตกออกมา) และ branch คือ feature-b ดังนั้น commits ที่อยู่หลัง feature-a (คือ G และ H) จึงถูกยกไปวางบน main

Rebase ที่มี merge commit: --rebase-merges

rebase ปกติอาจทำให้โครงสร้าง merge เดิมกลายเป็นเส้นตรง เพราะ Git จะ replay เฉพาะ commit ธรรมดาและทิ้ง merge commit ไป

หากต้องการพยายามรักษา merge topology เดิมเอาไว้:

git rebase --rebase-merges main

หรือเขียนย่อ:

git rebase -r main

เหมาะกับ branch ที่มี sub-branches หรือมี merge commits ภายในอยู่แล้ว และเราต้องการให้โครงสร้างการ merge ยังคงรูปเดิมหลัง rebase

Rebase + fast-forward merge

นี่คือแพตเทิร์นที่รวม rebase เข้ากับ fast-forward merge เพื่อให้ได้ history เส้นตรงโดยยังเก็บ commit ย่อยของ feature ไว้ครบ

เริ่มจาก feature branch แตกจาก C ในขณะที่ main เดินหน้าไปถึง G:

          D---E
         /
A---B---C---F---G  main

ทำสองขั้น rebase feature ให้ทัน main ก่อน แล้วค่อย fast-forward:

git switch feature
git rebase main

git switch main
git merge --ff-only feature

ผลลัพธ์เป็นเส้นตรง โดย D และ E ถูกสร้างใหม่เป็น D' และ E' ต่อท้าย G:

A B C F G D′ E′
Rebase + fast-forward merge: ได้เส้นตรงเส้นเดียว A–B–C–F–G–D′–E′ โดย D และ E ถูกสร้างใหม่เป็น D′ และ E′ — ไม่มี merge commit

วิธีนี้ให้ผลสามอย่างพร้อมกัน:

Rebase + squash

แพตเทิร์นนี้จัด history ภายใน branch ให้สะอาดด้วย interactive rebase ก่อน แล้วค่อย merge เข้ามา ต่างจาก squash merge ตรงที่เราควบคุมการรวม commit เองบน branch

จัด branch ก่อนด้วย interactive rebase บน main:

git switch feature/login
git rebase -i main

รวม commits ย่อยเข้ากับ commit หลักด้วย fixup:

pick   Add login feature
fixup  Fix typo
fixup  Fix CSS
fixup  Fix tests

จากนั้น fast-forward merge เข้า main:

git switch main
git merge --ff-only feature/login

ผลลัพธ์บน main เหลือ commit เดียวคือ S:

A B C S main
Rebase + squash: หลังจัด branch ด้วย rebase -i แล้ว fast-forward เข้า main จะเหลือ commit เดียวคือ S

ผลลัพธ์คล้าย squash merge แต่ต่างกันที่ branch ถูกจัด history ให้สะอาดก่อนแล้วจึงเลื่อน pointer เข้ามาแบบ fast-forward ไม่ใช่ให้ Git รวมทั้ง branch เป็น commit เดียวตอน merge

Key takeaways

0
ถึงนักอ่านชาวไทยและต่างชาติทำความเข้าใจบริบททางการแพทย์ของผมอ่านต่อ →ถึงนักอ่านชาวไทยและต่างชาติทำความเข้าใจเนื้อหาของผมที่นอกเหนือจากการแพทย์อ่านต่อ →

ความคิดเห็น

ยังไม่มีความคิดเห็น มาเป็นคนแรกกันเลย

เข้าสู่ระบบเพื่อแสดงความคิดเห็น