Edgewall Software

Ticket #1602: trac-post-commit-hook_4bat

File trac-post-commit-hook_4bat, 8.3 KB (added by coreywangler@…, 3 years ago)

renamed python script given in #897

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# LINUX:
34#
35# REPOS="$1"
36# REV="$2"
37# LOG=`/usr/bin/svnlook log -r $REV $REPOS`
38# AUTHOR=`/usr/bin/svnlook author -r $REV $REPOS`
39# TRAC_ENV='/somewhere/trac/project/'
40#
41# /usr/bin/python /usr/local/src/trac/contrib/trac-post-commit-hook \
42#  -p "$TRAC_ENV"  \
43#  -r "$REV"       \
44#  -u "$AUTHOR"    \
45#  -m "$LOG"
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
101import sqlite
102
103try:
104    from optparse import OptionParser
105except ImportError:
106    try:
107        from optik import OptionParser
108    except ImportError:
109        raise ImportError, 'Requires Python 2.3 or the Optik option parsing library.'
110
111parser = OptionParser()
112parser.add_option('-e', '--require-envelope', dest='env', default='',
113                  help='Require commands to be enclosed in an envelope. If -e[], then commands must be in the form of [closes #4]. Must be two characters.')
114parser.add_option('-p', '--project', dest='project', help='Path to the Trac project.')
115parser.add_option('-r', '--revision', dest='rev', help='Repository revision number.')
116parser.add_option('-u', '--user', dest='user', help='The user who is responsible for this action')
117parser.add_option('-m', '--msg', dest='msg', help='The log message to search.')
118
119(options, args) = parser.parse_args(sys.argv[1:])
120
121print options.env
122
123if options.env:
124    leftEnv = '\\' + options.env[0]
125    rghtEnv = '\\' + options.env[1]
126else:
127    leftEnv = ''
128    rghtEnv = ''
129   
130commandPattern = re.compile(leftEnv + r'(?P<action>[A-Za-z]*).?(?P<ticket>#[0-9]+(?:(?:[, &]*|[ ]?and[ ]?)#[0-9]+)*)' + rghtEnv)
131ticketPattern = re.compile(r'#([0-9]*)')
132
133class CommitHook:
134    _supported_cmds = { "closes":     '_cmdClose',
135                        "fixes":      '_cmdClose',
136                        "addresses":  '_cmdRefs',
137                        "references": '_cmdRefs',
138                        "refs":       '_cmdRefs',
139                        "re":         '_cmdRefs',
140                      } 
141    def __init__(self, project=options.project, author=options.user, rev=options.rev, msg=options.msg):
142        self.author = author
143        self.rev = rev
144        msg = self._readFromFile( msg )
145        self.msg = "(In [%s]) %s" % (rev, msg)
146        self.now = int(time.time()) 
147        self.con = sqlite.connect(os.path.join(project, 'db', 'trac.db'), autocommit=0) 
148
149        self.verifyDatabaseVersion()
150
151        cmdGroups = commandPattern.findall(msg) 
152        for cmd, tkts in cmdGroups:
153            try:
154                getattr(self, CommitHook._supported_cmds[cmd.lower()])(ticketPattern.findall(tkts))
155            except: 
156                self.con.rollback()
157        self.con.commit()
158       
159    def _cmdClose(self, tickets):
160        cur = self.con.cursor() 
161        for tkt in tickets: 
162            cur.execute("SELECT status FROM ticket WHERE id=%d", int(tkt))
163            row = cur.fetchone()
164            oldStatus = row[0]   
165            cur.execute("UPDATE ticket SET status='closed', resolution='fixed', changetime=%s WHERE id=%s", self.now, int(tkt)) 
166            cur.execute("INSERT INTO ticket_change (ticket, time, author, field, oldvalue, newvalue) VALUES (%s, %s, %s, %s, %s, %s)",
167                        int(tkt), self.now, self.author, 'comment', '', self.msg)
168            cur.execute("INSERT INTO ticket_change (ticket, time, author, field, oldvalue, newvalue) VALUES (%s, %s, %s, %s, %s, %s)",
169                        int(tkt), self.now, self.author, 'status', oldStatus, 'closed') 
170            cur.execute("INSERT INTO ticket_change (ticket, time, author, field, oldvalue, newvalue) VALUES (%s, %s, %s, %s, %s, %s)",
171                        int(tkt), self.now, self.author, 'resolution', '', 'fixed') 
172           
173    def _cmdRefs(self, tickets):
174        cur = self.con.cursor() 
175        now = int(time.time())
176        for tkt in tickets: 
177            cur.execute("UPDATE ticket SET changetime=%s WHERE id=%s", self.now, int(tkt))
178            cur.execute("INSERT INTO ticket_change (ticket, time, author, field, oldvalue, newvalue) VALUES (%s, %s, %s, %s, %s, %s)",
179                        int(tkt), self.now, self.author, 'comment', '', self.msg)
180
181    def _readFromFile(self, message):
182        """ Ivan Melnychuk: SOLUTION FOR WINDOWS SYSTEMS:
183        Windows does not support returning values from script.
184        Therefore temporary files may be used to keep some information like commit message
185        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
186        Even though actual message also may start with such prefix, it is very unlikely that the file exists with such a name by accident
187        """
188        if message[:5] == 'file:' and os.path.exists( message[5:] ):
189            f = open( message[5:], 'r' )
190            message = f.read()
191            f.close()
192            return message
193        return message;
194
195    def verifyDatabaseVersion(self):
196        """Make sure the database uses a known schema version"""
197        cursor = self.con.cursor()
198        cursor.execute('SELECT value FROM system WHERE '
199                       'name=%s AND value=%s', 'database_version', '7')
200        if not cursor.fetchone():
201            raise Exception('Expected Trac database version 7')
202
203if __name__ == "__main__":
204    if len(sys.argv) < 5:
205        print "For usage: %s --help" % (sys.argv[0])
206    else:
207        CommitHook()