Открой любой свежий проект, набери
Код: Выделить всё
git statusРепозиторий должен хранить исходники и только исходники - то, из чего проект собирается. Всё, что можно восстановить из исходников (артефакты сборки, скачанные зависимости, кеши) или что относится к конкретной машине разработчика (настройки IDE, локальные пути, секреты), в git попадать не должно. Именно для этого существует механизм игнорирования. Когда люди спрашивают, как заставить git игнорировать файлы, ответ почти всегда один - файл
Код: Выделить всё
.gitignoreГлавное, что надо усвоить сразу: gitignore влияет только на неотслеживаемые (untracked) файлы. Если файл уже закоммичен - git его отслеживает и будет отслеживать, что бы ты ни написал в .gitignore. Это источник боли номер один, к нему вернёмся отдельно.

Синтаксис паттернов: glob, слеши, отрицание
Файл .gitignore - это просто список паттернов, по одному на строку. Git читает их сверху вниз, и последний совпавший паттерн решает судьбу файла. Это критично для отрицаний - запомни порядок. Разберём gitignore синтаксис по кусочкам.
- Пустая строка ничего не игнорирует, используется для читаемости.
- Строка, начинающаяся с , - комментарий. Если нужен буквальный символ решётки в начале имени, экранируй:
Код: Выделить всё
#.Код: Выделить всё
\#file - Обычный glob: совпадает с любым числом символов кроме слеша,
Код: Выделить всё
*- ровно один символ,Код: Выделить всё
?- диапазон.Код: Выделить всё
[a-z] - Завершающий пробелы игнорируются, если их не экранировать через .
Код: Выделить всё
\
Код: Выделить всё
# совпадёт с файлом/папкой build в ЛЮБОМ месте дерева
build
# ведущий или серединный слеш привязывает паттерн к корню репозитория
# совпадёт только с /build в корне, но НЕ с src/build
/build
# завершающий слеш = совпадать только с директорией, не с файлом
logs/
# два слеша вокруг - тоже привязка к расположению паттерна
doc/frontpage.txt
Код: Выделить всё
*.logКод: Выделить всё
/*.logОтдельно про
Код: Выделить всё
**Код: Выделить всё
# любой .json в любом подкаталоге config (config/a/b/x.json тоже)
config/**/*.json
# ведущий ** = в любом месте дерева
**/temp
# завершающий ** = всё внутри
build/**
Код: Выделить всё
!Код: Выделить всё
# игнорируем все логи
*.log
# ...кроме одного важного
!important.log
Код: Выделить всё
# так файл НЕ вернётся - git не заглядывает в logs/ вообще
logs/
!logs/keep.log
# а так заработает - сначала глубокий glob, потом исключение папки внутри
logs/**
!logs/keep.log
Три уровня игнорирования: репо, локально, глобально
Многие думают, что .gitignore один. На деле git собирает правила из нескольких источников, и у них строгий приоритет от высшего к низшему:
- Паттерны из командной строки - для команд, которые их принимают (например и подобные внутренние механизмы). Высший приоритет.
Код: Выделить всё
git add --ignore-errors - .gitignore в каталогах проекта - тот самый файл, который коммитится и едет всем в команду. Можно класть в подкаталоги, и правила из ближнего к файлу .gitignore перебивают правила из родительского.
- - локальный список исключений конкретного клона. Он НЕ коммитится и НЕ виден другим. Сюда кладёшь то, что мусорит лично у тебя, но засорять общий .gitignore нет смысла.
Код: Выделить всё
.git/info/exclude - - глобальный файл на всю машину, для всех твоих репозиториев. Низший приоритет.
Код: Выделить всё
core.excludesFile
Код: Выделить всё
git config --global core.excludesFile ~/.gitignore_global
Код: Выделить всё
~/.gitignore_globalКод: Выделить всё
.DS_StoreКод: Выделить всё
Thumbs.dbЗапомни иерархию на пальцах: глобальный = "моя машина", info/exclude = "мой клон этого репо", .gitignore = "весь проект для всей команды".
Главная боль: добавил .gitignore, а файл всё равно в git
Классика жанра и самый частый вопрос новичка. Ты закоммитил
Код: Выделить всё
config.phpКод: Выделить всё
git statusЛечится через git rm cached - удаляем файл из индекса, но оставляем на диске:
Код: Выделить всё
$ git rm --cached config.php
rm 'config.php'
$ git status
On branch main
Changes to be committed:
deleted: config.php
Untracked files:
config.php
Код: Выделить всё
$ git commit -m "stop tracking config.php, add to gitignore"
Код: Выделить всё
-rКод: Выделить всё
git rm -r --cached node_modules/
Код: Выделить всё
git log -passume-unchanged и skip-worktree - и почему это НЕ для секретов
Есть два флага индекса, которые периодически советуют как "способ игнорировать локальные изменения отслеживаемого файла". Звучит заманчиво, но применять их надо осторожно и точно не для секретов.
Код: Выделить всё
# git перестанет замечать изменения файла
git update-index --assume-unchanged config.php
# почти то же, но с другой семантикой
git update-index --skip-worktree config.php
Почему ни тот, ни другой не подходят для секретов и вообще для игнорирования:
- Файл остаётся в репозитории и в истории - ты просто прячешь локальные правки, а не убираешь файл из git.
- Флаг живёт только в твоём индексе, его не видят коллеги. У них файл отслеживается как обычно.
- Флаг ненадёжен: git его сбрасывает в ряде операций, и однажды твой "скрытый" пароль уедет в коммит.
Код: Выделить всё
config.example.phpКод: Выделить всё
config.phpПосмотреть, на каких файлах висят эти флаги:
Код: Выделить всё
git ls-files -v | grep -E '^[hsS]'
Готовые шаблоны и поиск, почему файл игнорируется
Не пиши .gitignore с нуля. Есть официальная коллекция github/gitignore на GitHub - сотни выверенных шаблонов под языки и среды: Node, Python, Java, JetBrains, VisualStudio и так далее. Скопировал нужный, докинул своё - готово. Хорошие .gitignore примеры там же.
Теперь инструмент, который экономит часы отладки -
Код: Выделить всё
git check-ignore -vКод: Выделить всё
$ git check-ignore -v build/output.bin
.gitignore:7:build/ build/output.bin
Код: Выделить всё
.gitignoreКод: Выделить всё
7Код: Выделить всё
build/Пустая папка и .gitkeep
Маленький, но вечный вопрос: git не умеет хранить пустые директории - он версионирует файлы, а не папки. Если тебе нужна структура каталогов (например пустая
Код: Выделить всё
uploads/Код: Выделить всё
logs/Код: Выделить всё
.gitkeepКод: Выделить всё
$ touch uploads/.gitkeep
$ git add uploads/.gitkeep
Код: Выделить всё
.placeholderКод: Выделить всё
# игнорируем всё в logs, кроме самой заглушки
logs/*
!logs/.gitkeep
Пройди по шагам, это 5 минут и всё уляжется в голове.
Код: Выделить всё
# 1. свежий репозиторий (вручную ставим main, в ванильном git 2026 дефолт ещё master)
git init -b main ignore-lab && cd ignore-lab
# 2. насорим
mkdir build logs
echo "secret=123" > .env
echo "binary" > build/app.bin
echo "log line" > logs/run.log
touch logs/.gitkeep
# 3. посмотри, что git хочет добавить - всё подряд
git status
# 4. напиши .gitignore
printf '.env\nbuild/\nlogs/*\n!logs/.gitkeep\n' > .gitignore
# 5. теперь status чистый, кроме .gitignore и .gitkeep
git status
# 6. узнай, почему .env игнорируется
git check-ignore -v .env
# 7. демонстрация боли: закоммить файл, ПОТОМ запрети
echo "tracked" > tracked.tmp
git add tracked.tmp && git commit -m "oops"
echo 'tracked.tmp' >> .gitignore
git status # игнор не сработал, файл отслеживается
git rm --cached tracked.tmp
git status # теперь deleted + untracked, всё как надо
Код: Выделить всё
.envКод: Выделить всё
build/Код: Выделить всё
logs/.gitkeepКод: Выделить всё
logs/*Код: Выделить всё
logs/Типичные грабли
- Игнорируешь папку, а отрицание не работает. Замени на
Код: Выделить всё
dir/- git не заходит внутрь исключённой директории.Код: Выделить всё
dir/* - Добавил в .gitignore, файл всё равно в git. Он уже отслеживается. и коммит.
Код: Выделить всё
git rm --cached - Лишний завершающий пробел в паттерне - git его учтёт и паттерн не совпадёт. Экранируй или убери.
- Слеш в середине привязывает к корню. не поймает
Код: Выделить всё
src/temp. Если нужно везде -Код: Выделить всё
a/src/tempили простоКод: Выделить всё
**/src/temp.Код: Выделить всё
temp - Прячешь секрет через assume-unchanged - однажды флаг слетит и пароль уедет в коммит. Используй .gitignore + example-шаблон.
- Думаешь, что rm --cached стёр секрет. Нет, он в истории. Переписывай историю и ротируй ключ.
- Ты дописал файл в .gitignore, но продолжает его показывать. Что произошло и какие две команды это чинят?
Код: Выделить всё
git status - Чем отличается от
Код: Выделить всё
logs/, если ниже стоитКод: Выделить всё
logs/*?Код: Выделить всё
!logs/.gitkeep - В каком из трёх уровней (.gitignore / info/exclude / global) логично держать и почему?
Код: Выделить всё
.DS_Store - Почему - плохая идея для хранения секретного config.php, и что правильно вместо него?
Код: Выделить всё
skip-worktree
.gitignore - это фильтр для untracked-файлов, собранный из трёх уровней с чётким приоритетом: глобальный, локальный info/exclude и командный .gitignore. Паттерны - это glob со своей логикой слешей и отрицаний, где последнее совпадение решает всё. Главная ловушка - уже отслеживаемый файл: его лечит только
Код: Выделить всё
git rm --cachedКод: Выделить всё
git check-ignore -v