Поиск по истории: log, blame, bisect

Код сломался. Тесты были зелёные, а теперь красные. Или хуже - баг на проде, и никто не знает, когда он появился. Git хранит полную историю каждого изменения, и в нём есть инструменты, чтобы найти виновный коммит за минуты, а не за часы ручного перебора. Чем аккуратнее ты оформлял коммиты, тем проще этот поиск.

Умение читать и искать по истории Git - это навык, который отличает разработчика, который «просто коммитит», от разработчика, который понимает, что происходит в проекте. log, blame и bisect - три основных инструмента для расследования.

git log: чтение истории

Базовые команды

# Стандартный вывод (полный формат)
git log

# Компактный формат: один коммит - одна строка
git log --oneline

# С графом веток
git log --oneline --graph --decorate

# С графом и всеми ветками (не только текущей)
git log --oneline --graph --decorate --all

Фильтрация по времени

# Коммиты за последнюю неделю
git log --since="1 week ago"

# Коммиты за конкретный период
git log --after="2026-04-01" --before="2026-04-30"

# Последние 10 коммитов
git log -10

Фильтрация по автору

# Все коммиты конкретного автора
git log --author="Dmitriy"

# Поддерживается regex
git log --author="dmitriy\|popov" -i

Фильтрация по сообщению

# Коммиты с "fix" в сообщении
git log --grep="fix"

# Case-insensitive
git log --grep="fix" -i

# Несколько паттернов (OR)
git log --grep="feat:" --grep="fix:"

# Несколько паттернов (AND)
git log --grep="feat:" --grep="auth" --all-match

Фильтрация по содержимому (pickaxe)

Самый мощный фильтр - поиск по тому, что было изменено в коде:

# Коммиты, где строка "MaxRequestSize" появилась или исчезла
git log -S "MaxRequestSize"

# С показом diff
git log -S "MaxRequestSize" -p

# Поиск по регулярному выражению
git log -G "func.*Handler" --oneline

-S ищет коммиты, где количество вхождений строки изменилось (добавлена или удалена). -G ищет коммиты, где diff содержит совпадение с regex.

Форматирование вывода

# Кастомный формат
git log --format="%h %an %ar %s"
# a1b2c3d Dmitriy 2 hours ago feat: add rate limiting

# Полезные плейсхолдеры:
# %h - короткий хеш
# %H - полный хеш
# %an - имя автора
# %ae - email автора
# %ar - относительная дата (2 hours ago)
# %ad - дата (формат настраивается через --date)
# %s - тема коммита (первая строка)
# %b - тело коммита
# Красивый однострочный формат с датой
git log --format="%C(yellow)%h%C(reset) %C(green)%ad%C(reset) %s %C(blue)<%an>%C(reset)" --date=short

История конкретного файла

# Все коммиты, менявшие файл
git log - backend/handler/auth.go

# С показом diff для этого файла
git log -p - backend/handler/auth.go

# Следить за переименованиями
git log --follow - backend/handler/auth.go
Без `--follow` история обрывается на коммите, где файл был переименован. С `--follow` Git проследит историю до исходного имени. Используй всегда, когда смотришь историю конкретного файла.

git shortlog: статистика по авторам

# Количество коммитов по авторам
git shortlog -s -n
#  142  Dmitriy Popov
#   38  Ivan Ivanov
#   12  Bot

# За период
git shortlog -s -n --since="2026-04-01"

git blame: кто написал эту строку

blame показывает, кто последний раз менял каждую строку файла и в каком коммите:

git blame backend/handler/auth.go
# a1b2c3d4 (Dmitriy Popov 2026-04-15 14:23:01 +0300  1) package handler
# a1b2c3d4 (Dmitriy Popov 2026-04-15 14:23:01 +0300  2)
# d4e5f6g7 (Ivan Ivanov   2026-04-20 09:12:33 +0300  3) import (
# d4e5f6g7 (Ivan Ivanov   2026-04-20 09:12:33 +0300  4)     "net/http"

Blame для диапазона строк

Если интересует конкретный участок кода:

# Строки 50-70
git blame -L 50,70 backend/handler/auth.go

# От строки 50 до конца функции (если функция начинается на строке 50)
git blame -L 50,+20 backend/handler/auth.go

# По имени функции (Go, PHP)
git blame -L :HandleLogin backend/handler/auth.go

Полезные флаги

# Игнорировать изменения пробелов (whitespace)
git blame -w backend/handler/auth.go

# Показать email вместо имени
git blame -e backend/handler/auth.go

# Определить, откуда код был скопирован (внутри файла)
git blame -M backend/handler/auth.go

# Определить, откуда код был скопирован (из других файлов)
git blame -C backend/handler/auth.go
Кто-то прогнал `gofmt` и blame показывает его автором всех строк? Добавь `-w`, и Git проигнорирует изменения пробелов, показав реального автора.

Blame в IDE и GitLab

