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

Revision 3686, 9.9 kB (checked in by bobbysmith007, 4 months ago)

made commit hook regexes be case insensitive

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
94 import re
95 import os
96 import sys
97 import time
98
99
100 from trac.env import open_environment
101 from trac.ticket.notification import TicketNotifyEmail
102 from trac.ticket import Ticket
103 from trac.ticket.web_ui import TicketModule
104 # TODO: move grouped_changelog_entries to model.py
105 from trac.versioncontrol.api import NoSuchChangeset
106
107 logfile = "/var/trac/commithook.log"
108 LOG = False
109
110 if 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()
119 else:
120     def log (s, *params):
121         pass
122
123 from optparse import OptionParser
124
125 parser = OptionParser()
126 depr = '(not used anymore)'
127 parser.add_option('-e', '--require-envelope', dest='envelope', default='',
128                   help="""
129 Require commands to be enclosed in an envelope.
130 If -e[], then commands must be in the form of [closes #4].
131 Must be two characters.""")
132 parser.add_option('-p', '--project', dest='project',
133                   help='Path to the Trac project.')
134 parser.add_option('-r', '--revision', dest='rev',
135                   help='Repository revision number.')
136 parser.add_option('-u', '--user', dest='user',
137                   help='The user who is responsible for this action '+depr)
138 parser.add_option('-m', '--msg', dest='msg',
139                   help='The log message to search '+depr)
140 parser.add_option('-c', '--encoding', dest='encoding',
141                   help='The encoding used by the log message '+depr)
142 parser.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
159 ticket_prefix = '(?:#|(?:ticket|issue|bug)[: ]?)'
160 time_pattern = r'[ ]?(?:\((?:(?:spent|sp)[ ]?)?(-?[0-9]*(?:\.[0-9]+)?)\))?'
161 ticket_reference = ticket_prefix + '[0-9]+'+time_pattern
162 support_cmds_pattern = '|'.join(_supported_cmds.keys())
163 ticket_command =  (r'(?P<action>(?:%s))[ ]*'
164                    '(?P<ticket>%s(?:(?:[, &]*|[ ]?and[ ]?)%s)*)' %
165                    (support_cmds_pattern,ticket_reference, ticket_reference))
166
167 if options.envelope:
168     ticket_command = r'\%s%s\%s' % (options.envelope[0], ticket_command,
169                                     options.envelope[1])
170    
171 command_re = re.compile(ticket_command, re.IGNORECASE)
172 ticket_re = re.compile(ticket_prefix + '([0-9]+)'+time_pattern, re.IGNORECASE)
173
174 class 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
259 if __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()
Note: See TracBrowser for help on using the browser.