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 212 base.PollingChangeSource.__init__(self, name=bonsaiURL, pollInterval=pollInterval) 213 214 self.bonsaiURL = bonsaiURL 215 self.module = module 216 self.branch = branch 217 self.tree = tree 218 self.cvsroot = cvsroot 219 self.repository = module != 'all' and module or '' 220 self.lastChange = time.time() 221 self.lastPoll = time.time()
222
223 - def describe(self):
224 str = "" 225 str += "Getting changes from the Bonsai service running at %s " \ 226 % self.bonsaiURL 227 str += "<br>Using tree: %s, branch: %s, and module: %s" % (self.tree, \ 228 self.branch, self.module) 229 return str
230
231 - def poll(self):
232 d = self._get_changes() 233 d.addCallback(self._process_changes) 234 return d
235
236 - def _make_url(self):
237 args = ["treeid=%s" % self.tree, "module=%s" % self.module, 238 "branch=%s" % self.branch, "branchtype=match", 239 "sortby=Date", "date=explicit", 240 "mindate=%d" % self.lastChange, 241 "maxdate=%d" % int(time.time()), 242 "cvsroot=%s" % self.cvsroot, "xml=1"] 243 # build the bonsai URL 244 url = self.bonsaiURL 245 url += "/cvsquery.cgi?" 246 url += "&".join(args) 247 248 return url
249
250 - def _get_changes(self):
251 url = self._make_url() 252 log.msg("Polling Bonsai tree at %s" % url) 253 254 self.lastPoll = time.time() 255 # get the page, in XML format 256 return client.getPage(url, timeout=self.pollInterval)
257 258 @defer.inlineCallbacks
259 - def _process_changes(self, query):
260 try: 261 bp = BonsaiParser(query) 262 result = bp.getData() 263 except InvalidResultError, e: 264 log.msg("Could not process Bonsai query: " + e.value) 265 return 266 except EmptyResult: 267 return 268 269 for cinode in result.nodes: 270 files = [file.filename + ' (revision '+file.revision+')' 271 for file in cinode.files] 272 self.lastChange = self.lastPoll 273 yield self.master.addChange(author = cinode.who, 274 files = files, 275 comments = cinode.log, 276 when_timestamp = epoch2datetime(cinode.date), 277 branch = self.branch)
278