linux - 如何设置 Gitlab 钩子(Hook)来验证 git 推送到远程

标签 linux git bash gitlab devops

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

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

2) 我需要一个钩子(Hook)来确保表单中有有效的 Gitlab 票证或问题编号,例如 #PROJECTNAME123,然后允许他们的推送继续到他们的远程分支(在代码审查和 merge 到远程分支之前)掌握)。此外,如果工单不存在或尚未打开,他们不得推送。

我已经使用来自以下两个网站的信息创建了一个 Bash 预接收 Hook ,但它们被调用但仍然允许 git 推送到达服务器,即使我没有传递 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

预接收函数.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) 对于任何新项目或 git repo 的每个默认受限分支,默认情况下已经存在限制。这些限制适用于 Gitlab 的非管理员和非根用户。

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

我们确保开发人员必须有一个有效的票号作为他/她的 git 提交消息的一部分,然后他/她才能成功推送到他工作分支的远程副本。这张有效的票必须分配给他,有一个有效的里程碑,并且选择了正确的标签(NEW FEATURE、BUG、TASK 等)才能使推送成功。

我们通过使用执行 jar 文件的 bash shell 脚本与 Gitlab 服务器上的 git Hook 集成,并根据 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)

关于linux - 如何设置 Gitlab 钩子(Hook)来验证 git 推送到远程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42136891/

相关文章:

ios - 苹果自己的 ASLR 实现是如何工作的?

c - Linux中的计时器

Git:将提交的一部分应用到另一个分支

eclipse - 由于 SunCertPathBuilderException : certificate not found,无法在 Eclipse 中连接到 GitHub

python - 在 Python 中排序和使用 map

git:更新当前分支

bash - 交错两个通配符命令

python - 文件内容是否完全存在于库文件中

linux - 在 (Bash/Docker RUN) 中以非交互方式自动回答 bash 命令的多个提示

php二进制和系统时间戳奇怪的区别