
.gitignore 적용 안 됨 — 이미 커밋된 파일은 git rm --cached로 캐시 삭제
.gitignore에 분명히 넣었는데 파일이 계속 커밋되는 이유는 거의 하나다 — 이미 git이 추적(tracked) 중인 파일. git rm --cached로 인덱스에서만 빼내 .gitignore를 되살리는 방법과, 패턴이 안 먹을 때 git check-ignore로 범인을 찾는 법까지 정리했다.
분명 .gitignore에 node_modules/를 적어 뒀는데, git status를 치면 여전히 수백 개 파일이 "올라갈 준비 완료" 상태로 떠 있다. 지워도 보고, 다시 적어도 보고, 혹시 오타인가 싶어 띄어쓰기까지 의심해 봤는데 그대로. 이쯤 되면 ".gitignore가 고장 난 거 아냐?" 싶은 마음이 든다. 그 답답함, 충분히 안다.
먼저 안심부터 하자. .gitignore는 멀쩡하고, 당신이 적은 문법도 대부분 맞다. 문제는 그 파일이 이미 git에 등록돼 있다는 것 딱 하나다. 왜 그런지 짚고, 로컬 파일은 건드리지 않으면서 git에서만 빼내는 정확한 명령, 그리고 패턴이 진짜 안 먹을 때 범인을 찾는 한 줄까지 순서대로 가 보자.

왜 .gitignore를 무시할까 — 이미 추적 중인 파일이라서
딱 하나만 기억하면 된다. .gitignore는 git이 아직 모르는 파일(untracked)에만 적용된다. 한 번이라도 git add 후 커밋으로 등록된 파일, 즉 git이 "추적(tracked) 중"인 파일에는 효력이 없다.
생각해 보면 당연한 동작이다. .gitignore가 하는 말은 "이 파일을 버전 관리에 새로 들이지 마라"이지, "이미 들인 파일을 내보내라"가 아니다. 그래서 config.env를 실수로 한 번 커밋한 뒤 뒤늦게 .gitignore에 적어 봐야 git은 눈 하나 깜짝 안 한다. 이미 제 식구로 등록된 파일이니까.
정리하면 —
.gitignore가 안 먹는 파일은 십중팔구 이미 커밋(또는 스테이징)된 파일이다. 반대로 새로 만든 파일인데도 안 먹는다면 그건 패턴 문법 쪽 문제다(아래에서 다룬다).
해결 — git rm --cached로 인덱스에서만 빼내기
추적을 끊는 명령은 git rm --cached다. 파일 하나면 이렇게 친다.
git rm --cached config.env
폴더를 통째로 빼려면 -r(재귀)을 붙인다.
git rm -r --cached node_modules
그리고 커밋한다.
git commit -m "Stop tracking files now covered by .gitignore"
여기서 다들 가장 겁내는 부분을 분명히 하고 가자. --cached를 붙이면 로컬 파일은 절대 지워지지 않는다. git의 인덱스(추적 목록)에서만 빠질 뿐, 디스크에 있는 실제 파일은 그대로 남는다. 반대로 --cached 없이 그냥 git rm config.env를 치면 그때는 작업 폴더의 파일까지 같이 삭제되니, 이 옵션을 빼먹지 말자.
커밋이 끝난 순간부터 .gitignore가 비로소 제 일을 시작한다. 방금 빼낸 파일을 또 수정해도 git status에는 더 이상 뜨지 않는다.

