From:To:
11¶ # Зачем это нужно
22
33 Типичная ситуация: что-то сломалось или выстрелило, ты нашёл в файле строку, которая всё объясняет. Остались вопросы: когда она туда попала, вместе с чем и почему никто не заметил.
44
5− Главное сразу: **цель расследования — не виноватый, а механизм.** Фамилия в коммите ничего не чинит. Ответ на вопрос ´как это прошло ревью и выкатку´ — чинит.
5+ Главное сразу: **цель расследования — не виноватый, а механизм.** Фамилия в коммите ничего не чинит. Ответ на вопрос «как это прошло ревью и выкатку» — чинит.
66
77 Список непоследовательный — бери ту команду, которая подходит к твоему вопросу.
88## Подготовка
99• Убедиться, что история вообще есть
1010 Самая обидная преграда: репозиторий клонировали с обрезанной историей, и старых коммитов просто нет.
1111
1212 ```bash
1313 git rev-parse --is-shallow-repository # true = история обрезана
1414 git fetch --unshallow # дотянуть всю
1515 git log --oneline | wc -l # сколько коммитов теперь
1616 ```
1717
1818 Так бывает чаще, чем кажется: системы развёртывания клонируют с `--depth 1` ради скорости.
1919 $ git rev-parse --is-shallow-repository
2020 why: На обрезанной истории `git log` по файлу покажет один коммит и создаст впечатление, что файл сразу таким и родился. Можно потратить час на неверный вывод.
2121 - [ ] Команда отвечает false
2222 - [ ] Число коммитов похоже на правду
23+ → git fetch --unshallow — https://git-scm.com/docs/git-fetch
2324## Поиск
2425• Найти коммит, где появилась конкретная строка
2526 Главный инструмент расследования — флаг `-S`, в документации он называется pickaxe, «кирка»:
2627
2728 ```bash
2829 # коммиты, где число вхождений строки изменилось
2930 git log -S'5432:5432' --oneline -- docker-compose.yml
3031
3132 # то же, но сразу с датами и авторами
3233 git log -S'5432:5432' --format='%h %ad %an %s' --date=short
3334
3435 # если нужна регулярка по тексту изменений
3536 git log -G'ports:\s*$' --oneline
3637 ```
3738
3839 Разница между ними важная. `-S` ищет коммиты, где строка **появилась или исчезла**. `-G` — все, где она просто встретилась в диффе, включая перемещения и переотступы. Для вопроса «когда это появилось» нужен `-S`.
3940 $ git log -S'искомая строка' --format='%h %ad %an %s' --date=short
4041 why: Поиск глазами по `git log -p` работает на десятке коммитов и проваливается на тысяче. `-S` даёт ответ за секунды на любом размере истории.
4142 - [ ] Найден коммит, в котором строка появилась
4243 - [ ] Ты понимаешь разницу между -S и -G
4344 → git log — документация — https://git-scm.com/docs/git-log
4445• Посмотреть историю файла через переименования [recommended]
4546 ```bash
4647 git log --oneline --follow -- путь/к/файлу
4748 ```
4849
4950 Флаг `--follow` продолжает историю через переименования и перемещения. Без него история обрывается в точке, где файл переехал в другую папку.
5051
51− Работает только с одним путᑑм — это ограничение самой команды.
52+ Работает только с одним путём — это ограничение самой команды.
5253 $ git log --oneline --follow -- путь/к/файлу
5354 why: Файлы переезжают при любой перестройке структуры. Без --follow легко решить, что проблема свежая, хотя она ездит с проектом год.
5455 - [ ] Видна вся история файла, включая жизнь под старым именем
5556• Прочитать файл таким, каким он был в тот момент
5657 Когда коммит найден, нужно увидеть контекст целиком, а не только строку:
5758
5859 ```bash
5960 # файл целиком на момент коммита
6061 git show <sha>:путь/к/файлу
6162
6263 # только изменения по этому файлу
6364 git show <sha> -- путь/к/файлу
6465
6566 # что ещё ехало в том же коммите
6667 git show --stat <sha>
6768 ```
6869
6970 Обрати внимание на двоеточие против двойного тире: `<sha>:путь` — содержимое файла, `<sha> -- путь` — дифф.
7071 $ git show --stat <sha>
7172 why: Отдельная строка часто выглядит глупостью, а в контексте оказывается частью большого коммита на сотни строк — такого, который физически невозможно внимательно проревьюить. Это уже ответ на вопрос «как прошло мимо».
7273 - [ ] Виден весь файл на момент коммита
7374 - [ ] Известен размер того коммита целиком
7475• Посмотреть построчно, откуда взялась каждая строка [recommended]
7576 ```bash
7677 # только нужный диапазон строк
7778 git blame -L 40,55 путь/к/файлу
7879
7980 # игнорировать правки пробелов и отследить перенос строк из других файлов
8081 git blame -w -C -C -L 40,55 путь/к/файлу
8182 ```
8283
8384 Флаги важны: `-w` игнорирует изменения пробелов, `-C -C` ищет, откуда строку скопировали. Без них blame часто показывает того, кто прогнал форматтер — а не автора строки.
8485 $ git blame -w -C -C -L 40,55 путь/к/файлу
8586 why: Половина претензий к blame — из-за этих флагов. Люди видят чужую фамилию, махают рукой и решают, что инструмент врёт.
8687 - [ ] Ты понимаешь, зачем нужны -w и -C
8788 → git blame — документация — https://git-scm.com/docs/git-blame
8889• Если опорной строки нет — бинарный поиск [recommended]
8990 Когда известно только «раньше работало, теперь нет»:
9091
9192 ```bash
9293 git bisect start
9394 git bisect bad # текущий — сломанный
9495 git bisect good <старый-sha> # там было хорошо
9596
9697 # git переключает на середину, ты проверяешь и говоришь:
9798 git bisect good # или
9899 git bisect bad
99100
100101 git bisect reset # вернуться как было
101102 ```
102103
103104 Если проверку можно автоматизировать, git сделает всё сам:
104105
105106 ```bash
106107 git bisect run ./check.sh # код 0 = хорошо, не 0 = плохо
107108 ```
108109
109110 За 20 шагов просеивается миллион коммитов — это обычный двоичный поиск.
110111 $ git bisect start
111112 why: Не всякая проблема выражается строкой, которую можно поискать. Падение производительности или плавающая ошибка ищется только так.
112113 - [ ] Ты знаешь про git bisect run
113114 - [ ] Помнишь про git bisect reset в конце
114115 → git bisect — документация — https://git-scm.com/docs/git-bisect
115116¶ ## Собрать таймлайн
116117
117118 Отдельный коммит мало что говорит. Смысл появляется, когда время из git ложится рядом со временем из системных логов:
118119
119120 ```bash
120121 # события из git за период
121122 git log --since='2 weeks ago' --format='%ad %h %an %s' --date=iso
122123
123124 # события системы за тот же период
124125 journalctl --since '2025-10-08' --until '2025-10-14' | grep -i 'oom\|killed'
125126
126127 # когда контейнер создали и запустили
127128 docker inspect --format '{{.Created}} {{.State.StartedAt}}' имя
128129 ```
129130
130131 В разобранном случае таймлайн выглядел так:
131132
132133 | Время | Источник | Событие |
133134 |---|---|---|
134135 | 08:07 | git | коммит с открытым портом |
135136 | ~08:30 | docker | контейнер создан |
136137 | 16:29 | системный лог | OOM killer убил неизвестный процесс |
137138
138139 Последняя строка — самая важная. **Сигнал был в первый же день**, просто его никто не читал. Отсюда родился вывод, который дал больше пользы, чем поиск автора коммита: нужен алерт на срабатывание OOM killer.
139140¶ ## Главное правило разбора
140141
141− Когда виновная строка найдена, появляется соблазн написать в отчᑑте фамилию автора и распределить ответственность в процентах. Это бесполезно и вредно.
142+ Когда виновная строка найдена, появляется соблазн написать в отчёте фамилию автора и распределить ответственность в процентах. Это бесполезно и вредно.
142143
143− Бесполезно — потому что не меняет вероятность повтора. Вредно — потому что следующий человек, наткнувшийся на странность, предпочтᑑт промолчать.
144+ Бесполезно — потому что не меняет вероятность повтора. Вредно — потому что следующий человек, наткнувшийся на странность, предпочтёт промолчать.
144145
145146 Заканчивай разбор тремя пунктами:
146147
147− 1. **Что сработало как надо.** Аварию всᑑ-таки заметили — как именно?
148+ 1. **Что сработало как надо.** Аварию всё-таки заметили — как именно?
148149 2. **Где был шанс поймать раньше.** Не «кто пропустил», а «какой проверки не было».
149− 3. **Какая проверка поймала бы это автоматически.** И сразу завести на неᑑ задачу.
150+ 3. **Какая проверка поймала бы это автоматически.** И сразу завести на неё задачу.
150151
151152 Для того инцидента ответ на третий пункт — проверка в конвейере, которая не пускает в бой compose-файл с прокинутым портом базы:
152153
153154 ```bash
154155 # в простейшем виде — одна строка в конвейере
155156 grep -nE '^\s*-\s*"?[0-9]+:(5432|6379|3306|27017)"?' docker-compose.yml && exit 1
156157 ```
157158
158159 Такая строка полезнее любого вывода о том, кто виноват.
159160¶ ## Шпаргалка
160161
161162 ```bash
162163 # есть ли история
163164 git rev-parse --is-shallow-repository
164165 git fetch --unshallow
165166
166167 # когда появилась строка
167168 git log -S'строка' --format='%h %ad %an %s' --date=short
168169 git log -G'регулярка' --oneline
169170
170171 # история файла
171172 git log --oneline --follow -- путь
172173
173174 # состояние на момент
174175 git show <sha>:путь # файл целиком
175176 git show <sha> -- путь # только дифф
176177 git show --stat <sha> # что ещё ехало рядом
177178
178179 # построчно
179180 git blame -w -C -C -L 40,55 путь
180181
181182 # если строки нет
182183 git bisect start / bad / good / run ./check.sh / reset
183184 ```
184185¶
185186¶
186187¶