OLD | NEW |
| (Empty) |
1 URL: http://buildbot.net/trac | |
2 GIT: git://github.com/nsylvain/buildbot.git in branch "chromium" | |
3 Sources: http://github.com/nsylvain/buildbot/tree/chromium | |
4 Version: 0.7.12 | |
5 License: GNU General Public License (GPL) Version 2 | |
6 | |
7 This is a forked copy of buildbot v0.7.12 with the modifications living | |
8 in github. To list the local modifications, use: | |
9 git log nsylvain/chromium ^v0.7.12 | |
10 | |
11 | |
12 The following change has been made to fix issues that are now fixed in | |
13 a newer version of the upstream repository. | |
14 | |
15 --- buildbot/status/web/console.py (revision 54225) | |
16 +++ buildbot/status/web/console.py (working copy) | |
17 @@ -431,11 +431,8 @@ | |
18 builds.append(devBuild) | |
19 | |
20 # Now break if we have enough builds. | |
21 - current_revision = self.getChangeForBuild( | |
22 - builder.getBuild(-1), revision) | |
23 - if self.comparator.isRevisionEarlier( | |
24 - devBuild, current_revision): | |
25 - break | |
26 + if int(got_rev) < int(revision): | |
27 + break; | |
28 | |
29 | |
30 Then made the following embelishment to allow the old behavior for | |
31 time based comparators. | |
32 | |
33 --- third_party/buildbot_7_12/buildbot/status/web/console.py (revision 68693) | |
34 +++ third_party/buildbot_7_12/buildbot/status/web/console.py (working copy) | |
35 @@ -450,8 +450,15 @@ | |
36 builds.append(devBuild) | |
37 | |
38 # Now break if we have enough builds. | |
39 - if int(got_rev) < int(revision): | |
40 - break; | |
41 + if self.comparator.getSortingKey() == "when": | |
42 + current_revision = self.getChangeForBuild( | |
43 + builder.getBuild(-1), revision) | |
44 + if self.comparator.isRevisionEarlier( | |
45 + devBuild, current_revision): | |
46 + break | |
47 + else: | |
48 + if int(got_rev) < int(revision): | |
49 + break; | |
50 | |
51 | |
52 build = build.getPreviousBuild() | |
53 | |
54 | |
55 Ignore invalid utf-8 strings in logs. | |
56 | |
57 --- third_party/buildbot_7_12/buildbot/status/web/console.py | |
58 +++ third_party/buildbot_7_12/buildbot/status/web/console.py | |
59 @@ -891,8 +891,11 @@ class ConsoleStatusResource(HtmlResource): | |
60 subs["date"] = revision.date | |
61 comment = revision.comments or "" | |
62 subs["comments"] = comment.replace('<', '<').replace('>', '>'
) | |
63 - comment_quoted = urllib.quote(subs["comments"].encode("utf-8")) | |
64 - | |
65 + try: | |
66 + comment_quoted = urllib.quote( | |
67 + subs["comments"].decode("utf-8", "ignore").encode( | |
68 + "ascii", "xmlcharrefreplace")) | |
69 + except UnicodeEncodeError: | |
70 + # TODO(maruel): Figure out what's happening. | |
71 + comment_quoted = urllib.quote(subs["comments"].encode("utf-8")) | |
72 json += ( "{'revision': '%s', 'date': '%s', 'comments': '%s'," | |
73 "'results' : " ) % (subs["revision"], subs["date"], | |
74 comment_quoted) | |
75 | |
76 | |
77 The following patch was ported back from upstream adding log names and URLs to | |
78 the JSON output. | |
79 | |
80 --- third_party/buildbot_7_12/buildbot/status/builder.py (revision 70505) | |
81 +++ third_party/buildbot_7_12/buildbot/status/builder.py (working copy) | |
82 @@ -1132,9 +1132,9 @@ | |
83 result['eta'] = self.getETA() | |
84 result['urls'] = self.getURLs() | |
85 result['step_number'] = self.step_number | |
86 - # TODO(maruel): Move that to a sub-url or just publish the log_url | |
87 - # instead. | |
88 - #result['logs'] = self.getLogs() | |
89 + result['logs'] = [[l.getName(), | |
90 + self.build.builder.status.getURLForThing(l)] | |
91 + for l in self.getLogs()] | |
92 return result | |
93 | |
94 | |
95 @@ -1565,8 +1565,8 @@ | |
96 result['slave'] = self.getSlavename() | |
97 # TODO(maruel): Add. | |
98 #result['test_results'] = self.getTestResults() | |
99 - # TODO(maruel): Include the url? It's too heavy otherwise. | |
100 - #result['logs'] = self.getLogs() | |
101 + result['logs'] = [[l.getName(), | |
102 + self.builder.status.getURLForThing(l)] for l in self.getLogs()] | |
103 result['eta'] = self.getETA() | |
104 result['steps'] = [bss.asDict() for bss in self.steps] | |
105 if self.getCurrentStep(): | |
106 | |
107 | |
108 The following patch disables rss and atom output. | |
109 | |
110 --- a/third_party/buildbot_7_12/buildbot/status/web/baseweb.py | |
111 +++ b/third_party/buildbot_7_12/buildbot/status/web/baseweb.py | |
112 @@ -594,8 +594,9 @@ class WebStatus(service.MultiService): | |
113 root.putChild(name, child_resource) | |
114 | |
115 status = self.getStatus() | |
116 - root.putChild("rss", Rss20StatusResource(status)) | |
117 - root.putChild("atom", Atom10StatusResource(status)) | |
118 + # Disabled from Chromium. | |
119 + # root.putChild("rss", Rss20StatusResource(status)) | |
120 + # root.putChild("atom", Atom10StatusResource(status)) | |
121 root.putChild("json", JsonStatusResource(status)) | |
122 | |
123 self.site.resource = root | |
124 | |
125 | |
126 Fixes c.number is None | |
127 --- a/master/buildbot/status/web/status_json.py | |
128 +++ b/master/buildbot/status/web/status_json.py | |
129 @@ -547,15 +547,13 @@ class ChangesJsonResource(JsonResource): | |
130 def __init__(self, status, changes): | |
131 JsonResource.__init__(self, status) | |
132 for c in changes: | |
133 - # TODO(maruel): Problem with multiple changes with the same number. | |
134 - # Probably try server hack specific so we could fix it on this side | |
135 - # instead. But there is still the problem with multiple pollers fro
m | |
136 - # different repo where the numbers could clash. | |
137 - number = str(c.number) | |
138 - while number in self.children: | |
139 - # TODO(maruel): Do something better? | |
140 - number = str(int(c.number)+1) | |
141 - self.putChild(number, ChangeJsonResource(status, c)) | |
142 + # c.number can be None or clash another change if the change was | |
143 + # generated inside buildbot or if using multiple pollers. | |
144 + if c.number is not None and str(c.number) not in self.children: | |
145 + self.putChild(str(c.number), ChangeJsonResource(status, c)) | |
146 + else: | |
147 + # Temporary hack since it creates information exposure. | |
148 + self.putChild(str(id(c)), ChangeJsonResource(status, c)) | |
149 | |
150 def asDict(self, request): | |
151 """Don't throw an exception when there is no child.""" | |
152 | |
153 | |
154 Add extra parameters to HttpStatusPush as a very basic authentication mechanism. | |
155 --- a/third_party/buildbot_7_12/buildbot/status/status_push.py | |
156 +++ b/third_party/buildbot_7_12/buildbot/status/status_push.py | |
157 @@ -321,7 +321,7 @@ class HttpStatusPush(StatusPush): | |
158 | |
159 def __init__(self, serverUrl, debug=None, maxMemoryItems=None, | |
160 maxDiskItems=None, chunkSize=200, maxHttpRequestSize=2**20, | |
161 - **kwargs): | |
162 + extra_post_params=None, **kwargs): | |
163 """ | |
164 @serverUrl: Base URL to be used to push events notifications. | |
165 @maxMemoryItems: Maximum number of items to keep queued in memory. | |
166 @@ -334,6 +334,7 @@ class HttpStatusPush(StatusPush): | |
167 """ | |
168 # Parameters. | |
169 self.serverUrl = serverUrl | |
170 + self.extra_post_params = extra_post_params or {} | |
171 self.debug = debug | |
172 self.chunkSize = chunkSize | |
173 self.lastPushWasSuccessful = True | |
174 @@ -371,7 +372,9 @@ class HttpStatusPush(StatusPush): | |
175 packets = json.dumps(items, indent=2, sort_keys=True) | |
176 else: | |
177 packets = json.dumps(items, separators=(',',':')) | |
178 - data = urllib.urlencode({'packets': packets}) | |
179 + params = {'packets': packets} | |
180 + params.update(self.extra_post) | |
181 + data = urllib.urlencode(params) | |
182 if (not self.maxHttpRequestSize or | |
183 len(data) < self.maxHttpRequestSize): | |
184 return (data, items) | |
185 @@ -389,6 +392,8 @@ class HttpStatusPush(StatusPush): | |
186 def pushHttp(self): | |
187 """Do the HTTP POST to the server.""" | |
188 (encoded_packets, items) = self.popChunk() | |
189 + if not self.serverUrl: | |
190 + return | |
191 | |
192 def Success(result): | |
193 """Queue up next push.""" | |
194 | |
195 | |
196 Add pendingBuilds | |
197 --- a/third_party/buildbot_7_12/buildbot/status/builder.py | |
198 +++ b/third_party/buildbot_7_12/buildbot/status/builder.py | |
199 @@ -2171,10 +2171,7 @@ class BuilderStatus(styles.Versioned): | |
200 result['cachedBuilds'] = cached_builds | |
201 result['currentBuilds'] = current_builds | |
202 result['state'] = self.getState()[0] | |
203 - # BuildRequestStatus doesn't have a number so display the SourceStamp. | |
204 - result['pendingBuilds'] = [ | |
205 - b.getSourceStamp().asDict() for b in self.getPendingBuilds() | |
206 - ] | |
207 + result['pendingBuilds'] = len(self.getPendingBuilds()) | |
208 return result | |
209 | |
210 | |
211 diff --git a/third_party/buildbot_7_12/buildbot/status/web/status_json.py b/thir
d_party/buildbot_7_12/buildbot/status/web/status_json. | |
212 index e3aaafe..6c272a6 100644 | |
213 --- a/third_party/buildbot_7_12/buildbot/status/web/status_json.py | |
214 +++ b/third_party/buildbot_7_12/buildbot/status/web/status_json.py | |
215 @@ -344,6 +344,20 @@ class HelpResource(HtmlResource): | |
216 return self.text | |
217 | |
218 | |
219 +class BuilderPendingBuildsJsonResource(JsonResource): | |
220 + help = """Describe pending builds for a builder. | |
221 +""" | |
222 + title = 'Builder' | |
223 + | |
224 + def __init__(self, status, builder_status): | |
225 + JsonResource.__init__(self, status) | |
226 + self.builder_status = builder_status | |
227 + | |
228 + def asDict(self, request): | |
229 + # buildbot.status.builder.BuilderStatus | |
230 + return [b.asDict() for b in self.builder_status.getPendingBuilds()] | |
231 + | |
232 + | |
233 class BuilderJsonResource(JsonResource): | |
234 help = """Describe a single builder. | |
235 """ | |
236 @@ -355,6 +369,9 @@ class BuilderJsonResource(JsonResource): | |
237 self.putChild('builds', BuildsJsonResource(status, builder_status)) | |
238 self.putChild('slaves', BuilderSlavesJsonResources(status, | |
239 builder_status)) | |
240 + self.putChild( | |
241 + 'pendingBuilds', | |
242 + BuilderPendingBuildsJsonResource(status, builder_status)) | |
243 | |
244 def asDict(self, request): | |
245 # buildbot.status.builder.BuilderStatus | |
246 | |
247 | |
248 Increase console customization build range: | |
249 | |
250 --- status/web/console.py (revision 75203) | |
251 +++ status/web/console.py (working copy) | |
252 @@ -971,10 +971,10 @@ | |
253 # Keep only the revisions we care about. | |
254 # By default we process the last 40 revisions. | |
255 # If a dev name is passed, we look for the changes by this person in th
e | |
256 - # last 80 revisions. | |
257 + # last 160 revisions. | |
258 numRevs = 40 | |
259 if devName: | |
260 - numRevs *= 2 | |
261 + numRevs *= 4 | |
262 numBuilds = numRevs | |
263 | |
264 | |
265 The following patches add support for ANSI to HTML conversion of logs | |
266 | |
267 Index: status/web/ansi2html.py | |
268 =================================================================== | |
269 --- status/web/ansi2html.py (revision 0) | |
270 +++ status/web/ansi2html.py (revision 0) | |
271 @@ -0,0 +1,161 @@ | |
272 +# Copyright (c) 2011 The Chromium Authors. All rights reserved. | |
273 +# Use of this source code is governed by a BSD-style license that can be | |
274 +# found in the LICENSE file. | |
275 + | |
276 +import cStringIO | |
277 +import re | |
278 + | |
279 +DEFAULTS = { | |
280 + 'color' : 'white', | |
281 + 'background-color' : 'black', | |
282 + 'font-weight' : 'normal', | |
283 + 'text-decoration' : 'none', | |
284 +} | |
285 + | |
286 + | |
287 +class Ansi2HTML: | |
288 + """Class for converting text streams with ANSI codes into html""" | |
289 + | |
290 + ANSIEscape = '[' | |
291 + | |
292 + ANSIAttributes = { | |
293 + 0 : ['color:' + DEFAULTS['color'], | |
294 + 'font-weight:' + DEFAULTS['font-weight'], | |
295 + 'text-decoration:' + DEFAULTS['text-decoration'], | |
296 + 'background-color:' + DEFAULTS['background-color']], # reset | |
297 + 1 : ['font-weight:bold'], | |
298 + 2 : ['font-weight:lighter'], | |
299 + 4 : ['text-decoration:underline'], | |
300 + 5 : ['text-decoration:blink'], | |
301 + 7 : [], # invert attribute? | |
302 + 8 : [], # invisible attribute? | |
303 + 30 : ['color:black'], | |
304 + 31 : ['color:red'], | |
305 + 32 : ['color:green'], | |
306 + 33 : ['color:yellow'], | |
307 + 34 : ['color:blue'], | |
308 + 35 : ['color:magenta'], | |
309 + 36 : ['color:cyan'], | |
310 + 37 : ['color:white'], | |
311 + 39 : ['color:' + DEFAULTS['color']], | |
312 + 40 : ['background-color:black'], | |
313 + 41 : ['background-color:red'], | |
314 + 42 : ['background-color:green'], | |
315 + 43 : ['background-color:yellow'], | |
316 + 44 : ['background-color:blue'], | |
317 + 45 : ['background-color:magenta'], | |
318 + 46 : ['background-color:cyan'], | |
319 + 47 : ['background-color:white'], | |
320 + 49 : ['background-color:' + DEFAULTS['background-color']], | |
321 + } | |
322 + | |
323 + def __init__(self): | |
324 + self.ctx = {} | |
325 + # Send a 0 code, resetting ctx to defaults. | |
326 + self.attrib('0') | |
327 + # Prepare a regexp recognizing all ANSI codes. | |
328 + code_src = '|'.join(self.ANSICodes) | |
329 + # This captures non-greedy code argument and code itself, both grouped. | |
330 + self.code_re = re.compile("(.*?)(" + code_src + ")") | |
331 + | |
332 + def noop(self, arg): | |
333 + """Noop code, for ANSI codes that have no html equivalent.""" | |
334 + return '' | |
335 + | |
336 + def attrib(self, arg): | |
337 + """Text atribute code""" | |
338 + if arg == '': | |
339 + # Apparently, empty code argument means reset (0). | |
340 + arg = '0' | |
341 + for attr in arg.split(";"): | |
342 + try: | |
343 + for change in self.ANSIAttributes[int(attr)]: | |
344 + pieces = change.split(":") | |
345 + self.ctx[pieces[0]] = pieces[1] | |
346 + except KeyError: | |
347 + # Invalid key? Hmmm. | |
348 + return 'color:red">ANSI code not found: ' + \ | |
349 + arg + '<font style="color:' + self.ctx['color'] | |
350 + return self.printStyle() | |
351 + | |
352 + ANSICodes = { | |
353 + 'H' : noop, # cursor_pos, # ESC[y,xH - Cursor position y,x | |
354 + 'A' : noop, # cursor_up, # ESC[nA - Cursor Up n lines | |
355 + 'B' : noop, # cursor_down, # ESC[nB - Cursor Down n lines | |
356 + 'C' : noop, # cursor_forward, # ESC[nC - Cursor Forward n characters | |
357 + 'D' : noop, # cursor_backward, # ESC[nD - Cursor Backward n characters | |
358 + 'f' : noop, # cursor_xy, # ESC[y;xf - Cursor pos y,x (infrequent) | |
359 + 'R' : noop, # cursor_report, # ESC[y;xR - Cursor position report y,x | |
360 + 'n' : noop, # device_status, # ESC[6n - Dev status report (cursor pos) | |
361 + 's' : noop, # save_cursor, # ESC[s - Save cursor position | |
362 + 'u' : noop, # restore_cursor, # ESC[u - Restore cursor position | |
363 + 'J' : noop, # clrscr, # ESC[2J - Erase display | |
364 + 'K' : noop, # erase2eol, # ESC[K - Erase to end of line | |
365 + 'L' : noop, # insertlines, # ESC[nL - Inserts n blank lines at cursor | |
366 + 'M' : noop, # deletelines, # ESC[nM - Deletes n lines including cursor | |
367 + '@' : noop, # insertchars, # ESC[n@ - Inserts n blank chars at cursor | |
368 + 'P' : noop, # deletechars, # ESC[nP - Deletes n chars including cursor | |
369 + 'y' : noop, # translate, # ESC[n;ny - Output char translate | |
370 + 'p' : noop, # key_reassign, #ESC["str"p - Keyboard Key Reassignment | |
371 + 'm' : attrib, # ESC[n;n;...nm - Set attributes | |
372 + } | |
373 + | |
374 + def printStyle(self, showDefaults=False): | |
375 + """Returns a text representing the style of the current context.""" | |
376 + style = '' | |
377 + for attr in DEFAULTS: | |
378 + if self.ctx[attr] != DEFAULTS[attr] or showDefaults: | |
379 + style += attr + ':' + self.ctx[attr] + ';' | |
380 + return style | |
381 + | |
382 + def printHtmlHeader(self, title): | |
383 + text = '<html><head><title>%s</title></head>' % title | |
384 + text += '<body bgcolor="%s"><pre>' % DEFAULTS['background-color'] | |
385 + return text | |
386 + | |
387 + def printHtmlFooter(self): | |
388 + return '</pre></body></html>' | |
389 + | |
390 + def printHeader(self): | |
391 + """Envelopes everything into defaults <font> tag and opens a stub.""" | |
392 + self.attrib("0") # this means reset to default | |
393 + return '<font style="%s"><font>' % self.printStyle(showDefaults=True) | |
394 + | |
395 + def printFooter(self): | |
396 + """Closes both stub and envelope font tags.""" | |
397 + return '</font></font>' | |
398 + | |
399 + def parseBlock(self, string): | |
400 + """Takes a block of text and transform into html""" | |
401 + output = cStringIO.StringIO() | |
402 + skipfirst = True | |
403 + # Splitting by ANSIEscape turns the line into following elements: | |
404 + # arg,code,text | |
405 + # First two change the context, text is carried. | |
406 + for block in string.split(self.ANSIEscape): | |
407 + if not block: | |
408 + # First block is empty -> the line starts with escape code. | |
409 + skipfirst = False | |
410 + continue | |
411 + | |
412 + if skipfirst: | |
413 + # The line doesn't start with escape code -> skip first block. | |
414 + output.write(block) | |
415 + skipfirst = False | |
416 + continue | |
417 + | |
418 + match = self.code_re.match(block) | |
419 + if not match: | |
420 + # If there's no match, it is the line end. Don't parse it. | |
421 + output.write(block) | |
422 + continue | |
423 + | |
424 + parseFunc = self.ANSICodes[match.group(2)] | |
425 + # Replace ANSI codes with </font><font> sequence | |
426 + output.write('</font><font style="') | |
427 + output.write(parseFunc(self, match.group(1))) | |
428 + output.write('">') | |
429 + # Output the text | |
430 + output.write(block.split(match.group(2),1)[1]) | |
431 + | |
432 + return output.getvalue() | |
433 | |
434 | |
435 Index: status/web/logs.py | |
436 =================================================================== | |
437 --- status/web/logs.py (revision 95541) | |
438 +++ status/web/logs.py (working copy) | |
439 @@ -9,6 +9,7 @@ | |
440 from buildbot import interfaces | |
441 from buildbot.status import builder | |
442 from buildbot.status.web.base import IHTMLLog, HtmlResource | |
443 +from buildbot.status.web.ansi2html import Ansi2HTML | |
444 | |
445 | |
446 textlog_stylesheet = """ | |
447 @@ -59,7 +60,7 @@ | |
448 # it, so we can afford to track the request in the Resource. | |
449 implements(IHTMLLog) | |
450 | |
451 - asText = False | |
452 + printAs = "html" | |
453 subscribed = False | |
454 | |
455 def __init__(self, original): | |
456 @@ -67,9 +68,13 @@ | |
457 self.original = original | |
458 | |
459 def getChild(self, path, req): | |
460 - if path == "text": | |
461 - self.asText = True | |
462 + if path == "ansi": | |
463 + self.ansiParser = Ansi2HTML() | |
464 + | |
465 + if path == "text" or path == "ansi": | |
466 + self.printAs = path | |
467 return self | |
468 + | |
469 return HtmlResource.getChild(self, path, req) | |
470 | |
471 def htmlHeader(self, request): | |
472 @@ -80,6 +85,8 @@ | |
473 data += "<body vlink=\"#800080\">\n" | |
474 texturl = request.childLink("text") | |
475 data += '<a href="%s">(view as text)</a><br />\n' % texturl | |
476 + ansiurl = request.childLink("ansi") | |
477 + data += '<a href="%s">(view as ansi)</a><br />\n' % ansiurl | |
478 data += "<pre>\n" | |
479 return data | |
480 | |
481 @@ -90,9 +97,12 @@ | |
482 if type >= len(builder.ChunkTypes) or type < 0: | |
483 # non-std channel, don't display | |
484 continue | |
485 - if self.asText: | |
486 + if self.printAs == "text": | |
487 if type != builder.HEADER: | |
488 data += entry | |
489 + elif self.printAs == "ansi": | |
490 + if type != builder.HEADER: | |
491 + data += self.ansiParser.parseBlock(entry) | |
492 else: | |
493 data += spanfmt % (builder.ChunkTypes[type], | |
494 html.escape(entry)) | |
495 @@ -104,7 +114,7 @@ | |
496 return data | |
497 | |
498 def render_HEAD(self, request): | |
499 - if self.asText: | |
500 + if self.printAs == "text": | |
501 request.setHeader("content-type", "text/plain") | |
502 else: | |
503 request.setHeader("content-type", "text/html") | |
504 @@ -116,13 +126,16 @@ | |
505 def render_GET(self, req): | |
506 self.req = req | |
507 | |
508 - if self.asText: | |
509 + if self.printAs == "text": | |
510 req.setHeader("content-type", "text/plain") | |
511 else: | |
512 req.setHeader("content-type", "text/html") | |
513 | |
514 - if not self.asText: | |
515 + if self.printAs == "html": | |
516 req.write(self.htmlHeader(req)) | |
517 + if self.printAs == "ansi": | |
518 + req.write(self.ansiParser.printHtmlHeader("Log File Contents")) | |
519 + req.write(self.ansiParser.printHeader()) | |
520 | |
521 self.original.subscribeConsumer(ChunkConsumer(req, self)) | |
522 return server.NOT_DONE_YET | |
523 @@ -131,8 +144,11 @@ | |
524 if not self.req: | |
525 return | |
526 try: | |
527 - if not self.asText: | |
528 + if self.printAs == "html": | |
529 self.req.write(self.htmlFooter()) | |
530 + if self.printAs == "ansi": | |
531 + self.req.write(self.ansiParser.printFooter()) | |
532 + self.req.write(self.ansiParser.printHtmlFooter()) | |
533 self.req.finish() | |
534 except pb.DeadReferenceError: | |
535 pass | |
536 | |
537 | |
538 | |
539 Fix chrome-bot mis-syncs. | |
540 | |
541 Index: buildbot/changes/svnpoller.py | |
542 =================================================================== | |
543 --- buildbot/changes/svnpoller.py | |
544 +++ buildbot/changes/svnpoller.py | |
545 @@ -367,6 +367,19 @@ class SVNPoller(base.ChangeSource, util.ComparableMixin): | |
546 break | |
547 new_logentries.append(el) | |
548 new_logentries.reverse() # return oldest first | |
549 + | |
550 + # If the newest commit's author is chrome-bot, skip this commit. This | |
551 + # is a guard to ensure that we don't poll on our mirror while it could | |
552 + # be mid-sync. In that case, the author data could be wrong and would | |
553 + # look like it was a commit by chrome-bot@google.com. A downside: the | |
554 + # chrome-bot account may have a legitimate commit. This should not | |
555 + # happen generally, so we're okay waiting to see it until there's a | |
556 + # later commit with a non-chrome-bot author. | |
557 + if len(new_logentries) > 0: | |
558 + if new_logentries[-1].getAttribute("author") == 'chrome-bot@google.co
m': | |
559 + new_logentries.pop(-1) | |
560 + mostRecent = int(logentries[1].getAttribute("revision")) | |
561 + | |
562 return (mostRecent, new_logentries) | |
563 | |
564 def get_new_logentries(self, logentries): | |
OLD | NEW |