【问题标题】:git pre-commit or update hook for stopping commit with branch names having Case Insensitive matchgit pre-commit 或 update 钩子,用于停止具有不区分大小写匹配的分支名称的提交
【发布时间】:2016-06-10 02:12:12
【问题描述】:

有没有办法编写一个 git pre-commit 钩子来停止具有相同名称的提交,唯一的区别是大写和小写。

例如
分支名称 1:firstBranch
分支名称 2:FirstBrancH
分支名称 3 : firsTbranch

但是分支名称:firstbranchname 应该是允许的。

如果在时间 T 为分支名称 firstBranch 完成提交,那么在 T+n ,使用分支名称“FirstBrancH”或任何组合提交,git pre-hook 将不允许提交。这需要是一个服务器钩子,因为客户端钩子可以很容易地被 pypassed。

所以我的想法是:

所以我得到了被提交到的分支的 $NAME,然后将它与忽略 CASE 的所有分支进行比较,并通过一条消息使其失败 如果比赛通过。

我已经在 gitlab 服务器上设置了一个 pre-receive 钩子:

#!/bin/bash

check_dup_branch=`git branch -a | sed 's; remotes/origin/;;g' | tr '[:upper:]' '[:lower:]' | uniq -d`
  if [ check_dup_branch ]
  then
    echo "Duplicate CaseInsensitive Branch Name Detected"
    exit 1
  fi
exit 0

按照说明:

  1. 选择一个需要自定义 git 挂钩的项目。

  2. 在 GitLab 服务器上,导航到项目的存储库目录。对于从源代码安装,路径通常是 /home/git/repositories//.git。对于 Omnibus 安装,路径通常是 /var/opt/gitlab/git-data/repositories//.git。

  3. 在此位置创建一个名为 custom_hooks 的新目录。

  4. 在新的 custom_hooks 目录中,创建一个名称与挂钩类型匹配的文件。对于预接收挂钩,文件名应为预接收且不带扩展名。

  5. 使挂钩文件可执行并确保它归 git 所有。

  6. 编写代码以使 git 挂钩按预期运行。 Hooks 可以是任何语言。确保顶部的“shebang”正确反映语言类型。例如,如果脚本在 Ruby 中,则 shebang 可能是 #!/usr/bin/env ruby​​。

但它没有按预期工作。

如果我 push aaa,当 AAA 已经在 gitlab 中时,会报错:

remote: Duplicate CaseInsensitive Branch Name Detected

但当我尝试推送分支 bbb 时,它也给了我相同的“重复”消息

如果分支名称重复,我希望它不允许提交)忽略大小写)

After a bit more study on git hooks:

ref:如果您想逐个接受或拒绝分支,则需要使用更新挂钩。

当更新钩子是:

#!/usr/bin/python
import sys
print "Testing pre-receive Hook in Python"

branch = sys.argv[1]

print "Branch '%s' pushing" %(branch)

sys.exit(0)

git push origin AAA

Total 0 (delta 0), reused 0 (delta 0)
remote: Testing pre-receive Hook in Python
remote: Branch 'refs/heads/AAA' pushing
  • [新分支] AAA -> AAA

现在我们必须像 grep -i , git branch -a 和做一个 uniq -d 和 aaa ,在小写所有分支之后

然后比较,如果有MATCH,调用sys.exit(1)

不允许推送

python 更新钩子:

#!/usr/bin/python
import sys
import subprocess

#print "Testing pre-receive Hook"

branch = sys.argv[1]
old_commit = sys.argv[2]
new_commit = sys.argv[3]

#print "Moving '%s' from %s to %s" % (branch, old_commit, new_commit)
#print "Branch '%s' pushing" %(branch)
#print "old_commit '%s' pushing" %(old_commit)
#print "new_commit '%s' pushing" %(new_commit)

def git(*args):
    return subprocess.check_call(['git'] + list(args))

if __name__ == "__main__":
    #git("status")
    #git("for-each-ref" , "refs/heads/" , "--format='%(refname:short)'")
    git("for-each-ref" , "--format='%(refname:short)'")
sys.exit(0)

python更新钩子的进一步增强:

#!/usr/bin/python
import sys
import subprocess

#print "Testing pre-receive Hook"

branch = sys.argv[1]
old_commit = sys.argv[2]
new_commit = sys.argv[3]

# order is important, for update hook: refname oldsha1 newsha1
print "Moving '%s' from %s to %s" % (branch, old_commit, new_commit)
print "Branch '%s' " %(branch)
print "old_commit '%s' " %(old_commit)
print "new_commit '%s' " %(new_commit)

