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.web import client 
 21   
 22  from buildbot.changes import base, changes 
 23   
24 -class InvalidResultError(Exception):
25 - def __init__(self, value="InvalidResultError"):
26 self.value = value
27 - def __str__(self):
28 return repr(self.value)
29
30 -class EmptyResult(Exception):
31 pass
32
33 -class NoMoreCiNodes(Exception):
34 pass
35
36 -class NoMoreFileNodes(Exception):
37 pass
38
39 -class BonsaiResult:
40 """I hold a list of CiNodes"""
41 - def __init__(self, nodes=[]):
42 self.nodes = nodes
43
44 - def __cmp__(self, other):
45 if len(self.nodes) != len(other.nodes): 46 return False 47 for i in range(len(self.nodes)): 48 if self.nodes[i].log != other.nodes[i].log \ 49 or self.nodes[i].who != other.nodes[i].who \ 50 or self.nodes[i].date != other.nodes[i].date \ 51 or len(self.nodes[i].files) != len(other.nodes[i].files): 52 return -1 53 54 for j in range(len(self.nodes[i].files)): 55 if self.nodes[i].files[j].revision \ 56 != other.nodes[i].files[j].revision \ 57 or self.nodes[i].files[j].filename \ 58 != other.nodes[i].files[j].filename: 59 return -1 60 61 return 0
62
63 -class CiNode:
64 """I hold information baout one <ci> node, including a list of files"""
65 - def __init__(self, log="", who="", date=0, files=[]):
66 self.log = log 67 self.who = who 68 self.date = date 69 self.files = files
70
71 -class FileNode:
72 """I hold information about one <f> node"""
73 - def __init__(self, revision="", filename=""):
74 self.revision = revision 75 self.filename = filename
76
77 -class BonsaiParser:
78 """I parse the XML result from a bonsai cvsquery.""" 79
80 - def __init__(self, data):
81 try: 82 # this is a fix for non-ascii characters 83 # because bonsai does not give us an encoding to work with 84 # it impossible to be 100% sure what to decode it as but latin1 covers 85 # the broadest base 86 data = data.decode("latin1") 87 data = data.encode("ascii", "replace") 88 self.dom = minidom.parseString(data) 89 log.msg(data) 90 except: 91 raise InvalidResultError("Malformed XML in result") 92 93 self.ciNodes = self.dom.getElementsByTagName("ci") 94 self.currentCiNode = None # filled in by _nextCiNode() 95 self.fileNodes = None # filled in by _nextCiNode() 96 self.currentFileNode = None # filled in by _nextFileNode() 97 self.bonsaiResult = self._parseData()
98
99 - def getData(self):
100 return self.bonsaiResult
101
102 - def _parseData(self):
103 """Returns data from a Bonsai cvsquery in a BonsaiResult object""" 104 nodes = [] 105 try: 106 while self._nextCiNode(): 107 files = [] 108 try: 109 while self._nextFileNode(): 110 files.append(FileNode(self._getRevision(), 111 self._getFilename())) 112 except NoMoreFileNodes: 113 pass 114 except InvalidResultError: 115 raise 116 cinode = CiNode(self._getLog(), self._getWho(), 117 self._getDate(), files) 118 # hack around bonsai xml output bug for empty check-in comments 119 if not cinode.log and nodes and \ 120 not nodes[-1].log and \ 121 cinode.who == nodes[-1].who and \ 122 cinode.date == nodes[-1].date: 123 nodes[-1].files += cinode.files 124 else: 125 nodes.append(cinode) 126 127 except NoMoreCiNodes: 128 pass 129 except (InvalidResultError, EmptyResult): 130 raise 131 132 return BonsaiResult(nodes)
133 134
135 - def _nextCiNode(self):
136 """Iterates to the next <ci> node and fills self.fileNodes with 137 child <f> nodes""" 138 try: 139 self.currentCiNode = self.ciNodes.pop(0) 140 if len(self.currentCiNode.getElementsByTagName("files")) > 1: 141 raise InvalidResultError("Multiple <files> for one <ci>") 142 143 self.fileNodes = self.currentCiNode.getElementsByTagName("f") 144 except IndexError: 145 # if there was zero <ci> nodes in the result 146 if not self.currentCiNode: 147 raise EmptyResult 148 else: 149 raise NoMoreCiNodes 150 151 return True
152
153 - def _nextFileNode(self):
154 """Iterates to the next <f> node""" 155 try: 156 self.currentFileNode = self.fileNodes.pop(0) 157 except IndexError: 158 raise NoMoreFileNodes 159 160 return True
161
162 - def _getLog(self):
163 """Returns the log of the current <ci> node""" 164 logs = self.currentCiNode.getElementsByTagName("log") 165 if len(logs) < 1: 166 raise InvalidResultError("No log present") 167 elif len(logs) > 1: 168 raise InvalidResultError("Multiple logs present") 169 170 # catch empty check-in comments 171 if logs[0].firstChild: 172 return logs[0].firstChild.data 173 return ''
174
175 - def _getWho(self):
176 """Returns the e-mail address of the commiter""" 177 # convert unicode string to regular string 178 return str(self.currentCiNode.getAttribute("who"))
179
180 - def _getDate(self):
181 """Returns the date (unix time) of the commit""" 182 # convert unicode number to regular one 183 try: 184 commitDate = int(self.currentCiNode.getAttribute("date")) 185 except ValueError: 186 raise InvalidResultError 187 188 return commitDate
189
190 - def _getFilename(self):
191 """Returns the filename of the current <f> node""" 192 try: 193 filename = self.currentFileNode.firstChild.data 194 except AttributeError: 195 raise InvalidResultError("Missing filename") 196 197 return filename
198
199 - def _getRevision(self):
200 return self.currentFileNode.getAttribute("rev")
201 202
203 -class BonsaiPoller(base.PollingChangeSource):
204 compare_attrs = ["bonsaiURL", "pollInterval", "tree", 205 "module", "branch", "cvsroot"] 206 207 parent = None # filled in when we're added 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 - def _process_changes(self, query):
257 try: 258 bp = BonsaiParser(query) 259 result = bp.getData() 260 except InvalidResultError, e: 261 log.msg("Could not process Bonsai query: " + e.value) 262 return 263 except EmptyResult: 264 return 265 266 for cinode in result.nodes: 267 files = [file.filename + ' (revision '+file.revision+')' 268 for file in cinode.files] 269 c = changes.Change(who = cinode.who, 270 files = files, 271 comments = cinode.log, 272 when = cinode.date, 273 branch = self.branch) 274 self.parent.addChange(c) 275 self.lastChange = self.lastPoll
276