Como remover blobs não referenciados do meu repositório git

124

Eu tenho um repositório do GitHub que tinha dois ramos - master e release.

O ramo de lançamento continha arquivos de distribuição binária que estavam contribuindo para um tamanho de repositório muito grande (> 250 MB), então decidi limpar as coisas.

Primeiro, excluí o ramo de lançamento remoto, via git push origin :release

Em seguida, excluí o ramo de lançamento local. Primeiro tentei git branch -d release, mas o git disse "erro: o ramo 'release' não é um ancestral do seu HEAD atual". o que é verdade, então eu fizgit branch -D release para forçar a exclusão.

Mas o tamanho do meu repositório, localmente e no GitHub, ainda era enorme. Então, eu corri pela lista usual de comandos git, comogit gc --prune=today --aggressive , sem sorte.

Seguindo as instruções de Charles Bailey no SO 1029969 , consegui obter uma lista de SHA1s para os maiores blobs. Eu então usei o script do SO 460331 para encontrar os blobs ... e os cinco maiores não existem, embora sejam encontrados blobs menores, então eu sei que o script está funcionando.

Eu acho que esses blogs são os binários do ramo de lançamento e, de alguma forma, foram deixados de lado após a exclusão desse ramo. Qual é o caminho certo para se livrar deles?

kkrugler
fonte
Qual versão do Git você está usando? E você tentou stackoverflow.com/questions/1106529/… ?
VonC 15/12/2009
git versão 1.6.2.3 Tentei gc e podar com vários argumentos. Eu não tinha tentado reembalar -a -d -l, apenas executei, nenhuma alteração.
Kkrugler
2
Novas informações - um novo clone do GitHub não possui mais os blobs não referenciados e reduz "apenas" 84 MB a partir de 250 MB.
Kkrugler

Respostas:

218

... e sem mais delongas, posso apresentar a você este comando útil, "git-gc-all", garantido para remover todo o seu lixo git até que possam aparecer variáveis ​​extras de configuração:

git -c gc.reflogExpire=0 -c gc.reflogExpireUnreachable=0 -c gc.rerereresolved=0 -c gc.rerereunresolved=0 -c gc.pruneExpire=now gc

Você também pode precisar executar algo como estes primeiro, oh querido, o git é complicado!

