28 คะแนน โดย GN⁺ 2023-11-24 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • หลายคนมองว่าวิธีการทำงานของ Git branch นั้นไม่ค่อยเป็นไปตามสัญชาตญาณ
  • อธิบายความแตกต่างระหว่างโมเดลเชิงสัญชาตญาณที่คนทั่วไปมีต่อ Git branch กับวิธีที่ branch ถูกแสดงจริง ๆ ภายใน Git
  • แสดงให้เห็นว่าโมเดลเชิงสัญชาตญาณกับวิธีการทำงานจริงของ Git นั้นเกี่ยวข้องกันอย่างใกล้ชิดมาก
  • พูดถึงข้อจำกัดของโมเดลเชิงสัญชาตญาณและเหตุผลที่มันอาจก่อปัญหาได้

โมเดล branch แบบสัญชาตญาณ

  • หลายคนเปรียบ branch ว่าเป็นเหมือน 'กิ่งของต้นแอปเปิล'
  • ใน Git branch ไม่มีแนวคิดเรื่อง 'พ่อแม่' และไม่เหมือนกับการคิดว่ามันแตกออกมาจาก main

ใน Git branch คือประวัติทั้งหมด

  • ใน Git branch ไม่ได้เป็นแค่ commit ที่แยกออกมา แต่รวมประวัติทั้งหมดของ commit ก่อนหน้าด้วย
  • ผ่าน repository ตัวอย่าง แสดงให้เห็นว่า main และ mybranch ต่างก็มี 4 commit ทั้งคู่

branch ถูกเก็บเป็น commit ID

  • ภายใน Git branch ถูกเก็บเป็นไฟล์ข้อความขนาดเล็กที่มี commit ID อยู่ข้างใน
  • commit ล่าสุดของแต่ละ branch จะถูกบันทึกไว้ในไฟล์นั้น
  • เนื่องจากไม่มีความสัมพันธ์แบบพ่อ-ลูกระหว่าง branch Git จึงไม่รู้ความสัมพันธ์ระหว่าง branch ต่าง ๆ

สัญชาตญาณของผู้คนมักไม่ได้ผิดนัก

  • การบอกว่าสัญชาตญาณของผู้คนเกี่ยวกับ Git นั้น 'ผิด' อาจเป็นเรื่องที่ไร้สาระอยู่บ้าง
  • แม้โมเดลที่ 'ผิด' ก็ยังมีประโยชน์ได้จริง

rebase ใช้แนวคิด branch แบบ 'สัญชาตญาณ'

  • rebase จะนำเฉพาะ commit ของ branch แบบ 'สัญชาตญาณ' ไปปรับใช้ใหม่บน main
  • ผลลัพธ์ของ rebase สอดคล้องกับโมเดลเชิงสัญชาตญาณ

merge ก็ใช้แนวคิด branch แบบ 'สัญชาตญาณ' เช่นกัน

  • merge ไม่ได้คัดลอก commit แต่ต้องอาศัย base commit ที่ใช้ร่วมกัน
  • merge base จะช่วยหา commit ที่ branch แบบสัญชาตญาณแยกตัวออกมา

GitHub Pull Request ก็ใช้แนวคิดเชิงสัญชาตญาณ

  • เมื่อสร้าง Pull Request เพื่อ merge mybranch เข้า main บน GitHub ระบบจะแสดงเฉพาะ commit ของ branch แบบสัญชาตญาณเท่านั้น

สัญชาตญาณนั้นดี แต่ก็มีข้อจำกัด

  • คำนิยาม branch แบบสัญชาตญาณเข้ากันได้ดีกับการทำงานจริงใน Git แต่ Git ไม่สามารถรับรู้ branch ที่แยกมาจาก main ได้แตกต่างเป็นพิเศษ

trunk กับ branch ที่แตกออกมา

  • ผู้คนรับรู้ main และ mybranch ว่าแตกต่างกัน และสิ่งนี้ส่งผลต่อวิธีการใช้งาน Git
  • Git ไม่ได้แยกแยะว่า branch ใดเป็น 'กิ่งที่แตกออกมา' จากอีก branch หนึ่ง

Git สามารถ rebase แบบ 'ย้อนทาง' ได้

  • เพราะ Git ไม่ได้บอกว่า branch ใดเป็น 'กิ่งที่แตกออกมา' จากอีก branch หนึ่ง ผู้ใช้จึงต้องรู้เองว่าเมื่อไรควร rebase branch ไหน
  • ทั้ง git rebase main และการ rebase ย้อนทางอย่าง git rebase mybranch ต่างก็ทำได้ เช่นเดียวกับ merge

การไม่มีโครงสร้างลำดับชั้นระหว่าง Git branch ก็ค่อนข้างแปลก

  • การที่บอกว่า branch main ไม่ได้พิเศษ เกิดจาก Git ไม่สามารถรับรู้ความสัมพันธ์ระหว่าง branch ได้
  • ระหว่าง branch ต่าง ๆ มีความสัมพันธ์กันอยู่ แต่ git ไม่รู้อะไรเลย

UI ของ Git branch ก็แปลกเหมือนกัน

  • เวลาที่อยากดูเฉพาะ commit ที่ 'แตกออกมา' วิธีใช้ git log และ git diff นั้นแตกต่างกัน

บน GitHub default branch เป็นสิ่งพิเศษ

  • GitHub มี 'default branch' ซึ่งมีบทบาทพิเศษ

ความเห็นของ GN⁺

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

