FreeSource: RuslanHihin/gitusermanual/Chapter2

Глава 2. Изучение Git истории

Git – это один из наилучших инструментов для хранения истории множества файлов.

Это объясняется тем, что он хранит сжатые снапшоты содержащие файла иерархии, вместе с «коммитами», которые отслеживают связи между ними.

Git является чрезвычайно гибким и быстрым инструментом для изучения истории проекта.

Начнём с одного специализированного инструмента, которое обычно используется для поиска программной ошибки («бага») в проекте.

Как используя bisect найти регрессию

Предположим, версия 2.6.18 вашего проекта была рабочей, но в версия «мастер» возникла ошибка.

Иногда лучшим способом найти причину такой регрессии, состоит в том, чтобы выполнить поиск с отдельного коммита в истории проекта который является причиной проблемы. Команда git-bisect(1) может помочь вам сделать это:

$ git bisect start

$ git bisect good v2.6.18

$ git bisect bad master

Bisecting: 3537 revisions left to test after this

[65934a9a028b88e83e2b0f8b36618fe503349f8e] BLOCK: Make USB storage depend on SCSI rather than selecting it [try #6]

Если вы запускаете сейчас “git branch” вы увидите, что git временно переместился в новую ветку с названием “bisect”. Эта ветка указывает на коммит ( с именем снапшота 65934…), который доступен из «мастер», но из v2.6.18. Откомпилируйте и протестировать его и посмотрите есть-ли в нём проблема. Допустим, что есть. Тогда:

$ git bisect bad

Bisecting: 1769 revisions left to test after this

[7eff82c8b1511017ae605f0c99ac275a7e21b867] i2c-core: Drop useless bitmaskings

Извлекаем для проверки более старую версию. Продолжая аналогично, проверяем на каждом этапе версию и говорим git`у хорошая это версия, или плохая. Можно заметить что каждый раз число подозрительных версий сокращается примерно вдвое. После примерно 13 тестов (в данном случае), получаем идентификатор «виновного» коммита. Затем можно проанализировать коммит с помощью git-show(1), найти кто записал этот коммит, и направить ему по e-mail баг-рапорт об ошибке с идентификатором этого коммита. Наконец, запустив :

$ git bisect reset

возвращаемся к ветке удаляя перед этим временную ветвь «bisect».

Заметим, что версия, которую извлекает вам для проверки git-bisect является лишь предложением, и Вы можете сразу пробовать другую версию, если считаете что это более правильно. Например, время от времени вы можете «приземлиться» в коммит который не связан c предыдущей последовательностью, запустив

$ git bisect visualize

если запустить gitk, то ярлык коммита в этом случае будет отмечен маркером “bisect”.

Выбрав безопасной просмотр ближайшего коммита, замечаем его идентификатор и проверив его уходим из него командой (Chose a safe-looking commit nearby, note its commit id, and check it out with):

$ git reset --hard fb47ddb2db...

затем проверив его, запускаем соответственно «bisect good» или «bisect bad» и продолжаем.

Именование коммитов

Мы уже видели несколько способов именования коммитов:

* Шестнадцатеричное 40-значное имя объекта (снапшот).

* Имя ветки: относится к коммиту на вершине данной ветки

* Имя тэга: ссылка на коммит на который учитывает тэг (мы видели ветки и тэги на которые ссылаются в специальных случаях).

* HEAD: ссылка на вершину текущей ветки

Есть много других имён, см .в странице man git-rev-parse(1) раздел “SPECIFYING REVISIONS” с полным перечнем способов задания имени версий.

Некоторые примеры:

$ git show fb47ddb2 # первые несколько символов имени объекта

# Как правило, достаточно указать его несколько которые делают его уникальным.

$ git show HEAD^ # родители коммита HEAD

$ git show HEAD^^ # «дедушки» коммита HEAD

$ git show HEAD4 # «пра-прадедушки»

Напомним, что если произошло слияние, то коммит может иметь больше чем одного родителя; по умолчанию, ^ и ~ следует за первым родителем, перечисленным в коммите, но вы также можете сделать другой выбор :

$ git show HEAD^1 # показать первого родителя HEAD

$ git show HEAD^2 # показать второго родителя HEAD

В дополнение к HEAD, существует ряд других специальных имён для коммитов:

Слияния (будет обсуждаться позже) это такие операции подобные git-reset которые изменяют текущий проверяемый коммит и как правило устанавливают имя ORIG_HEAD на значение HEAD перед выполнением операции.

Операция git-fetch всегда сохраняет вершину последней извлекаемой ветки в имени FETCH_HEAD. Например, если вы запускаете git fetch без задания локальной ветки в качестве цели операции

$ git fetch git://example.com/proj.git theirbranch

взятые коммиты всё-равно будет доступны через FETCH_HEAD.

Когда мы обсуждаем слияния мы также видим особых имя MERGE_HEAD, которое относится к другой ветви которую мы объединяем с текущей веткой.

Команда git-rev-parse(1) — это команда низкого уровня, которая иногда полезна для перевода некоторых имён коммита в его имя объекта (имя снапшота) :

$ git rev-parse origin

e05db0fd4f31dde7005f075a84f96b360d05984b

Создание тегов

Мы можем создать тег к ссылке на особый коммит. После запуска

$ git tag stable-1 1b2e1d63ff

вы можете использовать имя stable-1 как ссылку на коммит 1b2e1d63ff.

Это создаст «легковесный» тег. Если вы хотели бы также включать комментарии в тег, и, возможно, подписать его криптографическими, то вы должны создайте вместо тэга объект, подробнее см.. man страницу git-tag(1).

Просмотр версий

Команда git-log(1) может показать лист коммитов.

Собственно она показывает все коммиты доступные из родительского коммита: но она так-же может обработать специфичные запросы :

$ git log v2.5.. # коммиты от v2.5 (только доступные)

$ git log test..master # доступные коммиты от master но не из test

$ git log master..test # ...доступные от test но не из master (две точки)

$ git log master...test # ...или от test или от master (три точки)

# но не из обоих
$ git log --since="2 weeks ago" # коммиты за последние две недели

$ git log Makefile # коммиты в которых менялся Makefile

$ git log fs/ # ... в которых изменился какой-нибудь файл в fs/

$ git log -S'foo()' # коммиты, которые добавили или удалили любые файлы

# имеющую строку 'foo()'

И, конечно, вы можете комбинировать все это; следующая команда находит коммиты из v2.5 в которых изменяются Makefile и изменения в любых файлов в fs/:

$ git log v2.5.. Makefile fs/

Также вы можете использовать git log, для просмотра патчей:

$ git log -p

См. описание опции "—pretty" в man странице git-log(1) страницу и о других опциях.

Заметим, что git log начинается с самых последних коммитов, и от родителей далее, однако, поскольку git история может содержать несколько независимых линий разработки, порядок в некоторой степени будет произвольным.

Формирование сравнений (diff)

Вы можете создавать сравнения (diff) между любыми двумя версиями используя git-diff(1):

$ git diff master..test

Эта команда создаст текст различий (diff) между двумя ветками test и master.

Если вы хотите найти различия от их общего предка до test, вы можете использовать три точки вместо двух:

$ git diff master...test

Иногда вы хотите вместо различий создать набор патчей, для этого можно использовать git format-patch(1):

$ git format-patch master..test

будет сгенерирован файл патча для совершения каждого коммита доступного из test, до master.

Просмотр старых версии файлов

Вы всегда можете просмотреть старую версию файла, перейдя к коммиту с его первой версией..

Но иногда бывает удобнее иметь возможность просмотреть старую версию одного файла, не проверив ничего другого; это делает команда:

$ git show v2.5:fs/locks.c

До двоеточия может быть любое имя коммита, а после него может быть любой путь к файлу из репозитория git.

Примеры

Подсчёт числа коммитов в ветке :

Предположим, вы хотите узнать, сколько коммитов вы создали на “mybranch”, после отделения от “origin”:

$ git log --pretty=oneline origin..mybranch | wc -l

Альтернативно, можно часто видеть вид всего сделанного командой низкого уровня git-rev-list(1), которая перечисляет SHA1 снапшоты всех всех имеющихся коммитов ветки:

$ git rev-list origin..mybranch | wc -l

Проверка наличия истории между двумя точками ветки

Предположим, вы хотите проверить, обе ветви указывают на тот же момент в истории.

$ git diff origin..master

вам скажет что содержание проекта одно и то-же в обоих ветках; теоретически, однако возможно, что содержание этого проекта могло быть получено двумя различными исторических маршрутами. Вы можете сравнить имена объектов (снапшоты) :

$ git rev-list origin

e05db0fd4f31dde7005f075a84f96b360d05984b

$ git rev-list master

e05db0fd4f31dde7005f075a84f96b360d05984b

Или вы вспомните об операторе ... (три точки), который выбирает все коммиты, доступные от каждой из ссылок но не доступные от обеих:

$ git log origin...master

Команда вернёт no commits (нет коммитов), когда две ветви эквивалентны

Поиск первой помеченной версии с решением определённой проблемы

Предположим, вы знаете, что коммит e05db0fd фиксирует определённые проблемы.

Вы хотите найти ближайшее помеченный тегом релиз, который содержит это исправление.

Конечно, могут существовать более одного варианта ответа на вопрос, если история ветки после коммита 05db0fd имеет несколько «ранних» тегов релизов.

Можно визуально просмотреть коммиты после e05db0fd:

$ gitk e05db0fd..

Вы также можете использовать git-name-rev(1) которая даст коммит с именем любого тега найденного среди потомков коммита :

$ git name-rev --tags e05db0fd

e05db0fd tags/v1.5.0-rc1^023 e05db0fd tags/v1.5.0-rc1 ^ 0 ~ 23

Команда git-describe(1) делает противоположное, выдавая имя версии используя тег, на которым базируется коммит.

$ git describe e05db0fd

v1.5.0-rc0–260-ge05db0f

Иногда вам может помочь догадка, как может называться тег, которые мог возникнуть после решения проблемы.

Если вы просто хотите проверить содержит-ли помеченная тегом версия данный коммит, вы могли бы использовать git-merge-base(1):

$ git merge-base e05db0fd v1.5.0-rc1

e05db0fd4f31dde7005f075a84f96b360d05984b

Команда merge-base находит общего предка у заданных коммитов, и всегда возвращает или тот или другой коммит, когда один является потомком другого; вышеприведённые результаты свидетельствуют о том, что на самом деле e05db0fd предок v1.5.0-rc1 .

Альтернативно отметим :,

$ git log v1.5.0-rc1..e05db0fd

будет производить пустой вывод, только если в v1.5.0-rc1 включает e05db0fd, потому что он совершает только результаты, которые не доступны из v1.5.0-rc1.

Как ещё один вариант, команда git-show-branch(1) перечисляет коммиты доступные её аргументам которые показываются левой стороне дисплея, указывая на каким аргументам, что доступно.

Таким образом, вы можете запустить-то вроде

$ git show-branch e05db0fd v1.5.0-rc0 v1.5.0-rc1 v1.5.0-rc2

! [e05db0fd] Fix warnings in sha1_file.c – use C99 printf format if

available

! [v1.5.0-rc0] GIT v1.5.0 preview

! [v1.5.0-rc1] GIT v1.5.0-rc1

! [v1.5.0-rc2] GIT v1.5.0-rc2

...

Затем поиски строки, которая выглядит так

+ ++ [e05db0fd] Fix warnings in sha1_file.c – use C99 printf format if available

Которая показывает, что e05db0fd доступен из себя, из v1.5.0-rc1, и из v1.5.0-rc2, но не из v1.5.0-rc0.

Просмотр уникальных коммитов в данной ветке

Предположим, вы хотели бы видеть все доступные коммиты от имени вершины ветки “master”, но недоступные от любой другой вершины ветки вашего репозитория.

Мы можем перечислить все вершины в этом репозитории командой git-show-ref(1):

$ git show-ref --heads]

bf62196b5e363d73353a9dcf094c59595f3153b7 refs/heads/core-tutorial

db768d5504c1bb46f63ee9d6e1772bd047e05bf9 refs/heads/maint

a07157ac624b2524a059a3414e99f6f44bebc1e7 refs/heads/master

24dbc180ea14dc1aebe09f14c8ecf32010690627 refs/heads/tutorial-2

1e87486ae06626c2f31eaa63d26fc0fd646c8af2 refs/heads/tutorial-fixes

Мы можем получить только имя вершины ветки, удалить “master” из вывода, с помощью стандартных утилит cut и grep:

$ git show-ref --heads | cut -d' ' -f2 | grep -v '^refs/heads/master'

refs/heads/core-tutorial

refs/heads/maint

refs/heads/tutorial-2

refs/heads/tutorial-fixes

И так-же мы можем создать запрос чтобы увидеть все доступные коммиты от вершины «master», но не от этих других вершин:

$ gitk master --not $( git show-ref --heads | cut -d' ' -f2 | grep -v '^refs/heads/master' )

Очевидно, возможны бесконечные вариации на эту тему; например, чтобы увидеть все доступные коммиты некоторые от некоторой вершины но не от но без включения тэгов репозитория :

$ gitk $( git show-ref --heads ) --not $( git show-ref --tags )

(См. git-rev-parse(1) для объяснения синтаксиса выбора коммитов таких, как —not).

Создание журнала изменений (changelog) и тарбола релиза

Команда git-archive(1) может создать tar или zip архив любой версии проекта, например:

$ git archive --format=tar --prefix=project/ HEAD | gzip >latest.tar.gz

Будет использоваться вершина HEAD для создания tar архива, в котором каждому имени файла будет предшествовать путь «project/».

Если Вы создаёте релиз новой версии вашего в вашем проекте, вы можете одновременно создать файл изменений (changelog) который включены в анонс релиза.

Линуса Торвальдса, например, делает новых версиях ядра путём пометки их тегом, а затем выполняя:

$ release-script 2.6.12 2.6.13-rc6 2.6.13-rc7

где-релиз скрипта скрипт, выглядит наподобии :

#!/bin/sh

stable="$1"

last="$2"

new="$3"

echo "# git tag v$new"

echo «git archive --prefix=linux-$new/ v$new | gzip -9 > ../linux-$new.tar.gz»

echo «git diff v$stable v$new | gzip -9 > ../patch-$new.gz»

echo «git log --no-merges v$new ^v$last > ../ChangeLog-$new"

echo «git shortlog --no-merges v$new ^v$last > ../ShortLog"

echo «git diff --stat --summary -M v$last v$new > ../diffstat-$new"

а потом он просто вставляет выход команд после проверки того, что отмечено OK.

Поиск ссылок коммитов на файл с заданным содержанием

Кто-то дал вам в руки копию файла, и спросил в каком коммите файл был изменён уверенный, что вы содержите содержимое файла до и после коммита Вы можете найти это командой :

$ git log --raw --abbrev=40 --pretty=oneline | grep -B 1 `git hash-object filename`

Понять как это работает, как далее требует дополнительного изучения. Страницы man git-log(1), git-diff-tree(1), и git-hash-object(1) могут помочь вам разобраться подробнее.

Назад Содержание Далее