ไม่ต้องกลัว Merge Conflict! วิธีรวมโค้ดและแก้ปัญหา (ที่ทุกคนต้องเจอ) ฉบับเข้าใจง่าย

บทความนี้มาจากที่ผมอยากแนะนำ Front-end มือใหม่ให้รู้จักกับ Git Workflow เพื่อนำไปประยุกต์ใช้ในการทำงานจริง เลยตั้งใจจะเขียนเป็นซีรีส์ "Git Workflow สำหรับ Front-End Dev จาก Zero สู่ Hero ในทีม" และนี่คือบทความสามครับ


ในสองบทความที่ผ่านมา เราได้ปูพื้นฐาน Git ที่แข็งแรง เรียนรู้ศิลปะการเขียน Commit Message ที่ดี และได้รู้จักการแตก Branch ซึ่งเป็นหัวใจของการทำงานร่วมกัน ตอนนี้ "จักรวาลคู่ขนาน" ที่เราสร้างขึ้นเพื่อทำฟีเจอร์ใหม่หรือแก้บั๊ก ก็พร้อมที่จะถูกรวมกลับเข้ามาใน "จักรวาลหลัก" (main) แล้ว

กระบวนการนี้เรียกว่า git merge ครับ มันคือคำสั่งที่เราใช้เพื่อบอก Git ว่า "ช่วยเอาโค้ดทั้งหมดที่ทำใน Branch นี้ ไปรวมกับอีก Branch หนึ่งที"

แต่แล้ว... ในขณะที่คุณรันคำสั่ง merge อย่างมั่นใจ หน้าจอ Terminal กลับแสดงข้อความสีแดงที่คุณไม่เคยเห็นมาก่อน

CONFLICT (content): Merge conflict in styles/main.css
Automatic merge failed; fix conflicts and then commit the result.

วินาทีแรกที่เห็น หลายคนจะใจหายวาบ "ฉันทำ Git พังรึเปล่า?" "โค้ดหายมั้ย?" "ต้องทำยังไงต่อ!"

ผมอยากจะบอกคุณตรงนี้ว่า ใจเย็นๆ ครับ คุณไม่ได้ทำอะไรพังเลย... Merge Conflict ไม่ใช่ Error แต่มันคือคำถามที่ Git ถามเราอย่างสุภาพ มันเป็นสัญญาณปกติที่บอกว่า "ผมเจอโค้ดสองชุดที่แก้ไขที่เดียวกัน แต่ผมไม่แน่ใจว่าอันไหนถูก ช่วยตัดสินใจให้ผมหน่อยได้ไหมครับ?"

มันคือสัญญาณของการทำงานร่วมกัน และเมื่อคุณอ่านบทความนี้จบ คุณจะสามารถตอบคำถามนี้ให้ Git ได้อย่างมั่นใจ

Merge Conflict เกิดขึ้นได้อย่างไร? (จำลองสถานการณ์)

เพื่อให้เห็นภาพชัดเจนที่สุด ลองดูสถานการณ์สมมุตินี้ครับ

สมมติว่าในไฟล์ styles/main.css เรามีโค้ดสำหรับปุ่มอยู่ 1 ปุ่ม

/* styles/main.css */
.btn-primary {
  color: white;
  background-color: #6c757d; /* grey */
  border-radius: 4px;
}

ทีนี้ เกิดเหตุการณ์สองอย่างขึ้นพร้อมกันในจักรวาลคู่ขนาน

  1. ที่จักรวาลหลัก (main): มีคนเข้าไปแก้บั๊กด่วน ทำให้ปุ่มต้องเปลี่ยนเป็นสีแดงเพื่อแจ้งเตือน

    โค้ดใน main ถูกเปลี่ยนเป็น: background-color: #dc3545; /* red */
  2. ที่จักรวาลของคุณ (feature/new-login-ui): คุณกำลังทำฟีเจอร์ใหม่ และตามดีไซน์ ปุ่มนี้ต้องเป็นสีน้ำเงิน

    โค้ดใน Branch ของคุณถูกเปลี่ยนเป็น: background-color: #0d6efd; /* blue */

เห็นไหมครับว่าทั้งสอง Branch ได้แก้ไขโค้ด บรรทัดเดียวกัน แต่แก้เป็นคนละค่า เมื่อคุณพยายามจะ merge Branch feature/new-login-ui ของคุณกลับเข้า main, Git ก็เลยเกิดอาการสับสน มันไม่สามารถตัดสินใจแทนคุณได้ว่า ตกลงแล้ว...ปุ่มนี้ควรจะเป็นสีแดงหรือสีน้ำเงินกันแน่?

Git จึงหยุดกระบวนการ merge ชั่วคราว และรอคำตอบจากคุณ

วิธีรับมือและแก้ไข Merge Conflict (Step-by-Step)

เมื่อเจอสถานการณ์นี้ ให้ทำตามขั้นตอนต่อไปนี้อย่างใจเย็น

ขั้นตอนที่ 1: ตั้งสติและอ่าน

อย่างแรกเลยคืออย่าเพิ่งตกใจ อ่านข้อความใน Terminal มันจะบอกเราชัดเจนว่าไฟล์ไหน (styles/main.css) ที่เกิด Conflict

ขั้นตอนที่ 2: เปิดไฟล์เจ้าปัญหาใน VS Code (เพื่อนซี้คู่ใจ)

