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" # ✅ รอบที่สอง
# jj — แก้ไขอะไร = commit ทันที (แบบ snapshot อัตโนมัติ)
vim file.go # แก้
jj commit -m "fix" # ✅ ทุกอย่างที่แก้ไปถูก commit หมด
ทำไมไม่ต้อง 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 # หวังว่าจำถูก
# jj — undo คือ undo
jj undo # ย้อนทุกอย่าง — commit, rebase, merge
# undo 2 ครั้ง
jj undo --number 2
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 ปัจจุบัน
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 ล่าสุด
เทียบกับ 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 ไม่จำเป็นต้องมีตั้งแต่แรก
4. Conflict — jj เก็บ conflict state ไว้
# Git — ระหว่าง rebase มี conflict
git rebase main
# CONFLICT!
# ... แก้ conflict ...
git add file.go
git rebase --continue
# ถ้าทำผิดขั้นตอน — หายหมด เริ่มใหม่จาก reflog
# jj — conflict คือ first-class citizen
jj rebase -d main
# CONFLICT!
jj resolve file.go # jj รู้ว่าคุณแก้แล้ว
# ถ้าอยากย้อน: jj undo — กลับมาก่อน rebase ได้เลย
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 รู้
5. History Rewriting — jj มันง่ายกว่า
# Git — แก้ commit message ของ commit 3 อันก่อน
git rebase -i HEAD~3
# เปลี่ยน pick → reword ทีละอัน
# บันทึกปิด — แก้ message — บันทึกปิด — แก้ message — ...
# jj — แก้ commit message ตรง ๆ
jj describe -m "new message" <commit-id>
# หรือใช้ revset:
jj describe -m "new message" @-- # commit ทุกอันที่ไม่ใช่ main
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
สรุป
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)