Как Git создает коммиты так быстро?

Из того, что я понимаю, каждый коммит в Git является "снимком" всего репозитория, что означает, что, по крайней мере, каждый файл должен быть прочитан. Мой репозиторий занимает 9,2 ГБ, а фиксация занимает доли секунды. Не имеет смысла, как это происходит так быстро.

2 ответа

Решение

по крайней мере, каждый файл должен быть прочитан

Наоборот, это самое большее, что могло бы произойти.

Бег git commit зафиксировать ваши поэтапные изменения, как правило, быстро, потому что на самом деле постановка изменений сделала большую часть работы. Создание коммита просто превращает индекс (он же "промежуточная область") в очень легкий объект коммита, который содержит метаданные о вашем коммите, и несколько объектов дерева, которые содержат структуру репозитория.

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

Другой тонкой вещью является то, что индекс содержит информацию обо всех файлах в вашем хранилище - и он хранит информацию о рабочем каталоге, такую ​​как отметка времени, когда он последний раз проверял файл, и его размер. Так что даже если вы запускаете что-то вроде git add . чтобы подготовить все измененные файлы, нужно только stat файл, чтобы узнать, изменился ли он, и он может игнорировать его, если нет.

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

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

Заметка; если у вас есть репозиторий с большим количеством коммитов, например, " самый большой репозиторий Git на планете " с более чем 250000 коммитов, добавление новых коммитов может быть медленным.

Вот почему Git 2.23 (3 квартал 2019 г.) вводит цепочки фиксации графов.

См совершать 5b15eb3, совершать 16110c9, совершают a09c130, совершают e2017c4, совершают ba41112, совершают 3da4b60, совершают c2bc6e6, совершают 8d84097, совершают c523035, совершают 1771be9, совершают 135a712, совершают 6c622f9, совершают 144354b, совершают 118bd57, совершают 5c84b33, совершают 3cbc6ed, совершить d4f4d60, совершить 890345a (18 июня 2019 г.) Деррик Столи (derrickstolee).
(Слияние Junio ​​C Hamano -gitster- в коммите 92b1ea6, 19 июля 2019 г.)

commit-graph: документ цепочки фиксации-графа

В документации теперь есть:

Цепочки графиков фиксации

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

Создавая "цепочку" графиков фиксации, мы обеспечиваем быструю запись новых данных фиксации без перезаписи всей истории фиксации - по крайней мере, большую часть времени.

Макет файла

Цепочка фиксации-графа использует несколько файлов, и мы используем фиксированное соглашение об именах для организации этих файлов.
У каждого файла графика фиксации есть имя$OBJDIR/info/commit-graphs/graph-{hash}.graph где {hash}- это шестнадцатеричный хэш, хранящийся в нижнем колонтитуле этого файла (который является хешем содержимого файла перед этим хешем).
Для цепочки файлов с графом фиксации простой текстовый файл по адресу$OBJDIR/info/commit-graphs/commit-graph-chain содержит хэши файлов в порядке от "самого низкого" до "самого высокого".

Например, если commit-graph-chain файл содержит строки:

    {hash0}
    {hash1}
    {hash2}

тогда цепочка фиксации-графа выглядит как следующая диаграмма:

 +-----------------------+
 |  graph-{hash2}.graph  |
 +-----------------------+
      |
 +-----------------------+
 |                       |
 |  graph-{hash1}.graph  |
 |                       |
 +-----------------------+
      |
 +-----------------------+
 |                       |
 |                       |
 |                       |
 |  graph-{hash0}.graph  |
 |                       |
 |                       |
 |                       |
 +-----------------------+

  • Позволять X0 быть количеством коммитов в graph-{hash0}.graph,
  • X1 быть количеством коммитов в graph-{hash1}.graph, а также
  • X2 - количество коммитов в graph-{hash2}.graph.

Если коммит появляется в позиции i в graph-{hash2}.graph, то мы интерпретируем это как фиксацию в позиции (X0 + X1 + i), и это будет использоваться в качестве его "позиции на графике".
Коммиты вgraph-{hash2}.graph используйте эти позиции для обозначения своих родителей, которые могут быть в graph-{hash1}.graph или graph-{hash0}.graph.
Мы можем перейти к произвольной фиксации в позицииj проверяя его наличие в интервалах [0, X0), [X0, X0 + X1), [X0 + X1, X0 + X1 + X2).


Это означает git commit-grah есть новый writeпараметр команды:--split.

commit-graph: Добавить --split возможность встроить

Добавить новый "--split"вариант"git commit-graph writeподкоманда.
Эта опция разрешает необязательное поведение при написании цепочки фиксации-графа.

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

Добавьте новый тестовый сценарий (t5324-split-commit-graph.sh), который демонстрирует такое поведение.

И та же документация добавляет:

С --split вариант, запишите граф фиксации как цепочку из нескольких файлов графа фиксации, хранящихся в <dir>/info/commit-graphs.
Новые коммиты, которых еще нет в графике коммитов, добавляются в новый файл "подсказок".
Этот файл объединяется с существующим файлом, если выполняются следующие условия объединения:

  • Если --size-multiple=<X> не указано, пусть X равно 2. Если бы в новом файле наконечников N совершает и предыдущий совет имеет M совершает и X раз N больше, чем M, вместо этого объедините два файла в один.

  • Если --max-commits=<M> указывается с M положительное целое число, и в новом файле подсказок будет больше M совершает, а затем вместо этого объединяет новую подсказку с предыдущей.

Наконец, если --expire-time=<datetime> не указано, пусть datetimeбыть текущим временем. После записи разделенного графа фиксации удалите все неиспользуемые графы фиксации, время изменения которых старше, чемdatetime.


