Spec-Zone.ru › Git

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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API