如何防止用户将二进制文件提交到Subversion?

13

我有一个固执的用户,他坚持将他的二进制文件(可执行文件、DLL)提交到我们的子版本库中。虽然有时候我们需要提交二进制文件,但我不希望用户把这视为例行公事。我可以设置忽略属性,但这并不能阻止那些真正决心要提交二进制文件的用户。我想做的是能够在每个目录下控制能否提交指定类型的文件,尤其是 .exe 和 .dll 文件。

在 SVN 中有办法实现吗?如果有区别的话,我们正在使用 VisualSVN 服务器和 TortoiseSVN。


好的,三个回答都这么快速,而且很难选择,我相信一个示例脚本会确保获得“被采纳”的地位 :) - Tim Long
2
管教一下你的用户怎么样?并不是每个问题都需要技术解决的,你知道吗? - Lasse V. Karlsen
@Lasse:我同意,但实际上我发现这对我来说很有用,它可以防止我不小心将二进制文件扔到 SVN 仓库中(例如在新机器上设置 Tortoise 并忘记添加“bin”和“obj”异常)。 - Aaronaught
3
很不幸,在志愿者工作中,不能对用户采取纪律处分。他为该项目做出的贡献过于宝贵,冒着完全失去他的风险,因此我更喜欢的解决方案是默默忽略大多数二进制文件。 - Tim Long
我同意这一点。个人而言,我使用VisualSVN,当我使用它将项目添加到Subversion时,它会自动为我添加这些忽略项,以及其他一些好的措施。但请注意,如果你正在处理一个蠢笨的用户,他无论你做什么都能够做到。他下一步可能会尝试以某种方式伪装文件。 - Lasse V. Karlsen
“什么都不会真正被删除”……除非你使用svn obliterate。 - Ether
7个回答

5
以下是一个小的钩子脚本,可以实现你想要的功能: 你需要配置两个东西:
- illegal_suffixes:一个Python列表,包含所有应该中止提交的后缀 - cmdSVNLOOK:svnlook程序的路径
import sys
import subprocess 
import re

#this is a list of illegal suffixes:
illegal_suffixes = ['.exe','.dll']

# Path to svnlook command:
cmdSVNLOOK="/usr/bin/svnlook";

def isIllegalSuffix(progname):
    for suffix in illegal_suffixes:
        if (ptFilename.endswith(suffix)):
            return True
    return False

######### main program ################
repopath = sys.argv[1]
transact = sys.argv[2]

retVal = 0
svninfo = subprocess.Popen([cmdSVNLOOK, 'changed', '-t', transact, repopath], 
                                                        stdout = subprocess.PIPE, stderr=subprocess.PIPE)
(stdout, stderr) = svninfo.communicate();

prog = re.compile('(^[ACUDRM_])[ACUDRM]*\s+(.+)')  # regex for svnlook output
for line in stdout.split("\n"):
    if (line.strip()!=""):
        match=re.search(prog, line.strip())
        if match:
            mode = match.group(1) 
            ptFilename = match.group(2)
            if mode == 'A' and isIllegalSuffix(ptFilename): 
              retVal = 1
              sys.stderr.write("Please do not add the following ")
              sys.stderr.write("filetypes to repository:\n")
              sys.stderr.write(str(illegal_suffixes)+"\n")
              break
        else:
            sys.stderr.write("svnlook output parsing failed!\n")
            retVal = 1
            break
    else:
        # an empty line is fine!
        retVal = 0
sys.exit(retVal)

谢谢,我很感激您抽出时间发布这个 - 不幸的是,我们正在使用运行在Windows上的VisualSVN服务器。 我需要VBScript、JScript或DOS批处理文件。尽管如此,感谢您发布脚本。 - Tim Long
你可以在Windows下使用Python,并且Visual SVN中提供了svnlook功能: https://www.visualsvn.com/support/svnbook/ref/svnlook/ - Valentin Rocher
我在Windows下开发了这个程序;-) 它已经在Linux和Windows上测试过。你可以使用Python来进行钩子操作,而svnlook是VisualSVN的一部分,否则你可以(并且应该)安装svn命令行工具。 - Peter Parker
我接受了这个答案,因为发布者花时间提供了示例代码。实际上,这并没有解决我的问题,因为我们是一个Windows商店,在Windows Server上运行VisualSVN,而且我们真的只能使用VBScript(呃!)。尽管如此,逻辑是正确的,我相信我可以翻译它。 - Tim Long

