Когда именно git удаляет объекты: почему "git gc" не удаляет коммиты?

Я работаю над курсом git и хотел бы упомянуть, что потерянные реферы действительно не теряются до запуска git gc, Но проверив это, я обнаружил, что это не так. Даже после бега git gc --prune=all --aggressive потерянные рефери все еще там.

Я явно что-то не так понял. И прежде чем сказать что-то неправильное в курсе, я хочу разъяснить свои факты! Вот пример сценария, иллюстрирующий эффект:

 #!/bin/bash

 git init

 # add 10 dummy commits
 for i in {1..10}; do
     date > foo.txt
     git add foo.txt
     git commit -m "bump" foo.txt
     sleep 1
 done;

 CURRENT=$(git rev-parse HEAD)
 echo HEAD before reset: ${CURRENT}

 # rewind
 git reset --hard HEAD~5

 # add another 10 commits
 for i in {1..10}; do
     date > foo.txt
     git add foo.txt
     git commit -m "bump" foo.txt
     sleep 1
 done;

Этот скрипт добавит 10 фиктивных коммитов, сбросит до 5 коммитов в прошлом и добавит еще 10 коммитов. Непосредственно перед сбросом он напечатает хеш текущего заголовка.

Я ожидал бы потерять объект в CURRENT после запуска git gc --prune=all, Тем не менее, я все еще могу бежать git show на этот хэш.

Я понимаю, что после запуска git reset и добавив новые коммиты, я по сути создал новую ветку. Но моя первоначальная ветка больше не имеет ссылки, поэтому она не отображается в git log --all, Полагаю, он также не будет перенесен на любой пульт.

Мое понимание git gc было то, что удаляет эти объекты. Это не похоже на случай.

Зачем? И когда именно git gc удалить объекты?

1 ответ

Решение

Для того, чтобы объект был обрезан, он должен соответствовать двум критериям. Один из них связан с датой / временем: он должен был быть создан достаточно давно, чтобы созреть для сбора. Часть "достаточно давно" - это то, что вы устанавливаете --prune=all: вы переопределяете нормальную настройку "минимум две недели".

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

Для каждого такого коммита есть две записи reflog: одна для HEAD и один для имени ветви, к которой HEAD сам ссылался во время совершения (в этом случае refs/heads/master филиал master). Каждая запись reflog имеет свою собственную отметку времени, и git gc также истекает срок действия записей reflog, хотя с более сложным набором правил, чем простое значение по умолчанию "14 дней" для срока действия объекта. 2

Следовательно, git gc может сначала удалить все записи reflog, которые хранят старый объект, а затем удалить объект. Это просто не происходит здесь.

Чтобы просмотреть или даже удалить записи журнала вручную, используйте git reflog, Обратите внимание, что git reflog отображает записи, запустив git log с -g / --walk-reflogs опция (плюс некоторые дополнительные опции форматирования дисплея). Вы можете запустить git reflog --all --expire=all чтобы очистить все, хотя это дубинка, когда скальпель может быть более подходящим. использование --expire-unreachable для немного большей избирательности. Подробнее об этом см. git log документация и, конечно, git reflog документация


1 Некоторые файловые системы Unix-y вообще не хранят время создания файла ("рождение"): st_ctime поле stat структура - это время изменения инода, а не время создания. Если есть время создания, оно находится в st_birthtime или же st_birthtimespec, 3 Однако каждый объект Git доступен только для чтения, поэтому время создания файла также является временем его изменения. следовательно st_mtime, который всегда доступен, дает время создания объекта.

2 Точные правила описаны в git gc документация, но я думаю, что по умолчанию 30 дней для недостижимых коммитов и 90 дней для достижимых коммитов - это неплохое резюме. Однако определение достижимости здесь необычно: оно означает достижимость по текущему значению ссылки, для которой этот reflog содержит старые значения. То есть, если мы смотрим на рефлог для master мы находим коммит, который master идентифицирует (например, 1234567), а затем посмотреть, если каждая запись reflog для master (например, master@{27}) достижим от этого конкретного коммита (1234567 снова).

3 Эта конкретная путаница с именами преподносится вам специалистами по стандартизации POSIX.:-) st_birthtimespec поле является struct timespec, который записывает как секунды, так и наносекунды.

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