กลัวทำ Git พัง? ใช้ Jujutsu (jj) กับ repo เดิม แล้วย้อนคำสั่งที่พลาดได้ทันที
Jujutsu (jj) คือ version control หรือระบบเก็บประวัติการแก้โค้ดที่ใช้กับ Git repo เดิมได้เลย ไม่ต้องสั่ง add ไม่ต้องหาที่พักงานค้าง และย้อนคำสั่งล่าสุดที่พลาดได้ทันที ลองแล้วไม่ชอบ ก็แค่ลบโฟลเดอร์ .jj ทิ้ง แล้วกลับไปใช้คำสั่ง git ในโฟลเดอร์เดิมได้ตามปกติ

Jujutsu (jj) คือ version control (ระบบเก็บประวัติการแก้โค้ด) ตัวใหม่ที่ใช้กับ Git เดิมได้เลย Git เป็นระบบที่ทีมพัฒนาซอฟต์แวร์ทั่วโลกใช้กันอยู่ ส่วน jj เกิดมาแก้ปัญหาข้อเดียว คือความกลัวว่าจะทำ Git พัง
คนที่ใช้ Git อยู่ทุกวันส่วนใหญ่ไม่ได้เกลียดมัน พวกเขาสั่ง commit เพื่อบันทึกการแก้โค้ดเข้าประวัติ สั่ง push เพื่อส่งงานขึ้นไปเก็บบน GitHub (ที่เก็บโค้ดออนไลน์ของทีม) และทำงานร่วมกับคนอื่นได้ตามปกติ แต่พอถึงจังหวะที่ต้องพิมพ์คำสั่งรื้อประวัติเก่าอย่าง git rebase มือจะชะงักไปหนึ่งวินาที เพราะไม่มั่นใจว่าถ้าพลาดแล้วจะกู้กลับยังไง หลายคนเคยติดอยู่ในสถานะ rebase in progress กลางทางแล้วออกไม่เป็น จนต้องลบโฟลเดอร์โปรเจกต์ทิ้งและดึงโค้ดลงมาใหม่ทั้งก้อน
จุดที่ทำให้ jj น่าลองไม่ใช่ตัวคำสั่ง แต่คือการลองใช้ได้โดยแทบไม่กระทบของเดิม มันเก็บข้อมูลในรูปแบบ Git ลงใน repo (โฟลเดอร์โปรเจกต์ที่เก็บทั้งโค้ดและประวัติการแก้ไว้ด้วยกัน) เดิมของทีม จึงมองได้ว่า jj เป็นอีกวิธีหนึ่งในการใช้งาน Git เครื่องมือที่ทำงานกับ Git และทีมใช้อยู่แล้วอย่าง GitHub จึงยังใช้ต่อได้ตามปกติ ถ้าลองแล้วไม่ชอบ ก็แค่ลบโฟลเดอร์ .jj ทิ้ง แล้วกลับไปใช้คำสั่ง git ในโฟลเดอร์นั้นต่อได้ทันที ภาพรวมของเครื่องมือตัวนี้เคยเล่าไว้แล้วในเรื่อง jj ที่รันบน git repo เดิม รอบนี้จะไล่ดูทีละข้อว่า jj จัดการกับความกลัวเหล่านั้นอย่างไร
ไม่มี staging area ก็ไม่มีอะไรให้ลืม add
ความกลัวข้อแรกเริ่มตั้งแต่ก่อนบันทึกงานลงประวัติด้วยซ้ำ ต้นเหตุคือ staging area หรือ index (พื้นที่พักไฟล์ที่เลือกไว้รอบันทึก) ผู้ใช้ต้องสั่ง git add ก่อน แล้วค่อย commit ถ้าลืม add อะไร สิ่งนั้นก็หลุดจาก commit ก้อนนั้นไปโดยไม่รู้ตัว
jj ไม่มีขั้นตอนนี้เลย มันติดตามไฟล์ใหม่ ไฟล์ที่แก้ และไฟล์ที่ลบให้เองทั้งหมด ถ้าไม่อยากให้ jj ติดตามไฟล์ไหน ก็ใส่ชื่อไฟล์นั้นลงใน .gitignore แล้วสั่ง jj file untrack <path> เท่านั้น
เหตุผลที่ jj ไม่ต้องมี staging area คือไฟล์ที่กำลังแก้อยู่ในเครื่อง (working copy) นับเป็น commit เต็มตัวแล้วและมีรหัสของตัวเอง ทุกครั้งที่ใช้คำสั่ง jj ครั้งต่อไป ระบบจะบันทึกการแก้ไขล่าสุดลงใน commit ก้อนนั้นเอง ต่างจาก Git ซึ่งยังไม่นับไฟล์ที่แก้ค้างไว้ในเครื่องเป็น commit จนกว่าจะสั่ง commit เอง
พอทุกอย่างเป็น commit อยู่แล้ว git stash (ที่พักงานค้างชั่วคราว) ก็ไม่จำเป็น เพราะ jj บันทึกงานที่กำลังทำไว้แล้ว จึงไม่ต้องหาที่พักชั่วคราว ถ้าอยากพักงานชิ้นนี้ไปทำชิ้นอื่นก่อน ก็เปิด change ใหม่ (change คืองานหนึ่งชิ้นที่ jj มองเป็นก้อนเดียว) ด้วย jj new แล้วค่อยกลับมาทำต่อด้วย jj edit
จุดที่แปลกตาที่สุดสำหรับคนที่มาจาก Git คือ commit หนึ่งก้อนมีรหัสสองชุด ชุดแรกคือ commit ID (รหัสแฮชแบบเดียวกับของ Git) อีกชุดคือ change ID เป็นรหัสแบบใหม่ที่ Jujutsu คิดขึ้นมาเอง ในตัวอย่างของเอกสารทางการ พอแก้ไฟล์ README แล้ว commit ID เปลี่ยนจาก e427edcf เป็น 5d39e19d แต่ change ID ยังเป็น kntqzsqt เท่าเดิม พูดง่ายๆ คือ change ID เป็นชื่อของงานชิ้นนั้น จึงไม่เปลี่ยนตามเนื้อหาที่ยังแก้ไม่เสร็จ
rebase ที่ไม่มีคำว่าค้างกลางทาง

