DEV Community

Gophernment
Gophernment

Posted on

Jujutsu (jj) — ทางเลือก Git ที่ไม่ได้มาแทนที่ แต่มาเติมเต็ม

Jujutsu (jj) — ทางเลือก Git ที่ไม่ได้มาแทนที่ แต่มาเติมเต็ม

ก่อนจะเล่าเรื่อง jj — ขอสารภาพว่า: ผมเคยสอน Git ให้ junior dev แล้วใช้เวลา 45 นาทีอธิบายแค่ staging area

"คือแบบนี้... git add เอาไฟล์ไปไว้ที่ staging area... แล้ว git commit ถึงจะ commit จาก staging area... แต่ถ้าแก้ไฟล์หลัง git add แล้วไม่ได้ git add อีกรอบ... ไฟล์เวอร์ชันที่แก้ใหม่จะไม่ถูก commit... เข้าใจไหม?"

สีหน้า junior: 🤯

นี่คือหนึ่งใน pain point ที่ Git สะสมมาตลอด 20 ปี — และ Jujutsu (jj) เกิดขึ้นมาเพื่อแก้ปัญหาเหล่านี้


jj คืออะไร

jj (Jujutsu) คือ version control system ตัวใหม่ที่:

  • ใช้งานร่วมกับ Git repo เดิมได้ — ไม่ต้อง migrate, git กับ jj ใช้ repo เดียวกัน
  • ไม่มี staging area — การแก้ไขทุกอย่างจะถูก "commit" อัตโนมัติ (แบบ snapshot)
  • undo ทุกอย่างได้ — ทุก operation มี jj undo
  • เขียนด้วย Rust — เร็ว, cross-platform
  • สร้างโดยทีม Google — นำโดย Martin von Zweigbergk (อดีต Mercurial maintainer)

jj ไม่ได้พยายาม "แทนที่ Git" — มันทำงาน บน Git repo — เหมือนใช้ Git แต่อีกหน้าตานึง


Git Pain Points — ทำไม jj ถึงเกิด

1. Staging Area — ภาระทางความคิดที่ junior เกลียด

# Git — ต้องคิดตลอดว่าอะไร staged อะไรไม่ staged
git add file.go          # stage
vim file.go              # แก้เพิ่ม
git commit -m "fix"      # ❌ แก้หลัง add ไม่ติดไปด้วย!
git add file.go          # ต้อง add อีก
git commit -m "fix"      # ✅ รอบที่สอง
Enter fullscreen mode Exit fullscreen mode
# jj — แก้ไขอะไร = commit ทันที (แบบ snapshot อัตโนมัติ)
vim file.go              # แก้
jj commit -m "fix"       # ✅ ทุกอย่างที่แก้ไปถูก commit หมด
Enter fullscreen mode Exit fullscreen mode

ทำไมไม่ต้อง git add? ใน jj — ทุกครั้งที่คุณแก้ไขไฟล์ jj จะบันทึก snapshot ของ working copy โดยอัตโนมัติ (คล้าย Google Docs ที่เซฟเองตลอด) — jj commit แค่เปลี่ยน description ของ snapshot ล่าสุด ไม่ใช่การ "สร้าง commit ใหม่"

ถ้าเทียบกับ Git — jj commit เหมือนกับ git add -A && git commit --amend — มันรวมทุกการเปลี่ยนแปลงเข้าไปใน commit ปัจจุบัน โดยไม่ต้องคิดว่าไฟล์ไหน staged แล้ว ไฟล์ไหนยัง

และถ้าคุณยังไม่พร้อมจะตั้งชื่อ — jj ให้ snapshot ทั้งหมดเป็น commit ชั่วคราวที่มี description เปล่าไว้ก่อน — คุณแก้ไปเรื่อย ๆ แล้วค่อย jj commit -m "..." ทีเดียวตอนจะ push ก็ได้

2. Undo — ใน Git ทำแล้วปวดหัว

# Git — undo commit
git reset --soft HEAD~1    # หรือ hard? หรือ mixed? อันไหนวะ?

# undo rebase ที่พัง?
git reflog                  # หา commit hash...
git reset --hard abc123     # หวังว่าจำถูก
Enter fullscreen mode Exit fullscreen mode
# jj — undo คือ undo
jj undo                     # ย้อนทุกอย่าง — commit, rebase, merge

# undo 2 ครั้ง
jj undo --number 2
Enter fullscreen mode Exit fullscreen mode

jj เก็บ operation log — ทุก action มี ID — undo ก็แค่ย้อนไปก่อนหน้านั้น

3. Branch — ใน Git คือ pointer เคลื่อนที่

