[번역] Git 그 너머로. jj(Jujutsu)가 바꾸는 버전 관리의 상식

yamitake - 2024년 03월 20일



들어가며

"git stash를 깜빡해서 체크아웃을 못 하겠네" "git rebase 하다가 충돌(Conflict)의 폭풍이..." "git reset --hard로 작업 내용이 날아갔어..."
Git을 사용하면서 이런 경험을 해본 적 없으신가요?
**jj(Jujutsu)**는 이러한 Git의 고통을 모두 해결하기 위해 설계된 차세대 버전 관리 시스템입니다. Google의 엔지니어가 개발하고 Rust로 작성된 이 툴은 Git 리포지토리와의 완전한 호환성을 유지하면서도, 근본적으로 뛰어난 워크플로우를 제공합니다.
이 글에서는 jj의 매력과 기본적인 사용법을 소개합니다.

jj란 무엇인가

Jujutsu(유술)는 Git과 호환되는 버전 관리 시스템입니다. 기존 Git 리포지토리 위의 레이어로 동작하며, 팀원들에게 영향을 주지 않고 도입할 수 있습니다.

가장 큰 특징: 락인(Lock-in) 없음

jj는 Git 리포지토리를 저장소로 사용합니다. 즉:
  • 기존 Git 리포지토리에서 그대로 사용 가능
  • 팀원들은 내가 jj를 쓰고 있다는 사실을 눈치채지 못함
  • 언제든지 Git으로 돌아갈 수 있음
  • VS Code 등의 Git 연동 툴도 그대로 작동함
"시도해 보고 별로면 그만두기"가 쉽다는 점은 새로운 툴을 시도하는 데 있어 최고의 조건입니다.

Git의 무엇이 문제인가

jj의 장점을 이해하기 위해, 먼저 Git의 설계상 문제점을 정리해 보겠습니다.

1. 스테이징 영역(인덱스)의 복잡성

# Git: 3가지 상태를 관리해야 함
git add -p # 워킹 트리 → 스테이징
git commit # 스테이징 → 커밋
git reset HEAD # 스테이징 해제
git checkout -- . # 워킹 트리의 변경 사항 파기
git reset의 각 플래그(--soft, --mixed, --hard)의 차이를 정확하게 설명할 수 있는 사람이 얼마나 될까요? 스테이징 영역은 Git의 학습 비용을 대폭 높이는 주범입니다.

2. 더티(Dirty)한 워킹 트리 문제

$ git checkout feature-branch
error: Your local changes to the following files would be overwritten by checkout:
src/main.rs
Please commit your changes or stash them before you switch branches.
Git 사용자라면 수없이 보았을 에러입니다. "그냥 브랜치만 잠깐 바꾸고 싶은 것뿐인데..."

3. 충돌 해결의 강제

Git에서는 rebase 중에 충돌이 발생하면, 그 자리에서 해결하지 않으면 앞으로 나아갈 수 없습니다. 여러 커밋을 rebase 할 때는 커밋마다 충돌을 해결해야 해서 아득해질 때도 있습니다.

4. 되돌리기의 어려움

# 아차... 실수했다...
git reset --hard HEAD~3
# 되돌리고 싶어... 근데 reflog가 뭐지?
git reflog # ???
Git의 undo(실행 취소)는 reflog라는 이해하기 어려운 메커니즘에 의존하고 있어, 초보자에게는 진입 장벽이 너무 높습니다.

jj가 해결하는 세계

1. 스테이징 영역이 존재하지 않음

jj에서는 워킹 카피(Working Copy) 그 자체가 커밋입니다. 파일을 편집하는 순간, 그 변경 사항은 자동으로 현재 커밋에 기록됩니다.
# jj: 파일을 편집하기만 하면 됨. add는 불필요
vim src/main.rs
# 변경 사항은 자동으로 현재 커밋에 반영됨
jj diff # 현재 변경 사항 확인
jj describe -m "기능 A 구현" # 커밋 메시지 설정
jj new # 새로운 빈 커밋을 생성하고 다음 작업으로 이동
git add의 굴레에서 해방됩니다.

