Remova a pasta e seu conteúdo do histórico do git / GitHub

318

Eu estava trabalhando em um repositório na minha conta do GitHub e esse é um problema que me deparei.

  • Projeto Node.js com uma pasta com alguns pacotes npm instalados
  • Os pacotes estavam na node_modulespasta
  • Adicionada a pasta ao repositório git e empurrou o código para o github (não estava pensando na parte npm naquela época)
  • Percebeu que você realmente não precisa dessa pasta para fazer parte do código
  • Excluiu essa pasta, empurrou-a

Nesse caso, o tamanho do repositório Git total era de cerca de 6 MB, enquanto o código real (todos, exceto a pasta) era de apenas 300 KB .

Agora, o que estou procurando no final é uma maneira de livrar-se dos detalhes dessa pasta de pacotes do histórico do git; portanto, se alguém o clona, ​​ele não precisa fazer o download de 6mb no histórico, onde os únicos arquivos reais serão recuperados a partir do último commit seria 300 KB.

Procurei possíveis soluções para isso e tentei esses 2 métodos

O Gist parecia que funcionava onde, depois de executar o script, mostrava que ele se livrava dessa pasta e depois mostrava que 50 commits diferentes foram modificados. Mas isso não me deixou levar esse código. Quando tentei empurrá-lo, ele disse, Branch up to datemas mostrou que 50 commits foram modificados em a git status. Os outros 2 métodos também não ajudaram.

Agora, apesar de mostrar que se livrou do histórico dessa pasta, quando verifiquei o tamanho desse repositório no meu host local, ele ainda estava em torno de 6 MB. (Também excluí a refs/originalpasta, mas não vi a alteração no tamanho do repositório).

O que pretendo esclarecer é que, se existe uma maneira de se livrar não apenas do histórico de consolidação (que é a única coisa que acho que aconteceu), mas também daqueles arquivos que o git continua assumindo que se deseja reverter.

Vamos dizer que uma solução é apresentada para isso e aplicada no meu host local, mas não pode ser reproduzida no repositório GitHub, é possível cloná-lo, a reversão para o primeiro commit executa o truque e o pressiona (ou isso significa que o git irá ainda tem um histórico de todos esses commits? - também conhecido como 6MB).

Meu objetivo final aqui é basicamente encontrar a melhor maneira de livrar-se do conteúdo da pasta do git, para que um usuário não precise baixar 6MB de material e ainda possua os outros commits que nunca tocaram na pasta modules (isso é bastante todos eles) na história do git.

Como posso fazer isso?

Kartik
fonte
3
Se alguma das respostas abaixo resolveu seu problema, talvez você deva considerar aceitar uma como resposta à sua pergunta. meta.stackexchange.com/questions/5234/…
starbeamrainbowlabs 22/03
A melhor resposta é: stackoverflow.com/a/32886427/5973334
Kuzeko 17/01/19

Respostas:

556

Se você está aqui para copiar e colar o código:

Este é um exemplo que remove node_modulesdo histórico

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

O que o git realmente faz:

A primeira linha percorre todas as referências na mesma árvore ( --tree-filter) que HEAD (sua ramificação atual), executando o comando rm -rf node_modules. Este comando exclui a pasta node_modules ( -rsem -r, rmnão exclui pastas), sem nenhum aviso ao usuário ( -f). As --prune-emptyexclusões excluídas são inúteis (sem alterar nada) confirmam recursivamente.

A segunda linha exclui a referência a esse ramo antigo.

O restante dos comandos é relativamente direto.

Mohsen
fonte
3
Apenas uma observação: eu costumava git count-objects -vverificar se os arquivos foram realmente removidos, mas o tamanho do repositório permanece o mesmo até que eu clonei o repositório novamente. Git mantém uma cópia de todos os arquivos originais, eu acho.
Davide Icardi
4
Com um git não antigo, isso provavelmente deve ler --force-with-lease, não --force.
Griwes
4
Nenhum desses comandos funciona no Windows. Ou pelo menos não o Windows 10 por favor poste o sistema operacional que a "cortar e colar" obras em
David
3
Para o Windows 10 usuários, isso funciona muito bem sob Bash para Windows (eu usei Ubuntu)
Andrej Kyselica
3
Eu tentei com o shell do windows e com o git bash, e não funcionou. Primeira passagem de comando, segunda falha de comando!
Mohy Eldeen
240

