git预先提交或更新挂钩以停止提交具有不区分大小写匹配的分支名称 [英] git pre-commit or update hook for stopping commit with branch names having Case Insensitive match

查看:150
本文介绍了git预先提交或更新挂钩以停止提交具有不区分大小写匹配的分支名称的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

有没有一种方法可以编写一个git pre-commit hook来停止具有相同名称的提交,唯一的区别是大写和小写。



例如

分支名称1:firstBranch

分支名称2:FirstBrancH

分支名称3:firsTbranch



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

如果在时间T对分支名称firstBranch进行了提交,则在T + n,使用分支名称 FirstBrancH或任何组合,git pre-hook将不允许提交。这需要是一个服务器钩子,因为客户端钩子很容易被盗用。



所以我的想法是:

所以我得到了被分支的$ NAME,然后将它与所有忽略CASE的分支进行比较,如果匹配通过,则使用消息
将其失败。



我在gitlab服务器上设置了预接收钩子:

 #!/ bin / bash 

check_dup_branch =`git branch -a | sed's;遥控器/ origin / ;; g'| tr'[:upper:]''[:lower:]'| uniq -d`
if [check_dup_branch]
then
echo检测到重复的CaseInsensitive分支名称
出口1
fi
出口0

按照说明操作:


    <选择一个需要定制git钩子的项目。
  1. 在GitLab服务器上,导航到项目的存储库目录。对于源代码安装,路径通常是/home/git/repositories//.git。对于Omnibus安装路径通常是/ var / opt / gitlab / git-data / repositories // .git。


  2. 在此位置创建一个新目录custom_hooks。


  3. 在新的custom_hooks目录中,创建一个名称与钩子类型匹配的文件。对于预先接收的钩子,文件名应该是预先接收的,没有扩展名。

  4. 使钩子文件成为可执行文件并确保其属于git。


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


但它没有按预期工作。



如果我按aaa,当AAA已经在gitlab中时,给我错误:

  remote:检测到重复的CaseInsensitive分支名称

但它也给我相同的Duplicate 消息,当我尝试推分支bbb



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

 在对git钩子进行更多研究之后:

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



更新挂钩是:

 #!/ usr / bin / python 
导入sys
print测试预接收Hook in Python

branch = sys.argv [1]

print分支'%s'推送%(分支)

sys .exit(0)

git push origin AAA

 总计0(delta 0),重用0(delta 0)
remote:测试预接收Hook in Python
remote:Branch'refs / heads / AAA'push




  • [新分支] AAA - > AAA



现在我们必须像grep -i,git branch -a和用aaa做一个uniq -d,然后比较下层的所有分支,然后比较,如果有MATCH,调用sys.exit(1)



不允许推送



python更新挂钩:

#!/ usr / bin / python 
导入sys
导入子流程

#print测试预接收钩子

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

#printMoving'% s'从%s到%s%(branch,old_commit,new_commit)
#print分支'%s'推送%(分支)
#printold_commit'%s'推送 (old_commit)
#printnew_commit'%s'推送%(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更新钩子的进一步增强:

<$
$ import
$ b $ print $ $ $ $ $
$
$ b branch = sys.argv [1]
old_commit = sys.argv [2]
new_commit = sys.argv [3]

#顺序很重要, for update hook:refname oldsha1 newsha1
print将'%s'从%s移动到%s%(branch,old_commit,new_commit)
printBranch'%s'%(branch)
printold_commit'%s'%(old_commit)
printnew_commit'%s'%(new_commit)

def git(* args):
返回subprocess.check_call(['git'] + list(args))
#if%(branch).lower()in [] git for-each-ref结果数组
#sys 。出口(1)

def get_name(target):
p = subprocess.Popen(['git','for-each-ref','refs / heads /'],stdout = subprocess .PIPE)
为p.stdout中的行:
sha1,kind,name = line.split()
如果sha1!=目标:
继续
返回名称
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)


