Как удалить / удалить большой файл из истории коммитов в репозитории Git?
Иногда я вставлял DVD-рип в проект веб-сайта, а затем небрежно git commit -a -m ...
и, зап, репо был раздут на 2,2 гига. В следующий раз я сделал некоторые изменения, удалил видеофайл и зафиксировал все, но сжатый файл все еще находится в хранилище, в истории.
Я знаю, что могу запускать ветки с этих коммитов и перебазировать одну ветку на другую. Но что я должен сделать, чтобы объединить 2 коммита, чтобы большой файл не отображался в истории и был очищен в процессе сборки мусора?
25 ответов
Используйте 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-50 раз быстрее, чем бег git-filter-branch
и вообще проще в использовании.
Полное раскрытие: я являюсь автором BFG Repo-Cleaner.
То, что вы хотите сделать, очень разрушительно, если вы опубликовали историю другим разработчикам. См. "Восстановление после исходной ребазы" в git rebase
Документация для необходимых шагов после восстановления вашей истории.
У вас есть как минимум два варианта:git filter-branch
и интерактивная перебазировка, оба объяснены ниже.
С помощьюgit filter-branch
У меня была похожая проблема с объемными данными двоичного теста из импорта Subversion и я писал об удалении данных из репозитория 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
Используяfile:///...
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
из "Неосторожного", как будто вы его никогда не добавляли, и тогда "Удалить 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
# ...
То есть мы удаляем строку с "Удалить 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
Как говорится в сообщении, мы выполняем коммит "Небрежный", который хотим редактировать, поэтому мы запускаем две команды.
$ 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 из каждого снимка, независимо от того, существует он или нет.
Смотрите эту ссылку.
(Лучший ответ, который я когда-либо видел на эту проблему: /questions/47823517/kak-najti-identifitsirovat-bolshie-fajlyi-kommityi-v-istorii-git/47823560#47823560, скопирован здесь, поскольку эта тема занимает высокое место в поисковом рейтинге 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
В этой ветке есть очень хорошие ответы, но пока что многие из них устарели. С помощьюgit-filter-branch
больше не рекомендуется, потому что его сложно использовать и он ужасно медленный в больших репозиториях.
git-filter-repo
намного быстрее и проще в использовании.
git-filter-repo
это скрипт Python, доступный на github: https://github.com/newren/git-filter-repo. После установки он выглядит как обычная команда git и может быть вызванgit filter-repo
.
Вам нужен только один файл: скрипт Python3 git-filter-repo. Скопируйте его по пути, который указан в переменной PATH. В Windows вам может потребоваться изменить первую строку сценария (см. INSTALL.md). В вашей системе должен быть установлен Python3, но это не имеет большого значения.
Сначала ты можешь бежать
git filter-repo --analyze
Это поможет вам определить, что делать дальше.
Вы можете удалить свой DVD-рип везде:
git filter-repo --invert-paths --path-match DVD-rip
Filter-repo работает очень быстро. Задача, которая заняла около 9 часов на моем компьютере с помощью filter-branch, была завершена за 4 минуты с помощью filter-repo. Вы можете делать еще много приятных вещей с помощью filter-repo. Обратитесь к документации для этого.
Предупреждение: сделайте это с копией вашего репозитория. Многие действия filter-repo нельзя отменить. filter-repo изменит хэши всех измененных коммитов (конечно) и всех их потомков вплоть до последних коммитов!
Попробовав практически каждый ответ в SO, я наконец нашел этот драгоценный камень, который быстро удалил и удалил большие файлы в моем хранилище и позволил мне снова синхронизироваться: http://www.zyxware.com/articles/4027/how-to-delete -файлы-постоянно-из-ваш-местного и дистанционного-GIT-репозитории
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
Это очистит удаленный репозиторий.
Эти команды работали в моем случае:
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
Согласно документации GitHub, просто выполните следующие действия:
- Избавьтесь от большого файла
Вариант 1. Вы не хотите хранить большой файл:
rm path/to/your/large/file # delete the large file
Вариант 2: вы хотите сохранить большой файл в неотслеживаемом каталоге
mkdir large_files # create directory large_files
touch .gitignore # create .gitignore file if needed
'/large_files/' >> .gitignore # untrack directory large_files
mv path/to/your/large/file large_files/ # move the large file into the untracked directory
- Сохраните изменения
git add path/to/your/large/file # add the deletion to the index
git commit -m 'delete large file' # commit the deletion
- Удалите большой файл из всех коммитов
git filter-branch --force --index-filter \
"git rm --cached --ignore-unmatch path/to/your/large/file" \
--prune-empty --tag-name-filter cat -- --all
git push <remote> <branch>
НОВЫЙ ОТВЕТ, КОТОРЫЙ РАБОТАЕТ В 20222 году.
НЕ ИСПОЛЬЗОВАТЬ:
git filter-branch
эта команда может не изменить удаленное репо после нажатия. Если вы клонируете его после использования, то увидите, что ничего не изменилось и репо по-прежнему имеет большой размер. эта команда уже устарела. Например, если вы используете шаги в https://github.com/18F/C2/issues/439, это не сработает.
Вам нужно использовать
git filter-repo
Шаги:
(1) Найдите самые большие файлы в .git:
git rev-list --objects --all | grep -f <(git verify-pack -v .git/objects/pack/*.idx| sort -k 3 -n | cut -f 1 -d " " | tail -10)
(2) Начните фильтровать эти большие файлы:
git filter-repo --path-glob '../../src/../..' --invert-paths --force
или
git filter-repo --path-glob '*.zip' --invert-paths --force
или
git filter-repo --path-glob '*.a' --invert-paths --force
или что вы найдете на шаге 1.
(3)
git remote add origin git@github.com:.../...git
(4)
git push --all --force
git push --tags --force
СДЕЛАННЫЙ!!!
Я столкнулся с этим с помощью учетной записи 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
Просто отметьте, что эти команды могут быть очень разрушительными. Если больше людей работают над репо, им всем придется потянуть новое дерево. Три средние команды не нужны, если ваша цель НЕ уменьшить размер. Поскольку ветвь фильтра создает резервную копию удаленного файла, он может оставаться там в течение длительного времени.
$ 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 -f path/to/file' HEAD
работал очень хорошо для меня, хотя я столкнулся с той же проблемой, как описано здесь, которую я решил, следуя этому предложению.
В этой книге есть целая глава о переписывании истории - взгляните на filter-branch
/ Удаление файла из раздела Every Commit.
Если вы знаете, что ваш коммит был последним, а не проходил через все дерево, сделайте следующее:
git filter-branch --tree-filter 'rm LARGE_FILE.zip' HEAD~10..HEAD
Это удалит его из вашей истории
git filter-branch --force --index-filter 'git rm -r --cached --ignore-unmatch bigfile.txt' --prune-empty --tag-name-filter cat -- --all
git filter-branch
is a powerful command which you can use it to delete a huge file from the commits history. The file will stay for a while and Git will remove it in the next garbage collection.
Below is the full process from deleteing files from commit history. For safety, below process runs the commands on a new branch first. If the result is what you needed, then reset it back to the branch you actually want to change.
# 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
Используйте Git Extensions, это инструмент пользовательского интерфейса. Он имеет плагин под названием "Найти большие файлы", который находит файлы lage в репозиториях и позволяет удалять их постоянно.
Не используйте "git filter-branch" перед использованием этого инструмента, так как он не сможет найти файлы, удаленные с помощью "filter-branch" (хотя "filter-branch" не удаляет файлы полностью из файлов пакета репозитория),
Я в основном сделал то, что было на этот ответ: /questions/16266597/kak-udalit-udalit-bolshoj-fajl-iz-istorii-kommitov-v-repozitorii-git/16266607#16266607
(для истории, я скопирую это здесь)
$ 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
) изменено с 32 МБ до 388 КБ, что даже ветвь фильтра не может очистить.
Вы можете сделать это с помощью branch filter
команда:
git filter-branch --tree-filter 'rm -rf path/to/your/file' HEAD
Это был настолько полезный комментарий @Lucas, что я решил опубликовать его как ответ, чтобы его увидело больше людей.
Они сказали использовать git-filter-repo и запустить команду:git filter-repo --strip-blobs-bigger-than 10M
Если у вас возникли проблемы с установкойgit-filter-repo
в Windows (как и я), см. это.
Что это делает и как это работает? Я не знаю. Если да, пожалуйста, оставьте комментарий.
Однако впоследствии моя история коммитов осталась со всеми огромными файлами, которые больше не входили в историю коммитов. Это сработало.
Как всегда, создайте резервную копию своего репозитория перед запуском этого файла .
У меня это отлично работает: в расширениях git:
Щелкните правой кнопкой мыши выбранный коммит:
сбросить текущую ветку сюда:
Аппаратный сброс;
Удивительно, что никто другой не может дать такой простой ответ.
https://stackru.com/images/e06794f7d945ba8fa7cb99a7c7d01efd3fbf5afe.png g
https://stackru.com/images/0f0d001249071d886fef8de056667615d3f91955.png g
Когда вы столкнетесь с этой проблемой, git rm
этого будет недостаточно, так как git помнит, что файл когда-то существовал в нашей истории, и, таким образом, сохранит ссылку на него.
Что еще хуже, перебазировка также не легка, потому что любые ссылки на большой двоичный объект не позволят сборщику мусора убрать пространство. Это включает в себя удаленные ссылки и ссылки reflog.
Я собрал git forget-blob
Небольшой скрипт, который пытается удалить все эти ссылки, а затем использует git filter-branch для перезаписи каждого коммита в ветке.
Как только ваш блоб полностью не имеет ссылок, git gc
избавится от этого
Использование довольно просто git forget-blob file-to-forget
, Вы можете получить больше информации здесь
Я собрал это воедино благодаря ответам от Stack Overflow и некоторым записям в блоге. Кредиты им!
У меня такая же проблема. так сgit rebase -i HEAD~15
Я превратил коммит с большим файлом вedit
режим, затемgit rm {relative/path/largeFile}
удалил большой файл из коммита и сделалgit rebase --continue
.
Также я добавил{relative/path/largeFile} filter=lfs diff=lfs merge=lfs -text
к.gitattributes
и сделал коммит.
Обратите вниманиеgit filter-repo
хотя сообщение об успешном завершении у меня не сработало. Обратите внимание, что я клонировалgit clone https://github.com/newren/git-filter-repo.git
в другом каталоге. Затем из этого каталога запустилсяpython git-filter-repo --path "{large\File\Path}" --invert-paths
.
Кроме как git filter-branch
(медленное, но чистое решение git) и BFG (более простое и очень эффективное), есть еще один инструмент для фильтрации с хорошей производительностью:
https://github.com/xoofx/git-rocket-filter
Из его описания:
Назначение git-rocket-filter аналогично команде git-filter-branch
обеспечивая при этом следующие уникальные функции:
- Быстрое переписывание коммитов и деревьев (порядка от x10 до x100).
- Встроенная поддержка как белого списка с опцией --keep (сохраняет файлы или каталоги), так и черного списка с опцией --remove.
- Использование шаблона.gitignore для фильтрации дерева
- Быстрый и простой сценарий C# для фильтрации фиксации и фильтрации дерева
- Поддержка сценариев в древовидной фильтрации по шаблону файла / каталога
- Автоматически удалять пустые / неизмененные коммиты, включая коммиты слияния
Сохраните резервную копию текущего кода на случай, если во время этого процесса что-то пойдет не так.
git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch path/to/large_file' --prune-empty --tag-name-filter cat -- --all
Замените путь/к/большому_файлу фактическим путем к большому файлу, который вы хотите удалить. Эта команда перезапишет историю Git и удалит большой файл из всех коммитов.
После запуска команды git filter-branch вы можете увидеть сообщение «Ref 'refs/heads/master' не изменилось» или подобное. Это указывает на то, что ветка еще не обновлена. Чтобы обновить ветку и применить изменения, используйте:
git push origin --force --all
git reset --soft HEAD~1
Он сохранит изменения, но удалит фиксацию, после чего вы сможете повторно зафиксировать эти изменения.