Getting started with jj

Ellie Huxtable

jj 시작하기

지난 몇 주간 jj를 써보고 있다! 예전에 한 번 써본 적은 있지만, 이렇게까지 쭉 쓰게 된 건 처음이다.

수년간 내 git 워크플로는 브랜치에서 “수정하고 나서 git commit --amend --no-edit && git push --force-with-lease”를 실행하는 방식이었다. 이렇게 하면 원격에 변경 사항이 거의 지연 없이 바로 반영됐다. PR도 보통 커밋을 1개 정도로 아주 적게 유지했고, 최대한 작게 쪼개려고 노력했다.

에이전트와 함께 작업하기 시작하면서 브랜치를 훨씬 더 자주 옮겨 다니게 됐다. 그러다 보니 매번 git stash를 하거나, 변경 사항이 서로 꼬이지 않게 하려고 의미 없는 WIP 커밋을 자꾸 만들게 됐다.

그때 등장한 새로운 주인공이 바로 jj다! 동작 모델이 조금 다르다. 이미 훌륭한 매뉴얼이 많으니, 내가 대충 끄적이는 것보다 훨씬 잘 정리된 그 자료들을 보는 게 낫다고 생각해서 장황한 설명은 생략하려 한다.

간단히 말해 가장 마음에 드는 점은 변경 사항이 항상 커밋된 상태로 유지된다는 것이다. 편집할 때마다 가장 최근 커밋이 자동으로 amend되고, push하면 원격이 덮어써진다(jj 식으로 말하면 “옆으로” 이동하는 셈이다).

change를 수정하는 것도 쉽다. checkout이나 rebase 같은 복잡한 절차가 필요 없다. 마치 내가 선호하던 git 워크플로를 그대로 가져다 도구로 만들어 둔 느낌이다. 왜 진작 깨닫지 못했는지 모르겠다.

그냥 머릿속에 있는 걸 쏟아낸 글이다. 더 알게 되는 내용이 생기면 또 정리해서 올려볼 생각이다.

워크플로

변경 작업을 할 때 내 워크플로는 이렇다.

jj new main

<do some coding>

jj describe -m 'feat: something really cool'
jj git push -c @

끝이다. PR을 머지하고 반복하면 된다.

jj에서는 커밋보다 change라는 말을 더 많이 쓴다. change는 계속해서 진화하는 커밋이다. 그리고 “@”는 현재 change를 가리킨다.

새로운 change 시작하기

작업을 시작하기 전에 change를 하나 만들어야 한다. 앞으로 하게 될 모든 편집은 여기에 담긴다.

jj new main

이 명령은 main(북마크 main이 붙어 있는 커밋) 위에 당신의 커밋을 만든다(change와 commit의 구분은 실제로 존재하고 처음엔 꽤 헷갈린다). 현재 위치의 자식으로 새로운 change를 만들고 싶다면 그냥 jj new를 실행하면 된다.

change에 설명 달기

아마 스테이징 영역에 변경 사항을 추가한 뒤 메시지와 함께 커밋하는 방식에 익숙할 것이다. 그런데 jj에서는 변경 사항이 이미 다 담겨 있다. 다만 change에 설명(커밋 메시지 같은 것)이 없을 뿐이다!

jj describe -m 'hello i am a message'

change를 만들 때 바로 설명을 덧붙일 수도 있다.

jj new -m "hello i am a message"

커밋을 새 브랜치에 푸시하기

현재 change가 마음에 들고, 이를 GitHub에 올려서 에이전트가 당신의 (사실은 Claude의) 코드를 신랄하게 평가하게 하고 싶다면?

간단하다.

jj git push -c @

“현재 커밋을 git으로 푸시한다”는 뜻이다. 자동으로 브랜치를 만들고 원격으로 푸시해 준다. 작업을 하면서 이 명령을 계속 반복해도 되고, jj의 마법이 자동으로 git amend와 force push로 변환되어 처리된다.

그래도 jj가 자체 프로토콜과 호스트를 갖추게 될 날이 기대된다.

커밋 수정하기

기존 change로 돌아가서 수정하고 싶다면? 간단하다.

jj edit <change-id>

이 부분부터 진짜 강력하다는 느낌이 들기 시작하는데, 동시에 내가 기존에 가지고 있던 멘탈 모델이 무너지기 시작하는 지점이기도 하다. checkout이나 rebase 같은 걸로 번거롭게 씨름할 필요가 없다.

jj에는 change와 commit이 모두 있다. 스테이징 영역 대신 커밋을 계속 진화시킨다는 점을 기억하면, change를 고정된 ID로, commit ID를 편집할 때마다 바뀌는 ID로 이해하면 된다.

되돌리기

jj에서 하는 거의 모든 작업은 op 로그에 저장된다. 뭔가 잘못됐나? jj undo가 해결해 준다.

그 외 git 관련 기능

jj는 아직 대부분 git 위에서 동작한다. 기존 리포지토리를 변환하거나 GitHub와 연동하는 것도 쉽게 만들어 두었다.

# Create a jj repo inside of a git repo
jj git init

# Fetch from the remote (github etc)
jj git fetch

# Push
jj git push

# Clone
jj git clone

그럼 브랜치는 어디에 있나요

없다! 적어도 당신이 익숙한 그런 브랜치는 없다. jj는 “북마크”를 사용한다. 커밋에 붙일 수 있는 문자열이라고 보면 된다. change를 푸시하면 자동으로 북마크를 만들어 준다. 예를 들면 push-refrefref 같은 식이다.

# bookmark the current change with a name
jj bookmark create <name>

# move a bookmark to a new commit
jj bookmark set <name> -r <commit>

# other things exist just use --help

내가 느끼는 가장 큰 불편함은 새로운 change를 만들어도 북마크가 따라 움직이지 않는다는 점이다.

용어집에서 발췌하면 다음과 같다.

Git과 달리 “현재 북마크”라는 개념이 없다. 새로운 커밋을 만들어도 북마크는 움직이지 않는다. 다만 커밋이 재작성되면 북마크는 자동으로 그 커밋을 따라간다.

리베이스나 편집 같은 재미있는 이야기는 나중에 따로 정리해 보겠다.


참고: 이 글은 Vim으로 썼는데, jj<esc>에 바인딩해 둬서 엄청 불편했다.

원문은 Ellie Huxtable님이 에 게재했습니다.

이 글은 muse-spark-1.2-contributor 모델을 사용해 번역했습니다.