Как найти / идентифицировать большие файлы / коммиты в истории 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 теперь есть плагин, который делает это в пользовательском интерфейсе (а также обрабатывает переписывание истории).
Шаг 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 МБ / с.