주석 한 줄을 고치고 커밋했는데 git이 295줄을 바뀐 것으로 올렸습니다.
파일이 295줄짜리였으니 전부입니다. 바꾼 것은 한 줄인데 파일 전체가 바뀐 것으로 잡힌 겁니다.
눈으로 봐도 다른 줄은 그대로입니다. 그런데 git은 전부 다르다고 말합니다.
범인은 눈에 안 보이는 글자입니다.
줄바꿈도 글자입니다
엔터를 치면 화면에서는 커서가 다음 줄로 내려갈 뿐이지만, 파일 안에는 실제로 글자가 하나 들어갑니다. 이걸 줄바꿈 문자라고 부르는데, git은 이 글자도 파일 내용의 일부로 셉니다.
문제는 이 글자를 쓰는 방식이 두 가지라는 것입니다.
| 방식 | 들어가는 글자 | 주로 쓰는 곳 |
|---|---|---|
| LF | \n 하나 | 맥, 리눅스 |
| CRLF | \r\n 두 개 | 윈도우 |
CR은 타자기 시절에 종이를 왼쪽 끝으로 되돌리던 동작이고, LF는 종이를 한 칸 올리던 동작입니다. 윈도우는 그 둘을 그대로 물려받아 아직도 두 글자를 씁니다.
여기까지는 그냥 역사입니다. 문제는 그 다음입니다.
git 입장에서 const a = 1 뒤에 \n이 붙은 줄과 \r\n이 붙은 줄은 서로 다른 줄입니다.
보이는 글자가 똑같아도 다른 줄입니다.
그래서 파일 전체의 줄바꿈이 한 방식에서 다른 방식으로 바뀌면, git은 모든 줄이 바뀌었다고 판단합니다.
왜 갑자기 바뀌나
파일을 열어서 엔터를 다시 친 것도 아닌데 왜 바뀌느냐는 게 이상하게 느껴집니다.
파일을 통째로 다시 쓰는 도구들이 그렇게 만듭니다. 편집기가 저장할 때, 자동 정리 도구가 파일을 갈아끼울 때, 스크립트가 파일을 읽어서 다시 쓸 때입니다.
이때 도구는 원래 파일이 어느 방식이었는지 신경 쓰지 않고 자기 기본값으로 씁니다. 윈도우에서 도는 도구면 CRLF로 쓰고, 그 파일이 원래 LF였다면 그 순간 전부 바뀝니다.
여기서 중요한 건 아무 오류도 안 난다는 점입니다. 코드는 그대로 돌아가고 화면도 똑같습니다. 바뀐 것은 기록뿐입니다.
얼마나 커지는지 재봤습니다
말로만 하면 감이 안 와서 직접 만들어 재봤습니다.
빈 저장소를 하나 만들고 제 프로젝트에서 295줄짜리 파일을 하나 가져왔습니다. 그리고 두 경우를 비교했습니다.
첫 번째는 주석 한 줄만 고친 경우입니다.
1 file changed, 1 insertion(+), 1 deletion(-)
두 번째는 같은 한 줄을 고치되, 저장하면서 파일 전체가 CRLF로 바뀐 경우입니다.
1 file changed, 295 insertions(+), 295 deletions(-)
1줄이 295줄이 됐습니다. 파일이 295줄이니 정확히 전부입니다.
이 상태로 리뷰를 올리면 읽는 사람은 295줄을 훑어야 하고, 그중에 진짜 바뀐 한 줄이 숨어 있습니다. 현실에서는 아무도 안 읽고 그냥 승인 버튼을 누릅니다.
진짜 손해는 diff가 아니라 git blame입니다
diff는 그 순간만 지저분하고 끝납니다. 사람이 참고 넘기면 됩니다.
되돌릴 수 없는 손해는 그 다음에 옵니다.
git에는 blame이라는 기능이 있습니다. 파일의 각 줄을 누가 언제 왜 그렇게 썼는지 알려주는 기능인데, 코드를 고칠 때 가장 자주 쓰게 됩니다. 이상한 코드를 발견했을 때 “이건 왜 이래?”의 답이 거기 있으니까요.
아까 그 커밋을 실제로 반영하고 blame을 돌려봤습니다.
295 summary fix: 알림 주석 문구 다듬기
295줄 전부가 그 커밋 것으로 바뀌었습니다.
이 파일에는 원래 여러 사람이 여러 달에 걸쳐 쌓은 기록이 있었을 텐데, 그게 전부 “주석 문구 다듬기”라는 한 줄짜리 설명으로 덮였습니다.
이게 커밋 로그가 의미 없어진다는 말의 정체입니다. 기록이 사라진 건 아니지만, 평소에 쓰는 도구로는 안 보입니다.
저는 처음에 재현에 실패했습니다
여기서 제가 헛다리를 짚었습니다.
처음 실험했을 때 결과가 이렇게 나왔습니다.
1 file changed, 1 insertion(+), 1 deletion(-)
분명히 파일 전체를 CRLF로 바꿨는데 한 줄만 바뀌었다고 나옵니다. “이거 원래 안 생기는 문제인가” 싶었습니다.
이유는 제 맥의 git 설정이었습니다.
git에는 core.autocrlf라는 설정이 있습니다.
이걸 input으로 두면 커밋할 때 CRLF를 LF로 조용히 되돌려 놓습니다.
제 맥이 그 설정이었고, 그래서 제가 아무리 망가뜨려도 git이 몰래 고쳐주고 있었습니다.
설정을 끄고 다시 재니 그제야 295줄이 나왔습니다.
이게 이 문제에서 제일 중요한 부분입니다.
같은 코드를 같은 방법으로 커밋해도, 커밋하는 사람의 컴퓨터 설정에 따라 결과가 다릅니다. 어떤 사람은 아무 일도 안 생기고, 어떤 사람은 파일 전체가 바뀐 것으로 올라갑니다.
그래서 이 문제가 어쩌다 한 번씩 생기는 것처럼 보입니다. 규칙이 없어 보이는 게 아니라, 규칙이 사람마다 다른 겁니다.
팀에서 한 명만 윈도우를 쓰거나, 한 명만 설정이 다르면 그 사람이 만지는 파일에서만 이 일이 생깁니다.
이미 덮인 기록을 되찾는 법
blame은 옵션 하나로 되찾을 수 있습니다.
git blame -w 파일이름
-w는 공백 차이를 무시하라는 뜻인데, 줄 끝의 CR도 공백으로 칩니다.
돌려보니 이렇게 나왔습니다.
294 summary base
1 summary fix: 알림 주석 문구 다듬기
295줄 중 294줄이 원래 주인에게 돌아갔고, 진짜로 고친 1줄만 새 커밋 것으로 남았습니다. 정확합니다.
여기서 예상 밖의 결과가 하나 나왔습니다.
이런 경우에 흔히 권하는 방법은 --ignore-rev입니다.
“이 커밋은 없는 셈 치고 blame해줘”라는 뜻이라, 이름만 보면 이쪽이 더 정확할 것 같습니다.
같이 재봤습니다.
281 summary base
14 summary fix: 알림 주석 문구 다듬기
281줄만 돌아왔습니다. 14줄은 여전히 엉뚱한 커밋 것으로 남았습니다.
이름이 더 그럴듯한 쪽이 더 나빴습니다. 왜 14줄이 남는지는 정확히 모르겠습니다. git이 줄을 하나씩이 아니라 덩어리로 묶어서 따라가기 때문이라고 짐작하고 있지만, 확인은 못 했습니다.
diff 쪽에도 같은 옵션이 있습니다.
git diff -w # 공백 차이 무시
git diff --ignore-cr-at-eol # 줄 끝 CR만 무시
둘 다 295줄짜리 diff를 1줄로 되돌려줬습니다. 리뷰할 때 이걸 걸면 진짜 바뀐 것만 봅니다.
다만 이건 보는 쪽에서 가리는 것일 뿐, 저장소에 들어간 기록 자체는 그대로입니다.
애초에 안 생기게 하는 법
저장소 맨 위에 .gitattributes라는 파일을 하나 두면 됩니다.
* text=auto
*.bat text eol=crlf
*.sh text eol=lf
첫 줄이 핵심입니다. 텍스트 파일은 저장소에 넣을 때 항상 LF로 통일하라는 뜻입니다.
누가 어떤 편집기로 CRLF를 만들어 넣든, 커밋되는 순간 LF로 정리돼서 들어갑니다.
정말 되는지 확인해봤습니다.
core.autocrlf를 꺼놓은 상태, 그러니까 아까 295줄이 나왔던 그 조건에서 .gitattributes만 넣고 다시 했습니다.
1 file changed, 1 insertion(+), 1 deletion(-)
1줄로 돌아왔습니다.
core.autocrlf 설정으로도 같은 효과를 낼 수 있지만, 그건 각자 컴퓨터에 있는 설정입니다.
검사가 사람 손에 달려 있으면 언젠가 조용히 빠진다는 건 타입 검사에서 한 번 겪었습니다.
새 사람이 들어오면 안 되어 있고, 되어 있는지 확인할 방법도 마땅치 않습니다.
.gitattributes는 저장소 안에 있어서 받아가는 모든 사람에게 똑같이 적용됩니다.
이 차이가 큽니다.
두세 번째 줄은 예외를 적은 것입니다. 윈도우 배치 파일은 CRLF여야 제대로 돌고, 셸 스크립트는 LF여야 돕니다. 전부 LF로 통일하면 이 둘이 망가지니 따로 적어줍니다.
제 저장소를 확인해보니 추적 중인 3,860개 파일 중 CR이 들어간 것은 2개였고, 둘 다 gradlew.bat이었습니다.
윈도우 배치 파일이니 CRLF인 게 맞습니다.
그런데 그게 맞다는 걸 저장소 어디에도 안 적어놨더군요.
커밋 메시지에 적는 것으로는 부족합니다
“줄바꿈 정리 포함”이라고 커밋 메시지에 적어두면 리뷰하는 사람에게는 도움이 됩니다. 안 적는 것보다 훨씬 낫습니다.
그런데 blame은 커밋 메시지를 안 읽습니다. 석 달 뒤에 그 줄을 짚어봤을 때 나오는 건 여전히 “줄바꿈 정리 포함”이고, 그 줄이 왜 그렇게 쓰였는지는 여전히 안 나옵니다.
그래서 순서는 이렇게 잡는 게 낫습니다.
첫째, .gitattributes로 애초에 안 생기게 합니다.
둘째, 그래도 생겼으면 줄바꿈만 바꾸는 커밋을 따로 떼어냅니다.
셋째, 그 커밋 해시를 .git-blame-ignore-revs라는 파일에 적어둡니다.
깃허브와 최근 git은 이 파일을 알아서 읽습니다.
떼어내는 게 핵심입니다. 줄바꿈 정리와 실제 수정이 한 커밋에 섞여 있으면 어느 쪽도 되돌릴 수 없습니다.
어디까지 확인한 것인지
이 글의 숫자는 2026년 8월 21일에 git 2.40.0으로 재본 값입니다. 295줄짜리 파일 하나로 실험했고, 파일이 다르면 숫자도 당연히 달라집니다.
원래 이 일을 겪은 건 다른 곳이었고 그 상황은 다시 만들 수 없었습니다. 그래서 증상만 같게 맞춰 새로 실험을 짰습니다. 원인이 같은지는 제가 확인하지 못했습니다.
--ignore-rev에서 14줄이 남은 이유도 짐작만 적었습니다.
확인하면 다시 쓰겠습니다.