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
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
69
71 if self.sourcedirIsPatched() or self.always_purge:
72 return self._purgeAndUpdate()
73 revision = self.args['revision'] or 'HEAD'
74
75 return self._dovccmd('update', ['--revision', str(revision)])
76
88
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
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
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
149
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
158
159
160 svnversion_command = utils.getCommand("svnversion")
161
162
163 return [svnversion_command, "."]
164
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
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