git log: что это на самом деле и как читать вывод
git log - это не "показать список коммитов". Это обход графа коммитов от заданной точки (по умолчанию от HEAD) вглубь по родительским связям. Напомню легенду из прошлых уроков: узел - это коммит, стрелка идёт от коммита к его родителю, ярлык - это ветка, HEAD или тег. git log стартует с HEAD и идёт по стрелкам назад, печатая каждый встреченный узел.
Запусти голый
Код: Выделить всё
git logКод: Выделить всё
commit 9f3a1c7e2b4d8a01f6c3e9b7a25d4f08c1e6b3a9 (HEAD -> main, origin/main)
Author: Marina Volkova <marina@example.com>
Date: Wed Jun 10 14:22:03 2026 +0300
fix: округление цены при скидке больше 50 процентов
commit 2c8b4f19a7e03d6155b9c0e4f72a18d3e6b09c5a
Author: Oleg Petrov <oleg@example.com>
Date: Tue Jun 9 18:05:41 2026 +0300
refactor: вынес расчёт скидки в отдельный метод
Код: Выделить всё
HEAD -> mainКод: Выделить всё
origin/mainГолый log многословен. На практике почти всегда хочется компактно. Вот рабочий набор:
Код: Выделить всё
git log --oneline
git log --oneline --graph --all --decorate
git log --stat
git log -p
Код: Выделить всё
9f3a1c7 fix: округление цены при скидке больше 50 процентов
2c8b4f1 refactor: вынес расчёт скидки в отдельный метод
a01d9e3 feat: добавлен расчёт цены с учётом валюты

