Git-форензика: когда появилась проблема и как она прошла мимо всех
Как найти коммит, в котором появилась опасная строка, восстановить таймлайн инцидента и сделать из этого вывод, а не список виноватых.
miki/git-forenzika-kogda-poyavilas-problema-i-kak-ona-proshla-mim · v2
Как найти коммит, в котором появилась опасная строка, восстановить таймлайн инцидента и сделать из этого вывод, а не список виноватых.
Типичная ситуация: что-то сломалось или выстрелило, ты нашёл в файле строку, которая всё объясняет. Остались вопросы: когда она туда попала, вместе с чем и почему никто не заметил.
Главное сразу: цель расследования — не виноватый, а механизм. Фамилия в коммите ничего не чинит. Ответ на вопрос «как это прошло ревью и выкатку» — чинит.
Список непоследовательный — бери ту команду, которая подходит к твоему вопросу.
Подготовка
Самая обидная преграда: репозиторий клонировали с обрезанной историей, и старых коммитов просто нет.
Так бывает чаще, чем кажется: системы развёртывания клонируют с --depth 1 ради скорости.
git rev-parse --is-shallow-repository- Команда отвечает false
- Число коммитов похоже на правду
Поиск
Главный инструмент расследования — флаг -S, в документации он называется pickaxe, «кирка»:
Разница между ними важная. -S ищет коммиты, где строка появилась или исчезла. -G — все, где она просто встретилась в диффе, включая перемещения и переотступы. Для вопроса «когда это появилось» нужен -S.
git log -S'искомая строка' --format='%h %ad %an %s' --date=short- Найден коммит, в котором строка появилась
- Ты понимаешь разницу между -S и -G
Флаг --follow продолжает историю через переименования и перемещения. Без него история обрывается в точке, где файл переехал в другую папку.
Работает только с одним путём — это ограничение самой команды.
git log --oneline --follow -- путь/к/файлу- Видна вся история файла, включая жизнь под старым именем
Когда коммит найден, нужно увидеть контекст целиком, а не только строку:
Обрати внимание на двоеточие против двойного тире: <sha>:путь — содержимое файла, <sha> -- путь — дифф.
git show --stat <sha>- Виден весь файл на момент коммита
- Известен размер того коммита целиком
Флаги важны: -w игнорирует изменения пробелов, -C -C ищет, откуда строку скопировали. Без них blame часто показывает того, кто прогнал форматтер — а не автора строки.
git blame -w -C -C -L 40,55 путь/к/файлу- Ты понимаешь, зачем нужны -w и -C
Когда известно только «раньше работало, теперь нет»:
Если проверку можно автоматизировать, git сделает всё сам:
За 20 шагов просеивается миллион коммитов — это обычный двоичный поиск.
git bisect start- Ты знаешь про git bisect run
- Помнишь про git bisect reset в конце
Отдельный коммит мало что говорит. Смысл появляется, когда время из git ложится рядом со временем из системных логов:
В разобранном случае таймлайн выглядел так:
| Время | Источник | Событие |
|---|---|---|
| 08:07 | git | коммит с открытым портом |
| ~08:30 | docker | контейнер создан |
| 16:29 | системный лог | OOM killer убил неизвестный процесс |
Последняя строка — самая важная. Сигнал был в первый же день, просто его никто не читал. Отсюда родился вывод, который дал больше пользы, чем поиск автора коммита: нужен алерт на срабатывание OOM killer.
Когда виновная строка найдена, появляется соблазн написать в отчёте фамилию автора и распределить ответственность в процентах. Это бесполезно и вредно.
Бесполезно — потому что не меняет вероятность повтора. Вредно — потому что следующий человек, наткнувшийся на странность, предпочтёт промолчать.
Заканчивай разбор тремя пунктами:
- Что сработало как надо. Аварию всё-таки заметили — как именно?
- Где был шанс поймать раньше. Не «кто пропустил», а «какой проверки не было».
- Какая проверка поймала бы это автоматически. И сразу завести на неё задачу.
Для того инцидента ответ на третий пункт — проверка в конвейере, которая не пускает в бой compose-файл с прокинутым портом базы:
Такая строка полезнее любого вывода о том, кто виноват.
