Remova arquivos confidenciais e suas confirmações do histórico do Git

353

Gostaria de colocar um projeto Git no GitHub, mas ele contém certos arquivos com dados confidenciais (nomes de usuário e senhas, como /config/deploy.rb para capistrano).

Eu sei que posso adicionar esses nomes de arquivo ao .gitignore , mas isso não removeria o histórico deles no Git.

Também não quero começar de novo excluindo o diretório /.git.

Existe uma maneira de remover todos os vestígios de um arquivo específico no seu histórico do Git?

Stefan
fonte

Respostas:

448

Para todos os fins práticos, a primeira coisa com que você deve se preocupar é MUDAR SUAS SENHAS! Não está claro em sua pergunta se o seu repositório git é totalmente local ou se você possui um repositório remoto em outro lugar; se for remoto e não protegido por terceiros, você tem um problema. Se alguém clonou esse repositório antes de você consertar isso, eles terão uma cópia de suas senhas na máquina local e não há como você forçá-los a atualizar para a sua versão "fixa" com o histórico retirado. A única coisa segura que você pode fazer é alterar sua senha para outra em qualquer lugar em que você a tenha usado.


Com isso fora do caminho, veja como corrigi-lo. O GitHub respondeu exatamente a essa pergunta como uma FAQ :

