Как 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 должен делать много итераций / поиска. ИДК, если я прав, я только сегодня начал читать кое-что о мерзавце. Не стесняйтесь понижать / повышать голос и комментировать: Я думаю, что это тема, в которой только несколько человек действительно имеют глубокие знания