Что делает 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.

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

Спасибо


person L Y E S - C H I O U K H    schedule 02.05.2018    source источник
comment
Возможный дубликат Есть ли разница между `git gc` и` git repack -ad; git prune`?   -  person phd    schedule 02.05.2018
comment
См. Также stackoverflow.com/a/37734293/7976758   -  person phd    schedule 02.05.2018


Ответы (2)


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
comment
Спасибо за подробности ... Но есть одна вещь, на которую я не могу найти ответа ... Предположим, я только что проделал кучу работы (возможно, включая удаление веток) ... Я хочу знать, какой файл исчезнет, ​​если я оставлю систему в покое на долгое время (достаточно долго, чтобы истек срок действия рефлогов и т. д.)? - person David V. Corbin; 10.06.2021
comment
@ DavidV.Corbin: вам нужна модифицированная версия git fsck, которая (а) пропускает журналы рефлогов, (б) находит висячие блобы, а затем (в) возвращает рефлоги и находит коммиты, которые ссылаются на эти висячие блобы, и накапливает имена файлов, как показано в тех коммитах. Написание этой работы - нетривиальная задача, независимо от того, на каком языке вы можете ее выбрать. (Здесь я предполагаю, что под файлом, который исчезнет, вы имеете в виду файлы, которые находятся в коммитах, которые будут быть отброшенным.) - person torek; 11.06.2021
comment
Однако обратите внимание, что файлы не уходят сами по себе: кто-то должен запустить git gc (или новомодный git maintenance; см. Ответ VonC), чтобы Git истек срок действия старых журналов рефлогов, пометил доступные объекты, перепакуйте старые пакеты (хотя файлы, помеченные .keep, не будут выброшены!), и, наконец, очистите старые неиспользуемые объекты. - person torek; 11.06.2021
comment
благодаря. Я стараюсь избегать потери данных (то есть даже самого тривиального изменения, содержащегося в одной фиксации и отмененного в более поздней фиксации). Эффективно гарантируя, что репозиторий является неизменной записью. Чем дальше я исследую это, тем больше убеждаюсь, что Git (хотя он и хорош для огромного количества вещей), скорее всего, не подходит для этого случая. - person David V. Corbin; 11.06.2021
comment
Хм, ну, Git с ограничением - что никакое имя ветки или тега никогда не перемещается назад, в том числе запрещающие операции смещения - может соответствовать этой цели, но сам Git не налагает этого ограничения. Для этого вам понадобится что-то внешнее. Если есть официальная точка перетаскивания (чтобы коммиты могли быть удалены, пока они не были официально отброшены), вы можете принудительно применить это на сервере, используя простой хук предварительного приема, который запрещает все удаления имен и любые обратные движения. Вероятно, вы захотите разрешить удаление определенных имен после слияния, немного усложнив это. - person torek; 11.06.2021

После недавнего добавления git maintenance команды (Git 2.29 (4 квартал 2020 г.)) заменой git gc -prune будет:

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

В Git 2.31 (первый квартал 2021 г.) git maintenance < sup> (man) освоил новую pack-refs задачу обслуживания.

См. commit acc1c4d, commit 41abfe1 (09 февраля 2021 г.), (derrickstolee).
(Объединено Junio ​​C Hamano - gitster - в commit d494433, 17 февраля 2021 г.)

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

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

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

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

git describe --tags --exact-match HEAD

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

Добавьте новую задачу обслуживания 'pack-refs'.
Она запускает '_ 11_ '(man), чтобы переместить свободные ссылки в упакованную форму.
На данный момент это файл упакованных ссылок, но его можно адаптировать к другому файлу. форматы в будущем.

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

Указатель на функцию 'auto_condition' пока оставлен NULL.
В будущем мы могли бы расширить это, чтобы иметь возможность проверять, нужно ли запускать pack-refs во время '_ 14_ '(man).

git maintenance теперь включает в свою >:

pack-refs

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

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

maintenance: добавочная стратегия запускается еженедельно

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

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

git config теперь включает в свою >:

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


git maintenance register href = "https://git-scm.com/docs/git-main maintenance#Documentation/git-mainasted.txt-register" rel = "nofollow noreferrer"> man) не удалось зарегистрировать команду голые репозитории, исправленные с помощью Git 2.31 (первый квартал 2021 года).

См. фиксацию 26c7974 (23 февраля 2021 г.) по Эрик Саншайн (sunshineco).
(Объединено Junio ​​C Hamano - gitster - в совершить d166 a>, 25 февраля 2021 г.)

maintenance: исправить неверный путь maintenance.repo с пустым репозиторием

Автор: Клемент Мойруд
Подписан: Эрик Саншайн

Задачи периодического обслуживания, настроенные с помощью git maintenance start < sup> (man) вызвать git for-each-repo (man) для запуска _ 34_ (man) на каждом пути, заданном многозначным глобальным переменная конфигурации maintenance.repo.
Поскольку git for-each-repo будет li kely запускаться вне репозиториев, требующих периодического обслуживания, обязательно, чтобы пути к репозиториям, указанные в maintenance.repo, были абсолютными.

К сожалению, однако, git maintenance register (человек) ничего, чтобы гарантировать, что пути, которые он присваивает maintenance.repo, действительно абсолютны, и может фактически - особенно в случае чистого репозитория - вместо этого назначить относительный путь к maintenance.repo.
Исправьте эту проблему, преобразовав все пути в абсолютное перед присвоением им maintenance.repo.

При этом также исправьте git maintenance unregister < sup> (человек) для преобразования путей в абсолютные, чтобы гарантировать, что он может правильно удалить из maintenance.repo путь, назначенный через git maintenance register.

person VonC    schedule 21.02.2021