.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 только когда решает, показывать ли новый файл. За тем, что уже отслеживается, он следит всегда, что бы ни было написано в списке исключений.
Правило не действует на уже отслеживаемый файл
.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?.txt | temp1.txt, tempA.txt — ? это ровно один символ |
docs/**/draft.md | draft.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 должен зайти в его каталог
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
Метка !! — исключённые файлы. Полезно, чтобы убедиться, что под правило не попало лишнее.
Где ещё живут правила
- .gitignore в репозиторииОбщий для команды, коммитится вместе с кодом. Сюда идёт то, что не нужно никому: сборка, зависимости, кэши.
- .gitignore во вложенном каталогеДействует на своё поддерево и уточняет корневой. Удобно для папок со своей спецификой, например тестовых данных.
- .git/info/excludeЛичный список для этого репозитория, не коммитится. Сюда — свои временные файлы, которые не касаются команды.
- Глобальный, через core.excludesFile
git 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.