madssj /
mercurial-trac-hook
A trac commit hook for mercurial.
Clone URL : http://bitbucket.org/madssj/mercurial-trac-hook/ (size: 6.4 KB)
| commit 0: | c691ee29117c |
| branch: | default |
Added hook.
Changed (Δ6.4 KB):
raw changeset »
trachook.py (168 lines added, 0 lines removed)
1 |
#!/usr/bin/env python |
|
2 |
||
3 |
# trac-post-commit-hook |
|
4 |
# ---------------------------------------------------------------------------- |
|
5 |
# Copyright (c) 2004 Stephen Hansen, Mads Sulau Joergensen |
|
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 |
||
27 |
import re |
|
28 |
import os |
|
29 |
import sys |
|
30 |
from datetime import datetime |
|
31 |
||
32 |
from trac.env import open_environment |
|
33 |
from trac.ticket.notification import TicketNotifyEmail |
|
34 |
from trac.ticket import Ticket |
|
35 |
from trac.ticket.web_ui import TicketModule |
|
36 |
# TODO: move grouped_changelog_entries to model.py |
|
37 |
from trac.util.text import to_unicode |
|
38 |
from trac.util.datefmt import utc |
|
39 |
from trac.versioncontrol.api import NoSuchChangeset |
|
40 |
||
41 |
from mercurial.i18n import _ |
|
42 |
from mercurial.node import short |
|
43 |
from mercurial import cmdutil, templater, util |
|
44 |
||
45 |
_supported_cmds = {'close': '_cmdClose', |
|
46 |
'closed': '_cmdClose', |
|
47 |
'closes': '_cmdClose', |
|
48 |
'fix': '_cmdClose', |
|
49 |
'fixed': '_cmdClose', |
|
50 |
'fixes': '_cmdClose', |
|
51 |
'addresses': '_cmdRefs', |
|
52 |
're': '_cmdRefs', |
|
53 |
'references': '_cmdRefs', |
|
54 |
'refs': '_cmdRefs', |
|
55 |
'see': '_cmdRefs'} |
|
56 |
||
57 |
ticket_prefix = '(?:#|(?:ticket|issue|bug)[: ]?)' |
|
58 |
time_pattern = r'[ ]?(?:\((?:(?:spent|sp)[ ]?)?(-?[0-9]*(?:\.[0-9]+)?)\))?' |
|
59 |
ticket_reference = ticket_prefix + '[0-9]+'+time_pattern |
|
60 |
support_cmds_pattern = '|'.join(_supported_cmds.keys()) |
|
61 |
ticket_command = (r'(?P<action>(?:%s))[ ]*' |
|
62 |
'(?P<ticket>%s(?:(?:[, &]*|[ ]?and[ ]?)%s)*)' % |
|
63 |
(support_cmds_pattern,ticket_reference, ticket_reference)) |
|
64 |
command_re = re.compile(ticket_command) |
|
65 |
ticket_re = re.compile(ticket_prefix + '([0-9]+)') |
|
66 |
||
67 |
command_re = re.compile(ticket_command) |
|
68 |
ticket_re = re.compile(ticket_prefix + '([0-9]+)'+time_pattern) |
|
69 |
||
70 |
class CommitHook: |
|
71 |
def init_env(self, project): |
|
72 |
self.env = open_environment(project) |
|
73 |
||
74 |
def __init__(self, project): |
|
75 |
self.init_env(project) |
|
76 |
||
77 |
self.repos = self.env.get_repository() |
|
78 |
self.repos.sync() |
|
79 |
||
80 |
def update(self, author, rev, url=None): |
|
81 |
# Instead of bothering with the encoding, we'll use unicode data |
|
82 |
# as provided by the Trac versioncontrol API (#1310). |
|
83 |
try: |
|
84 |
chgset = self.repos.get_changeset(rev) |
|
85 |
except NoSuchChangeset: |
|
86 |
return # out of scope changesets are not cached |
|
87 |
self.author = chgset.author |
|
88 |
self.rev = rev |
|
89 |
self.msg = "(In [%s]) %s" % (rev, chgset.message) |
|
90 |
self.now = datetime.now(utc) |
|
91 |
||
92 |
cmd_groups = command_re.findall(self.msg) |
|
93 |
tickets = {} |
|
94 |
for cmd, tkts, xxx1, xxx2 in cmd_groups: |
|
95 |
funcname = _supported_cmds.get(cmd.lower(), '') |
|
96 |
if funcname: |
|
97 |
for tkt_id, spent in ticket_re.findall(tkts): |
|
98 |
func = getattr(self, funcname) |
|
99 |
lst = tickets.setdefault(tkt_id, []) |
|
100 |
lst.append([func, spent]) |
|
101 |
||
102 |
for tkt_id, vals in tickets.iteritems(): |
|
103 |
spent_total = 0.0 |
|
104 |
try: |
|
105 |
db = self.env.get_db_cnx() |
|
106 |
||
107 |
ticket = Ticket(self.env, int(tkt_id), db) |
|
108 |
for (cmd, spent) in vals: |
|
109 |
cmd(ticket) |
|
110 |
if spent: |
|
111 |
spent_total += float(spent) |
|
112 |
||
113 |
# determine sequence number... |
|
114 |
cnum = 0 |
|
115 |
tm = TicketModule(self.env) |
|
116 |
for change in tm.grouped_changelog_entries(ticket, db): |
|
117 |
if change['permanent']: |
|
118 |
cnum += 1 |
|
119 |
if spent_total: |
|
120 |
self._setTimeTrackerFields(ticket, spent_total) |
|
121 |
ticket.save_changes(self.author, self.msg, self.now, db, cnum+1) |
|
122 |
db.commit() |
|
123 |
||
124 |
tn = TicketNotifyEmail(self.env) |
|
125 |
tn.notify(ticket, newticket=0, modtime=self.now) |
|
126 |
except Exception, e: |
|
127 |
# import traceback |
|
128 |
# traceback.print_exc(file=sys.stderr) |
|
129 |
print>>sys.stderr, 'Unexpected error while processing ticket ' \ |
|
130 |
'ID %s: %s' % (tkt_id, e) |
|
131 |
continue |
|
132 |
||
133 |
def _cmdClose(self, ticket): |
|
134 |
ticket['status'] = 'closed' |
|
135 |
ticket['resolution'] = 'fixed' |
|
136 |
||
137 |
def _cmdRefs(self, ticket): |
|
138 |
pass |
|
139 |
||
140 |
def _setTimeTrackerFields(self, ticket, spent): |
|
141 |
if (spent != ''): |
|
142 |
spentTime = float(spent) |
|
143 |
if (ticket.values.has_key('hours')): |
|
144 |
ticket['hours'] = str(spentTime) |
|
145 |
||
146 |
def hook(ui, repo, hooktype, node=None, **kwargs): |
|
147 |
""" |
|
148 |
Mercurial trac commit hook. |
|
149 |
""" |
|
150 |
if node is None: |
|
151 |
raise util.Abort(_('hook type %s does not pass a changeset id') % hooktype) |
|
152 |
||
153 |
project = ui.config('trac-hook', 'root', None) |
|
154 |
url = ui.config('trac-hook', 'url', None) |
|
155 |
||
156 |
if project is None: |
|
157 |
raise util.Abort(_('you need to configure the trac-hook in your hgrc - root missing')) |
|
158 |
||
159 |
ctx = repo.changectx(node) |
|
160 |
rev = ctx.rev() |
|
161 |
until = repo.changelog.count() |
|
162 |
||
163 |
trac_hook = CommitHook(project) |
|
164 |
||
165 |
for r in set(range(rev, until)): |
|
166 |
r = short(repo.lookup(r)) |
|
167 |
c = repo.changectx(r) |
|
168 |
trac_hook.update(c.user(), r, url) |
