| 70 | | |
|---|
| 71 | | # |
|---|
| 72 | | # Changes for the Timing and Estimation plugin |
|---|
| 73 | | # |
|---|
| 74 | | # "Blah refs #12 (1)" will add 1h to the spent time for issue #12 |
|---|
| 75 | | # "Blah refs #12 (spent 1)" will add 1h to the spent time for issue #12 |
|---|
| 76 | | # |
|---|
| 77 | | # As above it is possible to use complicated messages: |
|---|
| 78 | | # |
|---|
| 79 | | # "Changed blah and foo to do this or that. Fixes #10 (1) and #12 (2), and refs #13 (0.5)." |
|---|
| 80 | | # |
|---|
| 81 | | # This will close #10 and #12, and add a note to #13 and also add 1h spent time to #10, |
|---|
| 82 | | # add 2h spent time to #12 and add 30m spent time to #13. |
|---|
| 83 | | # |
|---|
| 84 | | # Note that: |
|---|
| 85 | | # spent, sp or simply nothing may be used for spent |
|---|
| 86 | | # ' ', ',', '&' or 'and' may be used between spent |
|---|
| 87 | | # |
|---|
| 99 | | from trac.util.text import to_unicode |
|---|
| 100 | | from trac.web.href import Href |
|---|
| 101 | | |
|---|
| 102 | | # f = open ("/tmp/commithook.log","w") |
|---|
| 103 | | # f.write("Begin Log\n") |
|---|
| 104 | | # f.close() |
|---|
| 105 | | # def log (s): |
|---|
| 106 | | # f = open ("/tmp/commithook.log","a") |
|---|
| 107 | | # f.write(s); |
|---|
| 108 | | # f.write("\n") |
|---|
| 109 | | # f.close() |
|---|
| 110 | | |
|---|
| 111 | | try: |
|---|
| 112 | | from optparse import OptionParser |
|---|
| 113 | | except ImportError: |
|---|
| 114 | | try: |
|---|
| 115 | | from optik import OptionParser |
|---|
| 116 | | except ImportError: |
|---|
| 117 | | raise ImportError, 'Requires Python 2.3 or the Optik option parsing library.' |
|---|
| | 86 | from trac.versioncontrol.api import NoSuchChangeset |
|---|
| | 87 | |
|---|
| | 88 | logfile = "/var/trac/commithook.log" |
|---|
| | 89 | LOG = True |
|---|
| | 90 | |
|---|
| | 91 | if LOG: |
|---|
| | 92 | f = open (logfile,"w") |
|---|
| | 93 | f.write("Begin Log\n") |
|---|
| | 94 | f.close() |
|---|
| | 95 | def log (s, *params): |
|---|
| | 96 | f = open (logfile,"a") |
|---|
| | 97 | f.write(s % params); |
|---|
| | 98 | f.write("\n") |
|---|
| | 99 | f.close() |
|---|
| | 100 | else: |
|---|
| | 101 | def log (s, *params): |
|---|
| | 102 | pass |
|---|
| | 103 | |
|---|
| | 104 | from optparse import OptionParser |
|---|
| 120 | | parser.add_option('-e', '--require-envelope', dest='env', default='', |
|---|
| 121 | | help='Require commands to be enclosed in an envelope. If -e[], ' |
|---|
| 122 | | 'then commands must be in the form of [closes #4]. Must ' |
|---|
| 123 | | 'be two characters.') |
|---|
| | 107 | depr = '(not used anymore)' |
|---|
| | 108 | parser.add_option('-e', '--require-envelope', dest='envelope', default='', |
|---|
| | 109 | help=""" |
|---|
| | 110 | Require commands to be enclosed in an envelope. |
|---|
| | 111 | If -e[], then commands must be in the form of [closes #4]. |
|---|
| | 112 | Must be two characters.""") |
|---|
| 141 | | if options.env: |
|---|
| 142 | | leftEnv = '\\' + options.env[0] |
|---|
| 143 | | rghtEnv = '\\' + options.env[1] |
|---|
| 144 | | else: |
|---|
| 145 | | leftEnv = '' |
|---|
| 146 | | rghtEnv = '' |
|---|
| 147 | | |
|---|
| 148 | | andPattern = '(?:[ ]*(?:[, &]|[ ]and[ ])[ ]*)' |
|---|
| 149 | | timePattern = '(?:\((?:(?:(?:spent|sp)[ ])?[0-9]*(?:\.[0-9]+)?)?(?:\))?)?' |
|---|
| 150 | | commandPattern = re.compile(leftEnv + r'(?P<action>[A-Za-z]*).?(?P<ticket>#[0-9]+[ ]*' + timePattern + '(?:' + andPattern + '#[0-9]+.?' + timePattern + '.?)*)' + rghtEnv) |
|---|
| 151 | | ticketSplitPattern = re.compile(r'(#[0-9]+[ ]*' + timePattern + ')') |
|---|
| 152 | | ticketPattern = re.compile(r'(?:#([0-9]+)[ ]*(?:\((?:(?:(?:spent|sp)[ ])?([0-9]*(?:\.[0-9]*)?))?\))?)') |
|---|
| | 128 | _supported_cmds = {'close': '_cmdClose', |
|---|
| | 129 | 'closed': '_cmdClose', |
|---|
| | 130 | 'closes': '_cmdClose', |
|---|
| | 131 | 'fix': '_cmdClose', |
|---|
| | 132 | 'fixed': '_cmdClose', |
|---|
| | 133 | 'fixes': '_cmdClose', |
|---|
| | 134 | 'addresses': '_cmdRefs', |
|---|
| | 135 | 're': '_cmdRefs', |
|---|
| | 136 | 'references': '_cmdRefs', |
|---|
| | 137 | 'refs': '_cmdRefs', |
|---|
| | 138 | 'see': '_cmdRefs'} |
|---|
| | 139 | |
|---|
| | 140 | ticket_prefix = '(?:#|(?:ticket|issue|bug)[: ]?)' |
|---|
| | 141 | time_pattern = r'[ ]?(?:\((?:(?:spent|sp)[ ]?)?(-?[0-9]*(?:\.[0-9]+)?)\))?' |
|---|
| | 142 | ticket_reference = ticket_prefix + '[0-9]+'+time_pattern |
|---|
| | 143 | support_cmds_pattern = '|'.join(_supported_cmds.keys()) |
|---|
| | 144 | ticket_command = (r'(?P<action>(?:%s))[ ]*' |
|---|
| | 145 | '(?P<ticket>%s(?:(?:[, &]*|[ ]?and[ ]?)%s)*)' % |
|---|
| | 146 | (support_cmds_pattern,ticket_reference, ticket_reference)) |
|---|
| | 147 | |
|---|
| | 148 | if options.envelope: |
|---|
| | 149 | ticket_command = r'\%s%s\%s' % (options.envelope[0], ticket_command, |
|---|
| | 150 | options.envelope[1]) |
|---|
| | 151 | |
|---|
| | 152 | command_re = re.compile(ticket_command) |
|---|
| | 153 | ticket_re = re.compile(ticket_prefix + '([0-9]+)'+time_pattern) |
|---|
| 168 | | rev=options.rev, msg=options.msg, url=options.url, |
|---|
| 169 | | encoding=options.encoding): |
|---|
| 170 | | msg = to_unicode(msg, encoding) |
|---|
| 171 | | self.author = author |
|---|
| | 162 | rev=options.rev, url=options.url): |
|---|
| | 163 | self.init_env( project ) |
|---|
| | 164 | |
|---|
| | 165 | repos = self.env.get_repository() |
|---|
| | 166 | repos.sync() |
|---|
| | 167 | # Instead of bothering with the encoding, we'll use unicode data |
|---|
| | 168 | # as provided by the Trac versioncontrol API (#1310). |
|---|
| | 169 | try: |
|---|
| | 170 | chgset = repos.get_changeset(rev) |
|---|
| | 171 | except NoSuchChangeset: |
|---|
| | 172 | return # out of scope changesets are not cached |
|---|
| | 173 | self.author = chgset.author |
|---|
| 173 | | self.msg = "(In [%s]) %s" % (rev, msg) |
|---|
| 174 | | self.now = int(time.time()) |
|---|
| 175 | | self.env = open_environment(project) |
|---|
| 176 | | if url is None: |
|---|
| 177 | | url = self.env.config.get('project', 'url') |
|---|
| 178 | | self.env.href = Href(url) |
|---|
| 179 | | self.env.abs_href = Href(url) |
|---|
| 180 | | |
|---|
| 181 | | cmdGroups = commandPattern.findall(msg) |
|---|
| 182 | | for cmd, tkts in cmdGroups: |
|---|
| 183 | | funcname = CommitHook._supported_cmds.get(cmd.lower(), '') |
|---|
| | 175 | self.msg = "(In [%s]) %s" % (rev, chgset.message) |
|---|
| | 176 | self.now = int(time.time()) |
|---|
| | 177 | |
|---|
| | 178 | cmd_groups = command_re.findall(self.msg) |
|---|
| | 179 | log ("cmd_groups:%s", cmd_groups) |
|---|
| | 180 | tickets = {} |
|---|
| | 181 | for cmd, tkts, xxx1, xxx2 in cmd_groups: |
|---|
| | 182 | log ("cmd:%s, tkts%s ", cmd, tkts) |
|---|
| | 183 | funcname = _supported_cmds.get(cmd.lower(), '') |
|---|
| 185 | | func = getattr(self, funcname) |
|---|
| 186 | | tickets = ticketSplitPattern.findall(tkts) |
|---|
| 187 | | for ticket in tickets: |
|---|
| 188 | | ticketAndTimes = ticketPattern.findall(ticket) |
|---|
| 189 | | for tkt_id, spent in ticketAndTimes: |
|---|
| 190 | | try: |
|---|
| 191 | | db = self.env.get_db_cnx() |
|---|
| 192 | | |
|---|
| 193 | | ticket = Ticket(self.env, int(tkt_id), db) |
|---|
| 194 | | func(ticket) |
|---|
| 195 | | |
|---|
| 196 | | # determine sequence number... |
|---|
| 197 | | cnum = 0 |
|---|
| 198 | | tm = TicketModule(self.env) |
|---|
| 199 | | for change in tm.grouped_changelog_entries(ticket, db): |
|---|
| 200 | | if change['permanent']: |
|---|
| 201 | | cnum += 1 |
|---|
| 202 | | |
|---|
| 203 | | self._setTimeTrackerFields(ticket, spent) |
|---|
| 204 | | ticket.save_changes(self.author, self.msg, self.now, db, cnum+1) |
|---|
| 205 | | db.commit() |
|---|
| 206 | | |
|---|
| 207 | | tn = TicketNotifyEmail(self.env) |
|---|
| 208 | | tn.notify(ticket, newticket=0, modtime=self.now) |
|---|
| 209 | | except Exception, e: |
|---|
| 210 | | # import traceback |
|---|
| 211 | | # traceback.print_exc(file=sys.stderr) |
|---|
| 212 | | print>>sys.stderr, 'Unexpected error while processing ticket ' \ |
|---|
| 213 | | 'ID %s: %s' % (tkt_id, e) |
|---|
| | 185 | for tkt_id, spent in ticket_re.findall(tkts): |
|---|
| | 186 | func = getattr(self, funcname) |
|---|
| | 187 | lst = tickets.setdefault(tkt_id, []) |
|---|
| | 188 | lst.append([func, spent]) |
|---|
| | 189 | |
|---|
| | 190 | |
|---|
| | 191 | for tkt_id, vals in tickets.iteritems(): |
|---|
| | 192 | log ("tkt_id:%s, vals%s ", tkt_id, vals) |
|---|
| | 193 | spent_total = 0.0 |
|---|
| | 194 | try: |
|---|
| | 195 | db = self.env.get_db_cnx() |
|---|
| | 196 | |
|---|
| | 197 | ticket = Ticket(self.env, int(tkt_id), db) |
|---|
| | 198 | for (cmd, spent) in vals: |
|---|
| | 199 | cmd(ticket) |
|---|
| | 200 | if spent: |
|---|
| | 201 | spent_total += float(spent) |
|---|
| | 202 | |
|---|
| | 203 | # determine sequence number... |
|---|
| | 204 | cnum = 0 |
|---|
| | 205 | tm = TicketModule(self.env) |
|---|
| | 206 | for change in tm.grouped_changelog_entries(ticket, db): |
|---|
| | 207 | if change['permanent']: |
|---|
| | 208 | cnum += 1 |
|---|
| | 209 | if spent_total: |
|---|
| | 210 | self._setTimeTrackerFields(ticket, spent_total) |
|---|
| | 211 | ticket.save_changes(self.author, self.msg, self.now, db, cnum+1) |
|---|
| | 212 | db.commit() |
|---|
| | 213 | |
|---|
| | 214 | tn = TicketNotifyEmail(self.env) |
|---|
| | 215 | tn.notify(ticket, newticket=0, modtime=self.now) |
|---|
| | 216 | except Exception, e: |
|---|
| | 217 | # import traceback |
|---|
| | 218 | # traceback.print_exc(file=sys.stderr) |
|---|
| | 219 | log('Unexpected error while processing ticket ' \ |
|---|
| | 220 | 'ID %s: %s' % (tkt_id, e)) |
|---|
| | 221 | print>>sys.stderr, 'Unexpected error while processing ticket ' \ |
|---|
| | 222 | 'ID %s: %s' % (tkt_id, e) |
|---|
| | 223 | |
|---|