git 预提交或更新挂钩,用于停止提交具有不区分大小写匹配的分支名称
git pre-commit or update hook for stopping commit with branch names having Case Insensitive match
有没有办法编写 git 预提交挂钩来停止具有相同名称的提交,唯一的区别是大小写。
例如
分支名称 1 : firstBranch
分支名称 2:FirstBrancH
分支名称 3 : firsTbranch
但分支名称:firstbranchname 应该被允许。
如果在时间 T 完成分支名称 firstBranch 的提交,则在 T+n 提交分支名称 "FirstBrancH" 或任何组合,git pre-hook 不允许提交.这需要是一个服务器挂钩,因为客户端挂钩可以很容易地通过 pypassed。
所以我的想法是:
所以我得到了提交到的分支的 $NAME ,然后将它与忽略 CASE 的所有分支进行比较,并通过消息使其失败
如果比赛通过。
我已经在 git 实验室服务器上设置了预接收挂钩:
#!/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
按照说明:
选择一个需要自定义 git 挂钩的项目。
在 GitLab 服务器上,导航到项目的存储库目录。对于源安装,路径通常是 /home/git/repositories//.git。对于 Omnibus 安装,路径通常是 /var/opt/gitlab/git-data/repositories//.git.
在此位置创建一个名为 custom_hooks 的新目录。
在新的 custom_hooks 目录中,创建一个名称与挂钩类型匹配的文件。对于预接收挂钩,文件名应该是预接收的,没有扩展名。
使钩子文件可执行并确保它属于 git。
编写代码使 git 钩子函数按预期运行。钩子可以使用任何语言。确保顶部的 'shebang' 正确反映了语言类型。例如,如果脚本在 Ruby 中,shebang 可能是 #!/usr/bin/env ruby.
但它没有按预期工作。
如果我按 aaa,当 AAA 已经在 gitlab 中时,会出现错误:
remote: Duplicate CaseInsensitive Branch Name Detected
但当我尝试推送分支 bbb
时,它也给了我相同的 "Duplicate" 消息
如果分支名称重复,我希望它不允许提交)忽略大小写)
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 推送来源 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 并用 aaa 做一个 uniq -d ,在小写后所有分支
然后比较,如果匹配,调用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 或许多) 然后用消息 "Duplicate branch name"
执行 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'
当然有办法(或几种方法)。现在您已经添加了您的编辑,我将做一些笔记:
在接收推送的服务器上,有两个钩子可以获取您需要的信息和可以拒绝推送的。这些是 pre-receive
和 update
钩子。
pre-receive
钩子在其标准输入中获取一系列格式如下的行:oldsha1
newsha1
refname
。它应该通读所有这些行,处理它们并做出决定:接受(exit 0)或拒绝(exit nonzero)。退出非零将导致接收服务器拒绝 entire 推送。
假设 pre-receive
挂钩(如果有)尚未拒绝整个推送: update
挂钩作为参数获取 refname
oldsha1
newsha1
(注意不同的顺序,以及这些是参数的事实)。每个要更新的引用都会调用一次,即,如果 pre-receive
钩子扫描五行,则 update
钩子会被调用五次。 update
挂钩应检查其参数并决定是接受(退出 0)还是拒绝(退出非零)此 特定 引用更新。
在所有情况下,refname
是 完全限定的 引用名称。这意味着对于分支,它以 refs/heads/
开头;对于标签,它以 refs/tags/
开头;对于注释(参见 git notes
),它以 refs/notes
开头;等等。同样,at most one of oldsha1
and newsha1
可能都是-zeros,表示正在创建引用(旧的全是0
s)或删除(新的全是0
s)。
如果您想拒绝某些创建案例,但允许更新将被拒绝创建的引用名称,请检查 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 预提交挂钩来停止具有相同名称的提交,唯一的区别是大小写。
例如
分支名称 1 : firstBranch
分支名称 2:FirstBrancH
分支名称 3 : firsTbranch
但分支名称:firstbranchname 应该被允许。
如果在时间 T 完成分支名称 firstBranch 的提交,则在 T+n 提交分支名称 "FirstBrancH" 或任何组合,git pre-hook 不允许提交.这需要是一个服务器挂钩,因为客户端挂钩可以很容易地通过 pypassed。
所以我的想法是:
所以我得到了提交到的分支的 $NAME ,然后将它与忽略 CASE 的所有分支进行比较,并通过消息使其失败 如果比赛通过。
我已经在 git 实验室服务器上设置了预接收挂钩:
#!/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
按照说明:
选择一个需要自定义 git 挂钩的项目。
在 GitLab 服务器上,导航到项目的存储库目录。对于源安装,路径通常是 /home/git/repositories//.git。对于 Omnibus 安装,路径通常是 /var/opt/gitlab/git-data/repositories//.git.
在此位置创建一个名为 custom_hooks 的新目录。
在新的 custom_hooks 目录中,创建一个名称与挂钩类型匹配的文件。对于预接收挂钩,文件名应该是预接收的,没有扩展名。
使钩子文件可执行并确保它属于 git。
编写代码使 git 钩子函数按预期运行。钩子可以使用任何语言。确保顶部的 'shebang' 正确反映了语言类型。例如,如果脚本在 Ruby 中,shebang 可能是 #!/usr/bin/env ruby.
但它没有按预期工作。
如果我按 aaa,当 AAA 已经在 gitlab 中时,会出现错误:
remote: Duplicate CaseInsensitive Branch Name Detected
但当我尝试推送分支 bbb
时,它也给了我相同的 "Duplicate" 消息如果分支名称重复,我希望它不允许提交)忽略大小写)
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 推送来源 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 并用 aaa 做一个 uniq -d ,在小写后所有分支
然后比较,如果匹配,调用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 或许多) 然后用消息 "Duplicate branch name"
执行 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'
当然有办法(或几种方法)。现在您已经添加了您的编辑,我将做一些笔记:
在接收推送的服务器上,有两个钩子可以获取您需要的信息和可以拒绝推送的。这些是
pre-receive
和update
钩子。pre-receive
钩子在其标准输入中获取一系列格式如下的行:oldsha1
newsha1
refname
。它应该通读所有这些行,处理它们并做出决定:接受(exit 0)或拒绝(exit nonzero)。退出非零将导致接收服务器拒绝 entire 推送。假设
pre-receive
挂钩(如果有)尚未拒绝整个推送:update
挂钩作为参数获取refname
oldsha1
newsha1
(注意不同的顺序,以及这些是参数的事实)。每个要更新的引用都会调用一次,即,如果pre-receive
钩子扫描五行,则update
钩子会被调用五次。update
挂钩应检查其参数并决定是接受(退出 0)还是拒绝(退出非零)此 特定 引用更新。在所有情况下,
refname
是 完全限定的 引用名称。这意味着对于分支,它以refs/heads/
开头;对于标签,它以refs/tags/
开头;对于注释(参见git notes
),它以refs/notes
开头;等等。同样,at most one ofoldsha1
andnewsha1
可能都是-zeros,表示正在创建引用(旧的全是0
s)或删除(新的全是0
s)。
如果您想拒绝某些创建案例,但允许更新将被拒绝创建的引用名称,请检查 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')