darcs_buildbot.py :  » Build » Buildbot » buildbot-0.8.0 » contrib » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Build » Buildbot 
Buildbot » buildbot 0.8.0 » contrib » darcs_buildbot.py
#! /usr/bin/python

# This is a script which delivers Change events from Darcs to the buildmaster
# each time a patch is pushed into a repository. Add it to the 'apply' hook
# on your canonical "central" repository, by putting something like the
# following in the _darcs/prefs/defaults file of that repository:
#
#  apply posthook /PATH/TO/darcs_buildbot.py BUILDMASTER:PORT
#  apply run-posthook
#
# (the second command is necessary to avoid the usual "do you really want to
# run this hook" prompt. Note that you cannot have multiple 'apply posthook'
# lines: if you need this, you must create a shell script to run all your
# desired commands, then point the posthook at that shell script.)
#
# Note that both Buildbot and Darcs must be installed on the repository
# machine. You will also need the Python/XML distribution installed (the
# "python2.3-xml" package under debian).

import os
import sys
import commands
import xml

from buildbot.clients import sendchange
from twisted.internet import defer,reactor
from xml.dom import minidom


def getText(node):
    return "".join([cn.data
                    for cn in node.childNodes
                    if cn.nodeType == cn.TEXT_NODE])


def getTextFromChild(parent, childtype):
    children = parent.getElementsByTagName(childtype)
    if not children:
        return ""
    return getText(children[0])


def makeChange(p):
    author = p.getAttribute("author")
    revision = p.getAttribute("hash")
    comments = (getTextFromChild(p, "name") + "\n" +
                getTextFromChild(p, "comment"))

    summary = p.getElementsByTagName("summary")[0]
    files = []
    for filenode in summary.childNodes:
        if filenode.nodeName in ("add_file", "modify_file", "remove_file"):
            filename = getText(filenode).strip()
            files.append(filename)
        elif filenode.nodeName == "move":
            from_name = filenode.getAttribute("from")
            to_name = filenode.getAttribute("to")
            files.append(to_name)

    # note that these are all unicode. Because PB can't handle unicode, we
    # encode them into ascii, which will blow up early if there's anything we
    # can't get to the far side. When we move to something that *can* handle
    # unicode (like newpb), remove this.
    author = author.encode("ascii", "replace")
    comments = comments.encode("ascii", "replace")
    files = [f.encode("ascii", "replace") for f in files]
    revision = revision.encode("ascii", "replace")

    change = {
        # note: this is more likely to be a full email address, which would
        # make the left-hand "Changes" column kind of wide. The buildmaster
        # should probably be improved to display an abbreviation of the
        # username.
        'username': author,
        'revision': revision,
        'comments': comments,
        'files': files,
        }
    return change


def getChangesFromCommand(cmd, count):
    out = commands.getoutput(cmd)
    try:
        doc = minidom.parseString(out)
    except xml.parsers.expat.ExpatError, e:
        print "failed to parse XML"
        print str(e)
        print "purported XML is:"
        print "--BEGIN--"
        print out
        print "--END--"
        sys.exit(1)

    c = doc.getElementsByTagName("changelog")[0]
    changes = []
    for i, p in enumerate(c.getElementsByTagName("patch")):
        if i >= count:
            break
        changes.append(makeChange(p))
    return changes


def getSomeChanges(count):
    cmd = "darcs changes --last=%d --xml-output --summary" % count
    return getChangesFromCommand(cmd, count)


LASTCHANGEFILE = ".darcs_buildbot-lastchange"


def findNewChanges():
    if os.path.exists(LASTCHANGEFILE):
        f = open(LASTCHANGEFILE, "r")
        lastchange = f.read()
        f.close()
    else:
        return getSomeChanges(1)
    lookback = 10
    while True:
        changes = getSomeChanges(lookback)
        # getSomeChanges returns newest-first, so changes[0] is the newest.
        # we want to scan the newest first until we find the changes we sent
        # last time, then deliver everything newer than that (and send them
        # oldest-first).
        for i, c in enumerate(changes):
            if c['revision'] == lastchange:
                newchanges = changes[:i]
                newchanges.reverse()
                return newchanges
        if 2*lookback > 100:
            raise RuntimeError("unable to find our most recent change "
                               "(%s) in the last %d changes" % (lastchange,
                                                                lookback))
        lookback = 2*lookback


def sendChanges(master):
    changes = findNewChanges()
    s = sendchange.Sender(master, None)

    d = defer.Deferred()
    reactor.callLater(0, d.callback, None)

    if not changes:
        print "darcs_buildbot.py: weird, no changes to send"
        return
    elif len(changes) == 1:
        print "sending 1 change to buildmaster:"
    else:
        print "sending %d changes to buildmaster:" % len(changes)

    # the Darcs Source class expects revision to be a context, not a
    # hash of a patch (which is what we have in c['revision']).  For
    # the moment, we send None for everything but the most recent, because getting
    # contexts is Hard.

    # get the context for the most recent change
    latestcontext = commands.getoutput("darcs changes --context")
    changes[-1]['context'] = latestcontext

    def _send(res, c):
        branch = None
        print " %s" % c['revision']
        return s.send(branch, c.get('context'), c['comments'], c['files'],
                      c['username'])
    for c in changes:
        d.addCallback(_send, c)

    d.addCallbacks(s.printSuccess, s.printFailure)
    d.addBoth(s.stop)
    s.run()

    if changes:
        lastchange = changes[-1]['revision']
        f = open(LASTCHANGEFILE, "w")
        f.write(lastchange)
        f.close()


if __name__ == '__main__':
    MASTER = sys.argv[1]
    sendChanges(MASTER)
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.