Как удалить / удалить большой файл из истории коммитов в репозитории 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. Избавьтесь от большого файла

Вариант 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
  1. Сохраните изменения
git add path/to/your/large/file   # add the deletion to the index
git commit -m 'delete large file' # commit the deletion
  1. Удалите большой файл из всех коммитов
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, Вы можете получить больше информации здесь

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

Я собрал это воедино благодаря ответам от 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

Он сохранит изменения, но удалит фиксацию, после чего вы сможете повторно зафиксировать эти изменения.

Другие вопросы по тегам