git log, diff, show и blame: как читать историю проекта в Git
git log показывает список коммитов и умеет фильтровать их по автору, дате, файлу и тексту; git diff сравнивает рабочую папку, индекс и любые коммиты; git show выводит один коммит целиком; git blame показывает, какой коммит последним менял каждую строку файла.
Четыре команды закрывают почти все вопросы к истории проекта. git log отвечает «что происходило», git diff — «что именно изменилось», git show — «что внутри конкретного коммита», git blame — «кто и когда написал эту строку». Ниже — рабочие приёмы для каждой, с флагами, которые реально используют в ежедневной разработке.
git log: список коммитов
Без аргументов git log выводит коммиты текущей ветки от новых к старым: полный хеш SHA-1, автор, дата и сообщение. Листается в пейджере (обычно less): пробел — следующая страница, /текст — поиск, q — выход.
commit 7be41d0c9a2e4f1b8d6c3e5a7f9b0c2d4e6f8a1b
Author: Anna Petrova <anna@example.com>
Date: Mon Oct 5 14:22:10 2026 +0300
feat: кэширование ответов API
Хеш не нужно копировать целиком: Git понимает любой уникальный префикс от 4 символов, на практике хватает 7.
Компактный вывод: --oneline
git log --oneline -10
# 7be41d0 (HEAD -> main, origin/main) feat: кэширование ответов API
# 1c02e8f fix: таймаут запросов
# 9a4b3c2 refactor: вынес клиент в модуль
Флаг -10 (или -n 10) ограничивает число коммитов. В скобках — указатели: где находятся HEAD, локальные и удалённые ветки, теги.
Граф веток: git log --graph
Запрос «git graph» обычно означает именно это: визуализацию ветвлений и слияний прямо в терминале.
git log --oneline --graph --all
# * 3e1f0aa (feature/auth) feat: вход по токену
# | * 7be41d0 (HEAD -> main) feat: кэширование ответов API
# |/
# * 1c02e8f fix: таймаут запросов
# * 5d2c7b1 Merge branch 'feature/search'
# |\
# | * 8f3a9e0 feat: поиск
# |/
# * 9a4b3c2 refactor: вынес клиент в модуль
--all показывает все ветки, а не только текущую. Удобно завести алиас: git config --global alias.lg "log --oneline --graph --all --decorate", после чего хватит git lg.
Свой формат: --pretty=format
git log --pretty=format:"%h %ad %an %s" --date=short
# 7be41d0 2026-10-05 Anna Petrova feat: кэширование ответов API
Основные плейсхолдеры: %h — короткий хеш, %H — полный, %an — автор, %ae — его e-mail, %ad — дата автора, %ar — относительная дата («2 days ago»), %s — заголовок сообщения.
Как отфильтровать историю в git log
По автору и дате
git log --author="Anna"
git log --since="2 weeks ago" --until="2026-10-01"
git log --since=yesterday --oneline
--author принимает регулярное выражение и ищет по имени и e-mail. Даты Git понимает и в свободной форме, и в ISO.
По тексту сообщения
git log --grep="fix" -i --oneline
-i делает поиск нечувствительным к регистру. Если ваша команда пишет номера задач в сообщениях, так быстро находятся все коммиты по задаче.
По файлу или папке
git log --oneline -- src/api/client.py
git log --follow -p -- src/api/client.py
Двойное тире отделяет пути от веток. --follow продолжает историю файла через переименования (работает для одного файла), -p показывает патч каждого коммита.
По изменённому коду: поиск «кирки»
git log -S "retry_count" --oneline # коммиты, где менялось число вхождений строки
git log -G "timeout\s*=" --oneline # коммиты, где в diff есть строки по regex
-S находит коммит, где функцию или переменную добавили либо удалили, — незаменимо, когда нужно понять, куда пропал кусок кода.
Диапазоны коммитов
git log main..feature # что есть в feature, но нет в main
git log origin/main..HEAD # мои локальные коммиты, ещё не отправленные
git log --no-merges # без merge-коммитов
git log --stat # какие файлы и сколько строк изменено
git diff: что именно изменилось
Главное, что нужно понять про git diff, — какие два состояния он сравнивает. Путаница здесь — причина вопроса «почему diff пустой, я же всё поменял».
| Команда | Что сравнивает |
|---|---|
git diff | рабочая папка против индекса (неподготовленные изменения) |
git diff --staged (--cached) | индекс против последнего коммита (что войдёт в коммит) |
git diff HEAD | рабочая папка против последнего коммита (всё вместе) |
git diff A B | два коммита, ветки или тега |
git diff A...B | изменения в B с момента ответвления от A |
Если вы сделали git add, обычный git diff станет пустым — изменения уже в индексе, смотрите --staged. Новые, ни разу не добавленные файлы diff не показывает вообще.
Как читать вывод diff
diff --git a/config.py b/config.py
index 3b18e51..a1c4f02 100644
--- a/config.py
+++ b/config.py
@@ -10,7 +10,7 @@ DEFAULTS = {
"host": "localhost",
- "timeout": 5,
+ "timeout": 30,
"retries": 3,
Строки с - удалены, с + добавлены, остальные — контекст. Заголовок @@ -10,7 +10,7 @@ означает: фрагмент начинается со строки 10 и занимает 7 строк в старой и новой версиях.
Полезные флаги git diff
git diff --stat main feature # сводка по файлам
git diff --name-only HEAD~3 # только имена изменённых файлов
git diff --word-diff # изменения по словам, удобно для текстов
git diff -w # игнорировать изменения пробелов
git diff main feature -- src/ # ограничить папкой
Перед каждым коммитом полезно смотреть git diff --staged: это последний шанс заметить отладочный print или случайно добавленный файл.
git show: один коммит целиком
git show выводит метаданные коммита и его diff относительно родителя. Без аргументов — последний коммит.
git show 7be41d0
git show HEAD~2 --stat # без патча, только сводка
git show v1.2.0 # тег: аннотация и коммит
git show main:src/config.py # файл в том виде, как он был в коммите
Последняя форма <ревизия>:<путь> особенно полезна: можно посмотреть старую версию файла, не переключаясь на неё. Перенаправьте вывод в файл, и вы восстановите удалённое содержимое без checkout.
git blame: кто изменил эту строку
git blame файл для каждой строки показывает последний коммит, который её менял, автора и дату. Название звучит обвинительно, но на деле это инструмент поиска контекста: найти коммит, прочитать сообщение и понять, зачем так сделано.
git blame -L 40,60 src/api/client.py
# 1c02e8f3 (Anna Petrova 2026-09-28 11:04:51 +0300 41) timeout = settings.timeout
# 9a4b3c2e (Ivan Smirnov 2026-09-14 18:30:02 +0300 42) session = build_session()
-L 40,60— только диапазон строк; можно и-L :имя_функции.-w— игнорировать изменения пробелов, чтобы не попадать на коммиты форматирования.-C— отслеживать строки, перенесённые из других файлов.--ignore-rev <хеш>или файл.git-blame-ignore-revs— пропускать массовые коммиты вроде автоформатирования.
Типичный сценарий расследования: git blame находит коммит, git show показывает его целиком, git log по файлу даёт соседние изменения.
Навигация по прошлому: HEAD~ и HEAD^
Ссылаться на коммиты можно не только хешами. HEAD~1 — родитель текущего коммита, HEAD~3 — прадед. HEAD^2 — второй родитель merge-коммита. Работают и имена веток, тегов, удалённых веток: origin/main~2. Посмотреть проект целиком на старом коммите можно через git switch --detach <хеш>, вернуться — git switch -.
Хорошая и плохая история
Все эти команды полезны ровно настолько, насколько осмысленна история. Сообщения вида «fix», «wip», «ещё правки» делают git log --grep и git blame бесполезными. Хороший коммит — одно логическое изменение и заголовок, который объясняет, что и зачем: fix: увеличен таймаут API до 30 с из-за медленного шлюза. Через полгода именно такой коммит найдёт blame и сэкономит вам час.
Практика: расследуем баг по истории
Допустим, после очередного релиза запросы к API стали падать по таймауту. Вот как связка команд приводит к причине за пару минут.
- Смотрим, что вошло в релиз:
git log v1.4.0..v1.5.0 --oneline. Список короткий, но по заголовкам виновник неочевиден. - Сужаем по файлу:
git log v1.4.0..v1.5.0 --oneline -- src/api/. Остаются два коммита. - Ищем по коду:
git log -S "timeout" --oneline -- src/api/находит коммит, где значение таймаута поменялось. - Смотрим его целиком:
git show 1c02e8f. В diff видно, что таймаут уменьшили с 30 до 5 секунд, а в сообщении объяснение — «ускоряем отклик UI». - Проверяем, кто ещё трогал эту строку:
git blame -L :build_session src/api/client.py.
Дальше решение уже инженерное: вернуть значение, вынести таймаут в настройки или откатить коммит через git revert. Если же коммитов сотни и найти виновника глазами нельзя, на помощь приходит git bisect — бинарный поиск по истории, который за несколько шагов указывает на коммит, сломавший поведение.
Настройки, которые упрощают жизнь
git config --global log.date iso # формат дат в log
git config --global diff.colorMoved zebra # подсветка перемещённых строк в diff
git config --global core.pager "less -FRX" # не открывать пейджер для короткого вывода
Если вывод нужно обработать скриптом, отключите цвета и пейджер: git --no-pager log --format="%H %s" --no-color. Для машинной обработки используйте полный хеш %H: короткие префиксы со временем могут стать неоднозначными в большом репозитории.
Итог
git log с фильтрами --oneline, --graph, --author, -S и путями отвечает на вопрос «что происходило». git diff сравнивает рабочую папку, индекс и коммиты — главное помнить, что он сравнивает по умолчанию. git show раскрывает один коммит или старую версию файла, а git blame ведёт от строки кода к решению, которое её породило. Чтение истории и навигация по коммитам разобраны на практике в курсе по Git.
Частые вопросы
Как выйти из git log?
Нажмите q — вывод открыт в пейджере less; чтобы печатать без пейджера, используйте git --no-pager log или флаг -n с небольшим числом.
Как посмотреть историю удалённого файла?
Выполните git log --oneline -- путь/к/файлу (с двойным тире), а содержимое до удаления — git show <хеш>^:путь/к/файлу.
Как сравнить локальную ветку с удалённой?
Сначала git fetch, затем git diff origin/main или git log origin/main..HEAD, чтобы увидеть ваши неотправленные коммиты.
Есть ли графический просмотр истории?
Да, в комплекте с Git идёт gitk, а также граф истории встроен в VS Code, JetBrains IDE и веб-интерфейсы GitHub и GitLab.
Почему git diff ничего не показывает?
Скорее всего, изменения уже добавлены в индекс через git add — смотрите git diff --staged; новые неотслеживаемые файлы git diff не показывает вовсе.
Эта тема подробно разобрана с практикой в курсе «Git с нуля до уверенного разработчика».
Перейти к курсу