Como encontrar / identificar grandes confirmações no histórico do git?

366

Eu tenho um repositório Git de 300 MB. O tamanho total dos meus arquivos com saída atualmente registrada é de 2 MB e o tamanho total do restante do repositório git é de 298 MB. Este é basicamente um repositório somente de código que não deve ter mais do que alguns MB.

Suspeito que alguém acidentalmente tenha cometido alguns arquivos grandes (vídeo, imagens etc.) e os tenha removido ... mas não do git, portanto a história ainda contém arquivos grandes inúteis. Como encontrar os arquivos grandes no histórico do git? Existem mais de 400 confirmações, portanto, ir um a um não é prático.

NOTA : minha pergunta não é sobre como remover o arquivo , mas como encontrá- lo em primeiro lugar.

calça
fonte

Respostas:

143

Eu encontrei esse script muito útil no passado para encontrar objetos grandes (e não óbvios) em um repositório git:


#!/bin/bash
#set -x 

# Shows you the largest objects in your repo's pack file.
# Written for osx.
#
# @see https://stubbisms.wordpress.com/2009/07/10/git-script-to-show-largest-pack-objects-and-trim-your-waist-line/
# @author Antony Stubbs

# set the internal field separator to line break, so that we can iterate easily over the verify-pack output
IFS=$'\n';

# list all objects including their size, sort by size, take top 10
objects=`git verify-pack -v .git/objects/pack/pack-*.idx | grep -v chain | sort -k3nr | head`

echo "All sizes are in kB's. The pack column is the size of the object, compressed, inside the pack file."

output="size,pack,SHA,location"
allObjects=`git rev-list --all --objects`
for y in $objects
do
    # extract the size in bytes
    size=$((`echo $y | cut -f 5 -d ' '`/1024))
    # extract the compressed size in bytes
    compressedSize=$((`echo $y | cut -f 6 -d ' '`/1024))
    # extract the SHA
    sha=`echo $y | cut -f 1 -d ' '`
    # find the objects location in the repository tree
    other=`echo "${allObjects}" | grep $sha`
    #lineBreak=`echo -e "\n"`
    output="${output}\n${size},${compressedSize},${other}"
done

echo -e $output | column -t -s ', '

Isso fornecerá o nome do objeto (SHA1sum) do blob e você poderá usar um script como este:

... para encontrar o commit que aponta para cada um desses blobs.

Mark Longair
fonte
31
Esta resposta foi realmente útil, porque me enviou para o post acima. Enquanto o script da postagem funcionava, achei-a dolorosamente lenta. Então, eu a reescrevi, e agora é significativamente mais rápido em grandes repositórios. Dê uma olhada: gist.github.com/nk9/b150542ef72abc7974cb
Nick K9
7
Inclua instruções completas em suas respostas e não apenas links externos; O que fazemos quando stubbisms.wordpress.com inevitavelmente cai, hein?
ThorSummoner
@ NickK9, curiosamente, recebo resultados diferentes do seu script e do outro. há um monte de objetos maiores que o seu parece perder. Tem algo que estou perdendo?
UpAndAdam
Oh fixe! Obrigado por tornar meu script mais rápido @nick \ k9: D @UpAndAdam, você está dizendo que meu script produziu uma saída incorreta?
Antony Stubbs
11
Esses comentários fazem parecer que estamos relatando tamanho em bytes, mas recebo kilobytes.
Kat
683

🚀 Um one-liner shell incrivelmente rápido 🚀

Esse script de shell exibe todos os objetos de blob no repositório, classificados do menor para o maior.

Para o meu repositório de amostras, ele foi executado 100 vezes mais rápido que os outros encontrados aqui.
No meu confiável sistema Athlon II X4, ele lida com o repositório Linux Kernel com seus 5,6 milhões de objetos em pouco mais de um minuto .

O Script Base

git rev-list --objects --all \
| git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' \
| sed -n 's/^blob //p' \
| sort --numeric-sort --key=2 \
| cut -c 1-12,41- \
| $(command -v gnumfmt || echo numfmt) --field=2 --to=iec-i --suffix=B --padding=7 --round=nearest

