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(правку содержимого объекта руками). Это работа текстового редактора над сырым объектом и граната с выдернутой чекой, если обвязать её интерфейсом.
См. также: Обозреватель объектов · Удалить файл из истории · Обслуживание репозитория