5

Tim:

你可以尝试使用这个Python钩子脚本。它基于上面的那个脚本,但允许使用正则表达式模式来拒绝路径,并允许通过在日志消息中添加以

Overide:

开头的行来覆盖检查。它使用新的Python打印语法,因此需要一个相当新的Python版本(2.6+?)。

from __future__ import print_function

import sys,os
import subprocess 
import re

#this is a list of illegal patterns:
illegal_patterns = [
    '\.exe$',
    '\.dll$',
    '[\^|/]bin/',
    '[\^|/]obj/',
]

# Path to svnlook command:
cmdSVNLOOK=r"{}bin\svnlook.exe".format(os.environ["VISUALSVN_SERVER"])

print(illegal_patterns, file=sys.stderr)

print("cmdSVNLook={}".format(cmdSVNLOOK), file=sys.stderr)

def runSVNLook(subCmd, transact, repoPath):
    svninfo =  subprocess.Popen([cmdSVNLOOK, subCmd, '-t', transact, repoPath], 
                          stdout = subprocess.PIPE, stderr=subprocess.PIPE)
    (stdout, stderr) = svninfo.communicate()

    if len(stderr) > 0:
        print("svnlook generated stderr: " + stderr, file=sys.stderr)
        sys.exit(1)

    return [ line.strip() for line in stdout.split("\n") ]

def findIllegalPattern(fileName):
    for pattern in illegal_patterns:
        if re.search(pattern, fileName):
            print("pattern: {} matched filename:{}".format(pattern, fileName))
            return pattern
    return None

def containsOverRide(logOutput):
    retVal = False
    for line in logOutput:
        print("log line: {}".format(line), file=sys.stderr)
        if re.match("^override:", line.lower()):
            retVal = True
            break
    print("contiansOverRide={}".format(retVal), file=sys.stderr)
    return retVal

def findIllegalNames(changeOutput):
    illegalNames = []
    prog = re.compile('(^[ACUDRM_])[ACUDRM]*\s+(.+)')  # regex for svnlook output
    for line in changeOutput:
        print("processing:{}".format(line), file=sys.stderr)
        if (line != ""):
            match=re.search(prog, line.strip())
            if match:
                mode = match.group(1) 
                ptFilename = match.group(2)
                if mode == 'A':
                  pattern = findIllegalPattern(ptFilename)
                  if pattern:
                      illegalNames.append((pattern, ptFilename))
            else:
                print("svnlook output parsing failed!", file=sys.stderr)
                sys.exit(1)
    return illegalNames

######### main program ################
def main(args):
    repopath = args[1]
    transact = args[2]

    retVal = 0

    overRidden = containsOverRide(runSVNLook("log", transact, repopath))
    illegalFiles = findIllegalNames(runSVNLook("changed", transact, repopath))

    if len(illegalFiles):
        msg = "****************************************************************************\n"

        if len(illegalFiles) == 1:
            msg += "* This commit contains a file which matches a forbidden pattern            *\n"
        else:
            msg += "* This commit contains files which match a forbidden pattern               *\n"

        if overRidden:
            msg += "* and contains an Override line so the checkin will be allowed            *\n"
        else:
            retVal = 1

            msg += "* and is being rejected.                                                   *\n"
            msg += "*                                                                          *\n"
            msg += "* Files which match these patterns are genreraly created by the            *\n"
            msg += "* built process and should not be added to svn.                            *\n"
            msg += "*                                                                          *\n"
            msg += "* If you intended to add this file to the svn repository, you neeed to     *\n"
            msg += "* modify your commit message to include a line that looks like:            *\n"
            msg += "*                                                                          *\n"
            msg += "* OverRide: <reason for override>                                          *\n"
            msg += "*                                                                          *\n"
        msg +=  "****************************************************************************\n"

        print(msg, file=sys.stderr)

        if len(illegalFiles) == 1:
            print("The file and the pattern it matched are:", file=sys.stderr)
        else:
            print("The files and the patterns they matched are:", file=sys.stderr)

        for (pattern, fileName) in illegalFiles:
              print('\t{}\t{}'.format(fileName, str(pattern)), file=sys.stderr)

    return retVal

