Отклонение больших файлов в git
-
21-08-2019 - |
Вопрос
Недавно мы начали использовать git, и у нас возникла неприятная проблема, когда кто-то зафиксировал большой файл (около 1,5 ГБ), что затем приводило к сбою git на различных 32-битных ОС.Кажется, это известная ошибка (файлы git mmaps помещаются в память, что не работает, если не хватает непрерывного пространства), которая не будет исправлена в ближайшее время.
Самым простым (для нас) решением было бы заставить git отклонять любые коммиты размером более 100 МБ или около того, но я не могу придумать, как это сделать.
РЕДАКТИРОВАТЬ:Проблема возникает из-за случайной отправки большого файла, в данном случае большого дампа вывода программы.Цель состоит в том, чтобы избежать случайной отправки, просто потому, что если разработчик случайно отправляет большой файл, то попытка вернуть его обратно из репозитория — это полдень, когда никто не может выполнять какую-либо работу, и ему приходится исправлять все локальные ветки, которые они иметь.
Решение
Когда именно возникла проблема?Когда они изначально зафиксировали файл или когда он был отправлен в другое место?Если у вас есть промежуточный репозиторий, к которому все обращаются, вы можете реализовать функцию обновления для сканирования изменяющихся ссылок на наличие больших файлов, а также проверки других разрешений и т. д.
Очень грубый и готовый пример:
git --no-pager log --pretty=oneline --name-status $2..$3 -- | \
perl -MGit -lne 'if (/^[0-9a-f]{40}/) { ($rev, $message) = split(/\s+/, $_, 2) }
else { ($action, $file) = split(/\s+/, $_, 2); next unless $action eq "A";
$filesize = Git::command_oneline("cat-file", "-s", "$rev:$file");
print "$rev added $file ($filesize bytes)"; die "$file too big" if ($filesize > 1024*1024*1024) }';
(просто показывает, что все можно сделать с помощью однострочника Perl, хотя это может занять несколько строк;))
Вызывается так же, как вызывается $GIT_DIR/hooks/update (аргументы: ref-name, old-rev, new-rev;например«refs/heads/master master~2 master»), это покажет добавленные файлы и прекратит работу, если будет добавлен слишком большой файл.
Обратите внимание: я бы сказал, что если вы собираетесь контролировать подобные вещи, вам нужен централизованный пункт, где это можно делать.Если вы доверяете своей команде просто обмениваться изменениями друг с другом, вы должны доверять им и понять, что добавление гигантских двоичных файлов — это плохо.
Другие советы
Вы можете распространить ловушку предварительной фиксации, которая предотвращает фиксации.В центральных репозиториях вы можете использовать перехватчик предварительного получения, который отклоняет большие BLOB-объекты, анализируя полученные данные и предотвращая обращение к ним.Данные будут получены, но поскольку вы отклоняете обновления ссылок, все полученные новые объекты не будут иметь ссылок и могут быть выбраны и удалены с помощью git gc.
Хотя у меня нет для вас сценария.
Если у вас есть контроль над набором инструментов ваших коммиттеров, возможно, вам будет несложно изменить git commit так, чтобы он выполнял проверку разумности размера файла перед «настоящим» коммитом.Поскольку такое изменение в ядре будет обременять всех пользователей git при каждом коммите, а альтернативная стратегия «изгнать любого, кто сделает 1,5 ГБ изменения» имеет привлекательную простоту, я подозреваю, что такой тест никогда не будет принят в ядре.Я предлагаю вам взвесить бремя поддержки локальной ветки git — nannygit — с бременем восстановления сбойного git после чрезмерно амбициозного коммита.
Должен признаться, мне любопытно, как появился коммит размером 1,5 ГБ.Участвуют ли видеофайлы?
Here is my solution. I must admit it doesn't look like others I have seen, but to me it makes the most sense. It only checks the inbound commit. It does detect when a new file is too large, or an existing file becomes too big. It is a pre-receive hook. Since tags are size 0, it does not check them.
#!/usr/bin/env bash
#
# This script is run after receive-pack has accepted a pack and the
# repository has been updated. It is passed arguments in through stdin
# in the form
# <oldrev> <newrev> <refname>
# For example:
# aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master
#
# see contrib/hooks/ for an sample, or uncomment the next line (on debian)
#
set -e
let max=1024*1024
count=0
echo "Checking file sizes..."
while read oldrev newrev refname
do
# echo $oldrev $newrev $refname
# skip the size check for tag refs
if [[ ${refname} =~ ^refs/tags/* ]]
then
continue
fi
if [[ ${newrev} =~ ^[0]+$ ]]
then
continue
fi
# find all refs we don't care about and exclude them from diff
if [[ ! ${oldrev} =~ ^[0]+$ ]]
then
excludes=^${oldrev}
else
excludes=( $(git for-each-ref --format '^%(refname:short)' refs/heads/) )
fi
# echo "excludes " ${excludes}
commits=$(git rev-list $newrev "${excludes[@]}")
for commit in ${commits};
do
# echo "commit " ${commit}
# get a list of the file changes in this commit
rawdiff=$(git diff-tree --no-commit-id ${commit})
while read oldmode newmode oldsha newsha code fname
do
# echo "reading " ${oldmode} ${newmode} ${oldsha} ${newsha} ${code} ${fname}
# if diff-tree returns anything, new sha is not all 0's, and it is a file (blob)
if [[ "${newsha}" != "" ]] && [[ ! ${newsha} =~ ^[0]+$ ]] && [[ $(git cat-file -t ${newsha}) == "blob" ]]
then
echo -n "${fname} "
newsize=$(git cat-file -s ${newsha})
if (( ${newsize} > ${max} ))
then
echo " size ${newsize}B > ${max}B"
let "count+=1"
else
echo "ok"
fi
fi
done <<< "${rawdiff}"
done
done
exit ${count}