Nota para usuários do Windows : use aspas duplas (") em vez de singles neste comando

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

Atualização 2019:

Este é o código atual da 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

Lembre-se de que depois de enviar esse código para um repositório remoto como o GitHub e outros clonarem esse repositório remoto, você estará em uma situação em que está reescrevendo o histórico. Quando outras pessoas tentam retirar suas alterações mais recentes depois disso, elas receberão uma mensagem indicando que as alterações não podem ser aplicadas porque não é um avanço rápido.

Para corrigir isso, eles terão que excluir o repositório existente e cloná-lo novamente ou seguir as instruções em "RECUPERANDO DO UPSTREAM REBASE" na página de manual do git-rebase .

Dica : Executargit rebase --interactive


No futuro, se você acidentalmente confirmar algumas alterações com informações confidenciais, mas perceber antes de enviar para um repositório remoto, existem algumas correções mais fáceis. Se você tiver confirmado por último a inclusão de informações confidenciais, basta remover as informações confidenciais e executar:

git commit -a --amend

Isso alterará a confirmação anterior com as novas alterações feitas, incluindo remoções de arquivos inteiras feitas com a git rm. Se as alterações estiverem mais antigas, mas ainda não forem enviadas para um repositório remoto, você poderá fazer uma nova análise interativa:

git rebase -i origin/master

Isso abre um editor com os commit que você fez desde seu último ancestral comum com o repositório remoto. Altere "pick" para "edit" em qualquer linha que represente um commit com informações confidenciais e salve e saia. O Git analisará as alterações e o deixará em um local onde você pode:

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

Para cada alteração com informações confidenciais. Eventualmente, você retornará à sua filial e poderá enviar com segurança as novas alterações.

natacado
fonte
5
Cara perfeito, é uma ótima resposta. Você salva meu dia.
Zzeroo 09/09/10
18
Só para acrescentar um pouco - no Windows, você deve usar aspas ( ") em vez de singles.
ripper234
4
Isso funcionou. Eu estava perdido em traduções. Eu usei o link em vez do comando aqui. Além disso, o comando do Windows acabou exigindo aspas duplas, como menciona o ripper234, caminho completo como sugere o MigDus, e não incluindo os caracteres "\" que o link colou como novos indicadores de quebra de linha. O comando final era parecido com: git filter-branch --force --index-filter "git rm --cached --ignore-unmatch src [Projeto] [Arquivo]. [Ext]" --prune-empty --tag- nome-filtro cat - --all
Eric Swanson
3
Parece haver algumas diferenças substanciais entre o seu filter-branchcódigo e o da página do github ao qual você vinculou. Por exemplo, sua terceira linha --prune-empty --tag-name-filter cat -- --all. A solução mudou ou estou faltando alguma coisa?
Geotheory
2
Essa solução parece muito boa, mas se eu apresentei o arquivo a ser removido na confirmação inicial <introduction-revision-sha1>..HEADnão funcionará. Ele remove apenas o arquivo do segundo commit em diante. (Como faço para incluir o commit inicial no intervalo de commits?) A maneira de salvar é apontada aqui: 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

Alterar suas senhas é uma boa ideia, mas, para o processo de remoção de senhas do histórico de seu repositório, recomendo o BFG Repo-Cleaner , uma alternativa mais rápida e simples ao git-filter-branchprojetado explicitamente para remover dados particulares dos repositórios Git.

Crie um private.txtarquivo listando as senhas, etc, que você deseja remover (uma entrada por linha) e, em seguida, execute este comando:

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

Todos os arquivos abaixo de um tamanho limite (1 MB por padrão) no histórico do seu repositório serão verificados e qualquer sequência correspondente (que não esteja no seu último commit) será substituída pela sequência "*** REMOVED ***" ". Você pode usar git gcpara limpar os dados mortos:

$ git gc --prune=now --aggressive

O BFG geralmente é 10 a 50 vezes mais rápido que a execução git-filter-branche as opções são simplificadas e adaptadas a esses dois casos de uso comuns:

  • Removendo arquivos grandes e loucos
  • Remoção de senhas, credenciais e outros dados privados

Divulgação completa: sou o autor do BFG Repo-Cleaner.

Roberto Tyley
fonte
Esta é uma opção, mas pode interromper o seu aplicativo quando as senhas são usadas, por exemplo, para configurar uma conexão com o banco de dados. Prefiro a resposta atualmente aceita, porque ainda é possível manter as senhas na sua cópia de trabalho e ignorar os arquivos que as contêm com .gitignore.
18713 Henridv
6
Esta é uma grande vitória aqui. Depois de algumas tentativas, eu pude usar isso para remover confirmações que contêm informações confidenciais de um repositório particular de maneira minuciosa e atualizar vigorosamente o repositório remoto com o histórico revisado. Uma observação lateral é que você deve garantir que a dica do seu repositório (HEAD) esteja limpa sem dados confidenciais, pois esse commit é considerado "protegido" e não será revisado por esta ferramenta. Caso contrário, limpe / substitua manualmente e git commit. Caso contrário, uma nova ferramenta para a caixa de ferramentas do desenvolvedor :)
Matt Borja
11
@Henridv De acordo com meu comentário recente, ele não deve interromper o seu aplicativo como você pode prever, supondo que o seu aplicativo esteja atualmente na ponta ou no topo da sua filial (ou seja, confirmação mais recente). Essa ferramenta reportará explicitamente seu último commit These are your protected commits, and so their contents will NOT be alteredao percorrer e revisar o restante do seu histórico de commit. Se você precisou reverter, no entanto, sim, seria necessário fazer uma pesquisa ***REMOVED***no commit para o qual acabou de reverter.
Matt Borja
11
+1 para BFG (se você tiver o Java instalado ou não se importa de instalá-lo). Um problema é que o BFG se recusa a excluir um arquivo se ele estiver contido no HEAD. Portanto, é melhor primeiro fazer uma consolidação na qual os arquivos desejados serão excluídos e só depois executar o BFG. Depois disso, você pode reverter o último commit, agora não muda nada.
Fr0sT
11
Na verdade, isso deve ser aceito como a resposta correta. Faz o que diz na caixa!
28516 Gjoris
21

Se você empurrou para o GitHub, forçar o envio não é suficiente, exclua o repositório ou entre em contato com o suporte

Mesmo se você forçar a pressão um segundo depois, não será suficiente, conforme explicado abaixo.

Os únicos cursos de ação válidos são:

  • é o que vazou uma credencial mutável como uma senha?

    • sim: modifique suas senhas imediatamente e considere usar mais chaves OAuth e API!
    • não (fotos nuas):

      • você se importa se todos os problemas no repositório forem resolvidos?

        • não: exclua o repositório
        • sim:

          • entre em contato com o suporte
          • se o vazamento for muito crítico para você, a ponto de estar disposto a obter algum tempo de inatividade do repositório para diminuir a probabilidade de vazamento, torne-o privado enquanto aguarda a resposta do suporte do GitHub

Forçar um segundo depois não é suficiente porque:

Se você excluir o repositório em vez de apenas forçar o envio, as confirmações desaparecem mesmo da API imediatamente e fornecem 404, por exemplo, https://api.github.com/repos/cirosantilli/test-dangling-delete/commits/8c08448b5fbf0f891696819f3b2b2d653f7a3824 Isso funciona mesmo se você recriar outro repositório com o mesmo nome.

Para testar isso, criei um repositório: https://github.com/cirosantilli/test-dangling e fiz:

git init
git remote add origin [email protected]: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

Veja também: Como remover um commit dangling do GitHub?

Ciro Santilli adicionou uma nova foto
fonte
20

Eu recomendo este script de David Underhill, funcionou como um encanto para mim.

Ele adiciona esses comandos além do ramo de filtro do natacado para limpar a bagunça que deixa para trás:

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

Roteiro completo (todo o crédito a David Underhill)

#!/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

Os dois últimos comandos podem funcionar melhor se forem alterados para o seguinte:

git reflog expire --expire=now --all && \
git gc --aggressive --prune=now
Jason Goemaat
fonte
11
Observe que o uso de expiração e remoção está incorreto; se você não especificar a data, o padrão será todos os commits anteriores a 2 semanas para remoção. O que você quer é que todos os commits o façam:git gc --aggressive --prune=now
Adam Parkin
@ Adam Parkin Vou deixar o código na resposta da mesma forma, porque é do script no site de David Underhill, você pode comentar lá e, se ele mudar, eu mudaria essa resposta, já que realmente não sei o que é bem. O comando expirar antes da remoção não afeta isso, afeta?
21712 Jason Goemaat
11
@ MarkusUnterwaditzer: Esse não funcionará para confirmações por push.
Max Beikirch
Talvez você deva colocar todos os comandos em sua resposta; seria muito mais consistente e não exigiria combinando o mental de mensagens separadas :)
Andrew Mao
9

