Удалить папку и ее содержимое из истории Git / GitHub

318

Я работал над репозиторием в моей учетной записи GitHub, и я столкнулся с этой проблемой.

  • Проект Node.js с папкой с несколькими установленными пакетами npm
  • Пакеты были в node_modulesпапке
  • Добавил эту папку в репозиторий git и отправил код в github (в то время не думал о части npm)
  • Понял, что вам не нужно, чтобы эта папка была частью кода
  • Удалил эту папку, нажал на нее

В тот момент размер общего репозитория git составлял около 6 МБ, тогда как фактический код (все, кроме этой папки) составлял всего около 300 КБ .

Теперь, в конце концов, я ищу способ избавиться от деталей папки этого пакета из истории git, поэтому, если кто-то клонирует его, ему не нужно загружать историю в 6 Мб, где будут получены только реальные файлы. по состоянию на последний коммит будет 300KB.

Я искал возможные решения для этого и попробовал эти 2 метода

Казалось, что Gist сработал, когда после запуска скрипта он показал, что избавился от этой папки, и после этого показал, что было изменено 50 различных коммитов. Но это не позволило мне нажать этот код. Когда я попытался выдвинуть его, он сказал, Branch up to dateно показал, что 50 коммитов были изменены на a git status. Другие 2 метода тоже не помогли.

Теперь, несмотря на то, что он показал, что он избавился от истории этой папки, когда я проверил размер этого репо на моем локальном хосте, он все еще был около 6 МБ. (Я также удалил refs/originalпапку, но не увидел изменения в размере репо).

Я хочу уточнить, есть ли способ избавиться не только от истории коммитов (что, как мне кажется, единственное, что произошло), но и от тех файлов, которые git продолжает предполагать, что кто-то хочет откатиться.

Допустим, решение для этого представлено и применяется на моем локальном хосте, но не может быть воспроизведено для этого репозитория GitHub, возможно ли клонировать это репо, выполнить откат к первому коммиту и выполнить его (или это означает, что git будет еще есть история всех этих коммитов? - ака. 6MB).

Моя конечная цель - найти лучший способ избавиться от содержимого папки с помощью git, чтобы пользователю не приходилось загружать 6 МБ материала и, возможно, иметь другие коммиты, которые никогда не касались папки модулей (это довольно много всего) в истории Git.

Как я могу это сделать?

Картик
источник
3
Если какой-либо из приведенных ниже ответов решил вашу проблему, возможно, вам следует принять его в качестве ответа на ваш вопрос. meta.stackexchange.com/questions/5234/…
starbeamrainbowlabs
Лучший ответ: stackoverflow.com/a/32886427/5973334
Kuzeko

Ответы:

556

Если вы здесь, чтобы скопировать и вставить код:

Это пример, который удаляет node_modulesиз истории

git filter-branch --tree-filter "rm -rf node_modules" --prune-empty HEAD
git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
echo node_modules/ >> .gitignore
git add .gitignore
git commit -m 'Removing node_modules from git history'
git gc
git push origin master --force

Что на самом деле делает Git:

Первая строка перебирает все ссылки в том же дереве ( --tree-filter), что и HEAD (ваша текущая ветвь), выполняя команду rm -rf node_modules. Эта команда удаляет node_modules папки ( -rбез -r, rmне будет удалять папки), без подсказки пользователю предоставляется ( -f). Добавленное --prune-emptyудаляет бесполезные (не меняющие ничего) коммиты рекурсивно.

Вторая строка удаляет ссылку на эту старую ветку.

Остальные команды относительно просты.

Мохсен
источник
3
Просто примечание: раньше я git count-objects -vпроверял, действительно ли файлы были удалены, но размер хранилища не изменился, пока я снова не клонировал хранилище. Git хранит копию всех оригинальных файлов, я думаю.
Давиде Икарди
4
С не древним мерзавцем, это, вероятно, следует читать --force-with-lease, а не --force.
Griwes
4
Ни одна из этих команд не работает на окнах. Или, по крайней мере, не Windows 10, пожалуйста, опубликуйте ОС, на которой работает «вырезать и вставить»
David
3
Для пользователей Windows 10 это хорошо работает под Bash для Windows (я использовал Ubuntu)
Андрей Киселица
3
Я попробовал это с Windows Shell и Git Bash, и не работает. Первый проход команды, второй сбой команды!
Моги Элдин
240

Я считаю, что --tree-filterопция, используемая в других ответах, может быть очень медленной, особенно в больших репозиториях с большим количеством коммитов.

Вот метод, который я использую, чтобы полностью удалить каталог из истории git, используя --index-filterопцию, которая работает намного быстрее:

# Make a fresh clone of YOUR_REPO
git clone YOUR_REPO
cd YOUR_REPO

# Create tracking branches of all branches
for remote in `git branch -r | grep -v /HEAD`; do git checkout --track $remote ; done

# Remove DIRECTORY_NAME from all commits, then remove the refs to the old commits
# (repeat these two commands for as many directories that you want to remove)
git filter-branch --index-filter 'git rm -rf --cached --ignore-unmatch DIRECTORY_NAME/' --prune-empty --tag-name-filter cat -- --all
git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d