def git(*args):
    return subprocess.check_call(['git'] + list(args))
    #if %(branch).lower() in []array of results from the git for-each-ref
    #sys.exit(1)

def get_name(target):
    p = subprocess.Popen(['git', 'for-each-ref', 'refs/heads/'], stdout=subprocess.PIPE)
    for line in p.stdout:
        sha1, kind, name = line.split()
        if sha1 != target:
            continue
        return name
    return None
if __name__ == "__main__":
    #git("status")
    #git("for-each-ref" , "refs/heads/" , "--format='%(refname:short)'")
    #cmd = git("for-each-ref" , "--format='%(refname:short)'")
    cmd = git("for-each-ref" , "--format='%(refname:short)'")
    #print cmd
    #print get_name(branch)
    #print get_name(old_commit)
    print get_name(new_commit)
sys.exit(0)

所以拒绝案例当然是比较当前的 %(branch) 或 % (refname:short) ,并以 IgnoreCase 方式将其与所有现有的 refnames 进行比较,如果找到( 1 或 many )然后使用消息“重复的分支名称”执行 sys.exit(1)

但目前我得到的是:

remote: Moving 'refs/heads/IIII' from 0000000000000000000000000000000000000000 to 4453eb046fe11c8628729d74c3bec1dd2018512e
remote: Branch 'refs/heads/IIII'
remote: old_commit '0000000000000000000000000000000000000000'
remote: new_commit '4453eb046fe11c8628729d74c3bec1dd2018512e'
remote: refs/heads/10B

不知何故,远程:refs/heads/10B 保持静态。所以我不确定如何转换结果:

cmd = git("for-each-ref" , "--format='%(refname:short)'")

放入列表或数组,然后在每个元素之间进行字符串比较,然后 远程:分支'refs/heads/IIII'

