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

Source Code for Module buildbot.sourcestamp

  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   
 17  from zope.interface import implements 
 18  from twisted.persisted import styles 
 19  from twisted.internet import defer 
 20  from buildbot.changes.changes import Change 
 21  from buildbot import util, interfaces 
22 23 # TODO: kill this class, or at least make it less significant 24 -class SourceStamp(util.ComparableMixin, styles.Versioned):
25 """This is a tuple of (branch, revision, patchspec, changes, project, repository). 26 27 C{branch} is always valid, although it may be None to let the Source 28 step use its default branch. There are three possibilities for the 29 remaining elements: 30 - (revision=REV, patchspec=None, changes=None): build REV. If REV is 31 None, build the HEAD revision from the given branch. Note that REV 32 must always be a string: SVN, Perforce, and other systems which use 33 integers should provide a string here, but the Source checkout step 34 will integerize it when making comparisons. 35 - (revision=REV, patchspec=(LEVEL, DIFF), changes=None): checkout REV, 36 then apply a patch to the source, with C{patch -pPATCHLEVEL <DIFF}. 37 If REV is None, checkout HEAD and patch it. 38 - (revision=None, patchspec=None, changes=[CHANGES]): let the Source 39 step check out the latest revision indicated by the given Changes. 40 CHANGES is a tuple of L{buildbot.changes.changes.Change} instances, 41 and all must be on the same branch. 42 43 @ivar ssid: sourcestamp ID, or None if this sourcestamp has not yet been 44 added to the database 45 46 @ivar branch: branch name or None 47 48 @ivar revision: revision string or None 49 50 @ivar patch: tuple (patch level, patch body) or None 51 52 @ivar patch_info: tuple (patch author, patch comment) or None 53 54 @ivar changes: tuple of changes that went into this source stamp, sorted by 55 number 56 57 @ivar project: project name 58 59 @ivar repository: repository URL 60 """ 61 62 persistenceVersion = 2 63 persistenceForgets = ( 'wasUpgraded', ) 64 65 # all seven of these are publicly visible attributes 66 branch = None 67 revision = None 68 patch = None 69 patch_info = None 70 changes = () 71 project = '' 72 repository = '' 73 sourcestampsetid = None 74 ssid = None 75 76 compare_attrs = ('branch', 'revision', 'patch', 'patch_info', 'changes', 'project', 'repository') 77 78 implements(interfaces.ISourceStamp) 79 80 @classmethod
81 - def fromSsdict(cls, master, ssdict):
82 """ 83 Class method to create a L{SourceStamp} from a dictionary as returned 84 by L{SourceStampConnectorComponent.getSourceStamp}. 85 86 @param master: build master instance 87 @param ssdict: source stamp dictionary 88 89 @returns: L{SourceStamp} via Deferred 90 """ 91 # try to fetch from the cache, falling back to _make_ss if not 92 # found 93 cache = master.caches.get_cache("SourceStamps", cls._make_ss) 94 return cache.get(ssdict['ssid'], ssdict=ssdict, master=master)
95 96 @classmethod
97 - def _make_ss(cls, ssid, ssdict, master):
98 sourcestamp = cls(_fromSsdict=True) 99 sourcestamp.ssid = ssid 100 sourcestamp.branch = ssdict['branch'] 101 sourcestamp.revision = ssdict['revision'] 102 sourcestamp.project = ssdict['project'] 103 sourcestamp.repository = ssdict['repository'] 104 sourcestamp.sourcestampsetid = ssdict['sourcestampsetid'] 105 106 sourcestamp.patch = None 107 if ssdict['patch_body']: 108 sourcestamp.patch = (ssdict['patch_level'], ssdict['patch_body'], 109 ssdict.get('patch_subdir')) 110 sourcestamp.patch_info = (ssdict['patch_author'], 111 ssdict['patch_comment']) 112 113 if ssdict['changeids']: 114 # sort the changeids in order, oldest to newest 115 sorted_changeids = sorted(ssdict['changeids']) 116 def gci(id): 117 d = master.db.changes.getChange(id) 118 d.addCallback(lambda chdict : 119 Change.fromChdict(master, chdict)) 120 return d
121 d = defer.gatherResults([ gci(id) 122 for id in sorted_changeids ]) 123 else: 124 d = defer.succeed([]) 125 def got_changes(changes): 126 sourcestamp.changes = tuple(changes) 127 return sourcestamp
128 d.addCallback(got_changes) 129 return d 130
131 - def __init__(self, branch=None, revision=None, patch=None, 132 patch_info=None, changes=None, project='', repository='', 133 _fromSsdict=False, _ignoreChanges=False):
134 self._getSourceStampSetId_lock = defer.DeferredLock(); 135 136 # skip all this madness if we're being built from the database 137 if _fromSsdict: 138 return 139 140 if patch is not None: 141 assert 2 <= len(patch) <= 3 142 assert int(patch[0]) != -1 143 self.branch = branch 144 self.patch = patch 145 self.patch_info = patch_info 146 self.project = project or '' 147 self.repository = repository or '' 148 if changes: 149 self.changes = tuple(changes) 150 if changes and not _ignoreChanges: 151 # set branch and revision to most recent change 152 self.branch = changes[-1].branch 153 revision = changes[-1].revision 154 if not self.project and hasattr(changes[-1], 'project'): 155 self.project = changes[-1].project 156 if not self.repository and hasattr(changes[-1], 'repository'): 157 self.repository = changes[-1].repository 158 159 if revision is not None: 160 if isinstance(revision, int): 161 revision = str(revision) 162 163 self.revision = revision
164
165 - def canBeMergedWith(self, other):
166 # this algorithm implements the "compatible" mergeRequests defined in 167 # detail in cfg-buidlers.texinfo; change that documentation if the 168 # algorithm changes! 169 if other.repository != self.repository: 170 return False 171 if other.branch != self.branch: 172 return False # the builds are completely unrelated 173 if other.project != self.project: 174 return False 175 if self.patch or other.patch: 176 return False # you can't merge patched builds with anything 177 178 if self.changes and other.changes: 179 return True 180 elif self.changes and not other.changes: 181 return False # we're using changes, they aren't 182 elif not self.changes and other.changes: 183 return False # they're using changes, we aren't 184 185 if self.revision == other.revision: 186 # both builds are using the same specific revision, so they can 187 # be merged. It might be the case that revision==None, so they're 188 # both building HEAD. 189 return True 190 191 return False
192
193 - def mergeWith(self, others):
194 """Generate a SourceStamp for the merger of me and all the other 195 SourceStamps. This is called by a Build when it starts, to figure 196 out what its sourceStamp should be.""" 197 198 # either we're all building the same thing (changes==None), or we're 199 # all building changes (which can be merged) 200 changes = [] 201 changes.extend(self.changes) 202 for ss in others: 203 changes.extend(ss.changes) 204 newsource = SourceStamp(branch=self.branch, 205 revision=self.revision, 206 patch=self.patch, 207 patch_info=self.patch_info, 208 project=self.project, 209 repository=self.repository, 210 changes=changes) 211 return newsource
212
213 - def getAbsoluteSourceStamp(self, got_revision):
214 return SourceStamp(branch=self.branch, revision=got_revision, 215 patch=self.patch, repository=self.repository, 216 patch_info=self.patch_info, 217 project=self.project, changes=self.changes, 218 _ignoreChanges=True)
219
220 - def getText(self):
221 # note: this won't work for VC systems with huge 'revision' strings 222 text = [] 223 if self.project: 224 text.append("for %s" % self.project) 225 if self.repository: 226 text.append("in %s" % self.repository) 227 if self.revision is None: 228 return text + [ "latest" ] 229 text.append(str(self.revision)) 230 if self.branch: 231 text.append("in '%s'" % self.branch) 232 if self.patch: 233 text.append("[patch]") 234 return text
235
236 - def asDict(self):
237 result = {} 238 # Constant 239 result['revision'] = self.revision 240 # TODO(maruel): Make the patch content a suburl. 241 result['hasPatch'] = self.patch is not None 242 result['branch'] = self.branch 243 result['changes'] = [c.asDict() for c in getattr(self, 'changes', [])] 244 result['project'] = self.project 245 result['repository'] = self.repository 246 return result
247
248 - def __setstate__(self, d):
249 styles.Versioned.__setstate__(self, d) 250 self._getSourceStampSetId_lock = defer.DeferredLock();
251
252 - def upgradeToVersion1(self):
253 # version 0 was untyped; in version 1 and later, types matter. 254 if self.branch is not None and not isinstance(self.branch, str): 255 self.branch = str(self.branch) 256 if self.revision is not None and not isinstance(self.revision, str): 257 self.revision = str(self.revision) 258 if self.patch is not None: 259 self.patch = ( int(self.patch[0]), str(self.patch[1]) ) 260 self.wasUpgraded = True
261
262 - def upgradeToVersion2(self):
263 # version 1 did not have project or repository; just set them to a default '' 264 self.project = '' 265 self.repository = '' 266 self.wasUpgraded = True
267 268 @util.deferredLocked('_getSourceStampSetId_lock')
269 - def getSourceStampSetId(self, master):
270 "temporary; do not use widely!" 271 if self.sourcestampsetid: 272 return defer.succeed(self.sourcestampsetid) 273 # add it to the DB 274 patch_body = None 275 patch_level = None 276 patch_subdir = None 277 if self.patch: 278 patch_level = self.patch[0] 279 patch_body = self.patch[1] 280 if len(self.patch) > 2: 281 patch_subdir = self.patch[2] 282 283 patch_author = None 284 patch_comment = None 285 if self.patch_info: 286 patch_author, patch_comment = self.patch_info 287 288 def get_setid(): 289 if self.sourcestampsetid != None: 290 return defer.succeed( self.sourcestampsetid ) 291 else: 292 return master.db.sourcestampsets.addSourceStampSet() 293 return d
294 295 def set_setid(setid): 296 self.sourcestampsetid = setid 297 return setid 298 299 def add_sourcestamp(setid): 300 return master.db.sourcestamps.addSourceStamp( 301 sourcestampsetid=setid, 302 branch=self.branch, revision=self.revision, 303 repository=self.repository, project=self.project, 304 patch_body=patch_body, patch_level=patch_level, 305 patch_author=patch_author, patch_comment=patch_comment, 306 patch_subdir=patch_subdir, 307 changeids=[c.number for c in self.changes]) 308 309 def set_ssid(ssid): 310 self.ssid = ssid 311 return ssid 312 313 d = get_setid() 314 d.addCallback(set_setid) 315 d.addCallback(add_sourcestamp) 316 d.addCallback(set_ssid) 317 d.addCallback(lambda _ : self.sourcestampsetid) 318 return d 319