Para ser claro: a resposta aceita está correta. Experimente primeiro. No entanto, pode ser desnecessariamente complexo para alguns casos de uso, principalmente se você encontrar erros desagradáveis, como 'fatal: revisão ruim - poda vazia', ou realmente não se importa com o histórico do seu repo.

Uma alternativa seria:

  1. cd à ramificação base do projeto
  2. Remova o código / arquivo sensível
  3. rm -rf .git / # Remova todas as informações do git do seu código
  4. Vá para o github e exclua seu repositório
  5. Siga este guia para enviar seu código para um novo repositório como faria normalmente - https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/

Obviamente, isso removerá todas as ramificações do histórico de consolidação e os problemas do seu repositório github e do repositório git local. Se isso for inaceitável, você terá que usar uma abordagem alternativa.

Chame isso de opção nuclear.

filósofo perdido
fonte
9

Você pode usar git forget-blob.

O uso é bem simples git forget-blob file-to-forget. Você pode obter mais informações aqui

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

Ele desaparecerá de todos os commits em seu histórico, reflog, tags e assim por diante

Eu sempre encontro o mesmo problema de vez em quando, e toda vez que tenho que voltar a este post e a outros, é por isso que automatizei o processo.

Créditos para colaboradores do Stack Overflow que me permitiram juntar isso

nachoparker
fonte
8

Aqui está a minha solução no Windows

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

git push --force

verifique se o caminho está correto, caso contrário não funcionará

Espero que ajude

vertigo71
fonte
8

Use ramo de filtro :

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
Shiv Krishna Jaiswal
fonte
3

Eu tive que fazer isso algumas vezes até hoje. Observe que isso funciona apenas em 1 arquivo por vez.

  1. Obtenha uma lista de todas as confirmações que modificaram um arquivo. O da parte inferior será o primeiro commit:

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

  2. Para remover o arquivo do histórico, use o primeiro commit sha1 e o caminho para o arquivo do comando anterior e preencha-os neste comando:

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

b01
fonte
3

Então, parece algo como isto:

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

Remova o cache do arquivo rastreado do git e adicione esse arquivo à .gitignorelista

przbadu
fonte
2

No meu projeto android, eu tinha admob_keys.xml como arquivo xml separado na pasta app / src / main / res / values ​​/ . Para remover esse arquivo sensível, usei o script abaixo e funcionei perfeitamente.

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
Ercan
fonte