| Index: third_party/buildbot_7_12/buildbot/changes/changes.py
|
| diff --git a/third_party/buildbot_7_12/buildbot/changes/changes.py b/third_party/buildbot_7_12/buildbot/changes/changes.py
|
| deleted file mode 100644
|
| index 0be5e3029f423908faecb35eca6bcd1ca6471bae..0000000000000000000000000000000000000000
|
| --- a/third_party/buildbot_7_12/buildbot/changes/changes.py
|
| +++ /dev/null
|
| @@ -1,360 +0,0 @@
|
| -import sys, os, time
|
| -from cPickle import dump
|
| -
|
| -from zope.interface import implements
|
| -from twisted.python import log
|
| -from twisted.internet import defer
|
| -from twisted.application import service
|
| -from twisted.web import html
|
| -
|
| -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 comment for the group as a whole.
|
| -
|
| - If the version control system supports sequential repository- (or
|
| - branch-) wide change numbers (like SVN, P4, and Arch), then revision=
|
| - should be set to that number. The highest such number will be used at
|
| - checkout time to get the correct set of files.
|
| -
|
| - If it does not (like CVS), when= should be set to the timestamp (seconds
|
| - since epoch, as returned by time.time()) when the change was made. when=
|
| - will be filled in for you (to the current time) if you omit it, which is
|
| - suitable for ChangeSources which have no way of getting more accurate
|
| - timestamps.
|
| -
|
| - Changes should be submitted to ChangeMaster.addChange() in
|
| - chronologically increasing order. Out-of-order changes will probably
|
| - cause the html.Waterfall display to be corrupted."""
|
| -
|
| - implements(interfaces.IStatusEvent)
|
| -
|
| - number = None
|
| -
|
| - branch = None
|
| - category = None
|
| - revision = None # used to create a source-stamp
|
| - repository = None # optional repository
|
| -
|
| - def __init__(self, who, files, comments, isdir=0, links=None,
|
| - revision=None, when=None, branch=None, category=None,
|
| - repository='', revlink='', properties={}):
|
| - self.who = who
|
| - self.comments = comments
|
| - self.isdir = isdir
|
| - if links is None:
|
| - links = []
|
| - self.links = links
|
| - self.revision = revision
|
| - if when is None:
|
| - when = util.now()
|
| - self.when = when
|
| - self.branch = branch
|
| - self.category = category
|
| - self.repository = repository
|
| - self.revlink = revlink
|
| - self.properties = Properties()
|
| - self.properties.update(properties, "Change")
|
| -
|
| - # keep a sorted list of the files, for easier display
|
| - self.files = files[:]
|
| - self.files.sort()
|
| -
|
| - def __setstate__(self, dict):
|
| - self.__dict__ = dict
|
| - # Older Changes won't have a 'properties' attribute in them
|
| - if not hasattr(self, 'properties'):
|
| - self.properties = Properties()
|
| -
|
| - def asText(self):
|
| - data = ""
|
| - data += self.getFileContents()
|
| - data += "At: %s\n" % self.getTime()
|
| - data += "Changed By: %s\n" % self.who
|
| - data += "Comments: %s" % self.comments
|
| - data += "Properties: \n%s\n\n" % self.getProperties()
|
| - return data
|
| -
|
| - def asHTML(self):
|
| - links = []
|
| - for file in self.files:
|
| - link = filter(lambda s: s.find(file) != -1, self.links)
|
| - if len(link) == 1:
|
| - # could get confused
|
| - links.append('<a href="%s"><b>%s</b></a>' % (link[0], file))
|
| - else:
|
| - links.append('<b>%s</b>' % file)
|
| - if self.revision:
|
| - if getattr(self, 'revlink', ""):
|
| - revision = 'Revision: <a href="%s"><b>%s</b></a>\n' % (
|
| - self.revlink, self.revision)
|
| - else:
|
| - revision = "Revision: <b>%s</b><br />\n" % self.revision
|
| - else:
|
| - revision = ''
|
| -
|
| - if self.repository:
|
| - repository = "Repository: <b>%s</b><br />\n" % self.repository
|
| - else:
|
| - repository = ''
|
| -
|
| - branch = ""
|
| - if self.branch:
|
| - branch = "Branch: <b>%s</b><br />\n" % self.branch
|
| -
|
| - properties = []
|
| - for prop in self.properties.asList():
|
| - properties.append("%s: %s<br />" % (prop[0], prop[1]))
|
| -
|
| - kwargs = { 'who' : html.escape(self.who),
|
| - 'at' : self.getTime(),
|
| - 'files' : html.UL(links) + '\n',
|
| - 'repository': repository,
|
| - 'revision' : revision,
|
| - 'branch' : branch,
|
| - 'comments' : html.PRE(self.comments),
|
| - 'properties': html.UL(properties) + '\n' }
|
| - return html_tmpl % kwargs
|
| -
|
| - def get_HTML_box(self, url):
|
| - """Return the contents of a TD cell for the waterfall display.
|
| -
|
| - @param url: the URL that points to an HTML page that will render
|
| - using our asHTML method. The Change is free to use this or ignore it
|
| - as it pleases.
|
| -
|
| - @return: the HTML that will be put inside the table cell. Typically
|
| - this is just a single href named after the author of the change and
|
| - pointing at the passed-in 'url'.
|
| - """
|
| - who = self.getShortAuthor()
|
| - if self.comments is None:
|
| - title = ""
|
| - else:
|
| - title = html.escape(self.comments)
|
| - return '<a href="%s" title="%s">%s</a>' % (url,
|
| - title,
|
| - html.escape(who))
|
| -
|
| - def getShortAuthor(self):
|
| - return self.who
|
| -
|
| - def getTime(self):
|
| - if not self.when:
|
| - return "?"
|
| - return time.strftime("%a %d %b %Y %H:%M:%S",
|
| - time.localtime(self.when))
|
| -
|
| - def getTimes(self):
|
| - return (self.when, None)
|
| -
|
| - def getText(self):
|
| - return [html.escape(self.who)]
|
| - def getLogs(self):
|
| - return {}
|
| -
|
| - def getFileContents(self):
|
| - data = ""
|
| - if len(self.files) == 1:
|
| - if self.isdir:
|
| - data += "Directory: %s\n" % self.files[0]
|
| - else:
|
| - data += "File: %s\n" % self.files[0]
|
| - else:
|
| - data += "Files:\n"
|
| - for f in self.files:
|
| - data += " %s\n" % f
|
| - return data
|
| -
|
| - def getProperties(self):
|
| - data = ""
|
| - for prop in self.properties.asList():
|
| - data += " %s: %s" % (prop[0], prop[1])
|
| - return data
|
| -
|
| - def asDict(self):
|
| - result = {}
|
| - # Constant
|
| - result['number'] = self.number
|
| - result['branch'] = self.branch
|
| - result['category'] = self.category
|
| - result['who'] = self.getShortAuthor()
|
| - result['comments'] = self.comments
|
| - result['revision'] = self.revision
|
| - result['repository'] = self.repository
|
| - result['when'] = self.when
|
| - result['files'] = self.files
|
| - result['revlink'] = self.revlink
|
| - result['properties'] = self.properties.asList()
|
| - return result
|
| -
|
| -
|
| -class ChangeMaster(service.MultiService):
|
| -
|
| - """This is the master-side service which receives file change
|
| - notifications from CVS. It keeps a log of these changes, enough to
|
| - provide for the HTML waterfall display, and to tell
|
| - temporarily-disconnected bots what they missed while they were
|
| - offline.
|
| -
|
| - Change notifications come from two different kinds of sources. The first
|
| - is a PB service (servicename='changemaster', perspectivename='change'),
|
| - which provides a remote method called 'addChange', which should be
|
| - called with a dict that has keys 'filename' and 'comments'.
|
| -
|
| - The second is a list of objects derived from the ChangeSource class.
|
| - These are added with .addSource(), which also sets the .changemaster
|
| - attribute in the source to point at the ChangeMaster. When the
|
| - application begins, these will be started with .start() . At shutdown
|
| - time, they will be terminated with .stop() . They must be persistable.
|
| - They are expected to call self.changemaster.addChange() with Change
|
| - objects.
|
| -
|
| - There are several different variants of the second type of source:
|
| -
|
| - - L{buildbot.changes.mail.MaildirSource} watches a maildir for CVS
|
| - commit mail. It uses DNotify if available, or polls every 10
|
| - seconds if not. It parses incoming mail to determine what files
|
| - were changed.
|
| -
|
| - - L{buildbot.changes.freshcvs.FreshCVSSource} makes a PB
|
| - connection to the CVSToys 'freshcvs' daemon and relays any
|
| - changes it announces.
|
| -
|
| - """
|
| -
|
| - implements(interfaces.IEventSource)
|
| -
|
| - debug = False
|
| - # todo: use Maildir class to watch for changes arriving by mail
|
| -
|
| - changeHorizon = 0
|
| -
|
| - def __init__(self):
|
| - service.MultiService.__init__(self)
|
| - self.changes = []
|
| - # self.basedir must be filled in by the parent
|
| - self.nextNumber = 1
|
| -
|
| - def addSource(self, source):
|
| - assert interfaces.IChangeSource.providedBy(source)
|
| - assert service.IService.providedBy(source)
|
| - if self.debug:
|
| - print "ChangeMaster.addSource", source
|
| - source.setServiceParent(self)
|
| -
|
| - def removeSource(self, source):
|
| - assert source in self
|
| - if self.debug:
|
| - print "ChangeMaster.removeSource", source, source.parent
|
| - d = defer.maybeDeferred(source.disownServiceParent)
|
| - return d
|
| -
|
| - def addChange(self, change):
|
| - """Deliver a file change event. The event should be a Change object.
|
| - This method will timestamp the object as it is received."""
|
| - log.msg("adding change, who %s, %d files, rev=%s, branch=%s, "
|
| - "comments %s, category %s" % (change.who, len(change.files),
|
| - change.revision, change.branch,
|
| - change.comments, change.category))
|
| - change.number = self.nextNumber
|
| - self.nextNumber += 1
|
| - self.changes.append(change)
|
| - self.parent.addChange(change)
|
| - self.pruneChanges()
|
| -
|
| - def pruneChanges(self):
|
| - if self.changeHorizon and len(self.changes) > self.changeHorizon:
|
| - log.msg("pruning %i changes" % (len(self.changes) - self.changeHorizon))
|
| - self.changes = self.changes[-self.changeHorizon:]
|
| -
|
| - def eventGenerator(self, branches=[], categories=[], committers=[], minTime=0):
|
| - for i in range(len(self.changes)-1, -1, -1):
|
| - c = self.changes[i]
|
| - if (c.when < minTime):
|
| - break
|
| - if (not branches or c.branch in branches) and (
|
| - not categories or c.category in categories) and (
|
| - not committers or c.who in committers):
|
| - yield c
|
| -
|
| - def getChangeNumbered(self, num):
|
| - if not self.changes:
|
| - return None
|
| - first = self.changes[0].number
|
| - if first + len(self.changes)-1 != self.changes[-1].number:
|
| - log.msg(self,
|
| - "lost a change somewhere: [0] is %d, [%d] is %d" % \
|
| - (self.changes[0].number,
|
| - len(self.changes) - 1,
|
| - self.changes[-1].number))
|
| - for c in self.changes:
|
| - log.msg("c[%d]: " % c.number, c)
|
| - return None
|
| - offset = num - first
|
| - log.msg(self, "offset", offset)
|
| - if 0 <= offset <= len(self.changes):
|
| - return self.changes[offset]
|
| - else:
|
| - return None
|
| -
|
| - def __getstate__(self):
|
| - d = service.MultiService.__getstate__(self)
|
| - del d['parent']
|
| - del d['services'] # lose all children
|
| - del d['namedServices']
|
| - return d
|
| -
|
| - def __setstate__(self, d):
|
| - self.__dict__ = d
|
| - # self.basedir must be set by the parent
|
| - self.services = [] # they'll be repopulated by readConfig
|
| - self.namedServices = {}
|
| -
|
| -
|
| - def saveYourself(self):
|
| - filename = os.path.join(self.basedir, "changes.pck")
|
| - tmpfilename = filename + ".tmp"
|
| - try:
|
| - dump(self, open(tmpfilename, "wb"))
|
| - if sys.platform == 'win32':
|
| - # windows cannot rename a file on top of an existing one
|
| - if os.path.exists(filename):
|
| - os.unlink(filename)
|
| - os.rename(tmpfilename, filename)
|
| - except Exception, e:
|
| - log.msg("unable to save changes")
|
| - log.err()
|
| -
|
| - def stopService(self):
|
| - self.saveYourself()
|
| - return service.MultiService.stopService(self)
|
| -
|
| -class TestChangeMaster(ChangeMaster):
|
| - """A ChangeMaster for use in tests that does not save itself"""
|
| - def stopService(self):
|
| - return service.MultiService.stopService(self)
|
| -
|
| -# vim: set ts=4 sts=4 sw=4 et:
|
|
|