ความกลัวข้อที่สองใหญ่กว่าข้อแรกมาก rebase คือการย้ายชุด commit ของเราไปต่อบนฐานใหม่ ใน Git ถ้าระหว่างทางเจอ conflict (จุดที่โค้ดสองฝั่งแก้ที่เดียวกันจนระบบเลือกให้ไม่ได้) กระบวนการจะหยุดและทำให้เราติดอยู่ในสถานะ rebase in progress คนส่วนใหญ่มักใจเสียตรงนี้ เพราะไม่รู้ว่าจะทำต่ออย่างไร และถอยกลับก็กลัวว่างานที่ทำมาจะหายไปด้วย
ใน jj ไม่มีสถานะแบบนั้นอยู่เลย rebase ทำงานจนจบเสมอ ถ้าเจอ conflict มันจะบันทึก conflict นั้นไว้ในประวัติ แปะป้าย (conflict) ที่ commit ก้อนที่มีปัญหา จากนั้นก็ rebase commit ที่ต่ออยู่ด้านบนจนครบ
rebase ใน jj ไม่มีสถานะค้างกลางทาง มีแต่ conflict ที่รอให้ตามแก้ทีหลัง
การตามแก้ก็ไม่ใช่ท่ายาก เริ่มจากสร้าง commit ใหม่ต่อจาก commit ที่มีปัญหาด้วย jj new แล้วแก้ conflict ด้วย jj resolve หรือเปิดไฟล์ไปแก้เครื่องหมาย conflict ตรงๆ ก็ได้ จากนั้นตรวจผลด้วย jj diff (ดูว่าโค้ดเปลี่ยนไปตรงไหนบ้าง) แล้วสั่ง jj squash (รวมงานก้อนปัจจุบันเข้าไปใน commit ก่อนหน้า) เพื่อรวมงานที่แก้เสร็จกลับเข้า commit เดิม เครื่องหมาย conflict ของ jj ยังให้รายละเอียดมากกว่าของ Git เพราะเขียนกำกับไว้เลยว่าแต่ละฝั่งของ conflict มาจาก commit ไหน ไม่ได้มีแค่ <<<<<<< กับ >>>>>>> ลอยๆ
พอ rebase ไม่ใช่เรื่องน่ากลัวอีกต่อไป ท่าใหม่ๆ ก็ตามมา อย่าง megamerge คือการเอางานที่ทำค้างไว้ในหลาย branch (กิ่งงานที่แยกออกไปทำคนละเรื่อง) มารวมกันชั่วคราว เพื่อทดสอบว่างานหลายชิ้นที่ยังทำไม่เสร็จทำงานร่วมกันได้ไหม นี่เป็นวิธีที่แม้แต่คนใช้ Git มานานจนชำนาญก็ยังไม่เคยได้ใช้
undo ที่ครอบคลุมกว่า reflog
ความกลัวข้อสุดท้ายคือคำถามที่ค้างอยู่ในหัวทุกครั้งก่อนกด Enter ว่าถ้าสั่งผิดแล้วย้อนกลับได้ไหม
jj undo ย้อนคำสั่งล่าสุดได้ โดยคืนการเปลี่ยนแปลงทั้งหมดจากคำสั่งนั้นพร้อมกัน เอกสารทางการยกตัวอย่างการสั่ง jj undo หลังจากเพิ่งสั่ง jj squash ผลคือทุกอย่างกลับสู่สภาพก่อน squash พร้อมกัน ทั้งการยุบ commit, conflict ที่เพิ่งเกิด และไฟล์ที่แก้ค้างไว้ในเครื่อง
ที่ทำแบบนั้นได้เพราะ jj เก็บ operation log (บันทึกทุกปฏิบัติการที่ทำกับ repo ไม่ใช่แค่ประวัติของ commit) เมื่อเปิดดูด้วย jj op log จะเห็นทุกบรรทัดว่าใครสั่ง สั่งตอนไหน ใช้เวลาไปเท่าไร และพิมพ์คำสั่งอะไรลงไป ถ้าอยากย้อนดูสภาพ repo หลังปฏิบัติการใด ก็สั่ง jj log --at-op=<id> ความสามารถนี้ครอบคลุมกว่า reflog ของ Git (สมุดบันทึกว่าปลายกิ่งเคยชี้ไปที่ commit ไหนมาบ้าง) เพราะ reflog ติดตามได้เฉพาะการขยับของกิ่ง
ลงมือจริง เริ่มที่ jj git clone

