某團隊希望做到Continuous Code Review, 想在每次check-in 到SVN之前,先判斷特定用戶羣體否在commit log裏面包含了"Review By: xxx"的字樣。
記得以前NSN裏面有人用過這個法子,記不太清了。
於是研究了一下腳本,其實SVN/GIT都提供了類似的hook, 在<your repository>/hooks 目錄下,都是shell或cmd腳本(要看服務器的操作系統了),會在不同的事件時觸發。
爲了實現更復雜的功能或者需要跨平臺,那不妨用shell或cmd去調用Python腳本咯。
SVN的基本原理就是用"svnlook"命令來查看服務器的元數據,比如"svnlook info REPOS_PATH --revision 23“(見http://i18n-zh.googlecode.com/svn/www/svnbook-1.4/svn.ref.svnlook.c.info.html)
其中 REPOS_PATH是服務器上的絕對路徑。--revision後面跟已有版本號碼,而--transaction後面跟提交事務ID,也就是我們要利用的。
shell腳本如下,其中路徑根據需要自行修改:
# pre-commit.sh
# %1 is REPOS_PATH passed in by svn server
# %2 is transaction ID passed in by svn server
python ./pre-commit.py %1 %2
#!/bin/env python
" Example Subversion pre-commit hook. "
def command_output(cmd):
" Capture a command's standard output. "
import subprocess
return subprocess.Popen(
cmd.split(), stdout=subprocess.PIPE).communicate()[0]
def files_changed(look_cmd):
""" List the files added or updated by this transaction.
"svnlook changed" gives output like:
U trunk/file1.cpp
A trunk/file2.cpp
"""
def filename(line):
return line[4:]
def added_or_updated(line):
return line and line[0] in ("A", "U")
return [
filename(line)
for line in command_output(look_cmd % "changed").split("\n")
if added_or_updated(line)]
def txn_log(look_cmd):
""" Print the author, datestamp, log message size (in bytes),
and log message, followed by a newline character.
"svnlook info" gives output like:
sally
2003-02-22 17:44:49 -0600 (Sat, 22 Feb 2003)
16
Rearrange lunch.
"""
print look_cmd
lines = command_output(look_cmd % "info").splitlines()
return [lines[0], ''.join(lines[3:])]
def file_contents(filename, look_cmd):
" Return a file's contents for this transaction. "
return command_output(
"%s %s" % (look_cmd % "cat", filename))
def contains_tabs(filename, look_cmd):
" Return True if this version of the file contains tabs. "
return "\t" in file_contents(filename, look_cmd)
def check_cpp_files_for_tabs(look_cmd):
" Check C++ files in this transaction are tab-free. "
def is_cpp_file(fname):
import os
return os.path.splitext(fname)[1] in ".cpp .cxx .h".split()
cpp_files_with_tabs = [
ff for ff in files_changed(look_cmd)
if is_cpp_file(ff) and contains_tabs(ff, look_cmd)]
if len(cpp_files_with_tabs) > 0:
sys.stderr.write("The following files contain tabs:\n%s\n"
% "\n".join(cpp_files_with_tabs))
return len(cpp_files_with_tabs)
def check_changed_file():
usage = """usage: %prog REPOS TXN
Run pre-commit options on a repository transaction."""
from optparse import OptionParser
parser = OptionParser(usage=usage)
parser.add_option("-r", "--revision",
help="Test mode. TXN actually refers to a revision.",
action="store_true", default=False)
errors = 0
try:
(opts, (repos, txn_or_rvn)) = parser.parse_args()
look_opt = ("--transaction", "--revision")[opts.revision]
look_cmd = "svnlook %s %s %s %s" % (
"%s", repos, look_opt, txn_or_rvn)
errors += check_cpp_files_for_tabs(look_cmd)
except:
parser.print_help()
errors += 1
return errors
def check_txn_log():
usage = """usage: %prog REPOS
Run pre-commit options on a repository last log."""
from optparse import OptionParser
parser = OptionParser(usage=usage)
errors = 0
try:
(opts, (repos)) = parser.parse_args()
print parser.parse_args()
look_cmd = "svnlook %s -t %s %s " % (
"%s", repos[1], repos[0])
(user, message) = txn_log(look_cmd)
print user
print message
user_list = ['jacky', 'jimmy']
if not 'review by' in message.lower() and user in user_list:
errors = 33
except:
parser.print_help()
errors = 1
return errors
if __name__ == "__main__":
import sys
sys.exit(check_txn_log())