Какая информация хранится как содержимое каждого дерева git коммитов - PullRequest
1 голос
/ 02 мая 2020

Каждый Git коммит объект указывает на объект дерева. Сохраняет ли каждый объект дерева коммитов все свои записи вместе с ним, или он только добавляет новые записи и содержит только дельты из родительского комита?

Например, Linux исходный код имеет 1M коммитов и тысяч объектов (мастер имеет 70000). Если каждый объект коммита содержит все записи объектов, в долгосрочной перспективе это займет огромное пространство. Кроме того, это большая обработка и передача, даже когда одна строка изменений зафиксирована / отправлена.

Я понимаю, что философия Git заключается в хранении снимков, а не дельт для файлов, но в этом случае только измененный файл сохраняется.

В приведенном ниже примере, 70951b429e0e1191a8c1d9e34248cd76453ef544 содержит (или отображается как содержащее) все 5 файлов, даже если был добавлен только один файл.

[test]$ls
a.txt  b.txt  c.txt  d.txt
[test]$echo r5 > e.txt
[test]$git add -A && git commit -m "r5"
[master 51f6941] r5
[test]$git cat-file -p 51f6941
tree 70951b429e0e1191a8c1d9e34248cd76453ef544
[test]$git cat-file -p 70951b429e0e1191a8c1d9e34248cd76453ef544
100644 blob 9a6c8d12dea8859b821b2ba705f7efd6cc914aa5    a.txt
100644 blob 9a6c8d12dea8859b821b2ba705f7efd6cc914aa5    b.txt
100644 blob b6693b64f528de38cde5533acd781fde743bc3df    c.txt
100644 blob 91174caefafdc81d34e302874c86c6e4d5212075    d.txt
100644 blob 29f4cfc46ba3a0bde55bce8f44ac3590e2108da4    e.txt

Ответы [ 3 ]

2 голосов
/ 02 мая 2020

Сам объект дерева всегда завершен. Он представляет один уровень каталога = иерархии. Так что если у вас есть каталог src и каталоги внутри, которые называются foo и bar, каждый с содержимым, у вас будут объекты дерева для верхнего уровня, для src, для src/foo и для src/bar.

Однако фактические данные в файлах хранятся в виде больших двоичных объектов. Если файл не изменяется, Git не сохраняет его новую копию: он просто ссылается на существующий объект BLOB-объекта. Это верно и для деревьев, поэтому, если вы просто измените файл в src/foo, вы получите новые объекты дерева для верхнего уровня, src и src/foo, но не src/bar.

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

Это просто упакованная форма; если Git необходимо прочитать реальный объект, он разрешает каждую дельту и вытягивает ее в память, чтобы он мог читать данные. Свободные объекты хранятся в сжатом виде, но не делимитируются. Упаковка производится периодически с помощью git gc.

2 голосов
/ 02 мая 2020

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

Если вы выбираете коммит, например, по его номеру ha sh и запустите git checkout для этого коммита, ваше рабочее дерево заполняется из файлов, которые находятся в этом коммите. То есть ваше рабочее дерево берет этот снимок. Переключитесь с этого коммита на другой коммит, который имеет, скажем, на три файла меньше, и Git удаляет эти три файла (и обновляет оставшиеся, если / при необходимости).

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

За исключением ... это не так. Здесь задействованы два удивительных (или не очень удивительных) умения умения.

Первый обнаруживается прямо здесь:

[test]$git cat-file -p 70951b429e0e1191a8c1d9e34248cd76453ef544
100644 blob 9a6c8d12dea8859b821b2ba705f7efd6cc914aa5    a.txt
100644 blob 9a6c8d12dea8859b821b2ba705f7efd6cc914aa5    b.txt
100644 blob b6693b64f528de38cde5533acd781fde743bc3df    c.txt
100644 blob 91174caefafdc81d34e302874c86c6e4d5212075    d.txt
100644 blob 29f4cfc46ba3a0bde55bce8f44ac3590e2108da4    e.txt

Обратите внимание, что blob ha sh ID 9a6c8d12dea8859b821b2ba705f7efd6cc914aa5 отображается дважды: один раз для a.txt и один раз для b.txt.

Существует только одна копия содержимого как a.txt, так и b.txt. Из этого мы можем сделать вывод, что все, что равно в a.txt и в b.txt, содержимое одинаково.

Итак, если вы передадите 100 файлов, затем сделайте новый коммит, в котором 99 файлов совпадают с 99 из файлов предыдущего коммита, вы просто повторно использовали 99 объектов BLOB. Их не нужно было снова сохранять.

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

Второй бит ловкости происходит позже . Первоначально все объекты хранятся в виде zlib-сжатых файлов (файлы в .git/objects/, хотя вы не должны на это рассчитывать). Если вы измените несколько байтов в файле и используете git add, а новый объект BLOB-объекта не будет на 100% точно соответствовать некоторому уже существующему объекту BLOB-объекта, вы получите новый один из этих объектов. Они называются незакрепленными объектами, внутренне.

Когда вокруг достаточно много незакрепленных предметов или раньше, если необходимо, Git упаковывает незакрепленных предметов в файл пакета . В это время объекты, которые могут быть выгодно дельта-сжаты, обычно являются. Это сжатие - действительно умный код.

Когда вы используете git fetch или git push, Git выяснит, какие объекты необходимо передать по сети, и создаст так называемый тонкий пакет . Здесь вы видите сообщения counting и compressing objects. Git затем отправляет тонкий пакет по проводу; Git на другом конце исправляет тонкую пачку, чтобы сделать ее обычной (жирной) пачкой. Когда слишком много файлов пакета, Git будет перепаковывать файлы пакета, перенося вас от множества файлов *.pack и *.idx до нескольких (или одного) снова.

(Здесь были некоторые случайные ошибки. Недавно было исправлено большое количество файлов пакетов. Есть несколько старых ошибок, когда вокруг оставалось слишком много незакрепленных объектов. Иногда полезно работать с ручным руководством git gc). вокруг этих ошибок, но слишком частое использование git gc может привести к обратным результатам.)

2 голосов
/ 02 мая 2020

Сохраняет ли каждый объект дерева коммитов все свои записи вместе с ним или только добавляет новые записи и содержит только дельты от родителя коммита?

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

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

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