ติดตั้งก่อนตามหน้า install and setup ของ Jujutsu แล้วเปิดเทอร์มินัลขึ้นมา สามขั้นแรกที่ทำได้ทันทีในโปรเจกต์ที่มีอยู่แล้ว มีแค่นี้
- ดึงโค้ดของโปรเจกต์เดิมลงมาทั้งก้อน (clone) ด้วยคำสั่ง
jj git clone https://github.com/octocat/Hello-Worldโดยตอนนี้ยังดึง repo ที่เป็นของ jj แท้ๆ มาตรงๆ ไม่ได้ ต้องดึงผ่าน git เท่านั้น - เขียนคำอธิบายงานไว้ก่อนลงมือ
jj describeแล้วพิมพ์ลงไปว่ากำลังจะทำอะไร เช่น "แก้ปุ่มส่งฟอร์มให้กดซ้ำไม่ได้" - แก้ไฟล์ตามปกติ แล้วสั่ง
jj stดูว่าอะไรเปลี่ยนไปบ้าง โดยไม่ต้อง add อะไรทั้งนั้น
พองานชิ้นแรกนิ่งแล้ว สั่ง jj new เพื่อเปิดงานชิ้นถัดไปต่อจากชิ้นแรก จากนั้นก็วนทำสามขั้นนี้ซ้ำ ซึ่งเป็น workflow พื้นฐานทั้งหมดของ jj
ส่วนคำสั่งที่เหลือ ค่อยหยิบมาใช้ตอนเจอสถานการณ์จริง ตารางนี้เทียบเฉพาะท่าที่ใช้บ่อยที่สุด
| อยากทำอะไร | ฝั่ง Git | ฝั่ง jj |
|---|---|---|
| บันทึกงานที่แก้ไป | git add แล้ว commit | ไม่ต้อง add เพราะ jj บันทึกไฟล์ที่แก้เข้า commit ให้เอง |
| แก้ commit ล่าสุดเพิ่ม | git commit --amend | jj squash |
| พักงานค้างไว้ไปทำอย่างอื่น | git stash | jj new แล้วกลับมาด้วย jj edit |
| ย้อนคำสั่งที่เพิ่งสั่งผิดไป | ไล่หาใน git reflog แล้ว reset | jj undo |
| ยกกองงานไปต่อบนฐานใหม่ | git rebase | jj rebase -s <ต้นทาง> -o <ปลายทาง> |
| ย้อนไปแก้ commit เก่า | ต้องพึ่ง rebase แบบโต้ตอบ | jj edit <commit> แล้วแก้ได้เลย |
แถวสุดท้ายส่งผลต่อวิธีทำงานมากที่สุด ใน Git การย้อนกลับไปแก้ commit ที่อยู่ย้อนหลังไปสามก้อนถือเป็นท่ายากที่ต้องตั้งสติก่อนพิมพ์ ส่วนใน jj ให้สั่ง jj edit แล้วแก้ได้เลย จากนั้น jj จะ rebase commit ทั้งหมดที่ต่ออยู่ด้านบนให้เอง
สิ่งที่ต้องพูดตรงๆ ก่อนใช้ jj
ข้อจำกัดแรกคือเรื่องเอกสาร ตอนนี้เอกสารที่ใช้เรียน jj มีสองชุด คือบทแนะนำจากผู้ใช้จริงอย่าง Evan's Jujutsu Tutorial และเอกสารทางการ Jujutsu docs ทั้งสองชุดเขียนมาสำหรับคนที่รู้จัก Git มาก่อน ใครไม่เคยแตะ version control มาเลยจะอ่านแล้วสะดุดแน่นอน อีกทางเลือกสำหรับคนกลุ่มนี้คือเอกสารชื่อ Jujutsu for everyone ที่เขียนให้คนเริ่มจากศูนย์โดยเฉพาะ
ข้อที่สอง เอกสารทางการระบุไว้เองว่าทูโทเรียลชุดนี้เก่าแล้ว เนื้อหาพื้นฐานยังถูกต้อง แต่หลังจากเผยแพร่ทูโทเรียล Jujutsu ก็เพิ่มความสามารถใหม่อีกมาก ตัวอย่างหนึ่งคือคำสั่ง jj run ที่สั่งทีเดียวแล้วทำงานทั่วทั้งกอง commit ที่เพิ่มเข้ามาภายหลัง
ข้อที่สาม แม้ rebase จะสำเร็จเสมอ แต่ก็ไม่ได้แปลว่า conflict หายไป เมื่อโค้ดสองฝั่งแก้จุดเดียวกัน ยังไงก็ต้องมีคนตัดสินใจว่าจะเอาฝั่งไหน สิ่งที่ jj เปลี่ยนคือเวลาที่ต้องแก้ conflict แทนที่จะหยุดทุกอย่างเพื่อแก้ทันที เราสามารถบันทึก conflict ไว้ในประวัติแล้วเลือกกลับมาแก้เองได้ งานจึงไม่สะดุด แต่ conflict ยังอยู่และต้องแก้เหมือนเดิม
ถึงจะมีข้อจำกัดเหล่านี้ การลองใช้ jj ก็ยังแทบไม่กระทบของเดิมอยู่ดี ไม่ต้องขออนุญาตใคร ไม่ต้องชวนทั้งทีมย้ายตาม และไม่ต้องเปลี่ยนที่เก็บโค้ด เพราะเบื้องหลัง jj ยังใช้ Git repo เดิมของทีม
เมื่อย้อนกลับไปแก้ประวัติได้ตลอดเวลา การลองผิดลองถูกก็ไม่ทำให้งานเสียหายอีกต่อไป และเมื่อทดลองได้โดยไม่เสียงาน คนก็กล้าลงมือบ่อยขึ้นเอง
ที่มา:
- บทความ The Sales Pitch จาก Evan's Jujutsu Tutorial
- บทความ Tutorial and bird's eye view จาก Jujutsu docs (jj-vcs.dev)



