Игнорирование файлов через .gitignore
Файл .gitignore говорит git, какие пути оставить неотслеживаемыми, через glob-шаблоны. Разбираем якорение, правила каталогов и отрицания, шаблон ** и главный подвох: gitignore влияет только на НЕотслеживаемые файлы — уже отслеживаемому нужен git rm --cached, чтобы не коммититься.
Не всё в папке проекта должно попадать в контроль версий. Скомпилированный вывод, 200-мегабайтный node_modules, настройки редактора и — опаснее всего — файл .env, полный секретов, лежат прямо рядом с исходниками. Закоммить их — и ты раздуваешь репозиторий, утекаешь учётками и ловишь конфликты слияния на машинно-специфичном мусоре. Фильтр, который держит их снаружи, — это .gitignore.
К концу этого урока ты сможешь точно писать шаблоны .gitignore — якорные, рекурсивные, отрицающие — и чинить провал номер один gitignore: файл, который продолжает коммититься, хотя ты его «проигнорировал».
После этого урока ты сможешь писать glob-шаблоны .gitignore для файлов, каталогов, якорных путей и исключений, объяснять, почему gitignore влияет только на неотслеживаемые файлы, и использовать git rm --cached, чтобы перестать отслеживать файл, который уже в истории.
Файл .gitignore перечисляет шаблоны путей, которые git должен оставить неотслеживаемыми. Ты кладёшь его в корень репозитория (или в любой подкаталог, ограничивая той папкой), и git пропускает совпавшие файлы в git status и git add .:
# комментарии начинаются с решётки
*.log # игнорировать каждый файл, кончающийся на .log
node_modules/ # игнорировать весь каталог node_modules
.env # игнорировать файл секретов
build # игнорировать всё с именем build (файл или каталог, где угодно)Каждая строка — это glob-шаблон. Пустая строка не совпадает ни с чем (для отступов); строка, начинающаяся с #, — комментарий. Сам файл .gitignore обычно коммитится — он общий, чтобы вся команда игнорировала одно и то же.
Glob-шаблоны: * совпадает внутри сегмента пути, ** совпадает через каталоги. Подстановочные знаки — это базовый словарь:
*.log # любой файл, кончающийся .log, в любом каталоге
temp?.txt # temp1.txt, tempA.txt (? = один символ)
logs/**/*.log # файлы .log на любой глубине под logs/
**/cache/ # каталог cache где угодно в дереве* останавливается на слэше — он совпадает в пределах одного уровня каталога. ** — рекурсивный подстановочный знак, переходящий границы каталогов. Большинство реальных строк .gitignore — это просто правило расширения *.ext или правило каталога dir/; тяжёлые шаблоны встречаются редко.
Ведущий слэш якорит шаблон к каталогу самого .gitignore; без него совпадение идёт где угодно. Это различие подводит многих:
/build # ТОЛЬКО каталог build в корне репозитория
build # ЛЮБОЙ файл/каталог с именем build, на любой глубинеbuild (без слэша) игнорирует build, src/build, lib/x/build — каждый build в дереве. /build игнорирует только верхнеуровневый, оставляя исходник src/build отслеживаемым. Завершающий слэш вроде build/ дополнительно ограничивает совпадение каталогами, никогда — файлом с именем build.
Ведущий ! отрицает шаблон, заново включая файл, который более раннее правило исключило. Используй, чтобы игнорировать целый класс, но оставить исключение:
*.log # игнорировать все логи
!important.log # ...кроме этого — продолжать отслеживатьПорядок и родительские каталоги важны: нельзя заново включить файл, если его родительский каталог исключён, потому что git никогда не заходит в игнорируемый каталог, чтобы вообще увидеть исключение. Чтобы !secrets/keep.txt сработало, игнорируй содержимое (secrets/*), а не сам каталог (secrets/), чтобы git всё ещё заглядывал внутрь.
▸Частая ошибка
Критический подвох: .gitignore влияет только на НЕотслеживаемые файлы. Если файл уже отслеживается — закоммичен хоть раз — добавление его в .gitignore ничего не делает; git продолжает записывать его изменения. Это классическое «я добавил .env в gitignore, но он всё равно появляется». Чинится так — перестать отслеживать, сохранив файл на диске:
git rm --cached .env # перестать отслеживать; файл остаётся на диске
echo ".env" >> .gitignore # убедиться, что он игнорируется впредь
git commit -m "Stop tracking .env; ignore it"git rm --cached удаляет файл из индекса (так что следующий коммит его выбросит), но оставляет твою копию в рабочем дереве нетронутой. Заметь: содержимое файла всё ещё живёт в прошлых коммитах — если там были реальные секреты, смени их, потому что историю можно достать.
Секрет, который не хочет игнорироваться.
В начале проекта ты случайно закоммитил .env. Спустя недели ты добавляешь его в .gitignore, но git status всё равно показывает .env как изменённый каждый раз, когда ты его правишь. Причина: он уже отслеживается, так что правило игнорирования инертно.
git rm --cached .env
# rm '.env' — удалён из индекса, всё ещё на диске
cat .gitignore
# .env — уже есть, хорошо
git commit -m "Stop tracking .env and ignore it"Теперь .env неотслеживаемый, и правило gitignore наконец применяется — будущие правки больше не появляются в git status. Но старые коммиты всё ещё содержат то, что было в .env, когда ты впервые его закоммитил, так что ты меняешь эти учётки. Для всего остального машинно-специфичного берёшь готовый шаблон: репозиторий GitHub github/gitignore и gitignore.io генерируют файлы под язык и инструмент (Node, Python, JetBrains, macOS), чтобы не писать каждое правило руками.
▸Почему это работает
Почему git игнорирует файл игнорирования для отслеживаемых путей? Потому что .gitignore — про решение, что входит в контроль версий, а не про удаление записанной истории. Git считает отслеживаемый файл намеренным решением, которое ты уже принял; молча выбросить его из будущих коммитов из-за появившегося шаблона было бы неожиданно и опасно. Заставляя тебя запустить git rm --cached, git вынуждает сделать решение явным — та же философия, что у области индекса: ничто не покидает и не входит в историю случайно.
Ты закоммитил config.json месяцы назад. Сегодня добавляешь config.json в .gitignore, но git всё равно отслеживает твои правки в нём. Почему и что это чинит?
Файл .gitignore перечисляет glob-шаблоны путей, которые git должен оставить неотслеживаемыми: *.log (расширение), node_modules/ (каталог), # (комментарий). Ведущий слэш якорит шаблон к каталогу файла (/build = только корень) против совпадения где угодно (build); ** — рекурсивный подстановочный знак; ведущий ! отрицает, заново включая исключение (но не если его родительский каталог исключён). Критический подвох: gitignore влияет только на неотслеживаемые файлы — уже отслеживаемый файл игнорирует правило, пока ты не сделаешь git rm --cached <file>, чтобы выбросить его из индекса, сохранив на диске (и смени любые секреты, ведь история хранит старое содержимое). Берись за шаблоны github/gitignore или gitignore.io вместо ручного написания правил. Дальше ты узнаешь, что отличает хороший коммит от шумного.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.