TL;DR
git prune
удаляет только незакрепленные, недоступные, устаревшие объекты (объекты должны иметь все три свойства, чтобы их можно было удалить). Недоступные упакованные объекты остаются в своих файлах пакетов. Доступные незакрепленные предметы остаются доступными и незакрепленными. Объекты, которые недоступны, но еще не устарели, также остаются нетронутыми. Определение устаревшего немного сложно (см. Подробности ниже).
git gc
делает больше: он упаковывает ссылки, упаковывает полезные объекты, удаляет записи в рефлоге, удаляет незакрепленные объекты, удаляет удаленные рабочие деревья и удаляет старые git rerere
данные / gc.
Длинная
Я не уверен, что вы имеете в виду под "в фоновом режиме" выше (фон имеет техническое значение в оболочках, и все действия здесь происходят на переднем плане оболочки, но Я подозреваю, что вы имели в виду не эти термины).
Что делает git gc
, так это организовывает целую серию действий по сбору, включая, помимо прочего, git prune
. Список ниже представляет собой набор команд, выполняемых gc
передним планом без --auto
(без их аргументов, которые в некоторой степени зависят от git gc
аргументов):
git pack-refs
: компактные ссылки (превращайте записи .git/refs/heads/...
и .git/refs/tags/...
в записи в .git/packed-refs
, удаляя отдельные файлы)
git reflog expire
: удалить старые записи рефлога
git repack
: упаковать незакрепленные объекты в формат упакованного объекта
git prune
: удалить ненужные незакрепленные предметы
git worktree prune
: удалить данные рабочего дерева для добавленных рабочих деревьев, которые удалил пользователь
git rerere gc
: удалить старые перезаписываемые записи
Есть еще несколько отдельных операций с файлами, которые git gc
выполняются сами по себе, но приведенная выше является основной последовательностью. Обратите внимание, что git prune
происходит после (1) истечения срока действия рефлогов и (2) запуска git repack
: это связано с тем, что удаленная запись рефлога с истекшим сроком действия может привести к тому, что объект перестанет ссылаться на него и, следовательно, не будет упакован, а затем получит обрезать так, чтобы он полностью исчез.
Что нужно знать, прежде чем мы рассмотрим переупаковку и обрезку
Прежде чем вдаваться в подробности, рекомендуется определить, что такое объект в Git и что означает свободный или упакованный объект. . Нам также необходимо понять, что означает достижимость объекта.
У каждого объекта есть хэш-идентификатор - один из тех больших уродливых идентификаторов, которые вы видели, например, в git log
, - то есть имя этого объекта для целей поиска. Git хранит все объекты в базе данных ключ-значение, где имя является ключом, а сам объект является значением. Таким образом, объекты Git - это то, как Git хранит файлы и фиксирует, и на самом деле существует четыре типа объектов: Объект commit содержит фактическую фиксацию. Объект tree содержит наборы пар, 1 - удобочитаемое имя, например README
или subdir
, вместе с хеш-идентификатором другого объекта. Этот другой объект является объектом blob, если имя в дереве является именем файла, или другим объектом дерева, если это имя подкаталога. Объекты blob содержат фактическое содержимое файла (но обратите внимание, что имя файла находится в дереве, связанном с blob!). Последний тип объекта - это аннотированный тег, используемый для аннотированных тегов, которые здесь не особенно интересны.
После создания ни один объект не может быть изменен. Это связано с тем, что имя объекта - его хэш-идентификатор - вычисляется путем просмотра каждого бита содержимого объекта. Измените любой один бит с нуля на единицу или наоборот, и идентификатор хэша изменится: теперь у вас есть другой объект с другим именем. Вот как Git проверяет, что ни один файл никогда не был испорчен: если бы содержимое файла было изменено, хэш-идентификатор объекта изменился бы. Идентификатор объекта сохраняется в записи дерева, и если объект дерева был изменен, идентификатор дерева изменился бы. Идентификатор дерева сохраняется в фиксации, и если бы идентификатор дерева был изменен, хеш фиксации изменился бы. Итак, если вы знаете, что хэш фиксации равен a234b67...
, а содержимое фиксации по-прежнему хешируется до a234b67...
, в фиксации ничего не изменилось, и идентификатор дерева все еще действителен. Если дерево по-прежнему хеширует свое собственное имя, его содержимое остается действительным, поэтому идентификатор большого двоичного объекта правильный; поэтому до тех пор, пока содержимое большого двоичного объекта хешируется на свое собственное имя, этот большой двоичный объект также является правильным.
Объекты могут быть свободными, что означает, что они хранятся в виде файлов. Имя файла - это просто идентификатор хэша. 2 Содержимое свободного объекта сдувается с помощью zlib. Или объекты могут быть упакованы, что означает, что многие объекты хранятся в одном пак-файле. В этом случае содержимое не просто сжимается, оно сначала выполняется с дельта-сжатием < / а>. Git выбирает базовый объект - часто последняя версия какого-то большого двоичного объекта (файла) - а затем находит дополнительные объекты, которые могут быть представлены в виде серии команд: возьмите базовый файл, удалите в нем некоторый текст смещение, добавить другой текст с другим смещением и т. д. Фактический формат файлов пакетов задокументирован здесь -А если немного полегче. Обратите внимание, что в отличие от большинства систем управления версиями, дельта-сжатие происходит на уровне ниже абстракции сохраненного объекта: Git сохраняет снимки целиком, а затем выполняет дельта-сжатие позже при лежащие в основе объекты. Git по-прежнему обращается к объекту по его имени хеш-идентификатора; просто чтение этого объекта включает чтение файла пакета, поиск объекта и лежащих в его основе дельта-баз и реконструкцию всего объекта на лету.
Существует общее правило для файлов пакетов, которое гласит, что любой объект с дельта-сжатием внутри файла пакета должен иметь все свои основания в одном и том же файле пакета. Это означает, что файл пакета является самодостаточным: нет необходимости открывать несколько дополнительных файлов пакета, чтобы получить объект из пакета, который имеет объект. (Это конкретное правило может быть намеренно нарушено, создавая то, что Git называет тонким пакетом, но они предназначены для использования только для отправки объектов через сетевое соединение в другой Git, у которого уже есть базовые объекты. другой Git должен "исправить" или "утолить" тонкий пакет, чтобы сделать обычный файл пакета, прежде чем оставить его для остальной части Git.)
Достижимость объекта немного сложна. Давайте сначала посмотрим на достижимость фиксации.
Обратите внимание, что когда у нас есть объект фиксации, сам этот объект фиксации содержит несколько идентификаторов хэша. У него есть один хэш-идентификатор для дерева, в котором хранится моментальный снимок, связанный с этой фиксацией. Он также имеет один или несколько идентификаторов хэша для родительских коммитов, если только этот конкретный коммит не является корневым коммитом. Корневая фиксация определяется как фиксация без родителей, так что это немного замкнутый круг: у фиксации есть родители, если только у нее нет родителей. Однако это достаточно ясно: при некоторой фиксации мы можем нарисовать эту фиксацию как узел на графике со стрелками, выходящими из узла, по одной на каждого родителя:
<--o
|
v
Эти родительские стрелки указывают на родителя или родителей коммита. Учитывая серию однопородных коммитов, мы получаем простую линейную цепочку:
... <--o <--o <--o ...
Один из этих коммитов должен быть началом цепочки: это корневой коммит. Один из них должен быть концом, и это фиксация наконечника. Все внутренние стрелки указывают назад (влево), поэтому мы можем нарисовать это без наконечников стрелок, зная, что корень находится слева, а кончик справа:
o--o--o--o--o
Теперь мы можем добавить имя ветки, например master
. Название просто указывает на фиксацию подсказки:
o--o--o--o--o <--master
Ни одна из стрелок, встроенных внутри фиксации, никогда не может измениться, потому что ничто в любом объекте не может измениться. Однако стрелка в имени ветки master
на самом деле является просто идентификатором хэша некоторой фиксации, и это может измениться. Давайте использовать буквы для обозначения хэшей коммитов:
A--B--C--D--E <-- master
имя master
теперь просто хранит хеш фиксации E
. Если мы добавляем новую фиксацию в master
, мы делаем это, записывая фиксацию, родительский элемент которой E
, а дерево является нашим снимком, давая нам совершенно новый хэш, который мы можем назвать F
. Коммит F
указывает обратно на E
. Git записывает идентификатор хэша F
в master
, и теперь у нас есть:
A--B--C--D--E--F <-- master
Мы добавили одну фиксацию и изменили одно имя, master
. Все предыдущие коммиты достижимы, начиная с имени master
. Мы считываем хэш-идентификатор F
и читаем фиксацию F
. У него идентификатор хэша E
, поэтому мы достигли фиксации E
. Мы читаем E
, чтобы получить идентификатор хэша D
и, таким образом, достичь D
. Мы повторяем, пока не прочитаем A
, не обнаружим, что у него нет родителя, и все готово.
Если есть ветки, это просто означает, что у нас есть коммиты, найденные с другим именем, чьи родители являются одним из коммитов, также найденных по имени master
:
A--B--C--D--E--F <-- master
\
G--H <-- develop
Имя develop
указывает на фиксацию H
; H
находит G
; а G
относится к E
. Итак, все эти коммиты достижимы.
Коммиты с более чем одним родителем, т. Е. коммиты слиянием, делают всех их родителей доступными, если достижима сама фиксация. Итак, как только вы сделаете фиксацию слияния, вы можете (но не обязаны) удалить имя ветки, которое идентифицирует фиксацию, которая была объединена: теперь она доступна из конца ветки, в которой вы были, когда выполняли операцию слияния. . Это:
...--o--o---o <-- name
\ /
o--o <-- delete-able
коммиты в нижней строке здесь доступны из name
через слияние, так же как коммиты в верхней строке всегда доступны из name
. Удаление имени delete-able
оставляет их по-прежнему доступными. Если фиксации слияния нет, как в этом случае:
...--o--o <-- name2
\
o--o <-- not-delete-able
затем удаление not-delete-able
эффективно отменяет два коммита в нижней строке: они становятся недоступными и, следовательно, имеют право на сборку мусора.
Это же свойство достижимости применяется к объектам дерева и BLOB-объекта. Например, в Commit G
есть tree
, а в этом tree
есть пары ‹name, ID›:
A--B--C--D--E--F <-- master
\
G--H <-- develop
|
tree=d097...
/ \
README=9fa3... Makefile=0b41...
Итак, из фиксации G
, объект tree d097...
становится достижимым; из этого дерева доступен объект blob 9fa3...
, как и объект blob 0b41...
. Фиксация H
может иметь тот же самый README
объект с тем же именем (хотя и другим деревом): это нормально, это просто делает 9fa3
дважды достижимым, что не интересует Git: Git заботится только о том, чтобы он вообще был достижим.
Внешние ссылки - имена веток и тегов, а также другие ссылки, найденные в репозиториях Git (включая записи в index Git и любые ссылки через связанные добавленные рабочие деревья), предоставляют точки входа в граф объектов. Из этих точек входа любой объект либо доступен - имеет одно или несколько имен, которые могут вести к нему, - либо недоступен, что означает отсутствие имен, по которым можно найти сам объект. Я пропустил аннотированные теги из этого описания, но они обычно находятся по именам тегов, а объект аннотированного тега имеет одну ссылку на объект (произвольного типа объекта), которую он находит, что делает этот один объект доступным, если сам объект тега доступен. .
Поскольку ссылки относятся только к одному объекту, но иногда мы делаем что-то с именем ветки, которое впоследствии хотим отменить, Git ведет журнал каждого значения, которое имеет ссылка, и когда. Эти справочные журналы или справочные журналы позволяют нам узнать, что было в master
вчера или что было в develop
на прошлой неделе. В конце концов, эти записи рефлога устарели и устарели и вряд ли будут больше полезны, и git reflog expire
их отбросит.
Переупаковывать и обрезать
То, что делает git repack
на высоком уровне, теперь должно быть достаточно ясно: он превращает набор множества отдельных объектов в пакетный файл, полный всех этих объектов. Однако он может делать больше: он может включать в себя все объекты из предыдущего пакета. Предыдущий пакет становится лишним, и его можно будет удалить позже. Он также может опускать любые недоступные объекты из пакета, превращая их вместо этого в свободные объекты. Когда git gc
запускает git repack
, он делает это с параметрами, зависящими от параметров git gc
, поэтому точная семантика здесь различается, но по умолчанию для git gc
переднего плана используется git repack -d -l
, который git repack
удаляет избыточные пакеты и запускает git prune-packed
. Программа prune-packed
удаляет незакрепленные объектные файлы, которые также присутствуют в пакетных файлах, таким образом удаляя незакрепленные объекты, которые попали в пакет. Программа repack
передает параметр -l
в git pack-objects
(который фактически является рабочей лошадкой, которая создает файл пакета), что означает исключение объектов, заимствованных из других репозиториев. (Этот последний параметр не важен для большинства случаев обычного использования Git.)
В любом случае это git repack
- или технически git pack-objects
- печатает счет, сжатие и запись сообщений. Когда это будет сделано, у вас будет новый файл пакета, а старые файлы пакета исчезнут. Новый файл пакета содержит все доступные объекты, включая старые доступные упакованные объекты и старые доступные свободные объекты. Если незакрепленные объекты были извлечены из одного из старых (теперь разорванных и удаленных) файлов пакета, они присоединяются к другим незакрепленным (и недоступным) объектам, загромождающим ваш репозиторий. Если они были уничтожены во время сноса, останутся только существующие незакрепленные и недоступные объекты.
Пришло время для git prune
: он находит незакрепленные, недоступные объекты и удаляет их. Однако у него есть переключатель безопасности, --expire 2.weeks.ago
: по умолчанию, запущенный git gc
, он не удаляет такие объекты, если им меньше двух недель. Это означает, что любая программа Git, которая находится в процессе создания новых объектов, но еще не подключила их, имеет льготный период. Новые объекты могут быть незакрепленными и недоступными (по умолчанию) в течение четырнадцати дней, прежде чем git prune
удалит их. Итак, программа Git, которая занята созданием объектов, имеет четырнадцать дней, в течение которых она может завершить подключение этих объектов к графу. Если он решит, что эти объекты не стоит подключать, он может просто оставить их; Через 14 дней с этого момента они будут удалены в будущем git prune
.
Если вы запускаете git prune
вручную, вы должны выбрать свой --expire
аргумент. По умолчанию без --expire
это не 2.weeks.ago
, а просто now
.
1 Объекты дерева на самом деле содержат тройки: имя, режим, хэш. Режим: 100644
или 100755
для объекта blob, 004000
для поддерева, 120000
для символьной ссылки и так далее.
2 Для скорости поиска в Linux хэш разделяется после первых двух символов: имя хеша ab34ef56...
становится ab/34e567...
в каталоге .git/objects
. Это сохраняет размер каждого подкаталога в пределах .git/objects
small-ish, что укрощает поведение O (n 2) некоторых операций с каталогом. Это связано с git gc --auto
, который автоматически переупаковывает, когда каталог одного объекта становится достаточно большим. Git предполагает, что каждый подкаталог примерно того же размера, что и хэши, которые в основном должны быть распределены равномерно, поэтому ему нужно подсчитать только один подкаталог.
person
torek
schedule
02.05.2018