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 ssid = None 74 75 compare_attrs = ('branch', 'revision', 'patch', 'patch_info', 'changes', 'project', 'repository') 76 77 implements(interfaces.ISourceStamp) 78 79 @classmethod
80 - def fromSsdict(cls, master, ssdict):
81 """ 82 Class method to create a L{SourceStamp} from a dictionary as returned 83 by L{SourceStampConnectorComponent.getSourceStamp}. 84 85 @param master: build master instance 86 @param ssdict: source stamp dictionary 87 88 @returns: L{SourceStamp} via Deferred 89 """ 90 # try to fetch from the cache, falling back to _make_ss if not 91 # found 92 cache = master.caches.get_cache("SourceStamps", cls._make_ss) 93 return cache.get(ssdict['ssid'], ssdict=ssdict, master=master)
94 95 @classmethod
96 - def _make_ss(cls, ssid, ssdict, master):
97 sourcestamp = cls(_fromSsdict=True) 98 sourcestamp.ssid = ssid 99 sourcestamp.branch = ssdict['branch'] 100 sourcestamp.revision = ssdict['revision'] 101 sourcestamp.project = ssdict['project'] 102 sourcestamp.repository = ssdict['repository'] 103 104 sourcestamp.patch = None 105 if ssdict['patch_body']: 106 # note that this class does not store the patch_subdir 107 sourcestamp.patch = (ssdict['patch_level'], 108 ssdict['patch_body']) 109 sourcestamp.patch_info = (ssdict['patch_author'], 110 ssdict['patch_comment']) 111 112 if ssdict['changeids']: 113 # sort the changeids in order, oldest to newest 114 sorted_changeids = sorted(ssdict['changeids']) 115 def gci(id): 116 d = master.db.changes.getChange(id) 117 d.addCallback(lambda chdict : 118 Change.fromChdict(master, chdict)) 119 return d
120 d = defer.gatherResults([ gci(id) 121 for id in sorted_changeids ]) 122 else: 123 d = defer.succeed([]) 124 def got_changes(changes): 125 sourcestamp.changes = tuple(changes) 126 return sourcestamp
127 d.addCallback(got_changes) 128 return d 129
130 - def __init__(self, branch=None, revision=None, patch=None, 131 patch_info=None, changes=None, project='', repository='', 132 _fromSsdict=False, _ignoreChanges=False):
133 # skip all this madness if we're being built from the database 134 if _fromSsdict: 135 return 136 137 if patch is not None: 138 assert len(patch) == 2 139 assert int(patch[0]) != -1 140 self.branch = branch 141 self.patch = patch 142 self.patch_info = patch_info 143 self.project = project or '' 144 self.repository = repository or '' 145 if changes and not _ignoreChanges: 146 self.changes = tuple(changes) 147 # set branch and revision to most recent change 148 self.branch = changes[-1].branch 149 revision = changes[-1].revision 150 if not self.project and hasattr(changes[-1], 'project'): 151 self.project = changes[-1].project 152 if not self.repository and hasattr(changes[-1], 'repository'): 153 self.repository = changes[-1].repository 154 155 if revision is not None: 156 if isinstance(revision, int): 157 revision = str(revision) 158 159 self.revision = revision 160 self._getSourceStampId_lock = defer.DeferredLock();
161
162 - def canBeMergedWith(self, other):
163 # this algorithm implements the "compatible" mergeRequests defined in 164 # detail in cfg-buidlers.texinfo; change that documentation if the 165 # algorithm changes! 166 if other.repository != self.repository: 167 return False 168 if other.branch != self.branch: 169 return False # the builds are completely unrelated 170 if other.project != self.project: 171 return False 172 173 if self.changes and other.changes: 174 return True 175 elif self.changes and not other.changes: 176 return False # we're using changes, they aren't 177 elif not self.changes and other.changes: 178 return False # they're using changes, we aren't 179 180 if self.patch or other.patch: 181 return False # you can't merge patched builds with anything 182 if self.revision == other.revision: 183 # both builds are using the same specific revision, so they can 184 # be merged. It might be the case that revision==None, so they're 185 # both building HEAD. 186 return True 187 188 return False
189
190 - def mergeWith(self, others):
191 """Generate a SourceStamp for the merger of me and all the other 192 SourceStamps. This is called by a Build when it starts, to figure 193 out what its sourceStamp should be.""" 194 195 # either we're all building the same thing (changes==None), or we're 196 # all building changes (which can be merged) 197 changes = [] 198 changes.extend(self.changes) 199 for ss in others: 200 changes.extend(ss.changes) 201 newsource = SourceStamp(branch=self.branch, 202 revision=self.revision, 203 patch=self.patch, 204 patch_info=self.patch_info, 205 project=self.project, 206 repository=self.repository, 207 changes=changes) 208 return newsource
209
210 - def getAbsoluteSourceStamp(self, got_revision):
211 return SourceStamp(branch=self.branch, revision=got_revision, 212 patch=self.patch, repository=self.repository, 213 project=self.project, changes=self.changes, 214 _ignoreChanges=True)
215
216 - def getText(self):
217 # note: this won't work for VC systems with huge 'revision' strings 218 text = [] 219 if self.project: 220 text.append("for %s" % self.project) 221 if self.repository: 222 text.append("in %s" % self.repository) 223 if self.revision is None: 224 return text + [ "latest" ] 225 text.append(str(self.revision)) 226 if self.branch: 227 text.append("in '%s'" % self.branch) 228 if self.patch: 229 text.append("[patch]") 230 return text
231
232 - def asDict(self):
233 result = {} 234 # Constant 235 result['revision'] = self.revision 236 # TODO(maruel): Make the patch content a suburl. 237 result['hasPatch'] = self.patch is not None 238 result['branch'] = self.branch 239 result['changes'] = [c.asDict() for c in getattr(self, 'changes', [])] 240 result['project'] = self.project 241 result['repository'] = self.repository 242 return result
243
244 - def upgradeToVersion1(self):
245 # version 0 was untyped; in version 1 and later, types matter. 246 if self.branch is not None and not isinstance(self.branch, str): 247 self.branch = str(self.branch) 248 if self.revision is not None and not isinstance(self.revision, str): 249 self.revision = str(self.revision) 250 if self.patch is not None: 251 self.patch = ( int(self.patch[0]), str(self.patch[1]) ) 252 self.wasUpgraded = True
253
254 - def upgradeToVersion2(self):
255 # version 1 did not have project or repository; just set them to a default '' 256 self.project = '' 257 self.repository = '' 258 self.wasUpgraded = True
259 260 @util.deferredLocked('_getSourceStampId_lock')
261 - def getSourceStampId(self, master):
262 "temporary; do not use widely!" 263 if self.ssid: 264 return defer.succeed(self.ssid) 265 # add it to the DB 266 patch_body = None 267 patch_level = None 268 if self.patch: 269 patch_level, patch_body = self.patch 270 271 patch_author = None 272 patch_comment = None 273 if self.patch_info: 274 patch_author, patch_comment = self.patch_info 275 276 d = master.db.sourcestamps.addSourceStamp( 277 branch=self.branch, revision=self.revision, 278 repository=self.repository, project=self.project, 279 patch_body=patch_body, patch_level=patch_level, 280 patch_author=patch_author, patch_comment=patch_comment, 281 patch_subdir=None, changeids=[c.number for c in self.changes]) 282 def set_ssid(ssid): 283 self.ssid = ssid 284 return ssid
285 d.addCallback(set_ssid) 286 return d 287