if __name__ == "__main__":
    ret = main(sys.argv)
    sys.exit(ret)

这很完美。我的服务器上有IronPython,这个脚本完全满足我的需求。我喜欢给用户提供覆盖hookscript的能力的概念。不过VisualSVN需要一个批处理文件,所以我不得不创建一个一行代码来调用Python脚本。 - Tim Long

3
编写一个 pre-commit hook,检查添加的文件是否符合您的标准。
您可以使用 pre-commit-check.py 作为起点。

你认为我应该如何在每个目录上进行控制?有些目录我需要允许二进制文件被检入,而有些则不需要。我希望不必将这些信息硬编码到脚本中。 - Tim Long
您的脚本可以从文件中读取允许路径列表(您可能希望将文件存储在服务器上,而不是存储库中,以便用户无法更改它)。如果您想将信息存储在存储库中,可以使用目录上的属性。这使得信息更加本地化,并且会自动处理新的分支/标签。 - oefe

3
你可以使用pre-commit钩子。您需要编写一个简单的程序(使用任何语言),如果文件是二进制文件,则返回非零值。
请参阅此处以获取有关存储库钩子的通用文档,以及来自Apache的此处Python示例。
您可以查看文件名称,或使用file查看它们的类型。

一般而言,考虑到该用户的顽固性,您可能还需要检查.dll、.exe等文件名。 - Michael Greene

3

2
嗯,我正在处理一个固执的用户。已经多次要求他不要提交二进制文件,但他仍然这样做。我认为这不是记忆的问题。这就是为什么我需要“强制执行”政策的原因。 - Tim Long
直言不讳地说,强制执行这个问题的一种方法是解雇他。我并不是说这是你处理问题的第一种方法,但如果情况迫使,非团队合作者在团队中没有立足之地。 - Lasse V. Karlsen
你也可以拒绝任何提交访问。因此,他只能将差异作为补丁发送给他的同事。这是Subversion本身限制其对存储库的写访问的方式:您必须通过向邮件列表发送补丁来证明您编写了正确的代码。 - Peter Parker
@Lasse - 这是志愿者的努力,我不能真正考虑“解雇”一位正在做出贡献的志愿者。我们确实需要他对项目的贡献。我只需要阻止他提交二进制文件。如果这不是自愿的努力,并且假设我是经理,那么我显然会处于更强的地位。 - Tim Long
问题在于,他可能不知道不要将它们检入。他只是将目录中的所有内容添加并提交。通过让他的Tortoise忽略它们,问题可能会消失。 - Joel

1
你可以使用svnlook命令。这里有一个Python类可以完成这个任务:
    SVNTransactionParser(object):
        def __init__(self, repos, txn):
            self.repos = repos
            self.txn = txn
            self.ms = magic.open(magic.MAGIC_NONE)
            self.ms.load()

        def tx_files(self):
            files_to_analyze = list()
            for l in self.__svnlook('changed')[0].readlines():
                l = l.replace('\n', '');
                if not l.endswith('/') and l[0] in ['A', 'U']:
                    files_to_analyze.append(l.split(' ')[-1:][0])

            files = dict()        
            for file_to_analyze in files_to_analyze:
                files[file_to_analyze] = {
                                'size': self.__svnlook('filesize', file_to_analyze)[0].readlines()[0].replace('\n', ''),
                                'type': self.ms.buffer(self.__svnlook('cat', file_to_analyze)[0].readline(4096)),
                                'extension': os.path.splitext(file_to_analyze)[1]}

            return files

        def __svnlook(self, command, extra_args=""):
            cmd = '%s %s %s -t "%s" %s' % (SVNLOOK, command, self.repos, self.txn, extra_args)
            out = popen2.popen3(cmd)
            return (out[0], out[2])

tx_files() 方法返回一个包含以下信息的映射:

{ 
    '/path/to/file1.txt': {'size': 10, 'type': 'ASCII', 'extension': '.txt'}, 
    '/path/to/file2.pdf': {'size': 10134, 'type': 'PDF', 'extension': '.dpf'}, 
}

您需要使用Python-magic库(https://github.com/ahupp/python-magic)。


0
你可以使用一个 pre-commit 钩子脚本来检查文件是二进制还是文本。

不好的主意,比如说你不能给网站添加图片。最好进行扩展检查。 - Sander Rijken

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接