Git ignore и секреты
Репозиторий должен содержать исходный код, конфигурации и документацию. Не должен содержать: скомпилированные бинарники, зависимости, локальные настройки IDE, логи и - самое важное - секреты. .gitignore определяет, какие файлы Git должен полностью игнорировать (мы уже коротко трогали его в базовом цикле). Правильный .gitignore экономит время и предотвращает утечки.
Если секрет попал в Git-историю - он утёк. Даже если удалить файл следующим коммитом, он останется в истории. Поэтому .gitignore настраивают в первом коммите проекта, до того как что-либо конфиденциальное появится в рабочей директории.
Синтаксис .gitignore
Базовые паттерны
# Комментарий
*.log # Все файлы с расширением .log
build/ # Директория build и всё внутри
/TODO.txt # Только TODO.txt в корне репозитория (не в поддиректориях)
doc/*.pdf # PDF в директории doc, но не в doc/subdir/
Wildcard-паттерны
* # Любая последовательность символов (кроме /)
? # Один любой символ
[abc] # Один символ из набора: a, b или c
[0-9] # Один символ из диапазона: 0-9
Двойная звёздочка (**)
** матчит любое количество директорий:
**/logs # директория logs на любом уровне вложенности
**/logs/*.log # .log файлы в директории logs на любом уровне
logs/** # всё внутри директории logs
a/**/b # a/b, a/x/b, a/x/y/b и т.д.
Отрицание (!)
Восклицательный знак отменяет игнорирование:
# Игнорировать все .env файлы
*.env
# Но не .env.example (шаблон для разработчиков)
!.env.example
Порядок строк важен - правила применяются сверху вниз. Нельзя «раз-игнорировать» файл, если его родительская директория уже игнорируется:
# Это НЕ работает:
build/
!build/important.txt # Не поможет - build/ уже полностью игнорируется
# Это работает:
build/*
!build/important.txt # build/* игнорирует содержимое, но не саму директорию
.gitignore для Go-проекта
Типичный .gitignore для Go-проекта:
# Бинарники
/backend/backend
/backend/bin/
*.exe
*.dll
*.so
*.dylib
# Зависимости (если используется vendor, можно оставить)
# vendor/
# Тестовое покрытие
*.out
coverage.html
# Переменные окружения
.env
.env.local
.env.*.local
# IDE
.idea/
.vscode/
*.swp
*.swo
*~
# OS файлы
.DS_Store
Thumbs.db
# Docker
docker-compose.override.yml
# Логи
*.log
logs/
# Временные файлы
tmp/
*.tmp
Обрати внимание на .env: сам файл в репозиторий не попадает, а значения подставляются при запуске. Как это устроено для контейнеров - в уроке про переменные окружения, volumes и сети в Docker.
Глобальный .gitignore
Файлы IDE и OS-специфичный мусор (.DS_Store, .idea/) не стоит добавлять в .gitignore каждого проекта. Лучше настроить глобальный gitignore:
# Создать файл
touch ~/.gitignore_global
# Сказать Git использовать его
git config --global core.excludesFile ~/.gitignore_global
Содержимое ~/.gitignore_global:
# macOS
.DS_Store
._*
# Linux
*~
# IDE
.idea/
.vscode/
*.swp
*.swo
# Директория для локальных заметок
.scratch/
Теперь эти паттерны работают во всех репозиториях на твоей машине, и коллегам не нужно видеть твои настройки IDE в .gitignore проекта.
.gitignore в поддиректориях
Git обрабатывает .gitignore в каждой директории. Правила из вложенного .gitignore дополняют родительский:
project/
├── .gitignore # *.log, .env
├── backend/
│ └── .gitignore # /bin/, *.out
└── frontend/
└── .gitignore # node_modules/, dist/
Это удобно в монорепозитории: backend и frontend имеют разные артефакты сборки.
Игнорирование уже отслеживаемых файлов
Если файл уже был закоммичен, добавление его в .gitignore не поможет - Git продолжит отслеживать изменения. Нужно явно убрать его из индекса:
# Удалить из Git, но оставить на диске
git rm --cached .env
# Для директории
git rm -r --cached node_modules/
# Добавить в .gitignore
echo ".env" >> .gitignore
# Закоммитить
git add .gitignore
git commit -m "chore: remove .env from tracking, add to .gitignore"
.gitkeep для пустых директорий
Git не отслеживает пустые директории. Если нужно сохранить структуру (например, logs/ или uploads/), добавь пустой файл-маркер:
mkdir -p backend/logs
touch backend/logs/.gitkeep
git add backend/logs/.gitkeep
.gitkeep - это конвенция, а не функция Git. Файл может называться как угодно, но .gitkeep - общепринятое имя.
Проверка правил игнорирования
Если непонятно, почему файл игнорируется (или не игнорируется):
# Показать, какое правило игнорирует файл
git check-ignore -v .env
# .gitignore:4:.env .env
# Проверить несколько файлов
git check-ignore -v .env backend/bin/server logs/app.log
# Показать все игнорируемые файлы в репозитории
git status --ignored
Защита от утечки секретов
git-secrets
git-secrets - утилита от AWS, которая предотвращает коммит секретов:
# Установка (macOS)
brew install git-secrets
# Настройка для репозитория
cd project
git secrets --install
git secrets --register-aws # паттерны для AWS ключей
# Добавить свои паттерны
git secrets --add 'password\s*=\s*.+'
git secrets --add 'PRIVATE.KEY'
# Теперь git commit проверяет содержимое на секреты
gitleaks
gitleaks сканирует репозиторий на предмет уже утёкших секретов и хорошо ложится в CI-пайплайн команды:
# Установка
brew install gitleaks
# Сканировать текущее состояние
gitleaks detect --source .
# Сканировать всю историю
gitleaks detect --source . --log-opts="--all"
# Использовать в CI
gitleaks detect --source . --verbose --exit-code 1
Конфигурация в .gitleaks.toml:
[allowlist]
paths = [
'''\.env\.example#x27;'',
'''test/fixtures/'''
]
Pre-commit hooks
Автоматическая проверка перед каждым коммитом:
# .git/hooks/pre-commit (или через pre-commit framework)
#!/bin/bash
# Проверка на секреты
if git diff --cached --name-only | xargs grep -l 'PRIVATE_KEY\|SECRET_KEY\|password=' 2>/dev/null; then
echo "ERROR: Possible secret detected in staged files"
exit 1
fi
# Проверка на .env файлы
if git diff --cached --name-only | grep -q '\.env#x27;; then
echo "ERROR: .env file is staged for commit"
exit 1
fi
Что делать, если секрет уже в истории
Если ты запушил .env с паролем БД - пароль скомпрометирован. Первое действие: сменить секрет (пароль, токен, ключ). Это важнее, чем чистить историю.
BFG Repo-Cleaner
Самый быстрый способ очистить историю:
# Установка
brew install bfg
# Удалить файл из всей истории
bfg --delete-files .env
# Заменить текст во всей истории
echo "OLD_DB_PASSWORD" >> passwords.txt
bfg --replace-text passwords.txt
# Очистить и запушить
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push --force
git filter-repo
Более мощная альтернатива (рекомендована Git вместо filter-branch):
pip install git-filter-repo
# Удалить файл из всей истории
git filter-repo --invert-paths --path .env
# Force push после очистки
git push --force --all
Мини-задание
- Создай
.gitignoreдля Go-проекта с паттернами для бинарников, .env, IDE файлов - Настрой глобальный
~/.gitignore_globalдля своей OS и IDE - Добавь файл в Git, потом удали из отслеживания через
git rm --cached, не удаляя с диска - Проверь правило игнорирования командой
git check-ignore -v <file> - Установи gitleaks и просканируй свой репозиторий:
gitleaks detect --source .