def rebuildBuild(self, bs, reason="<rebuild, no reason given>", extraProperties=None): if not bs.isFinished(): return # Make a copy of the properties so as not to modify the original build. properties = Properties() # Don't include runtime-set properties in a rebuild request properties.updateFromPropertiesNoRuntime(bs.getProperties()) if extraProperties is None: properties.updateFromProperties(extraProperties) properties_dict = dict((k,(v,s)) for (k,v,s) in properties.asList()) ssList = bs.getSourceStamps(absolute=True) if ssList: sourcestampsetid = yield ssList[0].getSourceStampSetId(self.control.master) dl = [] for ss in ssList[1:]: # add defered to the list dl.append(ss.addSourceStampToDatabase(self.control.master, sourcestampsetid)) yield defer.gatherResults(dl) bsid, brids = yield self.control.master.addBuildset( builderNames=[self.original.name], sourcestampsetid=sourcestampsetid, reason=reason, properties=properties_dict) defer.returnValue((bsid, brids)) else: log.msg('Cannot start rebuild, rebuild has no sourcestamps for a new build') defer.returnValue(None)
def rebuildBuild(self, bs, reason="<rebuild, no reason given>", extraProperties=None): if not bs.isFinished(): return # Make a copy of the properties so as not to modify the original build. properties = Properties() # Don't include runtime-set properties in a rebuild request properties.updateFromPropertiesNoRuntime(bs.getProperties()) if extraProperties is None: properties.updateFromProperties(extraProperties) properties_dict = dict((k,(v,s)) for (k,v,s) in properties.asList()) ss = bs.getSourceStamp(absolute=True) d = ss.getSourceStampId(self.master.master) def add_buildset(ssid): return self.master.master.addBuildset( builderNames=[self.original.name], ssid=ssid, reason=reason, properties=properties_dict) d.addCallback(add_buildset) return d
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.""" number = None branch = None category = None revision = None # used to create a source-stamp links = [] # links are gone, but upgrade code expects this attribute @classmethod def fromChdict(cls, master, chdict): """ Class method to create a L{Change} from a dictionary as returned by L{ChangesConnectorComponent.getChange}. @param master: build master instance @param ssdict: change dictionary @returns: L{Change} via Deferred """ cache = master.caches.get_cache("Changes", cls._make_ch) return cache.get(chdict['changeid'], chdict=chdict, master=master) @classmethod def _make_ch(cls, changeid, master, chdict): change = cls(None, None, None, _fromChdict=True) change.who = chdict['author'] change.comments = chdict['comments'] change.revision = chdict['revision'] change.branch = chdict['branch'] change.category = chdict['category'] change.revlink = chdict['revlink'] change.repository = chdict['repository'] change.codebase = chdict['codebase'] change.project = chdict['project'] change.number = chdict['changeid'] when = chdict['when_timestamp'] if when: when = datetime2epoch(when) change.when = when change.files = sorted(chdict['files']) change.properties = Properties() for n, (v, s) in iteritems(chdict['properties']): change.properties.setProperty(n, v, s) return defer.succeed(change) def __init__(self, who, files, comments, revision=None, when=None, branch=None, category=None, revlink='', properties=None, repository='', codebase='', project='', _fromChdict=False): if properties is None: properties = {} # skip all this madness if we're being built from the database if _fromChdict: return self.who = who self.comments = comments def none_or_unicode(x): if x is None: return x return text_type(x) self.revision = none_or_unicode(revision) now = util.now() if when is None: self.when = now elif when > now: # this happens when the committing system has an incorrect clock, for example. # handle it gracefully log.msg( "received a Change with when > now; assuming the change happened now") self.when = now else: self.when = when self.branch = none_or_unicode(branch) self.category = none_or_unicode(category) self.revlink = revlink self.properties = Properties() self.properties.update(properties, "Change") self.repository = repository self.codebase = codebase self.project = project # keep a sorted list of the files, for easier display self.files = sorted(files or []) 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() if not hasattr(self, 'revlink'): self.revlink = "" def __str__(self): return (u"Change(revision=%r, who=%r, branch=%r, comments=%r, " + u"when=%r, category=%r, project=%r, repository=%r, " + u"codebase=%r)") % ( self.revision, self.who, self.branch, self.comments, self.when, self.category, self.project, self.repository, self.codebase) def __cmp__(self, other): return self.number - other.number def asText(self): data = "" data += "Files:\n" for f in self.files: data += " %s\n" % f if self.repository: data += "On: %s\n" % self.repository if self.project: data += "For: %s\n" % self.project data += "At: %s\n" % self.getTime() data += "Changed By: %s\n" % self.who data += "Comments: %s" % self.comments data += "Properties: \n" for prop in self.properties.asList(): data += " %s: %s" % (prop[0], prop[1]) data += '\n\n' return data def asDict(self): '''returns a dictonary with suitable info for html/mail rendering''' files = [dict(name=f) for f in self.files] files.sort(key=lambda a: a['name']) result = { # Constant 'number': self.number, 'branch': self.branch, 'category': self.category, 'who': self.getShortAuthor(), 'comments': self.comments, 'revision': self.revision, 'rev': self.revision, 'when': self.when, 'at': self.getTime(), 'files': files, 'revlink': getattr(self, 'revlink', None), 'properties': self.properties.asList(), 'repository': getattr(self, 'repository', None), 'codebase': getattr(self, 'codebase', ''), 'project': getattr(self, 'project', None) } return result 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 {}
class TestProperties(unittest.TestCase): def setUp(self): self.props = Properties() def testDictBehavior(self): # note that dictionary-like behavior is deprecated and not exposed to # users! self.props.setProperty("do-tests", 1, "scheduler") self.props.setProperty("do-install", 2, "scheduler") self.assert_(self.props.has_key('do-tests')) self.failUnlessEqual(self.props['do-tests'], 1) self.failUnlessEqual(self.props['do-install'], 2) self.assertRaises(KeyError, lambda : self.props['do-nothing']) self.failUnlessEqual(self.props.getProperty('do-install'), 2) self.assertIn('do-tests', self.props) self.assertNotIn('missing-do-tests', self.props) def testAsList(self): self.props.setProperty("happiness", 7, "builder") self.props.setProperty("flames", True, "tester") self.assertEqual(sorted(self.props.asList()), [ ('flames', True, 'tester'), ('happiness', 7, 'builder') ]) def testAsDict(self): self.props.setProperty("msi_filename", "product.msi", 'packager') self.props.setProperty("dmg_filename", "product.dmg", 'packager') self.assertEqual(self.props.asDict(), dict(msi_filename=('product.msi', 'packager'), dmg_filename=('product.dmg', 'packager'))) def testUpdate(self): self.props.setProperty("x", 24, "old") newprops = { 'a' : 1, 'b' : 2 } self.props.update(newprops, "new") self.failUnlessEqual(self.props.getProperty('x'), 24) self.failUnlessEqual(self.props.getPropertySource('x'), 'old') self.failUnlessEqual(self.props.getProperty('a'), 1) self.failUnlessEqual(self.props.getPropertySource('a'), 'new') def testUpdateRuntime(self): self.props.setProperty("x", 24, "old") newprops = { 'a' : 1, 'b' : 2 } self.props.update(newprops, "new", runtime=True) self.failUnlessEqual(self.props.getProperty('x'), 24) self.failUnlessEqual(self.props.getPropertySource('x'), 'old') self.failUnlessEqual(self.props.getProperty('a'), 1) self.failUnlessEqual(self.props.getPropertySource('a'), 'new') self.assertEqual(self.props.runtime, set(['a', 'b'])) def testUpdateFromProperties(self): self.props.setProperty("a", 94, "old") self.props.setProperty("x", 24, "old") newprops = Properties() newprops.setProperty('a', 1, "new") newprops.setProperty('b', 2, "new") self.props.updateFromProperties(newprops) self.failUnlessEqual(self.props.getProperty('x'), 24) self.failUnlessEqual(self.props.getPropertySource('x'), 'old') self.failUnlessEqual(self.props.getProperty('a'), 1) self.failUnlessEqual(self.props.getPropertySource('a'), 'new') def testUpdateFromPropertiesNoRuntime(self): self.props.setProperty("a", 94, "old") self.props.setProperty("b", 84, "old") self.props.setProperty("x", 24, "old") newprops = Properties() newprops.setProperty('a', 1, "new", runtime=True) newprops.setProperty('b', 2, "new", runtime=False) newprops.setProperty('c', 3, "new", runtime=True) newprops.setProperty('d', 3, "new", runtime=False) self.props.updateFromPropertiesNoRuntime(newprops) self.failUnlessEqual(self.props.getProperty('a'), 94) self.failUnlessEqual(self.props.getPropertySource('a'), 'old') self.failUnlessEqual(self.props.getProperty('b'), 2) self.failUnlessEqual(self.props.getPropertySource('b'), 'new') self.failUnlessEqual(self.props.getProperty('c'), None) # not updated self.failUnlessEqual(self.props.getProperty('d'), 3) self.failUnlessEqual(self.props.getPropertySource('d'), 'new') self.failUnlessEqual(self.props.getProperty('x'), 24) self.failUnlessEqual(self.props.getPropertySource('x'), 'old') # IProperties methods def test_getProperty(self): self.props.properties['p1'] = (['p', 1], 'test') self.assertEqual(self.props.getProperty('p1'), ['p', 1]) def test_getProperty_default_None(self): self.assertEqual(self.props.getProperty('p1'), None) def test_getProperty_default(self): self.assertEqual(self.props.getProperty('p1', 2), 2) def test_hasProperty_false(self): self.assertFalse(self.props.hasProperty('x')) def test_hasProperty_true(self): self.props.properties['x'] = (False, 'test') self.assertTrue(self.props.hasProperty('x')) def test_has_key_false(self): self.assertFalse(self.props.has_key('x')) def test_setProperty(self): self.props.setProperty('x', 'y', 'test') self.assertEqual(self.props.properties['x'], ('y', 'test')) self.assertNotIn('x', self.props.runtime) def test_setProperty_runtime(self): self.props.setProperty('x', 'y', 'test', runtime=True) self.assertEqual(self.props.properties['x'], ('y', 'test')) self.assertIn('x', self.props.runtime) def test_setProperty_no_source(self): self.assertRaises(TypeError, lambda : self.props.setProperty('x', 'y')) def test_getProperties(self): self.assertIdentical(self.props.getProperties(), self.props) def test_getBuild(self): self.assertIdentical(self.props.getBuild(), self.props.build) def test_render(self): class FakeRenderable(object): implements(IRenderable) def getRenderingFor(self, props): return props.getProperty('x') + 'z' self.props.setProperty('x', 'y', 'test') self.assertEqual(self.props.render(FakeRenderable()), 'yz')
class TestProperties(unittest.TestCase): def setUp(self): self.props = Properties() def testDictBehavior(self): self.props.setProperty("do-tests", 1, "scheduler") self.props.setProperty("do-install", 2, "scheduler") self.assert_(self.props.has_key('do-tests')) self.failUnlessEqual(self.props['do-tests'], 1) self.failUnlessEqual(self.props['do-install'], 2) self.assertRaises(KeyError, lambda : self.props['do-nothing']) self.failUnlessEqual(self.props.getProperty('do-install'), 2) def testAsList(self): self.props.setProperty("happiness", 7, "builder") self.props.setProperty("flames", True, "tester") self.assertEqual(sorted(self.props.asList()), [ ('flames', True, 'tester'), ('happiness', 7, 'builder') ]) def testAsDict(self): self.props.setProperty("msi_filename", "product.msi", 'packager') self.props.setProperty("dmg_filename", "product.dmg", 'packager') self.assertEqual(self.props.asDict(), dict(msi_filename='product.msi', dmg_filename='product.dmg')) def testUpdate(self): self.props.setProperty("x", 24, "old") newprops = { 'a' : 1, 'b' : 2 } self.props.update(newprops, "new") self.failUnlessEqual(self.props.getProperty('x'), 24) self.failUnlessEqual(self.props.getPropertySource('x'), 'old') self.failUnlessEqual(self.props.getProperty('a'), 1) self.failUnlessEqual(self.props.getPropertySource('a'), 'new') def testUpdateFromProperties(self): self.props.setProperty("a", 94, "old") self.props.setProperty("x", 24, "old") newprops = Properties() newprops.setProperty('a', 1, "new") newprops.setProperty('b', 2, "new") self.props.updateFromProperties(newprops) self.failUnlessEqual(self.props.getProperty('x'), 24) self.failUnlessEqual(self.props.getPropertySource('x'), 'old') self.failUnlessEqual(self.props.getProperty('a'), 1) self.failUnlessEqual(self.props.getPropertySource('a'), 'new') def testUpdateFromPropertiesNoRuntime(self): self.props.setProperty("a", 94, "old") self.props.setProperty("b", 84, "old") self.props.setProperty("x", 24, "old") newprops = Properties() newprops.setProperty('a', 1, "new", runtime=True) newprops.setProperty('b', 2, "new", runtime=False) newprops.setProperty('c', 3, "new", runtime=True) newprops.setProperty('d', 3, "new", runtime=False) self.props.updateFromPropertiesNoRuntime(newprops) self.failUnlessEqual(self.props.getProperty('a'), 94) self.failUnlessEqual(self.props.getPropertySource('a'), 'old') self.failUnlessEqual(self.props.getProperty('b'), 2) self.failUnlessEqual(self.props.getPropertySource('b'), 'new') self.failUnlessEqual(self.props.getProperty('c'), None) # not updated self.failUnlessEqual(self.props.getProperty('d'), 3) self.failUnlessEqual(self.props.getPropertySource('d'), 'new') self.failUnlessEqual(self.props.getProperty('x'), 24) self.failUnlessEqual(self.props.getPropertySource('x'), 'old')
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 def __init__(self, who, files, comments, isdir=0, links=None, revision=None, when=None, branch=None, category=None, 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.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 = '' 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', '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
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.""" implements(interfaces.IStatusEvent) number = None branch = None category = None revision = None # used to create a source-stamp @classmethod def fromChdict(cls, master, chdict): """ Class method to create a L{Change} from a dictionary as returned by L{ChangesConnectorComponent.getChange}. @param master: build master instance @param ssdict: change dictionary @returns: L{Change} via Deferred """ change = cls(None, None, None, _fromChdict=True) change.who = chdict['author'] change.comments = chdict['comments'] change.isdir = chdict['is_dir'] change.links = chdict['links'] change.revision = chdict['revision'] change.branch = chdict['branch'] change.category = chdict['category'] change.revlink = chdict['revlink'] change.repository = chdict['repository'] change.project = chdict['project'] change.number = chdict['changeid'] when = chdict['when_timestamp'] if when: when = datetime2epoch(when) change.when = when change.files = chdict['files'][:] change.files.sort() change.properties = Properties() for n, (v,s) in chdict['properties'].iteritems(): change.properties.setProperty(n, v, s) return defer.succeed(change) def __init__(self, who, files, comments, isdir=0, links=None, revision=None, when=None, branch=None, category=None, revlink='', properties={}, repository='', project='', _fromChdict=False): # skip all this madness if we're being built from the database if _fromChdict: return self.who = who self.comments = comments self.isdir = isdir if links is None: links = [] self.links = links def none_or_unicode(x): if x is None: return x return unicode(x) self.revision = none_or_unicode(revision) now = util.now() if when is None: self.when = now elif when > now: # this happens when the committing system has an incorrect clock, for example. # handle it gracefully log.msg("received a Change with when > now; assuming the change happened now") self.when = now else: self.when = when self.branch = none_or_unicode(branch) self.category = none_or_unicode(category) self.revlink = revlink self.properties = Properties() self.properties.update(properties, "Change") self.repository = repository self.project = project # 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() if not hasattr(self, 'revlink'): self.revlink = "" def __str__(self): return (u"Change(who=%r, files=%r, comments=%r, revision=%r, " + u"when=%r, category=%r, project=%r, repository=%r)") % ( self.who, self.files, self.comments, self.revision, self.when, self.category, self.project, self.repository) def asText(self): data = "" data += self.getFileContents() if self.repository: data += "On: %s\n" % self.repository if self.project: data += "For: %s\n" % self.project 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 asDict(self): '''returns a dictonary with suitable info for html/mail rendering''' result = {} files = [] for file in self.files: link = filter(lambda s: s.find(file) != -1, self.links) if len(link) == 1: url = link[0] else: url = None files.append(dict(url=url, name=file)) files = sorted(files, cmp=lambda a, b: a['name'] < b['name']) # 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['rev'] = self.revision result['when'] = self.when result['at'] = self.getTime() result['files'] = files result['revlink'] = getattr(self, 'revlink', None) result['properties'] = self.properties.asList() result['repository'] = getattr(self, 'repository', None) result['project'] = getattr(self, 'project', None) return result 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 force(self, req): """ Custom properties can be passed from the web form. To do this, subclass this class, overriding the force() method. You can then determine the properties (usually from form values, by inspecting req.args), then pass them to this superclass force method. """ name = req.args.get("username", ["<unknown>"])[0] reason = req.args.get("comments", ["<no reason specified>"])[0] branch = req.args.get("branch", [""])[0] revision = req.args.get("revision", [""])[0] properties = Properties() for i in (1,2,3): pname = req.args.get("prop%dname" % i, [""])[0] pvalue = req.args.get("prop%dvalue" % i, [""])[0] if pname and pvalue: properties.setProperty(pname, pvalue, "Force Build Form") r = "The web-page 'force build' button was pressed by '%s': %s\n" \ % (html.escape(name), html.escape(reason)) log.msg("web forcebuild of builder '%s', branch='%s', revision='%s'" " by user '%s'" % (self.builder_status.getName(), branch, revision, name)) if not self.builder_control: # TODO: tell the web user that their request was denied log.msg("but builder control is disabled") return Redirect("..") if self.isUsingUserPasswd(req): if not self.authUser(req): return Redirect("../../authfail") # keep weird stuff out of the branch revision, and property strings. # TODO: centralize this somewhere. if not re.match(r'^[\w\.\-\/]*$', branch): log.msg("bad branch '%s'" % branch) return Redirect("..") if not re.match(r'^[\w\.\-\/]*$', revision): log.msg("bad revision '%s'" % revision) return Redirect("..") for p in properties.asList(): key = p[0] value = p[1] if not re.match(r'^[\w\.\-\/]*$', key) \ or not re.match(r'^[\w\.\-\/]*$', value): log.msg("bad property name='%s', value='%s'" % (key, value)) return Redirect("..") if not branch: branch = None if not revision: revision = None # TODO: if we can authenticate that a particular User pushed the # button, use their name instead of None, so they'll be informed of # the results. # TODO2: we can authenticate that a particular User pushed the button # now, so someone can write this support. but it requires a # buildbot.changes.changes.Change instance which is tedious at this # stage to compute s = SourceStamp(branch=branch, revision=revision) req = BuildRequest(r, s, builderName=self.builder_status.getName(), properties=properties) try: self.builder_control.requestBuildSoon(req) except interfaces.NoSlaveError: # TODO: tell the web user that their request could not be # honored pass # send the user back to the builder page return Redirect(".")
class TestProperties(unittest.TestCase): def setUp(self): self.props = Properties() def testDictBehavior(self): # note that dictionary-like behavior is deprecated and not exposed to # users! self.props.setProperty("do-tests", 1, "scheduler") self.props.setProperty("do-install", 2, "scheduler") self.assert_(self.props.has_key('do-tests')) self.failUnlessEqual(self.props['do-tests'], 1) self.failUnlessEqual(self.props['do-install'], 2) self.assertRaises(KeyError, lambda: self.props['do-nothing']) self.failUnlessEqual(self.props.getProperty('do-install'), 2) self.assertIn('do-tests', self.props) self.assertNotIn('missing-do-tests', self.props) def testAsList(self): self.props.setProperty("happiness", 7, "builder") self.props.setProperty("flames", True, "tester") self.assertEqual(sorted(self.props.asList()), [('flames', True, 'tester'), ('happiness', 7, 'builder')]) def testAsDict(self): self.props.setProperty("msi_filename", "product.msi", 'packager') self.props.setProperty("dmg_filename", "product.dmg", 'packager') self.assertEqual( self.props.asDict(), dict(msi_filename=('product.msi', 'packager'), dmg_filename=('product.dmg', 'packager'))) def testUpdate(self): self.props.setProperty("x", 24, "old") newprops = {'a': 1, 'b': 2} self.props.update(newprops, "new") self.failUnlessEqual(self.props.getProperty('x'), 24) self.failUnlessEqual(self.props.getPropertySource('x'), 'old') self.failUnlessEqual(self.props.getProperty('a'), 1) self.failUnlessEqual(self.props.getPropertySource('a'), 'new') def testUpdateRuntime(self): self.props.setProperty("x", 24, "old") newprops = {'a': 1, 'b': 2} self.props.update(newprops, "new", runtime=True) self.failUnlessEqual(self.props.getProperty('x'), 24) self.failUnlessEqual(self.props.getPropertySource('x'), 'old') self.failUnlessEqual(self.props.getProperty('a'), 1) self.failUnlessEqual(self.props.getPropertySource('a'), 'new') self.assertEqual(self.props.runtime, set(['a', 'b'])) def testUpdateFromProperties(self): self.props.setProperty("a", 94, "old") self.props.setProperty("x", 24, "old") newprops = Properties() newprops.setProperty('a', 1, "new") newprops.setProperty('b', 2, "new") self.props.updateFromProperties(newprops) self.failUnlessEqual(self.props.getProperty('x'), 24) self.failUnlessEqual(self.props.getPropertySource('x'), 'old') self.failUnlessEqual(self.props.getProperty('a'), 1) self.failUnlessEqual(self.props.getPropertySource('a'), 'new') def testUpdateFromPropertiesNoRuntime(self): self.props.setProperty("a", 94, "old") self.props.setProperty("b", 84, "old") self.props.setProperty("x", 24, "old") newprops = Properties() newprops.setProperty('a', 1, "new", runtime=True) newprops.setProperty('b', 2, "new", runtime=False) newprops.setProperty('c', 3, "new", runtime=True) newprops.setProperty('d', 3, "new", runtime=False) self.props.updateFromPropertiesNoRuntime(newprops) self.failUnlessEqual(self.props.getProperty('a'), 94) self.failUnlessEqual(self.props.getPropertySource('a'), 'old') self.failUnlessEqual(self.props.getProperty('b'), 2) self.failUnlessEqual(self.props.getPropertySource('b'), 'new') self.failUnlessEqual(self.props.getProperty('c'), None) # not updated self.failUnlessEqual(self.props.getProperty('d'), 3) self.failUnlessEqual(self.props.getPropertySource('d'), 'new') self.failUnlessEqual(self.props.getProperty('x'), 24) self.failUnlessEqual(self.props.getPropertySource('x'), 'old') @compat.usesFlushWarnings def test_setProperty_notJsonable(self): self.props.setProperty("project", ConstantRenderable('testing'), "test") self.props.setProperty("project", object, "test") self.assertEqual( len(self.flushWarnings([self.test_setProperty_notJsonable])), 2) # IProperties methods def test_getProperty(self): self.props.properties['p1'] = (['p', 1], 'test') self.assertEqual(self.props.getProperty('p1'), ['p', 1]) def test_getProperty_default_None(self): self.assertEqual(self.props.getProperty('p1'), None) def test_getProperty_default(self): self.assertEqual(self.props.getProperty('p1', 2), 2) def test_hasProperty_false(self): self.assertFalse(self.props.hasProperty('x')) def test_hasProperty_true(self): self.props.properties['x'] = (False, 'test') self.assertTrue(self.props.hasProperty('x')) def test_has_key_false(self): self.assertFalse(self.props.has_key('x')) def test_setProperty(self): self.props.setProperty('x', 'y', 'test') self.assertEqual(self.props.properties['x'], ('y', 'test')) self.assertNotIn('x', self.props.runtime) def test_setProperty_runtime(self): self.props.setProperty('x', 'y', 'test', runtime=True) self.assertEqual(self.props.properties['x'], ('y', 'test')) self.assertIn('x', self.props.runtime) def test_setProperty_no_source(self): self.assertRaises(TypeError, lambda: self.props.setProperty('x', 'y')) def test_getProperties(self): self.assertIdentical(self.props.getProperties(), self.props) def test_getBuild(self): self.assertIdentical(self.props.getBuild(), self.props.build) def test_render(self): class Renderable(object): implements(IRenderable) def getRenderingFor(self, props): return props.getProperty('x') + 'z' self.props.setProperty('x', 'y', 'test') d = self.props.render(Renderable()) d.addCallback(self.assertEqual, 'yz') return d
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.""" number = None branch = None category = None revision = None # used to create a source-stamp links = [] # links are gone, but upgrade code expects this attribute @classmethod def fromChdict(cls, master, chdict): """ Class method to create a L{Change} from a dictionary as returned by L{ChangesConnectorComponent.getChange}. @param master: build master instance @param ssdict: change dictionary @returns: L{Change} via Deferred """ cache = master.caches.get_cache("Changes", cls._make_ch) return cache.get(chdict['changeid'], chdict=chdict, master=master) @classmethod def _make_ch(cls, changeid, master, chdict): change = cls(None, None, None, _fromChdict=True) change.who = chdict['author'] change.committer = chdict['committer'] change.comments = chdict['comments'] change.revision = chdict['revision'] change.branch = chdict['branch'] change.category = chdict['category'] change.revlink = chdict['revlink'] change.repository = chdict['repository'] change.codebase = chdict['codebase'] change.project = chdict['project'] change.number = chdict['changeid'] when = chdict['when_timestamp'] if when: when = datetime2epoch(when) change.when = when change.files = sorted(chdict['files']) change.properties = Properties() for n, (v, s) in chdict['properties'].items(): change.properties.setProperty(n, v, s) return defer.succeed(change) def __init__(self, who, files, comments, committer=None, revision=None, when=None, branch=None, category=None, revlink='', properties=None, repository='', codebase='', project='', _fromChdict=False): if properties is None: properties = {} # skip all this madness if we're being built from the database if _fromChdict: return self.who = who self.committer = committer self.comments = comments def none_or_unicode(x): if x is None: return x return str(x) self.revision = none_or_unicode(revision) now = util.now() if when is None: self.when = now elif when > now: # this happens when the committing system has an incorrect clock, for example. # handle it gracefully log.msg( "received a Change with when > now; assuming the change happened now" ) self.when = now else: self.when = when self.branch = none_or_unicode(branch) self.category = none_or_unicode(category) self.revlink = revlink self.properties = Properties() self.properties.update(properties, "Change") self.repository = repository self.codebase = codebase self.project = project # keep a sorted list of the files, for easier display self.files = sorted(files or []) 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() if not hasattr(self, 'revlink'): self.revlink = "" def __str__(self): return ( "Change(revision=%r, who=%r, committer=%r, branch=%r, comments=%r, " + "when=%r, category=%r, project=%r, repository=%r, " + "codebase=%r)") % (self.revision, self.who, self.committer, self.branch, self.comments, self.when, self.category, self.project, self.repository, self.codebase) def __eq__(self, other): return self.number == other.number def __ne__(self, other): return self.number != other.number def __lt__(self, other): return self.number < other.number def __le__(self, other): return self.number <= other.number def __gt__(self, other): return self.number > other.number def __ge__(self, other): return self.number >= other.number def asText(self): data = "" data += "Files:\n" for f in self.files: data += " {}\n".format(f) if self.repository: data += "On: {}\n".format(self.repository) if self.project: data += "For: {}\n".format(self.project) data += "At: {}\n".format(self.getTime()) data += "Changed By: {}\n".format(self.who) data += "Committed By: {}\n".format(self.committer) data += "Comments: {}".format(self.comments) data += "Properties: \n" for prop in self.properties.asList(): data += " {}: {}".format(prop[0], prop[1]) data += '\n\n' return data def asDict(self): '''returns a dictionary with suitable info for html/mail rendering''' files = [dict(name=f) for f in self.files] files.sort(key=lambda a: a['name']) result = { # Constant 'number': self.number, 'branch': self.branch, 'category': self.category, 'who': self.getShortAuthor(), 'committer': self.committer, 'comments': self.comments, 'revision': self.revision, 'rev': self.revision, 'when': self.when, 'at': self.getTime(), 'files': files, 'revlink': getattr(self, 'revlink', None), 'properties': self.properties.asList(), 'repository': getattr(self, 'repository', None), 'codebase': getattr(self, 'codebase', ''), 'project': getattr(self, 'project', None) } return result 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 {}
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 Bzr), 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. The revision= and branch= values must be ASCII bytestrings, since they will eventually be used in a ShellCommand and passed to os.exec(), which requires bytestrings. These values will also be stored in a database, possibly as unicode, so they must be safely convertable back and forth. This restriction may be relaxed in the future. Changes should be submitted to ChangeMaster.addChange() in chronologically increasing order. Out-of-order changes will probably cause the web status displays to be corrupted.""" implements(interfaces.IStatusEvent) number = None branch = None category = None revision = None # used to create a source-stamp def __init__(self, who, files, comments, isdir=0, links=None, revision=None, when=None, branch=None, category=None, revlink='', properties={}, repository='', project=''): self.who = who self.comments = comments self.isdir = isdir if links is None: links = [] self.links = links def none_or_unicode(x): if x is None: return x return unicode(x) self.revision = none_or_unicode(revision) now = util.now() if when is None: self.when = now elif when > now: # this happens when the committing system has an incorrect clock, for example. # handle it gracefully log.msg( "received a Change with when > now; assuming the change happened now" ) self.when = now else: self.when = when self.branch = none_or_unicode(branch) self.category = none_or_unicode(category) self.revlink = revlink self.properties = Properties() self.properties.update(properties, "Change") self.repository = repository self.project = project # 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() if not hasattr(self, 'revlink'): self.revlink = "" def asText(self): data = "" data += self.getFileContents() if self.repository: data += "On: %s\n" % self.repository if self.project: data += "For: %s\n" % self.project 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 asDict(self): '''returns a dictonary with suitable info for html/mail rendering''' result = {} files = [] for file in self.files: link = filter(lambda s: s.find(file) != -1, self.links) if len(link) == 1: url = link[0] else: url = None files.append(dict(url=url, name=file)) files = sorted(files, cmp=lambda a, b: a['name'] < b['name']) # 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['rev'] = self.revision result['when'] = self.when result['at'] = self.getTime() result['files'] = files result['revlink'] = getattr(self, 'revlink', None) result['properties'] = self.properties.asList() result['repository'] = getattr(self, 'repository', None) result['project'] = getattr(self, 'project', None) return result 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
class TestProperties(unittest.TestCase): def setUp(self): self.props = Properties() def testDictBehavior(self): self.props.setProperty("do-tests", 1, "scheduler") self.props.setProperty("do-install", 2, "scheduler") self.assert_(self.props.has_key('do-tests')) self.failUnlessEqual(self.props['do-tests'], 1) self.failUnlessEqual(self.props['do-install'], 2) self.assertRaises(KeyError, lambda: self.props['do-nothing']) self.failUnlessEqual(self.props.getProperty('do-install'), 2) def testAsList(self): self.props.setProperty("happiness", 7, "builder") self.props.setProperty("flames", True, "tester") self.assertEqual(sorted(self.props.asList()), [('flames', True, 'tester'), ('happiness', 7, 'builder')]) def testAsDict(self): self.props.setProperty("msi_filename", "product.msi", 'packager') self.props.setProperty("dmg_filename", "product.dmg", 'packager') self.assertEqual( self.props.asDict(), dict(msi_filename=('product.msi', 'packager'), dmg_filename=('product.dmg', 'packager'))) def testUpdate(self): self.props.setProperty("x", 24, "old") newprops = {'a': 1, 'b': 2} self.props.update(newprops, "new") self.failUnlessEqual(self.props.getProperty('x'), 24) self.failUnlessEqual(self.props.getPropertySource('x'), 'old') self.failUnlessEqual(self.props.getProperty('a'), 1) self.failUnlessEqual(self.props.getPropertySource('a'), 'new') def testUpdateFromProperties(self): self.props.setProperty("a", 94, "old") self.props.setProperty("x", 24, "old") newprops = Properties() newprops.setProperty('a', 1, "new") newprops.setProperty('b', 2, "new") self.props.updateFromProperties(newprops) self.failUnlessEqual(self.props.getProperty('x'), 24) self.failUnlessEqual(self.props.getPropertySource('x'), 'old') self.failUnlessEqual(self.props.getProperty('a'), 1) self.failUnlessEqual(self.props.getPropertySource('a'), 'new') def testUpdateFromPropertiesNoRuntime(self): self.props.setProperty("a", 94, "old") self.props.setProperty("b", 84, "old") self.props.setProperty("x", 24, "old") newprops = Properties() newprops.setProperty('a', 1, "new", runtime=True) newprops.setProperty('b', 2, "new", runtime=False) newprops.setProperty('c', 3, "new", runtime=True) newprops.setProperty('d', 3, "new", runtime=False) self.props.updateFromPropertiesNoRuntime(newprops) self.failUnlessEqual(self.props.getProperty('a'), 94) self.failUnlessEqual(self.props.getPropertySource('a'), 'old') self.failUnlessEqual(self.props.getProperty('b'), 2) self.failUnlessEqual(self.props.getPropertySource('b'), 'new') self.failUnlessEqual(self.props.getProperty('c'), None) # not updated self.failUnlessEqual(self.props.getProperty('d'), 3) self.failUnlessEqual(self.props.getPropertySource('d'), 'new') self.failUnlessEqual(self.props.getProperty('x'), 24) self.failUnlessEqual(self.props.getPropertySource('x'), 'old')
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.""" implements(interfaces.IStatusEvent) number = None branch = None category = None revision = None # used to create a source-stamp @classmethod def fromChdict(cls, master, chdict): """ Class method to create a L{Change} from a dictionary as returned by L{ChangesConnectorComponent.getChange}. @param master: build master instance @param ssdict: change dictionary @returns: L{Change} via Deferred """ cache = master.caches.get_cache("Changes", cls._make_ch) return cache.get(chdict['changeid'], chdict=chdict, master=master) @classmethod def _make_ch(cls, changeid, master, chdict): change = cls(None, None, None, _fromChdict=True) change.who = chdict['author'] change.comments = chdict['comments'] change.isdir = chdict['is_dir'] change.links = chdict['links'] change.revision = chdict['revision'] change.branch = chdict['branch'] change.category = chdict['category'] change.revlink = chdict['revlink'] change.repository = chdict['repository'] change.project = chdict['project'] change.number = chdict['changeid'] when = chdict['when_timestamp'] if when: when = datetime2epoch(when) change.when = when change.files = chdict['files'][:] change.files.sort() change.properties = Properties() for n, (v,s) in chdict['properties'].iteritems(): change.properties.setProperty(n, v, s) return defer.succeed(change) def __init__(self, who, files, comments, isdir=0, links=None, revision=None, when=None, branch=None, category=None, revlink='', properties={}, repository='', project='', _fromChdict=False): # skip all this madness if we're being built from the database if _fromChdict: return self.who = who self.comments = comments self.isdir = isdir if links is None: links = [] self.links = links def none_or_unicode(x): if x is None: return x return unicode(x) self.revision = none_or_unicode(revision) now = util.now() if when is None: self.when = now elif when > now: # this happens when the committing system has an incorrect clock, for example. # handle it gracefully log.msg("received a Change with when > now; assuming the change happened now") self.when = now else: self.when = when self.branch = none_or_unicode(branch) self.category = none_or_unicode(category) self.revlink = revlink self.properties = Properties() self.properties.update(properties, "Change") self.repository = repository self.project = project # 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() if not hasattr(self, 'revlink'): self.revlink = "" def __str__(self): return (u"Change(revision=%r, who=%r, branch=%r, comments=%r, " + u"when=%r, category=%r, project=%r, repository=%r)") % ( self.revision, self.who, self.branch, self.comments, self.when, self.category, self.project, self.repository) def asText(self): data = "" data += self.getFileContents() if self.repository: data += "On: %s\n" % self.repository if self.project: data += "For: %s\n" % self.project 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 asDict(self): '''returns a dictonary with suitable info for html/mail rendering''' result = {} files = [] for file in self.files: link = filter(lambda s: s.find(file) != -1, self.links) if len(link) == 1: url = link[0] else: url = None files.append(dict(url=url, name=file)) files = sorted(files, cmp=lambda a, b: a['name'] < b['name']) # 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['rev'] = self.revision result['when'] = self.when result['at'] = self.getTime() result['files'] = files result['revlink'] = getattr(self, 'revlink', None) result['properties'] = self.properties.asList() result['repository'] = getattr(self, 'repository', None) 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 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
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 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.""" implements(interfaces.IStatusEvent) number = None branch = None category = None revision = None # used to create a source-stamp def __init__(self, who, files, comments, isdir=0, links=None, revision=None, when=None, branch=None, category=None, revlink='', properties={}, repository='', project=''): self.who = who self.comments = comments self.isdir = isdir if links is None: links = [] self.links = links def none_or_unicode(x): if x is None: return x return unicode(x) self.revision = none_or_unicode(revision) now = util.now() if when is None: self.when = now elif when > now: # this happens when the committing system has an incorrect clock, for example. # handle it gracefully log.msg("received a Change with when > now; assuming the change happened now") self.when = now else: self.when = when self.branch = none_or_unicode(branch) self.category = none_or_unicode(category) self.revlink = revlink self.properties = Properties() self.properties.update(properties, "Change") self.repository = repository self.project = project # 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() if not hasattr(self, 'revlink'): self.revlink = "" def __str__(self): return (u"Change(who=%r, files=%r, comments=%r, revision=%r, " + u"when=%r, category=%r, project=%r, repository=%r)") % ( self.who, self.files, self.comments, self.revision, self.when, self.category, self.project, self.repository) def asText(self): data = "" data += self.getFileContents() if self.repository: data += "On: %s\n" % self.repository if self.project: data += "For: %s\n" % self.project 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 asDict(self): '''returns a dictonary with suitable info for html/mail rendering''' result = {} files = [] for file in self.files: link = filter(lambda s: s.find(file) != -1, self.links) if len(link) == 1: url = link[0] else: url = None files.append(dict(url=url, name=file)) files = sorted(files, cmp=lambda a, b: a['name'] < b['name']) # 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['rev'] = self.revision result['when'] = self.when result['at'] = self.getTime() result['files'] = files result['revlink'] = getattr(self, 'revlink', None) result['properties'] = self.properties.asList() result['repository'] = getattr(self, 'repository', None) result['project'] = getattr(self, 'project', None) return result 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
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 Bzr), 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. The revision= and branch= values must be ASCII bytestrings, since they will eventually be used in a ShellCommand and passed to os.exec(), which requires bytestrings. These values will also be stored in a database, possibly as unicode, so they must be safely convertable back and forth. This restriction may be relaxed in the future. Changes should be submitted to ChangeMaster.addChange() in chronologically increasing order. Out-of-order changes will probably cause the web status displays to be corrupted.""" implements(interfaces.IStatusEvent) number = None branch = None category = None revision = None # used to create a source-stamp def __init__(self, who, files, comments, isdir=0, links=None, revision=None, when=None, branch=None, category=None, revlink='', properties={}, repository='', project=''): self.who = who self.comments = comments self.isdir = isdir if links is None: links = [] self.links = links def none_or_unicode(x): if x is None: return x return unicode(x) self.revision = none_or_unicode(revision) now = util.now() if when is None: self.when = now elif when > now: # this happens when the committing system has an incorrect clock, for example. # handle it gracefully log.msg("received a Change with when > now; assuming the change happened now") self.when = now else: self.when = when self.branch = none_or_unicode(branch) self.category = none_or_unicode(category) self.revlink = revlink self.properties = Properties() self.properties.update(properties, "Change") self.repository = repository self.project = project # 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() if not hasattr(self, 'revlink'): self.revlink = "" def asText(self): data = "" data += self.getFileContents() if self.repository: data += "On: %s\n" % self.repository if self.project: data += "For: %s\n" % self.project 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 asDict(self): '''returns a dictonary with suitable info for html/mail rendering''' result = {} files = [] for file in self.files: link = filter(lambda s: s.find(file) != -1, self.links) if len(link) == 1: url = link[0] else: url = None files.append(dict(url=url, name=file)) files = sorted(files, cmp=lambda a, b: a['name'] < b['name']) # 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['rev'] = self.revision result['when'] = self.when result['at'] = self.getTime() result['files'] = files result['revlink'] = getattr(self, 'revlink', None) result['properties'] = self.properties.asList() result['repository'] = getattr(self, 'repository', None) result['project'] = getattr(self, 'project', None) return result 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
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 def __init__(self, who, files, comments, isdir=0, links=None, revision=None, when=None, branch=None, category=None, 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.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 html_dict(self): '''returns a dictonary with suitable info for html/mail rendering''' files = [] for file in self.files: link = filter(lambda s: s.find(file) != -1, self.links) if len(link) == 1: url = link[0] else: url = None files.append(dict(url=url, name=file)) files = sorted(files, cmp=lambda a,b: a['name'] < b['name']) kwargs = { 'who' : self.who, 'at' : self.getTime(), 'files' : files, 'revision' : self.revision, 'revlink' : getattr(self, 'revlink', None), 'branch' : self.branch, 'comments' : self.comments, 'properties': self.properties.asList() } return kwargs 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
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.""" implements(interfaces.IStatusEvent) number = None branch = None category = None revision = None # used to create a source-stamp links = [] # links are gone, but upgrade code expects this attribute @classmethod def fromChdict(cls, master, chdict): """ Class method to create a L{Change} from a dictionary as returned by L{ChangesConnectorComponent.getChange}. @param master: build master instance @param ssdict: change dictionary @returns: L{Change} via Deferred """ cache = master.caches.get_cache("Changes", cls._make_ch) return cache.get(chdict["changeid"], chdict=chdict, master=master) @classmethod def _make_ch(cls, changeid, master, chdict): change = cls(None, None, None, _fromChdict=True) change.who = chdict["author"] change.comments = chdict["comments"] change.isdir = chdict["is_dir"] change.revision = chdict["revision"] change.branch = chdict["branch"] change.category = chdict["category"] change.revlink = chdict["revlink"] change.repository = chdict["repository"] change.codebase = chdict["codebase"] change.project = chdict["project"] change.number = chdict["changeid"] when = chdict["when_timestamp"] if when: when = datetime2epoch(when) change.when = when change.files = chdict["files"][:] change.files.sort() change.properties = Properties() for n, (v, s) in chdict["properties"].iteritems(): change.properties.setProperty(n, v, s) return defer.succeed(change) def __init__( self, who, files, comments, isdir=0, revision=None, when=None, branch=None, category=None, revlink="", properties={}, repository="", codebase="", project="", _fromChdict=False, ): # skip all this madness if we're being built from the database if _fromChdict: return self.who = who self.comments = comments self.isdir = isdir def none_or_unicode(x): if x is None: return x return unicode(x) self.revision = none_or_unicode(revision) now = util.now() if when is None: self.when = now elif when > now: # this happens when the committing system has an incorrect clock, for example. # handle it gracefully log.msg("received a Change with when > now; assuming the change happened now") self.when = now else: self.when = when self.branch = none_or_unicode(branch) self.category = none_or_unicode(category) self.revlink = revlink self.properties = Properties() self.properties.update(properties, "Change") self.repository = repository self.codebase = codebase self.project = project # keep a sorted list of the files, for easier display self.files = (files or [])[:] 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() if not hasattr(self, "revlink"): self.revlink = "" def __str__(self): return ( u"Change(revision=%r, who=%r, branch=%r, comments=%r, " + u"when=%r, category=%r, project=%r, repository=%r, " + u"codebase=%r)" ) % ( self.revision, self.who, self.branch, self.comments, self.when, self.category, self.project, self.repository, self.codebase, ) def asText(self): data = "" data += self.getFileContents() if self.repository: data += "On: %s\n" % self.repository if self.project: data += "For: %s\n" % self.project 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 asDict(self): """returns a dictonary with suitable info for html/mail rendering""" result = {} files = [dict(name=f) for f in self.files] files.sort(cmp=lambda a, b: a["name"] < b["name"]) # 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["rev"] = self.revision result["when"] = self.when result["at"] = self.getTime() result["files"] = files result["revlink"] = getattr(self, "revlink", None) result["properties"] = self.properties.asList() result["repository"] = getattr(self, "repository", None) result["codebase"] = getattr(self, "codebase", "") result["project"] = getattr(self, "project", None) return result 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