Package buildbot :: Package steps :: Package source :: Module git
[frames] | no frames]

Source Code for Module buildbot.steps.source.git

  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 twisted.python import log 
 17  from twisted.internet import defer 
 18   
 19  from buildbot.process import buildstep 
 20  from buildbot.steps.source.base import Source 
 21  from buildbot.interfaces import BuildSlaveTooOldError 
22 23 -def isTrueOrIsExactlyZero(v):
24 # nonzero values are true... 25 if v: 26 return True 27 28 # ... and True for the number zero, but we have to 29 # explicitly guard against v==False, since 30 # isinstance(False, int) is surprisingly True 31 if isinstance(v, int) and v is not False: 32 return True 33 34 # all other false-ish values are false 35 return False
36 37 git_describe_flags = [ 38 # on or off 39 ('all', lambda v: ['--all'] if v else None), 40 ('always', lambda v: ['--always'] if v else None), 41 ('contains', lambda v: ['--contains'] if v else None), 42 ('debug', lambda v: ['--debug'] if v else None), 43 ('long', lambda v: ['--long'] if v else None), 44 ('exact-match', lambda v: ['--exact-match'] if v else None), 45 ('tags', lambda v: ['--tags'] if v else None), 46 # string parameter 47 ('match', lambda v: ['--match', v] if v else None), 48 # numeric parameter 49 ('abbrev', lambda v: ['--abbrev=%s' % v] if isTrueOrIsExactlyZero(v) else None), 50 ('candidates', lambda v: ['--candidates=%s' % v] if isTrueOrIsExactlyZero(v) else None), 51 # optional string parameter 52 ('dirty', lambda v: ['--dirty'] if (v is True or v=='') else None), 53 ('dirty', lambda v: ['--dirty=%s' % v] if (v and v is not True) else None), 54 ]
55 56 -class Git(Source):
57 """ Class for Git with all the smarts """ 58 name='git' 59 renderables = [ "repourl"] 60
61 - def __init__(self, repourl=None, branch='HEAD', mode='incremental', 62 method=None, submodules=False, shallow=False, progress=False, 63 retryFetch=False, clobberOnFailure=False, getDescription=False, 64 **kwargs):
65 """ 66 @type repourl: string 67 @param repourl: the URL which points at the git repository 68 69 @type branch: string 70 @param branch: The branch or tag to check out by default. If 71 a build specifies a different branch, it will 72 be used instead of this. 73 74 @type submodules: boolean 75 @param submodules: Whether or not to update (and initialize) 76 git submodules. 77 78 @type mode: string 79 @param mode: Type of checkout. Described in docs. 80 81 @type method: string 82 @param method: Full builds can be done is different ways. This parameter 83 specifies which method to use. 84 85 @type progress: boolean 86 @param progress: Pass the --progress option when fetching. This 87 can solve long fetches getting killed due to 88 lack of output, but requires Git 1.7.2+. 89 @type shallow: boolean 90 @param shallow: Use a shallow or clone, if possible 91 92 @type retryFetch: boolean 93 @param retryFetch: Retry fetching before failing source checkout. 94 95 @type getDescription: boolean or dict 96 @param getDescription: Use 'git describe' to describe the fetched revision 97 """ 98 if not getDescription and not isinstance(getDescription, dict): 99 getDescription = False 100 101 self.branch = branch 102 self.method = method 103 self.prog = progress 104 self.repourl = repourl 105 self.retryFetch = retryFetch 106 self.submodules = submodules 107 self.shallow = shallow 108 self.fetchcount = 0 109 self.clobberOnFailure = clobberOnFailure 110 self.mode = mode 111 self.getDescription = getDescription 112 Source.__init__(self, **kwargs) 113 114 assert self.mode in ['incremental', 'full'] 115 assert self.repourl is not None 116 if self.mode == 'full': 117 assert self.method in ['clean', 'fresh', 'clobber', 'copy', None] 118 assert isinstance(self.getDescription, (bool, dict))
119
120 - def startVC(self, branch, revision, patch):
121 self.branch = branch or 'HEAD' 122 self.revision = revision 123 self.method = self._getMethod() 124 self.stdio_log = self.addLog("stdio") 125 126 d = self.checkGit() 127 def checkInstall(gitInstalled): 128 if not gitInstalled: 129 raise BuildSlaveTooOldError("git is not installed on slave") 130 return 0
131 d.addCallback(checkInstall) 132 133 if self.mode == 'incremental': 134 d.addCallback(lambda _: self.incremental()) 135 elif self.mode == 'full': 136 d.addCallback(lambda _: self.full()) 137 if patch: 138 d.addCallback(self.patch, patch) 139 d.addCallback(self.parseGotRevision) 140 d.addCallback(self.parseCommitDescription) 141 d.addCallback(self.finish) 142 d.addErrback(self.failed) 143 return d
144 145 @defer.inlineCallbacks
146 - def full(self):
147 if self.method == 'clobber': 148 yield self.clobber() 149 return 150 elif self.method == 'copy': 151 yield self.copy() 152 return 153 154 updatable = yield self._sourcedirIsUpdatable() 155 if not updatable: 156 log.msg("No git repo present, making full clone") 157 yield self._doFull() 158 elif self.method == 'clean': 159 yield self.clean() 160 elif self.method == 'fresh': 161 yield self.fresh() 162 else: 163 raise ValueError("Unknown method, check your configuration")
164 165 @defer.inlineCallbacks
166 - def incremental(self):
167 updatable = yield self._sourcedirIsUpdatable() 168 169 # if not updateable, do a full checkout 170 if not updatable: 171 yield self._doFull() 172 return 173 174 # test for existence of the revision; rc=1 indicates it does not exist 175 if self.revision: 176 rc = yield self._dovccmd(['cat-file', '-e', self.revision], 177 abandonOnFailure=False) 178 else: 179 rc = 1 180 181 # if revision exists checkout to that revision 182 # else fetch and update 183 if rc == 0: 184 yield self._dovccmd(['reset', '--hard', self.revision, '--']) 185 186 if self.branch != 'HEAD': 187 yield self._dovccmd(['branch', '-M', self.branch], 188 abandonOnFailure=False) 189 else: 190 yield self._doFetch(None) 191 192 yield self._updateSubmodule(None)
193
194 - def clean(self):
195 command = ['clean', '-f', '-d'] 196 d = self._dovccmd(command) 197 d.addCallback(self._doFetch) 198 d.addCallback(self._updateSubmodule) 199 d.addCallback(self._cleanSubmodule) 200 return d
201
202 - def clobber(self):
203 cmd = buildstep.RemoteCommand('rmdir', {'dir': self.workdir, 204 'logEnviron': self.logEnviron,}) 205 cmd.useLog(self.stdio_log, False) 206 d = self.runCommand(cmd) 207 def checkRemoval(res): 208 if res != 0: 209 raise RuntimeError("Failed to delete directory") 210 return res
211 d.addCallback(lambda _: checkRemoval(cmd.rc)) 212 d.addCallback(lambda _: self._doFull()) 213 return d 214
215 - def fresh(self):
216 command = ['clean', '-f', '-d', '-x'] 217 d = self._dovccmd(command) 218 d.addCallback(self._doFetch) 219 d.addCallback(self._updateSubmodule) 220 d.addCallback(self._cleanSubmodule) 221 return d
222
223 - def copy(self):
224 cmd = buildstep.RemoteCommand('rmdir', {'dir': self.workdir, 225 'logEnviron': self.logEnviron,}) 226 cmd.useLog(self.stdio_log, False) 227 d = self.runCommand(cmd) 228 229 self.workdir = 'source' 230 d.addCallback(lambda _: self.incremental()) 231 def copy(_): 232 cmd = buildstep.RemoteCommand('cpdir', 233 {'fromdir': 'source', 234 'todir':'build', 235 'logEnviron': self.logEnviron,}) 236 cmd.useLog(self.stdio_log, False) 237 d = self.runCommand(cmd) 238 return d
239 d.addCallback(copy) 240 def resetWorkdir(_): 241 self.workdir = 'build' 242 return 0 243 244 d.addCallback(resetWorkdir) 245 return d 246
247 - def finish(self, res):
248 d = defer.succeed(res) 249 def _gotResults(results): 250 self.setStatus(self.cmd, results) 251 log.msg("Closing log, sending result of the command %s " % \ 252 (self.cmd)) 253 return results
254 d.addCallback(_gotResults) 255 d.addCallbacks(self.finished, self.checkDisconnect) 256 return d 257 258 @defer.inlineCallbacks
259 - def parseGotRevision(self, _=None):
260 stdout = yield self._dovccmd(['rev-parse', 'HEAD'], collectStdout=True) 261 revision = stdout.strip() 262 if len(revision) != 40: 263 raise buildstep.BuildStepFailed() 264 log.msg("Got Git revision %s" % (revision, )) 265 self.updateSourceProperty('got_revision', revision) 266 267 defer.returnValue(0)
268 269 @defer.inlineCallbacks
270 - def parseCommitDescription(self, _=None):
271 if self.getDescription==False: # dict() should not return here 272 defer.returnValue(0) 273 return 274 275 cmd = ['describe'] 276 if isinstance(self.getDescription, dict): 277 for opt, arg in git_describe_flags: 278 opt = self.getDescription.get(opt, None) 279 arg = arg(opt) 280 if arg: 281 cmd.extend(arg) 282 cmd.append('HEAD') 283 284 try: 285 stdout = yield self._dovccmd(cmd, collectStdout=True) 286 desc = stdout.strip() 287 self.updateSourceProperty('commit-description', desc) 288 except: 289 pass 290 291 defer.returnValue(0)
292
293 - def _dovccmd(self, command, abandonOnFailure=True, collectStdout=False, initialStdin=None):
294 cmd = buildstep.RemoteShellCommand(self.workdir, ['git'] + command, 295 env=self.env, 296 logEnviron=self.logEnviron, 297 timeout=self.timeout, 298 collectStdout=collectStdout, 299 initialStdin=initialStdin) 300 cmd.useLog(self.stdio_log, False) 301 log.msg("Starting git command : git %s" % (" ".join(command), )) 302 d = self.runCommand(cmd) 303 def evaluateCommand(cmd): 304 if abandonOnFailure and cmd.didFail(): 305 log.msg("Source step failed while running command %s" % cmd) 306 raise buildstep.BuildStepFailed() 307 if collectStdout: 308 return cmd.stdout 309 else: 310 return cmd.rc
311 d.addCallback(lambda _: evaluateCommand(cmd)) 312 return d 313
314 - def _fetch(self, _):
315 command = ['fetch', '-t', self.repourl, self.branch] 316 # If the 'progress' option is set, tell git fetch to output 317 # progress information to the log. This can solve issues with 318 # long fetches killed due to lack of output, but only works 319 # with Git 1.7.2 or later. 320 if self.prog: 321 command.append('--progress') 322 323 d = self._dovccmd(command) 324 def checkout(_): 325 if self.revision: 326 rev = self.revision 327 else: 328 rev = 'FETCH_HEAD' 329 command = ['reset', '--hard', rev, '--'] 330 abandonOnFailure = not self.retryFetch and not self.clobberOnFailure 331 return self._dovccmd(command, abandonOnFailure)
332 d.addCallback(checkout) 333 def renameBranch(res): 334 if res != 0: 335 return res 336 d = self._dovccmd(['branch', '-M', self.branch], abandonOnFailure=False) 337 # Ignore errors 338 d.addCallback(lambda _: res) 339 return d 340 341 if self.branch != 'HEAD': 342 d.addCallback(renameBranch) 343 return d 344
345 - def patch(self, _, patch):
346 d = self._dovccmd(['apply', '--index', '-p', str(patch[0])], 347 initialStdin=patch[1]) 348 return d
349 350 @defer.inlineCallbacks
351 - def _doFetch(self, _):
352 """ 353 Handles fallbacks for failure of fetch, 354 wrapper for self._fetch 355 """ 356 res = yield self._fetch(None) 357 if res == 0: 358 defer.returnValue(res) 359 return 360 elif self.retryFetch: 361 yield self._fetch(None) 362 elif self.clobberOnFailure: 363 yield self.clobber() 364 else: 365 raise buildstep.BuildStepFailed()
366
367 - def _full(self):
368 args = [] 369 if self.branch != 'HEAD': 370 args += ['--branch', self.branch] 371 if self.shallow: 372 args += ['--depth', '1'] 373 command = ['clone'] + args + [self.repourl, '.'] 374 #Fix references 375 if self.prog: 376 command.append('--progress') 377 378 d = self._dovccmd(command, not self.clobberOnFailure) 379 # If revision specified checkout that revision 380 if self.revision: 381 d.addCallback(lambda _: self._dovccmd(['reset', '--hard', 382 self.revision, '--'], 383 not self.clobberOnFailure)) 384 # init and update submodules, recurisively. If there's not recursion 385 # it will not do it. 386 if self.submodules: 387 d.addCallback(lambda _: self._dovccmd(['submodule', 'update', 388 '--init', '--recursive'], 389 not self.clobberOnFailure)) 390 return d
391
392 - def _doFull(self):
393 d = self._full() 394 def clobber(res): 395 if res != 0: 396 if self.clobberOnFailure: 397 return self.clobber() 398 else: 399 raise buildstep.BuildStepFailed() 400 else: 401 return res
402 d.addCallback(clobber) 403 return d 404
405 - def computeSourceRevision(self, changes):
406 if not changes: 407 return None 408 return changes[-1].revision
409
410 - def _sourcedirIsUpdatable(self):
411 cmd = buildstep.RemoteCommand('stat', {'file': self.workdir + '/.git', 412 'logEnviron': self.logEnviron,}) 413 cmd.useLog(self.stdio_log, False) 414 d = self.runCommand(cmd) 415 def _fail(tmp): 416 if cmd.didFail(): 417 return False 418 return True
419 d.addCallback(_fail) 420 return d 421
422 - def _updateSubmodule(self, _):
423 if self.submodules: 424 return self._dovccmd(['submodule', 'update', '--recursive']) 425 else: 426 return defer.succeed(0)
427
428 - def _cleanSubmodule(self, _):
429 if self.submodules: 430 command = ['submodule', 'foreach', 'git', 'clean', '-f', '-d'] 431 if self.mode == 'full' and self.method == 'fresh': 432 command.append('-x') 433 return self._dovccmd(command) 434 else: 435 return defer.succeed(0)
436
437 - def _getMethod(self):
438 if self.method is not None and self.mode != 'incremental': 439 return self.method 440 elif self.mode == 'incremental': 441 return None 442 elif self.method is None and self.mode == 'full': 443 return 'fresh'
444
445 - def checkGit(self):
446 d = self._dovccmd(['--version']) 447 def check(res): 448 if res == 0: 449 return True 450 return False
451 d.addCallback(check) 452 return d 453