Как удалить/удалить большой файл из истории фиксации в репозитории Git?

Иногда я удалял DVD-рип в проект веб-сайта, затем небрежно git commit -a -m ..., и zap, репо было раздуто на 2,2 концерта. В следующий раз я внес некоторые изменения, удалил видеофайл и все зафиксировал, но сжатый файл все еще находится в репозитории в истории.

Я знаю, что могу начинать ветки с этих коммитов и переставлять одну ветку на другую. Но что мне делать, чтобы объединить 2 коммита, чтобы большой файл не отображался в истории и был очищен в процессе сбора мусора?

Ответ 1

Используйте BFG Repo-Cleaner, более быструю и быструю альтернативу git-filter-branch, специально разработанную для удаления нежелательных файлов из истории Git.

Внимательно следуйте инструкциям основная часть - это:

$ java -jar bfg.jar --strip-blobs-bigger-than 100M my-repo.git

Любые файлы размером более 100 МБ (которые не находятся в последней фиксации) будут удалены из истории хранилища Git. Затем вы можете использовать git gc для удаления мертвых данных:

$ git gc --prune=now --aggressive

BFG обычно не менее 10-50x быстрее, чем запуск git-filter-branch и, как правило, проще в использовании.

Полное раскрытие: я являюсь автором BFG Repo-Cleaner.

Ответ 2

То, что вы хотите сделать, очень разрушительно, если вы опубликовали историю другим разработчикам. См. "Восстановление из восходящей базы" в документации git rebase для необходимых шагов после восстановления вашей истории.

У вас есть как минимум два варианта: git filter-branch и интерактивная перестановка, как описано ниже.

Используя git filter-branch

У меня была аналогичная проблема с массивными двоичными данными теста из импорта Subversion и написано о удалении данных из репозитория git.

Скажите, что ваша история git:

$ git lola --name-status
* f772d66 (HEAD, master) Login page
| A     login.html
* cb14efd Remove DVD-rip
| D     oops.iso
* ce36c98 Careless
| A     oops.iso
| A     other.html
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html

Обратите внимание, что git lola является нестандартным, но очень полезным псевдонимом. С помощью переключателя --name-status мы можем видеть изменения дерева, связанные с каждой фиксацией.

В "Неосторожном" фиксации (чье имя объекта SHA1 - ce36c98) файл oops.iso - это DVD-рип, добавленный случайно, и удаленный в следующем commit, cb14efd. Используя технику, описанную в вышеупомянутом блоге, команда для выполнения:

git filter-branch --prune-empty -d /dev/shm/scratch \
  --index-filter "git rm --cached -f --ignore-unmatch oops.iso" \
  --tag-name-filter cat -- --all

Параметры:

  • --prune-empty удаляет коммиты, которые становятся пустыми (т.е. не меняют дерево) в результате операции фильтра. В типичном случае этот параметр создает более чистую историю.
  • -d указывает временный каталог, который еще не существует для создания истории фильтров. Если вы работаете с современным дистрибутивом Linux, указание дерева в /dev/shm приведет к более быстрому выполнению.
  • --index-filter является основным событием и работает против индекса на каждом этапе истории. Вы хотите удалить oops.iso везде, где он найден, но он не присутствует во всех коммитах. Команда git rm --cached -f --ignore-unmatch oops.iso удаляет DVD-рип, когда он присутствует, и в противном случае не работает.
  • --tag-name-filter описывает, как переписывать имена тегов. Фильтр cat - это операция идентификации. В вашем репозитории, как и в примере выше, могут не быть никаких тегов, но я включил эту опцию для полной общности.
  • -- указывает конец опций git filter-branch
  • --all после -- является сокращением для всех ссылок. В вашем репозитории, как и в примере выше, может быть только один ref (master), но я включил эту опцию для полной общности.

После некоторого взбалтывания история теперь:

$ git lola --name-status
* 8e0a11c (HEAD, master) Login page
| A     login.html
* e45ac59 Careless
| A     other.html
| * f772d66 (refs/original/refs/heads/master) Login page
| | A   login.html
| * cb14efd Remove DVD-rip
| | D   oops.iso
| * ce36c98 Careless
|/
|   A   oops.iso
|   A   other.html
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html

