限制git存储库中的文件大小

时间:2011-08-22 12:48:17

标签: git version-control

我正在考虑将我的VCS(从subversion)更改为git。是否可以在git存储库中的提交中限制文件大小?前面。 G。 subversion有一个钩子:http://www.davidgrant.ca/limit_size_of_subversion_commits_with_this_hook

根据我的经验,人们,特别是那些没有经验的人,有时倾向于提交不应该进入VCS的文件(例如大文件系统图像)。

11 个答案:

答案 0 :(得分:24)

由于我一直在努力,即使有描述,我认为这也与其他人相关,我想我会发布一个如何实现J16 SDiZ described的实现。

所以,我采用服务器端update挂钩来防止推送太大的文件:

#!/bin/bash

# Script to limit the size of a push to git repository.
# Git repo has issues with big pushes, and we shouldn't have a real need for those
#
# eis/02.02.2012

# --- Safety check, should not be run from command line
if [ -z "$GIT_DIR" ]; then
        echo "Don't run this script from the command line." >&2
        echo " (if you want, you could supply GIT_DIR then run" >&2
        echo "  $0 <ref> <oldrev> <newrev>)" >&2
        exit 1
fi

# Test that tab replacement works, issue in some Solaris envs at least
testvariable=`echo -e "\t" | sed 's/\s//'`
if [ "$testvariable" != "" ]; then
        echo "Environment check failed - please contact git hosting." >&2
        exit 1
fi


# File size limit is meant to be configured through 'hooks.filesizelimit' setting
filesizelimit=$(git config hooks.filesizelimit)

# If we haven't configured a file size limit, use default value of about 100M
if [ -z "$filesizelimit" ]; then
        filesizelimit=100000000
fi

# Reference to incoming checkin can be found at $3
refname=$3

# With this command, we can find information about the file coming in that has biggest size
# We also normalize the line for excess whitespace
biggest_checkin_normalized=$(git ls-tree --full-tree -r -l $refname | sort -k 4 -n -r | head -1 | sed 's/^ *//;s/ *$//;s/\s\{1,\}/ /g' )

# Based on that, we can find what we are interested about
filesize=`echo $biggest_checkin_normalized | cut -d ' ' -f4,4`

# Actual comparison
# To cancel a push, we exit with status code 1
# It is also a good idea to print out some info about the cause of rejection
if [ $filesize -gt $filesizelimit ]; then

        # To be more user-friendly, we also look up the name of the offending file
        filename=`echo $biggest_checkin_normalized | cut -d ' ' -f5,5`

        echo "Error: Too large push attempted." >&2
        echo  >&2
        echo "File size limit is $filesizelimit, and you tried to push file named $filename of size $filesize." >&2
        echo "Contact configuration team if you really need to do this." >&2
        exit 1
fi

exit 0

请注意,此代码仅检查最新提交commented,因此需要调整此代码以迭代2美元到3美元之间的提交,并对所有提交进行检查。

答案 1 :(得分:8)

eis和J-16 SDiZ的答案遭遇严重问题。 他们只检查最终提交$ 3或$ newrev的状态。 他们还需要检查其他提交中提交的内容 在udpate钩子中$ 2(或$ oldrev)和$ 3(或$ newrev)之间。

J-16 SDiZ更接近正确答案。

最大的缺陷是,某个部门服务器安装了此更新挂钩以保护它的人会发现以下方面的困难:

使用git rm删除意外检入的大文件后, 那么当前树或最后一次提交只会很好,它会 拉入整个提交链,包括大文件 被删除了,创造了一个没人想要的不快乐的胖子历史。

解决方案要么检查从$ oldrev到$ newrev的每次提交,要么指定整个范围$ oldrev .. $ newrev。 要确保你不仅仅是单独检查$ newrev,否则这将失败 在你的git历史中有大量垃圾,推出与他人分享, 然后很难或不可能删除。

答案 2 :(得分:4)

如果您使用的是gitolite,您也可以尝试VREF。 默认情况下已经提供了一个VREF(代码位于gitolite / src / VREF / MAX_NEWBIN_SIZE)。 它被称为MAX_NEWBIN_SIZE。 它的工作原理如下:

repo name
RW+     =   username
-   VREF/MAX_NEWBIN_SIZE/1000   =   usernames 

其中1000是字节中的示例阈值。

此VREF的工作方式类似于更新挂钩,如果您要推送的文件大于阈值,它将拒绝您的推送。

答案 3 :(得分:2)

是的,git也有钩子(git hooks)。但这取决于您将使用的实际工作流程。

如果您没有经验的用户,那么拉动更安全,然后让他们推动。这样,您可以确保它们不会搞砸主存储库。

答案 4 :(得分:2)

This one很好:

#!/bin/bash -u
#
# git-max-filesize
#
# git pre-receive hook to reject large files that should be commited
# via git-lfs (large file support) instead.
#
# Author: Christoph Hack <chack@mgit.at>
# Copyright (c) 2017 mgIT GmbH. All rights reserved.
# Distributed under the Apache License. See LICENSE for details.
#
set -o pipefail

readonly DEFAULT_MAXSIZE="5242880" # 5MB
readonly CONFIG_NAME="hooks.maxfilesize"
readonly NULLSHA="0000000000000000000000000000000000000000"
readonly EXIT_SUCCESS="0"
readonly EXIT_FAILURE="1"

