Лечебный урок на Git Trees - PullRequest
4 голосов
/ 16 июля 2010

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

Пузыри, конечно, легко, потому что они гранулированные. Деревья, по крайней мере концептуально, кажутся мне гораздо более туманными. Есть ли какой-то способ объяснить - чем-то приближающимся к исправлению:

  1. Как Git обнаруживает, что нужно создать дерево?
  2. Что хранится под деревом в любой данный момент?
  3. Новое дерево "ревизия" создается каждый раз, когда капля под этим деревом модифицируется?

Могут быть и другие вопросы, которые я даже не знаю достаточно, чтобы задать их, поэтому не стесняйтесь разрабатывать любым способом, необходимым для облегчения согласованного понимания типа объекта и его контекста.

Очень ценится.

Ответы [ 3 ]

8 голосов
/ 16 июля 2010

Это может быть первое описание:

альтернативный текст http://eagain.net/articles/git-for-computer-scientists/git-storage.2.png
(от Git для учёных )

Но Git From the снизу вверх будет иметь самое подробное описание.

указатель
В отличие от других подобных инструментов, которые вы, возможно, использовали, Git не фиксирует изменения непосредственно из рабочего дерева в хранилище. Вместо этого изменения сначала регистрируются в так называемом индексе.
Думайте об этом как о способе «подтверждения» ваших изменений, один за другим, перед выполнением коммита (который записывает все ваши одобренные изменения сразу).
Некоторые считают полезным называть это вместо «промежуточной области» вместо индекса.

рабочее дерево
Рабочее дерево - это любой каталог в вашей файловой системе, с которым связан репозиторий (обычно на это указывает наличие подкаталога с именем .git.).
Он включает в себя все файлы и подкаталоги в этом каталоге.

Разница между Git-BLOB-файлом и файлом файловой системы заключается в том, что BLOB-объект не хранит метаданные о своем содержимом. Вся такая информация хранится в дереве, которое содержит блоб.

Одно дерево может знать это содержимое как файл с именем «foo», созданный в августе 2004 года, тогда как другое дерево может знать то же содержимое, что и файл с именем «bar», созданный пять лет спустя.
В обычной файловой системе два файла с одинаковым содержимым, но с такими разными метаданными, всегда будут представлены как два независимых файла.

Почему эта разница? Главным образом, это потому, что файловая система предназначена для поддержки файлов, которые изменяются, а Git - нет.
Тот факт, что данные являются неизменяемыми в Git-репозитории, является тем, что делает всю эту работу, и поэтому был необходим другой дизайн.


Короче, процитировать Git Internal (очень короткая выдержка)

Дерево - это простой список деревьев и BLOB-объектов, которые оно содержит, а также названия и режимы этих деревьев и BLOB-объектов.

Более конкретно, содержание дерева:

очень простой текстовый файл со списком:

  • режим
  • тип
  • sha1 и
  • имя

каждой сущности.

( Якуб Наренбский подробности в комментариях:

На самом деле объект дерева не является текстовым файлом : по какой-то причине он хранит SHA-1 в двоичном формате.

Но:

Объект фиксации использует текстовый формат для SHA-1 родителей и верхнего дерева.

)


ОП добавляет в комментариях:

Мне кажется, мне трудно понять, что у каждого коммита есть дерево.

Конечно, имеет. ** Коммит - это указатель на ** дерево верхнего уровня ****, на которое ссылается его SHA1.

А что запускает Git для создания дерева изначально?

Ваш первый коммит (git init не создает дерево, просто пустой репозиторий Git)

Согласно Pro Git, индекс привязан, но никакой дополнительной информации предоставлено не было.

Вы должны ссылаться на главу Внутренние объекты :

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

Таким образом, как только вы 'git add' заполняете некоторые файлы (то есть "размещаете их" или "добавляете их в индекс"), вы разрешаете Git создавать дерево из индекса при следующей фиксации.

альтернативный текст http://progit.org/figures/ch9/18333fig0901-tn.png

