언어:한국어
개발 도구

Gitignore Debugger

Git이 파일을 무시하지 않는 이유를 정확히 찾아냅니다.

파일 경로와 .gitignore 규칙을 붙여넣으세요. 어떤 규칙이 일치했는지, 그리고 그 이유를 보여 드립니다.

모든 처리는 브라우저에서 실행됩니다. 파일이 전송되는 일은 없습니다.

.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의 실제 출력과 맞춰 검증했으며, 짐작으로 만든 것이 아닙니다.

사용 방법

  1. 경로는 저장소에 나타나는 그대로, 루트를 기준으로 입력하세요 — src/config/local.json처럼 쓰고 C:\projects\app\src\config\local.json처럼 쓰지 마세요. 구분자는 슬래시를 쓰면 되고, Windows의 백슬래시는 알아서 변환됩니다.
  2. .gitignore전체를 붙여넣으세요. 의심스러운 한 줄만 남기고 줄이지 마세요. 진짜 원인은 보통 규칙 사이의 우선순위이고, 파일을 잘라내면 바로 그 정보가 사라집니다.
  3. 먼저 판정을 읽고, 그 아래의 규칙 사슬을 따라가 보세요. 동작하지 않는 gitignore 규칙이 더 이상 미스터리가 아니게 되는 곳이 여기입니다.

파일이 아니라 디렉터리를 대상으로 gitignore 규칙을 시험하려면 경로를 슬래시로 끝내세요 — build/처럼요. 이 구분은 중요합니다. /로 끝나는 규칙은 디렉터리에만 일치하기 때문입니다.

gitignore 규칙이 동작하지 않을 때가 있는 이유

대략 빈도순으로 정리한 원인은 다음과 같습니다.

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.txt

build/가 무시되면 Git은 그 안으로 들어가지 않습니다. build/keep.txt를 살펴보지 않으므로 2번째 줄에는 결코 도달하지 않습니다. 대신 디렉터리의 내용을 무시하세요. 그러면 디렉터리 자체는 계속 탐색할 수 있습니다.

