且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

如何使用Trac& amp;实施Post Commit Hook Windows环境中的SVN?

更新时间:2023-12-04 08:06:34

好了,既然我已经花了很多时间在总结了这些经验之后就发表自己的经验,并感谢Craig使我走上正确的道路.这是您需要做的(至少在SVN v1.4和Trac v0.10.3中):

Alright, now that I've got some time to post my experience after figuring this all out, and thanks to Craig for getting me on the right track. Here's what you need to do (at least with SVN v1.4 and Trac v0.10.3):

  1. 找到要为其启用Post Commit Hook的SVN存储库.
  2. 在SVN存储库中有一个名为hooks的目录,您将在其中放置后提交钩子.
  3. 创建一个文件post-commit.bat(这是SVN后提交自动调用的批处理文件).
  4. 将以下代码放入post-commit.bat文件中(这将调用您的post commit cmd文件,并传入SVN自动传递的参数,其中%1是存储库,%2是已提交的修订版.

%〜dp0 \ trac-post-commit-hook.cmd%1%2

%~dp0\trac-post-commit-hook.cmd %1 %2

  1. 现在创建trac-post-commit-hook.cmd文件,如下所示:

@ECHO OFF
::
:: Trac 提交后挂接脚本 Windows
::
::供稿人: markus,由cboos修改.

:: 用法:
::
:: 1)插入 post-commit.bat中的以下行 脚本
::
::调用 %〜dp0 \ trac-post-commit-hook.cmd%1 %2
::
:: 2)选中修改 路径"部分,请务必进行设置 至少TRAC_ENV


:: -------------------------------------------------- --------
::在此处修改路径:

::- 必须设置此
SET TRAC_ENV = C:\ trac \ MySpecialProject

::-设置Python是否不在 系统路径
:: SET PYTHON_PATH =

::-设置为 包含trac/的文件夹(如果已安装) 在非标准位置
:: SET TRAC_PATH =
:: -------------------------------------------------- --------

::如果执行trac,请勿执行钩子 环境不存在
如果不存在 EXIST%TRAC_ENV%GOTO:EOF

设置PATH =%PYTHON_PATH%;设置%PATH%
PYTHONPATH =%TRAC_PATH%;%PYTHONPATH%

SET REV =%2

::获取 /F的作者和日志消息
%% A in('svnlook author -r%REV%%1') 确实将/F设置为AUTHOR = %% A
(delims ==" %% B in('svnlook log -r %REV%%1')确实设置了LOG = %% B

:: 调用PYTHON脚本
Python %〜dp0 \ trac-post-commit-hook" -p %TRAC_ENV%" -r%REV%" -u%AUTHOR%" -m%LOG%"

@ECHO OFF
::
:: Trac post-commit-hook script for Windows
::
:: Contributed by markus, modified by cboos.

:: Usage:
::
:: 1) Insert the following line in your post-commit.bat script
::
:: call %~dp0\trac-post-commit-hook.cmd %1 %2
::
:: 2) Check the 'Modify paths' section below, be sure to set at least TRAC_ENV


:: ----------------------------------------------------------
:: Modify paths here:

:: -- this one must be set
SET TRAC_ENV=C:\trac\MySpecialProject

:: -- set if Python is not in the system path
:: SET PYTHON_PATH=

:: -- set to the folder containing trac/ if installed in a non-standard location
:: SET TRAC_PATH=
:: ----------------------------------------------------------

:: Do not execute hook if trac environment does not exist
IF NOT EXIST %TRAC_ENV% GOTO :EOF

set PATH=%PYTHON_PATH%;%PATH%
set PYTHONPATH=%TRAC_PATH%;%PYTHONPATH%

SET REV=%2

:: GET THE AUTHOR AND THE LOG MESSAGE
for /F %%A in ('svnlook author -r %REV% %1') do set AUTHOR=%%A
for /F "delims==" %%B in ('svnlook log -r %REV% %1') do set LOG=%%B

:: CALL THE PYTHON SCRIPT
Python "%~dp0\trac-post-commit-hook" -p "%TRAC_ENV%" -r "%REV%" -u "%AUTHOR%" -m "%LOG%"

这里最重要的部分是设置TRAC_ENV,它是存储库根目录的路径(SET TRAC_ENV = C:\ trac \ MySpecialProject)

The most important parts here are to set your TRAC_ENV which is the path to the repository root (SET TRAC_ENV=C:\trac\MySpecialProject)

此脚本中的下一个重要事项是执行以下操作:

The next MAJORLY IMPORTANT THING in this script is to do the following:

::获取作者和日志 MESSAGE
表示/F %% A,位于('svnlook 作者-r%REV%%1')进行设置 /F的AUTHOR = %% A
"delims ==" %% B 在('svnlook log -r%REV%%1')中进行设置 LOG = %% B

