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 push→refs/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 เดิม
📚 อ่านต่อ:
답글 남기기