$ b $因此,拒绝案例当然要比较当前的%(分支)或%(refname:short),并以IgnoreCase方式将其与所有现有的refnames进行比较,如果找到(1或多个)然后用mes做一个sys.exit(1) sage重复的分行名称



但目前我得到的是:

  remote:从'000000000000000000000000000000000000'移动'refs / heads / IIII'到4453eb046fe11c8628729d74c3bec1dd2018512e 
remote:
remote:refs / heads / 10B

以及遥控器:refs / heads / 10B保持静态。所以我不知道如何转换结果:
$ b $ $ p code $ cmd = git(for-each-ref, --format ='%(refname:short)')

到列表或数组中,然后做每个元素之间的字符串比较,并
remote:Branch'refs / heads / IIII'

解决方案

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




  • 在接收推送的服务器上,两个钩子可以获得您需要的信息,可以拒绝推送。这些是 pre-receive 更新挂钩。


  • pre-receive 钩子在其标准输入上获取一系列形式的行: oldsha1 newsha1 refname 。它应该通读所有这些行,处理它们并做出决定:接受(退出0)或拒绝(退出非零)。假设预先接收到 / code>钩子(如果有的话)还没有拒绝整个推送: update 钩子作为参数获取 refname oldsha1 newsha1 (注意不同的顺序,这些是争论)。它被调用一次,每个引用被更新,即如果 pre-receive 钩子扫描五行, update hook被称为五次。 update 钩子应该检查它的参数并决定是接受(退出0)还是拒绝(退出非零)这个特定的引用更新。

    >

  • 在所有情况下, refname 是完全限定的参考名称。这意味着对于分支,它以 refs / heads / 开头;对于标签,它以 refs / tags / 开头; (参见 git notes ),它以 refs / notes 开头。等等。同样,至多 oldsha1 newsha1 0 s)或删除的(新的都是<$ c $)可能是全零的,表示引用正在被创建如果你想拒绝某些创建案例,但是,如果你想拒绝某些创建案例,但是允许更新将被拒绝创建的refname,请检查 oldsha1 值以及ref-name。如果您想拒绝更新,只需检查ref-name。



    要获取所有现有的 ref名称的列表,请使用gitplumbing command git for-each-ref 。要将输出限制为分支名称,可以给它加前缀 refs / heads 。请阅读其文档,以它有很多你可以打开的旋钮。






    编辑Python代码:如果你打算这么做在Python中,您可以利用Python的相对智能。看起来你正处在正确的轨道上。以下是我如何编码它(这可能有点过度设计,我倾向于尝试处理可能的未来问题有时太早):

     <!c $ c>#!/ usr / bin / python 


    拒绝分支创建的更新挂钩(但
    不拒绝正常更新或删除)的一个分支
    ,其名称与其他一些现有分支相匹配


    #注意:我们实际上无法让git提供额外的
    #但是这可以让我们在本地测试一些东西,在
    #一个存储库中。
    导入argparse
    导入sys
    导入子流程

    NULL_SHA1 = b'0'* 40

    #因为我们使用git,将ref
    #名称作为字节字符串进行存储和匹配(这对于Python3来说很重要,但对于Python2来说不是
    #)。
    PREFIX_TO_TYPE = {
    b'refs / heads /':'branch',
    b'refs / tags /':'tag',
    b'refs / remotes /' :'remote-branch',
    }

    def get_reftype(refname):

    将完整的字节串参考名转换为类型;返回
    是类型(常规Python字符串)和短名称(二进制
    字符串)。类型可能是'unknown',在这种情况下短名称
    是全名。

    作为PREFIX_TO_TYPE.keys()中的键值:
    如果refname.startswith(key):
    返回PREFIX_TO_TYPE [key],refname [len(key):]
    return'unknown ',refname

    class RefUpdate(object):

    引用更新有一个引用名和两个哈希,
    新旧

    def __init __(self,refname,old,new):
    self.refname = refname
    self.reftype,self._shortref = get_reftype(refname)
    self .old =旧
    self.new =新的

    def __str __(self):
    返回'{0}({1} [{2} {3}],{4},{5})'。format .__ class __.__ name__,
    self.refname.decode('ascii'),
    self.reftype,self.shortref.decode('ascii'),
    self.old.decode(' ascii'),
    self.new.decode('ascii'))

    @property
    def shortref(self):
    获取短版本的ref(只读,属性fn)
    返回self._shortref

    @property
    def is_branch(self):
    return self.reftype ==''branch '

    @property
    def is_create(self):
    return self.old == NULL_SHA1
    $ b $ def get_existing_branches():

    使用git for-each-ref查找现有的ref名称。
    请注意,我们只关心这里的分支机构,我们可以以
    的形式将它们作为简短形式。

    返回所有分支名称的列表。请注意,这些是
    二进制字符串。

    proc = subprocess.Popen(['git','for-each-ref',
    '--format =%(refname:short)','refs / ()),
    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))
    返回结果

    def update_hook ):
    parser = argparse.ArgumentParser(描述=
    '拒绝分支创建'
    '的不区分大小写的名称冲突的git更新钩子)
    parser.add_argument(' v','--verbose',action ='store_true')
    parser.add_argument(' - d','--debug',action ='store_true')
    parser.add_argument('ref ',help =
    '更新的完整引用名称(例如,refs / heads / branch)')
    parser.add_argument('old_hash',help ='ref'的前一个哈希)
    parser.add_argument('new_hash ,help ='提议的新的散列ref')

    args = parser.parse_args()
    update = RefUpdate(args.ref.encode('utf-8'),
    args.old_hash.encode('utf-8'),args.new_hash.encode('utf-8'))

    如果args.debug:
    args.verbose = True

    如果args.verbose:
    print('checking update {0}'。format(update))

    #如果不是分支,则允许
    如果不是update.is_branch:
    如果args.debug:
    print('不是分支;允许')
    sys.exit(0)
    #如果不是创建,则允许
    如果不是update.is_create:
    如果args.debug:
    print('不是创建;允许')
    sys.exit(0)

    #检查名称冲突 - 获取现有分支名称
    如果args.debug:
    print( ''分支创建!检查现有名称...')
    名称= get_existing_branches()
    名称中的名称:
    如果args.debug:
    print('check vs {0 } = {1}'。format(name.decode('ascii'),
    name.lower()。decode('ascii')))
    if update.shortref.lower()== name.lower():
    sys.exit('创建分支{0}被拒绝:与'$ b $'现有分支{1}'发生冲突格式(update.shortref.decode('ascii'),
    name.decode('ascii')))

    #whew,创建它,如果args.verbose允许

    print('all tests passed,allow ')
    返回0
    $ b $如果__name__ ==__main__:
    try:
    sys.exit(update_hook())
    除了KeyboardInterrupt:
    sys .exit('\\\
    Interrupted')


    Is there a way to write a git pre-commit hook to stop commits with identical names with the only difference being upper and lower cases.

    e.g
    branch name 1 : firstBranch
    branch name 2 : FirstBrancH
    branch name 3 : firsTbranch

    but branch name: firstbranchname should be allowed.

    if a commit is done at time T for the branch name firstBranch , then at T+n , Commit with branch name "FirstBrancH" or any combination, git pre-hook would not allow commit. This needs to be a server hook, as client hooks can be pypassed easily.

    so my thoughts are:

    so i get the $NAME of the branch being committed to , then compare it with ALL the branches ignoring CASE, and FAIL it with a message if the match passes.

    i have setup a pre-receive hook on the gitlab server:

    #!/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
    

    as per the instructions :

    1. Pick a project that needs a custom git hook.

    2. On the GitLab server, navigate to the project's repository directory. For an installation from source the path is usually /home/git/repositories//.git. For Omnibus installs the path is usually /var/opt/gitlab/git-data/repositories//.git.

    3. Create a new directory in this location called custom_hooks.

    4. Inside the new custom_hooks directory, create a file with a name matching the hook type. For a pre-receive hook the file name should be pre-receive with no extension.

    5. Make the hook file executable and make sure it's owned by git.

    6. Write the code to make the git hook function as expected. Hooks can be in any language. Ensure the 'shebang' at the top properly reflects the language type. For example, if the script is in Ruby the shebang will probably be #!/usr/bin/env ruby.

    But its not working as expected.

    if i push aaa, when AAA is already in gitlab, gives me error:

    remote: Duplicate CaseInsensitive Branch Name Detected
    

    but it also gives me the same "Duplicate" message when i try to push branch bbb

    I expect it to not allow the commit if the branch name is duplicate ) ignoring case )

    After a bit more study on git hooks:
    

    ref: If you want to accept or reject branches on a case-by-case basis, you need to use the update hook instead.

    when update hook is:

    #!/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
    

    • [new branch] AAA -> AAA

    now we have to compare like grep -i , git branch -a and do a uniq -d with aaa , after lower casing ALL branches

    and then comparing, and IF there is a MATCH, call sys.exit(1)

    to NOT allow the push

    python update hook:

    #!/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)
    

    further enhancement in the python update hook:

    #!/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)
    

    so the reject case, is of course compare the current %(branch) , or % (refname:short) , and compare it with ALL the existing refnames, in an IgnoreCase manner, and if found ( 1 or many ) then do a sys.exit(1) with a message "Duplicate branch name"

    but currently what i get is:

    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
    

    and somehow the remote: refs/heads/10B stays static. so i am not sure how i can convert the result of :

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

    into a list or array, and then do a string comparison between each element, and remote: Branch 'refs/heads/IIII'

    解决方案

    Surely there is a way (or several ways). Now that you've added your edit I'll make a few notes:

    • On the server receiving a push, there are two hooks that get the kind of information you need and that can reject the push. These are the pre-receive and update hooks.

    • The pre-receive hook gets, on its standard input, a series of lines of the form: oldsha1 newsha1 refname. It should read through all of these lines, process them, and make a decision: accept (exit 0) or reject (exit nonzero). Exiting nonzero will cause the receiving server to reject the entire push.

    • Assuming the pre-receive hook, if there is one, has not already rejected the entire push: The update hook gets, as arguments, refname oldsha1 newsha1 (note the different order, and the fact that these are arguments). It is called once for each reference to be updated, i.e., if the pre-receive hook scans through five lines, the update hook is called five times. The update hook should examine its arguments and decide whether to accept (exit 0) or reject (exit nonzero) this particular reference update.

    • In all cases, the refname is the fully-qualified reference name. This means that for a branch, it begins with refs/heads/; for a tag, it begins with refs/tags/; for a note (see git notes), it begins with refs/notes; and so on. Similarly, at most one of oldsha1 and newsha1 may be all-zeros, to indicate that the reference is being created (old is all 0s) or deleted (new is all 0s).

    If you want to reject certain create cases, but allow updates to refnames that would be rejected for creation, do check the oldsha1 values as well as the ref-names. If you want to reject updates as well, just check the ref-names.

    To get a list of all existing ref names, use the git "plumbing command" git for-each-ref. To restrict its output to just branch names, you can give it the prefix refs/heads. Read its documentation, as it has a lot of knobs you can turn.


    Edit re the Python code: if you're going to do this in Python you can take advantage of Python's relative smartness. It looks like you're sort of on the right track. Here's how I'd code it though (this might be a little bit over-engineered, I tend to try to handle possible future problems too early sometimes):

    #!/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预先提交或更新挂钩以停止提交具有不区分大小写匹配的分支名称的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