Перейти к основному содержимому

Удалить большой файл из истории коммитов в Git

· 7 мин. чтения

1. Обзор

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

2. Использование git filter-branch

Это наиболее часто используемый метод, и он помогает нам переписать историю совершенных ветвей.

Например, предположим, что мы по ошибке поместили файл большого двоичного объекта в папку проекта, и после его удаления мы все еще видим этот файл в нашей истории git:

$ git log --graph --full-history --all --pretty=format:"%h%x09%d%x20%s"
* 9e87646 (HEAD -> master) blob file removed
* 2583677 blob file
* 34ea256 my first commit

Мы можем удалить файл blob из нашей истории git, переписав дерево и его содержимое с помощью этой команды:

$ git filter-branch --tree-filter 'rm -f blob.txt' HEAD

Здесь опция rm удаляет файл из дерева. Кроме того, параметр -f предотвращает сбой команды, если файл отсутствует в других зафиксированных каталогах в нашем проекте. Без опции -f команда может завершиться ошибкой, если в нашем проекте более одного каталога.

Вот наш журнал git после запуска команды:

* 8f39d86        (HEAD -> master) blob file removed
* e99a81d blob file
| * 9e87646 (refs/original/refs/heads/master) blob file removed
| * 2583677 blob file
|/
* 34ea256 my first commit

Мы можем заменить HEAD ключом SHA1 истории коммитов, чтобы свести к минимуму перезапись.

Наш журнал git все еще содержит ссылку на удаленный файл. Мы можем удалить ссылку, обновив наше репо:

$ git update-ref -d refs/original/refs/heads/master

Параметр -d удаляет указанную ссылку после проверки того, что она все еще содержит старые значения .

Нам нужно записать, что наша ссылка изменилась в репозитории:

$ git reflog expire --expire=now --all

Подкоманда expire удаляет старые записи журнала ссылок.

Наконец, нам нужно очистить и оптимизировать наш репозиторий:

$ git gc --prune=now

Параметр –prune=now удаляет незакрепленные объекты независимо от их возраста.

После запуска команды вот наш журнал git:

* 6f49d86        (HEAD -> master) my first commit

Мы видим, что ссылки были удалены.

В качестве альтернативы мы можем запустить:

$ git filter-branch --index filter 'git rm --cached --ignore-unmatched blob.txt' HEAD

Это работает точно так же, как древовидный фильтр, но быстрее, потому что перезаписывает только индекс, т. е. рабочий каталог. Подкоманда –ignore-unmatched предотвращает сбой команды, если файл отсутствует в других зафиксированных каталогах в нашем проекте.

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

3. Использование git-filter-repo

Альтернативный подход — использовать команду git-filter-repo . Это стороннее дополнение, более простое в использовании и более быстрое, чем другие подходы. Более того, это решение рекомендовано в официальной документации git .

3.1. Монтаж

Требуется как минимум python3 >= 3.5 и git >= 2.22.0; для некоторых функций требуется git 2.24.0 или выше .

Мы собираемся установить git-filter-repo на нашу машину с Linux. Руководство по установке Windows можно найти в документации .

Во- первых, мы собираемся установить python-pip и git-filter-repo с помощью следующих команд:

$ sudo apt install python3-pip
$ pip install --user git-filter-repo

Кроме того, мы можем использовать следующие команды для установки git-filter-repo :

# Add to bashrc.
export PATH="${HOME}/bin:${PATH}"

mkdir -p ~/bin
wget -O ~/bin/git-filter-repo https://raw.githubusercontent.com/newren/git-filter-repo/7b3e714b94a6e5b9f478cb981c7f560ef3f36506/git-filter-repo
chmod +x ~/bin/git-filter-repo

3.2. Удаление файла

Давайте запустим команду, чтобы проверить наш журнал git:

$ git log --graph --full-history --all --pretty=format:"%h%x09%d%x20%s"
* ee36517 (HEAD -> master) blob.txt removed
* a480073 project folder

Следующее, что нам нужно, это проанализировать наш репо:

$ git filter-repo --analyze
Processed 5 blob sizes
Processed 2 commits
Writing reports to .git/filter-repo/analysis...done.

Это создает каталог отчетов о состоянии нашего репо. Отчет можно найти по адресу .git/filter-repo/analysis. Эта информация может помочь определить, что фильтровать при последующем запуске. Это также может помочь нам определить, действительно ли наша предыдущая команда фильтрации сделала то, что мы хотели.

Затем давайте запустим эту команду с опцией --path-match, которая поможет указать файл для включения в отфильтрованную историю:

$ git filter-repo --force --invert-paths --path-match blob.txt

Вот наш новый журнал git:

* 8940776        (HEAD -> master) project folder

После выполнения он изменит хэши измененного коммита.

4. Использование BRG Repo-Cleaner

Еще один отличный вариант — BRG Repo-Cleaner , стороннее дополнение, написанное на Java.

Это быстрее, чем подход git filter-branch . Кроме того, он удобен для удаления больших файлов, паролей, учетных данных и других личных данных .

Предположим, мы хотим удалить файлы больших двоичных объектов размером более 200 МБ. Это дополнение позволяет легко сделать это:

$ java -jar bfg.jar --strip-blob-bigger-than 200M my-repo.git

Затем давайте запустим эту команду, чтобы очистить мертвые данные:

$ git gc --prune=now --aggressive

5. Использование git-rebase

Нам нужен ключ SHA1 из журнала git, чтобы использовать этот подход:

$ git log --graph --full-history --all --pretty=format:"%h%x09%d%x20%s"
* 535f7ea (HEAD -> master) blob file removed
* 8bffdfa blob file
* 5bac30b index.html

Наша цель — удалить файл BLOB-объектов из истории коммитов. Поэтому мы будем использовать ключ SHA1 из истории записи, предшествующей той, которую мы хотим удалить.

Этой командой мы входим в интерактивную перебазировку:

$ git rebase -i 5bac30b

Откроется наш редактор nano , показывающий:

pick 535f7ea blob file removed
pick 8bffdfa blob file

# Rebase 5bac30b..535f7ea onto 535f7ea (2 command)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# . create a merge commit using the original merge commit's
# . message (or the oneline, if no original merge commit was
# . specified). Use -c <commit> to reword the commit message.

Теперь мы изменим это, удалив текст « выбрать файл большого двоичного объекта 535f7ea удален ». Это помогает нам изменить историю коммитов и удалить историю, которую мы удалили ранее.

Затем мы сохраняем файл и выходим из редактора, который приводит нас к терминалу со следующим сообщением:

interactive rebase in progress; onto 535f7ea
Last command done (1 command done):
pick 535f7ea blob file removed
No commands remaining.
You are currently rebasing branch 'master' on '535f7ea'.
(all conflicts fixed: run "git rebase --continue")

Наконец, давайте продолжим операцию перебазирования:

$ git rebase --continue
Successfully rebased and updated refs/heads/master.

Затем мы можем проверить нашу историю коммитов:

$ git log --graph --full-history --all --pretty=format:"%h%x09%d%x20%s"
* 5bac30b (HEAD -> master) index.html

Следует отметить, что этот подход не такой быстрый, как git-filter-repo .

6. Заключение

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