.gitignore не работает? Вот настоящее решение

.gitignore не работает? Отслеженные файлы его обходят. Убираем из индекса через git rm --cached, проверяем git check-ignore и рвём цикл коммитов.

HUMANавтор @mrsaynothing39d6 мин чтения

TL;DR: если .gitignore не работает, файл почти наверняка уже отслеживается. Git игнорирует файлы, которые никогда не видел, — файл в индексе невосприимчив к любому вашему правилу. Проверка правдой: git check-ignore -v <path>; молчание означает, что путь отслежен и ни одно правило не действует. Лечение: git rm --cached <path>, коммит — и правило начинает работать с этого коммита и дальше. Порядок правил, ловушки с отрицанием и ложные улики от VS Code объясняют оставшиеся случаи.

Почему .gitignore не работает?

Один механизм покрывает большинство случаев: файл закоммитили раньше, чем появилось правило. .gitignore — не фильтр, прячущий файлы от git, а правило о том, что git add должен подбирать из неотслеженного состояния. Попав в индекс, файл отслеживается по содержимому вечно, пока вы явно не снимете его с учёта. Правка .gitignore задним числом на отслеженные файлы не влияет — поэтому классическая последовательность «закоммитил .env, испугался, добавил .env в .gitignore, закоммитил снова» продолжает публиковать секрет при каждом пуше.

Это кусает всех, потому что git почти повсюду: опрос Stack Overflow 2022 года показал использование Git более чем у 93 % профессиональных разработчиков (survey.stackoverflow.co) — и каждый из них рано или поздно пишет правило для файла, закоммиченного на прошлой неделе.

Дальше в посте — меньшинство случаев: ошибки порядка правил, ловушки отрицания, краевые случаи папок и вопрос про IDE. Но сначала прогоните проверку из следующей секции — больше чем в девяти случаях из десяти она закрывает расследование.

Как узнать, какое правило gitignore срабатывает для файла?

git check-ignore — диагностический инструмент, и его молчание — тоже диагноз:

# Печатает сработавшее правило + файл + номер строки, если правило действует
git check-ignore -v debug.log
# .gitignore:3:*.log    debug.log

# Ничего не печатает, если правило не действует — файл отслежен (или правил нет)
git check-ignore -v src/.env
# (тишина = .gitignore этот путь не игнорирует, что бы вы ни написали)

# Коды выхода: 0 = игнорируется, 1 = не игнорируется — годится для скриптов
git check-ignore -q debug.log && echo "ignored" || echo "tracked or unruly"

Разбор вывода -v: источник правила (.gitignore, .git/info/exclude или ваш глобальный файл), затем номер-строки:шаблон, затем путь. Если последнее правило вас удивило, вспомните приоритет: побеждает последнее совпавшее правило, поэтому !important.log после *.log возвращает один файл.

Почему gitignore не работает для уже закоммиченных файлов?

Снимите файл с учёта, оставьте на диске, закоммитьте удаление:

# Снять один файл с учёта (локальная копия выживает — --cached трогает только индекс)
git rm --cached .env
git commit -m "stop tracking .env"

# Проверить, что правило теперь действует
git check-ignore -v .env

С этого коммита путь принадлежит .gitignore: правки больше не видны в git status, а git add . не подберёт файл снова. Но в истории файл остаётся — если это был секрет, удаления из последнего коммита мало. Единственное настоящее лекарство — ротация учётных данных; переписывание истории — косметика (а отмена последнего коммита помогает лишь пока плохой коммит ещё на кончике ветки).

Для репозитория, набитого ранее закоммиченным хламом — артефактами сборки, мусором редактора, ранним node_modules — массовое снятие с учёта умещается в две строки:

git rm -r --cached .
git add .
git commit -m "apply .gitignore to tracked files"

Это пересобирает индекс по текущим правилам: игнорируемые пути выпадают, всё прочее добавляется заново без изменений. Diff выглядит драматично (тысячи удалений), но с диска не стирает ничего. Если цель — именно удалить эти файлы, а не только снять с учёта, это территория git clean -fdx: см. git remove untracked files, безопасно.

Git игнорирует файлы, которые никогда не видел. Файл в индексе невосприимчив к .gitignore — ни одно правило его не «развидит».

Почему gitignore не работает для папки?

Три папочные ловушки:

