Edgewall Software

Ticket #6135: trac-post-commit-hook_for-enterprice-workflow.py

File trac-post-commit-hook_for-enterprice-workflow.py, 8.6 KB (added by vinogradniy@…, 14 months ago)

Enhancement for Enterprise Workflow

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# !!!!!!!!!!!!!! FOR ENTERPISE WORKFLOW ONLY !!!!!!!!!!!!!!
27# This Subversion post-commit hook script is meant to interface to the
28# Trac (http://www.edgewall.com/products/trac/) issue tracking/wiki/etc
29# system.
30#
31# It should be called from the 'post-commit' script in Subversion, such as
32# via:
33#
34# REPOS="$1"
35# REV="$2"
36#
37# /usr/bin/python /usr/local/src/trac/contrib/trac-post-commit-hook \
38#  -p "$TRAC_ENV" -r "$REV"
39#
40# (all the other arguments are now deprecated and not needed anymore)
41#
42# It searches commit messages for text in the form of:
43#   command #1
44#   command #1, #2
45#   command #1 & #2
46#   command #1 and #2
47#
48# Instead of the short-hand syntax "#1", "ticket:1" can be used as well, e.g.:
49#   command ticket:1
50#   command ticket:1, ticket:2
51#   command ticket:1 & ticket:2
52#   command ticket:1 and ticket:2
53#
54# In addition, the ':' character can be omitted and issue or bug can be used
55# instead of ticket.
56#
57# You can have more then one command in a message. The following commands
58# are supported. There is more then one spelling for each command, to make
59# this as user-friendly as possible.
60#
61#   close, closed, closes, fix, fixed, fixes
62#     The specified issue numbers are closed with the contents of this
63#     commit message being added to it.
64#   references, refs, addresses, re, see
65#     The specified issue numbers are left in their current status, but
66#     the contents of this commit message are added to their notes.
67#   test, testing, tested, verify, verified
68#     The specified issuen numbers are set in_QA status (test resolution) with
69#     the contents of this commit message being added to it.
70#
71# A fairly complicated example of what you can do is with a commit message
72# of:
73#
74#    Changed blah and foo to do this or that. Fixes #10 and #12, and refs #12.
75#
76# This will close #10 and #12, and add a note to #12.
77
78import re
79import os
80import sys
81from datetime import datetime
82
83from trac.env import open_environment
84from trac.ticket.notification import TicketNotifyEmail
85from trac.ticket import Ticket
86from trac.ticket.web_ui import TicketModule
87# TODO: move grouped_changelog_entries to model.py
88from trac.util.text import to_unicode
89from trac.util.datefmt import utc
90from trac.versioncontrol.api import NoSuchChangeset
91
92from optparse import OptionParser
93
94parser = OptionParser()
95depr = '(not used anymore)'
96parser.add_option('-e', '--require-envelope', dest='envelope', default='',
97                  help="""
98Require commands to be enclosed in an envelope.
99If -e[], then commands must be in the form of [closes #4].
100Must be two characters.""")
101parser.add_option('-p', '--project', dest='project',
102                  help='Path to the Trac project.')
103parser.add_option('-r', '--revision', dest='rev',
104                  help='Repository revision number.')
105parser.add_option('-u', '--user', dest='user',
106                  help='The user who is responsible for this action '+depr)
107parser.add_option('-m', '--msg', dest='msg',
108                  help='The log message to search '+depr)
109parser.add_option('-c', '--encoding', dest='encoding',
110                  help='The encoding used by the log message '+depr)
111parser.add_option('-s', '--siteurl', dest='url',
112                  help=depr+' the base_url from trac.ini will always be used.')
113
114(options, args) = parser.parse_args(sys.argv[1:])
115
116
117ticket_prefix = '(?:#|(?:ticket|issue|bug)[: ]?)'
118ticket_reference = ticket_prefix + '[0-9]+'
119ticket_command =  (r'(?P<action>[A-Za-z]*).?'
120                   '(?P<ticket>%s(?:(?:[, &]*|[ ]?and[ ]?)%s)*)' %
121                   (ticket_reference, ticket_reference))
122
123if options.envelope:
124    ticket_command = r'\%s%s\%s' % (options.envelope[0], ticket_command,
125                                    options.envelope[1])
126   
127command_re = re.compile(ticket_command)
128ticket_re = re.compile(ticket_prefix + '([0-9]+)')
129
130class CommitHook:
131    _supported_cmds = {'close':      '_cmdClose',
132                       'closed':     '_cmdClose',
133                       'closes':     '_cmdClose',
134                       'fix':        '_cmdClose',
135                       'fixed':      '_cmdClose',
136                       'fixes':      '_cmdClose',
137                       'addresses':  '_cmdRefs',
138                       're':         '_cmdRefs',
139                       'references': '_cmdRefs',
140                       'refs':       '_cmdRefs',
141                       'see':        '_cmdRefs',
142                       'test':       '_cmdTest',
143                       'testing':    '_cmdTest',
144                       'tested':     '_cmdTest',
145                       'verify':     '_cmdTest',
146                       'verified':   '_cmdTest'
147                       }
148
149    def __init__(self, project=options.project, author=options.user,
150                 rev=options.rev, url=options.url):
151        self.env = open_environment(project)
152        repos = self.env.get_repository()
153        repos.sync()
154       
155        # Instead of bothering with the encoding, we'll use unicode data
156        # as provided by the Trac versioncontrol API (#1310).
157        try:
158            chgset = repos.get_changeset(rev)
159        except NoSuchChangeset:
160            return # out of scope changesets are not cached
161        self.author = chgset.author
162        self.rev = rev
163        self.msg = "(In [%s]) %s" % (rev, chgset.message)
164        self.now = datetime.now(utc)
165
166        cmd_groups = command_re.findall(self.msg)
167
168        tickets = {}
169        for cmd, tkts in cmd_groups:
170            funcname = CommitHook._supported_cmds.get(cmd.lower(), '')
171            if funcname:
172                for tkt_id in ticket_re.findall(tkts):
173                    func = getattr(self, funcname)
174                    tickets.setdefault(tkt_id, []).append(func)
175
176        for tkt_id, cmds in tickets.iteritems():
177            try:
178                db = self.env.get_db_cnx()
179               
180                ticket = Ticket(self.env, int(tkt_id), db)
181                for cmd in cmds:
182                    cmd(ticket)
183
184                # determine sequence number...
185                cnum = 0
186                tm = TicketModule(self.env)
187                for change in tm.grouped_changelog_entries(ticket, db):
188                    if change['permanent']:
189                        cnum += 1
190               
191                ticket.save_changes(self.author, self.msg, self.now, db, cnum+1)
192                db.commit()
193               
194                tn = TicketNotifyEmail(self.env)
195                tn.notify(ticket, newticket=0, modtime=self.now)
196            except Exception, e:
197                # import traceback
198                # traceback.print_exc(file=sys.stderr)
199                print>>sys.stderr, 'Unexpected error while processing ticket ' \
200                                   'ID %s: %s' % (tkt_id, e)
201           
202
203    def _cmdClose(self, ticket):
204        ticket['status'] = 'closed'
205        ticket['resolution'] = 'fixed'
206
207    def _cmdRefs(self, ticket):
208        pass
209   
210    def _cmdTest(self, ticket):
211        ticket['status'] = 'in_QA'
212        ticket['resolution'] = 'test'
213
214
215if __name__ == "__main__":
216    if len(sys.argv) < 5:
217        print "For usage: %s --help" % (sys.argv[0])
218        print
219        print "Note that the deprecated options will be removed in Trac 0.12."
220    else:
221        CommitHook()