Merge bk://oe-devel.bkbits.net/openembedded
authornslu2-linux.adm@bkbits.net <nslu2-linux.adm@bkbits.net>
Thu, 6 Jan 2005 22:16:17 +0000 (22:16 +0000)
committernslu2-linux.adm@bkbits.net <nslu2-linux.adm@bkbits.net>
Thu, 6 Jan 2005 22:16:17 +0000 (22:16 +0000)
into bkbits.net:/repos/n/nslu2-linux/openembedded

2005/01/06 16:16:16-06:00 ti.com!kergoth
CIA notification script: split the useful bits that are independent of SCM tool from the BKClient class into a base CIAClient class.

2005/01/06 16:01:36-06:00 ti.com!kergoth
BUGFIX in the CIA trigger: strip off leading and trailing whitespace from the files list we get from bk, to ensure that the unnecessary '.' file doesn't show up in the notifications.

2005/01/06 15:58:12-06:00 ti.com!kergoth
Update our CIA notification script to be able to get through http proxies using the http_proxy variable and the urllib python module.

BKrev: 41ddb8b1MZkGpHKh4hN_t_3PIKd3LA

BitKeeper/triggers/ciabot_bk.py

index e69de29..fe055b4 100644 (file)
@@ -0,0 +1,263 @@
+#!/usr/bin/env python
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# CIA bot client script for Bitkeeper repositories, written in python.
+# This generates commit messages using CIA's XML commit format, and can
+# deliver them using either XML-RPC or email.
+#
+# -- Micah Dowty <micah@navi.cx>
+#
+# This script is cleaner, more featureful, and faster than the shell
+# script version, but won't work on systems without Python or that don't
+# allow outgoing HTTP connections.
+#
+# To use the CIA bot in your Bitkeeper repository...
+#
+# 1. Customize the parameters below
+#
+# 2. This script should be called from your repository's post-commit
+#    hook with the repository and revision as arguments. For example,
+#    you could copy this script into your repository's "hooks" directory
+#    and add something like the following to the "post-commit" script,
+#    also in the repository's "hooks" directory:
+#
+#      REPOS="$1"
+#      REV="$2"
+#      $REPOS/hooks/ciabot_bk.py "$REPOS" "$REV" &
+#
+#    Or, if you have multiple project hosted, you can add each
+#    project's name to the commandline in that project's post-commit
+#    hook:
+#
+#      $REPOS/hooks/ciabot_bk.py "$REPOS" "$REV" "My Project" &
+#
+############# There are some parameters for this script that you can customize:
+
+class config:
+    # Replace this with your project's name, or always provide a
+    # project name on the commandline.
+    project = "openembedded"
+
+    # If your repository is accessable over the web, put its base URL here
+    # and 'uri' attributes will be given to all <file> elements. This means
+    # that in CIA's online message viewer, each file in the tree will link
+    # directly to the file in your repository
+    repositoryURI = None
+
+    # This can be the http:// URI of the CIA server to deliver commits over
+    # XML-RPC, or it can be an email address to deliver using SMTP. The
+    # default here should work for most people. If you need to use e-mail
+    # instead, you can replace this with "cia@cia.navi.cx"
+    server = "http://cia.navi.cx"
+
+    # The SMTP server to use, only used if the CIA server above is an
+    # email address
+    smtpServer = "localhost"
+
+    # The 'from' address to use. If you're delivering commits via email, set
+    # this to the address you would normally send email from on this host.
+    fromAddress = "cia-user@localhost"
+
+    # When nonzero, print the message to stdout instead of delivering it to CIA
+    debug = 0
+
+
+############# Normally the rest of this won't need modification
+
+import sys, os, re, urllib, xmlrpclib
+
+class UrllibTransport(xmlrpclib.Transport):
+    '''Handles an HTTP transaction to an XML-RPC server via urllib
+    (urllib includes proxy-server support)
+    jjk  07/02/99'''
+
+    def __init__(self):
+        self.verbose = 0
+
+    def request(self, host, handler, request_body, verbose=0):
+        '''issue XML-RPC request
+        jjk  07/02/99'''
+        import urllib
+        urlopener = urllib.FancyURLopener()
+        urlopener.addheaders = [('User-agent', self.user_agent)]
+        # probably should use appropriate 'join' methods instead of 'http://'+host+handler
+        f = urlopener.open('http://'+host+handler, request_body)
+        return(self.parse_response(f))
+
+class File:
+    """A file in a Bitkeeper repository. According to our current
+    configuration, this may have a module, branch, and URI in addition
+    to a path."""
+
+    def __init__(self, fullPath):
+        self.fullPath = fullPath
+        self.path = fullPath
+
+    def getURI(self, repo):
+        """Get the URI of this file, given the repository's URI. This
+        encodes the full path and joins it to the given URI."""
+        quotedPath = urllib.quote(self.fullPath)
+        if quotedPath[0] == '/':
+            quotedPath = quotedPath[1:]
+        if repo[-1] != '/':
+            repo = repo + '/'
+        return repo + quotedPath
+
+    def makeTag(self, config):
+        """Return an XML tag for this file, using the given config"""
+        attrs = {}
+
+        if config.repositoryURI is not None:
+            attrs['uri'] = self.getURI(config.repositoryURI)
+
+        attrString = ''.join([' %s="%s"' % (key, escapeToXml(value,1))
+                              for key, value in attrs.iteritems()])
+        return "<file%s>%s</file>" % (attrString, escapeToXml(self.path))
+
+
+class CIAClient:
+    """Base CIA client class"""
+    name = 'Python client for CIA'
+    version = '1.0'
+
+    def __init__(self, repository, revision, config):
+        self.repository = repository
+        self.revision = revision
+        self.config = config
+
+    def deliver(self, message):
+        if config.debug:
+            print message
+        else:
+            server = self.config.server
+            if server.startswith('http:') or server.startswith('https:'):
+                # Deliver over XML-RPC
+                proxy = os.environ.get('http_proxy')
+                if proxy:
+                    os.environ['HTTP_PROXY'] = proxy
+                    s = xmlrpclib.ServerProxy(server, UrllibTransport())
+                else:
+                    s = xmlrpclib.ServerProxy(server)
+                s.hub.deliver(message)
+            else:
+                # Deliver over email
+                import smtplib
+                smtp = smtplib.SMTP(self.config.smtpServer)
+                smtp.sendmail(self.config.fromAddress, server,
+                              "From: %s\r\nTo: %s\r\n"
+                              "Subject: DeliverXML\r\n\r\n%s" %
+                              (self.config.fromAddress, server, message))
+
+    def main(self):
+        self.collectData()
+        import socket
+        try:
+            self.deliver("<message>" +
+                         self.makeGeneratorTag() +
+                         self.makeSourceTag() +
+                         self.makeBodyTag() +
+                         "</message>")
+            return 0
+        except socket.error, e:
+            print "ERROR: socket: %s" % e
+            return 1
+
+    def makeAttrTags(self, *names):
+        """Given zero or more attribute names, generate XML elements for
+           those attributes only if they exist and are non-None.
+           """
+        s = ''
+        for name in names:
+            if hasattr(self, name):
+                v = getattr(self, name)
+                if v is not None:
+                    s += "<%s>%s</%s>" % (name, escapeToXml(str(v)), name)
+        return s
+
+    def makeGeneratorTag(self):
+        return "<generator>%s</generator>" % self.makeAttrTags(
+            'name',
+            'version',
+            )
+
+    def makeSourceTag(self):
+        self.project = self.config.project
+        return "<source>%s</source>" % self.makeAttrTags(
+            'project',
+            'module',
+            'branch',
+            )
+
+    def makeBodyTag(self):
+        return "<body><commit>%s%s</commit></body>" % (
+            self.makeAttrTags(
+            'revision',
+            'author',
+            'log',
+            'diffLines',
+            ),
+            self.makeFileTags(),
+            )
+
+    def makeFileTags(self):
+        """Return XML tags for our file list"""
+        return "<files>%s</files>" % ''.join([file.makeTag(self.config)
+                                              for file in self.files])
+
+    def collectData(self):
+        raise NotImplementedError("collectData method not implemented in the base CIA client class.")
+
+def escapeToXml(text, isAttrib=0):
+    text = text.replace("&", "&amp;")
+    text = text.replace("<", "&lt;")
+    text = text.replace(">", "&gt;")
+    if isAttrib == 1:
+        text = text.replace("'", "&apos;")
+        text = text.replace("\"", "&quot;")
+    return text
+
+class BKClient(CIAClient):
+    """A CIA client for Bitkeeper repositories."""
+    name = 'Python Bitkeeper client for CIA'
+    version = '1.0'
+
+    def __init__(self, repository, revision, config):
+        CIAClient.__init__(self, repository, revision, config)
+        os.chdir(self.repository)
+
+    def bkchanges(self, command):
+        """Run the given bkchanges command on our current repository and
+        revision, returning all output"""
+        return os.popen('bk changes %s -r"%s"' % \
+                        (command, self.revision)).read()
+
+    def collectData(self):
+        self.author = self.bkchanges('-d\':P:\'').strip()
+        self.log = self.bkchanges('-d\'$if(:C:){$each(:C:){:C: \\\\n}}\'').strip()
+        self.diffLines = len(os.popen('bk export -tpatch -r"%s"|grep -v \'^#\'' % self.revision).read().split('\n'))
+        self.files = self.collectFiles()
+        self.module = os.path.basename(os.environ.get('BKD_ROOT') or '')
+        self.branch = self.bkchanges('-d\':TAG:\'')
+
+    def collectFiles(self):
+        # Extract all the files from the output of 'bkchanges changed'
+        files = []
+        for line in self.bkchanges('-n -v -d\'$unless(:GFILE:=ChangeSet){:GFILE:}\'').strip().split('\n'):
+            files.append(File(line))
+        return files
+
+
+if __name__ == "__main__":
+    # Print a usage message when not enough parameters are provided.
+    if len(sys.argv) < 3:
+        sys.stderr.write("USAGE: %s REPOS-PATH REVISION [PROJECTNAME]\n" %
+                         sys.argv[0])
+        sys.exit(1)
+
+    # If a project name was provided, override the default project name.
+    if len(sys.argv) > 3:
+        config.project = sys.argv[3]
+
+    # Go do the real work.
+    BKClient(sys.argv[1], sys.argv[2], config).main()