Как найти / идентифицировать крупные коммиты в истории git?

366

У меня есть 300 МБ GIT-репо. Общий размер моих извлеченных файлов составляет 2 МБ, а общий размер остальной части репозитория git - 298 МБ. По сути, это репо с кодом, размер которого не должен превышать нескольких МБ.

Я подозреваю, что кто-то случайно передал некоторые большие файлы (видео, изображения и т. Д.), А затем удалил их ... но не из git, поэтому история по-прежнему содержит бесполезные большие файлы. Как найти большие файлы в истории git? Есть более 400 коммитов, так что идти один за другим непрактично.

ПРИМЕЧАНИЕ : мой вопрос не о том, как удалить файл , а о том, как его найти .

штаны
источник

Ответы:

143

Я нашел этот скрипт очень полезным в прошлом для поиска больших (и неочевидных) объектов в репозитории 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 ', '

Это даст вам имя объекта (SHA1sum) большого двоичного объекта, а затем вы можете использовать такой скрипт:

... чтобы найти коммит, который указывает на каждый из этих BLOB-объектов.

Марк Лонгэйр
источник
31
Этот ответ был действительно полезным, потому что отправил меня на пост выше. В то время как сценарий поста работал, я нашел его мучительно медленным. Поэтому я переписал его, и теперь он значительно быстрее в больших репозиториях. Посмотрите: gist.github.com/nk9/b150542ef72abc7974cb
Ник К9
7
Пожалуйста, включите в свои ответы полные инструкции, а не только ссылки вне сайта; Что мы делаем, когда stubbisms.wordpress.com неизбежно падает, а?
ThorSummoner
@ NickK9 интересно, я получаю разные результаты из вашего сценария и других. есть куча более крупных объектов, которые, кажется, твой скучает. Я что-то упускаю?
UpAndAdam
О, круто! Спасибо, что сделали мой скрипт быстрее @nick \ k9: D @UpAndAdam, вы говорите, что мой скрипт выдает неправильный вывод?
Энтони Стаббс
1
Эти комментарии звучат так, будто мы сообщаем размер в байтах, но я получаю килобайты.
Кат
685

Fast Чертовски быстрый однострочный корпус 🚀

Этот сценарий оболочки отображает все объекты BLOB-объектов в хранилище, отсортированные от наименьшего к наибольшему.

Для моего примера репо он работал примерно в 100 раз быстрее, чем другие, найденные здесь.
В моей надежной системе Athlon II X4 она обрабатывает репозиторий ядра Linux с 5,6 миллионами объектов всего за минуту .

Базовый сценарий

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

Когда вы запустите код выше, вы получите хороший читабельный вывод, подобный этому:

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

пользователи macOS : поскольку numfmtв macOS это невозможно, вы можете либо пропустить последнюю строку и работать с необработанными байтами, либо brew install coreutils.

фильтрация

Для дальнейшей фильтрации вставьте любую из следующих строк перед sortстрокой .

Чтобы исключить файлы, присутствующие вHEAD , вставьте следующую строку:

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

к показать только файлы, размер которых превышает заданный размер (например, 1 МБ = 2 20  Б), вставьте следующую строку:

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

Выход для компьютеров

Для генерации выхода это более подходящий для дальнейшей обработки компьютерами, пропустите две последние строки базового сценария. Они делают все форматирование. Это оставит вас с чем-то вроде этого:

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

Удаление файла

Для фактического удаления файла, проверьте этот SO вопрос по теме .

raphinesse
источник
14
Это заслуживает большего, чем просто мое возражение! Особая благодарность за то, чтобы обеспечить, и компьютер и человеческий читаемый вывод.
Мишель Юнг
2
Это очень быстро и просто в использовании!
Чин
32
Чтобы использовать это на Mac, вам нужно, brew install coreutilsа затем заменить cutна gcutи numfmtс gnumfmt.
Ник Свитинг
2
Позвольте мне еще раз подчеркнуть - это намного быстрее, чем все другие списки, которые я видел.
Шридхар Сарнобат
4
это делает отличный псевдоним Git :) git largeкто-нибудь?
Анаркат
160

Я нашел однострочное решение на вики-странице ETH Zurich Department of Physics (ближе к концу этой страницы). Просто сделайте, git gcчтобы удалить несвежий мусор, а затем

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

даст вам 10 самых больших файлов в хранилище.

Также доступно более ленивое решение, в GitExtensions теперь есть плагин, который делает это в пользовательском интерфейсе (а также обрабатывает переписывание истории).