Git branch คือ pointer ที่ชี้ไปที่ commit — พอ commit ใหม่ branch ก็เลื่อนตาม

แต่พนักงานใหม่มักเข้าใจว่า branch คือ "สำเนาของโค้ด" — พอ git checkout main แล้วแก้ไข แล้วงงว่าทำไม branch ตัวเองไม่เปลี่ยน

# jj — แยก "ตำแหน่งที่ทำงาน" ออกจาก "ชื่อ"
jj new main                      # สร้าง working copy ใหม่โดยเริ่มจาก main
# แก้ไขไฟล์...
jj bookmark create my-feature    # ตั้งชื่อให้ commit ปัจจุบัน
Enter fullscreen mode Exit fullscreen mode

bookmark กับ branch กับ tag — ต่างกันยังไง?

bookmark มีพฤติกรรม ก้ำกึ่งระหว่าง Git branch กับ Git tag:

  • 🏷️ เหมือน Git tag — มันคือชื่อที่แปะบน commit แล้วไม่ขยับตาม คุณต้องสั่ง jj bookmark move ถึงจะขยับ
  • 🌿 แต่เวลา push — bookmark จะกลายเป็น Git branch บน remote (jj git pushrefs/heads/my-feature)
# bookmark ไม่ขยับตาม — แก้ไฟล์ต่อ bookmark ยังอยู่ที่เดิม
jj bookmark create v1.0     # ติด bookmark ที่ commit ปัจจุบัน
vim file.go                 # แก้ต่อ
jj commit -m "more work"    # commit ใหม่ — bookmark v1.0 ยังอยู่ที่เดิม!
jj log                      # v1.0 อยู่คนละบรรทัดกับ working copy ล่าสุด
Enter fullscreen mode Exit fullscreen mode

เทียบกับ Git:

Git branch Git tag jj bookmark
ขยับตาม commit ✅ อัตโนมัติ
ใช้ git push ส่งขึ้น remote ✅ (แยกคำสั่ง) ✅ (กลายเป็น branch)
เหมาะกับ release version bookmark create v1.0
เหมาะกับ feature branch bookmark create feat-x

สรุป: bookmark หน้าตาเหมือน tag (ไม่ขยับตาม) แต่ เวลา push ทำงานเหมือน branch — jj ออกแบบมาแบบนี้เพราะไม่ต้องการให้ "ชื่อ" ผูกติดกับ "ตำแหน่งที่ทำงาน"

พอคุณ jj bookmark create my-feature แล้วแก้ไฟล์ต่อ — bookmark ยังอยู่ที่เดิม! — เพราะ jj ไม่ยึดติดว่า "ต้องมี branch หลักที่เดินตามเรา" — มันแยก "working copy" กับ "bookmark" ออกจากกันชัดเจน คุณทำงานบน working copy — จะติด bookmark ตอนไหนก็ได้

jj new main                     # เริ่มจาก main (ยังไม่มีชื่อ)
vim file.go                     # แก้ไข
jj commit -m "add feature"      # commit
jj bookmark create my-feature   # เพิ่งตั้งชื่อตอนนี้ — หลังจาก commit แล้ว
# แตกต่างจาก Git ตรงที่: bookmark ไม่จำเป็นต้องมีตั้งแต่แรก
Enter fullscreen mode Exit fullscreen mode

4. Conflict — jj เก็บ conflict state ไว้

# Git — ระหว่าง rebase มี conflict
git rebase main
# CONFLICT!
# ... แก้ conflict ...
git add file.go
git rebase --continue
# ถ้าทำผิดขั้นตอน — หายหมด เริ่มใหม่จาก reflog
Enter fullscreen mode Exit fullscreen mode
# jj — conflict คือ first-class citizen
jj rebase -d main
# CONFLICT!
jj resolve file.go          # jj รู้ว่าคุณแก้แล้ว
# ถ้าอยากย้อน: jj undo — กลับมาก่อน rebase ได้เลย
Enter fullscreen mode Exit fullscreen mode

jj ไม่ได้ treat conflict เป็น "error" — แต่มันคือ "state" ที่ commit เก็บไว้ได้เหมือนปกติ

"แก้ทีหลัง" คืออะไร? ใน Git — ถ้าเกิด conflict ระหว่าง rebase คุณต้องแก้ให้เสร็จก่อนจะทำอย่างอื่นต่อได้ — ยัง commit ไม่ได้, ยัง checkout ไป branch อื่นไม่ได้ — ทุกอย่างหยุด

