Введение: у вас есть 5 доступных решений
Оригинальный плакат гласит:
Я случайно отправил нежелательный файл ... в свой репозиторий несколько коммитов назад ... Я хочу полностью удалить файл из истории репозитория.
Можно ли переписать историю изменений так, чтобы filename.orig
она никогда не добавлялась в хранилище?
Есть много разных способов полностью удалить историю файла из git:
- Поправка фиксирует.
- Хард ресет (возможно плюс ребаз).
- Неинтерактивный ребаз.
- Интерактивные ребазы.
- Фильтрация веток.
В случае с оригинальным постером, изменение коммита на самом деле не вариант, поскольку впоследствии он сделал несколько дополнительных коммитов, но для полноты картины я также объясню, как это сделать, для тех, кто хочет только изменить их предыдущий коммит.
Обратите внимание, что все эти решения включают изменение / переписывание истории / фиксаций одним способом другим, поэтому любой, у кого есть старые копии коммитов, должен будет выполнить дополнительную работу для повторной синхронизации своей истории с новой историей.
Решение 1: внесение поправок в комитеты
Если вы случайно внесли изменение (например, добавление файла) в свой предыдущий коммит и не хотите, чтобы история этого изменения больше существовала, вы можете просто изменить предыдущий коммит, чтобы удалить файл из него:
git rm <file>
git commit --amend --no-edit
Решение 2. Жесткий сброс (возможно, плюс перебаз)
Как и в решении № 1, если вы просто хотите избавиться от своего предыдущего коммита, у вас также есть возможность просто сделать полный сброс его родителю:
git reset --hard HEAD^
Эта команда жестко сбросит вашу ветку к предыдущему 1- му родительскому коммиту.
Однако , если, подобно оригинальному постеру, вы сделали несколько коммитов после коммита, для которого вы хотите отменить изменение, вы все равно можете использовать жесткий сброс, чтобы изменить его, но для этого также необходимо использовать ребаз. Вот шаги, которые вы можете использовать, чтобы изменить коммит дальше в истории:
# Create a new branch at the commit you want to amend
git checkout -b temp <commit>
# Amend the commit
git rm <file>
git commit --amend --no-edit
# Rebase your previous branch onto this new commit, starting from the old-commit
git rebase --preserve-merges --onto temp <old-commit> master
# Verify your changes
git diff master@{1}
Решение 3: Неинтерактивная Rebase
Это будет работать, если вы просто хотите полностью удалить коммит из истории:
# Create a new branch at the parent-commit of the commit that you want to remove
git branch temp <parent-commit>
# Rebase onto the parent-commit, starting from the commit-to-remove
git rebase --preserve-merges --onto temp <commit-to-remove> master
# Or use `-p` insteda of the longer `--preserve-merges`
git rebase -p --onto temp <commit-to-remove> master
# Verify your changes
git diff master@{1}
Решение 4: Интерактивные ребазы
Это решение позволит вам выполнить те же действия, что и решения № 2 и № 3, т. Е. Изменить или удалить коммиты дальше в истории, чем ваш непосредственно предыдущий коммит, так что какое решение вы выберете, зависит от вас. Интерактивные перебазировки не подходят для перебазирования сотен коммитов по соображениям производительности, поэтому я бы использовал неинтерактивные перебазировки или решение с ветвями фильтра (см. Ниже) в подобных ситуациях.
Чтобы начать интерактивную перебазировку, используйте следующее:
git rebase --interactive <commit-to-amend-or-remove>~
# Or `-i` instead of the longer `--interactive`
git rebase -i <commit-to-amend-or-remove>~
Это заставит git перемотать историю коммитов назад к родителю коммита, который вы хотите изменить или удалить. Затем он предоставит вам список перемотанных коммитов в обратном порядке в любом редакторе, который будет использовать git (по умолчанию это Vim):
pick 00ddaac Add symlinks for executables
pick 03fa071 Set `push.default` to `simple`
pick 7668f34 Modify Bash config to use Homebrew recommended PATH
pick 475593a Add global .gitignore file for OS X
pick 1b7f496 Add alias for Dr Java to Bash config (OS X)
Фиксация, которую вы хотите изменить или удалить, будет в верхней части этого списка. Чтобы удалить его, просто удалите его строку в списке. В противном случае замените «pick» на «edit» в 1- й строке, например так:
edit 00ddaac Add symlinks for executables
pick 03fa071 Set `push.default` to `simple`
Далее введите git rebase --continue
. Если вы решили полностью удалить коммит, то это все, что вам нужно сделать (кроме проверки, см. Последний шаг для этого решения). Если, с другой стороны, вы хотите изменить фиксацию, то git повторно применит фиксацию и затем приостановит перебазирование.
Stopped at 00ddaacab0a85d9989217dd9fe9e1b317ed069ac... Add symlinks
You can amend the commit now, with
git commit --amend
Once you are satisfied with your changes, run
git rebase --continue
На этом этапе вы можете удалить файл и изменить коммит, а затем продолжить перебазирование:
git rm <file>
git commit --amend --no-edit
git rebase --continue
Вот и все. В качестве последнего шага, независимо от того, изменили ли вы фиксацию или удалили ее полностью, всегда полезно проверить, что в вашу ветку не было внесено никаких неожиданных изменений, перед тем как перебазировать ее с ее состоянием:
git diff master@{1}
Решение 5: Фильтрация ветвей
Наконец, это решение лучше всего, если вы хотите полностью стереть все следы существования файла из истории, и ни одно из других решений не вполне соответствует задаче.
git filter-branch --index-filter \
'git rm --cached --ignore-unmatch <file>'
Это удалит <file>
все коммиты, начиная с корневого коммита. Если вместо этого вы просто хотите переписать диапазон фиксации HEAD~5..HEAD
, вы можете передать его в качестве дополнительного аргумента filter-branch
, как указано в
этом ответе :
git filter-branch --index-filter \
'git rm --cached --ignore-unmatch <file>' HEAD~5..HEAD
Опять же, после того, filter-branch
как завершено, обычно хорошей идеей является проверка отсутствия других неожиданных изменений, если перед ветвью фильтровать ветвь с предыдущим состоянием.
git diff master@{1}
Альтернативный фильтр-ответвление: BFG Repo Cleaner
Я слышал, что инструмент BFG Repo Cleaner работает быстрее, чем git filter-branch
, поэтому вы можете проверить это как вариант. Это даже упоминается официально в документации ветки фильтра как жизнеспособная альтернатива:
git-filter-branch позволяет вам делать сложные переписки в истории Git с использованием сценариев оболочки, но вам, вероятно, не нужна эта гибкость, если вы просто удаляете ненужные данные, такие как большие файлы или пароли. Для этих операций вы можете рассмотреть возможность использования BFG Repo-Cleaner , альтернативы git-filter-branch на основе JVM, как правило, в 10-50 раз быстрее для этих сценариев использования и с совершенно другими характеристиками:
Любая конкретная версия файла очищается ровно один раз . BFG, в отличие от git-filter-branch, не дает вам возможности обрабатывать файл иначе, в зависимости от того, где или когда он был зафиксирован в вашей истории. Это ограничение дает основное преимущество производительности BFG и хорошо подходит для очистки плохих данных - вам все равно, где находятся плохие данные, вы просто хотите, чтобы они исчезли .
По умолчанию BFG использует все преимущества многоядерных машин, параллельно очищая деревья файлов коммитов. ГИТ-фильтр-ветвь Чистит фиксации последовательно (т.е. в однопоточных образом), хотя это
можно писать фильтры , которые включают в свои собственные параллельности, в сценарии , выполняемый на каждую фиксацию.
Эти опции команды гораздо более ограничительные , чем ГИТ-фильтр ветвь, и посвящен только к задачам удаления нежелательного данных- например --strip-blobs-bigger-than 1M
.
Дополнительные ресурсы
- Pro Git § 6.4 Инструменты Git - История переписывания .
- git-filter-branch (1) Руководство пользователя .
- git-commit (1) Страница руководства .
- git-reset (1) .
- git-rebase (1) .
- Очиститель репо BFG (см. Также этот ответ от самого создателя ).