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'
15 # Note that both Buildbot and Darcs must be installed on the repository
16 # machine. You will also need the Python/XML distribution installed (the
17 # "python2.3-xml" package under debian).
19 import os
, sys
, commands
20 from buildbot
.clients
import sendchange
21 from twisted
.internet
import defer
, reactor
23 from xml
.dom
import minidom
26 return "".join([cn
.data
27 for cn
in node
.childNodes
28 if cn
.nodeType
== cn
.TEXT_NODE
])
29 def getTextFromChild(parent
, childtype
):
30 children
= parent
.getElementsByTagName(childtype
)
33 return getText(children
[0])
37 author
= p
.getAttribute("author")
38 revision
= p
.getAttribute("hash")
39 comments
= (getTextFromChild(p
, "name") + "\n" +
40 getTextFromChild(p
, "comment"))
42 summary
= p
.getElementsByTagName("summary")[0]
44 for filenode
in summary
.childNodes
:
45 if filenode
.nodeName
in ("add_file", "modify_file", "remove_file"):
46 filename
= getText(filenode
).strip()
47 files
.append(filename
)
48 elif filenode
.nodeName
== "move":
49 from_name
= filenode
.getAttribute("from")
50 to_name
= filenode
.getAttribute("to")
53 # note that these are all unicode. Because PB can't handle unicode, we
54 # encode them into ascii, which will blow up early if there's anything we
55 # can't get to the far side. When we move to something that *can* handle
56 # unicode (like newpb), remove this.
57 author
= author
.encode("ascii")
58 comments
= comments
.encode("ascii")
59 files
= [f
.encode("ascii") for f
in files
]
60 revision
= revision
.encode("ascii")
63 # note: this is more likely to be a full email address, which would
64 # make the left-hand "Changes" column kind of wide. The buildmaster
65 # should probably be improved to display an abbreviation of the
77 LASTCHANGEFILE
= ".darcs_buildbot-lastchange"
79 def getSomeChanges(count
):
80 out
= commands
.getoutput("darcs changes --last=%d --xml-output --summary"
83 doc
= minidom
.parseString(out
)
84 except xml
.parsers
.expat
.ExpatError
, e
:
85 print "failed to parse XML"
87 print "purported XML is:"
93 c
= doc
.getElementsByTagName("changelog")[0]
95 for i
,p
in enumerate(c
.getElementsByTagName("patch")):
98 changes
.append(makeChange(p
))
101 def findNewChanges():
102 if os
.path
.exists(LASTCHANGEFILE
):
103 f
= open(LASTCHANGEFILE
, "r")
104 lastchange
= f
.read()
107 return getSomeChanges(1)
110 changes
= getSomeChanges(lookback
)
111 # getSomeChanges returns newest-first, so changes[0] is the newest.
112 # we want to scan the newest first until we find the changes we sent
113 # last time, then deliver everything newer than that (and send them
115 for i
,c
in enumerate(changes
):
116 if c
['revision'] == lastchange
:
117 newchanges
= changes
[:i
]
121 raise RuntimeError("unable to find our most recent change "
122 "(%s) in the last %d changes" % (lastchange
,
124 lookback
= 2*lookback
126 changes
= findNewChanges()
127 s
= sendchange
.Sender(MASTER
, None)
130 reactor
.callLater(0, d
.callback
, None)
133 print "darcs_buildbot.py: weird, no changes to send"
134 elif len(changes
) == 1:
135 print "sending 1 change to buildmaster:"
137 print "sending %d changes to buildmaster:" % len(changes
)
141 print " %s" % c
['revision']
142 return s
.send(branch
, c
['revision'], c
['comments'], c
['files'],
145 d
.addCallback(_send
, c
)
147 d
.addCallbacks(s
.printSuccess
, s
.printFailure
)
152 lastchange
= changes
[-1]['revision']
153 f
= open(LASTCHANGEFILE
, "w")