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

Source Code for Module buildslave.commands.svn

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