1 ความคิดเห็น

 
GN⁺ 2023-11-24
ความคิดเห็นจาก Hacker News
  • บรานช์คือพอยน์เตอร์ที่ชี้ไปยังคอมมิต และพอยน์เตอร์นี้จะถูกอัปเดตทุกครั้งที่มีการสร้างคอมมิตใหม่ มองได้ว่าบรานช์เป็นชื่อที่ลอยไปมาเหมือนแท็ก เนื่องจากตัวคอมมิตเองชี้ไปยังคอมมิตแม่ บรานช์จึงเป็นสายโซ่ของคอมมิตที่เกี่ยวข้องกันและมีจุดเริ่มต้นที่ถูกตั้งชื่อไว้ เมื่อลบบรานช์ ชื่อเลเบลนั้นก็จะหายไป และจะเหลือเพียงสายโซ่ของคอมมิตที่เกี่ยวข้องกันเท่านั้น
  • ถ้ามองลำดับวงศ์ของคอมมิตเป็นพอยน์เตอร์ที่ชี้ "ย้อนกลับ" แทนที่จะชี้ "ไปข้างหน้า" จะเข้าใจได้ง่ายกว่า บรานช์คือ ID ของคอมมิต ดังนั้นหากไล่ย้อนขึ้นไปตามลิงก์ของคอมมิตแม่ ก็จะพบประวัติทั้งหมดของบรานช์นั้น "จุดแตกกิ่ง" คือจุดที่สายโซ่คอมมิตสองเส้นมาบรรจบกัน และคอมมิต merge เป็นกรณีพิเศษ เพราะมันแสดงให้เห็นว่าประวัติสองสายถูกรวมเป็นหนึ่งเดียว
  • เวลาผมใช้ git reset --hard และ git stash เพื่อจัดการการเปลี่ยนแปลงและพอยน์เตอร์ของบรานช์ในโปรเจกต์ส่วนตัว เพื่อน ๆ มักจะโกรธกันบ่อย ๆ ถ้าต้องยกเลิก merge ที่ผิดพลาด ก็ใช้ git reset --hard <병합 전 마지막 커밋> และถ้าต้องการนำการแก้ไขเล็ก ๆ บน local branch ไปใช้กับ main branch ก็ใช้ git stash จากนั้น checkout ไปที่ main branch แล้วใช้ git stash apply
  • ใน Git ไม่มีแนวคิดว่า "main เป็นสิ่งพิเศษ" แต่เครื่องมืออย่าง GitLab มีฟีเจอร์ protected branch ที่ช่วยลดความผิดพลาดได้ แนวคิดเรื่องบรานช์ "parent" และ "child" อาจน่าสนใจจริง ๆ และควรรองรับบรานช์ "parent" หลายตัวสำหรับบรานช์แบบ long-term support
  • เวลาทำ merge, rebase หรือ pull request ต้องระบุอีกบรานช์หนึ่งอย่างชัดเจน เพราะ Git ไม่รู้ว่าบรานช์ไหนคือบรานช์ฐานที่ผู้ใช้คิดไว้ บางครั้งคุณอาจต้องการ merge feature branch หนึ่งเข้ากับอีก feature branch หนึ่ง จึงต้องระบุให้ชัดว่าจะ merge บรานช์ไหนเข้ากับบรานช์ไหน
  • ต่อให้สัญชาตญาณที่ผู้คนมีอยู่จะผิดทางเทคนิคไปบางส่วน แต่ก็มีเหตุผลที่สมควรอยู่เบื้องหลังว่าทำไมพวกเขาถึงมีสัญชาตญาณแบบนั้น
  • มีบทเรียนแบบโต้ตอบสำหรับคนที่รู้วิธีใช้ git add และ git commit อยู่แล้ว บทเรียนนี้ช่วยให้เรียนรู้ไปพร้อมกับการมองเห็นภาพของบรานช์ได้
  • ถ้าจำไว้ว่าเวลารันคำสั่ง Git นั้นคุณกำลังแก้ไขบรานช์ปัจจุบัน "เสมอ" ก็จะเข้าใจไวยากรณ์ของ Git ได้ "ง่าย" ตัวอย่างเช่น git merge my-branch คือการ merge my-branch เข้ากับบรานช์ปัจจุบัน และ git rebase my-branch คือการ rebase บรานช์ปัจจุบันขึ้นไปบน my-branch
  • น่าจะดีถ้าบรานช์ (head) มี "หาง" ที่ชี้ไปยังคอมมิตฐานซึ่งเป็นจุดเริ่มต้นของบรานช์นั้น เนื่องจากบรานช์ถูก rebase บ่อย จึงมีหลายครั้งที่ต้องคิดว่ามันเริ่มจากตรงไหน ถ้า Git บอกได้ว่าคอมมิตฐานนั้นอยู่ใน main ก็จะสะดวกกว่า
  • เวลาส่ง "patch" ไปยัง mailing list สามารถใส่คอมมิตฐานเข้าไปแบบเลือกได้ เพราะอาจไม่ชัดเจนว่าการเปลี่ยนแปลงนั้นอิงกับรีลีสล่าสุด main development branch หรือ integration branch และเวลาจะใช้ git range-diff ก็ต้องคำนึงถึงฐานนี้ด้วย เครื่องมือนี้เปรียบเทียบสองช่วง เช่น main..previous และ main..current
  • พอกลับมาอ่านมุมมองส่วนตัวเกี่ยวกับบรานช์อีกครั้ง ก็ได้เรียนรู้บางอย่างที่เคยลืมไปใหม่อีกครั้ง