GitExtensions диалоговое окно «Найти большие файлы»

skolima
источник
8
Этот однострочник работает, только если вы хотите получить самый большой файл (т. Е. Использовать хвост -1). Новые строки мешают чему-то большему. Вы можете использовать sed для преобразования строк, поэтому grep будет хорошо играть: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: Нет такого файла или каталога
Джонатан Аллард
1
Ссылка на вики перенесена на: readme.phys.ethz.ch/documentation/git_advanced_hints
outsmartin
11
Найти GitExtensions - это все равно, что найти горшок с золотом и конец радуги - спасибо!
ckapilla
3
Есть ли также расширение, которое печатает размер файлов?
Майкл
27

Шаг 1 Запишите все файлы SHA1 в текстовый файл:

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

Шаг 2 Сортировка больших двоичных объектов с самых больших на маленькие и запись результатов в текстовый файл:

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

Шаг 3a Объедините оба текстовых файла, чтобы получить информацию об имени файла / 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;

Шаг 3b Если у вас есть имена файлов или пути, содержащие пробелы, попробуйте этот вариант шага 3a. Используется cutвместо того, awkчтобы получить нужные столбцы вкл. пробелы от столбца 7 до конца строки:

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;

Теперь вы можете посмотреть файл bigtosmall.txt, чтобы решить, какие файлы вы хотите удалить из своей истории Git.

Шаг 4 Чтобы выполнить удаление (обратите внимание, что эта часть медленная, так как она собирается проверять каждый коммит в вашей истории на предмет данных о файле, который вы идентифицировали):

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

Источник

Шаги 1-3a были скопированы из поиска и очистки больших файлов из истории Git

РЕДАКТИРОВАТЬ

Статья была удалена где-то во второй половине 2017 года, но к ее архивной копии все еще можно получить доступ с помощью Wayback Machine .

friederbluemle
источник
6
Один лайнер, чтобы сделать то же самое: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
1
@ Иван, спасибо за лайнер! Он не обрабатывает имена файлов с пробелами в них, это , кажется: 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. Обратите внимание, что после ввода join -t'CTRL + V <TAB> необходимо ввести фактический символ табуляции в geekbraindump.blogspot.ru/2009/04/unix-join-with-tabs.html
Николай
2
@ Николай с Bash $'\t'должен дать вам вкладку. echo -n $'\t' | xxd -ps->09
Iwan Aucamp
1
@IwanAucamp: еще лучше, спасибо за совет! (Жаль, что я не могу отредактировать предыдущий комментарий .. да ладно.)
Николай
1
@ Sridhar-Sarnobat Статья была сохранена на Wayback Machine! :) web.archive.org/web/20170621125743/http://www.naleid.com/blog/…
friederbluemle
18

Вы должны использовать BFG Repo-Cleaner .

По данным сайта:

BFG - это более простая и быстрая альтернатива git-filter-branch для удаления плохих данных из истории вашего репозитория Git:

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

Классическая процедура для уменьшения размера хранилища будет:

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
Уоррен Сена
источник
4
BFG Repo-Cleaner очень хорош. Он светит быстро и работает очень надежно.
fschmitt
30
Это не говорит вам, как перечислить все самые большие файлы, хотя.
Энди Джей
5
Проблема в том, что вы не можете просто ВИДЕТЬ большие файлы, не удаляя их. Мне неудобно делать это без пробного запуска, в котором просто перечислены большие файлы.
Шридхар Сарнобат
Что делает --strip-biggest-blobs 500?
2540625
Git будет отклонять изменения, сделанные этим инструментом.
Кристофер
9

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

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

Чей вывод будет:

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

Последняя запись в списке указывает на самый большой файл в вашей истории git.

Вы можете использовать этот вывод, чтобы гарантировать, что вы не удаляете вещи с BFG, которые вам понадобились бы в вашей истории.

schmijos
источник
2
Потрясающие!! Однако вы должны заметить, что вам нужно клонировать репозиторий с параметрами --mirror перед запуском этой команды.
Энди Джей
Мне интересно, для чего нужны 1.1, 1.2, 2.3цифры?
Импостор
Числа представляют собой список, <filenumber>.<field>указывающий порядок комбинации. Смотрите man.cx/join для получения дополнительной информации.
schmijos
6

Если вы работаете в Windows, вот скрипт PowerShell, который напечатает 10 самых больших файлов в вашем хранилище:

