Что делает git, когда мы делаем: git gc - git prune

Что происходит в фоновом режиме при запуске,

  • git gc
  • git prune

Вывод git gc:

Counting objects: 945490, done. 
Delta compression using up to 4 threads.   
Compressing objects: 100% (334718/334718), done. 
Writing objects: 100%   (945490/945490), done. 
Total 945490 (delta 483105), reused 944529 (delta 482309) 
Checking connectivity: 948048, done.

Вывод git prune:

Checking connectivity: 945490, done.

В чем разница между этими двумя вариантами?

Спасибо

2 ответа

Решение

TL;DR

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

git gc делает больше: упаковывает ссылки, упаковывает полезные объекты, истекает записи reflog, удаляет незакрепленные объекты, удаляет удаленные рабочие деревья и чернослив / старые файлы gc git rerere данные.

Долго

Я не уверен, что вы подразумеваете под "на заднем плане" выше (фон имеет техническое значение в оболочках, и вся деятельность здесь происходит на переднем плане оболочки, но я подозреваю, что вы не имели в виду эти термины).

Какие git gc делает это организовать целый ряд коллекционных мероприятий, в том числе, но не ограничиваясь git prune, Список ниже представляет собой набор команд, выполняемых передним планом gc без --auto (опуская свои аргументы, которые в некоторой степени зависят от git gc аргументы):

  • git pack-refs: компактные ссылки .git/refs/heads/... а также .git/refs/tags/... записи в записи в .git/packed-refs, исключая отдельные файлы)
  • git reflog expire: истекают старые записи reflog
  • git repack: упаковать свободные объекты в формат упакованного объекта
  • git prune: удалить ненужные незакрепленные предметы
  • git worktree prune: удалить данные рабочего дерева для добавленных рабочих деревьев, которые пользователь удалил
  • git rerere gc: удалить старые записи

Есть еще несколько отдельных файловых операций git gc делает сам по себе, но выше, это основная последовательность. Обратите внимание, что git prune происходит после (1) истечения срока действия повторных флагов и (2) запуска git repack Это связано с тем, что удаленная запись reflog с истекшим сроком действия может привести к тому, что объект станет не связанным, и, следовательно, не будет упакован, а затем удален, чтобы полностью исчезнуть.

Вещи, чтобы знать, прежде чем мы смотрим на упаковать и подрезать

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

Каждый объект имеет хеш-идентификатор - один из тех больших уродливых идентификаторов, которые вы видели в git log например, это имя этого объекта, для целей поиска. Git хранит все объекты в базе данных значений ключей, где имя является ключом, а сам объект является значением. Поэтому объекты Git - это то, как Git хранит файлы и коммиты, и на самом деле существует четыре типа объектов: объект коммитов содержит фактический коммит. Объект дерева содержит наборы пар, 1 удобочитаемое имя, например README или же subdir вместе с идентификатором хэша другого объекта. Этот другой объект является объектом BLOB- объекта, если имя в дереве является именем файла, или это другой объект дерева, если имя является именем подкаталога. Объекты BLOB-объектов содержат фактическое содержимое файла (но обратите внимание, что имя файла находится в дереве, ссылающемся на BLOB-объект!). Последний тип объекта - аннотированный тег, используемый для аннотированных тегов, которые здесь не особенно интересны.

После создания ни один объект не может быть изменен. Это потому, что имя объекта - его хэш-идентификатор - вычисляется путем просмотра каждого бита содержимого объекта. Измените любой бит с нуля на единицу или наоборот, и идентификатор хеша изменится: теперь у вас есть другой объект с другим именем. Вот как Git проверяет, что ни один файл никогда не был испорчен: если содержимое файла было изменено, хеш-идентификатор объекта изменится. Идентификатор объекта сохраняется в записи дерева, и если бы объект дерева был изменен, идентификатор дерева изменился бы. Идентификатор дерева сохраняется в коммите, и если идентификатор дерева был изменен, хэш коммита изменился бы. Так что, если вы знаете, что хеш коммита a234b67... и содержание коммита по-прежнему хэши a234b67... в коммите ничего не изменилось, а идентификатор дерева все еще действителен. Если дерево все еще хеширует свое собственное имя, его содержимое все еще допустимо, поэтому идентификатор BLOB-объекта является правильным; так что, пока содержимое BLOB-объекта хэшируется на собственное имя, BLOB-объект также является правильным.

