Changeset 2805

Show
Ignore:
Timestamp:
11/20/07 15:26:09 (1 year ago)
Author:
bobbysmith007
Message:

updated the trac-post-commit.py to be more like the official trac version of this.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • timingandestimationplugin/branches/trac0.10/scripts/trac-post-commit.py

    r2254 r2805  
     1#!/usr/bin/env python 
    12#!/usr/bin/env python 
    23 
     
    3334# REPOS="$1" 
    3435# 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 # TRAC_URL='http://trac.mysite.com/project/' 
    3936# 
    4037# /usr/bin/python /usr/local/src/trac/contrib/trac-post-commit-hook \ 
    41 #  -p "$TRAC_ENV"  \ 
    42 #  -r "$REV"       \ 
    43 #  -u "$AUTHOR"    \ 
    44 #  -m "$LOG"       \ 
    45 #  -s "$TRAC_URL" 
     38#  -p "$TRAC_ENV" -r "$REV" 
     39
     40# (all the other arguments are now deprecated and not needed anymore) 
    4641# 
    4742# It searches commit messages for text in the form of: 
     
    5146#   command #1 and #2 
    5247# 
     48# Instead of the short-hand syntax "#1", "ticket:1" can be used as well, e.g.: 
     49#   command ticket:1 
     50#   command ticket:1, ticket:2 
     51#   command ticket:1 & ticket:2  
     52#   command ticket:1 and ticket:2 
     53# 
     54# In addition, the ':' character can be omitted and issue or bug can be used 
     55# instead of ticket. 
     56# 
    5357# You can have more then one command in a message. The following commands 
    5458# are supported. There is more then one spelling for each command, to make 
    5559# this as user-friendly as possible. 
    5660# 
    57 #   closes, fixes 
     61#   close, closed, closes, fix, fixed, fixes 
    5862#     The specified issue numbers are closed with the contents of this 
    5963#     commit message being added to it.  
    60 #   references, refs, addresses, re  
     64#   references, refs, addresses, re, see  
    6165#     The specified issue numbers are left in their current status, but  
    6266#     the contents of this commit message are added to their notes.  
     
    6872# 
    6973# This will close #10 and #12, and add a note to #12. 
    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 # 
    8874 
    8975import re 
    9076import os 
    9177import sys 
    92 import time  
     78import time 
     79 
    9380 
    9481from trac.env import open_environment 
     
    9784from trac.ticket.web_ui import TicketModule 
    9885# TODO: move grouped_changelog_entries to model.py 
    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.' 
     86from trac.versioncontrol.api import NoSuchChangeset 
     87 
     88logfile = "/var/trac/commithook.log" 
     89LOG = True 
     90 
     91if 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() 
     100else
     101    def log (s, *params)
     102        pass 
     103 
     104from optparse import OptionParser 
    118105 
    119106parser = 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.') 
     107depr = '(not used anymore)' 
     108parser.add_option('-e', '--require-envelope', dest='envelope', default='', 
     109                  help=""" 
     110Require commands to be enclosed in an envelope. 
     111If -e[], then commands must be in the form of [closes #4]. 
     112Must be two characters.""") 
    124113parser.add_option('-p', '--project', dest='project', 
    125114                  help='Path to the Trac project.') 
     
    127116                  help='Repository revision number.') 
    128117parser.add_option('-u', '--user', dest='user', 
    129                   help='The user who is responsible for this action'
     118                  help='The user who is responsible for this action '+depr
    130119parser.add_option('-m', '--msg', dest='msg', 
    131                   help='The log message to search.'
     120                  help='The log message to search '+depr
    132121parser.add_option('-c', '--encoding', dest='encoding', 
    133                   help='The encoding used by the log message.'
     122                  help='The encoding used by the log message '+depr
    134123parser.add_option('-s', '--siteurl', dest='url', 
    135                   help='The base URL to the project\'s trac website (to which ' 
    136                        '/ticket/## is appended).  If this is not specified, ' 
    137                        'the project URL from trac.ini will be used.') 
     124                  help=depr+' the base_url from trac.ini will always be used.') 
    138125 
    139126(options, args) = parser.parse_args(sys.argv[1:]) 
    140127 
    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 
     140ticket_prefix = '(?:#|(?:ticket|issue|bug)[: ]?)' 
     141time_pattern = r'[ ]?(?:\((?:(?:spent|sp)[ ]?)?(-?[0-9]*(?:\.[0-9]+)?)\))?' 
     142ticket_reference = ticket_prefix + '[0-9]+'+time_pattern 
     143support_cmds_pattern = '|'.join(_supported_cmds.keys()) 
     144ticket_command =  (r'(?P<action>(?:%s))[ ]*' 
     145                   '(?P<ticket>%s(?:(?:[, &]*|[ ]?and[ ]?)%s)*)' % 
     146                   (support_cmds_pattern,ticket_reference, ticket_reference)) 
     147 
     148if options.envelope: 
     149    ticket_command = r'\%s%s\%s' % (options.envelope[0], ticket_command, 
     150                                    options.envelope[1]) 
     151     
     152command_re = re.compile(ticket_command) 
     153ticket_re = re.compile(ticket_prefix + '([0-9]+)'+time_pattern) 
    153154 
    154155class CommitHook: 
    155     _supported_cmds = {'close':      '_cmdClose', 
    156                        'closed':     '_cmdClose', 
    157                        'closes':     '_cmdClose', 
    158                        'fix':        '_cmdClose', 
    159                        'fixed':      '_cmdClose', 
    160                        'fixes':      '_cmdClose', 
    161                        'addresses':  '_cmdRefs', 
    162                        're':         '_cmdRefs', 
    163                        'references': '_cmdRefs', 
    164                        'refs':       '_cmdRefs', 
    165                        'see':        '_cmdRefs'} 
     156 
     157    def init_env(self, project): 
     158        self.env = open_environment(project) 
     159         
    166160 
    167161    def __init__(self, project=options.project, author=options.user, 
    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 
    172174        self.rev = rev 
    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(), '') 
    184184            if funcname: 
    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             
    214224 
    215225    def _cmdClose(self, ticket): 
     
    221231 
    222232    def _setTimeTrackerFields(self, ticket, spent): 
    223         if (spent != ''): 
     233        log ("Setting ticket:%s spent: %s", ticket, spent) 
     234        if (spent): 
    224235            spentTime = float(spent) 
    225236            if (ticket.values.has_key('hours')): 
    226237                ticket['hours'] = str(spentTime) 
     238                 
    227239 
    228240if __name__ == "__main__": 
    229241    if len(sys.argv) < 5: 
    230242        print "For usage: %s --help" % (sys.argv[0]) 
     243        print 
     244        print "Note that the deprecated options will be removed in Trac 0.12." 
    231245    else: 
    232246        CommitHook() 
     247