Когда именно 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
, который записывает как секунды, так и наносекунды.