Что делает git, когда мы делаем: git gc - git prune - PullRequest
0 голосов
/ 02 мая 2018

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

  • 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.

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

Спасибо

1 Ответ

0 голосов
/ 02 мая 2018

TL; DR

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

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

Long

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

Что делает 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) истечения срока действия reflogs и (2) выполнения git repack: это потому, что удаленная запись reflog, которая удаляется, может привести к тому, что объект станет не связанным, и, следовательно, не будет упакован а затем обрезать, чтобы он полностью исчез.

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

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

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

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

Объекты могут быть свободно , что означает, что они хранятся в виде файлов. Имя файла - это просто хэш-идентификатор. 2 Содержимое свободного объекта является zlib-deflated. Или объекты могут быть упакованы , что означает, что многие объекты хранятся в одном пакете. В этом случае содержимое не просто дефлируется, оно сначала дельта-сжато . Git выбирает объект base - часто последнюю версию некоторого большого двоичного объекта (файла) - и затем находит дополнительные объекты, которые могут быть представлены в виде серии команд: взять базовый файл, удалить некоторый текст с этим смещением добавить другой текст с другим смещением и т. д. Фактический формат файлов пакета - , документированный здесь , хотя и слегка. Обратите внимание, что в отличие от большинства систем управления версиями, дельта-сжатие происходит на уровне ниже абстракции хранимого объекта: Git сохраняет целые снимки, а затем выполняет дельта-сжатие позже на базовом объекты. Git по-прежнему обращается к объекту по имени хеш-идентификатора; просто чтение этого объекта включает чтение файла пакета, поиск объекта и лежащих в его основе дельта-баз и восстановление всего объекта на лету.

Существует общее правило для файлов пакета, которое гласит, что любой дельта-сжатый объект в файле пакета должен иметь все свои базы в одном и том же файле пакета. Это означает, что файл пакета является автономным: никогда не требуется открывать несколько дополнительных файлов пакета, чтобы извлечь объект из пакета, в котором есть объект. (Это конкретное правило может быть намеренно нарушено, создавая то, что Git называет thin pack , но они предназначены для использования только для отправки объектов по сетевому соединению в другой Git, который уже имеет базовые объекты. Другой Git должен «исправить» или «откармливать» тонкий пакет, чтобы создать обычный файл пакета, прежде чем оставить его позади для остальной части Git.

Достижимость объекта немного сложнее. Давайте сначала посмотрим на коммит достижимости .

Обратите внимание, что когда у нас есть объект фиксации, этот объект фиксации сам содержит несколько хеш-идентификаторов. У него есть один хэш-идентификатор для дерева, которое содержит снимок, который идет с этим коммитом. Он также имеет один или несколько хеш-идентификаторов для родительских коммитов , если только этот конкретный коммит не является root commit. Корневой коммит определяется как коммит без родителей, так что это несколько циклично: коммит имеет родителей, если у него нет родителей. Это достаточно ясно: учитывая некоторый коммит, мы можем нарисовать этот коммит как узел на графике со стрелками, выходящими из узла, по одной на каждого родителя:

<--o
   |
   v

Эти родительские стрелки указывают на родителя или родителей коммита. Учитывая последовательность коммитов с одним родителем, мы получаем простую линейную цепочку:

... <--o  <--o  <--o ...

НаКаждый из этих коммитов должен быть началом цепочки: это root коммит. Одним из них должен быть end , а это коммит tip . Все внутренние стрелки указывают назад (влево), поэтому мы можем нарисовать это без наконечников стрел, зная, что корень находится слева, а верхушка справа:

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 и читаем commit 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 есть пары :

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 index и любые ссылки через связанные добавленные рабочие деревья), обеспечивают точки входа в граф объектов. Из этих точек входа любой объект либо достижим, имеет одно или несколько имен, которые могут привести к нему, либо недоступен , что означает, что не существует имен, по которым можно найти сам объект. Я исключил аннотированные теги из этого описания, но они обычно находятся через имена тегов, и у аннотированного объекта тега есть одна ссылка на объект (произвольного типа объекта), которую он находит, что делает этот один объект достижимым, если сам объект тега доступен .

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

...