如何设置Gitlab挂钩来验证git push到remote

时间:2017-02-09 12:35:13

标签: linux git bash gitlab devops

我们有一个DevOps流程,人们从远程主分支机构检出或更新,从主机创建一个新的本地工作分支,并开始在本地分支机构工作。

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

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

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

https://github.com/Praqma/git-hooks/commit/2aa087fada0b0da51724f37a902362ddd78e168f

http://blog.hgomez.net/2015/03/02/Gitlab-custom-hooks-Bash-Way.html

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

预收(无延期)

#!/usr/bin/env bash
#

source /var/opt/gitlab/git-data/repositories/Product-common/ProductCommonParent.git/custom_hooks/pre-receive-functions.sh

# enforced custom commit message format
while read old_revision new_revision refname ; do
        process_revision
done

exit 0

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


}

以下是我尝试推送时的输出(我正在从Windows 8.1上的Git Bash shell推送到安装了Gitlab的Fedora Core 24):

xxx@xxxxx-HP MINGW64 ~/Documents/DevOps Re-Engineering/ProductCommonParent (ProductCommonParent002)
$ git push
Counting objects: 3, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 369 bytes | 0 bytes/s, done.
Total 3 (delta 2), reused 0 (delta 0)
remote: In pre-receive hook. Just before retrieving the revisions
remote: In pre-receive hook. Just before IFS
remote:
remote: To create a merge request for ProductCommonParent002, visit:
remote:   http://localhost/Product-common/ProductCommonParent/merge_requests/new?merge_request%5Bsource_branch%5D=ProductCommonParent002
remote:
To http://192.168.56.101/Product-common/ProductCommonParent.git
 * [new branch]      ProductCommonParent002 -> ProductCommonParent002

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

我将非常感谢您快速帮助我们解决这个问题。非常感谢您的帮助。

1 个答案:

答案 0 :(得分:1)

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

2)我们通过编写调用Gitlab API的Java Spring Boot CommandLineRunner应用程序来实现检查我们的开发人员是否符合我们的开发策略和流程的规则。此应用程序打包为jar文件。

我们确保开发人员必须拥有有效的票号作为其git提交消息的一部分才能成功推送到其工作分支的远程对应方。必须为此有效票证分配给他,拥有有效的里程碑,并选择正确的标签(NEW FEATURE,BUG,TASK等),以便推动成功。

我们使用执行jar文件的bash shell脚本与Gitlab服务器上的git钩子集成,并根据java应用程序的输出允许或失败推送请求。这个shell脚本是http://blog.hgomez.net/2015/03/02/Gitlab-custom-hooks-Bash-Way.html的改编版,可以在下面找到:

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

3)虽然不是问题的一部分,但是在不使用PMD Jenkins插件的情况下在服务器端集成PMD检查,需要下载PMD可执行启动依赖项,从python脚本中执行PMD到静态分析开发人员推送到git服务器(Gitlab服务器)的源文件。引导PMD的python脚本可以很容易地集成到上面的bash shell脚本中。 python脚本是http://bluec0re.blogspot.com.ng/2012/05/git-pre-receive-hook-with-checkstyle.html的改编版,可以在下面找到:

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