Edgewall Software

root/branches/0.8-stable/contrib/trac-post-commit-hook

Revision 1247, 6.9 KB (checked in by cmlenz, 4 years ago)

Ported [1246] to stable.

  • Property svn:executable set to *
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# REPOS="$1"
34# REV="$2"
35# LOG=`/usr/bin/svnlook log -r $REV $REPOS`
36# AUTHOR=`/usr/bin/svnlook author -r $REV $REPOS`
37# TRAC_ENV='/somewhere/trac/project/'
38#
39# /usr/bin/python /usr/local/src/trac/contrib/trac-post-commit-hook \
40#  -p "$TRAC_ENV"  \
41#  -r "$REV"       \
42#  -u "$AUTHOR"    \
43#  -m "$LOG"
44#
45# It searches commit messages for text in the form of:
46#   command #1
47#   command #1, #2
48#   command #1 & #2
49#   command #1 and #2
50#
51# You can have more then one command in a message. The following commands
52# are supported. There is more then one spelling for each command, to make
53# this as user-friendly as possible.
54#
55#   closes, fixes
56#     The specified issue numbers are closed with the contents of this
57#     commit message being added to it.
58#   references, refs, addresses, re
59#     The specified issue numbers are left in their current status, but
60#     the contents of this commit message are added to their notes.
61#
62# A fairly complicated example of what you can do is with a commit message
63# of:
64#
65#    Changed blah and foo to do this or that. Fixes #10 and #12, and refs #12.
66#
67# This will close #10 and #12, and add a note to #12.
68
69import re
70import os
71import sys
72import time 
73
74import sqlite
75
76try:
77    from optparse import OptionParser
78except ImportError:
79    try:
80        from optik import OptionParser
81    except ImportError:
82        raise ImportError, 'Requires Python 2.3 or the Optik option parsing library.'
83
84parser = OptionParser()
85parser.add_option('-e', '--require-envelope', dest='env', default='',
86                  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.')
87parser.add_option('-p', '--project', dest='project', help='Path to the Trac project.')
88parser.add_option('-r', '--revision', dest='rev', help='Repository revision number.')
89parser.add_option('-u', '--user', dest='user', help='The user who is responsible for this action')
90parser.add_option('-m', '--msg', dest='msg', help='The log message to search.')
91
92(options, args) = parser.parse_args(sys.argv[1:])
93
94print options.env
95
96if options.env:
97    leftEnv = '\\' + options.env[0]
98    rghtEnv = '\\' + options.env[1]
99else:
100    leftEnv = ''
101    rghtEnv = ''
102   
103commandPattern = re.compile(leftEnv + r'(?P<action>[A-Za-z]*).?(?P<ticket>#[0-9]+(?:(?:[, &]*|[ ]?and[ ]?)#[0-9]+)*)' + rghtEnv)
104ticketPattern = re.compile(r'#([0-9]*)')
105
106class CommitHook:
107    _supported_cmds = { "closes":     '_cmdClose',
108                        "fixes":      '_cmdClose',
109                        "addresses":  '_cmdRefs',
110                        "references": '_cmdRefs',
111                        "refs":       '_cmdRefs',
112                        "re":         '_cmdRefs',
113                      } 
114    def __init__(self, project=options.project, author=options.user, rev=options.rev, msg=options.msg):
115        self.author = author
116        self.rev = rev
117        self.msg = "(In [%s]) %s" % (rev, msg)
118        self.now = int(time.time()) 
119        self.con = sqlite.connect(os.path.join(project, 'db', 'trac.db'), autocommit=0) 
120
121        self.verifyDatabaseVersion()
122
123        cmdGroups = commandPattern.findall(msg) 
124        for cmd, tkts in cmdGroups:
125            try:
126                if CommitHook._supported_cmds.has_key(cmd.lower()):
127                    getattr(self, CommitHook._supported_cmds[cmd.lower()])(ticketPattern.findall(tkts))
128            except: 
129                self.con.rollback()
130        self.con.commit()
131       
132    def _cmdClose(self, tickets):
133        cur = self.con.cursor() 
134        for tkt in tickets: 
135            cur.execute("SELECT status FROM ticket WHERE id=%d", int(tkt))
136            row = cur.fetchone()
137            oldStatus = row[0]   
138            cur.execute("UPDATE ticket SET status='closed', resolution='fixed', changetime=%s WHERE id=%s", self.now, int(tkt)) 
139            cur.execute("INSERT INTO ticket_change (ticket, time, author, field, oldvalue, newvalue) VALUES (%s, %s, %s, %s, %s, %s)",
140                        int(tkt), self.now, self.author, 'comment', '', self.msg)
141            cur.execute("INSERT INTO ticket_change (ticket, time, author, field, oldvalue, newvalue) VALUES (%s, %s, %s, %s, %s, %s)",
142                        int(tkt), self.now, self.author, 'status', oldStatus, 'closed') 
143            cur.execute("INSERT INTO ticket_change (ticket, time, author, field, oldvalue, newvalue) VALUES (%s, %s, %s, %s, %s, %s)",
144                        int(tkt), self.now, self.author, 'resolution', '', 'fixed') 
145           
146    def _cmdRefs(self, tickets):
147        cur = self.con.cursor() 
148        now = int(time.time())
149        for tkt in tickets: 
150            cur.execute("UPDATE ticket SET changetime=%s WHERE id=%s", self.now, int(tkt))
151            cur.execute("INSERT INTO ticket_change (ticket, time, author, field, oldvalue, newvalue) VALUES (%s, %s, %s, %s, %s, %s)",
152                        int(tkt), self.now, self.author, 'comment', '', self.msg)
153
154    def verifyDatabaseVersion(self):
155        """Make sure the database uses a known schema version"""
156        cursor = self.con.cursor()
157        cursor.execute('SELECT value FROM system WHERE '
158                       'name=%s AND value=%s', 'database_version', '7')
159        if not cursor.fetchone():
160            raise Exception('Expected Trac database version 7')
161                       
162if __name__ == "__main__":
163    if len(sys.argv) < 5:
164        print "For usage: %s --help" % (sys.argv[0])
165    else:
166        CommitHook()
167   
Note: See TracBrowser for help on using the browser.