Package buildslave :: Package commands :: Module hg
[frames] | no frames]

Source Code for Module buildslave.commands.hg

  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, re 
 17   
 18  from twisted.python import log, runtime 
 19   
 20  from buildslave.commands.base import SourceBaseCommand, AbandonChain 
 21  from buildslave import runprocess 
 22  from buildslave.util import remove_userpassword 
 23   
 24   
25 -class Mercurial(SourceBaseCommand):
26 """Mercurial specific VC operation. In addition to the arguments 27 handled by SourceBaseCommand, this command reads the following keys: 28 29 ['repourl'] (required): the Mercurial repository string 30 ['clobberOnBranchChange']: Document me. See ticket #462. 31 """ 32 33 header = "mercurial operation" 34
35 - def setup(self, args):
36 SourceBaseCommand.setup(self, args) 37 self.repourl = args['repourl'] 38 self.clobberOnBranchChange = args.get('clobberOnBranchChange', True) 39 self.sourcedata = "%s\n" % self.repourl 40 self.branchType = args.get('branchType', 'dirname') 41 self.stdout = "" 42 self.stderr = "" 43 self.clobbercount = 0 # n times we've clobbered
44
45 - def sourcedirIsUpdateable(self):
46 return os.path.isdir(os.path.join(self.builder.basedir, 47 self.srcdir, ".hg"))
48
49 - def doVCUpdate(self):
50 hg = self.getCommand('hg') 51 d = os.path.join(self.builder.basedir, self.srcdir) 52 command = [hg, 'pull', '--verbose', self.repourl] 53 c = runprocess.RunProcess(self.builder, command, d, 54 sendRC=False, timeout=self.timeout, 55 maxTime=self.maxTime, keepStdout=True, 56 logEnviron=self.logEnviron, usePTY=False) 57 self.command = c 58 d = c.start() 59 d.addCallback(self._handleEmptyUpdate) 60 d.addCallback(self._update) 61 return d
62
63 - def _handleEmptyUpdate(self, res):
64 if type(res) is int and res == 1: 65 if self.command.stdout.find("no changes found") != -1: 66 # 'hg pull', when it doesn't have anything to do, exits with 67 # rc=1, and there appears to be no way to shut this off. It 68 # emits a distinctive message to stdout, though. So catch 69 # this and pretend that it completed successfully. 70 return 0 71 return res
72
73 - def doVCFull(self):
74 hg = self.getCommand('hg') 75 command = [hg, 'clone', '--verbose', '--noupdate'] 76 77 # if got revision, clobbering and in dirname, only clone to specific revision 78 # (otherwise, do full clone to re-use .hg dir for subsequent builds) 79 if self.args.get('revision') and self.mode == 'clobber' and self.branchType == 'dirname': 80 command.extend(['--rev', self.args.get('revision')]) 81 command.extend([self.repourl, self.srcdir]) 82 83 c = runprocess.RunProcess(self.builder, command, self.builder.basedir, 84 sendRC=False, timeout=self.timeout, 85 maxTime=self.maxTime, logEnviron=self.logEnviron, 86 usePTY=False) 87 self.command = c 88 cmd1 = c.start() 89 cmd1.addCallback(self._update) 90 return cmd1
91
92 - def _clobber(self, dummy, dirname):
93 self.clobbercount += 1 94 95 if self.clobbercount > 3: 96 raise Exception, "Too many clobber attempts. Aborting step" 97 98 def _vcfull(res): 99 return self.doVCFull()
100 101 c = self.doClobber(dummy, dirname) 102 c.addCallback(_vcfull) 103 104 return c
105
106 - def _purge(self, dummy, dirname):
107 hg = self.getCommand('hg') 108 d = os.path.join(self.builder.basedir, self.srcdir) 109 purge = [hg, 'purge', '--all'] 110 purgeCmd = runprocess.RunProcess(self.builder, purge, d, 111 keepStdout=True, keepStderr=True, 112 logEnviron=self.logEnviron, usePTY=False) 113 114 def _clobber(res): 115 if res != 0: 116 # purge failed, we need to switch to a classic clobber 117 msg = "'hg purge' failed: %s\n%s. Clobbering." % (purgeCmd.stdout, purgeCmd.stderr) 118 self.sendStatus({'header': msg + "\n"}) 119 log.msg(msg) 120 121 return self._clobber(dummy, dirname) 122 123 # Purge was a success, then we need to update 124 return res
125 126 p = purgeCmd.start() 127 p.addCallback(_clobber) 128 return p 129
130 - def _update(self, res):
131 hg = self.getCommand('hg') 132 if res != 0: 133 return res 134 135 # compare current branch to update 136 self.update_branch = self.args.get('branch', 'default') 137 138 d = os.path.join(self.builder.basedir, self.srcdir) 139 parentscmd = [hg, 'identify', '--num', '--branch'] 140 cmd = runprocess.RunProcess(self.builder, parentscmd, d, 141 sendRC=False, timeout=self.timeout, keepStdout=True, 142 keepStderr=True, logEnviron=self.logEnviron, 143 usePTY=False) 144 145 self.clobber = None 146 147 def _parseIdentify(res): 148 if res != 0: 149 msg = "'hg identify' failed." 150 self.sendStatus({'header': msg + "\n"}) 151 log.msg(msg) 152 raise AbandonChain(-1) 153 154 log.msg('Output: %s' % cmd.stdout) 155 156 match = re.search(r'^(.+) (.+)$', cmd.stdout) 157 if not match: 158 msg = "'hg identify' did not give a recognizable output" 159 self.sendStatus({'header': msg + "\n"}) 160 log.msg(msg) 161 raise AbandonChain(-1) 162 163 rev = match.group(1) 164 current_branch = match.group(2) 165 166 if rev == '-1': 167 msg = "Fresh hg repo, don't worry about in-repo branch name" 168 log.msg(msg) 169 170 elif self.sourcedirIsPatched(): 171 self.clobber = self._purge 172 173 elif self.update_branch != current_branch: 174 msg = "Working dir is on in-repo branch '%s' and build needs '%s'." % (current_branch, self.update_branch) 175 if self.clobberOnBranchChange: 176 msg += ' Cloberring.' 177 else: 178 msg += ' Updating.' 179 180 self.sendStatus({'header': msg + "\n"}) 181 log.msg(msg) 182 183 # Clobbers only if clobberOnBranchChange is set 184 if self.clobberOnBranchChange: 185 self.clobber = self._purge 186 187 else: 188 msg = "Working dir on same in-repo branch as build (%s)." % (current_branch) 189 log.msg(msg) 190 191 return 0
192 193 def _checkRepoURL(res): 194 hg = self.getCommand('hg') 195 parentscmd = [hg, 'paths', 'default'] 196 cmd2 = runprocess.RunProcess(self.builder, parentscmd, d, 197 keepStdout=True, keepStderr=True, usePTY=False, 198 timeout=self.timeout, sendRC=False, 199 logEnviron=self.logEnviron) 200 201 def _parseRepoURL(res): 202 if res == 1: 203 if "not found!" == cmd2.stderr.strip(): 204 msg = "hg default path not set. Not checking repo url for clobber test" 205 log.msg(msg) 206 return 0 207 else: 208 msg = "'hg paths default' failed." 209 log.msg(msg) 210 return 1 211 212 oldurl = cmd2.stdout.strip() 213 214 log.msg("Repo cloned from: '%s'" % oldurl) 215 216 if runtime.platformType == 'win32': 217 oldurl = oldurl.lower().replace('\\', '/') 218 repourl = self.repourl.lower().replace('\\', '/') 219 else: 220 repourl = self.repourl 221 222 if repourl.startswith('file://'): 223 repourl = repourl.split('file://')[1] 224 if oldurl.startswith('file://'): 225 oldurl = oldurl.split('file://')[1] 226 227 oldurl = remove_userpassword(oldurl) 228 repourl = remove_userpassword(repourl) 229 230 if oldurl.rstrip('/') != repourl.rstrip('/'): 231 self.clobber = self._clobber 232 msg = "RepoURL changed from '%s' in wc to '%s' in update. Clobbering" % (oldurl, repourl) 233 log.msg(msg) 234 235 return 0 236 237 c = cmd2.start() 238 c.addCallback(_parseRepoURL) 239 return c 240 241 def _maybeClobber(res): 242 if self.clobber: 243 msg = "Clobber flag set. Doing clobbering" 244 log.msg(msg) 245 246 return self.clobber(None, self.srcdir) 247 248 return 0 249 250 c = cmd.start() 251 c.addCallback(_parseIdentify) 252 c.addCallback(_checkRepoURL) 253 c.addCallback(_maybeClobber) 254 c.addCallback(self._update2) 255 return c 256
257 - def _update2(self, res):
258 hg = self.getCommand('hg') 259 updatecmd=[hg, 'update', '--clean', '--repository', self.srcdir] 260 if self.args.get('revision'): 261 updatecmd.extend(['--rev', self.args['revision']]) 262 else: 263 updatecmd.extend(['--rev', self.args.get('branch', 'default')]) 264 self.command = runprocess.RunProcess(self.builder, updatecmd, 265 self.builder.basedir, sendRC=False, 266 timeout=self.timeout, maxTime=self.maxTime, 267 logEnviron=self.logEnviron, usePTY=False) 268 return self.command.start()
269
270 - def parseGotRevision(self):
271 hg = self.getCommand('hg') 272 # we use 'hg parents' to find out what we wound up with 273 command = [hg, "parents", "--template", "{node}\\n"] # get full rev id 274 c = runprocess.RunProcess(self.builder, command, 275 os.path.join(self.builder.basedir, self.srcdir), 276 environ=self.env, timeout=self.timeout, 277 sendRC=False, 278 keepStdout=True, usePTY=False, 279 logEnviron=self.logEnviron) 280 d = c.start() 281 def _parse(res): 282 m = re.search(r'^(\w+)', c.stdout) 283 return m.group(1)
284 d.addCallback(_parse) 285 return d 286