Как найти / идентифицировать большие файлы / коммиты в истории Git?

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

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

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

15 ответов

Решение

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

Сверхбыстрая оболочка с одним вкладышем

Этот сценарий оболочки отображает все объекты 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 вопрос по теме.

Я нашел однострочное решение на вики-странице 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

Шаг 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

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

Вы должны использовать 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

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

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, которые вам понадобились бы в вашей истории.

Если вы работаете в 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

Для Windows я написал версию этого ответа для Powershell :

      function Get-BiggestBlobs {
  param ([Parameter(Mandatory)][String]$RepoFolder, [int]$Count = 10)
  Write-Host ("{0} biggest files:" -f $Count)
  git -C $RepoFolder rev-list --objects --all | git -C $RepoFolder cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | ForEach-Object {
    $Element = $_.Trim() -Split '\s+'
    $ItemType = $Element[0]
    if ($ItemType -eq 'blob') {
      New-Object -TypeName PSCustomObject -Property @{
          ObjectName = $Element[1]
          Size = [int]([int]$Element[2] / 1kB)
          Path = $Element[3]
      }
    }
  } | Sort-Object Size | Select-Object -last $Count | Format-Table ObjectName, @{L='Size [kB]';E={$_.Size}}, Path -AutoSize
}

Вы, вероятно, захотите точно настроить, отображается ли он в килобайтах или мегабайтах или только в байтах, в зависимости от вашей ситуации.

Вероятно, есть потенциал для оптимизации производительности, поэтому не стесняйтесь экспериментировать, если вас это беспокоит.

Чтобы получить все изменения, просто опустите | Select-Object -last $Count.
Чтобы получить более машиночитаемую версию, просто опустите | Format-Table @{L='Size [kB]';E={$_.Size}}, Path -AutoSize.

Пытаться 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

Решение 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

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

Ключевые понятия

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

3a51a45e12d4aedcad53d3a0d4cf42079c62958e
7c357f2c2a7b33f939f9b7125b155adbd7890be2

Делать:

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

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

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

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

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

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

#!/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 виртуальных ядра:

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

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

Использовать--analyzeособенность git-filter-repo :

      $ cd my-repo-folder
$ git-filter-repo --analyze
$ less .git/filter-repo/analysis/path-all-sizes.txt

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

чтобы получить представление о «размере diff» последних коммитов в истории git

      git log --stat

это покажет размер diff в строках: строки добавлены, строки удалены

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

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

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

Другие вопросы по тегам