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 links = [] # links are gone, but upgrade code expects this attribute 40 41 @classmethod
42 - def fromChdict(cls, master, chdict):
43 """ 44 Class method to create a L{Change} from a dictionary as returned 45 by L{ChangesConnectorComponent.getChange}. 46 47 @param master: build master instance 48 @param ssdict: change dictionary 49 50 @returns: L{Change} via Deferred 51 """ 52 cache = master.caches.get_cache("Changes", cls._make_ch) 53 return cache.get(chdict['changeid'], chdict=chdict, master=master)
54 55 @classmethod
56 - def _make_ch(cls, changeid, master, chdict):
57 change = cls(None, None, None, _fromChdict=True) 58 change.who = chdict['author'] 59 change.comments = chdict['comments'] 60 change.isdir = chdict['is_dir'] 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, 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 95 def none_or_unicode(x): 96 if x is None: return x 97 return unicode(x)
98 99 self.revision = none_or_unicode(revision) 100 now = util.now() 101 if when is None: 102 self.when = now 103 elif when > now: 104 # this happens when the committing system has an incorrect clock, for example. 105 # handle it gracefully 106 log.msg("received a Change with when > now; assuming the change happened now") 107 self.when = now 108 else: 109 self.when = when 110 self.branch = none_or_unicode(branch) 111 self.category = none_or_unicode(category) 112 self.revlink = revlink 113 self.properties = Properties() 114 self.properties.update(properties, "Change") 115 self.repository = repository 116 self.project = project 117 118 # keep a sorted list of the files, for easier display 119 self.files = files[:] 120 self.files.sort()
121
122 - def __setstate__(self, dict):
123 self.__dict__ = dict 124 # Older Changes won't have a 'properties' attribute in them 125 if not hasattr(self, 'properties'): 126 self.properties = Properties() 127 if not hasattr(self, 'revlink'): 128 self.revlink = ""
129
130 - def __str__(self):
131 return (u"Change(revision=%r, who=%r, branch=%r, comments=%r, " + 132 u"when=%r, category=%r, project=%r, repository=%r)") % ( 133 self.revision, self.who, self.branch, self.comments, 134 self.when, self.category, self.project, self.repository)
135
136 - def asText(self):
137 data = "" 138 data += self.getFileContents() 139 if self.repository: 140 data += "On: %s\n" % self.repository 141 if self.project: 142 data += "For: %s\n" % self.project 143 data += "At: %s\n" % self.getTime() 144 data += "Changed By: %s\n" % self.who 145 data += "Comments: %s" % self.comments 146 data += "Properties: \n%s\n\n" % self.getProperties() 147 return data
148
149 - def asDict(self):
150 '''returns a dictonary with suitable info for html/mail rendering''' 151 result = {} 152 153 files = [ dict(name=f) for f in self.files ] 154 files.sort(cmp=lambda a, b: a['name'] < b['name']) 155 156 # Constant 157 result['number'] = self.number 158 result['branch'] = self.branch 159 result['category'] = self.category 160 result['who'] = self.getShortAuthor() 161 result['comments'] = self.comments 162 result['revision'] = self.revision 163 result['rev'] = self.revision 164 result['when'] = self.when 165 result['at'] = self.getTime() 166 result['files'] = files 167 result['revlink'] = getattr(self, 'revlink', None) 168 result['properties'] = self.properties.asList() 169 result['repository'] = getattr(self, 'repository', None) 170 result['project'] = getattr(self, 'project', None) 171 return result
172
173 - def getShortAuthor(self):
174 return self.who
175
176 - def getTime(self):
177 if not self.when: 178 return "?" 179 return time.strftime("%a %d %b %Y %H:%M:%S", 180 time.localtime(self.when))
181
182 - def getTimes(self):
183 return (self.when, None)
184
185 - def getText(self):
186 return [html.escape(self.who)]
187 - def getLogs(self):
188 return {}
189
190 - def getFileContents(self):
191 data = "" 192 if len(self.files) == 1: 193 if self.isdir: 194 data += "Directory: %s\n" % self.files[0] 195 else: 196 data += "File: %s\n" % self.files[0] 197 else: 198 data += "Files:\n" 199 for f in self.files: 200 data += " %s\n" % f 201 return data
202
203 - def getProperties(self):
204 data = "" 205 for prop in self.properties.asList(): 206 data += " %s: %s" % (prop[0], prop[1]) 207 return data
208
209 210 -class ChangeMaster: # pragma: no cover
211 # this is a stub, retained to allow the "buildbot upgrade-master" tool to 212 # read old changes.pck pickle files and convert their contents into the 213 # new database format. This is only instantiated by that tool, or by 214 # test_db.py which tests that tool. The functionality that previously 215 # lived here has been moved into buildbot.changes.manager.ChangeManager 216
217 - def __init__(self):
218 self.changes = [] 219 # self.basedir must be filled in by the parent 220 self.nextNumber = 1
221
222 - def saveYourself(self):
223 filename = os.path.join(self.basedir, "changes.pck") 224 tmpfilename = filename + ".tmp" 225 try: 226 dump(self, open(tmpfilename, "wb")) 227 if runtime.platformType == 'win32': 228 # windows cannot rename a file on top of an existing one 229 if os.path.exists(filename): 230 os.unlink(filename) 231 os.rename(tmpfilename, filename) 232 except Exception: 233 log.msg("unable to save changes") 234 log.err()
235 236 # This method is used by contrib/fix_changes_pickle_encoding.py to recode all 237 # bytestrings in an old changes.pck into unicode strings
238 - def recode_changes(self, old_encoding, quiet=False):
239 """Processes the list of changes, with the change attributes re-encoded 240 unicode objects""" 241 nconvert = 0 242 for c in self.changes: 243 # give revision special handling, in case it is an integer 244 if isinstance(c.revision, int): 245 c.revision = unicode(c.revision) 246 247 for attr in ("who", "comments", "revlink", "category", "branch", "revision"): 248 a = getattr(c, attr) 249 if isinstance(a, str): 250 try: 251 setattr(c, attr, a.decode(old_encoding)) 252 nconvert += 1 253 except UnicodeDecodeError: 254 raise UnicodeError("Error decoding %s of change #%s as %s:\n%r" % 255 (attr, c.number, old_encoding, a)) 256 257 # filenames are a special case, but in general they'll have the same encoding 258 # as everything else on a system. If not, well, hack this script to do your 259 # import! 260 newfiles = [] 261 for filename in util.flatten(c.files): 262 if isinstance(filename, str): 263 try: 264 filename = filename.decode(old_encoding) 265 nconvert += 1 266 except UnicodeDecodeError: 267 raise UnicodeError("Error decoding filename '%s' of change #%s as %s:\n%r" % 268 (filename.decode('ascii', 'replace'), 269 c.number, old_encoding, a)) 270 newfiles.append(filename) 271 c.files = newfiles 272 if not quiet: 273 print "converted %d strings" % nconvert
274
275 -class OldChangeMaster(ChangeMaster): # pragma: no cover
276 # this is a reminder that the ChangeMaster class is old 277 pass 278 # vim: set ts=4 sts=4 sw=4 et: 279