$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
Юлия Шварц
источник
1
Это дает ответ, отличный от @raphinesse, пропуская кучу самых больших файлов в моем хранилище. Также, когда один большой файл имеет много модификаций, сообщается только о самом большом размере.
kristianp
Этот сценарий не смог для меня, с ошибкой: You cannot call a method on a null-valued expression. At line: 2 char: 1. Однако этот ответ сработал: stackoverflow.com/a/57793716/2441655 (он также короче)
Venryx
4

Пытаться git ls-files | xargs du -hs --threshold=1M .

Мы используем приведенную ниже команду в нашем конвейере CI, она останавливается, если находит в 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
Войтех Витек
источник
2

Я не смог использовать самый популярный ответ, потому что --batch-check переключатель командной строки для Git 1.8.3 (который я должен использовать) не принимает никаких аргументов. Последующие шаги были опробованы на CentOS 6.5 с Bash 4.1.2

Ключевые идеи

В Git термин blob подразумевает содержимое файла. Обратите внимание, что фиксация может изменить содержимое файла или пути. Таким образом, один и тот же файл может ссылаться на другой BLOB-объект в зависимости от фиксации. Определенный файл может быть самым большим в иерархии каталогов в одном коммите, а не в другом. Поэтому вопрос поиска больших коммитов вместо больших файлов ставит вопросы в правильном ракурсе.

Для нетерпеливых

Команда для печати списка больших двоичных объектов в порядке убывания размера:

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

Пример вывода:

3a51a45e12d4aedcad53d3a0d4cf42079c62958e blob 305971200
7c357f2c2a7b33f939f9b7125b155adbd7890be2 blob 289163620

Чтобы удалить такие капли, используйте BFG Repo Cleaner , как указано в других ответах. Имеется файл, blobs.txtкоторый содержит только хэши больших двоичных объектов, например:

3a51a45e12d4aedcad53d3a0d4cf42079c62958e
7c357f2c2a7b33f939f9b7125b155adbd7890be2

Делать:

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

Вопрос в том, чтобы найти коммиты, а это больше работы, чем поиск блобов. Чтобы узнать, пожалуйста, читайте дальше.

Дальнейшая работа

С учетом хэша коммита команда, которая печатает хэши всех объектов, связанных с ним, включая большие двоичные объекты:

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

Таким образом, если у нас есть такие выходные данные, доступные для всех коммитов в репо, то с учетом хэша большого двоичного фрагмента, те коммиты, которые соответствуют любому из выходных данных. Эта идея закодирована в следующем сценарии:

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

Если содержимое сохранено в файле с именем, find-commits.shтипичный вызов будет выглядеть так:

cat blobs.txt | find-commits.sh

Как и ранее, в файле blobs.txtперечислены хэши BLOB-объектов, по одному на строку. create_db()Функция сохраняет кэш всех фиксации списков в подкаталог в текущем каталоге.

Немного статистики из моих экспериментов на системе с двумя процессорами Intel (R) Xeon (R) CPU E5-2620 2,00 ГГц, представленной ОС как 24 виртуальных ядра:

  • Общее количество коммитов в репо = почти 11 000
  • Скорость создания файла = 126 файлов / с. Сценарий создает один файл на коммит. Это происходит только тогда, когда кэш создается впервые.
  • Затраты на создание кэша = 87 с.
  • Средняя скорость поиска = 522 коммитов / с. Оптимизация кэша привела к сокращению времени выполнения на 80%.

Обратите внимание, что скрипт является однопоточным. Следовательно, только одно ядро ​​будет использоваться одновременно.

ПРП
источник
2

Решение Powershell для Windows Git, найти самые большие файлы:

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
Аарон
источник
0

Как я могу отследить большие файлы в истории git?

Начните с анализа, проверки и выбора основной причины. Используйте, git-repo-analysisчтобы помочь.

Вы также можете найти некоторую ценность в подробных отчетах, сгенерированных BFG Repo-Cleaner , которые можно очень быстро запустить путем клонирования в каплю Digital Ocean с использованием пропускной способности сети 10 МБ / с.

Джош Хабдас
источник
Я думаю, что у вас есть хороший общий ответ в предложении BFG, но вы портите его, не сообщая никаких подробностей, а затем предлагая использовать сторонний сервис (также без каких-либо объяснений). Можете ли вы очистить это, чтобы предоставить пример использования BFG в командной строке?
phord
0

Я наткнулся на это по той же причине, что и все остальные. Но приведенные сценарии не совсем сработали для меня. Я сделал один, который является более гибридным из тех, что я видел, и теперь он живет здесь - https://gitlab.com/inorton/git-size-calc

IanNorton
источник