← All posts

Git Merge, Rebase & Squash — ตอนที่ 5: Conflict, ours/theirs และ Merge Strategies

Data-Sci & Digital HealthBasic InfoTech & Computing Nexus
Git Merge, Rebase & Squash — ตอนที่ 5: Conflict, ours/theirs และ Merge Strategies
On this page

บทคัดย่อ

ระบบควบคุมเวอร์ชันมักจะสามารถรวมประวัติการทำงานได้อย่างอัตโนมัติ แต่เมื่อมีการแก้ไขข้อมูลในบรรทัดเดียวกันบน branch ที่แตกต่างกัน Git จะหยุดทำงานและคืนสิทธิ์การตัดสินใจในการจัดการข้อขัดแย้งให้กับผู้ใช้งาน ซึ่งการทำความเข้าใจกลไกเหล่านี้มีความสำคัญอย่างยิ่งต่อการป้องกันความผิดพลาด การผสานโค้ดผ่านการทำ merge มักจะหยุดเพื่อให้แก้ไขข้อขัดแย้งเพียงครั้งเดียว ในขณะที่กระบวนการ rebase จะนำ commit มาทำซ้ำทีละรายการ ส่งผลให้ข้อขัดแย้งเดิมอาจปรากฏขึ้นซ้ำได้หลายรอบ ในการจัดการไฟล์ที่ขัดแย้งกัน ผู้ใช้งานสามารถเลือกเนื้อหาฝ่ายใดฝ่ายหนึ่งผ่านคำสั่ง ours หรือ theirs ได้ ทว่าความหมายของคำสั่งเหล่านี้จะถูกสลับกันอย่างสิ้นเชิงเมื่ออยู่ภายใต้กระบวนการ rebase โดยที่ ours จะหมายถึงฐานของ branch ที่กำลังถูกสร้างขึ้นใหม่แทน นอกจากนี้ ผู้ใช้งานยังต้องทำความเข้าใจความแตกต่างระหว่างวิธีการรวมโค้ดและกลไกการคำนวณเบื้องหลังที่เรียกว่า merge strategy โดยปกติ Git จะใช้กลยุทธ์ ort เป็นค่าเริ่มต้นสำหรับการรวมสอง branch และใช้ octopus สำหรับการรวมหลาย branch ที่ไม่มีข้อขัดแย้ง รวมถึงใช้ subtree สำหรับโครงสร้างที่แตกต่างกัน จุดที่ต้องระมัดระวังเป็นพิเศษคือการแยกแยะระหว่างคำสั่ง -s ours ซึ่งเป็นกลยุทธ์ที่ละทิ้งเนื้อหาจาก branch อื่นอย่างสิ้นเชิง กับการใช้ตัวเลือก -X ours หรือ -X theirs ซึ่งทำหน้าที่ตัดสินข้อขัดแย้งแบบอัตโนมัติแต่เสี่ยงต่อการทำให้โค้ดสำคัญสูญหายไปโดยไม่แจ้งเตือน บทความนี้อธิบายกลไกและกลยุทธ์เบื้องหลังการจัดการข้อขัดแย้งเหล่านี้ เพื่อช่วยให้นักพัฒนาสามารถวินิจฉัยและเลือกใช้วิธีการรวมโค้ดได้อย่างถูกต้องและปลอดภัย


อินโฟกราฟิกสรุปเนื้อหา Git Merge, Rebase & Squash — ตอนที่ 5: Conflict, ours/theirs และ Merge Strategies
Visual summary · ภาพสรุป

ตอนที่ 1 ถึง 4 เราสมมติว่าทุกอย่างราบรื่น — Git รวมงานให้อัตโนมัติได้ ตอนนี้เราจะพูดถึงกรณีที่ Git ทำเองไม่ได้ เมื่อสอง branches แก้บรรทัดเดียวกัน Git จะหยุดและโยนการตัดสินใจกลับมาให้เรา และวิธีที่ conflict ปรากฏ (รวมถึงวิธีแก้) จะต่างกันระหว่าง merge กับ rebase จากนั้นเราจะเปิดดูกลไกที่ Git ใช้รวม branch: ความต่างระหว่าง merge method (สิ่งที่เราเลือกใน UI) กับ merge strategy (algorithm ที่อยู่เบื้องหลัง) พร้อมกับ strategy และ strategy option ที่บางครั้งต้องรู้ — ort, octopus, -s ours, -X ours/-X theirs และ subtree

