7.10 Инструменты Git – Обнаружение ошибок с помощью Git
Git предоставляет несколько инструментов, которые помогут вам найти и устранить проблемы в ваших проектах. Так как Git рассчитан на работу с проектом почти любого типа, эти инструменты предоставляют довольно обобщённые возможности, но часто они могут помочь вам отловить ошибку или её виновника.
Аннотация файла
Если вы обнаружили ошибку в вашем коде и хотите знать, когда она была добавлена и почему, то в большинстве случаев лучшим инструментом для этого будет аннотация файла. С ее помощью для любого файла можно увидеть, каким коммитом последний раз изменяли каждую из строк. Поэтому, если вы видите, что какой-то метод в вашем коде работает неправильно, вы можете с помощью команды git blame
снабдить файл аннотацией и, таким образом, увидеть, когда каждая строка метода была изменена последний раз и кем.
В следующем примере используется git blame
, чтобы определить, какой коммит и коммиттер отвечал за строки в Makefile ядра Linux верхнего уровня, здесь также используется параметр -L
для ограничения вывода аннотации строками с 69 по 82 из этого файла:
$ git blame -L 69,82 Makefile
b8b0618cf6fab (Cheng Renquan 2009-05-26 16:03:07 +0800 69) ifeq ("$(origin V)", "command line")
b8b0618cf6fab (Cheng Renquan 2009-05-26 16:03:07 +0800 70) KBUILD_VERBOSE = $(V)
^1da177e4c3f4 (Linus Torvalds 2005-04-16 15:20:36 -0700 71) endif
^1da177e4c3f4 (Linus Torvalds 2005-04-16 15:20:36 -0700 72) ifndef KBUILD_VERBOSE
^1da177e4c3f4 (Linus Torvalds 2005-04-16 15:20:36 -0700 73) KBUILD_VERBOSE = 0
^1da177e4c3f4 (Linus Torvalds 2005-04-16 15:20:36 -0700 74) endif
^1da177e4c3f4 (Linus Torvalds 2005-04-16 15:20:36 -0700 75)
066b7ed955808 (Michal Marek 2014-07-04 14:29:30 +0200 76) ifeq ($(KBUILD_VERBOSE),1)
066b7ed955808 (Michal Marek 2014-07-04 14:29:30 +0200 77) quiet =
066b7ed955808 (Michal Marek 2014-07-04 14:29:30 +0200 78) Q =
066b7ed955808 (Michal Marek 2014-07-04 14:29:30 +0200 79) else
066b7ed955808 (Michal Marek 2014-07-04 14:29:30 +0200 80) quiet=quiet_
066b7ed955808 (Michal Marek 2014-07-04 14:29:30 +0200 81) Q = @
066b7ed955808 (Michal Marek 2014-07-04 14:29:30 +0200 82) endif
Обратите внимание, что первое поле – это неполная сумма SHA-1 последнего коммита, который изменял соответствующую строку. Следующими двумя полями являются значения, извлечённые из этого коммита (имя автора и время создания коммита); таким образом, вы можете легко увидеть, кто изменял строку и когда. После этого следуют номер строки и содержимое файла. Обратите внимание на строки со значением ^4832fe2
в поле коммита, так обозначаются те строки, которые были в первом коммите этого файла. Этот коммит был сделан, когда данный файл был впервые добавлен в проект, и с тех пор эти строки не были изменены. Немного сбивает с толку то, что вы уже видели, по крайней мере, три различных варианта использования символа ^
в Git для изменения SHA-1 коммита, в данном случае этот символ имеет такое значение.
Другая отличная вещь в Git – это то, что он явно не отслеживает переименования файлов (пользователю не нужно явно указывать какой файл в какой был переименован). Он сохраняет снимки и уже после выполнения самого переименования неявно попытается выяснить, что было переименовано. Одна из интересных возможностей, вытекающих из этого – это то, что вы также можете попросить Git выявить перемещения кода всех других видов. Если передать опцию -C
команде git blame
, Git проанализирует аннотируемый файл и попытается выяснить, откуда изначально появились фрагменты кода, если они, конечно же, были откуда-то скопированы. Например, предположим при реорганизации кода в файле GITServerHandler.m вы разнесли его по нескольким файлам, один из которых – GITPackUpload.m. Вызывая git blame
с опцией -C
для файла GITPackUpload.m, вы можете увидеть, откуда изначально появились разные фрагменты этого файла.
$ git blame -C -L 141,153 GITPackUpload.m
f344f58d GITServerHandler.m (Scott 2009-01-04 141)
f344f58d GITServerHandler.m (Scott 2009-01-04 142) - (void) gatherObjectShasFromC
f344f58d GITServerHandler.m (Scott 2009-01-04 143) {
70befddd GITServerHandler.m (Scott 2009-03-22 144) //NSLog(@"GATHER COMMI
ad11ac80 GITPackUpload.m (Scott 2009-03-24 145)
ad11ac80 GITPackUpload.m (Scott 2009-03-24 146) NSString *parentSha;
ad11ac80 GITPackUpload.m (Scott 2009-03-24 147) GITCommit *commit = [g
ad11ac80 GITPackUpload.m (Scott 2009-03-24 148)
ad11ac80 GITPackUpload.m (Scott 2009-03-24 149) //NSLog(@"GATHER COMMI
ad11ac80 GITPackUpload.m (Scott 2009-03-24 150)
56ef2caf GITServerHandler.m (Scott 2009-01-05 151) if(commit) {
56ef2caf GITServerHandler.m (Scott 2009-01-05 152) [refDict setOb
56ef2caf GITServerHandler.m (Scott 2009-01-05 153)
Это, действительно, полезно. Обычно вы получаете в качестве изначального коммит, в котором вы скопировали код, так как это первый коммит, в котором вы обращаетесь к этим строкам в этом файле. Но в данном случае Git сообщает вам первый коммит, в котором эти строки были написаны, даже если это было сделано в другом файле.
Бинарный поиск
Аннотирование файла помогает, если вы знаете, где находится проблема и можете начать исследование с этого места. Если вы не знаете, что сломано, а с тех пор как код работал, были сделаны десятки или сотни коммитов, вы вероятно воспользуетесь командой git bisect
. Эта команда выполняет бинарный поиск по истории коммитов, чтобы помочь вам как можно быстрее определить коммит, который создал проблему.
Допустим, вы только что развернули некоторую версию вашего кода в боевом окружении и теперь получаете отчёты о какой-то ошибке, которая не возникала в вашем окружении разработки, и вы не можете представить, почему код ведет себя так. Вы возвращаетесь к вашему коду и выясняете, что можете воспроизвести проблему, но всё ещё не понимаете, что работает неправильно. Чтобы выяснить это, вы можете воспользоваться бинарным поиском,. Во-первых, выполните команду git bisect start
для запуска процесса поиска, а затем используйте git bisect bad, чтобы сообщить Git, что текущий коммит сломан. Затем, используя git bisect good [good_commit]
, вы должны указать, когда было последнее известное рабочее состояние:
$ git bisect start
$ git bisect bad
$ git bisect good v1.0
Bisecting: 6 revisions left to test after this
[ecb6e1bc347ccecc5f9350d878ce677feb13d3b2] Error handling on repo
Git выяснил, что произошло около 12 коммитов между коммитом, который вы отметили как последний рабочий коммит (v1.0), и текущим нерабочим коммитом, и выгрузил вам один из середины. В этот момент вы можете запустить ваши тесты, чтобы проверить присутствует ли проблема в этом коммите. Если это так, значит она была внесена до выгруженного промежуточного коммита, если нет, значит проблема была внесена после этого коммита. Пусть в данном коммите проблема не проявляется, вы сообщаете об этом Git с помощью git bisect good
и продолжаете ваше путешествие:
$ git bisect good
Bisecting: 3 revisions left to test after this
[b047b02ea83310a70fd603dc8cd7a6cd13d15c04] Secure this thing
Теперь вы оказались на другом коммите, расположенном посредине между только что протестированным и плохим коммитами. Вы снова выполняете ваши тесты, обнаруживаете, что текущий коммит сломан, и сообщаете об этом Git с помощью команды git bisect bad
:
$ git bisect bad
Bisecting: 1 revisions left to test after this
[f71ce38690acf49c1f3c9bea38e09d82a5ce6014] Drop exceptions table
Следующий коммит хороший, и теперь Git имеет всю необходимую информацию для определения того, где была внесена ошибка. Он сообщает вам SHA-1 первого плохого коммита и отображает некоторую информацию о коммите и файлах, которые были изменены в этом коммите, так, чтобы вы смогли разобраться, что же случилось, что могло привнести эту ошибку:
$ git bisect good
b047b02ea83310a70fd603dc8cd7a6cd13d15c04 is first bad commit
commit b047b02ea83310a70fd603dc8cd7a6cd13d15c04
Author: PJ Hyett <pjhyett@example.com>
Date: Tue Jan 27 14:48:32 2009 -0800
Secure this thing
:040000 040000 40ee3e7821b895e52c1695092db9bdc4c61d1730
f24d3c6ebcfc639b1a3814550e62d60b8e68a8e4 M config
Когда вы закончили бинарный поиск, нужно выполнить git bisect reset
, чтобы вернуть HEAD
туда, где он был до начала поиска, иначе вы останетесь в, довольно, причудливом состоянии:
$ git bisect reset
Это мощный инструмент, который помогает вам за считанные минуты проверить сотни коммитов на возможность внесения ошибки. В действительности, если у вас есть скрипт, который будет возвращать 0, если проект находится в рабочем состоянии, и любое другое число в противном случае, то вы можете полностью автоматизировать git bisect
. Сперва, вы снова сообщаете границы бинарного поиска, указывая известные плохой и хороший коммиты. Вы можете сделать это, передав их команде bisect start
, – первым аргументом известный плохой коммит, а вторым известный хороший коммит:
$ git bisect start HEAD v1.0
$ git bisect run test-error.sh
Это приведёт к автоматическом выполнению test-error.sh
на каждый выгруженный коммит до тех пор, пока Git не найдёт первый сломанный коммит. Вы также можете использовать что-то вроде make
или make tests
, или что-то ещё, что у вас есть для запуска автоматизированных тестов.