# Ensure all old refs are fully removed
rm -Rf .git/logs .git/refs/original

# Perform a garbage collection to remove commits with no refs
git gc --prune=all --aggressive

# Force push all branches to overwrite their history
# (use with caution!)
git push origin --all --force
git push origin --tags --force

Вы можете проверить размер хранилища до и после gc:

git count-objects -vH
Ли Нетертон
источник
3
не могли бы вы объяснить, почему это намного быстрее?
knocte
7
@knocte: из документов ( git-scm.com/docs/git-filter-branch ). «--index-filter: ... похож на древовидный фильтр, но не проверяет дерево, что делает его намного быстрее»
Ли Нетертон
23
Почему это не принятый ответ? Это так тщательно.
Безумный физик
2
Если вы делаете это в Windows, вам нужны двойные кавычки вместо одинарных.
Крис Морнесс
12
Переход --quietк git rmвышесказанному ускорил мое переписывание, по крайней мере, в 4 раза.
ctusch
46

В дополнение к популярному ответу выше, я хотел бы добавить несколько заметок для Windows- систем. Команда

git filter-branch --tree-filter 'rm -rf node_modules' --prune-empty HEAD
  • работает отлично без каких-либо изменений! Таким образом, вы не должны использовать Remove-Item, delили что-нибудь еще вместо rm -rf.

  • Если вам нужно указать путь к файлу или каталогу, используйте косую черту, например./path/to/node_modules

участник
источник
Это не будет работать в Windows, если каталог содержит. (точка) в названии.
Корнелиу Середюк
4
И я нашел решение. Используйте двойные кавычки для команды rm, например: «rm -rf node.modules».
Корнелиу Середюк
23

Лучший и самый точный метод, который я нашел, это загрузить файл bfg.jar: https://rtyley.github.io/bfg-repo-cleaner/

Затем запустите команды:

git clone --bare https://project/repository project-repository
cd project-repository
java -jar bfg.jar --delete-folders DIRECTORY_NAME  # i.e. 'node_modules' in other examples
git reflog expire --expire=now --all && git gc --prune=now --aggressive
git push --mirror https://project/new-repository

Если вы хотите удалить файлы, используйте вместо этого опцию delete-files:

java -jar bfg.jar --delete-files *.pyc
Ким Т
источник
1
очень просто :) если вы хотите убедиться, что удалена только определенная папка, это поможет: stackoverflow.com/questions/21142986/…
emjay
9

Похоже, что современный ответ на этот вопрос заключается в том, чтобы не использовать filter-branchнапрямую (по крайней мере, сам git не рекомендует его больше) и перенести эту работу на внешний инструмент. В частности, в настоящее время рекомендуется git-filter-repo . Автор этого инструмента приводит аргументы о том, почему использование filter-branchнапрямую может привести к проблемам.

Большинство вышеперечисленных многострочных сценариев для удаления dirиз истории можно переписать так:

git filter-repo --path dir --invert-paths

Инструмент более мощный, чем просто, по-видимому. Вы можете применять фильтры по автору, электронной почте, имени и т. Д. ( Полная страница руководства здесь ). Кроме того, это быстро . Установка проста - она распространяется в различных форматах .

Андре Анхос
источник
Хороший инструмент! Хорошо работает на Ubuntu 20.04, вы можете сделать это только pip3 install git-filter-repoпотому, что он предназначен только для stdlib и не устанавливает никаких зависимостей. На Ubuntu 18 он несовместим с git-версией дистрибутива Error: need a version of git whose diff-tree command has the --combined-all-paths option, но достаточно просто запустить его наdocker run -ti ubuntu:20.04
kubanczyk
7

Завершите рецепт копирования и вставки, просто добавив команды в комментарии (для решения копирования и вставки) после их тестирования:

git filter-branch --tree-filter 'rm -rf node_modules' --prune-empty HEAD
echo node_modules/ >> .gitignore
git add .gitignore
git commit -m 'Removing node_modules from git history'
git gc
git push origin master --force

После этого вы можете удалить строку "node_modules /" из .gitignore

jgbarah
источник
Почему бы вам тогда удалить node_modulesиз .gitignore? Чтобы они могли быть случайно совершены снова ??
Адамски
1
Он не удаляется из gitignore, он добавляется в gitignore. В сообщении коммита написано "git history", а не "gitignore" :)
Дэнни Таппени
но комментарий говорит, что вы можете удалить node_modulesиз .gitignore.
Завр
7

Для пользователя Windows, пожалуйста, обратите внимание на использование "вместо « ' Также добавлено» -fдля принудительного выполнения команды, если другая резервная копия уже существует.

git filter-branch -f --tree-filter "rm -rf FOLDERNAME" --prune-empty HEAD
git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
echo FOLDERNAME/ >> .gitignore
git add .gitignore
git commit -m "Removing FOLDERNAME from git history"
git gc
git push origin master --force
kcode
источник
3

Я удалил папки bin и obj из старых проектов C #, используя git на windows. Будь осторожен с

git filter-branch --tree-filter "rm -rf bin" --prune-empty HEAD

Это разрушает целостность установки git, удаляя папку usr / bin в папке git install.

LordObi
источник