เมื่ออ่านจบ คุณจะแก้ conflict ได้ทั้งตอน merge และ rebase ใช้ ours/theirs ได้โดยไม่โดนความหมายที่กลับด้านตอน rebase หลอก และแยกความต่างของสองคำสั่งที่หน้าตาอันตรายคล้ายกันแต่ทำงานต่างกันมากคือ -s ours กับ -X ours

Conflict ตอน Merge

สมมติทั้งสอง branches แก้บรรทัดเดียวกัน เริ่ม merge ตามปกติ:

git switch main
git merge feature/login

Git ตัดสินใจแทนเราไม่ได้ว่าเวอร์ชันไหนควรชนะ จึงแสดง conflict:

CONFLICT (content): Merge conflict in app.js

เหตุผลมองเห็นได้จากกราฟ — main และ feature/login ต่างแก้บรรทัดเดียวกันหลังแยกกันที่ C:

D E A B C F feature/login (edits the same title line) main (edits the title line)
ทั้งสอง branch แก้บรรทัดเดียวกันหลังแยกจากกันที่ C เมื่อ merge feature/login เข้า main จึงเกิด conflict ใน app.js

ในไฟล์ Git จะทำเครื่องหมาย conflict ไว้:

<<<<<<< HEAD
const title = "Main version";
=======
const title = "Feature version";
>>>>>>> feature/login

แก้ให้เหลือเวอร์ชันที่ต้องการจริง ๆ:

const title = "Final version";

จากนั้น stage ไฟล์และปิดงาน merge:

git add app.js
git commit

บาง Git version ปิดงานด้วย:

git merge --continue

ยกเลิก merge

ถ้าอยากถอยกลับไปยังจุดก่อนเริ่ม:

git merge --abort

Conflict ตอน Rebase

คราวนี้เริ่ม rebase จากฝั่ง feature branch:

git switch feature/login
git rebase main

เมื่อ conflict ทำให้ rebase หยุด ให้ดูสถานะก่อน:

git status

แก้ไฟล์ จากนั้นไปต่อ:

git add app.js
git rebase --continue

ข้าม commit ปัจจุบัน

git rebase --skip

ใช้เมื่อมั่นใจว่า commit ที่กำลัง replay นั้นไม่จำเป็นแล้ว

ยกเลิก rebase ทั้งหมด

git rebase --abort

กลับไปเหมือนตอนก่อนเริ่ม rebase ทุกประการ

ทำไม rebase อาจ conflict หลายรอบ

นี่คือความต่างสำคัญเชิงความคิด rebase จะ replay commits ทีละ commit ความขัดแย้งพื้นฐานเดียวกันจึงอาจโผล่มาทุก commit ที่ replay ในขณะที่ merge มักให้แก้ conflict แค่รอบเดียว:

merge — resolve once D E F A B C G M one conflict round, at M rebase — replay commit by commit A B C G D′ E′ F′ a conflict can appear at each replayed commit
merge แก้ conflict รอบเดียวที่ merge commit M ส่วน rebase จะ replay D, E, F ทีละ commit ต่อจาก G จึงเกิด conflict ได้ทุก commit ที่ replay

ดังนั้น feature branch อายุยาวที่ชนกับการเปลี่ยนแปลงหนัก ๆ บน main อาจทำให้ต้องแก้เรื่องเดิมซ้ำหลายรอบตอน rebase — เป็นสิ่งที่ต้องชั่งน้ำหนักเวลาเลือกระหว่างสองวิธี

ours และ theirs (และทำไม rebase ถึงกลับด้าน)

เมื่อเจอ conflict เราสามารถเลือกฝั่งของไฟล์ทั้งฝั่งได้แทนการแก้มือ ตอน merge:

git checkout --ours app.js

หมายถึงใช้ไฟล์จาก branch ปัจจุบัน ส่วน:

git checkout --theirs app.js

หมายถึงใช้ไฟล์จาก branch ที่กำลัง merge เข้ามา Git รุ่นใหม่ทำแบบเดียวกันได้ผ่าน restore:

git restore --ours app.js
git restore --theirs app.js

จากนั้น stage และไปต่อตามปกติ:

git add app.js
git merge --continue

การกลับด้านตอน Rebase — คำเตือนด้านความปลอดภัย

ตอน rebase ความหมายของ ours และ theirs จะดูสลับกับ intuition ที่เราสร้างไว้ตอน merge

เพราะ rebase นำ commits ของเราไป replay ต่อบน branch ฐาน Git จึงถือว่าฝั่งฐานคือฝั่งที่กำลังสร้างต่อ:

ดังนั้นควรเปิดไฟล์จริงและตรวจเนื้อหาเสมอ ไม่ควรเลือกฝั่งตามชื่อโดยไม่ดูเนื้อหา

