.gitignore: правила, папки и почему файл всё равно виден

Git Автор: Среда и версия: git 2.55.0, Linux; всё воспроизведено на одноразовых репозиториях 31 августа 2026
содержание

.gitignore — это список того, что Git должен не замечать: артефакты сборки, виртуальное окружение, логи, файлы редактора, секреты. Обычный текстовый файл в корне проекта, по правилу на строку.

И у него есть одно свойство, которое ловит почти каждого: .gitignore действует только на файлы, которые Git ещё не отслеживает.

Файл уже в репозитории — запись не поможет

Проект, где .env попал в первый же коммит:

git ls-files
.env
build/out.bin
logs/app.log
src/main.py

Пишем .gitignore задним числом и меняем .env:

printf '*.log\nbuild/\n.env\n' > .gitignore
echo "изменение" >> .env
git status -s
 M .env
?? .gitignore

.env перечислен в .gitignore — и всё равно в git status. Это не сбой: Git проверяет .gitignore только когда решает, показывать ли новый файл. За тем, что уже отслеживается, он следит всегда, что бы ни было написано в списке исключений.

Git / 01

Правило не действует на уже отслеживаемый файл

Правило не действует на уже отслеживаемый файл01 Уже в индексе *.log → всё ещё виден Чтобы перестать отслеживать файл, нужен git rm --cached. 02 Ещё не отслеживается *.log → скрыт Правило .gitignore предотвращает случайное добавление нового файла.01Уже в индексе*.log → всё ещё виденЧтобы перестать отслеживать файл, нуженgit rm --cached.02Ещё не отслеживается*.log → скрытПравило .gitignore предотвращаетслучайное добавление нового файла.
Правило проверяется только для неотслеживаемых файлов. Поэтому «добавил в .gitignore, а он всё равно в git status» — не баг, а ровно описанное поведение.

Как убрать уже отслеживаемое

git rm --cached удаляет файл из индекса, не трогая диск:

git rm -r --cached .env build logs/app.log
rm '.env'
rm 'build/out.bin'
rm 'logs/app.log'
git add -A && git commit -m "убрал мусор из репозитория"
git status -s

Пусто — и файлы при этом на месте на диске. Дальше .gitignore уже работает как ожидается.

Два предупреждения, о которых обычно узнают поздно.

У коллег файл удалится. Коммит с git rm --cached — это коммит удаления, и при git pull он удалит файл из их рабочих каталогов:

# у коллеги, до pull
cat .env          # SECRET=123
git pull
cat .env          # No such file or directory

Для .env это ровно тот случай, когда всех надо предупредить заранее: у каждого свой файл, и восстанавливать его придётся руками.

Секрет остаётся в истории. Это важнее всего остального на странице:

git log --oneline --all -- .env
bb2e3f0 убрал .env из репозитория
f46b55b первый
git show f46b55b:.env
SECRET=123

Одна команда, и пароль на экране. .gitignore и git rm --cached прячут файл из будущих коммитов, но не удаляют его из прошлых. Если в репозиторий утёк реальный ключ, единственное надёжное действие — отозвать и перевыпустить его. Чистка истории (git filter-repo, BFG) переписывает все коммиты, ломает работу всем, у кого есть копия, и всё равно не гарантирует, что копию не успели снять.

Правила

По правилу на строку, # начинает комментарий, пустые строки игнорируются.

ПравилоЧто исключает
*.logвсе файлы с расширением .log в любом каталоге
build/каталог build целиком, в любом месте дерева
/build/каталог build только в корне проекта
.envфайл .env в любом каталоге
temp?.txttemp1.txt, tempA.txt? это ровно один символ
docs/**/draft.mddraft.md на любой глубине внутри docs
!important.logисключение из исключения: этот файл вернуть

Ведущий слеш привязывает правило к корню, завершающий — означает «это каталог». Порядок важен: правила читаются сверху вниз, и побеждает последнее совпавшее.

Ловушка отрицания

Логика «исключим весь каталог, а один файл вернём» выглядит естественно и не работает:

logs/
!logs/important.log
git status -s
?? .gitignore
?? main.py

Каталога logs не видно вообще, important.log не вернулся. Причину показывает git check-ignore:

git check-ignore -v logs/important.log
.gitignore:1:logs/	logs/important.log