В IDE (GoLand, VS Code) blame встроен - наведи на строку и увидишь автора, дату, коммит. В GitLab: открой файл → кнопка Blame сверху. Это удобнее терминала для быстрого просмотра. На практике blame чаще всего нужен не чтобы найти виноватого, а чтобы найти контекст решения - именно поэтому осмысленные комментарии в ревью и внятные сообщения коммитов окупаются.

git bisect: бинарный поиск бага

bisect - самый мощный инструмент отладки в Git. Он выполняет бинарный поиск по истории коммитов, чтобы найти тот, который сломал код. Если между «всё работало» и «всё сломалось» 100 коммитов, bisect найдёт виновный за 7 шагов (log2(100) ≈ 7).

Ручной bisect

# Начать сессию bisect
git bisect start

# Текущий коммит - плохой (баг есть)
git bisect bad

# Указать коммит, где всё работало (здесь пригодятся теги релизов)
git bisect good v1.1.0
# Bisecting: 50 revisions left to test after this (roughly 6 steps)

# Git переключит на середину. Проверяешь - работает?
go test ./...

# Если баг есть:
git bisect bad

# Если бага нет:
git bisect good

# Повторяй, пока Git не найдёт:
# a1b2c3d is the first bad commit
# commit a1b2c3d
# Author: Someone
# Date: ...
# feat: add caching to user service

# Завершить (вернуться к исходному состоянию)
git bisect reset

Автоматический bisect

Если у тебя есть команда, которая возвращает 0 при успехе и не-0 при ошибке (например, тест), bisect может работать автоматически:

git bisect start
git bisect bad HEAD
git bisect good v1.1.0

# Запустить автоматический поиск
git bisect run go test ./internal/service/user_test.go -run TestGetUser -count=1

# Git сам будет переключать коммиты и запускать тест
# Результат: первый коммит, где тест падает

Можно использовать скрипт:

git bisect run bash -c '
  cd backend && go build ./... && go test ./internal/service/... -count=1
'
При переключении коммитов файлы в репозитории меняются. Если скрипт для bisect лежит в репозитории и был изменён между good и bad коммитами, он может сломаться. Лучше использовать inline-команды или скрипт из /tmp.

Пропуск коммитов

Иногда коммит невозможно проверить (не компилируется, например):

git bisect skip
# Git выберет соседний коммит

git show: просмотр конкретного коммита

# Показать коммит: метаданные + diff
git show a1b2c3d

# Только статистику изменений (без diff)
git show --stat a1b2c3d

# Показать конкретный файл в конкретном коммите
git show a1b2c3d:backend/handler/auth.go

# Показать файл на момент тега
git show v1.1.0:backend/handler/auth.go

Здесь видно, зачем нужны теги релизов: v1.1.0 в команде читается однозначно, а a1b2c3d - нет.

git diff: сравнение

# Разница между working directory и staging
git diff

# Разница между staging и последним коммитом
git diff --staged

# Разница между двумя коммитами
git diff a1b2c3d d4e5f6g

# Разница между двумя ветками
git diff develop..main

# Разница только для конкретного файла
git diff develop..main - backend/handler/auth.go

# Только имена изменённых файлов
git diff --name-only develop..main

# Статистика изменений
git diff --stat develop..main

git reflog: восстановление потерянного

reflog записывает каждое движение HEAD. Если ты потерял коммит после reset или неудачного rebase - он почти наверняка в reflog:

git reflog
# a1b2c3d HEAD@{0}: reset: moving to HEAD~5    ← вот тут ты потерял коммиты
# d4e5f6g HEAD@{1}: commit: feat: add caching
# h7i8j9k HEAD@{2}: commit: fix: user service

# Восстановить
git reset --hard d4e5f6g

# Или создать ветку из потерянного коммита
git branch recovered-work d4e5f6g

Практический workflow отладки

Сценарий: тесты на CI красные, но ты не знаешь, какой коммит сломал.

# 1. Найти, когда последний раз тесты проходили
git log --oneline -20
# Знаешь, что v1.1.0 был стабильный

# 2. Запустить автоматический bisect
git bisect start
git bisect bad HEAD
git bisect good v1.1.0
git bisect run make test

# 3. bisect нашёл коммит a1b2c3d
# Посмотреть, что там изменилось
git show a1b2c3d

# 4. Понять, кто автор и спросить контекст
git log -1 --format="%an <%ae>" a1b2c3d

# 5. Посмотреть blame конкретной строки, которая ломает
git blame -L :problematicFunction backend/internal/service/user.go

# 6. Завершить bisect
git bisect reset

Весь процесс занимает 5-10 минут вместо часов ручного поиска.

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

  • Посмотри историю проекта: git log --oneline --graph --all и git shortlog -s -n
  • Найди все коммиты с "fix" в сообщении: git log --grep="fix" --oneline
  • Запусти git blame на любом файле с флагом -w и найди автора конкретной строки
  • Сравни две ветки: git diff main..develop --stat
  • Попробуй git bisect на учебном репозитории (создай 10 коммитов, в одном сломай тест, найди его через bisect)

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