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

Source Code for Module buildslave.commands.svn

  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 
 17  from xml.dom.minidom import parseString 
 18   
 19  from twisted.python import log 
 20  from twisted.internet import defer 
 21   
 22  from buildslave.commands.base import SourceBaseCommand 
 23  from buildslave import runprocess 
 24  from buildslave.commands import utils 
 25  from buildslave.util import Obfuscated 
26 27 -class SVN(SourceBaseCommand):
28 """Subversion-specific VC operation. In addition to the arguments 29 handled by SourceBaseCommand, this command reads the following keys: 30 31 ['svnurl'] (required): the SVN repository string 32 ['username']: Username passed to the svn command 33 ['password']: Password passed to the svn command 34 ['keep_on_purge']: Files and directories to keep between updates 35 ['ignore_ignores']: Ignore ignores when purging changes 36 ['always_purge']: Always purge local changes after each build 37 ['depth']: Pass depth argument to subversion 1.5+ 38 """ 39 40 header = "svn operation" 41
42 - def setup(self, args):
43 SourceBaseCommand.setup(self, args) 44 self.svnurl = args['svnurl'] 45 self.sourcedata = "%s\n" % self.svnurl 46 self.keep_on_purge = args.get('keep_on_purge', []) 47 self.keep_on_purge.append(".buildbot-sourcedata") 48 self.ignore_ignores = args.get('ignore_ignores', True) 49 self.always_purge = args.get('always_purge', False) 50 51 self.exported_rev = 'HEAD' 52 53 self.svn_args = [] 54 if args.has_key('username'): 55 self.svn_args.extend(["--username", args['username']]) 56 if args.has_key('password'): 57 self.svn_args.extend(["--password", Obfuscated(args['password'], "XXXX")]) 58 if args.get('extra_args', None) is not None: 59 self.svn_args.extend(args['extra_args']) 60 61 if args.has_key('depth'): 62 self.svn_args.extend(["--depth",args['depth']])
63
64 - def _dovccmd(self, command, args, rootdir=None, cb=None, **kwargs):
65 svn = self.getCommand("svn") 66 if rootdir is None: 67 rootdir = os.path.join(self.builder.basedir, self.srcdir) 68 fullCmd = [svn, command, '--non-interactive', '--no-auth-cache'] 69 fullCmd.extend(self.svn_args) 70 fullCmd.extend(args) 71 c = runprocess.RunProcess(self.builder, fullCmd, rootdir, 72 environ=self.env, sendRC=False, timeout=self.timeout, 73 maxTime=self.maxTime, usePTY=False, 74 logEnviron=self.logEnviron, **kwargs) 75 self.command = c 76 d = c.start() 77 if cb: 78 d.addCallback(self._abandonOnFailure) 79 d.addCallback(cb) 80 return d
81
82 - def sourcedirIsUpdateable(self):
83 return os.path.isdir(os.path.join(self.builder.basedir, 84 self.srcdir, ".svn"))
85
86 - def doVCUpdate(self):
87 if self.sourcedirIsPatched() or self.always_purge: 88 return self._purgeAndUpdate() 89 revision = self.args['revision'] or 'HEAD' 90 # update: possible for mode in ('copy', 'update') 91 return self._dovccmd('update', ['--revision', str(revision)])
92
93 - def doVCFull(self):
94 revision = self.args['revision'] or 'HEAD' 95 args = ['--revision', str(revision), "%s@%s" % (self.svnurl, str(revision)), self.srcdir] 96 97 if self.mode == 'export': 98 if revision == 'HEAD': return self.doSVNExport() 99 else: command = 'export' 100 else: 101 # mode=='clobber', or copy/update on a broken workspace 102 command = 'checkout' 103 return self._dovccmd(command, args, rootdir=self.builder.basedir)
104
105 - def doSVNExport(self):
106 ''' Since svnversion cannot be used on a svn export, we find the HEAD 107 revision from the repository and pass it to the --revision arg''' 108 109 def parseInfo(res): 110 answer = [i.split(': ') for i in self.command.stdout.splitlines() if i] 111 answer = dict(answer) 112 self.exported_rev = answer['Revision'] 113 return self.exported_rev
114 115 def exportCmd(res): 116 args = ['--revision', str(res), self.svnurl, self.srcdir] 117 return self._dovccmd('export', args, rootdir=self.builder.basedir)
118 119 svn_info_d = self._dovccmd('info', (self.svnurl,), rootdir=self.builder.basedir, keepStdout=True) 120 121 svn_info_d.addCallbacks(parseInfo, self._abandonOnFailure) 122 svn_info_d.addCallbacks(exportCmd) 123 124 return svn_info_d 125
126 - def _purgeAndUpdate(self):
127 """svn revert has several corner cases that make it unpractical. 128 129 Use the Force instead and delete everything that shows up in status.""" 130 args = ['--xml'] 131 if self.ignore_ignores: 132 args.append('--no-ignore') 133 return self._dovccmd('status', args, keepStdout=True, sendStdout=False, 134 cb=self._purgeAndUpdate2)
135 136 @staticmethod
137 - def getUnversionedFiles(stdout, keep_on_purge):
138 """Delete everything that shown up on status.""" 139 result_xml = parseString(stdout) 140 for entry in result_xml.getElementsByTagName('entry'): 141 (wc_status,) = entry.getElementsByTagName('wc-status') 142 if wc_status.getAttribute('item') == 'external': 143 continue 144 if wc_status.getAttribute('item') == 'missing': 145 continue 146 filename = entry.getAttribute('path') 147 if filename in keep_on_purge: 148 continue 149 yield filename
150
151 - def _purgeAndUpdate2(self, res):
152 for filename in self.getUnversionedFiles(self.command.stdout, self.keep_on_purge): 153 filepath = os.path.join(self.builder.basedir, self.workdir, 154 filename) 155 self.sendStatus({'stdout': "%s\n" % filepath}) 156 if os.path.isfile(filepath): 157 os.chmod(filepath, 0700) 158 os.remove(filepath) 159 else: 160 utils.rmdirRecursive(filepath) 161 # Now safe to update. 162 revision = self.args['revision'] or 'HEAD' 163 return self._dovccmd('update', ['--revision', str(revision)], 164 keepStdout=True)
165
166 - def getSvnVersionCommand(self):
167 """ 168 Get the (shell) command used to determine SVN revision number 169 of checked-out code 170 171 return: list of strings, passable as the command argument to RunProcess 172 """ 173 # svn checkout operations finish with 'Checked out revision 16657.' 174 # svn update operations finish the line 'At revision 16654.' 175 # But we don't use those. Instead, run 'svnversion'. 176 svnversion_command = utils.getCommand("svnversion") 177 # older versions of 'svnversion' (1.1.4) require the WC_PATH 178 # argument, newer ones (1.3.1) do not. 179 return [svnversion_command, "."]
180
181 - def parseGotRevision(self):
182 if self.mode == 'export': 183 ss_rev = self.args['revision'] 184 got_revision = ss_rev and ss_rev or self.exported_rev 185 return defer.succeed(got_revision) 186 187 c = runprocess.RunProcess(self.builder, 188 self.getSvnVersionCommand(), 189 os.path.join(self.builder.basedir, self.srcdir), 190 environ=self.env, timeout=self.timeout, 191 sendStdout=False, sendStderr=False, sendRC=False, 192 keepStdout=True, usePTY=False, 193 logEnviron=self.logEnviron) 194 d = c.start() 195 def _parse(res): 196 r_raw = c.stdout.strip() 197 # Extract revision from the version "number" string 198 r = r_raw.rstrip('MS') 199 r = r.split(':')[-1] 200 got_version = None 201 try: 202 got_version = int(r) 203 except ValueError: 204 msg =("SVN.parseGotRevision unable to parse output " 205 "of svnversion: '%s'" % r_raw) 206 log.msg(msg) 207 self.sendStatus({'header': msg + "\n"}) 208 return got_version
209 d.addCallback(_parse) 210 return d 211