skip to content
Архитектура в курилке
Bob Ross

Чистим историю Git-а

/ чтиво на 4 минуты

Обновлено:
Содержание

Проблематика

Будем честны, все мы хотя бы раз коммитили файл с секретами, забывали добавить что-то в .gitignore или после git add . видели в коммите здоровый файл, которому бы место в Release-ах или в хранилище артефактов. Но суть в том, что git помнит все, и даже если мы удалили файл, переписали его, засквошили все коммиты, то все равно с помощью того же git reflog можно откопать старые данные. Поэтому расмотрим пару интересных программ для исправления этих ошибок.

git-filter-repo

Github

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

Terminal window
# Macos
brew install git-filter-repo
# Arch
sudo pacman -S git-filter-repo
# Python!
pip install git-filter-repo

Все! теперь мы можем зайти в любую репу и проверить, что filter-repo работает:

Terminal window
git filter-repo --analyze

Мы увидим что-то вроде:

Processed 8 blob sizes
Processed 1 commits
Writing reports to .git/filter-repo/analysis...done.

Это удобная штука, которая может подсказать, что в дальнейшем стоит почистить или правильно ли сработала предыдущая команда. Отчет - это просто набор текстовых файлов, которые можно просмотреть.

Общий подход

git-filter-repo - очень мощная утилита, пользоваться которой нужно очень острожно. Ведь любая из выполняемых команд по сути изменяет хэши коммитов, и требует push --force в оригинальную репу. Поэтому общий флоу работы такой:

  1. Клонируем свежую копию нашей репы (желательно в новую папку, чтобы случайно не напортачить чего-то в старой) и заходим в нее:
Terminal window
git clone <URL оригинальной репы> some_dir
cd some_dir
  1. Запускаем команды необходимые в данный момент:
Terminal window
git filter-repo <MAGIC>
  1. В результате у нас будет удален origin, поэтому его надо будет заново добавить:
Terminal window
git remote add origin <URL оригинальной репы>
  1. Пушим новые изменения:
Terminal window
git push --force --mirror origin
Ошибка

Лишние файлы

Самое типичное, с чем часто приходится сталкиваться.

TLDR

Terminal window
git clone <repo_url> <repo_dir>
cd <repo_dir>
git filter-repo --invert-paths --path <file_path>
git remote add origin <repo_url>
git push --force --mirror origin

Подробно

Рассмотрим на примере. Есть небольшой репозиторий, в историю которого по ошибке закрался файл .env, с чувствительными данными. Наш коллега удалил его, добавил в .gitignore, но если откатиться к старому коммиту, то он все равно есть:

Файл остается

Для удаления из истории одного файла выполняем такую команду:

Terminal window
git filter-repo --invert-paths --path <путь до файла от корня git репы>
Удаляем один файл

Лишние папки

Все аналогично инструкции по удалению лишних файлов.

Просто при указании --path пишем путь до директории.

Авторы коммитов

В нынешние времена часто мы не особо задумываемся над тем, где, что, какой автор у коммита. В больших Open-Source проектах это может быть крайне важно, будь то найти виновника, кто притащил червя в библиотеку, или же просто докапаться, чтобы понять, кто сломал тот или иной билд. Из-за подобныхвещей появились всякие приколы, вроде подписей для коммитов.

Кому-то может быть важно спрятать следы ИИ слопа, чтобы не тратить токены Claude на то, что можно легко и просто сделать парой команд в терминале.

В любом случае, поехали.

Для начала посмотрим ВСЕХ авторов ВСЕХ коммитов в нашем проекте.

Terminal window
git shortlog -sne --all

Выводим все авторов всех коммитов с их email-ами, количество коммитов, во всех ветках.

50 renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
17 Linus Torvalds <linus@linux.com>
8 Zhurik <zhurik@zhurik.com>
4 Some Dude <some@dude.com>
2 Cool Dude <user12343@users.noreply.github.com>

Предположим, что мы хотим стереть всю историю, которая была с Linus Torvalds, потому что это был просто прикол. И нам надо, чтобы вместо него был правильный email - zhurik@zhurik.com

Тогда команда будет следующая:

Terminal window
git filter-repo --email-callback '
if email == b"<old email>":
return b"<new email>"
return email
'

В нашем случае это будет:

Terminal window
git filter-repo --email-callback '
if email == b"linus@linux.com":
return b"zhurik@zhurik.com"
return email
'

После прогона можно еще раз проверить, что все ок:

Terminal window
git shortlog -sne --all
50 renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
17 Linus Torvalds <zhurik@zhurik.com>
8 Zhurik <zhurik@zhurik.com>
4 Some Dude <some@dude.com>
2 Cool Dude <user12343@users.noreply.github.com>

Все, наш Linus теперь имеет правильные коммиты, теперь надо выставить корректное имя. Синтаксис тут максимально похожий:

Terminal window
git filter-repo --name-callback '
if name == b"<old name>":
return b"<new name>"
return name
'

Если правки нужно делать для нескольких имен, то можно просто добавить условий:

Terminal window
git filter-repo --name-callback '
if name == b"<old name 1>":
return b"<new name 1>"
if name == b"<old name 2>":
return b"<new name 2>"
if name == b"<old name 3>":
return b"<new name 3>"
return name
'

Все, проверяем снова:

Terminal window
git shortlog -sne --all
50 renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
25 Zhurik <zhurik@zhurik.com>
4 Some Dude <some@dude.com>
2 Cool Dude <user12343@users.noreply.github.com>

Дальше все так, как и было раньше описано.

Terminal window
git remote add origin <URL>
git push --force --mirror origin

Шалось удалась.

Секреты

TBD

Сноски

  1. Файл, конечно, один, но все же рекомендую пользоваться пакетными менеджерами, а то мало ли.