Репозиторий и история

Replace и графты

git replace говорит git: там, где ты собирался прочитать объект A, читай вместо него B. Ничего не переписывается. Ни один sha не меняется. Каждый коммит остаётся ровно там, где был, — git просто смотрит по дороге в другое место.

Звучит как курьёз ровно до того момента, когда вам нужен клон поменьше. Тогда это честная альтернатива переписыванию истории: приграфтите коммит к пустому списку родителей, и всё, что было до него, выпадает из лога, из графа и из каждого клона, сделанного оттуда, — при этом всё ещё хранится, всё ещё доступно для фетча и отделено от возвращения одной удалённой ссылкой.

⌘K → Replace и графты.

Существующие замены и форма графта под ними

Графты

Что даёте Что получаете
Коммит, без родителей Этот коммит становится началом истории
Коммит, с одним или несколькими родителями Он цепляется туда, а не туда, где на самом деле стоит

Вторая форма — самая интересная. Держите полную историю в архивном репозитории, обрежьте рабочий, и графт, указывающий на вершину архива, снова соединяет их — тот же трюк, которым GitHub отдаёт shallow-клон, который всё ещё можно углубить.

Графт к пустому списку родителей сначала переспрашивает, потому что «истории больше нет» и «история спрятана» из лога выглядят одинаково, а это совершенно разные вещи. Объекты живут, пока их не подчистит gc; см. обслуживание.

Как с этим жить

Замены — это ссылки, под refs/replace/. Отсюда три следствия, о которых стоит знать:

  • Они локальны, пока их не отправишь: git push origin "refs/replace/*" делится ими, а тот, кто склонирует без них, увидит нетронутую историю.
  • Отмена работает — удаление ссылки немедленно восстанавливает настоящее происхождение, и Gitcito записывает графт как отменяемое действие, как и всё прочее.
  • core.useReplaceRefs=false заставляет git разом игнорировать их все. Переключатель здесь пишет ровно это, и диалог об этом говорит, когда он выключен, — потому что репозиторий, который молча игнорирует собственные замены, место запутанное.

Из командной строки git --no-replace-objects log показывает настоящую историю, не меняя никаких настроек.

Когда браться за это, а не за переписывание

Цель Инструмент
Клон слишком большой, с историей всё в порядке Графт — ничего не переписано, обратимо
Секрет или огромный блоб должен исчезнуть Удалить файл из истории — настоящее переписывание
Просто хочется скачать поменьше, один раз git clone --depth — shallow, никакими ссылками управлять не надо

Графт ничего не удаляет. Если вы хотите убрать старые коммиты потому, что в них лежит то, чего там вообще не должно было быть, — это не та страница: объекты по-прежнему на месте, по-прежнему достаются по sha и по-прежнему есть в каждом существующем клоне.

Ограничения, о которых стоит знать

  • То, что вы видите, перестаёт совпадать с тем, что хранится. Это и есть фича, и она же — опасность. Всякий, кто отлаживает клон с заменами, должен знать, что они есть.
  • По умолчанию замены никуда не путешествуют, так что git log коллеги и ваш могут законно расходиться.
  • Замена прячет коммит от инструментов, но не от git. git cat-file и обозреватель объектов всё так же открывают оригинал по sha.
  • Gitcito не предлагает git replace --edit (правку содержимого объекта руками). Это работа текстового редактора над сырым объектом и граната с выдернутой чекой, если обвязать её интерфейсом.

См. также: Обозреватель объектов · Удалить файл из истории · Обслуживание репозитория

Улучшить этот перевод на GitHub