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