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


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

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

14 504

14 ответов:

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

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

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

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

$ git gc --prune=now --aggressive

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

полное раскрытие информации: я автор BFG Repo-Cleaner.

то, что вы хотите сделать, очень разрушительно, если вы опубликовали историю другим разработчикам. Смотрите "восстановление из апстрима перебазировать" в 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-rip, когда он присутствует, и не терпит неудачу в противном случае.
  • --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

обратите внимание, что новый "Неосторожный" коммит добавляет только other.html и что фиксация "удалить 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

С помощью 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 для первых двух коммитов ("Index "и" Admin page") остались прежними, поскольку операция фильтрации не изменила эти коммиты. "Неосторожный" потерял 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-rip" бесполезно для вас. Таким образом, наш план идет в интерактивный rebase является сохранить страницу "admin," редактировать "нерадивых", и отбросить "удалить 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

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

это дает история:

$ 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

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

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

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

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

посмотреть этой ссылке.

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

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

(лучший ответ, который я видел на эту проблему: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(,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

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

замените имя папки на файл или папку, которые вы хотите удалить из данного 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

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

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

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

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

$ 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

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

я столкнулся с этим с учетной записью bitbucket, где я случайно сохранил ginormous *.резервное копирование СПД моего сайта.

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

Вы можете сделать это с помощью :

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

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

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

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

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

Я собрал git forget-blob, небольшой скрипт, который пытается удалить все эти ссылки, а затем использует 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/

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

Я в основном сделал то, что было на этот ответ: https://stackoverflow.com/a/11032521/1286423

(для истории, я буду копипастить сюда)

$ 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's 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

мое РЕПО (the .git) изменился с 32 МБ до 388 КБ, что даже фильтр-ветка не могла очистить.