Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1134)

Unified Diff: third_party/buildbot_8_4p1/README.chromium

Issue 1383143003: Remove deprecated metrics from buildbot /varz. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | third_party/buildbot_8_4p1/buildbot/status/web/status_json.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: third_party/buildbot_8_4p1/README.chromium
diff --git a/third_party/buildbot_8_4p1/README.chromium b/third_party/buildbot_8_4p1/README.chromium
index 9beb04ba651d6231c752cf63d272b40faef8065c..5af4ce9e639c78085028c54277a2b47041d42bc6 100644
--- a/third_party/buildbot_8_4p1/README.chromium
+++ b/third_party/buildbot_8_4p1/README.chromium
@@ -4,5953 +4,8 @@ License: GNU General Public License (GPL) Version 2
This is a forked copy of buildbot v0.8.4p1.
-Make hidden steps stay hidden even if not finished, add brDoStepIf
-to support buildrunner.
+The fork starts at ece89304e7e6e7f06016de19cdb623e3c7137d2e.
+To produce the current diff, run:
-diff --git a/third_party/buildbot_8_4p1/buildbot/process/buildstep.py b/third_party/buildbot_8_4p1/buildbot/process/buildstep.py
-index 4aa307d..21044ea 100644
---- a/third_party/buildbot_8_4p1/buildbot/process/buildstep.py
-+++ b/third_party/buildbot_8_4p1/buildbot/process/buildstep.py
-@@ -621,6 +621,7 @@ class BuildStep:
- 'progressMetrics',
- 'doStepIf',
- 'hideStepIf',
-+ 'brDoStepIf',
- ]
-
- name = "generic"
-@@ -633,6 +634,9 @@ class BuildStep:
- # doStepIf can be False, True, or a function that returns False or True
- doStepIf = True
- hideStepIf = False
-+ # like doStepIf, but evaluated at runtime if executing under runbuild.py
-+ # we also overload 'False' to signify this isn't a buildrunner step
-+ brDoStepIf = False
-
- def __init__(self, **kwargs):
- self.factory = (self.__class__, dict(kwargs))
-@@ -679,6 +683,8 @@ class BuildStep:
-
- def setStepStatus(self, step_status):
- self.step_status = step_status
-+ hidden = self._maybeEvaluate(self.hideStepIf, self.step_status)
-+ self.step_status.setHidden(hidden)
-
- def setupProgress(self):
- if self.useProgress:
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/build.py b/third_party/buildbot_8_4p1/buildbot/status/web/build.py
-index 02e4b4d..c634060 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/web/build.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/build.py
-@@ -95,10 +95,10 @@ class StatusResourceBuild(HtmlResource):
- for s in b.getSteps():
- step = {'name': s.getName() }
-
-- if s.isFinished():
-- if s.isHidden():
-- continue
-+ if s.isHidden():
-+ continue
-
-+ if s.isFinished():
- step['css_class'] = css_classes[s.getResults()[0]]
- (start, end) = s.getTimes()
- step['time_to_run'] = util.formatInterval(end - start)
-
-
-Apply 2577dac35b39c4ee9c419681021d7971e8f1e5d2 and
-3db80d46d3c35d71591f033e5c3d0a2fa0001c59 from buildbot upstream.
-
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/process/buildstep.py b/third_party/buildbot_8_4p1/buildbot/process/buildstep.py
-index ea42207..4aa307d 100644
---- a/third_party/buildbot_8_4p1/buildbot/process/buildstep.py
-+++ b/third_party/buildbot_8_4p1/buildbot/process/buildstep.py
-@@ -620,6 +620,7 @@ class BuildStep:
- 'alwaysRun',
- 'progressMetrics',
- 'doStepIf',
-+ 'hideStepIf',
- ]
-
- name = "generic"
-@@ -631,6 +632,7 @@ class BuildStep:
- progress = None
- # doStepIf can be False, True, or a function that returns False or True
- doStepIf = True
-+ hideStepIf = False
-
- def __init__(self, **kwargs):
- self.factory = (self.__class__, dict(kwargs))
-@@ -920,6 +922,10 @@ class BuildStep:
- if self.progress:
- self.progress.finish()
- self.step_status.stepFinished(results)
-+
-+ hidden = self._maybeEvaluate(self.hideStepIf, self.step_status)
-+ self.step_status.setHidden(hidden)
-+
- self.releaseLocks()
- self.deferred.callback(results)
-
-@@ -938,6 +944,9 @@ class BuildStep:
- self.step_status.setText([self.name, "exception"])
- self.step_status.setText2([self.name])
- self.step_status.stepFinished(EXCEPTION)
-+
-+ hidden = self._maybeEvaluate(self.hideStepIf, EXCEPTION, self)
-+ self.step_status.setHidden(hidden)
- except:
- log.msg("exception during failure processing")
- log.err()
-@@ -1048,6 +1057,11 @@ class BuildStep:
- d = c.run(self, self.remote)
- return d
-
-+ @staticmethod
-+ def _maybeEvaluate(value, *args, **kwargs):
-+ if callable(value):
-+ value = value(*args, **kwargs)
-+ return value
-
- class OutputProgressObserver(LogObserver):
- length = 0
-@@ -1294,4 +1308,3 @@ def regex_log_evaluator(cmd, step_status, regexes):
- from buildbot.process.properties import WithProperties
- _hush_pyflakes = [WithProperties]
- del _hush_pyflakes
--
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/buildstep.py b/third_party/buildbot_8_4p1/buildbot/status/buildstep.py
-index 781f7e8..264b599 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/buildstep.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/buildstep.py
-@@ -46,7 +46,7 @@ class BuildStepStatus(styles.Versioned):
- # corresponding BuildStep has started.
- implements(interfaces.IBuildStepStatus, interfaces.IStatusEvent)
-
-- persistenceVersion = 3
-+ persistenceVersion = 4
- persistenceForgets = ( 'wasUpgraded', )
-
- started = None
-@@ -60,6 +60,7 @@ class BuildStepStatus(styles.Versioned):
- finishedWatchers = []
- statistics = {}
- step_number = None
-+ hidden = False
-
- def __init__(self, parent, step_number):
- assert interfaces.IBuildStatus(parent)
-@@ -67,6 +68,7 @@ class BuildStepStatus(styles.Versioned):
- self.build_number = parent.getNumber()
- self.builder = parent.getBuilder()
- self.step_number = step_number
-+ self.hidden = False
- self.logs = []
- self.urls = {}
- self.watchers = []
-@@ -120,6 +122,9 @@ class BuildStepStatus(styles.Versioned):
- def isFinished(self):
- return (self.finished is not None)
-
-+ def isHidden(self):
-+ return self.hidden
-+
- def waitUntilFinished(self):
- if self.finished:
- d = defer.succeed(self)
-@@ -214,6 +219,9 @@ class BuildStepStatus(styles.Versioned):
- def setProgress(self, stepprogress):
- self.progress = stepprogress
-
-+ def setHidden(self, hidden):
-+ self.hidden = hidden
-+
- def stepStarted(self):
- self.started = util.now()
- build = self.getBuild()
-@@ -354,6 +362,11 @@ class BuildStepStatus(styles.Versioned):
- self.step_number = 0
- self.wasUpgraded = True
-
-+ def upgradeToVersion4(self):
-+ if not hasattr(self, "hidden"):
-+ self.hidden = False
-+ self.wasUpgraded = True
-+
- def asDict(self):
- result = {}
- # Constant
-@@ -370,6 +383,7 @@ class BuildStepStatus(styles.Versioned):
- result['eta'] = self.getETA()
- result['urls'] = self.getURLs()
- result['step_number'] = self.step_number
-+ result['hidden'] = self.hidden
- result['logs'] = [[l.getName(),
- self.builder.status.getURLForThing(l)]
- for l in self.getLogs()]
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/build.py b/third_party/buildbot_8_4p1/buildbot/status/web/build.py
-index 907adc8..02e4b4d 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/web/build.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/build.py
-@@ -94,9 +94,11 @@ class StatusResourceBuild(HtmlResource):
-
- for s in b.getSteps():
- step = {'name': s.getName() }
-- cxt['steps'].append(step)
-
- if s.isFinished():
-+ if s.isHidden():
-+ continue
-+
- step['css_class'] = css_classes[s.getResults()[0]]
- (start, end) = s.getTimes()
- step['time_to_run'] = util.formatInterval(end - start)
-@@ -111,6 +113,8 @@ class StatusResourceBuild(HtmlResource):
- step['css_class'] = "not_started"
- step['time_to_run'] = ""
-
-+ cxt['steps'].append(step)
-+
- step['link'] = req.childLink("steps/%s" % urllib.quote(s.getName()))
- step['text'] = " ".join(s.getText())
- step['urls'] = map(lambda x:dict(url=x[1],logname=x[0]), s.getURLs().items())
-@@ -255,4 +259,3 @@ class BuildsResource(HtmlResource):
- return StatusResourceBuild(build_status)
-
- return HtmlResource.getChild(self, path, req)
--
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/waterfall.py b/third_party/buildbot_8_4p1/buildbot/status/web/waterfall.py
-index 2c04824..923fe0d 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/web/waterfall.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/waterfall.py
-@@ -576,13 +576,19 @@ class WaterfallStatusResource(HtmlResource):
- try:
- while True:
- e = g.next()
-- # e might be builder.BuildStepStatus,
-+ # e might be buildstep.BuildStepStatus,
- # builder.BuildStatus, builder.Event,
- # waterfall.Spacer(builder.Event), or changes.Change .
- # The showEvents=False flag means we should hide
- # builder.Event .
- if not showEvents and isinstance(e, builder.Event):
- continue
-+
-+ if isinstance(e, buildstep.BuildStepStatus):
-+ # unfinished steps are always shown
-+ if e.isFinished() and e.isHidden():
-+ continue
-+
- break
- event = interfaces.IStatusEvent(e)
- if debug:
---
-
-
-
-
-Applied f591d3369270b7897989c242fa7b827ca58b07b7 from buildbot upstream.
-
-
-Add extra parameters to HttpStatusPush as a very basic authentication mechanism.
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/status_push.py b/third_party/buildbot_8_4p1/buildbot/status/status_push.py
-index b7b3b0a..ca83fdb 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/status_push.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/status_push.py
-@@ -328,7 +328,7 @@ class HttpStatusPush(StatusPush):
-
- def __init__(self, serverUrl, debug=None, maxMemoryItems=None,
- maxDiskItems=None, chunkSize=200, maxHttpRequestSize=2**20,
-- **kwargs):
-+ extra_post_params=None, **kwargs):
- """
- @serverUrl: Base URL to be used to push events notifications.
- @maxMemoryItems: Maximum number of items to keep queued in memory.
-@@ -341,6 +341,7 @@ class HttpStatusPush(StatusPush):
- """
- # Parameters.
- self.serverUrl = serverUrl
-+ self.extra_post_params = extra_post_params or {}
- self.debug = debug
- self.chunkSize = chunkSize
- self.lastPushWasSuccessful = True
-@@ -378,7 +379,9 @@ class HttpStatusPush(StatusPush):
- packets = json.dumps(items, indent=2, sort_keys=True)
- else:
- packets = json.dumps(items, separators=(',',':'))
-- data = urllib.urlencode({'packets': packets})
-+ params = {'packets': packets}
-+ params.update(self.extra_post_params)
-+ data = urllib.urlencode(params)
- if (not self.maxHttpRequestSize or
- len(data) < self.maxHttpRequestSize):
- return (data, items)
-@@ -395,6 +398,8 @@ class HttpStatusPush(StatusPush):
-
- def pushHttp(self):
- """Do the HTTP POST to the server."""
-+ if not self.serverUrl:
-+ return
- (encoded_packets, items) = self.popChunk()
-
- def Success(result):
-
-
-
-
-Increase console customization build range.
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/console.py b/third_party/buildbot_8_4p1/buildbot/status/web/console.py
-index b00b871..e513ec0 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/web/console.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/console.py
-@@ -727,10 +727,10 @@ class ConsoleStatusResource(HtmlResource):
- # Keep only the revisions we care about.
- # By default we process the last 40 revisions.
- # If a dev name is passed, we look for the changes by this person in the
-- # last 80 revisions.
-+ # last 160 revisions.
- numRevs = int(request.args.get("revs", [40])[0])
- if devName:
-- numRevs *= 2
-+ numRevs *= 4
- numBuilds = numRevs
-
- # Get all changes we can find. This is a DB operation, so it must use
-
-
-
-Port console caching from 0.8.3p1 to 0.8.4p1.
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/console.py b/third_party/buildbot_8_4p1/buildbot/status/web/console.py
-index 59cbc0e..c95ac7f 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/web/console.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/console.py
-@@ -53,12 +53,74 @@ def getResultsClass(results, prevResults, inProgress):
- else:
- # The previous build also failed.
- return "warnings"
--
-+
- # Any other results? Like EXCEPTION?
- return "exception"
-
- class ANYBRANCH: pass # a flag value, used below
-
-+class CachedStatusBox(object):
-+ """Basic data class to remember the information for a box on the console."""
-+ def __init__(self, color, pageTitle, details, url, tag):
-+ self.color = color
-+ self.pageTitle = pageTitle
-+ self.details = details
-+ self.url = url
-+ self.tag = tag
-+
-+
-+class CacheStatus(object):
-+ """Basic cache of CachedStatusBox based on builder names and revisions.
-+
-+ Current limitation: If the revisions are not numerically increasing, the
-+ "trim" feature will not work and the cache will grow
-+ indefinitely.
-+ """
-+ def __init__(self):
-+ self.allBoxes = dict()
-+
-+ def display(self):
-+ """Display the available data in the cache. Used for debugging only."""
-+ data = ""
-+ for builder in self.allBoxes:
-+ for revision in self.allBoxes[builder]:
-+ data += "%s %s %s\n" % (builder, str(revision),
-+ self.allBoxes[builder][revision].color)
-+ return data
-+
-+ def insert(self, builderName, revision, color, pageTitle, details, url, tag):
-+ """Insert a new build into the cache."""
-+ box = CachedStatusBox(color, pageTitle, details, url, tag)
-+ if not self.allBoxes.get(builderName):
-+ self.allBoxes[builderName] = {}
-+
-+ self.allBoxes[builderName][revision] = box
-+
-+ def get(self, builderName, revision):
-+ """Retrieve a build from the cache."""
-+ if not self.allBoxes.get(builderName):
-+ return None
-+ if not self.allBoxes[builderName].get(revision):
-+ return None
-+ return self.allBoxes[builderName][revision]
-+
-+ def trim(self):
-+ """Remove old revisions from the cache. (For integer revisions only)"""
-+ try:
-+ for builder in self.allBoxes:
-+ allRevs = []
-+ for revision in self.allBoxes[builder]:
-+ allRevs.append(revision)
-+
-+ if len(allRevs) > 250:
-+ allRevs.sort(cmp=lambda x,y: cmp(int(x), int(y)))
-+ deleteCount = len(allRevs) - 250
-+ for i in range(0, deleteCount):
-+ del self.allBoxes[builder][allRevs[i]]
-+ except:
-+ pass
-+
-+
- class DevRevision:
- """Helper class that contains all the information we need for a revision."""
-
-@@ -97,6 +159,7 @@ class ConsoleStatusResource(HtmlResource):
- HtmlResource.__init__(self)
-
- self.status = None
-+ self.cache = CacheStatus()
-
- if orderByTime:
- self.comparator = TimeRevisionComparator()
-@@ -133,22 +196,22 @@ class ConsoleStatusResource(HtmlResource):
- def fetchChangesFromHistory(self, status, max_depth, max_builds, debugInfo):
- """Look at the history of the builders and try to fetch as many changes
- as possible. We need this when the main source does not contain enough
-- sourcestamps.
-+ sourcestamps.
-
- max_depth defines how many builds we will parse for a given builder.
- max_builds defines how many builds total we want to parse. This is to
- limit the amount of time we spend in this function.
--
-+
- This function is sub-optimal, but the information returned by this
- function is cached, so this function won't be called more than once.
- """
--
-+
- allChanges = list()
- build_count = 0
- for builderName in status.getBuilderNames()[:]:
- if build_count > max_builds:
- break
--
-+
- builder = status.getBuilder(builderName)
- build = self.getHeadBuild(builder)
- depth = 0
-@@ -160,7 +223,7 @@ class ConsoleStatusResource(HtmlResource):
- build = build.getPreviousBuild()
-
- debugInfo["source_fetch_len"] = len(allChanges)
-- return allChanges
-+ return allChanges
-
- @defer.deferredGenerator
- def getAllChanges(self, request, status, debugInfo):
-@@ -191,6 +254,7 @@ class ConsoleStatusResource(HtmlResource):
- prevChange = change
- allChanges = newChanges
-
-+ debugInfo["source_len"] = len(allChanges)
- yield allChanges
-
- def getBuildDetails(self, request, builderName, build):
-@@ -232,7 +296,7 @@ class ConsoleStatusResource(HtmlResource):
- build, and we go down until we find a build that was built prior to the
- last change we are interested in."""
-
-- revision = lastRevision
-+ revision = lastRevision
-
- builds = []
- build = self.getHeadBuild(builder)
-@@ -299,7 +363,7 @@ class ConsoleStatusResource(HtmlResource):
- display the console page. The key is the builder name, and the value is
- an array of build we care about. We also returns a dictionary of
- builders we care about. The key is it's category.
--
-+
- lastRevision is the last revision we want to display in the page.
- categories is a list of categories to display. It is coming from the
- HTTP GET parameters.
-@@ -364,7 +428,7 @@ class ConsoleStatusResource(HtmlResource):
-
- cs = []
-
-- for category in categories:
-+ for category in categories:
- c = {}
-
- c["name"] = category
-@@ -372,9 +436,9 @@ class ConsoleStatusResource(HtmlResource):
- # To be able to align the table correctly, we need to know
- # what percentage of space this category will be taking. This is
- # (#Builders in Category) / (#Builders Total) * 100.
-- c["size"] = (len(builderList[category]) * 100) / count
-+ c["size"] = (len(builderList[category]) * 100) / count
- cs.append(c)
--
-+
- return cs
-
- def displaySlaveLine(self, status, builderList, debugInfo):
-@@ -448,6 +512,23 @@ class ConsoleStatusResource(HtmlResource):
- introducedIn = None
- firstNotIn = None
-
-+ cached_value = self.cache.get(builder, revision.revision)
-+ if cached_value:
-+ debugInfo["from_cache"] += 1
-+
-+ b = {}
-+ b["url"] = cached_value.url
-+ b["pageTitle"] = cached_value.pageTitle
-+ b["color"] = cached_value.color
-+ b["tag"] = cached_value.tag
-+
-+ builds[category].append(b)
-+
-+ if cached_value.details and cached_value.color == "failure":
-+ details.append(cached_value.details)
-+
-+ continue
-+
- # Find the first build that does not include the revision.
- for build in allBuilds[builder]:
- if self.comparator.isRevisionEarlier(build, revision):
-@@ -504,6 +585,13 @@ class ConsoleStatusResource(HtmlResource):
- if current_details and resultsClass == "failure":
- details.append(current_details)
-
-+ # Add this box to the cache if it's completed so we don't have
-+ # to compute it again.
-+ if resultsClass not in ("running", "notstarted"):
-+ debugInfo["added_blocks"] += 1
-+ self.cache.insert(builder, revision.revision, resultsClass,
-+ pageTitle, current_details, url, tag)
-+
- return (builds, details)
-
- def filterRevisions(self, revisions, filter=None, max_revs=None):
-@@ -553,7 +641,8 @@ class ConsoleStatusResource(HtmlResource):
-
- if builderList:
- subs["categories"] = self.displayCategories(builderList, debugInfo)
-- subs['slaves'] = self.displaySlaveLine(status, builderList, debugInfo)
-+ subs['slaves'] = self.displaySlaveLine(status, builderList,
-+ debugInfo)
- else:
- subs["categories"] = []
-
-@@ -574,14 +663,14 @@ class ConsoleStatusResource(HtmlResource):
-
- # Display the status for all builders.
- (builds, details) = self.displayStatusLine(builderList,
-- allBuilds,
-- revision,
-- debugInfo)
-+ allBuilds,
-+ revision,
-+ debugInfo)
- r['builds'] = builds
- r['details'] = details
-
- # Calculate the td span for the comment and the details.
-- r["span"] = len(builderList) + 2
-+ r["span"] = len(builderList) + 2
-
- subs['revisions'].append(r)
-
-@@ -678,6 +767,13 @@ class ConsoleStatusResource(HtmlResource):
- debugInfo)
-
- debugInfo["added_blocks"] = 0
-+ debugInfo["from_cache"] = 0
-+
-+ if request.args.get("display_cache", None):
-+ data = ""
-+ data += "\nGlobal Cache\n"
-+ data += self.cache.display()
-+ return data
-
- cxt.update(self.displayPage(request, status, builderList,
- allBuilds, revisions, categories,
-@@ -686,6 +782,11 @@ class ConsoleStatusResource(HtmlResource):
- templates = request.site.buildbot_service.templates
- template = templates.get_template("console.html")
- data = template.render(cxt)
-+
-+ # Clean up the cache.
-+ if debugInfo["added_blocks"]:
-+ self.cache.trim()
-+
- return data
- d.addCallback(got_changes)
- return d
-
-
-commit ca9f4ed52b4febcebd30ad77a8e40737f3a5ad1f
-Author: Nicolas Sylvain <nsylvain@chromium.org>
-Date: Fri Nov 23 12:24:04 2012 -0500
-
- Optionally add revision to the waterfall display of changes.
-
- Used in chromium's waterfalls extensively.
- publicly reviewed here: https://codereview.chromium.org/7276032
-
- Modified to not change the look by default.
-
-diff --git a/master/buildbot/status/web/changes.py b/master/buildbot/status/web/changes.py
-index 6be37b5..0971bea 100644
---- a/master/buildbot/status/web/changes.py
-+++ b/master/buildbot/status/web/changes.py
-@@ -63,7 +63,8 @@ class ChangeBox(components.Adapter):
- template = req.site.buildbot_service.templates.get_template("change_macros.html")
- text = template.module.box_contents(url=url,
- who=self.original.getShortAuthor(),
-- pageTitle=self.original.comments)
-+ pageTitle=self.original.comments,
-+ revision=self.original.revision)
- return Box([text], class_="Change")
- components.registerAdapter(ChangeBox, Change, IBox)
-
-diff --git a/master/buildbot/status/web/templates/change_macros.html b/master/buildbot/status/web/templates/change_macros.html
-index 9b46191..dc6a9b2 100644
---- a/master/buildbot/status/web/templates/change_macros.html
-+++ b/master/buildbot/status/web/templates/change_macros.html
-@@ -71,6 +71,6 @@
- {% endif %}
- {%- endmacro %}
-
--{% macro box_contents(who, url, pageTitle) -%}
-+{% macro box_contents(who, url, pageTitle, revision) -%}
- <a href="{{ url }}" title="{{ pageTitle|e }}">{{ who|user }}</a>
- {%- endmacro %}
-
-Add revision to the chromium waterfalls.
-
-Index: buildbot/status/web/templates/change_macros.html
-===================================================================
---- buildbot/status/web/templates/change_macros.html (revision 167249)
-+++ buildbot/status/web/templates/change_macros.html (working copy)
-@@ -67,6 +67,6 @@
- {% endif %}
- {%- endmacro %}
-
- {% macro box_contents(who, url, pageTitle, revision) -%}
--<a href="{{ url }}" title="{{ pageTitle|e }}">{{ who|user }}</a>
-+<a href="{{ url }}" title="{{ pageTitle|e }}">{{ who|user }}</a><br>r{{ revision }}
- {%- endmacro %}
-
-
-
-
-commit e6b9fad4373d6e55f7957ee8312d58cf0461d98c
-Author: Chase Phillips <cmp@google.com>
-Date: Mon Jul 25 10:46:54 2011 -0700
-
- Import upstream fix: set journal mode.
-
- SQLite database fix from buildbot.net to increase
- concurrency. This is 4050c5e7a:
- https://github.com/buildbot/buildbot/commit/4050c5e7a2641df56f792b06fc1aea6c16221e8f#diff-0
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/db/enginestrategy.py b/third_party/buildbot_8_4p1/buildbot/db/enginestrategy.py
-index 7be9196..3d05cb6 100644
---- a/third_party/buildbot_8_4p1/buildbot/db/enginestrategy.py
-+++ b/third_party/buildbot_8_4p1/buildbot/db/enginestrategy.py
-@@ -25,6 +25,7 @@ special cases that Buildbot needs. Those include:
-
- import os
- import sqlalchemy
-+from twisted.python import log
- from sqlalchemy.engine import strategies, url
- from sqlalchemy.pool import NullPool
-
-@@ -83,6 +84,16 @@ class BuildbotEngineStrategy(strategies.ThreadLocalEngineStrategy):
-
- return u, kwargs, max_conns
-
-+ def set_up_sqlite_engine(self, u, engine):
-+ """Special setup for sqlite engines"""
-+ # try to enable WAL logging
-+ if u.database:
-+ log.msg("setting database journal mode to 'wal'")
-+ try:
-+ engine.execute("pragma journal_mode = wal")
-+ except:
-+ log.msg("failed to set journal mode - database may fail")
-+
- def special_case_mysql(self, u, kwargs):
- """For mysql, take max_idle out of the query arguments, and
- use its value for pool_recycle. Also, force use_unicode and
-@@ -148,9 +159,12 @@ class BuildbotEngineStrategy(strategies.ThreadLocalEngineStrategy):
- # by DBConnector to configure the surrounding thread pool
- engine.optimal_thread_pool_size = max_conns
-
-- # and keep the basedir
-+ # keep the basedir
- engine.buildbot_basedir = basedir
-
-+ if u.drivername.startswith('sqlite'):
-+ self.set_up_sqlite_engine(u, engine)
-+
- return engine
-
- BuildbotEngineStrategy()
-
-
-
-commit a2a0d76cbd2b016b628decf36ef8e298a9b1e4e8
-Author: Chase Phillips <cmp@google.com>
-Date: Thu Jul 28 16:24:09 2011 -0700
-
- Backport postgres fix from Buildbot trunk.
-
- This fixes http://trac.buildbot.net/ticket/2010.
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/db/buildrequests.py b/third_party/buildbot_8_4p1/buildbot/db/buildrequests.py
-index 650ac5d..b7d2b08 100644
---- a/third_party/buildbot_8_4p1/buildbot/db/buildrequests.py
-+++ b/third_party/buildbot_8_4p1/buildbot/db/buildrequests.py
-@@ -206,8 +206,6 @@ class BuildRequestsConnectorComponent(base.DBConnectorComponent):
- master_incarnation = self.db.master.master_incarnation
- tbl = self.db.model.buildrequests
-
-- transaction = conn.begin()
--
- # first, create a temporary table containing all of the ID's
- # we want to claim
- tmp_meta = sa.MetaData(bind=conn)
-@@ -216,6 +214,8 @@ class BuildRequestsConnectorComponent(base.DBConnectorComponent):
- prefixes=['TEMPORARY'])
- tmp.create()
-
-+ transaction = conn.begin()
-+
- try:
- q = tmp.insert()
- conn.execute(q, [ dict(brid=id) for id in brids ])
-@@ -268,8 +268,10 @@ class BuildRequestsConnectorComponent(base.DBConnectorComponent):
- raise AlreadyClaimedError
- res.close()
- finally:
-- # clean up after ourselves, even though it's a temporary table
-- tmp.drop(checkfirst=True)
-+ # clean up after ourselves, even though it's a temporary table;
-+ # note that checkfirst=True does not work here for Postgres
-+ # (#2010).
-+ tmp.drop()
-
- return self.db.pool.do(thd)
-
-
-Truncate commit comments to 1024 characters, which is the maximum size in the
-db schema (see buildbot/db/model.py:181).
-
---- buildbot/db/changes.py (revision 103214)
-+++ buildbot/db/changes.py (working copy)
-@@ -53,7 +53,7 @@
- string)
- """
-
-- def addChange(self, author=None, files=None, comments=None, is_dir=0,
-+ def addChange(self, author=None, files=None, comments='', is_dir=0,
- links=None, revision=None, when_timestamp=None, branch=None,
- category=None, revlink='', properties={}, repository='',
- project='', _reactor=reactor):
-@@ -130,7 +130,7 @@
- ins = self.db.model.changes.insert()
- r = conn.execute(ins, dict(
- author=author,
-- comments=comments,
-+ comments=comments[:1024],
- is_dir=is_dir,
- branch=branch,
- revision=revision,
-
-
-Fix inheritance bug.
-
-http://trac.buildbot.net/ticket/2120
-
-Index: buildbot/status/web/logs.py
-===================================================================
---- buildbot/status/web/logs.py (revision 103214)
-+++ buildbot/status/web/logs.py (working copy)
-@@ -65,7 +65,7 @@
- if path == "text":
- self.asText = True
- return self
-- return HtmlResource.getChild(self, path, req)
-+ return Resource.getChild(self, path, req)
-
- def content(self, entries):
- html_entries = []
-
-
-
-**** Added by cmp on 10/4/2011
-Fix updateSourceStamp=False to work as expected.
-
-Index: buildbot/steps/trigger.py
-===================================================================
---- buildbot/steps/trigger.py
-+++ buildbot/steps/trigger.py
-@@ -77,15 +77,18 @@ class Trigger(LoggingBuildStep):
-
- """
- assert schedulerNames, "You must specify a scheduler to trigger"
-- if sourceStamp and updateSourceStamp:
-+ if sourceStamp and (updateSourceStamp is not None):
- raise ValueError("You can't specify both sourceStamp and updateSourceStamp")
- if sourceStamp and alwaysUseLatest:
- raise ValueError("You can't specify both sourceStamp and alwaysUseLatest")
-- if alwaysUseLatest and updateSourceStamp:
-+ if alwaysUseLatest and (updateSourceStamp is not None):
- raise ValueError("You can't specify both alwaysUseLatest and updateSourceStamp")
- self.schedulerNames = schedulerNames
- self.sourceStamp = sourceStamp
-- self.updateSourceStamp = updateSourceStamp or not (alwaysUseLatest or sourceStamp)
-+ if updateSourceStamp is not None:
-+ self.updateSourceStamp = updateSourceStamp
-+ else:
-+ self.updateSourceStamp = not (alwaysUseLatest or sourceStamp)
- self.alwaysUseLatest = alwaysUseLatest
- self.waitForFinish = waitForFinish
- self.set_properties = set_properties
-
-
-Merge from HEAD to pick up a bug fix.
-
-index 1c7b4ab..7474c0d 100644
---- a/master/buildbot/util/lru.py
-+++ b/master/buildbot/util/lru.py
-@@ -47,6 +47,7 @@ class AsyncLRUCache(object):
- @ivar hits: cache hits so far
- @ivar refhits: cache misses found in the weak ref dictionary, so far
- @ivar misses: cache misses leading to re-fetches, so far
-+ @ivar max_size: maximum allowed size of the cache
- """
-
- __slots__ = ('max_size max_queue miss_fn '
-@@ -72,7 +73,7 @@ class AsyncLRUCache(object):
- self.weakrefs = WeakValueDictionary()
- self.concurrent = {}
- self.hits = self.misses = self.refhits = 0
-- self.refcount = defaultdict(default_factory = lambda : 0)
-+ self.refcount = defaultdict(lambda : 0)
-
- def get(self, key, **miss_fn_kwargs):
- """
-@@ -99,7 +100,7 @@ class AsyncLRUCache(object):
- # utility function to record recent use of this key
- def ref_key():
- queue.append(key)
-- refcount[key] = refcount.get(key, 0) + 1
-+ refcount[key] = refcount[key] + 1
-
- # periodically compact the queue by eliminating duplicate keys
- # while preserving order of most recent access. Note that this
-@@ -151,11 +152,12 @@ class AsyncLRUCache(object):
- cache[key] = result
- weakrefs[key] = result
-
-- self._purge()
-+ # reference the key once, possibly standing in for multiple
-+ # concurrent accesses
-+ ref_key()
-
-- # reference the key once, possibly standing in for multiple
-- # concurrent accesses
-- ref_key()
-+ self.inv()
-+ self._purge()
-
- # and fire all of the waiting Deferreds
- dlist = concurrent.pop(key)
-@@ -182,8 +184,8 @@ class AsyncLRUCache(object):
- queue = self.queue
- max_size = self.max_size
-
-- # purge least recently used entries, using refcount
-- # to count repeatedly-used entries
-+ # purge least recently used entries, using refcount to count entries
-+ # that appear multiple times in the queue
- while len(cache) > max_size:
- refc = 1
- while refc:
-@@ -216,3 +218,31 @@ class AsyncLRUCache(object):
- self.max_size = max_size
- self.max_queue = max_size * self.QUEUE_SIZE_FACTOR
- self._purge()
-+
-+ def inv(self):
-+ """Check invariants and log if they are not met; used for debugging"""
-+ global inv_failed
-+
-+ # the keys of the queue and cache should be identical
-+ cache_keys = set(self.cache.keys())
-+ queue_keys = set(self.queue)
-+ if queue_keys - cache_keys:
-+ log.msg("INV: uncached keys in queue:", queue_keys - cache_keys)
-+ inv_failed = True
-+ if cache_keys - queue_keys:
-+ log.msg("INV: unqueued keys in cache:", cache_keys - queue_keys)
-+ inv_failed = True
-+
-+ # refcount should always represent the number of times each key appears
-+ # in the queue
-+ exp_refcount = dict()
-+ for k in self.queue:
-+ exp_refcount[k] = exp_refcount.get(k, 0) + 1
-+ if exp_refcount != self.refcount:
-+ log.msg("INV: refcounts differ:")
-+ log.msg(" expected:", sorted(exp_refcount.items()))
-+ log.msg(" got:", sorted(self.refcount.items()))
-+ inv_failed = True
-+
-+# for tests
-+inv_failed = False
-
-
-
-Truncate comments to maximum length during migration.
-
-Index: db/migrate/versions/001_initial.py
-===================================================================
---- db/migrate/versions/001_initial.py (revision 104114)
-+++ db/migrate/versions/001_initial.py (working copy)
-@@ -216,7 +216,7 @@
- values = dict(
- changeid=c.number,
- author=c.who,
-- comments=c.comments,
-+ comments=c.comments[:1024],
- is_dir=c.isdir,
- branch=c.branch,
- revision=c.revision,
-
-
-Add the 'revlinktmpl' field to GitPoller, used the same way as in
-SVNPoller.
-
-Index: buildbot/changes/gitpoller.py
-===================================================================
---- buildbot/changes/gitpoller.py (revision 104853)
-+++ buildbot/changes/gitpoller.py (working copy)
-@@ -16,6 +16,7 @@
- import time
- import tempfile
- import os
-+import urllib
- from twisted.python import log
- from twisted.internet import defer, utils
-
-@@ -36,7 +37,7 @@
- gitbin='git', usetimestamps=True,
- category=None, project=None,
- pollinterval=-2, fetch_refspec=None,
-- encoding='utf-8'):
-+ encoding='utf-8', revlinktmpl=''):
- # for backward compatibility; the parameter used to be spelled with 'i'
- if pollinterval != -2:
- pollInterval = pollinterval
-@@ -57,6 +58,7 @@
- self.changeCount = 0
- self.commitInfo = {}
- self.initLock = defer.DeferredLock()
-+ self.revlinktmpl = revlinktmpl
-
- if self.workdir == None:
- self.workdir = tempfile.gettempdir() + '/gitpoller_work'
-@@ -273,6 +275,10 @@
- # just fail on the first error; they're probably all related!
- raise failures[0]
-
-+ revlink = ''
-+ if self.revlinktmpl and rev:
-+ revlink = self.revlinktmpl % urllib.quote_plus(rev)
-+
- timestamp, name, files, comments = [ r[1] for r in results ]
- d = self.master.addChange(
- author=name,
-@@ -283,7 +289,8 @@
- branch=self.branch,
- category=self.category,
- project=self.project,
-- repository=self.repourl)
-+ repository=self.repourl,
-+ revlink=revlink)
- wfd = defer.waitForDeferred(d)
- yield wfd
- results = wfd.getResult()
-
-Add limit query argument to /console
-
-
---- buildbot/status/web/console.py
-+++ buildbot/status/web/console.py
-@@ -228,9 +228,9 @@ class ConsoleStatusResource(HtmlResource):
- @defer.deferredGenerator
- def getAllChanges(self, request, status, debugInfo):
- master = request.site.buildbot_service.master
--
-+ limit = min(100, max(1, int(request.args.get('limit', [25])[0])))
- wfd = defer.waitForDeferred(
-- master.db.changes.getRecentChanges(25))
-+ master.db.changes.getRecentChanges(limit))
- yield wfd
- chdicts = wfd.getResult()
-
-
-
-Also cache builderName used in console json-like data.
-
-Index: buildbot/status/web/console.py
-===================================================================
---- buildbot/status/web/console.py
-+++ buildbot/status/web/console.py
-@@ -61,12 +61,13 @@ class ANYBRANCH: pass # a flag value, used below
-
- class CachedStatusBox(object):
- """Basic data class to remember the information for a box on the console."""
-- def __init__(self, color, pageTitle, details, url, tag):
-+ def __init__(self, color, pageTitle, details, url, tag, builderName):
- self.color = color
- self.pageTitle = pageTitle
- self.details = details
- self.url = url
- self.tag = tag
-+ self.builderName = builderName
-
-
- class CacheStatus(object):
-@@ -90,7 +91,7 @@ class CacheStatus(object):
-
- def insert(self, builderName, revision, color, pageTitle, details, url, tag):
- """Insert a new build into the cache."""
-- box = CachedStatusBox(color, pageTitle, details, url, tag)
-+ box = CachedStatusBox(color, pageTitle, details, url, tag, builderName)
- if not self.allBoxes.get(builderName):
- self.allBoxes[builderName] = {}
-
-@@ -467,6 +468,7 @@ class ConsoleStatusResource(HtmlResource):
- s["color"] = "notstarted"
- s["pageTitle"] = builder
- s["url"] = "./builders/%s" % urllib.quote(builder)
-+ s["builderName"] = builder
- state, builds = status.getBuilder(builder).getState()
- # Check if it's offline, if so, the box is purple.
- if state == "offline":
-@@ -521,6 +523,7 @@ class ConsoleStatusResource(HtmlResource):
- b["pageTitle"] = cached_value.pageTitle
- b["color"] = cached_value.color
- b["tag"] = cached_value.tag
-+ b["builderName"] = cached_value.builderName
-
- builds[category].append(b)
-
-@@ -577,6 +580,7 @@ class ConsoleStatusResource(HtmlResource):
- b["pageTitle"] = pageTitle
- b["color"] = resultsClass
- b["tag"] = tag
-+ b["builderName"] = builder
-
- builds[category].append(b)
-
-
-
-
-Re-add support for asHTML to Buildbot (used by ChromiumNotifier).
-
-Index: buildbot/changes/changes.py
-===================================================================
---- buildbot/changes/changes.py
-+++ buildbot/changes/changes.py
-@@ -25,6 +25,25 @@ from buildbot.util import datetime2epoch
- from buildbot import interfaces, util
- from buildbot.process.properties import Properties
-
-+html_tmpl = """
-+<p>Changed by: <b>%(who)s</b><br />
-+Changed at: <b>%(at)s</b><br />
-+%(repository)s
-+%(branch)s
-+%(revision)s
-+<br />
-+
-+Changed files:
-+%(files)s
-+
-+Comments:
-+%(comments)s
-+
-+Properties:
-+%(properties)s
-+</p>
-+"""
-+
- class Change:
- """I represent a single change to the source tree. This may involve several
- files, but they are all changed by the same person, and there is a change
-@@ -181,6 +200,47 @@ class Change:
- result['project'] = getattr(self, 'project', None)
- return result
-
-+ def asHTML(self):
-+ info = self.asDict()
-+ links = []
-+ for file in info['files']:
-+ if file['url'] is not None:
-+ # could get confused
-+ links.append('<a href="%s"><b>%s</b></a>' % (file['url'], file['name']))
-+ else:
-+ links.append('<b>%s</b>' % file['name'])
-+ if info['revision']:
-+ if getattr(self, 'revlink', ""):
-+ revision = 'Revision: <a href="%s"><b>%s</b></a>\n' % (
-+ info['revlink'], info['revision'])
-+ else:
-+ revision = "Revision: <b>%s</b><br />\n" % info['revision']
-+ else:
-+ revision = ''
-+
-+ if self.repository:
-+ repository = "Repository: <b>%s</b><br />\n" % info['repository']
-+ else:
-+ repository = ''
-+
-+ branch = ""
-+ if info['branch']:
-+ branch = "Branch: <b>%s</b><br />\n" % info['branch']
-+
-+ properties = []
-+ for prop in info['properties']:
-+ properties.append("%s: %s<br />" % (prop[0], prop[1]))
-+
-+ kwargs = { 'who' : html.escape(info['who']),
-+ 'at' : info['at'],
-+ 'files' : html.UL(links) + '\n',
-+ 'repository': repository,
-+ 'revision' : revision,
-+ 'branch' : branch,
-+ 'comments' : html.PRE(info['comments']),
-+ 'properties': html.UL(properties) + '\n' }
-+ return html_tmpl % kwargs
-+
- def getShortAuthor(self):
- return self.who
-
-
-
-
-Add running_failure build status.
-
-Index: third_party/buildbot_8_4p1/buildbot/status/web/console.py
-===================================================================
---- third_party/buildbot_8_4p1/buildbot/status/web/console.py (revision 105249)
-+++ third_party/buildbot_8_4p1/buildbot/status/web/console.py (working copy)
-@@ -25,11 +25,27 @@
-
- class DoesNotPassFilter(Exception): pass # Used for filtering revs
-
--def getResultsClass(results, prevResults, inProgress):
-+def isBuildGoingToFail(build):
-+ """Returns True if one of the step in the running build has failed."""
-+ for step in build.getSteps():
-+ if step.getResults()[0] == builder.FAILURE:
-+ return True
-+ return False
-+
-+def getInProgressResults(build):
-+ """Returns build status expectation for an incomplete build."""
-+ if not build.isFinished() and isBuildGoingToFail(build):
-+ return builder.FAILURE
-+
-+ return build.getResults()
-+
-+def getResultsClass(results, prevResults, inProgress, inProgressResults=None):
- """Given the current and past results, return the class that will be used
- by the css to display the right color for a box."""
-
- if inProgress:
-+ if inProgressResults == builder.FAILURE:
-+ return "running_failure"
- return "running"
-
- if results is None:
-@@ -139,7 +155,7 @@
- class DevBuild:
- """Helper class that contains all the information we need for a build."""
-
-- def __init__(self, revision, build, details):
-+ def __init__(self, revision, build, details, inProgressResults=None):
- self.revision = revision
- self.results = build.getResults()
- self.number = build.getNumber()
-@@ -149,6 +165,7 @@
- self.details = details
- self.when = build.getTimes()[0]
- self.source = build.getSourceStamp()
-+ self.inProgressResults = inProgressResults
-
-
- class ConsoleStatusResource(HtmlResource):
-@@ -331,7 +348,8 @@
- # user that his change might have broken the source update.
- if got_rev and got_rev != -1:
- details = self.getBuildDetails(request, builderName, build)
-- devBuild = DevBuild(got_rev, build, details)
-+ devBuild = DevBuild(got_rev, build, details,
-+ getInProgressResults(build))
- builds.append(devBuild)
-
- # Now break if we have enough builds.
-@@ -543,9 +561,11 @@
- # Get the results of the first build with the revision, and the
- # first build that does not include the revision.
- results = None
-+ inProgressResults = None
- previousResults = None
- if introducedIn:
- results = introducedIn.results
-+ inProgressResults = introducedIn.inProgressResults
- if firstNotIn:
- previousResults = firstNotIn.results
-
-@@ -573,7 +593,8 @@
- if isRunning:
- pageTitle += ' ETA: %ds' % (introducedIn.eta or 0)
-
-- resultsClass = getResultsClass(results, previousResults, isRunning)
-+ resultsClass = getResultsClass(results, previousResults, isRunning,
-+ inProgressResults)
-
- b = {}
- b["url"] = url
-@@ -591,7 +612,8 @@
-
- # Add this box to the cache if it's completed so we don't have
- # to compute it again.
-- if resultsClass not in ("running", "notstarted"):
-+ if resultsClass not in ("running", "running_failure",
-+ "notstarted"):
- debugInfo["added_blocks"] += 1
- self.cache.insert(builder, revision.revision, resultsClass,
- pageTitle, current_details, url, tag)
-@@ -840,4 +862,3 @@
-
- def getSortingKey(self):
- return operator.attrgetter('revision')
--
-
-
-Add diagnostic info for an error seen frequently in the wild.
-
-Index: buildbot/db/buildsets.py
-===================================================================
---- buildbot/db/buildsets.py (revision 106949)
-+++ buildbot/db/buildsets.py (working copy)
-@@ -131,7 +131,9 @@
- complete_at=_reactor.seconds())
-
- if res.rowcount != 1:
-- raise KeyError
-+ raise KeyError(('"SELECT * FROM buildsets WHERE id=%d AND '
-+ 'complete != 1;" returned %d rows') % (
-+ bsid, res.rowcount))
- return self.db.pool.do(thd)
-
- def getBuildset(self, bsid):
-@@ -291,4 +293,3 @@
- complete=bool(row.complete),
- complete_at=mkdt(row.complete_at), results=row.results,
- bsid=row.id)
--
-
-
-Workaround incomplete Change object.
-
---- buildbot/status/web/waterfall.py
-+++ buildbot/status/web/waterfall.py
-@@ -320,7 +320,7 @@ class ChangeEventSource(object):
- continue
- if categories and change.category not in categories:
- continue
-- if committers and change.author not in committers:
-+ if committers and change.who not in committers:
- continue
- if minTime and change.when < minTime:
- continue
-
-
-Add back support for patch_subdir.
-
---- buildbot/sourcestamp.py
-+++ buildbot/sourcestamp.py
-@@ -100,8 +100,8 @@ class SourceStamp(util.ComparableMixin, styles.Versioned):
-
- sourcestamp.patch = None
- if ssdict['patch_body']:
-- # note that this class does not store the patch_subdir
-- sourcestamp.patch = (ssdict['patch_level'], ssdict['patch_body'])
-+ sourcestamp.patch = (ssdict['patch_level'], ssdict['patch_body'],
-+ ssdict.get('patch_subdir'))
-
- if ssdict['changeids']:
- # sort the changeids in order, oldest to newest
-@@ -129,7 +129,7 @@ class SourceStamp(util.ComparableMixin, styles.Versioned):
- return
-
- if patch is not None:
-- assert len(patch) == 2
-+ assert 2 <= len(patch) <= 3
- assert int(patch[0]) != -1
- self.branch = branch
- self.patch = patch
-@@ -257,13 +257,17 @@ class SourceStamp(util.ComparableMixin, styles.Versioned):
- # add it to the DB
- patch_body = None
- patch_level = None
-+ patch_subdir = None
- if self.patch:
-- patch_level, patch_body = self.patch
-+ patch_level = self.patch[0]
-+ patch_body = self.patch[1]
-+ if len(self.patch) > 2:
-+ patch_subdir = self.patch[2]
- d = master.db.sourcestamps.addSourceStamp(
- branch=self.branch, revision=self.revision,
- repository=self.repository, project=self.project,
- patch_body=patch_body, patch_level=patch_level,
-- patch_subdir=None, changeids=[c.number for c in self.changes])
-+ patch_subdir=patch_subdir, changeids=[c.number for c in self.changes])
- def set_ssid(ssid):
- self.ssid = ssid
- return ssid
-
-
-Changes with a patch must never be merged.
-
---- buildbot/sourcestamp.py
-+++ buildbot/sourcestamp.py
-@@ -162,6 +162,8 @@ class SourceStamp(util.ComparableMixin, styles.Versioned):
- return False # the builds are completely unrelated
- if other.project != self.project:
- return False
-+ if self.patch or other.patch:
-+ return False # you can't merge patched builds with anything
-
- if self.changes and other.changes:
- return True
-@@ -170,8 +172,6 @@ class SourceStamp(util.ComparableMixin, styles.Versioned):
- elif not self.changes and other.changes:
- return False # they're using changes, we aren't
-
-- if self.patch or other.patch:
-- return False # you can't merge patched builds with anything
- if self.revision == other.revision:
- # both builds are using the same specific revision, so they can
- # be merged. It might be the case that revision==None, so they're
-
-
-Add back 'reason' to json/builders/<builder>/pendingBuilds. It is necessary for
-the commit queue.
-
---- buildbot/status/buildrequest.py
-+++ buildbot/status/buildrequest.py
-@@ -143,10 +143,12 @@ class BuildRequestStatus:
- result = {}
-
- wfd = defer.waitForDeferred(
-- self.getSourceStamp())
-+ self._getBuildRequest())
- yield wfd
-- ss = wfd.getResult()
-+ br = wfd.getResult()
-+ ss = br.source
- result['source'] = ss.asDict()
-+ result['reason'] = br.reason
-
- result['builderName'] = self.getBuilderName()
-
-
-Fix exception on a race condition with slave disconnecting during a build trigger.
-
---- third_party/buildbot_8_4p1/buildbot/process/slavebuilder.py
-+++ third_party/buildbot_8_4p1/buildbot/process/slavebuilder.py
-@@ -117,7 +117,7 @@ class AbstractSlaveBuilder(pb.Referenceable):
- return d
-
- def prepare(self, builder_status, build):
-- if not self.slave.acquireLocks():
-+ if not self.slave or not self.slave.acquireLocks():
- return defer.succeed(False)
- return defer.succeed(True)
-
-
-Fix exception on a race condition with slave disconnecting during a build trigger.
-
---- third_party/buildbot_8_4p1/buildbot/process/builder.py
-+++ third_party/buildbot_8_4p1/buildbot/process/builder.py
-@@ -439,7 +439,8 @@ class Builder(pb.Referenceable, service.MultiService):
- "request" % (build, slavebuilder))
-
- self.building.remove(build)
-- slavebuilder.slave.releaseLocks()
-+ if slavebuilder.slave:
-+ slavebuilder.slave.releaseLocks()
-
- # release the buildrequest claims
- wfd = defer.waitForDeferred(
-
-
-Cherry-pick command list flattening functionality from upstream buildbot -
-726e9f81c103939d22bd18d53ca65c66cfb8aed7.
-
---- buildbot/steps/shell.py
-+++ buildbot/steps/shell.py
-@@ -222,6 +222,13 @@
- # now prevent setupLogfiles() from adding them
- self.logfiles = {}
-
-+ def _flattenList(self, mainlist, commands):
-+ for x in commands:
-+ if isinstance(x, (str, unicode)):
-+ mainlist.append(x)
-+ elif x != []:
-+ self._flattenList(mainlist, x)
-+
- def start(self):
- # this block is specific to ShellCommands. subclasses that don't need
- # to set up an argv array, an environment, or extra logfiles= (like
-@@ -231,8 +238,13 @@
-
- # create the actual RemoteShellCommand instance now
- kwargs = self.remote_kwargs
-- command = self.command
-- kwargs['command'] = command
-+ tmp = []
-+ if isinstance(self.command, list):
-+ self._flattenList(tmp, self.command)
-+ else:
-+ tmp = self.command
-+
-+ kwargs['command'] = tmp
- kwargs['logfiles'] = self.logfiles
-
- # check for the usePTY flag
-
-
-Replace the simple (and very slow) LRU cache implementation in BuilderStatus
-with a better one. Added SyncLRUCache to avoid the expensive concurrency
-protection in AsyncLRUCache.
-
-Index: buildbot/status/builder.py
-===================================================================
---- buildbot/status/builder.py (revision 127129)
-+++ buildbot/status/builder.py (working copy)
-@@ -86,8 +86,8 @@
- self.currentBuilds = []
- self.nextBuild = None
- self.watchers = []
-- self.buildCache = weakref.WeakValueDictionary()
-- self.buildCache_LRU = []
-+ self.buildCache = util.lru.SyncLRUCache(self.cacheMiss,
-+ self.buildCacheSize)
- self.logCompressionLimit = False # default to no compression for tests
- self.logCompressionMethod = "bz2"
- self.logMaxSize = None # No default limit
-@@ -103,7 +103,6 @@
- d = styles.Versioned.__getstate__(self)
- d['watchers'] = []
- del d['buildCache']
-- del d['buildCache_LRU']
- for b in self.currentBuilds:
- b.saveYourself()
- # TODO: push a 'hey, build was interrupted' event
-@@ -119,8 +118,8 @@
- # when loading, re-initialize the transient stuff. Remember that
- # upgradeToVersion1 and such will be called after this finishes.
- styles.Versioned.__setstate__(self, d)
-- self.buildCache = weakref.WeakValueDictionary()
-- self.buildCache_LRU = []
-+ self.buildCache = util.lru.SyncLRUCache(self.cacheMiss,
-+ self.buildCacheSize)
- self.currentBuilds = []
- self.watchers = []
- self.slavenames = []
-@@ -132,6 +131,7 @@
- # gets pickled and unpickled.
- if buildmaster.buildCacheSize is not None:
- self.buildCacheSize = buildmaster.buildCacheSize
-+ self.buildCache.set_max_size(buildmaster.buildCacheSize)
-
- def upgradeToVersion1(self):
- if hasattr(self, 'slavename'):
-@@ -186,33 +186,17 @@
- except:
- log.msg("unable to save builder %s" % self.name)
- log.err()
--
-
-+
- # build cache management
-
- def makeBuildFilename(self, number):
- return os.path.join(self.basedir, "%d" % number)
-
-- def touchBuildCache(self, build):
-- self.buildCache[build.number] = build
-- if build in self.buildCache_LRU:
-- self.buildCache_LRU.remove(build)
-- self.buildCache_LRU = self.buildCache_LRU[-(self.buildCacheSize-1):] + [ build ]
-- return build
--
- def getBuildByNumber(self, number):
-- # first look in currentBuilds
-- for b in self.currentBuilds:
-- if b.number == number:
-- return self.touchBuildCache(b)
-+ return self.buildCache.get(number)
-
-- # then in the buildCache
-- if number in self.buildCache:
-- metrics.MetricCountEvent.log("buildCache.hits", 1)
-- return self.touchBuildCache(self.buildCache[number])
-- metrics.MetricCountEvent.log("buildCache.misses", 1)
--
-- # then fall back to loading it from disk
-+ def loadBuildFromFile(self, number):
- filename = self.makeBuildFilename(number)
- try:
- log.msg("Loading builder %s's build %d from on-disk pickle"
-@@ -235,12 +219,20 @@
- build.upgradeLogfiles()
- # check that logfiles exist
- build.checkLogfiles()
-- return self.touchBuildCache(build)
-+ return build
- except IOError:
- raise IndexError("no such build %d" % number)
- except EOFError:
- raise IndexError("corrupted build pickle %d" % number)
-
-+ def cacheMiss(self, number):
-+ # first look in currentBuilds
-+ for b in self.currentBuilds:
-+ if b.number == number:
-+ return b
-+ # then fall back to loading it from disk
-+ return self.loadBuildFromFile(number)
-+
- def prune(self, events_only=False):
- # begin by pruning our own events
- self.events = self.events[-self.eventHorizon:]
-@@ -287,7 +279,7 @@
- is_logfile = True
-
- if num is None: continue
-- if num in self.buildCache: continue
-+ if num in self.buildCache.cache: continue
-
- if (is_logfile and num < earliest_log) or num < earliest_build:
- pathname = os.path.join(self.basedir, filename)
-@@ -510,7 +502,7 @@
- assert s.number == self.nextBuildNumber - 1
- assert s not in self.currentBuilds
- self.currentBuilds.append(s)
-- self.touchBuildCache(s)
-+ self.buildCache.put(s.number, s)
-
- # now that the BuildStatus is prepared to answer queries, we can
- # announce the new build to all our watchers
-@@ -620,7 +612,7 @@
- # Collect build numbers.
- # Important: Only grab the *cached* builds numbers to reduce I/O.
- current_builds = [b.getNumber() for b in self.currentBuilds]
-- cached_builds = list(set(self.buildCache.keys() + current_builds))
-+ cached_builds = list(set(self.buildCache.cache.keys() + current_builds))
- cached_builds.sort()
- result['cachedBuilds'] = cached_builds
- result['currentBuilds'] = current_builds
-Index: buildbot/util/lru.py
-===================================================================
---- buildbot/util/lru.py (revision 127129)
-+++ buildbot/util/lru.py (working copy)
-@@ -244,5 +244,82 @@
- log.msg(" got:", sorted(self.refcount.items()))
- inv_failed = True
-
-+
-+class SyncLRUCache(AsyncLRUCache):
-+ """
-+
-+ A least-recently-used cache using the same strategy as AsyncLRUCache,
-+ minus the protections for concurrent access. The motivation for this
-+ class is to provide a speedier implementation for heavily-used caches
-+ that don't need the concurrency protections.
-+
-+ The constructor takes the same arguments as the AsyncLRUCache
-+ constructor, except C{miss_fn} must return the missing value, I{not} a
-+ deferred.
-+ """
-+
-+ # utility function to record recent use of this key
-+ def _ref_key(key):
-+ refcount = self.refcount
-+ queue = self.queue
-+
-+ queue.append(key)
-+ refcount[key] = refcount[key] + 1
-+
-+ # periodically compact the queue by eliminating duplicate keys
-+ # while preserving order of most recent access. Note that this
-+ # is only required when the cache does not exceed its maximum
-+ # size
-+ if len(queue) > self.max_queue:
-+ refcount.clear()
-+ queue_appendleft = queue.appendleft
-+ queue_appendleft(self.sentinel)
-+ for k in ifilterfalse(refcount.__contains__,
-+ iter(queue.pop, self.sentinel)):
-+ queue_appendleft(k)
-+ refcount[k] = 1
-+
-+ def get(self, key, **miss_fn_kwargs):
-+ """
-+ Fetch a value from the cache by key, invoking C{self.miss_fn(key)} if
-+ the key is not in the cache.
-+
-+ No protection is provided against concurrent access.
-+
-+ @param key: cache key
-+ @param **miss_fn_kwargs: keyword arguments to the miss_fn
-+ @returns: cache value
-+ """
-+ cache = self.cache
-+ weakrefs = self.weakrefs
-+
-+ try:
-+ result = cache[key]
-+ self.hits += 1
-+ self._ref_key(key)
-+ return result
-+ except KeyError:
-+ try:
-+ result = weakrefs[key]
-+ self.refhits += 1
-+ cache[key] = result
-+ self._ref_key(key)
-+ return result
-+ except KeyError:
-+ pass
-+
-+ # if we're here, we've missed and need to fetch
-+ self.misses += 1
-+
-+ result = self.miss_fn(key, **miss_fn_kwargs)
-+ if result is not None:
-+ cache[key] = result
-+ weakrefs[key] = result
-+ self._ref_key(key)
-+ self._purge()
-+
-+ return result
-+
-+
- # for tests
- inv_failed = False
-Index: buildbot/test/unit/test_status_builder_cache.py
-===================================================================
---- buildbot/test/unit/test_status_builder_cache.py (revision 0)
-+++ buildbot/test/unit/test_status_builder_cache.py (revision 0)
-@@ -0,0 +1,60 @@
-+# This file is part of Buildbot. Buildbot is free software: you can
-+# redistribute it and/or modify it under the terms of the GNU General Public
-+# License as published by the Free Software Foundation, version 2.
-+#
-+# This program is distributed in the hope that it will be useful, but WITHOUT
-+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
-+# details.
-+#
-+# You should have received a copy of the GNU General Public License along with
-+# this program; if not, write to the Free Software Foundation, Inc., 51
-+# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-+#
-+# Copyright Buildbot Team Members
-+
-+import os
-+from mock import Mock
-+from twisted.trial import unittest
-+from buildbot.status import builder, master
-+
-+class TestBuildStatus(unittest.TestCase):
-+
-+ # that buildstep.BuildStepStatus is never instantiated here should tell you
-+ # that these classes are not well isolated!
-+
-+ def setupBuilder(self, buildername, category=None):
-+ b = builder.BuilderStatus(buildername=buildername, category=category)
-+ # Ackwardly, Status sets this member variable.
-+ b.basedir = os.path.abspath(self.mktemp())
-+ os.mkdir(b.basedir)
-+ # Otherwise, builder.nextBuildNumber is not defined.
-+ b.determineNextBuildNumber()
-+ # Must initialize these fields before pickling.
-+ b.currentBigState = 'idle'
-+ b.status = 'idle'
-+ return b
-+
-+ def setupStatus(self, b):
-+ m = Mock()
-+ m.buildbotURL = 'http://buildbot:8010/'
-+ m.basedir = '/basedir'
-+ s = master.Status(m)
-+ b.status = s
-+ return s
-+
-+ def testBuildCache(self):
-+ b = self.setupBuilder('builder_1')
-+ builds = []
-+ for i in xrange(5):
-+ build = b.newBuild()
-+ build.setProperty('propkey', 'propval%d' % i, 'test')
-+ builds.append(build)
-+ build.buildStarted(build)
-+ build.buildFinished()
-+ for build in builds:
-+ build2 = b.getBuild(build.number)
-+ self.assertTrue(build2)
-+ self.assertEqual(build2.number, build.number)
-+ self.assertEqual(build2.getProperty('propkey'),
-+ 'propval%d' % build.number)
-+ # Do another round, to make sure we're hitting the cache
-+ hits = b.buildCache.hits
-+ for build in builds:
-+ build2 = b.getBuild(build.number)
-+ self.assertTrue(build2)
-+ self.assertEqual(build2.number, build.number)
-+ self.assertEqual(build2.getProperty('propkey'),
-+ 'propval%d' % build.number)
-+ self.assertEqual(b.buildCache.hits, hits+1)
-+ hits = hits + 1
-
-Import patch from buildbot trunk to remove unnecessary calls to
-gc.collect, which cause synchronous hangs in the master.
-
-Index: buildbot/status/builder.py
-===================================================================
---- buildbot/status/builder.py (revision 128258)
-+++ buildbot/status/builder.py (working copy)
-@@ -239,8 +239,6 @@
- if events_only:
- return
-
-- gc.collect()
--
- # get the horizons straight
- if self.buildHorizon is not None:
- earliest_build = self.nextBuildNumber - self.buildHorizon
-
-
-
-Add a handler for the signal USR1 that cancels all new builds.
-
-Index: buildbot/master.py
---- buildbot/master.py
-+++ buildbot/master.py
-@@ -161,6 +161,8 @@ class BuildMaster(service.MultiService):
- self.loadTheConfigFile()
- if hasattr(signal, "SIGHUP"):
- signal.signal(signal.SIGHUP, self._handleSIGHUP)
-+ if hasattr(signal, "SIGUSR1"):
-+ signal.signal(signal.SIGUSR1, self._handleSIGUSR1)
- for b in self.botmaster.builders.values():
- b.builder_status.addPointEvent(["master", "started"])
- b.builder_status.saveYourself()
-@@ -168,12 +170,51 @@ class BuildMaster(service.MultiService):
- def _handleSIGHUP(self, *args):
- reactor.callLater(0, self.loadTheConfigFile)
-
-+ def _handleSIGUSR1(self, *args):
-+ reactor.callLater(0, self.noNewBuilds)
-+
- def getStatus(self):
- """
- @rtype: L{buildbot.status.builder.Status}
- """
- return self.status
-
-+ @defer.deferredGenerator
-+ def cancelAllPendingBuilds(self):
-+ log.msg("canceling pending builds")
-+ c = interfaces.IControl(self)
-+ for bname in self.botmaster.builders:
-+ builder_control = c.getBuilder(bname)
-+ wfd = defer.waitForDeferred(
-+ builder_control.getPendingBuildRequestControls())
-+ yield wfd
-+ brcontrols = wfd.getResult()
-+ build_controls = dict((x.brid, x) for x in brcontrols)
-+ builder_status = self.status.getBuilder(bname)
-+ wfd = defer.waitForDeferred(
-+ builder_status.getPendingBuildRequestStatuses())
-+ yield wfd
-+ build_req_statuses = wfd.getResult()
-+ number_cancelled_builds = 0
-+ for build_req in build_req_statuses:
-+ control = build_controls[build_req.brid]
-+ control.cancel()
-+ number_cancelled_builds += 1
-+ log.msg("builder '%s' cancelled %d pending builds" % (
-+ bname, number_cancelled_builds))
-+
-+ def noNewBuilds(self):
-+ log.msg("stopping schedulers")
-+ self.loadConfig_Schedulers([])
-+ log.msg("stopping sources")
-+ self.loadConfig_Sources([])
-+ d = self.cancelAllPendingBuilds()
-+ def doneStopping(res):
-+ log.msg("new builds stopped")
-+ return res
-+ d.addCallback(doneStopping)
-+ return d
-+
- def loadTheConfigFile(self, configFile=None):
- if not configFile:
- configFile = os.path.join(self.basedir, self.configFileName)
-
-
-Use weakrefs to avoid circular references between status objects.
-
-http://codereview.chromium.org/9991001/
-
-Index: buildbot/status/master.py
-===================================================================
---- buildbot/status/master.py (revision 132430)
-+++ buildbot/status/master.py (working copy)
-@@ -124,7 +124,7 @@
-
- logs = step.getLogs()
- for i in range(len(logs)):
-- if loog is logs[i]:
-+ if loog.getName() == logs[i].getName():
- break
- else:
- return None
-Index: buildbot/status/buildstep.py
-===================================================================
---- buildbot/status/buildstep.py (revision 132430)
-+++ buildbot/status/buildstep.py (working copy)
-@@ -13,7 +13,7 @@
- #
- # Copyright Buildbot Team Members
-
--import os
-+import os, weakref
- from zope.interface import implements
- from twisted.persisted import styles
- from twisted.python import log
-@@ -63,7 +63,9 @@
-
- def __init__(self, parent, step_number):
- assert interfaces.IBuildStatus(parent)
-- self.build = parent
-+ self.build = weakref.ref(parent)
-+ self.build_number = parent.getNumber()
-+ self.builder = parent.getBuilder()
- self.step_number = step_number
- self.logs = []
- self.urls = {}
-@@ -81,7 +83,12 @@
- return self.name
-
- def getBuild(self):
-- return self.build
-+ result = self.build()
-+ if result is not None:
-+ return result
-+ result = self.builder.getBuildByNumber(self.build_number)
-+ self.build = weakref.ref(result)
-+ return result
-
- def getTimes(self):
- return (self.started, self.finished)
-@@ -179,7 +186,7 @@
- def sendETAUpdate(self, receiver, updateInterval):
- self.updates[receiver] = None
- # they might unsubscribe during stepETAUpdate
-- receiver.stepETAUpdate(self.build, self,
-+ receiver.stepETAUpdate(self.getBuild(), self,
- self.getETA(), self.getExpectations())
- if receiver in self.watchers:
- self.updates[receiver] = reactor.callLater(updateInterval,
-@@ -209,19 +216,21 @@
-
- def stepStarted(self):
- self.started = util.now()
-- if self.build:
-- self.build.stepStarted(self)
-+ build = self.getBuild()
-+ if build:
-+ build.stepStarted(self)
-
- def addLog(self, name):
- assert self.started # addLog before stepStarted won't notify watchers
-- logfilename = self.build.generateLogfileName(self.name, name)
-+ build = self.getBuild()
-+ logfilename = build.generateLogfileName(self.name, name)
- log = LogFile(self, name, logfilename)
-- log.logMaxSize = self.build.builder.logMaxSize
-- log.logMaxTailSize = self.build.builder.logMaxTailSize
-- log.compressMethod = self.build.builder.logCompressionMethod
-+ log.logMaxSize = self.builder.logMaxSize
-+ log.logMaxTailSize = self.builder.logMaxTailSize
-+ log.compressMethod = self.builder.logCompressionMethod
- self.logs.append(log)
- for w in self.watchers:
-- receiver = w.logStarted(self.build, self, log)
-+ receiver = w.logStarted(build, self, log)
- if receiver:
- log.subscribe(receiver, True)
- d = log.waitUntilFinished()
-@@ -232,28 +241,32 @@
-
- def addHTMLLog(self, name, html):
- assert self.started # addLog before stepStarted won't notify watchers
-- logfilename = self.build.generateLogfileName(self.name, name)
-+ build = self.getBuild()
-+ logfilename = build.generateLogfileName(self.name, name)
- log = HTMLLogFile(self, name, logfilename, html)
- self.logs.append(log)
- for w in self.watchers:
-- w.logStarted(self.build, self, log)
-- w.logFinished(self.build, self, log)
-+ w.logStarted(build, self, log)
-+ w.logFinished(build, self, log)
-
- def logFinished(self, log):
-+ build = self.getBuild()
- for w in self.watchers:
-- w.logFinished(self.build, self, log)
-+ w.logFinished(build, self, log)
-
- def addURL(self, name, url):
- self.urls[name] = url
-
- def setText(self, text):
- self.text = text
-+ build = self.getBuild()
- for w in self.watchers:
-- w.stepTextChanged(self.build, self, text)
-+ w.stepTextChanged(build, self, text)
- def setText2(self, text):
- self.text2 = text
-+ build = self.getBuild()
- for w in self.watchers:
-- w.stepText2Changed(self.build, self, text)
-+ w.stepText2Changed(build, self, text)
-
- def setStatistic(self, name, value):
- """Set the given statistic. Usually called by subclasses.
-@@ -267,7 +280,7 @@
- self.finished = util.now()
- self.results = results
- cld = [] # deferreds for log compression
-- logCompressionLimit = self.build.builder.logCompressionLimit
-+ logCompressionLimit = self.builder.logCompressionLimit
- for loog in self.logs:
- if not loog.isFinished():
- loog.finish()
-@@ -307,6 +320,7 @@
- def __getstate__(self):
- d = styles.Versioned.__getstate__(self)
- del d['build'] # filled in when loading
-+ del d['builder'] # filled in when loading
- if d.has_key('progress'):
- del d['progress']
- del d['watchers']
-@@ -316,11 +330,11 @@
-
- def __setstate__(self, d):
- styles.Versioned.__setstate__(self, d)
-- # self.build must be filled in by our parent
-+ # self.build and self.builder must be filled in by our parent
-
- # point the logs to this object
- for loog in self.logs:
-- loog.step = self
-+ loog.step = weakref.ref(self)
- self.watchers = []
- self.finishedWatchers = []
- self.updates = {}
-@@ -357,8 +371,6 @@
- result['urls'] = self.getURLs()
- result['step_number'] = self.step_number
- result['logs'] = [[l.getName(),
-- self.build.builder.status.getURLForThing(l)]
-+ self.builder.status.getURLForThing(l)]
- for l in self.getLogs()]
- return result
--
--
-Index: buildbot/status/build.py
-===================================================================
---- buildbot/status/build.py (revision 132430)
-+++ buildbot/status/build.py (working copy)
-@@ -13,7 +13,7 @@
- #
- # Copyright Buildbot Team Members
-
--import os, shutil, re
-+import os, shutil, re, weakref
- from cPickle import dump
- from zope.interface import implements
- from twisted.python import log, runtime
-@@ -361,7 +361,7 @@
- styles.Versioned.__setstate__(self, d)
- # self.builder must be filled in by our parent when loading
- for step in self.steps:
-- step.build = self
-+ step.build = weakref.ref(self)
- self.watchers = []
- self.updates = {}
- self.finishedWatchers = []
-@@ -459,6 +459,3 @@
- else:
- result['currentStep'] = None
- return result
--
--
--
-Index: buildbot/status/buildrequest.py
-===================================================================
---- buildbot/status/buildrequest.py (revision 132430)
-+++ buildbot/status/buildrequest.py (working copy)
-@@ -70,10 +70,6 @@
-
- yield self._buildrequest
-
-- def buildStarted(self, build):
-- self.status._buildrequest_buildStarted(build.status)
-- self.builds.append(build.status)
--
- # methods called by our clients
- @defer.deferredGenerator
- def getSourceStamp(self):
-Index: buildbot/status/logfile.py
-===================================================================
---- buildbot/status/logfile.py (revision 132430)
-+++ buildbot/status/logfile.py (working copy)
-@@ -13,7 +13,7 @@
- #
- # Copyright Buildbot Team Members
-
--import os
-+import os, weakref
- from cStringIO import StringIO
- from bz2 import BZ2File
- from gzip import GzipFile
-@@ -216,7 +216,10 @@
- @type logfilename: string
- @param logfilename: the Builder-relative pathname for the saved entries
- """
-- self.step = parent
-+ self.step = weakref.ref(parent)
-+ self.step_number = parent.step_number
-+ self.build_number = parent.getBuild().getNumber()
-+ self.builder = parent.builder
- self.name = name
- self.filename = logfilename
- fn = self.getFilename()
-@@ -236,7 +239,7 @@
- self.tailBuffer = []
-
- def getFilename(self):
-- return os.path.join(self.step.build.builder.basedir, self.filename)
-+ return os.path.join(self.builder.basedir, self.filename)
-
- def hasContents(self):
- return os.path.exists(self.getFilename() + '.bz2') or \
-@@ -247,7 +250,13 @@
- return self.name
-
- def getStep(self):
-- return self.step
-+ result = self.step()
-+ if result is not None:
-+ return result
-+ build = self.builder.getBuildByNumber(self.build_number)
-+ result = build.getSteps()[self.step_number]
-+ self.step = weakref.ref(result)
-+ return result
-
- def isFinished(self):
- return self.finished
-@@ -368,7 +377,7 @@
- if catchup:
- for channel, text in self.getChunks():
- # TODO: add logChunks(), to send over everything at once?
-- receiver.logChunk(self.step.build, self.step, self,
-+ receiver.logChunk(self.getStep().getBuild(), self.getStep(), self,
- channel, text)
-
- def unsubscribe(self, receiver):
-@@ -439,8 +448,10 @@
- if self.runLength >= self.chunkSize:
- self.merge()
-
-+ step = self.getStep()
-+ build = step.getBuild()
- for w in self.watchers:
-- w.logChunk(self.step.build, self.step, self, channel, text)
-+ w.logChunk(build, step, self, channel, text)
- self.length += len(text)
-
- def addStdout(self, text):
-@@ -526,6 +537,7 @@
- def __getstate__(self):
- d = self.__dict__.copy()
- del d['step'] # filled in upon unpickling
-+ del d['builder'] # filled in upon unpickling
- del d['watchers']
- del d['finishedWatchers']
- d['entries'] = [] # let 0.6.4 tolerate the saved log. TODO: really?
-@@ -539,7 +551,7 @@
- self.__dict__ = d
- self.watchers = [] # probably not necessary
- self.finishedWatchers = [] # same
-- # self.step must be filled in by our parent
-+ # self.step and self.builder must be filled in by our parent
- self.finished = True
-
- def upgrade(self, logfilename):
-@@ -561,7 +573,10 @@
- filename = None
-
- def __init__(self, parent, name, logfilename, html):
-- self.step = parent
-+ self.step = weakref.ref(parent)
-+ self.step_number = parent.step_number
-+ self.build_number = parent.getBuild().getNumber()
-+ self.builder = parent.builder
- self.name = name
- self.filename = logfilename
- self.html = html
-@@ -569,7 +584,13 @@
- def getName(self):
- return self.name # set in BuildStepStatus.addLog
- def getStep(self):
-- return self.step
-+ result = self.step()
-+ if result is not None:
-+ return result
-+ build = self.builder.getBuildByNumber(self.build_number)
-+ result = build.getSteps()[self.step_number]
-+ self.step = weakref.ref(result)
-+ return result
-
- def isFinished(self):
- return True
-@@ -596,6 +617,7 @@
- def __getstate__(self):
- d = self.__dict__.copy()
- del d['step']
-+ del d['builder']
- return d
-
- def upgrade(self, logfilename):
-@@ -617,4 +639,3 @@
- else:
- log.msg("giving up on removing %s after over %d seconds" %
- (filename, timeout))
--
-Index: buildbot/status/builder.py
-===================================================================
---- buildbot/status/builder.py (revision 132430)
-+++ buildbot/status/builder.py (working copy)
-@@ -202,6 +202,10 @@
- % (self.name, number))
- build = load(open(filename, "rb"))
- build.builder = self
-+ for step in build.getSteps():
-+ step.builder = self
-+ for loog in step.getLogs():
-+ loog.builder = self
-
- # (bug #1068) if we need to upgrade, we probably need to rewrite
- # this pickle, too. We determine this by looking at the list of
-
-
-Add nextSlaveAndBuilder to builder config
-
-https://chromiumcodereview.appspot.com/10032026/
-
-Index: buildbot/config.py
-===================================================================
---- buildbot/config.py (revision 132861)
-+++ buildbot/config.py (working copy)
-@@ -45,6 +45,7 @@
- category=None,
- nextSlave=None,
- nextBuild=None,
-+ nextSlaveAndBuild=None,
- locks=None,
- env=None,
- properties=None,
-@@ -95,6 +96,7 @@
- self.category = category
- self.nextSlave = nextSlave
- self.nextBuild = nextBuild
-+ self.nextSlaveAndBuild = nextSlaveAndBuild
- self.locks = locks
- self.env = env
- self.properties = properties
-@@ -114,6 +116,8 @@
- rv['nextSlave'] = self.nextSlave
- if self.nextBuild:
- rv['nextBuild'] = self.nextBuild
-+ if self.nextSlaveAndBuild:
-+ rv['nextSlaveAndBuild'] = self.nextSlaveAndBuild
- if self.locks:
- rv['locks'] = self.locks
- if self.env:
-Index: buildbot/process/builder.py
-===================================================================
---- buildbot/process/builder.py (revision 132861)
-+++ buildbot/process/builder.py (working copy)
-@@ -102,6 +102,14 @@
- self.nextBuild = setup.get('nextBuild')
- if self.nextBuild is not None and not callable(self.nextBuild):
- raise ValueError("nextBuild must be callable")
-+ self.nextSlaveAndBuild = setup.get('nextSlaveAndBuild')
-+ if self.nextSlaveAndBuild is not None:
-+ if not callable(self.nextSlaveAndBuild):
-+ raise ValueError("nextSlaveAndBuild must be callable")
-+ if self.nextBuild or self.nextSlave:
-+ raise ValueError("nextSlaveAndBuild cannot be specified"
-+ " together with either nextSlave or nextBuild")
-+
- self.buildHorizon = setup.get('buildHorizon')
- self.logHorizon = setup.get('logHorizon')
- self.eventHorizon = setup.get('eventHorizon')
-@@ -178,6 +186,8 @@
- diffs.append('nextSlave changed from %s to %s' % (self.nextSlave, setup.get('nextSlave')))
- if setup.get('nextBuild') != self.nextBuild:
- diffs.append('nextBuild changed from %s to %s' % (self.nextBuild, setup.get('nextBuild')))
-+ if setup.get('nextSlaveAndBuild') != self.nextSlaveAndBuild:
-+ diffs.append('nextSlaveAndBuild changed from %s to %s' % (self.nextSlaveAndBuild, setup.get('nextSlaveAndBuild')))
- if setup.get('buildHorizon', None) != self.buildHorizon:
- diffs.append('buildHorizon changed from %s to %s' % (self.buildHorizon, setup['buildHorizon']))
- if setup.get('logHorizon', None) != self.logHorizon:
-@@ -605,6 +615,26 @@
-
- # Build Creation
-
-+ def _checkSlaveBuilder(self, slavebuilder, available_slavebuilders):
-+ if slavebuilder not in available_slavebuilders:
-+ next_func = 'nextSlave'
-+ if self.nextSlaveAndBuild:
-+ next_func = 'nextSlaveAndBuild'
-+ log.msg("%s chose a nonexistent slave for builder '%s'; cannot"
-+ " start build" % (next_func, self.name))
-+ return False
-+ return True
-+
-+ def _checkBrDict(self, brdict, unclaimed_requests):
-+ if brdict not in unclaimed_requests:
-+ next_func = 'nextBuild'
-+ if self.nextSlaveAndBuild:
-+ next_func = 'nextSlaveAndBuild'
-+ log.msg("%s chose a nonexistent request for builder '%s'; cannot"
-+ " start build" % (next_func, self.name))
-+ return False
-+ return True
-+
- @defer.deferredGenerator
- def maybeStartBuild(self):
- # This method is called by the botmaster whenever this builder should
-@@ -645,34 +675,53 @@
-
- # match them up until we're out of options
- while available_slavebuilders and unclaimed_requests:
-- # first, choose a slave (using nextSlave)
-- wfd = defer.waitForDeferred(
-- self._chooseSlave(available_slavebuilders))
-- yield wfd
-- slavebuilder = wfd.getResult()
-+ brdict = None
-+ if self.nextSlaveAndBuild:
-+ # convert brdicts to BuildRequest objects
-+ wfd = defer.waitForDeferred(
-+ defer.gatherResults([self._brdictToBuildRequest(brdict)
-+ for brdict in unclaimed_requests]))
-+ yield wfd
-+ breqs = wfd.getResult()
-
-- if not slavebuilder:
-- break
-+ wfd = defer.waitForDeferred(defer.maybeDeferred(
-+ self.nextSlaveAndBuild,
-+ available_slavebuilders,
-+ breqs))
-+ yield wfd
-+ slavebuilder, br = wfd.getResult()
-
-- if slavebuilder not in available_slavebuilders:
-- log.msg(("nextSlave chose a nonexistent slave for builder "
-- "'%s'; cannot start build") % self.name)
-- break
-+ # Find the corresponding brdict for the returned BuildRequest
-+ if br:
-+ for brdict_i in unclaimed_requests:
-+ if brdict_i['brid'] == br.id:
-+ brdict = brdict_i
-+ break
-
-- # then choose a request (using nextBuild)
-- wfd = defer.waitForDeferred(
-- self._chooseBuild(unclaimed_requests))
-- yield wfd
-- brdict = wfd.getResult()
-+ if (not self._checkSlaveBuilder(slavebuilder,
-+ available_slavebuilders)
-+ or not self._checkBrDict(brdict, unclaimed_requests)):
-+ break
-+ else:
-+ # first, choose a slave (using nextSlave)
-+ wfd = defer.waitForDeferred(
-+ self._chooseSlave(available_slavebuilders))
-+ yield wfd
-+ slavebuilder = wfd.getResult()
-
-- if not brdict:
-- break
-+ if not self._checkSlaveBuilder(slavebuilder,
-+ available_slavebuilders):
-+ break
-
-- if brdict not in unclaimed_requests:
-- log.msg(("nextBuild chose a nonexistent request for builder "
-- "'%s'; cannot start build") % self.name)
-- break
-+ # then choose a request (using nextBuild)
-+ wfd = defer.waitForDeferred(
-+ self._chooseBuild(unclaimed_requests))
-+ yield wfd
-+ brdict = wfd.getResult()
-
-+ if not self._checkBrDict(brdict, unclaimed_requests):
-+ break
-+
- # merge the chosen request with any compatible requests in the
- # queue
- wfd = defer.waitForDeferred(
-
-
-Eliminate circular references between JsonResource and HelpResource.
-
-Index: buildbot/status/web/status_json.py
-===================================================================
---- buildbot/status/web/status_json.py (revision 132909)
-+++ buildbot/status/web/status_json.py (working copy)
-@@ -131,17 +131,16 @@
- resource.Resource.__init__(self)
- # buildbot.status.builder.Status
- self.status = status
-- if self.help:
-- pageTitle = ''
-- if self.pageTitle:
-- pageTitle = self.pageTitle + ' help'
-- self.putChild('help',
-- HelpResource(self.help, pageTitle=pageTitle, parent_node=self))
-
- def getChildWithDefault(self, path, request):
- """Adds transparent support for url ending with /"""
- if path == "" and len(request.postpath) == 0:
- return self
-+ if (path == "help" or path == "help/") and self.help:
-+ pageTitle = ''
-+ if self.pageTitle:
-+ pageTitle = self.pageTitle + ' help'
-+ return HelpResource(self.help, pageTitle=pageTitle, parent_node=self)
- # Equivalent to resource.Resource.getChildWithDefault()
- if self.children.has_key(path):
- return self.children[path]
-@@ -340,12 +339,13 @@
- HtmlResource.__init__(self)
- self.text = text
- self.pageTitle = pageTitle
-- self.parent_node = parent_node
-+ self.parent_level = parent_node.level
-+ self.parent_children = parent_node.children.keys()
-
- def content(self, request, cxt):
-- cxt['level'] = self.parent_node.level
-+ cxt['level'] = self.parent_level
- cxt['text'] = ToHtml(self.text)
-- cxt['children'] = [ n for n in self.parent_node.children.keys() if n != 'help' ]
-+ cxt['children'] = [ n for n in self.parent_children if n != 'help' ]
- cxt['flags'] = ToHtml(FLAGS)
- cxt['examples'] = ToHtml(EXAMPLES).replace(
- 'href="/json',
-
-
-Don't cache json build pages; that defeats the purpose of
-buildbot.status.builder.buildCache by holding references to all
-builds.
-
-Index: buildbot/status/web/status_json.py
-===================================================================
---- buildbot/status/web/status_json.py (revision 133407)
-+++ buildbot/status/web/status_json.py (working copy)
-@@ -451,24 +451,16 @@
- if isinstance(path, int) or _IS_INT.match(path):
- build_status = self.builder_status.getBuild(int(path))
- if build_status:
-- build_status_number = str(build_status.getNumber())
-- # Happens with negative numbers.
-- child = self.children.get(build_status_number)
-- if child:
-- return child
-- # Create it on-demand.
-- child = BuildJsonResource(self.status, build_status)
-- # Cache it. Never cache negative numbers.
-- # TODO(maruel): Cleanup the cache once it's too heavy!
-- self.putChild(build_status_number, child)
-- return child
-+ # Don't cache BuildJsonResource; that would defeat the cache-ing
-+ # mechanism in place for BuildStatus objects (in BuilderStatus).
-+ return BuildJsonResource(self.status, build_status)
- return JsonResource.getChild(self, path, request)
-
- def asDict(self, request):
- results = {}
-- # If max > buildCacheSize, it'll trash the cache...
-+ # If max is too big, it'll trash the cache...
- max = int(RequestArg(request, 'max',
-- self.builder_status.buildCacheSize))
-+ self.builder_status.buildCacheSize/2))
- for i in range(0, max):
- child = self.getChildWithDefault(-i, request)
- if not isinstance(child, BuildJsonResource):
-
-
-
-Fix getLastFinishedBuild() so it works correctly when there are 2 or more
-running builds for a given builder. Previously, it would always return the
-second-to-last build if the first was missing or running.
-
-Index: buildbot/status/builder.py
-===================================================================
---- buildbot/status/builder.py
-+++ buildbot/status/builder.py
-@@ -313,10 +313,12 @@ class BuilderStatus(styles.Versioned):
- return self.currentBuilds
-
- def getLastFinishedBuild(self):
- - b = self.getBuild(-1)
- - if not (b and b.isFinished()):
- - b = self.getBuild(-2)
- - return b
- + for build in self.generateFinishedBuilds(num_builds=1):
- + assert build and build.isFinished, \
- + 'builder %s build %s is not finished' % (
- + self.getName(), build)
- + return build
- + return None
-
- def getCategory(self):
- return self.category
-
-Fix bug introduced in previous fix to status_json.py (line 2168 in this file).
-
-Index: buildbot/status/web/status_json.py
-===================================================================
---- buildbot/status/web/status_json.py (revision 147894)
-+++ buildbot/status/web/status_json.py (working copy)
-@@ -140,7 +140,9 @@
- pageTitle = ''
- if self.pageTitle:
- pageTitle = self.pageTitle + ' help'
-- return HelpResource(self.help, pageTitle=pageTitle, parent_node=self)
-+ res = HelpResource(self.help, pageTitle=pageTitle, parent_node=self)
-+ res.level = self.level + 1
-+ return res
- # Equivalent to resource.Resource.getChildWithDefault()
- if self.children.has_key(path):
- return self.children[path]
-
-
-Make buildbot names in urls from console more readable.
-
-Index: buildbot/status/web/console.py
-===================================================================
---- a/scripts/master/chromium_status_bb8.py
-+++ b/scripts/master/chromium_status_bb8.py
-@@ -141,7 +141,7 @@ class HorizontalOneBoxPerBuilder(base.HtmlResource):
- title = builder_name
- show_name = 'off' not in request.args.get('titles', ['off'])
- url = (base.path_to_root(request) + "waterfall?builder=" +
-- urllib.quote(builder_name, safe=''))
-+ urllib.quote(builder_name, safe='() '))
- cxt_builders.append({'outcome': classname,
- 'name': title,
- 'url': url,
-diff --git a/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py b/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py
-index e70b72d..5bf2725 100644
---- a/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py
-+++ b/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py
-@@ -96,7 +96,7 @@ class GitPoller(base.PollingChangeSource):
- def git_init(_):
- log.msg('gitpoller: initializing working dir from %s' % self.repourl)
- d = utils.getProcessOutputAndValue(self.gitbin,
-- ['init', self.workdir], env=dict(PATH=os.environ['PATH']))
-+ ['init', self.workdir], env=os.environ)
- d.addCallback(self._convert_nonzero_to_failure)
- d.addErrback(self._stop_on_failure)
- return d
-@@ -105,7 +105,7 @@ class GitPoller(base.PollingChangeSource):
- def git_remote_add(_):
- d = utils.getProcessOutputAndValue(self.gitbin,
- ['remote', 'add', 'origin', self.repourl],
-- path=self.workdir, env=dict(PATH=os.environ['PATH']))
-+ path=self.workdir, env=os.environ)
- d.addCallback(self._convert_nonzero_to_failure)
- d.addErrback(self._stop_on_failure)
- return d
-@@ -115,7 +115,7 @@ class GitPoller(base.PollingChangeSource):
- args = ['fetch', 'origin']
- self._extend_with_fetch_refspec(args)
- d = utils.getProcessOutputAndValue(self.gitbin, args,
-- path=self.workdir, env=dict(PATH=os.environ['PATH']))
-+ path=self.workdir, env=os.environ)
- d.addCallback(self._convert_nonzero_to_failure)
- d.addErrback(self._stop_on_failure)
- return d
-@@ -126,11 +126,11 @@ class GitPoller(base.PollingChangeSource):
- if self.branch == 'master': # repo is already on branch 'master', so reset
- d = utils.getProcessOutputAndValue(self.gitbin,
- ['reset', '--hard', 'origin/%s' % self.branch],
-- path=self.workdir, env=dict(PATH=os.environ['PATH']))
-+ path=self.workdir, env=os.environ)
- else:
- d = utils.getProcessOutputAndValue(self.gitbin,
- ['checkout', '-b', self.branch, 'origin/%s' % self.branch],
-- path=self.workdir, env=dict(PATH=os.environ['PATH']))
-+ path=self.workdir, env=os.environ)
- d.addCallback(self._convert_nonzero_to_failure)
- d.addErrback(self._stop_on_failure)
- return d
-@@ -169,7 +169,7 @@ class GitPoller(base.PollingChangeSource):
-
- def _get_commit_comments(self, rev):
- args = ['log', rev, '--no-walk', r'--format=%s%n%b']
-- d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=dict(PATH=os.environ['PATH']), errortoo=False )
-+ d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=os.environ, errortoo=False )
- def process(git_output):
- stripped_output = git_output.strip().decode(self.encoding)
- if len(stripped_output) == 0:
-@@ -181,7 +181,7 @@ class GitPoller(base.PollingChangeSource):
- def _get_commit_timestamp(self, rev):
- # unix timestamp
- args = ['log', rev, '--no-walk', r'--format=%ct']
-- d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=dict(PATH=os.environ['PATH']), errortoo=False )
-+ d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=os.environ, errortoo=False )
- def process(git_output):
- stripped_output = git_output.strip()
- if self.usetimestamps:
-@@ -198,7 +198,7 @@ class GitPoller(base.PollingChangeSource):
-
- def _get_commit_files(self, rev):
- args = ['log', rev, '--name-only', '--no-walk', r'--format=%n']
-- d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=dict(PATH=os.environ['PATH']), errortoo=False )
-+ d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=os.environ, errortoo=False )
- def process(git_output):
- fileList = git_output.split()
- return fileList
-@@ -207,7 +207,7 @@ class GitPoller(base.PollingChangeSource):
-
- def _get_commit_name(self, rev):
- args = ['log', rev, '--no-walk', r'--format=%aE']
-- d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=dict(PATH=os.environ['PATH']), errortoo=False )
-+ d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=os.environ, errortoo=False )
- def process(git_output):
- stripped_output = git_output.strip().decode(self.encoding)
- if len(stripped_output) == 0:
-@@ -231,7 +231,7 @@ class GitPoller(base.PollingChangeSource):
- # deferred will not use the response.
- d = utils.getProcessOutput(self.gitbin, args,
- path=self.workdir,
-- env=dict(PATH=os.environ['PATH']), errortoo=True )
-+ env=os.environ, errortoo=True )
-
- return d
-
-@@ -241,7 +241,7 @@ class GitPoller(base.PollingChangeSource):
- revListArgs = ['log', '%s..origin/%s' % (self.branch, self.branch), r'--format=%H']
- self.changeCount = 0
- d = utils.getProcessOutput(self.gitbin, revListArgs, path=self.workdir,
-- env=dict(PATH=os.environ['PATH']), errortoo=False )
-+ env=os.environ, errortoo=False )
- wfd = defer.waitForDeferred(d)
- yield wfd
- results = wfd.getResult()
-@@ -307,7 +307,7 @@ class GitPoller(base.PollingChangeSource):
- return
- log.msg('gitpoller: catching up tracking branch')
- args = ['reset', '--hard', 'origin/%s' % (self.branch,)]
-- d = utils.getProcessOutputAndValue(self.gitbin, args, path=self.workdir, env=dict(PATH=os.environ['PATH']))
-+ d = utils.getProcessOutputAndValue(self.gitbin, args, path=self.workdir, env=os.environ)
- d.addCallback(self._convert_nonzero_to_failure)
- return d
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/test/unit/test_changes_gitpoller.py b/third_party/buildbot_8_4p1/buildbot/test/unit/test_changes_gitpoller.py
-index 9a47d41..c5e1960 100644
---- a/third_party/buildbot_8_4p1/buildbot/test/unit/test_changes_gitpoller.py
-+++ b/third_party/buildbot_8_4p1/buildbot/test/unit/test_changes_gitpoller.py
-@@ -13,6 +13,7 @@
- #
- # Copyright Buildbot Team Members
-
-+import os
- from twisted.trial import unittest
- from twisted.internet import defer
- from exceptions import Exception
-@@ -20,6 +21,9 @@ from buildbot.changes import gitpoller
- from buildbot.test.util import changesource, gpo
- from buildbot.util import epoch2datetime
-
-+# Test that environment variables get propagated to subprocesses (See #2116)
-+os.environ['TEST_THAT_ENVIRONMENT_GETS_PASSED_TO_SUBPROCESSES'] = 'TRUE'
-+
- class GitOutputParsing(gpo.GetProcessOutputMixin, unittest.TestCase):
- """Test GitPoller methods for parsing git output"""
- def setUp(self):
-@@ -120,6 +124,11 @@ class TestGitPoller(gpo.GetProcessOutputMixin,
- self.assertSubstring("GitPoller", self.poller.describe())
-
- def test_poll(self):
-+ # Test that environment variables get propagated to subprocesses (See #2116)
-+ os.putenv('TEST_THAT_ENVIRONMENT_GETS_PASSED_TO_SUBPROCESSES', 'TRUE')
-+ self.addGetProcessOutputExpectEnv({'TEST_THAT_ENVIRONMENT_GETS_PASSED_TO_SUBPROCESSES': 'TRUE'})
-+ self.addGetProcessOutputAndValueExpectEnv({'TEST_THAT_ENVIRONMENT_GETS_PASSED_TO_SUBPROCESSES': 'TRUE'})
-+
- # patch out getProcessOutput and getProcessOutputAndValue for the
- # benefit of the _get_changes method
- self.addGetProcessOutputResult(
-diff --git a/third_party/buildbot_8_4p1/buildbot/test/util/gpo.py b/third_party/buildbot_8_4p1/buildbot/test/util/gpo.py
-index a23bdb2..c9daf0c 100644
---- a/third_party/buildbot_8_4p1/buildbot/test/util/gpo.py
-+++ b/third_party/buildbot_8_4p1/buildbot/test/util/gpo.py
-@@ -39,6 +39,8 @@ class GetProcessOutputMixin:
- self._gpo_patched = False
- self._gpo_patterns = []
- self._gpoav_patterns = []
-+ self._gpo_expect_env = {}
-+ self._gpoav_expect_env = {}
-
- def tearDownGetProcessOutput(self):
- pass
-@@ -54,11 +56,23 @@ class GetProcessOutputMixin:
-
- # these can be overridden if necessary
- def patched_getProcessOutput(self, bin, args, env=None, **kwargs):
-+ for var, value in self._gpo_expect_env.items():
-+ if env.get(var) != value:
-+ self._flag_error('Expected environment to have %s = %r' % (var, value))
-+
- return self._patched(self._gpo_patterns, bin, args, env=env, **kwargs)
-
- def patched_getProcessOutputAndValue(self, bin, args, env=None, **kwargs):
-+ for var, value in self._gpoav_expect_env.items():
-+ if env.get(var) != value:
-+ self._flag_error('Expected environment to have %s = %r' % (var, value))
-+
- return self._patched(self._gpoav_patterns, bin, args, env=env, **kwargs)
-
-+ def _flag_error(self, msg):
-+ # print msg
-+ assert False, msg
-+
- def _patch_gpo(self):
- if not self._gpo_patched:
- self.patch(utils, "getProcessOutput",
-@@ -67,6 +81,12 @@ class GetProcessOutputMixin:
- self.patched_getProcessOutputAndValue)
- self._gpo_patched = True
-
-+ def addGetProcessOutputExpectEnv(self, d):
-+ self._gpo_expect_env.update(d)
-+
-+ def addGetProcessOutputAndValueExpectEnv(self, d):
-+ self._gpoav_expect_env.update(d)
-+
- def addGetProcessOutputResult(self, pattern, result):
- self._patch_gpo()
- self._gpo_patterns.append((pattern, result))
-
-
-
-
-Correctly escape the committer in the waterfall/help page.
-
-Index: waterfallhelp.html
-===================================================================
---- waterfallhelp.html (revision 164735)
-+++ waterfallhelp.html (working copy)
-@@ -99,7 +99,7 @@
- {% for cn in committers %}
- <tr>
- <td>
-- Show Committer: <input type="text" name="committer" value="{{ cn }}">
-+ Show Committer: <input type="text" name="committer" value="{{ cn|e }}">
- </td>
- </tr>
- {% endfor %}
-
-
-commit 1fe8fbcf99cd98546a98d2dd6a3be1206ea590c7
-Author: Peter Mayo <petermayo@chromium.org>
-Date: Fri Nov 23 12:37:00 2012 -0500
-
- Optionally add project to changes in waterfall display.
-
- In some waterfalls it is useful to separate different change
- sources visually. This allows an attribute to reflect that to
- be passed though from config to template.
-
- Related reviews: https://codereview.chromium.org/11425002
-
-diff --git a/master/buildbot/status/web/changes.py b/master/buildbot/status/web/changes.py
-index 0971bea..e579669 100644
---- a/master/buildbot/status/web/changes.py
-+++ b/master/buildbot/status/web/changes.py
-@@ -64,7 +64,8 @@ class ChangeBox(components.Adapter):
- text = template.module.box_contents(url=url,
- who=self.original.getShortAuthor(),
- pageTitle=self.original.comments,
-- revision=self.original.revision)
-+ revision=self.original.revision,
-+ project=self.original.project)
- return Box([text], class_="Change")
- components.registerAdapter(ChangeBox, Change, IBox)
-
-diff --git a/master/buildbot/status/web/templates/change_macros.html b/master/buildbot/status/web/templates/change_macros.html
-index dc6a9b2..2ca01d9 100644
---- a/master/buildbot/status/web/templates/change_macros.html
-+++ b/master/buildbot/status/web/templates/change_macros.html
-@@ -71,6 +71,6 @@
- {% endif %}
- {%- endmacro %}
-
--{% macro box_contents(who, url, pageTitle, revision) -%}
-+{% macro box_contents(who, url, pageTitle, revision, project) -%}
- <a href="{{ url }}" title="{{ pageTitle|e }}">{{ who|user }}</a>
- {%- endmacro %}
-
-
-
-Disable an assert that is causing internal inconsistent state because of
-out-of-order build processing.
-
---- a/third_party/buildbot_8_4p1/buildbot/status/builder.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/builder.py
-@@ -502,7 +502,8 @@ class BuilderStatus(styles.Versioned):
- Steps, its ETA, etc), so it is safe to notify our watchers."""
-
- assert s.builder is self # paranoia
-- assert s.number == self.nextBuildNumber - 1
-+ # They can happen out of order.
-+ #assert s.number == self.nextBuildNumber - 1
- assert s not in self.currentBuilds
- self.currentBuilds.append(s)
- self.buildCache.put(s.number, s)
-
-Change buildbot to use the epoll reactor instead of the default select()
-reaction. Breaks compatability with non linux26 systems.
-diff --git a/third_party/buildbot_8_4p1/buildbot/master.py b/third_party/buildbot_8_4p1/buildbot/master.py
-index 28e46de..7ec0032 100644
---- a/third_party/buildbot_8_4p1/buildbot/master.py
-+++ b/third_party/buildbot_8_4p1/buildbot/master.py
-@@ -19,6 +19,16 @@ import signal
- import time
- import textwrap
-
-+try:
-+ import pyximport
-+ pyximport.install()
-+ from twisted.internet import epollreactor
-+ epollreactor.install()
-+except ImportError:
-+ print 'Unable to load the epoll module, falling back to select.'
-+ print 'This may be caused by the lack of cython, python-dev, or'
-+ print 'you may be on a platform other than linux 2.6'
-+
- from zope.interface import implements
- from twisted.python import log, components
- from twisted.internet import defer, reactor
-
-
---- a/third_party/buildbot_8_4p1/buildbot/status/web/builder.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/builder.py
-@@ -110,7 +110,7 @@ class StatusResourceBuilder(HtmlResource, BuildLineMixin):
- 'num_changes' : len(changes),
- })
-
-- numbuilds = int(req.args.get('numbuilds', ['5'])[0])
-+ numbuilds = int(req.args.get('numbuilds', ['20'])[0])
- recent = cxt['recent'] = []
- for build in b.generateFinishedBuilds(num_builds=int(numbuilds)):
- recent.append(self.get_line_values(req, build, False))
-
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/master.py b/third_party/buildbot_8_4p1/buildbot/master.py
-index 7ec0032..425572e 100644
---- a/third_party/buildbot_8_4p1/buildbot/master.py
-+++ b/third_party/buildbot_8_4p1/buildbot/master.py
-@@ -295,7 +295,7 @@ class BuildMaster(service.MultiService):
- "logHorizon", "buildHorizon", "changeHorizon",
- "logMaxSize", "logMaxTailSize", "logCompressionMethod",
- "db_url", "multiMaster", "db_poll_interval",
-- "metrics", "caches"
-+ "metrics", "caches", "autoBuildCacheRatio"
- )
- for k in config.keys():
- if k not in known_keys:
-@@ -357,6 +357,7 @@ class BuildMaster(service.MultiService):
-
- metrics_config = config.get("metrics")
- caches_config = config.get("caches", {})
-+ autoBuildCacheRatio = config.get("autoBuildCacheRatio", None)
-
- except KeyError:
- log.msg("config dictionary is missing a required parameter")
-@@ -555,6 +556,7 @@ class BuildMaster(service.MultiService):
- self.logHorizon = logHorizon
- self.buildHorizon = buildHorizon
- self.slavePortnum = slavePortnum # TODO: move this to master.config.slavePortnum
-+ self.autoBuildCacheRatio = autoBuildCacheRatio
-
- # Set up the database
- d.addCallback(lambda res:
-@@ -784,6 +786,16 @@ class BuildMaster(service.MultiService):
- for builder in allBuilders.values():
- builder.builder_status.reconfigFromBuildmaster(self)
-
-+ # Adjust the caches if autoBuildCacheRatio is on. Each builder's
-+ # build cache is set to (autoBuildCacheRatio * number of slaves).
-+ # This assumes that each slave-entry can only execute one concurrent
-+ # build.
-+ if self.autoBuildCacheRatio:
-+ builder_status = builder.builder_status
-+ slavecount = len(builder_status.slavenames)
-+ max_size = max(self.autoBuildCacheRatio * slavecount, 15)
-+ builder_status.buildCache.set_max_size(max_size)
-+ builder_status.buildCacheSize = max_size
-+
- metrics.MetricCountEvent.log("num_builders",
- len(allBuilders), absolute=True)
-
-
-Added error catching so buildbot would not crash during initial config.
-diff --git a/third_party/buildbot_8_4p1/buildbot/master.py b/third_party/buildbot_8_4p1/buildbot/master.py
-index df80ca6..5304401 100644
---- a/third_party/buildbot_8_4p1/buildbot/master.py
-+++ b/third_party/buildbot_8_4p1/buildbot/master.py
-@@ -18,6 +18,7 @@ import os
- import signal
- import time
- import textwrap
-+import twisted.internet.error
-
- try:
- import pyximport
-@@ -28,6 +29,8 @@ except ImportError:
- print 'Unable to load the epoll module, falling back to select.'
- print 'This may be caused by the lack of cython, python-dev, or'
- print 'you may be on a platform other than linux 2.6'
-+except twisted.internet.error.ReactorAlreadyInstalledError:
-+ pass
-
- from zope.interface import implements
- from twisted.python import log, components
-
-Suppress a warning about old name.
-Index: buildbot/changes/svnpoller.py
-===================================================================
---- buildbot/changes/svnpoller.py (revision 195053)
-+++ buildbot/changes/svnpoller.py (working copy)
-@@ -352,7 +352,7 @@
- log.msg("Ignoring deletion of branch '%s'" % branch)
- else:
- chdict = dict(
-- who=author,
-+ author=author,
- files=files,
- comments=comments,
- revision=revision,
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/process/buildstep.py b/third_party/buildbot_8_4p1/buildbot/process/buildstep.py
-index ed12ea0..32e1bd3 100644
---- a/third_party/buildbot_8_4p1/buildbot/process/buildstep.py
-+++ b/third_party/buildbot_8_4p1/buildbot/process/buildstep.py
-@@ -635,8 +635,8 @@ class BuildStep:
- doStepIf = True
- hideStepIf = False
- # like doStepIf, but evaluated at runtime if executing under runbuild.py
-- # we also overload 'False' to signify this isn't a buildrunner step
-- brDoStepIf = False
-+ # We use None to signify that a step is not a buildrunner step.
-+ brDoStepIf = None
-
- def __init__(self, **kwargs):
- self.factory = (self.__class__, dict(kwargs))
-
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-index b656563..e48542d 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-@@ -16,6 +16,7 @@
-
- """Simple JSON exporter."""
-
-+import collections
- import datetime
- import os
- import re
-@@ -635,21 +636,38 @@ class SlaveJsonResource(JsonResource):
- self.builders.append(builderName)
- return self.builders
-
-+ def getSlaveBuildMap(self, buildcache, buildercache):
-+ for builderName in self.getBuilders():
-+ if builderName not in buildercache:
-+ buildercache.add(builderName)
-+ builder_status = self.status.getBuilder(builderName)
-+ for i in range(1, builder_status.buildCacheSize - 1):
-+ build_status = builder_status.getBuild(-i)
-+ if not build_status or not build_status.isFinished():
-+ # If not finished, it will appear in runningBuilds.
-+ break
-+ slave = buildcache[build_status.getSlavename()]
-+ slave.setdefault(builderName, []).append(
-+ build_status.getNumber())
-+ return buildcache[self.name]
-+
- def asDict(self, request):
-+ if not hasattr(request, 'custom_data'):
-+ request.custom_data = {}
-+ if 'buildcache' not in request.custom_data:
-+ # buildcache is used to cache build information across multiple
-+ # invocations of SlaveJsonResource. It should be set to an empty
-+ # collections.defaultdict(dict).
-+ request.custom_data['buildcache'] = collections.defaultdict(dict)
-+
-+ # Tracks which builders have been stored in the buildcache.
-+ request.custom_data['buildercache'] = set()
-+
- results = self.slave_status.asDict()
-- # Enhance it by adding more informations.
-- results['builders'] = {}
-- for builderName in self.getBuilders():
-- builds = []
-- builder_status = self.status.getBuilder(builderName)
-- for i in range(1, builder_status.buildCacheSize - 1):
-- build_status = builder_status.getBuild(-i)
-- if not build_status or not build_status.isFinished():
-- # If not finished, it will appear in runningBuilds.
-- break
-- if build_status.getSlavename() == self.name:
-- builds.append(build_status.getNumber())
-- results['builders'][builderName] = builds
-+ # Enhance it by adding more information.
-+ results['builders'] = self.getSlaveBuildMap(
-+ request.custom_data['buildcache'],
-+ request.custom_data['buildercache'])
- return results
-
-
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/builder.py b/third_party/buildbot_8_4p1/buildbot/status/builder.py
-index 3f42eb6..c5bd1ec 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/builder.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/builder.py
-@@ -17,6 +17,7 @@
- import weakref
- import gc
- import os, re, itertools
-+import random
- from cPickle import load, dump
-
- from zope.interface import implements
-@@ -323,17 +324,58 @@ class BuilderStatus(styles.Versioned):
- def getCategory(self):
- return self.category
-
-- def getBuild(self, number):
-+ def _resolveBuildNumber(self, number):
- if number < 0:
- number = self.nextBuildNumber + number
- if number < 0 or number >= self.nextBuildNumber:
- return None
-+ return number
-
-+ def _safeGetBuild(self, build_number):
- try:
-- return self.getBuildByNumber(number)
-+ return self.getBuildByNumber(build_number)
- except IndexError:
- return None
-
-+ def getBuild(self, number):
-+ number = self._resolveBuildNumber(number)
-+
-+ if number is None:
-+ return None
-+
-+ return self._safeGetBuild(number)
-+
-+ def getBuilds(self, numbers):
-+ """Cache-aware method to get multiple builds.
-+
-+ Prevents cascading evict/load when multiple builds are requested in
-+ succession: requesting build 1 evicts build 2, requesting build 2 evicts
-+ build 3, etc.
-+
-+ We query the buildCache and load hits first, then misses. When loading,
-+ we randomize the load order to alleviate the problem when external web
-+ requests load builds sequentially (they don't have access to this
-+ function).
-+ """
-+
-+ numbers = list(enumerate(self._resolveBuildNumber(x) for x in numbers))
-+ random.shuffle(numbers)
-+
-+ builds = [None] * len(numbers)
-+ misses = []
-+ for idx, build_number in numbers:
-+ if build_number is None:
-+ continue
-+ if build_number in self.buildCache.cache:
-+ builds[idx] = self._safeGetBuild(build_number)
-+ else:
-+ misses.append((idx, build_number))
-+
-+ for idx, build_number in misses:
-+ builds[idx] = self._safeGetBuild(build_number)
-+
-+ return builds
-+
- def getEvent(self, number):
- try:
- return self.events[number]
-@@ -616,6 +658,11 @@ class BuilderStatus(styles.Versioned):
- # Collect build numbers.
- # Important: Only grab the *cached* builds numbers to reduce I/O.
- current_builds = [b.getNumber() for b in self.currentBuilds]
-+
-+ # Populates buildCache with last N builds.
-+ buildnums = range(-1, -(self.buildCacheSize - 1), -1)
-+ self.getBuilds(buildnums)
-+
- cached_builds = list(set(self.buildCache.cache.keys() + current_builds))
- cached_builds.sort()
- result['cachedBuilds'] = cached_builds
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-index c357939..e7cd932 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-@@ -641,8 +641,11 @@ class SlaveJsonResource(JsonResource):
- if builderName not in buildercache:
- buildercache.add(builderName)
- builder_status = self.status.getBuilder(builderName)
-- for i in range(1, builder_status.buildCacheSize - 1):
-- build_status = builder_status.getBuild(-i)
-+
-+ buildnums = range(-1, -(builder_status.buildCacheSize - 1), -1)
-+ builds = builder_status.getBuilds(buildnums)
-+
-+ for build_status in builds:
- if not build_status or not build_status.isFinished():
- # If not finished, it will appear in runningBuilds.
- break
-
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/buildstep.py b/third_party/buildbot_8_4p1/buildbot/status/buildstep.py
-index 264b599..f64e0b9 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/buildstep.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/buildstep.py
-@@ -13,7 +13,10 @@
- #
- # Copyright Buildbot Team Members
-
-+import collections
- import os, weakref
-+
-+
- from zope.interface import implements
- from twisted.persisted import styles
- from twisted.python import log
-@@ -21,6 +24,9 @@ from twisted.internet import reactor, defer
- from buildbot import interfaces, util
- from buildbot.status.logfile import LogFile, HTMLLogFile
-
-+# This allows use of OrderedDict on python 2.6, found in scripts/common.
-+import common.python26_polyfill # pylint: disable=W0611
-+
- class BuildStepStatus(styles.Versioned):
- """
- I represent a collection of output status for a
-@@ -70,7 +76,7 @@ class BuildStepStatus(styles.Versioned):
- self.step_number = step_number
- self.hidden = False
- self.logs = []
-- self.urls = {}
-+ self.urls = collections.OrderedDict()
- self.watchers = []
- self.updates = {}
- self.finishedWatchers = []
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py b/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py
-index 5bf2725..71a0386 100644
---- a/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py
-+++ b/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py
-@@ -238,7 +238,8 @@ class GitPoller(base.PollingChangeSource):
- @defer.deferredGenerator
- def _process_changes(self, unused_output):
- # get the change list
-- revListArgs = ['log', '%s..origin/%s' % (self.branch, self.branch), r'--format=%H']
-+ revListArgs = ['log', '%s..origin/%s' % (self.branch, self.branch),
-+ r'--format=%H', '--first-parent']
- self.changeCount = 0
- d = utils.getProcessOutput(self.gitbin, revListArgs, path=self.workdir,
- env=os.environ, errortoo=False )
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/master.py b/third_party/buildbot_8_4p1/buildbot/master.py
-index 5304401..c90ad32 100644
---- a/third_party/buildbot_8_4p1/buildbot/master.py
-+++ b/third_party/buildbot_8_4p1/buildbot/master.py
-@@ -796,7 +796,7 @@ class BuildMaster(service.MultiService):
- if self.autoBuildCacheRatio:
- builder_status = builder.builder_status
- slavecount = len(builder_status.slavenames)
-- max_size = max(self.autoBuildCacheRatio * slavecount, 15)
-+ max_size = max(self.autoBuildCacheRatio * slavecount, 30)
- builder_status.buildCache.set_max_size(max_size)
- builder_status.buildCacheSize = max_size
-
-
-*** maruel on 2013-12-05
-Add support for transferring 'reason' from a build to its triggered build.
-Otherwise the default is 'Triggerable(%(name))' which is not very useful.
-
---- a/third_party/buildbot_8_4p1/buildbot/schedulers/triggerable.py
-+++ b/third_party/buildbot_8_4p1/buildbot/schedulers/triggerable.py
-@@ -28,7 +28,7 @@ class Triggerable(base.BaseScheduler):
- self._bsc_subscription = None
- self.reason = "Triggerable(%s)" % name
-
-- def trigger(self, ssid, set_props=None):
-+ def trigger(self, ssid, set_props=None, reason=None):
- """Trigger this scheduler with the given sourcestamp ID. Returns a
- deferred that will fire when the buildset is finished."""
- # properties for this buildset are composed of our own properties,
-@@ -42,10 +42,10 @@ class Triggerable(base.BaseScheduler):
- # the duration of interest to the caller is bounded by the lifetime of
- # this process.
- if ssid:
-- d = self.addBuildsetForSourceStamp(reason=self.reason, ssid=ssid,
-+ d = self.addBuildsetForSourceStamp(reason=reason or self.reason, ssid=ssid,
- properties=props)
- else:
-- d = self.addBuildsetForLatest(reason=self.reason, properties=props)
-+ d = self.addBuildsetForLatest(reason=reason or self.reason, properties=props)
- def setup_waiter((bsid,brids)):
- self._waiters[bsid] = d = defer.Deferred()
- self._updateWaiters()
---- a/third_party/buildbot_8_4p1/buildbot/steps/trigger.py
-+++ b/third_party/buildbot_8_4p1/buildbot/steps/trigger.py
-@@ -167,7 +167,7 @@ class Trigger(LoggingBuildStep):
- dl = []
- for scheduler in triggered_schedulers:
- sch = all_schedulers[scheduler]
-- dl.append(sch.trigger(ssid, set_props=props_to_set))
-+ dl.append(sch.trigger(ssid, set_props=props_to_set, reason=self.build.reason))
- self.step_status.setText(['triggered'] + triggered_schedulers)
-
- d = defer.DeferredList(dl, consumeErrors=1)
-
-Hard-disables shutdown hooks for https://code.google.com/p/chromium/issues/detail?id=309046
-===================================================================
---- a/third_party/buildbot_8_4p1/buildbot/status/web/root.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/root.py
-@@ -25,17 +25,9 @@
- status = self.getStatus(request)
-
- if request.path == '/shutdown':
-- if self.getAuthz(request).actionAllowed("cleanShutdown", request):
-- eventually(status.cleanShutdown)
-- return redirectTo("/", request)
-- else:
-- return redirectTo(path_to_authfail(request), request)
-+ return redirectTo(path_to_authfail(request), request)
- elif request.path == '/cancel_shutdown':
-- if self.getAuthz(request).actionAllowed("cleanShutdown", request):
-- eventually(status.cancelCleanShutdown)
-- return redirectTo("/", request)
-- else:
-- return redirectTo(path_to_authfail(request), request)
-+ return redirectTo(path_to_authfail(request), request)
-
- cxt.update(
- shutting_down = status.shuttingDown,
-
-
-Provide support for messageFormatter to create custom subject lines.
---- a/third_party/buildbot_8_4p1/buildbot/status/mail.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/mail.py
-@@ -587,7 +587,12 @@ class MailNotifier(base.StatusReceiverMultiService):
- build=build, results=build.results)
- msgdict['body'] += tmp['body']
- msgdict['body'] += '\n\n'
-+ # This is terrible. Whichever build is iterated over last will
-+ # overwrite the subject and type set by all previous builds.
-+ # But we never iterate over more than one build, so we'll
-+ # just do this anyway and try not to lose any sleep over it.
- msgdict['type'] = tmp['type']
-+ if 'subject' in tmp:
-+ msgdict['subject'] = tmp['subject']
-
- m = self.createEmail(msgdict, name, self.master_status.getTitle(),
- results, builds, patches, logs)
-
-
-Change minimum cache size to 50 to prevent waterfall and console view bustage.
---- a/third_party/buildbot_8_4p1/buildbot/master.py
-+++ b/third_party/buildbot_8_4p1/buildbot/master.py
-@@ -796,7 +796,7 @@ class BuildMaster(service.MultiService):
- if self.autoBuildCacheRatio:
- builder_status = builder.builder_status
- slavecount = len(builder_status.slavenames)
-- max_size = max(self.autoBuildCacheRatio * slavecount, 30)
-+ max_size = max(self.autoBuildCacheRatio * slavecount, 50)
- builder_status.buildCache.set_max_size(max_size)
- builder_status.buildCacheSize = max_size
-
-
-Cherry-pick ANSI color codes in logs from upstream buildbot. These commits were manually
-resolved.
-
- ** https://github.com/buildbot/buildbot/commit/3a563189374737aa2ebf3f6467bf461bc3ec5443
-commit 3a563189374737aa2ebf3f6467bf461bc3ec5443
-Author: Pierre Tardy <pierre.tardy@intel.com>
-Date: Mon Jul 15 18:48:35 2013 +0200
-
- logs: implement on the fly parsing of ansi code
-
- grunt and js tools have the habit of using ansi
- code a lot, which are ugly inside python log window.
- http://en.wikipedia.org/wiki/ANSI_escape_code
-
- This is a simple enough implementation of ansicode parser
- which integrates well into buildbot's way of displaying logs
-
- Signed-off-by: Pierre Tardy <pierre.tardy@intel.com>
-
-diff --git a/master/buildbot/status/web/files/default.css b/master/buildbot/status/web/files/default.css
-index d109be1..2d24c2b 100644
---- a/master/buildbot/status/web/files/default.css
-+++ b/master/buildbot/status/web/files/default.css
-@@ -564,6 +564,30 @@ span.stderr {
- span.header {
- color: blue;
- }
-+span.ansi30 {
-+ color: black;
-+}
-+span.ansi31 {
-+ color: red;
-+}
-+span.ansi32 {
-+ color: green;
-+}
-+span.ansi33 {
-+ color: orange;
-+}
-+span.ansi34 {
-+ color: yellow;
-+}
-+span.ansi35 {
-+ color: purple;
-+}
-+span.ansi36 {
-+ color: blue;
-+}
-+span.ansi37 {
-+ color: white;
-+}
-
- /* revision & email */
- .revision .full {
-diff --git a/master/buildbot/status/web/logs.py b/master/buildbot/status/web/logs.py
-index d7da811..b883dc2 100644
---- a/master/buildbot/status/web/logs.py
-+++ b/master/buildbot/status/web/logs.py
-@@ -24,6 +24,9 @@ from buildbot import interfaces
- from buildbot.status import logfile
- from buildbot.status.web.base import IHTMLLog, HtmlResource, path_to_root
-
-+import re
-+
-+
- class ChunkConsumer:
- implements(interfaces.IStatusLogConsumer)
-
-@@ -46,6 +49,7 @@ class ChunkConsumer:
- def finish(self):
- self.textlog.finished()
-
-+ANSI_RE = re.compile(r"^(\d*)(;(\d+))?([a-zA-Z])")
-
- # /builders/$builder/builds/$buildnum/steps/$stepname/logs/$logname
- class TextLog(Resource):
-@@ -80,9 +84,26 @@ class TextLog(Resource):
- # jinja only works with unicode, or pure ascii, so assume utf-8 in logs
- if not isinstance(entry, unicode):
- entry = unicode(entry, 'utf-8', 'replace')
-- html_entries.append(dict(type = logfile.ChunkTypes[type],
-- text = entry,
-- is_header = is_header))
-+ first_entry = True
-+ _type = logfile.ChunkTypes[type]
-+ for ansi_entry in entry.split("\033["):
-+ code = ""
-+ if not first_entry:
-+ res = ANSI_RE.search(ansi_entry)
-+ if res:
-+ mode = res.group(4)
-+ ansi_entry = ansi_entry[len(res.group(0)):]
-+ if mode == 'm':
-+ code = " ansi" + res.group(1)
-+ else:
-+ # illegal code, restore the CSI
-+ ansi_entry = "\033[" + ansi_entry
-+
-+ html_entries.append(dict(type=_type + code,
-+ text=ansi_entry,
-+ is_header=is_header))
-+ first_entry = False
-+
- elif not is_header:
- text_data += entry
-
- ** https://github.com/buildbot/buildbot/commit/c451dd5c7cd377e774c4bf5c5d16d53986a9ccbf
-commit c451dd5c7cd377e774c4bf5c5d16d53986a9ccbf
-Author: Pierre Tardy <pierre.tardy@intel.com>
-Date: Thu Aug 22 11:48:31 2013 +0200
-
- ansi codes: add support for multiple sgr classes
-
- Add some unit tests and factorize for potencial reuse
-
- nine plan is to reuse the regex and overall idea, but probably
- rewrite this client side.
-
- Signed-off-by: Pierre Tardy <pierre.tardy@intel.com>
-
-diff --git a/master/buildbot/status/web/logs.py b/master/buildbot/status/web/logs.py
-index b883dc2..37eaf29 100644
---- a/master/buildbot/status/web/logs.py
-+++ b/master/buildbot/status/web/logs.py
-@@ -23,9 +23,7 @@ from twisted.web.resource import Resource, NoResource
- from buildbot import interfaces
- from buildbot.status import logfile
- from buildbot.status.web.base import IHTMLLog, HtmlResource, path_to_root
--
--import re
--
-+from buildbot.util.ansicodes import parse_ansi_sgr
-
- class ChunkConsumer:
- implements(interfaces.IStatusLogConsumer)
-@@ -49,8 +47,6 @@ class ChunkConsumer:
- def finish(self):
- self.textlog.finished()
-
--ANSI_RE = re.compile(r"^(\d*)(;(\d+))?([a-zA-Z])")
--
- # /builders/$builder/builds/$buildnum/steps/$stepname/logs/$logname
- class TextLog(Resource):
- # a new instance of this Resource is created for each client who views
-@@ -77,7 +73,7 @@ class TextLog(Resource):
- if type >= len(logfile.ChunkTypes) or type < 0:
- # non-std channel, don't display
- continue
--
-+
- is_header = type == logfile.HEADER
-
- if not self.asText:
-@@ -89,16 +85,9 @@ class TextLog(Resource):
- for ansi_entry in entry.split("\033["):
- code = ""
- if not first_entry:
-- res = ANSI_RE.search(ansi_entry)
-- if res:
-- mode = res.group(4)
-- ansi_entry = ansi_entry[len(res.group(0)):]
-- if mode == 'm':
-- code = " ansi" + res.group(1)
-- else:
-- # illegal code, restore the CSI
-- ansi_entry = "\033[" + ansi_entry
--
-+ ansi_entry, ansi_classes = parse_ansi_sgr(ansi_entry)
-+ if ansi_classes:
-+ code = "".join([" ansi" + i for i in ansi_classes])
- html_entries.append(dict(type=_type + code,
- text=ansi_entry,
- is_header=is_header))
-diff --git a/master/buildbot/test/unit/test_util_ansi_codes.py b/master/buildbot/test/unit/test_util_ansi_codes.py
-new file mode 100644
-index 0000000..f9ca02c
---- /dev/null
-+++ b/master/buildbot/test/unit/test_util_ansi_codes.py
-@@ -0,0 +1,39 @@
-+# This file is part of Buildbot. Buildbot is free software: you can
-+# redistribute it and/or modify it under the terms of the GNU General Public
-+# License as published by the Free Software Foundation, version 2.
-+#
-+# This program is distributed in the hope that it will be useful, but WITHOUT
-+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
-+# details.
-+#
-+# You should have received a copy of the GNU General Public License along with
-+# this program; if not, write to the Free Software Foundation, Inc., 51
-+# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-+#
-+# Copyright Buildbot Team Members
-+
-+from twisted.trial import unittest
-+from buildbot.util.ansicodes import parse_ansi_sgr
-+
-+
-+class TestAnsiCodes(unittest.TestCase):
-+
-+ def runTest(self, string, expected):
-+ ret = parse_ansi_sgr(string)
-+ self.assertEqual(ret, expected)
-+
-+ def test_ansi1m(self):
-+ self.runTest("33mfoo", ("foo", ["33"]))
-+
-+ def test_ansi2m(self):
-+ self.runTest("1;33mfoo", ("foo", ["1", "33"]))
-+
-+ def test_ansi5m(self):
-+ self.runTest("1;2;3;4;33mfoo", ("foo", ["1", "2", "3", "4", "33"]))
-+
-+ def test_ansi_notm(self):
-+ self.runTest("33xfoo", ("foo", []))
-+
-+ def test_ansi_invalid(self):
-+ self.runTest("<>foo", ("\033[<>foo", []))
-diff --git a/master/buildbot/util/ansicodes.py b/master/buildbot/util/ansicodes.py
-new file mode 100644
-index 0000000..bc58066
---- /dev/null
-+++ b/master/buildbot/util/ansicodes.py
-@@ -0,0 +1,36 @@
-+# This file is part of Buildbot. Buildbot is free software: you can
-+# redistribute it and/or modify it under the terms of the GNU General Public
-+# License as published by the Free Software Foundation, version 2.
-+#
-+# This program is distributed in the hope that it will be useful, but WITHOUT
-+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
-+# details.
-+#
-+# You should have received a copy of the GNU General Public License along with
-+# this program; if not, write to the Free Software Foundation, Inc., 51
-+# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-+#
-+# Copyright Buildbot Team Members
-+
-+import re
-+
-+ANSI_RE = re.compile(r"^((\d*)(;\d+)*)([a-zA-Z])")
-+
-+
-+def parse_ansi_sgr(ansi_entry):
-+ """simple utility to extract ansi sgr (Select Graphic Rendition) codes,
-+ and ignore other codes.
-+ Invalid codes are restored
-+ """
-+ classes = []
-+ res = ANSI_RE.search(ansi_entry)
-+ if res:
-+ mode = res.group(4)
-+ ansi_entry = ansi_entry[len(res.group(0)):]
-+ if mode == 'm':
-+ classes = res.group(1).split(";")
-+ else:
-+ # illegal code, restore the CSI
-+ ansi_entry = "\033[" + ansi_entry
-+ return ansi_entry, classes
-
- ** https://github.com/buildbot/buildbot/commit/94e11e9888ad3933cd5c3fc61d7e78ed3d0d79d1
-commit 94e11e9888ad3933cd5c3fc61d7e78ed3d0d79d1
-Author: Dustin J. Mitchell <dustin@mozilla.com>
-Date: Thu Sep 26 08:34:36 2013 -0400
-
- render '\e[m' as a class-less SGR; fixes #2565
-
-diff --git a/master/buildbot/test/unit/test_util_ansi_codes.py b/master/buildbot/test/unit/test_util_ansi_codes.py
-deleted file mode 100644
-index bde441f..0000000
---- a/master/buildbot/test/unit/test_util_ansi_codes.py
-+++ /dev/null
-@@ -1,42 +0,0 @@
--# This file is part of Buildbot. Buildbot is free software: you can
--# redistribute it and/or modify it under the terms of the GNU General Public
--# License as published by the Free Software Foundation, version 2.
--#
--# This program is distributed in the hope that it will be useful, but WITHOUT
--# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
--# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
--# details.
--#
--# You should have received a copy of the GNU General Public License along with
--# this program; if not, write to the Free Software Foundation, Inc., 51
--# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
--#
--# Copyright Buildbot Team Members
--
--from twisted.trial import unittest
--from buildbot.util.ansicodes import parse_ansi_sgr
--
--
--class TestAnsiCodes(unittest.TestCase):
--
-- def runTest(self, string, expected):
-- ret = parse_ansi_sgr(string)
-- self.assertEqual(ret, expected)
--
-- def test_ansi1m(self):
-- self.runTest("33mfoo", ("foo", ["33"]))
--
-- def test_ansi2m(self):
-- self.runTest("1;33mfoo", ("foo", ["1", "33"]))
--
-- def test_ansi5m(self):
-- self.runTest("1;2;3;4;33mfoo", ("foo", ["1", "2", "3", "4", "33"]))
--
-- def test_ansi_notm(self):
-- self.runTest("33xfoo", ("foo", []))
--
-- def test_ansi_invalid(self):
-- self.runTest("<>foo", ("\033[<>foo", []))
--
-- def test_ansi_invalid_start_by_semicolon(self):
-- self.runTest(";3m", ("\033[;3m", []))
-diff --git a/master/buildbot/test/unit/test_util_ansicodes.py b/master/buildbot/test/unit/test_util_ansicodes.py
-new file mode 100644
-index 0000000..e0d1926
---- /dev/null
-+++ b/master/buildbot/test/unit/test_util_ansicodes.py
-@@ -0,0 +1,45 @@
-+# This file is part of Buildbot. Buildbot is free software: you can
-+# redistribute it and/or modify it under the terms of the GNU General Public
-+# License as published by the Free Software Foundation, version 2.
-+#
-+# This program is distributed in the hope that it will be useful, but WITHOUT
-+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
-+# details.
-+#
-+# You should have received a copy of the GNU General Public License along with
-+# this program; if not, write to the Free Software Foundation, Inc., 51
-+# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-+#
-+# Copyright Buildbot Team Members
-+
-+from twisted.trial import unittest
-+from buildbot.util.ansicodes import parse_ansi_sgr
-+
-+
-+class TestAnsiCodes(unittest.TestCase):
-+
-+ def runTest(self, string, expected):
-+ ret = parse_ansi_sgr(string)
-+ self.assertEqual(ret, expected)
-+
-+ def test_ansi0m(self):
-+ self.runTest("mfoo", ("foo", []))
-+
-+ def test_ansi1m(self):
-+ self.runTest("33mfoo", ("foo", ["33"]))
-+
-+ def test_ansi2m(self):
-+ self.runTest("1;33mfoo", ("foo", ["1", "33"]))
-+
-+ def test_ansi5m(self):
-+ self.runTest("1;2;3;4;33mfoo", ("foo", ["1", "2", "3", "4", "33"]))
-+
-+ def test_ansi_notm(self):
-+ self.runTest("33xfoo", ("foo", []))
-+
-+ def test_ansi_invalid(self):
-+ self.runTest("<>foo", ("\033[<>foo", []))
-+
-+ def test_ansi_invalid_start_by_semicolon(self):
-+ self.runTest(";3m", ("\033[;3m", []))
-diff --git a/master/buildbot/util/ansicodes.py b/master/buildbot/util/ansicodes.py
-index ebcf95f..01054ae 100644
---- a/master/buildbot/util/ansicodes.py
-+++ b/master/buildbot/util/ansicodes.py
-@@ -15,7 +15,7 @@
-
- import re
-
--ANSI_RE = re.compile(r"^((\d+)(;\d+)*)([a-zA-Z])")
-+ANSI_RE = re.compile(r"^((\d+)(;\d+)*)?([a-zA-Z])")
-
-
- def parse_ansi_sgr(ansi_entry):
-@@ -29,7 +29,11 @@ def parse_ansi_sgr(ansi_entry):
- mode = res.group(4)
- ansi_entry = ansi_entry[len(res.group(0)):]
- if mode == 'm':
-- classes = res.group(1).split(";")
-+ classes = res.group(1)
-+ if classes:
-+ classes = res.group(1).split(";")
-+ else:
-+ classes = []
- else:
- # illegal code, restore the CSI
- ansi_entry = "\033[" + ansi_entry
- ]
-
-
-GitPoller used .split() instead of .split('\n') to split files. As a
-result, it parsed a file with a space in the name as separate files.
-
---- a/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py
-+++ b/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py
-@@ -200,7 +200,7 @@ class GitPoller(base.PollingChangeSource):
- args = ['log', rev, '--name-only', '--no-walk', r'--format=%n']
- d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=os.environ, errortoo=False )
- def process(git_output):
-- fileList = git_output.split()
-+ fileList = git_output.splitlines()
- return fileList
- d.addCallback(process)
- return d
-
-
-Cherry-pick change to allow serialization of sqlite access to de-flake unittests.
-See https://github.com/buildbot/buildbot/commit/2c58b952ae93c45ee5bfe37f6cf70043da8b5d70.
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/db/enginestrategy.py b/third_party/buildbot_8_4p1/buildbot/db/enginestrategy.py
-index 3d05cb6..cb711ce 100644
---- a/third_party/buildbot_8_4p1/buildbot/db/enginestrategy.py
-+++ b/third_party/buildbot_8_4p1/buildbot/db/enginestrategy.py
-@@ -82,6 +82,11 @@ class BuildbotEngineStrategy(strategies.ThreadLocalEngineStrategy):
- kwargs['pool_size'] = 1
- max_conns = 1
-
-+ # allow serializing access to the db
-+ if 'serialize_access' in u.query:
-+ u.query.pop('serialize_access')
-+ max_conns = 1
-+
- return u, kwargs, max_conns
-
- def set_up_sqlite_engine(self, u, engine):
-
-Enable serialized access from the above patch.
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/master.py b/third_party/buildbot_8_4p1/buildbot/master.py
-index cbebc0f..9a1b6d0 100644
---- a/third_party/buildbot_8_4p1/buildbot/master.py
-+++ b/third_party/buildbot_8_4p1/buildbot/master.py
-@@ -315,7 +315,8 @@ class BuildMaster(service.MultiService):
- #change_source = config['change_source']
-
- # optional
-- db_url = config.get("db_url", "sqlite:///state.sqlite")
-+ db_url = config.get("db_url",
-+ "sqlite:///state.sqlite?serialize_access=1")
- db_poll_interval = config.get("db_poll_interval", None)
- debugPassword = config.get('debugPassword')
- manhole = config.get('manhole')
-
-Fix to file parsing. GitPoller produced blank lines
-
---- a/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py
-+++ b/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py
-@@ -200,7 +200,8 @@ class GitPoller(base.PollingChangeSource):
- args = ['log', rev, '--name-only', '--no-walk', r'--format=%n']
- d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=os.environ, errortoo=False )
- def process(git_output):
-- fileList = git_output.splitlines()
-+ fileList = [f.strip() for f in git_output.splitlines()]
-+ fileList = [f for f in fileList if f]
- return fileList
- d.addCallback(process)
- return d
-
-GitPoller: made self.master.addChange call overridable, so it can be used in
-TryJobGit.
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py b/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py
-index 0b6d388..d5c18ba 100644
---- a/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py
-+++ b/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py
-@@ -282,21 +282,31 @@ class GitPoller(base.PollingChangeSource):
- revlink = self.revlinktmpl % urllib.quote_plus(rev)
-
- timestamp, name, files, comments = [ r[1] for r in results ]
-- d = self.master.addChange(
-+ d = self.add_change(
- author=name,
- revision=rev,
- files=files,
- comments=comments,
- when_timestamp=epoch2datetime(timestamp),
-- branch=self.branch,
-- category=self.category,
-- project=self.project,
-- repository=self.repourl,
- revlink=revlink)
- wfd = defer.waitForDeferred(d)
- yield wfd
- results = wfd.getResult()
-
-+ def add_change(self, author, revision, files, comments, when_timestamp,
-+ revlink):
-+ return self.master.addChange(
-+ author=author,
-+ revision=revision,
-+ files=files,
-+ comments=comments,
-+ when_timestamp=when_timestamp,
-+ branch=self.branch,
-+ category=self.category,
-+ project=self.project,
-+ repository=self.repourl,
-+ revlink=revlink)
-+
- def _process_changes_failure(self, f):
- log.msg('gitpoller: repo poll failed')
- log.err(f)
-
-
-Fix broken links to step names with '/' characters in them.
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/build.py b/third_party/buildbot_8_4p1/buildbot/status/web/build.py
-index c634060..711d36a 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/web/build.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/build.py
-@@ -115,7 +115,8 @@ class StatusResourceBuild(HtmlResource):
-
- cxt['steps'].append(step)
-
-- step['link'] = req.childLink("steps/%s" % urllib.quote(s.getName()))
-+ step['link'] = req.childLink("steps/%s" % urllib.quote(s.getName(),
-+ safe=''))
- step['text'] = " ".join(s.getText())
- step['urls'] = map(lambda x:dict(url=x[1],logname=x[0]), s.getURLs().items())
-
-@@ -123,7 +124,7 @@ class StatusResourceBuild(HtmlResource):
- for l in s.getLogs():
- logname = l.getName()
- step['logs'].append({ 'link': req.childLink("steps/%s/logs/%s" %
-- (urllib.quote(s.getName()),
-+ (urllib.quote(s.getName(), safe=''),
- urllib.quote(logname))),
- 'name': logname })
-
-
-Backport BuildsetsConnectComponent.getRecentBuildsets from upstream
-(1ee6d421be2ea814c11757263eb43152f8c3928e).
-
-Index: third_party/buildbot_8_4p1/buildbot/db/buildsets.py
-diff --git a/third_party/buildbot_8_4p1/buildbot/db/buildsets.py b/third_party/buildbot_8_4p1/buildbot/db/buildsets.py
-index e70a51242ca09ba7ec7034e2092f4053c3d0332c..c90af5bec8eb57e5075858623fcc5d59a62eadd7 100644
---- a/third_party/buildbot_8_4p1/buildbot/db/buildsets.py
-+++ b/third_party/buildbot_8_4p1/buildbot/db/buildsets.py
-@@ -190,6 +190,35 @@ class BuildsetsConnectorComponent(base.DBConnectorComponent):
- return [ self._row2dict(row) for row in res.fetchall() ]
- return self.db.pool.do(thd)
-
-+ def getRecentBuildsets(self, count, branch=None, repository=None,
-+ complete=None):
-+ def thd(conn):
-+ bs_tbl = self.db.model.buildsets
-+ ch_tbl = self.db.model.changes
-+ j = sa.join(self.db.model.buildsets,
-+ self.db.model.sourcestamps)
-+ j = j.join(self.db.model.sourcestamp_changes)
-+ j = j.join(ch_tbl)
-+ q = sa.select(columns=[bs_tbl], from_obj=[j],
-+ distinct=True)
-+ q = q.order_by(sa.desc(bs_tbl.c.id))
-+ q = q.limit(count)
-+
-+ if complete is not None:
-+ if complete:
-+ q = q.where(bs_tbl.c.complete != 0)
-+ else:
-+ q = q.where((bs_tbl.c.complete == 0) |
-+ (bs_tbl.c.complete == None))
-+ if branch:
-+ q = q.where(ch_tbl.c.branch == branch)
-+ if repository:
-+ q = q.where(ch_tbl.c.repository == repository)
-+ res = conn.execute(q)
-+ return list(reversed([self._row2dict(row)
-+ for row in res.fetchall()]))
-+ return self.db.pool.do(thd)
-+
- def getBuildsetProperties(self, buildsetid):
- """
- Return the properties for a buildset, in the same format they were
-Index: third_party/buildbot_8_4p1/buildbot/test/unit/test_db_buildsets.py
-diff --git a/third_party/buildbot_8_4p1/buildbot/test/unit/test_db_buildsets.py b/third_party/buildbot_8_4p1/buildbot/test/unit/test_db_buildsets.py
-index a6d4253cae43572992eac8d73b24cead97f63d9e..d28bcc61a72fd875af51c533b4ae189172225041 100644
---- a/third_party/buildbot_8_4p1/buildbot/test/unit/test_db_buildsets.py
-+++ b/third_party/buildbot_8_4p1/buildbot/test/unit/test_db_buildsets.py
-@@ -441,3 +441,91 @@ class TestBuildsetsConnectorComponent(
- d.addCallbacks(cb, eb)
- return d
-
-+ def insert_test_getRecentBuildsets_data(self):
-+ return self.insertTestData([
-+ fakedb.Change(changeid=91, branch='branch_a', repository='repo_a'),
-+ fakedb.SourceStamp(id=91, branch='branch_a', repository='repo_a'),
-+ fakedb.SourceStampChange(sourcestampid=91, changeid=91),
-+
-+ fakedb.Buildset(id=91, sourcestampid=91, complete=0,
-+ complete_at=298297875, results=-1, submitted_at=266761875,
-+ external_idstring='extid', reason='rsn1'),
-+ fakedb.Buildset(id=92, sourcestampid=91, complete=1,
-+ complete_at=298297876, results=7, submitted_at=266761876,
-+ external_idstring='extid', reason='rsn2'),
-+
-+ # buildset unrelated to the change
-+ fakedb.Buildset(id=93, sourcestampid=1, complete=1,
-+ complete_at=298297877, results=7, submitted_at=266761877,
-+ external_idstring='extid', reason='rsn2'),
-+ ])
-+
-+ def test_getRecentBuildsets_all(self):
-+ d = self.insert_test_getRecentBuildsets_data()
-+ d.addCallback(lambda _ :
-+ self.db.buildsets.getRecentBuildsets(2, branch='branch_a',
-+ repository='repo_a'))
-+ def check(bsdictlist):
-+ self.assertEqual(bsdictlist, [
-+ dict(external_idstring='extid', reason='rsn1', sourcestampid=91,
-+ submitted_at=datetime.datetime(1978, 6, 15, 12, 31, 15,
-+ tzinfo=UTC),
-+ complete_at=datetime.datetime(1979, 6, 15, 12, 31, 15,
-+ tzinfo=UTC),
-+ complete=False, results=-1, bsid=91),
-+ dict(external_idstring='extid', reason='rsn2', sourcestampid=91,
-+ submitted_at=datetime.datetime(1978, 6, 15, 12, 31, 16,
-+ tzinfo=UTC),
-+ complete_at=datetime.datetime(1979, 6, 15, 12, 31, 16,
-+ tzinfo=UTC),
-+ complete=True, results=7, bsid=92),
-+ ])
-+ d.addCallback(check)
-+ return d
-+
-+ def test_getRecentBuildsets_one(self):
-+ d = self.insert_test_getRecentBuildsets_data()
-+ d.addCallback(lambda _ :
-+ self.db.buildsets.getRecentBuildsets(1, branch='branch_a',
-+ repository='repo_a'))
-+ def check(bsdictlist):
-+ self.assertEqual(bsdictlist, [
-+ dict(external_idstring='extid', reason='rsn2', sourcestampid=91,
-+ submitted_at=datetime.datetime(1978, 6, 15, 12, 31, 16,
-+ tzinfo=UTC),
-+ complete_at=datetime.datetime(1979, 6, 15, 12, 31, 16,
-+ tzinfo=UTC),
-+ complete=True, results=7, bsid=92),
-+ ])
-+ d.addCallback(check)
-+ return d
-+
-+ def test_getRecentBuildsets_zero(self):
-+ d = self.insert_test_getRecentBuildsets_data()
-+ d.addCallback(lambda _ :
-+ self.db.buildsets.getRecentBuildsets(0, branch='branch_a',
-+ repository='repo_a'))
-+ def check(bsdictlist):
-+ self.assertEqual(bsdictlist, [])
-+ d.addCallback(check)
-+ return d
-+
-+ def test_getRecentBuildsets_noBranchMatch(self):
-+ d = self.insert_test_getRecentBuildsets_data()
-+ d.addCallback(lambda _ :
-+ self.db.buildsets.getRecentBuildsets(2, branch='bad_branch',
-+ repository='repo_a'))
-+ def check(bsdictlist):
-+ self.assertEqual(bsdictlist, [])
-+ d.addCallback(check)
-+ return d
-+
-+ def test_getRecentBuildsets_noRepoMatch(self):
-+ d = self.insert_test_getRecentBuildsets_data()
-+ d.addCallback(lambda _ :
-+ self.db.buildsets.getRecentBuildsets(2, branch='branch_a',
-+ repository='bad_repo'))
-+ def check(bsdictlist):
-+ self.assertEqual(bsdictlist, [])
-+ d.addCallback(check)
-+ return d
-
-Date: Mon Jun 9 15:17:46 2014 -0700
-
-Buildbot fix in triggerable.py
-
-The _waiters field is a dictionary, but it is treated as a list.
-
-R=agable@chromium.org
-BUG=382693
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/schedulers/triggerable.py b/third_party/buildbot_8_4p1/buildbot/schedulers/triggerable.py
-index 0619977..19d6c4d 100644
---- a/third_party/buildbot_8_4p1/buildbot/schedulers/triggerable.py
-+++ b/third_party/buildbot_8_4p1/buildbot/schedulers/triggerable.py
-@@ -62,7 +62,7 @@ class Triggerable(base.BaseScheduler):
- # and errback any outstanding deferreds
- if self._waiters:
- msg = 'Triggerable scheduler stopped before build was complete'
-- for d in self._waiters:
-+ for d in self._waiters.itervalues():
- d.errback(failure.Failure(RuntimeError(msg)))
- self._waiters = {}
-
-
-Date: 2014-07-16 09:52:34 UTC
-Added logging to the status_json.py
-
-BUG=393856
-R=agable@chromium.org
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-index e7cd932d10a0f874175a8b03bd0a9e29436e2278..1047dd55a791479f7be6143bc5992890eb6c4437 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-@@ -22,6 +22,7 @@ import os
- import re
-
- from twisted.internet import defer
-+from twisted.python import log as twlog
- from twisted.web import html, resource, server
-
- from buildbot.status.web.base import HtmlResource
-@@ -162,6 +163,10 @@ class JsonResource(resource.Resource):
-
- def render_GET(self, request):
- """Renders a HTTP GET at the http request level."""
-+ userAgent = request.requestHeaders.getRawHeaders(
-+ 'user-agent', ['unknown'])[0]
-+ twlog.msg('Received request for %s from %s, id: %s' %
-+ (request.uri, userAgent, id(request)))
- d = defer.maybeDeferred(lambda : self.content(request))
- def handle(data):
- if isinstance(data, unicode):
-@@ -183,6 +188,7 @@ class JsonResource(resource.Resource):
- return data
- d.addCallback(handle)
- def ok(data):
-+ twlog.msg('Finished processing request with id: %s' % id(request))
- request.write(data)
- request.finish()
- def fail(f):
-
-Date: Thu Jul 31 07:57:54 2014 -0400
-
- Console page: do revision filtering in the database instead of afterward
-
- Part of a set of changes split out from https://codereview.chromium.org/417513003/
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/db/changes.py b/third_party/buildbot_8_4p1/buildbot/db/changes.py
-index a5b6482..c6516b6 100644
---- a/third_party/buildbot_8_4p1/buildbot/db/changes.py
-+++ b/third_party/buildbot_8_4p1/buildbot/db/changes.py
-@@ -194,7 +194,8 @@ class ChangesConnectorComponent(base.DBConnectorComponent):
- d = self.db.pool.do(thd)
- return d
-
-- def getRecentChanges(self, count):
-+ def getRecentChanges(self, count, repository=None, author=None,
-+ branch=None):
- """
- Get a list of the C{count} most recent changes, represented as
- dictionaies; returns fewer if that many do not exist.
-@@ -207,8 +208,15 @@ class ChangesConnectorComponent(base.DBConnectorComponent):
- # get the changeids from the 'changes' table
- changes_tbl = self.db.model.changes
- q = sa.select([changes_tbl.c.changeid],
-- order_by=[sa.desc(changes_tbl.c.changeid)],
-- limit=count)
-+ order_by=[sa.desc(changes_tbl.c.changeid)])
-+ if repository:
-+ q = q.where(changes_tbl.c.repository == repository)
-+ if author:
-+ q = q.where(changes_tbl.c.author == author)
-+ if branch:
-+ q = q.where(changes_tbl.c.branch == branch)
-+ if count:
-+ q = q.limit(count)
- rp = conn.execute(q)
- changeids = [ row.changeid for row in rp ]
- rp.close()
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/console.py b/third_party/buildbot_8_4p1/buildbot/status/web/console.py
-index de922a4..fd4bd11 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/web/console.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/console.py
-@@ -173,9 +173,10 @@ class ConsoleStatusResource(HtmlResource):
- Every change is a line in the page, and it shows the result of the first
- build with this change for each slave."""
-
-- def __init__(self, orderByTime=False):
-+ def __init__(self, orderByTime=False, repository=None):
- HtmlResource.__init__(self)
-
-+ self.repository = repository
- self.status = None
- self.cache = CacheStatus()
-
-@@ -244,11 +245,11 @@ class ConsoleStatusResource(HtmlResource):
- return allChanges
-
- @defer.deferredGenerator
-- def getAllChanges(self, request, status, debugInfo):
-+ def getAllChanges(self, request, status, debugInfo, **kwargs):
- master = request.site.buildbot_service.master
- limit = min(100, max(1, int(request.args.get('limit', [25])[0])))
- wfd = defer.waitForDeferred(
-- master.db.changes.getRecentChanges(limit))
-+ master.db.changes.getRecentChanges(limit, **kwargs))
- yield wfd
- chdicts = wfd.getResult()
-
-@@ -762,19 +763,14 @@ class ConsoleStatusResource(HtmlResource):
-
- # Get all changes we can find. This is a DB operation, so it must use
- # a deferred.
-- d = self.getAllChanges(request, status, debugInfo)
-+ filter_branch = None if branch == ANYBRANCH else branch
-+ filter_repo = repository if repository else self.repository
-+ filter_author = devName[0] if devName else None
-+ d = self.getAllChanges(request, status, debugInfo, author=filter_author,
-+ branch=filter_branch, repository=filter_repo)
- def got_changes(allChanges):
- debugInfo["source_all"] = len(allChanges)
--
-- revFilter = {}
-- if branch != ANYBRANCH:
-- revFilter['branch'] = branch
-- if devName:
-- revFilter['who'] = devName
-- if repository:
-- revFilter['repository'] = repository
-- revisions = list(self.filterRevisions(allChanges, max_revs=numRevs,
-- filter=revFilter))
-+ revisions = list(self.filterRevisions(allChanges, max_revs=numRevs))
- debugInfo["revision_final"] = len(revisions)
-
- # Fetch all the builds for all builders until we get the next build
-
-Date: Wed Jul 30 12:11:07 2014 -0400
-
- Add optional builder filter function to console page
-
- This is needed for client.skia, where we want to hide trybots from the console.
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/console.py b/third_party/buildbot_8_4p1/buildbot/status/web/console.py
-index fd4bd11..60383f4 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/web/console.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/console.py
-@@ -173,10 +173,12 @@ class ConsoleStatusResource(HtmlResource):
- Every change is a line in the page, and it shows the result of the first
- build with this change for each slave."""
-
-- def __init__(self, orderByTime=False, repository=None):
-+ def __init__(self, orderByTime=False, repository=None,
-+ builder_filter_fn=lambda builderName: True):
- HtmlResource.__init__(self)
-
- self.repository = repository
-+ self.builder_filter_fn = builder_filter_fn
- self.status = None
- self.cache = CacheStatus()
-
-@@ -228,6 +230,8 @@ class ConsoleStatusResource(HtmlResource):
- allChanges = list()
- build_count = 0
- for builderName in status.getBuilderNames()[:]:
-+ if not self.builder_filter_fn(builderName):
-+ continue
- if build_count > max_builds:
- break
-
-@@ -407,6 +411,8 @@ class ConsoleStatusResource(HtmlResource):
- continue
- if builders and builderName not in builders:
- continue
-+ if not self.builder_filter_fn(builderName):
-+ continue
-
- # We want to display this builder.
- category = builder.category or "default"
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/builder.py b/third_party/buildbot_8_4p1/buildbot/status/web/builder.py
-index 4a4f19d..92c9544 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/web/builder.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/builder.py
-@@ -132,7 +132,8 @@ class StatusResourceBuilder(HtmlResource, BuildLineMixin):
-
- cxt['authz'] = self.getAuthz(req)
- cxt['builder_url'] = path_to_builder(req, b)
--
-+ cxt['mastername'] = (
-+ req.site.buildbot_service.master.properties['mastername'])
- template = req.site.buildbot_service.templates.get_template("builder.html")
- yield template.render(**cxt)
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/console.py b/third_party/buildbot_8_4p1/buildbot/status/web/console.py
-index 60383f4..2d48aca 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/web/console.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/console.py
-@@ -77,14 +77,15 @@ class ANYBRANCH: pass # a flag value, used below
-
- class CachedStatusBox(object):
- """Basic data class to remember the information for a box on the console."""
-- def __init__(self, color, pageTitle, details, url, tag, builderName):
-+ def __init__(self, color, pageTitle, details, url, tag, builderName,
-+ buildNumber=None):
- self.color = color
- self.pageTitle = pageTitle
- self.details = details
- self.url = url
- self.tag = tag
- self.builderName = builderName
--
-+ self.buildNumber = buildNumber
-
- class CacheStatus(object):
- """Basic cache of CachedStatusBox based on builder names and revisions.
-@@ -105,9 +106,11 @@ class CacheStatus(object):
- self.allBoxes[builder][revision].color)
- return data
-
-- def insert(self, builderName, revision, color, pageTitle, details, url, tag):
-+ def insert(self, builderName, revision, color, pageTitle, details, url,
-+ tag, buildNumber=None):
- """Insert a new build into the cache."""
-- box = CachedStatusBox(color, pageTitle, details, url, tag, builderName)
-+ box = CachedStatusBox(color, pageTitle, details, url, tag, builderName,
-+ buildNumber)
- if not self.allBoxes.get(builderName):
- self.allBoxes[builderName] = {}
-
-@@ -285,7 +288,7 @@ class ConsoleStatusResource(HtmlResource):
- details = {}
- if not build.getLogs():
- return details
--
-+
- for step in build.getSteps():
- (result, reason) = step.getResults()
- if result == builder.FAILURE:
-@@ -294,7 +297,7 @@ class ConsoleStatusResource(HtmlResource):
- # Remove html tags from the error text.
- stripHtml = re.compile(r'<.*?>')
- strippedDetails = stripHtml.sub('', ' '.join(step.getText()))
--
-+
- details['buildername'] = builderName
- details['status'] = strippedDetails
- details['reason'] = reason
-@@ -304,7 +307,7 @@ class ConsoleStatusResource(HtmlResource):
- for log in step.getLogs():
- logname = log.getName()
- logurl = request.childLink(
-- "../builders/%s/builds/%s/steps/%s/logs/%s" %
-+ "../builders/%s/builds/%s/steps/%s/logs/%s" %
- (urllib.quote(builderName, safe=''),
- build.getNumber(),
- urllib.quote(name, safe=''),
-@@ -371,7 +374,7 @@ class ConsoleStatusResource(HtmlResource):
- def getChangeForBuild(self, build, revision):
- if not build or not build.getChanges(): # Forced build
- return DevBuild(revision, build, None)
--
-+
- for change in build.getChanges():
- if change.revision == revision:
- return change
-@@ -380,7 +383,7 @@ class ConsoleStatusResource(HtmlResource):
- changes = list(build.getChanges())
- changes.sort(key=self.comparator.getSortingKey())
- return changes[-1]
--
-+
- def getAllBuildsForRevision(self, status, request, lastRevision, numBuilds,
- categories, builders, debugInfo):
- """Returns a dictionary of builds we need to inspect to be able to
-@@ -451,9 +454,9 @@ class ConsoleStatusResource(HtmlResource):
-
- categories = builderList.keys()
- categories.sort()
--
-+
- cs = []
--
-+
- for category in categories:
- c = {}
-
-@@ -526,14 +529,14 @@ class ConsoleStatusResource(HtmlResource):
- # Sort the categories.
- categories = builderList.keys()
- categories.sort()
--
-+
- builds = {}
--
-+
- # Display the boxes by category group.
- for category in categories:
--
-+
- builds[category] = []
--
-+
- # Display the boxes for each builder in this category.
- for builder in builderList[category]:
- introducedIn = None
-@@ -549,6 +552,7 @@ class ConsoleStatusResource(HtmlResource):
- b["color"] = cached_value.color
- b["tag"] = cached_value.tag
- b["builderName"] = cached_value.builderName
-+ b["buildNumber"] = cached_value.buildNumber
-
- builds[category].append(b)
-
-@@ -564,7 +568,7 @@ class ConsoleStatusResource(HtmlResource):
- break
- else:
- introducedIn = build
--
-+
- # Get the results of the first build with the revision, and the
- # first build that does not include the revision.
- results = None
-@@ -584,11 +588,13 @@ class ConsoleStatusResource(HtmlResource):
- pageTitle = builder
- tag = ""
- current_details = {}
-+ buildNumber = None
- if introducedIn:
- current_details = introducedIn.details or ""
- url = "./buildstatus?builder=%s&number=%s" % (
- urllib.quote(builder, safe=''),
- introducedIn.number)
-+ buildNumber = introducedIn.number
- pageTitle += " "
- pageTitle += urllib.quote(' '.join(introducedIn.text), ' \n\\/:')
-
-@@ -600,17 +606,17 @@ class ConsoleStatusResource(HtmlResource):
-
- if isRunning:
- pageTitle += ' ETA: %ds' % (introducedIn.eta or 0)
--
-+
- resultsClass = getResultsClass(results, previousResults, isRunning,
- inProgressResults)
-
-- b = {}
-+ b = {}
- b["url"] = url
- b["pageTitle"] = pageTitle
- b["color"] = resultsClass
- b["tag"] = tag
- b["builderName"] = builder
--
-+ b["buildNumber"] = buildNumber
- builds[category].append(b)
-
- # If the box is red, we add the explaination in the details
-@@ -624,7 +630,8 @@ class ConsoleStatusResource(HtmlResource):
- "notstarted"):
- debugInfo["added_blocks"] += 1
- self.cache.insert(builder, revision.revision, resultsClass,
-- pageTitle, current_details, url, tag)
-+ pageTitle, current_details, url, tag,
-+ buildNumber)
-
- return (builds, details)
-
-@@ -685,10 +692,10 @@ class ConsoleStatusResource(HtmlResource):
- # For each revision we show one line
- for revision in revisions:
- r = {}
--
-+
- # Fill the dictionary with this new information
- r['id'] = revision.revision
-- r['link'] = revision.revlink
-+ r['link'] = revision.revlink
- r['who'] = revision.who
- r['date'] = revision.date
- r['comments'] = revision.comments
-@@ -735,7 +742,7 @@ class ConsoleStatusResource(HtmlResource):
- if not reload_time:
- reload_time = 60
-
-- # Append the tag to refresh the page.
-+ # Append the tag to refresh the page.
- if reload_time is not None and reload_time != 0:
- cxt['refresh'] = reload_time
-
-@@ -810,6 +817,8 @@ class ConsoleStatusResource(HtmlResource):
-
- templates = request.site.buildbot_service.templates
- template = templates.get_template("console.html")
-+ cxt['mastername'] = (
-+ request.site.buildbot_service.master.properties['mastername'])
- data = template.render(cxt)
-
- # Clean up the cache.
-@@ -825,9 +834,9 @@ class RevisionComparator(object):
- VCS use a plain counter for revisions (like SVN)
- while others use different concepts (see Git).
- """
--
-+
- # TODO (avivby): Should this be a zope interface?
--
-+
- def isRevisionEarlier(self, first_change, second_change):
- """Used for comparing 2 changes"""
- raise NotImplementedError
-@@ -838,7 +847,7 @@ class RevisionComparator(object):
-
- def getSortingKey(self):
- raise NotImplementedError
--
-+
- class TimeRevisionComparator(RevisionComparator):
- def isRevisionEarlier(self, first, second):
- return first.when < second.when
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/waterfall.py b/third_party/buildbot_8_4p1/buildbot/status/web/waterfall.py
-index 923fe0d..3262d1c 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/web/waterfall.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/waterfall.py
-@@ -172,7 +172,7 @@ class StepBox(components.Adapter):
- text = []
- text = text[:]
- logs = self.original.getLogs()
--
-+
- cxt = dict(text=text, logs=[], urls=[])
-
- for num in range(len(logs)):
-@@ -188,9 +188,12 @@ class StepBox(components.Adapter):
-
- template = req.site.buildbot_service.templates.get_template("box_macros.html")
- text = template.module.step_box(**cxt)
--
-+
- class_ = "BuildStep " + build_get_class(self.original)
-- return Box(text, class_=class_)
-+ return Box(text, class_=class_,
-+ buildNumber=self.original.build_number,
-+ builder=self.original.builder.getName(),
-+ stepName=self.original.getName())
- components.registerAdapter(StepBox, buildstep.BuildStepStatus, IBox)
-
-
-@@ -202,7 +205,7 @@ class EventBox(components.Adapter):
- class_ = "Event"
- return Box(text, class_=class_)
- components.registerAdapter(EventBox, builder.Event, IBox)
--
-+
-
- class Spacer:
- implements(interfaces.IStatusEvent)
-@@ -301,7 +304,7 @@ class WaterfallHelp(HtmlResource):
- times.insert(0, (current_reload_time, current_reload_time) )
-
- cxt['times'] = times
-- cxt['current_reload_time'] = current_reload_time
-+ cxt['current_reload_time'] = current_reload_time
-
- template = request.site.buildbot_service.templates.get_template("waterfallhelp.html")
- return template.render(**cxt)
-@@ -459,10 +462,10 @@ class WaterfallStatusResource(HtmlResource):
- failuresOnly = request.args.get("failures_only", ["false"])[0]
- if failuresOnly.lower() == "true":
- builders = [b for b in builders if not self.isSuccess(b)]
--
-+
- (changeNames, builderNames, timestamps, eventGrid, sourceEvents) = \
- self.buildGrid(request, builders, changes)
--
-+
- # start the table: top-header material
- locale_enc = locale.getdefaultlocale()[1]
- if locale_enc is not None:
-@@ -471,19 +474,20 @@ class WaterfallStatusResource(HtmlResource):
- locale_tz = unicode(time.tzname[time.localtime()[-1]])
- ctx['tz'] = locale_tz
- ctx['changes_url'] = request.childLink("../changes")
--
-+
- bn = ctx['builders'] = []
--
-+
- for name in builderNames:
- builder = status.getBuilder(name)
- top_box = ITopBox(builder).getBox(request)
- current_box = ICurrentBox(builder).getBox(status, brcounts)
- bn.append({'name': name,
-- 'url': request.childLink("../builders/%s" % urllib.quote(name, safe='')),
-- 'top': top_box.text,
-+ 'category': builder.category,
-+ 'url': request.childLink("../builders/%s" % urllib.quote(name, safe='')),
-+ 'top': top_box.text,
- 'top_class': top_box.class_,
- 'status': current_box.text,
-- 'status_class': current_box.class_,
-+ 'status_class': current_box.class_,
- })
-
- ctx.update(self.phase2(request, changeNames + builderNames, timestamps, eventGrid,
-@@ -528,9 +532,11 @@ class WaterfallStatusResource(HtmlResource):
- ctx['no_reload_page'] = with_args(request, remove_args=["reload"])
-
- template = request.site.buildbot_service.templates.get_template("waterfall.html")
-+ ctx['mastername'] = (
-+ request.site.buildbot_service.master.properties['mastername'])
- data = template.render(**ctx)
- return data
--
-+
- def buildGrid(self, request, builders, changes):
- debug = False
- # TODO: see if we can use a cached copy
-@@ -677,21 +683,21 @@ class WaterfallStatusResource(HtmlResource):
-
- if len(timestamps) > maxPageLen:
- break
--
--
-+
-+
- # now loop
--
-+
- # loop is finished. now we have eventGrid[] and timestamps[]
- if debugGather: log.msg("finished loop")
- assert(len(timestamps) == len(eventGrid))
- return (changeNames, builderNames, timestamps, eventGrid, sourceEvents)
--
-+
- def phase2(self, request, sourceNames, timestamps, eventGrid,
- sourceEvents):
-
- if not timestamps:
- return dict(grid=[], gridlen=0)
--
-+
- # first pass: figure out the height of the chunks, populate grid
- grid = []
- for i in range(1+len(sourceNames)):
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/status_push.py b/third_party/buildbot_8_4p1/buildbot/status/status_push.py
-index ca83fdb..af764f1 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/status_push.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/status_push.py
-@@ -36,6 +36,7 @@ from buildbot.status.persistent_queue import DiskQueue, IndexedQueue, \
- from buildbot.status.web.status_json import FilterOut
- from twisted.internet import defer, reactor
- from twisted.python import log
-+from twisted.python.logfile import LogFile
- from twisted.web import client
-
-
-@@ -109,6 +110,9 @@ class StatusPush(StatusReceiverMultiService):
- # Last shutdown was not clean, don't wait to send events.
- self.queueNextServerPush()
-
-+ self.verboseLog = LogFile.fromFullPath(
-+ 'status_push.log', rotateLength=10*1024*1024, maxRotatedFiles=14)
-+
- def startService(self):
- """Starting up."""
- StatusReceiverMultiService.startService(self)
-@@ -390,6 +394,10 @@ class HttpStatusPush(StatusPush):
- # This packet is just too large. Drop this packet.
- log.msg("ERROR: packet %s was dropped, too large: %d > %d" %
- (items[0]['id'], len(data), self.maxHttpRequestSize))
-+ self.verboseLog.write(
-+ "ERROR: packet %s was dropped, too large: %d > %d; %s" %
-+ (items[0]['id'], len(data), self.maxHttpRequestSize,
-+ json.dumps(items, indent=2, sort_keys=True)))
- chunkSize = self.chunkSize
- else:
- # Try with half the packets.
-@@ -405,6 +413,9 @@ class HttpStatusPush(StatusPush):
- def Success(result):
- """Queue up next push."""
- log.msg('Sent %d events to %s' % (len(items), self.serverUrl))
-+ self.verboseLog.write('Sent %d events to %s; %s' % (
-+ len(items), self.serverUrl,
-+ json.dumps(items, indent=2, sort_keys=True)))
- self.lastPushWasSuccessful = True
- return self.queueNextServerPush()
-
-@@ -413,6 +424,9 @@ class HttpStatusPush(StatusPush):
- # Server is now down.
- log.msg('Failed to push %d events to %s: %s' %
- (len(items), self.serverUrl, str(result)))
-+ self.verboseLog.write('Failed to push %d events to %s: %s; %s' %
-+ (len(items), self.serverUrl, str(result),
-+ json.dumps(items, indent=2, sort_keys=True)))
- self.queue.insertBackChunk(items)
- if self.stopped:
- # Bad timing, was being called on shutdown and the server died
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/status_push.py b/third_party/buildbot_8_4p1/buildbot/status/status_push.py
-index af764f1..e8319e0 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/status_push.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/status_push.py
-@@ -51,7 +51,7 @@ class StatusPush(StatusReceiverMultiService):
- """
-
- def __init__(self, serverPushCb, queue=None, path=None, filter=True,
-- bufferDelay=1, retryDelay=5, blackList=None):
-+ bufferDelay=1, retryDelay=5, blackList=None, filterFunc=None):
- """
- @serverPushCb: callback to be used. It receives 'self' as parameter. It
- should call self.queueNextServerPush() when it's done to queue the next
-@@ -67,6 +67,7 @@ class StatusPush(StatusReceiverMultiService):
- @retryDelay: amount of time between retries when no items were pushed on
- last serverPushCb call.
- @blackList: events that shouldn't be sent.
-+ @filterFunc: optional function applied to items added to packet payload
- """
- StatusReceiverMultiService.__init__(self)
-
-@@ -90,6 +91,7 @@ class StatusPush(StatusReceiverMultiService):
- return serverPushCb(self)
- self.serverPushCb = hookPushCb
- self.blackList = blackList
-+ self.filterFunc = filterFunc
-
- # Other defaults.
- # IDelayedCall object that represents the next queued push.
-@@ -234,6 +236,8 @@ class StatusPush(StatusReceiverMultiService):
- obj = obj.asDict()
- if self.filter:
- obj = FilterOut(obj)
-+ if self.filterFunc:
-+ obj = self.filterFunc(obj)
- packet['payload'][obj_name] = obj
- self.queue.pushItem(packet)
- if self.task is None or not self.task.active():
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/process/buildstep.py b/third_party/buildbot_8_4p1/buildbot/process/buildstep.py
-index 9099a08..f5b4f43 100644
---- a/third_party/buildbot_8_4p1/buildbot/process/buildstep.py
-+++ b/third_party/buildbot_8_4p1/buildbot/process/buildstep.py
-@@ -272,6 +272,7 @@ class LoggedRemoteCommand(RemoteCommand):
- def __init__(self, *args, **kwargs):
- self.logs = {}
- self.delayedLogs = {}
-+ self.wasTimeout = False
- self._closeWhenFinished = {}
- RemoteCommand.__init__(self, *args, **kwargs)
-
-@@ -375,6 +376,8 @@ class LoggedRemoteCommand(RemoteCommand):
- self.addHeader("program finished with exit code %d\n" % rc)
- if update.has_key('elapsed'):
- self._remoteElapsed = update['elapsed']
-+ if update.has_key('timeout'):
-+ self.wasTimeout = self.wasTimeout or update['timeout']
-
- for k in update:
- if k not in ('stdout', 'stderr', 'header', 'rc'):
-@@ -1241,6 +1244,8 @@ class LoggingBuildStep(BuildStep):
-
- if self.log_eval_func:
- return self.log_eval_func(cmd, self.step_status)
-+ if getattr(cmd, 'wasTimeout', False):
-+ return EXCEPTION
- if cmd.rc != 0:
- return FAILURE
- return SUCCESS
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/templates/layout.html b/third_party/buildbot_8_4p1/buildbot/status/web/templates/layout.html
-index e988807..3a582ef 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/web/templates/layout.html
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/templates/layout.html
-@@ -60,5 +60,15 @@
- Page built: <b>{{ time }}</b> ({{ tz }})
- </div>
- {% endblock -%}
-+ <script>
-+ (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
-+ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
-+ m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
-+ })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
-+
-+ ga('create', 'UA-55762617-2', 'auto');
-+ ga('send', 'pageview');
-+
-+ </script>
- </body>
- </html>
-diff --git a/third_party/buildbot_8_4p1/buildbot/process/buildstep.py b/third_party/buildbot_8_4p1/buildbot/process/buildstep.py
-index f5b4f43..160a4de 100644
---- a/third_party/buildbot_8_4p1/buildbot/process/buildstep.py
-+++ b/third_party/buildbot_8_4p1/buildbot/process/buildstep.py
-@@ -1198,7 +1198,8 @@ class LoggingBuildStep(BuildStep):
- self.step_status.setText(self.describe(True) +
- ["exception", "slave", "lost"])
- self.step_status.setText2(["exception", "slave", "lost"])
-- return self.finished(RETRY)
-+ # Retry if we're stopping the reactor (resarting the master)
-+ return self.finished(RETRY if reactor._stopped else EXCEPTION)
-
- # to refine the status output, override one or more of the following
- # methods. Change as little as possible: start with the first ones on
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/templates/logs.html b/third_party/buildbot_8_4p1/buildbot/status/web/templates/logs.html
-index 116bbd6..99708b4 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/web/templates/logs.html
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/templates/logs.html
-@@ -4,6 +4,16 @@
- <html>
- <head><title>{{ pageTitle }}</title>
- <link rel="stylesheet" href="{{ path_to_root }}default.css" type="text/css" />
-+ <script>
-+ (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
-+ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
-+ m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
-+ })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
-+
-+ ga('create', 'UA-55762617-2', 'auto');
-+ ga('send', 'pageview');
-+
-+ </script>
- </head>
- <body class='log'>
- <a href="{{ texturl }}">(view as text)</a><br/>
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/templates/layout.html b/third_party/buildbot_8_4p1/buildbot/status/web/templates/layout.html
-index 3a582ef..a8a449e 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/web/templates/layout.html
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/templates/layout.html
-@@ -66,7 +66,7 @@
- m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
- })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
-
-- ga('create', 'UA-55762617-2', 'auto');
-+ ga('create', 'UA-55762617-2', {'siteSpeedSampleRate': 100});
- ga('send', 'pageview');
-
- </script>
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/templates/logs.html b/third_party/buildbot_8_4p1/buildbot/status/web/templates/logs.html
-index 99708b4..bdd35c6 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/web/templates/logs.html
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/templates/logs.html
-@@ -10,7 +10,7 @@
- m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
- })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
-
-- ga('create', 'UA-55762617-2', 'auto');
-+ ga('create', 'UA-55762617-2', {'siteSpeedSampleRate': 100});
- ga('send', 'pageview');
-
- </script>
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/process/botmaster.py b/third_party/buildbot_8_4p1/buildbot/process/botmaster.py
-index 3ac7bbc..7703e57 100644
---- a/third_party/buildbot_8_4p1/buildbot/process/botmaster.py
-+++ b/third_party/buildbot_8_4p1/buildbot/process/botmaster.py
-@@ -602,11 +602,11 @@ class DuplicateSlaveArbitrator(object):
- # slave to connect. If this does not kill it, then we disconnect
- # the new slave.
- self.ping_old_slave_done = False
-+ self.ping_new_slave_done = False
- self.old_slave_connected = True
- self.ping_old_slave(new_tport.getPeer())
-
- # Print a message on the new slave, if possible.
-- self.ping_new_slave_done = False
- self.ping_new_slave()
-
- return self.new_slave_d
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/process/botmaster.py b/third_party/buildbot_8_4p1/buildbot/process/botmaster.py
-index 7703e57..7a83ed7 100644
---- a/third_party/buildbot_8_4p1/buildbot/process/botmaster.py
-+++ b/third_party/buildbot_8_4p1/buildbot/process/botmaster.py
-@@ -634,8 +634,14 @@ class DuplicateSlaveArbitrator(object):
- self.ping_old_slave_timeout = reactor.callLater(self.PING_TIMEOUT, timeout)
- self.ping_old_slave_timed_out = False
-
-- d = self.old_slave.slave.callRemote("print",
-- "master got a duplicate connection from %s; keeping this one" % new_peer)
-+ try:
-+ d = self.old_slave.slave.callRemote(
-+ "print",
-+ "master got a duplicate connection from %s; keeping this one"
-+ % new_peer)
-+ except pb.DeadReferenceError():
-+ timeout()
-+ return
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/master.py b/third_party/buildbot_8_4p1/buildbot/master.py
-index 9a1b6d0..3618b3c 100644
---- a/third_party/buildbot_8_4p1/buildbot/master.py
-+++ b/third_party/buildbot_8_4p1/buildbot/master.py
-@@ -217,16 +217,8 @@ class BuildMaster(service.MultiService):
- bname, number_cancelled_builds))
-
- def noNewBuilds(self):
-- log.msg("stopping schedulers")
-- self.loadConfig_Schedulers([])
-- log.msg("stopping sources")
-- self.loadConfig_Sources([])
-- d = self.cancelAllPendingBuilds()
-- def doneStopping(res):
-- log.msg("new builds stopped")
-- return res
-- d.addCallback(doneStopping)
-- return d
-+ log.msg("stopping build request driver")
-+ return self.botmaster.brd.stopService()
-
- def loadTheConfigFile(self, configFile=None):
- if not configFile:
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/master.py b/third_party/buildbot_8_4p1/buildbot/master.py
-index 3618b3c..d045b1e 100644
---- a/third_party/buildbot_8_4p1/buildbot/master.py
-+++ b/third_party/buildbot_8_4p1/buildbot/master.py
-@@ -995,7 +995,8 @@ class BuildMaster(service.MultiService):
- """
- d = self.db.buildsets.addBuildset(**kwargs)
- def notify((bsid,brids)):
-- log.msg("added buildset %d to database" % bsid)
-+ log.msg("added buildset %d to database (build requests: %s)" %
-+ (bsid, brids))
- # note that buildset additions are only reported on this master
- self._new_buildset_subs.deliver(bsid=bsid, **kwargs)
- # only deliver messages immediately if we're not polling
-diff --git a/third_party/buildbot_8_4p1/buildbot/process/botmaster.py b/third_party/buildbot_8_4p1/buildbot/process/botmaster.py
-index 7a83ed7..0c0bcef 100644
---- a/third_party/buildbot_8_4p1/buildbot/process/botmaster.py
-+++ b/third_party/buildbot_8_4p1/buildbot/process/botmaster.py
-@@ -305,6 +305,7 @@ class BotMaster(service.MultiService):
-
- def startService(self):
- def buildRequestAdded(notif):
-+ log.msg("Processing new build request: %s" % notif)
- self.maybeStartBuildsForBuilder(notif['buildername'])
- self.buildrequest_sub = \
- self.master.subscribeToBuildRequests(buildRequestAdded)
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/slave.py b/third_party/buildbot_8_4p1/buildbot/status/slave.py
-index dbb8aab..21f7fb8 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/slave.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/slave.py
-@@ -54,6 +54,8 @@ class SlaveStatus:
- def getConnectCount(self):
- then = time.time() - 3600
- return len([ t for t in self.connect_times if t > then ])
-+ def getConnectTimes(self):
-+ return self.connect_times
-
- def setAdmin(self, admin):
- self.admin = admin
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-index 9eb0858..1b36daf 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-@@ -27,7 +27,7 @@ from twisted.web import html, resource, server
-
- from buildbot.changes import changes
- from buildbot.status.web.base import HtmlResource
--from buildbot.util import json
-+from buildbot.util import json, now
-
-
- _IS_INT = re.compile('^[-+]?\d+$')
-@@ -82,6 +82,8 @@ EXAMPLES = """\
- - Builder information plus details information about its slaves. Neat eh?
- - /json/slaves/<A_SLAVE>
- - A specific slave.
-+ - /json/buildstate
-+ - The current build state.
- - /json?select=slaves/<A_SLAVE>/&select=project&select=builders/<A_BUILDER>/builds/<A_BUILD>
- - A selection of random unrelated stuff as an random example. :)
- """
-@@ -265,7 +267,7 @@ class JsonResource(resource.Resource):
- if callback:
- # Only accept things that look like identifiers for now
- callback = callback[0]
-- if re.match(r'^[a-zA-Z$][a-zA-Z$0-9.]*$', callback):
-+ if re.match(r'^[_a-zA-Z$][_a-zA-Z$0-9.]*$', callback):
- data = '%s(%s);' % (callback, data)
- yield data
-
-@@ -349,6 +351,7 @@ class HelpResource(HtmlResource):
- HtmlResource.__init__(self)
- self.text = text
- self.pageTitle = pageTitle
-+ self.flags = getattr(parent_node, 'FLAGS', None) or FLAGS
- self.parent_level = parent_node.level
- self.parent_children = parent_node.children.keys()
-
-@@ -356,7 +359,7 @@ class HelpResource(HtmlResource):
- cxt['level'] = self.parent_level
- cxt['text'] = ToHtml(self.text)
- cxt['children'] = [ n for n in self.parent_children if n != 'help' ]
-- cxt['flags'] = ToHtml(FLAGS)
-+ cxt['flags'] = ToHtml(self.flags)
- cxt['examples'] = ToHtml(EXAMPLES).replace(
- 'href="/json',
- 'href="%sjson' % (self.level * '../'))
-@@ -745,6 +748,204 @@ class MetricsJsonResource(JsonResource):
- return None
-
-
-+class BuildStateJsonResource(JsonResource):
-+ help = ('Holistic build state JSON endpoint.\n\n'
-+ 'This endpoint is a fast (unless otherwise noted) and '
-+ 'comprehensive source for full BuildBot state queries. Any '
-+ 'augmentations to this endpoint MUST keep this in mind.')
-+
-+ pageTitle = 'Build State JSON'
-+
-+ # Keyword for 'completed_builds' to indicate that all cached builds should
-+ # be returned.
-+ CACHED = 'cached'
-+
-+ EXTRA_FLAGS = """\
-+ - builder
-+ - A builder name to explicitly include in the results. This can be supplied
-+ multiple times. If omitted, data for all builders will be returned.
-+ - current_builds
-+ - Controls whether the builder's current-running build data will be
-+ returned. By default, no current build data will be returned; setting
-+ current_builds=1 will enable this.
-+ - completed_builds
-+ - Controls whether the builder's completed build data will be returned. By
-+ default, no completed build data will be retured. Setting
-+ completed_builds=cached will return build data for all cached builds.
-+ Setting it to a positive integer 'N' (e.g., completed_builds=3) will cause
-+ data for the latest 'N' completed builds to be returned.
-+ - pending_builds
-+ - Controls whether the builder's pending build data will be
-+ returned. By default, no pending build data will be returned; setting
-+ pending_builds=1 will enable this.
-+ - slaves
-+ - Controls whether the builder's slave data will be returned. By default, no
-+ slave build data will be returned; setting slaves=1 will enable this.
-+"""
-+ def __init__(self, status):
-+ JsonResource.__init__(self, status)
-+ self.FLAGS = FLAGS + self.EXTRA_FLAGS
-+
-+ self.putChild('project', ProjectJsonResource(status))
-+
-+ @classmethod
-+ def _CountOrCachedRequestArg(cls, request, arg):
-+ value = RequestArg(request, arg, 0)
-+ if value == cls.CACHED:
-+ return value
-+ try:
-+ value = int(value)
-+ except ValueError:
-+ return 0
-+ return max(0, value)
-+
-+ @defer.deferredGenerator
-+ def asDict(self, request):
-+ builders = request.args.get('builder', ())
-+ current_builds = RequestArgToBool(request, 'current_builds', False)
-+ completed_builds = self._CountOrCachedRequestArg(request,
-+ 'completed_builds')
-+ pending_builds = RequestArgToBool(request, 'pending_builds', False)
-+ slaves = RequestArgToBool(request, 'slaves', False)
-+
-+ builder_names = self.status.getBuilderNames()
-+ if builders:
-+ builder_names = [b for b in builder_names
-+ if b in set(builders)]
-+
-+ # Collect child endpoint data.
-+ wfd = defer.waitForDeferred(
-+ defer.maybeDeferred(JsonResource.asDict, self, request))
-+ yield wfd
-+ response = wfd.getResult()
-+
-+ # Collect builder data.
-+ wfd = defer.waitForDeferred(
-+ defer.gatherResults(
-+ [self._getBuilderData(self.status.getBuilder(builder_name),
-+ current_builds, completed_builds,
-+ pending_builds)
-+ for builder_name in builder_names]))
-+ yield wfd
-+ response['builders'] = wfd.getResult()
-+
-+ # Add slave data.
-+ if slaves:
-+ response['slaves'] = self._getAllSlavesData()
-+
-+ # Add timestamp and return.
-+ response['timestamp'] = now()
-+ yield response
-+
-+ @defer.deferredGenerator
-+ def _getBuilderData(self, builder, current_builds, completed_builds,
-+ pending_builds):
-+ # Load the builder dictionary. We use the synchronous path, since the
-+ # asynchronous waits for pending builds to load. We handle that path
-+ # explicitly via the 'pending_builds' option.
-+ #
-+ # This also causes the cache to be updated with recent builds, so we
-+ # will call it first.
-+ response = builder.asDict()
-+ tasks = []
-+
-+ # Get current/completed builds.
-+ if current_builds or completed_builds:
-+ tasks.append(
-+ defer.maybeDeferred(self._loadBuildData, builder,
-+ current_builds, completed_builds))
-+
-+ # Get pending builds.
-+ if pending_builds:
-+ tasks.append(
-+ self._loadPendingBuildData(builder))
-+
-+ # Collect a set of build data dictionaries to combine.
-+ wfd = defer.waitForDeferred(
-+ defer.gatherResults(tasks))
-+ yield wfd
-+ build_data_entries = wfd.getResult()
-+
-+ # Construct our build data from the various task entries.
-+ build_state = response.setdefault('buildState', {})
-+ for build_data_entry in build_data_entries:
-+ build_state.update(build_data_entry)
-+ yield response
-+
-+ def _loadBuildData(self, builder, current_builds, completed_builds):
-+ build_state = {}
-+ builds = set()
-+ build_data_entries = []
-+
-+ current_build_numbers = set(b.getNumber()
-+ for b in builder.currentBuilds)
-+ if current_builds:
-+ builds.update(current_build_numbers)
-+ build_data_entries.append(('current', current_build_numbers))
-+
-+ if completed_builds:
-+ if completed_builds == self.CACHED:
-+ build_numbers = set(builder.buildCache.cache.keys())
-+ build_numbers.difference_update(current_build_numbers)
-+ else:
-+ build_numbers = []
-+ candidate = -1
-+ while len(build_numbers) < completed_builds:
-+ build_number = builder._resolveBuildNumber(candidate)
-+ if not build_number:
-+ break
-+
-+ candidate -= 1
-+ if build_number in current_build_numbers:
-+ continue
-+ build_numbers.append(build_number)
-+ builds.update(build_numbers)
-+ build_data_entries.append(('completed', build_numbers))
-+
-+ # Load all builds referenced by 'builds'.
-+ builds = builder.getBuilds(builds)
-+ build_map = dict((build_dict['number'], build_dict)
-+ for build_dict in [build.asDict()
-+ for build in builds
-+ if build])
-+
-+ # Map the collected builds to their repective keys. This dictionary
-+ # takes the form: Build# => BuildDict.
-+ for key, build_numbers in build_data_entries:
-+ build_state[key] = dict((number, build_map.get(number, {}))
-+ for number in build_numbers)
-+ return build_state
-+
-+ def _loadPendingBuildData(self, builder):
-+ d = builder.getPendingBuildRequestStatuses()
-+
-+ def cb_load_status(statuses):
-+ statuses.sort(key=lambda s: s.getSubmitTime())
-+ return defer.gatherResults([status.asDict_async()
-+ for status in statuses])
-+ d.addCallback(cb_load_status)
-+
-+ def cb_collect(status_dicts):
-+ return {
-+ 'pending': status_dicts,
-+ }
-+ d.addCallback(cb_collect)
-+
-+ return d
-+
-+ def _getAllSlavesData(self):
-+ return dict((slavename, self._getSlaveData(slavename))
-+ for slavename in self.status.getSlaveNames())
-+
-+ def _getSlaveData(self, slavename):
-+ slave = self.status.getSlave(slavename)
-+ return {
-+ 'host': slave.getHost(),
-+ 'connected': slave.isConnected(),
-+ 'connect_times': slave.getConnectTimes(),
-+ 'last_message_received': slave.lastMessageReceived(),
-+ }
-+
-
- class JsonStatusResource(JsonResource):
- """Retrieves all json data."""
-@@ -766,6 +967,7 @@ For help on any sub directory, use url /child/help
- self.putChild('project', ProjectJsonResource(status))
- self.putChild('slaves', SlavesJsonResource(status))
- self.putChild('metrics', MetricsJsonResource(status))
-+ self.putChild('buildstate', BuildStateJsonResource(status))
- # This needs to be called before the first HelpResource().body call.
- self.hackExamples()
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/build.py b/third_party/buildbot_8_4p1/buildbot/status/build.py
-index ab3150f..723f6ad 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/build.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/build.py
-@@ -433,29 +433,36 @@ class BuildStatus(styles.Versioned):
- self.number))
- log.err()
-
-- def asDict(self):
-+ def asDict(self, blame=True, logs=True, properties=True, steps=True,
-+ sourceStamp=True, eta=True):
- result = {}
- # Constant
- result['builderName'] = self.builder.name
- result['number'] = self.getNumber()
-- result['sourceStamp'] = self.getSourceStamp().asDict()
-+ if sourceStamp:
-+ result['sourceStamp'] = self.getSourceStamp().asDict()
- result['reason'] = self.getReason()
-- result['blame'] = self.getResponsibleUsers()
-+ if blame:
-+ result['blame'] = self.getResponsibleUsers()
-
- # Transient
-- result['properties'] = self.getProperties().asList()
-+ if properties:
-+ result['properties'] = self.getProperties().asList()
- result['times'] = self.getTimes()
- result['text'] = self.getText()
- result['results'] = self.getResults()
- result['slave'] = self.getSlavename()
- # TODO(maruel): Add.
- #result['test_results'] = self.getTestResults()
-- result['logs'] = [[l.getName(),
-- self.builder.status.getURLForThing(l)] for l in self.getLogs()]
-- result['eta'] = self.getETA()
-- result['steps'] = [bss.asDict() for bss in self.steps]
-- if self.getCurrentStep():
-- result['currentStep'] = self.getCurrentStep().asDict()
-- else:
-- result['currentStep'] = None
-+ if logs:
-+ result['logs'] = [[l.getName(),
-+ self.builder.status.getURLForThing(l)] for l in self.getLogs()]
-+ if eta:
-+ result['eta'] = self.getETA()
-+ if steps:
-+ result['steps'] = [bss.asDict() for bss in self.steps]
-+ if self.getCurrentStep():
-+ result['currentStep'] = self.getCurrentStep().asDict()
-+ else:
-+ result['currentStep'] = None
- return result
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-index 1b36daf..9fe46ef 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-@@ -760,6 +760,14 @@ class BuildStateJsonResource(JsonResource):
- # be returned.
- CACHED = 'cached'
-
-+ # Specific build fields that will be stripped unless requested. These map
-+ # user-specified query parameters (lowercase) to
-+ # buildbot.status.Build.asDict keyword arguments.
-+ BUILD_FIELDS = ('blame', 'logs', 'sourceStamp', 'properties', 'steps',
-+ 'eta',)
-+ # Special build field to indicate all fields should be returned.
-+ BUILD_FIELDS_ALL = 'all'
-+
- EXTRA_FLAGS = """\
- - builder
- - A builder name to explicitly include in the results. This can be supplied
-@@ -771,20 +779,32 @@ class BuildStateJsonResource(JsonResource):
- - completed_builds
- - Controls whether the builder's completed build data will be returned. By
- default, no completed build data will be retured. Setting
-- completed_builds=cached will return build data for all cached builds.
-- Setting it to a positive integer 'N' (e.g., completed_builds=3) will cause
-- data for the latest 'N' completed builds to be returned.
-+ completed_builds=%(completed_builds_cached)s will return build data for
-+ all cached builds. Setting it to a positive integer 'N' (e.g.,
-+ completed_builds=3) will cause data for the latest 'N' completed builds to
-+ be returned.
- - pending_builds
- - Controls whether the builder's pending build data will be
- returned. By default, no pending build data will be returned; setting
- pending_builds=1 will enable this.
-+ - build_field
-+ - The specific build fields to include. Collecting and packaging more fields
-+ will take more time. This can be supplied multiple times to request more
-+ than one field. If '%(build_fields_all)s' is supplied, all build fields
-+ will be included. Available individual fields are: %(build_fields)s.
- - slaves
- - Controls whether the builder's slave data will be returned. By default, no
- slave build data will be returned; setting slaves=1 will enable this.
- """
- def __init__(self, status):
- JsonResource.__init__(self, status)
-- self.FLAGS = FLAGS + self.EXTRA_FLAGS
-+ context = {
-+ 'completed_builds_cached': self.CACHED,
-+ 'build_fields_all': self.BUILD_FIELDS_ALL,
-+ 'build_fields': ', '.join(sorted([f.lower()
-+ for f in self.BUILD_FIELDS])),
-+ }
-+ self.FLAGS = FLAGS + self.EXTRA_FLAGS % context
-
- self.putChild('project', ProjectJsonResource(status))
-
-@@ -802,6 +822,7 @@ class BuildStateJsonResource(JsonResource):
- @defer.deferredGenerator
- def asDict(self, request):
- builders = request.args.get('builder', ())
-+ build_fields = request.args.get('build_field', ())
- current_builds = RequestArgToBool(request, 'current_builds', False)
- completed_builds = self._CountOrCachedRequestArg(request,
- 'completed_builds')
-@@ -824,7 +845,7 @@ class BuildStateJsonResource(JsonResource):
- defer.gatherResults(
- [self._getBuilderData(self.status.getBuilder(builder_name),
- current_builds, completed_builds,
-- pending_builds)
-+ pending_builds, build_fields)
- for builder_name in builder_names]))
- yield wfd
- response['builders'] = wfd.getResult()
-@@ -839,7 +860,7 @@ class BuildStateJsonResource(JsonResource):
-
- @defer.deferredGenerator
- def _getBuilderData(self, builder, current_builds, completed_builds,
-- pending_builds):
-+ pending_builds, build_fields):
- # Load the builder dictionary. We use the synchronous path, since the
- # asynchronous waits for pending builds to load. We handle that path
- # explicitly via the 'pending_builds' option.
-@@ -853,7 +874,8 @@ class BuildStateJsonResource(JsonResource):
- if current_builds or completed_builds:
- tasks.append(
- defer.maybeDeferred(self._loadBuildData, builder,
-- current_builds, completed_builds))
-+ current_builds, completed_builds,
-+ build_fields))
-
- # Get pending builds.
- if pending_builds:
-@@ -872,7 +894,8 @@ class BuildStateJsonResource(JsonResource):
- build_state.update(build_data_entry)
- yield response
-
-- def _loadBuildData(self, builder, current_builds, completed_builds):
-+ def _loadBuildData(self, builder, current_builds, completed_builds,
-+ build_fields):
- build_state = {}
- builds = set()
- build_data_entries = []
-@@ -902,10 +925,18 @@ class BuildStateJsonResource(JsonResource):
- builds.update(build_numbers)
- build_data_entries.append(('completed', build_numbers))
-
-+ # Determine which build fields to request.
-+ build_fields = [f.lower() for f in build_fields]
-+ build_field_kwargs = {}
-+ if self.BUILD_FIELDS_ALL not in build_fields:
-+ build_field_kwargs.update(dict(
-+ (kwarg, kwarg.lower() in build_fields)
-+ for kwarg in self.BUILD_FIELDS))
-+
- # Load all builds referenced by 'builds'.
- builds = builder.getBuilds(builds)
- build_map = dict((build_dict['number'], build_dict)
-- for build_dict in [build.asDict()
-+ for build_dict in [build.asDict(**build_field_kwargs)
- for build in builds
- if build])
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-index 9fe46ef..6c701ad 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-@@ -868,6 +868,7 @@ class BuildStateJsonResource(JsonResource):
- # This also causes the cache to be updated with recent builds, so we
- # will call it first.
- response = builder.asDict()
-+ response['builderName'] = builder.getName()
- tasks = []
-
- # Get current/completed builds.
-
-Trim long comment fields to 1024 characters, but preserve important tags
-
-BUG=407345
-R=agable@chromium.org,iannucci@chromium.org
-
-Index: third_party/buildbot_8_4p1/buildbot/db/changes.py
-diff --git a/third_party/buildbot_8_4p1/buildbot/db/changes.py b/third_party/buildbot_8_4p1/buildbot/db/changes.py
-index c6516b6fd26c86bedee27d35fbaeca667ae5697a..eff93e417e28db0e4a57c592e1cb9c7047433017 100644
---- a/third_party/buildbot_8_4p1/buildbot/db/changes.py
-+++ b/third_party/buildbot_8_4p1/buildbot/db/changes.py
-@@ -127,10 +127,17 @@ class ChangesConnectorComponent(base.DBConnectorComponent):
-
- transaction = conn.begin()
-
-+ # Trim long comment fields to 1024 characters, but preserve header
-+ # and footer with important tags such as Cr-Commit-Position.
-+ trimmed_comments = comments
-+ if len(trimmed_comments) > 1024:
-+ header, footer = trimmed_comments[:506], trimmed_comments[-506:]
-+ trimmed_comments = '%s\n...skip...\n%s' % (header, footer)
-+
- ins = self.db.model.changes.insert()
- r = conn.execute(ins, dict(
- author=author,
-- comments=comments[:1024],
-+ comments=trimmed_comments,
- is_dir=is_dir,
- branch=branch,
- revision=revision,
-Index: third_party/buildbot_8_4p1/buildbot/db/migrate/versions/001_initial.py
-diff --git a/third_party/buildbot_8_4p1/buildbot/db/migrate/versions/001_initial.py b/third_party/buildbot_8_4p1/buildbot/db/migrate/versions/001_initial.py
-index 875c5ea0d6f33190c53a36a1e4790d441ef12493..c51a06f5f0769ddafe6fc184d859b6080fcacde7 100644
---- a/third_party/buildbot_8_4p1/buildbot/db/migrate/versions/001_initial.py
-+++ b/third_party/buildbot_8_4p1/buildbot/db/migrate/versions/001_initial.py
-@@ -213,10 +213,17 @@ def import_changes(migrate_engine):
- if not c.revision:
- continue
- try:
-+ # Trim long comment fields to 1024 characters, but preserve header
-+ # and footer with important tags such as Cr-Commit-Position.
-+ trimmed_comments = c.comments
-+ if len(trimmed_comments) > 1024:
-+ header, footer = trimmed_comments[:506], trimmed_comments[-506:]
-+ trimmed_comments = '%s\n...skip...\n%s' % (header, footer)
-+
- values = dict(
- changeid=c.number,
- author=c.who[:256],
-- comments=c.comments[:1024],
-+ comments=trimmed_comments,
- is_dir=c.isdir,
- branch=c.branch[:256],
- revision=c.revision[:256],
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-index 6c701ad..36f8806 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-@@ -18,6 +18,7 @@
-
- import collections
- import datetime
-+import fnmatch
- import os
- import re
-
-@@ -772,6 +773,7 @@ class BuildStateJsonResource(JsonResource):
- - builder
- - A builder name to explicitly include in the results. This can be supplied
- multiple times. If omitted, data for all builders will be returned.
-+ Globbing via asterisk is permitted (e.g., "*_rel")
- - current_builds
- - Controls whether the builder's current-running build data will be
- returned. By default, no current build data will be returned; setting
-@@ -831,8 +833,10 @@ class BuildStateJsonResource(JsonResource):
-
- builder_names = self.status.getBuilderNames()
- if builders:
-+ builder_regex = re.compile('|'.join(fnmatch.translate(b)
-+ for b in builders))
- builder_names = [b for b in builder_names
-- if b in set(builders)]
-+ if builder_regex.match(b)]
-
- # Collect child endpoint data.
- wfd = defer.waitForDeferred(
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-index 36f8806..36b1899 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-@@ -678,7 +678,7 @@ class SlaveJsonResource(JsonResource):
- for build_status in builds:
- if not build_status or not build_status.isFinished():
- # If not finished, it will appear in runningBuilds.
-- break
-+ continue
- slave = buildcache[build_status.getSlavename()]
- slave.setdefault(builderName, []).append(
- build_status.getNumber())
-
-
-commit c956c1bf5b7c3726d80675d311e0a61d6d3f759c
-Author: Ryan Kubiak <rkubiak@google.com>
-Date: Thu Jan 22 17:19:49 2015 -0800
-
- Fix MailNotifier bug for buildsets
-
- Implementing a fix from buildbot 0.8.6p1.
- http://trac.buildbot.net/ticket/2254
- https://github.com/buildbot/buildbot/commit/4d3b0318025ca38c148a09e2fec5f2f97f61be57
-
- I wasn't entirely sure of the implications of removing self.db from
- status/master.py. I found two files using status.master.db
- (buildbot/status/builder.py, buildbot/status/web/status_json.py).
- Neither of these are imports and they both seem to still work by
- inherting status from their parent.
-
- I've run this patch on a testing instance and everything seems to still
- work, although I have not tested the MailNotifier.
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/mail.py b/third_party/buildbot_8_4p1/buildbot/status/mail.py
-index fd62b52..c03f609 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/mail.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/mail.py
-@@ -332,12 +332,13 @@ class MailNotifier(base.StatusReceiverMultiService):
- def setup(self):
- self.master_status = self.parent.getStatus()
- self.master_status.subscribe(self)
-+ self.master = self.master_status.master
-
-
- def startService(self):
- if self.buildSetSummary:
- self.buildSetSubscription = \
-- self.parent.subscribeToBuildsetCompletions(self.buildsetFinished)
-+ self.master.subscribeToBuildsetCompletions(self.buildsetFinished)
-
- base.StatusReceiverMultiService.startService(self)
-
-@@ -428,18 +429,18 @@ class MailNotifier(base.StatusReceiverMultiService):
- for breq in breqs:
- buildername = breq['buildername']
- builders.append(self.master_status.getBuilder(buildername))
-- d = self.parent.db.builds.getBuildsForRequest(breq['brid'])
-+ d = self.master.db.builds.getBuildsForRequest(breq['brid'])
- d.addCallback(builddicts.append)
- dl.append(d)
- d = defer.DeferredList(dl)
- d.addCallback(self._gotBuilds, builddicts, buildset, builders)
-
- def _gotBuildSet(self, buildset, bsid):
-- d = self.parent.db.buildrequests.getBuildRequests(bsid=bsid)
-+ d = self.master.db.buildrequests.getBuildRequests(bsid=bsid)
- d.addCallback(self._gotBuildRequests, buildset)
-
- def buildsetFinished(self, bsid, result):
-- d = self.parent.db.buildsets.getBuildset(bsid=bsid)
-+ d = self.master.db.buildsets.getBuildset(bsid=bsid)
- d.addCallback(self._gotBuildSet, bsid)
-
- return d
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/master.py b/third_party/buildbot_8_4p1/buildbot/status/master.py
-index 1df47a0..7864e2a 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/master.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/master.py
-@@ -34,7 +34,6 @@ class Status:
- def __init__(self, master):
- self.master = master
- self.botmaster = master.botmaster
-- self.db = None
- self.basedir = master.basedir
- self.watchers = []
- # compress logs bigger than 4k, a good default on linux
-diff --git a/third_party/buildbot_8_4p1/buildbot/test/unit/test_status_mail_MailNotifier.py b/third_party/buildbot_8_4p1/buildbot/test/unit/test_status_mail_MailNotifier.py
-index 3c8a70b..3ee4f49 100644
---- a/third_party/buildbot_8_4p1/buildbot/test/unit/test_status_mail_MailNotifier.py
-+++ b/third_party/buildbot_8_4p1/buildbot/test/unit/test_status_mail_MailNotifier.py
-@@ -140,7 +140,7 @@ class TestMailNotifier(unittest.TestCase):
- buildername='Builder'),
- fakedb.Build(number=0, brid=11, results=SUCCESS)
- ])
-- mn.parent = self
-+ mn.master = self # FIXME: Should be FakeMaster
-
- self.status = Mock()
- mn.master_status = Mock()
-
-
-commit eabc3bfef4de0456ef66a3eecac1689b4c18b4ea
-Author: Ryan Kubiak <rkubiak@google.com>
-Date: Mon Jan 26 12:10:52 2015 -0800
-
- Fix MailNotifier startup when buildsets are enabled
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/mail.py b/third_party/buildbot_8_4p1/buildbot/status/mail.py
-index c03f609..cd86e86 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/mail.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/mail.py
-@@ -327,22 +327,20 @@ class MailNotifier(base.StatusReceiverMultiService):
- @type parent: L{buildbot.master.BuildMaster}
- """
- base.StatusReceiverMultiService.setServiceParent(self, parent)
-- self.setup()
-
- def setup(self):
- self.master_status = self.parent.getStatus()
- self.master_status.subscribe(self)
- self.master = self.master_status.master
-
--
- def startService(self):
-+ self.setup()
- if self.buildSetSummary:
- self.buildSetSubscription = \
- self.master.subscribeToBuildsetCompletions(self.buildsetFinished)
-
- base.StatusReceiverMultiService.startService(self)
-
--
- def stopService(self):
- if self.buildSetSubscription is not None:
- self.buildSetSubscription.unsubscribe()
-
-
-commit 0832eba0b0d223ef028a84cd93956addc9173b0e
-Author: Mike Stipicevic <stip@chromium.org>
-Date: Wed Feb 11 17:23:44 2015 -0800
-
- Add server start time, current time, and uptime to json endpoint.
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-index 36b1899..ff2758f 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-@@ -31,6 +31,22 @@ from buildbot.status.web.base import HtmlResource
- from buildbot.util import json, now
-
-
-+def get_timeblock():
-+ def dt_to_ts(date):
-+ return (date - datetime.datetime.utcfromtimestamp(0)).total_seconds()
-+
-+ utcnow = datetime.datetime.utcnow()
-+
-+ return {
-+ 'local': datetime.datetime.now().isoformat(),
-+ 'utc': utcnow.isoformat(),
-+ 'utc_ts': dt_to_ts(utcnow),
-+ }
-+
-+
-+SERVER_STARTED = get_timeblock()
-+
-+
- _IS_INT = re.compile('^[-+]?\d+$')
-
-
-@@ -983,6 +999,23 @@ class BuildStateJsonResource(JsonResource):
- }
-
-
-+class MasterClockResource(JsonResource):
-+ help = """Show current time, boot time and uptime of the master."""
-+ pageTitle = 'MasterClock'
-+
-+ def asDict(self, _request):
-+ # The only reason we include local time is because the buildbot UI
-+ # displays it. Any computations on time should be done in UTC.
-+ current_timeblock = get_timeblock()
-+
-+ return {
-+ 'server_started': SERVER_STARTED,
-+ 'current': current_timeblock,
-+ 'server_uptime': (
-+ current_timeblock['utc_ts'] - SERVER_STARTED['utc_ts'])
-+ }
-+
-+
- class JsonStatusResource(JsonResource):
- """Retrieves all json data."""
- help = """JSON status
-@@ -1003,6 +1036,7 @@ For help on any sub directory, use url /child/help
- self.putChild('project', ProjectJsonResource(status))
- self.putChild('slaves', SlavesJsonResource(status))
- self.putChild('metrics', MetricsJsonResource(status))
-+ self.putChild('clock', MasterClockResource(status))
- self.putChild('buildstate', BuildStateJsonResource(status))
- # This needs to be called before the first HelpResource().body call.
- self.hackExamples()
-
-commit 343c20bf0aff6a5240670a415e3ee41d7c488e8a
-Author: Mike Stipicevic <stip@chromium.org>
-Date: Fri Feb 20 14:44:41 2015 -0800
-
- Record submittedAt as a buildproperty.
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/process/build.py b/third_party/buildbot_8_4p1/buildbot/process/build.py
-index 04cf472..3376b5b 100644
---- a/third_party/buildbot_8_4p1/buildbot/process/build.py
-+++ b/third_party/buildbot_8_4p1/buildbot/process/build.py
-@@ -60,11 +60,15 @@ class Build:
- finished = False
- results = None
- stopped = False
-+ requestedAt = None
-
- def __init__(self, requests):
- self.requests = requests
- self.locks = []
-
-+ self.requestedAt = (
-+ sorted(r.submittedAt for r in requests) or [None])[0]
-+
- # build a source stamp
- self.source = requests[0].mergeWith(requests[1:])
- self.reason = requests[0].mergeReasons(requests[1:])
-@@ -183,6 +187,7 @@ class Build:
- props.setProperty("revision", self.source.revision, "Build")
- props.setProperty("repository", self.source.repository, "Build")
- props.setProperty("project", self.source.project, "Build")
-+ props.setProperty("requestedAt", self.requestedAt, "Build")
- self.builder.setupProperties(props)
-
- def setupSlaveBuilder(self, slavebuilder):
-
-
-commit d543bc773c6066d1a06ccd147020384429f67c57
-Author: Mike Stipicevic <stip@chromium.org>
-Date: Wed Feb 25 15:16:54 2015 -0800
-
- Report no-new-builds status.
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-index ff2758f..8469c25 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-@@ -1016,6 +1016,16 @@ class MasterClockResource(JsonResource):
- }
-
-
-+class AcceptingBuildsResource(JsonResource):
-+ help = """Show whether the master is scheduling new builds."""
-+ pageTitle = 'AcceptingBuilds'
-+
-+ def asDict(self, _request):
-+ return {
-+ 'accepting_builds': bool(self.status.master.botmaster.brd.running),
-+ }
-+
-+
- class JsonStatusResource(JsonResource):
- """Retrieves all json data."""
- help = """JSON status
-@@ -1037,6 +1047,7 @@ For help on any sub directory, use url /child/help
- self.putChild('slaves', SlavesJsonResource(status))
- self.putChild('metrics', MetricsJsonResource(status))
- self.putChild('clock', MasterClockResource(status))
-+ self.putChild('accepting_builds', AcceptingBuildsResource(status))
- self.putChild('buildstate', BuildStateJsonResource(status))
- # This needs to be called before the first HelpResource().body call.
- self.hackExamples()
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-index 8469c25..531199b 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-@@ -876,6 +876,8 @@ class BuildStateJsonResource(JsonResource):
-
- # Add timestamp and return.
- response['timestamp'] = now()
-+ response['accepting_builds'] = bool(
-+ self.status.master.botmaster.brd.running)
- yield response
-
- @defer.deferredGenerator
-
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/builder.py b/third_party/buildbot_8_4p1/buildbot/status/web/builder.py
-index 92c9544..f9d5b82 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/web/builder.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/builder.py
-@@ -155,6 +155,10 @@ class StatusResourceBuilder(HtmlResource, BuildLineMixin):
- if not self.getAuthz(req).actionAllowed('forceBuild', req, self.builder_status):
- log.msg("..but not authorized")
- return Redirect(path_to_authfail(req))
-+ # ensure that they've filled out the username field at least.
-+ if name == "<unknown>":
-+ log.msg("..but didn't include a username to blame")
-+ return Redirect(path_to_authfail(req))
-
- # keep weird stuff out of the branch revision, and property strings.
- # TODO: centralize this somewhere.
-
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/templates/buildslave.html b/third_party/buildbot_8_4p1/buildbot/status/web/templates/buildslave.html
-index 0689e87..7ae3c9a 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/web/templates/buildslave.html
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/templates/buildslave.html
-@@ -12,10 +12,9 @@
- <ul>
- {% for b in current %}
- <li>{{ build_line(b, True) }}
-- <form method="post" action="{{ b.buildurl }}/stop" class="command stopbuild" style="display:inline">
-- <input type="submit" value="Stop Build" />
-- <input type="hidden" name="url" value="{{ this_url }}" />
-- </form>
-+ {% if authz.advertiseAction('stopBuild') %}
-+ {{ forms.stop_build(b.buildurl + '/stop', authz, on_all=False, short=True, label='Build') }}
-+ {% endif %}
- </li>
- {% endfor %}
- </ul>
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-index 531199b..8675da9 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-@@ -293,20 +293,17 @@ class JsonResource(resource.Resource):
- """Generates the json dictionary.
-
- By default, renders every childs."""
-- if self.children:
-- data = {}
-- for name in self.children:
-- child = self.getChildWithDefault(name, request)
-- if isinstance(child, JsonResource):
-- wfd = defer.waitForDeferred(
-- defer.maybeDeferred(lambda :
-- child.asDict(request)))
-- yield wfd
-- data[name] = wfd.getResult()
-- # else silently pass over non-json resources.
-- yield data
-- else:
-- raise NotImplementedError()
-+ data = {}
-+ for name in self.children:
-+ child = self.getChildWithDefault(name, request)
-+ if isinstance(child, JsonResource):
-+ wfd = defer.waitForDeferred(
-+ defer.maybeDeferred(lambda :
-+ child.asDict(request)))
-+ yield wfd
-+ data[name] = wfd.getResult()
-+ # else silently pass over non-json resources.
-+ yield data
-
-
- def ToHtml(text):
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-index 8675da9..082b098 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-@@ -1025,6 +1025,42 @@ class AcceptingBuildsResource(JsonResource):
- }
-
-
-+class VarzResource(JsonResource):
-+ help = 'Minimal set of metrics that are scraped frequently for monitoring.'
-+ pageTitle = 'Varz'
-+
-+ @defer.deferredGenerator
-+ def asDict(self, _request):
-+ builders = {}
-+ for builder_name in self.status.getBuilderNames():
-+ builder = self.status.getBuilder(builder_name)
-+ slaves = builder.getSlaves()
-+ builders[builder_name] = {
-+ 'connected_slaves': sum(1 for x in slaves if x.connected),
-+ 'current_builds': len(builder.getCurrentBuilds()),
-+ 'pending_builds': 0,
-+ 'state': builder.currentBigState,
-+ 'total_slaves': len(slaves),
-+ }
-+
-+ # Get pending build requests directly from the db for all builders at
-+ # once.
-+ d = self.status.master.db.buildrequests.getBuildRequests(claimed=False)
-+ def pending_builds_callback(brdicts):
-+ for brdict in brdicts:
-+ if brdict['buildername'] in builders:
-+ builders[brdict['buildername']]['pending_builds'] += 1
-+ d.addCallback(pending_builds_callback)
-+ yield defer.waitForDeferred(d)
-+
-+ yield {
-+ 'accepting_builds': bool(self.status.master.botmaster.brd.running),
-+ 'builders': builders,
-+ 'server_uptime': (
-+ get_timeblock()['utc_ts'] - SERVER_STARTED['utc_ts']),
-+ }
-+
-+
- class JsonStatusResource(JsonResource):
- """Retrieves all json data."""
- help = """JSON status
-@@ -1048,6 +1084,7 @@ For help on any sub directory, use url /child/help
- self.putChild('clock', MasterClockResource(status))
- self.putChild('accepting_builds', AcceptingBuildsResource(status))
- self.putChild('buildstate', BuildStateJsonResource(status))
-+ self.putChild('varz', VarzResource(status))
- # This needs to be called before the first HelpResource().body call.
- self.hackExamples()
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-index 082b098..84c0e09 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-@@ -27,6 +27,7 @@ from twisted.python import log as twlog
- from twisted.web import html, resource, server
-
- from buildbot.changes import changes
-+from buildbot.status.builder import SUCCESS, WARNINGS, FAILURE
- from buildbot.status.web.base import HtmlResource
- from buildbot.util import json, now
-
-@@ -1029,15 +1030,49 @@ class VarzResource(JsonResource):
- help = 'Minimal set of metrics that are scraped frequently for monitoring.'
- pageTitle = 'Varz'
-
-+ RECENT_BUILDS_COUNT = 50
-+
- @defer.deferredGenerator
- def asDict(self, _request):
- builders = {}
- for builder_name in self.status.getBuilderNames():
- builder = self.status.getBuilder(builder_name)
-+
-+ build_range = range(-1, -self.RECENT_BUILDS_COUNT - 1, -1)
-+ recent_builds = [b for b in builder.getBuilds(build_range)
-+ if b is not None]
-+ recent_pending_builds = [b for b in recent_builds
-+ if not b.isFinished()]
-+ recent_finished_builds = [b for b in recent_builds
-+ if b.isFinished()]
-+ recent_successful_builds = [
-+ b for b in recent_finished_builds
-+ if b.getResults() in (SUCCESS, WARNINGS)]
-+ recent_infra_failed_builds = [
-+ b for b in recent_finished_builds
-+ if b.getResults() not in (SUCCESS, WARNINGS, FAILURE)]
-+ recent_failed_builds = [
-+ b for b in recent_finished_builds
-+ if b.getResults() == FAILURE]
-+
-+ recent_successful_build_times = [
-+ int(b.getTimes()[1] - b.getTimes()[0])
-+ for b in recent_successful_builds]
-+ recent_finished_build_times = [
-+ int(b.getTimes()[1] - b.getTimes()[0])
-+ for b in recent_finished_builds]
-+
- slaves = builder.getSlaves()
- builders[builder_name] = {
- 'connected_slaves': sum(1 for x in slaves if x.connected),
- 'current_builds': len(builder.getCurrentBuilds()),
-+ 'recent_builds': len(recent_builds),
-+ 'recent_pending_builds': len(recent_pending_builds),
-+ 'recent_successful_builds': len(recent_successful_builds),
-+ 'recent_infra_failed_builds': len(recent_infra_failed_builds),
-+ 'recent_failed_builds': len(recent_failed_builds),
-+ 'recent_successful_build_times': recent_successful_build_times,
-+ 'recent_finished_build_times': recent_finished_build_times,
- 'pending_builds': 0,
- 'state': builder.currentBigState,
- 'total_slaves': len(slaves),
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-index 84c0e09..70cf371 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
-@@ -1030,30 +1030,37 @@ class VarzResource(JsonResource):
- help = 'Minimal set of metrics that are scraped frequently for monitoring.'
- pageTitle = 'Varz'
-
-- RECENT_BUILDS_COUNT = 50
-+ RECENT_BUILDS_COUNT_DEFAULT = 50
-+ RECENT_BUILDS_COUNT_LIMIT = 200
-
- @defer.deferredGenerator
-- def asDict(self, _request):
-+ def asDict(self, request):
-+ recent_builds_count = int(RequestArg(
-+ request, 'recent_builds_count', self.RECENT_BUILDS_COUNT_DEFAULT))
-+
-+ # Enforce a hard limit to avoid DoS-ing buildbot with a heavy request.
-+ recent_builds_count = min(
-+ recent_builds_count, self.RECENT_BUILDS_COUNT_LIMIT)
-+
- builders = {}
- for builder_name in self.status.getBuilderNames():
- builder = self.status.getBuilder(builder_name)
-
-- build_range = range(-1, -self.RECENT_BUILDS_COUNT - 1, -1)
-+ build_range = range(-1, -recent_builds_count - 1, -1)
- recent_builds = [b for b in builder.getBuilds(build_range)
- if b is not None]
-- recent_pending_builds = [b for b in recent_builds
-+ recent_running_builds = [b for b in recent_builds
- if not b.isFinished()]
- recent_finished_builds = [b for b in recent_builds
- if b.isFinished()]
- recent_successful_builds = [
- b for b in recent_finished_builds
- if b.getResults() in (SUCCESS, WARNINGS)]
-- recent_infra_failed_builds = [
-- b for b in recent_finished_builds
-- if b.getResults() not in (SUCCESS, WARNINGS, FAILURE)]
-- recent_failed_builds = [
-- b for b in recent_finished_builds
-- if b.getResults() == FAILURE]
-+
-+ recent_builds_by_status = collections.defaultdict(int)
-+ recent_builds_by_status['running'] = len(recent_running_builds)
-+ for b in recent_finished_builds:
-+ recent_builds_by_status[b.getResults()] += 1
-
- recent_successful_build_times = [
- int(b.getTimes()[1] - b.getTimes()[0])
-@@ -1066,11 +1073,7 @@ class VarzResource(JsonResource):
- builders[builder_name] = {
- 'connected_slaves': sum(1 for x in slaves if x.connected),
- 'current_builds': len(builder.getCurrentBuilds()),
-- 'recent_builds': len(recent_builds),
-- 'recent_pending_builds': len(recent_pending_builds),
-- 'recent_successful_builds': len(recent_successful_builds),
-- 'recent_infra_failed_builds': len(recent_infra_failed_builds),
-- 'recent_failed_builds': len(recent_failed_builds),
-+ 'recent_builds_by_status': recent_builds_by_status,
- 'recent_successful_build_times': recent_successful_build_times,
- 'recent_finished_build_times': recent_finished_build_times,
- 'pending_builds': 0,
-
-commit 51ce3a66aadbbe847dd0501ad023f4598601f793
-Author: Rico Wind <ricow@google.com>
-Date: Fri Aug 21 13:08:28 2015 +0200
-
- Fix buildbot console view in order_by_time mode
-
- Applying a rebased version of http://trac.buildbot.net/ticket/2364
-
-diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/console.py b/third_party/buildbot_8_4p1/buildbot/status/web/console.py
-index 2d48aca..36def52 100644
---- a/third_party/buildbot_8_4p1/buildbot/status/web/console.py
-+++ b/third_party/buildbot_8_4p1/buildbot/status/web/console.py
-@@ -158,7 +158,8 @@ class DevRevision:
- class DevBuild:
- """Helper class that contains all the information we need for a build."""
-
-- def __init__(self, revision, build, details, inProgressResults=None):
-+ def __init__(self, revision, build, details, inProgressResults=None,
-+ revisions=[]):
- self.revision = revision
- self.results = build.getResults()
- self.number = build.getNumber()
-@@ -169,6 +170,10 @@ class DevBuild:
- self.when = build.getTimes()[0]
- self.source = build.getSourceStamp()
- self.inProgressResults = inProgressResults
-+ for rev in revisions:
-+ if rev.revision == revision:
-+ self.when = rev.when
-+ break
-
-
- class ConsoleStatusResource(HtmlResource):
-@@ -316,7 +321,7 @@ class ConsoleStatusResource(HtmlResource):
- return details
-
- def getBuildsForRevision(self, request, builder, builderName, lastRevision,
-- numBuilds, debugInfo):
-+ numBuilds, debugInfo, revisions):
- """Return the list of all the builds for a given builder that we will
- need to be able to display the console page. We start by the most recent
- build, and we go down until we find a build that was built prior to the
-@@ -357,7 +362,7 @@ class ConsoleStatusResource(HtmlResource):
- if got_rev and got_rev != -1:
- details = self.getBuildDetails(request, builderName, build)
- devBuild = DevBuild(got_rev, build, details,
-- getInProgressResults(build))
-+ getInProgressResults(build), revisions)
- builds.append(devBuild)
-
- # Now break if we have enough builds.
-@@ -385,7 +390,7 @@ class ConsoleStatusResource(HtmlResource):
- return changes[-1]
-
- def getAllBuildsForRevision(self, status, request, lastRevision, numBuilds,
-- categories, builders, debugInfo):
-+ categories, builders, debugInfo, revisions):
- """Returns a dictionary of builds we need to inspect to be able to
- display the console page. The key is the builder name, and the value is
- an array of build we care about. We also returns a dictionary of
-@@ -436,7 +441,8 @@ class ConsoleStatusResource(HtmlResource):
- builderName,
- lastRevision,
- numBuilds,
-- debugInfo)
-+ debugInfo,
-+ revisions)
-
- return (builderList, allBuilds)
-
-@@ -800,7 +806,8 @@ class ConsoleStatusResource(HtmlResource):
- numBuilds,
- categories,
- builders,
-- debugInfo)
-+ debugInfo,
-+ revisions)
-
- debugInfo["added_blocks"] = 0
- debugInfo["from_cache"] = 0
+ $ git diff ece89304e7e6e7f06016de19cdb623e3c7137d2e -- buildbot
« no previous file with comments | « no previous file | third_party/buildbot_8_4p1/buildbot/status/web/status_json.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698