#!/usr/bin/env python # trac-post-commit-hook # ---------------------------------------------------------------------------- # Copyright (c) 2004 Stephen Hansen, 2008 Mads Sulau Joergensen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to # deal in the Software without restriction, including without limitation the # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or # sell copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. # ---------------------------------------------------------------------------- import re import os import sys from datetime import datetime from trac.env import open_environment from trac.ticket.notification import TicketNotifyEmail from trac.ticket import Ticket from trac.ticket.web_ui import TicketModule # TODO: move grouped_changelog_entries to model.py from trac.util.text import to_unicode from trac.util.datefmt import utc from trac.versioncontrol.api import NoSuchChangeset from mercurial.i18n import _ from mercurial.node import short from mercurial import cmdutil, templater, util _supported_cmds = {'close': '_cmdClose', 'closed': '_cmdClose', 'closes': '_cmdClose', 'fix': '_cmdClose', 'fixed': '_cmdClose', 'fixes': '_cmdClose', 'addresses': '_cmdRefs', 're': '_cmdRefs', 'references': '_cmdRefs', 'refs': '_cmdRefs', 'see': '_cmdRefs'} ticket_prefix = '(?:#|(?:ticket|issue|bug)[: ]?)' time_pattern = r'[ ]?(?:\((?:(?:spent|sp)[ ]?)?(-?[0-9]*(?:\.[0-9]+)?)\))?' ticket_reference = ticket_prefix + '[0-9]+'+time_pattern support_cmds_pattern = '|'.join(_supported_cmds.keys()) ticket_command = (r'(?P(?:%s))[ ]*' '(?P%s(?:(?:[, &]*|[ ]?and[ ]?)%s)*)' % (support_cmds_pattern,ticket_reference, ticket_reference)) command_re = re.compile(ticket_command) ticket_re = re.compile(ticket_prefix + '([0-9]+)') command_re = re.compile(ticket_command) ticket_re = re.compile(ticket_prefix + '([0-9]+)'+time_pattern) class CommitHook: def init_env(self, project): self.env = open_environment(project) def __init__(self, project): self.init_env(project) self.repos = self.env.get_repository() self.repos.sync() def update(self, author, rev, url=None): # Instead of bothering with the encoding, we'll use unicode data # as provided by the Trac versioncontrol API (#1310). try: chgset = self.repos.get_changeset(rev) except NoSuchChangeset: return # out of scope changesets are not cached self.author = chgset.author self.rev = rev self.msg = "(In [%s]) %s" % (rev, chgset.message) self.now = datetime.now(utc) cmd_groups = command_re.findall(self.msg) tickets = {} for cmd, tkts, xxx1, xxx2 in cmd_groups: funcname = _supported_cmds.get(cmd.lower(), '') if funcname: for tkt_id, spent in ticket_re.findall(tkts): func = getattr(self, funcname) lst = tickets.setdefault(tkt_id, []) lst.append([func, spent]) for tkt_id, vals in tickets.iteritems(): spent_total = 0.0 try: db = self.env.get_db_cnx() ticket = Ticket(self.env, int(tkt_id), db) for (cmd, spent) in vals: cmd(ticket) if spent: spent_total += float(spent) # determine sequence number... cnum = 0 tm = TicketModule(self.env) for change in tm.grouped_changelog_entries(ticket, db): if change['permanent']: cnum += 1 if spent_total: self._setTimeTrackerFields(ticket, spent_total) ticket.save_changes(self.author, self.msg, self.now, db, cnum+1) db.commit() tn = TicketNotifyEmail(self.env) tn.notify(ticket, newticket=0, modtime=self.now) except Exception, e: # import traceback # traceback.print_exc(file=sys.stderr) print>>sys.stderr, 'Unexpected error while processing ticket ' \ 'ID %s: %s' % (tkt_id, e) continue def _cmdClose(self, ticket): ticket['status'] = 'closed' ticket['resolution'] = 'fixed' def _cmdRefs(self, ticket): pass def _setTimeTrackerFields(self, ticket, spent): if (spent != ''): spentTime = float(spent) if (ticket.values.has_key('hours')): ticket['hours'] = str(spentTime) def hook(ui, repo, hooktype, node=None, **kwargs): """ Mercurial trac commit hook. """ if node is None: raise util.Abort(_('hook type %s does not pass a changeset id') % hooktype) project = ui.config('trac-hook', 'root', None) url = ui.config('trac-hook', 'url', None) if project is None: raise util.Abort(_('you need to configure the trac-hook in your hgrc - root missing')) ctx = repo.changectx(node) rev = ctx.rev() until = repo.changelog.count() trac_hook = CommitHook(project) for r in set(range(rev, until)): r = short(repo.lookup(r)) c = repo.changectx(r) trac_hook.update(c.user(), r, url)