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, **kwargs) 74 self.command = c 75 d = c.start() 76 if cb: 77 d.addCallback(self._abandonOnFailure) 78 d.addCallback(cb) 79 return d
80
81 - def sourcedirIsUpdateable(self):
82 return os.path.isdir(os.path.join(self.builder.basedir, 83 self.srcdir, ".svn"))
84
85 - def doVCUpdate(self):
86 if self.sourcedirIsPatched() or self.always_purge: 87 return self._purgeAndUpdate() 88 revision = self.args['revision'] or 'HEAD' 89 # update: possible for mode in ('copy', 'update') 90 return self._dovccmd('update', ['--revision', str(revision)])
91
92 - def doVCFull(self):
93 revision = self.args['revision'] or 'HEAD' 94 args = ['--revision', str(revision), "%s@%s" % (self.svnurl, str(revision)), self.srcdir] 95 96 if self.mode == 'export': 97 if revision == 'HEAD': return self.doSVNExport() 98 else: command = 'export' 99 else: 100 # mode=='clobber', or copy/update on a broken workspace 101 command = 'checkout' 102 return self._dovccmd(command, args, rootdir=self.builder.basedir)
103
104 - def doSVNExport(self):
105 ''' Since svnversion cannot be used on a svn export, we find the HEAD 106 revision from the repository and pass it to the --revision arg''' 107 108 def parseInfo(res): 109 answer = [i.split(': ') for i in self.command.stdout.splitlines() if i] 110 answer = dict(answer) 111 self.exported_rev = answer['Revision'] 112 return self.exported_rev
113 114 def exportCmd(res): 115 args = ['--revision', str(res), self.svnurl, self.srcdir] 116 return self._dovccmd('export', args, rootdir=self.builder.basedir)
117 118 svn_info_d = self._dovccmd('info', (self.svnurl,), rootdir=self.builder.basedir, keepStdout=True) 119 120 svn_info_d.addCallbacks(parseInfo, self._abandonOnFailure) 121 svn_info_d.addCallbacks(exportCmd) 122 123 return svn_info_d 124
125 - def _purgeAndUpdate(self):
126 """svn revert has several corner cases that make it unpractical. 127 128 Use the Force instead and delete everything that shows up in status.""" 129 args = ['--xml'] 130 if self.ignore_ignores: 131 args.append('--no-ignore') 132 return self._dovccmd('status', args, keepStdout=True, sendStdout=False, 133 cb=self._purgeAndUpdate2)
134 135 @staticmethod
136 - def getUnversionedFiles(stdout, keep_on_purge):
137 """Delete everything that shown up on status.""" 138 result_xml = parseString(stdout) 139 for entry in result_xml.getElementsByTagName('entry'): 140 (wc_status,) = entry.getElementsByTagName('wc-status') 141 if wc_status.getAttribute('item') == 'external': 142 continue 143 if wc_status.getAttribute('item') == 'missing': 144 continue 145 filename = entry.getAttribute('path') 146 if filename in keep_on_purge: 147 continue 148 yield filename
149
150 - def _purgeAndUpdate2(self, res):
151 for filename in self.getUnversionedFiles(self.command.stdout, self.keep_on_purge): 152 filepath = os.path.join(self.builder.basedir, self.workdir, 153 filename) 154 self.sendStatus({'stdout': "%s\n" % filepath}) 155 if os.path.isfile(filepath): 156 os.chmod(filepath, 0700) 157 os.remove(filepath) 158 else: 159 utils.rmdirRecursive(filepath) 160 # Now safe to update. 161 revision = self.args['revision'] or 'HEAD' 162 return self._dovccmd('update', ['--revision', str(revision)], 163 keepStdout=True)
164
165 - def getSvnVersionCommand(self):
166 """ 167 Get the (shell) command used to determine SVN revision number 168 of checked-out code 169 170 return: list of strings, passable as the command argument to RunProcess 171 """ 172 # svn checkout operations finish with 'Checked out revision 16657.' 173 # svn update operations finish the line 'At revision 16654.' 174 # But we don't use those. Instead, run 'svnversion'. 175 svnversion_command = utils.getCommand("svnversion") 176 # older versions of 'svnversion' (1.1.4) require the WC_PATH 177 # argument, newer ones (1.3.1) do not. 178 return [svnversion_command, "."]
179
180 - def parseGotRevision(self):
181 if self.mode == 'export': 182 ss_rev = self.args['revision'] 183 got_revision = ss_rev and ss_rev or self.exported_rev 184 return defer.succeed(got_revision) 185 186 c = runprocess.RunProcess(self.builder, 187 self.getSvnVersionCommand(), 188 os.path.join(self.builder.basedir, self.srcdir), 189 environ=self.env, timeout=self.timeout, 190 sendStdout=False, sendStderr=False, sendRC=False, 191 keepStdout=True, usePTY=False) 192 d = c.start() 193 def _parse(res): 194 r_raw = c.stdout.strip() 195 # Extract revision from the version "number" string 196 r = r_raw.rstrip('MS') 197 r = r.split(':')[-1] 198 got_version = None 199 try: 200 got_version = int(r) 201 except ValueError: 202 msg =("SVN.parseGotRevision unable to parse output " 203 "of svnversion: '%s'" % r_raw) 204 log.msg(msg) 205 self.sendStatus({'header': msg + "\n"}) 206 return got_version
207 d.addCallback(_parse) 208 return d 209