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.

Go community разделено: одни коммитят vendor/ для воспроизводимых сборок без интернета, другие - нет, потому что `go mod download` достаточно. Для приложений обычно не коммитят. Для библиотек - тоже нет. Для проектов с CI без доступа к proxy - коммитят.

Глобальный .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
После BFG или filter-repo нужен `git push --force` - это то же [переписывание истории, что и при rebase](./05-merge-rebase.md), только для всего репозитория. Это перезаписывает историю remote. Все коллеги должны сделать `git clone` заново (не pull, а именно clone). Координируй с командой.

Мини-задание

  • Создай .gitignore для Go-проекта с паттернами для бинарников, .env, IDE файлов
  • Настрой глобальный ~/.gitignore_global для своей OS и IDE
  • Добавь файл в Git, потом удали из отслеживания через git rm --cached, не удаляя с диска
  • Проверь правило игнорирования командой git check-ignore -v <file>
  • Установи gitleaks и просканируй свой репозиторий: gitleaks detect --source .

Зарегистрируйтесь бесплатно, чтобы пройти квиз, решить задание с автопроверкой и вести прогресс.