:: GET THE AUTHOR AND THE LOG MESSAGE
for /F %%A in ('svnlook author -r %REV% %1') do set AUTHOR=%%A
for /F "delims==" %%B in ('svnlook log -r %REV% %1') do set LOG=%%B

如果您在上面的脚本文件中看到,我正在使用svnlook(这是SVN的命令行实用程序)来获取LOG消息和做出对存储库的提交的作者.

if you see in the script file above I'm using svnlook (which is a command line utility with SVN) to get the LOG message and the author that made the commit to the repository.

然后,脚本的下一行实际上是在调用Python代码来执行票证的关闭并解析日志消息.我必须对其进行修改,以传递日志消息和作者(我在Trac中使用的用户名与SVN中的用户名相匹配,所以很简单).

Then, the next line of the script is actually calling the Python code to perform the closing of the tickets and parse the log message. I had to modify this to pass in the Log message and the author (which the usernames I use in Trac match the usernames in SVN so that was easy).

调用PYTHON脚本
Python %〜dp0 \ trac-post-commit-hook" -p %TRAC_ENV%" -r%REV%" -u%AUTHOR%" -m%LOG%"

CALL THE PYTHON SCRIPT
Python "%~dp0\trac-post-commit-hook" -p "%TRAC_ENV%" -r "%REV%" -u "%AUTHOR%" -m "%LOG%"

脚本中的上述行将Trac环境,修订版,提交人员及其评论传递到python脚本中.

The above line in the script will pass into the python script the Trac Environment, the revision, the person that made the commit, and their comment.

这是我使用的Python脚本.除了常规脚本外,我要做的一件事是,我们使用了一个自定义字段(fixed_in_ver),供我们的质量检查小组使用,它可以确定他们正在验证的修复程序是否在他们正在质量检查中测试的代码版本中.因此,我修改了python脚本中的代码以更新票证上的该字段.您可以删除不需要的代码,但这是一个很好的示例,说明了如果您也想这样做,可以在Trac中更新自定义字段.

Here's the Python script that I used. One thing that I did additional to the regular script is we use a custom field (fixed_in_ver) which is used by our QA team to tell if the fix they're validating is in the version of code that they're testing in QA. So, I modified the code in the python script to update that field on the ticket. You can remove that code as you won't need it, but it's a good example of what you can do to update custom fields in Trac if you also want to do that.

我这样做的目的是让用户可以选择在其评论中添加以下内容:

I did that by having the users optionally include in their comment something like:

(版本2.1.2223.0)

(version 2.1.2223.0)

然后,我使用python脚本与正则表达式使用的相同技术来获取信息.还算不错.

I then use the same technique that the python script uses with regular expressions to get the information out. It wasn't too bad.

无论如何,这是我使用的python脚本,希望这是一个很好的教程,准确说明了我如何使其在Windows世界中正常工作,以便大家都可以在自己的商店中利用它...

Anyway, here's the python script I used, Hopefully this is a good tutorial on exactly what I did to get it to work in the windows world so you all can leverage this in your own shop...