git log graph: рисуем дерево веток в терминале
Линейный список врёт о форме истории - он не показывает ветвления и слияния. Чтобы увидеть настоящую топологию, нужен git log graph. Магическая тройка:
Код: Выделить всё
git log --oneline --graph --all --decorate
- --graph рисует слева ASCII-граф связей коммит-родитель.
- --all стартует обход не только от HEAD, а от всех ссылок (всех веток и тегов). Без --all ты увидишь только то, что достижимо из текущей ветки.
- --decorate печатает ярлыки веток/тегов у коммитов (в современном git он включён по умолчанию через log.decorate=auto, но писать явно не вредно).
Код: Выделить всё
* 7d4e9a1 (HEAD -> main) Merge branch feature-currency
|\
| * a01d9e3 (feature-currency) feat: расчёт цены с учётом валюты
| * 88c2f0b feat: таблица курсов валют
* | 9f3a1c7 fix: округление цены при скидке
|/
* 2c8b4f1 refactor: вынес расчёт скидки
Код: Выделить всё
|\Код: Выделить всё
|/--pretty=format: лепим свой формат вывода
Когда нужно вытащить из истории конкретные поля (для скрипта, для отчёта, для красивого алиаса), используют --pretty=format: с плейсхолдерами. Каждый плейсхолдер - это поле коммита:
Код: Выделить всё
git log --pretty=format:"%h %ad | %an: %s" --date=short
Код: Выделить всё
9f3a1c7 2026-06-10 | Marina Volkova: fix: округление цены при скидке
2c8b4f1 2026-06-09 | Oleg Petrov: refactor: вынес расчёт скидки
Код: Выделить всё
git help logФильтры: ищем нужное в git история коммитов
История большого проекта - тысячи коммитов. Листать руками бессмысленно, надо фильтровать. Базовые фильтры комбинируются (логическое И):
Код: Выделить всё
git log --author="Marina"
git log --since="2 weeks ago" --until="2026-06-13"
git log --grep="округлени"
Код: Выделить всё
git log --grep="JIRA-451"Код: Выделить всё
-iКод: Выделить всё
--all-matchВажный нюанс: все эти фильтры работают по метаданным и сообщению, но не по содержимому изменений. "Найди коммит, который упомянул округление в сообщении" - это --grep. А вот "найди коммит, который реально изменил код, где есть слово round" - это уже другая, гораздо более мощная история.
Pickaxe: поиск по содержимому изменений (-S и -G) - оружие профи
Это та самая фича, которая отличает человека, умеющего git, от человека, который просто коммитит. Вопрос дежурного из вступления: "в каком коммите функция calculatePrice появилась или исчезла". Сообщения коммитов могут врать или молчать. Но diff не врёт. Pickaxe ищет по дельте изменений.
-S<строка> (pickaxe) показывает коммиты, в которых изменилось количество вхождений этой строки. То есть строку добавили или удалили. Это идеально для вопроса "когда эта функция/константа/конфиг появилась и когда пропала":
Код: Выделить всё
git log -S"calculatePrice" --oneline
Код: Выделить всё
9f3a1c7 fix: округление цены при скидке
a01d9e3 feat: добавлен расчёт цены с учётом валюты
-G<regex> - брат pickaxe, но другой логики: он показывает коммиты, в diff которых любая изменённая строка совпадает с регуляркой. Разница тонкая, но критичная. Пример: строку с calculatePrice переформатировали (отступ поправили), число вхождений не изменилось - -S это пропустит, а -G покажет, потому что строка фигурировала в diff. Эмпирика: -S отвечает на "когда сущность появилась/исчезла", -G отвечает на "когда этой строки касались вообще". Добавь --pickaxe-regex к -S, чтобы трактовать аргумент как регулярку. А -p вместе с pickaxe сразу покажет конкретные хунки:
Код: Выделить всё
git log -S"DEBUG_MODE" -p
Код: Выделить всё
DEBUG_MODE = TrueКод: Выделить всё
git log -S"DEBUG_MODE" -pДиапазоны коммитов: A..B против A...B
Это место, где спотыкаются даже опытные. Диапазон в git - это не "от строки X до строки Y в списке". Это операция над множествами достижимых коммитов.
A..B (две точки) - "коммиты, достижимые из B, но НЕ достижимые из A". По-человечески: "что есть в B, чего нет в A". Самый частый вопрос на свете: "что я наделал в своей ветке, чего ещё нет в main":
Код: Выделить всё
git log --oneline main..feature-currency
Код: Выделить всё
a01d9e3 feat: расчёт цены с учётом валюты
88c2f0b feat: таблица курсов валют
Код: Выделить всё
git log feature-currency..mainКод: Выделить всё
git log origin/main..HEADA...B (три точки) - симметрическая разность: "коммиты, которые есть либо в A, либо в B, но НЕ в обоих". То есть всё, что развело две ветки с момента их общего предка, с обеих сторон. Часто комбинируют с --left-right, который помечает каждый коммит стрелкой - из какой он стороны:
Код: Выделить всё
git log --oneline --left-right main...feature-currency
Код: Выделить всё
< 9f3a1c7 fix: округление цены при скидке
> a01d9e3 feat: расчёт цены с учётом валюты
> 88c2f0b feat: таблица курсов валют
Код: Выделить всё
<Код: Выделить всё
>История по merge-воркфлоу: --first-parent, --merges, --no-merges
В команде с pull request'ами история main часто выглядит как лапша из мелких коммитов фич, влитых через merge. Тебе на релизе не нужны эти 300 промежуточных коммитов - тебе нужны "крупные мазки": какие фичи влились в main. Для этого есть --first-parent.
У merge-коммита два родителя: первый - это состояние ветки, в которую вливали (main), второй - то, что вливали (feature). --first-parent идёт по истории, всегда выбирая только первого родителя, игнорируя содержимое влитых веток:
Код: Выделить всё
git log --oneline --first-parent main
Код: Выделить всё
7d4e9a1 Merge PR #214: расчёт цены с учётом валюты
9f3a1c7 fix: округление цены при скидке
6b1a0d8 Merge PR #209: новая система скидок
Код: Выделить всё
git log --no-merges --author="Oleg"git shortlog: релиз-ноуты в одну команду
Когда нужно собрать "кто что сделал в релизе", руками никто не пишет - есть git shortlog. Он группирует коммиты по авторам и показывает их сообщения:
Код: Выделить всё
git shortlog -sn v1.2.0..v1.3.0
Код: Выделить всё
42 Marina Volkova
31 Oleg Petrov
8 Anna Sidorova
Код: Выделить всё
v1.2.0..v1.3.0История одного файла и переименования: -- путь и --follow
Часто история нужна не всего проекта, а одного файла. Двойное тире -- отделяет ревизии от путей и говорит git "дальше идут файлы":
Код: Выделить всё
git log --oneline -- src/pricing.py
Код: Выделить всё
git log --oneline --follow -- src/pricing.py
git grep: поиск по рабочему дереву и истории
git log -S ищет коммиты с изменением строки. А git grep отвечает на другой вопрос: "где прямо сейчас в коде лежит эта строка". Это grep, который понимает git и в разы быстрее обычного, потому что ищет только по версионируемым файлам, игнорируя .gitignore, node_modules и .git:
Код: Выделить всё
git grep -n "calculatePrice"
Код: Выделить всё
git grep -n "DEBUG_MODE" v1.2.0
git grep -n "TODO" HEAD~10
Анонс: красивый алиас lg
Команду
Код: Выделить всё
git log --oneline --graph --all --decorateКод: Выделить всё
git config --global alias.lg "log --graph --abbrev-commit --decorate --all --pretty=format:'%C(yellow)%h%C(reset) %C(green)(%ar)%C(reset) %s %C(blue)<%an>%C(reset)%C(auto)%d%C(reset)'"
Код: Выделить всё
git lgТипичные грабли
- --graph без --all. Видишь только текущую ветку и удивляешься, "где остальные". --all обязателен, чтобы видеть всю картину.
- Путаница A..B и A...B. Для "что в моей ветке нет в main" нужны ДВЕ точки (main..feature). Три точки дадут разницу с обеих сторон - и ты испугаешься лишних коммитов.
- --grep вместо -S. --grep ищет в сообщении, а -S/-G - в содержимом diff. Если код менялся, а в сообщении про это ни слова, --grep промолчит. Это самая частая причина "я точно знаю, что строку добавляли, но log её не находит".
- Забыть -- перед путём. Если у тебя есть ветка и файл с одинаковым именем, git не поймёт, что ты имел в виду. Двойное тире снимает двусмысленность.
- --follow на нескольких файлах. Работает только с одним путём. Для нескольких - либо несколько вызовов, либо без --follow.
- -S без кавычек на спецсимволах. Если ищешь строку со скобками или звёздочкой, бери в кавычки и помни про --pickaxe-regex, если хочешь регулярку.
Собери учебный репозиторий и пройди весь арсенал по шагам.
Код: Выделить всё
git init lab-history && cd lab-history
git config init.defaultBranch main
printf 'def price(x):\n return x * 100\n' > price.py
git add price.py && git commit -m "feat: базовый расчёт цены"
printf 'def price(x):\n DEBUG = True\n return x * 100\n' > price.py
git commit -am "wip: временный дебаг"
git checkout -b discount
printf 'def price(x):\n return x * 90\n' > price.py
git commit -am "feat: скидка 10 процентов"
git checkout main
printf 'def price(x):\n return x * 100\n' > price.py
git commit -am "fix: убрал дебаг"
git merge discount -m "Merge branch discount"
- - увидь форму с merge.
Код: Выделить всё
git log --oneline --graph --all --decorate - - найди ровно два коммита, где DEBUG появился и исчез.
Код: Выделить всё
git log -S"DEBUG" --oneline - - посмотри сами diff'ы этих касаний.
Код: Выделить всё
git log -S"DEBUG" -p - и наоборот discount..main - сравни выдачи.
Код: Выделить всё
git log --oneline main..discount - - кто с какой стороны.
Код: Выделить всё
git log --oneline --left-right main...discount - - чистая лента main без содержимого ветки.
Код: Выделить всё
git log --oneline --first-parent main - - поищи в прошлом состоянии.
Код: Выделить всё
git grep -n "return" HEAD~3 - - собери список авторов.
Код: Выделить всё
git shortlog -sn
- Чем отличается результат от
Код: Выделить всё
git log main..featureи почему порядок аргументов важен?Код: Выделить всё
git log feature..main - Ты точно знаешь, что строку API_KEY кто-то добавлял в код, но ничего не находит. Почему и какой командой искать правильно?
Код: Выделить всё
git log --grep="API_KEY" - В чём разница между -S и -G в pickaxe, и в каком случае -G найдёт коммит, а -S - нет?
- Зачем нужен --first-parent при просмотре истории main в команде с pull request'ами?
git log - это не журнал, а инструмент обхода графа коммитов, который ты гнёшь под свой вопрос. --oneline --graph --all --decorate показывает форму истории. Фильтры --author, --since, --grep сужают по метаданным, а pickaxe -S/-G - по реальному содержимому изменений (это уровень профи, который окупается каждый раз на расследованиях). Диапазоны A..B и A...B - про множества достижимых коммитов, а не про строки в списке. --first-parent даёт чистую релизную ленту, git shortlog - готовые контрибьюторы, git grep - мгновенный поиск по коду в любой точке истории. Освой это - и история проекта перестанет быть свалкой, а станет картой, по которой ты находишь нужное за секунды.