Обратите внимание, что новый "небрежный" commit добавляет только other.html и что фиксация "Удалить DVD-рип" больше не находится на главной ветке. Филиал с меткой refs/original/refs/heads/master содержит ваши первоначальные фиксации на случай, если вы допустили ошибку. Чтобы удалить его, выполните шаги в Контрольный список для сокращения хранилища.

$ git update-ref -d refs/original/refs/heads/master
$ git reflog expire --expire=now --all
$ git gc --prune=now

Для более простой альтернативы, клонируйте репозиторий, чтобы отбросить нежелательные биты.

$ cd ~/src
$ mv repo repo.old
$ git clone file:///home/user/src/repo.old repo

Использование file:///... clone URL копирует объекты, а не создает только жесткие ссылки.

Теперь ваша история:

$ git lola --name-status
* 8e0a11c (HEAD, master) Login page
| A     login.html
* e45ac59 Careless
| A     other.html
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html

Имена объектов SHA1 для первых двух коммитов ( "Индекс" и "Страница администратора" ) остались теми же, потому что операция фильтра не изменяла эти коммиты. "Небрежно" потерял oops.iso, а "Страница входа" получил нового родителя, поэтому их SHA1 изменились.

Интерактивная переадресация

С историей:

$ git lola --name-status
* f772d66 (HEAD, master) Login page
| A     login.html
* cb14efd Remove DVD-rip
| D     oops.iso
* ce36c98 Careless
| A     oops.iso
| A     other.html
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html

вы хотите удалить oops.iso из "Careless", как если бы вы его еще не добавили, а затем "Удалить DVD-рип" вам бесполезно. Таким образом, наш план, включающий интерактивную переустановку, заключается в том, чтобы сохранить "страницу администратора", изменить "небрежно" и отказаться от "Удалить DVD-рип".

Запуск $ git rebase -i 5af4522 запускает редактор со следующим содержимым.

pick ce36c98 Careless
pick cb14efd Remove DVD-rip
pick f772d66 Login page

# Rebase 5af4522..f772d66 onto 5af4522
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit log message
#  x, exec = run command (the rest of the line) using shell
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

Выполняя наш план, мы его модифицируем до

edit ce36c98 Careless
pick f772d66 Login page

# Rebase 5af4522..f772d66 onto 5af4522
# ...

То есть мы удаляем строку с помощью "Удалить DVD-рип" и изменяем операцию "Небрежно" как edit, а не pick.

Сохранить-выключение редактора поместит нас в командной строке со следующим сообщением.

Stopped at ce36c98... Careless
You can amend the commit now, with

        git commit --amend

Once you are satisfied with your changes, run

        git rebase --continue

Как говорится в сообщении, мы находимся в команде "Careless", которую хотим изменить, поэтому мы запускаем две команды.

$ git rm --cached oops.iso
$ git commit --amend -C HEAD
$ git rebase --continue

Первый удаляет оскорбительный файл из индекса. Второй изменяет или изменяет "Небрежно" как обновленный индекс, а -C HEAD инструктирует git повторно использовать старое сообщение фиксации. Наконец, git rebase --continue выполняет оставшуюся часть операции rebase.

Это дает историю:

$ git lola --name-status
* 93174be (HEAD, master) Login page
| A     login.html
* a570198 Careless
| A     other.html
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html

что вы хотите.

Ответ 3

Почему бы не использовать эту простую, но мощную команду?

git filter-branch --tree-filter 'rm -f DVD-rip' HEAD

Параметр --tree-filter запускает указанную команду после каждой проверки проекта, а затем подтверждает результаты. В этом случае вы удаляете файл с именем DVD-rip из каждого снимка, независимо от того, существует он или нет.

Если вы знаете, в каком коммите появился огромный файл (скажем, 35dsa2), вы можете заменить HEAD на 35dsa2..HEAD, чтобы избежать переписывания слишком большого количества истории, что позволит избежать расходящихся коммитов, если вы еще не нажали. Этот комментарий любезно @alpha_989 кажется слишком важным, чтобы оставить его здесь.

Смотрите эту ссылку.

Ответ 4

