which included commits to RCS files with non-trunk default branches. git-svn-id: svn://10.0.0.236/trunk@238587 18797224-902f-48f8-a5cc-f745e15eee43
165 lines
5.4 KiB
Python
Executable File
165 lines
5.4 KiB
Python
Executable File
#! /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, sys, commands
|
|
from buildbot.clients import sendchange
|
|
from twisted.internet import defer, reactor
|
|
import xml
|
|
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")
|
|
comments = comments.encode("ascii")
|
|
files = [f.encode("ascii") for f in files]
|
|
revision = revision.encode("ascii")
|
|
|
|
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"
|
|
elif len(changes) == 1:
|
|
print "sending 1 change to buildmaster:"
|
|
else:
|
|
print "sending %d changes to buildmaster:" % len(changes)
|
|
|
|
def _send(res, c):
|
|
branch = None
|
|
print " %s" % c['revision']
|
|
return s.send(branch, c['revision'], 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)
|