Package buildbot :: Package changes :: Module changes
[frames] | no frames]

Source Code for Module buildbot.changes.changes

  1  # This file is part of Buildbot.  Buildbot is free software: you can 
  2  # redistribute it and/or modify it under the terms of the GNU General Public 
  3  # License as published by the Free Software Foundation, version 2. 
  4  # 
  5  # This program is distributed in the hope that it will be useful, but WITHOUT 
  6  # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
  7  # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more 
  8  # details. 
  9  # 
 10  # You should have received a copy of the GNU General Public License along with 
 11  # this program; if not, write to the Free Software Foundation, Inc., 51 
 12  # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 
 13  # 
 14  # Copyright Buildbot Team Members 
 15   
 16  from __future__ import with_statement 
 17   
 18  import os, time 
 19  from cPickle import dump 
 20   
 21  from zope.interface import implements 
 22  from twisted.python import log, runtime 
 23  from twisted.internet import defer 
 24  from twisted.web import html 
 25  from buildbot.util import datetime2epoch 
 26   
 27  from buildbot import interfaces, util 
 28  from buildbot.process.properties import Properties 
29 30 -class Change:
31 """I represent a single change to the source tree. This may involve several 32 files, but they are all changed by the same person, and there is a change 33 comment for the group as a whole.""" 34 35 implements(interfaces.IStatusEvent) 36 37 number = None 38 branch = None 39 category = None 40 revision = None # used to create a source-stamp 41 links = [] # links are gone, but upgrade code expects this attribute 42 43 @classmethod
44 - def fromChdict(cls, master, chdict):
45 """ 46 Class method to create a L{Change} from a dictionary as returned 47 by L{ChangesConnectorComponent.getChange}. 48 49 @param master: build master instance 50 @param ssdict: change dictionary 51 52 @returns: L{Change} via Deferred 53 """ 54 cache = master.caches.get_cache("Changes", cls._make_ch) 55 return cache.get(chdict['changeid'], chdict=chdict, master=master)
56 57 @classmethod
58 - def _make_ch(cls, changeid, master, chdict):
59 change = cls(None, None, None, _fromChdict=True) 60 change.who = chdict['author'] 61 change.comments = chdict['comments'] 62 change.isdir = chdict['is_dir'] 63 change.revision = chdict['revision'] 64 change.branch = chdict['branch'] 65 change.category = chdict['category'] 66 change.revlink = chdict['revlink'] 67 change.repository = chdict['repository'] 68 change.codebase = chdict['codebase'] 69 change.project = chdict['project'] 70 change.number = chdict['changeid'] 71 72 when = chdict['when_timestamp'] 73 if when: 74 when = datetime2epoch(when) 75 change.when = when 76 77 change.files = chdict['files'][:] 78 change.files.sort() 79 80 change.properties = Properties() 81 for n, (v,s) in chdict['properties'].iteritems(): 82 change.properties.setProperty(n, v, s) 83 84 return defer.succeed(change)
85
86 - def __init__(self, who, files, comments, isdir=0, 87 revision=None, when=None, branch=None, category=None, 88 revlink='', properties={}, repository='', codebase='', 89 project='', _fromChdict=False):
90 # skip all this madness if we're being built from the database 91 if _fromChdict: 92 return 93 94 self.who = who 95 self.comments = comments 96 self.isdir = isdir 97 98 def none_or_unicode(x): 99 if x is None: return x 100 return unicode(x)
101 102 self.revision = none_or_unicode(revision) 103 now = util.now() 104 if when is None: 105 self.when = now 106 elif when > now: 107 # this happens when the committing system has an incorrect clock, for example. 108 # handle it gracefully 109 log.msg("received a Change with when > now; assuming the change happened now") 110 self.when = now 111 else: 112 self.when = when 113 self.branch = none_or_unicode(branch) 114 self.category = none_or_unicode(category) 115 self.revlink = revlink 116 self.properties = Properties() 117 self.properties.update(properties, "Change") 118 self.repository = repository 119 self.codebase = codebase 120 self.project = project 121 122 # keep a sorted list of the files, for easier display 123 self.files = (files or [])[:] 124 self.files.sort()
125
126 - def __setstate__(self, dict):
127 self.__dict__ = dict 128 # Older Changes won't have a 'properties' attribute in them 129 if not hasattr(self, 'properties'): 130 self.properties = Properties() 131 if not hasattr(self, 'revlink'): 132 self.revlink = ""
133
134 - def __str__(self):
135 return (u"Change(revision=%r, who=%r, branch=%r, comments=%r, " + 136 u"when=%r, category=%r, project=%r, repository=%r, " + 137 u"codebase=%r)") % ( 138 self.revision, self.who, self.branch, self.comments, 139 self.when, self.category, self.project, self.repository, 140 self.codebase)
141
142 - def __cmp__(self, other):
143 return self.number - other.number
144
145 - def asText(self):
146 data = "" 147 data += self.getFileContents() 148 if self.repository: 149 data += "On: %s\n" % self.repository 150 if self.project: 151 data += "For: %s\n" % self.project 152 data += "At: %s\n" % self.getTime() 153 data += "Changed By: %s\n" % self.who 154 data += "Comments: %s" % self.comments 155 data += "Properties: \n%s\n\n" % self.getProperties() 156 return data
157
158 - def asDict(self):
159 '''returns a dictonary with suitable info for html/mail rendering''' 160 result = {} 161 162 files = [ dict(name=f) for f in self.files ] 163 files.sort(cmp=lambda a, b: a['name'] < b['name']) 164 165 # Constant 166 result['number'] = self.number 167 result['branch'] = self.branch 168 result['category'] = self.category 169 result['who'] = self.getShortAuthor() 170 result['comments'] = self.comments 171 result['revision'] = self.revision 172 result['rev'] = self.revision 173 result['when'] = self.when 174 result['at'] = self.getTime() 175 result['files'] = files 176 result['revlink'] = getattr(self, 'revlink', None) 177 result['properties'] = self.properties.asList() 178 result['repository'] = getattr(self, 'repository', None) 179 result['codebase'] = getattr(self, 'codebase', '') 180 result['project'] = getattr(self, 'project', None) 181 return result
182
183 - def getShortAuthor(self):
184 return self.who
185
186 - def getTime(self):
187 if not self.when: 188 return "?" 189 return time.strftime("%a %d %b %Y %H:%M:%S", 190 time.localtime(self.when))
191
192 - def getTimes(self):
193 return (self.when, None)
194
195 - def getText(self):
196 return [html.escape(self.who)]
197 - def getLogs(self):
198 return {}
199
200 - def getFileContents(self):
201 data = "" 202 if len(self.files) == 1: 203 if self.isdir: 204 data += "Directory: %s\n" % self.files[0] 205 else: 206 data += "File: %s\n" % self.files[0] 207 else: 208 data += "Files:\n" 209 for f in self.files: 210 data += " %s\n" % f 211 return data
212
213 - def getProperties(self):
214 data = "" 215 for prop in self.properties.asList(): 216 data += " %s: %s" % (prop[0], prop[1]) 217 return data
218
219 220 -class ChangeMaster: # pragma: no cover
221 # this is a stub, retained to allow the "buildbot upgrade-master" tool to 222 # read old changes.pck pickle files and convert their contents into the 223 # new database format. This is only instantiated by that tool, or by 224 # test_db.py which tests that tool. The functionality that previously 225 # lived here has been moved into buildbot.changes.manager.ChangeManager 226
227 - def __init__(self):
228 self.changes = [] 229 # self.basedir must be filled in by the parent 230 self.nextNumber = 1
231
232 - def saveYourself(self):
233 filename = os.path.join(self.basedir, "changes.pck") 234 tmpfilename = filename + ".tmp" 235 try: 236 with open(tmpfilename, "wb") as f: 237 dump(self, f) 238 if runtime.platformType == 'win32': 239 # windows cannot rename a file on top of an existing one 240 if os.path.exists(filename): 241 os.unlink(filename) 242 os.rename(tmpfilename, filename) 243 except Exception: 244 log.msg("unable to save changes") 245 log.err()
246 247 # This method is used by contrib/fix_changes_pickle_encoding.py to recode all 248 # bytestrings in an old changes.pck into unicode strings
249 - def recode_changes(self, old_encoding, quiet=False):
250 """Processes the list of changes, with the change attributes re-encoded 251 unicode objects""" 252 nconvert = 0 253 for c in self.changes: 254 # give revision special handling, in case it is an integer 255 if isinstance(c.revision, int): 256 c.revision = unicode(c.revision) 257 258 for attr in ("who", "comments", "revlink", "category", "branch", "revision"): 259 a = getattr(c, attr) 260 if isinstance(a, str): 261 try: 262 setattr(c, attr, a.decode(old_encoding)) 263 nconvert += 1 264 except UnicodeDecodeError: 265 raise UnicodeError("Error decoding %s of change #%s as %s:\n%r" % 266 (attr, c.number, old_encoding, a)) 267 268 # filenames are a special case, but in general they'll have the same encoding 269 # as everything else on a system. If not, well, hack this script to do your 270 # import! 271 newfiles = [] 272 for filename in util.flatten(c.files): 273 if isinstance(filename, str): 274 try: 275 filename = filename.decode(old_encoding) 276 nconvert += 1 277 except UnicodeDecodeError: 278 raise UnicodeError("Error decoding filename '%s' of change #%s as %s:\n%r" % 279 (filename.decode('ascii', 'replace'), 280 c.number, old_encoding, a)) 281 newfiles.append(filename) 282 c.files = newfiles 283 if not quiet: 284 print "converted %d strings" % nconvert
285
286 -class OldChangeMaster(ChangeMaster): # pragma: no cover
287 # this is a reminder that the ChangeMaster class is old 288 pass 289 # vim: set ts=4 sts=4 sw=4 et: 290