Это поможет с вилками:

commit-graph: разрешить перекрестно чередующиеся цепи

В такой среде, как сеть вилок, полезно иметь цепочку графов фиксации, которая охватывает как базовое репо, так и репозиторий вилки.
Форк обычно представляет собой небольшой набор данных поверх большого репо, но иногда форк намного больше.
Например, git-for-windows/git имеет почти вдвое больше коммитов, чем git / git, потому что он обновляет свои коммиты при каждом обновлении основной версии.

Документация теперь включает в себя:

Цепочки в нескольких каталогах объектов

В репо с альтернативами мы ищем commit-graph-chainфайл, начиная с локального каталога объектов, а затем в каждом альтернативном.
Первый существующий файл определяет нашу цепочку.
Когда мы ищемgraph-{hash} файлы для каждого {hash} в файле цепочки мы следуем тому же шаблону для каталогов хоста.

Это позволяет разделить графики фиксации на несколько вилок в сети вилок.
Типичный случай - это большое "базовое" репо с множеством меньших вилок.

По мере развития базового репо он, вероятно, будет обновлять и объединять свою цепочку графа фиксации чаще, чем вилки.
Если вилка обновляет свой граф фиксации после базового репо, она должна "переродить" цепочку графа фиксации в новую цепочку в базовом репо.
При чтении каждогоgraph-{hash}файл, мы отслеживаем каталог объекта, в котором он находится. Во время записи нового файла графа фиксации мы проверяем любые изменения в каталоге исходного объекта и читаемcommit-graph-chainфайл для этого источника и создайте новый файл на основе этих файлов.
Во время этой операции "повторного родителя" нам обязательно нужно свернуть все уровни в вилке, так как все файлы недействительны для нового базового файла.


Это также включает в себя истекающие файлы графика фиксации:

commit-graph: срок действия файлов графика фиксации

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

Это изменение вводит 'expiry_window'значение контекста, которое всегда равно нулю (на данный момент).
Затем мы проверяем измененное время каждогоgraph-{hash}.graph файл в $OBJDIR/info/commit-graphs папку и отсоедините файлы старше expiry_window.

В документации теперь есть ссылки:

Удаление файлов графика-{хэш}

После написания нового файла подсказок некоторые graph-{hash}файлы больше не могут быть частью цепочки. В конце концов, важно удалить эти файлы с диска.
Основная причина отсрочки удаления заключается в том, что другой процесс мог прочитатьcommit-graph-chain файл, прежде чем он будет перезаписан, но затем найдите graph-{hash} файлы после их удаления.

Чтобы позволить сохранять старые разделенные графики фиксации в течение некоторого времени после того, как на них нет ссылок, мы обновляем время изменения файлов, когда они перестают ссылаться на них.
Затем мы сканируем$OBJDIR/info/commit-graphs/ каталог для graph-{hash} файлы, время изменения которых старше указанного окна срока действия.
Это окно по умолчанию имеет нулевое значение, но его можно изменить с помощью аргументов командной строки или настройки конфигурации.


С Git 2.27 (второй квартал 2020 г.) " git commit-graph write"научились различным способам записи разделенных файлов.

См. Коммит dbd5e0a (29 апреля 2020 г.), автор Junio ​​C Hamano (gitster).
См. Commit 7a9ce02 (15 апреля 2020 г.) и commit 6830c36, commit f478106, commit 8a6ac28, commit fdbde82, commit 4f02735, commit 2fa05f3 (14 Apr 2020) by Taylor Blau (ttaylorr).
(Слияние Junio ​​C Hamano -gitster- в коммите 6a1c17d, 01 мая 2020 г.)

builtin/commit-graph.c: ввести стратегию разделения "без слияния"

Подписано: Тейлор Блау

В предыдущем коммите мы заложили основу для поддержки различных стратегий разделения. В этом коммите мы представляем первую стратегию разделения: 'no-merge'.

Проходящий '--split=no-merge'полезен для вызывающих, которые хотят написать новый инкрементный граф фиксации, но не хотят тратить усилия на уплотнение инкрементной цепочки (*1).

Раньше это было возможно при передаче '--size-multiple=0', но это уже не так после 63020f175f ("commit-graph: предпочитать по умолчанию size_multпри нулевом значении ", 02.01.2020, Git v2.25.0-rc2 - слияние).

Когда '--split=no-merge', механизм графа фиксации никогда не будет уплотнять существующую цепочку и всегда будет писать новый инкрементальный.

(*1): это может произойти, когда, например, администратор сервера, запускающий некоторую программу после каждого нажатия, может захотеть убедиться, что каждое задание выполняется пропорционально времени размеру нажатия, и не "прыгает" при фиксации- Механизм графа решает запустить слияние.

Насколько я понимаю, до сих пор... Представьте, что у вас есть много коммитов в основной ветке и другой ветке с большим количеством коммитов. Поэтому, если VCS не поддерживает концепцию git с хэшами и т. Д., А просто сохраняет разницу в файлах, а затем вы хотите выполнить ветвление. Затем другая VCS должна либо отменить все изменения, либо общий коммит, и применить все изменения другой ветви, либо сравнить все файлы один за другим. На мой взгляд, алгоритм хэширования в git кажется лучшим подходом, даже если я думаю, что git должен делать много итераций / поиска. ИДК, если я прав, я только сегодня начал читать кое-что о мерзавце. Не стесняйтесь понижать / повышать голос и комментировать: Я думаю, что это тема, в которой только несколько человек действительно имеют глубокие знания

Другие вопросы по тегам