ใน jj — conflict ถูกบันทึกลง commit เหมือนข้อมูลปกติ — คุณสามารถ:

  • ออกจาก conflict ไปทำงานอย่างอื่นก่อน — jj new main — แล้วกลับมาแก้ conflict ทีหลัง
  • commit conflict ไว้ก่อน — push ขึ้น remote ด้วยซ้ำ (เพื่อนช่วยแก้ต่อได้)
  • jj log จะแสดงว่า commit ไหนมี conflict รอแก้อยู่
jj rebase -d main
# CONFLICT — jj ยังให้ทำอย่างอื่นต่อได้:
jj log                          # commit ที่มี conflict จะมี marker
jj new main                     # ไปทำงานอื่นก่อน
jj edit <commit-with-conflict>  # กลับมาแก้ทีหลัง
jj resolve file.go              # แก้เสร็จ — jj รู้
Enter fullscreen mode Exit fullscreen mode

5. History Rewriting — jj มันง่ายกว่า

# Git — แก้ commit message ของ commit 3 อันก่อน
git rebase -i HEAD~3
# เปลี่ยน pick → reword ทีละอัน
# บันทึกปิด — แก้ message — บันทึกปิด — แก้ message — ...
Enter fullscreen mode Exit fullscreen mode
# jj — แก้ commit message ตรง ๆ
jj describe -m "new message" <commit-id>
# หรือใช้ revset:
jj describe -m "new message" @-- # commit ทุกอันที่ไม่ใช่ main
Enter fullscreen mode Exit fullscreen mode

jj vs Git — เทียบกัน

Git jj
Staging มี — git add ไม่มี — แก้ไข = snapshot
Undo reflog + git reset jj undo
History edit git rebase -i jj describe, jj rebase
Conflict หยุด rebase — ต้องแก้ให้เสร็จ เก็บเป็น state — จัดการทีหลังได้
Repo format .git/ .git/ (ใช้ repo เดิม!)
Learning curve สูง — staging, HEAD, remote ต่ำกว่า — ไม่มี staging
Speed เร็ว (C) เร็วมาก (Rust)
Maturity 20 ปี, ทุก tool รองรับ 4 ปี, ยังมี edge case

jj ยังไม่สมบูรณ์

  • GitHub/GitLab integration — ยังต้องใช้ git push อยู่ (jj มี jj git push แต่มันก็ใช้ git ข้างใต้)
  • Community ยังเล็ก — stackoverflow ยังมีไม่กี่คำถาม
  • IDE integration — VS Code มี plugin แล้ว แต่ยังไม่ mature เท่า GitLens
  • Edge cases — repo ใหญ่ ๆ หรือ workflow ซับซ้อนอาจเจอ bug

ใครควรลอง jj

คุณ... ลอง jj ไหม?
เพิ่งหัดใช้ version control ✅ jj ง่ายกว่า Git มาก
ใช้ Git อยู่แล้ว, โอเคกับมัน ⚠️ ยังไม่ต้องเปลี่ยน
เบื่อ staging area, rebase ปวดหัว ✅ จุดที่ jj เก่งที่สุด
ทำ open source บน GitHub ⚠️ ใช้ jj ทำงานแล้ว git push
ทำงานในทีมที่ทุกคนใช้ Git ✅ ใช้ jj ส่วนตัว, git push แชร์

ลองใน 2 นาที

# ติดตั้ง
brew install jj          # macOS
cargo install jujutsu    # หรือใช้ cargo

# ใน project Git เดิม — jj ใช้ repo เดียวกัน!
cd my-project
jj init --git-repo .

# สร้าง change
echo "hello" > test.txt
jj commit -m "add test"

# log
jj log

# undo
jj undo

# push ขึ้น GitHub (ใช้ git ข้างใต้)
jj git push
Enter fullscreen mode Exit fullscreen mode

สรุป

jj ไม่ใช่ "Git killer" — มันคือ "Git UX layer" — เหมือนใช้ Git อยู่ แต่มี interface ที่สะอาดกว่า, undo ง่ายกว่า, ไม่มี staging area ให้ปวดหัว

มันเกิดจากความเข้าใจว่า Git เก่งมากในฐานะ database — ส่วนเรื่อง UX นั้นถูกออกแบบมาสำหรับ kernel developer เมื่อ 20 ปีที่แล้ว ซึ่งบางเรื่องไม่เข้ากับ workflow ของ developer ทั่วไปในปัจจุบัน

แล้ว jj ก็ไม่ได้มาทับ — มันมา "ห่อ" ให้ Git ใช้สบายขึ้น โดยที่ทุกอย่างยังอยู่บน Git repo เดิม

📚 อ่านต่อ:

Top comments (0)