По сути, это то, что делает Git, когда вы запускаете git add и git commit commands

  • хранит капли для файлов, которые изменились,
  • обновляет индекс,
  • выписывает деревья,
  • и пишет соОбъекты mmit, которые ссылаются на деревья верхнего уровня и коммиты, пришедшие непосредственно перед ними.

Эти три основных объекта Git - blob, tree и commit - изначально хранятся в виде отдельных файлов в вашем каталоге .git/objects.

альтернативный текст http://progit.org/figures/ch9/18333fig0903-tn.png

3 голосов
/ 18 июля 2010

1. Как Git обнаруживает, что нужно создать дерево?

Когда вы фиксируете, git создает древовидную иерархию для содержимого индекса, а затем создает коммит, ссылающийся на корень этой древовидной иерархии. После операции git-add хранилище содержит объекты BLOB-объектов для всех добавленных файлов, а индекс содержит ссылки на BLOB-объекты в паре с именами путей. Здесь еще нет объектов из дерева.

Когда вы фиксируете (технически, во время операции дерева записи), git рекурсивно создает набор деревьев, используя информацию индекса. Он начинается с деревьев, которые содержат только капли, определяет их идентификаторы и записывает объекты дерева. Затем он поднимается на каждый уровень и создает следующий набор деревьев, поскольку это не может произойти до того, как станут известны идентификаторы поддерева. Затем он сохраняет дерево корневого уровня.

Операция фиксации разбита на этапы дерева записи и дерева фиксации. Шаг дерева записи использует текущее состояние индекса для идентификации и (при необходимости) хранения всех деревьев. Шаг дерева коммитов создает новый коммит, ссылающийся на все родительские коммиты и только что созданное корневое дерево.

2. Что хранится под деревом в любой момент?

Когда вы узнаете, как использовать git, основное внимание будет уделено ориентированному ациклическому графу (DAG) коммитов: каждый коммит содержит указатель на предыдущий коммит, и вы можете вернуться назад во времени, следуя этим ссылкам. Это имеет смысл, поскольку пользовательский интерфейс связан с коммитами, а деревья действительно вторичны.

Деревья также образуют DAG, но разница в том, что они не представляют историю коммитов. Точно так же, как блоб, когда дерево создано, его идентификатор всегда будет указывать на это дерево с этим содержимым. Если какой-либо из BLOB-объектов или деревьев, перечисленных в дереве, будет изменен или удален, у него будет новый идентификатор, а само дерево будет иметь новое имя в следующем коммите.

3. Новое дерево "ревизия" создается каждый раз, когда капля под этим деревом модифицируется?

Хорошо, допустим, ваш репозиторий выглядит так:

foo/
  a.txt
  b.txt
bar/
  a.txt
  b.txt

и все файлы пусты. Тогда в хранилище есть три объекта, не считая фиксации:

  1. Дерево верхнего уровня:

    $ git cat-file -p ebf247ec5ebc97b12cd7a56db330141568edb946
    040000 tree 2bdf04adb23d2b40b6085efb230856e5e2a775b7    bar
    040000 tree 2bdf04adb23d2b40b6085efb230856e5e2a775b7    foo
    
  2. Дерево с двумя каплями:

    $ git cat-file -p 2bdf04adb23d2b40b6085efb230856e5e2a775b7
    100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391    a.txt
    100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391    b.txt
    
  3. Пустой блоб:

    $ git cat-file -p e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
    

Сначала я объясню, почему деревья foo и bar хранятся в одном объекте, затем внесу изменения и посмотрю, что произойдет.

Идентификатор SHA1 дерева полностью определяется его содержимым, как и BLOB-объект. Обратите внимание, что его имя не задействовано, что означает, что переименование дерева воссоздает его родителя, но само дерево восстанавливать не нужно. Если вы вставите вышеприведенный вывод в git mktree, git ответит именем объекта получившегося дерева. Под капотом mktree выдает SHA1, как этот код рубина:

>> require 'digest/sha1'
>> sha1 = ['e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'].pack 'H*'
>> contents = "100644 a.txt\0#{sha1}100644 b.txt\0#{sha1}"
>> data = "tree #{contents.length}\0#{contents}"
>>  Digest::SHA1.hexdigest(data)
"2bdf04adb23d2b40b6085efb230856e5e2a775b7"

