เขียนข้อความ commit ให้ใช้กับ CI/CD ได้ด้วย Conventional Commits

Chonlasith Jucksriporn
odds.team
Published in
3 min readAug 10, 2020

ปกติเรามักจะเขียนข้อความ commit ไว้แบบง่าย ๆ คือ ฉันทำอะไรไปนะ แบบสรุป ๆ ลงใน commit นั้น ๆ บางคนไม่ได้ให้ความสำคัญกับข้อความ commit ด้วยซ้ำ แต่จริง ๆ แล้วข้อความ commit นี่มันทำอะไรให้เราได้มากกว่าทีเราคิด เช่น การที่ทีมจะต้องมานั่งทำ Release note ทุก ๆ release เอง หรือจะต้องมานั่งดูว่า เอ๊ะ commit นี้มันทำเกี่ยวกับเรื่องไหน ฟีเจอร์ไหนหว่า มันน่าจะดีถ้า commit ของเรามันมีรูปแบบอะไรบางอย่างที่จะช่วยให้เราทำงานได้สะดวกขึ้น และยิ่งถ้ามันช่วยให้เราไม่ต้องมานั่งทำ Release note เองได้ก็เยี่ยมไปเลย

โพสต์นี้เลยอยากจะแนะนำให้รู้จักสิ่งที่เรียกว่า Conventional Commits

ก่อนจะไปรู้จัก Conventional Commits เรามารู้จักกับ Commit ปกติกันก่อน

Commit คืออะไร?

Commit หรือข้อความ commit (Commit message) คือข้อความที่ใช้อธิบายถึงสิ่งที่เปลี่ยนแปลงใน git commit หนึ่ง ๆ

หลาย ๆ คนอาจจะไม่รู้ว่าเราสามารถเขียน commit ได้หลายบรรทัด แต่เวลาเราดูใน git UI ปกติ เราจะเห็นแค่บรรทัดแรกของข้อความ commit ส่วนที่เหลือเราจะเห็นเมื่อเรากดดูรายละเอียดของ commit นั้น ๆ หรือถ้าเราใช้คำสั่ง git log เราก็จะเห็นรายละเอียดเช่นกัน

วิธีพิมพ์ข้อความ commit หลาย ๆ บรรทัด คือ git commit -m "ข้อความ... แล้วอย่าเพิ่งพิมพ์ " ปิดท้าย แล้วเคาะ Enter แล้วพิมพ์ต่อได้เลย พอพิมพ์ครบแล้วค่อยปิดท้ายด้วย " แล้วค่อยเคาะ Enter อีกทีเพื่อ commit

ส่วนใครใช้ UI ส่วนใหญ่ก็จะทำให้เป็นกล่องใหญ่ ๆ ให้สามารถใส่หลาย ๆ บรรทัดได้อยู่แล้ว ก็ใส่ได้เลย

Conventional Commits คืออะไร?

Conventional Commits คือข้อตกลงในการเขียนข้อความ commit ให้มีรูปแบบเดียวกัน โดยจะถูกกำหนดให้มีรูปแบบที่ชัดเจน เรียกว่า Conventional Commits Specfication ซึ่งตอนที่กำลังเขียนโพสต์นี้ spec อยู่ที่เวอร์ชั่น 1.0.0 แล้ว

Conventional Commits ช่วยอะไรบ้าง?

เนื่องจาก Conventional Commits มีรูปแบบที่ชัดเจน ทำให้กระบวนการต่าง ๆ ที่จำเป็นต้องใช้ข้อความจากการ commit สามารถทำได้อย่างอัตโนมัติ

  • การสร้างไฟล์ CHANGELOG หรือการออก Release note
  • การปรับ version ที่มีรูปแบบ SemVer โดยอัตโนมัติ
  • ทำให้การสื่อสารกับเพื่อนร่วมทีม หรือผู้ที่เกี่ยวข้องมีรูปแบบมากขึ้น เข้าใจได้ง่ายขึ้น

ลักษณะของ Conventional Commits

ข้อความที่เป็น Conventional Commits จะอยู่ในรูปแบบตามนี้

<type>[optional scope]: <description>

[optional body]

[optional footer]

จะเห็นได้ว่า มีส่วนประกอบทั้งหมด 5 ส่วนคือ

  1. type หรือชนิดของ commit โดยจะมีตัวหลัก ๆ คือ feat และ fix ที่เป็นมาตรฐานตาม spec ส่วนชนิดอื่น ๆ ก็สามารถใช้ได้เช่นกัน แค่มันไม่ได้อยู่ใน spec นี้ เช่น build, chore, ci, docs, style หรือชนิดอื่น ๆ ตามแต่จะตกลงกัน
  2. optional scope หรือขอบเขตที commit นี้ครอบคลุมถึง จะหมายถึงส่วนของโค้ด หรือส่วนของ module ที่ commit นี้อ้างถึง ไม่เขียนก็ได้ แต่ถ้าจะเขียน เวลาเขียน ให้เขียนอยู่ในวงเล็บ ()
  3. description หรือคำอธิบาย commit คือข้อความ commit แบบสรุปสั้น ๆ ว่า commit นี้เกี่ยวกับอะไร ทำอะไรไป
  4. optional body หรือเนื้อหาของ commit คือรายละเอียดของ commit ว่า commit นี้ทำอะไรไปบ้าง อาจจะมีหลาย ๆ ย่อหน้าก็ได้ ไม่เขียนก็ได้
  5. optional footer หรือคำลงท้ายของ commit จะเป็นตัวใช้ reference ต่าง ๆ ของ commit จะไม่เขียนก็ได้ แต่ถ้าเขียน จะอยู่ในรูปแบบ token: ข้อความ หรือ token #ข้อความ โดย token จะหมายถึงกลุ่มคำที่ใช้ - แทนเว้นวรรค เช่น Reviewed-by เป็นต้น

