7

我们最近开始使用 git,当有人提交了一个大文件(~1.5GB 文件)时遇到了一个令人讨厌的问题,这导致 git 在各种 32 位操作系统上崩溃。这似乎是一个已知的错误(git mmaps 文件到内存中,如果它无法获得足够的连续空间,这将不起作用),不会很快得到修复。

简单的(对我们来说)解决方案是让 git 拒绝任何大于 100MB 左右的提交,但我想不出办法。

编辑:问题来自意外提交的大文件,在这种情况下是程序输出的大量转储。目的是避免意外提交,因为如果开发人员确实不小心提交了一个大文件,然后试图将其从存储库中取回是一个下午,没有人可以做任何工作,并且必须修复他们所有的本地分支有。

4

4 回答 4

2

问题究竟是什么时候发生的?他们最初提交文件的时间还是被推送到其他地方的时间?如果您有一个每个人都推送到的临时存储库,您可以实现一个更新挂钩来扫描更改的大文件的 refs,以及其他权限等检查。

非常粗略和现成的例子:

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”)这将显示添加的文件,如果一个是太大了。

请注意,我会说,如果您要对此类事情进行监管,则需要一个集中点来执行此操作。如果您相信您的团队只是相互交换更改,那么您应该相信他们知道添加巨大的二进制文件是一件坏事。

于 2009-05-13T16:51:35.377 回答
2

您可以分发防止提交的预提交挂钩。在中央存储库上,您可以有一个 pre-receive 钩子,通过分析接收到的数据来拒绝大 blob 并防止它被引用。数据将被接收,但由于您拒绝对 refs 的更新,所有收到的新对象都将被取消引用,并且可以被 git gc 拾取和删除。

不过我没有给你的剧本。

于 2009-05-13T20:45:10.613 回答
1

如果您可以控制提交者的工具链,则可以直接修改 git commit 以便在“真实”提交之前对文件大小执行合理性测试。由于内核中的这种更改会在每次提交时给所有 git 用户带来负担,并且“驱逐任何将提交 1.5GB 更改的人”的替代策略具有吸引人的简单性,我怀疑这样的测试永远不会在内核中被接受。我建议您权衡维护本地 git 分支的负担 - nannygit - 与在过于雄心勃勃的提交后修复崩溃的 git 的负担。

我必须承认我很好奇 1.5 GB 的提交是如何产生的。是否涉及视频文件?

于 2009-05-13T14:55:13.943 回答
0
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}
于 2014-06-27T02:49:04.407 回答