guide

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은 다음 네 곳에서 규칙을 읽는다. 위에 있을수록 우선순위가 높다.

  1. 명령줄로 받은 패턴(git ls-files --exclude 등 일부 명령)
  2. 경로와 같은 디렉터리 또는 상위 디렉터리의 .gitignore — 더 깊은 곳의 파일이 상위 파일을 덮어쓴다
  3. $GIT_DIR/info/exclude (보통 .git/info/exclude)
  4. core.excludesFile 설정이 가리키는 파일(전역 gitignore)

예를 들어 루트 .gitignore*.tmp가 있고 sub/.gitignore!*.tmp가 있으면, sub/y.tmp는 하위 파일의 부정 규칙에 따라 추적된다. 반대로 .git/info/exclude!x를 적어도 .gitignorex가 우선하므로 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 패턴 문법 총정리