เมื่อเปิดไฟล์ที่มีปัญหาขึ้นมาใน VS Code หรือ Code Editor สมัยใหม่ คุณจะเห็นสัญลักษณ์พิเศษที่ Git ใส่เข้ามาให้โดยอัตโนมัติ ซึ่งหน้าตาของมันอาจจะดูน่ากลัวในตอนแรก แต่จริงๆ แล้วมันมีประโยชน์มาก

/* styles/main.css */
.btn-primary {
  color: white;
<<<<<<< HEAD
  background-color: #dc3545; /* red */
=======
  background-color: #0d6efd; /* blue */
>>>>>>> feature/new-login-ui
  border-radius: 4px;
}

ขั้นตอนที่ 3: ตีความสัญลักษณ์ของ Git

สัญลักษณ์พวกนี้กำลังสื่อสารกับเราครับ

  • <<<<<<< HEAD: คือจุดเริ่มต้นของโค้ดที่มีปัญหา ส่วนที่อยู่ใต้นี้จนถึงเส้นประ คือโค้ดที่มาจาก Branch ปัจจุบันที่คุณอยู่ (ในที่นี้คือ main ที่เราจะ merge เข้า)
  • =======: คือเส้นแบ่งระหว่างโค้ดสองชุด
  • >>>>>>> feature/new-login-ui: คือจุดสิ้นสุดของโค้ดที่มีปัญหา ส่วนที่อยู่เหนือสัญลักษณ์นี้ คือโค้ดที่มาจาก Branch ที่คุณกำลังจะนำมารวม (ในที่นี้คือ feature/new-login-ui)

ง่ายๆ คือ Git กำลังบอกว่า "โค้ดตรงนี้เวอร์ชันนึงเป็นสีแดง อีกเวอร์ชันเป็นสีน้ำเงิน คุณจะเอาอันไหน?"

ขั้นตอนที่ 4: ตัดสินใจ!

นี่คือขั้นตอนที่คุณในฐานะมนุษย์ต้องตัดสินใจ VS Code จะมีปุ่มเล็กๆ ขึ้นมาให้คุณเลือกเพื่อความสะดวก

  • Accept Current Change: เลือกโค้ดของ HEAD (เอาสีแดง)
  • Accept Incoming Change: เลือกโค้ดที่มาจาก Branch อื่น (เอาสีน้ำเงิน)
  • Accept Both Changes: เก็บไว้ทั้งสองเวอร์ชัน (ซึ่งในกรณีของ CSS property เดียวกันแบบนี้คงไม่สมเหตุสมผล)
  • แก้ไขด้วยตัวเอง: คุณสามารถลบสัญลักษณ์ทั้งหมดทิ้ง แล้วแก้ไขโค้ดใหม่ทั้งหมดด้วยตัวเองก็ได้ เช่น อาจจะเปลี่ยนเป็นสีเขียวไปเลยถ้าคุยกับทีมแล้ว!

ในสถานการณ์นี้ สมมติว่าทีมตกลงกันว่าฟีเจอร์ใหม่สำคัญกว่า เราจึงตัดสินใจเลือกโค้ดสีน้ำเงิน (Accept Incoming Change)

ขั้นตอนที่ 5: ทำให้โค้ดกลับมาสะอาด

หลังจากคุณตัดสินใจแล้ว สัญลักษณ์พิเศษของ Git ทั้งหมดจะหายไป และไฟล์ของคุณจะกลับมาอยู่ในสภาพโค้ดที่สมบูรณ์อีกครั้ง

/* styles/main.css */
.btn-primary {
  color: white;
  background-color: #0d6efd; /* blue */
  border-radius: 4px;
}

ขั้นตอนที่ 6: บอก Git ว่าเราแก้ปัญหาแล้ว

เมื่อไฟล์กลับมาสะอาดแล้ว คุณต้องทำการ git add ไฟล์นั้นอีกครั้ง เพื่อเป็นการส่งสัญญาณบอก Git ว่า "โอเค ไฟล์นี้ฉันจัดการปัญหา Conflict เรียบร้อยแล้วนะ"

git add styles/main.css

ขั้นตอนที่ 7: สิ้นสุดกระบวนการ Merge

ขั้นตอนสุดท้ายคือการสร้าง Commit เพื่อบันทึกการ Merge ครั้งนี้ให้เสร็จสมบูรณ์

git commit

เมื่อรันคำสั่งนี้ Git มักจะเปิดหน้าต่าง Text Editor ขึ้นมาพร้อมกับเตรียมข้อความ Commit เริ่มต้นไว้ให้แล้ว เช่น Merge branch 'feature/new-login-ui' คุณสามารถใช้ข้อความนี้ได้เลย แค่บันทึกและปิดหน้าต่างนั้นไปก็เป็นอันเสร็จสิ้น

บทสรุป

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

เมื่อคุณเข้าใจแล้วว่าสัญลักษณ์ต่างๆ หมายถึงอะไรและมีขั้นตอนการแก้ไขอย่างไร ครั้งต่อไปที่คุณเจอข้อความสีแดงแจ้งเตือน CONFLICT คุณจะไม่ตกใจอีกต่อไป แต่จะยิ้มมุมปากแล้วพูดกับตัวเองว่า "โอเค Git, มีอะไรให้ช่วยตัดสินใจล่ะ?"

ในบทความต่อไป ซึ่งเป็นบทความสุดท้ายในซีรีส์พื้นฐานนี้ เราจะไปดูเทคนิคขั้นสูงขึ้นอีกนิดอย่าง git rebase ที่จะช่วยจัดระเบียบประวัติศาสตร์โค้ดของเราให้สวยงามน่าอ่านยิ่งขึ้นไปอีกครับ!