Сработало правило из первой строки, а не отрицание из второй. Git не заходит внутрь исключённого каталога вовсе, поэтому до правил про его содержимое дело не доходит.

Исправляется одним символом — исключать не каталог, а файлы в нём:

logs/*
!logs/important.log
git status -s
?? .gitignore
?? logs/
?? main.py
git check-ignore -v logs/app.log
git check-ignore -v logs/important.log
.gitignore:1:logs/*	logs/app.log
.gitignore:2:!logs/important.log	logs/important.log

С -v выводится и совпавшее отрицание: строка с !logs/important.log означает, что файл не игнорируется. Само наличие вывода ещё не означает исключение файла.

Git / 02

Чтобы вернуть файл, Git должен зайти в его каталог

Чтобы вернуть файл, Git должен зайти в его каталог01 Каталог исключён logs/ !logs/important.log Не работает: Git не заходит внутрь logs, исключение файла не применяется. 02 Исключено содержимое logs/* !logs/important.log Работает: каталог доступен, important.log возвращается из исключения.01Каталог исключёнlogs/!logs/important.logНе работает: Git не заходит внутрь logs,исключение файла не применяется.02Исключено содержимоеlogs/*!logs/important.logРаботает: каталог доступен, important.logвозвращается из исключения.
Разница в одной звёздочке. Проверять догадки не нужно — git check-ignore -v называет файл, номер строки и само правило, которое победило.

Диагностика

git check-ignore -v <файл>

Печатает источник, строку и правило: .gitignore:3:.env .env. Для неотслеживаемого пути отсутствие вывода означает, что правило не найдено. Отслеживаемые файлы по умолчанию вообще не проверяются; чтобы проверить правила независимо от индекса, добавь --no-index. Совпадение с ! означает исключение из игнорирования.

git status --ignored -s
?? .gitignore
?? logs/
?? main.py
!! logs/app.log

Метка !! — исключённые файлы. Полезно, чтобы убедиться, что под правило не попало лишнее.

Где ещё живут правила

  1. .gitignore в репозиторииОбщий для команды, коммитится вместе с кодом. Сюда идёт то, что не нужно никому: сборка, зависимости, кэши.
  2. .gitignore во вложенном каталогеДействует на своё поддерево и уточняет корневой. Удобно для папок со своей спецификой, например тестовых данных.
  3. .git/info/excludeЛичный список для этого репозитория, не коммитится. Сюда — свои временные файлы, которые не касаются команды.
  4. Глобальный, через core.excludesFilegit config --global core.excludesFile ~/.gitignore_global. Сюда — файлы твоей ОС и редактора: .DS_Store, .idea/, *.swp. В общий список их класть невежливо: у коллег другие инструменты.

Готовые шаблоны под язык и фреймворк лежат в коллекции GitHub — брать оттуда быстрее и надёжнее, чем вспоминать, что генерирует Python или Node.

Частые вопросы

Почему файл виден, хотя он в .gitignore

Скорее всего он уже отслеживается. Проверь git ls-files | grep имя: если файл там есть, .gitignore на него не действует. Убрать — git rm --cached имя, затем коммит.

Как исключить папку

Строкой с завершающим слешем: build/. Без слеша правило совпадёт и с файлом с таким именем. Пустые каталоги Git не хранит в принципе, так что исключать их незачем.

Нужно ли коммитить сам .gitignore

Да. Это часть проекта: без него каждый в команде будет заново решать, что не коммитить, и рано или поздно кто-нибудь закоммитит node_modules.

Почему не работает восклицательный знак

Потому что выше исключён родительский каталог целиком. Замени logs/ на logs/* — и отрицание заработает. Проверить догадку: git check-ignore -v покажет, какое именно правило победило.

Что делать, если закоммитил пароль

Считать его скомпрометированным и перевыпустить. Удаление файла из последующих коммитов пароль из истории не убирает: git show <коммит>:<файл> достаёт его одной командой, а копии репозитория могут быть у кого угодно.

Где потренироваться

Заведи одноразовый репозиторий, закоммить пару файлов, потом добавь их в .gitignore и посмотри git status — эффект (точнее, его отсутствие) запоминается лучше любого объяснения. Команды, которыми это чинится, разобраны в основных командах Git, а отмена неудачных коммитов — в разборе git reset.

Источники