2. 더티한 워킹 트리가 없음

워킹 카피가 항상 커밋이므로, "더티한 상태"라는 개념이 없습니다.
# jj: 언제든지 자유롭게 커밋 사이를 이동
jj edit <revision> # 임의의 커밋으로 이동. stash 불필요!
git stash는 영원히 필요 없게 됩니다.

3. 충돌을 커밋할 수 있음

jj에서 **충돌은 일급 객체(First-class object)**입니다. 충돌을 포함한 커밋을 그대로 저장할 수 있으며, 나중에 원하는 타이밍에 해결할 수 있습니다.
# jj: 충돌이 발생해도 작업을 계속할 수 있음
jj rebase -r @- -d main
# 충돌이 발생해도 그대로 커밋됨
# 나중에 해결하면 OK
jj resolve # 원하는 타이밍에 해결
게다가 충돌을 해결하면 자손 커밋에도 자동으로 전파됩니다.

4. 무엇이든 undo 할 수 있음

jj는 모든 조작을 **오퍼레이션 로그(Operation Log)**에 기록합니다.
jj undo # 직전 조작 취소
jj op log # 조작 이력 확인
jj op restore <op-id> # 임의의 시점으로 복원
git reset --hard로 날려버린 작업도 jj라면 한 번에 되돌릴 수 있습니다.

5. 자동 리베이스

커밋 이력을 변경하면, 자손 커밋이 자동으로 리베이스됩니다.
# 어떤 커밋을 수정하면, 그 아래의 커밋도 자동으로 따라옴
jj edit <이전 커밋>
# 수정을 가함
jj new # 새로운 작업으로 복귀
# → 자손 커밋은 자동으로 리베이스되어 있음!
Git과 같은 수동 rebase의 고행이 사라집니다.

Git vs jj 명령어 비교

하고 싶은 일
Git
jj
리포지토리 초기화
git init
jj git init
클론
git clone <url>
jj git clone <url>
상태 확인
git status
jj st
차이 확인
git diff HEAD
jj diff
커밋
git add -A && git commit
jj commit
메시지 변경
git commit --amend --only
jj describe
로그 표시
git log --oneline --graph
jj log
브랜치 생성
git checkout -b <name>
jj bookmark create <name>
스태시
git stash
jj new @- (개념 자체가 불필요)
스쿼시
git commit --amend -a
jj squash
리베이스
git rebase <target>
jj rebase -d <target>
변경 사항 파기
git reset --hard
jj abandon
blame
git blame <file>
jj file annotate <file>
실행 취소
❌ (reflog로 고군분투)
jj undo
페치
git fetch
jj git fetch
푸시
git push
jj git push

설치 방법

macOS (Homebrew)

brew install jj

Linux / macOS (cargo-binstall)

cargo binstall --strategies crate-meta-data jj-cli

소스에서 빌드

cargo install --git https://github.com/jj-vcs/jj.git --locked --bin jj jj-cli

mise를 사용하는 경우

mise install jujutsu@latest

셋업

초기 설정

# 사용자 이름과 이메일 설정 (필수)
jj config set --user user.name "Your Name"
jj config set --user user.email "[email protected]"

기존 Git 리포지토리에서 시작하기

cd your-git-repo
jj git init --colocate
이것만으로 기존 Git 리포지토리에서 jj를 사용할 수 있게 됩니다. .git 디렉토리는 그대로 남으며, jj가 자동으로 동기화를 유지합니다.

실전 워크플로우

기본적인 개발 플로우

# 1. 새로운 커밋을 생성하고 작업 시작
jj new -m "사용자 인증 기능 추가"

# 2. 파일 편집 (add 불필요, 자동 추적)
vim src/auth.rs
vim src/routes.rs

# 3. 변경 사항 확인
jj diff
jj st

# 4. 다음 작업으로 (현재 커밋은 그대로 확정)
jj new -m "테스트 추가"

# 5. 로그로 확인
jj log