git remote rm origin
rm -rf .git/refs/original/ .git/refs/remotes/ .git/*_HEAD .git/logs/
git for-each-ref --format="%(refname)" refs/original/ | xargs -n1 --no-run-if-empty git update-ref -d

Você também pode precisar remover algumas tags, graças ao Zitrax:

git tag | xargs git tag -d

Coloquei tudo isso em um script: git-gc-all-ferocious .

Sam Watkins
fonte
1
Interessante. Uma boa alternativa para minha resposta mais geral. +1
VonC
10
Isso merece mais votos. Finalmente, ele se livrou de muitos objetos git que outros métodos manteriam. Obrigado!
Jean-Philippe Pellet
1
Votado. Uau, eu não sei o que acabei de fazer, mas parece limpar bastante. Você pode elaborar o que faz? Tenho a sensação de que tudo foi esclarecido objects. O que são e por que são (aparentemente) irrelevantes?
Redsandro 16/01
1
@Redsandro, como eu entendo, os comandos "git rm origin", "rm" e "git update-ref -d" removem referências a confirmações antigas de controles remotos e coisas assim, o que pode estar impedindo a coleta de lixo. As opções para "git gc" dizem para ele não se apegar a vários commits antigos; caso contrário, ele se mantém por um tempo. Por exemplo, gc.rerereresolved é para "registros de mesclagem conflituosa que você resolveu anteriormente", por padrão mantidos por 60 dias. Essas opções estão na página de manual do git-gc. Não sou especialista em git e não sei exatamente o que todas essas coisas fazem. Eu os encontrei nas páginas de manual e grepping .git para commit refs.
Sam Watkins
1
Um objeto git é um arquivo ou uma árvore compactada ou confirmada em seu repositório git, incluindo coisas antigas da história. O git gc limpa objetos desnecessários. Ele mantém objetos que ainda são necessários para o seu repositório atual e seu histórico.
Sam Watkins
81

Conforme descrito aqui , se você deseja remover permanentemente tudo relacionado apenas via reflog , basta usar

git reflog expire --expire-unreachable=now --all
git gc --prune=now

git reflog expire --expire-unreachable=now --allremove todas as referências de confirmações inacessíveis em reflog.

git gc --prune=now remove os commits eles mesmos.

Atenção : Somente o uso git gc --prune=nownão funcionará, pois essas confirmações ainda são referenciadas no reflog. Portanto, limpar o reflog é obrigatório. Observe também que, se você o usar, terá rererereferências adicionais não limpas por esses comandos. Veja git help rererepara mais detalhes. Além disso, quaisquer confirmações referenciadas por ramificações ou tags locais ou remotas não serão removidas porque são consideradas dados valiosos pelo git.

jiasli
fonte
14
Funcionou, mas de alguma forma eu perdi meus esconderijos salvos no processo (nada grave no meu caso, apenas uma advertência para os outros)
Amro
1
por que não - agressivo?
JoelFan
2
Penso que esta resposta precisa de um aviso claro, de preferência no topo. Minha sugestão de edição foi rejeitada, porque acho que devo sugeri-la ao autor em um comentário? Aceite esta edição stackoverflow.com/review/suggested-edits/26023988 ou adicione um aviso à sua maneira. Além disso, isso derruba todos os seus esconderijos . Isso também deve constar no aviso!
Inigo
Testei com a versão 2.17 do git e as confirmações ocultas não serão removidas pelos comandos acima. Tem certeza de que não executou nenhum comando adicional?
Mikko Rantalainen
1
git fetch --prunereduzir ainda mais o tamanho porque a exclusão de blobs locais.
hectorpal 03/07
33

Conforme mencionado nesta resposta SO ,git gc pode realmente aumentar o tamanho do repositório!

Veja também este tópico

Agora, o git possui um mecanismo de segurança para não excluir objetos não referenciados imediatamente ao executar ' git gc'.
Por padrão, objetos não referenciados são mantidos por um período de 2 semanas. Isso facilita a recuperação de ramificações ou confirmações excluídas acidentalmente ou evita uma corrida em que um objeto recém-criado no processo de ser, mas ainda não referenciado, pode ser excluído por um git gcprocesso ' ' executado em paralelo.

Portanto, para conceder esse período de cortesia aos objetos compactados, mas não referenciados, o processo de reembalagem empurra esses objetos não referenciados para fora da embalagem em sua forma solta, para que possam ser envelhecidos e eventualmente removidos.
Objetos que se tornam não referenciados geralmente não são muitos. Ter 404855 objetos não referenciados é bastante, e enviar esses objetos em primeiro lugar por meio de um clone é estúpido e um desperdício completo de largura de banda da rede.

Enfim ... Para resolver seu problema, basta executar ' git gc' com o --prune=nowargumento para desativar esse período de carência e livrar-se desses objetos não referenciados imediatamente (seguro somente se nenhuma outra atividade git estiver ocorrendo ao mesmo tempo, o que deve seja fácil de garantir em uma estação de trabalho).

E BTW, usando ' git gc --aggressive' com uma versão posterior do git (ou ' git repack -a -f -d --window=250 --depth=250')

O mesmo segmento menciona :

 git config pack.deltaCacheSize 1

Isso limita o tamanho do cache delta a um byte (desativando-o efetivamente) em vez do padrão 0, o que significa ilimitado. Com isso, sou capaz de reembalar esse repositório usando o git repackcomando acima em um sistema x86-64 com 4 GB de RAM e usando 4 threads (este é um quad core). O uso de memória residente cresce para quase 3,3 GB.

Se sua máquina for SMP e você não tiver RAM suficiente, poderá reduzir o número de threads para apenas um:

git config pack.threads 1

Além disso, você pode limitar ainda mais o uso da memória com a --window-memory argumenttecla ' git repack'.
Por exemplo, o uso --window-memory=128Mdeve manter um limite superior razoável no uso da memória de pesquisa delta, embora isso possa resultar em uma correspondência menos otimizada se o repositório contiver muitos arquivos grandes.


Na parte da frente do filtro, você pode considerar (com cautela) esse script

#!/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
VonC
fonte
stackoverflow.com/questions/359424/… também é um bom começo para o filter-branchuso do comando.
VonC 15/12/2009
Oi VonC - A NI tentou o git gc ameixa = agora sem sorte. Parece realmente um bug do git, porque acabei localizando blobs não referenciados localmente após uma exclusão de ramificação, mas eles não estão lá com um novo clone do repositório GitHub ... então é apenas um problema de repositório local. Mas eu tenho arquivos adicionais que eu quero limpar, então o script que você mencionou acima é ótimo - obrigado!
Kkrugler
19

git gc --prune=now, ou nível baixo git prune --expire now.

Jakub Narębski
fonte
12

Cada vez que seu HEAD se move, o git rastreia isso no reflog. Se você removeu confirmações, você ainda tem "confirmações pendentes" porque elas ainda são referenciadas por reflog~ 30 dias. Esta é a rede de segurança quando você exclui confirmações por acidente.

Você pode usar o git reflogcomando remove commits específicos, reembalar, etc., ou apenas o comando de alto nível:

git gc --prune=now
vdboor
fonte
5

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

Deparo-me com o mesmo problema de vez em quando, e sempre que preciso voltar a este post e a outros, é por isso que automatizei o processo.

Créditos a colaboradores como Sam Watkins

nachoparker
fonte
2

Tente usar o git-filter-branch - ele não remove grandes blobs, mas pode remover arquivos grandes que você especificar em todo o repositório. Para mim, reduz o tamanho do repositório de centenas de MB para 12 MB.

W55tKQbuRu28Q4xv
fonte
6
Agora isso é um comando assustador :) Vou tentar quando meu git-fu parecer mais forte.
Kkrugler
Você pode dizer isso de novo. Sempre desconfio de qualquer comando que manipule a história de um repositório. As coisas tendem a dar muito errado quando várias pessoas estão empurrando e puxando desse repositório e de repente vários objetos que o git espera não estão lá.
Jonathan Dumaine
1

Às vezes, a razão pela qual "gc" não faz muito bem é que há uma recuperação ou esconderijo inacabado com base em um commit antigo.

StellarVortex
fonte
Ou o commit antigo é referenciado por HEAD, ORIG_HEAD, FETCH_HEAD, reflog ou alguma outra coisa que o git automaticamente tenta manter para garantir que nunca perca nada valioso. Se você realmente quer perder tudo isso, precisa percorrer uma milha extra para fazer isso.
Mikko Rantalainen
1

Para adicionar outra dica, não se esqueça de usar a remoção remota git para excluir as ramificações obsoletas de seus controles remotos antes de usar git gc

você pode vê-los com o git branch -a

Geralmente é útil quando você busca nos repositórios github e bifurcados ...

Tanguy
fonte
1

Antes de fazer git filter-branche git gc, revise as tags presentes no seu repositório. Qualquer sistema real que tenha marcação automática para coisas como integração e implantações contínuas fará com que objetos indesejados ainda sejam referenciados por essas tags, portanto, gcnão será possível removê-las e você continuará se perguntando por que o tamanho do repo ainda é tão grande.

A melhor maneira de se livrar de todas as coisas queria-un é executar git-filtere git gcem seguida, empurre mestre para um novo repo nua. O novo repositório vazio terá a árvore limpa.

v_abhi_v
fonte