build/*
!build/keep.txt

더 깊은 곳에 있는 것을 살리려면 중간의 모든 디렉터리도 계속 접근 가능한 상태여야 합니다 — build/*, 그다음 !build/assets/, 그다음 !build/assets/logo.svg처럼요.

무시된 상위 디렉터리

Git의 디렉터리 탐색은 처음 만난 무시 대상 디렉터리에서 멈추고, 그 아래의 모든 것은 내용을 살펴보지도 않은 채 그 판정을 물려받습니다. 그래서 무시된 상위 디렉터리는 파일 자체를 위해 쓴 어떤 규칙보다도 강합니다. 그 규칙이 아무리 구체적으로 보여도 마찬가지입니다.

그런 경우 디버거는 단순히 파일이 무시된다고만 말하지 않고, 탐색을 막은 상위 디렉터리의 이름을 알려 줍니다. 또한 파일을 위해 쓴 부정 규칙도 도달 불가로 표시해 함께 보여 줍니다. Git이 그만큼 깊이 내려오지 않아 고려조차 되지 않기 때문입니다. 디렉터리를 먼저 고치면 그 아래 규칙들은 저절로 다시 동작합니다.

흔한 .gitignore 실수

실제 예시 몇 가지

이 규칙에 대해 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이 같은 일을 해 주는 내장 명령입니다. 결정을 내린 원본 파일과 줄 번호, 패턴을 알려 주므로 알아 둘 만합니다.

자주 묻는 질문

Git이 왜 제 파일을 무시하나요?

어떤 패턴이 일치하고 있기 때문이며, 흔히 지금 보고 있는 그 패턴이 아닙니다. 늘 의심할 곳은 하위 디렉터리의 .gitignore, 무시된 상위 디렉터리, 그리고 전역 제외 파일입니다. git check-ignore -v path/to/file을 실행하면 결정을 내린 원본 파일과 줄 번호, 패턴이 그대로 출력됩니다. 위 디버거에 경로와 규칙을 붙여넣어도 같은 내용을 볼 수 있고, 일치했지만 진 규칙까지 함께 확인할 수 있습니다.

.gitignore 규칙이 왜 동작하지 않나요?

대략 빈도순으로 네 가지 원인이 거의 모든 경우를 설명합니다. 파일이 이미 추적되고 있어서 규칙이 완전히 건너뛰어졌거나, 마지막 일치가 이기므로 뒤의 규칙이 덮어썼거나, 상위 디렉터리가 무시되어 Git이 그 규칙에 도달하지 못했거나, 아니면 패턴이 생각과 다르게 고정되어 있는 경우입니다 — 끝이 아닌 위치의 슬래시는 패턴을 .gitignore가 있는 디렉터리에 고정시킵니다.

!로 파일이 다시 포함되지 않는 이유는 무엇인가요?

앞에서 그 경로를 무시한 것이 없어서 부정이 덮어쓸 대상이 없었거나 — 훨씬 가능성이 높은 쪽인데 — 그 파일이 무시된 디렉터리 안에 있기 때문입니다. Git은 무시한 디렉터리로 들어가지 않으므로 파일을 보지도 못하고, 여러분의 ! 규칙을 평가하지도 않습니다.

디렉터리 자체가 아니라 그 내용을 무시하세요. build/build/*로 바꾸고 그 뒤에 !build/keep.txt를 남겨 두면 부정에 도달합니다.

.gitignore는 마지막 규칙이 이기나요?

예. 한 파일 안에서는 어떤 경로에 일치한 마지막 패턴이 그 운명을 결정합니다 — 첫 번째도, 가장 구체적인 것도 아닙니다. *.log 다음에 !important.log를 쓰면 important.log는 남지만, 그 두 줄을 바꾸면 무시됩니다. 넓게 적용되는 규칙을 먼저, 예외를 나중에 두세요.

무시된 폴더가 있으면 왜 부정 규칙이 동작하지 않나요?

Git의 디렉터리 탐색이 처음 무시된 디렉터리에서 멈추기 때문입니다. build/가 일치하면 Git은 안에 무엇이 있는지 나열하지 않고 그 하위 트리 전체를 무시로 표시합니다. 그래서 !build/keep.txt는 덮어써진 것이 아니라 그저 도달되지 않은 것입니다. 순서를 바꿔도 달라지지 않습니다. 달라지는 것은 디렉터리를 다시 탐색 가능하게 만들었을 때뿐이고, 그 일을 하는 것이 build/*입니다.

.gitignore 규칙은 어떻게 테스트하나요?

이 페이지 맨 위의 디버거에 파일 경로와 규칙을 붙여넣으세요. 판정과 결정적인 규칙 및 줄, 그리고 도중에 일치한 모든 규칙을 알려 줍니다. 애초에 효과를 낼 수 없었던 부정 규칙까지 포함됩니다.

실제 저장소에서는 git check-ignore -v path/to/file이 원인이 된 원본 파일과 줄, 패턴을 알려 주고, git status --ignored가 현재 무시되고 있는 모든 것을 나열합니다.

.gitignore가 이미 추적 중인 파일에도 적용되나요?

아니요. .gitignore는 추적되지 않는 경로에만 적용됩니다. 한 번 커밋된 파일은 Git이 계속 추적하고 규칙은 건너뛰어집니다 — 완전히 올바른 규칙이 아무 일도 하지 않는 것처럼 보이는 가장 흔한 이유입니다. 파일을 삭제하지 않고 추적만 멈추려면 git rm --cached path/to/file을 실행하고 그 변경을 커밋하세요.

헷갈리기 쉬운 점이 하나 있습니다. git check-ignore는 추적 중인 파일에 대해서도 일치를 보고합니다. 인덱스를 보지 않고 패턴만 시험하기 때문입니다. 이 명령이 “예, 무시됩니다”라고 답해도 Git이 그 파일의 추적을 멈췄다는 뜻은 아닙니다.

.gitignore 규칙에 공백을 넣을 수 있나요?

중간이라면 넣을 수 있습니다. my logs/는 이름에 공백이 있는 디렉터리에 이스케이프 없이 일치합니다. 끝의 공백은 다른 이야기입니다. Git이 잘라내므로 *.log 뒤에 공백을 붙여도 *.log와 똑같이 동작합니다. 정말로 공백으로 끝나는 파일 이름에 일치시키려면 이스케이프하세요 — foo\ 처럼요.

반면 앞쪽 공백은 의미가 있고 잘려 나가지 않습니다. 실수로 들여쓴 규칙은 그 공백으로 시작하는 경로에만 일치합니다. 이유 없이 규칙이 죽어 있는 것처럼 보이면 불필요한 들여쓰기가 없는지 확인하세요.

.gitignore 말고 Git은 어디에서 제외 규칙을 찾나요?

다른 세 곳이 있고, 우선순위는 이 순서입니다. 먼저 .gitignore 파일 자체이며 더 깊은 디렉터리가 이깁니다. 다음은 저장소 전용이며 커밋되지 않는 .git/info/exclude. 그다음은 core.excludesFile이 가리키는 곳, 즉 전역 제외 목록입니다.

그러니 저장소 어디에서도 찾을 수 없는 규칙은 보통 뒤의 두 곳 중 하나에 있습니다. git config --get core.excludesFile이 전역 경로를 알려 주고, git check-ignore -v는 실제로 결정을 내린 파일을 알려 줍니다.

***의 차이는 무엇인가요?

*/를 제외한 임의의 문자열에 일치하므로 하나의 경로 구간을 벗어나지 않습니다. src/*/test.js는 정확히 한 단계만 내려갑니다.

**는 구분자를 넘지만, Git이 인식하는 세 위치에서만 그렇습니다 — 어느 깊이든 뜻하는 맨 앞의 **/, 디렉터리 안의 모든 것을 뜻하는 맨 뒤의 /**, 그리고 디렉터리 0개 이상을 뜻하는 중간의 /**/입니다. 그 밖의 위치, 예컨대 a**b에서는 여분의 별표가 하나의 *와 똑같이 동작합니다.