과거의 커밋 수정하기

# 1. 로그에서 수정하고 싶은 커밋 찾기
jj log

# 2. 해당 커밋 편집 (stash 불필요!)
jj edit <change-id>

# 3. 파일 수정
vim src/auth.rs

# 4. 최신 커밋으로 복귀
jj new

# → 사이의 커밋들은 자동으로 리베이스됨

커밋 분할하기

# 너무 큰 커밋을 분할
jj split
# → TUI가 열리고, 파일이나 헝크(hunk)를 선택해서 분할 가능

GitHub에 푸시

# 북마크(= Git 브랜치) 생성
jj bookmark create feature-auth -r @-

# 푸시
jj git push

jj만의 편리한 기능

Revsets: 커밋 쿼리 언어

jj에는 Revsets라는 강력한 쿼리 언어가 있습니다.
# 내 커밋만 표시
jj log -r "author(email:[email protected])"

# main에서 분기된 커밋 표시
jj log -r "main..@"

# 머지 커밋 제외
jj log -r "~merges()"

임의의 리비전에 대한 조작

Git에서는 많은 조작이 체크아웃 중인 브랜치로 제한되지만, jj에서는 임의의 리비전에 대해 조작할 수 있습니다.
# 체크아웃하지 않고 임의의 커밋을 squash
jj squash -r <revision>

# 체크아웃하지 않고 임의의 커밋을 rebase
jj rebase -r <revision> -d <destination>

병행 작업의 안전성

jj는 병행 조작에 대해 안전하게 설계되었습니다. rsync나 Dropbox로 리포지토리를 동기화해도 리포지토리가 깨지지 않습니다.

이런 분들께 jj를 추천합니다

  • Git의 stash / index / reset에 지친 분
  • rebase의 충돌 해결로 소모되고 있는 분
  • "Git 잘 모르겠어"라고 생각하면서도 계속 쓰고 있는 분
  • 새로운 툴을 시도하는 것을 좋아하는 분
  • Git의 파워는 원하지만, 더 심플한 인터페이스를 원하는 분

주의점 및 현재의 한계

jj는 아직 발전 중인 프로젝트입니다. 도입 전에 알아두어야 할 점:
  • GitHub / GitLab과의 친화성: rebase 위주의 워크플로우는 force push로 인해 리뷰 코멘트가 사라지는 GitHub의 메커니즘과 궁합이 맞지 않는 상황이 있을 수 있음
  • IDE 지원: VS Code용 VisualJJ 확장이 있지만, Git만큼 충실하지는 않음
  • 생태계: Git에 비하면 커뮤니티나 툴 규모가 아직 작음
  • 학습 비용: Git과는 다른 멘탈 모델(특히 change vs commit 개념)에 익숙해질 필요가 있음
하지만 Git 리포지토리와 완전히 공존할 수 있기 때문에 리스크는 최소화됩니다. 맞지 않는다면 jj 사용을 중단하기만 하면 됩니다.

요약

관점
Git
jj
스테이징 영역
있음 (복잡)
없음 (심플)
워킹 카피
더티 상태 있음
항상 커밋
충돌
즉시 해결 필요
나중으로 미룰 수 있음
undo
reflog (어려움)
jj undo (쉬움)
리베이스
수동
자동
Git 호환성
-
완전 호환
jj는 "Git을 더 좋게 만든 것"입니다. Git 지식은 헛되지 않으며, Git 리포지토리도 그대로 사용할 수 있습니다. 잃을 것은 아무것도 없고, 얻을 것은 큽니다.
먼저 기존 리포지토리에서 jj git init --colocate를 시도해 보세요. Git에서는 맛볼 수 없었던 쾌적함이 기다리고 있습니다.

참고 링크

발표 정보

이 글의 내용을 chigasaki.rb #14 스터디 모임에서 발표했습니다.
0
5

댓글

?

아직 댓글이 없습니다.

첫 번째 댓글을 작성해보세요!

arkjun님의 다른 글

더보기

유사한 내용의 글