Linux 如何设置Gitlab钩子来验证git推送到远程

Linux 如何设置Gitlab钩子来验证git推送到远程,linux,git,bash,gitlab,devops,Linux,Git,Bash,Gitlab,Devops,我们有一个DevOps流程,人们从远程主分支签出或更新,从主分支创建一个新的本地工作分支,然后在本地分支开始工作 1) 我需要设置一些限制,防止人们直接推到远程主分支。相反,人们需要将对本地分支的更改推送到远程上的同一分支,然后管理员或代码审阅者将其合并到远程主机中 2) 我需要一个钩子来确保表单中有一个有效的Gitlab票证或发行号,比如说#PROJECTNAME123,然后才允许推送到远程分支(在代码审查和合并到remote master之前)。此外,如果票证不存在或尚未打开,他们不得推送

我们有一个DevOps流程,人们从远程主分支签出或更新,从主分支创建一个新的本地工作分支,然后在本地分支开始工作

1) 我需要设置一些限制,防止人们直接推到远程主分支。相反,人们需要将对本地分支的更改推送到远程上的同一分支,然后管理员或代码审阅者将其合并到远程主机中

2) 我需要一个钩子来确保表单中有一个有效的Gitlab票证或发行号,比如说#PROJECTNAME123,然后才允许推送到远程分支(在代码审查和合并到remote master之前)。此外,如果票证不存在或尚未打开,他们不得推送

我已经使用来自以下两个网站的信息创建了一个Bash预接收钩子,但是它们都被调用了,但是即使我没有传递Gitlab票证/发行号,仍然允许git推送到服务器

下面是它调用的预接收脚本和bash函数脚本

预接收(无分机)

pre-receive-functions.sh

#!/usr/bin/env bash
#


regexp="#[0-9]\+"

grep_msg()
{
        grepped=$( echo $message | grep -i $regexp )
}