Quando você executa o código acima, você obtém uma boa saída legível por humanos como esta:

...
0d99bb931299  530KiB path/to/some-image.jpg
2ba44098e28f   12MiB path/to/hires-image.png
bd1741ddce0d   63MiB path/to/some-video-1080p.mp4

Usuários do macOS : como numfmtnão está disponível no macOS, você pode omitir a última linha e lidar com tamanhos de bytes brutos ou brew install coreutils.

Filtragem

Para obter mais filtragem , insira qualquer uma das seguintes linhas antes da sortlinha .

Para excluir arquivos presentesHEAD , insira a seguinte linha:

| grep -vF --file=<(git ls-tree -r HEAD | awk '{print $3}') \

Para mostrar apenas os arquivos que excedem o tamanho especificado (por exemplo, 1 MiB = 2 20  B), insira a seguinte linha:

| awk '$2 >= 2^20' \

Saída para computadores

Para gerar uma saída mais adequada para processamento adicional por computadores, omita as duas últimas linhas do script base. Eles fazem toda a formatação. Isso deixará você com algo assim:

...
0d99bb93129939b72069df14af0d0dbda7eb6dba 542455 path/to/some-image.jpg
2ba44098e28f8f66bac5e21210c2774085d2319b 12446815 path/to/hires-image.png
bd1741ddce0d07b72ccf69ed281e09bf8a2d0b2f 65183843 path/to/some-video-1080p.mp4

Remoção de arquivo

Para a remoção real do arquivo, consulte esta questão SO no tópico .

raphinesse
fonte
14
Isso merece mais do que apenas o meu voto! Agradecimentos especiais por fornecer saída legível por computador e humana.
Michel Jung
2
Isso é extremamente rápido e fácil de usar!
Chin
32
Para usar isso no Mac, você precisa brew install coreutilse substitui cutpor gcute numfmtcom gnumfmt.
Nick Sweeting
2
Deixe-me enfatizar novamente: isso é muito mais rápido do que todas as outras listagens que eu já vi.
Sridhar Sarnobat
4
isso faz um alias impressionante do git :) git largealguém?
Anarcat
160

Encontrei uma solução de uma linha na página wiki do ETH Zurich Department of Physics (perto do final dessa página). Basta fazer um git gcpara remover o lixo obsoleto e depois

git rev-list --objects --all \
  | grep "$(git verify-pack -v .git/objects/pack/*.idx \
           | sort -k 3 -n \
           | tail -10 \
           | awk '{print$1}')"

lhe dará os 10 maiores arquivos do repositório.

Agora também há uma solução mais preguiçosa, o GitExtensions agora possui um plug-in que faz isso na interface do usuário (e também lida com reescrições do histórico).

Caixa de diálogo 'Localizar arquivos grandes' do GitExtensions

Skolima
fonte
8
Essa linha única funciona apenas se você deseja obter o maior arquivo único (por exemplo, use tail -1). As novas linhas atrapalham qualquer coisa maior. Você pode usar o sed para converter as novas linhas, para que o grep seja agradável:git rev-list --objects --all | grep -E `git verify-pack -v .git/objects/pack/*.idx | sort -k 3 -n | tail -10 | awk '{print$1}' | sed ':a;N;$!ba;s/\n/|/g'`
Throctukes
10
grep: a70783fca9bfbec1ade1519a41b6cc4ee36faea0: Arquivo ou diretório não
Jonathan Allard
11
O link do wiki foi movido para: readme.phys.ethz.ch/documentation/git_advanced_hints
outsmartin
11
Encontrar GitExtensions é como encontrar o pote de ouro e o fim do arco-íris - obrigado!
ckapilla
3
Existe também uma extensão que imprime o tamanho dos arquivos?
Michael
27

Etapa 1 Escreva todos os SHA1s de arquivo em um arquivo de texto:

git rev-list --objects --all | sort -k 2 > allfileshas.txt

Etapa 2 Classifique os blobs do maior para o menor e grave os resultados no arquivo de texto:

git gc && git verify-pack -v .git/objects/pack/pack-*.idx | egrep "^\w+ blob\W+[0-9]+ [0-9]+ [0-9]+$" | sort -k 3 -n -r > bigobjects.txt

