3 # This is a script which delivers Change events from Darcs to the buildmaster
4 # each time a patch is pushed into a repository. Add it to the 'apply' hook
5 # on your canonical "central" repository, by putting something like the
6 # following in the _darcs/prefs/defaults file of that repository:
8 # apply posthook /PATH/TO/darcs_buildbot.py BUILDMASTER:PORT
11 # (the second command is necessary to avoid the usual "do you really want to
12 # run this hook" prompt. Note that you cannot have multiple 'apply posthook'
13 # lines: if you need this, you must create a shell script to run all your
14 # desired commands, then point the posthook at that shell script.)
16 # Note that both Buildbot and Darcs must be installed on the repository
17 # machine. You will also need the Python/XML distribution installed (the
18 # "python2.3-xml" package under debian).
20 import os
, sys
, commands
21 from buildbot
.clients
import sendchange
22 from twisted
.internet
import defer
, reactor
24 from xml
.dom
import minidom
27 return "".join([cn
.data
28 for cn
in node
.childNodes
29 if cn
.nodeType
== cn
.TEXT_NODE
])
30 def getTextFromChild(parent
, childtype
):
31 children
= parent
.getElementsByTagName(childtype
)
34 return getText(children
[0])
38 author
= p
.getAttribute("author")
39 revision
= p
.getAttribute("hash")
40 comments
= (getTextFromChild(p
, "name") + "\n" +
41 getTextFromChild(p
, "comment"))
43 summary
= p
.getElementsByTagName("summary")[0]
45 for filenode
in summary
.childNodes
:
46 if filenode
.nodeName
in ("add_file", "modify_file", "remove_file"):
47 filename
= getText(filenode
).strip()
48 files
.append(filename
)
49 elif filenode
.nodeName
== "move":
50 from_name
= filenode
.getAttribute("from")
51 to_name
= filenode
.getAttribute("to")
54 # note that these are all unicode. Because PB can't handle unicode, we
55 # encode them into ascii, which will blow up early if there's anything we
56 # can't get to the far side. When we move to something that *can* handle
57 # unicode (like newpb), remove this.
58 author
= author
.encode("ascii")
59 comments
= comments
.encode("ascii")
60 files
= [f
.encode("ascii") for f
in files
]
61 revision
= revision
.encode("ascii")
64 # note: this is more likely to be a full email address, which would
65 # make the left-hand "Changes" column kind of wide. The buildmaster
66 # should probably be improved to display an abbreviation of the
77 def getChangesFromCommand(cmd
, count
):
78 out
= commands
.getoutput(cmd
)
80 doc
= minidom
.parseString(out
)
81 except xml
.parsers
.expat
.ExpatError
, e
:
82 print "failed to parse XML"
84 print "purported XML is:"
90 c
= doc
.getElementsByTagName("changelog")[0]
92 for i
,p
in enumerate(c
.getElementsByTagName("patch")):
95 changes
.append(makeChange(p
))
98 def getSomeChanges(count
):
99 cmd
= "darcs changes --last=%d --xml-output --summary" % count
100 return getChangesFromCommand(cmd
, count
)
103 LASTCHANGEFILE
= ".darcs_buildbot-lastchange"
105 def findNewChanges():
106 if os
.path
.exists(LASTCHANGEFILE
):
107 f
= open(LASTCHANGEFILE
, "r")
108 lastchange
= f
.read()
111 return getSomeChanges(1)
114 changes
= getSomeChanges(lookback
)
115 # getSomeChanges returns newest-first, so changes[0] is the newest.
116 # we want to scan the newest first until we find the changes we sent
117 # last time, then deliver everything newer than that (and send them
119 for i
,c
in enumerate(changes
):
120 if c
['revision'] == lastchange
:
121 newchanges
= changes
[:i
]
125 raise RuntimeError("unable to find our most recent change "
126 "(%s) in the last %d changes" % (lastchange
,
128 lookback
= 2*lookback
130 def sendChanges(master
):
131 changes
= findNewChanges()
132 s
= sendchange
.Sender(master
, None)
135 reactor
.callLater(0, d
.callback
, None)
138 print "darcs_buildbot.py: weird, no changes to send"
139 elif len(changes
) == 1:
140 print "sending 1 change to buildmaster:"
142 print "sending %d changes to buildmaster:" % len(changes
)
146 print " %s" % c
['revision']
147 return s
.send(branch
, c
['revision'], c
['comments'], c
['files'],
150 d
.addCallback(_send
, c
)
152 d
.addCallbacks(s
.printSuccess
, s
.printFailure
)
157 lastchange
= changes
[-1]['revision']
158 f
= open(LASTCHANGEFILE
, "w")
162 if __name__
== '__main__':