Как удалить / удалить большой файл из истории коммитов в репозитории Git? - PullRequest
597 голосов
/ 20 января 2010

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

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

Ответы [ 15 ]

514 голосов
/ 29 января 2010

То, что вы хотите сделать, очень разрушительно, если вы опубликовали историю другим разработчикам. См. «Восстановление из исходной базы» в документации 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-рип, случайно добавленный и удаленный в следующем коммите 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 после -- является сокращением для всех ссылок. Ваш репозиторий, как и в приведенном выше примере, может иметь только одну ссылку (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

Обратите внимание, что новый коммит «Неосторожный» добавляет только other.html и что коммит «Remove DVD-rip» больше не находится в основной ветке. В ветке, помеченной 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

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

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

$ 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, а «Страница входа» получила нового родителя, поэтому их SHA1s изменили .

Интерактивная перебазировка

С историей:

$ 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 из «Неосторожного», как если бы вы его никогда не добавляли, и тогда «Удалить 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's 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
# ...

То есть мы удаляем строку с помощью «Remove DVD-rip» и изменяем операцию на «Careless» на 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

Как говорится в сообщении, мы выполняем коммит «Небрежный», который хотим редактировать, поэтому мы запускаем две команды.

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

Первый удаляет поврежденный файл из индекса. Второйизменяет или изменяет «Careless» на обновленный индекс, а -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

, что вам нужно.

507 голосов
/ 27 июля 2013

Используйте 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.

150 голосов
/ 16 мая 2015

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

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

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

См. эту ссылку .

57 голосов
/ 07 октября 2017

(лучший ответ, который я видел на эту проблему: https://stackoverflow.com/a/42544963/714112, скопирован здесь, так как эта тема показывается высоко в рейтинге поиска Google, а другая нет)

? Сверхбыстрая оболочка с одним вкладышем ?

Этот сценарий оболочки отображает все объекты BLOB-объектов в хранилище, отсортированные от наименьшего к наибольшему.

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

Базовый сценарий

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
33 голосов
/ 14 июня 2013

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

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 (я проверял это только с 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
29 голосов
/ 26 апреля 2017

Попробовав практически каждый ответ в 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

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

9 голосов
/ 25 октября 2012

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

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

9 голосов
/ 14 июня 2012

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

$ 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 голосов
/ 01 января 2016

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

5 голосов
/ 31 августа 2014

Я столкнулся с этим с помощью учетной записи 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

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

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

...