Вопрос, который рано или поздно задаёт каждый разработчик
В прошлом месяце всё работало. Вы в этом уверены. Сайдбар был выровнен, парсер дат корректно обрабатывал часовые пояса, кнопка была нужного оттенка синего. Потом прилетает баг-репорт, и вот вы смотрите на файл с двухлетней историей и пытаетесь ответить на обманчиво простой вопрос: когда это изменилось и какой commit за это отвечает?
Сразу за ним идёт второй вопрос: кто внёс это изменение и зачем? Возможно, это объяснит сообщение commit. Возможно, автор вспомнит. Но сначала нужно найти сам commit -- и тут начинается умственная гимнастика.
Инструменты CLI и где их не хватает
В Git уже есть всё необходимое, чтобы исследовать историю файла. Инструменты мощные и проверенные временем -- трение возникает не в какой-то отдельной команде, а в том, сколько их приходится связывать в цепочку.
git log --follow
git log --follow --oneline -- src/styles/sidebar.css
Эта команда выводит каждый commit, который затронул файл, даже через переименования. Это правильная отправная точка. Но она даёт вам SHA и сообщения commit, а не содержимое. Чтобы увидеть, как файл на самом деле выглядел в конкретный момент, нужна следующая команда.
git show SHA:path
git show a3b4c5d:src/styles/sidebar.css
Эта команда печатает файл целиком в том виде, в каком он существовал на момент этого commit. Чтобы сравнить две версии, запустите её дважды и сделайте diff вывода, либо используйте git diff a3b4c5d 9f2e1d0 -- src/styles/sidebar.css. Теперь повторите это для каждого подозрительного commit. Если файл затронули тридцать commit, вы надолго застрянете в копировании SHA, всё это время удерживая различия между версиями в голове.
git blame
git blame src/styles/sidebar.css
Blame помечает каждую строку последним commit, который её изменил. Это отлично работает, когда подозрительная строка всё ещё в файле и вы хотите узнать, кто её написал. Это куда менее полезно, когда регрессия пришла из строки, которая была удалена, или когда последнее изменение строки -- безобидное переформатирование, скрывающее то изменение, которое вас на самом деле интересует. Тогда вы раз за разом запускаете git blame SHA^ -- path, снимая историю слой за слоем.
git bisect
Bisect выполняет бинарный поиск по всей вашей истории, чтобы найти commit, сломавший тестируемое поведение. Когда «сломано» означает, что сборка падает или тест краснеет, bisect -- ровно тот инструмент, который нужен, и ничто в GUI его не заменит. Но это тяжёлая артиллерия для вопроса вроде «когда изменилось это CSS-правило?». Вам не нужно делать checkout и пересобирать проект на каждом шаге -- вы просто хотите посмотреть на один файл во времени.
Итак, ответ CLI на «посмотреть, как файл эволюционирует» таков: перечислить commit через log, напечатать каждую версию через show, вручную делать diff пар и держать всё состояние в голове. Каждая команда работает. Проблема -- в самом workflow.
Таймлайн файла: визуальный ответ
GitSquid выпустил таймлайн файла в v2.6, и он включён в тариф Free. Идея проста: вместо того чтобы восстанавливать эволюцию файла commit за commit у себя в голове, вы наблюдаете её на экране.
Откройте его правым кликом
Кликните правой кнопкой по любому файлу и выберите Показать историю. Откроется вид истории файла, целиком сосредоточенный на этом одном файле.
Одна метка на каждый commit
Над просмотрщиком файла располагается полоса таймлайна: одна метка на каждый commit, затронувший файл. Вся жизнь файла, развёрнутая по горизонтали. Файл с тремя commit выглядит спокойно; файл с шестьюдесятью метками говорит кое-что о своей изменчивости ещё до того, как вы прочитали хоть одну строку.
Перематывайте версии
Тяните ползунок -- и просмотрщик обновляется прямо во время перетаскивания. Это сердце функции: вы не прыгаете между отдельными снимками, которые приходится мысленно сравнивать через diff, -- вы наблюдаете, как файл меняется. После первого прохода перемотка туда-обратно ощущается мгновенной, так что, один раз пройдя историю, вы можете свободно колебаться вокруг подозрительного участка, сужая поиск до того самого commit, где всё изменилось.
Наведите курсор, чтобы увидеть, кто что менял
Наведите курсор на любую метку -- и вы получите автора и информацию о commit для этой точки истории. Вопрос «кто это написал и когда» решается, не покидая таймлайн и вообще не запуская blame.
Нажмите play
Нажмите ▶ -- и GitSquid автоматически проиграет историю. Для первого знакомства с незнакомым файлом это самый быстрый способ выстроить интуицию: вы видите, как структуры появляются, растут, подвергаются рефакторингу. Это разница между чтением тридцати diff и просмотром таймлапса.
Snapshot или Diff
Переключатель Snapshot / Diff в шапке переключает между двумя видами: полное содержимое файла, каким оно было на момент этого commit, или классический diff файла относительно родителя. Режим Snapshot отвечает на вопрос «как файл выглядел тогда?»; режим Diff отвечает на вопрос «что именно изменил этот commit?». Охота за регрессией обычно начинается в режиме Snapshot и заканчивается в режиме Diff.
Перейдите к версии
Когда вы нашли нужную версию, три действия справа ведут вас дальше: Скопировать SHA помещает хеш commit в буфер обмена для использования где угодно, Открыть commit переходит к полному commit, чтобы увидеть, что ещё изменилось вместе с вашим файлом, а Checkout detached переводит вашу рабочую директорию ровно на этот commit, когда нужно по-настоящему запустить старый код.
Разбор на практике: ищем CSS-регрессию
Конкретный пример. Сайдбар в вашем приложении потерял padding, и никто не знает, когда. Вот как выглядит охота с таймлайном файла:
- Правый клик по
sidebar.css→ Показать историю. Таймлайн показывает 24 метки за восемь месяцев. - Перематывайте от новых к старым, следя за правилом padding. Примерно на двух третях правило заметно меняется. После первого прохода перемотка мгновенна, поэтому вы двигаете ползунок туда-сюда вокруг этого участка, пока не изолируете ту самую метку, где значение поменялось.
- Наведите курсор на метку. Это был коллега, три недели назад, в commit с пометкой «рефакторинг лэйаута». Загадка наполовину разгадана.
- Переключитесь на Diff. Переключатель показывает diff файла относительно родителя: padding свернули в shorthand-свойство, и одно значение потерялось по дороге. Вот она, регрессия.
- Открыть commit. Полный commit показывает, что он затронул двенадцать файлов в рамках массовой правки -- тот самый тип изменений, который легко пропустить на ревью. Если ваша команда спорит о том, как такие изменения вообще должны попадать в историю, смотрите rebase vs merge.
- Скопируйте SHA и исправьте. Когда виновный commit найден, можно сделать revert, исправить новым commit или обойти его через cherry-pick -- наш гайд о том, как отменить commit в Git, разбирает, какой вариант подходит для какой ситуации.
Итоговое время: пара минут, ни одного заученного SHA, никакой археологии в скроллбэке терминала.
Когда blame или bisect всё ещё правильный инструмент
Таймлайн не заменяет остальной инструментарий, и притворяться обратным было бы нечестно:
- Используйте blame, когда вы уже смотрите на конкретную строку и хотите узнать её последнего автора прямо на месте. Для одной строки, которую недавно не переформатировали, blame по-прежнему кратчайший путь.
- Используйте bisect, когда симптом -- «сборка сломалась» или «тест падает», и вы не знаете, какой файл виноват. Bisect ищет по истории всего репозитория; таймлайн исследует один файл. Они отвечают на разные вопросы.
- Используйте таймлайн, когда вопрос касается эволюции одного файла: когда это изменилось, кто это изменил и как это выглядело раньше.
Посмотрите, как эволюционирует ваш собственный код
Таймлайн файла входит в тариф Free GitSquid -- без аккаунта, без телеметрии, нативно на macOS, Windows и Linux. Откройте репозиторий, кликните правой кнопкой по файлу, о котором всегда было любопытно узнать, и нажмите play.
Скачайте GitSquid Free -- и пусть в вашей следующей охоте за регрессией будет таймлайн вместо списка SHA.