gitignore 우선순위와 부정(!) 패턴
마지막 규칙이 이기는 원리, .gitignore·info/exclude·전역 설정 사이의 우선순위, 상위 디렉터리가 제외되면 !로 되살릴 수 없는 이유와 해결법을 설명합니다.
gitignore 규칙이 예상대로 동작하지 않을 때 원인은 대부분 순서와 상위 디렉터리에 있다. 이 글은 git이 여러 규칙 중 무엇을 따르는지 정리한다.
같은 파일 안: 마지막으로 일치한 규칙이 이긴다
한 .gitignore 안에서는 위에서 아래로 읽으면서 마지막으로 일치한 패턴이 결과를 정한다. 그 패턴이 !로 시작하면 무시하지 않고, 아니면 무시한다.
*.log
!important.log
important.log는 두 줄 모두에 일치하지만 마지막 줄이 부정이므로 추적된다. 순서를 뒤집으면 결과도 뒤집힌다.
!important.log
*.log
이제 important.log의 마지막 일치는 *.log이므로 무시된다. 생성기로 여러 템플릿을 합칠 때 선택 순서가 곧 규칙 순서가 되는 이유다. 예를 들어 Java 템플릿의 *.jar와 Gradle 템플릿의 !gradle-wrapper.jar를 함께 쓸 때는 Gradle이 뒤에 와야 한다.
여러 파일 사이의 우선순위
git은 다음 네 곳에서 규칙을 읽는다. 위에 있을수록 우선순위가 높다.
- 명령줄로 받은 패턴(
git ls-files --exclude등 일부 명령) - 경로와 같은 디렉터리 또는 상위 디렉터리의
.gitignore— 더 깊은 곳의 파일이 상위 파일을 덮어쓴다 $GIT_DIR/info/exclude(보통.git/info/exclude)core.excludesFile설정이 가리키는 파일(전역 gitignore)
예를 들어 루트 .gitignore에 *.tmp가 있고 sub/.gitignore에 !*.tmp가 있으면, sub/y.tmp는 하위 파일의 부정 규칙에 따라 추적된다. 반대로 .git/info/exclude에 !x를 적어도 .gitignore의 x가 우선하므로 x는 여전히 무시된다.
$ git check-ignore -v x y.tmp sub/y.tmp
.gitignore:1:x x
.gitignore:2:*.tmp y.tmp
sub/.gitignore:1:!*.tmp sub/y.tmp
가장 중요한 예외: 상위 디렉터리가 제외되면 되살릴 수 없다
git 문서는 이렇게 적고 있다. 파일의 상위 디렉터리가 제외되면 그 파일은 다시 포함할 수 없다. 성능 때문에 git은 제외된 디렉터리 안을 아예 살펴보지 않으므로, 그 안의 파일에 대한 ! 규칙은 읽히지도 않는다.
logs/
!logs/keep.txt
이 규칙에서 logs/keep.txt는 여전히 무시된다. git check-ignore -v를 실행하면 결정한 규칙이 logs/로 표시된다.
해결법은 디렉터리가 아니라 디렉터리의 내용을 제외하는 것이다.
logs/*
!logs/keep.txt
logs/*는 logs 디렉터리 자체에는 일치하지 않으므로 git이 안으로 들어가 keep.txt에 부정 규칙을 적용한다. Rails 템플릿의 /log/* + !/log/.keep, VS Code 템플릿의 .vscode/* + !.vscode/settings.json이 모두 이 원리를 쓴다.
더 깊은 경로를 되살리려면 중간 디렉터리도 하나씩 되살려야 한다.
config/*
!config/app/
config/app/*
!config/app/defaults.yml
허용 목록(allowlist) 패턴
무시할 것을 나열하는 대신 추적할 것만 나열하고 싶을 때도 같은 원리를 쓴다.
# 루트의 모든 것을 무시하고
/*
# 필요한 것만 다시 포함
!/.gitignore
!/src/
!/README.md
/*는 루트의 항목 하나하나에 일치할 뿐 루트 자체를 제외하지 않으므로 !/src/가 동작한다. 이 방식에서는 .gitignore 자신도 /*에 걸리므로 !/.gitignore를 잊지 말아야 한다. 실제로 이 줄 없이 git status --ignored를 실행하면 .gitignore가 무시 목록에 나타난다.
중복 규칙과 순서
같은 규칙이 여러 번 나오면 앞의 것은 결과에 영향을 주지 않는다. 뒤의 같은 규칙이 항상 더 나중에 일치하기 때문이다. 반면 뒤의 중복을 지우는 것은 사이에 부정 규칙이 있으면 결과를 바꿀 수 있다.
*.log
!debug.log
*.log
여기서 마지막 *.log를 지우면 debug.log가 추적 대상으로 바뀐다. 이 사이트의 생성기는 이런 경우를 감지해 사이에 부정 규칙이 없을 때만 뒤쪽 중복을 제거하고, 나머지는 남겨 둔다.
참고
- gitignore 공식 문서: PATTERN FORMAT, NOTES
- git-check-ignore 공식 문서
- 확인 기준일: 2026-09-23 (git 2.50.1로 예시 검증)