Acho que a --tree-filteropção usada em outras respostas pode ser muito lenta, especialmente em repositórios maiores com muitos commits.

Aqui está o método que eu uso para remover completamente um diretório do histórico do git usando a --index-filteropção, que é executada muito mais rapidamente:

# 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

Você pode verificar o tamanho do repositório antes e depois do gccom:

git count-objects -vH
Lee Netherton
fonte
3
você poderia explicar por que isso é muito mais rápido?
knocte
7
@knocte: dos documentos ( git-scm.com/docs/git-filter-branch ). "--index-filter: ... é semelhante ao filtro da árvore, mas não faz check-out da árvore, o que a torna muito mais rápida"
Lee Netherton
23
Por que essa não é a resposta aceita? É tão completo.
Mad Physicist
2
Ao fazer isso no Windows, você precisará de aspas duplas em vez de aspas simples.
Kris Morness
12
Passando --quietao git rmacima acelerou minha reescrita pelo menos pelo fator 4.
ctusch
46

Além da resposta popular acima , gostaria de adicionar algumas notas para os sistemas Windows . O comando

git filter-branch --tree-filter 'rm -rf node_modules' --prune-empty HEAD
  • funciona perfeitamente sem qualquer modificação! Portanto, você não deve usar Remove-Item, delou qualquer outra coisa em vez de rm -rf.

  • Se você precisar especificar um caminho para um arquivo ou diretório, use barras como./path/to/node_modules

participante
fonte
Isso não funcionará no Windows se o diretório contiver um. (ponto) no nome.
Corneliu Serediuc
4
E eu encontrei a solução. Use vírgulas invertidas duplas para o comando rm como este: "rm -rf node.modules".
Corneliu Serediuc
23

O melhor e mais preciso método que encontrei foi fazer o download do arquivo bfg.jar: https://rtyley.github.io/bfg-repo-cleaner/

Em seguida, execute os comandos:

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

Se você deseja excluir arquivos, use a opção delete-files:

java -jar bfg.jar --delete-files *.pyc
Kim T
fonte
1
muito fácil :) se você quiser garantir que apenas uma pasta específica seja removida, isso ajudará: stackoverflow.com/questions/21142986/…
emjay
9

Parece que a resposta atualizada para isso é não usar filter-branchdiretamente (pelo menos o próprio git não o recomenda mais) e adiar esse trabalho para uma ferramenta externa. Em particular, o git-filter-repo é atualmente recomendado. O autor dessa ferramenta fornece argumentos sobre por que usar filter-branchdiretamente pode levar a problemas.

A maioria dos scripts de várias linhas acima para remover dirdo histórico pode ser reescrita como:

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

A ferramenta é mais poderosa do que apenas isso, aparentemente. Você pode aplicar filtros por autor, email, refname e mais (página de manual completa aqui ). Além disso, é rápido . A instalação é fácil - é distribuída em uma variedade de formatos .

André Anjos
fonte
Nice tool! Funciona bem no Ubuntu 20.04, você pode apenas pip3 install git-filter-repoporque é apenas stdlib e não instala nenhuma dependência. No Ubuntu 18 é incompatível com a versão git da distro Error: need a version of git whose diff-tree command has the --combined-all-paths option, mas é fácil o suficiente para executá-la em umdocker run -ti ubuntu:20.04
kubanczyk
7

Receita completa de copiar e colar, apenas adicionando os comandos nos comentários (para a solução copiar e colar), depois de testá-los:

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

Depois disso, você pode remover a linha "node_modules /" de .gitignore

jgbarah
fonte
Por que você removeria node_modulesde .gitignore? Para que eles possam ser acidentalmente cometidos novamente?
Adamski
1
Ele não é removido do gitignore, é adicionado ao gitignore. A mensagem de commit diz "a história git", não "gitignore" :)
Danny Tuppeny
mas o comentário diz que você pode remover node_modulesde .gitignore.
zavr
7

Para usuário do Windows, observe o uso em "vez de ' Também adicionado -fpara forçar o comando se outro backup já estiver lá.

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

Eu removi as pastas bin e obj de projetos antigos de C # usando o git no Windows. Tenha cuidado com

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

Destrói a integridade da instalação do git excluindo a pasta usr / bin na pasta de instalação do git.

LordObi
fonte