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

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

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

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.


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

в" неосторожном " фиксации (имя объекта SHA1 которого ce36c98) файл oops.iso DVD-rip добавлен случайно и удален в следующем коммите, 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 после -- является стенографией для всех refs. Ваш репозиторий, как и пример выше, может иметь только один 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-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 для первых двух коммитов ("индекс "и" страница администратора") остались прежними, потому что операция фильтра не изменила эти коммиты. "Неосторожный" заблудился!--17--> и "страница входа" получила нового родителя, поэтому их 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" бесполезно для вас. Таким образом, наш план перехода в интерактивную ребазу-сохранить "страницу администратора", отредактировать "небрежно" и отбросить "удалить DVD-rip."

под управлением $ 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.

Save-выход из редактора выводит нас в командной строке со следующим сообщением.

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 идет вперед с остальной частью операция 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

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


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

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

на --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, но другой нет)

ослепительно быстрая оболочка one-liner

этот сценарий оболочки отображает все объекты 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, это инструмент пользовательского интерфейса. Он имеет плагин под названием "Найти большие файлы", который находит файлы lage в репозиториях и позволяет удалять их постоянно.

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


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

чтобы сделать вещи хуже, перебазирование также нелегко, потому что любые ссылки на blob предотвратят сборщик мусора 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

мое РЕПО (.git) изменен с 32MB на 388KB, что даже фильтр-ветка не может очистить.