1)既定では、新しいプロジェクトやgit repoのデフォルトの制限付きブランチごとに制限が既に用意されています。これらの制限は、Gitlabの非管理者および非rootユーザーに適用されます。
2)Gitlab APIを呼び出したJavaのSpringブートCommandLineRunnerアプリケーションを作成して、開発者が開発ポリシーとプロセスに準拠していることを確認するためのルールを実装しました。このアプリケーションはjarファイルとしてパッケージ化されています。
gitコミットメッセージの一部として開発者が有効なチケット番号を持っていることを確認してから、作業現場のリモートカウンターパートに正常にプッシュできるようにしました。この有効なチケットは、彼に割り当てられ、有効なマイルストーンを持ち、プッシュが成功するために選択された正しいラベル(新機能、バグ、タスクなどのいずれか)を持たなければなりません。
jitファイルを実行し、Javaアプリケーションからの出力に基づいてプッシュリクエストを許可または拒否するbashシェルスクリプトを使用して、Gitlabサーバー上のgitフックと統合しました。このシェル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/^.* \([^@ ]\[email protected][^ ]\+\) \?.*$/\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ジェンキンスプラグインを使用せずにサーバ側でPMDチェックを統合、必要PMD実行可能ファイルの起動依存関係のダウンロード、Pythonスクリプト内からのPMDの実行、開発者がgitサーバ(Gitlabサーバ)にプッシュしているソースファイルを静的に解析します。 PMDをブートストラップするpythonスクリプトは、上記のbashシェルスクリプトに簡単に統合できます。 http://bluec0re.blogspot.com.ng/2012/05/git-pre-receive-hook-with-checkstyle.htmlの順応であるpythonスクリプトは、以下にあります。
#!/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)