빌드노트
Git 브랜치에는 파일이 있는데 작업 폴더에는 없다
현재 커밋과 인덱스에는 있지만 작업 폴더에는 없는 파일을 cone sparse checkout에서 재현했다. 같은 경로의 ls-tree·ls-files·실제 존재 여부와 선택 목록을 비교하고, 기존 선택을 유지하는 add로 필요한 디렉터리를 복원한다.
Git에서 현재 커밋의 파일 목록을 보면 경로가 있는데, 작업 폴더에서는 그 파일을 찾을 수 없을 때가 있다. 이때 git status까지 깨끗하다면 sparse checkout 선택을 확인할 필요가 있다. sparse checkout은 추적 중인 파일 가운데 작업 폴더에 펼칠 범위를 고른다. 선택 밖의 파일은 커밋과 인덱스에 남아 있어도 디스크에는 없을 수 있다.
필요한 디렉터리가 선택에서 빠진 경우에는 git sparse-checkout add로 기존 선택에 더한다. 먼저 커밋에 없는 파일인지, 인덱스에서 빠진 파일인지, 작업 폴더에서만 빠진 파일인지 나누어 확인해야 한다.
커밋·인덱스·실제 파일을 같은 경로로 비교한다
Windows 11, Git 2.45.1.windows.1, PowerShell 7.6.6에서 새 로컬 저장소를 만들어 비교했다. src/app.txt에는 app-v1, docs/guide.txt에는 guide-v1, 루트의 notice.txt에는 root-v1 한 줄씩을 넣어 한 번 커밋했다. 처음에는 세 파일이 모두 있었다. 실제 프로젝트에서 발생한 삭제 사건을 재구성한 것은 아니다.
변경 사항이 없는 이 실험 저장소에서 git sparse-checkout set --cone --no-sparse-index src를 실행했다. 디렉터리 단위로 고르는 cone 모드를 쓰고, 인덱스를 압축하는 sparse-index는 끈 조건이다. 이후 src/app.txt와 notice.txt는 남았지만 docs/guide.txt는 없어졌다. 이 set 명령은 실험 조건을 만든 명령이며, 이미 sparse checkout을 쓰는 저장소에서 진단을 시작할 때 실행할 명령은 아니다.
자기 저장소에서는 Git이 설치된 PowerShell을 열고 git rev-parse --show-toplevel로 최상위 경로부터 확인한다. 출력된 저장소 루트로 이동한 뒤, 아래의 docs/guide.txt를 찾으려는 파일의 루트 기준 경로로 바꾼다. 명령이 오류를 내면 그 오류부터 확인하며, 오류 없는 빈 출력과 구분한다.
git ls-tree -r --name-only HEAD -- docs/guide.txt
git ls-files -- docs/guide.txt
Test-Path -LiteralPath './docs/guide.txt' -PathType Leaf
git status --short
실험에서는 다음 세 줄이 나왔다. 마지막 status는 아무것도 출력하지 않았다.
docs/guide.txt
docs/guide.txt
False
첫 줄은 현재 커밋인 HEAD의 트리에 파일이 있다는 뜻이다. 두 번째 줄은 다음 커밋에 넣을 내용을 관리하는 인덱스에도 경로가 있다는 뜻이다. False는 실제 파일이 없다는 확인이다. ls-files에 나온다고 작업 폴더에도 파일이 있는 것은 아니다. ls-tree의 트리 조회 (새 탭에서 열림), ls-files의 기본 인덱스 조회 (새 탭에서 열림)
첫 조회부터 빈 출력이면 현재 커밋과 입력 경로를 먼저 확인한다. 웹에서 보고 있는 다른 브랜치의 파일이 현재 HEAD에도 있다는 보장은 없다. 트리에는 있지만 인덱스 조회가 비어 있다면 삭제가 스테이징됐는지 등을 살펴야 한다. 위 세 줄과 다른 결과를 sparse checkout 하나로 설명하지 않는다.
선택에서 빠졌다면 디렉터리를 추가한다
파일이 작업 폴더에서만 빠졌다면 sparse 설정과 선택 목록을 읽는다. 같은 실험 저장소 루트에서 실행한 명령이다.
git config --bool core.sparseCheckout
git config --bool core.sparseCheckoutCone
git sparse-checkout list
git ls-files -t -- docs/guide.txt
true
true
src
S docs/guide.txt
이 결과에서는 sparse checkout과 cone 모드가 모두 켜져 있고, 선택한 디렉터리는 src다. S는 해당 인덱스 항목에 skip-worktree 표시가 있다는 뜻이다. Git은 이 표시가 있는 파일의 부재를 일반적인 작업 파일 삭제로 취급하지 않는다. 앞서 파일이 없는데도 status가 비었던 이유와 연결된다. sparse checkout의 동작 (새 탭에서 열림), ls-files의 S 표시 (새 탭에서 열림)
S 하나만으로 sparse 선택이 원인이라고 확정하지 않는다. 여기서는 두 설정 값, src만 있는 목록, docs/guide.txt의 실제 부재를 함께 확인했다. 설정이 꺼져 있거나, cone 모드가 아니거나, 명령이 sparse checkout 저장소가 아니라는 오류를 내면 이 실험과 조건이 다르다. 설정값이 없어서 git config가 출력 없이 종료 코드 1로 끝나는 경우도 true로 읽지 않는다.
notice.txt가 남은 것도 선택 실패가 아니다. cone 모드는 선택한 디렉터리의 하위 파일과 함께 루트 및 그 경로의 상위 디렉터리에 바로 놓인 파일도 포함한다. 이번에 src만 목록에 있어도 루트 파일이 남은 관찰은 이 규칙과 맞았다. cone 모드의 포함 범위 (새 탭에서 열림)
이제 docs도 필요하다면 기존 선택에 추가한다. 다음은 위와 같이 cone sparse checkout이 켜져 있고, 트리와 인덱스에 파일이 있으며, 미커밋 변경이 없는 조건에서 이어 실행한 명령이다. 자기 저장소에서도 status에 변경이나 충돌이 있으면 먼저 내용을 확인하고 보존한 뒤 적용한다. cone 모드의 입력은 파일 하나가 아니라 포함할 디렉터리다.
$before = git rev-parse HEAD
git sparse-checkout add docs
git sparse-checkout list
Test-Path -LiteralPath './docs/guide.txt' -PathType Leaf
Get-Content -LiteralPath './docs/guide.txt'
git ls-files -t -- docs/guide.txt
$before -eq (git rev-parse HEAD)
git status --short
add는 종료 코드 0으로 끝났고, 출력은 다음과 같았다. 마지막 status는 다시 빈 출력이었다.
docs
src
True
guide-v1
H docs/guide.txt
True
선택 목록에는 기존 src와 추가한 docs가 함께 있다. 파일은 guide-v1로 돌아왔고 skip-worktree 표시도 해제됐다. 마지막 True는 추가 전후 HEAD가 같다는 비교 결과다. 별도로 바이트를 비교했을 때 복원된 파일은 최초 내용과 같았고, 기존 src/app.txt와 notice.txt도 변하지 않았다. 인덱스의 경로·파일 모드·내용 객체 ID는 같았으며, 달라진 것은 skip-worktree 표시였다.
여기서 git sparse-checkout set docs로 바꾸면 기존 선택을 docs로 다시 정하게 된다. src를 계속 보면서 범위를 넓히려는 목적에는 add docs가 맞다. 공식 문서의 set과 add 설명 (새 탭에서 열림)
직접 확인한 범위는 깨끗한 독립 저장소, 일반 파일 두 디렉터리, cone 모드와 sparse-index를 끈 조건이다. 수정된 파일·충돌·서브모듈·non-cone 모드·부분 복제에서의 동작은 실행하지 않았다. add가 실패하거나 파일이 돌아오지 않으면 오류와 위 조회 결과를 다시 확인해야 한다. 이 실험처럼 같은 커밋에서 선택 밖으로 빠진 파일이라면, 필요한 디렉터리를 추가하는 것으로 작업 폴더의 범위를 넓힐 수 있다.