多人团队开发中,通常我们需要对提交的代码进行格式统一化,或者对提交的代码进行预编译,只有编译通过的代码才能上传到服务器。钩子脚本(hooks)正是实现这些功能的工具。

hooks 是什么?

根据特定的 SVN 操作(如提交(commit)、加锁(get-lock)和释放锁(release-lock)等),SVN 会在处理这些操作时调用对应的 hooks。这些 hooks 可以由用户自行编写,实现特定的功能。

hooks 可以在服务器上使用,也可以在每个程序员的客户端上使用。服务器端的 hooks 具有完整的功能,而客户端只有部分功能(Git)或者不能使用钩子(SVN),因此下面主要研究服务端 hooks 的使用。

hooks 脚本

打开一个 SVN 代码管理仓库的服务器文件夹(或者自己新建一个服务器文件夹)可以看到在 hooks 文件夹下有如下 hooks 模板文件:

    post-commit.tmpl
    post-lock.tmpl
    post-revprop-change.tmpl
    post-unlock.tmpl
    pre-commit.tmpl
    pre-lock.tmpl
    pre-revprop-change.tmpl
    pre-unlock.tmpl
    start-commit.tmpl

未来使用这些脚本,在 Windows 系统中需要将后缀修改为.bat,在 Linux 中需要修改为.sh。我们可以在这些文件中重新编写脚本,实现特定的功能。

注意:hooks 模板文件是对应的 Linux 系统,模板文件在 Windows 下是不能运行的,比如在 pre-commit.tmpl 中,获取传入参数的命令在 Windows 下应该修改为:

SET REPOS=%1
SET TXN=%2

svnlook 命令

svnlook 命令时 SVN 提供的一个命令行工具,用于查看仓库的信息,如提交日志、修改历史和文件属性等。

常用的 svnlook 命令如下:

  • svnlook changed: 显示指定版本的提交的文件和目录的变化。
  • svnlook cat: 显示提交文件的具体内容。

为了简单起见,下面只介绍 hooks 最常用的一个脚本:pre-commit。

pre-commit

当客户端执行 commit 时,服务器会调用该脚本,脚本结束时返回 0 则可以提交,返回非 0 则终止此次提交。

输入参数:

  1. REPOS,对应仓库的路径
  2. TXN,对应提交的版本号

因为我对 Python 脚本比较熟悉,命令行程序敲起来头疼,所以我就在 bat 文件中调用 Python 脚本实现钩子功能了。

SET REPOS=%1
SET TXN=%2

python path_to_your_python_script.py %REPOS% %TXN%

使用 Python 脚本

使用 Python 脚本实现如下功能:

  1. 从服务器仓库克隆出仓库的一个完整副本
  2. 获取提交的目录和文件,将它们更新到副本文件夹中
  3. 在副本文件夹中执行程序的编译、测试等,成功返回 0,失败则返回非 0(脚本中未实现)
import os, sys
from subprocess import check_output
import shutil

repos = sys.argv[1]
txn = sys.argv[2]
temp_dir = 'E:/temp'
repo_url = ''

def checkout_repo():
  if os.path.exists(temp_dir):
    shutil.rmtree(directory_path)
  cmd = 'svn checkout %s %s' % (repo_url, temp_dir)
  check_output(cmd)

def commit_changes():
  cmd = ('svnlook', 'changed', repos, '-t', txn)
  out = check_output(cmd).splitlines()
  for line in out:
    line = line.decode()
    yield line.split()

checkout_repo()

for status, path in commit_changes():
  if path[-1] == "/":
    target_dir = os.path.join(temp_dir, path)
    if not os.path.exists(target_dir):
      os.makedirs(target_dir)
  else:
    cmd = 'svnlook cat -t %s %s %s' % (txn, repos, path)
    file_content = check_output(cmd)
    file_content = file_content.decode('utf-8')
    target_filename = os.path.join(temp_dir, path)
    with open(target_filename, 'w', encodeing='utf-8') as f:
      f.write(file_content)

总结

SVN 钩子脚本可以在我们对仓库进行某些操作时调用一些自定义的脚本,扩展了版本管理软件功能,能够有效的提高开发的效率,保证代码质量。