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  import os, time 
 17  from cPickle import dump 
 18   
 19  from zope.interface import implements 
 20  from twisted.python import log, runtime 
 21  from twisted.internet import defer 
 22  from twisted.web import html 
 23  from buildbot.util import datetime2epoch 
 24   
 25  from buildbot import interfaces, util 
 26  from buildbot.process.properties import Properties 
27 28 -class Change:
29 """I represent a single change to the source tree. This may involve several 30 files, but they are all changed by the same person, and there is a change 31 comment for the group as a whole.""" 32 33 implements(interfaces.IStatusEvent) 34 35 number = None 36 branch = None 37 category = None 38 revision = None # used to create a source-stamp 39 40 @classmethod
41 - def fromChdict(cls, master, chdict):
42 """ 43 Class method to create a L{Change} from a dictionary as returned 44 by L{ChangesConnectorComponent.getChange}. 45 46 @param master: build master instance 47 @param ssdict: change dictionary 48 49 @returns: L{Change} via Deferred 50 """ 51 cache = master.caches.get_cache("Changes", cls._make_ch) 52 return cache.get(chdict['changeid'], chdict=chdict, master=master)
53 54 @classmethod
55 - def _make_ch(cls, changeid, master, chdict):
56 change = cls(None, None, None, _fromChdict=True) 57 change.who = chdict['author'] 58 change.comments = chdict['comments'] 59 change.isdir = chdict['is_dir'] 60 change.links = chdict['links'] 61 change.revision = chdict['revision'] 62 change.branch = chdict['branch'] 63 change.category = chdict['category'] 64 change.revlink = chdict['revlink'] 65 change.repository = chdict['repository'] 66 change.project = chdict['project'] 67 change.number = chdict['changeid'] 68 69 when = chdict['when_timestamp'] 70 if when: 71 when = datetime2epoch(when) 72 change.when = when 73 74 change.files = chdict['files'][:] 75 change.files.sort() 76 77 change.properties = Properties() 78 for n, (v,s) in chdict['properties'].iteritems(): 79 change.properties.setProperty(n, v, s) 80 81 return defer.succeed(change)
82
83 - def __init__(self, who, files, comments, isdir=0, links=None, 84 revision=None, when=None, branch=None, category=None, 85 revlink='', properties={}, repository='', project='', 86 _fromChdict=False):
87 # skip all this madness if we're being built from the database 88 if _fromChdict: 89 return 90 91 self.who = who 92 self.comments = comments 93 self.isdir = isdir 94 if links is None: 95 links = [] 96 self.links = links 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.project = project 120 121 # keep a sorted list of the files, for easier display 122 self.files = files[:] 123 self.files.sort()
124
125 - def __setstate__(self, dict):
126 self.__dict__ = dict 127 # Older Changes won't have a 'properties' attribute in them 128 if not hasattr(self, 'properties'): 129 self.properties = Properties() 130 if not hasattr(self, 'revlink'): 131 self.revlink = ""
132
133 - def __str__(self):
134 return (u"Change(revision=%r, who=%r, branch=%r, comments=%r, " + 135 u"when=%r, category=%r, project=%r, repository=%r)") % ( 136 self.revision, self.who, self.branch, self.comments, 137 self.when, self.category, self.project, self.repository)
138
139 - def asText(self):
140 data = "" 141 data += self.getFileContents() 142 if self.repository: 143 data += "On: %s\n" % self.repository 144 if self.project: 145 data += "For: %s\n" % self.project 146 data += "At: %s\n" % self.getTime() 147 data += "Changed By: %s\n" % self.who 148 data += "Comments: %s" % self.comments 149 data += "Properties: \n%s\n\n" % self.getProperties() 150 return data
151
152 - def asDict(self):
153 '''returns a dictonary with suitable info for html/mail rendering''' 154 result = {} 155 156 files = [] 157 for file in self.files: 158 link = filter(lambda s: s.find(file) != -1, self.links) 159 if len(link) == 1: 160 url = link[0] 161 else: 162 url = None 163 files.append(dict(url=url, name=file)) 164 165 files = sorted(files, cmp=lambda a, b: a['name'] < b['name']) 166 167 # Constant 168 result['number'] = self.number 169 result['branch'] = self.branch 170 result['category'] = self.category 171 result['who'] = self.getShortAuthor() 172 result['comments'] = self.comments 173 result['revision'] = self.revision 174 result['rev'] = self.revision 175 result['when'] = self.when 176 result['at'] = self.getTime() 177 result['files'] = files 178 result['revlink'] = getattr(self, 'revlink', None) 179 result['properties'] = self.properties.asList() 180 result['repository'] = getattr(self, 'repository', None) 181 result['project'] = getattr(self, 'project', None) 182 return result
183
184 - def getShortAuthor(self):
185 return self.who
186
187 - def getTime(self):
188 if not self.when: 189 return "?" 190 return time.strftime("%a %d %b %Y %H:%M:%S", 191 time.localtime(self.when))
192
193 - def getTimes(self):
194 return (self.when, None)
195
196 - def getText(self):
197 return [html.escape(self.who)]
198 - def getLogs(self):
199 return {}
200
201 - def getFileContents(self):
202 data = "" 203 if len(self.files) == 1: 204 if self.isdir: 205 data += "Directory: %s\n" % self.files[0] 206 else: 207 data += "File: %s\n" % self.files[0] 208 else: 209 data += "Files:\n" 210 for f in self.files: 211 data += " %s\n" % f 212 return data
213
214 - def getProperties(self):
215 data = "" 216 for prop in self.properties.asList(): 217 data += " %s: %s" % (prop[0], prop[1]) 218 return data
219
220 221 -class ChangeMaster: # pragma: no cover
222 # this is a stub, retained to allow the "buildbot upgrade-master" tool to 223 # read old changes.pck pickle files and convert their contents into the 224 # new database format. This is only instantiated by that tool, or by 225 # test_db.py which tests that tool. The functionality that previously 226 # lived here has been moved into buildbot.changes.manager.ChangeManager 227
228 - def __init__(self):
229 self.changes = [] 230 # self.basedir must be filled in by the parent 231 self.nextNumber = 1
232
233 - def saveYourself(self):
234 filename = os.path.join(self.basedir, "changes.pck") 235 tmpfilename = filename + ".tmp" 236 try: 237 dump(self, open(tmpfilename, "wb")) 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