hgext/hgcia.py
branchstable
changeset 29605 519bb4f9d3a4
parent 29460 a7d1532b26a1
parent 29604 db0095c83344
child 29606 59a0cbd71921
equal deleted inserted replaced
29460:a7d1532b26a1 29605:519bb4f9d3a4
     1 # Copyright (C) 2007-8 Brendan Cully <brendan@kublai.com>
       
     2 #
       
     3 # This software may be used and distributed according to the terms of the
       
     4 # GNU General Public License version 2 or any later version.
       
     5 
       
     6 """hooks for integrating with the CIA.vc notification service
       
     7 
       
     8 This is meant to be run as a changegroup or incoming hook. To
       
     9 configure it, set the following options in your hgrc::
       
    10 
       
    11   [cia]
       
    12   # your registered CIA user name
       
    13   user = foo
       
    14   # the name of the project in CIA
       
    15   project = foo
       
    16   # the module (subproject) (optional)
       
    17   #module = foo
       
    18   # Append a diffstat to the log message (optional)
       
    19   #diffstat = False
       
    20   # Template to use for log messages (optional)
       
    21   #template = {desc}\\n{baseurl}{webroot}/rev/{node}-- {diffstat}
       
    22   # Style to use (optional)
       
    23   #style = foo
       
    24   # The URL of the CIA notification service (optional)
       
    25   # You can use mailto: URLs to send by email, e.g.
       
    26   # mailto:cia@cia.vc
       
    27   # Make sure to set email.from if you do this.
       
    28   #url = http://cia.vc/
       
    29   # print message instead of sending it (optional)
       
    30   #test = False
       
    31   # number of slashes to strip for url paths
       
    32   #strip = 0
       
    33 
       
    34   [hooks]
       
    35   # one of these:
       
    36   changegroup.cia = python:hgcia.hook
       
    37   #incoming.cia = python:hgcia.hook
       
    38 
       
    39   [web]
       
    40   # If you want hyperlinks (optional)
       
    41   baseurl = http://server/path/to/repo
       
    42 """
       
    43 
       
    44 from mercurial.i18n import _
       
    45 from mercurial.node import bin, short
       
    46 from mercurial import cmdutil, patch, util, mail, error
       
    47 import email.Parser
       
    48 
       
    49 import socket, xmlrpclib
       
    50 from xml.sax import saxutils
       
    51 # Note for extension authors: ONLY specify testedwith = 'internal' for
       
    52 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
       
    53 # be specifying the version(s) of Mercurial they are tested with, or
       
    54 # leave the attribute unspecified.
       
    55 testedwith = 'internal'
       
    56 
       
    57 socket_timeout = 30 # seconds
       
    58 if util.safehasattr(socket, 'setdefaulttimeout'):
       
    59     # set a timeout for the socket so you don't have to wait so looooong
       
    60     # when cia.vc is having problems. requires python >= 2.3:
       
    61     socket.setdefaulttimeout(socket_timeout)
       
    62 
       
    63 HGCIA_VERSION = '0.1'
       
    64 HGCIA_URL = 'http://hg.kublai.com/mercurial/hgcia'
       
    65 
       
    66 
       
    67 class ciamsg(object):
       
    68     """ A CIA message """
       
    69     def __init__(self, cia, ctx):
       
    70         self.cia = cia
       
    71         self.ctx = ctx
       
    72         self.url = self.cia.url
       
    73         if self.url:
       
    74             self.url += self.cia.root
       
    75 
       
    76     def fileelem(self, path, uri, action):
       
    77         if uri:
       
    78             uri = ' uri=%s' % saxutils.quoteattr(uri)
       
    79         return '<file%s action=%s>%s</file>' % (
       
    80             uri, saxutils.quoteattr(action), saxutils.escape(path))
       
    81 
       
    82     def fileelems(self):
       
    83         n = self.ctx.node()
       
    84         f = self.cia.repo.status(self.ctx.p1().node(), n)
       
    85         url = self.url or ''
       
    86         if url and url[-1] == '/':
       
    87             url = url[:-1]
       
    88         elems = []
       
    89         for path in f.modified:
       
    90             uri = '%s/diff/%s/%s' % (url, short(n), path)
       
    91             elems.append(self.fileelem(path, url and uri, 'modify'))
       
    92         for path in f.added:
       
    93             # TODO: copy/rename ?
       
    94             uri = '%s/file/%s/%s' % (url, short(n), path)
       
    95             elems.append(self.fileelem(path, url and uri, 'add'))
       
    96         for path in f.removed:
       
    97             elems.append(self.fileelem(path, '', 'remove'))
       
    98 
       
    99         return '\n'.join(elems)
       
   100 
       
   101     def sourceelem(self, project, module=None, branch=None):
       
   102         msg = ['<source>', '<project>%s</project>' % saxutils.escape(project)]
       
   103         if module:
       
   104             msg.append('<module>%s</module>' % saxutils.escape(module))
       
   105         if branch:
       
   106             msg.append('<branch>%s</branch>' % saxutils.escape(branch))
       
   107         msg.append('</source>')
       
   108 
       
   109         return '\n'.join(msg)
       
   110 
       
   111     def diffstat(self):
       
   112         class patchbuf(object):
       
   113             def __init__(self):
       
   114                 self.lines = []
       
   115                 # diffstat is stupid
       
   116                 self.name = 'cia'
       
   117             def write(self, data):
       
   118                 self.lines += data.splitlines(True)
       
   119             def close(self):
       
   120                 pass
       
   121 
       
   122         n = self.ctx.node()
       
   123         pbuf = patchbuf()
       
   124         cmdutil.export(self.cia.repo, [n], fp=pbuf)
       
   125         return patch.diffstat(pbuf.lines) or ''
       
   126 
       
   127     def logmsg(self):
       
   128         if self.cia.diffstat:
       
   129             diffstat = self.diffstat()
       
   130         else:
       
   131             diffstat = ''
       
   132         self.cia.ui.pushbuffer()
       
   133         self.cia.templater.show(self.ctx, changes=self.ctx.changeset(),
       
   134                                 baseurl=self.cia.ui.config('web', 'baseurl'),
       
   135                                 url=self.url, diffstat=diffstat,
       
   136                                 webroot=self.cia.root)
       
   137         return self.cia.ui.popbuffer()
       
   138 
       
   139     def xml(self):
       
   140         n = short(self.ctx.node())
       
   141         src = self.sourceelem(self.cia.project, module=self.cia.module,
       
   142                               branch=self.ctx.branch())
       
   143         # unix timestamp
       
   144         dt = self.ctx.date()
       
   145         timestamp = dt[0]
       
   146 
       
   147         author = saxutils.escape(self.ctx.user())
       
   148         rev = '%d:%s' % (self.ctx.rev(), n)
       
   149         log = saxutils.escape(self.logmsg())
       
   150 
       
   151         url = self.url
       
   152         if url and url[-1] == '/':
       
   153             url = url[:-1]
       
   154         url = url and '<url>%s/rev/%s</url>' % (saxutils.escape(url), n) or ''
       
   155 
       
   156         msg = """
       
   157 <message>
       
   158   <generator>
       
   159     <name>Mercurial (hgcia)</name>
       
   160     <version>%s</version>
       
   161     <url>%s</url>
       
   162     <user>%s</user>
       
   163   </generator>
       
   164   %s
       
   165   <body>
       
   166     <commit>
       
   167       <author>%s</author>
       
   168       <version>%s</version>
       
   169       <log>%s</log>
       
   170       %s
       
   171       <files>%s</files>
       
   172     </commit>
       
   173   </body>
       
   174   <timestamp>%d</timestamp>
       
   175 </message>
       
   176 """ % \
       
   177             (HGCIA_VERSION, saxutils.escape(HGCIA_URL),
       
   178             saxutils.escape(self.cia.user), src, author, rev, log, url,
       
   179             self.fileelems(), timestamp)
       
   180 
       
   181         return msg
       
   182 
       
   183 
       
   184 class hgcia(object):
       
   185     """ CIA notification class """
       
   186 
       
   187     deftemplate = '{desc}'
       
   188     dstemplate = '{desc}\n-- \n{diffstat}'
       
   189 
       
   190     def __init__(self, ui, repo):
       
   191         self.ui = ui
       
   192         self.repo = repo
       
   193 
       
   194         self.ciaurl = self.ui.config('cia', 'url', 'http://cia.vc')
       
   195         self.user = self.ui.config('cia', 'user')
       
   196         self.project = self.ui.config('cia', 'project')
       
   197         self.module = self.ui.config('cia', 'module')
       
   198         self.diffstat = self.ui.configbool('cia', 'diffstat')
       
   199         self.emailfrom = self.ui.config('email', 'from')
       
   200         self.dryrun = self.ui.configbool('cia', 'test')
       
   201         self.url = self.ui.config('web', 'baseurl')
       
   202         # Default to -1 for backward compatibility
       
   203         self.stripcount = int(self.ui.config('cia', 'strip', -1))
       
   204         self.root = self.strip(self.repo.root)
       
   205 
       
   206         style = self.ui.config('cia', 'style')
       
   207         template = self.ui.config('cia', 'template')
       
   208         if not template:
       
   209             if self.diffstat:
       
   210                 template = self.dstemplate
       
   211             else:
       
   212                 template = self.deftemplate
       
   213         t = cmdutil.changeset_templater(self.ui, self.repo, False, None,
       
   214                                         template, style, False)
       
   215         self.templater = t
       
   216 
       
   217     def strip(self, path):
       
   218         '''strip leading slashes from local path, turn into web-safe path.'''
       
   219 
       
   220         path = util.pconvert(path)
       
   221         count = self.stripcount
       
   222         if count < 0:
       
   223             return ''
       
   224         while count > 0:
       
   225             c = path.find('/')
       
   226             if c == -1:
       
   227                 break
       
   228             path = path[c + 1:]
       
   229             count -= 1
       
   230         return path
       
   231 
       
   232     def sendrpc(self, msg):
       
   233         srv = xmlrpclib.Server(self.ciaurl)
       
   234         res = srv.hub.deliver(msg)
       
   235         if res is not True and res != 'queued.':
       
   236             raise error.Abort(_('%s returned an error: %s') %
       
   237                              (self.ciaurl, res))
       
   238 
       
   239     def sendemail(self, address, data):
       
   240         p = email.Parser.Parser()
       
   241         msg = p.parsestr(data)
       
   242         msg['Date'] = util.datestr(format="%a, %d %b %Y %H:%M:%S %1%2")
       
   243         msg['To'] = address
       
   244         msg['From'] = self.emailfrom
       
   245         msg['Subject'] = 'DeliverXML'
       
   246         msg['Content-type'] = 'text/xml'
       
   247         msgtext = msg.as_string()
       
   248 
       
   249         self.ui.status(_('hgcia: sending update to %s\n') % address)
       
   250         mail.sendmail(self.ui, util.email(self.emailfrom),
       
   251                       [address], msgtext)
       
   252 
       
   253 
       
   254 def hook(ui, repo, hooktype, node=None, url=None, **kwargs):
       
   255     """ send CIA notification """
       
   256     def sendmsg(cia, ctx):
       
   257         msg = ciamsg(cia, ctx).xml()
       
   258         if cia.dryrun:
       
   259             ui.write(msg)
       
   260         elif cia.ciaurl.startswith('mailto:'):
       
   261             if not cia.emailfrom:
       
   262                 raise error.Abort(_('email.from must be defined when '
       
   263                                    'sending by email'))
       
   264             cia.sendemail(cia.ciaurl[7:], msg)
       
   265         else:
       
   266             cia.sendrpc(msg)
       
   267 
       
   268     n = bin(node)
       
   269     cia = hgcia(ui, repo)
       
   270     if not cia.user:
       
   271         ui.debug('cia: no user specified')
       
   272         return
       
   273     if not cia.project:
       
   274         ui.debug('cia: no project specified')
       
   275         return
       
   276     if hooktype == 'changegroup':
       
   277         start = repo.changelog.rev(n)
       
   278         end = len(repo.changelog)
       
   279         for rev in xrange(start, end):
       
   280             n = repo.changelog.node(rev)
       
   281             ctx = repo.changectx(n)
       
   282             sendmsg(cia, ctx)
       
   283     else:
       
   284         ctx = repo.changectx(n)
       
   285         sendmsg(cia, ctx)