Объекты могут быть свободными, что означает, что они хранятся в виде файлов. Имя файла - это просто хэш-идентификатор. 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 хэш ID в 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 фактически отказывается от двух коммитов в нижнем ряду: они становятся недоступными и, следовательно, могут быть использованы для сбора мусора.

Это же свойство достижимости применяется к объектам дерева и блоба. совершить G имеет tree в этом, например, и это tree имеет <имя, ID> пары:

A--B--C--D--E--F   <-- master
             \
              G--H   <-- develop
              |
         tree=d097...
            /   \
 README=9fa3... Makefile=0b41...

Так из коммита G, объект дерева d097... достижимо; из этого дерева, объект BLOB 9fa3... достижим, как и объект BLOB 0b41..., совершить H может иметь то же самое README объект под тем же именем (хотя и другое дерево): это хорошо, это просто делает 9fa3 вдвойне достижимо, что не интересно для Git: Git заботится только о том, что он вообще доступен.

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

Поскольку ссылки ссылаются только на один объект, но иногда мы делаем что-то с именем ветви, которое мы хотим отменить позже, Git ведет журнал каждого значения, которое имела ссылка, и когда. Эти справочные журналы или журналы позволяют нам узнать, что master было в нем вчера, или что было в develop прошлая неделя. В конце концов, эти записи reflog устарели и устарели и вряд ли пригодятся больше, и 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 предполагает, что каждый подкаталог имеет примерно одинаковый размер, так как хеши в основном должны быть равномерно распределены, поэтому ему нужно только сосчитать один подкаталог.

После недавнего добавления команды (Git 2.29 (Q4 2020)) замена для git gc -prune было бы:

      git maintenance pack-refs
# for
git pack-refs --all --prune

С Git 2.31 (первый квартал 2021 г.) " " ( человек ) инструмент изучил новую задачу обслуживания.

См. Commit acc1c4d , commit 41abfe1 (09 февраля 2021 г.) Деррик Столи ( <tcode id="774767"></tcode>) .
(Слияние Junio ​​C Hamano - <tcode id="774768"></tcode>- в коммите d494433, 17 февраля 2021 г.)

: добавить задачу pack-refs

Подписано: Деррик Столи
Рецензент: Тейлор Блау

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

Например, со многими разнесенными ссылками такие команды, как

       git describe --tags --exact-match HEAD

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

Добавить новую »задачу обслуживания.
Это работает ' <tcode id="774772"></tcode>' ( человек ), чтобы переместить свободных ссылок в упакованную форму.
На данный момент это файл pack-refs, но в будущем его можно будет адаптировать к другим форматам файлов.

Это первая из нескольких подзадач задачи «gc», которые можно выделить в их собственные задачи.
В этом процессе мы не должны изменять поведение задачи «gc», поскольку это остается способом по умолчанию для поддержания репозиториев в рабочем состоянии.
Создание новой задачи для одной из этих подзадач предоставляет только дополнительные параметры настройки для тех, кто предпочитает не использовать задачу «gc».
Конечно, возможно, чтобы задачи «gc» и «pack-refs» выполнялись регулярно.
Хотя они могут повторять усилия, они не противоречат разрушительным образом.

' auto_condition'указатель на функцию остается NULLсейчас.
Мы могли бы расширить это в будущем, чтобы иметь возможность проверки состояния, следует ли запускать pack-refs во время ' <tcode id="774775"></tcode>' ( мужчина ) .

git maintenanceтеперь включает в свою справочную страницу :

Задача собирает отдельные справочные файлы и собирает их в один файл. Это ускоряет операции, которые требуют перебора множества ссылок.

И он может работать по расписанию в рамках своей новой задачи pack-refs:

<tcode id="774769"></tcode>: инкрементальная стратегия запускает pack-refs еженедельно

Подписано: Деррик Столи
Рецензент: Тейлор Блау

Когда ' maintenance.strategy'опция конфигурации установлена ​​в' incremental', включен график обслуживания по умолчанию.
Добавьте к этой стратегии задачу «pack-refs» еженедельно.

git configтеперь включает в свою справочную страницу :

задача, но запускает prefetch и commit-graph задачи ежечасно, loose-objects и incremental-repack задачи ежедневно, а pack-refs задача еженедельно.

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