gittutorial-2
Имя
gittutorial-2 - Введение в Git: часть вторая
Синопсис
git *
Описание
Перед чтением этого руководства необходимо изучить gittutorial[7].
Цель данного руководства – познакомить читателя с двумя фундаментальными компонентами архитектуры Git: базой данных объектов и файлом индекса, и предоставить все необходимое для понимания остальной документации Git.
База данных объектов Git
Давайте начнём новый проект и создадим небольшую историю:
$ mkdir test-project $ cd test-project $ git init Initialized empty Git repository in .git/ $ echo 'hello world' > file.txt $ git add . $ git commit -a -m "initial commit" [master (root-commit) 54196cc] initial commit 1 file changed, 1 insertion(+) create mode 100644 file.txt $ echo 'hello world!' >file.txt $ git commit -a -m "add emphasis" [master c4d59f3] add emphasis 1 file changed, 1 insertion(+), 1 deletion(-)
Какие 7 символов шестнадцатеричного кода Git вернула в ответ на коммит?
В первой части руководства мы видели, что коммиты имеют такие имена. Оказывается, каждый объект в истории Git хранится под именем из 40 шестнадцатеричных символов. Это имя является хешем SHA-1 содержимого объекта; среди прочего, это гарантирует, что Git никогда не будет хранить одинаковые данные дважды (так как идентичным данным присваивается одно и то же имя SHA-1), и что содержимое объекта Git никогда не изменится (так как это также изменит имя объекта). 7-символьные шестнадцатеричные строки здесь представляют собой просто сокращения таких 40-символьных строк. Сокращения можно использовать везде, где можно использовать 40-символьные строки, при условии, что они однозначны.
Ожидается, что содержимое объекта коммита, созданного вами в соответствии с примером выше, сгенерирует другой хеш SHA-1, чем тот, что показан выше, поскольку объект коммита записывает время его создания и имя пользователя, который выполнил коммит.
Мы можем узнать о конкретном объекте с помощью команды cat-file. Не копируйте 40 шестнадцатеричных символов из этого примера, а используйте свои собственные. Обратите внимание, что вы можете сократить их до нескольких символов, чтобы не вводить все 40 шестнадцатеричных цифр:
$ git cat-file -t 54196cc2 commit $ git cat-file commit 54196cc2 tree 92b8b694ffb1675e5975148e1121810081dbdffe author J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500 committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500 initial commit
Дерево может ссылаться на один или несколько объектов «blob», каждый из которых соответствует файлу. Кроме того, дерево может ссылаться и на другие объекты дерева, создавая таким образом иерархию каталогов. Вы можете просмотреть содержимое любого дерева, используя ls-tree (помните, что достаточно длинная начальная часть SHA-1 также будет работать):
$ git ls-tree 92b8b694 100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad file.txt
Таким образом, мы видим, что это дерево содержит один файл. Хеш SHA-1 ссылается на данные этого файла:
$ git cat-file -t 3b18e512 blob
«Blob» — это просто данные файла, которые мы также можем просмотреть с помощью cat-file:
$ git cat-file blob 3b18e512 hello world
Обратите внимание, что это старые данные файла; таким образом, объект, который Git назвал в ответ на первоначальное дерево, был деревом с моментальным снимком состояния каталога, зафиксированного первым коммитом.
Все эти объекты хранятся под своими именами SHA-1 внутри директории Git:
$ find .git/objects/ .git/objects/ .git/objects/pack .git/objects/info .git/objects/3b .git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad .git/objects/92 .git/objects/92/b8b694ffb1675e5975148e1121810081dbdffe .git/objects/54 .git/objects/54/196cc2703dc165cbd373a65a4dcf22d50ae7f7 .git/objects/a0 .git/objects/a0/423896973644771497bdc03eb99d5281615b51 .git/objects/d0 .git/objects/d0/492b368b66bdabf2ac1fd8c92b39d3db916e59 .git/objects/c4 .git/objects/c4/d59f390b9cfd4318117afde11d601c1085f241
и содержимое этих файлов – это просто сжатые данные плюс заголовок, определяющий их длину и тип. Тип может быть blob, tree, commit или tag.
Самый простой коммит для поиска – это коммит HEAD, который можно найти в .git/HEAD:
$ cat .git/HEAD ref: refs/heads/master
Как видно, это показывает, на какой ветке мы находимся в данный момент, и делает это, называя файл в директории .git, который сам содержит имя SHA-1, ссылающееся на объект коммита, который мы можем просмотреть с помощью cat-file:
$ cat .git/refs/heads/master c4d59f390b9cfd4318117afde11d601c1085f241 $ git cat-file -t c4d59f39 commit $ git cat-file commit c4d59f39 tree d0492b368b66bdabf2ac1fd8c92b39d3db916e59 parent 54196cc2703dc165cbd373a65a4dcf22d50ae7f7 author J. Bruce Fields <bfields@puzzle.fieldses.org> 1143418702 -0500 committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143418702 -0500 add emphasis
Объект «tree» здесь относится к новому состоянию дерева:
$ git ls-tree d0492b36 100644 blob a0423896973644771497bdc03eb99d5281615b51 file.txt $ git cat-file blob a0423896 hello world!
а объект «parent» относится к предыдущему коммиту:
$ git cat-file commit 54196cc2 tree 92b8b694ffb1675e5975148e1121810081dbdffe author J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500 committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500 initial commit
Объект «tree» — это то дерево, которое мы изучили вначале, и этот коммит необычен тем, что у него нет родительского коммита.
У большинства коммитов только один родитель, но также часто встречаются коммиты с несколькими родителями. В этом случае коммит представляет слияние, и ссылки на родителей указывают на вершины сливаемых веток.
Помимо blob, tree и commit, существует ещё один тип объекта – «tag», о котором мы здесь не будем говорить; для получения подробной информации обратитесь к git-tag[1].
Теперь мы знаем, как Git использует базу данных объектов для представления истории проекта:
-
Объекты «commit» ссылаются на объекты «tree», представляющие моментальный снимок дерева каталогов в определённый момент истории, и ссылаются на родительские коммиты, чтобы показать, как они связаны в истории проекта.
-
Объекты «tree» представляют состояние отдельного каталога, связывая имена каталогов с объектами «blob», содержащими данные файлов, и объектами «tree», содержащими информацию о подкаталогах.
-
Объекты «blob» содержат данные файла без какой-либо дополнительной структуры.
-
Ссылки на объекты коммитов в начале каждой ветки хранятся в файлах в .git/refs/heads/.
-
Имя текущей ветки хранится в .git/HEAD.
К слову, многие команды принимают дерево в качестве аргумента. Но, как мы видим выше, к дереву можно обратиться разными способами: по имени SHA-1 этого дерева, по имени коммита, который ссылается на дерево, по имени ветки, головка которой ссылается на это дерево и т.д., и большинство таких команд могут принять любое из этих имён.
В синопсисах команд иногда используется слово «tree-ish» для обозначения такого аргумента.
Файл индекса
Основной инструмент, который мы использовали для создания коммитов, — это git-commit
-a, который создаёт коммит, включающий все изменения, внесённые в рабочее дерево. Но что, если вы хотите сделать коммит только для определённых файлов? Или только определённых изменений в определённых файлах?
Если мы посмотрим на то, как коммиты создаются под капотом, мы увидим, что существуют более гибкие способы создания коммитов.
Продолжая работу с нашим тестовым проектом, давайте снова изменим файл file.txt:
$ echo "hello world, again" >>file.txt
но в этот раз вместо непосредственного создания коммита сделаем промежуточный шаг и запросим разницу, чтобы отслеживать происходящее:
$ git diff --- a/file.txt +++ b/file.txt @@ -1 +1,2 @@ hello world! +hello world, again $ git add file.txt $ git diff
Последняя разница пуста, но новых коммитов не создано, и головка всё ещё не содержит новой строки:
$ git diff HEAD diff --git a/file.txt b/file.txt index a042389..513feba 100644 --- a/file.txt +++ b/file.txt @@ -1 +1,2 @@ hello world! +hello world, again
Значит, git diff сравнивает не с головкой. Фактически, он сравнивает с файлом индекса, который хранится в .git/index в двоичном формате, но чьё содержимое мы можем просмотреть с помощью ls-files:
$ git ls-files --stage 100644 513feba2e53ebbd2532419ded848ba19de88ba00 0 file.txt $ git cat-file -t 513feba2 blob $ git cat-file blob 513feba2 hello world! hello world, again
Так что то, что сделала наша команда git add, заключалось в хранении нового blob и последующем размещении ссылки на него в файле индекса. Если мы снова изменим файл, мы увидим, что новые изменения отображаются в выводе команды git diff:
$ echo 'again?' >>file.txt $ git diff index 513feba..ba3da7b 100644 --- a/file.txt +++ b/file.txt @@ -1,2 +1,3 @@ hello world! hello world, again +again?
С подходящими аргументами, команда git diff также может показать нам разницу между рабочей директорией и последним коммитом, или между индексом и последним коммитом:
$ git diff HEAD diff --git a/file.txt b/file.txt index a042389..ba3da7b 100644 --- a/file.txt +++ b/file.txt @@ -1 +1,3 @@ hello world! +hello world, again +again? $ git diff --cached diff --git a/file.txt b/file.txt index a042389..513feba 100644 --- a/file.txt +++ b/file.txt @@ -1 +1,2 @@ hello world! +hello world, again
В любое время мы можем создать новый коммит с помощью git commit (без опции "-a"), и убедиться, что состояние, зафиксированное в коммите, включает только изменения, сохранённые в файле индекса, а не дополнительное изменение, которое всё ещё находится только в рабочем дереве:
$ git commit -m "repeat" $ git diff HEAD diff --git a/file.txt b/file.txt index 513feba..ba3da7b 100644 --- a/file.txt +++ b/file.txt @@ -1,2 +1,3 @@ hello world! hello world, again +again?
Итак, по умолчанию команда git commit использует индекс для создания коммита, а не рабочее дерево; опция "-a" при создании коммита говорит ей обновить индекс всеми изменениями в рабочем дереве.
Наконец, стоит посмотреть, какое действие оказывает команда git add на файл индекса:
$ echo "goodbye, world" >closing.txt $ git add closing.txt
Результат команды git add заключался в добавлении одной записи в файл индекса:
$ git ls-files --stage 100644 8b9743b20d4b15be3955fc8d5cd2b09cd2336138 0 closing.txt 100644 513feba2e53ebbd2532419ded848ba19de88ba00 0 file.txt
И, как вы можете увидеть с помощью cat-file, эта новая запись ссылается на текущее содержимое файла:
$ git cat-file blob 8b9743b2 goodbye, world
Команда «status» – полезный способ получить краткое резюме ситуации:
$ git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: closing.txt
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: file.txt Поскольку текущее состояние файла closing.txt кэшировано в файле индекса, оно отображается как «Изменения для коммита». Поскольку в файле file.txt есть изменения в рабочей директории, которые не отражены в индексе, он помечен как «изменённый, но не обновлённый». В этот момент выполнение команды «git commit» создаст коммит, который добавит closing.txt (с его новым содержимым), но не изменит file.txt.
Также обратите внимание, что команда «git status» в пустом репозитории показывает изменения в файле file.txt, но не добавление closing.txt, потому что версия closing.txt в файле индекса идентична версии в рабочей директории.
Помимо того, что он служит областью подготовки новых коммитов, файл индекса также заполняется данными из базы данных объектов при обновлении ветки и используется для хранения деревьев, участвующих в операции слияния. Смотрите gitcore-tutorial[7] и соответствующие страницы руководства для получения подробной информации.
Что дальше?
На данном этапе вы должны знать всё необходимое для чтения страниц руководства по любым командам git; хорошим началом станут команды, упомянутые в giteveryday[7]. Любые неизвестные термины вы сможете найти в gitglossary[7].
Руководство пользователя Git предоставляет более подробное введение в Git.
gitcvs-migration[7] объясняет, как импортировать репозиторий CVS в Git и как использовать Git в стиле CVS.
Для ознакомления с интересными примерами использования Git, смотрите howtos.
Для разработчиков Git, gitcore-tutorial[7] подробно описывает низкоуровневые механизмы Git, участвующие, например, в создании нового коммита.
См. также
gittutorial[7], gitcvs-migration[7], gitcore-tutorial[7], gitglossary[7], git-help[1], giteveryday[7], Руководство пользователя Git
gittutorial-2
© 2005–2026 Linus Torvalds and others
Licensed under the GNU General Public License version 2.
https://git-scm.com/docs/gittutorial-2