Удалить конфиденциальные файлы и их коммиты из истории Git

354

Я хотел бы поместить проект Git на GitHub, но он содержит определенные файлы с конфиденциальными данными (имена пользователей и пароли, например /config/deploy.rb для capistrano).

Я знаю, что могу добавить эти имена в .gitignore , но это не удалит их историю в Git.

Я также не хочу начинать все заново, удалив каталог /.git.

Есть ли способ удалить все следы определенного файла в вашей истории Git?

Стефан
источник

Ответы:

449

Для практических целей первое, о чем вы должны беспокоиться, это СМЕНА ВАШИХ ПАРОЛЕЙ! Из вашего вопроса не ясно, является ли ваш git-репозиторий полностью локальным или у вас еще есть удаленный репозиторий; если он удаленный и не защищен от других, у вас есть проблема. Если кто-то клонировал этот репозиторий до того, как вы это исправите, у него будет копия ваших паролей на их локальном компьютере, и вы не сможете заставить их обновиться до «фиксированной» версии, если она ушла из истории. Единственная надежная вещь, которую вы можете сделать, - это сменить пароль на другой, где бы вы его не использовали.


С этим из пути, вот как это исправить. GitHub ответил именно на этот вопрос в виде FAQ :

Примечание для пользователей Windows : используйте двойные кавычки (") вместо одинарных в этой команде

git filter-branch --index-filter \
'git update-index --remove PATH-TO-YOUR-FILE-WITH-SENSITIVE-DATA' <introduction-revision-sha1>..HEAD
git push --force --verbose --dry-run
git push --force

Обновление 2019:

Это текущий код из FAQ:

  git filter-branch --force --index-filter \
  "git rm --cached --ignore-unmatch PATH-TO-YOUR-FILE-WITH-SENSITIVE-DATA" \
  --prune-empty --tag-name-filter cat -- --all
  git push --force --verbose --dry-run
  git push --force

Имейте в виду, что как только вы отправили этот код в удаленное хранилище, такое как GitHub, и другие клонировали этот удаленный репозиторий, вы находитесь в ситуации, когда вы переписываете историю. Когда другие попытаются свернуть ваши последние изменения после этого, они получат сообщение о том, что изменения не могут быть применены, потому что это не ускоренная перемотка вперед.

Чтобы это исправить, им придется либо удалить свой существующий репозиторий и повторно клонировать его, либо следовать инструкциям в разделе «ВОССТАНОВЛЕНИЕ ОТ РЕБАЗЫ UPSTREAM» на странице руководства git-rebase .

Совет : выполнитьgit rebase --interactive


В будущем, если вы случайно зафиксируете некоторые изменения с помощью конфиденциальной информации, но заметите, прежде чем отправлять их в удаленный репозиторий, есть некоторые более простые исправления. Если вы в последний раз добавили конфиденциальную информацию, вы можете просто удалить конфиденциальную информацию и запустить:

git commit -a --amend

Это изменит предыдущий коммит с любыми внесенными вами новыми изменениями, включая удаление всего файла, сделанное с помощью git rm. Если изменения еще вернулись в историю, но все еще не перенесены в удаленный репозиторий, вы можете сделать интерактивное перебазирование:

git rebase -i origin/master

Откроется редактор с коммитами, которые вы сделали со времени вашего последнего общего предка с удаленным репозиторием. Измените «выбрать» на «редактировать» в любых строках, представляющих коммит с конфиденциальной информацией, и сохраните и выйдите. Git пройдет через изменения и оставит вас в месте, где вы можете:

$EDITOR file-to-fix
git commit -a --amend
git rebase --continue

Для каждого изменения с конфиденциальной информацией. В конце концов, вы вернетесь в свою ветку и сможете спокойно вносить новые изменения.

natacado
источник
5
Идеальный чувак, это отличный ответ. Ты спас мой день.
zzeroo
18
Просто добавьте один бит - в Windows вы должны использовать двойные кавычки (") вместо синглов.
ripper234
4
Получил это на работу. Я был потерян в переводах. Я использовал ссылку вместо команды здесь. Кроме того, команда Windows в конечном итоге требует двойных кавычек, как упоминает ripper234, полный путь, как предлагает MigDus, и не включает символы "\", которые ссылка вставила в качестве новых индикаторов переноса строк. Последняя команда выглядела примерно так: git filter-branch --force --index-filter "git rm --cached --ignore-unmatch src [Проект] [Файл]. [Ext]" --prune-empty --tag- кот фильтра имен - --all
Эрик Свансон
3
Кажется, между вашим filter-branchкодом и страницей на github, на которую вы ссылаетесь, есть существенные различия . Например, их 3-я строка --prune-empty --tag-name-filter cat -- --all. Изменилось ли решение или я что-то упустил?
Геометрия
2
Это решение выглядит довольно хорошо, но если я ввел файл для удаления в первоначальном коммите, <introduction-revision-sha1>..HEADон не работает. Это только удаляет файл со второго коммита вперед. (Как мне включить начальный коммит в диапазон коммитов?) Здесь указан способ сохранения: help.github.com/articles/… git filter-branch --force --index-filter \ 'git rm --cached --ignore-unmatch PATH-TO-YOUR-FILE-WITH-SENSITIVE-DATA' \ --prune-empty --tag-name-filter cat -- --all
white_gecko
91

Изменение ваших паролей - хорошая идея, но для процесса удаления паролей из истории вашего репо я рекомендую BFG Repo-Cleaner , более быструю и простую альтернативу git-filter-branchявным образом предназначенным для удаления личных данных из репозиториев Git.

Создайте private.txtфайл со списком паролей и т. Д., Который вы хотите удалить (по одной записи в строке), а затем выполните следующую команду:

$ java -jar bfg.jar  --replace-text private.txt  my-repo.git

Все файлы с пороговым размером (по умолчанию 1 МБ) в истории вашего репо будут отсканированы, и любая подходящая строка (которой нет в вашем последнем коммите) будет заменена строкой «*** REMOVED ***». Затем вы можете использовать git gcдля очистки мертвых данных:

$ git gc --prune=now --aggressive

BFG обычно в 10-50 раз быстрее, чем работает, git-filter-branchи варианты упрощены и адаптированы к этим двум распространенным сценариям использования:

  • Удаление сумасшедших больших файлов
  • Удаление паролей, учетных данных и других личных данных

Полное раскрытие: я являюсь автором BFG Repo-Cleaner.

Роберто Тайли
источник
Это вариант, но он может сломать ваше приложение при использовании паролей, например, для установки соединения с базой данных. Я предпочел бы принятый в настоящее время ответ, потому что возможно сохранить пароли в вашей рабочей копии и игнорировать файлы, содержащие их, с помощью .gitignore.
Henridv
6
Это большая победа прямо здесь. После пары попыток я смог использовать это для очень тщательного удаления коммитов, содержащих конфиденциальную информацию из частного репо, и принудительного обновления удаленного репо с исправленной историей. Одно замечание: вы должны убедиться, что верхушка репо (HEAD) сама по себе чиста, без конфиденциальных данных, так как этот коммит считается «защищенным» и не будет пересматриваться этим инструментом. Если это не так, просто почистите / замените вручную и git commit. В противном случае +1 за новый инструмент в наборе инструментов разработчика :)
Мэтт Борха
1
@Henridv Согласно моему недавнему комментарию, оно не должно сломать ваше приложение, как вы могли бы ожидать, если предположить, что ваше приложение в данный момент находится на кончике или в заголовке вашей ветви (т.е. последний коммит). Этот инструмент будет явно сообщать о вашем последнем коммите These are your protected commits, and so their contents will NOT be altered, просматривая и редактируя остальную часть вашей истории коммитов. Однако, если вам необходимо выполнить откат, то да, вам нужно просто выполнить поиск ***REMOVED***в коммите, на который вы только что откатились.
Мэтт Борха
1
+1 за BFG (если у вас установлена ​​Java или вы не против ее установить). Одна проблема в том, что BFG отказывается удалять файл, если он содержится в HEAD. Так что лучше сначала сделать коммит, где нужные файлы будут удалены, и только потом запускать BFG. После этого вы можете отменить последний коммит, теперь он ничего не меняет.
Fr0sT
1
Это на самом деле должно быть принято как правильный ответ. Делает то, что написано на коробке!
gjoris
21

Если вы нажали на GitHub, принудительное нажатие недостаточно, удалите репозиторий или обратитесь в службу поддержки

Даже если после этого вы нажмете одну секунду, этого недостаточно, как описано ниже.

Единственными действительными способами действий являются:

  • Что такое утечка изменяемых учетных данных, как пароль?

    • да: немедленно измените свои пароли и рассмотрите возможность использования большего количества ключей OAuth и API!
    • нет (голые фото):

      • Вас волнует, если все проблемы в хранилище будут уничтожены?

        • нет: удалить репозиторий
        • да:

          • Контактная поддержка
          • если утечка очень важна для вас, до такой степени, что вы готовы получить некоторое время простоя репозитория, чтобы уменьшить вероятность утечки, сделайте его закрытым, пока вы ждете ответа от поддержки GitHub

Силового толчка через секунду недостаточно, потому что:

Однако если вы удаляете репозиторий вместо принудительного нажатия, коммиты немедленно исчезают даже из API и дают 404, например, https://api.github.com/repos/cirosantilli/test-dangling-delete/commits/8c08448b5fbf0f891696819f3b2b2d653f7a3824 Это работает даже если вы воссоздаете другой репозиторий с тем же именем.

Чтобы проверить это, я создал репо: https://github.com/cirosantilli/test-dangling и сделал:

git init
git remote add origin git@github.com:cirosantilli/test-dangling.git

touch a
git add .
git commit -m 0
git push

touch b
git add .
git commit -m 1
git push

touch c
git rm b
git add .
git commit --amend --no-edit
git push -f

Смотрите также: Как удалить оборванный коммит из GitHub?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
источник
20

Я рекомендую этот сценарий Дэвида Андерхилла, который для меня сработал.

Он добавляет эти команды в дополнение к ветке-фильтру natacado, чтобы убрать беспорядок, который он оставляет:

rm -rf .git/refs/original/
git reflog expire --all
git gc --aggressive --prune

Полный сценарий (вся заслуга Дэвида Андерхилла)

#!/bin/bash
set -o errexit

# Author: David Underhill
# Script to permanently delete files/folders from your git repository.  To use 
# it, cd to your repository's root and then run the script with a list of paths
# you want to delete, e.g., git-delete-history path1 path2

if [ $# -eq 0 ]; then
    exit 0
fi

# make sure we're at the root of git repo
if [ ! -d .git ]; then
    echo "Error: must run this script from the root of a git repository"
    exit 1
fi

# remove all paths passed as arguments from the history of the repo
files=$@
git filter-branch --index-filter \
"git rm -rf --cached --ignore-unmatch $files" HEAD

# remove the temporary history git-filter-branch
# otherwise leaves behind for a long time
rm -rf .git/refs/original/ && \
git reflog expire --all && \
git gc --aggressive --prune

Последние две команды могут работать лучше, если их изменить на следующее:

git reflog expire --expire=now --all && \
git gc --aggressive --prune=now
Джейсон Гомаат
источник
1
Обратите внимание, что вы используете expire и prune некорректно, если вы не укажете дату, то по умолчанию для всех фиксаций будет более 2 недель. Все, что вы хотите, это сделать:git gc --aggressive --prune=now
Адам Паркин
@ Adam Parkin Я собираюсь оставить код в ответе тем же, потому что это из сценария на сайте Дэвида Андерхилла, вы можете прокомментировать там, и если он изменит его, я бы изменил этот ответ, так как я действительно не знаю, что это такое хорошо. Команда expire до удаления не влияет на это?
Джейсон Гомаат
1
@MarkusUnterwaditzer: Этот не будет работать для принудительных коммитов.
Макс Бейкирх
Может быть, вы должны просто поместить все команды в свой ответ; это было бы намного более последовательным и не потребовало бы умственного объединения отдельных постов :)
Эндрю Мао
9

Чтобы было понятно: принятый ответ правильный. Попробуйте сначала. Однако это может быть излишне сложно для некоторых случаев использования, особенно если вы сталкиваетесь с неприятными ошибками, такими как «fatal: bad revision --prune-empty», или действительно не заботитесь об истории вашего репо.

Альтернативой будет:

  1. перейдите в базовую ветку проекта
  2. Удалить секретный код / ​​файл
  3. rm -rf .git / # Удалить всю информацию git из вашего кода
  4. Зайдите на github и удалите свой репозиторий
  5. Следуйте этому руководству, чтобы перенести свой код в новый репозиторий, как обычно, - https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/

Это, конечно, удалит все ветки истории коммитов и проблемы как из вашего репозитория github, так и из вашего локального репозитория git. Если это неприемлемо, вам придется использовать альтернативный подход.

Назовите это ядерным вариантом.

lostphilosopher
источник
9

Вы можете использовать git forget-blob.

Использование довольно просто 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, которые позволили мне собрать это воедино

nachoparker
источник
8

Вот мое решение в Windows

git filter-branch --tree-filter "rm -f 'filedir / filename'" HEAD

git push --force

убедитесь, что путь правильный, иначе он не будет работать

Я надеюсь, что это помогает

vertigo71
источник
8

Используйте фильтр-ветку :

git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch *file_path_relative_to_git_repo*' --prune-empty --tag-name-filter cat -- --all

git push origin *branch_name* -f
Шив Кришна Джайсвал
источник
3

Я должен был сделать это несколько раз на сегодняшний день. Обратите внимание, что это работает только для 1 файла за раз.

  1. Получить список всех коммитов, которые изменили файл. Внизу будет первый коммит:

    git log --pretty=oneline --branches -- pathToFile

  2. Чтобы удалить файл из истории, используйте первый коммит sha1 и путь к файлу из предыдущей команды и введите их в эту команду:

    git filter-branch --index-filter 'git rm --cached --ignore-unmatch <path-to-file>' -- <sha1-where-the-file-was-first-added>..

b01
источник
3

Итак, это выглядит примерно так:

git rm --cached /config/deploy.rb
echo /config/deploy.rb >> .gitignore

Удалить кеш для отслеживаемого файла из Git и добавить этот файл в .gitignoreсписок

przbadu
источник
2

В моем проекте Android у меня был admob_keys.xml в виде отдельного XML-файла в папке app / src / main / res / values ​​/ . Для удаления этого секретного файла я использовал приведенный ниже скрипт и работал отлично.

git filter-branch --force --index-filter \
'git rm --cached --ignore-unmatch  app/src/main/res/values/admob_keys.xml' \
--prune-empty --tag-name-filter cat -- --all
Эркан
источник