【问题讨论】:

    标签: git githooks


    【解决方案1】:

    当然有一种方法(或几种方法)。现在你已经添加了你的编辑,我会做一些笔记:

    • 在接收推送的服务器上,有两个钩子可以获取您需要的信息类型可以拒绝推送。这些是pre-receiveupdate 钩子。

    • pre-receive 钩子在其标准输入上获得一系列如下形式的行:oldsha1newsha1refname。它应该通读所有这些行,处理它们,并做出决定:接受(退出 0)或拒绝(退出非零)。退出非零值将导致接收服务器拒绝整个推送。

    • 假设 pre-receive 钩子(如果有的话)尚未拒绝整个推送:update 钩子作为参数获取 refnameoldsha1newsha1 em>(注意不同的顺序,以及这些是参数的事实)。每个要更新的引用调用一次,即如果pre-receive 钩子扫描五行,则update 钩子被调用五次。 update 挂钩应检查其参数并决定是接受(退出 0)还是拒绝(退出非零)此特定参考更新。

    • 在所有情况下,refname 都是 完全限定 引用名称。这意味着对于一个分支,它以refs/heads/ 开头;对于标签,它以refs/tags/ 开头;注释(见git notes),它以refs/notes开头;等等。类似地,oldsha1newsha1 中的 最多一个 可能是全零,表示正在创建引用(旧全部为0s)或已删除(新全部为0s)。

    如果您想拒绝某些创建案例,但允许更新将被拒绝创建的引用名称,请检查 oldsha1 值以及引用名称。如果您也想拒绝更新,只需检查 ref-names。

    要获取所有现有引用名称的列表,请使用 git "plumbing command" git for-each-ref。要将其输出限制为仅分支名称,您可以为其指定前缀 refs/heads。阅读its documentation,因为它有很多可以转动的旋钮。


    重新编辑 Python 代码:如果您打算在 Python 中执行此操作,您可以利用 Python 的相对智能。看起来你在正确的轨道上。这是我的编码方式(这可能有点过度设计,有时我倾向于过早地处理未来可能出现的问题):

    #!/usr/bin/python
    
    """
    Update hook that rejects a branch creation (but does
    not reject normal update nor deletion) of a branch
    whose name matches some other, existing branch.
    """
    
    # NB: we can't actually get git to supply additional
    # arguments but this lets us test things locally, in
    # a repository.
    import argparse
    import sys
    import subprocess
    
    NULL_SHA1 = b'0' * 40
    
    # Because we're using git we store and match the ref
    # name as a byte string (this matters for Python3, but not
    # for Python2).
    PREFIX_TO_TYPE = {
        b'refs/heads/': 'branch',
        b'refs/tags/': 'tag',
        b'refs/remotes/': 'remote-branch',
    }
    
    def get_reftype(refname):
        """
        Convert full byte-string reference name to type; return
        the type (regular Python string) and the short name (binary
        string).  Type may be 'unknown' in which case the short name
        is the full name.
        """
        for key in PREFIX_TO_TYPE.keys():
            if refname.startswith(key):
                return PREFIX_TO_TYPE[key], refname[len(key):]
        return 'unknown', refname
    
    class RefUpdate(object):
        """
        A reference update has a reference name and two hashes,
        old and new.
        """
        def __init__(self, refname, old, new):
            self.refname = refname
            self.reftype, self._shortref = get_reftype(refname)
            self.old = old
            self.new = new
    
        def __str__(self):
            return '{0}({1} [{2} {3}], {4}, {5})'.format(self.__class__.__name__,
                self.refname.decode('ascii'),
                self.reftype, self.shortref.decode('ascii'),
                self.old.decode('ascii'),
                self.new.decode('ascii'))
    
        @property
        def shortref(self):
            "get the short version of the ref (read-only, property fn)"
            return self._shortref
    
        @property
        def is_branch(self):
            return self.reftype == 'branch'
    
        @property
        def is_create(self):
            return self.old == NULL_SHA1
    
    def get_existing_branches():
        """
        Use git for-each-ref to find existing ref names.
        Note that we only care about branches here, and we can
        take them in their short forms.
    
        Return a list of all branch names.  Note that these are
        binary strings.
        """
        proc = subprocess.Popen(['git', 'for-each-ref',
            '--format=%(refname:short)', 'refs/heads/'],
            stdout=subprocess.PIPE)
        result = proc.stdout.read().splitlines()
        status = proc.wait()
        if status != 0:
            sys.exit('help! git for-each-ref failed: exit {0}'.format(status))
        return result
    
    def update_hook():
        parser = argparse.ArgumentParser(description=
            'git update hook that rejects branch create'
            ' for case-insensitive name collision')
        parser.add_argument('-v', '--verbose', action='store_true')
        parser.add_argument('-d', '--debug', action='store_true')
        parser.add_argument('ref', help=
            'full reference name for update (e.g., refs/heads/branch)')
        parser.add_argument('old_hash', help='previous hash of ref')
        parser.add_argument('new_hash', help='proposed new hash of ref')
    
        args = parser.parse_args()
        update = RefUpdate(args.ref.encode('utf-8'),
            args.old_hash.encode('utf-8'), args.new_hash.encode('utf-8'))
    
        if args.debug:
            args.verbose = True
    
        if args.verbose:
            print('checking update {0}'.format(update))
    
        # if not a branch, allow
        if not update.is_branch:
            if args.debug:
                print('not a branch; allowing')
            sys.exit(0)
        # if not a creation, allow
        if not update.is_create:
            if args.debug:
                print('not a create; allowing')
            sys.exit(0)
    
        # check for name collision - get existing branch names
        if args.debug:
            print('branch creation! checking existing names...')
        names = get_existing_branches()
        for name in names:
            if args.debug:
                print('check vs {0} = {1}'.format(name.decode('ascii'),
                    name.lower().decode('ascii')))
            if update.shortref.lower() == name.lower():
                sys.exit('Create branch {0} denied: collides with'
                    ' existing branch {1}'.format(update.shortref.decode('ascii'),
                    name.decode('ascii')))
    
        # whew, made it, allow
        if args.verbose:
            print('all tests passed, allowing')
        return 0
    
    if __name__ == "__main__":
        try:
            sys.exit(update_hook())
        except KeyboardInterrupt:
            sys.exit('\nInterrupted')
    

    【讨论】:

    • 例如尝试git for-each-ref --format='%(refname:short)' refs/heads。但是,当您处于正确的钩子中时,传入的 refname 中已经包含 refs/heads/,因此您可能不想缩短它。你仍然需要折叠你的箱子(tr 很好)。我认为,您不想在 让它们被创建之后检测到重复项,就像检测到如果您允许的话,一个新的分支创建将是 重复项。您不会在预提交挂钩中执行此操作,而是在 pre-receiveupdate 中执行此操作。
    猜你喜欢
    • 2016-11-24
    • 2016-05-15
    • 2014-05-16
    • 2017-03-16
    • 1970-01-01
    • 2017-08-24
    • 2019-11-20
    • 2021-03-19
    • 1970-01-01
    相关资源
    最近更新 更多