Теперь я собираюсь изменить 'bar / b.txt' и исследовать новый набор деревьев:

$ echo hello > bar/b.txt
$ git add bar/b.txt
$ git write-tree
5fa578acc6695bf2af2975ed0ffa7ab448b52c22
$ git cat-file -p 5fa578acc6695bf2af2975ed0ffa7ab448b52c22
040000 tree 9a514e08691a9f636665a43a1c89dc1920dab0fa    bar
040000 tree 2bdf04adb23d2b40b6085efb230856e5e2a775b7    foo

Поскольку ничего, кроме «foo», не изменилось, оно сохраняется как то же самое дерево. Для крупных сооружений это огромная космическая победа. Существует новое дерево для 'bar', так как я изменил его:

$ git cat-file -p 9a514e08691a9f636665a43a1c89dc1920dab0fa
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391    a.txt
100644 blob ce013625030ba8dba906f756967f9e9ca394464a    b.txt
$ git cat-file -p ce013625030ba8dba906f756967f9e9ca394464a
hello

Опять же, ничего в объектах дерева ничего не говорит о ревизиях или фиксациях. Если дерево и его дочерние элементы неизменны от одного коммита к другому, они будут представлены одним и тем же объектом. Если в одном коммите есть два одинаковых дерева, они также будут представлены одним и тем же объектом.

Что касается индекса, то между ним и деревьями существует только минимальная связь. Одним из важных отличий является то, что в индексе хранятся имена и пути BLOB-объектов, используется плоский список и вообще не упоминаются деревья.

$ git ls-files -s
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0       bar/a.txt
100644 ce013625030ba8dba906f756967f9e9ca394464a 0       bar/b.txt
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0       foo/a.txt
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0       foo/b.txt

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

Ссылки

1 голос
/ 18 июля 2010

Дерево представляет состояние файлов на диске.Это вечное, неизменное состояние вещей.

Фиксация не представляет состояние файлов на диске.Задача коммитов состоит в том, чтобы представлять историю состояний, то есть связывать деревья (состояния) связей в хронологическом порядке.Одна фиксация представляет собой момент времени, когда кто-то фиксирует состояние файлов на диске в постоянном хранилище.Это делается путем удержания указателя на дерево («это состояние, которое зафиксировал автор»), указателя на предыдущий коммит («это была история до того, как автор зафиксировал его») и различных метаданных, необходимых для полученияхорошая история (временные метки, сообщения о коммитах, авторство).


Редактировать: В ответ на комментарий: «Так что каждый коммит, то есть, по сути, снимок всей базы кода (с помощью указателей, где контент)не изменился)? ": Каждый коммит содержит указатель на дерево (которое является снимком всей кодовой базы), но на самом деле, поскольку мы пытаемся быть точными здесь, ответ - нет:коммиты не представляют состояние кодовой базы.Они представляют момент времени, когда состояние кодовой базы было введено в постоянную историю.Однако дерево, на которое указывает коммит, абсолютно действительно представляет состояние всей кодовой базы (потому что это дерево верхнего уровня - дерево с корнем в корне репо).

Однако для практических целей вы можете думать о коммите как как о конкретном моменте времени, так и о конкретном состоянии кодовой базы.Если вы когда-либо видели команду, которая принимает «древовидную» в документах, это то, о чем они говорят: вы можете дать ей дерево или коммит, и если вы дадите ему коммит, он просто выполнит это додерево, на которое оно указывает.Так что да, git документация, и когда мы просто используем ее, не задумываясь о реализации, вы можете думать о коммите, как о знании всего состояния репо (а не только того, что изменилось).

Наоборотк тому, что вы, возможно, прочитали из неправильной статьи в блоге Джоэла Споельски, git не хранит различий.Он хранит все состояние кодовой базы при каждом коммите.Он просто использует хитрые трюки с хэшированием, чтобы обеспечить очень небольшую избыточность данных в хранилище объектов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...