.gitignore 파일이란?
.gitignore 파일은 어떤 파일을 버전 관리에서 제외할지 Git에게 알려 주는 텍스트 파일입니다. 각 줄이 하나의 패턴이며 — *.log, node_modules/, .env 등 — 패턴에 일치하는 추적되지 않은 경로는 git status에서 사라지고, git add .에서도 건너뛰어지며, 커밋되지 않습니다.
이 설명의 의미를 거의 혼자 떠받치는 표현이 추적되지 않은이고, “gitignore가 동작하지 않는다”는 사례는 패턴을 잘못 쓴 모든 경우를 합친 것보다 이 한마디로 설명되는 쪽이 더 많습니다. Git은 아직 추적하지 않는 파일에 대해서만 .gitignore를 봅니다. 한 번 커밋된 파일은 그 뒤에 .gitignore에 무엇을 덧붙여도 Git이 계속 추적합니다.
Gitignore Debugger가 하는 일
파일 경로와 .gitignore 내용을 붙여넣으면, 이 .gitignore 디버거가 Git과 똑같은 판정 — 무시됨 또는 무시되지 않음 — 을 내리고, 그 판정을 내린 정확한 규칙과 줄 번호까지 함께 알려 줍니다. 이어서 그 경로에 일치하는 모든 규칙을 Git이 평가하는 순서대로 나열하고, 실제로 결정을 내린 규칙을 표시합니다.
바로 이 마지막 부분이 단순한 gitignore 검사기에는 빠져 있는 것입니다. 파일이 무시된다는 사실 자체는 좀처럼 어려운 부분이 아닙니다. 어려운 것은 규칙 마흔 개 가운데 어느 규칙이 그렇게 했는지, 그리고 기대했던 규칙이 왜 졌는지를 아는 일입니다. 이는 양방향으로 도움이 됩니다. 커밋하고 싶은데 Git이 무시해 버리는 이유도, 지우고 싶은데 무시되지 않는 이유도 함께 알 수 있습니다.
엔진은 Git의 패턴 일치 규칙을 다시 구현했습니다. 기본 이름으로 일치하는 패턴과 고정된 패턴의 차이, *가 /를 절대 넘지 않는 점, **의 세 가지 형태, 디렉터리에만 해당한다는 뜻의 끝 /, 문자 클래스, 이스케이프된 끝 공백까지 다룹니다. 동작은 git check-ignore의 실제 출력과 맞춰 검증했으며, 짐작으로 만든 것이 아닙니다.
사용 방법
- 경로는 저장소에 나타나는 그대로, 루트를 기준으로 입력하세요 —
src/config/local.json처럼 쓰고C:\projects\app\src\config\local.json처럼 쓰지 마세요. 구분자는 슬래시를 쓰면 되고, Windows의 백슬래시는 알아서 변환됩니다. .gitignore는 전체를 붙여넣으세요. 의심스러운 한 줄만 남기고 줄이지 마세요. 진짜 원인은 보통 규칙 사이의 우선순위이고, 파일을 잘라내면 바로 그 정보가 사라집니다.- 먼저 판정을 읽고, 그 아래의 규칙 사슬을 따라가 보세요. 동작하지 않는 gitignore 규칙이 더 이상 미스터리가 아니게 되는 곳이 여기입니다.
파일이 아니라 디렉터리를 대상으로 gitignore 규칙을 시험하려면 경로를 슬래시로 끝내세요 — build/처럼요. 이 구분은 중요합니다. /로 끝나는 규칙은 디렉터리에만 일치하기 때문입니다.
gitignore 규칙이 동작하지 않을 때가 있는 이유
대략 빈도순으로 정리한 원인은 다음과 같습니다.
- 파일이 이미 추적되고 있습니다.
.gitignore는 소급 적용되지 않습니다. 규칙이 생기기 전에 커밋된 파일이라면 규칙은 완전히 건너뛰어집니다.git rm --cached path/to/file을 실행하고 커밋하세요. 파일은 디스크에 남고 Git은 추적을 멈춥니다. - 뒤의 규칙이 덮어썼습니다. Git은 처음 일치한 패턴에서 멈추지 않습니다.
- 상위 디렉터리가 무시되고 있습니다. 파일을 위해 쓴 규칙에 아예 도달하지 못합니다.
- 자유롭게 일치한다고 생각한 패턴이 실제로는 고정되어 있습니다. 끝이 아닌 위치에 슬래시가 있는 패턴은 그
.gitignore가 있는 디렉터리에 고정됩니다. 그래서src/*.log는src/debug.log에는 일치하지만 그보다 깊은 것에는 일치하지 않습니다. 슬래시가 전혀 없는 패턴은 기본 이름으로 어느 깊이에서나 일치하므로,*.log는debug.log는 물론src/debug.log와a/b/c/debug.log까지 잡아냅니다. - 패턴 형식이 잘못되었습니다.
[abc처럼 닫히지 않은 문자 클래스는 문자 그대로의[로 처리되지 않습니다. Git은 일치 판정을 포기하고, 그 규칙은 조용히 아무것에도 일치하지 않게 됩니다 — 아예 쓰지 않은 규칙과 구별할 수 없습니다. 그래서 디버거에서도 일치한 규칙 목록에 결코 나타나지 않습니다.
gitignore 규칙의 우선순위: 마지막으로 일치한 규칙이 이깁니다
모두가 걸려 넘어지는 부분입니다. 같은 .gitignore 안에서는 어떤 경로에 일치한 마지막 패턴이 그 운명을 결정합니다. 첫 번째가 아닙니다. 아래 두 파일은 같은 규칙을 담고 있지만 important.log에 대해 정반대의 결과를 냅니다.
*.log
!important.log → important.log는 커밋됩니다!important.log
*.log → important.log는 무시됩니다그러니 넓게 적용되는 규칙을 먼저, 예외를 나중에 두세요. 파일들 사이에서는 하위 디렉터리에 있는 .gitignore가 그 아래 경로에 대해 위쪽 파일을 이깁니다. Git은 저장소 전용이며 커밋되지 않는 .git/info/exclude와 전역 core.excludesFile도 읽습니다. 어느 .gitignore를 봐도 찾을 수 없는 규칙은 흔히 이 두 곳 중 하나에 숨어 있습니다 — git check-ignore -v가 결정을 내린 파일과 줄을 보여 줍니다.
부정 규칙, 그리고 !가 자주 실패하는 이유
패턴 앞에 !를 붙이면 앞선 규칙이 무시한 것을 다시 포함시킬 수 있습니다. 여기에는 두 가지 한계가 있고, 두 번째가 “gitignore 부정이 동작하지 않는다”는 경우의 거의 전부를 설명합니다.
첫째, 그보다 앞에서 무엇인가가 그 경로를 무시했어야 합니다. !important.log만 써 두면 아무 일도 일어나지 않습니다. 덮어쓸 대상이 애초에 없었기 때문입니다.
둘째, 그리고 훨씬 뜻밖의 사실입니다. 무시된 디렉터리 안에 있는 파일은 다시 포함시킬 수 없습니다. 아래는 동작하지 않으며, 순서를 바꿔도 해결되지 않습니다.
build/
!build/keep.txtbuild/가 무시되면 Git은 그 안으로 들어가지 않습니다. build/keep.txt를 살펴보지 않으므로 2번째 줄에는 결코 도달하지 않습니다. 대신 디렉터리의 내용을 무시하세요. 그러면 디렉터리 자체는 계속 탐색할 수 있습니다.
build/*
!build/keep.txt더 깊은 곳에 있는 것을 살리려면 중간의 모든 디렉터리도 계속 접근 가능한 상태여야 합니다 — build/*, 그다음 !build/assets/, 그다음 !build/assets/logo.svg처럼요.
무시된 상위 디렉터리
Git의 디렉터리 탐색은 처음 만난 무시 대상 디렉터리에서 멈추고, 그 아래의 모든 것은 내용을 살펴보지도 않은 채 그 판정을 물려받습니다. 그래서 무시된 상위 디렉터리는 파일 자체를 위해 쓴 어떤 규칙보다도 강합니다. 그 규칙이 아무리 구체적으로 보여도 마찬가지입니다.
그런 경우 디버거는 단순히 파일이 무시된다고만 말하지 않고, 탐색을 막은 상위 디렉터리의 이름을 알려 줍니다. 또한 파일을 위해 쓴 부정 규칙도 도달 불가로 표시해 함께 보여 줍니다. Git이 그만큼 깊이 내려오지 않아 고려조차 되지 않기 때문입니다. 디렉터리를 먼저 고치면 그 아래 규칙들은 저절로 다시 동작합니다.
흔한 .gitignore 실수
- 파일을 커밋한 뒤에 규칙을 추가합니다. 규칙은 옳고, 그리고 아무 효과도 없습니다. 위의
git rm --cached를 참고하세요. - 줄 끝 주석.
#는 줄 맨 앞에서만 주석을 시작합니다. 따라서*.log # debug output은 하나의 긴 패턴이며, 여러분이 가진 어떤 파일에도 일치하지 않습니다. - 구분자로 백슬래시를 씁니다.
.gitignore안에서\는 이스케이프 문자이고, 경로 구분자가 아닙니다.src\config는srcconfig라는 문자 그대로의 이름을 뜻합니다. 언제나src/config라고 쓰세요. *가 디렉터리를 넘는다고 기대합니다.*는 슬래시에서 멈추므로src/*/test.js는 한 단계만 내려갑니다. 어느 깊이에나 일치시키려면src/**/test.js를 쓰세요.- 끝의 슬래시를 잊습니다.
logs는logs라는 이름의 파일에도, 디렉터리에도 일치합니다.logs/는 디렉터리에만 일치합니다. - 무시하면 이력이 지워진다고 생각합니다. 규칙이 막는 것은 앞으로의 커밋뿐입니다. 이미 푸시된 비밀 정보는 사라지지 않습니다 — 자격 증명을 교체하세요.
실제 예시 몇 가지
이 규칙에 대해 src/logs/debug.log는 무시되지 않습니다. 부정이 마지막 일치이고 탐색을 막는 상위 디렉터리도 없으므로, 부정에 도달해서 이깁니다.
*.log
!src/logs/debug.log이 규칙에 대해 build/keep.txt는 무시됩니다. 1번째 줄의 build/가 상위 디렉터리에 일치하기 때문입니다. 2번째 줄은 결코 발동하지 않습니다.
build/
!build/keep.txt이 규칙에 대해 docs/api/notes.md는 무시되지 않습니다. 패턴이 슬래시 때문에 고정되어 있고, *는 api/notes.md의 슬래시를 넘지 않기 때문입니다. 의도한 것은 docs/**/*.md였을 것입니다.
docs/*.md위의 gitignore 테스터에 이 예시들을 붙여넣으면 규칙 사슬 전체를 볼 수 있습니다. 터미널로 돌아가면 git check-ignore -v path/to/file이 같은 일을 해 주는 내장 명령입니다. 결정을 내린 원본 파일과 줄 번호, 패턴을 알려 주므로 알아 둘 만합니다.