# main entry point
function main() {
  local status="$EXIT_SUCCESS"

  # get maximum filesize (from repository-specific config)
  local maxsize
  maxsize="$(get_maxsize)"
  if [[ "$?" != 0 ]]; then
    echo "failed to get ${CONFIG_NAME} from config"
    exit "$EXIT_FAILURE"
  fi

  # skip this hook entirely if maxsize is 0.
  if [[ "$maxsize" == 0 ]]; then
    cat > /dev/null
    exit "$EXIT_SUCCESS"
  fi

  # read lines from stdin (format: "<oldref> <newref> <refname>\n")
  local oldref
  local newref
  local refname
  while read oldref newref refname; do
    # skip branch deletions
    if [[ "$newref" == "$NULLSHA" ]]; then
      continue
    fi

    # find large objects
    # check all objects from $oldref (possible $NULLSHA) to $newref, but
    # skip all objects that have already been accepted (i.e. are referenced by
    # another branch or tag).
    local target
    if [[ "$oldref" == "$NULLSHA" ]]; then
      target="$newref"
    else
      target="${oldref}..${newref}"
    fi
    local large_files
    large_files="$(git rev-list --objects "$target" --not --branches=\* --tags=\* | \
      git cat-file $'--batch-check=%(objectname)\t%(objecttype)\t%(objectsize)\t%(rest)' | \
      awk -F '\t' -v maxbytes="$maxsize" '$3 > maxbytes' | cut -f 4-)"
    if [[ "$?" != 0 ]]; then
      echo "failed to check for large files in ref ${refname}"
      continue
    fi

    IFS=$'\n'
    for file in $large_files; do
      if [[ "$status" == 0 ]]; then
        echo ""
        echo "-------------------------------------------------------------------------"
        echo "Your push was rejected because it contains files larger than $(numfmt --to=iec "$maxsize")."
        echo "Please use https://git-lfs.github.com/ to store larger files."
        echo "-------------------------------------------------------------------------"
        echo ""
        echo "Offending files:"
        status="$EXIT_FAILURE"
      fi
      echo " - ${file} (ref: ${refname})"
    done
    unset IFS
  done

  exit "$status"
}

# get the maximum filesize configured for this repository or the default
# value if no specific option has been set. Suffixes like 5k, 5m, 5g, etc.
# can be used (see git config --int).
function get_maxsize() {
  local value;
  value="$(git config --int "$CONFIG_NAME")"
  if [[ "$?" != 0 ]] || [[ -z "$value" ]]; then
    echo "$DEFAULT_MAXSIZE"
    return "$EXIT_SUCCESS"
  fi
  echo "$value"
  return "$EXIT_SUCCESS"
}

main

您可以通过添加以下内容来配置服务器端config文件中的大小:

[hooks]
        maxfilesize = 1048576 # 1 MiB

答案 5 :(得分:1)

我想重点介绍在拉动请求阶段解决此问题的另一组方法:GitHub Actions和Apps。它不会阻止大文件提交到分支中,但是如果在合并之前将其删除,则生成的基本分支将不会在历史记录中包含大文件。

最近有一项开发的操作,用于根据用户定义的参考值lfs-warning检查添加的文件大小(通过GitHub API)。

我还亲自入侵了Probot应用程序,以在PR中筛查大文件(相对于用户定义的值),但效率低得多:sizeCheck

答案 6 :(得分:0)

另一种方法是版本.gitignore,这将阻止具有特定扩展名的任何文件显示在状态中。
您仍然可以拥有钩子(在下游或上游,如其他答案所示),但至少所有下游仓库都可以包含.gitignore以避免添加.exe.dll.iso,...

答案 7 :(得分:0)

这将是一个非常罕见的情况,从我看到的,当一个人检查,比如一个200Mb甚至更大的文件。

虽然您可以通过使用服务器端挂钩(不确定客户端挂钩,因为您必须依赖安装了挂钩的人)来防止这种情况发生,就像您在SVN中一样,您还必须考虑到在Git中,从存储库中删除这样的文件/提交要容易得多。你在SVN中没有这么奢侈,至少不是一个简单的方法。

答案 8 :(得分:0)

我正在使用gitolite并且已经使用了更新挂钩 - 而不是使用更新挂钩,我使用了预接收挂钩。 Chriki发布的脚本非常精彩,除了数据是通过stdin传递的 - 所以我换了一行:

- refname=$3
+ read a b refname

(可能有更优雅的方式,但它有效)

答案 9 :(得分:0)

您需要一种满足以下情况的解决方案。

  1. 如果某人将多个提交一起推送,则挂钩应检查该推送中的所有提交(在oldref和newref之间)以查找大于特定限制的文件
  2. 该挂钩应针对所有用户运行。如果您编写了客户端挂钩,则它不适用于所有用户,因为在执行git push时不会推送此类挂钩。因此,需要的是服务器端挂钩,例如预接收挂钩。

此钩子(https://github.com/mgit-at/git-max-filesize)处理上述2种情况,并且似乎还可以正确处理边缘情况,例如新分支推送和分支删除。

答案 10 :(得分:-1)

您可以使用hookpre-commit挂钩(在客户端上)或update挂钩(在服务器上)。执行git ls-files --cached(用于预提交)或git ls-tree --full-tree -r -l $3(用于更新)并采取相应行动。

git ls-tree -l会给出类似这样的内容:

100644 blob 97293e358a9870ac4ddf1daf44b10e10e8273d57    3301    file1
100644 blob 02937b0e158ff8d3895c6e93ebf0cbc37d81cac1     507    file2

抓住第四列,这是大小。使用git ls-tree --full-tree -r -l HEAD | sort -k 4 -n -r | head -1获取最大的文件。 cut提取,if [ a -lt b ]检查大小等。

对不起,我想如果你是一名程序员,你应该能够自己做。