source: timingandestimationplugin/branches/trac0.10/scripts/trac-post-commit.py

Last change on this file was 3686, checked in by Russ Tyndall, 15 years ago

made commit hook regexes be case insensitive

File size: 9.9 KB
Line 
1#!/usr/bin/env python
2#!/usr/bin/env python
3
4# trac-post-commit-hook
5# ----------------------------------------------------------------------------
6# Copyright (c) 2004 Stephen Hansen
7#
8# Permission is hereby granted, free of charge, to any person obtaining a copy
9# of this software and associated documentation files (the "Software"), to
10# deal in the Software without restriction, including without limitation the
11# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12# sell copies of the Software, and to permit persons to whom the Software is
13# furnished to do so, subject to the following conditions:
14#
15#   The above copyright notice and this permission notice shall be included in
16#   all copies or substantial portions of the Software.
17#
18# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24# IN THE SOFTWARE.
25# ----------------------------------------------------------------------------
26
27
28# Changes for the Timing and Estimation plugin
29#
30# "Blah refs #12 (1)" will add 1h to the spent time for issue #12
31# "Blah refs #12 (spent 1.5)" will add 1h to the spent time for issue #12
32#
33# As above it is possible to use complicated messages:
34#
35# "Changed blah and foo to do this or that. Fixes #10 (1) and #12 (2), and refs #13 (0.5)."
36#
37# This will close #10 and #12, and add a note to #13 and also add 1h spent time to #10,
38# add 2h spent time to #12 and add 30m spent time to #13.
39#
40# Note that:
41#     (spent 2), (sp 2) or simply (2) may be used for spent
42#     ' ', ',', '&' or 'and' may be used references
43#
44
45
46# This Subversion post-commit hook script is meant to interface to the
47# Trac (http://www.edgewall.com/products/trac/) issue tracking/wiki/etc
48# system.
49#
50# It should be called from the 'post-commit' script in Subversion, such as
51# via:
52#
53# REPOS="$1"
54# REV="$2"
55#
56# /usr/bin/python /usr/local/src/trac/contrib/trac-post-commit-hook \
57#  -p "$TRAC_ENV" -r "$REV"
58#
59# (all the other arguments are now deprecated and not needed anymore)
60#
61# It searches commit messages for text in the form of:
62#   command #1
63#   command #1, #2
64#   command #1 & #2
65#   command #1 and #2
66#
67# Instead of the short-hand syntax "#1", "ticket:1" can be used as well, e.g.:
68#   command ticket:1
69#   command ticket:1, ticket:2
70#   command ticket:1 & ticket:2
71#   command ticket:1 and ticket:2
72#
73# In addition, the ':' character can be omitted and issue or bug can be used
74# instead of ticket.
75#
76# You can have more then one command in a message. The following commands
77# are supported. There is more then one spelling for each command, to make
78# this as user-friendly as possible.
79#
80#   close, closed, closes, fix, fixed, fixes
81#     The specified issue numbers are closed with the contents of this
82#     commit message being added to it.
83#   references, refs, addresses, re, see
84#     The specified issue numbers are left in their current status, but
85#     the contents of this commit message are added to their notes.
86#
87# A fairly complicated example of what you can do is with a commit message
88# of:
89#
90#    Changed blah and foo to do this or that. Fixes #10 and #12, and refs #12.
91#
92# This will close #10 and #12, and add a note to #12.
93
94import re
95import os
96import sys
97import time
98
99
100from trac.env import open_environment
101from trac.ticket.notification import TicketNotifyEmail
102from trac.ticket import Ticket
103from trac.ticket.web_ui import TicketModule
104# TODO: move grouped_changelog_entries to model.py
105from trac.versioncontrol.api import NoSuchChangeset
106
107logfile = "/var/trac/commithook.log"
108LOG = False
109
110if LOG:
111    f = open (logfile,"w")
112    f.write("Begin Log\n")
113    f.close()
114    def log (s, *params):
115        f = open (logfile,"a")
116        f.write(s % params)
117        f.write("\n")
118        f.close()
119else:
120    def log (s, *params):
121        pass
122
123from optparse import OptionParser
124
125parser = OptionParser()
126depr = '(not used anymore)'
127parser.add_option('-e', '--require-envelope', dest='envelope', default='',
128                  help="""
129Require commands to be enclosed in an envelope.
130If -e[], then commands must be in the form of [closes #4].
131Must be two characters.""")
132parser.add_option('-p', '--project', dest='project',
133                  help='Path to the Trac project.')
134parser.add_option('-r', '--revision', dest='rev',
135                  help='Repository revision number.')
136parser.add_option('-u', '--user', dest='user',
137                  help='The user who is responsible for this action '+depr)
138parser.add_option('-m', '--msg', dest='msg',
139                  help='The log message to search '+depr)
140parser.add_option('-c', '--encoding', dest='encoding',
141                  help='The encoding used by the log message '+depr)
142parser.add_option('-s', '--siteurl', dest='url',
143                  help=depr+' the base_url from trac.ini will always be used.')
144
145(options, args) = parser.parse_args(sys.argv[1:])
146
147_supported_cmds = {'close':      '_cmdClose',
148                   'closed':     '_cmdClose',
149                   'closes':     '_cmdClose',
150                   'fix':        '_cmdClose',
151                   'fixed':      '_cmdClose',
152                   'fixes':      '_cmdClose',
153                   'addresses':  '_cmdRefs',
154                   're':         '_cmdRefs',
155                   'references': '_cmdRefs',
156                   'refs':       '_cmdRefs',
157                   'see':        '_cmdRefs'}
158
159ticket_prefix = '(?:#|(?:ticket|issue|bug)[: ]?)'
160time_pattern = r'[ ]?(?:\((?:(?:spent|sp)[ ]?)?(-?[0-9]*(?:\.[0-9]+)?)\))?'
161ticket_reference = ticket_prefix + '[0-9]+'+time_pattern
162support_cmds_pattern = '|'.join(_supported_cmds.keys())
163ticket_command =  (r'(?P<action>(?:%s))[ ]*'
164                   '(?P<ticket>%s(?:(?:[, &]*|[ ]?and[ ]?)%s)*)' %
165                   (support_cmds_pattern,ticket_reference, ticket_reference))
166
167if options.envelope:
168    ticket_command = r'\%s%s\%s' % (options.envelope[0], ticket_command,
169                                    options.envelope[1])
170   
171command_re = re.compile(ticket_command, re.IGNORECASE)
172ticket_re = re.compile(ticket_prefix + '([0-9]+)'+time_pattern, re.IGNORECASE)
173
174class CommitHook:
175
176    def init_env(self, project):
177        self.env = open_environment(project)
178       
179
180    def __init__(self, project=options.project, author=options.user,
181                 rev=options.rev, url=options.url):
182        self.init_env( project )
183       
184        repos = self.env.get_repository()
185        repos.sync()
186        # Instead of bothering with the encoding, we'll use unicode data
187        # as provided by the Trac versioncontrol API (#1310).
188        try:
189            chgset = repos.get_changeset(rev)
190        except NoSuchChangeset:
191            return # out of scope changesets are not cached
192        self.author = chgset.author
193        self.rev = rev
194        self.msg = "(In [%s]) %s" % (rev, chgset.message)
195        self.now = int(time.time())
196
197        cmd_groups = command_re.findall(self.msg)
198        log ("cmd_groups:%s", cmd_groups)
199        tickets = {}
200        for cmd, tkts, xxx1, xxx2 in cmd_groups:
201            log ("cmd:%s, tkts%s ", cmd, tkts)
202            funcname = _supported_cmds.get(cmd.lower(), '')
203            if funcname:
204                for tkt_id, spent in ticket_re.findall(tkts):
205                    func = getattr(self, funcname)
206                    lst = tickets.setdefault(tkt_id, [])
207                    lst.append([func, spent])
208                   
209
210        for tkt_id, vals in tickets.iteritems():
211            log ("tkt_id:%s, vals%s ", tkt_id, vals)
212            spent_total = 0.0
213            try:
214                db = self.env.get_db_cnx()
215               
216                ticket = Ticket(self.env, int(tkt_id), db)
217                for (cmd, spent) in vals:
218                    cmd(ticket)
219                    if spent:
220                        spent_total += float(spent)
221
222                # determine sequence number...
223                cnum = 0
224                tm = TicketModule(self.env)
225                for change in tm.grouped_changelog_entries(ticket, db):
226                    if change['permanent']:
227                        cnum += 1
228                if spent_total:
229                    self._setTimeTrackerFields(ticket, spent_total)
230                ticket.save_changes(self.author, self.msg, self.now, db, cnum+1)
231                db.commit()
232
233                tn = TicketNotifyEmail(self.env)
234                tn.notify(ticket, newticket=0, modtime=self.now)
235            except Exception, e:
236                # import traceback
237                # traceback.print_exc(file=sys.stderr)
238                log('Unexpected error while processing ticket ' \
239                                   'ID %s: %s' % (tkt_id, e))
240                print>>sys.stderr, 'Unexpected error while processing ticket ' \
241                                   'ID %s: %s' % (tkt_id, e)
242           
243
244    def _cmdClose(self, ticket):
245        ticket['status'] = 'closed'
246        ticket['resolution'] = 'fixed'
247
248    def _cmdRefs(self, ticket):
249        pass
250
251    def _setTimeTrackerFields(self, ticket, spent):
252        log ("Setting ticket:%s spent: %s", ticket, spent)
253        if (spent):
254            spentTime = float(spent)
255            if (ticket.values.has_key('hours')):
256                ticket['hours'] = str(spentTime)
257               
258
259if __name__ == "__main__":
260    if len(sys.argv) < 5:
261        print "For usage: %s --help" % (sys.argv[0])
262        print
263        print "Note that the deprecated options will be removed in Trac 0.12."
264    else:
265        CommitHook()
266
Note: See TracBrowser for help on using the repository browser.