빌드노트
Windows에서 파일명 대소문자를 바꿨는데 Git에는 변경이 없다
Windows에서 파일명 대소문자만 바꾼 뒤 폴더와 Git 인덱스의 철자가 달라지는 상태를 실제로 확인했다. 두 경로와 core.ignorecase를 조회하고, 충돌 없는 임시 이름을 거친 git mv로 새 철자를 스테이징하는 방법을 설명한다.
Windows에서 Sample.txt를 sample.txt로 바꿨는데 git status에 변경이 없을 수 있다. 대소문자를 구분하지 않는 폴더에서 Git이 두 이름을 같은 파일로 취급하면, 디스크에 표시되는 이름은 바뀌어도 인덱스에는 이전 철자가 남는다. 이때는 실제 이름과 인덱스 경로를 각각 확인한 뒤, 비어 있는 임시 이름을 거쳐 git mv로 옮기면 다음 커밋에 새 철자를 준비할 수 있다.
폴더의 이름과 인덱스의 이름을 따로 읽는다
Git과 PowerShell 7이 설치된 환경에서, 확인하려는 저장소의 최상위 폴더로 이동한다. 아래 예는 그 폴더 바로 아래에 이미 커밋된 Sample.txt가 있고, 내용 수정 없이 이름만 sample.txt로 바꾼 상황이다. 자기 파일에 적용할 때는 경로를 바꾸고, 다른 수정이나 충돌이 섞여 있다면 먼저 해당 변경을 확인한다.
다음 명령으로 작업 위치, 디스크에 표시되는 철자, Git이 추적하는 철자를 나란히 확인한다.
git rev-parse --show-toplevel
Get-ChildItem -LiteralPath . -File | Select-Object -ExpandProperty Name
git ls-files --cached
git status --short
첫 출력이 의도한 저장소인지 확인한다. Get-ChildItem은 실제 폴더의 파일 이름을 보여 준다. git ls-files --cached는 다음 커밋을 준비하는 영역인 인덱스에 기록된 경로를 보여 준다. Git ls-files 문서 (새 탭에서 열림)
새 독립 저장소에 한 줄짜리 합성 파일을 만들고 PowerShell의 Rename-Item으로 이름만 바꿨다. 그 뒤 폴더 조회에는 sample.txt, 인덱스 조회에는 Sample.txt가 나왔고 git status --short는 아무것도 출력하지 않았다. 이어 git add --all을 실행해도 인덱스의 철자는 그대로였다. 이 실험에서 빈 status는 원하는 이름이 커밋될 준비가 됐다는 뜻이 아니었다.
같은 Windows에서도 폴더의 대소문자 처리 설정이 다를 수 있으므로, 파일이 있는 폴더의 속성과 Git의 유효 설정도 읽는다. 예제처럼 저장소 루트에 파일이 있다면 다음과 같다.
fsutil.exe file queryCaseSensitiveInfo .
git config --show-origin --get core.ignorecase
실험 폴더의 첫 출력은 대소문자 구분 속성이 disabled임을 알렸고, 두 번째는 file:.git/config와 true를 출력했다. 저장소를 만들 때 Git이 정한 값을 읽었으며 core.ignorecase를 변경하지 않았다. Microsoft 문서는 Windows의 일반적인 동작과 별도로 폴더마다 대소문자 구분 속성을 둘 수 있다고 설명한다. 파일이 하위 폴더에 있다면 . 대신 그 파일이 들어 있는 폴더를 조회한다. 조회가 실패하면 이 속성을 확인한 것으로 간주하지 않는다. Windows 대소문자 구분 문서 (새 탭에서 열림)
Git의 core.ignoreCase 문서는 이 값이 켜져 있을 때, 폴더 조회에서 발견한 이름의 대소문자가 달라도 같은 파일로 보고 기존 철자를 기억할 수 있다고 설명한다. git init과 git clone은 저장소를 만들 때 파일시스템을 조사해 필요하면 이 값을 켠다. 따라서 Windows라는 이유만으로 값을 단정하지 말고 설정 출처와 실제 폴더를 함께 본다. 위와 다른 값이 나오면 이 실험과 같은 조건인지부터 확인한다. Git core.ignoreCase 문서 (새 탭에서 열림)
비어 있는 임시 이름을 거쳐 옮긴다
인덱스에는 Sample.txt, 디스크에는 sample.txt가 있는 위 상태에서 실행한다. 임시 이름 Sample.rename-tmp.txt는 디스크와 인덱스 어디에도 없어야 한다. 다음 블록은 두 곳의 충돌을 확인하고, 각 이동이 실패하면 멈춘다.
$tracked = @(git ls-files --cached)
if ($LASTEXITCODE -ne 0) { throw '인덱스 조회에 실패했습니다.' }
if ((Test-Path -LiteralPath './Sample.rename-tmp.txt') -or
($tracked -icontains 'Sample.rename-tmp.txt')) {
throw '임시 경로가 이미 있습니다. 다른 임시 이름을 고르세요.'
}
git mv -- Sample.txt Sample.rename-tmp.txt
if ($LASTEXITCODE -ne 0) { throw '첫 번째 이동에 실패했습니다.' }
git mv -- Sample.rename-tmp.txt sample.txt
if ($LASTEXITCODE -ne 0) { throw '두 번째 이동에 실패했습니다.' }
첫 명령의 출발 경로는 인덱스에 남아 있던 Sample.txt다. 이 실험 폴더에서는 대소문자를 구분하지 않아 디스크의 sample.txt를 그 경로로도 찾을 수 있었다. 첫 이동이 끝나자 디스크와 인덱스 모두 임시 이름으로 바뀌었고, 두 번째 이동 뒤에는 둘 다 sample.txt가 됐다. 임시 이름을 중간에 커밋할 필요는 없다.
git mv는 성공하면 인덱스도 갱신한다. 두 번째 이동만 실패했다면 첫 이동은 이미 반영된 상태이므로, 폴더와 인덱스에 남은 임시 이름을 확인하고 실패 원인을 해결한 뒤 두 번째 이동을 이어 간다. Git mv 문서 (새 탭에서 열림)
별도 합성 저장소에는 임시 이름과 같은 파일을 미리 만들어 두었다. 위 사전 검사는 이동 전에 중단됐다. 디스크에는 없지만 인덱스에는 임시 경로가 남은 조건에서도 중단됐고, 원본 파일의 해시와 인덱스 경로는 보존됐다. 어느 쪽이든 임시 경로가 이미 있다면 다른 이름을 고른다. -f는 기존 대상을 덮어쓸 수 있는 옵션이므로 이 절차에는 넣지 않는다.
다음 커밋에 들어갈 경로로 완료를 판단한다
이동을 마친 뒤에는 인덱스 경로와 스테이징된 차이를 확인한다. 다음 명령의 마지막 줄은 아직 스테이징하지 않은 차이가 남았는지도 조회한다.
git ls-files --cached
git diff --cached --name-status -M100%
git diff --cached --summary -M100%
git diff --name-status
합성 파일 하나만 있는 실험에서 앞의 세 명령은 차례로 다음을 출력했고, 마지막 명령은 비었다.
sample.txt
R100 Sample.txt sample.txt
rename Sample.txt => sample.txt (100%)
--cached는 현재 커밋과 인덱스를 비교한다. -M100%는 내용이 같은 삭제·추가 쌍을 이름 변경으로 탐지하게 한다. 여기서 R100은 내용이 같은 이름 변경으로 표시됐다는 뜻이고, 인덱스에는 최종 경로 sample.txt가 남았다. 실제 파일의 SHA-256도 원본, OS 이름 변경 뒤, add 뒤, 두 번의 이동 뒤까지 다섯 시점 모두 같았다. Git diff의 비교 대상과 이름 변경 탐지 (새 탭에서 열림)
이 단계는 커밋할 변경을 준비한 상태다. 실험에서도 HEAD는 이동 전후 같았다. ls-files에 옛 철자가 남아 있거나 임시 이름이 보이면 아직 끝나지 않은 것이다. R100 대신 내용 변경이 함께 보인다면 파일 수정이 섞였는지 staged diff 본문을 읽는다. 평소 커밋 전에 경로 제한 없는 git diff --cached로 다른 스테이징 변경까지 함께 확인한다.
대조 실험에서 Sample.txt를 대소문자 외의 글자도 다른 Guide.txt로 OS에서 바꾸자, status에는 옛 경로의 삭제와 새 미추적 파일이 보였다. 이 경우에는 git add --all 뒤 R100이 기록됐다. 앞선 add가 이름 변경을 전혀 처리하지 못한 것이 아니라, 두 경로를 같은 파일로 취급하는 조건에서 이전 철자가 유지된 것이다.
이 증상을 해결하려고 core.ignorecase를 일괄해서 끌 필요는 없다. 그 설정은 파일시스템 자체의 대소문자 처리 방식을 바꾸지 않으며, 공식 문서도 환경에 맞지 않게 바꾸면 예상 밖 동작이 생길 수 있다고 설명한다. 파일 하나의 새 이름을 인덱스에 명시하고 결과를 확인하는 것으로 해결 범위를 좁힐 수 있다.
직접 실행한 환경은 Windows 빌드 26200, NTFS, Git 2.45.1.windows.1, PowerShell 7.6.6이었다. 현재 공식 문서와 Git v2.45.1의 설정 문서 (새 탭에서 열림), mv 문서 (새 탭에서 열림)를 함께 확인했다. 대소문자 구분을 켠 폴더, 디렉터리 전체의 이름 변경, 충돌 중인 인덱스, 다른 운영체제는 실행하지 않았다. 한 번의 직접 git mv가 모든 환경에서 실패한다는 비교도 하지 않았다. 여기서 확인한 해법은 대소문자를 구분하지 않는 이 폴더에서, 충돌 없는 임시 경로를 거쳐 인덱스의 철자를 갱신하는 절차다.