Edgewall Software

Ticket #1602: trac-post-commit-hook_4bat.2

File trac-post-commit-hook_4bat.2, 8.4 KB (added by Denney <denney@…>, 3 years ago)

Updated to work with Trac 0.9-stable

Line 
1#!/usr/bin/env python
2
3# trac-post-commit-hook
4# ----------------------------------------------------------------------------
5# Copyright (c) 2004 Stephen Hansen
6#
7# Permission is hereby granted, free of charge, to any person obtaining a copy
8# of this software and associated documentation files (the "Software"), to
9# deal in the Software without restriction, including without limitation the
10# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
11# sell copies of the Software, and to permit persons to whom the Software is
12# furnished to do so, subject to the following conditions:
13#
14#   The above copyright notice and this permission notice shall be included in
15#   all copies or substantial portions of the Software.
16#
17# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
23# IN THE SOFTWARE.
24# ----------------------------------------------------------------------------
25
26# This Subversion post-commit hook script is meant to interface to the
27# Trac (http://www.edgewall.com/products/trac/) issue tracking/wiki/etc
28# system.
29#
30# It should be called from the 'post-commit' script in Subversion, such as
31# via:
32#
33# REPOS="$1"
34# REV="$2"
35# LOG=`/usr/bin/svnlook log -r $REV $REPOS`
36# AUTHOR=`/usr/bin/svnlook author -r $REV $REPOS`
37# TRAC_ENV='/somewhere/trac/project/'
38# TRAC_URL='http://trac.mysite.com/project/'
39#
40# /usr/bin/python /usr/local/src/trac/contrib/trac-post-commit-hook \
41#  -p "$TRAC_ENV"  \
42#  -r "$REV"       \
43#  -u "$AUTHOR"    \
44#  -m "$LOG"       \
45#  -s "$TRAC_URL"
46#
47# WINDOWS: sample of post-commit.bat (must use temporary files for the log message)
48#
49##SET REPOS=%1
50##SET REV=%2
51##
52##::-----------------------------
53##::Call the TRAC post-commit hook
54##::
55##SET TRAC_ENV=C:\somewhere\trac\project
56##SET LOG_FILE=%TEMP%.\svnfileR-%REV%
57##SET AUT_FILE=%TEMP%.\svnfileA-%REV%
58##
59##svnlook log -r %REV% %REPOS%>%LOG_FILE%
60##svnlook author -r %REV% %REPOS%>%AUT_FILE%
61##
62##:: SET THE AUTHOR FROM THE FILE. The file is expected to contain only one line with this value
63##FOR /F %%A IN (%AUT_FILE%) DO SET AUTHOR=%%A
64##
65##python [trac-path]\contrib\trac-post-commit-hook.py -p "%TRAC_ENV%" -r "%REV%" -u "%AUTHOR%" -m "file:%LOG_FILE%"
66##DEL %LOG_FILE%
67##DEL %AUT_FILE%
68##::
69##::-----------------------------
70##
71#
72# It searches commit messages for text in the form of:
73#   command #1
74#   command #1, #2
75#   command #1 & #2
76#   command #1 and #2
77#
78# You can have more then one command in a message. The following commands
79# are supported. There is more then one spelling for each command, to make
80# this as user-friendly as possible.
81#
82#   closes, fixes
83#     The specified issue numbers are closed with the contents of this
84#     commit message being added to it.
85#   references, refs, addresses, re
86#     The specified issue numbers are left in their current status, but
87#     the contents of this commit message are added to their notes.
88#
89# A fairly complicated example of what you can do is with a commit message
90# of:
91#
92#    Changed blah and foo to do this or that. Fixes #10 and #12, and refs #12.
93#
94# This will close #10 and #12, and add a note to #12.
95
96import re
97import os
98import sys
99import time 
100
101from trac.env import open_environment
102from trac.Notify import TicketNotifyEmail
103from trac.ticket import Ticket
104from trac.web.href import Href
105
106try:
107    from optparse import OptionParser
108except ImportError:
109    try:
110        from optik import OptionParser
111    except ImportError:
112        raise ImportError, 'Requires Python 2.3 or the Optik option parsing library.'
113
114parser = OptionParser()
115parser.add_option('-e', '--require-envelope', dest='env', default='',
116                  help='Require commands to be enclosed in an envelope. If -e[], '
117                       'then commands must be in the form of [closes #4]. Must '
118                       'be two characters.')
119parser.add_option('-p', '--project', dest='project',
120                  help='Path to the Trac project.')
121parser.add_option('-r', '--revision', dest='rev',
122                  help='Repository revision number.')
123parser.add_option('-u', '--user', dest='user',
124                  help='The user who is responsible for this action')
125parser.add_option('-m', '--msg', dest='msg',
126                  help='The log message to search.')
127parser.add_option('-s', '--siteurl', dest='url',
128                  help='The base URL to the project\'s trac website (to which '
129                       '/ticket/## is appended).  If this is not specified, '
130                       'the project URL from trac.ini will be used.')
131
132(options, args) = parser.parse_args(sys.argv[1:])
133
134if options.env:
135    leftEnv = '\\' + options.env[0]
136    rghtEnv = '\\' + options.env[1]
137else:
138    leftEnv = ''
139    rghtEnv = ''
140
141commandPattern = re.compile(leftEnv + r'(?P<action>[A-Za-z]*).?(?P<ticket>#[0-9]+(?:(?:[, &]*|[ ]?and[ ]?)#[0-9]+)*)' + rghtEnv)
142ticketPattern = re.compile(r'#([0-9]*)')
143
144class CommitHook:
145    _supported_cmds = {'close':      '_cmdClose',
146                       'closed':     '_cmdClose',
147                       'closes':     '_cmdClose',
148                       'fix':        '_cmdClose',
149                       'fixed':      '_cmdClose',
150                       'fixes':      '_cmdClose',
151                       'addresses':  '_cmdRefs',
152                       're':         '_cmdRefs',
153                       'references': '_cmdRefs',
154                       'refs':       '_cmdRefs',
155                       'see':        '_cmdRefs'}
156
157    def __init__(self, project=options.project, author=options.user,
158                 rev=options.rev, msg=options.msg, url=options.url):
159        self.author = author
160        self.rev = rev
161        msg = self._readFromFile( msg )
162        self.msg = "(In [%s]) %s" % (rev, msg)
163        self.now = int(time.time()) 
164        self.env = open_environment(project)
165        if url is None:
166            url = self.env.config.get('project', 'url')
167        self.env.href = Href(url)
168        self.env.abs_href = Href(url)
169
170        cmdGroups = commandPattern.findall(msg) 
171        for cmd, tkts in cmdGroups:
172            if CommitHook._supported_cmds.has_key(cmd.lower()):
173                func = getattr(self, CommitHook._supported_cmds[cmd.lower()])
174                func(ticketPattern.findall(tkts))
175
176    def _cmdClose(self, tickets):
177        for tkt_id in tickets:
178            try:
179                ticket = Ticket(self.env, tkt_id)
180                ticket['status'] = 'closed'
181                ticket['resolution'] = 'fixed'
182                ticket.save_changes(self.author, self.msg, self.now)
183                tn = TicketNotifyEmail(self.env)
184                tn.notify(ticket, newticket=0, modtime=self.now)
185            except Exception, e:
186                print>>sys.stderr, 'Unexpected error while processing ticket ' \
187                                   'ID %s: %s' % (tkt_id, e)
188
189    def _cmdRefs(self, tickets):
190        for tkt_id in tickets: 
191            try:
192                ticket = Ticket(self.env, tkt_id)
193                ticket.save_changes(self.author, self.msg, self.now)
194                tn = TicketNotifyEmail(self.env)
195                tn.notify(ticket, newticket=0, modtime=self.now)
196            except Exception, e:
197                print>>sys.stderr, 'Unexpected error while processing ticket ' \
198                                   'ID %s: %s' % (tkt_id, e)
199
200    def _readFromFile(self, message):
201        """ Ivan Melnychuk: SOLUTION FOR WINDOWS SYSTEMS:
202        Windows does not support returning values from script.
203        Therefore temporary files may be used to keep some information like commit message
204        If the 'message' starts with 'file:', and the file with indicated name exists, then read the text message from the file rather then using the text directly
205        Even though actual message also may start with such prefix, it is very unlikely that the file exists with such a name by accident
206        """
207        if message[:5] == 'file:' and os.path.exists( message[5:] ):
208            f = open( message[5:], 'r' )
209            message = f.read()
210            f.close()
211            return message
212        return message;
213
214if __name__ == "__main__":
215    if len(sys.argv) < 5:
216        print "For usage: %s --help" % (sys.argv[0])
217    else:
218        CommitHook()