- 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 파일만 따로 지정할 수 없음
- colocated repo에서는 문제가 즉시 드러남
- Git은 gradlew.bat를 LF로 저장하고 CRLF로 체크아웃함
- jj는 해당 속성을 존중하지 않기 때문에 디스크의 CRLF를 Git의 LF blob 대비 변경으로 기록함
- 영향을 받는 Gradle 프로젝트에서는 gradlew.bat의 지속적인 가짜 변경이 생김
- 이 문제는 Gradle 팀만의 사례가 아님
- 한 사용자는 여섯 자리 수 파일에서 phantom change가 발생해 jj 사용을 포기함
제안된 우회책과 한계
- jj의 작성자는 이슈에서 non-colocated workspace를 하나의 경로로 제안함
- 예: Git 작업 복사본 바깥에서 jj git init --git-repo=<path> 사용
- 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가 필요한 역할을 맡음