如果您不想处理我用于更新自定义字段的其他代码,请从上面Craig提到的位置获取基本脚本(

If you don't want to deal with my additional code for updating the custom field, get the base script from this location as mentioned by Craig above (Script From Edgewall)

#!/usr/bin/env python

# trac-post-commit-hook
# ----------------------------------------------------------------------------
# Copyright (c) 2004 Stephen Hansen 
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
#   The above copyright notice and this permission notice shall be included in
#   all copies or substantial portions of the Software. 
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
# ----------------------------------------------------------------------------

# This Subversion post-commit hook script is meant to interface to the
# Trac (http://www.edgewall.com/products/trac/) issue tracking/wiki/etc 
# system.
# 
# It should be called from the 'post-commit' script in Subversion, such as
# via:
#
# REPOS="$1"
# REV="$2"
# LOG=`/usr/bin/svnlook log -r $REV $REPOS`
# AUTHOR=`/usr/bin/svnlook author -r $REV $REPOS`
# TRAC_ENV='/somewhere/trac/project/'
# TRAC_URL='http://trac.mysite.com/project/'
#
# /usr/bin/python /usr/local/src/trac/contrib/trac-post-commit-hook \
#  -p "$TRAC_ENV"  \
#  -r "$REV"       \
#  -u "$AUTHOR"    \
#  -m "$LOG"       \
#  -s "$TRAC_URL"
#
# It searches commit messages for text in the form of:
#   command #1
#   command #1, #2
#   command #1 & #2 
#   command #1 and #2
#
# You can have more then one command in a message. The following commands
# are supported. There is more then one spelling for each command, to make
# this as user-friendly as possible.
#
#   closes, fixes
#     The specified issue numbers are closed with the contents of this
#     commit message being added to it. 
#   references, refs, addresses, re 
#     The specified issue numbers are left in their current status, but 
#     the contents of this commit message are added to their notes. 
#
# A fairly complicated example of what you can do is with a commit message
# of:
#
#    Changed blah and foo to do this or that. Fixes #10 and #12, and refs #12.
#
# This will close #10 and #12, and add a note to #12.

import re
import os
import sys
import time 

from trac.env import open_environment
from trac.ticket.notification import TicketNotifyEmail
from trac.ticket import Ticket
from trac.ticket.web_ui import TicketModule
# TODO: move grouped_changelog_entries to model.py
from trac.util.text import to_unicode
from trac.web.href import Href

try:
    from optparse import OptionParser
except ImportError:
    try:
        from optik import OptionParser
    except ImportError:
        raise ImportError, 'Requires Python 2.3 or the Optik option parsing library.'

parser = OptionParser()
parser.add_option('-e', '--require-envelope', dest='env', default='',
                  help='Require commands to be enclosed in an envelope. If -e[], '
                       'then commands must be in the form of [closes #4]. Must '
                       'be two characters.')
parser.add_option('-p', '--project', dest='project',
                  help='Path to the Trac project.')
parser.add_option('-r', '--revision', dest='rev',
                  help='Repository revision number.')
parser.add_option('-u', '--user', dest='user',
                  help='The user who is responsible for this action')
parser.add_option('-m', '--msg', dest='msg',
                  help='The log message to search.')
parser.add_option('-c', '--encoding', dest='encoding',
                  help='The encoding used by the log message.')
parser.add_option('-s', '--siteurl', dest='url',
                  help='The base URL to the project\'s trac website (to which '
                       '/ticket/## is appended).  If this is not specified, '
                       'the project URL from trac.ini will be used.')

(options, args) = parser.parse_args(sys.argv[1:])

if options.env:
    leftEnv = '\\' + options.env[0]
    rghtEnv = '\\' + options.env[1]
else:
    leftEnv = ''
    rghtEnv = ''

commandPattern = re.compile(leftEnv + r'(?P<action>[A-Za-z]*).?(?P<ticket>#[0-9]+(?:(?:[, &]*|[ ]?and[ ]?)#[0-9]+)*)' + rghtEnv)
ticketPattern = re.compile(r'#([0-9]*)')
versionPattern = re.compile(r"\(version[ ]+(?P<version>([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+))\)")

class CommitHook:
    _supported_cmds = {'close':      '_cmdClose',
                       'closed':     '_cmdClose',
                       'closes':     '_cmdClose',
                       'fix':        '_cmdClose',
                       'fixed':      '_cmdClose',
                       'fixes':      '_cmdClose',
                       'addresses':  '_cmdRefs',
                       're':         '_cmdRefs',
                       'references': '_cmdRefs',
                       'refs':       '_cmdRefs',
                       'see':        '_cmdRefs'}

    def __init__(self, project=options.project, author=options.user,
                 rev=options.rev, msg=options.msg, url=options.url,
                 encoding=options.encoding):
        msg = to_unicode(msg, encoding)
        self.author = author
        self.rev = rev
        self.msg = "(In [%s]) %s" % (rev, msg)
        self.now = int(time.time()) 
        self.env = open_environment(project)
        if url is None:
            url = self.env.config.get('project', 'url')
        self.env.href = Href(url)
        self.env.abs_href = Href(url)

        cmdGroups = commandPattern.findall(msg)


        tickets = {}

        for cmd, tkts in cmdGroups:
            funcname = CommitHook._supported_cmds.get(cmd.lower(), '')

            if funcname:

                for tkt_id in ticketPattern.findall(tkts):
                    func = getattr(self, funcname)
                    tickets.setdefault(tkt_id, []).append(func)

        for tkt_id, cmds in tickets.iteritems():
            try:
                db = self.env.get_db_cnx()

                ticket = Ticket(self.env, int(tkt_id), db)
                for cmd in cmds:
                    cmd(ticket)

                # determine sequence number... 
                cnum = 0
                tm = TicketModule(self.env)
                for change in tm.grouped_changelog_entries(ticket, db):
                    if change['permanent']:
                        cnum += 1

                # get the version number from the checkin... and update the ticket with it.
                version = versionPattern.search(msg)
                if version != None and version.group("version") != None:
                    ticket['fixed_in_ver'] = version.group("version")

                ticket.save_changes(self.author, self.msg, self.now, db, cnum+1)
                db.commit()

                tn = TicketNotifyEmail(self.env)
                tn.notify(ticket, newticket=0, modtime=self.now)
            except Exception, e:
                # import traceback
                # traceback.print_exc(file=sys.stderr)
                print>>sys.stderr, 'Unexpected error while processing ticket ' \
                                   'ID %s: %s' % (tkt_id, e)


    def _cmdClose(self, ticket):
        ticket['status'] = 'closed'
        ticket['resolution'] = 'fixed'

    def _cmdRefs(self, ticket):
        pass


if __name__ == "__main__":
    if len(sys.argv) < 5:
        print "For usage: %s --help" % (sys.argv[0])
    else:
        CommitHook()