환경 Git 2.50.1 · macOS(APFS) · 2026-08-05 공식 문서 확인
이름을 소문자로 바꾸고 커밋했는데 리눅스 CI 에서만 모듈을 못 찾는 경우가 있습니다. 맥의 파일시스템이 대소문자를 구분하지 않아서 인덱스에는 옛 경로가 그대로 남기 때문입니다. 재현과 정리 순서를 공식 문서 기준으로 정리했습니다.
파일시스템이 대소문자를 구분하지 않으면 Git도 그 전제를 따른다
맥의 APFS 는 기본 설정에서 파일 이름의 대소문자를 구분하지 않습니다. UserService.js 와 userService.js 는 같은 파일을 가리키고, 같은 폴더 안에 둘이 함께 존재할 수도 없습니다. 윈도우의 NTFS 도 대체로 같은 성질을 가집니다.
Git 은 이 환경에 맞춰 동작을 바꿉니다. 공식 git-config 문서의 core.ignoreCase 항목은 이 값이 APFS, HFS+, FAT, NTFS 처럼 대소문자를 구분하지 않는 파일시스템에서 Git 이 더 잘 동작하도록 여러 우회 동작을 켜는 내부 변수라고 설명합니다. 예로 든 상황도 정확히 같습니다. Git 이 Makefile 을 기대하는데 디렉터리 목록에서 makefile 이 발견되면 같은 파일로 간주하고 Makefile 로 계속 기억한다는 것입니다.
이 값은 기본이 false 이지만, 문서는 git clone 과 git init 이 저장소를 만들 때 환경을 조사해 적절하면 core.ignoreCase 를 true 로 설정한다고 밝힙니다. 맥에서 클론한 저장소라면 대부분 이미 true 로 잡혀 있습니다. 그래서 파일 이름의 대소문자만 바꾸는 변경은 Git 에게 아무 일도 일어나지 않은 것과 같아집니다.
# 이 저장소가 어떤 전제로 동작하는지 먼저 확인한다
git config core.ignorecase
# true ← 맥에서 클론했다면 대개 이 값
# 파일 이름만 바꿔 보고 상태를 본다
mv src/UserService.js src/userService.js
git status --short
# (아무것도 출력되지 않는다)
# 그런데 인덱스에는 옛 경로가 그대로 있다
git ls-files src/
# src/UserService.jsstatus 가 비어 있는데 ls-files 에는 옛 이름이 보인다면, 이 글에서 다루는 상황이 맞습니다.
status 가 조용한 것은 변경이 없어서가 아니라, Git 이 같은 파일로 보고 있어서입니다.
git add 로는 이 변경이 잡히지 않는다
보통 이름을 바꾼 뒤에는 git add -A 를 한 번 실행하면 삭제와 추가가 함께 잡힙니다. 대소문자만 바뀐 경우에는 그렇지 않습니다. add 를 실행해도 status 는 여전히 비어 있고, 커밋을 시도하면 커밋할 것이 없다는 메시지가 나옵니다. 실제로 위 상태에서 git add -A 뒤에 git commit 을 걸어 보면 nothing to commit, working tree clean 으로 끝납니다.
그 상태로 임포트 구문만 고쳐 커밋하면 저장소에는 파일 이름이 UserService.js 인 채로, 코드는 userService 를 가져오는 모양으로 남습니다. 맥에서는 두 이름이 같은 파일이라 테스트가 통과합니다. 대소문자를 구분하는 리눅스에서 체크아웃하는 순간 임포트 대상이 사라집니다. CI 에서만 실패하는 이유가 여기에 있습니다.
경로를 실제로 바꾸려면 인덱스를 직접 건드리는 명령을 써야 합니다. git mv 는 이름을 바꾸면서 인덱스를 함께 갱신합니다. 공식 git-mv 문서도 성공적으로 끝나면 인덱스가 갱신되지만 그 변경은 여전히 커밋되어야 한다고 적어 두었습니다. 검증한 2.50.1 에서는 대소문자만 다른 대상으로 바로 실행해도 이름 변경으로 스테이징됐습니다. 다만 이 부분은 Git 버전과 플랫폼에 따라 거절될 수 있어, 막히면 중간 이름을 거치는 두 단계 방식이 확실합니다.
# 인덱스까지 함께 바꾼다
git mv src/UserService.js src/userService.js
git status --short
# R src/UserService.js -> src/userService.js
# 위 명령이 거절된다면 중간 이름을 한 번 거친다
git mv src/UserService.js src/userService.tmp
git mv src/userService.tmp src/userService.js
# 인덱스 갱신일 뿐이므로 커밋해야 확정된다
git commit -m "refactor: UserService.js 파일명 소문자로 정정"R 로 표시되면 인덱스가 새 경로를 들고 있다는 뜻입니다. 여기서 커밋하지 않으면 원래대로 돌아갑니다.
디스크의 이름이 아니라 인덱스의 경로를 바꿔야 커밋에 반영됩니다.
두 경로가 함께 커밋된 저장소는 증상이 다르게 나타난다
이미 옛 경로가 남은 채로 새 경로가 추가돼 버린 저장소도 있습니다. 대소문자를 구분하는 환경에서 파일을 새로 만들었거나, 여러 사람이 각자 정리하다 양쪽이 모두 올라간 경우입니다. 이때 커밋된 트리에는 src/Config.js 와 src/config.js 가 둘 다 들어 있습니다.
맥에서 이 커밋을 체크아웃하면 디스크에는 한쪽만 남습니다. 두 이름이 같은 자리를 가리키므로 나중에 기록된 쪽이 앞의 것을 덮습니다. 그리고 git status 에는 살아남지 못한 경로가 수정된 것으로 계속 표시됩니다. 실제로 재현해 보면 파일은 config.js 하나뿐인데 status 는 src/Config.js 를 M 으로 잡고 있습니다.
여기서 흔히 하는 대응이 상황을 더 헷갈리게 만듭니다. 지저분해 보이는 변경을 되돌리려고 git checkout 으로 그 경로를 복원하면, 이번에는 반대쪽 경로가 수정된 것으로 바뀝니다. 표시만 왔다 갔다 할 뿐 사라지지 않습니다. 더 위험한 것은 이 상태를 그대로 커밋하는 경우입니다. 디스크에 남은 한쪽의 내용이 다른 경로의 내용으로 기록되어, 원래 그 경로에 있던 코드가 조용히 덮입니다.
# 커밋된 트리에는 두 경로가 다 있다
git ls-tree -r HEAD --name-only | grep -i config
# src/Config.js
# src/config.js
# 그런데 작업 디렉터리에는 하나뿐이다
ls src/
# config.js
git status --short
# M src/Config.js ← 없는 파일이 계속 수정 상태로 남는다
# 되돌리려 하면 표시가 반대쪽으로 옮겨갈 뿐이다
git checkout -- src/Config.js
git status --short
# M src/config.jsM 표시가 두 경로 사이를 오간다면 파일이 깨진 게 아니라 인덱스에 경로가 둘이라는 신호입니다.
이 상태에서의 커밋은 정리가 아니라 한쪽 내용을 덮어쓰는 일이 됩니다.
인덱스에서 필요 없는 경로를 내리고 확인한다
정리는 남길 경로 하나를 정하고, 나머지를 인덱스에서 제거하는 순서로 합니다. git-rm 공식 문서는 --cached 를 주면 인덱스에서만 경로를 언스테이징하고 제거하며 작업 트리의 파일은 수정 여부와 관계없이 그대로 둔다고 설명합니다. 디스크에 남은 파일을 건드리지 않으므로 잘못 지울 위험이 적습니다.
제거한 뒤에는 반드시 커밋해야 트리에서 빠집니다. 그리고 작업 디렉터리를 한 번 비우고 다시 체크아웃해 보는 것이 확실한 확인 방법입니다. 남긴 경로 하나만 만들어지고 status 가 비어 있다면 정리가 끝난 것입니다.
core.ignoreCase 값을 false 로 바꿔서 해결하려는 시도는 권하지 않습니다. 문서는 Git 이 운영체제와 파일시스템에 맞는 이 변수의 올바른 설정에 의존하며, 값을 수정하면 예상치 못한 동작이 생길 수 있다고 명시합니다. 파일시스템이 여전히 대소문자를 구분하지 않는 이상 설정만 바꾸는 것은 전제를 어긋나게 만들 뿐입니다. 문제를 반복해서 겪는 저장소라면 설정을 뒤집는 대신, 대소문자를 구분하는 환경에서 도는 CI 가 체크아웃 직후 경로를 검사하도록 두는 편이 낫습니다.
# 남길 경로를 정하고 나머지를 인덱스에서만 내린다
git rm --cached src/Config.js
git commit -m "fix: 대소문자만 다른 중복 경로 정리"
# 트리에 하나만 남았는지 확인
git ls-tree -r HEAD --name-only | grep -i config
# src/config.js
# 깨끗한 상태에서 다시 받아 본다
rm -rf src && git checkout -- .
ls src/ && git status --short
# config.js
# (status 가 비어 있으면 정리 완료)--cached 는 인덱스만 건드립니다. 이 옵션을 빼면 디스크의 파일까지 지워집니다.
조치 후 확인할 것
- git ls-files 와 실제 ls 결과의 파일 이름이 글자 단위로 같은지 확인하세요. 여기서 어긋나면 커밋해도 반영되지 않습니다.
- git ls-tree -r HEAD --name-only 를 grep -i 로 걸러, 대소문자만 다른 경로가 둘 남아 있지 않은지 봅니다.
- 이름을 바꾼 뒤 임포트나 설정 파일에 적힌 경로 문자열도 함께 고쳤는지 확인합니다. 맥에서는 틀려도 통과합니다.
- 정리 커밋을 푸시한 뒤 대소문자를 구분하는 CI 에서 한 번 돌려, 체크아웃 단계에서 경로가 맞는지 확인합니다.
확인한 문서
- git-config 공식 문서 (core.ignoreCase, core.precomposeUnicode) (2026-08-05 확인)
- git-mv 공식 문서 (DESCRIPTION, --force, --dry-run) (2026-08-05 확인)
- git-rm 공식 문서 (--cached) (2026-08-05 확인)
정리하면 확인 순서는 단순합니다. core.ignoreCase 값을 보고, ls-files 와 실제 파일 이름을 맞춰 보고, 트리에 대소문자만 다른 경로가 둘 있지 않은지 봅니다. 그 다음에야 git mv 나 git rm --cached 로 인덱스를 정리하고 커밋합니다. 파일 이름을 바꾸는 일은 디스크가 아니라 인덱스에서 끝나야 합니다.
'복사금지 블로그 짜증나서 만든 개발문서' 카테고리의 다른 글
| docker compose down을 stop 대신 쓰면 무엇이 사라질까 (0) | 2026.08.14 |
|---|---|
| npm 배포 전에 어떤 파일이 올라가는지 미리 확인하는 방법 (0) | 2026.08.07 |
| docker stop이 10초씩 걸리고 종료 코드 137이 뜰 때 확인하는 순서 (0) | 2026.08.03 |
| rm 했는데 디스크 용량이 안 줄어들 때 확인하는 순서 (0) | 2026.08.02 |
| cron에서만 스크립트가 실패한다면 확인할 세 가지 (0) | 2026.08.01 |