Package buildbot :: Package changes :: Module bonsaipoller
[frames] | no frames]

Source Code for Module buildbot.changes.bonsaipoller

  1  # This file is part of Buildbot.  Buildbot is free software: you can 
  2  # redistribute it and/or modify it under the terms of the GNU General Public 
  3  # License as published by the Free Software Foundation, version 2. 
  4  # 
  5  # This program is distributed in the hope that it will be useful, but WITHOUT 
  6  # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
  7  # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more 
  8  # details. 
  9  # 
 10  # You should have received a copy of the GNU General Public License along with 
 11  # this program; if not, write to the Free Software Foundation, Inc., 51 
 12  # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 
 13  # 
 14  # Copyright Buildbot Team Members 
 15   
 16  import time 
 17  from xml.dom import minidom 
 18   
 19  from twisted.python import log 
 20  from twisted.internet import defer 
 21  from twisted.web import client 
 22   
 23  from buildbot.changes import base 
 24  from buildbot.util import epoch2datetime 
25 26 -class InvalidResultError(Exception):
27 - def __init__(self, value="InvalidResultError"):
28 self.value = value
29 - def __str__(self):
30 return repr(self.value)
31
32 -class EmptyResult(Exception):
33 pass
34
35 -class NoMoreCiNodes(Exception):
36 pass
37
38 -class NoMoreFileNodes(Exception):
39 pass
40
41 -class BonsaiResult:
42 """I hold a list of CiNodes"""
43 - def __init__(self, nodes=[]):
44 self.nodes = nodes
45
46 - def __cmp__(self, other):
47 if len(self.nodes) != len(other.nodes): 48 return False 49 for i in range(len(self.nodes)): 50 if self.nodes[i].log != other.nodes[i].log \ 51 or self.nodes[i].who != other.nodes[i].who \ 52 or self.nodes[i].date != other.nodes[i].date \ 53 or len(self.nodes[i].files) != len(other.nodes[i].files): 54 return -1 55 56 for j in range(len(self.nodes[i].files)): 57 if self.nodes[i].files[j].revision \ 58 != other.nodes[i].files[j].revision \ 59 or self.nodes[i].files[j].filename \ 60 != other.nodes[i].files[j].filename: 61 return -1 62 63 return 0
64
65 -class CiNode:
66 """I hold information baout one <ci> node, including a list of files"""
67 - def __init__(self, log="", who="", date=0, files=[]):
68 self.log = log 69 self.who = who 70 self.date = date 71 self.files = files
72
73 -class FileNode:
74 """I hold information about one <f> node"""
75 - def __init__(self, revision="", filename=""):
76 self.revision = revision 77 self.filename = filename
78
79 -class BonsaiParser:
80 """I parse the XML result from a bonsai cvsquery.""" 81
82 - def __init__(self, data):
83 try: 84 # this is a fix for non-ascii characters 85 # because bonsai does not give us an encoding to work with 86 # it impossible to be 100% sure what to decode it as but latin1 covers 87 # the broadest base 88 data = data.decode("latin1") 89 data = data.encode("ascii", "replace") 90 self.dom = minidom.parseString(data) 91 log.msg(data) 92 except: 93 raise InvalidResultError("Malformed XML in result") 94 95 self.ciNodes = self.dom.getElementsByTagName("ci") 96 self.currentCiNode = None # filled in by _nextCiNode() 97 self.fileNodes = None # filled in by _nextCiNode() 98 self.currentFileNode = None # filled in by _nextFileNode() 99 self.bonsaiResult = self._parseData()
100
101 - def getData(self):
102 return self.bonsaiResult
103
104 - def _parseData(self):
105 """Returns data from a Bonsai cvsquery in a BonsaiResult object""" 106 nodes = [] 107 try: 108 while self._nextCiNode(): 109 files = [] 110 try: 111 while self._nextFileNode(): 112 files.append(FileNode(self._getRevision(), 113 self._getFilename())) 114 except NoMoreFileNodes: 115 pass 116 except InvalidResultError: 117 raise 118 cinode = CiNode(self._getLog(), self._getWho(), 119 self._getDate(), files) 120 # hack around bonsai xml output bug for empty check-in comments 121 if not cinode.log and nodes and \ 122 not nodes[-1].log and \ 123 cinode.who == nodes[-1].who and \ 124 cinode.date == nodes[-1].date: 125 nodes[-1].files += cinode.files 126 else: 127 nodes.append(cinode) 128 129 except NoMoreCiNodes: 130 pass 131 except (InvalidResultError, EmptyResult): 132 raise 133 134 return BonsaiResult(nodes)
135 136
137 - def _nextCiNode(self):
138 """Iterates to the next <ci> node and fills self.fileNodes with 139 child <f> nodes""" 140 try: 141 self.currentCiNode = self.ciNodes.pop(0) 142 if len(self.currentCiNode.getElementsByTagName("files")) > 1: 143 raise InvalidResultError("Multiple <files> for one <ci>") 144 145 self.fileNodes = self.currentCiNode.getElementsByTagName("f") 146 except IndexError: 147 # if there was zero <ci> nodes in the result 148 if not self.currentCiNode: 149 raise EmptyResult 150 else: 151 raise NoMoreCiNodes 152 153 return True
154
155 - def _nextFileNode(self):
156 """Iterates to the next <f> node""" 157 try: 158 self.currentFileNode = self.fileNodes.pop(0) 159 except IndexError: 160 raise NoMoreFileNodes 161 162 return True
163
164 - def _getLog(self):
165 """Returns the log of the current <ci> node""" 166 logs = self.currentCiNode.getElementsByTagName("log") 167 if len(logs) < 1: 168 raise InvalidResultError("No log present") 169 elif len(logs) > 1: 170 raise InvalidResultError("Multiple logs present") 171 172 # catch empty check-in comments 173 if logs[0].firstChild: 174 return logs[0].firstChild.data 175 return ''
176
177 - def _getWho(self):
178 """Returns the e-mail address of the commiter""" 179 # convert unicode string to regular string 180 return str(self.currentCiNode.getAttribute("who"))
181
182 - def _getDate(self):
183 """Returns the date (unix time) of the commit""" 184 # convert unicode number to regular one 185 try: 186 commitDate = int(self.currentCiNode.getAttribute("date")) 187 except ValueError: 188 raise InvalidResultError 189 190 return commitDate
191
192 - def _getFilename(self):
193 """Returns the filename of the current <f> node""" 194 try: 195 filename = self.currentFileNode.firstChild.data 196 except AttributeError: 197 raise InvalidResultError("Missing filename") 198 199 return filename
200
201 - def _getRevision(self):
202 return self.currentFileNode.getAttribute("rev")
203
204 205 -class BonsaiPoller(base.PollingChangeSource):
206 compare_attrs = ["bonsaiURL", "pollInterval", "tree", 207 "module", "branch", "cvsroot"] 208
209 - def __init__(self, bonsaiURL, module, branch, tree="default", 210 cvsroot="/cvsroot", pollInterval=30, project=''):
211 self.bonsaiURL = bonsaiURL 212 self.module = module 213 self.branch = branch 214 self.tree = tree 215 self.cvsroot = cvsroot 216 self.repository = module != 'all' and module or '' 217 self.pollInterval = pollInterval 218 self.lastChange = time.time() 219 self.lastPoll = time.time()
220
221 - def describe(self):
222 str = "" 223 str += "Getting changes from the Bonsai service running at %s " \ 224 % self.bonsaiURL 225 str += "<br>Using tree: %s, branch: %s, and module: %s" % (self.tree, \ 226 self.branch, self.module) 227 return str
228
229 - def poll(self):
230 d = self._get_changes() 231 d.addCallback(self._process_changes) 232 return d
233
234 - def _make_url(self):
235 args = ["treeid=%s" % self.tree, "module=%s" % self.module, 236 "branch=%s" % self.branch, "branchtype=match", 237 "sortby=Date", "date=explicit", 238 "mindate=%d" % self.lastChange, 239 "maxdate=%d" % int(time.time()), 240 "cvsroot=%s" % self.cvsroot, "xml=1"] 241 # build the bonsai URL 242 url = self.bonsaiURL 243 url += "/cvsquery.cgi?" 244 url += "&".join(args) 245 246 return url
247
248 - def _get_changes(self):
249 url = self._make_url() 250 log.msg("Polling Bonsai tree at %s" % url) 251 252 self.lastPoll = time.time() 253 # get the page, in XML format 254 return client.getPage(url, timeout=self.pollInterval)
255 256 @defer.deferredGenerator
257 - def _process_changes(self, query):
258 try: 259 bp = BonsaiParser(query) 260 result = bp.getData() 261 except InvalidResultError, e: 262 log.msg("Could not process Bonsai query: " + e.value) 263 return 264 except EmptyResult: 265 return 266 267 for cinode in result.nodes: 268 files = [file.filename + ' (revision '+file.revision+')' 269 for file in cinode.files] 270 self.lastChange = self.lastPoll 271 w = defer.waitForDeferred( 272 self.master.addChange(author = cinode.who, 273 files = files, 274 comments = cinode.log, 275 when_timestamp = epoch2datetime(cinode.date), 276 branch = self.branch)) 277 yield w 278 w.getResult()
279