프로젝트 전체를 한 번에 정리하고 싶다면
.gitignore를 크게 손봤고 거기 걸리는 걸 전부 한 방에 비우고 싶을 때가 있다. 이럴 땐 인덱스를 통째로 비웠다가 다시 채우는 방법을 쓴다.
git rm -r --cached .
git add .
git commit -m "Apply .gitignore to the whole tree"
첫 줄이 모든 파일의 추적을 잠깐 해제하고, git add .이 이번엔 .gitignore를 존중하면서 다시 담는다. 결과적으로 무시 대상만 깔끔하게 빠진다. 대신 두 가지는 꼭 지키자.
- 커밋 전에 반드시
git status로 확인한다. 의도치 않은 파일이 삭제 목록에 올라와 있을 수 있다. - 협업 중이라면 팀에 미리 알린다. 이 커밋을 당겨 받는 동료의 작업 폴더에서는 해당 파일이 '삭제'로 반영돼 사라진다. 내 디스크는 멀쩡해도 커밋 기록상으로는 삭제이기 때문이다(로컬에서 다시 생성되는 파일이면 자동으로 되살아난다).
그래도 새 파일이 안 걸러진다면 — 패턴 문법 함정
파일이 추적 중이 아닌 게 확실한데도 무시가 안 된다면, 이번엔 .gitignore 작성 규칙을 의심할 차례다. 실무에서 자주 걸리는 지점만 추렸다.
- 폴더는 뒤에 슬래시를 붙인다.
logs/는 logs 디렉토리 전체를,logs는 같은 이름의 파일과 폴더를 둘 다 노린다. 디렉토리만 막고 싶으면/를 붙이는 게 명확하다. - **
는 슬래시를 못 넘는다.*.log는 한 단계 안의 로그 파일만 잡는다. 하위 폴더까지 모든 깊이를 훑고 싶으면*/.log처럼*를 쓴다. - 맨 앞
/는 위치를 고정한다./build는.gitignore가 놓인 폴더 바로 아래 build만 잡고,build는 어느 깊이에 있든 build를 다 잡는다. !는 예외 복구다.*.log로 전부 막은 뒤!keep.log로 하나만 되살리는 식이다. 단, 부모 폴더가 이미 무시된 상태라면 그 안의 파일은!로도 되살릴 수 없다. 가장 흔히 놓치는 함정이다.#은 주석이다. 줄 맨 앞의#은 설명으로 처리된다. 진짜#으로 시작하는 파일명을 무시하려면\#으로 이스케이프한다.
범인을 특정하는 한 줄 — git check-ignore
"대체 어떤 규칙이 이 파일을 잡는(혹은 안 잡는) 거지?" 싶을 때 쓰는 명령이 따로 있다.
git check-ignore -v config.env
이 파일이 어느 .gitignore의 몇 번째 줄 규칙에 걸리는지 정확히 찍어 준다. 출력이 나오면 그 줄이 범인이고, 아무것도 안 나오면 애초에 어떤 패턴도 이 파일을 안 잡고 있다는 뜻이다(오타, 잘못된 경로, 슬래시 실수부터 의심하자). 전역 설정이나 상위 폴더의 .gitignore, 심지어 .git/info/exclude까지 일일이 뒤질 필요 없이 한 줄로 끝난다.
규칙은 제대로 걸리는데도 파일이 계속 커밋된다면? 그건 패턴 문제가 아니라 맨 앞에서 말한 추적 중 상태라는 신호다. 다시 git rm --cached로 돌아가면 된다.
몇 년간 주니어 개발자들의 코드 리뷰를 하면서 느낀 게 있다. 이 함정에 유독 자주 걸리는 파일 종류가 정해져 있다는 점이다. .env, IDE 설정 폴더(.idea/, .vscode/), 빌드 산출물 — 전부 프로젝트를 만들자마자 git init하고 바로 git add .부터 치는 습관에서 나온다. .gitignore는 그 다음에야 떠올린다. 순서가 뒤집히니 이미 추적 목록에 다 들어간 뒤인 것이다. 그래서 새 저장소를 열면 커밋을 단 한 줄 하기 전에 .gitignore부터 만들라고 권하고 싶다. GitHub이 저장소 생성 화면에서 언어별 .gitignore 템플릿을 고르게 해 둔 것도 같은 맥락이다.
더 뼈아픈 경우는 .env나 API 키를 이미 커밋해 버렸을 때다. git rm --cached로 지금 빼내도 과거 커밋 히스토리에는 그 값이 그대로 박혀 있다. 공개 저장소였다면 그 키는 이미 유출된 것으로 보고 즉시 폐기·재발급하는 게 안전하다. 히스토리 자체를 세탁하려면 git filter-repo 같은 별도 도구가 필요한데, 그건 지금 문제와는 결이 다른 큰 작업이다. 결국 비밀 파일은 커밋되기 전에 막는 쪽이 열 배는 싸게 먹힌다.
정리
.gitignore는 추적 안 되는 파일에만 작동한다. 이미 커밋된 파일은 무시되지 않는다.git rm --cached <경로>로 인덱스에서만 제거한 뒤 커밋하면.gitignore가 다시 작동한다. 로컬 파일은 지워지지 않는다.- 전체 재적용은
git rm -r --cached .→git add .→ 커밋. 커밋 전git status확인은 필수. - 새 파일인데도 안 걸리면 패턴 문법(
/··*·!·#)을 점검한다. - 뭐가 걸리는지 모르겠으면
git check-ignore -v <파일>한 줄이면 끝난다.
당신은 어떤 파일을 가장 자주 실수로 커밋했는지 궁금하다. .env? node_modules? 아니면 그 악명 높은 .DS_Store? 각자의 '흑역사 파일'을 댓글로 풀어 보자.
댓글 0
댓글을 불러오는 중…