Gradle이 jj를 쓰지 못한 사소한 이유
(blog.gradle.org)- Gradle 팀은 Git 호환 VCS인 Jujutsu(jj) 를 일상 개발에 도입하려 했지만, Windows용
gradlew.bat의 줄바꿈 처리 때문에 채택을 미룸 - 핵심 문제는
jj가.gitattributes의 파일별 EOL 규칙을 읽지 못해*.bat text eol=crlf를 적용할 수 없다는 점임 - Git은
gradlew.bat를 저장소에는 LF로 정규화하고 작업 디렉터리에는 CRLF로 체크아웃하지만,jj는 이를 변경으로 인식해 phantom modification을 계속 만듦 gradlew.bat를 CRLF 그대로 커밋하고*.bat -text로 바꾸면 우회할 수 있으나, 편집기가 LF로 바꿔도 Git이 자동 복구하지 않아 Windows 실행 안정성이 흔들릴 수 있음- Gradle 팀은 당분간
git worktree로 여러 작업 디렉터리를 운영하고,jj의eol지원이 구현되면 다시 시도할 계획임
채택을 막은 실제 원인: gradlew.bat와 .gitattributes
- Jujutsu(
jj)는 Git 백엔드를 사용할 때 커밋과 파일을 Git에 저장하고 기존 Git 저장소와 함께 동작하는 Git 호환 버전 관리 시스템임 jj는 스테이징 영역을 없애고, 작업 복사본을 계속 갱신되는 실제 커밋으로 표현함- 커밋 편집이나 재정렬 뒤에는 후속 커밋을 자동으로 리베이스하고, 충돌은 작업을 멈추기보다 기록에 남길 수 있으며, 작업 로그에서 되돌리기도 가능함
- Gradle 엔지니어가
git을jj로 대체하려던 시도는jj의 모델이 아니라 Gradle 프로젝트가 디스크에 놓이는 방식의 작은 세부사항에서 막힘
Windows에서 gradlew.bat가 CRLF를 필요로 함
- 일반적인 Gradle 저장소에는 Gradle Wrapper와 함께
gradlew.bat가 포함됨 gradlew.bat는 label과goto를 사용하며,cmd.exe는 LF 전용 줄바꿈을 가진 배치 파일을 잘못 처리할 수 있음- Windows에서 안정적으로 실행하려면
gradlew.bat가 CRLF로 체크아웃되어야 함 - Gradle 프로젝트는 이 요구를
.gitattributes로 강제함
*.bat text eol=crlf
- Git에서는 이 동작이 표준적인 흐름임
- 파일은 인덱스에 정규화된 형태로 저장됨
eol=crlf속성은 체크아웃 시 작업 복사본에 CRLF를 물리화함- 편집 내용은 다시 저장될 때 정규화됨
jj의 .gitattributes 미지원이 만드는 phantom diff
jj는.gitattributes를 읽지 않아 파일별 줄바꿈 규칙을 적용할 수 없음- 관련 기능은 오래된 이슈인 jj-vcs/jj#53에서 추적되고 있으며, 적어도
eol속성 지원이 논의 중임 - 현재
jj에서 쓸 수 있는 수단은 전역 설정인working-copy.eol-conversion뿐임- Git의
core.autocrlf처럼 모든 파일에 한꺼번에 적용됨 .bat파일만 따로 지정할 수 없음
- Git의
- colocated repo에서는 문제가 즉시 드러남
- Git은
gradlew.bat를 LF로 저장하고 CRLF로 체크아웃함 jj는 해당 속성을 존중하지 않기 때문에 디스크의 CRLF를 Git의 LF blob 대비 변경으로 기록함- 영향을 받는 Gradle 프로젝트에서는
gradlew.bat의 지속적인 가짜 변경이 생김
- Git은
- 이 문제는 Gradle 팀만의 사례가 아님
- 한 사용자는 여섯 자리 수 파일에서 phantom change가 발생해
jj사용을 포기함
- 한 사용자는 여섯 자리 수 파일에서 phantom change가 발생해
제안된 우회책과 한계
jj의 작성자는 이슈에서 non-colocated workspace를 하나의 경로로 제안함- 예: Git 작업 복사본 바깥에서
jj git init --git-repo=<path>사용
- 예: Git 작업 복사본 바깥에서
- Gradle 엔지니어는 이 방식도 문제를 해결하지 못한다고 봄
jj가 저장된 LF blob에서gradlew.bat를 물리화하면 디스크에는 LF로 내려옴- LF
gradlew.bat는 Windows에서 실행되지 않음
- 전역 설정
working-copy.eol-conversion = input-output으로 CRLF를 강제할 수는 있음- 하지만 모든 텍스트 파일을 체크아웃 시 CRLF로 다시 쓰기 때문에 별도의 문제가 됨
동작하지만 취약한 해결책
- 실제로 동작하는 방법은
gradlew.bat를 저장소에 CRLF 그대로 커밋하고 정규화를 중단하는 것임 - 규칙은 다음처럼 바뀜
- 기존:
*.bat text eol=crlf - 변경:
*.bat -text
- 기존:
- 이렇게 하면 Git과
jj가 저장 트리와 작업 복사본 모두에서 CRLF에 동의함- 변환이 없어짐
- phantom diff도 사라짐
- Windows 실행도 유지됨
- 대신
*.bat -text는text eol=crlf가 제공하던 자동 복구를 포기함- 이전에는 실수로 LF가 들어와도 커밋 시 조용히 다시 정규화됨
- 이제는 LF가 그대로 남아
gradlew.bat가 Windows에서 조용히 깨질 수 있음
- 이 변환은 한 번만 하면 되고, 잘못된 편집은 보통 리뷰에서 잡힐 수 있음
- 그래도 Gradle 팀은 모든 Gradle 프로젝트가 CRLF를 직접 커밋하고 어떤 편집기도 이를 조용히 바꾸지 않는다는 전제에 생태계를 걸기 어렵다고 판단함
당분간 유지하는 대안: git worktree
jj의 workspace는 하나의 저장소를 공유하는 여러 작업 디렉터리를 제공해, 긴 CI 실행이나 진행 중인 변경이 다음 작업을 막지 않게 해줌- Gradle 팀은 이미
git worktree로 같은 요구를 충분히 처리하고 있음
git worktree add ../gradle-feature-x feature-x
git worktree add ../gradle-hotfix hotfix-7.6
- 이 방식은 두 개의 체크아웃과 하나의 객체 저장소를 사용함
- stash나 두 번째 clone 없이도 병렬 작업과 장기 유지되는
main체크아웃을 함께 운영할 수 있음 - Gradle 팀은 이 패턴을 수년간 사용해 왔음
jj 재검토 가능성
- 이번 사례가 Gradle 프로젝트에서
jj자체에 대한 최종 판단은 아님 jj의 operation log만으로도 더 깊이 검토할 가치가 있음eol지원은 해결 가능한 문제로 다뤄지고 있으며, 이슈에는gix-filter를 통한 구현 논의도 있음- 해당 지원이 들어오면 Gradle 팀은 다시 시도할 계획임
- 그 전까지는
gradlew.bat때문에 Git을 유지하고,git worktree가 필요한 역할을 맡음
댓글과 토론
Lobste.rs 의견들
-
Git의
autocrlf설정과 뒤이은.gitattributes설정은 처음부터 실수였고 앞으로도 그럴 것이라고 봄
버전 관리 시스템이 저장소 안의 파일을 디스크 위 파일과 다르게 저장해서는 안 됨
파일 형식을 강제하고 싶다면 린터, 커밋 훅, 병합 훅이 할 일이지 버전 관리 시스템의 역할은 아님- 처음부터 실수였다고 보는 쪽에 가깝지만, 지금은 명확히 실수라고 선언하고 특히
core.{eol, safecrlf, autocrlf}는 폐기 예정으로 돌려야 한다고 봄
줄 끝 변환이 추가된 건 Windows 때문뿐이었고, ␍ 전용은 Git 이전에 이미 죽었으며 Windows만 빼고 모두 ␊ 전용으로 정착했음
이제는 실제로 ␍␊를 신경 쓰는 주요 대상이 배치 파일 정도일 듯함
모든 언어가 ␍␊를 지원하려고 애쓰지만, 이제는 그것을 요구하거나 생성하는 것은 거의 없음
Notepad가 아직도 ␍␊를 기본값으로 쓰거나 그것만 지원한다면, Notepad를 쓰는 사람이 그 결과를 감수해야 함
Windows 호환성에서 더 흥미로운 영역은 파일 모드임
Git은 Unix 모드를 가져왔고 그 흔적이 남아 있어서, Windows에서 작업하다 보면 의도치 않은 모드 오류가 생기기도 함 - 오히려 반대로, 버전 관리는 이런 일을 덜 할 게 아니라 더 해야 한다고 느낌
도구가 충분히 좋아져서 체크아웃할 때 자동으로 공백을 쓰거나, 누군가가 강하게 고집하는 이상하게 구체적인 공백 개수로 맞춰 준다면 탭과 공백 논쟁을 멈출 수 있을 것임
더 나아가 버전 관리 시스템이 원문 텍스트가 아니라 추상 구문 트리(AST) 를 저장해야 한다고까지 볼 수도 있음
지금은 설득하기 어렵겠지만, 이런 방식이 매우 자연스러운 다른 시간선이나 미래도 상상 가능함
- 처음부터 실수였다고 보는 쪽에 가깝지만, 지금은 명확히 실수라고 선언하고 특히
-
.bat파일을 Windows가 아니면 어디서 실행한다는 건지 모르겠음
다른 동작이 조금이라도 바람직한 경우가 어디에 있는지도 의문임 -
필요한 줄 끝 형식 그대로 파일을 커밋하면 안 된다는 논증에는 설득되지 않음
핵심은 “누군가 줄 끝을 망가뜨려 커밋하고, 코드 리뷰를 통과해 깨진 스크립트가 들어갈 수 있다”는 것처럼 보임
전부 맞지만 그래서 CI 린트 검사가 있는 것 아닌가?
해당 파일에서 CRLF가 아닌 줄 끝을 스캔하고 발견되면 CI를 실패시키면, 줄 끝을 망가뜨린 수정은 풀 리퀘스트 단계를 통과할 수 없음- 이 글의 목적은 Gradle에서의 jj만 다루는 게 아니라, JVM 생태계에서 jj를 쓰려는 모든 사람과 프로젝트에 이 문제가 관련된다는 점을 보여주는 것 같음
“그냥 CI를 추가하면 된다”는 답은 충분하지 않음
관련 프로젝트가 비교적 초보자가 만드는 Minecraft 모드일 수도 있고, 그중 누구든gradle.bat를 깨뜨리고도 알아차리지 못할 수 있음
나도 초보자가 시작하기에 CLI Git보다 훨씬 낫다며 jj를 대안으로 홍보한 책임이 있음
- 이 글의 목적은 Gradle에서의 jj만 다루는 게 아니라, JVM 생태계에서 jj를 쓰려는 모든 사람과 프로젝트에 이 문제가 관련된다는 점을 보여주는 것 같음
-
gradlew.bat에서gradlew.ps1로 옮기는 것, 즉cmd.exe/배치 파일에서 PowerShell로 옮기는 선택지는 가능할까?- Gradle에는 관여하지 않았지만, 몇 년 전 Leiningen에서 그렇게 해보려 했고 당시에는 완전히 엉망이라 전혀 실용적이지 않았음
지금은 MAYBE Windows 설치 기반의 90%가 충분히 최신 PowerShell을 갖췄다고 가정할 수 있을지도 모름
하지만 기본 실행 권한을 올려 줘야 하는 경우가 여전히 있고, 기업 환경에서는 잠겨 있을 수도 있음
PowerShell을 얼마나 써봤는지는 모르겠지만 “bash가 있으니.sh파일을 실행하면 된다” 같은 느낌이 아니었음
Windows 버전마다 기능 집합이 달라서,ksh와bash4를 비교하는 듯한 느낌이었음
- Gradle에는 관여하지 않았지만, 몇 년 전 Leiningen에서 그렇게 해보려 했고 당시에는 완전히 엉망이라 전혀 실용적이지 않았음