본문 바로가기
빌드노트 검색

빌드노트

Git에 커밋된 파일이 archive ZIP에서 빠지는 이유

커밋에 있는 파일도 export-ignore 속성 때문에 git archive ZIP에서 빠질 수 있다. 내보낼 커밋의 속성을 조회하고, 미커밋 수정과 --worktree-attributes의 차이를 확인한 뒤 공유할 포함 정책을 커밋하는 방법을 설명한다.

git ls-tree에는 config.ini가 있는데 git archive로 만든 ZIP에는 없을 수 있다. 파일을 커밋했는지와 archive에 포함하는지는 별도 조건이다. .gitattributes에서 그 파일에 export-ignore가 설정돼 있으면 archive에서 빠진다. 기본 명령은 현재 편집 중인 파일이 아니라, 내보내는 커밋의 .gitattributes를 읽는다. git archive의 attributes 적용 기준 (새 탭에서 열림)

그래서 .gitignore를 고치거나 파일을 다시 추가하기 전에, 내보낼 커밋의 파일 목록과 제외 속성을 함께 확인해야 한다. 여기서는 새 독립 저장소의 합성 README.md와 config.ini로 이 차이를 비교했다. 실제 배포 사고를 재구성한 것은 아니다.

HEAD에 있는 파일과 ZIP에 있는 파일을 대조한다

다음 명령은 Git이 설치된 PowerShell 7에서 저장소 루트를 현재 위치로 두고 실행한다. HEAD는 현재 체크아웃한 커밋을 뜻한다. 배포 명령이 태그나 특정 SHA를 내보낸다면 아래 조회와 archive 명령의 HEAD를 모두 그 대상으로 바꾼다. 첫 명령의 최상위 경로가 의도한 저장소인지 확인하고, 명령이 실패하면 그 지점에서 멈춘다.

먼저 두 파일이 커밋에 있는지, 그 커밋에서 어떤 제외 속성이 적용되는지 확인한다.

git rev-parse --show-toplevel
git ls-tree -r --name-only HEAD -- README.md config.ini
git check-attr --source=HEAD export-ignore -- README.md config.ini
git show HEAD:.gitattributes

실험에서 최상위 경로를 제외한 출력은 다음과 같았다.

README.md
config.ini
README.md: export-ignore: set
config.ini: export-ignore: set
README.md export-ignore
config.ini export-ignore

앞의 두 줄은 파일이 HEAD에 있다는 증거다. 이어지는 set은 두 경로에 export-ignore가 켜졌다는 뜻이고, 마지막 두 줄은 루트 .gitattributes의 실제 내용이다. --source=HEAD를 지정한 이유는 현재 작업 폴더가 아니라 내보낼 커밋을 기준으로 조회하기 위해서다. 이 옵션은 아래 실험 버전인 Git 2.45.1에서 실행했다. 구버전에서 옵션 오류가 나면 git show HEAD:.gitattributes로 규칙을 먼저 확인하되, 루트 파일만으로 하위 경로의 최종 속성을 단정하지 않는다. git check-attr의 --source와 출력 값 (새 탭에서 열림)

이제 같은 HEAD로 ZIP을 만들고 항목을 읽는다. 아래 tar는 ZIP을 읽을 수 있는 Windows의 bsdtar이며, 실제 실행 버전은 3.8.8이다. ZIP은 저장소 바로 위에 만들므로 before.zip, 뒤에서 쓸 ordinary.zip, preview.zip, after.zip이라는 기존 파일이 없는지 먼저 확인한다.

git archive --format=zip --output=../before.zip HEAD
tar -tf ../before.zip
.gitattributes
.gitignore

archive 명령은 종료 코드 0으로 끝났지만 두 파일은 없었다. HEAD에서 파일이 사라진 것이 아니라, 내보내기 규칙이 두 경로를 제외한 결과다. 자기 ZIP에 경로가 이미 있다면 이 원인으로 단정하지 말고, 실제 배포한 ZIP과 압축 해제 경로부터 대조한다. tar가 ZIP을 읽지 못한 경우도 파일 누락과 구별해야 한다.

이 저장소의 .gitignore에는 config.ini가 들어 있었다. 하지만 이미 추적하던 파일이므로, export-ignore를 넣기 전에는 ZIP에도 포함됐다. .gitignore는 추적하지 않을 파일을 정하는 규칙이며, 이미 추적 중인 파일을 archive에서 제외하는 규칙이 아니다. gitignore의 적용 대상 (새 탭에서 열림)

.gitattributes를 고쳤는데도 같은 ZIP이 나온다면

config.ini가 배포에 필요한 공유 기본 설정이라고 결정했다면, 그 경로의 제외 규칙을 고친다. 이 예제에서는 기존 .gitattributes의 config.ini 줄만 다음처럼 바꾸고 README 제외는 유지했다. 실제 파일 전체를 아래 두 줄로 덮어쓰라는 뜻은 아니다.