process_revision ()
{
  #revisions=$(git rev-list $old_revision..$new_revision)
echo "In pre-receive hook. Just before retrieving the revisions"
if [ "$old_revision" -eq 0 ]; then
    # list everything reachable from new_revision but not any heads
   revisions=$(git rev-list $(git for-each-ref --format='%(refname)' refs/heads/* | sed 's/^/\^/') $new_revision)
else
   revisions=$(git rev-list $old_revision..$new_revision)
fi

echo "In pre-receive hook. Just before IFS"
  IFS='\n' read -ra array <<< "$revisions"
  for rid in "${!array[@]}"; do
        revision=${array[rid]}
    message=$(git cat-file commit $revision | sed '1,/^$/d')
        grepped=$(echo $message | grep -i "#[0-9]\+")
    grep_msg()
    if [ -z "$grepped" ] ; then
                grepped_none=$(echo $message | grep -i "#none")
                if [ -n "$grepped_none" ] ; then
                        echo "Warning, you are committing without a ticket reference" >&1
                else
                        echo "You have not included a ticket reference" >&2
                        exit 1
                fi
    fi
  done


}
注意:Gitlab及其依赖项(包括git)安装在同一Fedora Core 24 Linux系统上

我将感谢你迅速帮助我度过难关。非常感谢您的帮助。

1)默认情况下,任何新项目或git回购的每个默认受限分支都有限制。这些限制适用于Gitlab的非管理员和非root用户

2) 我们通过编写名为Gitlab API的Java spring boot CommandLineRunner应用程序,实现了检查开发人员是否遵守开发策略和流程的规则。此应用程序打包为jar文件

我们确保开发人员必须拥有一个有效的票证号作为git提交消息的一部分,然后才能成功地推送到其工作分支的远程对应方。必须将此有效票证分配给他,具有有效的里程碑,并选择正确的标签(新功能、错误、任务等),推送才能成功

我们通过使用bashshell脚本与Gitlab服务器上的git钩子集成,该脚本执行jar文件,并根据java应用程序的输出允许或失败推送请求。此shell脚本是的改编版本,可在下面找到:

#!/bin/bash
#
# pre-receive hook for Commit Check
#
COMPANY_EMAIL="mycorp.org"

readonly PROGNAME=$(basename $0)
readonly PROGDIR=$(readlink -m $(dirname $0))
IS_MERGE=0

check_single_commit()
{
  COMMIT_CHECK_STATUS=1
    echo "Repo >> $REPOSITORY_BASENAME"

    if [[ "$COMMIT_MESSAGE" == "Merge branch"* ]]; then
      COMMIT_CHECK_STATUS=0
      IS_MERGE=1
    else
    workFlowResult=`java -jar -Dspring.config.location=/home/gitlab/gitlab_custom_hooks/application.properties /home/gitlab/gitlab_custom_hooks/gitlab-tool.jar -prercv "$COMMIT_AUTHOR" "$COMMIT_MESSAGE" "$REPOSITORY_BASENAME"`
    echo "COMMIT_AUTHOR=$COMMIT_AUTHOR, COMMIT_MESSAGE=$COMMIT_MESSAGE, REPOSITORY_BASE=$REPOSITORY_BASENAME"

      echo " >>>>>>>>>>>>>>>>> $workFlowResult >>>>>>>>>>>>>>>>>" >&2
    if [[ "$workFlowResult" == *"PRE_RECEIVE_OK"* ]]; then
      echo " >>>>>>>>>>>>>>>>> $workFlowResult >>>>>>>>>>>>>>>>>" >&2
      COMMIT_CHECK_STATUS=0
    fi

    fi
}

check_all_commits()
{
  REVISIONS=$(git rev-list $OLD_REVISION..$NEW_REVISION)
  IFS='\n' read -ra LIST_OF_REVISIONS <<< "$REVISIONS"
if [ $(git rev-parse --is-bare-repository) = true ]
then
    REPOSITORY_BASENAME=$(basename "$PWD")
else
    REPOSITORY_BASENAME=$(basename $(readlink -nf "$PWD"/..))
fi
    echo REPOSITORY_BASENAME is $REPOSITORY_BASENAME
    REPOSITORY_BASENAME=$(basename "$PWD")
    REPOSITORY_BASENAME=${REPOSITORY_BASENAME%.git}

  for rid in "${!LIST_OF_REVISIONS[@]}"; do
    REVISION=${LIST_OF_REVISIONS[rid]}
    COMMIT_MESSAGE=$(git cat-file commit $REVISION | sed '1,/^$/d')
    COMMIT_AUTHOR=$(git cat-file commit $REVISION | grep committer | sed 's/^.* \([^@ ]\+@[^ ]\+\) \?.*$/\1/' | sed 's/<//' | sed 's/>//' | sed 's/@$COMPANY_EMAIL//')
    check_single_commit

    if [ "$COMMIT_CHECK_STATUS" != "0" ]; then
      echo "Commit validation failed for commit $REVISION" >&2
      exit 1
    fi

  done
}





# Get custom commit message format
while read OLD_REVISION NEW_REVISION REFNAME ; do
  check_all_commits
done

exit 0
#!/usr/bin/env python

import subprocess
import sys
import tempfile
import shutil
import os
import errno

# variables for checkstyle
#checkstyle = '/var/opt/gitlab/git-data/repositories/product-common/ProductCommonParent.git/custom_hooks/checkstyle-7.5.1-all.jar'
#checkstyle_config = '/var/opt/gitlab/git-data/repositories/product-common/ProductCommonParent.git/custom_hooks/sun_checks.xml'
pmd = '/home/gitlab/gitlab_custom_hooks/pmd-bin-5.5.4/bin/run.sh'

# implementing check_output for python < 2.7
if not hasattr(subprocess, 'check_output'):
    def check_output(*popenargs, **kwargs):
        if 'stdout' in kwargs:
            raise ValueError('stdout argument not allowed, it will be overridden.')
        process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
        output, unused_err = process.communicate()
        retcode = process.poll()
        if retcode:
            cmd = kwargs.get("args")
            if cmd is None:
                cmd = popenargs[0]
            er = subprocess.CalledProcessError(retcode, cmd)
            er.output = output
            raise er
        return output
    subprocess.check_output = check_output


# helper for calling executables
def call(*args, **kwargs):
    return subprocess.check_output(*args, **kwargs).strip()


# helper for calling git
def call_git(cmd, *args, **kwargs):
    return call(['git'] + cmd, *args, **kwargs)


# get all new commits from stdin
def get_commits():
    commits = {}
    for line in sys.stdin:
        old, new, ref = line.strip().split(' ')
        if old == '0000000000000000000000000000000000000000':
            old = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'

        if ref not in commits:
            commits[ref] = []
        commits[ref].append({
            'old': old,
            'new': new,
            'files': get_changed_files(old, new)
            })

    return commits


# get a list of changed files between to commits
def get_changed_files(old, new):
    return call_git(['diff', '--name-only', old, new]).split('\n')


# get filemode, object type (blob,tree,commit), hash for the given file at the
# given commit
def get_change_type(commit, filename):
    return call_git(['ls-tree', commit, filename]).split('\t')[0].split(' ')


commits = get_commits()

# use the latest file commit only
print "Cleaning up file list..."

files = {}
count = 0
for ref, data in commits.iteritems():
    files[ref] = {}
    for commit in data:
        for filename in commit['files']:
            if not filename.lower().endswith('.java'): continue
            files[ref][filename] = get_change_type(commit['new'], filename)
    count += len(files[ref])

print "%d Files to check in %d branches" % (count, len(files))

# create temporary dir and save a copy of the new files
tempdir = tempfile.mkdtemp('git_hook')
for ref, files in files.iteritems():
    for filename, data in files.iteritems():
        dname = os.path.dirname(filename)
        bname = os.path.basename(filename)
        try:
            os.makedirs(os.path.join(tempdir, dname))
        except OSError, exc:
            if exc.errno == errno.EEXIST:  # directory exists already
                pass
            else:
                raise

        with open(os.path.join(tempdir, dname, bname), 'w') as fp:
            fp.write(call_git(['cat-file', data[1], data[2]]))

try:
    # call checkstyle and/or pmd and print output
    # print call(['java', '-jar', checkstyle, '-c', checkstyle_config, tempdir])
    # print call(['java', '-jar', '/var/opt/gitlab/git-data/repositories/product-common/ProductCommonParent.git/custom_hooks/hooks-0.0.1-SNAPSHOT.jar', '-prercv', '79', 'developer-email-id', "I am now done with issue #500 #501 #502"])
    print call([pmd, 'pmd', '-d', tempdir, '-f', 'text', '-R', 'rulesets/java/basic.xml,rulesets/java/unusedcode.xml,rulesets/java/imports.xml,rulesets/java/strings.xml,rulesets/java/braces.xml,rulesets/java/clone.xml,rulesets/java/design.xml,rulesets/java/clone.xml,rulesets/java/finalizers.xml,rulesets/java/junit.xml,rulesets/java/migrating.xml,rulesets/java/optimizations.xml,rulesets/java/strictexception.xml,rulesets/java/sunsecure.xml,rulesets/java/typeresolution.xml'])
    print "SUCCESS"
except subprocess.CalledProcessError, ex:
    print ex.output  # print checkstyle and/or pmd messages
    exit(1)
finally:
    # remove temporary directory
    shutil.rmtree(tempdir)
#/bin/bash
#
#提交检查的预接收钩子
#
公司电子邮件=“mycorp.org”
只读PROGNAME=$(basename$0)
只读PROGDIR=$(readlink-m$(dirname$0))
是_MERGE=0吗
检查单次提交()
{
提交检查状态=1
echo“Repo>>$REPOSITORY\u BASENAME”
如果[[“$COMMIT_MESSAGE”==“Merge branch”*];则
提交检查状态=0
是_MERGE=1吗
其他的
workFlowResult=`java-jar-Dspring.config.location=/home/gitlab/gitlab\u custom\u hooks/application.properties/home/gitlab/gitlab\u custom\u hooks/gitlab-tool.jar-prercv“$COMMIT\u AUTHOR”“$COMMIT\u MESSAGE”“$REPOSITORY\u BASENAME”`
echo“COMMIT\u AUTHOR=$COMMIT\u AUTHOR,COMMIT\u MESSAGE=$COMMIT\u MESSAGE,REPOSITORY\u BASE=$REPOSITORY\u BASENAME”
echo“>>>>>>>>>>>>>>>>>$workFlowResult”>>>>>>>>>>>>>>>>>>>>>&2
如果[[“$workFlowResult”=*“预接收”*];则
echo“>>>>>>>>>>>>>>>>>$workFlowResult”>>>>>>>>>>>>>>>>>>>>>&2
提交检查状态=0
fi
fi
}
检查所有提交()
{
修订=$(git修订列表$OLD\U修订..$NEW\U修订)
IFS='\n'已读-ra修订列表&2
出口1
fi
完成
}
#获取自定义提交消息格式
当读取旧版本时,读取新版本REFNAME;做
检查所有提交
完成
出口0
3) 尽管这不是问题的一部分,但在不使用PMD Jenkins插件的情况下在服务器端集成PMD检查需要下载PMD可执行启动依赖项,即从python脚本中执行PMD,以静态分析开发人员推送到git服务器(Gitlab服务器)的源文件。引导PMD的python脚本可以很容易地集成到上面的bash shell脚本中。python脚本是的改编版本,可以在下面找到:

#!/bin/bash
#
# pre-receive hook for Commit Check
#
COMPANY_EMAIL="mycorp.org"

readonly PROGNAME=$(basename $0)
readonly PROGDIR=$(readlink -m $(dirname $0))
IS_MERGE=0

check_single_commit()
{
  COMMIT_CHECK_STATUS=1
    echo "Repo >> $REPOSITORY_BASENAME"

    if [[ "$COMMIT_MESSAGE" == "Merge branch"* ]]; then
      COMMIT_CHECK_STATUS=0
      IS_MERGE=1
    else
    workFlowResult=`java -jar -Dspring.config.location=/home/gitlab/gitlab_custom_hooks/application.properties /home/gitlab/gitlab_custom_hooks/gitlab-tool.jar -prercv "$COMMIT_AUTHOR" "$COMMIT_MESSAGE" "$REPOSITORY_BASENAME"`
    echo "COMMIT_AUTHOR=$COMMIT_AUTHOR, COMMIT_MESSAGE=$COMMIT_MESSAGE, REPOSITORY_BASE=$REPOSITORY_BASENAME"

      echo " >>>>>>>>>>>>>>>>> $workFlowResult >>>>>>>>>>>>>>>>>" >&2
    if [[ "$workFlowResult" == *"PRE_RECEIVE_OK"* ]]; then
      echo " >>>>>>>>>>>>>>>>> $workFlowResult >>>>>>>>>>>>>>>>>" >&2
      COMMIT_CHECK_STATUS=0
    fi

    fi
}

check_all_commits()
{
  REVISIONS=$(git rev-list $OLD_REVISION..$NEW_REVISION)
  IFS='\n' read -ra LIST_OF_REVISIONS <<< "$REVISIONS"
if [ $(git rev-parse --is-bare-repository) = true ]
then
    REPOSITORY_BASENAME=$(basename "$PWD")
else
    REPOSITORY_BASENAME=$(basename $(readlink -nf "$PWD"/..))
fi
    echo REPOSITORY_BASENAME is $REPOSITORY_BASENAME
    REPOSITORY_BASENAME=$(basename "$PWD")
    REPOSITORY_BASENAME=${REPOSITORY_BASENAME%.git}

  for rid in "${!LIST_OF_REVISIONS[@]}"; do
    REVISION=${LIST_OF_REVISIONS[rid]}
    COMMIT_MESSAGE=$(git cat-file commit $REVISION | sed '1,/^$/d')
    COMMIT_AUTHOR=$(git cat-file commit $REVISION | grep committer | sed 's/^.* \([^@ ]\+@[^ ]\+\) \?.*$/\1/' | sed 's/<//' | sed 's/>//' | sed 's/@$COMPANY_EMAIL//')
    check_single_commit

    if [ "$COMMIT_CHECK_STATUS" != "0" ]; then
      echo "Commit validation failed for commit $REVISION" >&2
      exit 1
    fi

  done
}





# Get custom commit message format
while read OLD_REVISION NEW_REVISION REFNAME ; do
  check_all_commits
done

exit 0
#!/usr/bin/env python

import subprocess
import sys
import tempfile
import shutil
import os
import errno

# variables for checkstyle
#checkstyle = '/var/opt/gitlab/git-data/repositories/product-common/ProductCommonParent.git/custom_hooks/checkstyle-7.5.1-all.jar'
#checkstyle_config = '/var/opt/gitlab/git-data/repositories/product-common/ProductCommonParent.git/custom_hooks/sun_checks.xml'
pmd = '/home/gitlab/gitlab_custom_hooks/pmd-bin-5.5.4/bin/run.sh'

# implementing check_output for python < 2.7
if not hasattr(subprocess, 'check_output'):
    def check_output(*popenargs, **kwargs):
        if 'stdout' in kwargs:
            raise ValueError('stdout argument not allowed, it will be overridden.')
        process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
        output, unused_err = process.communicate()
        retcode = process.poll()
        if retcode:
            cmd = kwargs.get("args")
            if cmd is None:
                cmd = popenargs[0]
            er = subprocess.CalledProcessError(retcode, cmd)
            er.output = output
            raise er
        return output
    subprocess.check_output = check_output


# helper for calling executables
def call(*args, **kwargs):
    return subprocess.check_output(*args, **kwargs).strip()


# helper for calling git
def call_git(cmd, *args, **kwargs):
    return call(['git'] + cmd, *args, **kwargs)


# get all new commits from stdin
def get_commits():
    commits = {}
    for line in sys.stdin:
        old, new, ref = line.strip().split(' ')
        if old == '0000000000000000000000000000000000000000':
            old = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'

        if ref not in commits:
            commits[ref] = []
        commits[ref].append({
            'old': old,
            'new': new,
            'files': get_changed_files(old, new)
            })

    return commits


# get a list of changed files between to commits
def get_changed_files(old, new):
    return call_git(['diff', '--name-only', old, new]).split('\n')


# get filemode, object type (blob,tree,commit), hash for the given file at the
# given commit
def get_change_type(commit, filename):
    return call_git(['ls-tree', commit, filename]).split('\t')[0].split(' ')


commits = get_commits()

# use the latest file commit only
print "Cleaning up file list..."

files = {}
count = 0
for ref, data in commits.iteritems():
    files[ref] = {}
    for commit in data:
        for filename in commit['files']:
            if not filename.lower().endswith('.java'): continue
            files[ref][filename] = get_change_type(commit['new'], filename)
    count += len(files[ref])

print "%d Files to check in %d branches" % (count, len(files))

# create temporary dir and save a copy of the new files
tempdir = tempfile.mkdtemp('git_hook')
for ref, files in files.iteritems():
    for filename, data in files.iteritems():
        dname = os.path.dirname(filename)
        bname = os.path.basename(filename)
        try:
            os.makedirs(os.path.join(tempdir, dname))
        except OSError, exc:
            if exc.errno == errno.EEXIST:  # directory exists already
                pass
            else:
                raise

        with open(os.path.join(tempdir, dname, bname), 'w') as fp:
            fp.write(call_git(['cat-file', data[1], data[2]]))

try:
    # call checkstyle and/or pmd and print output
    # print call(['java', '-jar', checkstyle, '-c', checkstyle_config, tempdir])
    # print call(['java', '-jar', '/var/opt/gitlab/git-data/repositories/product-common/ProductCommonParent.git/custom_hooks/hooks-0.0.1-SNAPSHOT.jar', '-prercv', '79', 'developer-email-id', "I am now done with issue #500 #501 #502"])
    print call([pmd, 'pmd', '-d', tempdir, '-f', 'text', '-R', 'rulesets/java/basic.xml,rulesets/java/unusedcode.xml,rulesets/java/imports.xml,rulesets/java/strings.xml,rulesets/java/braces.xml,rulesets/java/clone.xml,rulesets/java/design.xml,rulesets/java/clone.xml,rulesets/java/finalizers.xml,rulesets/java/junit.xml,rulesets/java/migrating.xml,rulesets/java/optimizations.xml,rulesets/java/strictexception.xml,rulesets/java/sunsecure.xml,rulesets/java/typeresolution.xml'])
    print "SUCCESS"
except subprocess.CalledProcessError, ex:
    print ex.output  # print checkstyle and/or pmd messages
    exit(1)
finally:
    # remove temporary directory
    shutil.rmtree(tempdir)
#/usr/bin/env python
导入子流程
导入系统
导入临时文件
进口舒蒂尔
导入操作系统
输入错误号
#checkstyle的变量
#checkstyle='/var/opt/gitlab/git data/repositories/product common/ProductCommonParent.git/custom_hooks/checkstyle-7.5.1-all.jar'
#checkstyle_config='/var/opt/gitlab/git data/repositories/product common/ProductCommonParent.git/custom_hooks/sun_checks.xml'
pmd='/home/gitlab/gitlab_custom_hooks/pmd-bin-5.5.4/bin/run.sh'
#为python<2.7实现check_输出
如果不是hasattr(子流程“检查输出”):
def检查输出(*popenargs,**kwargs):
如果kwargs中的“stdout”:
raise VALUERROR('不允许标准参数,它将被重写')
process=subprocess.Popen(stdout=subprocess.PIPE,*popenargs,**kwargs)
输出,未使用的\u err=process.communicate()
retcode=process.poll()
如果重新编码:
cmd=kwargs.get(“args”)
如果cmd为None:
cmd=popenargs[0]
er=子进程。被调用的进程错误(retcode,cmd)
输出=输出
养儿育女
返回输出
subprocess.check\u output=检查\u输出
#用于调用可执行文件的助手
def调用(*args,**kwargs):
返回子流程。检查输出(*args,**kwargs).strip()
#用于调用git的助手
def call_git(cmd,*args,**kwargs):
回电(['git']+cmd,*args,**kwargs)
#从stdin获取所有新提交
def get_提交():
提交={}
对于sys.stdin中的行:
旧的,新的,ref=line.strip().split(“”)
如果old='0000000000000000000000000000':
old='4B825DC642CB6EB9A060E54BF8D6928FBEE4904'
如果ref不在commit中