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.
Mads Sülau Jørgensen / madssj
3 months ago

Changed (Δ6.4 KB):

raw changeset »

trachook.py (168 lines added, 0 lines removed)

Up to file-list trachook.py:

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)