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

Source Code for Module buildbot.steps.source.mercurial

  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  ## Source step code for mercurial 
 17   
 18  from twisted.python import log 
 19  from twisted.internet import defer 
 20   
 21  from buildbot.process import buildstep 
 22  from buildbot.steps.source.base import Source 
 23  from buildbot.interfaces import BuildSlaveTooOldError 
 24  from buildbot.config import ConfigErrors 
 25  from buildbot.status.results import SUCCESS 
26 27 -class Mercurial(Source):
28 """ Class for Mercurial with all the smarts """ 29 name = "hg" 30 31 renderables = [ "repourl" ] 32 possible_modes = ('incremental', 'full') 33 possible_methods = (None, 'clean', 'fresh', 'clobber') 34 possible_branchTypes = ('inrepo', 'dirname') 35
36 - def __init__(self, repourl=None, mode='incremental', 37 method=None, defaultBranch=None, branchType='dirname', 38 clobberOnBranchChange=True, **kwargs):
39 40 """ 41 @type repourl: string 42 @param repourl: the URL which points at the Mercurial repository. 43 if 'dirname' branches are enabled, this is the base URL 44 to which a branch name will be appended. It should 45 probably end in a slash. 46 47 @param defaultBranch: if branches are enabled, this is the branch 48 to use if the Build does not specify one 49 explicitly. 50 For 'dirname' branches, It will simply be 51 appended to C{repourl} and the result handed to 52 the 'hg update' command. 53 For 'inrepo' branches, this specifies the named 54 revision to which the tree will update after a 55 clone. 56 57 @param branchType: either 'dirname' or 'inrepo' depending on whether 58 the branch name should be appended to the C{repourl} 59 or the branch is a mercurial named branch and can be 60 found within the C{repourl} 61 62 @param clobberOnBranchChange: boolean, defaults to True. If set and 63 using inrepos branches, clobber the tree 64 at each branch change. Otherwise, just 65 update to the branch. 66 """ 67 68 self.repourl = repourl 69 self.defaultBranch = self.branch = defaultBranch 70 self.branchType = branchType 71 self.method = method 72 self.clobberOnBranchChange = clobberOnBranchChange 73 self.mode = mode 74 Source.__init__(self, **kwargs) 75 76 errors = [] 77 if self.mode not in self.possible_modes: 78 errors.append("mode %s is not one of %s" % 79 (self.mode, self.possible_modes)) 80 if self.method not in self.possible_methods: 81 errors.append("method %s is not one of %s" % 82 (self.method, self.possible_methods)) 83 if self.branchType not in self.possible_branchTypes: 84 errors.append("branchType %s is not one of %s" % 85 (self.branchType, self.possible_branchTypes)) 86 87 if repourl is None: 88 errors.append("you must provide a repourl") 89 90 if errors: 91 raise ConfigErrors(errors)
92
93 - def startVC(self, branch, revision, patch):
94 self.revision = revision 95 self.method = self._getMethod() 96 self.stdio_log = self.addLog("stdio") 97 d = self.checkHg() 98 def checkInstall(hgInstalled): 99 if not hgInstalled: 100 raise BuildSlaveTooOldError("Mercurial is not installed on slave") 101 return 0
102 d.addCallback(checkInstall) 103 104 if self.branchType == 'dirname': 105 self.repourl = self.repourl + (branch or '') 106 self.branch = self.defaultBranch 107 self.update_branch = branch 108 elif self.branchType == 'inrepo': 109 self.update_branch = (branch or 'default') 110 111 if self.mode == 'full': 112 d.addCallback(lambda _: self.full()) 113 elif self.mode == 'incremental': 114 d.addCallback(lambda _: self.incremental()) 115 116 if patch: 117 d.addCallback(self.patch, patch) 118 119 d.addCallback(self.parseGotRevision) 120 d.addCallback(self.finish) 121 d.addErrback(self.failed)
122 123 @defer.inlineCallbacks
124 - def full(self):
125 if self.method == 'clobber': 126 yield self.clobber(None) 127 return 128 129 updatable = yield self._sourcedirIsUpdatable() 130 if not updatable: 131 yield self._dovccmd(['clone', self.repourl, '.']) 132 elif self.method == 'clean': 133 yield self.clean(None) 134 elif self.method == 'fresh': 135 yield self.fresh(None) 136 else: 137 raise ValueError("Unknown method, check your configuration")
138
139 - def incremental(self):
140 if self.method is not None: 141 raise ValueError(self.method) 142 143 d = self._sourcedirIsUpdatable() 144 def _cmd(updatable): 145 if updatable: 146 command = ['pull', self.repourl] 147 else: 148 command = ['clone', self.repourl, '.', '--noupdate'] 149 return command
150 151 d.addCallback(_cmd) 152 d.addCallback(self._dovccmd) 153 d.addCallback(self._checkBranchChange) 154 return d 155
156 - def clean(self, _):
157 command = ['--config', 'extensions.purge=', 'purge'] 158 d = self._dovccmd(command) 159 d.addCallback(self._pullUpdate) 160 return d
161
162 - def clobber(self, _):
163 cmd = buildstep.RemoteCommand('rmdir', {'dir': self.workdir, 164 'logEnviron':self.logEnviron}) 165 cmd.useLog(self.stdio_log, False) 166 d = self.runCommand(cmd) 167 d.addCallback(lambda _: self._dovccmd(['clone', '--noupdate' 168 , self.repourl, "."])) 169 d.addCallback(self._update) 170 return d
171
172 - def fresh(self, _):
173 command = ['--config', 'extensions.purge=', 'purge', '--all'] 174 d = self._dovccmd(command) 175 d.addCallback(self._pullUpdate) 176 return d
177
178 - def finish(self, res):
179 d = defer.succeed(res) 180 def _gotResults(results): 181 self.setStatus(self.cmd, results) 182 return results
183 d.addCallback(_gotResults) 184 d.addCallbacks(self.finished, self.checkDisconnect) 185 return d 186
187 - def parseGotRevision(self, _):
188 d = self._dovccmd(['parents', '--template', '{node}\\n'], collectStdout=True) 189 def _setrev(stdout): 190 revision = stdout.strip() 191 if len(revision) != 40: 192 raise ValueError("Incorrect revision id") 193 log.msg("Got Mercurial revision %s" % (revision, )) 194 self.updateSourceProperty('got_revision', revision) 195 return 0
196 d.addCallback(_setrev) 197 return d 198 199 @defer.inlineCallbacks
200 - def _checkBranchChange(self, _):
201 current_branch = yield self._getCurrentBranch() 202 msg = "Working dir is on in-repo branch '%s' and build needs '%s'." % \ 203 (current_branch, self.update_branch) 204 if current_branch != self.update_branch and self.clobberOnBranchChange: 205 msg += ' Clobbering.' 206 log.msg(msg) 207 yield self.clobber(None) 208 return 209 msg += ' Updating.' 210 log.msg(msg) 211 yield self._removeAddedFilesAndUpdate(None)
212
213 - def _pullUpdate(self, res):
214 command = ['pull' , self.repourl] 215 if self.revision: 216 command.extend(['--rev', self.revision]) 217 d = self._dovccmd(command) 218 d.addCallback(self._checkBranchChange) 219 return d
220
221 - def _dovccmd(self, command, collectStdout=False, initialStdin=None, decodeRC={0:SUCCESS}):
222 if not command: 223 raise ValueError("No command specified") 224 cmd = buildstep.RemoteShellCommand(self.workdir, ['hg', '--verbose'] + command, 225 env=self.env, 226 logEnviron=self.logEnviron, 227 timeout=self.timeout, 228 collectStdout=collectStdout, 229 initialStdin=initialStdin, 230 decodeRC=decodeRC) 231 cmd.useLog(self.stdio_log, False) 232 log.msg("Starting mercurial command : hg %s" % (" ".join(command), )) 233 d = self.runCommand(cmd) 234 def evaluateCommand(cmd): 235 if cmd.didFail(): 236 log.msg("Source step failed while running command %s" % cmd) 237 raise buildstep.BuildStepFailed() 238 if collectStdout: 239 return cmd.stdout 240 else: 241 return cmd.rc
242 d.addCallback(lambda _: evaluateCommand(cmd)) 243 return d 244
245 - def computeSourceRevision(self, changes):
246 if not changes: 247 return None 248 # without knowing the revision ancestry graph, we can't sort the 249 # changes at all. So for now, assume they were given to us in sorted 250 # order, and just pay attention to the last one. See ticket #103 for 251 # more details. 252 if len(changes) > 1: 253 log.msg("Mercurial.computeSourceRevision: warning: " 254 "there are %d changes here, assuming the last one is " 255 "the most recent" % len(changes)) 256 return changes[-1].revision
257
258 - def patch(self, _, patch):
259 d = self._dovccmd(['import', '--no-commit', '-p', str(patch[0]), '-'], 260 initialStdin=patch[1]) 261 return d
262
263 - def _getCurrentBranch(self):
264 if self.branchType == 'dirname': 265 return defer.succeed(self.branch) 266 else: 267 d = self._dovccmd(['identify', '--branch'], collectStdout=True) 268 def _getbranch(stdout): 269 return stdout.strip()
270 d.addCallback(_getbranch).addErrback 271 return d 272
273 - def _getMethod(self):
274 if self.method is not None and self.mode != 'incremental': 275 return self.method 276 elif self.mode == 'incremental': 277 return None 278 elif self.method is None and self.mode == 'full': 279 return 'fresh'
280
281 - def _sourcedirIsUpdatable(self):
282 cmd = buildstep.RemoteCommand('stat', {'file': self.workdir + '/.hg', 283 'logEnviron': self.logEnviron}) 284 cmd.useLog(self.stdio_log, False) 285 d = self.runCommand(cmd) 286 def _fail(tmp): 287 if cmd.didFail(): 288 return False 289 return True
290 d.addCallback(_fail) 291 return d 292
293 - def _removeAddedFilesAndUpdate(self, _):
294 command = ['locate', 'set:added()'] 295 d = self._dovccmd(command, collectStdout=True, decodeRC={0:SUCCESS,1:SUCCESS}) 296 def parseAndRemove(stdout): 297 files = [] 298 for filename in stdout.splitlines() : 299 filename = self.workdir+'/'+filename 300 files.append(filename) 301 if len(files) == 0: 302 d = defer.succeed(0) 303 else: 304 if self.slaveVersionIsOlderThan('rmdir', '2.14'): 305 d = self.removeFiles(files) 306 else: 307 cmd = buildstep.RemoteCommand('rmdir', {'dir': files, 308 'logEnviron': 309 self.logEnviron,}) 310 cmd.useLog(self.stdio_log, False) 311 d = self.runCommand(cmd) 312 d.addCallback(lambda _: cmd.rc) 313 return d
314 d.addCallback(parseAndRemove) 315 d.addCallback(self._update) 316 return d 317 318 @defer.inlineCallbacks
319 - def removeFiles(self, files):
320 for filename in files: 321 cmd = buildstep.RemoteCommand('rmdir', {'dir': filename, 322 'logEnviron': self.logEnviron,}) 323 cmd.useLog(self.stdio_log, False) 324 yield self.runCommand(cmd) 325 if cmd.rc != 0: 326 defer.returnValue(cmd.rc) 327 return 328 defer.returnValue(0)
329
330 - def _update(self, _):
331 command = ['update', '--clean'] 332 if self.revision: 333 command += ['--rev', self.revision] 334 elif self.branchType == 'inrepo': 335 command += ['--rev', self.update_branch] 336 d = self._dovccmd(command) 337 return d
338
339 - def checkHg(self):
340 d = self._dovccmd(['--version']) 341 def check(res): 342 if res == 0: 343 return True 344 return False
345 d.addCallback(check) 346 return d 347