README.md export-ignore
config.ini -export-ignore

속성 이름 앞의 -는 해당 속성을 끈다. 이 경우에는 단순한 제외 줄을 지워도 되지만, 여기서는 export-ignore를 명시적으로 해제하는 형태를 사용했다. .gitignore의 !config.ini와는 문법과 목적이 다르다. gitattributes의 속성 상태 (새 탭에서 열림)

이 변경을 아직 커밋하지 않은 상태에서 두 기준을 비교했다.

git check-attr export-ignore -- config.ini
git check-attr --source=HEAD export-ignore -- config.ini
config.ini: export-ignore: unset
config.ini: export-ignore: set

첫 결과는 현재 작업 폴더의 수정이 반영된 상태다. 두 번째 결과는 HEAD가 아직 예전 규칙을 가진다는 뜻이다. 이때 기본 git archive ... HEAD를 반복해도 config.ini는 계속 빠졌다. 작업 폴더에서 속성을 해제한 사실만으로 HEAD의 내보내기 정책이 바뀌지는 않는다.

커밋 전에 규칙 변경을 미리 확인하려면 --worktree-attributes를 붙여 비교할 수 있다. 다음 두 ZIP은 같은 HEAD를 대상으로 만들었다.

git archive --format=zip --output=../ordinary.zip HEAD
git archive --worktree-attributes --format=zip --output=../preview.zip HEAD
tar -tf ../ordinary.zip
tar -tf ../preview.zip

ordinary.zip의 목록은 앞과 같이 .gitattributes, .gitignore뿐이었다. preview.zip에는 두 항목과 함께 config.ini가 추가됐고, README.md는 계속 빠졌다. 현재 작업 폴더의 속성을 사용할 때만 수정한 포함 정책이 적용된 것이다. git archive의 --worktree-attributes (새 탭에서 열림)

다만 이 옵션은 파일 내용까지 작업 폴더의 수정본으로 바꾸지 않는다. 같은 비교에서 config.ini도 커밋된 mode = committed에서 미커밋 값인 mode = working-tree로 바꿔 두었다. 그런데 preview.zip에 들어간 값은 mode = committed였다. 옵션으로 바꾼 것은 속성을 읽는 기준이고, 내보낼 파일 내용의 기준은 여전히 HEAD였다.

공유할 포함 정책은 내보낼 커밋에 남긴다

팀이나 배포 작업이 같은 커밋에서 같은 포함 정책을 사용해야 한다면 .gitattributes 수정을 커밋한다. 커밋 전 git diff --cached 전체를 읽고 의도한 변경만 준비돼 있는지 확인한다. 다른 변경이 보이면 먼저 정리한 뒤 커밋 단계로 진행한다.

git add -- .gitattributes
git diff --cached
git commit -m "Include config in archive"
git archive --format=zip --output=../after.zip HEAD
tar -tf ../after.zip
.gitattributes
.gitignore
config.ini

마지막 목록은 커밋 뒤 생성한 ZIP의 실제 항목이다. 이제 기본 archive 명령에도 config.ini가 들어갔고 README 제외는 유지됐다. 이 커밋에는 attributes 수정만 넣었으므로, ZIP의 config 내용은 여전히 커밋된 mode = committed였다. 설정 값 변경까지 배포하려면 그 변경도 내보낼 커밋에 있어야 한다.

새 HEAD에서 고쳤다고 이전 릴리스의 결과가 바뀌지는 않는다. 같은 저장소에서 HEAD~1을 다시 내보냈을 때는 config가 계속 빠졌다. 배포가 예전 태그나 SHA를 가리킨다면, 포함 정책을 수정한 커밋을 내보내도록 대상도 맞춰야 한다.

루트 규칙을 고쳤는데 속성이 예상과 다르면 경로에 더 가까운 .gitattributes와 저장소의 .git/info/attributes도 확인한다. 공식 문서는 하위 파일의 우선순위와, .git/info/attributes가 가장 높은 우선순위를 가진다는 점을 설명한다. 이 실험에서는 사용자·시스템 attributes의 영향을 차단했고 별도 info 규칙은 두지 않았다. attributes의 우선순위 (새 탭에서 열림)

직접 실행한 범위는 Windows 11, PowerShell 7.6.6, Git 2.45.1.windows.1의 로컬 git archive ZIP이다. 커밋 전후 규칙, 미커밋 attributes, 파일 내용의 기준까지 비교했다. 원격 archive, 호스팅 서비스의 ZIP 다운로드, Linux와 다른 Git 버전은 실행하지 않았으므로 그 결과까지 같다고 확장하지 않는다.

← 전체 글 보기