Удаление файла из истории
git rm перестаёт показывать файл в новых коммитах. С уже сделанными он не
делает ничего: блоб по-прежнему в базе объектов, по-прежнему в каждом клоне и
по-прежнему в одном git show от вас.
Это важно в двух случаях — когда файл был ключом доступа и когда он весил 400 МБ.
⌘K → Удалить файл из истории, либо правый клик по файлу — в дереве
проекта, в списке файлов коммита или в композере коммита. Обычно человек
осознаёт, что файл всё ещё в истории, именно на том коммите, который его
удалил, — поэтому выход находится и в этом меню.
Как найти путь
Два способа, потому что они отвечают на разные вопросы.
Ввести вручную — путь относительно корня репозитория, без ведущего слеша — когда вы уже знаете, что пришли удалять.
Просмотреть историю — когда не знаете. Здесь перечислены все пути, которые когда-либо коммитились, самые тяжёлые сверху, с числом версий у каждого и признаком, отслеживается ли он до сих пор. Удалённые пути помечены как таковые и обычно это как раз то, что нужно: файл, которого нет в рабочем дереве, но который есть в каждом клоне, — ровно тот случай, который обычный файловый диалог показать не может, потому что выбирать там нечего.
Тот же список отвечает и на другую причину прийти сюда — почему этот клон весит два гигабайта, — поскольку он отсортирован по числу байт, которые реально занимают блобы каждого пути. Выбор строки сразу же запускает измерение.

Измерьте, прежде чем соглашаться
Нажмите Измерить (или выберите строку). Пока ничего не записывается. Вы получаете:
| Коммитов будет переписано | Каждый коммит, начиная с первого, где файл присутствовал |
| Ветки / теги | Ссылки, которые сдвинутся |
| Занято блобами | Байты, которые эти версии реально занимают |
| Первый коммит | Место, откуда начинается переписывание — всё после него получит новый хеш |

Если счётчик нулевой — путь неверный. Обычно это опечатка или потерянный префикс каталога, а не отсутствие файла.
Что переписывание делает на самом деле
Gitcito копирует каждую ветку и каждый тег в
refs/gitcito/pre-purge/<timestamp>/…, а затем запускает:
git filter-branch --force \
--index-filter 'git rm --cached --ignore-unmatch -- <path>' \
--prune-empty --tag-name-filter cat -- --branches --tags
--index-filter переписывает индекс напрямую, вместо того чтобы выгружать
каждый коммит, — это разница между минутами и часами. --branches --tags
вместо --all выбрано намеренно: --all захватил бы и резервные ссылки, и
переписывание съело бы собственную страховку.
Коммиты, в которых не было ничего, кроме удаляемого файла, выбрасываются
(--prune-empty). Теги перенаправляются на свои переписанные коммиты.
Бэкап и почему место пока не возвращается
Чистку можно отменить, и цена этого в том, что место на диске не освобождается, пока вы не скажете. Пока существует бэкап, старые коммиты остаются достижимыми, и git их не соберёт.
| Действие | Эффект |
|---|---|
| Восстановить | Каждая ветка и каждый тег возвращаются на тот коммит, где они были до чистки; вместе с ними возвращается и файл |
| Удалить бэкап | Удаляет резервные ссылки, чистит reflog, запускает git gc --prune=now — место возвращено, чистка теперь необратима |
Два шага, а не один, потому что первый — это обратимая половина, а второй — нет.
Секрет всё равно смените
Если ключ хоть раз был отправлен на сервер, переписывание вашей истории не отменяет утечку. Кто-то мог его вытянуть; серверы форжей держат у себя объекты без ссылок; лог CI мог его напечатать. Переписывание останавливает дальнейшее распространение — оно не отменяет самого раскрытия.
Смените ключ. А потом чистите историю, чтобы следующий клонирующий его там не нашёл.
Чего это не сделает
- Оно не запушит. Переписывание локально. Опубликовать результат — значит сделать форс-пуш в каждую затронутую ветку, а всем остальным придётся заново клонировать или делать hard reset; за это решение отвечает защита от форс-пуша.
- Оно откажется работать на грязном рабочем дереве или посреди слияния/ребейза. Переписывание многократно двигает HEAD, и делать это вокруг незакоммиченной работы — верный способ её потерять.
- Оно переписывает по пути, а не по содержимому. Удалить секрет, вставленный
прямо в исходник, а не живущий в отдельном файле, — задача для фильтра по
содержимому: это территория
git filter-repo --replace-text, и Gitcito её не оборачивает. filter-branchмедленный на очень больших историях. Зато он есть в комплекте с git везде — поэтому его Gitcito и использует. На репозитории с десятками тысяч коммитовgit filter-repoв терминале — более быстрый инструмент.- Чужие клоны — не ваш репозиторий. Там останется старая история, пока их не переклонируют.