(Лучший ответ, который я видел в этой проблеме: fooobar.com/questions/8406/..., скопирован здесь, так как этот поток выглядит высоко в ранжировании поиска Google, но другой "т)

🚀 Быстрое быстрое однослойное обтекание оболочки 🚀

Эта оболочка script отображает все объекты blob в репозитории, отсортированные от минимального до самого большого.

Для моего образца репо он работал примерно в 100 раз быстрее, чем другие, найденные здесь.
На моей надежной системе Athlon II X4 она обрабатывает репозиторий ядра Linux с его 5 622 155 объектами всего за минуту.

База Script

git rev-list --objects --all \
| git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' \
| awk '/^blob/ {print substr($0,6)}' \
| sort --numeric-sort --key=2 \
| cut --complement --characters=13-40 \
| numfmt --field=2 --to=iec-i --suffix=B --padding=7 --round=nearest

Когда вы запускаете код выше, вы получите хороший человеко-читаемый вывод следующим образом:

...
0d99bb931299  530KiB path/to/some-image.jpg
2ba44098e28f   12MiB path/to/hires-image.png
bd1741ddce0d   63MiB path/to/some-video-1080p.mp4

🚀 Быстрое удаление файлов 🚀

Предположим, что вы хотите удалить файлы a и b из каждой транзакции, достижимой из HEAD, вы можете использовать эту команду:

git filter-branch --index-filter 'git rm --cached --ignore-unmatch a b' HEAD

Ответ 5

Эти команды работали в моем случае:

git filter-branch --force --index-filter 'git rm --cached -r --ignore-unmatch oops.iso' --prune-empty --tag-name-filter cat -- --all
rm -rf .git/refs/original/
git reflog expire --expire=now --all
git gc --prune=now
git gc --aggressive --prune=now

Он немного отличается от приведенных выше версий.

Для тех, кому нужно нажать это на github/bitbucket (я тестировал это только с битбакетом):

# WARNING!!!
# this will rewrite completely your bitbucket refs
# will delete all branches that you didn't have in your local

git push --all --prune --force

# Once you pushed, all your teammates need to clone repository again
# git pull will not work

Ответ 6

После того, как вы выполнили практически каждый ответ в SO, я, наконец, нашел этот камень, который быстро удалил и удалил большие файлы в моем репозитории и снова смог синхронизировать: http://www.zyxware.com/articles/4027/how-to-delete-files-permanently-from-your-local-and-remote-git-repositories

CD в локальную рабочую папку и выполните следующую команду:

git filter-branch -f --index-filter "git rm -rf --cached --ignore-unmatch FOLDERNAME" -- --all

замените FOLDERNAME файлом или папкой, которую вы хотите удалить из данного репозитория git.

Как только это будет сделано, запустите следующие команды для очистки локального репозитория:

rm -rf .git/refs/original/
git reflog expire --expire=now --all
git gc --prune=now
git gc --aggressive --prune=now

Теперь нажмите все изменения в удаленном репозитории:

git push --all --force

Это очистит удаленный репозиторий.

Ответ 7

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

$ git filter-branch --index-filter "git rm -rf --cached --ignore-unmatch YOURFILENAME" HEAD
$ rm -rf .git/refs/original/ 
$ git reflog expire --all 
$ git gc --aggressive --prune
$ git push origin master --force

Ответ 8

git filter-branch --tree-filter 'rm -f path/to/file' HEAD работал очень хорошо для меня, хотя я столкнулся с той же проблемой, что описал здесь, которую я решил, выполнив это предложение.

В книге pro git есть целая глава в история перезаписи - посмотрите на filter-branch/Удаление файла из каждой комманды.

Ответ 9

Если вы знаете, что ваш коммит был последним, а не прошел через все дерево, выполните следующие действия: git filter-branch --tree-filter 'rm LARGE_FILE.zip' HEAD~10..HEAD

Ответ 10

Я столкнулся с этим с помощью учетной записи bitbucket, где я случайно хранил огромные резервные копии *.jpa моего сайта.

git filter-branch --prune-empty --index-filter 'git rm -rf --cached --ignore-unmatch MY-BIG-DIRECTORY-OR-FILE' --tag-name-filter cat -- --all

Поместите MY-BIG-DIRECTORY в нужную папку, чтобы полностью переписать историю (включая теги).

источник: http://naleid.com/blog/2012/01/17/finding-and-purging-big-files-from-git-history

Ответ 11

Вы можете сделать это, используя команду branch filter:

git filter-branch --tree-filter 'rm -rf path/to/your/file' HEAD

Ответ 12

Я в основном делал то, что было на этом ответе: fooobar.com/questions/8976/...

(для истории я скопирую его здесь)

$ git filter-branch --index-filter "git rm -rf --cached --ignore-unmatch YOURFILENAME" HEAD
$ rm -rf .git/refs/original/ 
$ git reflog expire --all 
$ git gc --aggressive --prune
$ git push origin master --force

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

# First, apply what in the answer linked in the front
# and before doing the gc --prune --aggressive, do:

# Go back at the origin of the repository
git checkout -b newinit <sha1 of first commit>
# Create a parallel initial commit
git commit --amend
# go back on the master branch that has big file
# still referenced in history, even though 
# we thought we removed them.
git checkout master
# rebase on the newinit created earlier. By reapply patches,
# it will really forget about the references to hidden big files.
git rebase newinit

# Do the previous part (checkout + rebase) for each branch
# still connected to the original initial commit, 
# so we remove all the references.

# Remove the .git/logs folder, also containing references
# to commits that could make git gc not remove them.
rm -rf .git/logs/

# Then you can do a garbage collection,
# and the hidden files really will get gc'ed
git gc --prune --aggressive

My repo (.git) изменился с 32 МБ на 388 КБ, что даже ветвь фильтра не может очистить.

Ответ 13

git filter-branch - это мощная команда, которую вы можете использовать для удаления огромного файла из истории коммитов. Файл останется на некоторое время, и Git удалит его в следующей сборке мусора. Ниже представлен полный процесс удаления файлов из истории коммитов. В целях безопасности сначала запустите команду в новой ветке:

# Do it in a new testing branch
$ git checkout -b test

# Remove file-name from every commit on the new branch
# --index-filter, rewrite index without checking out
# --cached, remove it from index but not include working tree
# --ignore-unmatch, ignore if files to be removed are absent in a commit
# HEAD, execute the specified command for each commit reached from HEAD by parent link

$ git filter-branch --index-filter 'git rm --cached --ignore-unmatch file-name' HEAD

# The output is OK, reset it to the prior branch master
$ git checkout master
$ git reset --soft test

# Remove test branch
$ git branch -d test

# Push it with force
$ git push --force origin master

Ответ 14

Используйте Git Расширения, это инструмент пользовательского интерфейса. У этого есть плагин с именем "Найти большие файлы", который находит файлы lage в репозиториях и позволяет удалить их permenently.

Не используйте 'git filter-branch' перед использованием этого инструмента, так как он не сможет найти файлы, удаленные с помощью 'filter-branch' (Altough 'filter-branch' не удаляет файлы полностью из файлы пакета репозитория).

Ответ 15

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

Чтобы все ухудшилось, перезагрузка тоже нелегкая, потому что любые ссылки на blob предотвратят очистку сборщика мусора git. Это включает в себя удаленные ссылки и ссылки reflog.

Я собрал git forget-blob, немного script, который пытается удалить все эти ссылки, а затем использует git filter-branch для перезаписи каждого фиксации в ветке.

Как только ваш blob полностью не найден, git gc избавится от него

Использование довольно просто git forget-blob file-to-forget. Вы можете получить дополнительную информацию здесь.

https://ownyourbits.com/2017/01/18/completely-remove-a-file-from-a-git-repository-with-git-forget-blob/

Я собрал это вместе благодаря ответам из Qaru и некоторым блогам. Кредиты для них!

Ответ 16

Кроме git filter-branch (медленное, но чистое решение git) и BFG (проще и эффективнее), есть еще один инструмент для фильтрации с хорошей производительностью:

https://github.com/xoofx/git-rocket-filter

Из его описания:

Назначение git-rocket-filter аналогично команде git-filter-branch, но предоставляет следующие уникальные функции:

  • Быстрая перезапись коммитов и деревьев (порядка х10 до х100).
  • Встроенная поддержка как белого списка с --keep (сохраняет файлы или каталоги), так и черного списка с опциями --remove.
  • Использование шаблона, подобного .gitignore, для фильтрации деревьев
  • Быстрый и простой С# Scripting для фильтрации коммитов и фильтрации дерева
  • Поддержка сценариев в древовидной фильтрации для каждого шаблона файла/каталога
  • Автоматически удаляет пустой/неизменный коммит, включая коммиты слияния