1. Слэш на конце важен для замысла, а не для совпадения. build и build/ оба совпадают с каталогом, но build/ документирует, что вы имеете в виду только каталог — файл с именем build переживёт его. Правда, на повторном включении симметрия ломается (следующая ловушка).

2. Отрицание не спасёт файлы внутри исключённого каталога. Документация git недвусмысленна: «невозможно снова включить файл, если исключён родительский каталог этого файла» (git-scm.com/docs/gitignore). Это решение ради производительности — git пропускает исключённые каталоги целиком, не заглядывая внутрь. Поэтому это не работает:

build/
!build/keep.me        # мёртвое правило — git никогда не заглянет в build/

Лечение — исключать содержимое, а не каталог:

build/*
!build/keep.me        # работает — сам build/ остаётся открыт для осмотра

3. Вложенные .gitignore побеждают в своей зоне. Правила из subdir/.gitignore перекрывают корневой файл для путей внутри subdir. Когда git check-ignore -v называет неожиданный источник игнора — обычно вот почему.

Почему gitignore не работает в VS Code?

Почти никогда по причинам VS Code. Панель Source Control читает тот же индекс, что и git, поэтому симптом тот же: файл уже закоммичен, и никакой перезапуск IDE индекс не изменит. Две реальности около VS Code знать стоит:

  • Серый в Explorer = игнорируется; оранжевый/жёлтый = отслежен с изменениями. Файл, отображающийся как изменённый после того, как вы его игнорировали, — подтверждение, что он отслежен: применяйте git rm --cached выше.
  • Отсутствие .gitignore в списке изменённых файлов Explorer — признак, что правило работает: файл просто никогда не появится как untracked. Часто жалуются «VS Code игнорирует мой gitignore», когда CLI git status расходится с устаревшим видом SCM: перезагрузите окно (Cmd/Ctrl+Shift+P → «Reload Window»), прежде чем винить git.

.gitignore против .git/info/exclude и глобального файла: что когда?

Файл Область действия Коммитится? Для чего
.gitignore (репозиторий) Все, кто клонирует Да Артефакты сборки, зависимости, .env — общие правила
.git/info/exclude Только ваш клон Нет Личный мусор: .scratch/, droppings редактора
core.excludesFile (глобальный) Все ваши репозитории Нет Мусор ОС: .DS_Store, Thumbs.db, *.swp
.gitignore + отрицание Репозиторий Да Повторное включение исключений в отслеживаемых конфигах

Глобальный файл — тот, который большинство разработчиков не настраивают, а зря:

git config --global core.excludesFile ~/.gitignore_global
printf '.DS_Store
Thumbs.db
*.swp
' >> ~/.gitignore_global

Правило, которое стоит забрать себе: если правило полезно всей команде, оно принадлежит репозиторию; если только вам — exclude или глобальному файлу. Коммит личных игнор-правил — вот как .gitignore разрастается до 300 строк, в которых никто не помнит, какая половина ещё жива.

Как игнорировать изменения уже отслеженного файла?

Иногда отслеженный файл нужен (шаблон конфига, настройки IDE), но локальные правки должны перестать мелькать в git status. Два флага git update-index, ни один из которых не место в командном процессе:

git update-index --skip-worktree config/local.dev   # локальные правки замолкают
git update-index --no-skip-worktree config/local.dev  # ...и обратно

--skip-worktree — защищаемый вариант: он говорит «моя локальная версия расходится намеренно». Его кузен --assume-unchanged — обещание производительности («этот файл не изменится»), а не механизм игнора, и git может молча его нарушить. Оба флага громко падают при pull, если upstream тоже менял файл; прочные ответы — локальный конфиг через exclude с самого начала или файл-шаблон (config.example), который git отслеживает, а вы копируете.

Проверка gitignore за 30 секунд

git check-ignore -v <path>        # какое правило? (тишина = отслежен, правила нет)
git ls-files --error-unmatch <path>  # вообще отслеживается?
git rm --cached <path>            # снять с учёта, оставить на диске
git commit -m "stop tracking <path>"
git check-ignore -v <path>        # правило теперь видно

Диагностика, снятие с учёта, проверка. За каждой жалобой «gitignore not working» стоит один файл в двух ролях — отслеженный с одной стороны индекса, игнорируемый с другой, — и один флаг --cached снимает вторую роль.