Etapa 3a Combine os dois arquivos de texto para obter informações sobre o nome do arquivo / sha1 / size:

for SHA in `cut -f 1 -d\  < bigobjects.txt`; do
echo $(grep $SHA bigobjects.txt) $(grep $SHA allfileshas.txt) | awk '{print $1,$3,$7}' >> bigtosmall.txt
done;

Etapa 3b Se você tiver nomes de arquivos ou caminhos que contenham espaços, tente esta variação da Etapa 3a. Ele usa em cutvez de awkpara obter as colunas desejadas, incl. espaços da coluna 7 até o final da linha:

for SHA in `cut -f 1 -d\  < bigobjects.txt`; do
echo $(grep $SHA bigobjects.txt) $(grep $SHA allfileshas.txt) | cut -d ' ' -f'1,3,7-' >> bigtosmall.txt
done;

Agora você pode olhar para o arquivo bigtosmall.txt para decidir quais arquivos você deseja remover do seu histórico do Git.

Etapa 4 Para executar a remoção (observe que esta parte é lenta, pois ela examinará todas as confirmações em seu histórico quanto a dados sobre o arquivo que você identificou):

git filter-branch --tree-filter 'rm -f myLargeFile.log' HEAD

Fonte

As etapas 1-3a foram copiadas de Localização e remoção de arquivos grandes do histórico do Git

EDITAR

O artigo foi excluído em algum momento no segundo semestre de 2017, mas uma cópia arquivada ainda pode ser acessada usando a Wayback Machine .

friederbluemle
fonte
6
Um forro para fazer a mesma coisa:git gc && join -e ERROR -a 2 -j 1 -o 2.1,2.3,1.2 --check-order <( git rev-list --objects --all | sort -k 1 ) <( git verify-pack -v .git/objects/pack/pack-*.idx | gawk '( NF == 5 && $2 == "blob" ){print}' | sort -k1 ) | sort -k2gr
Iwan Aucamp
11
@Iwan, obrigado pelo one-liner! Ele não lida com nomes de arquivos com espaços entre eles, isto parece: join -t' ' -e ERROR -a 2 -j 1 -o 2.1,2.3,1.2 --check-order <( git rev-list --objects --all | sed 's/[[:space:]]/\t/' | sort -k 1 ) <( git verify-pack -v .git/objects/pack/pack-*.idx | gawk '( NF == 5 && $2 == "blob" ){print}' | sort -k1 | sed 's/[[:space:]]\+/\t/g' ) | sort -k2gr | less. Note que você tem que digitar o caractere real TAB depois join -t'com CTRL + V <TAB> per geekbraindump.blogspot.ru/2009/04/unix-join-with-tabs.html
Nickolay
2
@Nickolay com bash $'\t'deve fornecer uma guia. echo -n $'\t' | xxd -ps->09
Iwan Aucamp
11
@IwanAucamp: melhor ainda, obrigado pela dica! (Pena que eu não posso editar o comentário anterior .. oh bem.)
Nickolay
11
@ Sridhar-Sarnobat O artigo foi salvo pela Wayback Machine! :) web.archive.org/web/20170621125743/http://www.naleid.com/blog/…
friederbluemle
18

Você deve usar o BFG Repo-Cleaner .

De acordo com o site:

O BFG é uma alternativa mais simples e rápida ao git-filter-branch para limpar dados ruins do histórico do repositório Git:

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

O procedimento clássico para reduzir o tamanho de um repositório seria:

git clone --mirror git://example.com/some-big-repo.git
java -jar bfg.jar --strip-biggest-blobs 500 some-big-repo.git
cd some-big-repo.git
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push
Warren Seine
fonte
4
O BFG Repo-Cleaner é muito bom. É extremamente rápido e funciona com muita confiabilidade.
Fschmitt
30
Isso não mostra como listar todos os arquivos maiores.
Andi Jay
5
O problema é que você não pode simplesmente ver quais são os grandes arquivos sem removê-los. Não me sinto confortável fazendo isso sem uma execução a seco primeiro, que simplesmente lista os arquivos grandes.
Sridhar Sarnobat
O que --strip-biggest-blobs 500faz?
2540625
O git rejeitará as alterações feitas por essa ferramenta.
Christopher
9

