При работе с большой кодовой базой довольно естественно не помнить каждую подсистему или вариант реализации. Особенно если вы новичок в кодовой базе, или вы сосредоточились на определенной области кодовой базы.
Работая над рефакторингом или исправлением ошибок, я часто оказываюсь в областях, с которыми не слишком хорошо знаком.
Обычно это не так драматично, довольно часто я прекращаю текущую работу, чтобы решить, как двигаться дальше.
Как программисты, мы должны принимать так много микрорешений каждую минуту, что мы к этому уже привыкли.
В зависимости от кода и знаний предметной области мы часто оказываемся в ситуациях, когда нам требуется больше контекста для принятия обоснованного решения. Тесты обычно предоставляют довольно хороший контекст, но в зависимости от их детализации и наименования, они могут только сказать, «что» есть в реализации, но упустить «почему».
И если мы попытаемся решить, можем ли мы выполнить определенную проверку или переделать поведение, нам нужно понять, почему она была впервые введена. Обычно мы ищем коммит, в котором было внесено изменение, или тикетам, связанным с коммитом.
Большинство людей начнут с использования «git blame» (или соответствующей функциональности в своей среде IDE/редакторе). Но в большинстве нетривиальных проектов вы обычно заканчиваете коммитом рефакторинга, переименованием или тривиальным исправлением проекта, например, переключением на другую библиотеку assertion. При первом взгляде, мы видим только самые последние изменения, но не самые важные.
Нам нужно аккуратно удалить слой за слоем песок и грязь, которые были нанесены на реальные изменения, чтобы выявить их.
Движение назад во времени
Простой git blame
показывает нам только самые последние изменения.
Предполагая, что это простое перемещение внутри файла или исправление пробелов, мы можем игнорировать их, используя флаги, которые нам предлагает git blame
:
-w игнорировать изменения пробелов
-M игнорировать изменения, если строка была просто перемещена/скопирована в том же файле
-C игнорировать изменения, если строка была только что перемещена/скопирована из других файлов
Вышеуказанные флаги уже приносят пользу. Большинство из них доступны не только в командной строке, они обычно также доступны в различных редакторах (не стесняйтесь выяснять, как это делается в вашем редакторе, и поделитесь в Twitter).
Обычно это позволяет скрыть некоторые из наиболее тривиальных коммитов, но заставляет git
обнаруживать их. Чаще мы (как инженеры) можем легко оценить, релевантен ли коммит для нашего поиска, основываясь на сообщении коммита, авторе и методах кодирования.
К счастью, этого также можно легко добиться с помощью различных редакторов, двигаясь назад по коммитам. Просматривая историю на GitHub, вы можете использовать функцию «View blame prior to this change»:
Точно так же IDE на основе IntelliJ позволяют делать то же самое, используя «Annotate Previous Revision»:
Если вы используете Visual Studio Code, отличное расширение GitLense предлагает аналогичные команды:
Движение вперед
В зависимости от того, что мы ищем, мы можем не захотеть раскрывать коммит за коммитом, пока не найдем начальный коммит, который ввел изменение.
Допустим, мы работаем с клиентом, который видит конкретное сообщение об ошибке в более старой версии нашего продукта.
В main
это сообщение об ошибке больше не отображается, так как кто-то уже отредактировал код. Чтобы разобраться в этом, нам нужно выяснить, когда появилось это сообщение об ошибке. Один из способов пролистать всю историю в поисках сообщения об ошибке grail..err..the error - использовать git pickaxe. Pickaxe является частью журнала git и может использоваться следующим образом:
git log -S'errorMessage'
По умолчанию он будет выплевывать все коммиты, которые errorMessage
каким-либо образом вводили или удаляли строку.
Возможно, вы ищете шаблон сообщения об ошибке, в котором есть переменные, особенно с сообщениями об ошибках. К счастью, git позаботился об этом: вы можете использовать --pickaxe-regex
и использовать -S
для поиска подходящих коммитов.
Имейте в виду, что при использовании -S будут найдены только те коммиты, которые изменяют количество раз нахождения строки (добавление / удаление).
Если вы также ищете коммит, который изменил положение строки (например, рефакторинг, переместивший ключевое слово), вам лучше использовать ключ -G
:
git log -G"error.*"
Пропуск истории
Идете ли вы вперед или назад по истории, рефакторинг «большого взрыва» всегда будет на вашем пути. Будь это то, что ваша команда решила согласовать определенные окончания строк, большое разделение кодовой базы или переименование, чтобы привести кодовую базу в соответствие с Ubiquitous Language.
Таким образом, эти изменения обычно никогда не представляют интереса в контексте просмотра истории конкретной строки, и в Git есть некоторые полезные функции, которые позволяют нам пропустить их при поиске git blame
файла.
Самый простой подход - передать все идентификаторы фиксации, которые мы хотим игнорировать, --ignore-rev
как показано в следующем примере.
Учитывая эту историю для файла, состоящую из важных изменений, а также фиксации, которая только что реорганизовала файл:
❯ git log
commit 301b7eca0eb57737e160f5d2d16208f65c4156d6 (HEAD -> master)
Author: Benjamin Muskalla <bmuskalla@gradle.com>
Date: Tue Jan 12 11:38:40 2021 +0100
Reformat all source files
commit bd3fca50ee1659e740e2f6744d95e737418f1f40
Author: Benjamin Muskalla <bmuskalla@gradle.com>
Date: Tue Jan 12 11:38:12 2021 +0100
Important change
Как и ожидалось, обычный git blame показывает самые последние изменения в файле. Это коммит, переформатирующий файл, который для нас не важен. Мы хотим увидеть важные перемены.
❯ git blame sourcefile.py
301b7eca 1) import random
301b7eca 2)
301b7eca 3) print(random.randint(0,9))
301b7eca 4)
Таким образом, использование функции «игнорировать ревизии» позволяет нам явно игнорировать определенные коммиты, тем самым показывая нам интересующий нас коммит:
git blame --ignore-rev 301b7ec sourcefile.py
^bd3fca5 1) import random
301b7eca 2)
^bd3fca5 3) print(random.randint(0,9))
301b7eca 4)
На протяжении всего проекта этот подход, конечно, не масштабируется. Всегда будут коммиты, которые в таких случаях следует игнорировать. На основе того же подхода git blame
позволяет указать файл, содержащий список игнорируемых коммитов.
Это удобно, поскольку вы можете зарегистрировать его вместе со всеми другими своими файлами и также легко поделиться им в своей команде, чтобы каждый мог использовать.
В нашем примере давайте вызовем его .git-blame-ignored-revs
и укажем, какие коммиты следует игнорировать, используя их хэш коммитов. Вы также можете использовать #
для добавления комментариев к файлу.
# ignore pure formatting commits
301b7eca0eb57737e160f5d2d16208f65c4156d6
Теперь вы можете вручную использовать этот файл в качестве источника фильтров, используя --ignore-revs-file
.
❯ git blame -s --ignore-revs-file=.git-blame-ignored-revs sourcefile.py
Если вы хотите, чтобы это было поведение по умолчанию, вы можете либо создать псевдоним git для этого, либо даже настроить git, чтобы он всегда использовал этот файл. В этой конфигурации git blame всегда будет игнорировать коммиты в .git-blame-ignored-revs
:
❯ git config blame.ignoreRevsFile .git-blame-ignored-revs
Приятным побочным эффектом использования git config
является то, что другие инструменты и редакторы, которые полагаются на поведение инструментов командной строки, просто соответствуют этим настройкам.
Например, IntelliJ IDEA показывает «Important Change» при включении аннотаций:
Заключение
Разные проблемы требуют разных подходов. Ищете ли вы сообщение об ошибке, которое давно исчезло из ветки main
, или пытаетесь открепить слои для какого-то нового кода, git предлагает огромное количество инструментов для ваших нужд.
Иногда достаточно помнить, что существуют определенные инструменты или подходы, чтобы вы могли найти их, если решите, какой подход вы хотите подойти к поиску:
git blame
с его различными фильтрами, такими как-M
git log -S/-G
искать добавления / удаления для определенных ключевых слов или шаблоновgit blame --ignore-rev
скрыть известные, шумные коммиты
Надеюсь, мне удалось показать некоторые интересные детали, которые вы можете добавить в свой собственный набор инструментов, и был бы рад услышать о вашем подходе к археологии программного обеспечения.