Edgewall Software

Ticket #1602: trac-post-commit-hook.0.10.3.py

File trac-post-commit-hook.0.10.3.py, 8.2 KB (added by markus, 7 years ago)

Works with trac 0.10.3, international characters in changeset messages and ticket comments, no temporary file

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# SET REV=%2
34# SET REPNAME=%~nx1
35#
36# :: Modify paths and port number here
37# :: Presumes that trac environment directory has the same name as svn repository
38# SET TRAC_ENV=D:\trac\trac_env\%REPNAME%
39# SET TRAC_URL=http://%COMPUTERNAME%:8080/%REPNAME%
40# SET PYTHON="C:\Python24\python.exe"
41#
42# :: Do not execute hook if trac environment does not exist
43# IF NOT EXIST %TRAC_ENV% GOTO :EOF
44#
45# %PYTHON% "%~dp0\trac-post-commit-hook.py" -p "%TRAC_ENV%" -r "%REV%" -s "%TRAC_URL%"
46#
47# It searches commit messages for text in the form of:
48#   command #1
49#   command #1, #2
50#   command #1 & #2
51#   command #1 and #2
52#
53# You can have more then one command in a message. The following commands
54# are supported. There is more then one spelling for each command, to make
55# this as user-friendly as possible.
56#
57#   closes, fixes
58#     The specified issue numbers are closed with the contents of this
59#     commit message being added to it.
60#   references, refs, addresses, re
61#     The specified issue numbers are left in their current status, but
62#     the contents of this commit message are added to their notes.
63#
64# A fairly complicated example of what you can do is with a commit message
65# of:
66#
67#    Changed blah and foo to do this or that. Fixes #10 and #12, and refs #12.
68#
69# This will close #10 and #12, and add a note to #12.
70
71import re
72import os
73import sys
74import time 
75
76from trac.env import open_environment
77from trac.ticket.notification import TicketNotifyEmail
78from trac.ticket import Ticket
79from trac.ticket.web_ui import TicketModule
80# TODO: move grouped_changelog_entries to model.py
81from trac.util.text import to_unicode
82from trac.web.href import Href
83from trac.versioncontrol.svn_fs import SubversionRepository, SubversionChangeset
84from trac.log import logger_factory
85
86try:
87    from optparse import OptionParser
88except ImportError:
89    try:
90        from optik import OptionParser
91    except ImportError:
92        raise ImportError, 'Requires Python 2.3 or the Optik option parsing library.'
93
94parser = OptionParser()
95parser.add_option('-e', '--require-envelope', dest='env', default='',
96                  help='Require commands to be enclosed in an envelope. If -e[], '
97                       'then commands must be in the form of [closes #4]. Must '
98                       'be two characters.')
99parser.add_option('-p', '--project', dest='project',
100                  help='Path to the Trac project.')
101parser.add_option('-r', '--revision', dest='rev',
102                  help='Repository revision number.')
103parser.add_option('-u', '--user', dest='user',
104                  help='The user who is responsible for this action')
105parser.add_option('-m', '--msg', dest='msg',
106                  help='The log message to search.')
107parser.add_option('-c', '--encoding', dest='encoding',
108                  help='The encoding used by the log message.')
109parser.add_option('-s', '--siteurl', dest='url',
110                  help='The base URL to the project\'s trac website (to which '
111                       '/ticket/## is appended).  If this is not specified, '
112                       'the project URL from trac.ini will be used.')
113
114(options, args) = parser.parse_args(sys.argv[1:])
115
116if options.env:
117    leftEnv = '\\' + options.env[0]
118    rghtEnv = '\\' + options.env[1]
119else:
120    leftEnv = ''
121    rghtEnv = ''
122
123commandPattern = re.compile(leftEnv + r'(?P<action>[A-Za-z]*).?(?P<ticket>#[0-9]+(?:(?:[, &]*|[ ]?and[ ]?)#[0-9]+)*)' + rghtEnv)
124ticketPattern = re.compile(r'#([0-9]*)')
125
126class CommitHook:
127    _supported_cmds = {'close':      '_cmdClose',
128                       'closed':     '_cmdClose',
129                       'closes':     '_cmdClose',
130                       'fix':        '_cmdClose',
131                       'fixed':      '_cmdClose',
132                       'fixes':      '_cmdClose',
133                       'addresses':  '_cmdRefs',
134                       're':         '_cmdRefs',
135                       'references': '_cmdRefs',
136                       'refs':       '_cmdRefs',
137                       'see':        '_cmdRefs'}
138
139    def __init__(self, project=options.project, author=options.user,
140                 rev=options.rev, msg=options.msg, url=options.url,
141                 encoding=options.encoding):
142        # http://trac.edgewall.org/ticket/1310
143        self.env = open_environment(project)
144        repos_dir = self.env.config.get('trac', 'repository_dir')
145        repos = SubversionRepository(repos_dir, '', logger_factory('test'))
146        change = repos.get_changeset(rev)
147        msg = change.message
148        to_unicode(msg)
149        msg = msg.decode('utf-8')
150        self.author = change.author
151        self.rev = rev
152        self.msg = "(In [%s]) %s" % (rev, msg)
153        self.now = int(time.time()) 
154        if url is None:
155            url = self.env.config.get('project', 'url')
156        self.env.href = Href(url)
157        self.env.abs_href = Href(url)
158
159        cmdGroups = commandPattern.findall(msg)
160
161        tickets = {}
162        for cmd, tkts in cmdGroups:
163            funcname = CommitHook._supported_cmds.get(cmd.lower(), '')
164            if funcname:
165                for tkt_id in ticketPattern.findall(tkts):
166                    func = getattr(self, funcname)
167                    tickets.setdefault(tkt_id, []).append(func)
168
169        for tkt_id, cmds in tickets.iteritems():
170            try:
171                db = self.env.get_db_cnx()
172               
173                ticket = Ticket(self.env, int(tkt_id), db)
174                for cmd in cmds:
175                    cmd(ticket)
176
177                # determine sequence number...
178                cnum = 0
179                tm = TicketModule(self.env)
180                for change in tm.grouped_changelog_entries(ticket, db):
181                    if change['permanent']:
182                        cnum += 1
183               
184                ticket.save_changes(self.author, self.msg, self.now, db, cnum+1)
185                db.commit()
186               
187                tn = TicketNotifyEmail(self.env)
188                tn.notify(ticket, newticket=0, modtime=self.now)
189            except Exception, e:
190                # import traceback
191                # traceback.print_exc(file=sys.stderr)
192                print>>sys.stderr, 'Unexpected error while processing ticket ' \
193                                   'ID %s: %s' % (tkt_id, e)
194           
195
196    def _cmdClose(self, ticket):
197        ticket['status'] = 'closed'
198        ticket['resolution'] = 'fixed'
199
200    def _cmdRefs(self, ticket):
201        pass
202
203if __name__ == "__main__":
204    if len(sys.argv) < 3:
205        print "For usage: %s --help" % (sys.argv[0])
206    else:
207        CommitHook()