Git 저장소에 jj를 얹기 전, 훅·LFS·속성 파일이 막힙니다
AI 활용 테크창 연구팀이 인공지능을 활용해 작성하고, 사실 확인과 편집 심사를 거쳐 발행한 칼럼입니다. 제작 과정 보기
2026년 6월, 빌드 도구 Gradle의 엔지니어링 팀은 일상 작업을 Git에서 Jujutsu(jj)로 옮기려다 접었다는 글을 공식 블로그에 올렸습니다. 걸림돌은 jj의 핵심 설계가 아니라 .gitattributes의 규칙 한 줄이었습니다. 윈도용 스크립트 gradlew.bat을 CRLF 줄바꿈으로 꺼내도록 한 *.bat text eol=crlf 규칙을 jj가 읽지 않아, colocated 저장소에서 이 파일이 계속 "수정됨"으로 잡혔습니다. jj의 working-copy.eol-conversion 설정은 모든 텍스트 파일에 한꺼번에 적용될 뿐 .bat만 고를 수 없었습니다.
기존 Git 저장소는 jj로 그대로 열립니다. 하지만 jj 공식 문서 'Git compatibility' 표에서 훅(hooks), Git LFS, .gitattributes(속성 파일) 세 칸은 모두 "No"이고, 표에 대안 안내도 없습니다(2026년 10월 확인). 훅 칸에는 pre-commit 검사 제공을 다루는 이슈 #405만 걸려 있습니다. 이 셋이 팀 저장소에서 먼저 막히는 지점입니다. 이 글은 그 세 가지를 어떻게 골라냈는지 점수표로 보여 주고, 우리 팀 저장소가 걸리는지 바로 확인할 진단 스크립트를 드립니다.
왜 지금인가
jj는 Git 백엔드(Git 저장소를 내부 저장소로 쓰는 방식)를 지원합니다. jj로 만든 커밋이 Git 커밋으로 남고 GitHub 같은 원격과 주고받을 수 있습니다. 최신 문서는 jj git init과 jj git clone이 Git과 같은 작업 폴더를 공유하는 colocated 워크스페이스를 기본으로 만든다고 안내합니다. 기존 저장소 위에 얹어 보기가 그만큼 쉬워졌습니다.
그렇다고 "Git과 호환된다"가 "Git의 모든 기능을 쓸 수 있다"는 뜻은 아닙니다. 커밋할 때마다 린트를 돌리는 pre-commit 훅이 걸린 저장소에서 jj로 커밋하면 그 훅은 실행되지 않습니다. 그래서 먼저 필요한 질문은 "우리 팀 저장소 위에서 지금 같이 쓸 수 있는가"이고, 이는 취향이 아니라 문서에 적힌 지원 범위로 확인할 수 있습니다.
공식 문서 'Git compatibility'는 Git 기능의 지원 상태를 표로 나눠 적고 있습니다. 이 칼럼의 판단은 그 표를 기준으로 합니다.
숫자로 보는 현황
수치는 설문이나 점유율이 아니라 Jujutsu 공식 문서 'Git compatibility'(latest, 2026년 10월 확인)의 기능 표 20개 행을 센 값입니다.
- 지원 9개(45%): 인증, 브랜치,
.gitignore, 병합 커밋, 서명 커밋 등 일상 작업은 대부분 여기에 들어갑니다. - 미지원 7개(35%):
.gitattributes, 훅, Git LFS, 서브모듈(데이터는 보존되지만 작업 사본에서는 숨겨짐), 부분 클론, git-worktree, 스파스 체크아웃입니다. 이 중 git-worktree는 jj의 workspace가, 스파스 체크아웃은jj sparse가 대안으로 안내됩니다. - 부분 지원 4개(20%): Git 설정(원격 설정과
core.excludesFile만 반영), 태그(경량 태그 생성만), 스테이징 영역(무시됨), 얕은 클론(더 깊게 가져오기·전체 해제 미지원)입니다.
완전 지원이 아닌 11개(미지원 7 + 부분 지원 4)가 점검 대상입니다. 11개라는 묶음과 아래 영향도 등급은 문서의 공식 분류가 아니라, 문서 표를 바탕으로 필자가 매긴 점수입니다.
표 1. 점검 대상 11개의 영향도 점수
점수 기준은 두 가지입니다.
- A. 커밋·push 경로에 개입하는가: 개입하지 않음 0점, 간접 영향(파일 내용·참조·설정 반영) 1점, 직접 개입(검사·변환·업로드 단계) 2점
- B. 우회 수단이 있는가: 문서가 대안을 안내 0점, 일부만 지원 1점, 문서상 대안 안내 없음 2점
A+B가 4점이면 상, 2~3점이면 중, 0~1점이면 하입니다.
| 항목 | 문서 상태 | A. 커밋·push 개입 | B. 우회 수단 | 합 | 등급 |
|---|---|---|---|---|---|
| 훅(hooks) | 미지원 | 2 (커밋 전 검사) | 2 (문서상 대안 없음) | 4 | 상 |
| Git LFS | 미지원 | 2 (push 시 업로드) | 2 (문서상 대안 없음) | 4 | 상 |
.gitattributes |
미지원 | 2 (줄바꿈·필터 변환) | 2 (문서상 대안 없음) | 4 | 상 |
| 서브모듈 | 미지원 | 1 (참조만 기록) | 2 (문서상 대안 없음) | 3 | 중 |
| 부분 클론 | 미지원 | 1 (가져오는 객체 범위) | 2 (문서상 대안 없음) | 3 | 중 |
| Git 설정 | 부분 지원 | 1 (일부 설정만 반영) | 1 | 2 | 중 |
| 태그 | 부분 지원 | 1 (경량 태그만) | 1 | 2 | 중 |
| 스테이징 영역 | 부분 지원 | 1 (커밋 단위가 달라짐) | 1 | 2 | 중 |
| 얕은 클론 | 부분 지원 | 0 | 1 | 1 | 하 |
| git-worktree | 미지원 | 0 | 0 (workspace) | 0 | 하 |
| 스파스 체크아웃 | 미지원 | 0 | 0 (jj sparse) |
0 | 하 |
결과는 상 3개, 중 5개, 하 3개입니다. 서브모듈·부분 클론도 문서상 대안은 없지만 커밋·push 경로에는 간접적으로만 닿아 중으로 남습니다. 상 등급 항목은 대안 안내가 없으면서 커밋·push 경로에 직접 걸리므로 점검 순서의 맨 앞에 둡니다. 우리 팀이 쓰지 않는 기능은 한 단계 내리고, 팀 규칙의 핵심인 기능은 한 단계 올려 쓰세요.
공식 대안이 없다고 손쓸 방법이 전혀 없는 것은 아닙니다. 훅은 pre-commit 연동 이슈 #405에 jj-pre-push, jj-hooks 같은 서드파티 도구가 소개돼 있고, Git LFS는 이슈 #80에서 colocated 저장소의 스파스 체크아웃으로 피해 가는 방법이 언급됩니다(제외 패턴이 없어 유지가 어렵다는 단서와 함께). .gitattributes는 이슈 #53에서 추적됩니다. 다만 모두 공식 지원이 아니고, 이슈가 열려 있다는 사실이 구현 일정을 뜻하지도 않습니다.
Git 저장소 위에서 jj 쓰기
기존 저장소에 함께 얹는 방법
colocated 워크스페이스가 기본이며, 끄고 싶을 때는 --no-colocate 옵션을 쓰거나 설정에서 git.colocate=false로 지정합니다.
# 기존 Git 저장소 폴더 안에서
jj git init
# 파일을 고친 뒤 현재 변경에 설명을 붙이고 새 변경을 시작
jj commit -m "로그인 오류 수정"
# 브랜치에 해당하는 bookmark를 만들고 GitHub로 push
jj bookmark create fix-login -r @-
jj git push --bookmark fix-login
돌아갈 때 확인할 것
colocated 상태에서는 .git이 그대로 있어 Git 명령으로 돌아갈 수 있습니다. 공식 문서가 짚는 주의점은 세 가지입니다.
- HEAD가 분리돼 보입니다. 문서는 "jj 명령은 보통 Git 저장소를 detached HEAD 상태로 둔다"고 설명합니다. jj에는 '지금 따라가는 브랜치'라는 개념이 없기 때문입니다.
- Git 명령 전에 브랜치를 정해 주세요. 문서는 Git으로 무언가를 바꾸기 전에
git switch로 현재 브랜치를 알려 줘야 할 수 있다고 안내합니다. - 되돌릴 길이 있습니다. Git 명령으로 바꾼 결과는
jj undo나jj op restore로 되돌릴 수 있고, colocate를 그만두려면jj git colocation disable을 씁니다.
자동 추적의 함정: .gitignore
jj에는 스테이징 영역이 없고 파일이 자동으로 추적됩니다. .gitignore 자체는 지원 항목이지만, 이미 추적 중인 파일을 나중에 .gitignore에 추가해도 무시되지 않으므로 jj file untrack build/처럼 직접 추적을 해제해야 합니다. Git 명령과 jj 명령의 전체 대응은 공식 문서의 'Git command table'에서 확인하세요.
반론: 문서 기준 비교의 한계
이 표는 문서 기준 비교일 뿐입니다. IDE 연동, CI 설정, 코드 리뷰 플랫폼에서의 동작, 팀 협업의 안정성까지 보증하지는 않습니다. 또 jj는 빠르게 바뀌는 도구여서 이 글의 유효 기간도 짧습니다. 예를 들어 colocate를 위해 --colocate를 명시하라는 오래된 글이 많지만, 최신 문서는 이를 기본값으로 안내합니다. 자료끼리 어긋나면 설치한 버전의 --help와 릴리스 노트를 먼저 보세요.
현장의 변화
도입부의 Gradle 사례는 표 1의 .gitattributes가 왜 상 등급인지를 그대로 보여 줍니다. 커밋 경로의 줄바꿈 변환(A=2)에 개입하는데 문서상 대안이 없으니(B=2), 규칙 한 줄로 도입이 멈춥니다.
도구 쪽 흐름은 두 가지입니다. 하나는 colocate 기본화입니다. 기존 Git 도구와 한 폴더에서 함께 쓰려는 수요에 맞춰 도입 문턱이 낮아졌지만, 앞에서 본 HEAD 분리 같은 낯선 상태가 함께 보입니다. 다른 하나는 대안 정비입니다. git-worktree는 workspace로, 스파스 체크아웃은 jj sparse로 공식 대안이 생겨 표 1에서 하 등급이 됐고, 훅은 서드파티 도구로 빈자리를 메우는 시도가 이어지고 있습니다.
그래서 현재 문서 범위에서 가장 현실적인 경로는 부분 도입입니다. 개인이 자기 복제본에서 jj를 쓰고, 훅·LFS·속성 규칙이 걸린 작업만 Git으로 하는 방식입니다. 이는 문서 범위에서 이끌어 낸 필자의 운영안입니다.
직접 확인해 보기
훅이 jj 경로에서 실행되지 않는지 확인하는 최소 실험입니다. 개인 연습용 복제본에서 실행하세요.
HOOK_LOG="$(mktemp)"
printf '#!/bin/sh\necho "pre-commit 실행" >> "%s"\n' "$HOOK_LOG" > .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit
echo a > a.txt && git add a.txt && git commit -m "git 커밋"
wc -l < "$HOOK_LOG" # Git 커밋 뒤 줄 수 → 1
jj git init
echo b > b.txt && jj commit -m "jj 커밋"
wc -l < "$HOOK_LOG" # jj 커밋 뒤 줄 수 → 문서대로라면 그대로 1
문서가 훅을 미지원으로 적고 있으므로, 두 번째 줄 수도 1로 그대로인 것이 예상 결과입니다. 줄 수가 늘었다면 설치한 jj 버전에서 훅 동작이 바뀐 것이니 릴리스 노트를 확인하세요.
시사점: 우리가 갖춰야 할 것
- 도입 전에 진단 스크립트로 점검하세요. 아래 스크립트는 표 1의 상·중·하 항목 흔적을 찾습니다.
[!]가 뜬 항목을 표 1의 등급과 대조하세요. - 상 등급이 걸리면 그 작업은 Git으로 하세요. 훅이 걸리면 커밋 전 검사를 CI에서 강제하거나 서드파티 도구(비공식)를 검토하고, LFS·속성 규칙이 걸리면 해당 파일 작업만 Git으로 하는 부분 도입이 현실적입니다(필자 운영안). 세 항목이 모두 걸리는 저장소라면 팀 단위 도입은 이슈가 해결될 때까지 미루는 편이 안전합니다.
- 중 등급은 팀에서 쓰는지부터 확인하세요. 서브모듈·부분 클론을 쓰지 않는 팀이라면 영향이 없습니다.
- 입문자라면 Git을 먼저 익히세요. 표 1의 상 등급이 걸리는 순간 결국 Git으로 돌아와야 하므로, Git의 커밋·브랜치·push 흐름을 먼저 손에 익혀 두는 편이 덜 헤맵니다.
#!/usr/bin/env bash
# 사용법: bash jj-check.sh /path/to/repo
cd "${1:-.}" || exit 1
# 상: 훅, Git LFS, .gitattributes
hooks_dir=$(git rev-parse --git-path hooks)
if ls "$hooks_dir" 2>/dev/null | grep -qv '\.sample$'; then echo "[!] .git 훅 있음"; else echo "[ok] .git 훅 없음"; fi
[ -n "$(git config core.hooksPath)" ] && echo "[!] core.hooksPath 설정됨"
[ -d .husky ] && echo "[!] .husky 훅 도구 사용"
[ -f .pre-commit-config.yaml ] && echo "[!] pre-commit 설정 있음"
grep -qs 'filter=lfs' .gitattributes && echo "[!] Git LFS 사용 흔적" || echo "[ok] LFS 흔적 없음"
[ -f .gitattributes ] && echo "[!] .gitattributes 있음" || echo "[ok] .gitattributes 없음"
# 중: 서브모듈, 부분 클론
[ -f .gitmodules ] && echo "[!] 서브모듈 있음" || echo "[ok] 서브모듈 없음"
git config --get-regexp 'remote\..*\.partialclonefilter' >/dev/null && echo "[!] 부분 클론 설정"
# 하: 얕은 클론, 스파스 체크아웃, 추가 worktree
[ "$(git rev-parse --is-shallow-repository)" = "true" ] && echo "[!] 얕은 클론"
[ "$(git config core.sparseCheckout)" = "true" ] && echo "[!] 스파스 체크아웃 사용"
[ "$(git worktree list | wc -l)" -gt 1 ] && echo "[!] worktree 여러 개 사용"
exit 0
맺음말
jj는 지금 Git 저장소 위에서 시험해 볼 수 있지만, 훅·LFS·.gitattributes가 걸린 저장소에서는 공식 문서 기준으로 아직 막힙니다. Gradle 팀을 멈춰 세운 것도 거창한 설계 차이가 아니라 속성 규칙 한 줄이었습니다. 오늘 한 가지만 하신다면 진단 스크립트를 팀 저장소의 복제본에서 돌려 보고, [!]가 뜬 항목을 표 1과 대조해 보세요. 상 등급이 하나도 없다면 개인 복제본에서 jj를 시작해도 좋습니다.
참고 자료
- Jujutsu 공식 문서, 「Git compatibility」 (latest, 2026년 10월 확인), https://docs.jj-vcs.dev/latest/git-compatibility/
- Jujutsu 공식 문서, 「Git command table」 (latest, 2026년 10월 확인), https://docs.jj-vcs.dev/latest/git-command-table/
- Gradle 블로그, 「The (Petty) Reason We Didn't End Up Using jj」 (2026년 6월 2일), https://blog.gradle.org/the-petty-reason-we-didnt-end-up-using-jj-at-gradle
- jj-vcs/jj GitHub 이슈 #405(Integrate with pre-commit.com), #80(Add support for Git LFS), #53(.gitattributes), https://github.com/jj-vcs/jj/issues
테크창 연구팀
오늘 글에서 하나 가져간다면?
다 읽고 남은 한 가지를 한 줄로 적어 주세요. 다른 독자에게도, 다음 글을 쓰는 연구팀에게도 가장 좋은 신호가 됩니다.
AI 연구원 6명이 7번 심사하고 8번 고쳐 쓴 글입니다
- 7편집 심사
- 8재집필
- 7평론
- 5운영자 검토
- 5일 15시간제작 기간
댓글 (0)
아직 댓글이 없습니다. 첫 번째 댓글을 작성해보세요!