Se você deseja apenas ter uma lista de arquivos grandes, gostaria de fornecer o seguinte conteúdo:

join -o "1.1 1.2 2.3" <(git rev-list --objects --all | sort) <(git verify-pack -v objects/pack/*.idx | sort -k3 -n | tail -5 | sort) | sort -k3 -n

Cuja produção será:

commit       file name                                  size in bytes

72e1e6d20... db/players.sql 818314
ea20b964a... app/assets/images/background_final2.png 6739212
f8344b9b5... data_test/pg_xlog/000000010000000000000001 1625545
1ecc2395c... data_development/pg_xlog/000000010000000000000001 16777216
bc83d216d... app/assets/images/background_1forfinal.psd 95533848

A última entrada na lista aponta para o maior arquivo do seu histórico do git.

Você pode usar essa saída para garantir que não esteja excluindo itens com o BFG que você precisaria em seu histórico.

schmijos
fonte
2
Impressionante!! No entanto, você deve observar que é necessário clonar o repositório com as opções --mirror antes de executar este comando.
Andi Jay
Estou curioso, para que servem os 1.1, 1.2, 2.3números?
Ympostor
Os números são uma lista de <filenumber>.<field>especificação da ordem da combinação. Veja man.cx/join para mais informações.
schmijos
6

Se você estiver no Windows, aqui está um script do PowerShell que imprimirá os 10 maiores arquivos do seu repositório:

$revision_objects = git rev-list --objects --all;
$files = $revision_objects.Split() | Where-Object {$_.Length -gt 0 -and $(Test-Path -Path $_ -PathType Leaf) };
$files | Get-Item -Force | select fullname, length | sort -Descending -Property Length | select -First 10
Julia Schwarz
fonte
11
Isso produz uma resposta diferente para @raphinesse, perdendo muitos dos maiores arquivos do meu repositório. Além disso, quando um arquivo grande possui muitas modificações, apenas o maior tamanho é relatado.
22417 kristianp
Este script falhou para mim, com o erro: You cannot call a method on a null-valued expression. At line: 2 char: 1. No entanto, esta resposta funcionou: stackoverflow.com/a/57793716/2441655 (também é mais curto)
Venryx
4

Tente git ls-files | xargs du -hs --threshold=1M.

Usamos o comando abaixo em nosso pipeline de IC, ele para se encontrar algum arquivo grande no repositório git:

test $(git ls-files | xargs du -hs --threshold=1M 2>/dev/null | tee /dev/stderr | wc -l) -gt 0 && { echo; echo "Aborting due to big files in the git repository."; exit 1; } || true
Vojtech Vitek
fonte
2

Não pude usar a resposta mais popular porque a --batch-checkopção de linha de comando para o Git 1.8.3 (que eu tenho que usar) não aceita argumentos. As etapas a seguir foram tentadas no CentOS 6.5 com Bash 4.1.2

Conceitos chave

No Git, o termo blob implica o conteúdo de um arquivo. Observe que uma confirmação pode alterar o conteúdo de um arquivo ou nome de caminho. Portanto, o mesmo arquivo pode se referir a um blob diferente, dependendo da confirmação. Um determinado arquivo pode ser o maior na hierarquia de diretórios em um commit, enquanto não em outro. Portanto, a questão de encontrar confirmações grandes em vez de arquivos grandes coloca os assuntos na perspectiva correta.

Para o impaciente

O comando para imprimir a lista de blobs em ordem decrescente de tamanho é:

git cat-file --batch-check < <(git rev-list --all --objects  | \
awk '{print $1}')  | grep blob  | sort -n -r -k 3

Saída de amostra:

3a51a45e12d4aedcad53d3a0d4cf42079c62958e blob 305971200
7c357f2c2a7b33f939f9b7125b155adbd7890be2 blob 289163620

Para remover esses blobs, use o BFG Repo Cleaner , conforme mencionado em outras respostas. Dado um arquivo blobs.txtque contém apenas os hashes de blob, por exemplo:

3a51a45e12d4aedcad53d3a0d4cf42079c62958e
7c357f2c2a7b33f939f9b7125b155adbd7890be2

Faz:

java -jar bfg.jar -bi blobs.txt <repo_dir>

A questão é encontrar os commits, o que é mais trabalhoso do que encontrar blobs. Para saber, por favor, continue lendo.

Trabalho adicional

Dado um hash de confirmação, um comando que imprime hashes de todos os objetos associados a ele, incluindo blobs, é:

git ls-tree -r --full-tree <commit_hash>

Portanto, se tivermos essas saídas disponíveis para todas as confirmações no repositório, com um hash de blob, o conjunto de confirmações será o que corresponderá a qualquer uma das saídas. Essa ideia está codificada no seguinte script:

#!/bin/bash
DB_DIR='trees-db'

find_commit() {
    cd ${DB_DIR}
    for f in *; do
        if grep -q $1 ${f}; then
            echo ${f}
        fi
    done
    cd - > /dev/null
}

create_db() {
    local tfile='/tmp/commits.txt'
    mkdir -p ${DB_DIR} && cd ${DB_DIR}
    git rev-list --all > ${tfile}

    while read commit_hash; do
        if [[ ! -e ${commit_hash} ]]; then
            git ls-tree -r --full-tree ${commit_hash} > ${commit_hash}
        fi
    done < ${tfile}
    cd - > /dev/null
    rm -f ${tfile}
}

create_db

while read id; do
    find_commit ${id};
done

Se o conteúdo for salvo em um arquivo nomeado find-commits.sh, uma chamada típica será como em:

cat blobs.txt | find-commits.sh

Como anteriormente, o arquivo blobs.txtlista hashes de blob, um por linha. A create_db()função salva um cache de todas as listagens de confirmação em um subdiretório no diretório atual.

Algumas estatísticas dos meus experimentos em um sistema com dois processadores Intel (R) Xeon (E5) CPU E5-2620 2.00GHz apresentados pelo SO como 24 núcleos virtuais:

  • Número total de confirmações no repositório = quase 11.000
  • Velocidade de criação de arquivo = 126 arquivos / s. O script cria um único arquivo por confirmação. Isso ocorre apenas quando o cache está sendo criado pela primeira vez.
  • Sobrecarga de criação de cache = 87 s.
  • Velocidade média de pesquisa = 522 confirma / s. A otimização do cache resultou em uma redução de 80% no tempo de execução.

Observe que o script é de thread único. Portanto, apenas um núcleo seria usado por vez.

pdp
fonte
2

Solução Powershell para windows git, encontre os maiores arquivos:

git ls-tree -r -t -l --full-name HEAD | Where-Object {
 $_ -match '(.+)\s+(.+)\s+(.+)\s+(\d+)\s+(.*)'
 } | ForEach-Object {
 New-Object -Type PSObject -Property @{
     'col1'        = $matches[1]
     'col2'      = $matches[2]
     'col3' = $matches[3]
     'Size'      = [int]$matches[4]
     'path'     = $matches[5]
 }
 } | sort -Property Size -Top 10 -Descending
Aaron
fonte
0

Como posso rastrear os arquivos grandes no histórico do git?

Comece analisando, validando e selecionando a causa raiz. Usargit-repo-analysis para ajudar.

Você também pode encontrar algum valor nos relatórios detalhados gerados pelo BFG Repo-Cleaner , que podem ser executados muito rapidamente clonando uma gota do Digital Ocean usando sua taxa de transferência de rede de 10MiB / s.

Josh Habdas
fonte
Eu acho que você tem uma boa resposta geral na sugestão do BFG, mas você a estraga não fornecendo detalhes e sugerindo o uso de um serviço de terceiros diferente (também sem qualquer explicação). Você pode limpar isso alguns para fornecer um exemplo de uso de BFG na linha de comando?
phord
0

Eu me deparei com isso pela mesma razão que qualquer outra pessoa. Mas os scripts citados não funcionaram para mim. Eu fiz um que é mais um híbrido daqueles que eu já vi e agora mora aqui - https://gitlab.com/inorton/git-size-calc

IanNorton
fonte