Merge strategy กับ Merge method ไม่ใช่สิ่งเดียวกัน

Merge method

สิ่งที่คนส่วนใหญ่เลือกใน UI หรือในระดับบนสุด:

Fast-forward
Merge commit
Squash merge
Rebase and merge

Merge strategy

Algorithm ภายในที่ Git ใช้คำนวณการรวม เลือกด้วย:

git merge -s <strategy>

method คือ อยากให้ history หน้าตาเป็นอย่างไร ส่วน strategy คือ Git คำนวณ tree ที่รวมแล้วอย่างไร เป็นการเลือกคนละอย่าง และส่วนใหญ่เราแค่เลือก method แล้วปล่อยให้ Git เลือก strategy เอง

ort strategy (ค่า default สมัยใหม่)

เป็น strategy หลักสำหรับ merge สอง branches:

git merge -s ort feature/login

ปกติไม่จำเป็นต้องพิมพ์เอง เพราะ Git เลือกให้อัตโนมัติ ort ย่อมาจากแนวคิด "Ostensibly Recursive's Twin" เหมาะกับ:

octopus merge: รวมมากกว่าสอง branch

strategy แบบ octopus ใช้ merge มากกว่าสอง branches พร้อมกัน:

git merge feature-a feature-b feature-c
M feature-a feature-b feature-c
octopus merge: merge commit เดียว (M) รวม feature-a, feature-b และ feature-c พร้อมกันในครั้งเดียว

มักใช้รวม branches ที่ไม่มี conflict ถ้ามี conflict ที่ต้องแก้ด้วยมือแม้แต่จุดเดียว octopus จะปฏิเสธการ merge และต้องกลับไป merge แยกทีละ branch แทน

-s ours: เก็บ branch นี้ และบันทึกว่า merge แล้ว

git merge -s ours obsolete-branch

strategy นี้:

ต้องไม่สับสนกับ:

git merge -X ours feature

สองอย่างนี้ต่างกันจริง ๆ:

ประเด็น -s ours (strategy) -X ours (strategy option)
อะไรถูก merge ไม่เอาอะไรจากอีก branch เลย — ทิ้งเนื้อหาทั้งหมด merge ทุกอย่างตามปกติ
ผลต่อ conflict ไม่เกี่ยว — เพราะไม่มีการ merge เนื้อหาจริง เฉพาะส่วนที่ conflict เท่านั้นที่ถูกตัดสินไปทางฝั่งปัจจุบัน
tree ผลลัพธ์ เหมือน branch ปัจจุบัน แต่มี merge commit บันทึกว่าอีก branch ถูก merge tree ที่ merge ตามปกติ เอนเอียงไปฝั่งเราเฉพาะจุดที่ชนกัน
ใช้ตอนไหน ทำเครื่องหมายว่า branch ที่เลิกใช้ถูก merge แล้ว โดยไม่เอาการเปลี่ยนแปลงของมัน แก้ conflict อัตโนมัติไปทางฝั่งเราระหว่าง merge จริง

-X ours และ -X theirs: ตัวตัดสิน conflict

git merge -X ours feature/login

เมื่อ conflict ในแต่ละส่วน ให้เลือกฝั่ง ปัจจุบัน

git merge -X theirs feature/login

เมื่อ conflict ให้เลือกฝั่งที่ merge เข้ามา สิ่งสำคัญคือ ส่วนที่ ไม่ conflict ยังถูก merge ตามปกติ

ไม่ควรใช้ -X ours หรือ -X theirs โดยไม่ตรวจผล เพราะอาจทิ้ง code สำคัญโดยเงียบ ๆ เพราะ Git ตัดสินทุกส่วนที่ conflict ไปทางเดียวโดยไม่ถาม การเปลี่ยนแปลงจริงในฝั่งที่แพ้จึงอาจหายไปโดยไม่มีการเตือน

subtree merge: รวม repository ที่มี root ต่างกัน

strategy แบบ subtree ใช้รวม repository หรือ directory ที่มีโครงสร้าง root ต่างจากของเรา แนวคิดคือ:

main repository:
src/
docs/

external repository:
library files at the repository root

Git สามารถปรับ path ระหว่าง subtree merge เพื่อให้ tree ที่เข้ามาไปลงในไดเรกทอรีย่อยที่ถูกต้อง ในทางปฏิบัติ ปัจจุบันหลายทีมใช้คำสั่ง git subtree โดยเฉพาะ หรือใช้ package manager แทนการจัดการ strategy นี้ด้วยมือ

Key takeaways

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

ความคิดเห็น

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

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