การระบุ Breaking Change

หาก commit นั้น ๆ เป็น Breaking change หรือมีผลกระทบกับสิ่งที่ release ไปก่อนหน้า ให้ระบุด้วยว่าเป็น Breaking change โดยให้ใส่ ! ตามหลัง type (หรือถ้ามี scope ก็ให้ตามหลัง scope) ทันที หรือจะใส่ลงใน footer เป็นตัวอักษรพิมพ์ใหญ่ทั้งหมด และตามด้วย : เช่น

แบบที่มี scope

feat(core)!: Use new payment gateway engine 2.0.0

แบบที่ไม่มี scope

feat!: Use new payment gateway engine 2.0.0

แบบที่ใส่ลงใน footer

feat: Use new payment gateway engine 2.0.0BREAKING CHANGE: New payment gateway engine 2.0.0 provides additional core feature we need to implement the new feature.

แบบที่ใส่ทั้งใน type และ footer

feat!: Use new payment gateway engine 2.0.0BREAKING CHANGE: New payment gateway engine 2.0.0 provides additional core feature we need to implement the new feature.

แบบที่ใส่ทั้งใน type, scope และ footer

feat(core)!: Use new payment gateway engine 2.0.0BREAKING CHANGE: New payment gateway engine 2.0.0 provides additional core feature we need to implement the new feature.

มันจะช่วยกระบวนการใน CI/CD ได้ยังไง

เพราะว่า Conventional Commits ถูกออกแบบมาให้สอดคล้องกับการใช้เวอร์ชั่นแบบ SemVer นั่นคือ

  • commit ที่ type เป็น feat จะมีความหมายเป็น MINOR ใน SemVer
  • commit ที่ type เป็น fix จะมีความหมายเป็น PATCH ใน SemVer
  • ถ้ามีการระบุว่า commit นั้นเป็น BREAKING CHANGE จะมีความหมายเป็น MAJOR ใน SemVer

นั่นคือ เราสามารถให้เครื่องมือมาอ่านข้อความ commit และประเมินเลขเวอร์ชั่นถัดไปได้เลย และด้วยความที่มันถูกเขียนอย่างมี pattern เราสามารถใช้เครื่องมือมาดึงเอาส่วนที่เป็นรายละเอียดของ commit มาสร้างเป็น Release note ได้โดยอัตโนมัติ

ถ้ามีการ commit บ่อย ๆ แบบแก้นิดหน่อยก็ commit จะทำ Conventional Commits ยังไงดี

การ commit บ่อย ๆ เป็นเรื่องที่ดี จริง ๆ แล้วเป็นเรื่องที่น่าสนับสนุนด้วยซ้ำ แต่ถ้าเราอยากจะทำ Conventional Commits การมี commit ที่มีข้อความ commit ที่ชัดเจนก็เป็นสิ่งจำเป็น ความจริงแล้วเราสามารถยุบ commit หลาย ๆ commit เข้าด้วยกันก่อนที่จะ push ไปที่ upstream ได้

ลองดูเทคนิคที่เรียกว่า git squash และ squash rebase workflow เพื่อรวม commit เข้าด้วยกันดู

ถ้านาน ๆ ที commit ที จนมีของหลาย ๆ ฟีเจอร์ปนกันในแต่ละ commit จะทำ Conventional Commits ยังไงดี

การรวมของหลาย ๆ อย่างเข้าด้วยกันใน commit เดียว ในระยะสั้นอาจจะดูสะดวก แต่ในระยะยาวแล้ว การทำแบบนี้จะทำให้เกิดปัญหาขึ้นมาได้ เช่น

  • การทำงานร่วมกับคนอื่น เพราะคนอื่นมาอ่านข้อความ commit แล้วก็จะไม่เข้าใจว่ามันทำอะไรกันแน่
  • บางคนใช้เทคนิคที่เรียกว่า cherrypick ก็จะทำได้ยากขึ้นด้วยเช่นกัน เพราะมีของหลายอย่างที่อยู่ปะปนกันใน commit เดียว
  • การจัดการ PR ก็จะทำได้ลำบากกว่าเดิม เพราะมีของหลายอย่างใน PR จะเลือก review แค่บางส่วนก็ทำได้ยาก และการ approve PR บางส่วนก็เป็นไปไม่ได้

ดังนั้น ทางที่ดี พยายามทำให้มันเป็นหลาย ๆ commit จะดีกว่านะ

รายละเอียดเพิ่มเติมเกี่ยวกับ Conventional Commits

ถ้าหากอยากรู้รายละเอียดเพิ่มเติมหรืออ่านเกี่ยวกับ Conventional Commits Specification เต็ม ๆ สามารถดูได้ที่ https://www.conventionalcommits.org/ แบบแปลไทยก็มี ที่ https://www.conventionalcommits.org/th/

--

--